查看全课大纲(第 40 / 51 节)
  1. 1.为什么人人都该学点编程?
  2. 2.计算机与程序设计:你的电脑到底在“想”什么?
  3. 3.Python 这门语言,到底有什么魔力?
  4. 4.Python 开发环境配置:如何拥有你的第一个“编程工具箱”?
  5. 5.如何安装一个更强大的代码编辑器?
  6. 6.汇率兑换1.0:你的第一个“钱生钱”小程序
  7. 7.你的第一行代码,藏着哪些秘密?
  8. 8.汇率兑换2.0(上):让程序看懂“CNY”和“USD”
  9. 9.汇率兑换程序,如何让它“聪明”地判断币种?
  10. 10.汇率兑换程序能一直运行,直到我喊停吗?
  11. 11.汇率兑换4.0:如何把“算钱”功能打包成积木?
  12. 12.汇率兑换5.0:给你的程序穿上“结构化”的西装
  13. 13.怎么用 Python 画一个会动的五角星?
  14. 14.如何让程序自动画出不同大小的五角星?
  15. 15.函数自己调用自己,会发生什么?
  16. 16.用递归画出神奇的分形树
  17. 17.Python 怎么计算你一天躺着不动能消耗多少热量?
  18. 18.如何让程序“活”起来,持续为你服务?
  19. 19.如何让 BMR 计算器更“聪明”?
  20. 20.当程序遇到“不按套路出牌”的用户怎么办?
  21. 21.怎么用 Python 模拟一个“滚雪球”式的存钱计划?
  22. 22.如何用列表记录每一周的存款?
  23. 23.如何让程序自己数数?—— 用 for 循环解放你的双手
  24. 24.52周存钱挑战4.0:你的存款计算器能有多灵活?
  25. 25.52周存钱挑战:怎么根据日期自动查存款?
  26. 26.Python 怎么判断一年中的第几天?
  27. 27.列表 vs 元组:哪个更适合存储月份天数?
  28. 28.用集合给月份分个队,换个思路算天数
  29. 29.把月份和天数“锁”在一起:用字典给数据配个对
  30. 30.Python 怎么判断密码强弱?
  31. 31.Python 怎么给密码设置“防沉迷”系统?
  32. 32.Python 怎么把密码保存到文件里?
  33. 33.Python 怎么读取文件里的密码?
  34. 34.Python 怎么把密码检查工具“打包”起来?
  35. 35.Python 怎么把文件操作也“打包”起来?
  36. 36.计算机怎么“扔骰子”?——用Python模拟随机事件
  37. 37.如何用 Python 模拟掷两个骰子?
  38. 38.怎么用 Python 把骰子轨迹画出来?
  39. 39.骰子点数分布长啥样?用直方图一眼看穿!
  40. 40.用科学计算库让骰子程序“起飞”
  41. 41.空气质量指数怎么算?手把手教你写一个AQI计算器
  42. 42.空气质量指数计算:从零开始,让程序看懂空气
  43. 43.如何读取空气质量数据文件并找出空气质量最好的五个监测点?
  44. 44.JSON 数据怎么变成 Excel 表格?—— 让数据换个“马甲”
  45. 45.Python 怎么让程序自己判断文件类型?
  46. 46.如何让程序自己上网查空气质量?
  47. 47.用美味的汤来解析网页:Beautiful Soup 入门
  48. 48.如何让爬虫自动抓取全国所有城市的空气质量?
  49. 49.爬到的数据怎么存下来?—— 把全国空气质量数据保存为CSV文件
  50. 50.用 pandas 玩转空气质量数据:找出全国空气最好和最差的城市
  51. 51.空气质量数据怎么“洗”干净,还能画成图?

用科学计算库让骰子程序“起飞”

约 20 分钟

小象实战讲义 · Python 入门:8 个小项目边玩边学

还记得我们之前模拟掷骰子时,为了生成一万次结果,写了一个长长的循环吗?在5.0版本,我们要用一个“大招”——科学计算库NumPy,来彻底告别循环,让程序变得简洁又高效。学完这一节,你的程序不仅会更快,还会变得更“专业”,能直接输出统计数字,还能把图表上的数字标签换成“两点”、“三点”这样的中文!

💡 核心导读

  • 告别循环:用NumPy一次性生成一万个随机数,效率提升不止一点点。
  • 向量化运算:像做数学题一样,直接对“一整块”数据进行加减乘除。
  • 专业统计:用一行代码就能算出每个点数出现的具体次数。
  • 美化图表:把枯燥的“2, 3, 4…”换成“两点、三点、四点…”,让结果一目了然。

