足球犯规战术阻止反击统计脚本
下面给你一个可直接运行的 Python 脚本,用来统计一场比赛中「犯规战术阻止反击」的次数,核心思路是:用事件数据(坐标、时间、犯规类型、是否快攻/反击)来识别“战术犯规”场景。

判定逻辑
一次犯规被计为「战术犯规阻止反击」需同时满足:
- 事件类型 = 犯规(Foul)
- 犯规发生在防守方半场或中场(阻止对方推进)
- 犯规前对方处于反击/快攻状态(由数据字段
is_counter_attack或通过时间+位移推断) - 犯规者未得球(非抢断失败),且裁判未判点球
- 可选:犯规后对方进攻被中断(下一事件不是射门/进球)
脚本(Python + pandas)
import pandas as pd
# ============ 1. 读取事件数据 ============
# 假设 CSV 列: match_id, minute, second, team, player, event_type,
# x, y, is_counter_attack, foul_type, card, outcome
df = pd.read_csv("events.csv")
# ============ 2. 参数配置 ============
COUNTER_WINDOW_SEC = 8 # 反击判定时间窗(秒)
DEFENSIVE_HALF_X = 60 # 防守方半场 x 阈值(0-100 坐标系,进攻方向向右)
EXCLUDE_FOUL_TYPES = {"penalty"} # 排除点球犯规
def to_seconds(row):
return row["minute"] * 60 + row["second"]
df["t"] = df.apply(to_seconds, axis=1)
df = df.sort_values(["match_id", "t"]).reset_index(drop=True)
# ============ 3. 标记“反击状态” ============
# 若数据自带 is_counter_attack 字段直接用;否则用规则推断:
# 犯规前 COUNTER_WINDOW_SEC 秒内,对方在本方半场快速向前推进(x 增幅 > 15)
def mark_counter(df):
df["counter_flag"] = df.get("is_counter_attack", 0).fillna(0).astype(int)
if "is_counter_attack" not in df.columns:
for i in range(1, len(df)):
prev, cur = df.iloc[i-1], df.iloc[i]
if cur["team"] != prev["team"]:
dt = cur["t"] - prev["t"]
dx = cur["x"] - prev["x"]
if 0 < dt <= COUNTER_WINDOW_SEC and dx > 15:
df.at[i, "counter_flag"] = 1
return df
df = mark_counter(df)
# ============ 4. 识别战术犯规阻止反击 ============
def is_tactical_counter_foul(row, prev_row):
if row["event_type"] != "Foul":
return False
if row.get("foul_type") in EXCLUDE_FOUL_TYPES:
return False
if row.get("card") == "penalty":
return False
# 犯规发生在防守方半场(进攻方向向右,防守方在本方 x<50 区域)
if row["x"] > DEFENSIVE_HALF_X:
return False
# 犯规前对方处于反击状态
if prev_row is None or prev_row["team"] == row["team"]:
return False
if prev_row["counter_flag"] != 1 and row["counter_flag"] != 1:
return False
return True
results = []
for i in range(1, len(df)):
cur, prev = df.iloc[i], df.iloc[i-1]
if is_tactical_counter_foul(cur, prev):
results.append({
"match_id": cur["match_id"],
"minute": cur["minute"],
"second": cur["second"],
"team": cur["team"],
"player": cur["player"],
"x": cur["x"],
"y": cur["y"],
"foul_type": cur.get("foul_type"),
"card": cur.get("card"),
})
tactical_df = pd.DataFrame(results)
# ============ 5. 输出统计 ============
print(f"总战术犯规阻止反击次数: {len(tactical_df)}")
if not tactical_df.empty:
print("\n按球队统计:")
print(tactical_df.groupby("team").size().sort_values(ascending=False))
print("\n按球员统计 (Top 10):")
print(tactical_df.groupby(["team", "player"]).size()
.sort_values(ascending=False).head(10))
tactical_df.to_csv("tactical_counter_fouls.csv", index=False)
print("\n明细已保存到 tactical_counter_fouls.csv")
数据字段要求
| 字段 | 说明 | 是否必需 |
|---|---|---|
match_id |
比赛 ID | |
minute / second |
事件时间 | |
team / player |
犯规方 | |
event_type |
事件类型(Foul / Pass / Shot…) | |
x / y |
场地坐标(0–100) | |
is_counter_attack |
是否反击(0/1) | ⭕ 可选,无则脚本推断 |
foul_type |
犯规类型 | |
card |
牌 / 是否点球 |
如何校准
COUNTER_WINDOW_SEC:默认 8 秒,反击节奏快的联赛可调到 5–6 秒。DEFENSIVE_HALF_X:默认 60(即本方半场 0–60),可按坐标系调整。dx > 15:反击推进阈值,可根据联赛平均推进速度调整。- 若有 StatsBomb / Opta / Wyscout 数据,直接使用其
counter_attack标签更准确。
扩展建议
- 加入 “犯规后进攻是否中断” 判断:检查犯规后 10 秒内对方是否射门,若射门则可能不算“成功阻止”。
- 区分 “战术犯规吃牌” 与 “普通犯规”:
card in {Yellow, Red}可作为战术犯规的强信号。 - 输出 热力图:用
matplotlib画出犯规位置分布。
需要我帮你把脚本适配到 StatsBomb 开源数据 或 Wyscout 格式,或者加上可视化热力图吗?