Sports Model Interactive
Player Projections
Build player stat projections using usage, matchup, and context features. The foundation for pricing player props and DFS projections.
๐ Select Player
Projection Weights
0 0.8
0 0.8
0 0.5
Total Weight
100%
Contextual Adjustments
-5 min 5 min
-5 5
-5 5
-2 3
200 250
๐ Projection for Jayson Tatum
Season Avg
26.5
Recent Form
28.4
Adjusted Projection
27.7
Line: 26.0 | OVER Prob
60.4%
Line: 26.0 | UNDER Prob
39.6%
Projection Distribution
5th Percentile
17.7
Median
27.6
95th Percentile
37.9
Recent Game Log
๐ง Projection Feature Categories
Usage Features
- โข Minutes per game
- โข Usage rate
- โข Field goal attempts
- โข Touches per game
- โข Shot distribution (paint/mid/3pt)
Matchup Features
- โข Opponent defensive rating
- โข Position-specific defense rank
- โข Opponent pace
- โข Recent opponent performance vs position
- โข Key defender assignments
Context Features
- โข Vegas total / spread (implied game script)
- โข Home / away
- โข Rest days
- โข Back-to-back indicator
- โข Teammate injuries
โ๏ธ Weighting Strategies
Recency Weighting
Recent games often more predictive. Common approaches:
- โข Linear decay: Weight = (N - game_age) / N
- โข Exponential: Weight = ฮป^game_age
- โข Rolling window: Last 10-20 games only
Regression to Mean
Extreme recent performance tends to regress. Apply shrinkage:
- โข Bayesian: Prior = season avg, update with recent
- โข James-Stein: Shrink toward group mean
- โข Empirical: Calibrate on historical variance
R Code Equivalent
# Player projection model
library(dplyr)
# Feature engineering
player_features <- player_games %>%
group_by(player_id) %>%
mutate(
# Rolling averages
season_avg = mean(points),
last5_avg = zoo::rollmean(points, k = 5, fill = NA, align = "right"),
# Recency weighting (exponential)
recency_weight = 0.9 ^ row_number(),
weighted_avg = sum(points * recency_weight) / sum(recency_weight)
) %>%
ungroup()
# Build projection
build_projection <- function(player, game_context) {
proj <- 0.4 * player$season_avg +
0.4 * player$last5_avg +
0.2 * (player$season_avg + game_context$matchup_adj)
# Apply adjustments
proj <- proj + game_context$minutes_adj * 0.8
proj <- proj + (game_context$vegas_total - 220) / 20 * 1.5
return(proj)
}
# Simulate distribution
simulate_player <- function(projection, std_dev, n = 10000) {
sims <- rnorm(n, mean = projection, sd = std_dev)
over_prob <- mean(sims > player$line)
return(list(projection = projection, over_prob = over_prob, sims = sims))
}โ Key Takeaways
- โข Blend season average with recent form
- โข Usage features are most predictive (minutes!)
- โข Vegas totals encode valuable information
- โข Apply regression to mean for hot/cold streaks
- โข Simulate distribution, not just point estimate
- โข Calibrate weights on out-of-sample data