20 min read

Scoring is the fundamental currency of basketball. Every possession ultimately aims to produce points, and the efficiency with which a player or team converts opportunities into points often determines the outcome of games, series, and seasons. Yet...

Chapter 8: Shooting Efficiency Metrics

Introduction

Scoring is the fundamental currency of basketball. Every possession ultimately aims to produce points, and the efficiency with which a player or team converts opportunities into points often determines the outcome of games, series, and seasons. Yet for decades, the basketball community relied on metrics that provided an incomplete picture of shooting performance. This chapter explores the evolution of shooting efficiency measurement, from the basic Field Goal Percentage to sophisticated composite metrics that account for the varying values of different shot types.

Understanding shooting efficiency is not merely an academic exercise. Front offices use these metrics to evaluate player value and construct rosters. Coaches employ them to optimize shot selection and design offensive systems. Players leverage this knowledge to improve their games and understand their roles. Media and fans use these statistics to contextualize performances and compare players across eras.

We begin with the foundational metric—Field Goal Percentage—and systematically build toward more comprehensive measures. Along the way, we will derive formulas mathematically, implement calculations in Python, analyze historical trends, and examine how the understanding of shooting efficiency has fundamentally altered how basketball is played at the highest levels.


8.1 Field Goal Percentage (FG%): The Foundation

8.1.1 Definition and Calculation

Field Goal Percentage represents the most intuitive measure of shooting accuracy: the proportion of field goal attempts that successfully result in made baskets. The formula is elegantly simple:

$$FG\% = \frac{FGM}{FGA} \times 100$$

Where: - $FGM$ = Field Goals Made - $FGA$ = Field Goals Attempted

For example, if a player attempts 20 shots and makes 9 of them:

$$FG\% = \frac{9}{20} \times 100 = 45\%$$

8.1.2 Historical Context

Field Goal Percentage has been tracked since the earliest days of organized basketball statistics. The NBA has maintained official FG% records since its founding in 1946, making it one of the oldest continuously tracked basketball metrics. For decades, it served as the primary measure of shooting skill and efficiency.

The league-average FG% has fluctuated throughout NBA history, influenced by changes in rules, playing styles, athleticism, and shot selection:

Era Approximate League FG% Key Factors
1950s 38-40% Limited practice facilities, set shots
1960s 42-44% Jump shot proliferation, improved conditioning
1970s 45-46% ABA influence, more athletic play
1980s 48-49% Post-play emphasis, showtime era
1990s 45-47% Hand-checking, isolation basketball
2000s 44-46% Continued defensive emphasis
2010s 45-46% Three-point revolution begins
2020s 46-47% Spacing, analytics-driven shot selection

8.1.3 The Fundamental Limitation

While FG% provides useful information about shooting accuracy, it suffers from a critical flaw: it treats all made field goals as equal. Consider two hypothetical players:

Player A: - 100 two-point attempts, 50 made (50% from two) - 0 three-point attempts - Total: 100 points on 100 shots - FG%: 50%

Player B: - 0 two-point attempts - 100 three-point attempts, 40 made (40% from three) - Total: 120 points on 100 shots - FG%: 40%

Despite having a lower FG%, Player B generated 20% more points on the same number of shots. This example illustrates why FG% alone is insufficient for evaluating scoring efficiency in modern basketball, where three-point shooting plays an increasingly prominent role.

8.1.4 When FG% Remains Useful

Despite its limitations, FG% retains value in specific contexts:

  1. Comparing similar shot profiles: When comparing two post players who rarely shoot threes, FG% provides a reasonable comparison.

  2. Zone-specific analysis: FG% on mid-range jumpers, at the rim, or from specific floor locations provides actionable insights.

  3. Historical comparisons within eras: Comparing players who competed in the same offensive environment with similar shot selection patterns.

  4. Component of broader analysis: FG% from different areas, combined with shot distribution data, creates a more complete picture.

8.1.5 Python Implementation

def calculate_fg_percentage(field_goals_made: int, field_goals_attempted: int) -> float:
    """
    Calculate Field Goal Percentage.

    Parameters:
    -----------
    field_goals_made : int
        Number of successful field goal attempts
    field_goals_attempted : int
        Total number of field goal attempts

    Returns:
    --------
    float
        Field goal percentage (0-100 scale)

    Examples:
    ---------
    >>> calculate_fg_percentage(9, 20)
    45.0
    >>> calculate_fg_percentage(0, 10)
    0.0
    """
    if field_goals_attempted == 0:
        return 0.0
    return (field_goals_made / field_goals_attempted) * 100

8.2 Effective Field Goal Percentage (eFG%): Valuing the Three-Pointer

8.2.1 The Need for a Better Metric

As the three-point line became a permanent fixture in professional basketball (introduced in the NBA in 1979-80), analysts recognized that traditional FG% increasingly misrepresented scoring efficiency. A metric was needed that would properly weight the additional point generated by successful three-point shots.

8.2.2 Mathematical Derivation

The goal of eFG% is to express shooting efficiency in terms that are comparable to traditional FG%. We want to answer the question: "What two-point FG% would produce equivalent points per shot?"

Let us derive the formula systematically.

Step 1: Define Total Points from Field Goals

For any player or team: $$\text{Points from FG} = 2 \times \text{2PM} + 3 \times \text{3PM}$$

Where: - $\text{2PM}$ = Two-point field goals made - $\text{3PM}$ = Three-point field goals made

Step 2: Express in Terms of FGM

Since $\text{FGM} = \text{2PM} + \text{3PM}$: $$\text{Points from FG} = 2 \times \text{FGM} + 1 \times \text{3PM}$$

The additional term ($1 \times \text{3PM}$) captures the extra point earned for each made three.

Step 3: Calculate Points per Shot

$$\text{Points per FGA} = \frac{2 \times \text{FGM} + \text{3PM}}{\text{FGA}}$$

Step 4: Convert to Percentage Equivalent

To express this as an "equivalent FG%," we divide by 2 (since a 100% two-point shooter scores 2 points per attempt):

$$eFG\% = \frac{FGM + 0.5 \times 3PM}{FGA} \times 100$$

This is the standard eFG% formula.

8.2.3 Alternative Derivation: The Bonus Approach

Another way to understand eFG% is through the concept of "bonus shots." Each made three-pointer is worth 1.5 times a made two-pointer (3 points vs. 2 points). Therefore:

$$eFG\% = \frac{2PM + 1.5 \times 3PM}{FGA} \times 100$$

