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