实用脚本如何分析定位球战术的多样性
分析定位球战术多样性,核心是把非结构化的定位球事件转化为可量化、可聚类、可对比的数据特征,下面给出一套可落地的脚本思路和代码框架。

分析框架(先想清楚要测什么)
定位球多样性可以从四个维度刻画:
| 维度 | 含义 | 示例指标 |
|---|---|---|
| 空间多样性 | 落点/传球区域分布 | 落点熵、区域覆盖率 |
| 战术类型多样性 | 战术套路种类 | 短传/长传/战术跑位占比 |
| 执行者多样性 | 主罚人/接应点 | 主罚人香农熵 |
| 结果多样性 | 形成的威胁方式 | 射门/头球/二点球分布 |
核心量化工具:香农熵(Shannon Entropy) —— 熵越高,战术越多样;熵越低,越单一可预测。
数据准备
需要的数据字段(以足球为例):
# 每条记录 = 一次定位球
{
"match_id": "M001",
"team": "A",
"set_piece_type": "corner", # corner/free_kick/throw_in/penalty
"taker": "Player1",
"delivery_type": "inswing", # 内旋/外旋/短传/战术
"target_zone": "near_post", # 落点区域
"first_contact": "header",
"outcome": "shot", # shot/goal/clearance/lost
"x_start": 100, "y_start": 0, # 坐标
"x_end": 110, "y_end": 5
}
核心脚本
计算多样性熵
import pandas as pd
import numpy as np
from scipy.stats import entropy
from collections import Counter
def diversity_entropy(series):
"""归一化香农熵: 0=完全单一, 1=完全均匀分布"""
counts = Counter(series.dropna())
probs = np.array(list(counts.values())) / sum(counts.values())
if len(probs) <= 1:
return 0.0
raw = entropy(probs, base=2)
return raw / np.log2(len(probs)) # 归一化到[0,1]
def team_setpiece_profile(df):
"""按球队+定位球类型输出多样性画像"""
results = []
for (team, sp_type), g in df.groupby(["team", "set_piece_type"]):
results.append({
"team": team,
"set_piece_type": sp_type,
"count": len(g),
"zone_diversity": diversity_entropy(g["target_zone"]),
"delivery_diversity": diversity_entropy(g["delivery_type"]),
"taker_diversity": diversity_entropy(g["taker"]),
"outcome_diversity":diversity_entropy(g["outcome"]),
})
return pd.DataFrame(results)
落点空间分布(网格化 + 覆盖率)
def zone_coverage(df, bins=6, pitch_len=105, pitch_wid=68):
"""把落点映射到网格,计算覆盖率和集中度(基尼系数)"""
gx = np.clip((df["x_end"] / pitch_len * bins).astype(int), 0, bins-1)
gy = np.clip((df["y_end"] / pitch_wid * bins).astype(int), 0, bins-1)
cell = gx * bins + gy
counts = cell.value_counts()
total_cells = bins * bins
coverage = len(counts) / total_cells # 覆盖了多少格子
# 基尼系数: 越接近1越集中
vals = np.sort(counts.values)
n = len(vals)
cum = np.cumsum(vals) / vals.sum()
gini = 1 - 2 * np.trapz(cum, dx=1/n) + 1/n
return {"coverage": coverage, "gini": gini, "n_zones": len(counts)}
战术聚类(把套路自动归类)
用 KMeans 对轨迹/落点做无监督聚类,再看每队用了多少种"套路"。
from sklearn.cluster import KMeans
from sklearn.preprocessing import StandardScaler
def cluster_routines(df, n_clusters=6):
feats = df[["x_start","y_start","x_end","y_end"]].dropna()
X = StandardScaler().fit_transform(feats)
km = KMeans(n_clusters=n_clusters, n_init=10, random_state=0)
labels = km.fit_predict(X)
df = df.loc[feats.index].copy()
df["routine"] = labels
return df, km
def routine_usage(df):
"""每队使用了几类套路 + 分布均匀度"""
out = []
for team, g in df.groupby("team"):
usage = g["routine"].value_counts(normalize=True)
out.append({
"team": team,
"n_routines_used": g["routine"].nunique(),
"routine_entropy": diversity_entropy(g["routine"]),
"dominant_share": usage.iloc[0], # 最常用套路占比
})
return pd.DataFrame(out)
多样性综合评分
def composite_diversity(profile_df, weights=None):
weights = weights or {
"zone_diversity":0.3, "delivery_diversity":0.25,
"taker_diversity":0.2, "outcome_diversity":0.25
}
score = sum(profile_df[k]*w for k,w in weights.items())
profile_df = profile_df.copy()
profile_df["diversity_score"] = score
return profile_df.sort_values("diversity_score", ascending=False)
进阶:预测性分析
关键洞察 —— 多样性 ≠ 有效性,建议同时算:
def predictability_vs_threat(df):
"""可预测性 vs 威胁产出"""
res = []
for team, g in df.groupby("team"):
# 用熵衡量可预测性
ent = diversity_entropy(g["target_zone"])
# 威胁产出: 射门率、进球率
threat = (g["outcome"].isin(["shot","goal"])).mean()
res.append({"team":team, "entropy":ent, "threat_rate":threat})
return pd.DataFrame(res)
- 高熵 + 高威胁 = 战术灵活且致命(理想)
- 低熵 + 高威胁 = 套路固定但执行强(如经典近门柱)
- 高熵 + 低威胁 = 变化多但没质量
- 低熵 + 低威胁 = 单一且无效
可视化(可选)
import matplotlib.pyplot as plt
def plot_zone_heat(df, team):
g = df[df["team"]==team]
plt.hexbin(g["x_end"], g["y_end"], gridsize=15, cmap="Reds")
plt.title(f"{team} 定位球落点分布")
plt.colorbar(label="次数")
plt.show()
实操建议
- 样本量: 单队单赛事定位球通常 < 100 次,熵值波动大,建议跨赛季/多赛事合并。
- 区域定义要先统一: 落点用固定网格(如 6×6),否则无法对比。
- 区分定位球类型: 角球、任意球、界外球的战术逻辑完全不同,先分组再算。
- 结合视频: 熵只能告诉你"变没变",变的是不是主动设计需回看录像标签。
- 对手维度: 同一队伍的多样性会随对手变化,可加
opponent分组。
如果你告诉我具体场景(足球/篮球?数据来源是 StatsBomb、Opta 还是自采?),我可以把脚本改成直接适配你数据格式的版本。