Since $\text{FGM} = \text{2PM} + \text{3PM}$: $$eFG\% = \frac{(FGM - 3PM) + 1.5 \times 3PM}{FGA} \times 100$$ $$eFG\% = \frac{FGM + 0.5 \times 3PM}{FGA} \times 100$$

This confirms our original formula.

8.2.4 Interpreting eFG%

eFG% represents the field goal percentage a player would need shooting only two-point shots to produce the same points per attempt. Key interpretation points:

eFG% Interpretation
< 45% Below average efficiency
45-50% Average efficiency
50-55% Above average efficiency
55-60% Excellent efficiency
> 60% Elite efficiency (typically rim-centric or elite three-point shooters)

Example Calculation:

Stephen Curry in 2015-16: - FGM: 805 - 3PM: 402 - FGA: 1,598

$$eFG\% = \frac{805 + 0.5 \times 402}{1598} \times 100 = \frac{1006}{1598} \times 100 = 62.9\%$$

This elite eFG% reflects Curry's combination of volume and accuracy from three-point range.

8.2.5 Relationship Between FG% and eFG%

The difference between eFG% and FG% depends on: 1. The proportion of shots taken from three-point range 2. The accuracy on those three-point attempts

For a player who never shoots threes: $eFG\% = FG\%$

For a player who only shoots threes: $eFG\% = FG\% \times 1.5$

The general relationship: $$eFG\% - FG\% = \frac{0.5 \times 3PM}{FGA} \times 100$$

This "eFG% bonus" increases with both three-point volume and accuracy.

8.2.6 Python Implementation

def calculate_efg_percentage(
    field_goals_made: int,
    three_pointers_made: int,
    field_goals_attempted: int
) -> float:
    """
    Calculate Effective Field Goal Percentage.

    eFG% adjusts for the extra value of three-point shots by treating
    made threes as 1.5 made field goals.

    Parameters:
    -----------
    field_goals_made : int
        Total field goals made (includes 2PM and 3PM)
    three_pointers_made : int
        Three-point field goals made
    field_goals_attempted : int
        Total field goal attempts

    Returns:
    --------
    float
        Effective field goal percentage (0-100 scale)

    Examples:
    ---------
    >>> calculate_efg_percentage(10, 4, 20)  # 10 FGM, 4 3PM, 20 FGA
    60.0
    >>> calculate_efg_percentage(10, 0, 20)  # No threes
    50.0
    """
    if field_goals_attempted == 0:
        return 0.0
    return ((field_goals_made + 0.5 * three_pointers_made) / field_goals_attempted) * 100


def efg_bonus(three_pointers_made: int, field_goals_attempted: int) -> float:
    """
    Calculate the eFG% bonus from three-point shooting.

    This represents how much higher eFG% is compared to FG%
    due to three-point makes.
    """
    if field_goals_attempted == 0:
        return 0.0
    return (0.5 * three_pointers_made / field_goals_attempted) * 100

8.2.7 Limitations of eFG%

While eFG% improves upon FG%, it has its own limitations:

  1. Ignores free throws: A player who draws fouls and makes free throws at a high rate may be more efficient than their eFG% suggests.

  2. Ignores and-one opportunities: Made shots that also result in foul shots are not captured.

  3. Volume-agnostic: Does not distinguish between efficient low-volume scorers and efficient high-volume scorers.

  4. Context-blind: Does not account for difficulty of shots, quality of defense, or game situations.

These limitations motivate the development of True Shooting Percentage.


8.3 True Shooting Percentage (TS%): Comprehensive Scoring Efficiency

8.3.1 Motivation

Basketball scoring involves three types of made shots: two-pointers, three-pointers, and free throws. Any complete measure of scoring efficiency must account for all three. True Shooting Percentage was developed to provide this comprehensive view.

8.3.2 Mathematical Derivation

The goal of TS% is to measure points produced per scoring attempt, normalized to a percentage scale.

Step 1: Define Total Points

$$\text{Total Points} = 2 \times \text{2PM} + 3 \times \text{3PM} + 1 \times \text{FTM}$$

This can be simplified to: $$\text{Total Points (PTS)} = 2 \times \text{FGM} + \text{3PM} + \text{FTM}$$

Step 2: Define True Shooting Attempts

We need to account for the "cost" of free throw attempts. The standard approach treats two free throws as equivalent to one field goal attempt in terms of possession cost.

$$\text{True Shooting Attempts (TSA)} = FGA + 0.44 \times FTA$$

The coefficient 0.44 (not 0.5) reflects that: - And-one situations give one free throw for one field goal attempt - Technical free throws don't cost a possession - Three-shot fouls give three attempts for one "possession"

The weighted average of these scenarios yields approximately 0.44.

Step 3: Calculate TS%

$$TS\% = \frac{PTS}{2 \times TSA} \times 100$$

Or equivalently: $$TS\% = \frac{PTS}{2 \times (FGA + 0.44 \times FTA)} \times 100$$

The factor of 2 in the denominator normalizes the result so that a player making every two-point shot (and no free throws) would have a 100% TS%.

8.3.3 Understanding the 0.44 Coefficient

The 0.44 multiplier is an empirically derived approximation. Let's examine why it's not simply 0.5:

Scenario Analysis:

Situation FGA FTA Actual Possession Cost
Regular two FTs 0 2 1 possession
And-one 1 1 1 possession
Three-shot foul 0 3 1 possession
Technical FT 0 1 0 possessions
Flagrant FT 0 2 0 possessions

If we used 0.5, we would overestimate the number of possessions used for scoring, particularly for players who: - Shoot many and-ones - Benefit from technical or flagrant free throws

The 0.44 coefficient has proven robust across different eras and playing styles, though some analysts prefer player-specific or team-specific calculations for maximum precision.

8.3.4 TS% Benchmarks

TS% Interpretation Example Context
< 50% Poor efficiency Below replacement level
50-54% Below average Struggling scorer
54-56% Average League average typically 56-57%
56-58% Above average Solid contributor
58-62% Excellent Quality starter
62-65% Elite All-Star caliber efficiency
> 65% Historic MVP-level, era-defining

8.3.5 Worked Example: Comparing Scorers

Player X (Volume Three-Point Shooter): - 15 PPG on 12 FGA and 2 FTA - Makes 5 threes per game

$$TSA = 12 + 0.44 \times 2 = 12.88$$ $$TS\% = \frac{15}{2 \times 12.88} \times 100 = 58.2\%$$