从“一个一个”到“一整块”:认识NumPy

在之前的编程中,我们处理数据都是一个一个来的:生成一个随机数,存起来;再生成一个,再存起来。这在科学计算和数据分析中效率太低了。想象一下,如果你要处理一张1000万像素的图片,难道要循环1000万次吗?

NumPy (Numerical Python) 就是为解决这个问题而生的科学计算库。它的核心思想是向量化操作:把数据看成“一整块”(比如一列数字,或者一个矩阵),然后对整个数据块进行运算。

这就像做菜:

  • 传统方式(循环):你有一筐土豆,你拿起一个,削皮,放下;再拿起下一个…
  • NumPy方式(向量化):你有一个“土豆削皮机”,把整筐土豆倒进去,一次性全部削好。

本节我们只新增一件事:用NumPy库来生成随机数和进行统计计算,彻底替换掉之前的循环逻辑。

首先,我们看看怎么用NumPy生成随机数数组:

import numpy as np
np.random.seed(42)  # 固定随机种子,让示例输出可复现

# 生成3行4列,每个元素是1到10之间的随机整数
random_array = np.random.randint(1, 11, size=(3, 4))
print("生成的3x4随机数组:")
print(random_array)
生成的3x4随机数组:
[[ 7  4  8  5]
 [ 7 10  3  7]
 [ 8  5  4  8]]

注意这里的细节:np.random.randint(1, 11) 生成的是 [1, 11) 的随机数,也就是1到10(包含1,不包含11)。这和我们之前用的 random.randint(1, 6) 包含两端有点不同,需要特别注意。

重构掷骰子程序:告别循环

现在,让我们用NumPy来重写掷骰子的核心部分。你会发现,原来需要循环一万次的代码,现在只需要两三行:

import numpy as np
np.random.seed(42)  # 固定随机种子,让示例输出可复现

# 模拟投掷次数
total_times = 10000

# 生成第一个骰子的结果:一次性生成10000个1-6的随机数
# 注意:这里用1-7,因为不包含7,实际得到的是1-6
roll1_arr = np.random.randint(1, 7, size=total_times)

# 生成第二个骰子的结果
roll2_arr = np.random.randint(1, 7, size=total_times)

print("第一个骰子前10次结果:", roll1_arr[:10])
print("第二个骰子前10次结果:", roll2_arr[:10])
print("数据类型:", type(roll1_arr))
第一个骰子前10次结果: [4 5 3 5 5 2 3 3 3 5]
第二个骰子前10次结果: [1 2 4 2 3 2 6 6 2 5]
数据类型: <class 'numpy.ndarray'>

看!roll1_arr 和 roll2_arr 不是普通的Python列表,而是NumPy的 ndarray(N维数组)对象。我们一次性就得到了所有结果,完全不需要循环。

向量化运算:像做数学题一样编程

最酷的部分来了!在NumPy中,如果你想计算两个骰子的点数之和,不需要写循环逐个相加,直接像做数学题一样“加”就可以了:

import numpy as np
np.random.seed(42)  # 固定随机种子,让示例输出可复现

total_times = 10000
# 一次性生成两个骰子各 10000 次的点数(两个长度相同的数组)
roll1_arr = np.random.randint(1, 7, size=total_times)
roll2_arr = np.random.randint(1, 7, size=total_times)

# 计算两个骰子的点数之和(对应位置直接相加,无需手写循环)
result_arr = roll1_arr + roll2_arr

print("前10次投掷的点数和:", result_arr[:10])
print("总结果的数据形状:", result_arr.shape)
前10次投掷的点数和: [ 5  7  7  7  8  4  9  9  5 10]
总结果的数据形状: (10000,)

result_arr.shape 显示这是一个包含10000个元素的一维数组。NumPy会自动把 roll1_arr 和 roll2_arr 中对应位置的元素相加,这种操作就叫做向量化运算。

专业统计:一键获取直方图数据

在4.0版本,我们用matplotlib画出了直方图,但如果我们想知道每个点数具体出现了多少次,还需要自己统计。NumPy提供了一个超级方便的函数:

import numpy as np
np.random.seed(42)  # 固定随机种子,让示例输出可复现

total_times = 10000
roll1_arr = np.random.randint(1, 7, size=total_times)
roll2_arr = np.random.randint(1, 7, size=total_times)
result_arr = roll1_arr + roll2_arr

