Court Region Analysis

Beginner 10 min read 1 views Nov 27, 2025

Court Region Analysis in Basketball

Court region analysis has revolutionized basketball strategy by quantifying the value of shots taken from different areas of the floor. Understanding spatial dynamics, shot efficiency by zone, and the evolution of regional shot selection provides critical insights for both offensive schemes and defensive strategies. Modern analytics have fundamentally reshaped how teams approach shot selection, leading to the "three-point revolution" and the dramatic decline of mid-range attempts.

NBA Court Zones and Classifications

Standard Court Regions

The NBA court is typically divided into several key zones for analytical purposes:

  • Restricted Area (0-4 feet): The area directly under the basket within the 4-foot arc, representing the highest-value shots in basketball
  • Paint (Non-RA, 4-8 feet): The painted area excluding the restricted zone, including short-range hooks and floaters
  • Short Mid-Range (8-16 feet): The zone between the paint and the free-throw line extended
  • Long Mid-Range (16 feet to 3PT line): The area from the free-throw line extended to the three-point arc
  • Corner Three (0-2 feet from sideline): Three-point shots from the corners, the shortest three-point distance at 22 feet
  • Above-the-Break Three (Center): Three-point shots from the top of the arc, 23.75 feet from the basket
  • Wing Three: Three-point shots between corner and top-of-key regions
  • Backcourt: Shots beyond half-court (rare, typically end-of-quarter attempts)

Shot Value by Region (2023-24 NBA Season)

Region FG% Points Per Attempt Frequency Value Rank
Restricted Area 64.5% 1.29 30.2% 1
Corner Three 39.1% 1.17 8.7% 2
Above-Break Three 36.2% 1.09 30.5% 3
Paint (Non-RA) 45.8% 0.92 11.3% 4
Short Mid-Range 42.1% 0.84 9.2% 5
Long Mid-Range 39.8% 0.80 10.1% 6

Key Insight: The dramatic value gap between three-point shots (1.09-1.17 PPA) and mid-range attempts (0.80-0.84 PPA) has driven the modern NBA's emphasis on rim attacks and three-point shooting.

Python Analysis: Zone Efficiency Using nba_api

Analyzing Shot Distribution and Efficiency by Court Region


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from nba_api.stats.endpoints import (
    LeaguePlayerOnDetails,
    ShotChartDetail,
    LeagueDashTeamStats
)
from nba_api.stats.static import teams
from matplotlib.patches import Circle, Rectangle, Arc, Polygon
import warnings
warnings.filterwarnings('ignore')

def get_team_shot_chart(team_id, season='2023-24'):
    """
    Retrieve shot chart data for a team by zone
    """
    shot_chart = ShotChartDetail(
        team_id=team_id,
        player_id=0,  # 0 for entire team
        season_nullable=season,
        season_type_all_star='Regular Season',
        context_measure_simple='FGA'
    )

    shots_df = shot_chart.get_data_frames()[0]
    return shots_df

def classify_shot_zone(row):
    """
    Classify shots into analytical zones based on coordinates
    """
    x, y = row['LOC_X'], row['LOC_Y']
    distance = np.sqrt(x**2 + y**2) / 10  # Convert to feet

    # Restricted Area
    if distance <= 4:
        return 'Restricted Area'

    # Paint (Non-RA)
    elif distance <= 8:
        return 'Paint (Non-RA)'

    # Three-point shots
    elif row['SHOT_TYPE'] == '3PT Field Goal':
        # Corner three (within 2 feet of sideline)
        if abs(x) > 220:  # 22 feet from center
            return 'Corner Three'
        else:
            return 'Above-Break Three'

    # Mid-range
    else:
        if distance < 16:
            return 'Short Mid-Range'
        else:
            return 'Long Mid-Range'

def analyze_zone_efficiency(shots_df):
    """
    Calculate efficiency metrics by zone
    """
    # Classify each shot
    shots_df['ZONE'] = shots_df.apply(classify_shot_zone, axis=1)

    # Calculate zone statistics
    zone_stats = shots_df.groupby('ZONE').agg({
        'SHOT_MADE_FLAG': ['sum', 'count', 'mean'],
        'SHOT_VALUE': 'first'
    }).reset_index()

    zone_stats.columns = ['Zone', 'Made', 'Attempts', 'FG_PCT', 'Shot_Value']
    zone_stats['Points_Per_Attempt'] = (
        zone_stats['Made'] * zone_stats['Shot_Value']
    ) / zone_stats['Attempts']
    zone_stats['Frequency'] = (
        zone_stats['Attempts'] / zone_stats['Attempts'].sum() * 100
    )

    return zone_stats.sort_values('Points_Per_Attempt', ascending=False)