Player Y (Rim Attacker with FT Drawing): - 15 PPG on 10 FGA and 6 FTA - Makes 0 threes per game

$$TSA = 10 + 0.44 \times 6 = 12.64$$ $$TS\% = \frac{15}{2 \times 12.64} \times 100 = 59.3\%$$

Despite scoring the same points per game, Player Y is slightly more efficient, demonstrating the value of drawing free throws.

8.3.6 Python Implementation

def calculate_ts_percentage(
    points: int,
    field_goals_attempted: int,
    free_throws_attempted: int,
    ft_coefficient: float = 0.44
) -> float:
    """
    Calculate True Shooting Percentage.

    TS% provides a comprehensive measure of scoring efficiency
    that accounts for two-pointers, three-pointers, and free throws.

    Parameters:
    -----------
    points : int
        Total points scored
    field_goals_attempted : int
        Total field goal attempts
    free_throws_attempted : int
        Total free throw attempts
    ft_coefficient : float
        Coefficient for FTA in TSA calculation (default: 0.44)

    Returns:
    --------
    float
        True shooting percentage (0-100 scale)

    Examples:
    ---------
    >>> calculate_ts_percentage(30, 20, 10)  # 30 pts, 20 FGA, 10 FTA
    62.5
    """
    tsa = field_goals_attempted + ft_coefficient * free_throws_attempted
    if tsa == 0:
        return 0.0
    return (points / (2 * tsa)) * 100


def calculate_true_shooting_attempts(
    field_goals_attempted: int,
    free_throws_attempted: int,
    ft_coefficient: float = 0.44
) -> float:
    """
    Calculate True Shooting Attempts.

    TSA represents the total number of scoring attempts,
    with free throws weighted appropriately.
    """
    return field_goals_attempted + ft_coefficient * free_throws_attempted

8.3.7 Points Per Shooting Attempt (PPS)

A related metric that avoids the percentage normalization:

$$PPS = \frac{PTS}{TSA}$$

This tells us directly how many points a player produces per true shooting attempt. League average is typically around 1.12-1.14 points per attempt.

def calculate_pps(
    points: int,
    field_goals_attempted: int,
    free_throws_attempted: int,
    ft_coefficient: float = 0.44
) -> float:
    """
    Calculate Points Per Shooting Attempt.

    A more intuitive representation of scoring efficiency
    that avoids percentage normalization.
    """
    tsa = field_goals_attempted + ft_coefficient * free_throws_attempted
    if tsa == 0:
        return 0.0
    return points / tsa

8.4 Free Throw Rate and Its Impact on Efficiency

8.4.1 Defining Free Throw Rate

Free Throw Rate (FTr) measures how frequently a player gets to the free throw line relative to their field goal attempts:

$$FTr = \frac{FTA}{FGA}$$

A player who attempts 20 field goals and 10 free throws has an FTr of 0.50 (or 50%).

8.4.2 Why Free Throws Matter

Free throws are the most efficient shot in basketball:

  1. No defense: The shooter faces no contest
  2. High conversion rate: League average is typically 77-78%
  3. Guaranteed attempts: Once earned, cannot be blocked or stolen
  4. Bonus value: And-one opportunities add to existing made baskets

Expected Value Analysis:

Shot Type League Avg % Points Expected Value
Free Throw ~77% 1 0.77
Two Free Throws ~77% 2 1.54
Two-Pointer (all) ~52% 2 1.04
Three-Pointer ~36% 3 1.08
Rim Attempt ~65% 2 1.30
Mid-Range ~42% 2 0.84

Two free throws (expected value 1.54) exceed all other shot types in expected value.

8.4.3 Free Throw Rate Benchmarks

FTr Interpretation
< 0.20 Low (perimeter player, jumpshooter)
0.20-0.30 Below average
0.30-0.40 Average
0.40-0.50 Above average (drive-oriented or post player)
> 0.50 Elite foul drawer

8.4.4 The Relationship Between FTr and TS%

Players with high free throw rates often have elevated TS% relative to their eFG%, assuming reasonable free throw accuracy. The gap between TS% and eFG% increases with:

  1. Higher FTr
  2. Higher FT%
  3. Lower shot volume

Quantifying the Gap:

For a player with: - FG%: 45% - 3PT%: 35% on 30% of shots from three - FT%: 80% - FTr: 0.40

Their eFG% would be approximately: $$eFG\% = 45\% + 0.5 \times (0.35 \times 0.30 \times 100) = 45\% + 5.25\% = 50.25\%$$

Wait, let me recalculate this properly. If FG% is 45% overall and 30% of attempts are threes at 35%: - 2PT attempts: 70%, making at rate X - 3PT attempts: 30%, making at 35% - Overall FG%: 45%

So: $0.70X + 0.30(0.35) = 0.45$ $0.70X + 0.105 = 0.45$ $X = 0.493$ (49.3% on twos)

Then: $eFG\% = 45\% + 0.5 \times (3PM/FGA) \times 100$ $= 45\% + 0.5 \times 0.30 \times 0.35 \times 100$ $= 45\% + 5.25\%$ $= 50.25\%$

With FTr of 0.40 and FT% of 80%, the TS% boost from free throws would be significant.

8.4.5 Python Implementation

def calculate_free_throw_rate(
    free_throws_attempted: int,
    field_goals_attempted: int
) -> float:
    """
    Calculate Free Throw Rate.

    FTr measures how often a player gets to the line
    relative to their field goal attempts.

    Parameters:
    -----------
    free_throws_attempted : int
        Total free throw attempts
    field_goals_attempted : int
        Total field goal attempts

    Returns:
    --------
    float
        Free throw rate (typically 0.0 to 0.8)
    """
    if field_goals_attempted == 0:
        return 0.0
    return free_throws_attempted / field_goals_attempted


def free_throw_contribution(
    free_throws_made: int,
    free_throws_attempted: int,
    field_goals_attempted: int
) -> dict:
    """
    Analyze the contribution of free throws to overall scoring efficiency.

    Returns:
    --------
    dict with:
        - ftr: Free throw rate
        - ft_pct: Free throw percentage
        - points_from_ft: Points generated from free throws
        - expected_value_per_fta: Expected points per free throw attempt
    """
    ft_pct = (free_throws_made / free_throws_attempted * 100) if free_throws_attempted > 0 else 0

    return {
        'ftr': calculate_free_throw_rate(free_throws_attempted, field_goals_attempted),
        'ft_pct': ft_pct,
        'points_from_ft': free_throws_made,
        'expected_value_per_fta': free_throws_made / free_throws_attempted if free_throws_attempted > 0 else 0
    }

