Exercises: Strength of Schedule


Exercise 1: Basic SOS Calculation

Team A played the following opponents with these records: - Team X: 10-7 - Team Y: 8-9 - Team Z: 12-5 - Team W: 5-12

Calculate Team A's simple strength of schedule (opponent average win percentage).


Exercise 2: SOS Comparison

Two teams finished with identical 11-6 records: - Team A's SOS: 0.535 - Team B's SOS: 0.465

a) Which team likely had the harder path to 11 wins? b) Estimate the difference in "true quality" between these teams. c) If they played on a neutral field, who would be favored?


Exercise 3: Adjusted Wins

Team C finished 9-8 with an SOS of 0.540. League average SOS is 0.500.

Using the adjustment formula:

adjusted_wins = actual_wins + (SOS - 0.5) × 16 × games / 100

Calculate Team C's SOS-adjusted win total.


Exercise 4: Division Impact

Division X has the following teams and records (including games vs each other): - Team 1: 12-5 - Team 2: 10-7 - Team 3: 8-9 - Team 4: 4-13

Each team plays division opponents twice (6 games total).

a) Calculate the average win percentage of Division X teams. b) How does this affect the SOS of teams in weak divisions vs strong divisions?


Exercise 5: Implementing Simple SOS

Write a function to calculate simple SOS:

def calculate_simple_sos(team: str, games: pd.DataFrame) -> float:
    """
    Calculate simple strength of schedule.

    Args:
        team: Team abbreviation
        games: DataFrame with home_team, away_team, home_score, away_score

    Returns:
        SOS as opponent average win percentage
    """
    # Your code here
    pass

Test with sample data where Team A played: - vs Team B (B is 8-5) - vs Team C (C is 4-9) - vs Team D (D is 10-3)


Exercise 6: Head-to-Head Exclusion

When calculating SOS, we often exclude head-to-head games to reduce circularity.

Given: - Team A beat Team B - Team B's full record: 7-10 - Team B's record excluding games vs Team A: 7-9

a) What is Team B's win percentage including the A game? b) What is Team B's win percentage excluding the A game? c) Why is the excluded version more appropriate for SOS?


Exercise 7: Second-Order SOS

Team A's opponents have these individual SOS values: - Opponent 1: SOS = 0.480 - Opponent 2: SOS = 0.520 - Opponent 3: SOS = 0.510 - Opponent 4: SOS = 0.490

Team A's first-order SOS (opponent win %) is 0.500.

Using the formula: Combined = 0.67 × First-Order + 0.33 × Second-Order

Calculate Team A's combined SOS.


Exercise 8: Future SOS Analysis

Team X is 7-4 after Week 11. Their remaining opponents have these power ratings (points above average): - Week 12: +5.2 - Week 13: -2.1 - Week 14: +3.8 - Week 15: +6.5 - Week 16: -0.5 - Week 17: +2.0

a) Calculate Team X's remaining strength of schedule (average opponent rating). b) If Team X has a power rating of +3.0, estimate their expected remaining wins.


Exercise 9: Power Rating SOS

Implement a function to calculate SOS using power ratings:

def calculate_rating_sos(team: str, games: pd.DataFrame,
                         power_ratings: Dict[str, float]) -> Dict:
    """
    Calculate SOS using power ratings instead of win percentage.

    Args:
        team: Team abbreviation
        games: Schedule DataFrame
        power_ratings: Dict of team -> rating (points above average)

    Returns:
        Dict with sos_rating, hardest_opponent, easiest_opponent
    """
    # Your code here
    pass

Exercise 10: Pythagorean SOS

Calculate Pythagorean expected win percentage for these teams: - Team A: 400 points scored, 320 points allowed - Team B: 350 points scored, 380 points allowed - Team C: 420 points scored, 400 points allowed

Use the formula: Pyth = PF^2.37 / (PF^2.37 + PA^2.37)

If you played all three teams, what would your Pythagorean SOS be?


Exercise 11: SOS and Playoff Projections

Three teams are competing for two playoff spots with 4 games remaining:

Team Current Record Remaining SOS
Team A 8-5 0.580
Team B 8-5 0.420
Team C 7-6 0.500

Assuming teams win at a rate proportional to their record vs opponent SOS, project final standings.


Exercise 12: Adjusting EPA for Opponents