def compare_team_zone_profiles(team_ids, season='2023-24'):
    """
    Compare shot distribution across teams
    """
    team_profiles = []

    for team_id in team_ids:
        shots = get_team_shot_chart(team_id, season)
        shots['ZONE'] = shots.apply(classify_shot_zone, axis=1)

        zone_dist = shots.groupby('ZONE').size()
        zone_pct = (zone_dist / zone_dist.sum() * 100).to_dict()
        zone_pct['team_id'] = team_id

        team_profiles.append(zone_pct)

    return pd.DataFrame(team_profiles)

def plot_zone_heatmap(shots_df):
    """
    Create a court heatmap showing shot frequency by region
    """
    fig, ax = plt.subplots(figsize=(12, 11))

    # Draw court
    draw_court(ax)

    # Create hexbin heatmap
    hexbin = ax.hexbin(
        shots_df['LOC_X'],
        shots_df['LOC_Y'],
        gridsize=25,
        cmap='YlOrRd',
        alpha=0.8,
        edgecolors='none'
    )

    plt.colorbar(hexbin, ax=ax, label='Shot Frequency')
    ax.set_xlim(-250, 250)
    ax.set_ylim(-50, 400)
    ax.set_aspect('equal')
    ax.set_title('Shot Distribution by Court Region', fontsize=16, fontweight='bold')

    plt.tight_layout()
    return fig

def draw_court(ax, color='black'):
    """
    Draw NBA court lines
    """
    # Hoop
    hoop = Circle((0, 0), radius=7.5, linewidth=2, color=color, fill=False)

    # Backboard
    backboard = Rectangle((-30, -7.5), 60, -1, linewidth=2, color=color)

    # Paint
    outer_box = Rectangle((-80, -47.5), 160, 190, linewidth=2, color=color, fill=False)
    inner_box = Rectangle((-60, -47.5), 120, 190, linewidth=2, color=color, fill=False)

    # Free throw top arc
    top_free_throw = Arc((0, 142.5), 120, 120, theta1=0, theta2=180,
                          linewidth=2, color=color, fill=False)
    bottom_free_throw = Arc((0, 142.5), 120, 120, theta1=180, theta2=0,
                             linewidth=2, color=color, linestyle='dashed')

    # Restricted area
    restricted = Arc((0, 0), 80, 80, theta1=0, theta2=180,
                     linewidth=2, color=color)

    # Three point line
    corner_three_a = Rectangle((-220, -47.5), 0, 140, linewidth=2, color=color)
    corner_three_b = Rectangle((220, -47.5), 0, 140, linewidth=2, color=color)
    three_arc = Arc((0, 0), 475, 475, theta1=22, theta2=158,
                    linewidth=2, color=color)

    # Center court
    center_outer_arc = Arc((0, 422.5), 120, 120, theta1=180, theta2=0,
                           linewidth=2, color=color)

    # Add elements to plot
    court_elements = [
        hoop, backboard, outer_box, inner_box,
        top_free_throw, bottom_free_throw, restricted,
        corner_three_a, corner_three_b, three_arc,
        center_outer_arc
    ]

    for element in court_elements:
        ax.add_patch(element)

    return ax

def analyze_zone_evolution(team_id, seasons):
    """
    Track how a team's shot selection has evolved across seasons
    """
    evolution_data = []

    for season in seasons:
        try:
            shots = get_team_shot_chart(team_id, season)
            shots['ZONE'] = shots.apply(classify_shot_zone, axis=1)

            zone_dist = shots.groupby('ZONE').size()
            zone_pct = (zone_dist / zone_dist.sum() * 100).to_dict()
            zone_pct['Season'] = season

            evolution_data.append(zone_pct)
        except:
            print(f"Data not available for {season}")

    evolution_df = pd.DataFrame(evolution_data)
    return evolution_df

