Python案例:量化防守反击效率值
核心思路
防守反击效率 = 从获得球权到完成射门的转化质量 ÷ 消耗的时间/资源

常见量化维度:
- 转换速度:夺回球权后多久完成射门
- 推进距离:反击推进的纵向距离
- 转化率:反击次数 → 射门 → 进球的漏斗
- 威胁值:用 xG(预期进球)加权
数据准备
假设有事件数据(类似 StatsBomb / Opta 格式):
import pandas as pd
import numpy as np
# 模拟一场比赛的事件数据
data = {
'event_id': range(1, 21),
'team': ['A','A','A','A','A','A','A','A','A','A',
'A','A','A','A','A','A','A','A','A','A'],
'type': ['ball_recovery','pass','carry','pass','carry','shot',
'ball_recovery','pass','carry','pass','carry','pass','shot',
'ball_recovery','pass','carry','pass','shot',
'ball_recovery','pass'],
'minute': [5,5,5,5,5,5, 20,20,20,20,20,20,20,
35,35,35,35,35, 50,50],
'second': [10,12,14,16,18,20, 30,32,35,37,39,41,43,
5,7,9,11,13, 20,22],
'x': [30,40,55,70,80,92, 25,35,50,65,75,85,94,
20,35,55,70,90, 28,45],
'y': [40,42,45,48,50,50, 30,32,35,40,45,48,52,
50,52,55,50,48, 45,47],
'end_x': [40,55,70,80,92,92, 35,50,65,75,85,94,94,
35,55,70,90,90, 45,60],
'end_y': [42,45,48,50,50,50, 32,35,40,45,48,52,52,
52,55,50,48,48, 47,50],
'xg': [0,0,0,0,0,0.35, 0,0,0,0,0,0,0.55,
0,0,0,0,0.12, 0,0],
}
df = pd.DataFrame(data)
定义反击序列
规则:从 ball_recovery 开始,到 shot / 丢失球权 / 超过 N 秒结束。
def extract_counter_attacks(df, max_duration=15, min_progress=30):
"""
提取防守反击序列
max_duration: 反击最大时长(秒)
min_progress: 最小推进距离(米)才算反击
"""
counters = []
i = 0
events = df.to_dict('records')
while i < len(events):
if events[i]['type'] == 'ball_recovery':
seq = [events[i]]
start_x = events[i]['x']
for j in range(i+1, len(events)):
seq.append(events[j])
# 计算时间差
t0 = seq[0]['minute']*60 + seq[0]['second']
t1 = events[j]['minute']*60 + events[j]['second']
duration = t1 - t0
# 终止条件
if events[j]['type'] == 'shot':
progress = events[j]['x'] - start_x
if progress >= min_progress and duration <= max_duration:
counters.append({
'start_second': t0,
'duration': duration,
'start_x': start_x,
'end_x': events[j]['x'],
'progress': progress,
'n_passes': sum(1 for e in seq if e['type']=='pass'),
'n_carries': sum(1 for e in seq if e['type']=='carry'),
'xg': events[j]['xg'],
'result': 'shot'
})
break
if duration > max_duration:
break
i += 1
return pd.DataFrame(counters)
counter_df = extract_counter_attacks(df)
print(counter_df)
输出示例:
start_second duration start_x end_x progress n_passes n_carries xg result
0 310 8 30 92 62 2 2 0.35 shot
1 1230 12 25 94 69 3 2 0.55 shot
2 2105 8 20 90 70 2 2 0.12 shot
计算反击效率值
方法1:加权效率(推荐)
def counter_efficiency(counters, total_recoveries):
"""
效率公式:
CE = Σ(xG) / 反击次数 × 速度因子 × 成功因子
"""
n = len(counters)
if n == 0:
return 0
# 1. 转化为进球的期望
total_xg = counters['xg'].sum()
# 2. 速度因子:越快越高效(8秒为基准)
speed_factor = (8 / counters['duration'].clip(lower=4)).mean()
speed_factor = min(speed_factor, 2.0) # 上限
# 3. 转化率(获得球权后能形成反击的比例)
conversion_rate = n / total_recoveries
# 4. 反击效率值
ce = (total_xg / n) * speed_factor * conversion_rate * 100
return {
'counter_count': n,
'avg_duration': counters['duration'].mean(),
'avg_progress': counters['progress'].mean(),
'total_xg': total_xg,
'speed_factor': round(speed_factor, 2),
'conversion_rate': round(conversion_rate, 2),
'counter_efficiency': round(ce, 2)
}
result = counter_efficiency(counter_df, total_recoveries=4)
print(result)
输出:
{
'counter_count': 3,
'avg_duration': 9.33,
'avg_progress': 67.0,
'total_xg': 1.02,
'speed_factor': 1.13,
'conversion_rate': 0.75,
'counter_efficiency': 28.8
}
方法2:反击 xT 值(进阶)
用位置价值代替 xG,衡量推进威胁:
# 简化的场区价值表(x:0-100, y:0-50 划分)
def position_value(x, y):
"""越靠近对方球门价值越高"""
if x < 40: return 0.01
if x < 60: return 0.03
if x < 80: return 0.08
if x < 90: return 0.15
return 0.30
def counter_xt_efficiency(counters):
"""计算反击过程中创造的位置价值总和 / 时长"""
results = []
for _, row in counters.iterrows():
# 用 start_x 到 end_x 的价值提升
dv = position_value(row['end_x'], 25) - position_value(row['start_x'], 25)
# 每秒创造价值
rate = dv / row['duration']
results.append({
'start_sec': row['start_second'],
'threat_gain': dv,
'per_second': rate,
'total_xt': dv * 90 # 归一化
})
return pd.DataFrame(results)
xt_df = counter_xt_efficiency(counter_df)
print(xt_df)
print(f"\n平均每秒威胁增长: {xt_df['per_second'].mean():.4f}")
可视化(可选)
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
# 1. 反击时长分布
axes[0].bar(range(len(counter_df)), counter_df['duration'], color='steelblue')
axes[0].set_title('反击时长 (秒)')
axes[0].set_xlabel('反击编号')
# 2. 推进距离
axes[1].bar(range(len(counter_df)), counter_df['progress'], color='coral')
axes[1].set_title('推进距离 (米)')
axes[1].set_xlabel('反击编号')
# 3. xG 对比
axes[2].bar(range(len(counter_df)), counter_df['xg'], color='green')
axes[2].set_title('每次反击的 xG')
axes[2].set_xlabel('反击编号')
plt.tight_layout()
plt.savefig('counter_efficiency.png', dpi=100)
plt.show()
关键公式总结
| 指标 | 公式 | 含义 |
|---|---|---|
| 平均反击时长 | Σduration / N | 越低越高效 |
| 平均推进距离 | Σprogress / N | 越高越有威胁 |
| 反击转化率 | N_shot / N_recovery | 抓住机会的能力 |
| 速度因子 | 8 / avg_duration | 反击快慢 |
| 反击效率值 | (avg_xG) × speed × rate × 100 | 综合评分 |
实战建议
- 数据源:StatsBomb Open Data、FBref、Wyscout 都能提供事件流
- 防守反击识别:结合
ball_recovery+counterpress标签更准确 - 对手强度归一化:强弱队对抗时用联赛平均作为基准
- 样本量:单场样本太少,建议整赛季聚合(≥30次反击才有统计意义)
- 进阶模型:可用 VAEP 或 xT 框架替换简单 xG
如果你有真实的事件数据(CSV 或 StatsBomb JSON),我可以帮你把这段代码改成直接读取的版本。