Chapter 7 Beginner ~25 min read 5 sections

Shooting Efficiency Metrics (TS%, eFG%)

Shooting efficiency lies at the heart of offensive basketball. This chapter explores metrics that evaluate shooting performance, including True Shooting Percentage, Effective Field Goal Percentage, and points per shot attempt. You will learn to calculate these metrics, understand their theoretical foundations, and apply them appropriately.

Beyond Simple Field Goal Percentage

Scoring efficiency determines much of basketball success at both individual and team levels. Teams that generate more points per possession win more games, all else equal. Players who score efficiently contribute more than less efficient counterparts, even when total points are similar. Understanding efficiency metrics enables evaluation of offensive performance beyond raw counting statistics, revealing true shooting ability beneath the noise of volume and opportunity.

Simple field goal percentage—made field goals divided by attempted field goals—served as the primary efficiency measure for decades. While intuitive, this statistic fails to account for the different values of two-point and three-point shots. A player shooting 40% from three generates 1.2 expected points per attempt, equivalent to 60% two-point shooting. Treating these equivalently obscures meaningful differences in offensive value.

The three-point revolution in basketball has made this limitation increasingly consequential. As teams have recognized the mathematical advantage of three-point shooting, the share of attempts from beyond the arc has increased dramatically. Evaluating players and teams using simple field goal percentage increasingly misrepresents actual offensive efficiency. Modern metrics address this gap through explicit accounting for shot value.

Effective Field Goal Percentage

Effective field goal percentage adjusts for the added value of three-point shots by weighting them appropriately in the percentage calculation. The formula adds half of made threes to total makes before dividing by attempts: eFG% = (FGM + 0.5 Ă— 3PM) / FGA. This adjustment effectively values three-pointers at 1.5 times two-pointers, aligning the metric with expected point production.

The mathematics behind this adjustment warrant understanding. A made three-pointer is worth 1.5 times a made two-pointer in terms of points. By adding 0.5 × 3PM to the numerator, we credit each made three with the equivalent value of 1.5 made two-pointers. The resulting percentage directly relates to expected points per field goal attempt—multiply eFG% by two to get expected points per shot.

Effective field goal percentage enables fair comparison between different shooting profiles. A player shooting 35% from three and 50% from two can be compared directly to one shooting 45% on all twos. The former produces 1.05 expected points per three and 1.00 per two; the latter produces 0.90 per attempt regardless of location. Despite similar or lower raw percentages, the three-point shooter may generate better offensive efficiency.

At the team level, effective field goal percentage represents one of the strongest predictors of offensive success. Dean Oliver's Four Factors framework identifies eFG% as the most important factor in team offensive efficiency. Teams with the highest effective field goal percentages consistently rank among the best offenses, while those with the lowest struggle to score efficiently regardless of other strengths.

True Shooting Percentage

True shooting percentage extends efficiency measurement to include free throws, providing the most comprehensive single measure of scoring efficiency. Basketball possessions end in various ways—made shots, missed shots, turnovers, and trips to the free throw line. A complete picture of scoring efficiency must account for all scoring, not just field goals.

The true shooting formula divides points by true shooting attempts, where attempts equal field goal attempts plus a coefficient times free throw attempts: TS% = PTS / (2 Ă— (FGA + 0.44 Ă— FTA)). The coefficient 0.44 approximates the average possession cost of free throws, accounting for the fact that some free throws come in pairs while others come in threes or as technical/flagrant situations.

The 0.44 coefficient merits explanation. On average, free throw attempts cost approximately 0.44 possessions. This average reflects the mix of two-shot fouls, three-shot fouls on threes, and-one opportunities, technical fouls, and other situations. Using this coefficient converts free throw attempts into possession-equivalent terms, enabling direct comparison with field goal attempts.

True shooting percentage provides the closest single-number approximation to points per possession for an individual scorer. A player with 60% TS% produces about 1.2 points per true shooting attempt, regardless of whether those points come from twos, threes, or free throws. This comprehensive view enables comparison across players with different scoring profiles.

Contextualizing Efficiency

League average efficiency provides essential context for individual evaluation. What constitutes good efficiency depends heavily on the era, as both three-point shooting rates and overall efficiency have varied substantially over NBA history. A 55% true shooting percentage that was excellent in 2000 would be below average today. Metrics like TS+ that center on league average enable comparison across eras.

Position and role affect expected efficiency levels. Centers who primarily attempt shots near the basket typically post higher eFG% than guards who shoot from distance. Primary scorers who create their own shots often show lower efficiency than catch-and-shoot specialists who attempt only open looks. These contextual factors must inform interpretation of efficiency metrics.

Volume and efficiency typically trade off in important ways. Very few players maintain elite efficiency at high usage rates. As shot volume increases, the quality of available shots generally decreases—players must attempt more difficult shots as defensive attention increases. Evaluating scorers requires considering both dimensions, as a player with slightly lower efficiency but much higher volume may contribute more total value.