def plot_zone_evolution(evolution_df):
    """
    Visualize the evolution of shot selection by zone
    """
    fig, ax = plt.subplots(figsize=(14, 8))

    zones_to_plot = [
        'Restricted Area', 'Above-Break Three',
        'Corner Three', 'Long Mid-Range', 'Short Mid-Range'
    ]

    for zone in zones_to_plot:
        if zone in evolution_df.columns:
            ax.plot(
                evolution_df['Season'],
                evolution_df[zone],
                marker='o',
                linewidth=2.5,
                label=zone
            )

    ax.set_xlabel('Season', fontsize=12, fontweight='bold')
    ax.set_ylabel('Frequency (%)', fontsize=12, fontweight='bold')
    ax.set_title('Evolution of Shot Selection by Court Region',
                 fontsize=16, fontweight='bold')
    ax.legend(loc='best', frameon=True, shadow=True)
    ax.grid(True, alpha=0.3)
    plt.xticks(rotation=45)
    plt.tight_layout()

    return fig

# Example usage
if __name__ == "__main__":
    # Get Boston Celtics data (team_id = 1610612738)
    celtics_shots = get_team_shot_chart(1610612738, '2023-24')

    # Analyze zone efficiency
    print("Zone Efficiency Analysis:")
    zone_stats = analyze_zone_efficiency(celtics_shots)
    print(zone_stats)
    print("\n")

    # Track evolution over multiple seasons
    seasons = ['2018-19', '2019-20', '2020-21', '2021-22', '2022-23', '2023-24']
    evolution = analyze_zone_evolution(1610612738, seasons)
    print("Shot Selection Evolution:")
    print(evolution)

    # Create visualizations
    heatmap_fig = plot_zone_heatmap(celtics_shots)
    heatmap_fig.savefig('celtics_shot_heatmap.png', dpi=300, bbox_inches='tight')

    evolution_fig = plot_zone_evolution(evolution)
    evolution_fig.savefig('celtics_zone_evolution.png', dpi=300, bbox_inches='tight')

    plt.show()

Analysis Capabilities:

  • Shot Classification: Automatically categorizes shots into analytical zones based on coordinates
  • Zone Efficiency: Calculates FG%, points per attempt, and frequency for each region
  • Visual Heatmaps: Creates court visualizations showing shot density by location
  • Temporal Analysis: Tracks evolution of shot selection across multiple seasons
  • Comparative Analysis: Compares zone profiles across different teams

R Analysis: Regional Shooting Data with hoopR

Shot Quality and Expected Value by Region


library(hoopR)
library(tidyverse)
library(ggplot2)
library(viridis)
library(scales)
library(patchwork)

# Load NBA play-by-play data
load_nba_pbp <- function(seasons) {
  pbp_data <- map_dfr(seasons, ~{
    hoopR::load_nba_pbp(season = .x)
  })
  return(pbp_data)
}

# Classify shot zones from play-by-play data
classify_shot_region <- function(shot_data) {
  shot_data %>%
    mutate(
      region = case_when(
        str_detect(text, "Driving Layup|Dunk|Layup") ~ "Restricted Area",
        str_detect(text, "Hook Shot|Floating Jump Shot") &
          coordinate_x <= 8 ~ "Paint (Non-RA)",
        shot_type == "3PT" & abs(coordinate_x) > 22 ~ "Corner Three",
        shot_type == "3PT" ~ "Above-Break Three",
        !shot_type == "3PT" & coordinate_y < 16 ~ "Short Mid-Range",
        !shot_type == "3PT" & coordinate_y >= 16 ~ "Long Mid-Range",
        TRUE ~ "Other"
      ),
      shot_value = if_else(shot_type == "3PT", 3, 2),
      points_scored = if_else(scoring_play == TRUE, shot_value, 0)
    )
}

# Analyze zone efficiency with confidence intervals
analyze_regional_efficiency <- function(pbp_data) {
  shot_data <- pbp_data %>%
    filter(type_text == "Field Goal Made" | type_text == "Field Goal Missed") %>%
    filter(!is.na(coordinate_x) & !is.na(coordinate_y))

  shot_data <- classify_shot_region(shot_data)

  regional_stats <- shot_data %>%
    group_by(region) %>%
    summarise(
      attempts = n(),
      makes = sum(scoring_play),
      fg_pct = mean(scoring_play),
      points_per_attempt = mean(points_scored),
      se = sqrt(fg_pct * (1 - fg_pct) / attempts),
      ci_lower = fg_pct - 1.96 * se,
      ci_upper = fg_pct + 1.96 * se,
      frequency = attempts / sum(shot_data$region != "Other") * 100
    ) %>%
    arrange(desc(points_per_attempt))

  return(regional_stats)
}

