Chapter 8 Beginner ~25 min read 5 sections

Rebounding and Possession Metrics

Possessions form the fundamental unit of basketball analysis—every point scored requires a possession to generate it. This chapter examines how possessions are estimated, how rebounding affects possession count, and how rate statistics enable fair comparison across different contexts.

The Importance of Possessions

Possessions form the fundamental currency of basketball, the atomic unit from which all analysis builds. Every point scored requires a possession to generate it. Teams that create more possessions through rebounding and forcing turnovers gain more opportunities to score. Teams that use their possessions efficiently maximize the points they extract from each opportunity. Understanding possessions enables meaningful comparison across different game contexts and playing styles.

Pace—the number of possessions per game—varies substantially across teams and eras. Some teams play quickly, pushing the ball up court and shooting early in the shot clock. Others prefer deliberate half-court offense, using more clock on each possession. A team averaging 115 points in a 110-possession game differs meaningfully from one averaging the same points in 95 possessions. Per-possession analysis accounts for these differences.

The historical variation in pace provides essential context for era-adjusted analysis. The late 1960s and early 1970s featured dramatically higher pace than today, with some teams averaging over 120 possessions per game. Modern analytics-influenced basketball has seen pace increase from historical lows, but not to those early heights. Comparing statistics across eras without pace adjustment produces misleading conclusions about relative performance.

Estimating Possessions

Possessions cannot be counted directly from traditional box scores but must be estimated from available statistics. The fundamental insight is that possessions end in one of four ways: made field goals, missed field goals that become defensive rebounds, turnovers, or trips to the free throw line that do not result in offensive rebounds. By accounting for these endings, we can estimate total possessions.

The basic possession formula takes the form: Possessions ≈ FGA - OREB + TO + (0.44 × FTA). Field goal attempts represent shooting possessions. Offensive rebounds do not create new possessions but rather extend existing ones, so we subtract them. Turnovers end possessions without shots. Free throw attempts represent end-of-possession events at a rate approximated by the 0.44 coefficient, accounting for the various situations that lead to free throws.

This estimation produces possession counts accurate to within a few percentage points of true values. The approximations—particularly the 0.44 free throw coefficient—introduce small errors, but these are generally inconsequential for analytical purposes. More sophisticated formulas exist that account for team rebounds and other factors, but the basic version suffices for most applications.

Possessions should be roughly equal for both teams in any game, differing only due to end-of-period effects and possession arrow situations. This equality provides a check on possession calculations—if your estimates differ substantially between teams, an error has occurred. The possession concept enables comparison of what teams do with equal opportunity.

Offensive and Defensive Rating

Offensive rating measures points scored per 100 possessions, translating raw scoring into efficiency terms. A team scoring 110 points on 100 possessions has a 110 offensive rating—they average 1.1 points per possession. This normalization accounts for pace differences, enabling meaningful comparison between slow-paced defensive teams and fast-paced offensive teams.

Defensive rating applies the same logic to points allowed—opponents' points per 100 possessions. A team holding opponents to 102 points per 100 possessions has a 102 defensive rating. Better defense produces lower ratings, creating occasional confusion since lower is better for defense while higher is better for offense. This convention aligns with intuitive interpretation of points allowed.

Net rating combines offensive and defensive efficiency by subtraction: Net Rating = ORtg - DRtg. A team with 112 offensive rating and 105 defensive rating has a +7 net rating, indicating they outscore opponents by about 7 points per 100 possessions. Net rating correlates strongly with winning percentage and provides the best single predictor of team quality available from game-level data.

Individual offensive and defensive ratings estimate player efficiency, though with greater uncertainty than team ratings. Various methods exist for calculating individual ratings, differing in how they allocate credit for team outcomes to individuals. Dean Oliver's original approach uses possession counts and points produced; more modern methods incorporate plus-minus data. Understanding that individual ratings are estimates, not measurements, prevents overconfident interpretation.

Rebounding Rate Statistics

Rebounding rates measure the percentage of available rebounds a player or team secures while on the court. Offensive rebounding rate estimates what percentage of the team's missed shots the player rebounds. Defensive rebounding rate estimates what percentage of opponent missed shots the player rebounds. These rates account for opportunity differences that raw totals ignore.

The calculation divides individual rebounds by estimated available rebounds: OREB% = OREB / (OREB + Opp DREB). This team-based calculation estimates what percentage of available offensive rebounds the player secured. Individual context matters—a player's rebounding rate depends on teammates' rebounding tendencies and opponents' shot locations, not just individual ability.

Team rebounding rates influence possession counts directly. Teams that secure high percentages of offensive rebounds extend possessions and reduce opponent possession counts. Teams that secure high percentages of defensive rebounds limit opponent second chances and convert defensive stops into offensive opportunities. The overall rebounding differential affects the possession distribution between teams.

