综合实用脚本,中卫组合默契度如何量化?

wen 实用脚本 4

综合实用脚本

问题拆解

“中卫组合默契度”本质是两名中卫在协同防守中的配合效率,需从多维度量化:

综合实用脚本,中卫组合默契度如何量化?

维度 含义 可观测指标
空间互补 一人上抢时另一人是否补位 上抢-补位时间差、覆盖重叠率
距离协同 两人间距是否合理(过近漏边、过远留缝) 平均间距、间距标准差、危险间距占比
移动同步 造越位/退防是否一致 防线同步率、越位成功/失败次数
传接默契 后场出球配合 互传成功率、受压下互传成功率
危机互补 一人失位时另一人补救 补位拦截次数、失位后30秒内被射门率

数据需求

追踪数据(每帧):两名中卫的 x/y 坐标、球坐标、事件标签(传球/抢断/拦截/越位线)。 事件数据:传球网络、防守动作、失球过程。

最低可行数据:每帧球员坐标 + 事件流(StatsBomb/Opta 风格),若无追踪数据,可用事件数据做降级版(仅传接+补位)。


量化脚本(Python)

import numpy as np
import pandas as pd
from itertools import combinations
# ============ 输入假设 ============
# frames: DataFrame, columns = [frame_id, player_id, x, y, team, is_ball]
# events: DataFrame, columns = [frame_id, event_type, player_id, success]
# 球场坐标归一化到 105x68(米),防守方向为 x 减小方向
PITCH_L, PITCH_W = 105, 68
def get_cb_pair(frames, cb_ids):
    """提取两名中卫每帧坐标"""
    df = frames[frames.player_id.isin(cb_ids)].pivot_table(
        index='frame_id', columns='player_id', values=['x','y'])
    df.columns = [f'{c[0]}_{c[1]}' for c in df.columns]
    return df.dropna()
def spatial_complementarity(pair_df, cb_ids):
    """空间互补:间距合理性 + 覆盖重叠"""
    a, b = cb_ids
    dx = pair_df[f'x_{a}'] - pair_df[f'x_{b}']
    dy = pair_df[f'y_{a}'] - pair_df[f'y_{b}']
    dist = np.sqrt(dx**2 + dy**2)
    # 理想中卫间距 6~12 米(可调)
    ideal = (dist >= 6) & (dist <= 12)
    too_close = dist < 6      # 易被一打二
    too_far = dist > 15       # 中间留缝
    return {
        'avg_dist': dist.mean(),
        'dist_std': dist.std(),               # 越小越稳定
        'ideal_ratio': ideal.mean(),          # 越高越好
        'gap_risk_ratio': (too_close | too_far).mean(),
        'y_overlap': 1 - abs(dy).mean() / PITCH_W  # 纵向覆盖重叠
    }
def movement_sync(pair_df, cb_ids, frames):
    """移动同步:两人速度向量夹角与防线同步率"""
    a, b = cb_ids
    va = pair_df[[f'x_{a}', f'y_{a}']].diff().dropna()
    vb = pair_df[[f'x_{b}', f'y_{b}']].diff().dropna()
    # 夹角余弦(同向为1)
    cos = (va.values * vb.values).sum(1) / (
        np.linalg.norm(va.values,axis=1)*np.linalg.norm(vb.values,axis=1)+1e-9)
    # 同步率:夹角<30° 且都朝防守方向移动
    sync = ((cos > 0.87) & (va.values[:,0] < 0)).mean()
    return {'move_sync_rate': sync, 'avg_cos': np.nanmean(cos)}
def passing_chemistry(events, cb_ids):
    """后场互传默契"""
    a, b = cb_ids
    passes = events[(events.event_type=='pass') & (events.player_id.isin(cb_ids))]
    # 假设有 pass_target 字段
    mutual = passes[passes.get('pass_target').isin(cb_ids)]
    return {
        'mutual_pass_count': len(mutual),
        'mutual_pass_success': mutual.success.mean() if len(mutual) else np.nan,
        'under_pressure_success': mutual[mutual.get('under_pressure',False)].success.mean()
            if len(mutual) else np.nan
    }