# Calculate expected points added by region
calculate_epa_by_region <- function(shot_data) {
  league_avg_ppa <- mean(shot_data$points_scored)

  epa_stats <- shot_data %>%
    group_by(region, athlete_id, athlete_display_name) %>%
    summarise(
      attempts = n(),
      actual_ppa = mean(points_scored),
      .groups = 'drop'
    ) %>%
    filter(attempts >= 50) %>%  # Minimum threshold
    mutate(
      epa = actual_ppa - league_avg_ppa,
      total_epa = epa * attempts
    ) %>%
    arrange(desc(total_epa))

  return(epa_stats)
}

# Analyze team zone profiles
team_zone_profile <- function(pbp_data, team_id) {
  shot_data <- pbp_data %>%
    filter(type_text == "Field Goal Made" | type_text == "Field Goal Missed") %>%
    filter(team_id == !!team_id) %>%
    filter(!is.na(coordinate_x) & !is.na(coordinate_y))

  shot_data <- classify_shot_region(shot_data)

  team_profile <- shot_data %>%
    group_by(region) %>%
    summarise(
      attempts = n(),
      fg_pct = mean(scoring_play),
      ppa = mean(points_scored),
      frequency = attempts / sum(shot_data$region != "Other") * 100
    )

  return(team_profile)
}

# Visualize zone efficiency with error bars
plot_zone_efficiency <- function(regional_stats) {
  p <- ggplot(regional_stats, aes(x = reorder(region, points_per_attempt),
                                   y = points_per_attempt)) +
    geom_bar(stat = "identity", aes(fill = points_per_attempt),
             alpha = 0.8, width = 0.7) +
    geom_errorbar(aes(ymin = ci_lower * if_else(region == "Corner Three" |
                                                 region == "Above-Break Three", 3, 2),
                      ymax = ci_upper * if_else(region == "Corner Three" |
                                                region == "Above-Break Three", 3, 2)),
                  width = 0.2, alpha = 0.6) +
    scale_fill_viridis(option = "plasma", direction = -1) +
    coord_flip() +
    labs(
      title = "Points Per Attempt by Court Region",
      subtitle = "NBA 2023-24 Season | Error bars show 95% confidence intervals",
      x = "Court Region",
      y = "Points Per Attempt",
      fill = "PPA"
    ) +
    theme_minimal(base_size = 14) +
    theme(
      plot.title = element_text(face = "bold", size = 16),
      plot.subtitle = element_text(color = "gray40"),
      legend.position = "right",
      panel.grid.major.y = element_blank()
    )

  return(p)
}

# Create shot frequency distribution
plot_zone_frequency <- function(regional_stats) {
  p <- ggplot(regional_stats, aes(x = reorder(region, frequency),
                                   y = frequency)) +
    geom_bar(stat = "identity", aes(fill = frequency),
             alpha = 0.8, width = 0.7) +
    geom_text(aes(label = sprintf("%.1f%%", frequency)),
              hjust = -0.2, size = 4, fontface = "bold") +
    scale_fill_viridis(option = "mako", direction = -1) +
    coord_flip() +
    ylim(0, max(regional_stats$frequency) * 1.15) +
    labs(
      title = "Shot Distribution by Court Region",
      subtitle = "Frequency of attempts from each zone",
      x = "Court Region",
      y = "Frequency (%)",
      fill = "Frequency"
    ) +
    theme_minimal(base_size = 14) +
    theme(
      plot.title = element_text(face = "bold", size = 16),
      plot.subtitle = element_text(color = "gray40"),
      legend.position = "none",
      panel.grid.major.y = element_blank()
    )

  return(p)
}

