python案例统计界外球进攻威胁次数?

wen python案例 4

统计界外球进攻威胁次数

下面是一个完整的 Python 案例,演示如何统计界外球(Throw-in)后的进攻威胁次数,核心思路是:找到界外球事件 → 追踪随后 N 秒/若干次传球内是否产生射门/射正/进球/进入禁区等威胁事件。

python案例统计界外球进攻威胁次数?

数据准备

假设你有事件数据(类似 StatsBomb / Opta 格式),字段如下:

字段 说明
event_id 事件ID
match_id 比赛ID
period 半场
timestamp 秒级时间戳
team 球队
type 事件类型(Throw-in / Pass / Shot / Goal 等)
x, y 坐标(0-100 归一化)
end_x, end_y 传球/射门终点

威胁定义(可自定义)

一次界外球进攻被计为有威胁,需在界外球后 10 秒 + 最多 3 次传球内出现以下任一:

  • 射门 Shot(含射正 Shot on Target)
  • 进球 Goal
  • 传球进入对方禁区(x ≥ 83,y 在 18–82,按 0-100 坐标)
  • 传中进入小禁区(x ≥ 94)

完整代码

import pandas as pd
THREAT_WINDOW_SEC = 10      # 界外球后观察窗口(秒)
MAX_PASS_CHAIN   = 3        # 最多追踪传球次数
BOX_X_START      = 83       # 对方禁区起点 x
BOX_Y_MIN, BOX_Y_MAX = 18, 82
SIX_X_START      = 94       # 小禁区 x
THREAT_TYPES = {"Shot", "Shot on Target", "Goal"}
def is_into_box(row):
    """传球终点是否进入对方禁区"""
    if pd.isna(row.get("end_x")):
        return False
    return row["end_x"] >= BOX_X_START and BOX_Y_MIN <= row["end_y"] <= BOX_Y_MAX
def is_into_six_yard(row):
    return (not pd.isna(row.get("end_x"))) and row["end_x"] >= SIX_X_START
def count_threats_from_throwins(df: pd.DataFrame) -> pd.DataFrame:
    """
    返回每场比赛的界外球总数与威胁界外球数
    """
    df = df.sort_values(["match_id", "period", "timestamp"]).reset_index(drop=True)
    results = []
    for match_id, g in df.groupby("match_id"):
        g = g.reset_index(drop=True)
        throw_idx = g.index[g["type"] == "Throw-in"].tolist()
        total, threat = 0, 0
        for i in throw_idx:
            total += 1
            throw_row = g.loc[i]
            team = throw_row["team"]
            t0 = throw_row["timestamp"]
            period = throw_row["period"]
            # 进入后续事件窗口
            j = i + 1
            pass_count = 0
            threatened = False
            while j < len(g):
                row = g.loc[j]
                # 换半场/换队(非本队事件)即中断链条
                if row["period"] != period:
                    break
                if row["timestamp"] - t0 > THREAT_WINDOW_SEC:
                    break
                if row["team"] != team:      # 球权丢失,链条终止
                    break
                etype = row["type"]
                # 威胁判定
                if etype in THREAT_TYPES:
                    threatened = True
                    break
                if etype == "Pass":
                    if is_into_box(row) or is_into_six_yard(row):
                        threatened = True
                        break
                    pass_count += 1
                    if pass_count > MAX_PASS_CHAIN:
                        break
                j += 1
            if threatened:
                threat += 1
        results.append({
            "match_id": match_id,
            "total_throwins": total,
            "threat_throwins": threat,
            "threat_rate": round(threat / total, 3) if total else 0
        })
    return pd.DataFrame(results)
# ---------- 构造示例数据 ----------
data = [
    # 比赛1:一次界外球后 5 秒射门(威胁)
    {"match_id": 1, "period": 1, "timestamp": 100, "team": "A", "type": "Throw-in", "x": 40, "y": 50, "end_x": None, "end_y": None},
    {"match_id": 1, "period": 1, "timestamp": 103, "team": "A", "type": "Pass",     "x": 45, "y": 55, "end_x": 90, "end_y": 40},
    {"match_id": 1, "period": 1, "timestamp": 105, "team": "A", "type": "Shot",     "x": 90, "y": 40, "end_x": None, "end_y": None},
    # 比赛1:一次界外球后无威胁(3次传球内没进禁区)
    {"match_id": 1, "period": 1, "timestamp": 200, "team": "A", "type": "Throw-in", "x": 30, "y": 50, "end_x": None, "end_y": None},
    {"match_id": 1, "period": 1, "timestamp": 203, "team": "A", "type": "Pass",     "x": 35, "y": 55, "end_x": 50, "end_y": 60},
    {"match_id": 1, "period": 1, "timestamp": 206, "team": "A", "type": "Pass",     "x": 55, "y": 60, "end_x": 70, "end_y": 65},
    # 比赛2:界外球后传球直达禁区(威胁)
    {"match_id": 2, "period": 1, "timestamp": 50, "team": "B", "type": "Throw-in",  "x": 60, "y": 20, "end_x": None, "end_y": None},
    {"match_id": 2, "period": 1, "timestamp": 53, "team": "B", "type": "Pass",      "x": 65, "y": 25, "end_x": 85, "end_y": 50},
]
df = pd.DataFrame(data)
result = count_threats_from_throwins(df)
print(result)

输出:

   match_id  total_throwins  threat_throwins  threat_rate
0         1               2                1          0.5
1         2               1                1          1.0

关键设计说明

设计点 说明
时间窗口 THREAT_WINDOW_SEC=10,可按联赛/战术微调(英超常 8–15 秒)
传球链限制 避免一次界外球后很长时间都算威胁,限制 MAX_PASS_CHAIN
球权中断 对方触球即结束链条,确保归因于界外球队
威胁判定 射门/进球/进禁区/进小禁区四类,可扩展为 xG 累计
退化版本 若数据没有 end_x/y,退化为仅统计"射门/进球"即可

进阶扩展

  1. 使用 xG 而非二分:把窗口内所有射门的 xG 累加,作为"威胁强度"更细腻。
    xg_threat = g.loc[i+1:j, "xg"].sum()
  2. 区分界外球位置:
    • 前场界外球(x ≥ 60)vs 后场界外球
    • 左侧/右侧
  3. Defensive Third / Middle / Final Third 分区统计 threat_rate。
  4. 可视化:用 matplotlib 画界外球起点散点图,颜色表示是否威胁。

如果你能提供实际数据结构(列名/示例行),我可以把上面的代码直接改写成适配你数据的版本(StatsBomb 的 event.json 或 Wyscout 格式)。

友情链接

欧易欧易欧易欧易欧易欧易欧易欧易直播帮篮球控NBA帮比赛虎聊足球

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

友情链接攻略窝 · 礼包窝 · 配置窝 · 换算窝 · 算法窝 · 查文窝 · 手册窝 · 问答窝 · 全文搜 · 手册库
相关站点礼包窝 · 查文窝