# 使用 NumPy 一次性统计直方图
# bins=range(2, 14) 表示分组边界是 2,3,4,...,13
# 这样分组是 [2,3), [3,4), ..., [12,13),正好对应点数 2 到 12
hist, bins = np.histogram(result_arr, bins=range(2, 14))

print("各点数出现的次数:", hist)
print("分组边界:", bins)
各点数出现的次数: [ 273  608  811 1101 1383 1616 1414 1105  858  556  275]
分组边界: [ 2  3  4  5  6  7  8  9 10 11 12 13]

hist 数组告诉我们(固定种子下本次模拟的真实统计):

  • 点数为 2 出现了 273 次
  • 点数为 3 出现了 608 次
  • ……
  • 点数为 12 出现了 275 次

如果你想知道频率(概率),只需要用次数除以总投掷次数:

# 计算频率
frequency = hist / total_times
print("各点数出现的频率:", frequency)

(上面两行是在上一段完整程序的基础上追加的:hist(各点数出现的次数)和 total_times(总投掷次数)都是那个程序里已经算好的变量。)

各点数出现的频率: [0.0273 0.0608 0.0811 0.1101 0.1383 0.1616 0.1414 0.1105 0.0858 0.0556 0.0275]

把它和理论概率 [1/36, 2/36, 3/36, 4/36, 5/36, 6/36, 5/36, 4/36, 3/36, 2/36, 1/36](约 [0.0278, 0.0556, 0.0833, …, 0.1667, …, 0.0278])对照一下:实验频率已经非常接近理论概率,但因为只有 10000 次,还存在一点点偏差——这正是大数定律的体现。

完善可视化:让图表说中文

现在让我们把所有这些整合起来,并美化我们的直方图。我们想把X轴上的数字标签换成中文,比如“2”显示为“两点”:

import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)  # 固定随机种子,让示例输出可复现

# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False    # 用来正常显示负号

def main():
    """主函数"""
    total_times = 10000
    
    # 使用NumPy生成随机数
    roll1_arr = np.random.randint(1, 7, size=total_times)
    roll2_arr = np.random.randint(1, 7, size=total_times)
    result_arr = roll1_arr + roll2_arr
    
    # 计算统计结果
    hist, bins = np.histogram(result_arr, bins=range(2, 14))
    print("各点数出现次数:", hist)
    print("理论概率最高的点数是7点,出现次数:", hist[5])  # 7点对应索引5
    
    # 数据可视化
    plt.hist(result_arr, 
             bins=range(2, 14), 
             density=True,          # 显示频率而不是次数
             edgecolor='black',     # 柱子边框颜色
             linewidth=1,           # 边框线宽
             rwidth=0.8)           # 柱子相对宽度
    
    # 设置X轴坐标点显示为中文
    tick_labels = ['两点', '三点', '四点', '五点', '六点',
                   '七点', '八点', '九点', '十点', '十一点', '十二点']
    
    # 设置标签显示的位置(每个柱子的中间)
    tick_pos = np.arange(2, 13) + 0.5
    
    plt.xticks(tick_pos, tick_labels)
    plt.title('两个骰子投掷点数统计(10000次)')
    plt.xlabel('点数')
    plt.ylabel('频率')
    
    plt.show()

# 运行主函数
main()
各点数出现次数: [ 273  608  811 1101 1383 1616 1414 1105  858  556  275]
理论概率最高的点数是7点,出现次数: 1616

运行这段代码,你会看到一个专业的直方图,X轴显示的是清晰的中文标签,每个柱子正好在标签的中间位置。rwidth=0.8 让柱子宽度为分组宽度的80%,这样柱子之间有一点空隙,看起来更清晰。

完整代码一览

让我们看看5.0版本的完整代码,对比一下之前的版本,你会发现代码变得多么简洁:

"""
    作者:梁斌
    功能:模拟掷骰子
    版本:5.0
    新增功能:使用科学计算库NumPy简化程序,完善数据可视化
    日期:10/09/2017
"""
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)  # 固定随机种子,让示例输出可复现

# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


def main():
    """
        主函数
    """
    total_times = 10000

    # 使用NumPy一次性生成所有随机数
    roll1_arr = np.random.randint(1, 7, size=total_times)
    roll2_arr = np.random.randint(1, 7, size=total_times)
    result_arr = roll1_arr + roll2_arr

    # 使用NumPy计算直方图统计结果
    hist, bins = np.histogram(result_arr, bins=range(2, 14))
    print("各点数出现次数:", hist)
    print("分组边界:", bins)

    # 数据可视化
    plt.hist(result_arr, 
             bins=range(2, 14), 
             density=1, 
             edgecolor='black', 
             linewidth=1, 
             rwidth=0.8)

    # 设置x轴坐标点显示
    tick_labels = ['两点', '三点', '四点', '五点', '六点',
                   '七点', '八点', '九点', '十点', '十一点', '十二点']
    tick_pos = np.arange(2, 13) + 0.5
    plt.xticks(tick_pos, tick_labels)

    plt.title('骰子点数统计')
    plt.xlabel('点数')
    plt.ylabel('频率')
    plt.show()


