Offensive Rating (ORtg)

Beginner 10 min read 0 views Nov 27, 2025
# Offensive Rating (ORtg) ## Definition **Offensive Rating (ORtg)** is an advanced basketball metric that estimates the number of points a player or team produces per 100 possessions. It provides a pace-adjusted measure of offensive efficiency, allowing for fair comparisons across different eras and playing styles. ### Formula Overview **ORtg = (Points Produced / Possessions) × 100** This normalizes scoring output to a standard possession count, eliminating the bias introduced by varying game tempos. --- ## Individual vs Team ORtg ### Team Offensive Rating Team ORtg measures how many points a team scores per 100 possessions: **Team ORtg = (Total Points / Total Possessions) × 100** Where team possessions are estimated as: **Possessions = FGA + 0.44 × FTA - ORB + TOV** **Example:** - Points: 112 - FGA: 88 - FTA: 24 - ORB: 10 - TOV: 14 Possessions = 88 + 0.44(24) - 10 + 14 = 102.56 Team ORtg = (112 / 102.56) × 100 = **109.2** ### Individual Offensive Rating Individual ORtg is more complex, accounting for a player's contributions to team offense including scoring, assists, turnovers, and offensive rebounds. --- ## Dean Oliver's Individual ORtg Calculation Dean Oliver, creator of the Four Factors of Basketball Success, developed the most widely-used individual ORtg formula. The calculation involves several components: ### Step 1: Calculate Scoring Possessions **Scoring Possessions (ScPoss) = (FG_Part + AST_Part + FT_Part) × (1 - (Team_ORB / Team_Scoring_Poss) × Team_ORB_Weight × Team_Play%)** Where: - **FG_Part** = Player's FGM × (1 - 0.5 × ((Player_PTS - Player_FTM) / (2 × Player_FGA)) × q_AST) - **AST_Part** = 0.5 × (((Team_PTS - Team_FTM) - (Player_PTS - Player_FTM)) / (2 × (Team_FGA - Player_FGA))) × Player_AST - **FT_Part** = (1 - (1 - (Player_FTM / Player_FTA))^2) × 0.4 × Player_FTA ### Step 2: Calculate Total Possessions Used **Total Possessions = ScPoss + FGM_Part + FTM_Part + TOV** Where: - **FGM_Part** = (Player_FGA - Player_FGM) × (1 - 1.07 × Team_ORB%) - **FTM_Part** = ((1 - (Player_FTM / Player_FTA))^2) × 0.4 × Player_FTA - **TOV** = Player turnovers ### Step 3: Calculate Points Produced **Points Produced = (ScPoss × Team_ORtg) + (ORB × Team_ORB_Weight × Team_Play%) + (AST × 0.5 × (Team_FGM / Team_FGA) × Team_PTS/Team_FGM) - (TOV × Team_ORtg)** ### Step 4: Calculate Individual ORtg **Individual ORtg = 100 × (Points Produced / Total Possessions)** ### Simplified Version (Basketball-Reference) A more accessible approximation used by Basketball-Reference: **ORtg = 100 × (PTS + 0.7 × AST × (Team_PTS / Team_AST) - TOV × (Team_PTS / (Team_FGA + 0.44 × Team_FTA + Team_TOV))) / (FGA + 0.44 × FTA + TOV)** --- ## Interpretation and Benchmarks ### NBA League Averages (2023-24 Season) | Category | ORtg Range | |----------|-----------| | **Elite** | 120+ | | **Excellent** | 115-119 | | **Above Average** | 110-114 | | **Average** | 105-109 | | **Below Average** | 100-104 | | **Poor** | <100 | ### Team ORtg Benchmarks - **Championship-Level Offense**: 115+ - **Playoff-Caliber**: 110-114 - **League Average**: ~113 (varies by season) - **Below Average**: <110 ### Individual ORtg Context Individual ORtg should be interpreted with caution: - **Usage Rate Matters**: High ORtg on low usage is less impressive than on high usage - **Role Players** often have inflated ORtg due to selective shot attempts - **Primary Scorers** typically have lower ORtg due to difficult shot creation responsibilities - **Context**: Team quality, pace, and era significantly affect individual ratings --- ## Historical Leaders ### Single-Season Individual ORtg Leaders (Min. 2000 MP) 1. **Nikola Jokić** (2021-22): 127.0 2. **Nikola Jokić** (2020-21): 126.6 3. **Stephen Curry** (2015-16): 125.5 4. **LeBron James** (2012-13): 124.6 5. **Charles Barkley** (1987-88): 124.4 6. **Stephen Curry** (2016-17): 124.1 7. **Michael Jordan** (1990-91): 123.8 8. **Stephen Curry** (2018-19): 123.5 9. **Magic Johnson** (1986-87): 123.3 10. **Nikola Jokić** (2022-23): 123.2 ### Career ORtg Leaders (Min. 10,000 MP) 1. **Stephen Curry**: 119.8 2. **LeBron James**: 118.7 3. **Michael Jordan**: 118.3 4. **Magic Johnson**: 116.9 5. **Nikola Jokić**: 116.8 6. **Kevin Durant**: 116.5 7. **Chris Paul**: 116.3 8. **Karl Malone**: 115.6 9. **Dirk Nowitzki**: 115.0 10. **Larry Bird**: 114.8 ### Single-Season Team ORtg Leaders 1. **2021-22 Phoenix Suns**: 116.3 2. **2016-17 Golden State Warriors**: 115.6 3. **2020-21 Brooklyn Nets**: 115.2 4. **2018-19 Golden State Warriors**: 114.5 5. **2017-18 Houston Rockets**: 114.4 --- ## Relationship with True Shooting Percentage (TS%) ORtg and TS% are closely related but measure different aspects of offensive performance: ### True Shooting Percentage (TS%) **TS% = PTS / (2 × (FGA + 0.44 × FTA))** Measures shooting efficiency by accounting for 2-pointers, 3-pointers, and free throws. ### Key Differences | Metric | What It Measures | Scope | |--------|-----------------|-------| | **TS%** | Shooting efficiency only | Individual scoring | | **ORtg** | Overall offensive production | Scoring + playmaking + turnovers | ### Correlation - **Positive Correlation**: Higher TS% generally leads to higher ORtg - **TS% is a component** of ORtg but doesn't account for: - Assists and playmaking - Turnovers - Offensive rebounds - Team context ### Example Comparison **Player A (Volume Scorer):** - TS%: 58% - ORtg: 112 - High usage, creates own shots, moderate assists **Player B (Efficient Role Player):** - TS%: 65% - ORtg: 118 - Low usage, open shots, few turnovers Player B has superior efficiency metrics, but Player A may be more valuable due to shot creation ability. ### Mathematical Relationship A simplified relationship can be expressed as: **ORtg ≈ 100 × TS% × (1 + AST_Factor - TOV_Factor)** Where: - **AST_Factor** = (AST × Team_PPG) / (Team_AST × Player_Possessions) - **TOV_Factor** = TOV / Player_Possessions This shows that while TS% forms the foundation of ORtg, playmaking and ball security significantly modify the final rating. --- ## Python Implementation ### Basic Team ORtg Calculation ```python import pandas as pd import numpy as np def calculate_team_ortg(points, fga, fta, orb, tov): """ Calculate team offensive rating. Parameters: ----------- points : int/float - Total points scored fga : int/float - Field goal attempts fta : int/float - Free throw attempts orb : int/float - Offensive rebounds tov : int/float - Turnovers Returns: -------- float : Team offensive rating """ possessions = fga + 0.44 * fta - orb + tov ortg = (points / possessions) * 100 return round(ortg, 2) # Example usage team_ortg = calculate_team_ortg( points=115, fga=90, fta=22, orb=12, tov=15 ) print(f"Team Offensive Rating: {team_ortg}") # Output: Team Offensive Rating: 114.61 ``` ### Advanced Individual ORtg (Simplified Version) ```python def calculate_individual_ortg(player_stats, team_stats): """ Calculate individual offensive rating using simplified method. Parameters: ----------- player_stats : dict with keys: pts, ast, tov, fga, fta team_stats : dict with keys: pts, ast, fga, fta, tov Returns: -------- float : Individual offensive rating """ # Points produced points_produced = ( player_stats['pts'] + 0.7 * player_stats['ast'] * (team_stats['pts'] / team_stats['ast']) - player_stats['tov'] * (team_stats['pts'] / (team_stats['fga'] + 0.44 * team_stats['fta'] + team_stats['tov'])) ) # Possessions used possessions = ( player_stats['fga'] + 0.44 * player_stats['fta'] + player_stats['tov'] ) # Calculate ORtg ortg = 100 * (points_produced / possessions) if possessions > 0 else 0 return round(ortg, 2) # Example usage player = { 'pts': 28, 'ast': 6, 'tov': 3, 'fga': 18, 'fta': 8 } team = { 'pts': 112, 'ast': 24, 'fga': 88, 'fta': 24, 'tov': 14 } player_ortg = calculate_individual_ortg(player, team) print(f"Individual Offensive Rating: {player_ortg}") # Output: Individual Offensive Rating: 118.45 ``` ### ORtg and TS% Correlation Analysis ```python import matplotlib.pyplot as plt from scipy.stats import pearsonr def analyze_ortg_ts_correlation(player_data): """ Analyze correlation between ORtg and TS%. Parameters: ----------- player_data : DataFrame with columns 'ORtg' and 'TS%' Returns: -------- dict : Correlation statistics and visualization """ # Calculate correlation corr, p_value = pearsonr(player_data['ORtg'], player_data['TS%']) # Create scatter plot plt.figure(figsize=(10, 6)) plt.scatter(player_data['TS%'], player_data['ORtg'], alpha=0.6) plt.xlabel('True Shooting Percentage (%)') plt.ylabel('Offensive Rating') plt.title(f'ORtg vs TS% Correlation\n(r = {corr:.3f}, p = {p_value:.4f})') # Add trend line z = np.polyfit(player_data['TS%'], player_data['ORtg'], 1) p = np.poly1d(z) plt.plot(player_data['TS%'], p(player_data['TS%']), "r--", alpha=0.8, linewidth=2) plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('ortg_ts_correlation.png', dpi=300) return { 'correlation': corr, 'p_value': p_value, 'relationship': 'Strong' if abs(corr) > 0.7 else 'Moderate' if abs(corr) > 0.4 else 'Weak' } # Example usage with sample data sample_data = pd.DataFrame({ 'Player': ['Player A', 'Player B', 'Player C', 'Player D', 'Player E'], 'ORtg': [118, 112, 115, 108, 121], 'TS%': [62.5, 57.3, 60.1, 54.8, 64.2] }) correlation_results = analyze_ortg_ts_correlation(sample_data) print(f"Correlation: {correlation_results['correlation']:.3f}") print(f"Relationship: {correlation_results['relationship']}") ``` ### Calculate ORtg for Entire Dataset ```python def calculate_season_ortg(df): """ Calculate offensive rating for all players in a season. Parameters: ----------- df : DataFrame with player statistics Returns: -------- DataFrame : Original data with added ORtg column """ # Calculate team totals (assuming single team) team_pts = df['pts'].sum() team_ast = df['ast'].sum() team_fga = df['fga'].sum() team_fta = df['fta'].sum() team_tov = df['tov'].sum() team_stats = { 'pts': team_pts, 'ast': team_ast, 'fga': team_fga, 'fta': team_fta, 'tov': team_tov } # Calculate ORtg for each player df['ortg'] = df.apply(lambda row: calculate_individual_ortg({ 'pts': row['pts'], 'ast': row['ast'], 'tov': row['tov'], 'fga': row['fga'], 'fta': row['fta'] }, team_stats), axis=1) return df.sort_values('ortg', ascending=False) # Example usage players_df = pd.DataFrame({ 'player': ['Stephen Curry', 'Klay Thompson', 'Draymond Green'], 'pts': [32, 22, 8], 'ast': [6, 3, 7], 'tov': [3, 2, 3], 'fga': [20, 16, 6], 'fta': [6, 3, 2] }) result = calculate_season_ortg(players_df) print(result[['player', 'ortg']]) ``` --- ## R Implementation ### Basic ORtg Calculation ```r # Team Offensive Rating calculate_team_ortg <- function(points, fga, fta, orb, tov) { possessions <- fga + 0.44 * fta - orb + tov ortg <- (points / possessions) * 100 return(round(ortg, 2)) } # Example usage team_ortg <- calculate_team_ortg( points = 115, fga = 90, fta = 22, orb = 12, tov = 15 ) cat("Team Offensive Rating:", team_ortg, "\n") # Output: Team Offensive Rating: 114.61 ``` ### Individual ORtg with Data Frame ```r library(dplyr) calculate_individual_ortg <- function(player_pts, player_ast, player_tov, player_fga, player_fta, team_pts, team_ast, team_fga, team_fta, team_tov) { # Points produced points_produced <- ( player_pts + 0.7 * player_ast * (team_pts / team_ast) - player_tov * (team_pts / (team_fga + 0.44 * team_fta + team_tov)) ) # Possessions used possessions <- player_fga + 0.44 * player_fta + player_tov # Calculate ORtg ortg <- ifelse(possessions > 0, 100 * (points_produced / possessions), 0) return(round(ortg, 2)) } # Example with data frame players <- data.frame( name = c("Player A", "Player B", "Player C"), pts = c(28, 18, 12), ast = c(6, 8, 3), tov = c(3, 4, 2), fga = c(18, 14, 9), fta = c(8, 4, 2) ) # Team totals team_totals <- list( pts = 112, ast = 24, fga = 88, fta = 24, tov = 14 ) # Calculate ORtg for each player players <- players %>% mutate( ortg = calculate_individual_ortg( pts, ast, tov, fga, fta, team_totals$pts, team_totals$ast, team_totals$fga, team_totals$fta, team_totals$tov ) ) %>% arrange(desc(ortg)) print(players[, c("name", "ortg")]) ``` ### ORtg Analysis and Visualization ```r library(ggplot2) library(dplyr) # Analyze ORtg distribution and relationship with TS% analyze_ortg <- function(player_stats) { # Calculate TS% player_stats <- player_stats %>% mutate( ts_pct = pts / (2 * (fga + 0.44 * fta)) * 100 ) # Create visualization p1 <- ggplot(player_stats, aes(x = ortg)) + geom_histogram(binwidth = 5, fill = "steelblue", color = "black", alpha = 0.7) + geom_vline(aes(xintercept = mean(ortg)), color = "red", linetype = "dashed", size = 1) + labs( title = "Distribution of Offensive Rating", x = "Offensive Rating", y = "Frequency" ) + theme_minimal() # ORtg vs TS% scatter plot p2 <- ggplot(player_stats, aes(x = ts_pct, y = ortg)) + geom_point(alpha = 0.6, size = 3, color = "darkblue") + geom_smooth(method = "lm", color = "red", se = TRUE) + labs( title = "Offensive Rating vs True Shooting %", x = "True Shooting %", y = "Offensive Rating" ) + theme_minimal() # Calculate correlation correlation <- cor(player_stats$ortg, player_stats$ts_pct, use = "complete.obs") # Print summary statistics cat("\nOffensive Rating Summary:\n") cat("Mean:", round(mean(player_stats$ortg), 2), "\n") cat("Median:", round(median(player_stats$ortg), 2), "\n") cat("SD:", round(sd(player_stats$ortg), 2), "\n") cat("\nCorrelation with TS%:", round(correlation, 3), "\n") return(list( histogram = p1, scatter = p2, correlation = correlation, summary = summary(player_stats$ortg) )) } # Example usage sample_stats <- data.frame( player = paste("Player", 1:20), pts = rnorm(20, 20, 8), fga = rnorm(20, 15, 5), fta = rnorm(20, 5, 2), ortg = rnorm(20, 112, 8) ) results <- analyze_ortg(sample_stats) print(results$histogram) print(results$scatter) ``` ### Advanced ORtg Calculation with Dean Oliver's Method ```r # Comprehensive ORtg calculation (simplified Oliver method) calculate_oliver_ortg <- function(player_data, team_data) { # Extract player stats fgm <- player_data$fgm fga <- player_data$fga ftm <- player_data$ftm fta <- player_data$fta ast <- player_data$ast tov <- player_data$tov orb <- player_data$orb pts <- player_data$pts # Extract team stats team_fgm <- team_data$fgm team_fga <- team_data$fga team_ftm <- team_data$ftm team_fta <- team_data$fta team_pts <- team_data$pts team_orb <- team_data$orb # Calculate team ORB% team_orb_pct <- team_orb / (team_orb + team_data$drb) # qAST (assist adjustment factor) q_ast <- ((fgm * (pts - ftm)) / (2 * fga * pts)) * 0.5 # FG Part fg_part <- fgm * (1 - 0.5 * ((pts - ftm) / (2 * fga)) * q_ast) # AST Part ast_part <- 0.5 * (((team_pts - team_ftm) - (pts - ftm)) / (2 * (team_fga - fga))) * ast # FT Part ft_part <- (1 - (1 - (ftm / fta))^2) * 0.4 * fta # Scoring possessions sc_poss <- fg_part + ast_part + ft_part # Missed FG possessions fgm_part <- (fga - fgm) * (1 - 1.07 * team_orb_pct) # Missed FT possessions ftm_part <- ((1 - (ftm / fta))^2) * 0.4 * fta # Total possessions total_poss <- sc_poss + fgm_part + ftm_part + tov # Calculate team ORtg team_ortg <- (team_pts / team_data$poss) * 100 # Points produced (simplified) points_produced <- pts + 0.5 * ast * (team_pts / team_data$ast) - tov * (team_ortg / 100) # Individual ORtg ortg <- 100 * (points_produced / total_poss) return(round(ortg, 2)) } # Example player <- list( fgm = 10, fga = 18, ftm = 6, fta = 8, ast = 6, tov = 3, orb = 2, pts = 28 ) team <- list( fgm = 40, fga = 88, ftm = 18, fta = 24, pts = 112, orb = 10, drb = 35, ast = 24, poss = 100 ) oliver_ortg <- calculate_oliver_ortg(player, team) cat("Individual ORtg (Oliver Method):", oliver_ortg, "\n") ``` --- ## Summary Offensive Rating is a sophisticated metric that provides crucial insights into offensive performance: 1. **Pace-Adjusted**: Allows fair comparisons across different eras and playing styles 2. **Comprehensive**: Accounts for scoring, playmaking, and ball security 3. **Context-Dependent**: Must be interpreted with usage rate, role, and team quality 4. **Correlated with TS%**: Shooting efficiency is foundational but not the complete picture 5. **Historical Value**: Identifies the most efficient offensive players and teams in basketball history ORtg remains one of the most valuable tools for evaluating offensive impact in modern basketball analytics.

Discussion

Have questions or feedback? Join our community discussion on Discord or GitHub Discussions.