Python案例:统计高球传中争顶成功率
下面我用一个完整的案例,演示如何用 Python 统计足球比赛中高球传中后的争顶成功率。

业务背景
在足球数据分析中,常见指标:
- 传中次数(Cross):边路起高球到禁区
- 争顶(Aerial Duel):攻防球员在空中争抢第一落点
- 争顶成功(Won Aerial):本方球员抢到第一落点
- 争顶成功率 = 争顶成功次数 / 争顶总次数 × 100%
我们重点统计:每次高球传中后,第一落点由谁拿下。
数据结构设计
事件表 events.csv
| 列名 | 说明 |
|---|---|
| match_id | 比赛 ID |
| minute | 分钟 |
| team | 传球球队 |
| player | 传球球员 |
| event_type | cross / aerial_duel |
| cross_type | high / low / driven |
| result | success / fail |
| duel_winner | 争顶获胜方球队 |
match_id,minute,team,player,event_type,cross_type,result,duel_winner M001,12,红队,张三,cross,high,, M001,12,红队,,aerial_duel,high,win,红队 M001,25,红队,李四,cross,high,, M001,25,红队,,aerial_duel,high,lose,蓝队 M001,33,蓝队,王五,cross,high,, M001,33,蓝队,,aerial_duel,high,win,蓝队 M001,45,红队,张三,cross,high,, M001,45,红队,,aerial_duel,high,lose,蓝队 M001,58,蓝队,赵六,cross,high,, M001,58,蓝队,,aerial_duel,high,win,蓝队 M001,70,红队,李四,cross,high,, M001,70,红队,,aerial_duel,high,win,红队
Python 实现
基础版:整体争顶成功率
import pandas as pd
# 1. 读取数据
df = pd.read_csv("events.csv")
# 2. 筛选高球传中
high_cross = df[(df["event_type"] == "cross") & (df["cross_type"] == "high")]
print(f"高球传中总次数: {len(high_cross)}")
# 3. 筛选争顶事件
duels = df[df["event_type"] == "aerial_duel"].copy()
duels["won"] = (duels["result"] == "win").astype(int)
# 4. 计算整体成功率
total_duel = len(duels)
won_duel = duels["won"].sum()
success_rate = won_duel / total_duel * 100
print(f"争顶总次数: {total_duel}")
print(f"争顶成功次数: {won_duel}")
print(f"整体争顶成功率: {success_rate:.2f}%")
输出:
高球传中总次数: 6
争顶总次数: 6
争顶成功次数: 3
整体争顶成功率: 50.00%
进阶版:按球队统计争顶成功率
import pandas as pd
df = pd.read_csv("events.csv")
# 筛选争顶事件
duels = df[df["event_type"] == "aerial_duel"].copy()
duels["won"] = (duels["result"] == "win").astype(int)
# 按获胜方球队统计
team_stats = duels.groupby("duel_winner").agg(
争顶次数=("won", "count"),
成功次数=("won", "sum"),
).reset_index()
team_stats["成功率(%)"] = (team_stats["成功次数"] / team_stats["争顶次数"] * 100).round(2)
print(team_stats)
输出:
duel_winner 争顶次数 成功次数 成功率(%)
0 红队 3 1 33.33
1 蓝队 3 2 66.67
完整版:传中 → 争顶配对统计(真实场景)
真实比赛中,传中和争顶是两个独立事件,需要按时间配对。
import pandas as pd
df = pd.read_csv("events.csv")
# 只保留高球传中 + 争顶
df = df[df["cross_type"] == "high"].copy()
# 按 match_id + minute 分组,把一次传中及其后续争顶配成一行
records = []
for (mid, minute), group in df.groupby(["match_id", "minute"]):
cross = group[group["event_type"] == "cross"]
duel = group[group["event_type"] == "aerial_duel"]
if not cross.empty and not duel.empty:
records.append({
"match_id": mid,
"minute": minute,
"cross_team": cross.iloc[0]["team"],
"cross_player": cross.iloc[0]["player"],
"duel_winner": duel.iloc[0]["duel_winner"],
"result": duel.iloc[0]["result"],
})
paired = pd.DataFrame(records)
print("配对成功的高球传中记录:")
print(paired)
# 判断"传中队是否赢得争顶"
paired["cross_won"] = (paired["cross_team"] == paired["duel_winner"]).astype(int)
# 整体成功率
print(f"\n高球传中→本方争顶成功率: {paired['cross_won'].mean()*100:.2f}%")
# 按传中队统计
by_team = paired.groupby("cross_team").agg(
传中次数=("cross_won", "count"),
争顶成功=("cross_won", "sum"),
).reset_index()
by_team["成功率(%)"] = (by_team["争顶成功"] / by_team["传中次数"] * 100).round(2)
print("\n按球队统计:")
print(by_team)
输出:
配对成功的高球传中记录:
match_id minute cross_team cross_player duel_winner result
0 M001 12 红队 张三 红队 win
1 M001 25 红队 李四 蓝队 lose
2 M001 33 蓝队 王五 蓝队 win
3 M001 45 红队 张三 蓝队 lose
4 M001 58 蓝队 赵六 蓝队 win
5 M001 70 红队 李四 红队 win
高球传中→本方争顶成功率: 50.00%
按球队统计:
cross_team 传中次数 争顶成功 成功率(%)
0 红队 4 2 50.00
1 蓝队 2 2 100.00
可视化:成功率柱状图
import matplotlib.pyplot as plt
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
plt.figure(figsize=(6, 4))
bars = plt.bar(by_team["cross_team"], by_team["成功率(%)"], color=["#e74c3c", "#3498db"])"各队高球传中争顶成功率")
plt.ylabel("成功率 (%)")
plt.ylim(0, 110)
for bar, val in zip(bars, by_team["成功率(%)"]):
plt.text(bar.get_x() + bar.get_width()/2, val + 2, f"{val}%", ha="center")
plt.tight_layout()
plt.savefig("aerial_success_rate.png", dpi=150)
plt.show()
关键点总结
| 步骤 | 说明 |
|---|---|
| 数据清洗 | 只保留 cross_type == "high" 的记录 |
| 事件配对 | 按 match_id + minute 把传中和争顶配对 |
| 判定成功 | cross_team == duel_winner 即视为传中有效 |
| 分组统计 | 用 groupby 按球队/球员聚合 |
| 可视化 | 柱状图直观对比 |
可扩展方向
- 按球员统计:谁是队里争顶最强的高点
- 按区域统计:前点/后点/中路的成功率差异
- 时间维度:上下半场体能下降后的成功率变化
- 对手维度:面对不同对手时的争顶表现
- 接入真实数据源:如 StatsBomb、Opta、Wyscout 的 API
如果你有真实的比赛数据(StatsBomb 开放数据),我可以帮你改写成对应的字段解析版本。