main()
各点数出现次数: [ 273  608  811 1101 1383 1616 1414 1105  858  556  275]
分组边界: [ 2  3  4  5  6  7  8  9 10 11 12 13]

关键改进:

  1. 生成随机数:从循环10000次 → 一行代码 np.random.randint(1, 7, size=10000)
  2. 计算点数和:从循环相加 → 直接 roll1_arr + roll2_arr
  3. 统计次数:从手动计数 → np.histogram() 一键获取

📝 动手练一练

  1. 三个骰子挑战:修改程序,模拟投掷三个骰子10000次。三个骰子的点数之和范围是多少?哪个点数出现的概率最高?画出直方图看看。

  2. 自定义实验:尝试不同的投掷次数,比如100次、1000次、100000次。观察随着次数增加,频率分布有什么变化?是不是越来越接近理论概率?

  3. 美化大师:尝试修改直方图的样式,比如改变柱子颜色、添加网格线、在图表上直接标注出最高频率的点数。

👉 点击查看第一题参考答案

思路:三个骰子的点数范围是3到18。我们需要生成三个骰子的结果数组,然后相加。分组边界应该是range(3, 20)。

import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)  # 固定随机种子,让示例输出可复现

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 模拟三个骰子
total_times = 10000
roll1 = np.random.randint(1, 7, size=total_times)
roll2 = np.random.randint(1, 7, size=total_times)
roll3 = np.random.randint(1, 7, size=total_times)
result = roll1 + roll2 + roll3

# 统计
hist, bins = np.histogram(result, bins=range(3, 20))
print("三个骰子各点数出现次数:", hist)

# 找到出现次数最多的点数
max_count_index = np.argmax(hist)  # 找到最大值的索引
most_common_point = bins[max_count_index]  # 对应的点数
print(f"出现次数最多的点数是:{most_common_point}点")

# 可视化
plt.hist(result, bins=range(3, 20), density=True, 
         edgecolor='black', rwidth=0.8, alpha=0.7)
plt.title('三个骰子投掷统计(10000次)')
plt.xlabel('点数')
plt.ylabel('频率')
plt.grid(True, alpha=0.3)
plt.show()
三个骰子各点数出现次数: [  45  159  295  477  686  902 1226 1252 1204 1136  959  704  473  286
  146   50]
出现次数最多的点数是:10点

运行结果会显示,三个骰子点数之和的范围是3-18,其中10点和11点出现的概率最高(理论值都是12.5%左右)。

本章小结

从1.0到5.0,我们的掷骰子项目完成了一次华丽的升级:

  • 1.0:用 random 库生成随机数,学会模拟随机事件
  • 2.0:用 zip 函数处理多个列表,模拟两个骰子
  • 3.0:引入 matplotlib,让数据“看得见”
  • 4.0:用直方图进行统计分析,理解数据分布
  • 5.0:使用 NumPy 科学计算库,实现向量化运算,大大简化程序

这个迭代过程正是真实项目开发的缩影:从一个简单想法开始,不断添加新功能,优化代码结构,最终得到一个既实用又高效的程序。

📋 行动清单

学完这一节,你可以:

  • 在自己的电脑上运行5.0版本的完整代码,看看可视化效果
  • 完成“三个骰子”的练习,验证一下10点和11点是不是真的概率最高
  • 思考一下:除了掷骰子,NumPy的向量化运算还能用在哪些地方?(提示:成绩计算、图像处理、游戏开发…)

科学计算库是Python在数据分析、机器学习、科学研究领域的“超级武器”。掌握了NumPy的基本思想,你就打开了通往更高级数据分析世界的大门。下一节,我们将进入最后一个项目——空气质量数据分析,那里会有更多数据处理的神奇工具等着你!

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第8章代码包:模拟掷骰子(含全部版本 5 个 .py)
    ZIP · 3KB
    下载
看图文没弄懂?展开看本节视频讲解(约 20 分钟)
📺 正在播放小象学院官方高清录播(支持倍速与清晰度调节)