综合Python案例,射门质量与xG差异原因?

wen python案例 2

本文目录导读:

综合Python案例,射门质量与xG差异原因?

  1. 案例目标
  2. 所需Python库
  3. 第一步:构建模拟射门数据集
  4. 第二步:计算xG(预期进球)
  5. 第三步:定义“射门质量”指标
  6. 第四步:计算“差异指标”(核心分析)
  7. 第五步:数据可视化(找出差异原因)
  8. 第六步:深入分析差异原因
  9. 最终结论与业务解读
  10. 如何改进这个分析?

这是一个非常专业的足球数据分析问题,要分析“射门质量”与“预期进球(xG)”之间的差异原因,核心在于理解:xG衡量的是“射门前那一刻”的得分概率,而射门质量衡量的是“射门执行”的好坏。

一个高xG但低质量的射门(比如打飞)意味着球员浪费了机会;一个低xG但高质量的射门(比如世界波)意味着球员创造了奇迹。

下面我将提供一个完整的Python分析案例,通过模拟和真实数据逻辑(如果数据可用)来定量分析这种差异。

案例目标

  1. 构建xG模型(基于射门位置)。
  2. 计算“射门质量分数”(基于射门角度、射正与否、射门部位)。
  3. 找出哪些射门的 实际结果(进球/射正)与 xG预测 严重不符。
  4. 可视化分析差异原因(远射世界波 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())

第四步:计算“差异指标”(核心分析)

这是最关键的部分,我们计算两个指标:

  1. xG - 实际进球 (xG_diff):衡量机会浪费(正数)或超常发挥(负数)。
  2. 质量 - 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案例,我们可以得出以下关键结论:

  1. 射正率是最大变量

    • 高xG(如门前5码)但射偏 → 巨大负差异(浪费机会)。
    • 低xG(如30码远射)但射正且角度刁钻 → 世界波(正差异)。
  2. 防守压力是关键调节变量

    • 相同xG下,压力更大时射门质量更难保证,但一旦进球就是“高质量”。
    • 无压力下的高xG射门未进,通常是最大的“失分点”(需要重点训练)。
  3. 射门动作复杂性

    • 凌空抽射、倒钩等复杂动作,虽然xG低(因为位置差或角度小),但如果执行完美(高质量),进球概率远高于xG预测。
    • 头球攻门,xG模型通常低估(因为模型多基于脚射门数据)。
  4. 模型局限性

    • 简单的xG模型(如Logistic回归)只考虑位置等静态特征,无法捕捉“射门瞬间的执行质量”。
    • 真实业务中,需要引入射门速度触球部位精度门将位置等特征来缩小差异。

如何改进这个分析?

  1. 采用更真实的xG模型:使用XGBoost或神经网络,加入更多特征(如助攻类型、防守阵型、球员惯用脚等)。
  2. 引入时序数据:分析“射门前调整时间”。
  3. 球员个体差异:有些球员天生“逆xG”(如梅西在小角度进球多),有些球员稳定“低于xG”。
  4. 门将影响:加入门将的扑救能力(PSxG - 预期失球扑出数)来更精确分析射门质量。

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