实用脚本统计交叉跑位造成威胁几次?

wen 实用脚本 3

足球比赛"交叉跑位造成威胁"统计脚本

下面给你一个可直接用的 Python 脚本框架,用于统计一场比赛中"交叉跑位(crossing runs / overlapping runs)造成威胁"的次数,因为不同数据源(StatsBomb、Wyscout、事件流、跟踪数据)字段不同,我给的是通用可替换逻辑 + 示例。

实用脚本统计交叉跑位造成威胁几次?


先定义"交叉跑位造成威胁"的判定规则

建议用可量化规则,避免主观:

条件 说明
① 交叉跑位事件 两名同队球员在 2 秒内、5 米内相向/交错跑动,且跑动方向角度差 > 60°
② 造成威胁 交叉跑位后 5 秒内出现以下任一:射门、关键传球、进入禁区、xG 增量 > 0.05、防线被打穿(最后一名后卫被越过)
③ 排除 越位、死球、明显回传

示例脚本(基于事件数据 + 坐标)

import pandas as pd
import numpy as np
from math import atan2, degrees
# ---------- 1. 读取数据 ----------
# 期望列: match_id, event_id, timestamp(s), team, player, x, y, event_type, end_x, end_y
df = pd.read_csv("events.csv")
df = df.sort_values(["match_id", "timestamp"]).reset_index(drop=True)
# ---------- 2. 参数 ----------
TIME_WIN = 2.0      # 交叉跑位判定时间窗(秒)
DIST_WIN = 5.0      # 交叉跑位判定距离窗(米)
ANGLE_TH = 60       # 方向差阈值(度)
THREAT_WIN = 5.0    # 交叉后威胁判定时间窗(秒)
THREAT_EVENTS = {"shot", "key_pass", "carry_into_box", "through_ball"}
# ---------- 3. 计算每名球员的跑动向量 ----------
def get_runs(sub):
    runs = []
    for _, r in sub.iterrows():
        if pd.isna(r.end_x) or pd.isna(r.end_y):
            continue
        dx, dy = r.end_x - r.x, r.end_y - r.y
        dist = np.hypot(dx, dy)
        if dist < 0.5:   # 微动忽略
            continue
        ang = degrees(atan2(dy, dx))
        runs.append({
            "event_id": r.event_id, "t": r.timestamp,
            "team": r.team, "player": r.player,
            "x": r.x, "y": r.y, "dx": dx, "dy": dy,
            "dist": dist, "angle": ang,
        })
    return pd.DataFrame(runs)
def angle_diff(a, b):
    d = abs(a - b) % 360
    return min(d, 360 - d)
# ---------- 4. 识别交叉跑位 ----------
def find_cross_runs(runs):
    out = []
    for i, a in runs.iterrows():
        same_team = runs[(runs.team == a.team) & (runs.event_id != a.event_id)]
        cand = same_team[
            (same_team.t - a.t).between(0, TIME_WIN) &
            (np.hypot(same_team.x - a.x, same_team.y - a.y) <= DIST_WIN)
        ]
        for _, b in cand.iterrows():
            if angle_diff(a.angle, b.angle) > ANGLE_TH and a.player != b.player:
                out.append({
                    "team": a.team,
                    "player_a": a.player, "player_b": b.player,
                    "t_cross": max(a.t, b.t),
                    "x": (a.x + b.x) / 2, "y": (a.y + b.y) / 2,
                })
    return pd.DataFrame(out).drop_duplicates(
        subset=["team", "player_a", "player_b", "t_cross"]
    ) if out else pd.DataFrame()
# ---------- 5. 判定威胁 ----------
def is_threat(cross, team_events):
    win = team_events[
        (team_events.t >= cross.t_cross) &
        (team_events.t <= cross.t_cross + THREAT_WIN)
    ]
    # 条件1: 出现射门/关键传球等
    if win.event_type.isin(THREAT_EVENTS).any():
        return True
    # 条件2: xG 累计增量
    if "xg" in win.columns and win.xg.fillna(0).sum() > 0.05:
        return True
    # 条件3: 进入禁区
    if ((win.x > 84) & (win.y.between(18, 66))).any():   # StatsBomb 坐标系
        return True
    return False
# ---------- 6. 主流程 ----------
results = []
for mid, g in df.groupby("match_id"):
    for team, tg in g.groupby("team"):
        runs = get_runs(tg)
        if runs.empty:
            continue
        crosses = find_cross_runs(runs)
        if crosses.empty:
            continue
        for _, c in crosses.iterrows():
            if is_threat(c, tg[["t", "event_type", "x", "y", "xg"]].rename(columns={"timestamp":"t"})
                        if "t" not in tg.columns else tg.rename(columns={"timestamp":"t"})):
                results.append({**c.to_dict(), "match_id": mid})
res = pd.DataFrame(results)
print("交叉跑位造成威胁次数:")
print(res.groupby(["match_id", "team"]).size())
# 导出明细
res.to_csv("cross_run_threats.csv", index=False)

输出示例

match_id  team
M001      Team_A    7
          Team_B    4
M002      Team_A    5
          Team_B    9
dtype: int64

明细 CSV 会包含:哪两名球员、第几秒、交叉位置、所属球队。


你需要替换的地方

  1. 数据源字段名

    • StatsBomb:type / timestamp / location([x,y] 数组)、carry.end_location
    • Wyscout:eventSec / positions / subEventName
    • 跟踪数据:直接算每帧速度方向更准
  2. 坐标系:StatsBomb 是 x∈[0,120], y∈[0,80];Wyscout 是 0–100。禁区判定阈值要跟着改。

  3. 判定阈值(TIME_WIN、DIST_WIN、ANGLE_TH、THREAT_WIN)——建议先用 1–2 场比赛人工标注校准。

  4. 威胁定义:如果你们内部定义不同(防线被打穿"要单独看最后一名后卫位置),我可以帮你加一段。


如果你有跟踪数据(更准)

用跟踪数据时,把"跑动向量"换成每帧速度方向,用帧间速度向量夹角 > 60° 且两球员距离 < 5m 持续 ≥ 0.5s 判定交叉跑位,威胁判定不变,命中率会明显高于纯事件数据。


要不要告诉我你用的数据源(StatsBomb / Wyscout / 自采跟踪 / 其他)和字段结构?我可以把这个脚本改成一跑就能出数的版本。

上一篇这个实用脚本显示远射次数哪队多?

下一篇当前分类已是最新一篇

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