Chapter 34 Intermediate ~50 min read

Help Defense and Rotations

Analyzing help defense, closeouts, and defensive rotations that don't appear in individual matchup statistics.

The Help Defense Imperative

Modern NBA offense is designed to exploit individual matchups and create advantages. When a defender is beaten off the dribble or a big man can't recover from a pick-and-roll, help defense must rotate to prevent easy baskets. This help defense—and the rotations that follow—often determine defensive outcomes more than initial individual matchups.

Help defense represents the ultimate team defensive skill because it requires reading developing plays, making split-second decisions, and trusting teammates to rotate behind. Great help defenders can erase teammate mistakes; poor help defenders allow individual breakdowns to become scoring opportunities.

Measuring Help Defense Impact

Tracking data enables some help defense measurement through positioning analysis. We can identify when defenders leave their assignments to help, measure the outcomes of those help decisions, and track whether subsequent rotations prevent open shots.

However, help defense remains among the most difficult contributions to quantify. The decision to help involves tradeoffs—leaving your assignment to contest at the rim means someone becomes open on the perimeter. Good help decisions prevent points efficiently; bad ones trade rim attempts for open threes. Evaluating decision quality requires understanding the full court context, not just the individual action.

Closeout Analysis

Closeouts—recovering to shooters after helping or after defensive rotations—represent critical perimeter defense moments. A good closeout arrives in time to contest without fouling and maintains balance to react if the shooter drives. A poor closeout arrives late, out of control, or not at all.

Tracking data measures closeout distance (how far the defender traveled) and speed, as well as outcomes (shot contested, drive, foul). These metrics help identify which players execute closeouts effectively and which leave shooters too open or foul too frequently.

Rotation Quality

When help defense creates an open player somewhere else on the court, the next defender must rotate to cover that player. This rotation chain can involve all five defenders, each covering for someone else's movement. Teams that rotate quickly and accurately give up few wide-open shots even after being initially beaten.

Rotation quality is extremely difficult to measure because it involves anticipation and positioning rather than discrete events. Plus-minus approaches capture some team rotation quality (teams that rotate well should have better defensive ratings with those players on court), but attributing rotation quality to individuals is challenging.

Help Defense Specialists

Certain players provide outsized value through help defense despite modest individual matchup statistics. These players read plays exceptionally well, position themselves to help without fully vacating assignments, and make smart decisions about when to commit and when to stay home. Draymond Green represents the archetype: exceptional at help defense and rotation but not a traditional rim protector or on-ball defender.

Identifying help defense specialists requires either sophisticated tracking analysis or careful film study. Traditional metrics undervalue these players because their contributions don't generate individual defensive statistics.

Implementation in R

# Help defense analysis
library(tidyverse)

analyze_help_defense <- function(help_data) {
  help_data %>%
    group_by(helper_id, helper_name) %>%
    summarise(
      help_instances = n(),

      # Help quality
      help_arrived_in_time_pct = mean(help_in_time) * 100,
      avg_help_distance = mean(help_distance),
      avg_help_arrival_time = mean(time_to_help),

      # Outcome after help
      pts_allowed_after_help = sum(points_after_help),
      ppp_after_help = round(pts_allowed_after_help / help_instances, 2),

      # Recovery to original man
      recovery_success_pct = mean(recovered_to_man) * 100,

      .groups = "drop"
    )
}

help_defense <- read_csv("help_defense_tracking.csv")
help_analysis <- analyze_help_defense(help_defense)

# Best help defenders
best_helpers <- help_analysis %>%
  filter(help_instances >= 50) %>%
  arrange(desc(help_arrived_in_time_pct)) %>%
  select(helper_name, help_instances, help_arrived_in_time_pct,
         ppp_after_help, recovery_success_pct) %>%
  head(15)

print(best_helpers)
# Rotation analysis
library(tidyverse)

analyze_rotations <- function(rotation_data) {
  rotation_data %>%
    group_by(rotating_defender_id, rotating_defender_name) %>%
    summarise(
      rotations = n(),

      # Rotation types
      closeout_rotations = sum(rotation_type == "closeout"),
      gap_rotations = sum(rotation_type == "gap"),
      tag_rotations = sum(rotation_type == "tag"),

      # Quality
      on_time_pct = mean(rotation_on_time) * 100,
      avg_rotation_distance = mean(rotation_distance),

      # Outcomes
      open_shot_allowed_pct = mean(resulted_in_open_shot) * 100,
      pts_allowed_on_rotation = sum(points_after_rotation),

      .groups = "drop"
    )
}

rotations <- read_csv("defensive_rotations.csv")
rotation_analysis <- analyze_rotations(rotations)

