6 min read

The NFL schedule is not created equal. Some teams face easier opponents, enjoy favorable bye week timing, or avoid back-to-back road games. Others face grueling stretches with short weeks and cross-country travel. For analysts, schedule factors...

Chapter 26: Schedule and Rest Analysis

Part 5: Advanced Topics


Learning Objectives

By the end of this chapter, you will be able to:

  1. Quantify the impact of bye weeks and rest differentials on performance
  2. Calculate and interpret strength of schedule metrics
  3. Analyze short week (Thursday Night) effects
  4. Build schedule-aware prediction models
  5. Identify schedule-related advantages and disadvantages

Introduction: The Hidden Schedule Factor

The NFL schedule is not created equal. Some teams face easier opponents, enjoy favorable bye week timing, or avoid back-to-back road games. Others face grueling stretches with short weeks and cross-country travel. For analysts, schedule factors represent quantifiable variables that can improve predictions.

This chapter develops frameworks for understanding how the schedule itself affects team performance and outcomes.


26.1 The NFL Scheduling Process

How Schedules Are Built

The NFL uses a complex formula for scheduling:

Fixed Components (10 games): - 6 divisional games (each team 2x) - 4 games vs one division in each conference (rotating annually)

Variable Components (7 games): - 2 games vs teams with same divisional finish - 1 game vs AFC/NFC team (rotating)

Schedule Equity Goals

The league attempts to balance: - Travel distances - Rest between games - Prime-time game distribution - International games impact - Stadium availability

However, perfect equity is impossible, creating analytical opportunities.


26.2 Bye Week Analysis

The Rest Advantage

Teams coming off a bye week have significant advantages:

Bye Week Benefits: - Physical recovery time - Extra preparation for opponent - No new injuries from previous game - Strategic scheming opportunity

Quantifying the Bye Effect

Historical analysis shows consistent bye week advantages:

Scenario Win Rate ATS Rate Margin Impact
Team off bye 54% 52% +1.0 to +1.5 pts
Both off bye 50% 50% Neutral
Opponent off bye 46% 48% -1.0 to -1.5 pts

The Bye Week Premium:

Bye_Adjustment = +1.2 points for team coming off bye
                 -1.2 points for team facing bye-rested opponent

Net adjustment when one team off bye: ~2.4 points swing

Bye Week Timing

Not all bye weeks are equal:

Bye Week Benefit Notes
Week 4-5 Lower Less fatigue accumulated
Week 6-9 Standard Normal benefit
Week 10-12 Higher Peak fatigue relief
Week 13+ Highest Late season recovery
def bye_timing_adjustment(bye_week: int) -> float:
    """
    Adjust bye benefit based on timing.

    Args:
        bye_week: Week number of bye

    Returns:
        Multiplier for standard bye benefit
    """
    if bye_week <= 5:
        return 0.7  # Reduced benefit
    elif bye_week <= 9:
        return 1.0  # Standard
    elif bye_week <= 12:
        return 1.2  # Enhanced benefit
    else:
        return 1.4  # Maximum benefit

26.3 Short Week Effects

Thursday Night Football

Thursday games create rest differentials:

Rest Days Before Game: - Normal Sunday-to-Sunday: 6 days - Thursday Night: 3 days (short week) - Monday Night to Thursday: 2 days (extremely short)

Impact Measurement

Road Team Rest Win Rate Margin Notes
Normal (6 days) 44% -2.5 Standard road disadvantage
Short (3 days) 38% -4.0 Additional penalty
Very short (MNF→TNF) 30% -6.0 Severe disadvantage

Thursday Night Adjustment:

TNF_Road_Penalty = -1.5 points (vs normal road performance)

If road team played Monday:
TNF_Road_Penalty = -3.0 points

Injury Correlation

Short weeks correlate with increased injuries:

Injury Rate Increase (Thursday Games):
- Soft tissue injuries: +18%
- Fatigue-related injuries: +25%
- Overall injury rate: +15%

This compounds short-week disadvantages as teams may also be missing additional players.


26.4 Rest Differential Framework

Calculating Rest Days

A complete rest framework considers:

