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),我可以帮你把这段代码改成直接读取的版本。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播