# Compare teams by zone preference
compare_team_zones <- function(pbp_data, team_ids, team_names) {
  team_comparisons <- map2_dfr(team_ids, team_names, ~{
    profile <- team_zone_profile(pbp_data, .x)
    profile$team <- .y
    return(profile)
  })

  p <- ggplot(team_comparisons,
              aes(x = region, y = frequency, fill = team)) +
    geom_bar(stat = "identity", position = "dodge", alpha = 0.8) +
    scale_fill_brewer(palette = "Set2") +
    coord_flip() +
    labs(
      title = "Team Shot Distribution Comparison",
      subtitle = "Zone preferences across different teams",
      x = "Court Region",
      y = "Frequency (%)",
      fill = "Team"
    ) +
    theme_minimal(base_size = 14) +
    theme(
      plot.title = element_text(face = "bold", size = 16),
      plot.subtitle = element_text(color = "gray40"),
      legend.position = "bottom"
    )

  return(p)
}

# Analyze zone efficiency evolution over time
analyze_temporal_zones <- function(seasons) {
  temporal_data <- map_dfr(seasons, ~{
    pbp <- load_nba_pbp(season = .x)

    shot_data <- pbp %>%
      filter(type_text == "Field Goal Made" | type_text == "Field Goal Missed") %>%
      filter(!is.na(coordinate_x) & !is.na(coordinate_y))

    shot_data <- classify_shot_region(shot_data)

    season_stats <- shot_data %>%
      group_by(region) %>%
      summarise(
        frequency = n() / sum(shot_data$region != "Other") * 100,
        ppa = mean(points_scored),
        .groups = 'drop'
      ) %>%
      mutate(season = .x)

    return(season_stats)
  })

  return(temporal_data)
}

# Visualize temporal evolution
plot_zone_evolution <- function(temporal_data) {
  key_zones <- c("Restricted Area", "Above-Break Three",
                 "Corner Three", "Long Mid-Range", "Short Mid-Range")

  plot_data <- temporal_data %>%
    filter(region %in% key_zones)

  p <- ggplot(plot_data, aes(x = season, y = frequency,
                             color = region, group = region)) +
    geom_line(size = 1.5, alpha = 0.8) +
    geom_point(size = 3, alpha = 0.9) +
    scale_color_viridis(discrete = TRUE, option = "turbo") +
    labs(
      title = "Evolution of Shot Selection by Court Region",
      subtitle = "Tracking the three-point revolution and mid-range decline",
      x = "Season",
      y = "Frequency (%)",
      color = "Region"
    ) +
    theme_minimal(base_size = 14) +
    theme(
      plot.title = element_text(face = "bold", size = 16),
      plot.subtitle = element_text(color = "gray40"),
      legend.position = "right",
      axis.text.x = element_text(angle = 45, hjust = 1)
    )

  return(p)
}

# Create efficiency vs frequency scatter plot
plot_efficiency_frequency <- function(regional_stats) {
  p <- ggplot(regional_stats, aes(x = frequency, y = points_per_attempt)) +
    geom_point(aes(size = attempts, color = region), alpha = 0.7) +
    geom_text(aes(label = region), vjust = -1, size = 3.5, fontface = "bold") +
    scale_color_viridis(discrete = TRUE, option = "plasma") +
    scale_size_continuous(range = c(5, 20), labels = comma) +
    labs(
      title = "Shot Efficiency vs. Frequency by Region",
      subtitle = "Bubble size represents total attempts",
      x = "Frequency (%)",
      y = "Points Per Attempt",
      color = "Region",
      size = "Attempts"
    ) +
    theme_minimal(base_size = 14) +
    theme(
      plot.title = element_text(face = "bold", size = 16),
      plot.subtitle = element_text(color = "gray40"),
      legend.position = "right"
    )

  return(p)
}

# Example usage
seasons <- c(2024)
pbp_data <- load_nba_pbp(seasons)

# Analyze regional efficiency
regional_stats <- analyze_regional_efficiency(pbp_data)
print(regional_stats)

# Create visualizations
efficiency_plot <- plot_zone_efficiency(regional_stats)
frequency_plot <- plot_zone_frequency(regional_stats)

# Combine plots
combined_plot <- efficiency_plot + frequency_plot
ggsave("zone_analysis_combined.png", combined_plot,
       width = 16, height = 8, dpi = 300)

# Temporal analysis
temporal_seasons <- c(2019, 2020, 2021, 2022, 2023, 2024)
temporal_data <- analyze_temporal_zones(temporal_seasons)
evolution_plot <- plot_zone_evolution(temporal_data)

ggsave("zone_evolution.png", evolution_plot,
       width = 14, height = 8, dpi = 300)