Interpreting rebounding rates requires understanding the relationship between individual and team performance. A center with high defensive rebounding rate may be grabbing boards that teammates would have secured in his absence, providing less marginal value than the rate suggests. Tracking data that identifies contested versus uncontested rebounds provides additional context for evaluating rebounding contribution.

Turnover Analysis

Turnovers end possessions without shot attempts, representing pure waste of offensive opportunity. Turnover rate—turnovers per 100 possessions or as a percentage of possessions—measures how well teams or players protect the ball. Lower turnover rates preserve more possessions for scoring attempts and prevent easy transition opportunities for opponents.

The cost of turnovers extends beyond the lost possession itself. Many turnovers create transition opportunities where defenses have not set up, enabling easy baskets. Steals in particular often lead to fast break points. The full cost of a turnover includes both the lost possession and the expected points the opponent scores from the transition opportunity, which typically exceeds their half-court efficiency.

Context matters for turnover evaluation as in other areas. Players who handle the ball more face more opportunities for turnovers—point guards average higher turnover rates than low-usage wings, but this reflects role rather than skill. Assist-to-turnover ratio provides one context, though this ratio favors cautious players over aggressive playmakers. Usage-adjusted turnover rates better isolate ball-handling skill from opportunity.

Team turnover rate represents one of Dean Oliver's Four Factors, reflecting its importance for offensive efficiency. Teams that minimize turnovers maximize their shot opportunities and limit opponent transition chances. Strategic decisions about shot selection, passing risk, and pace should consider turnover implications alongside direct scoring considerations.

Implementation in R

# Calculate rebounding percentages
library(tidyverse)

calculate_rebound_rates <- function(player_stats, team_stats) {
  player_stats %>%
    left_join(team_stats, by = "team_id") %>%
    mutate(
      # Individual rebounding rates while on court
      orb_pct = round(100 * (oreb * (team_min / 5)) /
                      (min * (team_oreb + opp_dreb)), 1),
      drb_pct = round(100 * (dreb * (team_min / 5)) /
                      (min * (team_dreb + opp_oreb)), 1),
      trb_pct = round(100 * (reb * (team_min / 5)) /
                      (min * (team_reb + opp_reb)), 1)
    )
}

# Load data
players <- read_csv("player_stats.csv")
teams <- read_csv("team_stats.csv")

rebound_rates <- calculate_rebound_rates(players, teams)

# Top rebounders by TRB%
top_rebounders <- rebound_rates %>%
  filter(min >= 1000) %>%
  arrange(desc(trb_pct)) %>%
  select(player_name, reb, orb_pct, drb_pct, trb_pct) %>%
  head(15)

print(top_rebounders)
# Rebounding opportunity analysis
library(tidyverse)

analyze_rebound_opportunities <- function(shot_data) {
  shot_data %>%
    filter(shot_made == FALSE) %>%
    group_by(shot_zone) %>%
    summarise(
      missed_shots = n(),
      offensive_rebounds = sum(offensive_rebound),
      orb_rate = round(offensive_rebounds / missed_shots, 3),
      avg_rebound_distance = mean(rebound_distance, na.rm = TRUE)
    ) %>%
    arrange(desc(orb_rate))
}

shot_rebound_data <- read_csv("shots_with_rebounds.csv")
rebound_analysis <- analyze_rebound_opportunities(shot_rebound_data)
print(rebound_analysis)

Implementation in Python

# Calculate rebounding percentages
import pandas as pd

def calculate_rebound_rates(player_stats, team_stats):
    merged = player_stats.merge(team_stats, on="team_id")

    # Individual rebounding rates while on court
    merged["orb_pct"] = (
        100 * (merged["oreb"] * (merged["team_min"] / 5)) /
        (merged["min"] * (merged["team_oreb"] + merged["opp_dreb"]))
    ).round(1)

    merged["drb_pct"] = (
        100 * (merged["dreb"] * (merged["team_min"] / 5)) /
        (merged["min"] * (merged["team_dreb"] + merged["opp_oreb"]))
    ).round(1)

    merged["trb_pct"] = (
        100 * (merged["reb"] * (merged["team_min"] / 5)) /
        (merged["min"] * (merged["team_reb"] + merged["opp_reb"]))
    ).round(1)

    return merged

# Load data
players = pd.read_csv("player_stats.csv")
teams = pd.read_csv("team_stats.csv")

rebound_rates = calculate_rebound_rates(players, teams)

# Top rebounders by TRB%
top_rebounders = rebound_rates[rebound_rates["min"] >= 1000].nlargest(
    15, "trb_pct"
)[["player_name", "reb", "orb_pct", "drb_pct", "trb_pct"]]

print(top_rebounders)
# Rebounding opportunity analysis
import pandas as pd

