Pace and Possessions
Pace and Possessions: Understanding the Tempo of Basketball
Pace is a fundamental metric in basketball analytics that measures the speed at which a game is played. Understanding pace and possession counts is essential for contextualizing team and player statistics, making meaningful comparisons across different eras, and evaluating performance on a level playing field. This guide explores how pace is calculated, why it matters, and how analysts use it to standardize basketball statistics.
What is Pace in Basketball?
Pace measures the number of possessions a team uses per game (or per 48 minutes). It quantifies game tempo—how fast or slow a team plays—independent of game outcomes. A team with high pace pushes the ball in transition, shoots quickly, and generates more possessions. A team with low pace plays methodically, uses more shot clock, and generates fewer possessions.
Key Characteristics of Pace:
- Tempo-focused: Measures speed of play, not efficiency or effectiveness
- Context-neutral: A possession-based metric independent of points scored
- Two-sided: When one team has a possession, so does the opponent
- Era-dependent: Pace has varied dramatically across NBA history
Pace serves as the foundation for advanced per-possession statistics, allowing analysts to normalize raw counting stats and compare players from different eras or systems.
How to Calculate Possessions
Before understanding pace, we must understand how to count possessions. A possession is a team's opportunity to score, beginning when they gain control of the ball and ending when they lose control.
The Possession Formula
The standard formula for estimating team possessions is:
Possessions = FGA + 0.44 × FTA - ORB + TOV
Where:
- FGA = Field Goal Attempts (includes made and missed shots)
- FTA = Free Throw Attempts
- ORB = Offensive Rebounds (extends the possession)
- TOV = Turnovers (ends the possession without a shot)
- 0.44 = Free throw adjustment factor
Breaking Down the Formula
Each component represents how possessions are used or extended:
1. Field Goal Attempts (FGA): Every shot attempt (made or missed) typically ends a possession, unless the offensive team gets the rebound.
Example: A team takes 85 shots in a game, representing 85 potential possession-ending events.
2. Free Throw Attempts (FTA × 0.44): Not all free throw attempts represent separate possessions. The 0.44 coefficient adjusts for the fact that:
- Two-shot fouls use one possession (2 FTA ÷ 1 possession = 2.0)
- Three-shot fouls use one possession (3 FTA ÷ 1 possession = 3.0)
- And-one free throws don't add possessions (already counted in FGA)
- Technical and flagrant fouls follow special rules
Empirical analysis shows approximately 0.44 free throw attempts equal one possession on average across the league.
Example: If a team attempts 20 free throws, this adds approximately 20 × 0.44 = 8.8 possessions.
3. Offensive Rebounds (-ORB): When a team grabs an offensive rebound, they maintain possession rather than starting a new one. We subtract offensive rebounds to avoid double-counting.
Example: A team misses a shot (counted in FGA), grabs the offensive rebound, and shoots again. Without subtracting the offensive rebound, we'd count this as two possessions when it's really one extended possession.
4. Turnovers (+TOV): Turnovers end possessions without a shot attempt, so they don't appear in FGA. We add turnovers to capture these possession-ending events.
Example: A bad pass intercepted by the defense ends the possession but creates no shot attempt. Adding turnovers ensures this possession is counted.
Calculating Possessions: Example
Let's calculate possessions for a team with these stats:
- Field Goal Attempts (FGA): 88
- Free Throw Attempts (FTA): 24
- Offensive Rebounds (ORB): 12
- Turnovers (TOV): 15
Possessions = 88 + (0.44 × 24) - 12 + 15
Possessions = 88 + 10.56 - 12 + 15
Possessions = 101.56 ≈ 101.6 possessions
This team used approximately 101.6 possessions in the game.
Alternative Possession Formulas
Several variations exist for calculating possessions, each with slight differences:
Dean Oliver's Possession Formula
Possessions = 0.5 × [(FGA + 0.44 × FTA - ORB + TOV) + (Opp_FGA + 0.44 × Opp_FTA - Opp_ORB + Opp_TOV)]
This formula averages both teams' possessions to account for measurement errors and discrepancies. Since both teams should have nearly equal possessions (within 1-2 due to end-of-quarter situations), averaging provides a more stable estimate.
Simplified Possession Estimate
Possessions ≈ FGA - ORB + TOV + 0.475 × FTA
Some analysts use 0.475 instead of 0.44 as the free throw coefficient, though 0.44 remains the standard.
What is Pace?
Pace measures the number of possessions per game or per 48 minutes. It's calculated by estimating total possessions and normalizing to a standard time frame.
The Pace Formula
The standard NBA pace formula is:
Pace = 48 × [(Team Possessions + Opponent Possessions) / (2 × Minutes Played)]
Where:
- 48 = Minutes in regulation NBA game
- Team Possessions = Possessions used by the team
- Opponent Possessions = Possessions used by the opponent
- Minutes Played = Actual minutes played (48 for regulation, 53 for OT, etc.)
This formula normalizes pace to 48 minutes, allowing comparison of games with different lengths (overtime games) and accounting for the fact that both teams share possessions.
Simplified Pace Formula
Many sources, including Basketball-Reference, use a simplified per-team pace estimate:
Pace = Possessions × (48 / Minutes)
For a regulation game with no overtime:
Pace = Possessions (since 48 / 48 = 1)
This gives the estimated number of possessions per 48 minutes for that team.
Calculating Pace: Example
Let's calculate pace for a regulation game where:
- Team A Possessions: 101.6
- Team B Possessions: 100.8
- Minutes Played: 48
Pace = 48 × [(101.6 + 100.8) / (2 × 48)]
Pace = 48 × [202.4 / 96]
Pace = 48 × 2.108
Pace = 101.2 possessions per 48 minutes
This game was played at a pace of approximately 101.2 possessions per 48 minutes.
Overtime Adjustment Example
For an overtime game that lasted 53 minutes with 110 combined possessions per team:
Pace = 48 × [(110 + 109) / (2 × 53)]
Pace = 48 × [219 / 106]
Pace = 48 × 2.066
Pace = 99.2 possessions per 48 minutes
Despite the overtime period adding possessions, the normalized pace shows a slower tempo than it might appear.
Historical Pace Trends in the NBA
Pace has fluctuated dramatically throughout NBA history, reflecting changes in rules, strategy, and playing philosophy. Understanding these trends is crucial for interpreting statistics from different eras.
Early NBA Era (1950s-1960s): Breakneck Speed
Average Pace: 115-130 possessions per 48 minutes
Characteristics:
- Fastest pace in NBA history
- No shot clock until 1954-55 season
- After shot clock introduction (24 seconds), pace remained extremely high
- Limited strategy, emphasis on athletic transition play
- Smaller rosters led to less substitution and conditioning challenges
Notable: The 1959-60 Boston Celtics averaged 124.5 pace. Wilt Chamberlain's 50.4 PPG season (1961-62) came at a 131.1 pace—far faster than any modern season.
Context: Raw statistics from this era are inflated by pace. A player averaging 30 PPG at 125 pace is roughly equivalent to 24 PPG at 100 pace.
1970s: High-Scoring, Fast-Paced Basketball
Average Pace: 105-110 possessions per 48 minutes
Characteristics:
- Still significantly faster than modern NBA
- ABA merger (1976) brought faster, more athletic style
- Introduction of three-point line (1979-80 season)
- Emphasis on transition offense and running
Notable: The "Showtime" Lakers and other transition-heavy teams pushed pace even higher in late 1970s and early 1980s.
1980s-1990s: Gradual Slowdown
Average Pace: 98-102 possessions per 48 minutes
Characteristics:
- Increased emphasis on halfcourt execution
- More sophisticated defensive schemes
- Physical, grinding play style
- Teams like the Detroit Pistons and New York Knicks exemplified slow, defensive-minded basketball
Notable: The 1998-99 lockout-shortened season saw the slowest pace in modern NBA history (88.9), influenced by rule changes allowing hand-checking and physical defense.
2000s: Dead-Ball Era
Average Pace: 90-92 possessions per 48 minutes
Characteristics:
- Slowest pace in NBA history
- Isolation-heavy offenses
- Post-up play and mid-range focus
- Defensive emphasis following hand-checking rule changes (2004)
- Teams milked shot clock to generate better looks
Notable: The 2003-04 season averaged 90.1 pace. Teams like the San Antonio Spurs and Detroit Pistons won championships with methodical, slow-paced systems.
Context: Low scoring games and defensive struggles characterized this era. Statistics from 2000-2010 appear lower but must be adjusted for pace.
2010s: Analytics Revolution and Pace Increase
Average Pace: 92-100 possessions per 48 minutes
Characteristics:
- Gradual pace acceleration throughout decade
- Analytics-driven emphasis on transition opportunities
- Three-point revolution increases pace (quicker shots, more misses/long rebounds)
- Rule changes favoring offensive freedom of movement
- Warriors' fast-paced, three-point-heavy system influences league
Notable: Pace increased from 91.9 (2010-11) to 100.0 (2018-19), the first time league average pace reached triple digits since 1991-92.
2020s: Modern High-Pace Era
Average Pace: 99-101 possessions per 48 minutes
Characteristics:
- Pace stabilized near 100 possessions per game
- Emphasis on transition, three-pointers, and efficiency
- Positionless basketball and versatile players enable faster play
- Reduced mid-range attempts speed up offensive possessions
- League-wide adoption of pace-and-space principles
Notable: The 2023-24 season averaged 99.8 pace. Teams like the Sacramento Kings (103.2) and Golden State Warriors (101.5) lead in tempo, while teams like the New York Knicks (95.8) and Cleveland Cavaliers (96.6) play slower.
Pace Trends: Key Takeaways
- Era Matters: Comparing raw statistics across eras without pace adjustment is misleading
- Rules Drive Pace: Shot clock rules, defensive regulations, and offensive freedom-of-movement rules significantly impact pace
- Strategy Evolves: Pace fluctuations reflect strategic innovation and analytical understanding
- Current Pace is Moderate: Modern pace (~100) sits between historical extremes (60s: 130, 2000s: 90)
Fastest and Slowest Teams in NBA History
Understanding pace extremes helps contextualize how drastically tempo can vary between teams and eras.
All-Time Fastest Teams (Single Season)
- 1961-62 Philadelphia Warriors: 131.1 Pace
- Wilt Chamberlain's 50.4 PPG season
- Ran at every opportunity
- Pre-modern defensive systems
- 1959-60 Boston Celtics: 130.4 Pace
- Fast-break dynasty era
- Bill Russell's defensive rebounding fueled transition
- No strategic reason to slow down
- 1960-61 Cincinnati Royals: 129.6 Pace
- Oscar Robertson's triple-double season
- Run-and-gun style
- 1990-91 Denver Nuggets: 119.9 Pace
- Fastest team in modern era (post-1980)
- Paul Westhead's system: shoot within 7 seconds
- Sacrificed defense for offense
- 1966-67 San Francisco Warriors: 119.8 Pace
- Rick Barry's scoring title season
- Transition-oriented system
Fastest Modern Era Teams (Post-2000)
- 2016-17 Brooklyn Nets: 102.8 Pace - Aggressive transition style despite limited talent
- 2017-18 Los Angeles Lakers: 102.6 Pace - Young team emphasizing pace and athleticism
- 2023-24 Sacramento Kings: 103.2 Pace - Mike Brown's up-tempo offense
- 2018-19 Atlanta Hawks: 102.4 Pace - Rebuilding team playing fast
- 2019-20 Washington Wizards: 102.6 Pace - Poor defense leading to fast games
All-Time Slowest Teams (Single Season)
- 1998-99 Seattle SuperSonics: 83.6 Pace
- Lockout-shortened season with rule changes
- Physical defensive era
- Deliberate offensive execution
- 1998-99 Utah Jazz: 84.8 Pace
- John Stockton and Karl Malone's methodical pick-and-roll
- Maximize shot clock usage
- Elite defensive execution
- 2003-04 Detroit Pistons: 84.3 Pace
- Championship team built on defense
- Ground-and-pound style
- Limited opponents' possessions
- 2002-03 San Antonio Spurs: 84.5 Pace
- Tim Duncan-led defensive team
- Controlled tempo
- Deliberate offensive sets
- 1999-00 Miami Heat: 86.0 Pace
- Pat Riley's defensive system
- Alonzo Mourning anchoring defense
- Grind-it-out style
Slowest Modern Era Teams (Post-2010)
- 2010-11 Denver Nuggets: 91.9 Pace - Slow by recent standards, average for that era
- 2023-24 New York Knicks: 95.8 Pace - Tom Thibodeau's deliberate, defensive system
- 2022-23 Milwaukee Bucks: 96.4 Pace - Halfcourt-oriented despite talent
- 2021-22 Cleveland Cavaliers: 95.6 Pace - Defensive identity, controlled tempo
- 2020-21 New York Knicks: 95.4 Pace - Defensive-first approach
Pace Variation Insights
Historical Extremes: The gap between fastest (131.1) and slowest (83.6) teams is nearly 50 possessions per game—a massive difference representing 40-50+ more shot attempts and scoring opportunities.
Modern Convergence: Today's pace variation is much narrower (typically 95-103), suggesting more uniform strategic understanding and fewer extreme outliers.
Winning Strategy Varies: Both extremely fast teams (Run TMC Warriors, Showtime Lakers) and extremely slow teams (2004 Pistons, 2000s Spurs) have won championships, proving multiple paths to success.
Why Pace Matters: The Importance of Per-100 Possessions Statistics
Pace is foundational to modern basketball analytics because raw statistics are heavily influenced by tempo. A player on a fast-paced team naturally accumulates more counting stats (points, rebounds, assists) simply by having more opportunities.
The Problem with Raw Stats
Consider two players with identical efficiency:
Player A (Fast-Paced Team, 105 Pace):
- 25 points, 8 rebounds, 6 assists per game
- Plays 36 minutes per game
Player B (Slow-Paced Team, 90 Pace):
- 21 points, 7 rebounds, 5 assists per game
- Plays 36 minutes per game
At first glance, Player A appears superior. However, Player A plays on a team with ~16% more possessions per game (105 vs. 90 pace). When normalized to equal possessions, their production may be identical or even favor Player B.
Per-100 Possessions: The Solution
Per-100 statistics normalize player and team performance to 100 possessions, creating a level playing field for comparison regardless of pace.
Formula for Per-100 Stats
Stat Per 100 Possessions = (Stat / Possessions) × 100
For team statistics:
Points Per 100 = (Total Points / Team Possessions) × 100
For player statistics:
Points Per 100 = (Player Points / Estimated Player Possessions) × 100
Player possessions are estimated based on minutes played and team pace:
Player Possessions ≈ (Minutes Played / 48) × Team Pace
Per-100 Calculation Example
Let's calculate per-100 stats for both players above:
Player A:
- Minutes: 36, Team Pace: 105
- Player Possessions: (36/48) × 105 = 78.75 possessions per game
- Points Per 100: (25 / 78.75) × 100 = 31.7 points per 100 possessions
- Rebounds Per 100: (8 / 78.75) × 100 = 10.2 per 100
- Assists Per 100: (6 / 78.75) × 100 = 7.6 per 100
Player B:
- Minutes: 36, Team Pace: 90
- Player Possessions: (36/48) × 90 = 67.5 possessions per game
- Points Per 100: (21 / 67.5) × 100 = 31.1 points per 100 possessions
- Rebounds Per 100: (7 / 67.5) × 100 = 10.4 per 100
- Assists Per 100: (5 / 67.5) × 100 = 7.4 per 100
Result: When normalized for pace, the players are nearly identical in production. The raw stat advantage for Player A was entirely due to playing at a faster pace.
Benefits of Per-100 Statistics
- Cross-Era Comparisons: Compare Michael Jordan (1990s, ~95 pace) to LeBron James (2010s-2020s, ~97-100 pace) fairly
- Team Comparisons: Compare offensive efficiency between fast-paced and slow-paced teams
- Role Player Evaluation: Identify efficient role players on slow-paced teams who accumulate fewer raw stats
- Lineup Analysis: Evaluate lineup effectiveness independent of pace
- Injury Replacement: Project how players would perform in different pace environments
Common Per-100 Statistics
- Offensive Rating (ORtg): Points produced per 100 possessions
- Defensive Rating (DRtg): Points allowed per 100 possessions
- Net Rating: Point differential per 100 possessions (ORtg - DRtg)
- Points Per 100: Individual scoring rate normalized to possessions
- Rebounds Per 100: Rebounding rate per 100 possessions
- Assists Per 100: Assist rate per 100 possessions
Pace Adjustment and Statistical Context
Beyond per-100 statistics, analysts use pace adjustment to contextualize performance across eras and systems.
Why Pace Adjustment Matters
Raw statistics favor players from high-pace eras:
- 1960s players: More possessions = inflated counting stats (points, rebounds, assists)
- 2000s players: Fewer possessions = deflated counting stats despite similar efficiency
Without pace adjustment, historical comparisons are fundamentally flawed.
Pace Adjustment Formula
To adjust a player's statistics from their era to a modern pace:
Adjusted Stat = Raw Stat × (Modern Pace / Player's Era Pace)
Pace Adjustment Example: Wilt Chamberlain vs. Modern Players
Wilt Chamberlain's legendary 1961-62 season:
- Raw Stats: 50.4 PPG, 25.7 RPG
- Era Pace: 131.1 possessions per game
- Modern Pace (2023-24): 99.8 possessions per game
Adjusting to modern pace:
Adjusted PPG = 50.4 × (99.8 / 131.1) = 38.4 PPG
Adjusted RPG = 25.7 × (99.8 / 131.1) = 19.6 RPG
Result: Even pace-adjusted, Wilt's season is historically elite (38.4 PPG, 19.6 RPG), but not quite as astronomically dominant as raw numbers suggest. The pace difference accounts for ~12 fewer PPG.
Oscar Robertson's Triple-Double Season
Oscar Robertson's 1961-62 triple-double season:
- Raw Stats: 30.8 PPG, 12.5 RPG, 11.4 APG
- Era Pace: 125.2 possessions per game
- Modern Pace: 99.8 possessions per game
Pace-adjusted:
Adjusted PPG = 30.8 × (99.8 / 125.2) = 24.6 PPG
Adjusted RPG = 12.5 × (99.8 / 125.2) = 10.0 RPG
Adjusted APG = 11.4 × (99.8 / 125.2) = 9.1 APG
Result: Still outstanding (24.6/10.0/9.1), but more comparable to elite modern seasons like Russell Westbrook's 2016-17 MVP campaign (31.6/10.7/10.4 at 99.5 pace) or Nikola Jokic's recent triple-double seasons.
Comparing Modern Players Across Different Pace Systems
Even within the modern era, pace variation affects comparisons:
Player A (2023-24 Sacramento Kings, 103.2 pace): 25.0 PPG
Player B (2023-24 New York Knicks, 95.8 pace): 23.0 PPG
Adjusting Player A to Knicks pace:
Adjusted PPG = 25.0 × (95.8 / 103.2) = 23.2 PPG
Player B is actually more productive (23.0 raw vs. 23.2 pace-adjusted), despite lower counting stats.
Limitations of Pace Adjustment
While valuable, pace adjustment has limitations:
- Assumes Linear Scaling: Players might not maintain efficiency with more/fewer possessions
- Ignores Context: Defensive rules, physicality, and strategic evolution differ across eras
- Role Changes: Players' roles might differ in alternate pace environments
- Fatigue Factors: Higher pace can cause fatigue, affecting late-game performance
- Diminishing Returns: Additional possessions may yield lower-quality shots
Pace adjustment provides valuable context but shouldn't be the sole basis for cross-era comparisons.
How Teams Control Pace
While pace is determined by both teams, individual teams can influence game tempo through strategy and personnel.
Strategies to Increase Pace
- Transition Offense:
- Push ball immediately after defensive rebounds
- Prioritize quick outlets and fast breaks
- Run before defense can set up
- Quick Shots:
- Attack early in shot clock (7-10 seconds)
- Take first good look rather than probing for great look
- Three-point shooting enables quick possessions
- Defensive Pressure:
- Force turnovers that lead to transition opportunities
- Contest shots to create long rebounds (fuel fast breaks)
- Aggressive trapping schemes
- Personnel Decisions:
- Play athletic, transition-capable players
- Use smaller, faster lineups
- Emphasize versatile players who can push in transition
- Offensive Rebounding:
- Crash offensive glass to extend possessions
- More possessions naturally increases pace
Strategies to Decrease Pace
- Slow Halfcourt Sets:
- Walk ball up court after made baskets
- Run deliberate offensive sets
- Use full shot clock (18-24 seconds)
- Limit Transition:
- Send players back on defense immediately after shots
- Protect defensive glass to prevent fast breaks
- Foul when necessary to prevent layups
- Ball Control:
- Emphasize low-turnover basketball
- Patient ball movement
- High-percentage shots only
- Defensive Discipline:
- Limit opponent transition opportunities
- Force opponents into halfcourt sets
- Contest shots to prevent easy baskets
- Tempo-Controlling Players:
- Rely on deliberate, methodical point guards
- Emphasize post-up players (time-consuming plays)
- Larger, slower lineups
Teams Known for Pace Control
High-Pace Teams:
- 2015-18 Golden State Warriors: Three-point shooting + transition excellence
- 1990-91 Denver Nuggets: Paul Westhead's "7 seconds or less" system
- 2005-10 Phoenix Suns: Mike D'Antoni's Seven Seconds or Less offense
- Showtime Lakers (1980s): Magic Johnson's fast-break system
- 2023-24 Sacramento Kings: Modern up-tempo offense
Low-Pace Teams:
- 2000s San Antonio Spurs: Tim Duncan-era methodical offense
- 2004 Detroit Pistons: Defensive grind-it-out style
- 1990s Utah Jazz: John Stockton and Karl Malone pick-and-roll
- 2020s New York Knicks: Tom Thibodeau's defensive system
Code Examples: Calculating Pace and Per-100 Statistics
Python: Basic Pace and Possession Calculations
import pandas as pd
import numpy as np
def calculate_possessions(fga, fta, orb, tov):
"""
Calculate team possessions using the standard formula.
Parameters:
-----------
fga : int or float
Field goal attempts
fta : int or float
Free throw attempts
orb : int or float
Offensive rebounds
tov : int or float
Turnovers
Returns:
--------
float
Estimated possessions
"""
possessions = fga + (0.44 * fta) - orb + tov
return possessions
def calculate_pace(team_poss, opp_poss, minutes=48):
"""
Calculate pace (possessions per 48 minutes).
Parameters:
-----------
team_poss : float
Team possessions
opp_poss : float
Opponent possessions
minutes : int
Minutes played (48 for regulation, more for OT)
Returns:
--------
float
Pace (possessions per 48 minutes)
"""
pace = 48 * ((team_poss + opp_poss) / (2 * minutes))
return pace
def calculate_per_100(stat, possessions):
"""
Convert a raw stat to per-100 possessions rate.
Parameters:
-----------
stat : int or float
Raw statistic (points, rebounds, etc.)
possessions : float
Number of possessions
Returns:
--------
float
Stat per 100 possessions
"""
if possessions == 0:
return 0.0
return (stat / possessions) * 100
# Example: Calculate team pace for a game
game_stats = {
'team': {
'fga': 88,
'fta': 24,
'orb': 12,
'tov': 15,
'points': 112
},
'opponent': {
'fga': 85,
'fta': 20,
'orb': 10,
'tov': 13,
'points': 108
},
'minutes': 48
}
# Calculate possessions for both teams
team_poss = calculate_possessions(
game_stats['team']['fga'],
game_stats['team']['fta'],
game_stats['team']['orb'],
game_stats['team']['tov']
)
opp_poss = calculate_possessions(
game_stats['opponent']['fga'],
game_stats['opponent']['fta'],
game_stats['opponent']['orb'],
game_stats['opponent']['tov']
)
# Calculate pace
pace = calculate_pace(team_poss, opp_poss, game_stats['minutes'])
# Calculate per-100 stats
team_ortg = calculate_per_100(game_stats['team']['points'], team_poss)
opp_ortg = calculate_per_100(game_stats['opponent']['points'], opp_poss)
print(f"Team Possessions: {team_poss:.1f}")
print(f"Opponent Possessions: {opp_poss:.1f}")
print(f"Game Pace: {pace:.1f} possessions per 48 minutes")
print(f"\nTeam Offensive Rating: {team_ortg:.1f} points per 100 possessions")
print(f"Opponent Offensive Rating: {opp_ortg:.1f} points per 100 possessions")
Python: Season-Long Pace Analysis
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
def analyze_team_pace(team_stats_df):
"""
Analyze pace and efficiency for a team's season.
Parameters:
-----------
team_stats_df : pandas.DataFrame
DataFrame with game-by-game team statistics
Returns:
--------
dict
Pace and efficiency metrics
"""
# Calculate possessions for each game
team_stats_df['Possessions'] = (
team_stats_df['FGA'] +
0.44 * team_stats_df['FTA'] -
team_stats_df['ORB'] +
team_stats_df['TOV']
)
team_stats_df['Opp_Possessions'] = (
team_stats_df['Opp_FGA'] +
0.44 * team_stats_df['Opp_FTA'] -
team_stats_df['Opp_ORB'] +
team_stats_df['Opp_TOV']
)
# Calculate pace for each game
team_stats_df['Pace'] = 48 * (
(team_stats_df['Possessions'] + team_stats_df['Opp_Possessions']) /
(2 * team_stats_df['Minutes'])
)
# Calculate per-100 possessions stats
team_stats_df['ORtg'] = (
team_stats_df['Points'] / team_stats_df['Possessions']
) * 100
team_stats_df['DRtg'] = (
team_stats_df['Opp_Points'] / team_stats_df['Opp_Possessions']
) * 100
team_stats_df['NetRtg'] = team_stats_df['ORtg'] - team_stats_df['DRtg']
# Season averages
results = {
'avg_pace': team_stats_df['Pace'].mean(),
'avg_ortg': team_stats_df['ORtg'].mean(),
'avg_drtg': team_stats_df['DRtg'].mean(),
'avg_netrtg': team_stats_df['NetRtg'].mean(),
'pace_std': team_stats_df['Pace'].std(),
'games': len(team_stats_df)
}
return results, team_stats_df
# Example with sample data
sample_season = pd.DataFrame({
'Game': range(1, 83),
'Points': np.random.randint(95, 125, 82),
'FGA': np.random.randint(75, 95, 82),
'FTA': np.random.randint(15, 30, 82),
'ORB': np.random.randint(8, 15, 82),
'TOV': np.random.randint(10, 18, 82),
'Opp_Points': np.random.randint(95, 125, 82),
'Opp_FGA': np.random.randint(75, 95, 82),
'Opp_FTA': np.random.randint(15, 30, 82),
'Opp_ORB': np.random.randint(8, 15, 82),
'Opp_TOV': np.random.randint(10, 18, 82),
'Minutes': [48] * 82
})
results, analyzed_df = analyze_team_pace(sample_season)
print("Season Pace and Efficiency Analysis:")
print(f"Average Pace: {results['avg_pace']:.1f} possessions per 48 minutes")
print(f"Offensive Rating: {results['avg_ortg']:.1f} points per 100 possessions")
print(f"Defensive Rating: {results['avg_drtg']:.1f} points per 100 possessions")
print(f"Net Rating: {results['avg_netrtg']:.1f} points per 100 possessions")
print(f"Pace Standard Deviation: {results['pace_std']:.1f}")
# Visualize pace over the season
plt.figure(figsize=(12, 6))
plt.plot(analyzed_df['Game'], analyzed_df['Pace'], marker='o', linewidth=1, markersize=4)
plt.axhline(y=results['avg_pace'], color='r', linestyle='--',
label=f"Season Average: {results['avg_pace']:.1f}")
plt.xlabel('Game Number')
plt.ylabel('Pace (Possessions per 48 Minutes)')
plt.title('Team Pace Throughout the Season')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('pace_by_game.png', dpi=300)
plt.show()
Python: Pace Adjustment for Historical Comparisons
import pandas as pd
def pace_adjust_stats(raw_stats, player_pace, target_pace):
"""
Adjust player statistics from one pace to another.
Parameters:
-----------
raw_stats : dict
Dictionary of player statistics
player_pace : float
Pace of player's era/team
target_pace : float
Target pace for adjustment
Returns:
--------
dict
Pace-adjusted statistics
"""
pace_factor = target_pace / player_pace
adjusted_stats = {}
for stat, value in raw_stats.items():
if stat in ['PPG', 'RPG', 'APG', 'SPG', 'BPG', 'TOV']:
adjusted_stats[stat] = value * pace_factor
else:
adjusted_stats[stat] = value # Don't adjust percentages
return adjusted_stats
# Historical comparison: Wilt Chamberlain vs. Modern Era
wilt_1962 = {
'Player': 'Wilt Chamberlain',
'Season': '1961-62',
'PPG': 50.4,
'RPG': 25.7,
'APG': 2.4,
'FG%': 0.506,
'Pace': 131.1
}
modern_pace = 99.8
wilt_adjusted = pace_adjust_stats(
{'PPG': wilt_1962['PPG'], 'RPG': wilt_1962['RPG'], 'APG': wilt_1962['APG']},
wilt_1962['Pace'],
modern_pace
)
print("Wilt Chamberlain 1961-62 Season:")
print(f"Raw Stats (Pace: {wilt_1962['Pace']}): {wilt_1962['PPG']} PPG, {wilt_1962['RPG']} RPG, {wilt_1962['APG']} APG")
print(f"Pace-Adjusted to Modern Era (Pace: {modern_pace}): {wilt_adjusted['PPG']:.1f} PPG, {wilt_adjusted['RPG']:.1f} RPG, {wilt_adjusted['APG']:.1f} APG")
# Compare multiple historical players
historical_players = pd.DataFrame([
{'Player': 'Wilt Chamberlain', 'Season': '1961-62', 'PPG': 50.4, 'RPG': 25.7, 'APG': 2.4, 'Pace': 131.1},
{'Player': 'Oscar Robertson', 'Season': '1961-62', 'PPG': 30.8, 'RPG': 12.5, 'APG': 11.4, 'Pace': 125.2},
{'Player': 'Michael Jordan', 'Season': '1986-87', 'PPG': 37.1, 'RPG': 5.2, 'APG': 4.6, 'Pace': 100.8},
{'Player': 'LeBron James', 'Season': '2012-13', 'PPG': 26.8, 'RPG': 8.0, 'APG': 7.3, 'Pace': 92.0},
{'Player': 'James Harden', 'Season': '2018-19', 'PPG': 36.1, 'RPG': 6.6, 'APG': 7.5, 'Pace': 100.0}
])
# Adjust all to modern pace
for idx, row in historical_players.iterrows():
pace_factor = modern_pace / row['Pace']
historical_players.at[idx, 'Adjusted_PPG'] = row['PPG'] * pace_factor
historical_players.at[idx, 'Adjusted_RPG'] = row['RPG'] * pace_factor
historical_players.at[idx, 'Adjusted_APG'] = row['APG'] * pace_factor
print("\n\nHistorical Seasons Adjusted to Modern Pace (99.8):")
print(historical_players[['Player', 'Season', 'PPG', 'Adjusted_PPG', 'RPG', 'Adjusted_RPG']].to_string(index=False))
R: Pace and Per-100 Possessions Analysis
# Load required libraries
library(tidyverse)
library(ggplot2)
# Function to calculate possessions
calculate_possessions <- function(fga, fta, orb, tov) {
possessions <- fga + (0.44 * fta) - orb + tov
return(possessions)
}
# Function to calculate pace
calculate_pace <- function(team_poss, opp_poss, minutes = 48) {
pace <- 48 * ((team_poss + opp_poss) / (2 * minutes))
return(pace)
}
# Function to calculate per-100 stats
calculate_per_100 <- function(stat, possessions) {
if (possessions == 0) return(0)
return((stat / possessions) * 100)
}
# Example game data
game_data <- tibble(
team = "Home Team",
points = 112,
fga = 88,
fta = 24,
orb = 12,
tov = 15,
opp_points = 108,
opp_fga = 85,
opp_fta = 20,
opp_orb = 10,
opp_tov = 13,
minutes = 48
)
# Calculate possessions
game_data <- game_data %>%
mutate(
team_possessions = calculate_possessions(fga, fta, orb, tov),
opp_possessions = calculate_possessions(opp_fga, opp_fta, opp_orb, opp_tov),
pace = calculate_pace(team_possessions, opp_possessions, minutes),
ortg = calculate_per_100(points, team_possessions),
drtg = calculate_per_100(opp_points, opp_possessions),
net_rtg = ortg - drtg
)
# Display results
cat("Game Analysis:\n")
cat(sprintf("Team Possessions: %.1f\n", game_data$team_possessions))
cat(sprintf("Opponent Possessions: %.1f\n", game_data$opp_possessions))
cat(sprintf("Game Pace: %.1f possessions per 48 minutes\n", game_data$pace))
cat(sprintf("\nOffensive Rating: %.1f points per 100 possessions\n", game_data$ortg))
cat(sprintf("Defensive Rating: %.1f points per 100 possessions\n", game_data$drtg))
cat(sprintf("Net Rating: %+.1f points per 100 possessions\n", game_data$net_rtg))
R: Season-Long Pace Analysis and Visualization
library(tidyverse)
library(ggplot2)
# Generate sample season data
set.seed(42)
season_data <- tibble(
game = 1:82,
points = sample(95:125, 82, replace = TRUE),
fga = sample(75:95, 82, replace = TRUE),
fta = sample(15:30, 82, replace = TRUE),
orb = sample(8:15, 82, replace = TRUE),
tov = sample(10:18, 82, replace = TRUE),
opp_points = sample(95:125, 82, replace = TRUE),
opp_fga = sample(75:95, 82, replace = TRUE),
opp_fta = sample(15:30, 82, replace = TRUE),
opp_orb = sample(8:15, 82, replace = TRUE),
opp_tov = sample(10:18, 82, replace = TRUE),
minutes = 48
)
# Calculate pace and efficiency for each game
season_data <- season_data %>%
mutate(
possessions = calculate_possessions(fga, fta, orb, tov),
opp_possessions = calculate_possessions(opp_fga, opp_fta, opp_orb, opp_tov),
pace = calculate_pace(possessions, opp_possessions, minutes),
ortg = calculate_per_100(points, possessions),
drtg = calculate_per_100(opp_points, opp_possessions),
net_rtg = ortg - drtg,
result = ifelse(points > opp_points, "Win", "Loss")
)
# Season summary statistics
season_summary <- season_data %>%
summarise(
games = n(),
avg_pace = mean(pace),
avg_ortg = mean(ortg),
avg_drtg = mean(drtg),
avg_net_rtg = mean(net_rtg),
pace_sd = sd(pace),
wins = sum(result == "Win"),
losses = sum(result == "Loss")
)
cat("\n=== Season Pace and Efficiency Summary ===\n")
cat(sprintf("Games Played: %d (W: %d, L: %d)\n",
season_summary$games, season_summary$wins, season_summary$losses))
cat(sprintf("Average Pace: %.1f possessions per 48 minutes\n", season_summary$avg_pace))
cat(sprintf("Pace Std Dev: %.1f\n", season_summary$pace_sd))
cat(sprintf("\nOffensive Rating: %.1f points per 100 possessions\n", season_summary$avg_ortg))
cat(sprintf("Defensive Rating: %.1f points per 100 possessions\n", season_summary$avg_drtg))
cat(sprintf("Net Rating: %+.1f points per 100 possessions\n", season_summary$avg_net_rtg))
# Visualization: Pace throughout the season
p1 <- ggplot(season_data, aes(x = game, y = pace, color = result)) +
geom_point(size = 3, alpha = 0.6) +
geom_line(alpha = 0.3) +
geom_hline(yintercept = season_summary$avg_pace,
linetype = "dashed", color = "black", size = 1) +
scale_color_manual(values = c("Win" = "#2ecc71", "Loss" = "#e74c3c")) +
labs(
title = "Team Pace Throughout the Season",
subtitle = sprintf("Average Pace: %.1f possessions per 48 minutes", season_summary$avg_pace),
x = "Game Number",
y = "Pace (Possessions per 48 Minutes)",
color = "Result"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 16, face = "bold"),
plot.subtitle = element_text(size = 12),
legend.position = "top"
)
# Visualization: Pace vs. Net Rating
p2 <- ggplot(season_data, aes(x = pace, y = net_rtg, color = result)) +
geom_point(size = 3, alpha = 0.7) +
geom_hline(yintercept = 0, linetype = "dashed", color = "gray") +
geom_smooth(method = "lm", se = TRUE, color = "blue", alpha = 0.2) +
scale_color_manual(values = c("Win" = "#2ecc71", "Loss" = "#e74c3c")) +
labs(
title = "Pace vs. Net Rating",
subtitle = "Does pace correlate with performance?",
x = "Pace (Possessions per 48 Minutes)",
y = "Net Rating (Points per 100 Possessions)",
color = "Result"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 16, face = "bold"),
plot.subtitle = element_text(size = 12),
legend.position = "top"
)
# Display plots
print(p1)
print(p2)
# Save plots
ggsave("pace_by_game.png", plot = p1, width = 12, height = 6, dpi = 300)
ggsave("pace_vs_netrtg.png", plot = p2, width = 10, height = 6, dpi = 300)
# Correlation analysis
correlation <- cor(season_data$pace, season_data$net_rtg)
cat(sprintf("\nCorrelation between Pace and Net Rating: %.3f\n", correlation))
R: Pace Adjustment for Historical Comparisons
library(tidyverse)
# Function to pace-adjust statistics
pace_adjust_stats <- function(raw_stat, player_pace, target_pace) {
pace_factor <- target_pace / player_pace
adjusted_stat <- raw_stat * pace_factor
return(adjusted_stat)
}
# Historical player seasons
historical_seasons <- tibble(
player = c("Wilt Chamberlain", "Oscar Robertson", "Michael Jordan",
"LeBron James", "James Harden", "Nikola Jokic"),
season = c("1961-62", "1961-62", "1986-87", "2012-13", "2018-19", "2023-24"),
ppg = c(50.4, 30.8, 37.1, 26.8, 36.1, 26.4),
rpg = c(25.7, 12.5, 5.2, 8.0, 6.6, 12.4),
apg = c(2.4, 11.4, 4.6, 7.3, 7.5, 9.0),
pace = c(131.1, 125.2, 100.8, 92.0, 100.0, 99.8)
)
# Target pace for adjustment (modern era)
target_pace <- 99.8
# Adjust all statistics to target pace
historical_seasons <- historical_seasons %>%
mutate(
adjusted_ppg = pace_adjust_stats(ppg, pace, target_pace),
adjusted_rpg = pace_adjust_stats(rpg, pace, target_pace),
adjusted_apg = pace_adjust_stats(apg, pace, target_pace),
pace_impact = (pace - target_pace) / pace * 100 # Percentage difference
)
# Display comparison
cat(sprintf("Historical Seasons Adjusted to Modern Pace (%.1f):\n\n", target_pace))
comparison_table <- historical_seasons %>%
select(player, season, pace, ppg, adjusted_ppg, rpg, adjusted_rpg) %>%
mutate(
ppg_diff = ppg - adjusted_ppg,
rpg_diff = rpg - adjusted_rpg
)
print(comparison_table)
# Visualize pace-adjusted scoring
ggplot(historical_seasons, aes(x = reorder(paste(player, season), adjusted_ppg),
y = adjusted_ppg)) +
geom_bar(stat = "identity", fill = "#3498db", alpha = 0.8) +
geom_text(aes(label = sprintf("%.1f", adjusted_ppg)),
hjust = -0.2, size = 4) +
coord_flip() +
labs(
title = "Greatest Scoring Seasons (Pace-Adjusted to 99.8)",
subtitle = "Raw PPG adjusted to modern pace for fair comparison",
x = "Player (Season)",
y = "Pace-Adjusted Points Per Game"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 16, face = "bold"),
plot.subtitle = element_text(size = 12),
axis.text = element_text(size = 10)
) +
scale_y_continuous(expand = expansion(mult = c(0, 0.1)))
ggsave("pace_adjusted_scoring.png", width = 10, height = 7, dpi = 300)
Practical Applications of Pace Analysis
For Analysts
- Player Evaluation: Use per-100 stats to compare players on teams with different pace
- Historical Context: Adjust statistics across eras for meaningful all-time rankings
- Lineup Analysis: Identify which lineups perform best at different pace levels
- Matchup Analysis: Predict how teams with different pace preferences will interact
- Trade Evaluation: Project how players will adapt to new team's pace
For Coaches
- Strategic Planning: Decide whether to play fast or slow based on personnel
- Opponent Adjustment: Speed up or slow down to exploit opponent weaknesses
- Fatigue Management: Manage rotation based on pace demands
- Style Optimization: Identify optimal pace for team's skill set
For Fantasy Basketball
- Streamers: Target players on high-pace teams for short-term adds
- Value Identification: Find undervalued players on slow-pace teams
- Projections: Adjust expectations based on team pace changes
Conclusion
Pace and possessions form the foundation of modern basketball analytics. Understanding how to calculate possessions, measure pace, and normalize statistics to per-100 possessions is essential for meaningful analysis. Without accounting for pace, comparisons across eras, teams, and systems are fundamentally flawed.
The evolution of pace throughout NBA history—from the breakneck 130+ possession games of the 1960s to the sluggish 90-possession dead-ball era of the 2000s, and back to the modern 100-possession standard—demonstrates how dramatically tempo can vary. These variations have profound effects on raw statistics, making pace adjustment critical for fair evaluation.
Modern analytics relies heavily on per-100 possessions statistics (Offensive Rating, Defensive Rating, Net Rating) to create level playing fields for comparison. Whether evaluating players across different teams, comparing historical greats, or analyzing team efficiency, pace-adjusted metrics provide the context necessary for sophisticated basketball analysis.
As basketball continues to evolve, pace will remain a fundamental concept for anyone seeking to understand the game beyond surface-level box scores. Mastering pace analysis enables deeper insights into player value, team strategy, and the true nature of basketball performance.