本文目录导读:

这是一个很有意思的足球数据分析问题,我来用Python做一个完整的案例分析,探讨补时长短是否会影响进球概率。
分析思路
关键问题是:补时越长,进球概率是否越高? 这涉及几个层次:
- 补时阶段本身的进球率 vs 常规时间进球率
- 补时长度与补时内进球数的关系
- 需要控制混杂因素(如比分差距、球队实力)
Python 实现
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import statsmodels.api as sm
np.random.seed(42)
# ============================================================
# 1. 模拟数据(真实场景应替换为真实比赛数据)
# ============================================================
# 假设有 2000 场比赛,每场记录:补时分钟数、补时内进球数、比分差距等
n_matches = 2000
# 补时长度:1-8 分钟,少数更长(模拟真实分布)
added_time = np.random.choice(
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
size=n_matches,
p=[0.05, 0.10, 0.20, 0.25, 0.20, 0.10, 0.05, 0.03, 0.01, 0.01]
)
# 基础进球率:每分钟约 0.03 球(约每 33 分钟一球)
base_rate_per_min = 0.03
# 关键假设:补时内进球率略高于常规(体力下降+拼命进攻)
# 但这里我们用"真实"生成过程来检验能否检测到效应
extra_intensity = 1.15 # 补时强度系数
# 生成补时内进球数(泊松过程)
lam = added_time * base_rate_per_min * extra_intensity
goals_in_added = np.random.poisson(lam)
# 控制变量:比分差距(落后方更拼命)
score_diff = np.random.choice([0, 1, 2, 3], size=n_matches, p=[0.35, 0.35, 0.20, 0.10])
df = pd.DataFrame({
'added_time': added_time,
'goals': goals_in_added,
'score_diff': score_diff
})
print("数据概览:")
print(df.describe())
print(f"\n补时内总进球: {df['goals'].sum()}, 场均: {df['goals'].mean():.3f}")
# ============================================================
# 2. 描述性分析:不同补时长度下的进球率
# ============================================================
print("\n" + "="*60)
print("不同补时长度下的场均进球数:")
print("="*60)
grouped = df.groupby('added_time')['goals'].agg(['count', 'mean', 'sum'])
print(grouped)
# 关键指标:每分钟进球率(消除补时长度本身的影响)
df['goals_per_min'] = df['goals'] / df['added_time']
print("\n不同补时长度下的每分钟进球率:")
print(df.groupby('added_time')['goals_per_min'].mean())
# ============================================================
# 3. 统计检验
# ============================================================
print("\n" + "="*60)
print("统计检验")
print("="*60)
# 检验1:补时长度与进球数的相关性
corr, p_corr = stats.pearsonr(df['added_time'], df['goals'])
print(f"补时长度 vs 进球数 相关系数: {corr:.4f}, p值: {p_corr:.4f}")
# 检验2:补时长度与每分钟进球率的关系(核心问题)
corr_rate, p_rate = stats.pearsonr(df['added_time'], df['goals_per_min'])
print(f"补时长度 vs 每分钟进球率 相关系数: {corr_rate:.4f}, p值: {p_rate:.4f}")
# 检验3:按补时长短分组,比较进球数
short = df[df['added_time'] <= 3]['goals']
long_ = df[df['added_time'] >= 6]['goals']
t_stat, p_t = stats.ttest_ind(short, long_)
print(f"\n短补时(≤3分钟) vs 长补时(≥6分钟) 进球数 t检验: t={t_stat:.3f}, p={p_t:.4f}")
# ============================================================
# 4. 泊松回归(控制混杂因素)
# ============================================================
print("\n" + "="*60)
print("泊松回归:控制比分差距后,补时长度对进球数的影响")
print("="*60)
# 注意:这里需要用 offset 因为补时越长,暴露时间越长
# 但我们想检验的是"单位时间进球率"是否随补时变化
# 两种建模方式:
# 模型A:直接预测进球数(需含 offset=log(added_time) 检验率是否恒定)
X = sm.add_constant(df[['added_time', 'score_diff']])
model_a = sm.GLM(df['goals'], X, family=sm.families.Poisson()).fit()
print("\n模型A:进球数 ~ 补时长度 + 比分差距")
print(model_a.summary().tables[1])
# 模型B:offset 模型,检验单位时间进球率是否随补时变化
# added_time 系数不显著 → 单位时间进球率不随补时长度变化
X_b = sm.add_constant(df[['added_time', 'score_diff']])
model_b = sm.GLM(df['goals'], X_b, family=sm.families.Poisson(),
offset=np.log(df['added_time'])).fit()
print("\n模型B:进球数 ~ 补时长度 + 比分差距 + offset(log补时)")
print("(added_time 系数检验:单位时间进球率是否随补时长度变化)")
print(model_b.summary().tables[1])
# ============================================================
# 5. 可视化
# ============================================================
fig, axes = plt.subplots(1, 3, figsize=(16, 4.5))
# 图1:补时长度分布与进球
ax = axes[0]
stats_by_time = df.groupby('added_time')['goals'].agg(['mean', 'sem'])
ax.errorbar(stats_by_time.index, stats_by_time['mean'],
yerr=stats_by_time['sem'], marker='o', capsize=3, color='#2c7fb8')
ax.set_xlabel('补时长度(分钟)')
ax.set_ylabel('补时内场均进球')
ax.set_title('补时长度 vs 场均进球数')
ax.grid(alpha=0.3)
# 图2:每分钟进球率(关键图)
ax = axes[1]
rate_by_time = df.groupby('added_time')['goals_per_min'].agg(['mean', 'sem'])
ax.errorbar(rate_by_time.index, rate_by_time['mean'],
yerr=rate_by_time['sem'], marker='s', capsize=3, color='#d95f0e')
ax.axhline(y=base_rate_per_min, color='gray', linestyle='--', label='常规时间基准')
ax.set_xlabel('补时长度(分钟)')
ax.set_ylabel('每分钟进球率')
ax.set_title('补时长度 vs 每分钟进球率\n(关键:是否随补时变化?)')
ax.legend()
ax.grid(alpha=0.3)
# 图3:不同比分差距下的关系(交互效应)
ax = axes[2]
for diff in [0, 1, 2, 3]:
sub = df[df['score_diff'] == diff]
m = sub.groupby('added_time')['goals'].mean()
ax.plot(m.index, m.values, marker='o', label=f'分差={diff}')
ax.set_xlabel('补时长度(分钟)')
ax.set_ylabel('场均进球')
ax.set_title('按比分差距分层的补时长度效应')
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.savefig('added_time_analysis.png', dpi=100, bbox_inches='tight')
plt.show()
# ============================================================
# 6. 效应量估计
# ============================================================
print("\n" + "="*60)
print("效应量估计(泊松回归)")
print("="*60)
coef = model_b.params['added_time']
print(f"补时长度每增加1分钟,单位时间进球率的变化: {(np.exp(coef)-1)*100:.2f}%")
print(f"(若接近0且不显著 → 补时长度不影响单位时间进球率)")
# 补时的"边际"效应:每多1分钟补时,预期多进多少球
# 若单位进球率恒定 = λ,则 E[进球] = λ * 补时
# 但若检验发现率不变,则补时越长,进球总数越多(仅因暴露时间更长)
核心结论(基于分析框架)
回答你的问题:补时长短会影响"总进球概率",但不一定影响"单位时间进球率"
需要区分两个问题:
| 问题 | 答案 | 原因 |
|---|---|---|
| 补时更长 → 补时内总进球更多? | 是 | 暴露时间更长,即使每分钟进球率不变,总进球数也线性增加 |
| 补时更长 → 每分钟进球率更高? | 需要数据检验 | 这才是"补时长度是否改变进球强度"的真正问题 |
真实世界的混杂因素(必须控制)
- 比分差距:落后方会拼命进攻 → 补时长通常与胶着/落后局面相关,而这类比赛本就进球多
- 比赛强度:强强对话可能补时更长
- 选择偏差:补时决定本身可能受比赛进程影响(VAR、换人多的比赛补时长且进球多)
关键洞察
补时长度与进球之间是"内生"关系,直接比较长短补时的进球率会产生偏误,正确做法是:
- 用 offset 泊松回归(
offset=log(补时分钟))检验单位时间进球率是否随补时变化- 或用 每分钟进球率 作为因变量
- 控制比分差距、球队实力等混杂变量
如果要用真实数据
建议数据源:football-data.co.uk、StatsBomb、FBref,记录每场补时分钟数、补时内进球、比分状态等,然后跑上面的回归,历史上英超补时改革(2023-24 赛季平均补时从 7 分钟增至 ~11 分钟)是天然的准实验,可以用 断点回归 / 双重差分 估计补时延长的因果效应。
需要我用真实示例数据结构完整演示,或补上 DID/断点回归的设计吗?