# Efficiency vs frequency
eff_freq_plot <- plot_efficiency_frequency(regional_stats)
ggsave("efficiency_frequency_scatter.png", eff_freq_plot,
       width = 12, height = 8, dpi = 300)

R Analysis Features:

  • Confidence Intervals: Statistical significance testing for zone efficiency
  • Expected Points Added: Calculates value above/below league average by region
  • Team Profiling: Compares shot distribution strategies across teams
  • Temporal Trends: Multi-season analysis of zone preference evolution
  • Advanced Visualizations: Publication-quality plots with error bars and annotations

Evolution of Shot Selection by Region

The Three-Point Revolution (2010-2024)

Key Trends Over Time:

2010-2014: Early Adoption
  • Three-point attempts: ~20 per game league-wide
  • Mid-range shots still dominated at ~35% of all attempts
  • Corner three frequency: 5.2% of shots
  • Traditional post-ups and mid-range remained staples
2015-2018: Rapid Transformation
  • Three-point attempts surge to ~30 per game
  • Golden State Warriors' championship success accelerates trend
  • Mid-range frequency drops to ~25% of attempts
  • Analytics departments gain influence over shot selection
  • Moreyball philosophy (rim + three) becomes mainstream
2019-2024: Modern Optimization
  • Three-point attempts exceed 35 per game
  • Mid-range attempts fall to historic lows (~19% of shots)
  • Restricted area attempts remain stable at ~30%
  • Corner three optimization: frequency increased to 8.7%
  • Emergence of "heliocentric" offenses maximizing star efficiency

Regional Frequency Changes (2010 vs 2024)

Region 2010-11 Frequency 2023-24 Frequency Change Trend Direction
Restricted Area 28.4% 30.2% +1.8% Stable/Slight Increase
Paint (Non-RA) 14.2% 11.3% -2.9% Moderate Decline
Short Mid-Range 18.5% 9.2% -9.3% Dramatic Decline
Long Mid-Range 20.1% 10.1% -10.0% Dramatic Decline
Corner Three 5.2% 8.7% +3.5% Significant Increase
Above-Break Three 13.6% 30.5% +16.9% Explosive Growth

Key Insight: The most dramatic shift has been the near-doubling of above-the-break three-point attempts, while combined mid-range frequency has fallen by nearly 20 percentage points. This represents the most significant tactical evolution in basketball history.

Strategic Implications of Regional Analysis

Offensive Strategy

Shot Selection Hierarchy

  1. Rim Attacks (Priority 1): Highest efficiency at 1.29 PPA, but defense concentrates here
  2. Corner Threes (Priority 2): Second-highest value, shortest three-point distance, requires ball movement
  3. Above-Break Threes (Priority 3): Volume option for elite shooters, 1.09 PPA
  4. Mid-Range (Situational): Used primarily in late-clock situations or by elite mid-range shooters

Spacing and Floor Balance

  • Five-Out Offense: All players threaten three-point line, maximizes driving lanes
  • Corner Specialist Positioning: Stationing shooters in corners creates optimal spacing
  • Drive-and-Kick Sequences: Collapsing defense on rim attempts creates open corner looks
  • Elbow Actions: Using high post to trigger defensive rotations, creating corner opportunities

Player Deployment by Zone Strength

  • Rim Threats: Centers and athletic wings operating in restricted area (Giannis, Zion)
  • Corner Snipers: High-percentage three-point shooters stationed in corners (PJ Tucker, Danny Green archetype)
  • Pull-Up Shooters: Guards creating above-break threes off dribble (Curry, Lillard)
  • Mid-Range Artists: Elite scorers maintaining efficiency from 15-22 feet (DeRozan, Durant, CP3)

Defensive Strategy

Rim Protection Priority

  • Paint Packing: Preventing high-value restricted area attempts
  • Drop Coverage: Protecting rim while conceding pull-up mid-range
  • Weak-Side Help: Rotating from corners to contest drives
  • Vertical Spacing: Contesting without fouling in restricted area

Three-Point Defense

  • Closeout Discipline: Contesting threes while preventing blow-bys
  • Corner Coverage: Never leaving corner shooters unattended
  • Switch Everything: Preventing open threes through seamless switching
  • No-Middle Defense: Forcing drivers to baseline, away from help and corner kick-outs

The Mid-Range Tradeoff

