python案例统计禁区内射门次数对比?

wen python案例 2

本文目录导读:

python案例统计禁区内射门次数对比?

  1. 📖 目录导读(Table of Contents)
  2. 为什么要统计禁区内的射门?——战术价值与数据分析意义
  3. 数据获取与预处理:从比赛日志到结构化DataFrame
  4. Python核心实现:基于事件数据的射门区域判定逻辑
  5. 禁区内外射门效率对比:代码实操与可视化
  6. 进阶应用:多球队/多赛季的纵向对比分析
  7. 常见报错与性能优化技巧(QA问答环节)
  8. 总结与延伸:从射门统计到预期进球(xG)模型

Python实战案例:统计足球比赛中“禁区内射门次数”的高效对比分析法


📖 目录导读(Table of Contents)

  1. 为什么要统计禁区内的射门?——战术价值与数据分析意义
  2. 数据获取与预处理:从比赛日志到结构化DataFrame
  3. Python核心实现:基于事件数据的射门区域判定逻辑
  4. 禁区内外射门效率对比:代码实操与可视化
  5. 进阶应用:多球队/多赛季的纵向对比分析
  6. 常见报错与性能优化技巧(QA问答环节)
  7. 总结与延伸:从射门统计到预期进球(xG)模型

为什么要统计禁区内的射门?——战术价值与数据分析意义

在足球数据分析领域,“禁区内射门次数”(Shots in the Box)长期以来被视为衡量一支球队进攻威胁质量的重要指标,根据国际足球体育研究中心(CIES)的数据,禁区内的射门转化率约为15%-18%,而禁区外的射门转化率仅为2%-5%,统计并对比双方在禁区内射门的次数,不仅能反映进攻端能否渗透到对手的危险腹地,更能预判比赛的最终走势。

传统上,这类统计依赖人工视频标注,耗时且容易漏判,而利用Python对公开的赛事事件数据(如StatsBomb、Wyscout格式的JSON或CSV数据)进行自动化统计,已成为数据分析师和球迷解说的标配技能,本文将使用一个真实的模拟数据集(包含射门坐标、事件类型),带领你完成从数据清洗到禁区内外对比的全过程。


数据获取与预处理:从比赛日志到结构化DataFrame

1 数据说明与模拟结构

假设我们获取到一场英超比赛的射门事件文件 matches_events.csv,其字段结构如下:

字段 说明 示例值
match_id 比赛ID 3847561
team_name 球队名称 “Arsenal”
player_name 球员名 “Saka”
location_x 射门点的X坐标(0-120,球门方向为120) 5
location_y 射门点的Y坐标(0-80,球场中线为40) 3
shot_outcome 射门结果(Goal/On Target/Off Target) Goal
is_penalty 是否点球 False

注意:坐标系采用标准Opta坐标系,球门宽度位于Y=36至Y=44之间(实际为7.32米),球门线位于X=120。

2 加载与初步质检

import pandas as pd
df = pd.read_csv('matches_events.csv')
# 过滤出射门事件
shots = df[df['event_type'] == 'Shot'].copy()
print(f"总射门事件数: {len(shots)}")
# 检查缺失坐标
print(f"缺失坐标行数: {shots[['location_x', 'location_y']].isna().sum().sum()}")

预处理关键点:绝大多数公开数据集中,坐标均为坐标系相对值,需确认单位是否为米,若转换为“以球门中心为原点的相对距离”更有利于后续判定。


Python核心实现:基于事件数据的射门区域判定逻辑

1 定义“禁区”的几何多边形

根据国际足联标准,大禁区(Penalty area) 长度为16.5米(从球门线起算),宽度为40.3米(对称分布于球门区两侧),在Opta坐标系(长度为105米,不是120)中需进行比例换算,但大多数数据集(如StatsBomb)以105米为标准,为简化教学,我们使用以下通用判定函数

def is_in_box(x, y, pitch_length=120, pitch_width=80, box_depth=16.5):
    # 转换为以右侧球门为原点(假设进攻方向向右)
    penalty_area_left = pitch_length - box_depth  # 103.5
    # 球门中心Y坐标为40,禁区高度40.3/2 ≈ 20.15米
    box_half_width = 20.15
    # 判定条件:x坐标大于禁区左边界,且y坐标在禁区垂直范围内
    if x >= penalty_area_left and (y >= 40 - box_half_width) and (y <= 40 + box_half_width):
        return True
    # 处理进攻方向为向左(即x坐标接近0)时的情况
    if x <= box_depth and (y >= 40 - box_half_width) and (y <= 40 + box_half_width):
        return True
    return False

⚠️ 坑点提示:上述简化逻辑未考虑球场半场方向,实际应将每个射门事件根据进攻方向(从team_id判断)统一转换为“朝向右侧球门”的视角,建议先做方向归一化:若队伍进攻左球门,则 x_norm = pitch_length - x

2 实现正确的方向归一化与判定

def normalize_direction(df, team_attack_direction):
    # 假设已知该队在本场是进攻右侧球门(即X=120为对方门)
    if team_attack_direction == 'left':
        df['location_x'] = 120 - df['location_x']
        df['location_y'] = 80 - df['location_y']
    return df
