本文目录导读:

这是一个非常有趣且适合综合练习Python的数据分析案例,我们可以通过蒙特卡洛模拟(Monte Carlo Simulation)来对比不同球员或不同情境下的点球命中率,并运用数据可视化和统计检验来分析结果。
以下是一个完整的Python案例,模拟两位球员(球员A vs 球员B)的点球表现,并对比他们的命中率是否存在显著差异。
案例背景
假设:
- 球员A:命中率 75%(经验丰富)
- 球员B:命中率 65%(年轻新秀)
- 每人模拟1000次点球(每次独立)
我们将:
- 生成模拟数据
- 计算统计量
- 可视化对比
- 进行假设检验(卡方检验 / Z检验)
完整代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
# 设置随机种子,保证结果可复现
np.random.seed(42)
# ------------------ 1. 模拟点球数据 ------------------
# 设定参数
n_simulations = 1000 # 模拟次数(每次一脚点球)
p_a = 0.75 # 球员A真实命中率
p_b = 0.65 # 球员B真实命中率
# 生成模拟结果:1=进球, 0=未进
sim_a = np.random.binomial(1, p_a, n_simulations)
sim_b = np.random.binomial(1, p_b, n_simulations)
# ------------------ 2. 计算统计量 ------------------
goals_a = sim_a.sum()
goals_b = sim_b.sum()
misses_a = n_simulations - goals_a
misses_b = n_simulations - goals_b
# 命中率
rate_a = goals_a / n_simulations
rate_b = goals_b / n_simulations
# 输出结果
print(f"球员A: 进球 {goals_a}/{n_simulations} (命中率 {rate_a:.2%})")
print(f"球员B: 进球 {goals_b}/{n_simulations} (命中率 {rate_b:.2%})")
# ------------------ 3. 可视化对比 ------------------
# 3.1 柱状图对比命中率
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 左侧:进球/未进数量
data = pd.DataFrame({
'球员': ['A', 'B'],
'进球': [goals_a, goals_b],
'未进': [misses_a, misses_b]
})
data_melted = data.melt(id_vars='球员', var_name='结果', value_name='次数')
sns.barplot(data=data_melted, x='球员', y='次数', hue='结果', ax=axes[0])
axes[0].set_title('点球结果数量对比')
axes[0].set_ylabel('次数')
# 右侧:命中率对比(带误差线,使用二项分布标准误)
rates = [rate_a, rate_b]
se_a = np.sqrt(rate_a * (1 - rate_a) / n_simulations)
se_b = np.sqrt(rate_b * (1 - rate_b) / n_simulations)
se = [se_a, se_b]
axes[1].bar(['球员A', '球员B'], rates, yerr=se, capsize=5, color=['skyblue', 'salmon'])
axes[1].set_ylabel('命中率')
axes[1].set_title('点球命中率对比(含标准误)')
axes[1].set_ylim(0, 1)
plt.tight_layout()
plt.show()
# 3.2 模拟概率分布(多次模拟观察稳定性)
# 如果我们重复这个实验1000次,每次模拟100脚点球,会得到什么样的分布?
n_experiments = 10000
n_shots_per = 100
sim_rates_a = []
sim_rates_b = []
for _ in range(n_experiments):
sim_a_exp = np.random.binomial(1, p_a, n_shots_per)
sim_b_exp = np.random.binomial(1, p_b, n_shots_per)
sim_rates_a.append(sim_a_exp.mean())
sim_rates_b.append(sim_b_exp.mean())
plt.figure(figsize=(10, 6))
sns.histplot(sim_rates_a, bins=50, color='skyblue', alpha=0.6, label=f'球员A (真实p={p_a})')
sns.histplot(sim_rates_b, bins=50, color='salmon', alpha=0.6, label=f'球员B (真实p={p_b})')
plt.axvline(p_a, color='blue', linestyle='--', linewidth=2)
plt.axvline(p_b, color='red', linestyle='--', linewidth=2)
plt.xlabel('模拟命中率')
plt.ylabel('频次')f'基于{n_shots_per}次点球模拟的命中率分布 (重复{n_experiments}次)')
plt.legend()
plt.show()
# ------------------ 4. 统计检验 ------------------
# 使用卡方检验(2x2列联表)检验两球员命中率是否有显著差异
contingency_table = np.array([[goals_a, misses_a],
[goals_b, misses_b]])
chi2, p_value, dof, expected = stats.chi2_contingency(contingency_table, correction=False)
print("\n--- 卡方检验结果 ---")
print(f"卡方统计量: {chi2:.3f}")
print(f"自由度: {dof}")
print(f"P值: {p_value:.6f}")
if p_value < 0.05:
print(" 在α=0.05水平上,两球员命中率存在显著差异。")
else:
print(" 在α=0.05水平上,两球员命中率无显著差异。")
# 也可用两比例Z检验
z_stat, p_val_z = stats.ranksums? # 不,用比例检验
# 手动计算z统计量
p_pool = (goals_a + goals_b) / (n_simulations * 2)
se_diff = np.sqrt(p_pool * (1 - p_pool) * (2 / n_simulations))
z_stat = (rate_a - rate_b) / se_diff
p_val_z = 2 * (1 - stats.norm.cdf(abs(z_stat)))
print("\n--- 两比例Z检验结果 ---")
print(f"Z统计量: {z_stat:.3f}")
print(f"P值: {p_val_z:.6f}")
输出示例(每次运行略有随机波动)
球员A: 进球 748/1000 (命中率 74.80%)
球员B: 进球 640/1000 (命中率 64.00%)
--- 卡方检验结果 ---
卡方统计量: 27.160
自由度: 1
P值: 0.000000
在α=0.05水平上,两球员命中率存在显著差异。
--- 两比例Z检验结果 ---
Z统计量: 5.212
P值: 0.000000
核心分析要点
- 模拟方法:使用二项分布
np.random.binomial(1, p, n)模拟单次点球结果。 - 标准误:
sqrt(p*(1-p)/n),用于误差线展示。 - 分布可视化:重复实验多次,观察命中率的抽样分布(中心极限定理的体现)。
- 假设检验:
- 卡方检验:适用于2x2列联表,检验独立性。
- Z检验:比较两个比例差异,样本量较大时近似正态。
- 当真实命中率相差10%且样本量足够(1000次),统计检验几乎总是显著。
扩展思考
你可以修改代码来探索其他场景:
- 不同样本量:如果每人只踢10次点球,P值还会显著吗?
- 加入门将干扰:模拟门将扑对方向时的命中率变化。
- 贝叶斯方法:使用PyMC或Bambi进行贝叶斯推断,得到命中率的后验分布。
- 真实数据:用
pandas加载实际比赛点球数据(如世界杯、英超)进行对比。
这个案例综合了概率模拟、统计学检验、数据可视化,非常适合作为Python数据科学的综合练习。