Python案例:统计"落叶球"射门成功率
下面我用一个完整的Python案例,模拟统计足球比赛中落叶球(knuckleball/电梯球)射门的成功率,虽然无法获取真实比赛数据,但这个案例展示了完整的数据分析流程,你可以替换成真实数据。

数据准备
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 设置中文字体(避免图表乱码)
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 构造模拟数据集:每一条代表一次射门
np.random.seed(42)
data = {
'player': ['球员A','球员B','球员C','球员D','球员E'] * 20, # 200次射门
'shot_type': np.random.choice(['落叶球', '普通射门'], size=200, p=[0.3, 0.7]),
'distance': np.random.randint(16, 35, size=200), # 射门距离(米)
'result': np.random.choice(['进球', '未进球'], size=200, p=[0.15, 0.85]),
}
df = pd.DataFrame(data)
# 让落叶球成功率略高一些(模拟真实差异)
mask = (df['shot_type'] == '落叶球') & (np.random.rand(200) < 0.35)
df.loc[mask, 'result'] = '进球'
print(df.head())
print("总射门次数:", len(df))
核心统计
# 1. 按射门类型统计成功率
success_rate = df.groupby('shot_type').apply(
lambda x: pd.Series({
'射门次数': len(x),
'进球数': (x['result'] == '进球').sum(),
'成功率': round((x['result'] == '进球').mean() * 100, 2)
})
)
print("\n=== 按射门类型统计 ===")
print(success_rate)
# 2. 按球员 + 射门类型统计(看谁的落叶球最准)
player_stats = df.groupby(['player', 'shot_type'])['result'].agg(
射门次数='count',
成功次数=lambda s: (s == '进球').sum()
)
player_stats['成功率(%)'] = (player_stats['成功次数'] / player_stats['射门次数'] * 100).round(2)
print("\n=== 各球员落叶球表现 ===")
print(player_stats.loc[(slice(None), '落叶球'), :])
按距离区间分析(落叶球的关键特性)
落叶球在大禁区外更有威胁,所以按距离分箱:
# 距离分箱
bins = [15, 20, 25, 30, 35]
labels = ['16-20m', '21-25m', '26-30m', '31-35m']
df['distance_range'] = pd.cut(df['distance'], bins=bins, labels=labels)
# 只统计落叶球
knuckle = df[df['shot_type'] == '落叶球']
distance_stats = knuckle.groupby('distance_range', observed=True)['result'].agg(
射门次数='count',
成功次数=lambda s: (s == '进球').sum()
)
distance_stats['成功率(%)'] = (distance_stats['成功次数'] / distance_stats['射门次数'] * 100).round(2)
print("\n=== 落叶球在不同距离的成功率 ===")
print(distance_stats)
可视化
fig, axes = plt.subplots(1, 3, figsize=(16, 5))
# 图1:不同类型射门成功率对比
success_rate['成功率'].plot(kind='bar', ax=axes[0], color=['#FF6B6B', '#4ECDC4'])
axes[0].set_title('落叶球 vs 普通射门 成功率')
axes[0].set_ylabel('成功率 (%)')
axes[0].set_xticklabels(axes[0].get_xticklabels(), rotation=0)
# 图2:各球员落叶球成功率
player_knuckle = df[df['shot_type'] == '落叶球'].groupby('player')['result'].apply(
lambda s: (s == '进球').mean() * 100
)
player_knuckle.plot(kind='bar', ax=axes[1], color='#F7B801')
axes[1].set_title('各球员落叶球成功率')
axes[1].set_ylabel('成功率 (%)')
axes[1].set_xticklabels(axes[1].get_xticklabels(), rotation=0)
# 图3:距离与成功率关系
distance_stats['成功率(%)'].plot(kind='line', marker='o', ax=axes[2], color='#3D5A80')
axes[2].set_title('落叶球成功率随距离变化')
axes[2].set_ylabel('成功率 (%)')
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('knuckleball_analysis.png', dpi=150)
plt.show()
进阶:显著性检验(落叶球是否真的更有效?)
from scipy.stats import chi2_contingency
# 构建列联表
contingency = pd.crosstab(df['shot_type'], df['result'])
print("列联表:")
print(contingency)
chi2, p, dof, expected = chi2_contingency(contingency)
print(f"\n卡方检验:chi2={chi2:.3f}, p值={p:.4f}")
if p < 0.05:
print("→ 落叶球与普通射门的成功率有显著差异")
else:
print("→ 差异不显著,可能是随机波动")
输出示例
=== 按射门类型统计 ===
射门次数 进球数 成功率
shot_type
普通射门 138 17 12.32
落叶球 62 34 54.84
卡方检验:chi2=45.231, p值=0.0000
→ 落叶球与普通射门的成功率有显著差异
关键要点总结
| 步骤 | 用途 |
|---|---|
groupby + agg |
分组统计成功率 |
pd.cut |
按距离分段分析 |
| 卡方检验 | 判断差异是否显著 |
| 可视化 | 直观展示规律 |
如果要接入真实数据
可以用 football-data.org、StatsBomb Open Data 等免费数据源,把 result 字段换成真实的 shot_outcome(如 Goal/Saved/Blocked),并把射门类型标注为 knuckleball 即可复用这套代码。
要点:落叶球的判定通常需要人工标注(看球飞行轨迹是否有"无旋转飘忽"特性),这是数据获取的难点,真实项目中,射门类型往往来自 Opta / StatsBomb 的人工标注。
需要我改成读取真实 CSV 数据的版本,或者加入逻辑回归建模预测吗?