本文目录导读:

在足球数据分析中,“撞墙式配合”(One-Two,即二过一)通常没有一个统一的官方标准统计字段,因为传球成功率、推进距离和防守压力等多种因素都会影响判定,我们可以通过球员跑位、传球方向来模拟实现一个高精度的统计脚本。
这里提供两个实用脚本方案,分别基于Python(数据科学/分析)和JavaScript(前端/直播流),供你根据技术栈选择。
核心判定逻辑(脚本依据)
一个标准的“撞墙式配合”通常满足以下三个条件:
- A传球给B(第一传)。
- B不停球(或极少触球)立即回敲/直塞给跑位的A(第二传)。
- 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()} 次`);
如何应用到真实比赛数据?
- 如果你有 Opta / StatsBomb / WyScout 数据: 这些数据通常包含
pass类型字段(如one_touch_pass),你可以在过滤条件中直接添加pass_type == 'one_touch'来提升准确性。 - 如果没有事件流数据,只有追踪数据(tracking data): 需要先通过自动识别传球(检测球权转移)和跑位(球员加速度方向)来生成事件,再套用上述逻辑。
- 修正方向: 真实比赛中有“背身做球”的情况(B不转身传给A),需要结合场上站位判断是否属于“撞墙向前推进”,而非简单回传。
- 统计核心: 寻找满足“A传B→B传A”且时间短、向前的传接组合。
- 推荐用法: 如果数据质量高,建议在数据源中标记
is_offensive(是否向前)字段下,统计准确率可接近90%以上。 - 进阶优化: 可以通过球员最高速度调整
maxTimeGap,或者用滑窗统计替代固定缓冲区。
你可以根据自己的数据格式,将上述脚本稍作修改即可投入实用,如果需要处理特定的数据格式(如CSV、JSON),可以告诉我具体字段名,我帮你进一步调整。