本文目录导读:

统计头球争顶成功率,需要先明确几个关键定义,因为不同数据商的口径差异很大。
核心定义
头球争顶成功率 = 争顶成功次数 ÷ 争顶总次数 × 100%
关键分歧点:
| 口径 | 说明 |
|---|---|
| 争顶总次数 | 是否包含"未接触球"的空中对抗? |
| 争顶成功 | 是把球顶给队友,还是仅仅"先碰到球"? |
| 场景范围 | 是否只统计有对抗的争顶,还是包含无对抗头球? |
主流数据商(Opta、Wyscout、StatsBomb)通常采用:有身体接触的空中对抗中,先触球且球权归属有利的一方为成功。
Python 统计脚本
假设你有一份事件数据(JSON/CSV),字段包含 player、duel_type、outcome:
import pandas as pd
def aerial_duel_success(df, player_col='player',
type_col='duel_type',
outcome_col='outcome',
aerial_keyword='Aerial'):
"""
统计球员头球争顶成功率
df: 事件数据,每行一次争顶
outcome: 'won' / 'lost' / 'neutral'
"""
# 1. 筛选头球争顶事件
aerial = df[df[type_col].str.contains(aerial_keyword, case=False, na=False)].copy()
# 2. 排除中立结果(可选)
aerial = aerial[aerial[outcome_col].isin(['won', 'lost'])]
# 3. 分组统计
stats = aerial.groupby(player_col)[outcome_col].agg(
total='count',
won=lambda x: (x == 'won').sum()
)
stats['success_rate'] = (stats['won'] / stats['total'] * 100).round(1)
return stats.sort_values('success_rate', ascending=False)
# 使用示例
# df = pd.read_csv('events.csv')
# result = aerial_duel_success(df)
# print(result)
输出示例:
total won success_rate
Van Dijk 45 34 75.6
Haaland 38 22 57.9
...
进阶:按区域/场景细分
def aerial_by_zone(df, zone_col='pitch_zone'):
"""按场地区域统计争顶成功率"""
aerial = df[df['duel_type'].str.contains('Aerial', na=False)]
pivot = aerial.pivot_table(
index='player',
columns=zone_col,
values='outcome',
aggfunc=lambda x: (x=='won').mean()*100
).round(1)
return pivot
常见分区:防守三区 / 中场三区 / 进攻三区,或按"本方禁区 / 中场 / 对方禁区"。
数据源建议
| 数据源 | 特点 |
|---|---|
| FBref | 免费,有"Aerial duels won %"直接字段 |
| Understat | 免费,但头球数据较粗 |
| StatsBomb Open Data | 免费,事件级,可自定义口径 |
| Wyscout | 付费,分类最细 |
| Opta/SofaScore API | 付费/半开放 |
FBref 抓取示例:
import pandas as pd url = "https://fbref.com/en/comps/9/Premier-League-Stats" # 用 pandas.read_html 抓取,注意加 headers 避免被拒 tables = pd.read_html(url) # 通常第 N 张表包含 "Aerials Won" 列
注意事项
- 位置偏差:中后卫和门将争顶次数远高于边锋,直接比较成功率不公平,建议分位置对比。
- 样本量:少于 20 次争顶的成功率波动极大,应加最小样本过滤。
- 对手强度:可加权对手平均身高/争顶能力。
- "成功"定义:把球顶出边线算成功还是失败?不同数据商不同,跨源比较要谨慎。
如果你能告诉我:数据格式(JSON/CSV/API)、数据源、具体字段名,我可以写出直接可跑的完整脚本。