Skip to main content

Overview

The hustle.py script collects hustle statistics from NBA.com’s tracking data, including deflections, loose balls recovered, screen assists, charges drawn, and box outs. It also merges player tracking movement data (distance, speed) and possession counts.

Data Sources

  • NBA.com Hustle Stats: leaguehustlestatsplayer endpoint
  • NBA.com Player Tracking: leaguedashptstats endpoint (SpeedDistance measure)
  • NBA.com Advanced Stats: leaguedashplayerstats endpoint (possessions)

Core Function

get_hustle()

Collects hustle statistics for a single season.
year
integer
required
Year to collect data for (e.g., 2025 for 2024-25 season)
ps
boolean
default:"False"
Playoffs mode toggle
Returns: pd.DataFrame with hustle stats, speed/distance tracking, and possession counts merged
# From hustle.py:10
def get_hustle(year, ps=False):
    stype = "Playoffs" if ps else "Regular%20Season"
    season = str(year-1) + '-' + str(year)[-2:]
    
    # Hustle stats endpoint
    url1 = f'https://stats.nba.com/stats/leaguehustlestatsplayer?Season={season}&SeasonType={stype}&PerMode=Totals'
    
    # Speed/Distance tracking endpoint
    url2 = f'https://stats.nba.com/stats/leaguedashptstats?PlayerOrTeam=Player&PtMeasureType=SpeedDistance&Season={season}&SeasonType={stype}'
    
    # Advanced stats for possessions
    url3 = f'https://stats.nba.com/stats/leaguedashplayerstats?MeasureType=Advanced&Season={season}&SeasonType={stype}'

hustle_master()

Creates or updates the master hustle stats file.
ps
boolean
default:"False"
If True, processes playoff data
Returns: Combined DataFrame with all historical hustle data
# From hustle.py:94
def hustle_master(ps=False):
    trail = '_ps' if ps else ''
    data_rs = []
    
    # Load existing data
    old_df = pd.read_csv('hustle.csv')
    old_df = old_df[old_df.year < 2025]
    data_rs.append(old_df)
    
    # Add new year
    for year in range(2025, 2026):
        df = get_hustle(year, ps=ps)
        data_rs.append(df)
    
    hustle = pd.concat(data_rs)
    hustle.to_csv('hustle' + trail + '.csv', index=False)
    return hustle

Statistics Collected

Hustle Stats

PLAYER_ID
integer
NBA player ID
PLAYER_NAME
string
Player name
TEAM_ABBREVIATION
string
Team abbreviation
DEFLECTIONS
integer
Number of deflections (tipped passes or loose balls)
LOOSE_BALLS_RECOVERED
integer
Loose balls recovered
CHARGES_DRAWN
integer
Offensive fouls drawn by taking a charge
SCREEN_ASSISTS
integer
Screens that directly lead to a made field goal
SCREEN_AST_PTS
integer
Points generated from screen assists
OFF_BOXOUTS
integer
Offensive box outs
DEF_BOXOUTS
integer
Defensive box outs
BOX_OUT_PLAYER_TEAM_REBS
integer
Team rebounds resulting from player’s box out
BOX_OUT_PLAYER_REBS
integer
Player rebounds resulting from their own box out
CONTESTED_SHOTS
integer
Number of shots contested
CONTESTED_SHOTS_2PT
integer
Two-point shots contested
CONTESTED_SHOTS_3PT
integer
Three-point shots contested

Speed/Distance Tracking

DIST_MILES
float
Total distance traveled in miles
DIST_MILES_OFF
float
Distance traveled on offense (miles)
DIST_MILES_DEF
float
Distance traveled on defense (miles)
AVG_SPEED
float
Average speed (miles per hour)
AVG_SPEED_OFF
float
Average speed on offense (mph)
AVG_SPEED_DEF
float
Average speed on defense (mph)

Advanced Stats

POSS
integer
Total possessions played
MIN
float
Minutes played
GP
integer
Games played

Data Merging Process

The script merges three separate API calls:
# From hustle.py:58-88
# 1. Get hustle stats
json = requests.get(url, headers=headers).json()
df = pd.DataFrame(data, columns=columns)

# 2. Get speed/distance tracking
json2 = requests.get(url2, headers=headers).json()
df2 = pd.DataFrame(data2, columns=columns2)

# 3. Get possessions from advanced stats
json3 = requests.get(url3, headers=headers).json()
df3 = pd.DataFrame(data3, columns=columns3)
df3 = df3[['PLAYER_ID', 'POSS']]

# Merge datasets
df.drop(columns=['MIN'], inplace=True)
collist = [col for col in df2.columns if col not in df.columns]
collist.append('PLAYER_ID')
df2 = df2[collist]

combo_df = df.merge(df2, on=['PLAYER_ID'])
combo_df = combo_df.merge(df3)
combo_df['year'] = year

Output Files

hustle.csv
CSV
Regular season hustle stats (all years)Columns: All hustle stats + tracking data + year column
hustle_ps.csv
CSV
Playoff hustle stats (all years)Columns: Same as regular season file

Usage Example

# Set playoff mode
ps = True

# Generate master hustle file
hustle = hustle_master(ps=ps)

# Display column names
print(hustle.columns)
Output:
Data Length
612
612
Frame Length
Data Length
612
Sample columns:
Index(['PLAYER_ID', 'PLAYER_NAME', 'TEAM_ABBREVIATION', 'AGE', 'GP',
       'CONTESTED_SHOTS', 'CONTESTED_SHOTS_2PT', 'CONTESTED_SHOTS_3PT',
       'DEFLECTIONS', 'CHARGES_DRAWN', 'SCREEN_ASSISTS', 'SCREEN_AST_PTS',
       'OFF_BOXOUTS', 'DEF_BOXOUTS', 'BOX_OUT_PLAYER_TEAM_REBS',
       'BOX_OUT_PLAYER_REBS', 'LOOSE_BALLS_RECOVERED', 'DIST_MILES',
       'DIST_MILES_OFF', 'DIST_MILES_DEF', 'AVG_SPEED', 'AVG_SPEED_OFF',
       'AVG_SPEED_DEF', 'POSS', 'year'],
      dtype='object')

Key Features

  • Multi-source integration: Combines three different NBA.com endpoints
  • Movement tracking: Links hustle plays to player speed and distance data
  • Possession context: Includes possession counts for rate calculations
  • Historical data: Maintains master files from previous seasons
  • Playoff/regular season: Separate files for each season type

Analysis Use Cases

  • Effort metrics: Deflections, loose balls, charges drawn
  • Screening value: Screen assists and points generated
  • Rebounding effort: Box out statistics
  • Movement analysis: Distance covered and speed metrics
  • Contest rates: Shot contests per possession or per minute

Build docs developers (and LLMs) love