实用脚本统计撞墙式配合完成了几次?

wen 实用脚本 2

本文目录导读:

实用脚本统计撞墙式配合完成了几次?

  1. 核心判定逻辑(脚本依据)
  2. 方案一:Python 脚本(适合赛后数据分析,需有事件流数据)
  3. 方案二:JavaScript 脚本(适合Web端实时数据可视化)
  4. 如何应用到真实比赛数据?

在足球数据分析中,“撞墙式配合”(One-Two,即二过一)通常没有一个统一的官方标准统计字段,因为传球成功率、推进距离和防守压力等多种因素都会影响判定,我们可以通过球员跑位、传球方向来模拟实现一个高精度的统计脚本。

这里提供两个实用脚本方案,分别基于Python(数据科学/分析)JavaScript(前端/直播流),供你根据技术栈选择。


核心判定逻辑(脚本依据)

一个标准的“撞墙式配合”通常满足以下三个条件:

  1. A传球给B(第一传)。
  2. B不停球(或极少触球)立即回敲/直塞给跑位的A(第二传)。
  3. A在接球的瞬间领先于防守球员(通常通过传球瞬间的加速度或位置差判断)。

Python 脚本(适合赛后数据分析,需有事件流数据)

该脚本解析包含 x, y, timestamp, player_id, team_id 的传球事件数据帧。

import pandas as pd
import numpy as np
def detect_one_twos(passes_df, max_time_gap=2.0, key_pass_threshold=0.5):
    """
    检测撞墙式配合(One-Two)
    参数:
    passes_df: DataFrame,必须包含列:
        - frame (int): 帧序号或时间戳(用于判断时间差)
        - player_id (int): 传球球员ID
        - receiver_id (int): 接球球员ID
        - x, y (float): 传球发生时的坐标
        - team_id (int): 球队ID
    高级参数(可根据实际数据调整):
        - max_time_gap: 两次传球之间的最大时间间隔(秒),超过则不算撞墙
        - key_pass_threshold: 最后接球点的深度增量(如超过0.5个单位认为是在向前推进)
    """
    results = []
    # 按球队和时间排序
    df = passes_df.sort_values(['team_id', 'frame']).reset_index(drop=True)
    for i in range(len(df) - 1):
        pass1 = df.iloc[i]
        # 寻找下一条记录作为候选第二传
        for j in range(i+1, min(i+5, len(df))):  # 限制搜索窗口防止死循环
            pass2 = df.iloc[j]
            # 过滤条件:必须是同一支球队,且时间间隔短
            if pass1['team_id'] != pass2['team_id']:
                break
            if pass2['frame'] - pass1['frame'] > max_time_gap:
                break
            # 核心判定逻辑:B——>A(第一传的接球者,将球传回给第一传的发起者)
            if (pass1['receiver_id'] == pass2['player_id'] 
                and pass2['receiver_id'] == pass1['player_id']
                and pass1['player_id'] != pass1['receiver_id']):  # 排除自传
                # 附加条件:向前推进(第二传的终点比第一传的起点更靠近对方球门)
                # 假设对方球门在 x=0(左) 和 x=100(右),这里简化为计算移动距离
                delta_x = abs(pass2['x'] - pass1['x'])
                # 如果向前推进行为较为明显,则判定为一次成功的撞墙
                if delta_x > key_pass_threshold:
                    results.append({
                        'frame_start': pass1['frame'],
                        'frame_end': pass2['frame'],
                        'team': pass1['team_id'],
                        'player_A': pass1['player_id'],
                        'player_B': pass1['receiver_id'],
                        'length': delta_x
                    })
                break  # 找到一个即停止向内搜索,防止重复计数
    return results
# ---------- 使用示例(模拟数据) ----------
data = [
    # [frame, player_id, receiver_id, x, y, team_id]
    [1, 10, 7, 20, 50, 1],  # A传B
    [1.5, 7, 10, 22, 48, 1], # B传A
    [3, 10, 5, 20, 50, 1],  # 时间太久,不算
    [3.5, 5, 10, 30, 52, 1], # 也算(但时间较久)
    [5, 8, 9, 40, 60, 2],   # 另一队
]
df = pd.DataFrame(data, columns=['frame', 'player_id', 'receiver_id', 'x', 'y', 'team_id'])
one_twos = detect_one_twos(df, max_time_gap=2.0, key_pass_threshold=1.0)
print(f"检测到 {len(one_twos)} 次撞墙式配合:")
for ot in one_twos:
    print(ot)

