综合python案例,中场绞杀夺回球权对比?

wen python案例 2

Python 案例分析:中场绞杀与夺回球权对比

下面我设计一个完整的 Python 案例,用模拟数据 + 可视化对比不同球队/战术体系下"中场绞杀"能力与"夺回球权"效率,这是足球数据分析中常见的战术指标建模场景。

综合python案例,中场绞杀夺回球权对比?


核心指标定义

指标 含义 计算方式
PPDA 防守压迫强度(每次防守动作允许对手传球数) 对手传球数 / 防守动作数(越小越强)
Midfield Tackles 中场区域抢断 统计坐标 x∈[35,70] 的 Tackle 数
Ball Recovery Rate 夺回球权率 成功夺回次数 / 丢失球权次数
Recovery in Midfield % 中场夺回占比 中场夺回 / 总夺回
Counter-press Success 反抢成功率 丢球后5秒内夺回 / 丢球次数

Python 实现

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
sns.set_style("whitegrid")
np.random.seed(42)

构造模拟比赛事件数据

def generate_match_events(team, style, n_matches=10):
    """
    生成单支球队多个赛季/场次的事件数据
    style: 'high_press'(高位绞杀) / 'mid_block'(中场绞杀) / 'low_block'(低位防守)
    """
    params = {
        'high_press': dict(ppda=8.5, mid_tkl=14, rec_rate=0.62, mid_rec_pct=0.30),
        'mid_block':  dict(ppda=11.0, mid_tkl=22, rec_rate=0.58, mid_rec_pct=0.55),
        'low_block':  dict(ppda=15.0, mid_tkl=15, rec_rate=0.52, mid_rec_pct=0.40),
    }[style]
    rows = []
    for m in range(n_matches):
        rows.append({
            'team': team,
            'style': style,
            'match_id': f"{team}_{m}",
            'PPDA': np.random.normal(params['ppda'], 1.2),
            'midfield_tackles': max(0, np.random.poisson(params['mid_tkl'])),
            'ball_recovery_rate': np.clip(np.random.normal(params['rec_rate'], 0.05), 0.3, 0.85),
            'midfield_recovery_pct': np.clip(np.random.normal(params['mid_rec_pct'], 0.06), 0.1, 0.8),
            'counter_press_success': np.clip(np.random.normal(params['rec_rate']*0.9, 0.06), 0.2, 0.8),
        })
    return pd.DataFrame(rows)
teams = [
    ('利物浦', 'high_press'),
    ('曼城',   'high_press'),
    ('马竞',   'mid_block'),
    ('国际米兰','mid_block'),
    ('伯恩利', 'low_block'),
    ('谢菲联', 'low_block'),
]
df = pd.concat([generate_match_events(t, s) for t, s in teams], ignore_index=True)
print(df.groupby('team').mean(numeric_only=True).round(3))

分组对比分析

agg = df.groupby(['team', 'style']).agg({
    'PPDA': 'mean',
    'midfield_tackles': 'mean',
    'ball_recovery_rate': 'mean',
    'midfield_recovery_pct': 'mean',
    'counter_press_success': 'mean'
}).reset_index()
# 归一化(0-1),便于雷达图对比
from sklearn.preprocessing import MinMaxScaler
metrics = ['PPDA','midfield_tackles','ball_recovery_rate',
           'midfield_recovery_pct','counter_press_success']
scaler = MinMaxScaler()
agg_norm = agg.copy()
agg_norm[metrics] = scaler.fit_transform(agg[metrics])
# PPDA 越小越好,需反转
agg_norm['PPDA'] = 1 - agg_norm['PPDA']

雷达图:中场绞杀能力全景对比

def radar_plot(df_norm, teams, metrics):
    angles = np.linspace(0, 2*np.pi, len(metrics), endpoint=False).tolist()
    angles += angles[:1]
    fig, ax = plt.subplots(figsize=(8,8), subplot_kw=dict(polar=True))
    colors = plt.cm.tab10(np.linspace(0, 1, len(teams)))
    for c, team in zip(colors, teams):
        vals = df_norm[df_norm['team']==team][metrics].values.flatten().tolist()
        vals += vals[:1]
        ax.plot(angles, vals, color=c, linewidth=2, label=team)
        ax.fill(angles, vals, color=c, alpha=0.12)
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(['PPDA\n(压迫强度)','中场抢断','夺回率',
                        '中场夺回占比','反抢成功率'], fontsize=11)
    ax.set_ylim(0, 1)
    ax.legend(loc='upper right', bbox_to_anchor=(1.25, 1.1))
    ax.set_title('中场绞杀与夺回球权能力雷达对比', fontsize=14, pad=20)
    plt.tight_layout()
    plt.show()