def calculate_rest_differential(home_team_rest: int, away_team_rest: int,
                                home_traveled_last_week: bool,
                                away_traveled_last_week: bool) -> float:
    """
    Calculate rest-based adjustment.

    Args:
        home_team_rest: Days of rest for home team
        away_team_rest: Days of rest for away team
        home_traveled_last_week: If home team was on road
        away_traveled_last_week: If away team was on road

    Returns:
        Points adjustment (positive = favors home)
    """
    # Base rest differential
    rest_diff = home_team_rest - away_team_rest

    # Convert to points
    # Each day of rest differential worth ~0.3 points
    base_adjustment = rest_diff * 0.3

    # Travel fatigue adjustment
    if away_traveled_last_week and not home_traveled_last_week:
        base_adjustment += 0.3  # Home advantage increased

    # Cap the adjustment
    return max(-3.0, min(3.0, base_adjustment))

Rest Scenarios Table

Home Rest Away Rest Adjustment Example
13+ (bye) 6 +1.5 pts Post-bye vs normal
6 13+ (bye) -1.5 pts Normal vs post-bye
6 3 (TNF) +0.9 pts Normal vs short
3 3 0 pts Both short
10 (MNF) 6 +0.6 pts Long vs normal

26.5 Strength of Schedule

Defining SOS

Strength of Schedule (SOS) measures opponent quality:

Simple SOS = Average opponent win percentage

Weighted SOS = Σ (Opponent_Rating × Games) / Total_Games

Historical vs Future SOS

Past SOS (actual results): - Uses opponent records from completed games - Subject to randomness in results - Can be compared to expectations

Future SOS (projected): - Uses opponent power ratings - Accounts for home/away split - More stable than actual records

Calculating SOS

def calculate_sos(team: str, schedule: List[Dict],
                 team_ratings: Dict[str, float]) -> float:
    """
    Calculate strength of schedule.

    Args:
        team: Team identifier
        schedule: List of game dicts with opponent info
        team_ratings: Dict of team power ratings

    Returns:
        Strength of schedule (higher = harder)
    """
    total_difficulty = 0

    for game in schedule:
        opponent = game['opponent']
        opponent_rating = team_ratings.get(opponent, 0)

        # Adjust for home/away
        if game['location'] == 'away':
            opponent_rating += 2.5  # Harder on road
        else:
            opponent_rating -= 2.5  # Easier at home

        total_difficulty += opponent_rating

    return total_difficulty / len(schedule)

Interpreting SOS

SOS Rating Interpretation Win Impact
+2.0+ Very hard schedule -1 to -2 wins
+0.5 to +2.0 Hard schedule -0.5 to -1 wins
-0.5 to +0.5 Average Neutral
-2.0 to -0.5 Easy schedule +0.5 to +1 wins
Below -2.0 Very easy +1 to +2 wins

26.6 Schedule Density and Fatigue

Game Clustering

Some schedule periods are more demanding:

Demanding stretch indicators:
- 3+ games in 11 days
- 4+ games in 18 days
- 5+ games without bye in 30 days

Fatigue Accumulation Model

def calculate_fatigue(schedule: List[Dict], current_week: int) -> float:
    """
    Calculate accumulated fatigue from schedule density.

    Args:
        schedule: Team's full schedule
        current_week: Current week number

    Returns:
        Fatigue score (higher = more tired)
    """
    fatigue = 0
    recent_games = [g for g in schedule
                    if g['week'] <= current_week and g['week'] > current_week - 5]

    for game in recent_games:
        # Base fatigue per game
        base = 1.0

        # Short week penalty
        if game.get('short_week'):
            base *= 1.5

        # Travel penalty
        if game['location'] == 'away':
            base *= 1.2

        # Decay factor (older games matter less)
        weeks_ago = current_week - game['week']
        decay = 0.7 ** weeks_ago

        fatigue += base * decay

    return fatigue

Rest Before Important Games

Strategic schedule analysis considers:

Ideal pre-playoff rest: 10+ days
Typical Week 18 rest: 6 days
Short week before playoffs: -0.5 to -1.0 point penalty

Teams should prioritize rest in final weeks when clinched.

26.7 Travel Considerations

Travel Distance Analysis

NFL teams travel varying distances:

2023 Season Example Travel:

Team Total Miles Avg Per Road Game vs League Avg
Seattle 28,500 3,560 +45%
Miami 24,200 3,025 +23%
Dallas 15,800 1,975 -20%
Chicago 14,200 1,775 -28%