def crisis_coverage(events, cb_ids, frames):
    """危机互补:补位拦截 + 失位后短时被射门率"""
    a, b = cb_ids
    # 补位拦截:A 上抢事件后 3 秒内 B 的拦截
    cover = 0
    for _, e in events[events.event_type=='tackle'].iterrows():
        if e.player_id == a:
            window = events[(events.frame_id > e.frame_id) &
                            (events.frame_id <= e.frame_id + 75) &  # 3s@25fps
                            (events.player_id == b) &
                            (events.event_type.isin(['interception','clearance']))]
            cover += len(window)
    return {'cover_actions': cover}
def chemical_score(metrics):
    """综合默契度评分(0~100),权重可调"""
    w = {'ideal_ratio':25,'dist_std':-15,'move_sync_rate':25,
         'mutual_pass_success':20,'gap_risk_ratio':-15,'move_sync_rate2':0}
    # 归一化示例
    s = (metrics['ideal_ratio']*25
         + max(0,(1-metrics['dist_std']/5))*15
         + metrics['move_sync_rate']*25
         + (metrics.get('mutual_pass_success') or 0)*20
         + (1-metrics['gap_risk_ratio'])*15)
    return round(min(max(s,0),100),1)
# ============ 主流程 ============
def evaluate_cb_pair(frames, events, cb_ids):
    pair_df = get_cb_pair(frames, cb_ids)
    m = {}
    m.update(spatial_complementarity(pair_df, cb_ids))
    m.update(movement_sync(pair_df, cb_ids, frames))
    m.update(passing_chemistry(events, cb_ids))
    m.update(crisis_coverage(events, cb_ids, frames))
    m['chemical_score'] = chemical_score(m)
    return m
# 示例:多组合对比
def rank_pairs(frames, events, cb_pool):
    results = []
    for pair in combinations(cb_pool, 2):
        r = evaluate_cb_pair(frames, events, list(pair))
        r['pair'] = pair
        results.append(r)
    return pd.DataFrame(results).sort_values('chemical_score', ascending=False)

使用与解读

  1. 准备数据:frames 需含每帧坐标(建议 25fps),events 含传球/抢断/拦截/解围,带 frame_id 对齐。
  2. 调用:
    table = rank_pairs(frames, events, cb_pool=[1,2,3,4])
    print(table[['pair','chemical_score','ideal_ratio','move_sync_rate','gap_risk_ratio']])
  3. 解读要点:
    • ideal_ratio > 0.7 且 gap_risk_ratio < 0.15 → 间距默契好;
    • move_sync_rate > 0.6 → 造越位/退防同步可靠;
    • mutual_pass_success > 0.9 → 出球不慌;
    • chemical_score 横向比较不同搭档。

降级版(无追踪数据)

仅有事件数据时,用传球网络 + 补位事件窗口近似:

def events_only_score(events, a, b):
    mutual = events[(events.event_type=='pass') &
                    (events.player_id==a) & (events.pass_target==b) |
                    (events.player_id==b) & (events.pass_target==a)]
    cover = ...  # 同上补位窗口逻辑
    return mutual.success.mean()*60 + min(cover,10)*4  # 0~100

注意事项

  • 坐标方向需统一(防守朝 x 减小),否则同步率符号错。
  • 理想间距阈值(6~12m)需按球队战术调整:高压球队间距更小。
  • 样本量:单场噪声大,建议至少 5~10 场滚动平均。
  • 对手强度需加权,避免“虐菜默契”。
  • 追踪数据缺失帧用插值,但插值比例 >20% 的组合应标记低置信度。

如需我针对你手上的具体数据字段(如 StatsBomb 的 3701 事件类型、或 Metrica 追踪格式)改写成可直接跑的版本,贴一下列名即可。

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