True Shooting Percentage (TS%)
True Shooting Percentage (TS%): The Complete Guide
True Shooting Percentage (TS%) is the most comprehensive shooting efficiency metric in basketball analytics. Unlike traditional field goal percentage, TS% accounts for the varying point values of different shot types—two-pointers, three-pointers, and free throws—providing a unified measure of a player's scoring efficiency.
What is True Shooting Percentage?
True Shooting Percentage measures how efficiently a player scores points by accounting for all shooting possessions. It answers the question: "How many points does a player score per shooting attempt, adjusted for the fact that different shots are worth different amounts?"
The metric was developed to address the limitations of traditional field goal percentage (FG%), which treats all field goals equally regardless of whether they're worth two or three points, and ignores free throws entirely. TS% provides a holistic view of offensive efficiency by incorporating:
- Field Goal Attempts (FGA) - Both 2-point and 3-point attempts
- Free Throw Attempts (FTA) - Adjusted for possession usage
- Points Scored (PTS) - Total points from all sources
The True Shooting Percentage Formula
The formula for calculating True Shooting Percentage is:
TS% = PTS / (2 × (FGA + 0.44 × FTA))
Where:
- PTS = Total points scored
- FGA = Field goal attempts (2PT + 3PT attempts)
- FTA = Free throw attempts
- 0.44 = Free throw possession adjustment factor
The denominator represents the total number of shooting possessions used, multiplied by 2 to normalize the result as a percentage comparable to field goal percentage.
Breaking Down the Formula
Let's examine each component:
Numerator (PTS): The total points scored includes all scoring methods—two-point field goals, three-point field goals, and free throws. This captures the offensive output we're measuring for efficiency.
Denominator (2 × TSA): This represents twice the True Shooting Attempts (TSA). The factor of 2 normalizes the percentage so it's scaled similarly to field goal percentage, making it intuitively comparable.
True Shooting Attempts (TSA): Calculated as FGA + 0.44 × FTA, this estimates the number of possessions used by a player's scoring attempts.
Why 0.44 for Free Throws?
The 0.44 coefficient is one of the most frequently questioned aspects of the TS% formula. This factor exists because free throws don't always represent full possessions, and the relationship between free throw attempts and possessions used is not 1:1.
The Logic Behind 0.44
Consider these scenarios:
- Two-shot fouls: A player shoots 2 free throws, using 1 possession (ratio: 2 FTA / 1 possession = 2.0)
- Three-shot fouls: A player shoots 3 free throws, using 1 possession (ratio: 3 FTA / 1 possession = 3.0)
- And-one situations: A player shoots 1 free throw, but the possession was already counted in FGA (ratio: 1 FTA / 0 additional possessions = ∞)
- Technical fouls: 1 free throw, 0 possessions used (ratio: undefined)
- Flagrant fouls: 2 free throws, team retains possession (ratio: 2 FTA / 0 possessions = ∞)
Through empirical analysis of NBA data, researchers determined that on average, approximately 0.44 free throw attempts equal one possession used. This accounts for the mix of and-one opportunities, technical fouls, and flagrant fouls where free throws don't represent full possessions.
The coefficient has remained remarkably stable across different eras of basketball, though some analysts argue for slight adjustments (ranging from 0.40 to 0.475) depending on league rules and officiating trends. The value 0.44 represents the league-wide average and provides consistency for historical comparisons.
TS% vs FG% vs eFG%: Shooting Efficiency Metrics Compared
Understanding the differences between shooting efficiency metrics is crucial for proper basketball analysis:
Field Goal Percentage (FG%)
FG% = FGM / FGA
Strengths: Simple, intuitive, easy to calculate and understand
Limitations:
- Treats all field goals equally (ignores 3-point vs 2-point value difference)
- Completely excludes free throws
- Disadvantages players who take more three-pointers
- Provides incomplete picture of scoring efficiency
Example: A player shooting 10/20 on two-pointers (20 points) has the same FG% (50%) as a player shooting 7/20 on three-pointers (21 points), despite the latter being more efficient.
Effective Field Goal Percentage (eFG%)
eFG% = (FGM + 0.5 × 3PM) / FGA
Strengths: Adjusts for three-point value, providing better field goal efficiency measure than FG%
Limitations:
- Still ignores free throws completely
- Undervalues players who get to the free throw line frequently
- Incomplete for players whose game involves drawing fouls
Example: A player shooting 8/20 from the field with 4 three-pointers has eFG% = (8 + 0.5 × 4) / 20 = 50%, properly crediting the three-point shooting.
True Shooting Percentage (TS%)
TS% = PTS / (2 × (FGA + 0.44 × FTA))
Strengths:
- Comprehensive measure including all scoring methods
- Accounts for two-pointers, three-pointers, and free throws
- Most accurate reflection of overall scoring efficiency
- Properly values players who excel at drawing fouls
- Standard metric for modern basketball analytics
Limitations:
- Slightly more complex to calculate
- The 0.44 coefficient is an approximation
- Less intuitive than simple FG%
Comparative Example
Consider a player with this stat line: 8/18 FG (4 three-pointers made), 8/10 FT, 28 points
- FG%: 8/18 = 44.4% (looks below average)
- eFG%: (8 + 0.5 × 4) / 18 = 55.6% (above average)
- TS%: 28 / (2 × (18 + 0.44 × 10)) = 28 / 44.8 = 62.5% (elite efficiency)
This player is highly efficient, drawing fouls and converting free throws at a high rate, which only TS% fully captures.
League Average TS% Trends Over Time
True Shooting Percentage has evolved significantly throughout NBA history, reflecting changes in playing style, rules, and strategic understanding:
Historical Era (1980s-1990s)
- League Average TS%: 53-54%
- Characteristics: Mid-range focused, limited three-point shooting, physical defense
- Context: Three-point line introduced in 1979-80, but not widely utilized as strategic weapon
Traditional Era (2000s-2010)
- League Average TS%: 53-54%
- Characteristics: Post-play emphasis, isolation-heavy offense, increasing three-point attempts
- Context: Hand-checking rules changed (2004), opening up perimeter play
Analytics Revolution (2011-2016)
- League Average TS%: 53-55%
- Characteristics: Three-point revolution begins, decline of mid-range shots, pace increases
- Context: Warriors' success (starting 2015) validates three-point-heavy strategies
Modern Era (2017-2020)
- League Average TS%: 55-56%
- Characteristics: Three-point attempts surge, emphasis on rim attempts and three-pointers
- Context: "Moreyball" philosophy widespread, mid-range shots at historic lows
Current Era (2021-Present)
- League Average TS%: 56-58%
- Characteristics: Peak shooting efficiency, optimal shot selection, skilled big men
- Context: Every position shoots threes, defensive rules favor offense, spacing maximized
Factors Driving TS% Increases
- Shot Selection: Elimination of inefficient mid-range shots in favor of threes and rim attempts
- Spacing: Five-out offenses creating better driving lanes and open shots
- Player Development: Improved shooting training and skill development at all positions
- Analytics: Data-driven decision making optimizing shot selection
- Rule Changes: Defensive restrictions favoring offensive players
- Pace: Faster pace creating more transition opportunities (higher efficiency)
How to Interpret True Shooting Percentage
Understanding TS% benchmarks helps evaluate player performance in context:
Elite Efficiency: 60%+
Interpretation: Exceptional scoring efficiency, among the best in the league
Typical Players:
- Dominant big men with high FG% near the rim (e.g., Rudy Gobert, Clint Capela)
- Superstar scorers with well-rounded offensive games (e.g., Stephen Curry, Kevin Durant)
- Efficient role players with limited but high-quality shot selection
Context: Approximately 10-15% of rotation players achieve this level. For high-usage players (20+ FGA per game), 60%+ TS% is historically elite.
Good Efficiency: 55-60%
Interpretation: Above-average scoring efficiency, solid offensive contributors
Typical Players:
- Quality starters and key rotation players
- Well-rounded offensive players with balanced shot diets
- Good three-point shooters with decent volume
Context: This range represents above-league-average efficiency. Players consistently in this range are valuable offensive pieces.
Average Efficiency: 52-55%
Interpretation: League-average scoring efficiency
Typical Players:
- Rotation players with mixed efficiency
- Players still developing their offensive games
- High-volume scorers with moderate efficiency
Context: Roughly half of NBA players fall in or below this range. For role players, this is acceptable; for primary scorers, it suggests room for improvement.
Below Average: 48-52%
Interpretation: Below-average efficiency, offensive limitations
Typical Players:
- Poor shooters or shot-selectors
- Players forced into roles beyond their capabilities
- Rookies and young players still adjusting
Context: For rotation players, this efficiency level is concerning. May indicate poor fit, bad shot selection, or limited offensive skills.
Poor Efficiency: Below 48%
Interpretation: Significant offensive struggles
Typical Players:
- Non-shooters forced to create offense
- Players in severe slumps
- End-of-bench players with limited minutes
Context: Unsustainable for players with significant offensive roles. Often indicates need for role adjustment or development.
Context Matters
When evaluating TS%, always consider:
- Usage Rate: High-usage players (25%+ USG) face tougher defenses and more difficult shots. A 57% TS% at high usage is more impressive than 62% TS% as a low-usage role player.
- Role: Primary creators face different defensive attention than spot-up shooters or rim-runners.
- Sample Size: Small sample sizes (10-20 games) can show extreme TS% values that don't reflect true ability.
- Era: Compare players to league average of their era, not absolute numbers.
- Position: Different positions have different TS% norms (discussed below).
Limitations of True Shooting Percentage
Despite being the gold standard for scoring efficiency, TS% has important limitations:
1. Context-Neutral Measurement
TS% doesn't account for shot difficulty or defensive attention. A player taking wide-open catch-and-shoot threes as a role player may have a higher TS% than a superstar creating difficult shots against elite defenders, but the latter is often more valuable.
Example: A role player shooting 65% TS% on open shots isn't necessarily more efficient than a primary scorer posting 58% TS% while drawing double teams and creating for others.
2. The 0.44 Coefficient is an Approximation
The free throw adjustment factor is a league-wide average that doesn't perfectly apply to every player or situation. Players who draw many and-one fouls or technical free throws may have their possessions slightly miscounted.
3. Doesn't Measure Shot Creation
TS% treats all points equally regardless of whether they came from self-created shots, assisted shots, or free throws. A player who creates their own shot is often more valuable than one who requires creation from others, even at similar efficiency.
4. Sample Size Sensitivity
Small sample sizes can produce misleading TS% values. A player who makes their only shot attempt (3 points on 1 FGA) has 150% TS%, which is obviously unsustainable and meaningless.
5. Ignores Non-Scoring Value
TS% only measures scoring efficiency. It doesn't capture playmaking, defense, rebounding, or other valuable contributions. A player with 52% TS% who's an elite passer and defender may be more valuable than a 60% TS% player who's limited elsewhere.
6. Doesn't Account for Pace or Minutes
TS% is a rate stat that doesn't reflect volume. A player with 65% TS% scoring 8 points per game is less impactful than one with 58% TS% scoring 28 points per game.
7. Team Context Effects
Playing with better teammates creates better spacing and easier shots, inflating TS%. Playing on poor offensive teams with bad spacing depresses TS%. The metric doesn't fully isolate individual efficiency from team effects.
8. Doesn't Distinguish Shot Types
TS% combines all scoring into one number. A player might be incredibly efficient at threes but poor at twos, or vice versa, but you can't tell from TS% alone. Supplementary metrics like eFG% and FT rate provide important additional context.
Historical and Current TS% Leaders
All-Time Single-Season TS% Leaders (Minimum 1000 Minutes)
- Rudy Gobert (2020-21): 72.7% TS% - Elite rim-running center, almost exclusively dunks and layups
- Mitchell Robinson (2019-20): 72.4% TS% - Athletic rim-runner, minimal shot creation required
- DeAndre Jordan (2018-19): 71.4% TS% - Traditional rim-running big, high FG% near basket
- Cedric Maxwell (1978-79): 70.9% TS% - Early three-point era, efficient finisher
- Artis Gilmore (1981-82): 70.7% TS% - Dominant low-post scorer, limited range
Pattern: Single-season leaders are typically rim-running big men with limited offensive roles but extremely high shooting percentages on attempts near the basket.
All-Time Career TS% Leaders (Minimum 10,000 Minutes)
- Rudy Gobert: 67.0% TS% - Modern rim-running specialist
- Cedric Maxwell: 60.6% TS% - Efficient forward from 1970s-80s
- Tyson Chandler: 60.1% TS% - Defensive-minded center, efficient finisher
- Stephen Curry: 62.4% TS% - Highest among high-usage players, elite shooter
- Artis Gilmore: 60.0% TS% - Dominant center from 1970s-80s ABA/NBA
Pattern: Career leaders mix rim-running specialists with elite offensive superstars. Stephen Curry stands out as having elite TS% while also carrying massive offensive load.
TS% Leaders Among High-Usage Players (25%+ Career USG%)
- Stephen Curry: 62.4% TS%, 29.0% USG% - Revolutionary shooter, best efficiency/volume combination
- Kevin Durant: 61.3% TS%, 29.2% USG% - Elite scorer at all three levels
- LeBron James: 58.6% TS%, 31.5% USG% - Versatile scorer and playmaker
- Karl-Anthony Towns: 61.0% TS%, 27.1% USG% - Modern stretch big
- Giannis Antetokounmpo: 59.5% TS%, 32.0% USG% - Dominant paint scorer
Pattern: High-usage players with elite TS% are historically great offensive players. Combining volume and efficiency at this level is extremely rare.
Recent Season TS% Leaders (2023-24, Minimum 50 Games)
- Rudy Gobert: 69.2% TS% - Continued dominance as rim finisher
- Nic Claxton: 68.5% TS% - Athletic rim-runner and finisher
- Daniel Gafford: 68.1% TS% - High-energy big man
- Nikola Jokic: 65.3% TS% - Elite efficiency with high usage and creation
- Stephen Curry: 64.0% TS% - Maintains elite efficiency into mid-30s
Observation: Modern NBA sees multiple players exceeding 65% TS% annually, reflecting improved league-wide efficiency and shot selection.
Notable Current Stars and Their Career TS%
- Luka Doncic: 57.4% - High usage but below elite efficiency
- Joel Embiid: 59.2% - Elite center with well-rounded scoring
- Damian Lillard: 58.5% - High-volume three-point shooter
- Jayson Tatum: 57.4% - Developing efficiency with high usage
- Anthony Davis: 59.8% - Versatile big with high efficiency
Position-Specific TS% Expectations
Different positions face different offensive demands and opportunities, resulting in varying TS% norms:
Centers
Expected TS% Range: 58-65%
Characteristics:
- Highest TS% of any position due to shot proximity to basket
- Majority of attempts are high-percentage shots near rim
- Often assisted on field goals, reducing difficulty
- Modern centers increasingly adding three-point shooting
Elite Modern Centers (65%+ TS%): Rudy Gobert, Nikola Jokic, Jarrett Allen, Clint Capela
Context: Centers below 55% TS% are typically poor offensive players or forced into roles that don't match their skills. Traditional back-to-the-basket centers may have lower TS% than modern rim-runners.
Power Forwards
Expected TS% Range: 55-62%
Characteristics:
- Wide variation based on role (stretch 4s vs. traditional post players)
- Modern power forwards often function as floor-spacing shooters
- Mix of rim attempts, mid-range, and three-pointers
- More shot creation required than centers, less than guards
Elite Modern Power Forwards (60%+ TS%): Giannis Antetokounmpo, Kevin Durant (when playing PF), Zion Williamson, Pascal Siakam
Context: Position has evolved dramatically. Traditional power forwards had lower TS% (52-56%) compared to modern stretch 4s (56-60%+).
Small Forwards
Expected TS% Range: 54-60%
Characteristics:
- Most versatile position with widest range of offensive roles
- Balance between shot creation and efficiency
- Often primary or secondary scorers with moderate usage
- Shot diet typically includes all three zones (rim, mid-range, three)
Elite Modern Small Forwards (60%+ TS%): LeBron James, Kawhi Leonard (when healthy), Jimmy Butler
Context: 3-and-D wings (lower usage, spot-up shooting) typically post 57-62% TS%. Primary scorers at the position usually range 55-58% TS%.
Shooting Guards
Expected TS% Range: 53-58%
Characteristics:
- High volume of perimeter shots (mid-range and three-pointers)
- Often secondary shot creators
- More difficult shot profile than bigger positions
- Modern SGs increasingly three-point specialists
Elite Modern Shooting Guards (60%+ TS%): Devin Booker, Donovan Mitchell (in efficient seasons), Klay Thompson (pre-injury)
Context: Position average has increased with three-point revolution. Traditional mid-range-heavy SGs (52-55%) vs. modern three-point shooters (56-60%).
Point Guards
Expected TS% Range: 53-58%
Characteristics:
- Highest shot creation responsibility
- Often face top defensive attention
- Mix of finishing at rim, mid-range, and three-pointers
- Trade-off between scoring and playmaking
Elite Modern Point Guards (60%+ TS%): Stephen Curry, Chris Paul, Tyrese Haliburton
Context: Point guards with elite TS% (58%+) at high usage are extremely valuable. Traditional floor-general point guards (52-55%) vs. modern scoring point guards (56-60%).
Position Comparison Summary
| Position | Elite TS% | Good TS% | Average TS% | Below Average TS% |
|---|---|---|---|---|
| Center | 65%+ | 60-65% | 56-60% | <56% |
| Power Forward | 62%+ | 58-62% | 54-58% | <54% |
| Small Forward | 60%+ | 56-60% | 53-56% | <53% |
| Shooting Guard | 60%+ | 56-60% | 53-56% | <53% |
| Point Guard | 60%+ | 56-60% | 53-56% | <53% |
Code Examples: Calculating and Analyzing True Shooting Percentage
Python: Basic TS% Calculation
import pandas as pd
import numpy as np
def calculate_ts_percentage(points, fga, fta):
"""
Calculate True Shooting Percentage.
Parameters:
-----------
points : int or float
Total points scored
fga : int or float
Field goal attempts
fta : int or float
Free throw attempts
Returns:
--------
float
True Shooting Percentage (0-1 scale)
"""
if fga + (0.44 * fta) == 0:
return 0.0
ts_percentage = points / (2 * (fga + 0.44 * fta))
return ts_percentage
# Example usage
player_stats = {
'name': 'Stephen Curry',
'points': 267,
'fga': 201,
'fta': 45
}
ts_pct = calculate_ts_percentage(
player_stats['points'],
player_stats['fga'],
player_stats['fta']
)
print(f"{player_stats['name']}: {ts_pct:.1%} TS%")
# Output: Stephen Curry: 64.2% TS%
# Calculate for multiple players
players_data = {
'Name': ['Stephen Curry', 'LeBron James', 'Rudy Gobert', 'Luka Doncic'],
'PTS': [267, 312, 178, 289],
'FGA': [201, 245, 125, 253],
'FTA': [45, 87, 67, 96]
}
df = pd.DataFrame(players_data)
df['TS%'] = df.apply(
lambda row: calculate_ts_percentage(row['PTS'], row['FGA'], row['FTA']),
axis=1
)
print(df[['Name', 'PTS', 'TS%']].to_string(index=False))
Python: Advanced TS% Analysis with NBA API
import pandas as pd
import numpy as np
import requests
from datetime import datetime
def fetch_player_stats(season='2023-24'):
"""
Fetch player statistics from NBA API.
Parameters:
-----------
season : str
NBA season (e.g., '2023-24')
Returns:
--------
pandas.DataFrame
Player statistics including shooting data
"""
# Example using NBA stats API (simplified)
url = f"https://stats.nba.com/stats/leaguedashplayerstats"
headers = {
'User-Agent': 'Mozilla/5.0',
'Referer': 'https://stats.nba.com/',
}
params = {
'Season': season,
'SeasonType': 'Regular Season',
'PerMode': 'Totals',
'MeasureType': 'Base'
}
try:
response = requests.get(url, headers=headers, params=params)
data = response.json()
headers = data['resultSets'][0]['headers']
rows = data['resultSets'][0]['rowSet']
df = pd.DataFrame(rows, columns=headers)
return df
except Exception as e:
print(f"Error fetching data: {e}")
return None
def analyze_ts_percentage(df, min_games=20):
"""
Analyze True Shooting Percentage across players.
Parameters:
-----------
df : pandas.DataFrame
Player statistics dataframe
min_games : int
Minimum games played filter
Returns:
--------
pandas.DataFrame
Analysis results with TS% calculations
"""
# Filter by minimum games
df_filtered = df[df['GP'] >= min_games].copy()
# Calculate TS%
df_filtered['TS%'] = df_filtered.apply(
lambda row: row['PTS'] / (2 * (row['FGA'] + 0.44 * row['FTA']))
if (row['FGA'] + 0.44 * row['FTA']) > 0 else 0,
axis=1
)
# Calculate additional metrics
df_filtered['FG%'] = df_filtered['FG_PCT']
df_filtered['eFG%'] = (df_filtered['FGM'] + 0.5 * df_filtered['FG3M']) / df_filtered['FGA']
df_filtered['FTr'] = df_filtered['FTA'] / df_filtered['FGA'] # Free throw rate
# Calculate PPG
df_filtered['PPG'] = df_filtered['PTS'] / df_filtered['GP']
# Add efficiency tier
def categorize_ts(ts_pct):
if ts_pct >= 0.60:
return 'Elite'
elif ts_pct >= 0.55:
return 'Good'
elif ts_pct >= 0.52:
return 'Average'
else:
return 'Below Average'
df_filtered['Efficiency_Tier'] = df_filtered['TS%'].apply(categorize_ts)
return df_filtered[['PLAYER_NAME', 'PTS', 'FGA', 'FTA', 'PPG',
'FG%', 'eFG%', 'TS%', 'FTr', 'Efficiency_Tier']]
def compare_efficiency_metrics(df):
"""
Compare different efficiency metrics.
Parameters:
-----------
df : pandas.DataFrame
Player statistics with efficiency metrics
Returns:
--------
dict
Correlation and summary statistics
"""
correlations = {
'TS_vs_FG': df['TS%'].corr(df['FG%']),
'TS_vs_eFG': df['TS%'].corr(df['eFG%']),
'TS_vs_FTr': df['TS%'].corr(df['FTr']),
}
summary = {
'mean_ts': df['TS%'].mean(),
'median_ts': df['TS%'].median(),
'std_ts': df['TS%'].std(),
'max_ts': df['TS%'].max(),
'min_ts': df['TS%'].min()
}
return {
'correlations': correlations,
'summary': summary
}
# Example usage
if __name__ == "__main__":
# Fetch data (or load from CSV)
# df = fetch_player_stats('2023-24')
# Example with sample data
sample_data = {
'PLAYER_NAME': ['Stephen Curry', 'Rudy Gobert', 'Luka Doncic',
'Joel Embiid', 'Damian Lillard'],
'GP': [74, 68, 70, 66, 73],
'PTS': [2168, 1089, 2063, 2239, 1862],
'FGM': [759, 433, 706, 785, 629],
'FGA': [1625, 625, 1577, 1511, 1489],
'FG_PCT': [0.467, 0.693, 0.448, 0.520, 0.422],
'FG3M': [357, 0, 136, 39, 288],
'FTA': [403, 378, 506, 653, 486],
'FTM': [363, 267, 391, 558, 402]
}
df = pd.DataFrame(sample_data)
# Analyze TS%
results = analyze_ts_percentage(df, min_games=60)
print("\nPlayer Efficiency Analysis:")
print(results.to_string(index=False))
# Compare metrics
comparison = compare_efficiency_metrics(results)
print(f"\n\nLeague Average TS%: {comparison['summary']['mean_ts']:.1%}")
print(f"TS% Standard Deviation: {comparison['summary']['std_ts']:.3f}")
print(f"\nCorrelation between TS% and FG%: {comparison['correlations']['TS_vs_FG']:.3f}")
print(f"Correlation between TS% and eFG%: {comparison['correlations']['TS_vs_eFG']:.3f}")
Python: Visualizing TS% Distribution
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
def visualize_ts_distribution(df, min_games=20):
"""
Create visualizations for True Shooting Percentage analysis.
Parameters:
-----------
df : pandas.DataFrame
Player statistics dataframe with TS% calculated
min_games : int
Minimum games played filter
"""
# Filter data
df_filtered = df[df['GP'] >= min_games].copy()
# Set style
sns.set_style("whitegrid")
fig, axes = plt.subplots(2, 2, figsize=(15, 12))
# 1. TS% Distribution Histogram
axes[0, 0].hist(df_filtered['TS%'], bins=30, edgecolor='black', alpha=0.7)
axes[0, 0].axvline(df_filtered['TS%'].mean(), color='red',
linestyle='--', linewidth=2, label=f"Mean: {df_filtered['TS%'].mean():.1%}")
axes[0, 0].axvline(df_filtered['TS%'].median(), color='green',
linestyle='--', linewidth=2, label=f"Median: {df_filtered['TS%'].median():.1%}")
axes[0, 0].set_xlabel('True Shooting Percentage', fontsize=12)
axes[0, 0].set_ylabel('Number of Players', fontsize=12)
axes[0, 0].set_title('Distribution of True Shooting Percentage', fontsize=14, fontweight='bold')
axes[0, 0].legend()
# 2. TS% vs PPG Scatter Plot
axes[0, 1].scatter(df_filtered['PPG'], df_filtered['TS%'], alpha=0.6, s=50)
axes[0, 1].axhline(y=0.60, color='green', linestyle='--', alpha=0.5, label='Elite (60%)')
axes[0, 1].axhline(y=0.55, color='orange', linestyle='--', alpha=0.5, label='Good (55%)')
axes[0, 1].axhline(y=0.52, color='red', linestyle='--', alpha=0.5, label='Average (52%)')
axes[0, 1].set_xlabel('Points Per Game', fontsize=12)
axes[0, 1].set_ylabel('True Shooting Percentage', fontsize=12)
axes[0, 1].set_title('TS% vs Scoring Volume', fontsize=14, fontweight='bold')
axes[0, 1].legend()
# 3. TS% by Efficiency Tier
tier_counts = df_filtered['Efficiency_Tier'].value_counts()
axes[1, 0].bar(tier_counts.index, tier_counts.values, color=['#2ecc71', '#3498db', '#f39c12', '#e74c3c'])
axes[1, 0].set_xlabel('Efficiency Tier', fontsize=12)
axes[1, 0].set_ylabel('Number of Players', fontsize=12)
axes[1, 0].set_title('Players by Efficiency Tier', fontsize=14, fontweight='bold')
# 4. TS% vs eFG% Comparison
axes[1, 1].scatter(df_filtered['eFG%'], df_filtered['TS%'], alpha=0.6, s=50)
# Add diagonal reference line
min_val = min(df_filtered['eFG%'].min(), df_filtered['TS%'].min())
max_val = max(df_filtered['eFG%'].max(), df_filtered['TS%'].max())
axes[1, 1].plot([min_val, max_val], [min_val, max_val], 'r--', alpha=0.5, label='eFG% = TS%')
axes[1, 1].set_xlabel('Effective Field Goal Percentage', fontsize=12)
axes[1, 1].set_ylabel('True Shooting Percentage', fontsize=12)
axes[1, 1].set_title('TS% vs eFG%: Impact of Free Throws', fontsize=14, fontweight='bold')
axes[1, 1].legend()
plt.tight_layout()
plt.savefig('ts_percentage_analysis.png', dpi=300, bbox_inches='tight')
plt.show()
def plot_ts_trends_over_time(seasons_df):
"""
Plot True Shooting Percentage trends across seasons.
Parameters:
-----------
seasons_df : pandas.DataFrame
DataFrame with columns: Season, Average_TS
"""
plt.figure(figsize=(12, 6))
plt.plot(seasons_df['Season'], seasons_df['Average_TS'],
marker='o', linewidth=2, markersize=8, color='#3498db')
plt.xlabel('Season', fontsize=12)
plt.ylabel('League Average TS%', fontsize=12)
plt.title('NBA League Average True Shooting Percentage Over Time',
fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.savefig('ts_percentage_trends.png', dpi=300, bbox_inches='tight')
plt.show()
# Example usage
if __name__ == "__main__":
# Sample data (replace with actual data)
sample_data = pd.DataFrame({
'PLAYER_NAME': [f'Player_{i}' for i in range(100)],
'GP': np.random.randint(50, 82, 100),
'PPG': np.random.uniform(5, 30, 100),
'TS%': np.random.normal(0.56, 0.05, 100),
'eFG%': np.random.normal(0.53, 0.04, 100),
'Efficiency_Tier': np.random.choice(['Elite', 'Good', 'Average', 'Below Average'],
100, p=[0.15, 0.30, 0.35, 0.20])
})
visualize_ts_distribution(sample_data, min_games=40)
# Trends over time
seasons_data = pd.DataFrame({
'Season': ['2014-15', '2015-16', '2016-17', '2017-18',
'2018-19', '2019-20', '2020-21', '2021-22', '2022-23', '2023-24'],
'Average_TS': [0.537, 0.541, 0.551, 0.556, 0.560, 0.566, 0.570, 0.566, 0.578, 0.583]
})
plot_ts_trends_over_time(seasons_data)
R: True Shooting Percentage Analysis
# Load required libraries
library(tidyverse)
library(ggplot2)
library(dplyr)
# Function to calculate True Shooting Percentage
calculate_ts_percentage <- function(points, fga, fta) {
# Handle division by zero
denominator <- 2 * (fga + 0.44 * fta)
ts_pct <- ifelse(denominator == 0, 0, points / denominator)
return(ts_pct)
}
# Example player data
player_data <- tibble(
player_name = c("Stephen Curry", "LeBron James", "Rudy Gobert",
"Luka Doncic", "Joel Embiid", "Damian Lillard"),
points = c(2168, 2312, 1089, 2063, 2239, 1862),
fga = c(1625, 1845, 625, 1577, 1511, 1489),
fta = c(403, 387, 378, 506, 653, 486),
games = c(74, 71, 68, 70, 66, 73)
)
# Calculate TS% and additional metrics
player_data <- player_data %>%
mutate(
ts_percentage = calculate_ts_percentage(points, fga, fta),
ppg = points / games,
ftr = fta / fga, # Free throw rate
efficiency_tier = case_when(
ts_percentage >= 0.60 ~ "Elite",
ts_percentage >= 0.55 ~ "Good",
ts_percentage >= 0.52 ~ "Average",
TRUE ~ "Below Average"
)
)
# Display results
print("Player True Shooting Analysis:")
player_data %>%
select(player_name, ppg, ts_percentage, efficiency_tier) %>%
arrange(desc(ts_percentage)) %>%
mutate(ts_percentage = scales::percent(ts_percentage, accuracy = 0.1)) %>%
print()
# Summary statistics
cat("\nLeague Summary Statistics:\n")
cat(sprintf("Mean TS%%: %.1f%%\n", mean(player_data$ts_percentage) * 100))
cat(sprintf("Median TS%%: %.1f%%\n", median(player_data$ts_percentage) * 100))
cat(sprintf("Std Dev: %.3f\n", sd(player_data$ts_percentage)))
# Visualization: TS% Distribution
ggplot(player_data, aes(x = reorder(player_name, ts_percentage), y = ts_percentage)) +
geom_bar(stat = "identity", fill = "#3498db", alpha = 0.8) +
geom_hline(yintercept = 0.60, linetype = "dashed", color = "#2ecc71", size = 1) +
geom_hline(yintercept = 0.55, linetype = "dashed", color = "#f39c12", size = 1) +
coord_flip() +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
labs(
title = "True Shooting Percentage by Player",
subtitle = "Green line: Elite (60%), Orange line: Good (55%)",
x = "Player",
y = "True Shooting Percentage"
) +
theme_minimal() +
theme(
plot.title = element_text(size = 16, face = "bold"),
plot.subtitle = element_text(size = 12),
axis.title = element_text(size = 12),
axis.text = element_text(size = 10)
)
# Save plot
ggsave("ts_percentage_by_player.png", width = 10, height = 6, dpi = 300)
R: Advanced TS% Comparison and Analysis
library(tidyverse)
library(ggplot2)
library(gridExtra)
# Function to analyze efficiency metrics
analyze_efficiency_metrics <- function(df) {
df %>%
mutate(
ts_percentage = calculate_ts_percentage(points, fga, fta),
fg_percentage = fgm / fga,
efg_percentage = (fgm + 0.5 * fg3m) / fga,
ftr = fta / fga,
ppg = points / games
) %>%
select(player_name, points, fga, fta, ppg,
fg_percentage, efg_percentage, ts_percentage, ftr)
}
# Generate sample league data
set.seed(42)
league_data <- tibble(
player_name = paste0("Player_", 1:200),
points = rnorm(200, mean = 1200, sd = 400),
fga = rnorm(200, mean = 950, sd = 300),
fgm = rnorm(200, mean = 450, sd = 150),
fg3m = rnorm(200, mean = 100, sd = 50),
fta = rnorm(200, mean = 250, sd = 100),
games = sample(50:82, 200, replace = TRUE)
) %>%
filter(fga > 0, fta > 0, games > 0) # Remove invalid entries
# Analyze metrics
league_analysis <- analyze_efficiency_metrics(league_data)
# Create comprehensive visualization
p1 <- ggplot(league_analysis, aes(x = ts_percentage)) +
geom_histogram(bins = 30, fill = "#3498db", color = "black", alpha = 0.7) +
geom_vline(aes(xintercept = mean(ts_percentage)),
color = "red", linetype = "dashed", size = 1) +
scale_x_continuous(labels = scales::percent_format(accuracy = 1)) +
labs(
title = "Distribution of True Shooting Percentage",
x = "TS%",
y = "Frequency"
) +
theme_minimal()
p2 <- ggplot(league_analysis, aes(x = ppg, y = ts_percentage)) +
geom_point(alpha = 0.5, color = "#3498db") +
geom_smooth(method = "lm", color = "red", se = TRUE) +
geom_hline(yintercept = 0.60, linetype = "dashed", color = "#2ecc71", alpha = 0.5) +
geom_hline(yintercept = 0.55, linetype = "dashed", color = "#f39c12", alpha = 0.5) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
labs(
title = "TS% vs Scoring Volume",
x = "Points Per Game",
y = "TS%"
) +
theme_minimal()
p3 <- ggplot(league_analysis, aes(x = efg_percentage, y = ts_percentage)) +
geom_point(alpha = 0.5, color = "#3498db") +
geom_abline(slope = 1, intercept = 0, color = "red", linetype = "dashed") +
scale_x_continuous(labels = scales::percent_format(accuracy = 1)) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
labs(
title = "TS% vs eFG%: Free Throw Impact",
x = "eFG%",
y = "TS%"
) +
theme_minimal()
p4 <- ggplot(league_analysis, aes(x = ftr, y = ts_percentage)) +
geom_point(alpha = 0.5, color = "#3498db") +
geom_smooth(method = "lm", color = "red", se = TRUE) +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
labs(
title = "TS% vs Free Throw Rate",
x = "FT Rate (FTA/FGA)",
y = "TS%"
) +
theme_minimal()
# Combine plots
grid.arrange(p1, p2, p3, p4, ncol = 2)
# Save combined plot
ggsave("ts_percentage_comprehensive_analysis.png",
arrangeGrob(p1, p2, p3, p4, ncol = 2),
width = 14, height = 10, dpi = 300)
# Statistical analysis
cat("\n=== True Shooting Percentage Analysis ===\n\n")
cat("Summary Statistics:\n")
summary_stats <- league_analysis %>%
summarise(
mean_ts = mean(ts_percentage, na.rm = TRUE),
median_ts = median(ts_percentage, na.rm = TRUE),
sd_ts = sd(ts_percentage, na.rm = TRUE),
min_ts = min(ts_percentage, na.rm = TRUE),
max_ts = max(ts_percentage, na.rm = TRUE)
)
print(summary_stats)
cat("\nCorrelation Analysis:\n")
correlations <- league_analysis %>%
select(ts_percentage, fg_percentage, efg_percentage, ftr, ppg) %>%
cor(use = "complete.obs")
print(round(correlations[1, ], 3))
cat("\nEfficiency Tier Distribution:\n")
tier_distribution <- league_analysis %>%
mutate(
tier = case_when(
ts_percentage >= 0.60 ~ "Elite (60%+)",
ts_percentage >= 0.55 ~ "Good (55-60%)",
ts_percentage >= 0.52 ~ "Average (52-55%)",
TRUE ~ "Below Average (<52%)"
)
) %>%
count(tier) %>%
mutate(percentage = n / sum(n) * 100)
print(tier_distribution)
Practical Applications of True Shooting Percentage
For Analysts and Evaluators
- Player Evaluation: Use TS% as primary metric for scoring efficiency when evaluating players
- Lineup Optimization: Balance lineups with mix of high-TS% finishers and shot creators
- Trade Analysis: Compare players' offensive value accounting for efficiency and volume
- Draft Scouting: Project college/international players' NBA efficiency using TS%
For Coaches
- Shot Selection: Identify players taking inefficient shots and adjust play calls
- Role Definition: Match player roles to their efficiency profiles (creators vs. finishers)
- Offensive Strategy: Design systems that maximize team TS% through better shots
- Player Development: Track improvement in young players' efficiency over time
For Fantasy Basketball
- Category Leagues: High TS% players typically help FG%, FT%, and points categories
- Points Leagues: Combined with usage rate, TS% identifies efficient high-scorers
- Streaming Decisions: Target high-TS% players in favorable matchups
Conclusion
True Shooting Percentage represents a paradigm shift in how we measure scoring efficiency in basketball. By accounting for the varying point values of different shot types and including free throws, TS% provides the most comprehensive single-number assessment of a player's offensive efficiency.
While no single metric tells the complete story, TS% has become the gold standard for efficiency analysis in modern basketball. Understanding its calculation, interpretation, and limitations is essential for anyone seriously analyzing the game. Combined with context about usage rate, role, and shot creation, TS% enables sophisticated evaluation of offensive performance that far exceeds traditional statistics like field goal percentage.
As the NBA continues to evolve toward more efficient shot selection—prioritizing three-pointers and shots at the rim while eliminating mid-range attempts—league-average TS% will likely continue its upward trend. Players, teams, and analysts who understand and optimize for True Shooting Percentage will maintain a competitive advantage in the modern basketball landscape.