统计禁区内射门次数对比的实用脚本
下面提供几个不同场景的脚本方案,从简单到进阶,假设你的数据是足球比赛事件数据(如 StatsBomb、Wyscout 或自建 CSV)。

基础版(Pandas,CSV 数据)
假设数据结构如下:
match_id,team,player,event_type,x,y 001,TeamA,张三,Shot,102,38 001,TeamA,李四,Shot,108,40 001,TeamB,王五,Shot,55,30
脚本:
import pandas as pd
# 读取数据
df = pd.read_csv("events.csv")
# 只保留射门事件
shots = df[df["event_type"] == "Shot"].copy()
# 判断是否在禁区内
# 以攻方球门为 x=105 为基准(StatsBomb 坐标系,标准场地 105x68)
# 禁区宽 40.32m,从底线延伸到 16.5m,即 x >= 88.5,|y-34| <= 20.16
def in_box(row):
x, y = row["x"], row["y"]
return x >= 88.5 and 13.84 <= y <= 54.16
shots["in_box"] = shots.apply(in_box, axis=1)
# 按球队统计禁区内 / 禁区外射门
total = shots.groupby("team").size().rename("总射门")
in_box = shots[shots["in_box"]].groupby("team").size().rename("禁区内射门")
out_box = shots[~shots["in_box"]].groupby("team").size().rename("禁区外射门")
result = pd.concat([total, in_box, out_box], axis=1).fillna(0).astype(int)
result["禁区内占比"] = (result["禁区内射门"] / result["总射门"] * 100).round(1).astype(str) + "%"
print(result)
输出示例:
总射门 禁区内射门 禁区外射门 禁区内占比
team
TeamA 12 8 4 66.7%
TeamB 9 5 4 55.6%
按比赛逐场对比(含主客队)
import pandas as pd
df = pd.read_csv("events.csv")
shots = df[df["event_type"] == "Shot"].copy()
# 标记禁区内
shots["in_box"] = (shots["x"] >= 88.5) & (shots["y"].between(13.84, 54.16))
# 每场比赛每队统计
per_match = (
shots.groupby(["match_id", "team"])
.agg(总射门=("event_type", "size"), 禁区内=("in_box", "sum"))
.reset_index()
)
per_match["禁区外"] = per_match["总射门"] - per_match["禁区内"]
per_match["禁区内占比"] = (per_match["禁区内"] / per_match["总射门"] * 100).round(1)
# 透视成主客对比
pivot = per_match.pivot(index="match_id", columns="team",
values=["禁区内", "禁区外", "总射门"])
print(pivot)
StatsBomb 官方数据(JSON)直接统计
from statsbombpy import sb
import pandas as pd
def box_shot_compare(match_id):
events = sb.events(match_id=match_id)
shots = events[events["type"] == "Shot"].copy()
# StatsBomb 坐标:x 0~120, y 0~80,禁区 x>=102, 18<=y<=62
shots["in_box"] = (shots["location"].apply(lambda l: l[0]) >= 102) & \
(shots["location"].apply(lambda l: l[1]).between(18, 62))
summary = shots.groupby("team").agg(
总射门=("type", "size"),
禁区内=("in_box", "sum")
)
summary["禁区外"] = summary["总射门"] - summary["禁区内"]
summary["禁区内占比"] = (summary["禁区内"] / summary["总射门"] * 100).round(1)
return summary
# 示例
print(box_shot_compare(3788741))
带可视化对比图
import matplotlib.pyplot as plt
import pandas as pd
# 假设 result 是方案一输出
fig, ax = plt.subplots(figsize=(8, 5))
result[["禁区内射门", "禁区外射门"]].plot(
kind="bar", stacked=True, ax=ax, color=["#2E86AB", "#A23B72"]
)
ax.set_title("各队禁区内 vs 禁区外射门次数")
ax.set_ylabel("射门次数")
ax.set_xlabel("球队")
plt.xticks(rotation=0)
plt.tight_layout()
plt.savefig("shot_compare.png", dpi=150)
plt.show()
关键注意点
| 数据源 | 坐标系 | 禁区判定条件 |
|---|---|---|
| StatsBomb | 120 × 80 | x >= 102 且 18 <= y <= 62 |
| Wyscout | 100 × 100 | x >= 84 且 21 <= y <= 79 |
| Opta/F24 | 100 × 100 | x >= 83.5 且 1 <= y <= 78.9 |
| 标准米制 | 105 × 68 | x >= 88.5 且 84 <= y <= 54.16 |
常见坑:
- 方向问题:有些数据每队进攻方向不同,需要先归一化坐标(保证进攻方向都朝 x 增大)。
- 射门被挡/门框:若只统计"on target",需额外过滤
outcome字段。 - 点球:点球发生在禁区内,但是否计入"禁区内射门"取决于分析口径,可单独剔除
penalty。
需要我根据你实际的数据格式(列名、坐标系)定制脚本吗?把表头和一行样例贴出来即可。