Modern defensive schemes intentionally concede mid-range attempts to elite scorers, accepting 0.80-0.84 PPA rather than risk 1.09-1.29 PPA from threes and rim attempts. This represents a calculated value tradeoff based on regional efficiency data.

Exception: Elite mid-range shooters (Durant, DeRozan, CP3) can exploit this gap, shooting 45-50% from mid-range for ~0.90-1.00 PPA, which approaches three-point value when accounting for free-throw generation and lower turnover rates.

Advanced Tactical Considerations

Context-Dependent Zone Value

Situation Optimal Zone Reasoning
End of Quarter Above-Break Three Maximize expected value with limited time
Late Shot Clock Mid-Range Pull-Up Most available shot against set defense
Transition Restricted Area Highest percentage before defense sets
Bonus Free Throws Restricted Area Drawing fouls adds ~0.5 to expected value
Close Late Game Varies by Team Deploy best scorers in their optimal zones

Zone-Specific Personnel Requirements

Restricted Area Optimization:

  • Vertical athleticism for finishing through contact
  • Strength to create post position
  • Touch for floaters and runners when rim is protected

Corner Three Optimization:

  • Shooting accuracy (>38% threshold for value)
  • Court awareness to stay in-bounds
  • Quick release for catch-and-shoot opportunities

Above-Break Three Optimization:

  • Deep range (25+ feet for pull-up attempts)
  • Shot creation ability off dribble
  • Balance and footwork for contested attempts

Future Trends and Predictions

Practical Applications

For Coaches

  • Design offensive sets that generate high-value shots in restricted area and corner three zones
  • Identify player zone strengths through tracking data and deploy accordingly
  • Implement defensive schemes that force opponents toward low-value mid-range attempts
  • Use zone efficiency data to evaluate lineup effectiveness
  • Develop player skills aligned with high-value zones (rim finishing, corner shooting)

For Players

  • Understand personal zone efficiency to identify areas for skill development
  • Recognize defensive schemes designed to force specific zone attempts
  • Develop counter-moves when defenses take away preferred zones
  • Build awareness of spacing principles that create optimal zone opportunities
  • Study elite scorers' zone usage patterns in similar roles

For Analysts

  • Track zone efficiency trends to identify emerging tactical shifts
  • Compare team zone profiles to league averages for strategic insights
  • Evaluate player value through zone-specific expected points added
  • Model optimal shot distributions based on team personnel
  • Analyze opponent tendencies by zone to inform game-planning

For Scouts

  • Assess prospect value through zone efficiency profiles
  • Identify skill translation risks (mid-range dominant college players)
  • Project NBA impact based on skills aligned with high-value zones
  • Evaluate fit with team offensive philosophy through zone analysis
  • Track development of zone-specific skills (corner three accuracy, rim finishing)

Key Takeaways

  • Court region analysis has fundamentally transformed basketball strategy, creating a clear shot value hierarchy
  • Restricted area (1.29 PPA) and corner threes (1.17 PPA) represent the highest-value zones
  • Mid-range attempts have declined dramatically due to lower efficiency (0.80-0.84 PPA)
  • The three-point revolution has driven above-break three frequency from 13.6% (2010) to 30.5% (2024)
  • Modern offensive strategy centers on maximizing rim attacks and three-point attempts
  • Defensive schemes must balance protecting the rim while contesting three-point attempts
  • Python (nba_api) and R (hoopR) provide powerful tools for analyzing zone-specific performance
  • Context matters: situational factors can shift zone value (end of quarter, shot clock, foul situation)
  • Elite mid-range shooters can still create value, but they represent exceptions to the general optimization
  • Future evolution likely includes further three-point emphasis and potential rule changes to restore balance

Conclusion

Court region analysis represents one of the most impactful applications of basketball analytics, directly influencing how the game is played at every level. The quantification of shot value by zone has driven strategic optimization, leading to the dramatic shift away from mid-range attempts toward rim attacks and three-point shots. Understanding regional efficiency patterns is essential for modern coaches, players, analysts, and scouts.

The tools and methodologies presented here—from Python shot chart analysis to R statistical modeling—enable deep investigation of zone-specific performance. As the game continues to evolve, region-based analytics will remain central to tactical decision-making, player development, and strategic innovation. The data clearly shows that not all shots are created equal, and success increasingly depends on maximizing attempts from the highest-value zones while minimizing lower-efficiency opportunities.

Discussion

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