Shot quality adjustments attempt to separate shot selection from shot making. A player taking many difficult shots might show modest TS% despite excellent shot making ability. Expected points models that account for shot difficulty enable comparison of actual efficiency against expected efficiency given the shots attempted. Positive differences indicate shot making skill; negative differences suggest room for improvement.

Applying Efficiency Metrics

Player evaluation benefits from considering efficiency alongside volume and role. Raw efficiency comparisons can mislead when players face different shot distributions or defensive attention. Comparing players in similar situations—similar usage, similar shot types, similar defensive attention—provides more meaningful assessments. The exercises accompanying this chapter guide you through such comparisons.

Team efficiency analysis aggregates individual efficiency to assess offensive performance. Team effective field goal percentage summarizes shooting quality across all players and attempts. Decomposing team eFG% by shot type, location, and player reveals where efficiency comes from and where improvement might be found. This analysis informs strategic decisions about shot selection and player usage.

Trend analysis tracks efficiency changes over time. Career trajectories often show efficiency improvements in early years as players develop skills and learn to select better shots, followed by gradual decline as athleticism fades. Within-season trends might reveal the effects of fatigue, injury, or adjustment to new roles. Tracking these patterns supports player evaluation and projection.

Predictive applications use efficiency metrics to forecast future performance. While efficiency shows meaningful year-over-year stability, extreme values tend to regress toward the mean. A player with unusually high or low efficiency relative to their historical baseline will likely move back toward normal. Accounting for this regression improves prediction accuracy.

Implementation in R

# Calculate shooting efficiency metrics
library(tidyverse)

calculate_shooting_efficiency <- function(fgm, fga, fg3m, ftm, fta, pts) {
  tibble(
    fg_pct = round(fgm / fga, 3),
    efg_pct = round((fgm + 0.5 * fg3m) / fga, 3),
    ts_pct = round(pts / (2 * (fga + 0.44 * fta)), 3)
  )
}

# Apply to player data
player_stats <- read_csv("player_stats.csv")

shooting_metrics <- player_stats %>%
  mutate(
    fg_pct = round(fgm / fga, 3),
    efg_pct = round((fgm + 0.5 * fg3m) / fga, 3),
    ts_pct = round(pts / (2 * (fga + 0.44 * fta)), 3)
  ) %>%
  select(player_name, pts, fg_pct, efg_pct, ts_pct) %>%
  filter(pts >= 15) %>%
  arrange(desc(ts_pct))

head(shooting_metrics, 20)
# Shot profile analysis
library(tidyverse)

analyze_shot_profile <- function(player_data) {
  player_data %>%
    mutate(
      rim_rate = round(rim_fga / fga, 3),
      mid_rate = round(mid_fga / fga, 3),
      three_rate = round(fg3a / fga, 3),
      rim_pct = round(rim_fgm / rim_fga, 3),
      mid_pct = round(mid_fgm / mid_fga, 3),
      three_pct = round(fg3m / fg3a, 3)
    ) %>%
    mutate(
      # Expected points per shot by zone
      rim_pps = round(rim_pct * 2, 2),
      mid_pps = round(mid_pct * 2, 2),
      three_pps = round(three_pct * 3, 2)
    )
}

player_shots <- read_csv("player_shot_zones.csv")
shot_analysis <- analyze_shot_profile(player_shots)
print(shot_analysis)

Implementation in Python

# Calculate shooting efficiency metrics
import pandas as pd

def calculate_shooting_efficiency(row):
    fg_pct = row["fgm"] / row["fga"] if row["fga"] > 0 else 0
    efg_pct = (row["fgm"] + 0.5 * row["fg3m"]) / row["fga"] if row["fga"] > 0 else 0
    ts_pct = row["pts"] / (2 * (row["fga"] + 0.44 * row["fta"])) if (row["fga"] + row["fta"]) > 0 else 0
    return pd.Series({
        "fg_pct": round(fg_pct, 3),
        "efg_pct": round(efg_pct, 3),
        "ts_pct": round(ts_pct, 3)
    })

# Apply to player data
player_stats = pd.read_csv("player_stats.csv")
efficiency = player_stats.apply(calculate_shooting_efficiency, axis=1)
player_stats = pd.concat([player_stats, efficiency], axis=1)

# Filter high scorers and sort by TS%
high_scorers = player_stats[player_stats["pts"] >= 15].sort_values(
    "ts_pct", ascending=False
)[["player_name", "pts", "fg_pct", "efg_pct", "ts_pct"]]

print(high_scorers.head(20))
# Shot profile analysis
import pandas as pd

