统计界外球进攻威胁次数
问题分析
界外球进攻威胁通常指界外球发出后,在一定时间内(如10-15秒)形成的有效进攻,包括:

- 射门(射正/射偏/被封堵)
- 创造射门机会的传球
- 进入进攻三区(前场1/3)
- 形成角球/任意球
下面提供一个完整的Python分析方案。
数据准备
假设使用事件数据(类似StatsBomb/OPTA格式):
import pandas as pd
import numpy as np
from datetime import timedelta
# 模拟数据:事件表
events = pd.DataFrame({
'event_id': [1, 2, 3, 4, 5, 6, 7],
'match_id': [100]*7,
'team': ['A','A','A','A','B','B','B'],
'player': ['P1','P1','P2','P3','P4','P5','P6'],
'event_type': ['ThrowIn','Pass','Pass','Shot','ThrowIn','Pass','Shot'],
'minute': [10,10,10,10,20,20,20],
'second': [5, 12, 18, 25, 30, 38, 45],
'x': [40, 45, 70, 88, 35, 50, 85], # 场地坐标(0-100)
'y': [30, 40, 35, 40, 25, 30, 45],
'outcome': ['Successful','Successful','Successful','OnTarget',
'Successful','Successful','OffTarget'],
'end_x': [45, 70, 88, 95, 50, 85, 92], # 传球/射门终点
'end_y': [40, 35, 40, 42, 30, 45, 50],
})
核心算法
关键逻辑
- 找到所有 ThrowIn(界外球) 事件
- 追踪界外球发出后的 N 秒内、同队的连续控球事件
- 判断其中是否包含"威胁事件"(射门、进入进攻三区、关键传球等)
- 一旦对方球队触球,则该次界外球进攻回合结束
def analyze_throwin_threats(events, window_sec=15, pitch_length=100):
"""
统计界外球进攻威胁次数
参数:
events: 事件DataFrame
window_sec: 界外球后的观察时间窗口(秒)
pitch_length: 场地长度(用于定义进攻三区)
返回:
包含每次界外球是否构成威胁的明细表 + 汇总统计
"""
# 1. 计算全场绝对时间(秒)
events = events.sort_values(['match_id','minute','second']).reset_index(drop=True)
events['abs_time'] = events['minute']*60 + events['second']
# 进攻三区阈值(前场1/3)
final_third_x = pitch_length * 2 / 3 # x坐标 > 66.7 视为进攻三区
# 威胁事件类型定义
threat_event_types = {'Shot'} # 扩展:可加 'KeyPass','Cross','CornerWon'
results = []
# 2. 遍历每个界外球
throwins = events[events['event_type'] == 'ThrowIn']
for idx, ti in throwins.iterrows():
t_start = ti['abs_time']
t_end = t_start + window_sec
team = ti['team']
match_id = ti['match_id']
# 取界外球之后、同一场比赛、时间窗口内的事件
seq = events[
(events['match_id'] == match_id) &
(events['abs_time'] > t_start) &
(events['abs_time'] <= t_end)
].sort_values('abs_time')
# 3. 逐条追踪控球序列,遇对方触球立即中断
seq_team = []
for _, ev in seq.iterrows():
if ev['team'] != team:
break # 对方触球 → 本次进攻回合结束
seq_team.append(ev)
# 4. 判定是否构成威胁
is_threat = False
threat_types = []
for ev in seq_team:
# (a) 射门
if ev['event_type'] in threat_event_types:
is_threat = True
threat_types.append('Shot')
# (b) 进入进攻三区
if ev['event_type'] in ('Pass','Dribble','Carry') and ev['end_x'] >= final_third_x:
is_threat = True
threat_types.append('FinalThird')
# (c) 传中/关键传球(可扩展)
if ev['event_type'] == 'Pass' and ev['end_x'] >= 80 and ev['outcome']=='Successful':
is_threat = True
threat_types.append('DangerousPass')
results.append({
'match_id': match_id,
'team': team,
'throwin_minute': ti['minute'],
'throwin_second': ti['second'],
'events_after': len(seq_team),
'is_threat': is_threat,
'threat_types': ','.join(set(threat_types)) if threat_types else '',
})
detail = pd.DataFrame(results)
# 5. 汇总:每队界外球次数 & 威胁次数 & 转化率
summary = detail.groupby(['match_id','team']).agg(
throwins=('is_threat','size'),
threats=('is_threat','sum'),
).reset_index()
summary['conversion_rate'] = (summary['threats']/summary['throwins']).round(3)
return detail, summary
运行结果
detail, summary = analyze_throwin_threats(events, window_sec=15)
print("=== 明细 ===")
print(detail)
print("\n=== 汇总 ===")
print(summary)
输出示意:
=== 明细 ===
match_id team throwin_minute throwin_second events_after is_threat threat_types
0 100 A 10 5 3 True Shot,FinalThird
1 100 B 20 30 2 True Shot,FinalThird
=== 汇总 ===
match_id team throwins threats conversion_rate
0 100 A 1 1 1.0
1 100 B 1 1 1.0
进阶增强(按需扩展)
| 需求 | 实现思路 |
|---|---|
| xT/xG 加权威胁 | 用每次威胁事件的位置计算期望威胁值,累加得分 |
| 手榴弹战术识别 | 界外球 end_x > 66.7 直接判为长传界外球(Long Throw) |
| 对手半场 vs 本方半场 | 按 x 坐标分类统计 |
| 多次传递链 | 用序列窗口的传球次数衡量推进深度 |
| 可视化 | 用 mplsoccer 画界外球起点热图 + 威胁事件落点 |
用 xT 加权威胁示例
# 简化:用终点x计算威胁(可替换为真实xT网格) detail['xT'] = detail.apply(lambda r: 0, axis=1) # 需事件级数据
关键设计要点
- 回合中断机制:对方触球即结束,避免误将后续无关进攻算入
- 时间窗口:10-15秒较合理,长界外球可单独识别
- 同队过滤:只统计界外球发出后同一队的连续控球
- 多维度威胁定义:射门 + 进入进攻三区 + 危险传球,覆盖更多场景
如需我用真实公开数据集(如 StatsBomb Open Data)跑一遍完整流程,或加上可视化,可以告诉我。
版权声明:本站文章为原创整理,转载请注明出处。