你有没有想过,抛一枚硬币,正面朝上的概率真的是50%吗?今天我们就用Python来做一个有趣的动态模拟,亲眼看着概率逐渐趋近于50%!
先看完整的代码,然后我们一步步拆解:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
# 解决中文显示
plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
# —————— 配置总模拟次数 ——————
TOTAL_TIMES = 300
# 创建画布
fig, ax = plt.subplots(figsize=(8, 4))
ax.set_xlim(0, 100)
ax.set_ylim(-0.5, 2.5)
ax.set_title("抛硬币概率动态模拟", fontsize=16, pad=20)
# 数据初始化
labels = ["正面", "反面"]
counts = [0, 0]
colors = ["#ff5555", "#3399ff"]
# 横向柱状图
bars = ax.barh(labels, [0, 0], color=colors)
# 百分比文字
text_pct1 = ax.text(0, 0, "", va="center", fontsize=13, fontweight="bold")
text_pct2 = ax.text(0, 1, "", va="center", fontsize=13, fontweight="bold")
# 次数显示文字
text_count = ax.text(30, 2.2, "", fontsize=14, fontweight="bold", color="blue")
# —————— 动画更新函数 ——————
defupdate(frame):
# 达到总次数直接停止
if sum(counts) == TOTAL_TIMES:
return bars[0], bars[1], text_pct1, text_pct2, text_count
# 抛硬币
res = random.randint(0, 1)
counts[res] += 1
current = sum(counts)
# 计算百分比
p1 = counts[0] / current * 100
p2 = counts[1] / current * 100
# 更新柱子
bars[0].set_width(p1)
bars[1].set_width(p2)
# 百分比跟在柱子后面
text_pct1.set_position((p1 + 2, 0))
text_pct1.set_text(f"{p1:.1f}%")
text_pct2.set_position((p2 + 2, 1))
text_pct2.set_text(f"{p2:.1f}%")
# —————— 实时显示次数(界面上)——————
text_count.set_text(f"已抛:{current} / 总次数:{TOTAL_TIMES}")
return bars[0], bars[1], text_pct1, text_pct2, text_count
# —————— 启动动画 ——————
ani = animation.FuncAnimation(
fig,
update,
frames=TOTAL_TIMES,
interval=80,
blit=True,
repeat=False
)
plt.tight_layout()
plt.show()
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import random
详细说明:
import matplotlib.pyplot as plt
as plt:给模块起个别名plt,这样后面调用时可以少打字,比如plt.plot()而不是matplotlib.pyplot.plot()import matplotlib.animation as animation
as animation:别名animationimport random
plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False
详细说明:
plt.rcParams
"font.family"
SimHei:黑体,Windows系统常用Microsoft YaHei:微软雅黑,另一个常用中文字体["SimHei", "Microsoft YaHei"]:字体列表,按优先级尝试"axes.unicode_minus"
False:不使用Unicode负号,避免负号显示成方框TOTAL_TIMES = 300
详细说明:
TOTAL_TIMESfig, ax = plt.subplots(figsize=(8, 4))
ax.set_xlim(0, 100)
ax.set_ylim(-0.5, 2.5)
ax.set_title("抛硬币概率动态模拟", fontsize=16, pad=20)
详细说明:
plt.subplots(figsize=(8, 4))fig:画布对象,整个图表的容器ax:坐标轴对象,用来画图的具体区域figsize=(8, 4)(宽度, 高度),单位是英寸ax.set_xlim(0, 100)0:x轴最小值100:x轴最大值ax.set_ylim(-0.5, 2.5)-0.5:y轴最小值(稍微向下留一点空间,更美观)2.5:y轴最大值(我们有2个柱子,加上上面的文字,所以设为2.5)ax.set_title("抛硬币概率动态模拟", fontsize=16, pad=20)"抛硬币概率动态模拟":标题文本fontsize=16:字体大小,16磅pad=20:标题与图表的间距,20像素labels = ["正面", "反面"]
counts = [0, 0]
colors = ["#ff5555", "#3399ff"]
详细说明:
labels = ["正面", "反面"]
labels[0] = "正面",对应第一个柱子labels[1] = "反面",对应第二个柱子counts = [0, 0]
counts[0]:正面次数,初始为0counts[1]:反面次数,初始为0bars = ax.barh(labels, [0, 0], color=colors)
详细说明:
ax.barh()ax.bar()就是纵向柱状图labels**:y轴标签,即["正面", "反面"][0, 0]**:柱子的初始宽度,两个柱子都从0开始color=colors**:柱子的颜色,使用前面定义的colors列表barsbars[0]:第一个柱子(正面)bars[1]:第二个柱子(反面)text_pct1 = ax.text(0, 0, "", va="center", fontsize=13, fontweight="bold")
text_pct2 = ax.text(0, 1, "", va="center", fontsize=13, fontweight="bold")
详细说明:
ax.text(x, y, s, ...)x:文本的x坐标y:文本的y坐标s:文本内容(字符串)以text_pct1为例:
x | 0 | |
y | 0 | |
s | "" | |
va | "center" | |
fontsize | 13 | |
fontweight | "bold" |
text_pct1**:显示正面百分比的文字对象text_pct2**:显示反面百分比的文字对象(y坐标为1)text_count = ax.text(30, 2.2, "", fontsize=14, fontweight="bold", color="blue")
详细说明:
x | 30 | |
y | 2.2 | |
s | "" | |
fontsize | 14 | |
fontweight | "bold" | |
color | "blue" |
这个文字用来显示"已抛:X / 总次数:Y"
defupdate(frame):
# 达到总次数直接停止
if sum(counts) == TOTAL_TIMES:
return bars[0], bars[1], text_pct1, text_pct2, text_count
# 抛硬币
res = random.randint(0, 1)
counts[res] += 1
current = sum(counts)
# 计算百分比
p1 = counts[0] / current * 100
p2 = counts[1] / current * 100
# 更新柱子
bars[0].set_width(p1)
bars[1].set_width(p2)
# 百分比跟在柱子后面
text_pct1.set_position((p1 + 2, 0))
text_pct1.set_text(f"{p1:.1f}%")
text_pct2.set_position((p2 + 2, 1))
text_pct2.set_text(f"{p2:.1f}%")
# —————— 实时显示次数(界面上)——————
text_count.set_text(f"已抛:{current} / 总次数:{TOTAL_TIMES}")
return bars[0], bars[1], text_pct1, text_pct2, text_count
详细说明:
defupdate(frame):
def**:Python中定义函数的关键字update**:函数名frame**:参数,代表当前是第几帧(虽然我们在这个函数里没有直接用到它,但必须有这个参数)if sum(counts) == TOTAL_TIMES:
return bars[0], bars[1], text_pct1, text_pct2, text_count
sum(counts)**:计算counts数组的和,即总抛硬币次数if sum(counts) >= TOTAL_TIMES**:如果已经达到或超过总次数return ...**:直接返回这些对象,不做任何更新,动画就会停止res = random.randint(0, 1)
counts[res] += 1
current = sum(counts)
random.randint(0, 1)
counts[res] += 1
+= 1:自增1,等价于counts[res] = counts[res] + 1current = sum(counts)
p1 = counts[0] / current * 100
p2 = counts[1] / current * 100
counts[0] / current**:正面次数除以总次数,得到频率(0到1之间的小数)* 100**:乘以100,转换成百分比p1**:正面百分比p2**:反面百分比bars[0].set_width(p1)
bars[1].set_width(p2)
bars[0]**:第一个柱子(正面).set_width(p1)**:设置柱子宽度为p1text_pct1.set_position((p1 + 2, 0))
text_pct1.set_text(f"{p1:.1f}%")
text_pct2.set_position((p2 + 2, 1))
text_pct2.set_text(f"{p2:.1f}%")
.set_position((x, y))
(p1 + 2, 0):x坐标是p1+2(在柱子右边2个单位),y坐标保持0.set_text(f"{p1:.1f}%")
f"":表示这是一个格式化字符串{p1}:插入p1变量的值:.1f:格式说明,表示保留1位小数%:普通字符,直接显示f"{p1:.1f}%"text_count.set_text(f"已抛:{current} / 总次数:{TOTAL_TIMES}")
return bars[0], bars[1], text_pct1, text_pct2, text_count
blit=True时,matplotlib只会重绘这些对象,提高动画效率ani = animation.FuncAnimation(
fig,
update,
frames=TOTAL_TIMES,
interval=80,
blit=True,
repeat=False
)
详细说明:
animation.FuncAnimation()fig | fig | |
func | update | |
frames | TOTAL_TIMES | |
interval | 80 | |
blit | True | |
repeat | False |
interval=8020:非常快(每秒50帧)200:比较慢(每秒5帧)plt.tight_layout()
plt.show()
详细说明:
plt.tight_layout()
plt.show()
运行代码后,你会看到一个动态的柱状图:
TOTAL_TIMES = 1000# 改成1000次
interval=40# 更快,40毫秒
# 或者
interval=200# 更慢,200毫秒
colors = ["#99ff99", "#ff99ff"] # 绿色和紫色
在创建柱状图后添加:
ax.axvline(x=50, color="gray", linestyle="--", alpha=0.5)
axvline:垂直参考线x=50:在x=50的位置linestyle="--":虚线alpha=0.5:半透明fig, ax = plt.subplots(figsize=(10, 5)) # 更大的画布
这就是大数定律的直观展示:当试验次数足够多时,事件发生的频率会趋近于它的概率。
什么是大数定律?
简单来说:
试验次数越多,频率就越稳定在概率值附近!
如果你的电脑还没有安装matplotlib库,需要先安装:
pip install matplotlib
在命令行(CMD或PowerShell)中运行上面的命令即可。
现在你已经理解了每一行代码的作用,快去运行代码,亲身体验一下概率的魅力吧!🎉
提示:建议先用300次测试,然后慢慢增加到1000次,观察效果的变化!