高球传中争顶成功率统计脚本
下面给你一套可落地的脚本方案,用于统计足球比赛中"高球传中后争顶成功"的成功率,分为数据格式定义 → 核心算法 → 完整脚本 → 使用说明。

统计口径定义(关键!)
先明确指标,否则脚本没意义:
| 概念 | 定义 |
|---|---|
| 高球传中 (High Cross) | 传中球的落点高度 > 2m,或明确标注为 high/lob cross |
| 争顶事件 (Aerial Duel) | 传中落点 3 秒内发生的头球/胸部以上争抢 |
| 争顶成功 | 争顶后球权归属进攻方,或形成射门/二次进攻 |
| 成功率 | 争顶成功次数 ÷ 争顶总次数 × 100% |
数据输入格式(CSV / DataFrame)
建议事件数据如下(可按你现有数据源调整列名):
match_id,event_id,team,event_type,minute,second,x,y,target_x,target_y,cross_height,duel_winner,duel_outcome M001,1001,Home,cross,23,15,80,30,20,15,high,Away,loose M001,1002,Home,cross,45,02,75,25,18,12,high,Home,retained M001,1003,Away,cross,67,40,20,70,82,78,high,Home,cleared M001,1004,Home,cross,88,10,70,28,15,10,low,Home,retained
字段说明:
cross_height:high/lowduel_winner: 争顶获胜球队duel_outcome:retained(进攻方控球)/cleared(解围)/loose(球权不明)
完整 Python 脚本
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
高球传中争顶成功率统计脚本
用法: python cross_aerial_stats.py events.csv
"""
import sys
import pandas as pd
def load_data(path: str) -> pd.DataFrame:
df = pd.read_csv(path)
required = {"match_id", "team", "event_type", "cross_height",
"duel_winner", "duel_outcome"}
missing = required - set(df.columns)
if missing:
raise ValueError(f"缺少必要列: {missing}")
return df
def filter_high_crosses(df: pd.DataFrame) -> pd.DataFrame:
"""只保留高球传中 + 存在争顶记录的事件"""
mask = (
df["event_type"].str.lower().eq("cross")
& df["cross_height"].str.lower().eq("high")
& df["duel_winner"].notna()
)
return df[mask].copy()
def is_success(row) -> bool:
"""争顶成功判定:获胜方 == 传球方 且 结果为 retained"""
return (
str(row["duel_winner"]).strip() == str(row["team"]).strip()
and str(row["duel_outcome"]).strip().lower() == "retained"
)
def compute_stats(df: pd.DataFrame) -> pd.DataFrame:
df = df.copy()
df["success"] = df.apply(is_success, axis=1)
# 按球队统计(进攻方视角)
grp = df.groupby(["match_id", "team"]).agg(
crosses=("event_id", "count") if "event_id" in df.columns else ("team", "count"),
wins=("success", "sum"),
).reset_index()
grp["success_rate"] = (grp["wins"] / grp["crosses"] * 100).round(2)
# 汇总总计
total = pd.DataFrame([{
"match_id": "TOTAL",
"team": "ALL",
"crosses": grp["crosses"].sum(),
"wins": grp["wins"].sum(),
"success_rate": round(grp["wins"].sum() / grp["crosses"].sum() * 100, 2)
if grp["crosses"].sum() else 0.0,
}])
return pd.concat([grp, total], ignore_index=True)
def main():
if len(sys.argv) < 2:
print("用法: python cross_aerial_stats.py events.csv")
sys.exit(1)
path = sys.argv[1]
df = load_data(path)
high = filter_high_crosses(df)
if high.empty:
print("⚠️ 没有符合条件的高球传中记录")
sys.exit(0)
stats = compute_stats(high)
print("\n==== 高球传中争顶成功率 ====\n")
print(stats.to_string(index=False))
# 可选:导出
out = path.replace(".csv", "_aerial_stats.csv")
stats.to_csv(out, index=False, encoding="utf-8-sig")
print(f"\n已导出: {out}")
if __name__ == "__main__":
main()
运行与输出示例
$ python cross_aerial_stats.py events.csv
==== 高球传中争顶成功率 ====
match_id team crosses wins success_rate
M001 Away 2 0 0.00
M001 Home 2 1 50.00
TOTAL ALL 4 1 25.00
已导出: events_aerial_stats.csv
可扩展方向
- 分区域统计:按
target_y分左/中/右三区,看哪侧传中争顶效率高。 - 分时段统计:按 15 分钟区间,观察体能下降对争顶成功率影响。
- 二次进攻转化:结合
next_event字段,统计争顶成功后 10 秒内是否形成射门。 - 可视化:用
matplotlib/plotly出柱状图或热力图。 - 对接数据源:可替换为 StatsBomb / Opta / Wyscout 的事件导出(改列名映射即可)。
常见坑位提醒
- 数据源不同事件模型不同:Opta 的
Aerial和 StatsBomb 的Duel结构差异大,需先做字段映射。 - "争顶成功"口径要统一:是"赢下球权"还是"形成射门",两种口径成功率差异可达 20%+。
- 传中方向:需过滤掉角球/任意球直接传中(若只统计运动战),可用
event_type或 set-piece 标记筛掉。
需要我按你实际的数据格式(StatsBomb JSON / Opta feeds)定制解析部分吗?把样例数据贴一下我可以直接改成能跑的版本。