Python案例:统计高球传中争顶成功率
下面用一个完整的案例,演示如何从足球比赛事件数据中统计高球传中(High Cross) 的争顶成功率。

业务背景
在足球数据分析中,传中争顶成功率是衡量球队进攻效率的重要指标:
- 传中(Cross):从边路或肋部将球传向禁区
- 高球传中:传球类型为高球/挑传(
pass_type = 'high_cross') - 争顶成功:接球方球员在对抗中赢得头球(
aerial_won = True)
争顶成功率 = 争顶成功次数 / 传中总次数 × 100%
模拟数据结构
import pandas as pd
import numpy as np
# 模拟一场比赛的事件数据
data = {
'event_id': range(1, 21),
'minute': [3, 8, 12, 15, 22, 28, 33, 40, 45, 52,
58, 63, 68, 72, 78, 82, 85, 88, 90, 93],
'team': ['A', 'A', 'B', 'A', 'B', 'A', 'A', 'B', 'A', 'B',
'A', 'A', 'B', 'A', 'B', 'A', 'A', 'B', 'A', 'B'],
'player': ['P1', 'P2', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10',
'P11', 'P1', 'P3', 'P4', 'P5', 'P6', 'P7', 'P8', 'P9', 'P10'],
'pass_type': ['high_cross', 'short_pass', 'high_cross', 'high_cross',
'high_cross', 'through_ball', 'high_cross', 'high_cross',
'short_pass', 'high_cross', 'high_cross', 'high_cross',
'high_cross', 'short_pass', 'high_cross', 'high_cross',
'high_cross', 'high_cross', 'high_cross', 'high_cross'],
'aerial_won': [True, np.nan, False, True, False, np.nan, True, False,
np.nan, True, False, True, False, np.nan, True, False,
True, True, False, True]
}
df = pd.DataFrame(data)
print(df.head(10))
核心统计逻辑
基础统计(按球队)
# 1. 筛选出高球传中事件
high_cross = df[df['pass_type'] == 'high_cross'].copy()
# 2. 按球队分组统计
result = high_cross.groupby('team').agg(
total_cross=('event_id', 'count'), # 传中总次数
won_cross=('aerial_won', 'sum'), # 争顶成功次数
).reset_index()
# 3. 计算成功率
result['success_rate'] = (result['won_cross'] / result['total_cross'] * 100).round(2)
print(result)
输出示例:
| team | total_cross | won_cross | success_rate |
|---|---|---|---|
| A | 10 | 6 | 00 |
| B | 6 | 2 | 33 |
按球员统计(谁传中质量最好)
# 按球员统计传中争顶成功率
player_stats = high_cross.groupby(['team', 'player']).agg(
total_cross=('event_id', 'count'),
won_cross=('aerial_won', 'sum')
).reset_index()
player_stats['success_rate'] = (
player_stats['won_cross'] / player_stats['total_cross'] * 100
).round(2)
# 按成功率排序(传中次数≥2的球员)
player_stats = player_stats[player_stats['total_cross'] >= 2]
print(player_stats.sort_values('success_rate', ascending=False))
区分主客场 / 时间段
# 假设有主客场字段
df['venue'] = ['home' if m <= 45 else 'away' for m in df['minute']]
df['half'] = df['minute'].apply(lambda x: '1H' if x <= 45 else '2H')
high_cross = df[df['pass_type'] == 'high_cross']
# 按半场统计
half_stats = high_cross.groupby(['team', 'half']).agg(
total=('event_id', 'count'),
won=('aerial_won', 'sum')
).reset_index()
half_stats['success_rate'] = (half_stats['won'] / half_stats['total'] * 100).round(2)
print(half_stats)
进阶:结合可视化
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig, ax = plt.subplots(figsize=(8, 5))
colors = ['#2E86AB', '#E63946']
bars = ax.bar(result['team'], result['success_rate'],
color=colors, width=0.5, edgecolor='black')
# 添加数值标签
for bar, rate, won, total in zip(bars, result['success_rate'],
result['won_cross'], result['total_cross']):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
f'{rate}%\n({won}/{total})', ha='center', fontsize=11)
ax.set_title('高球传中争顶成功率对比', fontsize=14, fontweight='bold')
ax.set_ylabel('成功率 (%)')
ax.set_ylim(0, 100)
ax.grid(axis='y', linestyle='--', alpha=0.6)
plt.tight_layout()
plt.savefig('cross_success_rate.png', dpi=150)
plt.show()
关键要点与常见坑
| 问题 | 解决方案 |
|---|---|
aerial_won 有 NaN(非争顶事件) |
用 dropna() 或 fillna(False) 后再统计 |
| 分母要选"传中次数"还是"争顶次数"? | 成功率 = 争顶成功 / 传中总次数;口径二:胜率 = 争顶成功 / 实际争顶次数,业务上通常用口径一衡量传中质量 |
| 门将出击拦截 | 应算作"传中失败",需保留在分母中 |
| 被防守球员解围 | 若未发生争顶,归为"未成功",计入分母 |
| 比分/时间段影响 | 落后方传中更多,可单独分析比赛状态 |
完整可复用函数
def cross_aerial_success(df, group_col='team',
cross_type='high_cross',
cross_field='pass_type',
won_field='aerial_won'):
"""
统计高球传中争顶成功率
:param df: 事件DataFrame
:param group_col: 分组维度(team / player 等)
:param cross_type: 传中类型标识
:param cross_field: 传中类型列名
:param won_field: 争顶结果列名
:return: 含 total, won, success_rate 的 DataFrame
"""
sub = df[df[cross_field] == cross_type].copy()
sub[won_field] = sub[won_field].fillna(False)
res = sub.groupby(group_col).agg(
total=(won_field, 'count'),
won=(won_field, 'sum')
).reset_index()
res['success_rate'] = (res['won'] / res['total'] * 100).round(2)
return res.sort_values('success_rate', ascending=False)
# 使用
print(cross_aerial_success(df, group_col='team'))
print(cross_aerial_success(df, group_col='player'))
总结一句话: 用 pandas 先按 pass_type == 'high_cross' 过滤传中事件,再按球队/球员分组对 aerial_won 求和除以总数,即可得到争顶成功率;再结合可视化就能生成完整的分析报告,需要我帮你适配真实数据源(如 StatsBomb、Opta、Wyscout) 的字段吗?
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播