python案例如何应对小联赛数据缺失问题?

wen python案例 2

小联赛数据缺失问题的应对方案(Python案例)

小联赛(如低级别联赛、冷门联赛、青年队比赛)普遍存在数据稀疏、缺失严重的问题,以下是系统性的应对思路和 Python 实现。

python案例如何应对小联赛数据缺失问题?


问题分类与应对策略

缺失类型 典型场景 应对策略
完全无数据 新升班马、青年队 相似球队迁移、先验估计
部分比赛缺失 数据源不全 多重插补、时间衰减
特征缺失 无角球/控球统计 降维建模、只用核心特征
样本量小 整个联赛场次少 贝叶斯收缩、分层模型

核心代码实现

数据诊断:先量化缺失情况

import pandas as pd
import numpy as np
import missingno as msno
import matplotlib.pyplot as plt
def diagnose_missing(df: pd.DataFrame):
    """诊断缺失比例,按球队/特征两个维度"""
    # 按特征缺失率
    feat_missing = df.isnull().mean().sort_values(ascending=False)
    print("=== 特征缺失率 ===")
    print(feat_missing[feat_missing > 0])
    # 按球队缺失率(小球队缺失更严重)
    team_missing = df.groupby('home_team').apply(
        lambda x: x.isnull().mean().mean()
    ).sort_values(ascending=False)
    print("\n=== 缺失最严重的球队 ===")
    print(team_missing.head(10))
    msno.matrix(df.sample(min(500, len(df))))
    plt.show()
    return feat_missing

基础填充:区分"结构性缺失"和"随机缺失"

def smart_impute(df: pd.DataFrame):
    """
    小联赛关键:不要盲目用均值填充,要按联赛/主客场分组
    """
    df = df.copy()
    # 1) 结构性缺失 → 填0(如该联赛根本没有角球统计)
    structural_cols = ['corners', 'yellow_cards']  # 举例
    for col in structural_cols:
        if col in df.columns:
            df[col] = df[col].fillna(0)
    # 2) 数值型 → 分联赛+主客场的中位数(比全局均值更合理)
    num_cols = df.select_dtypes(include=[np.number]).columns
    for col in num_cols:
        if df[col].isnull().any():
            df[col] = df.groupby(['league', 'is_home'])[col].transform(
                lambda s: s.fillna(s.median())
            )
            # 仍缺失则用联赛整体中位数兜底
            df[col] = df.groupby('league')[col].transform(
                lambda s: s.fillna(s.median())
            )
            df[col] = df[col].fillna(df[col].median())
    return df

进阶:IterativeImputer(利用相关性插补)

from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.ensemble import RandomForestRegressor
def iterative_impute(df, feature_cols):
    """
    利用特征间相关性(如射门数↔进球数)进行迭代插补
    适合部分字段缺失的小联赛
    """
    imputer = IterativeImputer(
        estimator=RandomForestRegressor(n_estimators=50, random_state=42),
        max_iter=10,
        random_state=42,
        sample_posterior=True,   # 小样本下推荐开启
    )
    df_imputed = df.copy()
    df_imputed[feature_cols] = imputer.fit_transform(df[feature_cols])
    return df_imputed

关键方法:贝叶斯收缩(解决小样本球队偏差)

这是应对小联赛最有效的方法,小球队样本少→估计不稳定→向全局均值"收缩"。

def bayesian_shrinkage(df, team_col, metric_col, prior_weight=5):
    """
    对每支球队的指标做经验贝叶斯收缩
    - prior_weight 越大,越向全局均值靠拢(小样本球队更保守)
    - 公式: shrunk = (n * team_mean + k * global_mean) / (n + k)
    - n=出场数, k=先验强度
    """
    global_mean = df[metric_col].mean()
    stats = df.groupby(team_col)[metric_col].agg(['mean', 'count'])
    stats['shrunk'] = (
        (stats['count'] * stats['mean'] + prior_weight * global_mean)
        / (stats['count'] + prior_weight)
    )
    return stats['shrunk'].to_dict()
# 用法
# shots_shrunk = bayesian_shrinkage(df, 'home_team', 'home_shots', prior_weight=5)

完全无数据球队:相似球队映射

新升班马没有任何顶级联赛数据时,用"相似球队"迁移先验。

from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import NearestNeighbors
def find_similar_teams(df_known, new_team_profile, n=3):
    """
    根据球队画像(联赛水平、进攻/防守风格等)找相似球队
    new_team_profile: dict, 如 {'league_tier': 2, 'avg_goals': 1.2, ...}
    """
    features = ['league_tier', 'avg_goals', 'avg_conceded', 'win_rate']
    X = df_known[features].values
    scaler = StandardScaler().fit(X)
    X_scaled = scaler.transform(X)
    new_vec = scaler.transform([[
        new_team_profile[f] for f in features
    ]])
    nn = NearestNeighbors(n_neighbors=n).fit(X_scaled)
    _, idx = nn.kneighbors(new_vec)
    return df_known.iloc[idx[0]][['team'] + features]