Travel Impact Model

def travel_fatigue_adjustment(total_season_miles: int,
                             recent_travel: int,
                             timezone_changes: int) -> float:
    """
    Calculate travel-based fatigue adjustment.

    Args:
        total_season_miles: Cumulative travel for season
        recent_travel: Miles traveled in last 2 weeks
        timezone_changes: Number of TZ changes in last 2 weeks

    Returns:
        Points adjustment for fatigue
    """
    # Cumulative fatigue (minor effect)
    cumulative = 0.1 * (total_season_miles - 20000) / 10000

    # Recent travel (stronger effect)
    recent = 0.3 * (recent_travel - 3000) / 3000

    # Timezone disruption
    tz_penalty = 0.2 * timezone_changes

    return max(-1.0, min(1.0, cumulative + recent + tz_penalty))

26.8 Prime-Time Game Distribution

The Marquee Game Effect

Prime-time games (SNF, MNF, TNF) have unique characteristics:

Advantages: - Extra preparation time (known opponent earlier) - National exposure (motivation boost) - Usually better teams scheduled

Disadvantages: - More intense preparation by opponent - Higher scrutiny/pressure - Travel complications for late starts

Performance by Slot

Slot Home Win % Notes
1:00 PM Sunday 55% Baseline
4:00 PM Sunday 54% Slightly lower
Sunday Night 57% Marquee boost
Monday Night 56% Similar to SNF
Thursday Night 58% Rest differential

26.9 Building a Schedule-Aware Model

The Complete Framework

class ScheduleAdjustmentModel:
    """Complete schedule-based adjustment model."""

    def __init__(self):
        self.bye_base = 1.2
        self.short_week_penalty = 1.5
        self.travel_per_tz = 0.2

    def calculate_adjustment(self, home_team: str, away_team: str,
                            schedule_context: Dict) -> Dict:
        """
        Calculate all schedule-related adjustments.

        Args:
            home_team: Home team identifier
            away_team: Away team identifier
            schedule_context: Dict with schedule information

        Returns:
            Dict with adjustment breakdown
        """
        adjustments = {}

        # Bye week effects
        home_bye = schedule_context.get('home_off_bye', False)
        away_bye = schedule_context.get('away_off_bye', False)

        if home_bye and not away_bye:
            bye_timing = schedule_context.get('home_bye_week', 8)
            bye_mult = self._bye_timing_multiplier(bye_timing)
            adjustments['bye'] = -self.bye_base * bye_mult
        elif away_bye and not home_bye:
            bye_timing = schedule_context.get('away_bye_week', 8)
            bye_mult = self._bye_timing_multiplier(bye_timing)
            adjustments['bye'] = self.bye_base * bye_mult
        else:
            adjustments['bye'] = 0

        # Rest differential
        home_rest = schedule_context.get('home_rest_days', 6)
        away_rest = schedule_context.get('away_rest_days', 6)
        rest_diff = (home_rest - away_rest) * 0.15
        adjustments['rest'] = -rest_diff  # Negative = favors home

        # Short week
        if schedule_context.get('thursday_game', False):
            if schedule_context.get('away_played_monday', False):
                adjustments['short_week'] = -3.0  # Major home advantage
            elif home_rest < away_rest:
                adjustments['short_week'] = self.short_week_penalty
            else:
                adjustments['short_week'] = -self.short_week_penalty
        else:
            adjustments['short_week'] = 0

        # Travel
        away_tz_change = schedule_context.get('away_timezone_change', 0)
        adjustments['travel'] = -away_tz_change * self.travel_per_tz

        # Calculate total
        adjustments['total'] = sum(adjustments.values())

        return adjustments

    def _bye_timing_multiplier(self, week: int) -> float:
        """Get bye timing multiplier."""
        if week <= 5:
            return 0.7
        elif week <= 9:
            return 1.0
        elif week <= 12:
            return 1.2
        return 1.4

26.10 Strength of Schedule for Predictions

Using SOS in Predictions

SOS affects interpretation of team records:

Adjusted Win% = Actual Win% + (League_Avg_SOS - Team_SOS) × Adjustment_Factor

Where Adjustment_Factor ≈ 0.04 per SOS point