8.5 Shot Distribution Analysis by Zone

8.5.1 The Basketball Court Zones

Modern shot tracking divides the court into distinct zones, each with different expected values:

Primary Zones:

  1. Restricted Area (RA): The painted area within 4 feet of the basket - Highest FG% (60-70%) - Expected value: ~1.30 points

  2. Paint (Non-RA): The painted area outside the restricted area - Moderate FG% (38-42%) - Expected value: ~0.80 points

  3. Mid-Range: Two-point shots outside the paint - Lower FG% (38-45%) - Expected value: ~0.80-0.90 points

  4. Corner Three: Three-point shots from the corners - Highest 3PT% (38-42%) - Expected value: ~1.14-1.26 points

  5. Above the Break Three: Three-point shots from elsewhere - Lower 3PT% (34-38%) - Expected value: ~1.02-1.14 points

8.5.2 Expected Points by Zone

The expected value framework helps quantify why some shots are preferred:

$$EV_{zone} = FG\%_{zone} \times \text{Point Value}_{zone}$$

League Average Expected Values (approximate):

Zone FG% Points Expected Value
Restricted Area 65% 2 1.30
Paint (Non-RA) 40% 2 0.80
Mid-Range 42% 2 0.84
Corner Three 39% 3 1.17
Above Break Three 36% 3 1.08

This analysis reveals the "mid-range dead zone"—shots that are neither close enough to convert at high rates nor far enough to benefit from the three-point bonus.

8.5.3 Shot Distribution Metrics

Three-Point Attempt Rate (3PAr): $$3PAr = \frac{3PA}{FGA}$$

This measures what percentage of field goal attempts are three-pointers.

Rim Attempt Rate: $$\text{Rim Rate} = \frac{\text{RA Attempts}}{FGA}$$

Mid-Range Rate: $$\text{Mid-Range Rate} = \frac{\text{Mid-Range Attempts}}{FGA}$$

8.5.4 Python Implementation

from dataclasses import dataclass
from typing import Dict

@dataclass
class ShotZoneData:
    """Container for shot data from a specific zone."""
    attempts: int
    makes: int
    point_value: int

    @property
    def percentage(self) -> float:
        return (self.makes / self.attempts * 100) if self.attempts > 0 else 0.0

    @property
    def expected_value(self) -> float:
        return (self.makes / self.attempts * self.point_value) if self.attempts > 0 else 0.0


class ShotDistributionAnalyzer:
    """Analyze shot distribution and efficiency by zone."""

    def __init__(self):
        self.zones: Dict[str, ShotZoneData] = {}

    def add_zone(self, zone_name: str, attempts: int, makes: int, point_value: int):
        """Add shot data for a zone."""
        self.zones[zone_name] = ShotZoneData(attempts, makes, point_value)

    def total_attempts(self) -> int:
        """Calculate total field goal attempts."""
        return sum(zone.attempts for zone in self.zones.values())

    def total_makes(self) -> int:
        """Calculate total field goals made."""
        return sum(zone.makes for zone in self.zones.values())

    def total_points(self) -> int:
        """Calculate total points from field goals."""
        return sum(zone.makes * zone.point_value for zone in self.zones.values())

    def zone_distribution(self) -> Dict[str, float]:
        """Calculate the percentage of shots from each zone."""
        total = self.total_attempts()
        if total == 0:
            return {name: 0.0 for name in self.zones}
        return {name: zone.attempts / total * 100 for name, zone in self.zones.items()}

    def zone_efficiency(self) -> Dict[str, dict]:
        """Calculate efficiency metrics for each zone."""
        return {
            name: {
                'fg_pct': zone.percentage,
                'expected_value': zone.expected_value,
                'points': zone.makes * zone.point_value
            }
            for name, zone in self.zones.items()
        }

    def optimal_shot_analysis(self) -> str:
        """Provide analysis of shot selection optimality."""
        efficiencies = self.zone_efficiency()
        sorted_zones = sorted(
            efficiencies.items(),
            key=lambda x: x[1]['expected_value'],
            reverse=True
        )

        analysis = "Shot Efficiency Ranking:\n"
        for i, (zone, data) in enumerate(sorted_zones, 1):
            analysis += f"{i}. {zone}: {data['expected_value']:.3f} points per attempt\n"

        return analysis


# Example usage
def analyze_player_shot_distribution():
    """Example analysis of a player's shot distribution."""
    analyzer = ShotDistributionAnalyzer()

    # Add zone data (attempts, makes, point_value)
    analyzer.add_zone("Restricted Area", 200, 130, 2)
    analyzer.add_zone("Paint Non-RA", 50, 20, 2)
    analyzer.add_zone("Mid-Range", 100, 42, 2)
    analyzer.add_zone("Corner Three", 80, 32, 3)
    analyzer.add_zone("Above Break Three", 150, 54, 3)

    print("Shot Distribution:")
    for zone, pct in analyzer.zone_distribution().items():
        print(f"  {zone}: {pct:.1f}%")

    print(f"\nTotal Points: {analyzer.total_points()}")
    print(f"\n{analyzer.optimal_shot_analysis()}")

8.6 The Three-Point Revolution

8.6.1 Historical Evolution of Three-Point Shooting

The three-point line was introduced to the NBA in the 1979-80 season, borrowed from the ABA. Initial adoption was slow:

Three-Point Attempts Per Game (League Average):

Season 3PA/Game 3P% Notes
1979-80 2.8 28.0% First season
1984-85 3.1 28.2% Still novelty
1989-90 6.6 33.1% Beginning adoption
1994-95 15.3 35.9% Shortened line (22 ft)
1997-98 12.7 34.6% Line restored to 23'9"
2004-05 15.8 35.6% Steady growth
2009-10 18.1 35.5% Pre-revolution
2014-15 22.4 35.0% Revolution begins
2019-20 34.1 35.8% New normal
2023-24 35.1 36.5% Continued emphasis

8.6.2 The Analytics Movement's Influence

The explosion in three-point attempts since 2010 can be attributed to several factors:

  1. Expected value analysis: Teams recognized the mathematical advantage
  2. Spacing benefits: More shooters improve driving lanes
  3. Rule changes: Freedom of movement rules helped shooters
  4. Player development: Youth training emphasized perimeter shooting
  5. Front office adoption: Analytics-minded executives reshaped rosters

