Chapter 19: Exercises - Player Performance Forecasting
Exercise Overview
These exercises build progressively from basic projection methods to complete player forecasting systems.
Level 1: Conceptual Foundation
Exercise 1.1: Regression to the Mean
Task: A quarterback throws for 9.2 yards per attempt in his first season with 350 attempts. The league average is 7.5 yards per attempt.
- If his true talent is league average, what would we expect from luck alone?
- Using a stability factor of 0.4, what is his projected Y/A for next season?
- How would your projection change if he had 600 attempts instead?
Exercise 1.2: Sample Size Effects
Task: Compare these two running backs:
| Player | Carries | Yards/Carry |
|---|---|---|
| RB A | 50 | 6.2 |
| RB B | 200 | 5.1 |
- Which player has the more reliable sample?
- If league average is 4.3 Y/C, project each player's "true" Y/C
- Explain why the higher per-carry average might project lower
Exercise 1.3: Marcel Weights
Task: A receiver has these three seasons:
| Year | Targets | Yards/Target |
|---|---|---|
| 2023 | 100 | 9.5 |
| 2022 | 80 | 8.2 |
| 2021 | 40 | 7.0 |
Using Marcel weights (5/4/3): 1. Calculate the weighted yards per target 2. Apply 15% regression to league average (8.0) 3. Project total yards for 90 targets next season
Level 2: Building Projection Models
Exercise 2.1: Regression to Mean Projector
Task: Implement a basic regression projector.
class SimpleProjector:
"""
Implement regression to the mean projection.
"""
def __init__(self, league_averages: Dict[str, float]):
"""
Parameters:
-----------
league_averages : Dict[str, float]
League average for each stat
"""
self.league_avg = league_averages
def calculate_reliability(self, sample_size: int,
stabilization_point: int) -> float:
"""
Calculate reliability weight.
reliability = sample_size / (sample_size + stabilization_point)
Parameters:
-----------
sample_size : int
Number of opportunities
stabilization_point : int
Point at which stat is 50% reliable
Returns:
--------
float : Reliability between 0 and 1
"""
# YOUR CODE HERE
pass
def project(self, observed_rate: float, sample_size: int,
stat_name: str, stabilization: int = 300) -> float:
"""
Project a rate statistic.
projected = reliability * observed + (1 - reliability) * league_avg
Returns:
--------
float : Projected rate
"""
# YOUR CODE HERE
pass
Exercise 2.2: Marcel Projector
Task: Implement Marcel-style projections.
class MarcelProjector:
"""
Marcel projection using multi-year weighted data.
"""
def __init__(self, league_averages: Dict[str, float],
weights: Tuple[float, float, float] = (5, 4, 3)):
self.league_avg = league_averages
self.weights = weights
def calculate_weighted_stat(self, seasons: List[Dict],
stat_name: str,
opp_name: str) -> Tuple[float, float]:
"""
Calculate weighted average of a stat.
Parameters:
-----------
seasons : List[Dict]
List of season data (most recent first)
stat_name : str
Name of rate stat
opp_name : str
Name of opportunity stat (attempts, targets)
Returns:
--------
Tuple[float, float] : (weighted_rate, total_weighted_opportunities)
"""
# YOUR CODE HERE
pass
def project(self, seasons: List[Dict],
regression_opportunities: int = 1200) -> Dict[str, float]:
"""
Generate Marcel projection.
1. Calculate weighted stats from past seasons
2. Determine reliability based on weighted opportunities
3. Regress toward league average
Returns:
--------
Dict[str, float] : Projected statistics
"""
# YOUR CODE HERE
pass
Exercise 2.3: Confidence Intervals
Task: Add uncertainty quantification to projections.
def calculate_projection_interval(projected_value: float,
sample_size: int,
n_seasons: int,
stat_variance: float) -> Tuple[float, float]:
"""
Calculate 90% confidence interval for projection.
Use the formula:
interval_width = stat_variance * sqrt(1/sample_size + 1/n_baseline)
Parameters:
-----------
projected_value : float
Central projection
sample_size : int
Historical opportunities
n_seasons : int
Number of seasons of data
stat_variance : float
Historical variance of this stat
Returns:
--------
Tuple[float, float] : (lower_bound, upper_bound) for 90% CI
"""
# YOUR CODE HERE
pass
Level 3: Position-Specific Models
Exercise 3.1: Quarterback Projector
Task: Build a complete QB projection model.
class QBProjector:
"""
Quarterback projection model.
"""
def __init__(self):
self.league_avg = {
'completion_pct': 0.62,
'yards_per_attempt': 7.5,
'td_rate': 0.045,
'int_rate': 0.025
}
# Stabilization points (attempts needed for 50% reliability)
self.stabilization = {
'completion_pct': 200,
'yards_per_attempt': 300,
'td_rate': 500,
'int_rate': 400
}
def project_efficiency(self, past_seasons: List[Dict]) -> Dict[str, float]:
"""
Project QB efficiency metrics.
Steps:
1. Weight past seasons (Marcel style)
2. Calculate reliability for each stat
3. Regress appropriately
4. Return projected rates
Returns:
--------
Dict[str, float] : Projected efficiency rates
"""
# YOUR CODE HERE
pass
def project_volume(self, efficiency: Dict[str, float],
games: int, dropbacks_per_game: float) -> Dict[str, int]:
"""
Convert efficiency to counting stats.
Returns:
--------
Dict[str, int] : Projected counting statistics
"""
# YOUR CODE HERE
pass
def full_projection(self, player_data: Dict) -> Dict:
"""
Generate complete QB projection.
Returns:
--------
Dict : Complete projection with rates, counts, and intervals
"""
# YOUR CODE HERE
pass
Exercise 3.2: Running Back Projector
Task: Build RB projection handling both rushing and receiving.
class RBProjector:
"""
Running back projection model.
Must handle:
- Rushing (yards per carry, TDs)
- Receiving (targets, catches, yards)
- Usage projection uncertainty
"""
def __init__(self):
# YOUR CODE: Define league averages and stabilization points
pass
def project_rushing(self, past_seasons: List[Dict]) -> Dict[str, float]:
"""Project rushing efficiency."""
# YOUR CODE HERE
pass
def project_receiving(self, past_seasons: List[Dict]) -> Dict[str, float]:
"""Project receiving efficiency."""
# YOUR CODE HERE
pass
def project_usage(self, past_seasons: List[Dict],
team_context: Dict = None) -> Dict[str, int]:
"""
Project carries and targets.
Consider:
- Historical usage
- Team context (committee vs bell cow)
- Age/injury history
"""
# YOUR CODE HERE
pass
def full_projection(self, player_data: Dict) -> Dict:
"""Generate complete RB projection."""
# YOUR CODE HERE
pass
Level 4: Advanced Techniques
Exercise 4.1: Aging Curves
Task: Implement position-specific aging adjustments.
class AgingModel:
"""
Model player aging by position.
"""
def __init__(self):
# Define aging curves
self.curves = {
'QB': {'peak_age': 28, 'decline_rate': 0.015},
'RB': {'peak_age': 25, 'decline_rate': 0.05},
'WR': {'peak_age': 27, 'decline_rate': 0.025},
'TE': {'peak_age': 28, 'decline_rate': 0.02}
}
def get_adjustment(self, position: str, current_age: int,
target_age: int) -> float:
"""
Calculate aging adjustment factor.
Before peak: Small improvement expected
At peak: Factor = 1.0
After peak: Decline by position-specific rate
Returns:
--------
float : Adjustment factor (multiply projection by this)
"""
# YOUR CODE HERE
pass
def project_multi_year(self, current_performance: float,
position: str, current_age: int,
years: int) -> pd.DataFrame:
"""
Project performance over multiple years.
Returns:
--------
pd.DataFrame : Year-by-year projected performance
"""
# YOUR CODE HERE
pass
Exercise 4.2: Comparable Players
Task: Find and use historical comparables.
class ComparablesProjector:
"""
Project using historical comparable players.
"""
def __init__(self, historical_db: pd.DataFrame):
"""
Parameters:
-----------
historical_db : pd.DataFrame
Historical player-seasons with outcomes
"""
self.historical = historical_db
self.scaler = StandardScaler()
self._fit_model()
def _fit_model(self):
"""Prepare the comparables model."""
# YOUR CODE: Fit a nearest neighbors model on historical data
pass
def find_comparables(self, player_profile: Dict,
n: int = 10) -> pd.DataFrame:
"""
Find n most similar historical players.
Parameters:
-----------
player_profile : Dict
Current player's statistics
n : int
Number of comparables
Returns:
--------
pd.DataFrame : Similar players with similarity scores
"""
# YOUR CODE HERE
pass
def project_from_comparables(self, player_profile: Dict,
target_season: int = 1) -> Dict:
"""
Project based on what similar players did.
Parameters:
-----------
player_profile : Dict
Current player statistics
target_season : int
Years ahead to project (1 = next season)
Returns:
--------
Dict : Projection based on comparable outcomes
"""
# YOUR CODE HERE
pass
Level 5: Complete System
Exercise 5.1: Integrated Projection System
Task: Build a complete player projection pipeline.
class PlayerProjectionSystem:
"""
Complete player projection system.
Features:
- Position-specific models
- Aging adjustments
- Comparable players
- Confidence intervals
- Uncertainty quantification
"""
def __init__(self, historical_data: pd.DataFrame):
"""Initialize all component models."""
# YOUR CODE: Initialize position projectors, aging model, comparables
pass
def project_player(self, player_data: Dict,
projection_years: int = 1) -> Dict:
"""
Generate complete projection for any player.
Returns:
--------
Dict with:
- central_projection: Point estimates
- confidence_intervals: 90% CIs
- comparable_players: Similar historical players
- risk_factors: Potential concerns
- upside_factors: Potential for outperformance
"""
# YOUR CODE HERE
pass
def batch_project(self, players: List[Dict]) -> pd.DataFrame:
"""Project multiple players."""
# YOUR CODE HERE
pass
def evaluate_projections(self, past_projections: pd.DataFrame,
actual_results: pd.DataFrame) -> Dict:
"""
Evaluate projection accuracy.
Returns:
--------
Dict with:
- mean_absolute_error
- correlation
- calibration (are 90% CIs correct?)
- bias (over/under projecting?)
"""
# YOUR CODE HERE
pass
Exercise 5.2: Fantasy Football Projector
Task: Create fantasy-specific projections.
class FantasyProjector:
"""
Fantasy football projection system.
Scoring settings:
- Passing: 0.04 pts/yard, 4 pts/TD, -2 pts/INT
- Rushing: 0.1 pts/yard, 6 pts/TD
- Receiving: 0.1 pts/yard, 6 pts/TD, 0.5 PPR
"""
def __init__(self, base_projector: PlayerProjectionSystem):
self.base = base_projector
self.scoring = {
'pass_yard': 0.04,
'pass_td': 4,
'interception': -2,
'rush_yard': 0.1,
'rush_td': 6,
'rec_yard': 0.1,
'rec_td': 6,
'reception': 0.5
}
def project_fantasy_points(self, player_data: Dict) -> Dict:
"""
Project fantasy points with variance.
Returns:
--------
Dict with:
- projected_points: Central estimate
- floor: 10th percentile outcome
- ceiling: 90th percentile outcome
- position_rank: Projected rank at position
- value_over_replacement: VORP
"""
# YOUR CODE HERE
pass
def generate_rankings(self, player_pool: List[Dict],
format_type: str = 'ppr') -> pd.DataFrame:
"""
Generate full fantasy rankings.
Returns:
--------
pd.DataFrame : Ranked players with projections
"""
# YOUR CODE HERE
pass
def auction_values(self, rankings: pd.DataFrame,
budget: int = 200,
roster_size: int = 15) -> pd.DataFrame:
"""
Calculate auction values from projections.
"""
# YOUR CODE HERE
pass
Bonus Challenge: Breakout Prediction
Task: Build a model to identify players likely to break out.
A "breakout" is when a player significantly exceeds prior performance. Build a classifier that predicts breakouts before they happen.
class BreakoutPredictor:
"""
Predict which players will break out.
Breakout defined as:
- 50%+ improvement in fantasy points
- Or moving from outside top 50 to inside top 20
Features to consider:
- Age (young players break out more)
- Usage trend (increasing targets/carries)
- Efficiency trend (improving rates)
- Opportunity (starter leaves, draft capital)
- Historical breakout rate by profile
"""
# YOUR IMPLEMENTATION HERE
pass
Submission Guidelines
For each exercise: 1. Include complete, runnable code 2. Add docstrings and comments 3. Provide test cases with expected outputs 4. Include sample output demonstrating functionality
For Level 4-5 exercises: - Include evaluation metrics - Document assumptions - Compare to baseline approaches - Provide visualization of results