python案例统计高球传中争顶成功率?

wen python案例 6

Python案例:统计高球传中争顶成功率

下面我用一个完整的案例,演示如何用 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 按球队/球员聚合
可视化 柱状图直观对比

可扩展方向

  1. 按球员统计:谁是队里争顶最强的高点
  2. 按区域统计:前点/后点/中路的成功率差异
  3. 时间维度:上下半场体能下降后的成功率变化
  4. 对手维度:面对不同对手时的争顶表现
  5. 接入真实数据源:如 StatsBomb、Opta、Wyscout 的 API

如果你有真实的比赛数据(StatsBomb 开放数据),我可以帮你改写成对应的字段解析版本。

抱歉,评论功能暂时关闭!