8.6.3 The Mathematics Behind the Revolution

Let's formalize why three-pointers are mathematically favored:

Break-Even Analysis:

For a three-pointer to equal a two-pointer in expected value: $$3 \times 3P\% = 2 \times 2P\%$$ $$3P\% = \frac{2}{3} \times 2P\%$$

If a player shoots 50% on twos, they only need to shoot 33.3% on threes to generate equal expected value. If league average on twos is 52%, the break-even three-point percentage is: $$3P\% = \frac{2}{3} \times 52\% = 34.7\%$$

Since league average three-point shooting exceeds this threshold (~36%), the three-pointer is mathematically superior for the average shooter.

8.6.4 Impact on Shot Distribution

The shift toward three-point shooting has compressed the shot chart:

Modern Optimal Shot Distribution:

Zone 2010 Distribution 2020 Distribution Change
At Rim 28% 32% +4%
Paint (Non-RA) 12% 8% -4%
Mid-Range 30% 14% -16%
Three-Point 22% 40% +18%
Other 8% 6% -2%

The mid-range shot has been largely abandoned in favor of threes and rim attempts.

8.6.5 Python Implementation: Three-Point Analysis

def three_point_break_even(two_point_percentage: float) -> float:
    """
    Calculate the three-point percentage needed to match two-point efficiency.

    Parameters:
    -----------
    two_point_percentage : float
        The player's two-point field goal percentage (0-100 scale)

    Returns:
    --------
    float
        The break-even three-point percentage (0-100 scale)
    """
    return (2/3) * two_point_percentage


def three_point_advantage(
    three_point_pct: float,
    two_point_pct: float
) -> dict:
    """
    Calculate the expected value advantage of three-pointers over two-pointers.

    Returns:
    --------
    dict with:
        - ev_three: Expected value of three-point attempt
        - ev_two: Expected value of two-point attempt
        - advantage: Points per attempt advantage (positive favors three)
        - break_even: Three-point percentage needed to match two-point EV
    """
    ev_three = 3 * (three_point_pct / 100)
    ev_two = 2 * (two_point_pct / 100)
    break_even = three_point_break_even(two_point_pct)

    return {
        'ev_three': ev_three,
        'ev_two': ev_two,
        'advantage': ev_three - ev_two,
        'break_even': break_even,
        'recommendation': 'Shoot threes' if ev_three > ev_two else 'Shoot twos'
    }


def league_three_point_trend(season_data: list) -> dict:
    """
    Analyze trends in three-point shooting over multiple seasons.

    Parameters:
    -----------
    season_data : list of dict
        Each dict contains: season, three_pa, three_pm, total_fga

    Returns:
    --------
    dict with trend analysis
    """
    if not season_data:
        return {}

    first = season_data[0]
    last = season_data[-1]

    first_3par = first['three_pa'] / first['total_fga'] * 100
    last_3par = last['three_pa'] / last['total_fga'] * 100

    first_3ppct = first['three_pm'] / first['three_pa'] * 100 if first['three_pa'] > 0 else 0
    last_3ppct = last['three_pm'] / last['three_pa'] * 100 if last['three_pa'] > 0 else 0

    return {
        'first_season': first['season'],
        'last_season': last['season'],
        'three_par_change': last_3par - first_3par,
        'three_pct_change': last_3ppct - first_3ppct,
        'volume_increase_pct': (last['three_pa'] - first['three_pa']) / first['three_pa'] * 100
    }

8.6.6 Counter-Arguments and Limitations

While three-point shooting offers mathematical advantages, several factors complicate the analysis:

  1. Diminishing returns: As teams shoot more threes, defenders adjust
  2. Variance: Three-point shooting is inherently streaky
  3. Playoff basketball: Some analysts argue threes are less reliable in playoffs
  4. Offensive rebounding: Long rebounds favor the defense
  5. Individual skill: Not all players can shoot threes efficiently

8.7 Shot Selection Optimization

8.7.1 The Concept of Shot Quality

Shot quality attempts to measure how "good" a shot is, independent of whether it goes in. Factors include:

  • Distance from basket: Closer is generally better
  • Defender proximity: Open shots are higher quality
  • Shot type: Catch-and-shoot vs. off-dribble
  • Time on clock: Shot clock situation
  • Player ability: Individual shooting skill

8.7.2 Expected Points Added (EPA) Framework

We can evaluate shot selection by comparing actual shots to optimal alternatives:

$$EPA = EV_{actual} - EV_{optimal}$$

If a player takes a contested mid-range jumper (EV ~ 0.80) when an open corner three was available (EV ~ 1.17):

$$EPA = 0.80 - 1.17 = -0.37$$

This shot cost the team 0.37 expected points compared to the optimal choice.

8.7.3 Shot Selection Efficiency

We can measure how well players/teams optimize their shot selection:

$$\text{Shot Selection Efficiency} = \frac{\text{EV of Shots Taken}}{\text{EV of Best Available Shots}}$$

Teams approaching 100% are making optimal decisions; those below are leaving points on the table.

8.7.4 The Trade-Off: Volume vs. Efficiency

A critical insight is that shot selection cannot be evaluated in isolation from volume. A player might have perfect shot selection (only taking layups) but provide little value if they only score 5 points per game.

The Efficiency-Volume Framework:

Player Type Volume Efficiency Value
Role Player Low High Moderate
Primary Scorer High Moderate High
Inefficient Volume High Low Low
Limited Role Low Low Low

The most valuable scorers maintain high efficiency while absorbing large volume.

8.7.5 Python Implementation

from typing import List, Tuple

@dataclass
class ShotAttempt:
    """Represents a single shot attempt with context."""
    zone: str
    distance: float
    defender_distance: float
    shot_type: str  # 'catch_shoot', 'pullup', 'driving', etc.
    made: bool
    expected_value: float

