Measuring Offensive Excellence
Team offensive efficiency determines how many points a team generates per opportunity. While points per game captures raw output, Offensive Rating (ORtg)—points per 100 possessions—provides the pace-independent measure essential for meaningful comparison. Teams playing at different speeds generate different counting totals; ORtg reveals who uses possessions more effectively regardless of how many possessions they have.
Offensive Rating Calculation
def calculate_offensive_rating(team_stats):
"""Calculate team Offensive Rating"""
possessions = (team_stats['FGA'] + 0.44 * team_stats['FTA'] -
team_stats['OREB'] + team_stats['TOV'])
ortg = (team_stats['PTS'] / possessions) * 100
return round(ortg, 1)
The Four Factors of Offense
Dean Oliver's Four Factors decompose offensive efficiency into actionable components: Effective Field Goal Percentage (shot quality), Turnover Rate (ball security), Offensive Rebounding Rate (second chances), and Free Throw Rate (getting to the line). Elite offenses typically excel in multiple factors while maintaining at least average performance in others.
Shot Distribution and Quality
Modern offensive analysis examines not just shooting efficiency but shot distribution. Teams generating high frequencies of rim attempts and three-pointers while minimizing mid-range shots typically produce better offensive ratings. This "shot profile" analysis explains much of the variation in offensive efficiency across teams.
Pace and Style Considerations
While ORtg adjusts for pace when measuring efficiency, pace itself is a strategic choice with implications for total scoring. Fast-paced teams create more possessions and thus more total opportunities, which can compensate for slightly lower efficiency. Slow-paced teams with elite efficiency can achieve similar point totals with fewer possessions.
Implementation in R
# Calculate team offensive metrics
library(tidyverse)
calculate_team_offense <- function(team_stats) {
team_stats %>%
mutate(
# Offensive Rating
possessions = fga + 0.44 * fta - oreb + tov,
off_rtg = round(pts / possessions * 100, 1),
# Four Factors
efg_pct = round((fgm + 0.5 * fg3m) / fga * 100, 1),
tov_pct = round(tov / possessions * 100, 1),
orb_pct = round(oreb / (oreb + opp_dreb) * 100, 1),
ft_rate = round(ftm / fga * 100, 1),
# Shot profile
rim_rate = round(rim_fga / fga * 100, 1),
three_rate = round(fg3a / fga * 100, 1),
mid_rate = round(mid_fga / fga * 100, 1)
) %>%
arrange(desc(off_rtg))
}
team_stats <- read_csv("team_stats.csv")
team_offense <- calculate_team_offense(team_stats)
# Top offenses
top_offenses <- team_offense %>%
select(team_name, off_rtg, efg_pct, tov_pct, orb_pct, ft_rate) %>%
head(10)
print(top_offenses)
Implementation in R
# Calculate team offensive metrics
library(tidyverse)
calculate_team_offense <- function(team_stats) {
team_stats %>%
mutate(
# Offensive Rating
possessions = fga + 0.44 * fta - oreb + tov,
off_rtg = round(pts / possessions * 100, 1),
# Four Factors
efg_pct = round((fgm + 0.5 * fg3m) / fga * 100, 1),
tov_pct = round(tov / possessions * 100, 1),
orb_pct = round(oreb / (oreb + opp_dreb) * 100, 1),
ft_rate = round(ftm / fga * 100, 1),
# Shot profile
rim_rate = round(rim_fga / fga * 100, 1),
three_rate = round(fg3a / fga * 100, 1),
mid_rate = round(mid_fga / fga * 100, 1)
) %>%
arrange(desc(off_rtg))
}
team_stats <- read_csv("team_stats.csv")
team_offense <- calculate_team_offense(team_stats)
# Top offenses
top_offenses <- team_offense %>%
select(team_name, off_rtg, efg_pct, tov_pct, orb_pct, ft_rate) %>%
head(10)
print(top_offenses)