python案例如何量化主队球迷人数影响?

wen python案例 4

Python量化主队球迷人数对比赛结果的影响

下面我用一个完整案例,从数据构造→特征工程→建模→因果推断四个层面演示如何量化"主队球迷人数"对比赛结果的影响。

python案例如何量化主队球迷人数影响?


问题定义

目标:量化主队球迷人数(或上座率)对主队胜率/净胜球的影响。

难点:

  • 球迷人数不是随机分配的(强队球迷多),直接回归会有内生性偏误
  • 需要控制球队实力、对手强度等混淆变量
  • 最好用因果推断方法而非单纯相关分析

数据构造(示例)

import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf
np.random.seed(42)
n = 1000  # 1000场比赛
# 模拟:球队实力(1-10)
team_strength = np.random.uniform(1, 10, n)
opponent_strength = np.random.uniform(1, 10, n)
# 球场容量(固定)
stadium_capacity = np.random.choice([20000, 40000, 60000], n)
# 球迷人数:受球队实力影响(强队吸引人多)+ 随机波动
attendance = (
    stadium_capacity * 0.4
    + team_strength * 1500
    + np.random.normal(0, 3000, n)
)
attendance = np.clip(attendance, 0, stadium_capacity)
attendance_rate = attendance / stadium_capacity
# 比赛结果:受实力差 + 球迷人数影响(真实因果效应)
strength_diff = team_strength - opponent_strength
goal_diff = (
    1.2 * strength_diff
    + 0.8 * attendance_rate * 3   # 真实因果效应:上座率每+1,净胜球+0.8*3
    + np.random.normal(0, 1.5, n)
)
df = pd.DataFrame({
    'team_strength': team_strength,
    'opponent_strength': opponent_strength,
    'strength_diff': strength_diff,
    'attendance': attendance,
    'attendance_rate': attendance_rate,
    'stadium_capacity': stadium_capacity,
    'goal_diff': goal_diff,
    'win': (goal_diff > 0).astype(int),
})

方法一:朴素OLS(有偏)

model_naive = smf.ols('goal_diff ~ attendance_rate', data=df).fit()
print(model_naive.params)
# attendance_rate 系数会被高估,因为它吸收了球队实力的影响

问题:球迷多 → 球队强 → 更容易赢,OLS把"球队实力"的功劳算到了球迷头上。


方法二:控制混淆变量

model_ctrl = smf.ols(
    'goal_diff ~ attendance_rate + strength_diff + opponent_strength',
    data=df
).fit()
print(model_ctrl.summary())

加入实力差后,attendance_rate 的系数会更接近真实值,但仍可能有剩余混淆(如士气、天气等未观测因素)。


方法三:工具变量法(IV/2SLS)—— 推荐

思路:找一个只通过"球迷人数"影响比赛结果、不直接影响结果的变量。

经典IV:天气(下雨人少,但天气本身不影响球员实力)。

# 加入工具变量:天气(0晴 1雨)
df['rainy'] = np.random.binomial(1, 0.3, n)
# 雨天减少球迷
df['attendance_rate'] = np.clip(
    df['attendance_rate'] - 0.15 * df['rainy'], 0, 1
)
# 重新计算goal_diff(因果效应不变,但attendance被外生变化)
df['goal_diff'] = (
    1.2 * df['strength_diff']
    + 2.4 * df['attendance_rate']
    + np.random.normal(0, 1.5, n)
)
from linearmodels.iv import IV2SLS
iv_model = IV2SLS.from_formula(
    'goal_diff ~ 1 + strength_diff + [attendance_rate ~ rainy]',
    data=df
).fit()
print(iv_model.summary)

IV估计出的 attendance_rate 系数就是因果效应的无偏估计。


方法四:倾向得分匹配(PSM)

把"高上座率"与"低上座率"比赛在实力、对手等维度配对:

from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import NearestNeighbors
# 定义处理组:上座率是否高于中位数
median = df['attendance_rate'].median()
df['high_attendance'] = (df['attendance_rate'] > median).astype(int)
# 估计倾向得分
ps_model = LogisticRegression()
ps_model.fit(df[['strength_diff', 'opponent_strength']], df['high_attendance'])
df['ps'] = ps_model.predict_proba(df[['strength_diff', 'opponent_strength']])[:, 1]
# 最近邻匹配
treated = df[df['high_attendance'] == 1]
control = df[df['high_attendance'] == 0]
nn = NearestNeighbors(n_neighbors=1).fit(control[['ps']])
_, idx = nn.kneighbors(treated[['ps']])
matched_control = control.iloc[idx.flatten()]
# 计算ATT(处理组的平均处理效应)
att = treated['goal_diff'].mean() - matched_control['goal_diff'].mean()
print(f'ATT(高上座率的因果效应): {att:.3f}')

方法五:分位数回归看非线性

球迷人数可能存在边际递减(坐满后效果边际下降):

import statsmodels.quantreg as qr
for q in [0.25, 0.5, 0.75]:
    res = qr.quantreg('goal_diff ~ attendance_rate + strength_diff', df).fit(q=q)
    print(f'分位数 {q}: 系数 = {res.params["attendance_rate"]:.3f}')

实战注意事项

问题 解决思路
内生性(强队人多) IV、PSM、固定效应
非线性 加平方项、分位数回归、GAM
球队个体效应 面板固定效应 PanelOLS
数据来源 Transfermarkt、FBref、football-data.co.uk
真正随机 疫情期间空场/限流=天然实验(DiD)

DiD 思路(疫情空场):

# 处理组:疫情期比赛;对照组:疫情前
# goal_diff ~ treated + post + treated:post + controls
# treated:post 系数 = 球迷的真实因果效应

结论展示模板

控制球队实力、对手强度后,上座率每提高10个百分点,主队净胜球平均增加约 24 球(95% CI: [0.18, 0.30]),胜率提高约 6%,IV估计结果与PSM匹配结果一致,说明该效应稳健。


如果你需要,我可以针对真实数据集(如英超)写一个可直接运行的完整脚本,或者在 面板固定效应 或 DiD(疫情空场) 上进一步展开,需要哪一部分?

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