class ShotQualityModel:
    """Model for evaluating shot quality and selection."""

    # Base expected values by zone (can be customized)
    BASE_EV = {
        'restricted_area': 1.30,
        'paint': 0.80,
        'mid_range': 0.84,
        'corner_three': 1.17,
        'above_break_three': 1.08
    }

    # Adjustments for shot type
    SHOT_TYPE_ADJUSTMENTS = {
        'catch_shoot': 1.05,
        'pullup': 0.90,
        'driving': 1.00,
        'post_up': 0.95,
        'step_back': 0.88
    }

    # Adjustments for defender distance (feet)
    @staticmethod
    def defender_adjustment(distance: float) -> float:
        """Adjust EV based on defender proximity."""
        if distance >= 6:  # Wide open
            return 1.15
        elif distance >= 4:  # Open
            return 1.05
        elif distance >= 2:  # Contested
            return 0.95
        else:  # Tightly contested
            return 0.80

    def calculate_shot_ev(self, shot: ShotAttempt) -> float:
        """Calculate expected value for a specific shot."""
        base = self.BASE_EV.get(shot.zone, 1.0)
        type_adj = self.SHOT_TYPE_ADJUSTMENTS.get(shot.shot_type, 1.0)
        defender_adj = self.defender_adjustment(shot.defender_distance)

        return base * type_adj * defender_adj

    def evaluate_shot_selection(self, shots: List[ShotAttempt]) -> dict:
        """Evaluate overall shot selection quality."""
        if not shots:
            return {'error': 'No shots provided'}

        total_ev_taken = sum(self.calculate_shot_ev(s) for s in shots)
        avg_ev = total_ev_taken / len(shots)

        # Calculate actual points per shot
        actual_pps = sum(
            (2 if 'three' not in s.zone else 3) if s.made else 0
            for s in shots
        ) / len(shots)

        return {
            'total_shots': len(shots),
            'average_shot_ev': avg_ev,
            'actual_pps': actual_pps,
            'performance_vs_expected': actual_pps - avg_ev,
            'shot_selection_grade': self._grade_selection(avg_ev)
        }

    @staticmethod
    def _grade_selection(avg_ev: float) -> str:
        """Grade shot selection quality."""
        if avg_ev >= 1.15:
            return 'Excellent'
        elif avg_ev >= 1.05:
            return 'Good'
        elif avg_ev >= 0.95:
            return 'Average'
        elif avg_ev >= 0.85:
            return 'Below Average'
        else:
            return 'Poor'


def optimize_shot_distribution(
    player_percentages: dict,
    current_distribution: dict
) -> dict:
    """
    Suggest optimal shot distribution based on player shooting percentages.

    Parameters:
    -----------
    player_percentages : dict
        Shooting percentages by zone for this player
    current_distribution : dict
        Current shot distribution by zone

    Returns:
    --------
    dict with optimal distribution and expected improvement
    """
    # Calculate expected value for each zone
    zone_evs = {}
    point_values = {
        'restricted_area': 2,
        'paint': 2,
        'mid_range': 2,
        'corner_three': 3,
        'above_break_three': 3
    }

    for zone, pct in player_percentages.items():
        zone_evs[zone] = (pct / 100) * point_values.get(zone, 2)

    # Rank zones by EV
    sorted_zones = sorted(zone_evs.items(), key=lambda x: x[1], reverse=True)

    # Calculate current EV
    current_ev = sum(
        (current_distribution.get(zone, 0) / 100) * ev
        for zone, ev in zone_evs.items()
    )

    return {
        'zone_rankings': sorted_zones,
        'current_expected_pps': current_ev,
        'optimal_zone': sorted_zones[0][0],
        'zones_to_avoid': [z for z, ev in sorted_zones if ev < 0.90],
        'recommendation': f"Maximize shots from {sorted_zones[0][0]} (EV: {sorted_zones[0][1]:.2f})"
    }

8.8.1 League Average TS% Over Time

True Shooting Percentage has generally increased over NBA history, with some fluctuations:

Era Approximate TS% Key Influences
1980s 52-54% Post play, fast breaks
1990s 52-53% Hand-checking, isolation
Early 2000s 52-53% Zone defense legalized
2010-2015 54-55% Analytics adoption begins
2015-2020 56-57% Three-point revolution
2020-2025 57-58% Optimized shot selection

8.8.2 Evolution of Elite Scoring Thresholds

What constitutes "elite" efficiency has changed:

Era Elite TS% Threshold Notable Players
1990s 58%+ Shaquille O'Neal, Karl Malone
2000s 60%+ Steve Nash, Dirk Nowitzki
2010s 62%+ Stephen Curry, Kevin Durant
2020s 64%+ Nikola Jokic, Zion Williamson

8.8.3 The Compression of Efficiency

As shot selection has optimized, the variance in efficiency has decreased:

  • 1995: Standard deviation of starter TS% ~ 5.5%
  • 2020: Standard deviation of starter TS% ~ 4.2%

Teams and players have converged toward optimal shooting patterns.

8.8.4 Position-Based Efficiency Evolution

Different positions have seen different efficiency trends:

Centers: - Historically most efficient (close to basket) - TS% advantage has decreased as perimeter players optimized - Modern centers must shoot threes to remain valuable

Guards: - Historically least efficient (most difficult shots) - TS% has increased most dramatically - Three-point shooting has elevated guard efficiency

Forwards: - Moderate historical efficiency - "Stretch four" revolutionized power forward position - Small forwards increasingly resemble guards in shot selection

8.8.5 Python Implementation: Historical Analysis

import numpy as np
from typing import List, Dict

def analyze_era_efficiency(era_data: List[Dict]) -> Dict:
    """
    Analyze shooting efficiency trends across eras.

    Parameters:
    -----------
    era_data : list of dict
        Each dict contains: era_name, avg_ts_pct, avg_efg_pct,
        three_par, pace, etc.

    Returns:
    --------
    dict with trend analysis
    """
    ts_values = [d['avg_ts_pct'] for d in era_data]
    efg_values = [d['avg_efg_pct'] for d in era_data]

    return {
        'ts_trend': {
            'start': ts_values[0],
            'end': ts_values[-1],
            'change': ts_values[-1] - ts_values[0],
            'average': np.mean(ts_values)
        },
        'efg_trend': {
            'start': efg_values[0],
            'end': efg_values[-1],
            'change': efg_values[-1] - efg_values[0],
            'average': np.mean(efg_values)
        },
        'most_efficient_era': era_data[np.argmax(ts_values)]['era_name'],
        'least_efficient_era': era_data[np.argmin(ts_values)]['era_name']
    }


