Offensive Rating (ORtg)
Beginner
10 min read
1 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.
Table of Contents
Related Topics
Quick Actions