Python案例:量化主队球迷人数对比赛结果的影响
下面用一个完整的案例,演示如何用Python量化"主队球迷人数"对比赛结果(如主队胜率、净胜球)的影响。

问题定义
研究问题:主队球迷人数是否影响主队胜率/净胜球?
关键挑战:
- 球迷人数与球队实力相关(强队吸引更多球迷)→ 需要控制混淆变量
- 球迷人数本身是内生的 → 可用工具变量(如球场容量、天气)
数据构造(模拟示例)
import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt
import seaborn as sns
np.random.seed(42)
n = 1000
# 模拟:球队实力(1-10)
team_strength = np.random.uniform(1, 10, n)
away_strength = np.random.uniform(1, 10, n)
# 球迷人数:受球队实力 + 对手吸引力 + 随机因素影响
attendance = (20000 + 3000 * team_strength
+ 1500 * away_strength
+ np.random.normal(0, 4000, n)).clip(5000, 80000)
# 净胜球:受双方实力差 + 球迷人数(主场优势)影响
goal_diff = (0.3 * (team_strength - away_strength)
+ 0.00002 * attendance # 球迷效应
+ np.random.normal(0, 1, n))
df = pd.DataFrame({
'attendance': attendance,
'team_strength': team_strength,
'away_strength': away_strength,
'goal_diff': goal_diff
})
df['win'] = (df['goal_diff'] > 0).astype(int)
df.head()
基础分析
相关性可视化
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
sns.regplot(data=df, x='attendance', y='goal_diff', ax=axes[0],
scatter_kws={'alpha':0.3})
axes[0].set_title('球迷人数 vs 净胜球')
sns.boxplot(data=df, x='win', y='attendance', ax=axes[1])
axes[1].set_title('胜/负场次的球迷人数分布')
plt.tight_layout()
plt.show()
简单回归(有偏)
model_simple = smf.ols('goal_diff ~ attendance', data=df).fit()
print(model_simple.summary().tables[1])
⚠️ 这个系数是有偏的,因为没控制球队实力。
控制混淆变量
# 加入双方实力作为控制变量
model_ctrl = smf.ols(
'goal_diff ~ attendance + team_strength + away_strength',
data=df
).fit()
print(model_ctrl.summary().tables[1])
# 球迷人数的边际效应
print(f"每增加1名球迷,净胜球变化: {model_ctrl.params['attendance']:.8f}")
print(f"每增加1万名球迷,净胜球变化: {model_ctrl.params['attendance']*10000:.4f}")
量化"效果大小"
# 用标准化系数比较不同变量影响
from scipy import stats
df_std = df[['goal_diff','attendance','team_strength','away_strength']].apply(
stats.zscore)
model_std = smf.ols(
'goal_diff ~ attendance + team_strength + away_strength',
data=df_std
).fit()
print(model_std.params)
解释:若 attendance 标准化系数为 0.15,意味着球迷人数每增加1个标准差,净胜球增加 0.15 个标准差。
进阶:工具变量法(IV / 2SLS)
因为球迷人数是内生的(可能反过来受球队表现影响),用球场容量作为工具变量:
from linearmodels.iv import IV2SLS
# 假设球场容量是外生的工具变量
df['stadium_capacity'] = (df['attendance'] +
np.random.normal(0, 3000, n)).clip(10000, 90000)
iv_model = IV2SLS.from_formula(
'goal_diff ~ 1 + team_strength + away_strength + [attendance ~ stadium_capacity]',
data=df
).fit()
print(iv_model.summary)
非线性/异质性分析
# 分位数回归:看球迷人数对不同水平比赛的影响
import statsmodels.quantreg as qr
for q in [0.25, 0.5, 0.75]:
mod = qr.QuantReg('goal_diff ~ attendance + team_strength + away_strength',
data=df).fit(q=q)
print(f"Q{q}: attendance coef = {mod.params['attendance']:.8f}")
# 分组:强弱队球迷效应是否不同?
df['strong_team'] = (df['team_strength'] > df['team_strength'].median()).astype(int)
for g in [0, 1]:
sub = df[df['strong_team'] == g]
m = smf.ols('goal_diff ~ attendance + away_strength', data=sub).fit()
print(f"强队={g}: 球迷系数={m.params['attendance']:.8f}, p={m.pvalues['attendance']:.4f}")
结果汇总表
def report(model, name):
return {
'模型': name,
'attendance系数': model.params['attendance'],
'标准误': model.bse['attendance'],
'p值': model.pvalues['attendance'],
'R²': model.rsquared
}
results = pd.DataFrame([
report(model_simple, '仅球迷人数'),
report(model_ctrl, '控制球队实力'),
])
print(results)
关键结论与注意事项
| 事项 | 说明 |
|---|---|
| 内生性 | 球迷人数与球队实力高度相关,必须控制 |
| 因果识别 | 建议用 IV(球场容量、天气)或断点回归 |
| 效应量级 | 通常很小,1万球迷 ≈ 0.05~0.2 净胜球 |
| 异质性 | 弱队/关键比赛的球迷效应可能更强 |
| 数据来源 | Kaggle 有 European Soccer Database 可直接练习 |
推荐真实数据集
# Kaggle 命令
# kaggle datasets download -d hugomathien/soccer
# 用欧洲足球数据库做同样的分析
import sqlite3
conn = sqlite3.connect('database.sqlite')
matches = pd.read_sql("SELECT * FROM Match WHERE attendance IS NOT NULL", conn)
核心思路总结:
- 📊 描述性统计 + 可视化
- 🔧 多元回归控制混淆变量
- 🎯 工具变量法处理内生性
- 🔍 异质性/分位数分析
- 📈 将系数翻译成业务可解释的效应量
需要我针对真实数据集或因果推断方法(DID、RDD)展开进一步代码吗?