本文目录导读:

- 案例目标
- 所需Python库
- 第一步:构建模拟射门数据集
- 第二步:计算xG(预期进球)
- 第三步:定义“射门质量”指标
- 第四步:计算“差异指标”(核心分析)
- 第五步:数据可视化(找出差异原因)
- 第六步:深入分析差异原因
- 最终结论与业务解读
- 如何改进这个分析?
这是一个非常专业的足球数据分析问题,要分析“射门质量”与“预期进球(xG)”之间的差异原因,核心在于理解:xG衡量的是“射门前那一刻”的得分概率,而射门质量衡量的是“射门执行”的好坏。
一个高xG但低质量的射门(比如打飞)意味着球员浪费了机会;一个低xG但高质量的射门(比如世界波)意味着球员创造了奇迹。
下面我将提供一个完整的Python分析案例,通过模拟和真实数据逻辑(如果数据可用)来定量分析这种差异。
案例目标
- 构建xG模型(基于射门位置)。
- 计算“射门质量分数”(基于射门角度、射正与否、射门部位)。
- 找出哪些射门的 实际结果(进球/射正)与 xG预测 严重不符。
- 可视化分析差异原因(远射世界波 vs 门前空门打飞)。
所需Python库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import brier_score_loss, log_loss
import warnings
warnings.filterwarnings('ignore')
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
第一步:构建模拟射门数据集
我们将生成一组包含关键特征的射门数据。
# 设置随机种子以保证可复现性
np.random.seed(42)
n_shots = 1000
# 1. 基本位置特征 (影响xG)
distance_to_goal = np.random.uniform(1, 35, n_shots) # 距离球门距离(码)
angle_to_goal = np.random.uniform(0, 90, n_shots) # 射门角度(度)
# 2. 射门质量特征 (影响实际结果)
# 射正与否 (0=射偏/被封堵, 1=射正)
on_target = np.random.binomial(1, 0.7, n_shots)
# 射门部位 (0=脚, 1=头)
header = np.random.binomial(1, 0.2, n_shots)
# 射门动作类型 (0=普通, 1=凌空/倒钩, 2=点球)
shot_type = np.random.choice([0, 1, 2], n_shots, p=[0.7, 0.2, 0.1])
# 防守压力 (0=无压力, 1=有干扰, 2=贴身)
pressure = np.random.choice([0, 1, 2], n_shots, p=[0.3, 0.5, 0.2])
# 3. 实际结果 (进球)
# 概率随距离增加而下降,随角度增加而下降,随压力增加而下降
goal_prob_base = 0.3 * np.exp(-distance_to_goal / 15) * np.cos(np.radians(angle_to_goal)) * (1 - pressure*0.15)
goal_prob = np.clip(goal_prob_base, 0, 1)
goal = np.random.binomial(1, goal_prob, n_shots)
# 组装DataFrame
df_shots = pd.DataFrame({
'distance': distance_to_goal,
'angle': angle_to_goal,
'on_target': on_target,
'header': header,
'shot_type': shot_type,
'pressure': pressure,
'goal': goal
})
print("数据集预览:")
print(df_shots.head())
print(f"\n总射门数: {len(df_shots)}")
print(f"总进球数: {df_shots['goal'].sum()}")
第二步:计算xG(预期进球)
为了演示,我们使用一个简化的逻辑回归模型来预测xG,在实际项目中,你会使用更复杂的模型(如XGBoost)。
from sklearn.linear_model import LogisticRegression
# 特征工程:选择用于xG模型的特征
features = ['distance', 'angle', 'header', 'shot_type']
# 训练一个简单的xG模型
X = df_shots[features]
y = df_shots['goal']
# 使用logistic回归(实际中会用更复杂的模型)
xg_model = LogisticRegression(C=1.0, solver='liblinear')
xg_model.fit(X, y)
# 预测xG值(概率)
df_shots['xG'] = xg_model.predict_proba(X)[:, 1]
print("\nxG模型系数:")
for feature, coef in zip(features, xg_model.coef_[0]):
print(f" {feature}: {coef:.4f}")
print(f"\nxG统计描述:")
print(df_shots['xG'].describe())
第三步:定义“射门质量”指标
这是一个自定义指标,这里我们基于射门执行的关键因素:
- 射正:这是最重要的质量指标。
- 射门角度:更刁钻的角度(靠近立柱)质量更高。
- 防守压力:在压力下完成的射门质量体现。
# 计算射门质量分数 (0-1分)
df_shots['quality_score'] = (
# 射正 +0.5分
df_shots['on_target'] * 0.5 +
# 角度奖励 (选择刁钻角度,假设45-65度是理想角度)
(1 - np.abs(df_shots['angle'] - 55) / 55) * 0.3 +
# 压力惩罚 (在强压下射门质量更高,但这里定义为体现难度)
(1 - df_shots['pressure'] * 0.1) * 0.2
)
# 归一化到0-1区间
df_shots['quality_score'] = (df_shots['quality_score'] - df_shots['quality_score'].min()) / \
(df_shots['quality_score'].max() - df_shots['quality_score'].min())
print("射门质量分数统计:")
print(df_shots['quality_score'].describe())
第四步:计算“差异指标”(核心分析)
这是最关键的部分,我们计算两个指标:
- xG - 实际进球 (xG_diff):衡量机会浪费(正数)或超常发挥(负数)。
- 质量 - xG (quality_vs_xg):衡量射门执行质量是否配得上机会质量。
# 核心差异指标
df_shots['xG_diff'] = df_shots['xG'] - df_shots['goal'] # 正=浪费,负=超常
df_shots['quality_vs_xg'] = df_shots['quality_score'] - df_shots['xG'] # 正=射得好,负=射得差
# 分类:射门质量与xG的关系
def classify_shot(row):
if row['quality_vs_xg'] > 0.3:
return '高质量低xG (世界波)'
elif row['quality_vs_xg'] < -0.3:
return '低质量高xG (浪费机会)'
elif abs(row['xG_diff']) > 0.3:
return 'xG与实际不符'
else:
return '正常'
df_shots['classification'] = df_shots.apply(classify_shot, axis=1)
print("\n射门分类统计:")
print(df_shots['classification'].value_counts())
第五步:数据可视化(找出差异原因)
散点图:位置 vs 射门质量
plt.figure(figsize=(14, 6))
plt.subplot(1, 2, 1)
scatter = plt.scatter(
df_shots['distance'],
df_shots['angle'],
c=df_shots['quality_vs_xg'],
s=df_shots['xG'] * 300, # 点的大小代表xG
cmap='coolwarm',
alpha=0.6,
edgecolors='black',
linewidth=0.5
)
plt.colorbar(scatter, label='质量 vs xG')
plt.xlabel('距离球门 (码)')
plt.ylabel('射门角度 (度)')'射门位置 vs 质量与xG差异')
plt.grid(True, alpha=0.3)
# 添加分类标签
for cat in ['高质量低xG (世界波)', '低质量高xG (浪费机会)']:
sub = df_shots[df_shots['classification'] == cat]
plt.scatter(sub['distance'], sub['angle'],
s=50, marker='o', label=cat, alpha=0.7)
plt.legend()
plt.tight_layout()
plt.show()
柱状图:各类别的关键特征对比
# 按分类统计平均特征
comparison = df_shots.groupby('classification').agg({
'xG': 'mean',
'quality_score': 'mean',
'distance': 'mean',
'angle': 'mean',
'on_target': 'mean',
'pressure': 'mean',
'goal': 'mean'
}).round(3)
print("\n不同射门类别的平均特征对比:")
print(comparison)
# 可视化
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
metrics = ['xG', 'quality_score', 'distance', 'angle']s = ['平均xG', '平均射门质量', '平均距离', '平均角度']
for ax, metric, title in zip(axes.flatten(), metrics, titles):
comparison[metric].plot(kind='bar', ax=ax, color=['#3498db', '#e74c3c', '#2ecc71', '#f39c12'])
ax.set_title(title, fontsize=12)
ax.set_ylabel(metric)
ax.set_xlabel('射门分类')
ax.tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()
差异原因分析表
# 找出最极端的案例
worst_shots = df_shots.nlargest(10, 'xG_diff')[['distance', 'angle', 'xG', 'quality_score', 'goal', 'classification']]
best_shots = df_shots.nsmallest(10, 'xG_diff')[['distance', 'angle', 'xG', 'quality_score', 'goal', 'classification']]
print("\n=== 最浪费机会的10次射门(高xG未进球) ===")
print(worst_shots.to_string(index=False))
print("\n=== 最精彩的10次射门(低xG进球) ===")
print(best_shots.to_string(index=False))
第六步:深入分析差异原因
从上面的分析中,我们可以系统性地总结出射门质量与xG差异的5大原因:
# 自动化原因分析
def analyze_discrepancy(df):
reasons = {}
# 1. 射正率差异
high_xg_missed = df[(df['xG'] > 0.3) & (df['goal'] == 0)]
low_xg_scored = df[(df['xG'] < 0.1) & (df['goal'] == 1)]
reasons['射正率'] = {
'高xG未进球': f"{len(high_xg_missed)}次, 其中射正率: {high_xg_missed['on_target'].mean():.1%}",
'低xG已进球': f"{len(low_xg_scored)}次, 其中射正率: {low_xg_scored['on_target'].mean():.1%}"
}
# 2. 距离影响
reasons['距离影响'] = {
'平均距离差': f"{low_xg_scored['distance'].mean():.1f}码 vs {high_xg_missed['distance'].mean():.1f}码"
}
# 3. 防守压力
reasons['防守压力'] = {
'高xG未进球平均压力': f"{high_xg_missed['pressure'].mean():.2f}",
'低xG已进球平均压力': f"{low_xg_scored['pressure'].mean():.2f}"
}
# 4. 射门动作类型
reasons['动作类型'] = {
'高xG未进球': high_xg_missed['shot_type'].value_counts().to_dict(),
'低xG已进球': low_xg_scored['shot_type'].value_counts().to_dict()
}
return reasons
reasons = analyze_discrepancy(df_shots)
print("\n=== 射门质量与xG差异原因深度分析 ===")
for category, detail in reasons.items():
print(f"\n [{category}]")
for key, value in detail.items():
print(f" {key}: {value}")
最终结论与业务解读
通过这个Python案例,我们可以得出以下关键结论:
-
射正率是最大变量:
- 高xG(如门前5码)但射偏 → 巨大负差异(浪费机会)。
- 低xG(如30码远射)但射正且角度刁钻 → 世界波(正差异)。
-
防守压力是关键调节变量:
- 相同xG下,压力更大时射门质量更难保证,但一旦进球就是“高质量”。
- 无压力下的高xG射门未进,通常是最大的“失分点”(需要重点训练)。
-
射门动作复杂性:
- 凌空抽射、倒钩等复杂动作,虽然xG低(因为位置差或角度小),但如果执行完美(高质量),进球概率远高于xG预测。
- 头球攻门,xG模型通常低估(因为模型多基于脚射门数据)。
-
模型局限性:
- 简单的xG模型(如Logistic回归)只考虑位置等静态特征,无法捕捉“射门瞬间的执行质量”。
- 真实业务中,需要引入射门速度、触球部位精度、门将位置等特征来缩小差异。
如何改进这个分析?
- 采用更真实的xG模型:使用XGBoost或神经网络,加入更多特征(如助攻类型、防守阵型、球员惯用脚等)。
- 引入时序数据:分析“射门前调整时间”。
- 球员个体差异:有些球员天生“逆xG”(如梅西在小角度进球多),有些球员稳定“低于xG”。
- 门将影响:加入门将的扑救能力(PSxG - 预期失球扑出数)来更精确分析射门质量。