def analyze_rebound_opportunities(shot_data):
    missed = shot_data[shot_data["shot_made"] == False]

    analysis = missed.groupby("shot_zone").agg({
        "shot_id": "count",
        "offensive_rebound": "sum",
        "rebound_distance": "mean"
    }).rename(columns={
        "shot_id": "missed_shots",
        "offensive_rebound": "offensive_rebounds",
        "rebound_distance": "avg_rebound_distance"
    })

    analysis["orb_rate"] = (
        analysis["offensive_rebounds"] / analysis["missed_shots"]
    ).round(3)

    return analysis.sort_values("orb_rate", ascending=False)

shot_rebound_data = pd.read_csv("shots_with_rebounds.csv")
rebound_analysis = analyze_rebound_opportunities(shot_rebound_data)
print(rebound_analysis)

Implementation in R

# Calculate rebounding percentages
library(tidyverse)

calculate_rebound_rates <- function(player_stats, team_stats) {
  player_stats %>%
    left_join(team_stats, by = "team_id") %>%
    mutate(
      # Individual rebounding rates while on court
      orb_pct = round(100 * (oreb * (team_min / 5)) /
                      (min * (team_oreb + opp_dreb)), 1),
      drb_pct = round(100 * (dreb * (team_min / 5)) /
                      (min * (team_dreb + opp_oreb)), 1),
      trb_pct = round(100 * (reb * (team_min / 5)) /
                      (min * (team_reb + opp_reb)), 1)
    )
}

# Load data
players <- read_csv("player_stats.csv")
teams <- read_csv("team_stats.csv")

rebound_rates <- calculate_rebound_rates(players, teams)

# Top rebounders by TRB%
top_rebounders <- rebound_rates %>%
  filter(min >= 1000) %>%
  arrange(desc(trb_pct)) %>%
  select(player_name, reb, orb_pct, drb_pct, trb_pct) %>%
  head(15)

print(top_rebounders)
# Rebounding opportunity analysis
library(tidyverse)

analyze_rebound_opportunities <- function(shot_data) {
  shot_data %>%
    filter(shot_made == FALSE) %>%
    group_by(shot_zone) %>%
    summarise(
      missed_shots = n(),
      offensive_rebounds = sum(offensive_rebound),
      orb_rate = round(offensive_rebounds / missed_shots, 3),
      avg_rebound_distance = mean(rebound_distance, na.rm = TRUE)
    ) %>%
    arrange(desc(orb_rate))
}

shot_rebound_data <- read_csv("shots_with_rebounds.csv")
rebound_analysis <- analyze_rebound_opportunities(shot_rebound_data)
print(rebound_analysis)

Implementation in Python

# Calculate rebounding percentages
import pandas as pd

def calculate_rebound_rates(player_stats, team_stats):
    merged = player_stats.merge(team_stats, on="team_id")

    # Individual rebounding rates while on court
    merged["orb_pct"] = (
        100 * (merged["oreb"] * (merged["team_min"] / 5)) /
        (merged["min"] * (merged["team_oreb"] + merged["opp_dreb"]))
    ).round(1)

    merged["drb_pct"] = (
        100 * (merged["dreb"] * (merged["team_min"] / 5)) /
        (merged["min"] * (merged["team_dreb"] + merged["opp_oreb"]))
    ).round(1)

    merged["trb_pct"] = (
        100 * (merged["reb"] * (merged["team_min"] / 5)) /
        (merged["min"] * (merged["team_reb"] + merged["opp_reb"]))
    ).round(1)

    return merged

# Load data
players = pd.read_csv("player_stats.csv")
teams = pd.read_csv("team_stats.csv")

rebound_rates = calculate_rebound_rates(players, teams)

# Top rebounders by TRB%
top_rebounders = rebound_rates[rebound_rates["min"] >= 1000].nlargest(
    15, "trb_pct"
)[["player_name", "reb", "orb_pct", "drb_pct", "trb_pct"]]

print(top_rebounders)
# Rebounding opportunity analysis
import pandas as pd

def analyze_rebound_opportunities(shot_data):
    missed = shot_data[shot_data["shot_made"] == False]

    analysis = missed.groupby("shot_zone").agg({
        "shot_id": "count",
        "offensive_rebound": "sum",
        "rebound_distance": "mean"
    }).rename(columns={
        "shot_id": "missed_shots",
        "offensive_rebound": "offensive_rebounds",
        "rebound_distance": "avg_rebound_distance"
    })

    analysis["orb_rate"] = (
        analysis["offensive_rebounds"] / analysis["missed_shots"]
    ).round(3)

    return analysis.sort_values("orb_rate", ascending=False)

shot_rebound_data = pd.read_csv("shots_with_rebounds.csv")
rebound_analysis = analyze_rebound_opportunities(shot_rebound_data)
print(rebound_analysis)
Chapter Summary

You've completed Chapter 8: Rebounding and Possession Metrics.