def compare_player_to_era(
    player_stats: Dict,
    era_averages: Dict
) -> Dict:
    """
    Compare a player's efficiency to their era's averages.

    Parameters:
    -----------
    player_stats : dict
        Player's shooting statistics
    era_averages : dict
        League averages for the player's era

    Returns:
    --------
    dict with relative efficiency metrics
    """
    player_ts = calculate_ts_percentage(
        player_stats['points'],
        player_stats['fga'],
        player_stats['fta']
    )

    return {
        'player_ts': player_ts,
        'era_avg_ts': era_averages['ts_pct'],
        'relative_ts': player_ts - era_averages['ts_pct'],
        'percentile_estimate': _estimate_percentile(
            player_ts,
            era_averages['ts_pct'],
            era_averages.get('ts_std', 4.0)
        ),
        'era_adjusted_ts': player_ts + (57.0 - era_averages['ts_pct'])  # Normalize to 2020 baseline
    }


def _estimate_percentile(value: float, mean: float, std: float) -> float:
    """Estimate percentile assuming normal distribution."""
    from scipy import stats
    z_score = (value - mean) / std
    return stats.norm.cdf(z_score) * 100


def pace_adjusted_scoring(
    points_per_game: float,
    team_pace: float,
    league_pace: float
) -> float:
    """
    Adjust scoring for pace differences.

    Returns points per 100 possessions.
    """
    return points_per_game * (100 / team_pace)


def era_adjusted_efficiency_comparison(players: List[Dict]) -> List[Dict]:
    """
    Compare players across eras using era-adjusted metrics.

    Each player dict should contain:
        - name, season, ts_pct, usage_rate, era_ts_avg

    Returns list sorted by era-adjusted efficiency.
    """
    adjusted_players = []

    for player in players:
        # Era-adjust TS% (normalize to modern baseline of 57%)
        adjusted_ts = player['ts_pct'] + (57.0 - player['era_ts_avg'])

        # Weight by usage (high usage efficiency is more impressive)
        usage_weight = 1 + (player['usage_rate'] - 20) / 50  # Normalize around 20% usage
        weighted_ts = adjusted_ts * usage_weight

        adjusted_players.append({
            'name': player['name'],
            'season': player['season'],
            'raw_ts': player['ts_pct'],
            'era_adjusted_ts': adjusted_ts,
            'usage_weighted_ts': weighted_ts,
            'relative_to_era': player['ts_pct'] - player['era_ts_avg']
        })

    return sorted(adjusted_players, key=lambda x: x['era_adjusted_ts'], reverse=True)

8.9 Putting It All Together: A Comprehensive Efficiency Analysis Framework

8.9.1 The Complete Scoring Efficiency Profile

To fully evaluate a scorer's efficiency, we should consider multiple dimensions:

  1. Raw Efficiency: TS%, eFG%, FG%
  2. Volume Context: Usage rate, shots per game
  3. Shot Selection: Zone distribution, shot quality
  4. Era Context: Relative to league average
  5. Role Context: Primary scorer vs. role player

8.9.2 The Efficiency-Volume Matrix

We can visualize scorer value by plotting efficiency against volume:

         High Efficiency
              |
    Elite     |    Superstar
    Role      |    Scorer
    Player    |
              |
 Low Volume --+-- High Volume
              |
    Inefficient|   Inefficient
    Role      |    Volume
    Player    |    Scorer
              |
         Low Efficiency

8.9.3 Comprehensive Python Analysis

from dataclasses import dataclass, field
from typing import Optional
import numpy as np

@dataclass
class ScorerProfile:
    """Complete profile of a scorer's efficiency."""

    # Basic counting stats
    name: str
    season: str
    games: int
    points: int
    fgm: int
    fga: int
    three_pm: int
    three_pa: int
    ftm: int
    fta: int

    # Optional context
    team_pace: Optional[float] = None
    usage_rate: Optional[float] = None
    era_avg_ts: Optional[float] = 56.0

    # Computed properties
    @property
    def ppg(self) -> float:
        return self.points / self.games if self.games > 0 else 0.0

    @property
    def fg_pct(self) -> float:
        return (self.fgm / self.fga * 100) if self.fga > 0 else 0.0

    @property
    def three_pct(self) -> float:
        return (self.three_pm / self.three_pa * 100) if self.three_pa > 0 else 0.0

    @property
    def ft_pct(self) -> float:
        return (self.ftm / self.fta * 100) if self.fta > 0 else 0.0

    @property
    def efg_pct(self) -> float:
        if self.fga == 0:
            return 0.0
        return ((self.fgm + 0.5 * self.three_pm) / self.fga) * 100

    @property
    def ts_pct(self) -> float:
        tsa = self.fga + 0.44 * self.fta
        if tsa == 0:
            return 0.0
        return (self.points / (2 * tsa)) * 100

    @property
    def three_par(self) -> float:
        return (self.three_pa / self.fga) if self.fga > 0 else 0.0

    @property
    def ftr(self) -> float:
        return (self.fta / self.fga) if self.fga > 0 else 0.0

    @property
    def tsa_per_game(self) -> float:
        tsa = self.fga + 0.44 * self.fta
        return tsa / self.games if self.games > 0 else 0.0

    @property
    def pps(self) -> float:
        """Points per shooting attempt."""
        tsa = self.fga + 0.44 * self.fta
        return self.points / tsa if tsa > 0 else 0.0

    @property
    def relative_ts(self) -> float:
        """TS% relative to era average."""
        return self.ts_pct - self.era_avg_ts

    def generate_report(self) -> str:
        """Generate a comprehensive efficiency report."""
        report = f"""
=== SCORING EFFICIENCY REPORT ===
Player: {self.name}
Season: {self.season}

--- VOLUME METRICS ---
Games Played: {self.games}
Points Per Game: {self.ppg:.1f}
True Shooting Attempts/Game: {self.tsa_per_game:.1f}

--- EFFICIENCY METRICS ---
Field Goal %: {self.fg_pct:.1f}%
3-Point %: {self.three_pct:.1f}%
Free Throw %: {self.ft_pct:.1f}%
Effective FG%: {self.efg_pct:.1f}%
True Shooting %: {self.ts_pct:.1f}%
Points Per Attempt: {self.pps:.2f}

--- SHOT PROFILE ---
3-Point Attempt Rate: {self.three_par:.1%}
Free Throw Rate: {self.ftr:.2f}

--- ERA CONTEXT ---
Era Average TS%: {self.era_avg_ts:.1f}%
Relative TS%: {self.relative_ts:+.1f}%

--- CLASSIFICATION ---
{self._classify_scorer()}
"""
        return report

    def _classify_scorer(self) -> str:
        """Classify the scorer based on efficiency and volume."""
        # High volume threshold: 20+ PPG or 15+ TSA/game
        high_volume = self.ppg >= 20 or self.tsa_per_game >= 15

        # High efficiency threshold: TS% 3+ above era average
        high_efficiency = self.relative_ts >= 3

        if high_volume and high_efficiency:
            return "Classification: ELITE VOLUME SCORER"
        elif high_volume and not high_efficiency:
            if self.relative_ts >= 0:
                return "Classification: AVERAGE EFFICIENCY VOLUME SCORER"
            else:
                return "Classification: INEFFICIENT VOLUME SCORER"
        elif not high_volume and high_efficiency:
            return "Classification: EFFICIENT ROLE PLAYER"
        else:
            return "Classification: LIMITED SCORER"