radar_plot(agg_norm, [t for t,_ in teams], metrics)

战术风格散点:PPDA vs 中场夺回占比

fig, ax = plt.subplots(figsize=(9,6))
sns.scatterplot(data=agg, x='PPDA', y='midfield_recovery_pct',
                hue='style', size='midfield_tackles',
                sizes=(100, 400), alpha=0.8, ax=ax)
for _, r in agg.iterrows():
    ax.annotate(r['team'], (r['PPDA'], r['midfield_recovery_pct']),
                xytext=(4,4), textcoords='offset points', fontsize=9)
ax.axvline(agg['PPDA'].median(), ls='--', c='gray')
ax.axhline(agg['midfield_recovery_pct'].median(), ls='--', c='gray')
ax.set_xlabel('PPDA(越小=压迫越强)')
ax.set_ylabel('中场夺回占比')
ax.set_title('战术风格象限图:压迫强度 × 中场绞杀占比')
ax.invert_xaxis()
plt.tight_layout(); plt.show()

统计显著性检验(中场绞杀 vs 高位绞杀)

mid   = df[df['style']=='mid_block']['midfield_tackles']
high  = df[df['style']=='high_press']['midfield_tackles']
t, p = stats.ttest_ind(mid, high, equal_var=False)
print(f"中场抢断数 中场绞杀 {mid.mean():.1f} vs 高位绞杀 {high.mean():.1f}")
print(f"Welch t-test: t={t:.3f}, p={p:.4f} → {'显著' if p<0.05 else '不显著'}")
# 效应量
pooled_std = np.sqrt((mid.var()+high.var())/2)
cohen_d = (mid.mean()-high.mean())/pooled_std
print(f"Cohen's d = {cohen_d:.2f}")

分组箱线图:夺回球权率对比

fig, axes = plt.subplots(1, 2, figsize=(13,5))
sns.boxplot(data=df, x='style', y='ball_recovery_rate', ax=axes[0],
            palette='Set2', order=['high_press','mid_block','low_block'])
axes[0].set_title('总夺回球权率分布')
sns.boxplot(data=df, x='style', y='midfield_recovery_pct', ax=axes[1],
            palette='Set3', order=['high_press','mid_block','low_block'])
axes[1].set_title('中场夺回占比分布')
plt.tight_layout(); plt.show()

结论对比

战术体系 PPDA 中场抢断 夺回率 中场占比 特点
高位绞杀 低(8-9) 中(14) 高(62%) 低(30%) 前场反抢多,靠对手后场失误
中场绞杀 中(11) 高(22) 中(58%) 高(55%) 中路密集拦截,腰位核心
低位防守 高(15) 中(15) 低(52%) 中(40%) 被动防守,深度回收

关键洞察:

  1. 中场绞杀球队(如马竞、国米)中场抢断数显著高于高位绞杀(p<0.05,Cohen's d>1.0),但整体夺回率略低——说明其"效率换稳定"。
  2. 高位绞杀球队(利物浦、曼城)夺回率高但中场占比低,球权回收位置更靠前。
  3. 中场绞杀是"以空间换次数":牺牲部分反抢效率,换取更牢固的中路屏障,通常伴随更低的被射门数。

可扩展方向

  • 实际数据接入:改用 statsbombpy(StatsBomb 公开数据)或 soccerdata 抓取真实事件坐标,按 x 轴区间真正切分"中场",替换模拟数据。
  • 传球网络 + 夺回位置热图:用 mplsoccer 绘制夺回球权位置热图,直观呈现绞杀区域。
  • 时序建模:用 statsmodels 分析 PPDA 与丢球时长的相关性。

需要我把这个案例改成基于 StatsBomb 真实数据的版本,或者补充夺回球权位置热图吗?

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