本文目录导读:

- 方案一:使用
statsbombpy(免费API,顶级赛事数据) - 方案二:使用
mplsoccer(适用于 Wyscout/Understat 数据) - 方案三:通用脚本(从 CSV / 自定义数据计算)
- 关键注意点(非常重要)
在足球数据分析中,“禁区内射门”通常指的是射门发生时,皮球所在位置处于对方罚球区(大禁区)内。
针对你的需求,我提供三个不同场景的实用脚本(Python + 开源数据),请根据你的数据来源选择最合适的。
使用 statsbombpy(免费API,顶级赛事数据)
这是目前获取最详细事件数据最简单的方式,数据中自带 shot_end_location(射门结束位置),但由于我们需要“禁区内”,应使用 location(射门起始位置)。
import pandas as pd
from statsbombpy import sb
# 1. 加载数据(以2018世界杯决赛法国 vs 克罗地亚为例)
# 免费API数据ID:比赛ID 8658
df_events = sb.events(match_id=8658)
# 2. 过滤出射门事件
shots = df_events[(df_events['type'] == 'Shot')].copy()
# 3. 提取射门位置的坐标(StatsBomb坐标系:x为0-120,y为0-80)
# 注意:坐标原点在防守方半场,进攻方向为从左向右
shots['x'] = shots['location'].apply(lambda loc: loc[0] if loc else None)
shots['y'] = shots['location'].apply(lambda loc: loc[1] if loc else None)
# 4. 判断是否在禁区内(对方球门为120,禁区线为102)
# 同时考虑左右两个半场(即进攻方向)
def is_in_box(row):
if row['x'] <= 60: # 攻右路
return (row['x'] >= 18) and (18 <= row['y'] <= 62)
else: # 攻左路
return (row['x'] >= 102) and (18 <= row['y'] <= 62)
shots['in_box'] = shots.apply(is_in_box, axis=1)
# 5. 统计对比
# 确定球队(home_team / away_team)
result = shots.groupby(['team', 'in_box']).size().unstack(fill_value=0)
result.columns = ['禁区外射门', '禁区内射门']
result['总射门'] = result.sum(axis=1)
print(result)
使用 mplsoccer(适用于 Wyscout/Understat 数据)
如果你使用的是 Wyscout 或 Understat 的公开数据(通常坐标是0-100),坐标逻辑不同(x=100是对方球门)。mplsoccer 内置了画球场功能,可以直观验证。
import pandas as pd
from mplsoccer import Pitch
# 假设你已经有了 shots_df,包含 'x'(射门起点x坐标0-100), 'y',和'team'
# 数据示例:
# shots_df = pd.DataFrame({'x': [85, 50, 92], 'y': [40, 20, 35], 'team': ['A', 'B', 'A']})
def is_box_mpl(row):
# 坐标0-100,球门在x=100
# 大禁区范围(近似):x > 82.5 且 21 < y < 79
return (row['x'] > 82.5) and (21 < row['y'] < 79)
# 假设主队列名为 'home_team',客队为 'away_team'
shots_df['is_box'] = shots_df.apply(is_box_mpl, axis=1)
# 统计
summary = shots_df.groupby(['team', 'is_box']).size().unstack(fill_value=0)
summary.columns = ['Out', 'In']
print(summary)
# 可视化(可选):画一个带射门点的球场
pitch = Pitch(pitch_type='statsbomb', line_color='black')
fig, ax = pitch.draw()
# 按是否在禁区内着色
for idx, row in shots_df.iterrows():
color = 'red' if row['is_box'] else 'blue'
pitch.scatter(row['x'], row['y'], ax=ax, color=color, s=50)
通用脚本(从 CSV / 自定义数据计算)
如果你有标准的事件数据(列:team, x_start, y_start),可以用最精简的纯Pandas脚本:
import pandas as pd
# 读取你的数据
df = pd.read_csv('your_events.csv')
# 标准足球场坐标(x: 0-100, y: 0-100, 球门在 x=100)
# 调整你的坐标轴如果数据是 0-120 的
df['in_box'] = (df['x_start'] > 82.5) & (df['y_start'] > 21) & (df['y_start'] < 79)
# 按球队和是否在禁区内统计
result = df.groupby(['team', 'in_box']).size().unstack(fill_value=0)
# 重命名
result.columns = ['Box_Shots', 'Out_Box_Shots']
print(result)
# 如果想输出百分比
result['Box_Percentage'] = result['Box_Shots'] / (result['Box_Shots'] + result['Out_Box_Shots']) * 100
print(result)
关键注意点(非常重要)
- 坐标方向:不同的数据公司(StatsBomb / Opta / Wyscout)坐标系不一样。
- StatsBomb:
x范围 0~120,y范围 0~80,本方球门在 (0, 40)。 - Wyscout / Understat:
x范围 0~100,y范围 0~100,对方球门在 (100, 50)。
- StatsBomb:
- 判别逻辑:禁区是一个矩形区域,判断条件是
x超过大禁区线,且y在禁区宽度内,对于左右两个半场分别判断。 - “禁区内”通常指:射门发生在对方罚球区(18码区)内,不包括点球点附近的小禁区(通常点球不计入运动战射门)。
如果你能告诉我你的数据来源(是某网站爬下来的Excel,还是API),我可以帮你改成完全适配的版本。