统计禁区内射门次数对比 - 实用脚本
下面给你一个完整的 Python 脚本,用于统计两队(或两名球员)在禁区内射门次数的对比,适用于足球赛后数据分析。

基于事件数据(推荐)
假设你有比赛事件数据(CSV),字段包含:team、player、x、y、event_type,禁区内一般定义为:进攻方向球门中心 (x=105, y=34),禁区约为距球门 16.5m 范围。
import pandas as pd
import numpy as np
# ============ 1. 读取数据 ============
# 示例列: match_id, team, player, minute, event_type, x, y
df = pd.read_csv("match_events.csv")
# ============ 2. 只保留射门 ============
shots = df[df["event_type"].str.lower().isin(["shot", "射门"])].copy()
# ============ 3. 判断是否在禁区内 ============
# 假设球场坐标 105 x 68,进攻方向 +x,球门中心 (105, 34)
GOAL_X, GOAL_Y = 105.0, 34.0
PENALTY_DEPTH = 16.5 # 禁区纵深
PENALTY_WIDTH = 40.32 # 禁区宽度
def in_penalty_area(x, y):
"""判断是否在禁区内"""
if pd.isna(x) or pd.isna(y):
return False
# 距底线深度
dx = GOAL_X - x
# 距中轴横向距离
dy = abs(GOAL_Y - y)
return (0 <= dx <= PENALTY_DEPTH) and (dy <= PENALTY_WIDTH / 2)
shots["in_box"] = shots.apply(lambda r: in_penalty_area(r["x"], r["y"]), axis=1)
# ============ 4. 按球队统计 ============
box_shots = shots[shots["in_box"]].groupby("team").size()
all_shots = shots.groupby("team").size()
summary = pd.DataFrame({
"禁区内射门": box_shots,
"总射门": all_shots,
}).fillna(0).astype(int)
summary["禁区外射门"] = summary["总射门"] - summary["禁区内射门"]
summary["禁区占比"] = (summary["禁区内射门"] / summary["总射门"] * 100).round(1).astype(str) + "%"
print("\n===== 禁区内射门对比 =====")
print(summary)
# ============ 5. 两队横向对比 ============
teams = summary.index.tolist()
if len(teams) >= 2:
t1, t2 = teams[0], teams[1]
print(f"\n【{t1}】 vs 【{t2}】")
print(f"禁区内射门: {summary.loc[t1,'禁区内射门']} - {summary.loc[t2,'禁区内射门']}")
print(f"总射门: {summary.loc[t1,'总射门']} - {summary.loc[t2,'总射门']}")
print(f"禁区占比: {summary.loc[t1,'禁区占比']} vs {summary.loc[t2,'禁区占比']}")
输出示例:
===== 禁区内射门对比 =====
禁区内射门 总射门 禁区外射门 禁区占比
A队 9 14 5 64.3%
B队 4 11 7 36.4%
基于热图 / 坐标点直接统计(无事件类型)
如果你只有射门点的坐标数据(比如从 StatsBomb、Opta、Wyscout 导出的 shot events):
import pandas as pd
# 假设 shots.csv: 列为 team, x, y
df = pd.read_csv("shots.csv")
# StatsBomb 坐标系: x∈[0,120], y∈[0,80],禁区约 x≥102, 18≤y≤62
def in_box_statsbomb(x, y):
return x >= 102 and 18 <= y <= 62
df["in_box"] = df.apply(lambda r: in_box_statsbomb(r["x"], r["y"]), axis=1)
result = df.groupby("team")["in_box"].agg(
禁区内射门="sum",
总射门="count"
)
result["禁区外射门"] = result["总射门"] - result["禁区内射门"]
print(result)
命令行一行统计(快速版)
# 假设 CSV 已带 in_box 列 (0/1)
awk -F',' 'NR>1 {total[$1]++; if($5==1) box[$1]++}
END {for (t in total) printf "%s: 禁区内 %d / 总射门 %d\n", t, box[t], total[t]}' shots.csv
坐标系对照表(不同数据源禁区判断)
| 数据源 | x 范围 | 球门方向 | 禁区判断 |
|---|---|---|---|
| StatsBomb | 0–120 | +x | x ≥ 102 且 18 ≤ y ≤ 62 |
| Opta | 0–100 | +x | x ≥ 83 且 21.1 ≤ y ≤ 78.9 |
| Wyscout | 0–100 | +x | x ≥ 84 且 21 ≤ y ≤ 79 |
| 自定义 105×68 | 0–105 | +x | x ≥ 88.5 且 13.84 ≤ y ≤ 54.16 |
注:若进攻方向为 -x(反向),需先
x = 105 - x翻转。
建议扩展
- 按时间段:加
period字段,统计上/下半场对比 - 按球员:
groupby(["team","player"]) - xG 加权:禁区内射门乘 xG 值,得到"期望进球对比"
- 可视化:用 mplsoccer 画球场 + 射门散点图
需要哪个扩展版本(如 mplsoccer 可视化 或 含 xG 加权),告诉我具体数据源格式,我可以直接给你定制脚本。