本文目录导读:

在足球数据分析中,“撞墙式配合”(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),可以告诉我具体字段名,我帮你进一步调整。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播