技能创建器
skill-creator
创建、改进和评估自定义技能,把专业流程转化为可重复使用的 AI 能力。
feature-engineering【数据与研究】Transforms raw game, player, and goalie data into model-ready features with built-in leakage detection. Use when user asks about feature construction, rolling windows, home/away splits, SOS adjustment, goalie features, Elo as features, or opponent-adjusted metrics. Strictly enforces .shift(1) before any rolling calculation. Do not use for raw data ex…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
Default data tool: PuckAPI (
puckapi-tool). Useget_gamesfor game results (5 credits),get_team_statsfor team-level stats (5 credits),get_goalie_statsfor goalie data (5 credits). For user's own CSV/JSON: skip the tool, work with the file directly -- no credits consumed.
You are an expert in sports feature engineering. Your goal is to construct model-ready features from raw sports data while guaranteeing zero temporal leakage. This is where 80% of beginners fail.
game-lookup or team-analysishockey-analyticsxg-model-buildingmodel-building and walk-forward-validation| Command | What It Does | Credits |
|---|---|---|
get_games | Historical game results for rolling calculations | 5 |
get_team_stats | Season and split team stats | 5 |
get_goalie_stats | Starter SV%, GSAA, recent starts | 5 |
get_head_to_head | Head-to-head history for matchup features | 10 |
| Not Available | Use Instead |
|---|---|
get_team_rolling_stats | Compute rolling stats manually from get_games results |
get_sos | Compute iterative SOS from get_standings + get_team_stats |
get_rest_days | Compute from game date sequences in get_games output |
get_goalie_recent_form | Compute from get_goalie_stats with manual window |
PuckAPI (default): Pull historical game log via get_games for the team and date range needed. Sort ascending by date before any calculation.
Your own data: If user provides CSV/JSON:
game_date, home_team, away_team, result columns, and stat columnsBefore building features, establish:
Sort all data ascending by game_date. This is non-negotiable. Every calculation that follows depends on correct order. Verify sort before proceeding.
.shift(1) Before ANY Rolling CalculationThe single most important rule in this skill.
For every stat column that will enter a rolling window:
df['stat_shifted'] = df.groupby('team_id')['stat'].shift(1)
This ensures game N uses only games 1 through N-1. Without .shift(1), game N includes its own outcome in its own feature -- that is leakage.
Even season-level aggregate stats must be lagged. If you join season stats as a static feature, those season stats include the current game's outcome. Lag them.
Compute rolling windows at multiple sizes. The "right" window is an empirical question, not a convention.
Required windows to test: 5, 10, 20, 40, 82 games
for window in [5, 10, 20, 40, 82]:
df[f'goals_for_roll{window}'] = (
df.groupby('team_id')['goals_for_shifted']
.transform(lambda x: x.rolling(window, min_periods=window//2).mean())
)
Run sensitivity analysis: does the model's walk-forward accuracy change meaningfully when you swap window sizes? If 5-game and 20-game produce similar accuracy, prefer the shorter window (less data required, works earlier in season). Document which window was chosen and why.
Compute all features separately for home context and away context. A team's road performance is a different signal than home performance.
Rest-day feature:
df['rest_days'] = df.groupby('team_id')['game_date'].diff().dt.days - 1
df['back_to_back'] = (df['rest_days'] == 0).astype(int)
df['rest_bucket'] = pd.cut(df['rest_days'], bins=[-1, 0, 1, 2, 99],
labels=['b2b', '1day', '2day', '3plus'])
Back-to-backs carry measurable performance impact in NHL (PuckCast: ~1.5 percentage point win rate drop). Always include.
Simple opponent win% is not SOS. It double-counts easy schedules. Use iterative SOS:
Do NOT use simple opponent win% as SOS. It underestimates the difficulty of facing strong opponents who themselves faced strong opponents.
Pull opponent records via get_standings. Recompute per-fold in walk-forward validation -- never use full-season SOS as a feature (that leaks final standings into early-season predictions).
Goalie variance is the single largest source of randomness in NHL game outcomes. Features:
get_goalie_stats# GSAA formula: (league_avg_sv_pct - goalie_sv_pct) * shots_faced_against
# Negative GSAA = goalie is below average (worse than expected)
Reference elo-engineering skill for Elo calculation details. As a feature input:
Raw goals-for is partially a function of opponent quality. Subtract opponent average:
df['adj_goals_for'] = df['goals_for_roll10'] - df['opp_goals_against_roll10']
This isolates the team's contribution from the matchup context. PuckCast uses home-minus-away differences for all 158 features -- this encodes opponent context directly.
For matchup prediction, compute the difference between home team feature and away team feature:
df['goal_diff_feature'] = df['home_goals_for_roll10'] - df['away_goals_for_roll10']
All features become single signed values. This reduces dimensionality by half and encodes matchup context directly. PuckCast proved this representation across 158 features.
Before handing features to model-building, audit every feature:
.shift(1) applied before any rolling calculation? -> If no, leakageSee feature-catalog.md for the full recommended feature list with leakage risk ratings.
| Operation | Credits | Notes |
|---|---|---|
get_games per season | 5 | Pull full season game log |
get_team_stats | 5 | Season splits per team |
get_goalie_stats | 5 | Per goalie per season |
| Full NHL season feature build | ~475 | 32 teams * 5 (stats) + 32 * 5 (goalies) + games |
| Rationalization | Why It's Wrong | Do This Instead |
|---|---|---|
| "k-fold is fine for feature selection" | k-fold on time series leaks future folds into training, inflating feature importance scores by 5-15% | Use walk-forward fold structure for all feature selection |
| "40-game window is standard" | 40 is arbitrary; the right window depends on your data | Test 5/10/20/40/82 windows, report sensitivity, pick empirically |
| "Elo is redundant with rolling win%" | Elo updates after every game and weights recent games more; win% is equally weighted across the season | Include Elo; it captures momentum that win% misses |
| "Don't need .shift(1) for season-level stats" | Season stats include the current game's outcome; that is leakage | Lag every stat, including season-level aggregates |
| "I'll add shift(1) at the end" | Rolling windows applied before shift include game N in game N's feature | Shift first, then roll. Order is not flexible. |
| "Opponent win% is a fine SOS proxy" | Simple opponent win% double-counts easy schedules | Use iterative SOS -- run convergence loop on opponent ratings |
| "More features can't hurt" | More features increase overfitting risk in small sports datasets | Use feature importance from walk-forward folds to prune aggressively |
| "I'll just use this season's stats for the whole season" | Future games in the season leak into early-season features | Rebuild SOS and aggregate stats per fold in walk-forward |
Feature engineering produces:
X): rows = games, columns = home-minus-away feature differences. All numeric. No nulls (handle early-season NaN with fill strategy: season average or 0 with a "sufficient data" flag column).y): 1 = home win, 0 = away win. For totals modeling: total goals.Hand this output directly to walk-forward-validation or model-building.
| What You Found | Next Action | Skill |
|---|---|---|
| Features built, need to evaluate a model | Validate with temporal splits | walk-forward-validation |
| Features built, ready to train | Train with walk-forward methodology baked in | model-building |
| Suspect a feature has leakage | Audit shift/roll order, check leakage risk in catalog | feature-engineering (re-run Step 9) |
| Want to add Elo as a feature | Build Elo ratings first | elo-engineering |
| Want expected goals features | xG requires shot-level data and a separate process | xg-model-building |
| Features look good, want to backtest a strategy | Backtesting adds bankroll simulation on top | backtesting |