class TeamShootingAnalyzer:
    """Analyze team-level shooting efficiency."""

    def __init__(self, team_name: str, season: str):
        self.team_name = team_name
        self.season = season
        self.players: List[ScorerProfile] = []

    def add_player(self, player: ScorerProfile):
        """Add a player to the team."""
        self.players.append(player)

    def team_efg(self) -> float:
        """Calculate team-level eFG%."""
        total_fgm = sum(p.fgm for p in self.players)
        total_3pm = sum(p.three_pm for p in self.players)
        total_fga = sum(p.fga for p in self.players)

        if total_fga == 0:
            return 0.0
        return ((total_fgm + 0.5 * total_3pm) / total_fga) * 100

    def team_ts(self) -> float:
        """Calculate team-level TS%."""
        total_pts = sum(p.points for p in self.players)
        total_fga = sum(p.fga for p in self.players)
        total_fta = sum(p.fta for p in self.players)

        tsa = total_fga + 0.44 * total_fta
        if tsa == 0:
            return 0.0
        return (total_pts / (2 * tsa)) * 100

    def shot_distribution_summary(self) -> dict:
        """Summarize team shot distribution."""
        total_fga = sum(p.fga for p in self.players)
        total_3pa = sum(p.three_pa for p in self.players)

        return {
            'three_par': total_3pa / total_fga if total_fga > 0 else 0,
            'two_par': 1 - (total_3pa / total_fga) if total_fga > 0 else 0,
            'total_fga': total_fga
        }

    def efficiency_leaders(self, min_attempts: int = 100) -> List[ScorerProfile]:
        """Return players sorted by TS% (with minimum attempts)."""
        qualified = [p for p in self.players if p.fga >= min_attempts]
        return sorted(qualified, key=lambda p: p.ts_pct, reverse=True)

8.10 Advanced Topics and Extensions

8.10.1 Adjusted True Shooting Percentage

Some analysts adjust TS% for factors like:

  • Opponent defense quality: Facing elite defenses vs. weak defenses
  • Game situation: Clutch shots vs. garbage time
  • Shot difficulty: Contested vs. open shots
  • Home/away splits: Accounting for venue effects

8.10.2 Expected True Shooting (xTS%)

Using shot tracking data, we can calculate expected efficiency based on shot location and defender proximity, then compare to actual results:

$$\text{xTS\%} = \text{Expected Points} / (2 \times TSA)$$

$$\text{Luck Factor} = \text{TS\%} - \text{xTS\%}$$

Positive luck factor suggests shooting above expectation; negative suggests underperformance.

8.10.3 Usage-Adjusted Efficiency

High-usage scorers typically have lower efficiency due to taking more difficult shots. We can model expected efficiency as a function of usage:

$$E[\text{TS\%}] = \alpha - \beta \times \text{Usage Rate}$$

Players significantly above this curve are exceptional; those below are inefficient given their role.

8.10.4 The Future of Shooting Metrics

Emerging areas of analysis include:

  1. Spatial tracking: Exact shot locations and defender positions
  2. Shot creation value: Credit for creating shots for others
  3. Decision quality: Whether the best shot was taken
  4. Fatigue adjustments: Efficiency changes through games/seasons
  5. Injury impact: Efficiency changes when teammates are injured

8.11 Summary

This chapter has traced the evolution of shooting efficiency measurement from simple Field Goal Percentage through sophisticated composite metrics. Key takeaways include:

  1. FG% is insufficient: It fails to account for three-point shooting's extra value

  2. eFG% improves on FG%: By weighting made threes as 1.5 field goals, it properly values three-point shooting

  3. TS% is the gold standard: By incorporating free throws, it provides the most comprehensive measure of scoring efficiency

  4. Context matters: Era, usage, shot difficulty, and defense all influence interpretation

  5. Shot selection drives efficiency: Where and how players shoot matters as much as accuracy

  6. The three-point revolution is mathematical: Expected value analysis shows why modern teams prioritize threes

  7. Free throws are valuable: High free throw rates boost efficiency significantly

  8. Historical comparisons require adjustment: Era context is essential for cross-generation analysis

Understanding these metrics is foundational for modern basketball analysis. As you proceed through this textbook, you will see these concepts applied to player evaluation, team construction, and strategic decision-making. The ability to accurately measure scoring efficiency has fundamentally transformed how basketball is played and analyzed.


Chapter Summary Formulas

Metric Formula
FG% $\frac{FGM}{FGA} \times 100$
eFG% $\frac{FGM + 0.5 \times 3PM}{FGA} \times 100$
TS% $\frac{PTS}{2 \times (FGA + 0.44 \times FTA)} \times 100$
TSA $FGA + 0.44 \times FTA$
FTr $\frac{FTA}{FGA}$
3PAr $\frac{3PA}{FGA}$
PPS $\frac{PTS}{TSA}$
EV (2-pt) $2P\% \times 2$
EV (3-pt) $3P\% \times 3$
3PT Break-Even $\frac{2}{3} \times 2P\%$

References

  1. Kubatko, J., Oliver, D., Pelton, K., & Rosenbaum, D. T. (2007). A Starting Point for Analyzing Basketball Statistics. Journal of Quantitative Analysis in Sports, 3(3).

  2. Oliver, D. (2004). Basketball on Paper: Rules and Tools for Performance Analysis. Potomac Books.

  3. Goldsberry, K. (2019). SprawlBall: A Visual Tour of the New Era of the NBA. Houghton Mifflin Harcourt.

  4. Shea, S., & Baker, C. (2013). Basketball Analytics: Objective and Efficient Strategies for Understanding How Teams Win. CreateSpace.

  5. Silver, N. (2012). The Signal and the Noise: Why So Many Predictions Fail--but Some Don't. Penguin Press.