# Best rotation defenders
best_rotators <- rotation_analysis %>%
  filter(rotations >= 50) %>%
  arrange(desc(on_time_pct)) %>%
  select(rotating_defender_name, rotations, on_time_pct,
         open_shot_allowed_pct) %>%
  head(15)

print(best_rotators)

Implementation in R

# Help defense analysis
library(tidyverse)

analyze_help_defense <- function(help_data) {
  help_data %>%
    group_by(helper_id, helper_name) %>%
    summarise(
      help_instances = n(),

      # Help quality
      help_arrived_in_time_pct = mean(help_in_time) * 100,
      avg_help_distance = mean(help_distance),
      avg_help_arrival_time = mean(time_to_help),

      # Outcome after help
      pts_allowed_after_help = sum(points_after_help),
      ppp_after_help = round(pts_allowed_after_help / help_instances, 2),

      # Recovery to original man
      recovery_success_pct = mean(recovered_to_man) * 100,

      .groups = "drop"
    )
}

help_defense <- read_csv("help_defense_tracking.csv")
help_analysis <- analyze_help_defense(help_defense)

# Best help defenders
best_helpers <- help_analysis %>%
  filter(help_instances >= 50) %>%
  arrange(desc(help_arrived_in_time_pct)) %>%
  select(helper_name, help_instances, help_arrived_in_time_pct,
         ppp_after_help, recovery_success_pct) %>%
  head(15)

print(best_helpers)
# Rotation analysis
library(tidyverse)

analyze_rotations <- function(rotation_data) {
  rotation_data %>%
    group_by(rotating_defender_id, rotating_defender_name) %>%
    summarise(
      rotations = n(),

      # Rotation types
      closeout_rotations = sum(rotation_type == "closeout"),
      gap_rotations = sum(rotation_type == "gap"),
      tag_rotations = sum(rotation_type == "tag"),

      # Quality
      on_time_pct = mean(rotation_on_time) * 100,
      avg_rotation_distance = mean(rotation_distance),

      # Outcomes
      open_shot_allowed_pct = mean(resulted_in_open_shot) * 100,
      pts_allowed_on_rotation = sum(points_after_rotation),

      .groups = "drop"
    )
}

rotations <- read_csv("defensive_rotations.csv")
rotation_analysis <- analyze_rotations(rotations)

# Best rotation defenders
best_rotators <- rotation_analysis %>%
  filter(rotations >= 50) %>%
  arrange(desc(on_time_pct)) %>%
  select(rotating_defender_name, rotations, on_time_pct,
         open_shot_allowed_pct) %>%
  head(15)

print(best_rotators)

Implementation in Python

# Help defense analysis
import pandas as pd

def analyze_help_defense(help_data):
    """Analyze help defense effectiveness"""
    analysis = help_data.groupby(["helper_id", "helper_name"]).agg({
        "play_id": "count",
        "help_in_time": "mean",
        "help_distance": "mean",
        "time_to_help": "mean",
        "points_after_help": "sum",
        "recovered_to_man": "mean"
    }).rename(columns={"play_id": "help_instances"}).reset_index()

    analysis["help_in_time_pct"] = (analysis["help_in_time"] * 100).round(1)
    analysis["ppp_after_help"] = (
        analysis["points_after_help"] / analysis["help_instances"]
    ).round(2)
    analysis["recovery_success_pct"] = (analysis["recovered_to_man"] * 100).round(1)

    return analysis

help_defense = pd.read_csv("help_defense_tracking.csv")
help_analysis = analyze_help_defense(help_defense)

# Best help defenders
best_helpers = help_analysis[help_analysis["help_instances"] >= 50].nlargest(
    15, "help_in_time_pct"
)
print(best_helpers)
# Rotation analysis
import pandas as pd

def analyze_rotations(rotation_data):
    """Analyze defensive rotation effectiveness"""
    analysis = rotation_data.groupby(
        ["rotating_defender_id", "rotating_defender_name"]
    ).agg({
        "play_id": "count",
        "rotation_type": lambda x: x.value_counts().to_dict(),
        "rotation_on_time": "mean",
        "rotation_distance": "mean",
        "resulted_in_open_shot": "mean",
        "points_after_rotation": "sum"
    }).rename(columns={"play_id": "rotations"}).reset_index()

    analysis["on_time_pct"] = (analysis["rotation_on_time"] * 100).round(1)
    analysis["open_shot_allowed_pct"] = (
        analysis["resulted_in_open_shot"] * 100
    ).round(1)

    return analysis

rotations = pd.read_csv("defensive_rotations.csv")
rotation_analysis = analyze_rotations(rotations)
print(rotation_analysis.nlargest(15, "on_time_pct"))
Chapter Summary

You've completed Chapter 34: Help Defense and Rotations.