Team X has an offensive EPA/play of +0.12 against these opponents: - Game 1 vs defense ranked #5 (allows -0.08 EPA/play) - Game 2 vs defense ranked #28 (allows +0.15 EPA/play) - Game 3 vs defense ranked #15 (allows +0.02 EPA/play)

Calculate Team X's opponent-adjusted EPA/play.


Exercise 13: Building an SOS Calculator Class

Create a class that provides multiple SOS calculations:

class SOSCalculator:
    def __init__(self, games: pd.DataFrame):
        """Initialize with game data."""
        pass

    def simple_sos(self, team: str) -> float:
        """Calculate simple opponent win %."""
        pass

    def second_order_sos(self, team: str) -> float:
        """Calculate second-order SOS."""
        pass

    def rating_sos(self, team: str) -> float:
        """Calculate power rating-based SOS."""
        pass

    def rank_all_teams(self) -> pd.DataFrame:
        """Rank all teams by SOS."""
        pass

Exercise 14: Division-Weighted SOS

Given that division games occur twice while non-division games occur once, implement a weighted SOS:

def division_weighted_sos(team: str, games: pd.DataFrame,
                          divisions: Dict[str, List[str]]) -> float:
    """
    Calculate SOS with division games weighted 1.5x.
    """
    # Your code here
    pass

Exercise 15: Circularity Detection

Consider this round-robin result: - A beat B - B beat C - C beat A - D lost to all

Calculate each team's SOS excluding head-to-head games. Does this resolve the circularity?


Exercise 16: Colley Matrix Implementation

Implement the Colley Matrix method for team ratings:

def colley_ratings(games: pd.DataFrame) -> Dict[str, float]:
    """
    Calculate team ratings using Colley Matrix method.

    Matrix equation: Cr = b
    Where:
    - C[i,i] = 2 + games_played
    - C[i,j] = -games_vs_opponent_j
    - b[i] = 1 + (wins - losses) / 2
    """
    # Your code here
    pass

Exercise 17: SOS Volatility Analysis

Calculate year-over-year SOS volatility for a team:

Given Team A's SOS by year: - 2019: 0.485 - 2020: 0.520 - 2021: 0.468 - 2022: 0.545 - 2023: 0.502

a) Calculate the mean SOS b) Calculate the standard deviation c) Which year was most difficult? Easiest?


Exercise 18: Draft Position Tiebreaker

Four teams finished 6-11. Their SOS values are: - Team W: 0.512 - Team X: 0.498 - Team Y: 0.505 - Team Z: 0.488

Determine the draft order (lower SOS = earlier pick for same record).


Exercise 19: Prediction Model Integration

Build a game predictor that incorporates SOS:

def predict_game(home_team: str, away_team: str,
                 ratings: Dict[str, float],
                 past_sos: Dict[str, float],
                 hfa: float = 2.5) -> Dict:
    """
    Predict game outcome with SOS adjustment.

    Returns:
        Dict with spread, win_probability, sos_impact
    """
    # Adjust ratings based on SOS
    # A team with easy past SOS may be overrated
    # Your code here
    pass

Exercise 20: Complete SOS Report

Create a comprehensive SOS report function:

def generate_sos_report(team: str, games: pd.DataFrame,
                        current_week: int) -> Dict:
    """
    Generate complete SOS analysis including:
    - Past SOS (multiple methods)
    - Future SOS
    - Division breakdown
    - Home/away breakdown
    - Ranking among all teams
    - Difficulty trend
    """
    # Your code here
    pass

Test with real NFL data for a specific team and week.


Bonus Challenge: SOS Simulation

Write a simulation that: 1. Generates random NFL schedules 2. Assigns random team strengths 3. Simulates season results 4. Calculates SOS for each team 5. Compares different SOS methods to "true" opponent strength 6. Determines which method best captures actual opponent quality

def simulate_and_compare_sos_methods(n_simulations: int = 1000) -> pd.DataFrame:
    """
    Simulate seasons and compare SOS calculation methods.

    Returns DataFrame comparing method accuracy.
    """
    # Your code here
    pass

Submission Guidelines

For coding exercises: 1. Include all necessary imports 2. Add docstrings explaining your approach 3. Test with the provided sample data 4. Include example output

For conceptual exercises: 1. Show all calculations 2. Explain your reasoning 3. Discuss any assumptions made