Example: - Team A: 10-6 record, SOS = +1.5 (hard) - Team B: 10-6 record, SOS = -1.5 (easy)

Team A adjusted: 10-6 + (0 - 1.5) × 0.04 × 16 ≈ 10.96 wins equivalent
Team B adjusted: 10-6 + (0 - (-1.5)) × 0.04 × 16 ≈ 9.04 wins equivalent

Team A's 10-6 is more impressive than Team B's.

Future SOS for Predictions

When projecting remaining games:

def project_remaining_season(team: str, current_record: Tuple,
                            remaining_schedule: List,
                            team_ratings: Dict) -> Dict:
    """
    Project remaining season accounting for schedule.

    Args:
        team: Team identifier
        current_record: (wins, losses)
        remaining_schedule: List of remaining opponents
        team_ratings: Current team ratings

    Returns:
        Projection with schedule context
    """
    team_rating = team_ratings[team]
    projected_wins = current_record[0]
    projected_losses = current_record[1]

    for game in remaining_schedule:
        opponent = game['opponent']
        opponent_rating = team_ratings.get(opponent, 0)

        # Adjust for location
        if game['location'] == 'home':
            edge = team_rating - opponent_rating + 2.5
        else:
            edge = team_rating - opponent_rating - 2.5

        # Convert edge to win probability
        win_prob = 1 / (1 + 10 ** (-edge / 8))

        projected_wins += win_prob
        projected_losses += (1 - win_prob)

    return {
        'projected_wins': projected_wins,
        'projected_losses': projected_losses,
        'remaining_sos': calculate_remaining_sos(remaining_schedule, team_ratings)
    }

26.11 International Games

The London/International Series

Games played internationally have unique considerations:

Disadvantages: - Long travel for both teams - 5+ hour time zone shift - Unfamiliar environment - Jet lag recovery

2023+ International Schedule: - 5+ games in London - Games in Germany (Frankfurt, Munich) - Potential future expansion

International Game Adjustment

Both teams traveling internationally:
- HFA essentially neutralized (~0)
- Both teams -0.5 to -1.0 point penalty vs normal

"Home" team (marketing designation):
- +0.3 points for locker room choice
- May have UK-based fans

Travel recovery:
- Bye week after international: Standard benefit
- No bye after international: -0.5 next game

26.12 Case Study: Schedule-Driven Predictions

Example Analysis

Week 10 Game: Team A @ Team B

Schedule factors: - Team A: Coming off bye (Week 9) - Team B: Played Monday Night Week 9 (short rest) - Team A: Traveled 2 timezones - Team B: Home

Base spread: Team B -3.0 (home, better team)

Schedule adjustments:
- Team A bye: -1.2 (favors A)
- Team B short rest: -0.9 (favors A)
- Team A travel: +0.4 (favors B)
- Total adjustment: -1.7 points

Adjusted spread: Team B -1.3

This represents nearly 2 points of schedule-driven movement.

Summary

Schedule factors significantly impact NFL game outcomes:

  1. Bye weeks provide ~1.2 points of advantage
  2. Short weeks penalize road teams by ~1.5 points
  3. Rest differentials matter at ~0.3 points per day difference
  4. Strength of schedule affects win totals by 1-2 games
  5. Travel compounds with timezone changes
  6. International games neutralize home field but penalize both teams

A comprehensive model must account for all these factors to generate accurate predictions.


Key Formulas

Bye Week Adjustment:

Bye_Adjustment = 1.2 × Timing_Multiplier
Where Timing_Multiplier = 0.7 (early) to 1.4 (late)

Rest Differential:

Rest_Adjustment = (Home_Days - Away_Days) × 0.15

Thursday Night Penalty:

TNF_Away_Penalty = 1.5 pts (standard)
                 = 3.0 pts (if away played Monday)

Chapter 26 Summary

The NFL schedule creates measurable advantages and disadvantages that affect game outcomes. By systematically modeling bye weeks, rest differentials, short weeks, and travel, analysts can improve predictions. Schedule analysis is particularly valuable because these factors are known in advance, allowing for pre-game adjustments that markets may not fully incorporate.


Looking Ahead

This concludes Part 5: Advanced Topics. Part 6 will cover Applications, including Fantasy Football Analytics (Chapter 27) and Draft Analysis (Chapter 28).