# 假设Arsenal是主场进攻右边
home_team = 'Arsenal'
shots.loc[shots['team_name'] == home_team, 'loc_x_norm'] = shots['location_x']
shots.loc[shots['team_name'] != home_team, 'loc_x_norm'] = 120 - shots['location_x']
# 同理对y坐标归一化(y不用反转,因为左右对称)
shots['in_box'] = shots.apply(lambda row: is_in_box(row['loc_x_norm'], row['location_y']), axis=1)

解释:通过上面的归一化,无论球队实际在哪个半场,我们都可以统一用“右侧为进攻方向”的坐标进行区域判定。


禁区内外射门效率对比:代码实操与可视化

1 统计双方禁区内外射门次数及命中率

# 创建分组统计
result = shots.groupby(['team_name', 'in_box']).agg(
    射门次数=('shot_outcome', 'count'),
    命中目标次数=('shot_outcome', lambda x: ((x=='Goal')|(x=='On Target')).sum()),
    进球数=('shot_outcome', lambda x: (x=='Goal').sum())
).reset_index()
# 透视表展示
pivot = result.pivot_table(index='team_name', columns='in_box', values='射门次数', fill_value=0)
pivot.columns = ['禁区外射门', '禁区内射门']
print(pivot)

输出示例:

team_name   禁区外射门  禁区内射门
Arsenal         8          11
Chelsea         12         4

2 可视化:分组柱状图与射门散点位置图

import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig, ax = plt.subplots(figsize=(8, 5))
# 绘制球场轮廓(略)
for team in pivot.index:
    team_data = pivot.loc[team]
    ax.bar([f'{team}-box内', f'{team}-box外'], team_data.values, alpha=0.7)
# 叠加散点图展示射门位置
for idx, row in shots.iterrows():
    color = 'red' if row['in_box'] else 'blue'
    ax.scatter(row['loc_x_norm'], row['location_y'], c=color, s=20, alpha=0.5)'Box vs outside box shot comparison')
plt.show()

进阶应用:多球队/多赛季的纵向对比分析

在实际项目(如球队赛季总结报告)中,你可能需要一键生成整个英超联赛的“禁区内射门占比”排行,将前面的逻辑封装为函数,并添加一个 compare_across_matches(match_ids) 循环即可:

def box_shot_ratio(match_df):
    # ... 返回球队、禁区内射门数、总射门数、占比

此外可计算射门效率指数 (Box shot efficiency) = 禁区内射门/总射门,用于评价球队进攻是否“多层次”结合远射,配合时间序列分析(月度趋势)能有效揭示战术风格演变。


常见报错与性能优化技巧(QA问答环节)

❓ Q1:为什么我的is_in_box函数大量误判?尤其是底线附近的射门?

解答:请务必先处理进攻方向归一化,我遇到的最常见错误是主队和客队的坐标方向不统一,建议采用以下指纹验证:随机抽取5个已经人工标注过的事件,比较你的程序输出,另外注意如果数据集中的Y轴方向是反向的(不同标准),需要在归一化时同步处理Y(如 80 - y)。

❓ Q2:数据量极大(如单赛季数万条射门),apply方法太慢怎么办?

解答:将判定逻辑向量化,使用 numpy.where 配合布尔逻辑一次计算,比逐行 apply 快30倍以上,示例:

import numpy as np
cond1 = (shots['loc_x_norm'] >= 103.5) & (shots['location_y'].between(19.85,60.15))
cond2 = (shots['loc_x_norm'] <= 16.5) & (shots['location_y'].between(19.85,60.15))
shots['in_box_vectorized'] = np.where(cond1 | cond2, True, False)

❓ Q3:怎样验证我的区域边界是准确的标准?是否存在半自动、半自动的球场绘制包?

解答:可以安装 mplsoccerfootball-pitch 库,使用 from mplsoccer import Pitch; pitch = Pitch(); pitch.draw(ax=ax) 可以绘制标准球场,然后将你的坐标点画上去做视觉验证。

❓ Q4:如果禁区内射门次数多但进球很少,如何解读?

解答:这说明门将扑救出色或射正率低,建议结合另一个指标——“禁区内射正率”(禁区内命中目标次数/禁区内射门次数),高射门数+低射门转化率可能指向“强行起脚”而非绝对机会,应结合xG(预期进球)进一步分析。


总结与延伸:从射门统计到预期进球(xG)模型

通过本文的Python案例,你已经掌握了一套从原始数据到对比报告的可复用流程,统计禁区内射门次数只是第一步,更高级的分析流派——预期进球模型(xG)——正是基于这类空间判定逻辑,赋予每次射门一个“期望破门概率”,你可以使用 statsbombpy 开源库加载真实比赛数据,对每一次射门计算其实际位置与历史同位置射门的转化率均值,从而评估球队的“真实进攻创造力”。

数据分析能帮我们看到战术的隐藏语言:是偏爱远射的曼城,还是极度依赖禁区内机会的利物浦?下一次当你观看比赛时,不妨模拟思维在代码中快速跑一遍——你可能会发现,裁判视角之外的另一种比赛叙事。

行动建议:下载免费的StatsBomb公开数据集(包含2020欧洲杯数据),用本文的代码跑通一支队伍的某场比赛,再和转播商给出的官方统计做对比——误差应该控制在±1以内,这会让你对代码的严谨性信心大增。

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