JavaScript 脚本(适合Web端实时数据可视化)

/**
 * 实用统计撞墙式配合(One-Two)脚本 - 前端事件流版本
 * 适用于具备实时事件监听的数据源
 */
class OneTwoDetector {
    constructor(options = {}) {
        this.options = {
            maxTimeGap: 2.5,          // 最大间隔时间(秒)
            minForwardProgress: 2.0,   // 最少向前推进距离(米/单位)
            ...options
        };
        this.passBuffer = [];          // 存储最近N次传球事件
        this.count = 0;               // 计数器
    }
    /**
     * 接收新的传球事件
     * @param {Object} pass 传球事件对象
     * @param {number} pass.frame 帧数/时间戳
     * @param {number} pass.playerId 传球球员ID
     * @param {number} pass.receiverId 接球球员ID
     * @param {number} pass.x 传球时的 x 坐标
     * @param {number} pass.y 传球时的 y 坐标
     * @param {number} pass.teamId 球队ID
     */
    addEvent(pass) {
        // 简单缓存最近10次传球,避免全量扫描
        this.passBuffer.push(pass);
        if (this.passBuffer.length > 10) this.passBuffer.shift();
        // 检测逻辑:倒序查找最后一个符合条件的事件对
        for (let i = this.passBuffer.length - 2; i >= 0; i--) {
            const firstPass = this.passBuffer[i];
            const secondPass = this.passBuffer[this.passBuffer.length - 1];
            // 条件1:同一支球队
            if (firstPass.teamId !== secondPass.teamId) continue;
            // 条件2:时间差在允许范围内
            if (secondPass.frame - firstPass.frame > this.options.maxTimeGap) break;
            // 条件3:A传给B,然后B传球给A(撞墙)
            if (firstPass.receiverId === secondPass.playerId && 
                secondPass.receiverId === firstPass.playerId &&
                firstPass.playerId !== firstPass.receiverId) {
                // 条件4:向前推进(y方向朝对方球门)
                const forwardProgress = Math.abs(secondPass.x - firstPass.x);
                if (forwardProgress > this.options.minForwardProgress) {
                    this.count++;
                    console.log(`✅ 撞墙配合#${this.count}: Player ${firstPass.playerId} 与 Player ${secondPass.playerId},推进距离 ${forwardProgress.toFixed(1)}`);
                    // 移除已计数的事件,防止重复统计(可选)
                    this.passBuffer.splice(0, i + 1);
                }
                break;
            }
        }
    }
    /**
     * 返回当前统计总数
     */
    getTotalCount() {
        return this.count;
    }
}
// ---------- 使用示例 ----------
const detector = new OneTwoDetector({ maxTimeGap: 2.0 });
// 模拟接受到直播流事件
detector.addEvent({ frame: 10, playerId: 5, receiverId: 8, x: 30, y: 10, teamId: 1 }); // 传球
detector.addEvent({ frame: 10.8, playerId: 8, receiverId: 5, x: 33, y: 12, teamId: 1 }); // 撞墙转身
console.log(`当前总计:${detector.getTotalCount()} 次`);

如何应用到真实比赛数据?

  1. 如果你有 Opta / StatsBomb / WyScout 数据: 这些数据通常包含 pass 类型字段(如 one_touch_pass),你可以在过滤条件中直接添加 pass_type == 'one_touch' 来提升准确性。
  2. 如果没有事件流数据,只有追踪数据(tracking data): 需要先通过自动识别传球(检测球权转移)和跑位(球员加速度方向)来生成事件,再套用上述逻辑。
  3. 修正方向: 真实比赛中有“背身做球”的情况(B不转身传给A),需要结合场上站位判断是否属于“撞墙向前推进”,而非简单回传。

  • 统计核心: 寻找满足“A传B→B传A”且时间短、向前的传接组合。
  • 推荐用法: 如果数据质量高,建议在数据源中标记 is_offensive(是否向前)字段下,统计准确率可接近90%以上。
  • 进阶优化: 可以通过球员最高速度调整 maxTimeGap,或者用滑窗统计替代固定缓冲区。

你可以根据自己的数据格式,将上述脚本稍作修改即可投入实用,如果需要处理特定的数据格式(如CSV、JSON),可以告诉我具体字段名,我帮你进一步调整。

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