建模层面:分层贝叶斯(最强方案)

用 PyMC 建模,把小联赛球队参数视为从联赛分布中采样,天然处理小样本。

import pymc as pm
import arviz as az
def hierarchical_poisson_model(df):
    """
    分层泊松模型预测进球数
    - 球队攻防参数从联赛整体分布采样(自动收缩)
    - 小样本球队被拉回联赛均值
    """
    teams = pd.Categorical(df['home_team'].append(df['away_team']))
    team_idx_home = teams.codes[:len(df)]
    team_idx_away = teams.codes[len(df):]
    with pm.Model() as model:
        # 超先验
        mu_att = pm.Normal('mu_att', 0, 1)
        sigma_att = pm.HalfNormal('sigma_att', 1)
        mu_def = pm.Normal('mu_def', 0, 1)
        sigma_def = pm.HalfNormal('sigma_def', 1)
        # 球队参数(分层结构实现收缩)
        att = pm.Normal('att', mu_att, sigma_att, shape=len(teams))
        deff = pm.Normal('def', mu_def, sigma_def, shape=len(teams))
        home_adv = pm.Normal('home_adv', 0.2, 0.5)
        # 期望进球
        lam_home = pm.math.exp(att[team_idx_home] + deff[team_idx_away] + home_adv)
        lam_away = pm.math.exp(att[team_idx_away] + deff[team_idx_home])
        # 似然(用观测数据,缺失的主客场分别建模)
        pm.Poisson('home_goals', lam_home, observed=df['home_goals'].values)
        pm.Poisson('away_goals', lam_away, observed=df['away_goals'].values)
        trace = pm.sample(1000, tune=1000, return_inferencedata=True)
    return model, trace

特征工程兜底:只用"鲁棒特征"

# 小联赛推荐的核心特征(不依赖详细技术统计)
ROBUST_FEATURES = [
    'home_goals_avg_last5',      # 近5场进球(时间窗最短)
    'away_goals_avg_last5',
    'home_conceded_avg_last5',
    'away_conceded_avg_last5',
    'home_win_rate_season',      # 赛季胜率
    'rest_days_diff',            # 休息天数差
    'league_tier',               # 联赛层级
]
def build_robust_features(df, window=5):
    """滚动窗口 + 赛季累计,避免依赖易缺失的细粒度统计"""
    df = df.sort_values('date')
    for team_col, prefix in [('home_team', 'home'), ('away_team', 'away')]:
        df[f'{prefix}_goals_avg_last{window}'] = (
            df.groupby(team_col)['goals_for']
              .transform(lambda s: s.shift().rolling(window, min_periods=1).mean())
        )
        df[f'{prefix}_conceded_avg_last{window}'] = (
            df.groupby(team_col)['goals_against']
              .transform(lambda s: s.shift().rolling(window, min_periods=1).mean())
        )
    return df

完整流程建议

诊断缺失 → msno矩阵看清缺失模式
2. 区分缺失类型:
   ├─ 结构性缺失(该联赛不统计)→ 填0或删除该特征
   ├─ 随机缺失 → IterativeImputer / 分组中位数
   └─ 整支球队无数据 → 相似球队迁移 / 联赛先验
3. 小样本估计 → 贝叶斯收缩(先验权重=5~10)
4. 建模 → 分层贝叶斯 > 简单GradientBoosting
5. 特征 → 只用滚动窗口+赛季累计的鲁棒特征
6. 评估 → 用log loss/Brier,且对小样本球队单独看误差

实战建议(避坑)

  1. 不要用全局均值填充——小联赛各层级水平差异极大,会污染数据。
  2. 先验权重 k 要调:k 太大所有球队被抹平,太小失去收缩作用,建议 3~10,用交叉验证选。
  3. 监控数据质量:给每支球队打"数据可信度分"(样本量×字段完整度),低于阈值的不下注/不预测。
  4. 区分联赛层级:低级别联赛进球分布 ≠ 顶级联赛,先验均值要按层级分别算。
  5. 时间衰减:小联赛球队变动大,近半年数据权重应远高于两年前(可用指数衰减加权)。

需要我针对某个具体环节(比如分层贝叶斯完整可运行代码,或相似球队特征的自动构建)展开吗?

上一篇这个python案例如何看这次三线距离保持?

下一篇当前分类已是最新一篇

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