def analyze_shot_profile(df):
    result = df.copy()

    # Shot distribution rates
    result["rim_rate"] = (result["rim_fga"] / result["fga"]).round(3)
    result["mid_rate"] = (result["mid_fga"] / result["fga"]).round(3)
    result["three_rate"] = (result["fg3a"] / result["fga"]).round(3)

    # Zone percentages
    result["rim_pct"] = (result["rim_fgm"] / result["rim_fga"]).round(3)
    result["mid_pct"] = (result["mid_fgm"] / result["mid_fga"]).round(3)
    result["three_pct"] = (result["fg3m"] / result["fg3a"]).round(3)

    # Expected points per shot
    result["rim_pps"] = (result["rim_pct"] * 2).round(2)
    result["mid_pps"] = (result["mid_pct"] * 2).round(2)
    result["three_pps"] = (result["three_pct"] * 3).round(2)

    return result

player_shots = pd.read_csv("player_shot_zones.csv")
shot_analysis = analyze_shot_profile(player_shots)
print(shot_analysis)

Implementation in R

# Calculate shooting efficiency metrics
library(tidyverse)

calculate_shooting_efficiency <- function(fgm, fga, fg3m, ftm, fta, pts) {
  tibble(
    fg_pct = round(fgm / fga, 3),
    efg_pct = round((fgm + 0.5 * fg3m) / fga, 3),
    ts_pct = round(pts / (2 * (fga + 0.44 * fta)), 3)
  )
}

# Apply to player data
player_stats <- read_csv("player_stats.csv")

shooting_metrics <- player_stats %>%
  mutate(
    fg_pct = round(fgm / fga, 3),
    efg_pct = round((fgm + 0.5 * fg3m) / fga, 3),
    ts_pct = round(pts / (2 * (fga + 0.44 * fta)), 3)
  ) %>%
  select(player_name, pts, fg_pct, efg_pct, ts_pct) %>%
  filter(pts >= 15) %>%
  arrange(desc(ts_pct))

head(shooting_metrics, 20)
# Shot profile analysis
library(tidyverse)

analyze_shot_profile <- function(player_data) {
  player_data %>%
    mutate(
      rim_rate = round(rim_fga / fga, 3),
      mid_rate = round(mid_fga / fga, 3),
      three_rate = round(fg3a / fga, 3),
      rim_pct = round(rim_fgm / rim_fga, 3),
      mid_pct = round(mid_fgm / mid_fga, 3),
      three_pct = round(fg3m / fg3a, 3)
    ) %>%
    mutate(
      # Expected points per shot by zone
      rim_pps = round(rim_pct * 2, 2),
      mid_pps = round(mid_pct * 2, 2),
      three_pps = round(three_pct * 3, 2)
    )
}

player_shots <- read_csv("player_shot_zones.csv")
shot_analysis <- analyze_shot_profile(player_shots)
print(shot_analysis)

Implementation in Python

# Calculate shooting efficiency metrics
import pandas as pd

def calculate_shooting_efficiency(row):
    fg_pct = row["fgm"] / row["fga"] if row["fga"] > 0 else 0
    efg_pct = (row["fgm"] + 0.5 * row["fg3m"]) / row["fga"] if row["fga"] > 0 else 0
    ts_pct = row["pts"] / (2 * (row["fga"] + 0.44 * row["fta"])) if (row["fga"] + row["fta"]) > 0 else 0
    return pd.Series({
        "fg_pct": round(fg_pct, 3),
        "efg_pct": round(efg_pct, 3),
        "ts_pct": round(ts_pct, 3)
    })

# Apply to player data
player_stats = pd.read_csv("player_stats.csv")
efficiency = player_stats.apply(calculate_shooting_efficiency, axis=1)
player_stats = pd.concat([player_stats, efficiency], axis=1)

# Filter high scorers and sort by TS%
high_scorers = player_stats[player_stats["pts"] >= 15].sort_values(
    "ts_pct", ascending=False
)[["player_name", "pts", "fg_pct", "efg_pct", "ts_pct"]]

print(high_scorers.head(20))
# Shot profile analysis
import pandas as pd

def analyze_shot_profile(df):
    result = df.copy()

    # Shot distribution rates
    result["rim_rate"] = (result["rim_fga"] / result["fga"]).round(3)
    result["mid_rate"] = (result["mid_fga"] / result["fga"]).round(3)
    result["three_rate"] = (result["fg3a"] / result["fga"]).round(3)

    # Zone percentages
    result["rim_pct"] = (result["rim_fgm"] / result["rim_fga"]).round(3)
    result["mid_pct"] = (result["mid_fgm"] / result["mid_fga"]).round(3)
    result["three_pct"] = (result["fg3m"] / result["fg3a"]).round(3)

    # Expected points per shot
    result["rim_pps"] = (result["rim_pct"] * 2).round(2)
    result["mid_pps"] = (result["mid_pct"] * 2).round(2)
    result["three_pps"] = (result["three_pct"] * 3).round(2)

    return result

player_shots = pd.read_csv("player_shot_zones.csv")
shot_analysis = analyze_shot_profile(player_shots)
print(shot_analysis)
Chapter Summary

You've completed Chapter 7: Shooting Efficiency Metrics (TS%, eFG%).