统计交叉跑位造成威胁次数
下面给你一个可落地的统计方案 + Python 脚本模板,核心思路是:先定义“交叉跑位”和“威胁”的判定规则,再用事件数据或追踪数据自动统计。

先明确判定规则(关键)
什么是交叉跑位?
常见三种定义,选一种或组合:
| 类型 | 定义 | 数据需求 |
|---|---|---|
| A. 位置交叉 | 两名同队球员在短时间内(如2秒内)跑动轨迹相交 | 追踪数据(x,y,t) |
| B. 方向交叉 | 两人跑动方向夹角 > 90°且路径接近 | 追踪数据 |
| C. 事件型 | 传球后接球人穿越防守/队友跑位 | 事件数据 |
什么是“造成威胁”?
- 交叉后 3~5 秒内 出现以下之一:
- 射门
- 关键传球 / 助攻
- 进入禁区
- xG 提升超过阈值(如 +0.05)
- 对方失位 / 犯规
Python 脚本模板(基于追踪数据)
import pandas as pd
import numpy as np
from itertools import combinations
# ========== 参数配置 ==========
CROSS_WINDOW = 2.0 # 交叉判定时间窗(秒)
THREAT_WINDOW = 5.0 # 交叉后威胁判定时间窗(秒)
MIN_DIST = 1.5 # 轨迹相交距离阈值(米)
DIR_ANGLE_THRESH = 90 # 方向夹角阈值(度)
XG_GAIN_THRESH = 0.05 # xG提升阈值
# ========== 读取数据 ==========
# tracking: columns = [frame, time, player_id, team, x, y]
tracking = pd.read_csv("tracking.csv")
def get_player_track(df, pid):
return df[df.player_id == pid].sort_values("time").reset_index(drop=True)
def paths_cross(t1, t2, window=CROSS_WINDOW, min_dist=MIN_DIST):
"""判断两条轨迹在时间窗内是否相交"""
t_start = max(t1.time.min(), t2.time.min())
t_end = min(t1.time.max(), t2.time.max())
for t in np.arange(t_start, t_end, 0.1):
p1 = t1.iloc[(t1.time - t).abs().argmin()]
p2 = t2.iloc[(t2.time - t).abs().argmin()]
if abs(p1.time - t) > 0.2 or abs(p2.time - t) > 0.2:
continue
d = np.hypot(p1.x - p2.x, p1.y - p2.y)
if d < min_dist:
return True, t
return False, None
def directions_opposite(t1, t2, t_cross):
"""判断交叉瞬间两人方向是否对向/交叉"""
def vel(track, t):
i = (track.time - t).abs().argmin()
if i == 0 or i >= len(track) - 1:
return None
dt = track.time.iloc[i+1] - track.time.iloc[i-1]
if dt == 0: return None
vx = (track.x.iloc[i+1] - track.x.iloc[i-1]) / dt
vy = (track.y.iloc[i+1] - track.y.iloc[i-1]) / dt
return np.array([vx, vy])
v1, v2 = vel(t1, t_cross), vel(t2, t_cross)
if v1 is None or v2 is None: return False
cos = np.dot(v1, v2) / (np.linalg.norm(v1)*np.linalg.norm(v2) + 1e-9)
angle = np.degrees(np.arccos(np.clip(cos, -1, 1)))
return angle > DIR_ANGLE_THRESH
# ========== 主流程 ==========
results = []
for team in tracking.team.unique():
team_df = tracking[tracking.team == team]
players = team_df.player_id.unique()
for p1, p2 in combinations(players, 2):
t1, t2 = get_player_track(team_df, p1), get_player_track(team_df, p2)
crossed, t_cross = paths_cross(t1, t2)
if not crossed:
continue
if not directions_opposite(t1, t2, t_cross):
continue
# 检查交叉后威胁窗口内是否有威胁事件
threat = threats[
(threats.team == team) &
(threats.time >= t_cross) &
(threats.time <= t_cross + THREAT_WINDOW)
]
is_threat = (
(threat.event.isin(["shot","key_pass","assist","penalty_area_entry"])).any()
or (threat.xG.diff().max() > XG_GAIN_THRESH)
)
results.append({
"team": team,
"players": f"{p1}-{p2}",
"time": round(t_cross, 1),
"threat": is_threat
})
df_res = pd.DataFrame(results)
print("总交叉跑位次数:", len(df_res))
print("造成威胁次数:", df_res.threat.sum())
print("\n按球队统计:")
print(df_res.groupby("team").threat.agg(["count","sum"]))
如果没有追踪数据(只有事件数据)
用简化版规则:传球事件中,接球人在传球瞬间与另一队友位置交叉,且之后5秒内有射门/关键传球。
# events: [time, team, player, event, x, y, end_x, end_y]
cross_events = []
for _, pass_ in events[events.event == "pass"].iterrows():
t = pass_.time
teammates = events[
(events.team == pass_.team) &
(events.time.between(t-1, t+1)) &
(events.player != pass_.player)
]
for _, mate in teammates.iterrows():
if abs(mate.x - pass_.end_x) < 2 and abs(mate.y - pass_.end_y) < 2:
# 视为跑位交汇点
follow = events[
(events.team == pass_.team) &
(events.time.between(t, t+5)) &
(events.event.isin(["shot","key_pass","assist"]))
]
cross_events.append({
"time": t,
"players": f"{pass_.player}-{mate.player}",
"threat": len(follow) > 0
})
df = pd.DataFrame(cross_events).drop_duplicates(subset=["time","players"])
print(f"交叉跑位形成威胁 {df.threat.sum()} 次 / 共 {len(df)} 次")
输出示例
总交叉跑位次数: 47
造成威胁次数: 12
按球队统计:
count sum
teamA 28 8
teamB 19 4
可扩展优化方向
- 加入防守球员:只有当交叉导致防守错位才算威胁。
- xT / xG 模型:用位置威胁值替代简单事件。
- 可视化:用 mplsoccer 画出交叉轨迹与后续威胁。
- 防误判:加最小间隔时间,排除摆渡、回追。
需要我根据你手上的具体数据格式(StatsBomb / 自家追踪 / Opta)改写成可直接跑的版本吗?告诉我列名和数据来源即可。