Points, Rebounds, and Assists

Beginner 10 min read 0 views Nov 27, 2025
# Points, Rebounds, and Assists: Basketball's Core Statistical Trio ## Overview Points (PTS), Rebounds (REB), and Assists (AST) form the foundation of basketball statistics. These three metrics have been recorded since the earliest days of organized basketball and remain the primary measures of individual player contribution. Together, they capture the essential actions that determine game outcomes: scoring, possession control, and offensive facilitation. ## Definitions and Recording Methods ### Points (PTS) **Definition**: The total number of points scored by a player through field goals and free throws. **Scoring Values**: - Free throw: 1 point - Field goal inside the three-point arc: 2 points - Field goal beyond the three-point arc: 3 points **Recording**: Points are credited to the player who successfully completes the shot. Official scorekeepers track each made basket and the point value, which is then aggregated into the player's total. **Calculation**: ``` PTS = (FTM × 1) + (2PM × 2) + (3PM × 3) ``` Where: - FTM = Free Throws Made - 2PM = Two-Point Field Goals Made - 3PM = Three-Point Field Goals Made ### Rebounds (REB) **Definition**: The act of gaining possession of the basketball after a missed field goal or free throw attempt. **Types**: - **Offensive Rebounds (OREB)**: Rebounds collected by the offensive team, giving them an additional possession opportunity - **Defensive Rebounds (DREB)**: Rebounds collected by the defensive team, ending the opponent's possession - **Total Rebounds (TRB or REB)**: OREB + DREB **Recording**: A rebound is credited to the player who gains possession and control of the ball after a missed shot. If a player tips the ball to a teammate who secures it, the teammate receives the rebound credit. **Special Cases**: - Team rebounds are credited when no individual player can be assigned (e.g., ball goes out of bounds off a missed shot) - Defensive rebounds after missed free throws (when play continues) are credited normally - After the final free throw in a sequence that's made, no rebound is possible ### Assists (AST) **Definition**: A pass that directly leads to a made field goal by a teammate. **Criteria**: To be credited with an assist, a player must pass the ball to a teammate who: 1. Scores immediately or after minimal dribbling 2. Scores in a way where the pass is deemed to have created the scoring opportunity **Recording**: Assists are the most subjective of the major statistics. Official scorers have discretion in determining whether a pass "created" the basket. Generally: - A pass leading to a catch-and-shoot scores an assist - A pass leading to a basket after 1-2 dribbles typically scores an assist - A pass followed by extensive dribbling or individual creation usually does not **Home/Away Scoring Bias**: Studies have shown that home team players receive approximately 10-15% more assists than they would with neutral scoring, due to scorer subjectivity. ## Rate Statistics: Per-Game vs Per-36 Minutes ### Per-Game Averages **Definition**: Total statistics divided by games played. **Calculation**: ``` PPG = Total Points / Games Played RPG = Total Rebounds / Games Played APG = Total Assists / Games Played ``` **Advantages**: - Simple and intuitive - Easy to compare across seasons - Standard metric for most historical records **Limitations**: - Doesn't account for playing time differences - Favors players with more minutes - Can be misleading for injury-shortened seasons ### Per-36 Minutes **Definition**: Statistics projected to a 36-minute playing time (roughly the minutes of a typical starter). **Calculation**: ``` PTS per 36 = (Total Points / Total Minutes) × 36 REB per 36 = (Total Rebounds / Total Minutes) × 36 AST per 36 = (Total Assists / Total Minutes) × 36 ``` **Advantages**: - Accounts for playing time differences - Better for comparing bench players to starters - Reveals per-minute efficiency - Useful for projecting potential impact with increased minutes **Limitations**: - Can be misleading with small sample sizes - Doesn't account for fatigue (players may not maintain pace for 36 minutes) - Doesn't capture ability to maintain production over full games - Less meaningful for players with very low minutes ### Per-100 Possessions **Definition**: Statistics adjusted to a per-100-possession basis, accounting for pace of play. **Calculation**: ``` PTS per 100 = (Total Points / Team Possessions) × 100 REB per 100 = (Total Rebounds / Team Possessions) × 100 AST per 100 = (Total Assists / Team Possessions) × 100 ``` **Advantages**: - Adjusts for team pace - Better for era comparisons - More accurate efficiency measure ## Historical Leaders ### All-Time Career Points Leaders (through 2024) 1. **LeBron James**: 40,474+ points (active) 2. **Kareem Abdul-Jabbar**: 38,387 points 3. **Karl Malone**: 36,928 points 4. **Kobe Bryant**: 33,643 points 5. **Michael Jordan**: 32,292 points 6. **Dirk Nowitzki**: 31,560 points 7. **Wilt Chamberlain**: 31,419 points 8. **Shaquille O'Neal**: 28,596 points 9. **Carmelo Anthony**: 28,289 points 10. **Moses Malone**: 27,409 points **Single Season Record**: Wilt Chamberlain - 4,029 points (1961-62, 50.4 PPG) **Single Game Record**: Wilt Chamberlain - 100 points (March 2, 1962) ### All-Time Career Rebounds Leaders 1. **Wilt Chamberlain**: 23,924 rebounds 2. **Bill Russell**: 21,620 rebounds 3. **Kareem Abdul-Jabbar**: 17,440 rebounds 4. **Elvin Hayes**: 16,279 rebounds 5. **Moses Malone**: 16,212 rebounds 6. **Tim Duncan**: 15,091 rebounds 7. **Karl Malone**: 14,968 rebounds 8. **Robert Parish**: 14,715 rebounds 9. **Kevin Garnett**: 14,662 rebounds 10. **Dwight Howard**: 14,627 rebounds **Single Season Record**: Wilt Chamberlain - 2,149 rebounds (1960-61, 27.2 RPG) **Single Game Record**: Wilt Chamberlain - 55 rebounds (November 24, 1960) ### All-Time Career Assists Leaders 1. **John Stockton**: 15,806 assists 2. **Jason Kidd**: 12,091 assists 3. **Chris Paul**: 12,000+ assists (active) 4. **Steve Nash**: 10,335 assists 5. **Mark Jackson**: 10,334 assists 6. **Magic Johnson**: 10,141 assists 7. **Oscar Robertson**: 9,887 assists 8. **Russell Westbrook**: 9,500+ assists (active) 9. **Isiah Thomas**: 9,061 assists 10. **Gary Payton**: 8,966 assists **Single Season Record**: John Stockton - 1,164 assists (1990-91, 14.5 APG) **Single Game Record**: Scott Skiles - 30 assists (December 30, 1990) ## Double-Doubles and Triple-Doubles ### Double-Double **Definition**: Achieving double-digit totals in two of the five major statistical categories (points, rebounds, assists, steals, blocks) in a single game. **Most Common Types**: - Points and Rebounds (traditional big man double-double) - Points and Assists (guard/playmaker double-double) - Rebounds and Assists (rare, typically point-forwards) **Career Double-Double Leaders**: 1. **Wilt Chamberlain**: 968 double-doubles 2. **Tim Duncan**: 841 double-doubles 3. **Karl Malone**: 829 double-doubles 4. **Kareem Abdul-Jabbar**: 825 double-doubles 5. **Elvin Hayes**: 774 double-doubles **Modern Era Leaders**: Players like Nikola Jokic, Joel Embiid, and Domantas Sabonis lead the league in double-doubles most seasons, averaging 60-70 per season. ### Triple-Double **Definition**: Achieving double-digit totals in three of the five major statistical categories in a single game. **Historical Significance**: Once rare, triple-doubles have become more common in the modern era due to: - Increased pace of play - Positionless basketball allowing guards to rebound more - Statistical awareness and hunting - Evolution of point guard and point-forward roles **Career Triple-Double Leaders**: 1. **Russell Westbrook**: 199 triple-doubles 2. **Oscar Robertson**: 181 triple-doubles 3. **Magic Johnson**: 138 triple-doubles 4. **Nikola Jokic**: 130+ triple-doubles (active) 5. **LeBron James**: 117+ triple-doubles (active) 6. **Jason Kidd**: 107 triple-doubles 7. **Wilt Chamberlain**: 78 triple-doubles 8. **Luka Doncic**: 75+ triple-doubles (active) **Single Season Record**: Russell Westbrook - 42 triple-doubles (2016-17) **Notable Achievement**: Oscar Robertson averaged a triple-double for the entire 1961-62 season (30.8 PPG, 12.5 RPG, 11.4 APG). Russell Westbrook repeated this feat in 2016-17 (31.6 PPG, 10.7 RPG, 10.4 APG). ### Quadruple-Double **Definition**: Achieving double-digit totals in four statistical categories. **Rarity**: Only four players in NBA history have recorded an official quadruple-double: - Nate Thurmond (1974): 22 PTS, 14 REB, 13 AST, 12 BLK - Alvin Robertson (1986): 20 PTS, 11 REB, 10 AST, 10 STL - Hakeem Olajuwon (1990): 18 PTS, 16 REB, 10 AST, 11 BLK - David Robinson (1994): 34 PTS, 10 REB, 10 AST, 10 BLK ## Position Expectations and Benchmarks ### Point Guard (PG) **Typical Distribution**: - Points: 12-20 PPG (scorers: 20-30 PPG) - Rebounds: 3-5 RPG - Assists: 6-10 APG (elite playmakers: 10+ APG) **Elite Benchmarks**: - 20+ PPG, 8+ APG - Assist-to-turnover ratio above 3:1 - True shooting percentage above 56% **Examples**: Stephen Curry, Damian Lillard (scoring), Chris Paul, Tyrese Haliburton (playmaking) ### Shooting Guard (SG) **Typical Distribution**: - Points: 15-20 PPG (stars: 25+ PPG) - Rebounds: 3-5 RPG - Assists: 2-4 APG (combo guards: 5-7 APG) **Elite Benchmarks**: - 23+ PPG - 3+ APG - 50%+ effective field goal percentage **Examples**: Devin Booker, Donovan Mitchell (scorers), Jrue Holiday (two-way) ### Small Forward (SF) **Typical Distribution**: - Points: 15-20 PPG (stars: 25+ PPG) - Rebounds: 5-7 RPG - Assists: 3-5 APG (point-forwards: 6+ APG) **Elite Benchmarks**: - 23+ PPG, 7+ RPG, 5+ APG - Versatility across all three categories - 55%+ true shooting percentage **Examples**: LeBron James, Kevin Durant, Jayson Tatum ### Power Forward (PF) **Typical Distribution**: - Points: 15-20 PPG (stars: 20-25 PPG) - Rebounds: 7-10 RPG - Assists: 2-4 APG (modern stretch fours: 4-6 APG) **Elite Benchmarks**: - 20+ PPG, 10+ RPG - Double-double capability - Efficient scoring (60%+ true shooting for rim runners) **Examples**: Giannis Antetokounmpo (dominant two-way), Nikola Jokic (passing big) ### Center (C) **Typical Distribution**: - Points: 12-18 PPG (stars: 20-28 PPG) - Rebounds: 8-12 RPG - Assists: 1-3 APG (passing centers: 4-6 APG) **Elite Benchmarks**: - 20+ PPG, 12+ RPG - Regular double-doubles - High field goal percentage (60%+) **Examples**: Joel Embiid, Nikola Jokic, Anthony Davis ## Statistical Analysis Examples ### Python: Basic PTS/REB/AST Analysis ```python import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from scipy import stats # Load player statistics df = pd.read_csv('player_stats.csv') # Basic summary statistics print("League-wide PTS/REB/AST Averages:") print(df[['PTS', 'REB', 'AST']].describe()) # Calculate per-game averages df['PPG'] = df['PTS'] / df['GP'] df['RPG'] = df['REB'] / df['GP'] df['APG'] = df['AST'] / df['GP'] # Calculate per-36 minutes df['PTS_per36'] = (df['PTS'] / df['MIN']) * 36 df['REB_per36'] = (df['REB'] / df['MIN']) * 36 df['AST_per36'] = (df['AST'] / df['MIN']) * 36 # Calculate per-100 possessions # Assuming 'POSS' column contains team possessions df['PTS_per100'] = (df['PTS'] / df['POSS']) * 100 df['REB_per100'] = (df['REB'] / df['POSS']) * 100 df['AST_per100'] = (df['AST'] / df['POSS']) * 100 # Identify double-double games df['is_double_double'] = ((df['PPG'] >= 10) & (df['RPG'] >= 10)) | \ ((df['PPG'] >= 10) & (df['APG'] >= 10)) | \ ((df['RPG'] >= 10) & (df['APG'] >= 10)) print(f"\nPlayers averaging double-double: {df['is_double_double'].sum()}") # Correlation analysis correlation_matrix = df[['PPG', 'RPG', 'APG']].corr() print("\nCorrelation Matrix:") print(correlation_matrix) # Visualize correlations plt.figure(figsize=(8, 6)) sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm', center=0, square=True, linewidths=1, cbar_kws={"shrink": 0.8}) plt.title('Correlation Matrix: PPG, RPG, APG') plt.tight_layout() plt.savefig('pts_reb_ast_correlation.png', dpi=300) plt.close() # Position-based analysis position_stats = df.groupby('POS')[['PPG', 'RPG', 'APG']].agg(['mean', 'median', 'std']) print("\nStatistics by Position:") print(position_stats) # Identify elite performers (top 10% in multiple categories) df['pts_percentile'] = df['PPG'].rank(pct=True) df['reb_percentile'] = df['RPG'].rank(pct=True) df['ast_percentile'] = df['APG'].rank(pct=True) elite_scorers = df[df['pts_percentile'] >= 0.90] elite_rebounders = df[df['reb_percentile'] >= 0.90] elite_playmakers = df[df['ast_percentile'] >= 0.90] print(f"\nElite Scorers (90th percentile): {len(elite_scorers)}") print(f"Elite Rebounders (90th percentile): {len(elite_rebounders)}") print(f"Elite Playmakers (90th percentile): {len(elite_playmakers)}") # All-around players (high in all three) df['combined_percentile'] = (df['pts_percentile'] + df['reb_percentile'] + df['ast_percentile']) / 3 all_around_elite = df[df['combined_percentile'] >= 0.85] print(f"\nAll-Around Elite Players: {len(all_around_elite)}") print(all_around_elite[['PLAYER', 'PPG', 'RPG', 'APG', 'combined_percentile']].head(10)) ``` ### Python: Distribution Analysis and Visualization ```python import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from scipy import stats import numpy as np # Load data df = pd.read_csv('player_stats.csv') df['PPG'] = df['PTS'] / df['GP'] df['RPG'] = df['REB'] / df['GP'] df['APG'] = df['AST'] / df['GP'] # Filter to players with significant minutes (e.g., 20+ MPG) df_filtered = df[df['MIN'] / df['GP'] >= 20].copy() # Create distribution plots fig, axes = plt.subplots(2, 3, figsize=(15, 10)) # PPG distribution axes[0, 0].hist(df_filtered['PPG'], bins=30, edgecolor='black', alpha=0.7) axes[0, 0].set_xlabel('Points Per Game') axes[0, 0].set_ylabel('Frequency') axes[0, 0].set_title('PPG Distribution') axes[0, 0].axvline(df_filtered['PPG'].mean(), color='red', linestyle='--', label=f'Mean: {df_filtered["PPG"].mean():.1f}') axes[0, 0].axvline(df_filtered['PPG'].median(), color='green', linestyle='--', label=f'Median: {df_filtered["PPG"].median():.1f}') axes[0, 0].legend() # RPG distribution axes[0, 1].hist(df_filtered['RPG'], bins=30, edgecolor='black', alpha=0.7, color='orange') axes[0, 1].set_xlabel('Rebounds Per Game') axes[0, 1].set_ylabel('Frequency') axes[0, 1].set_title('RPG Distribution') axes[0, 1].axvline(df_filtered['RPG'].mean(), color='red', linestyle='--', label=f'Mean: {df_filtered["RPG"].mean():.1f}') axes[0, 1].axvline(df_filtered['RPG'].median(), color='green', linestyle='--', label=f'Median: {df_filtered["RPG"].median():.1f}') axes[0, 1].legend() # APG distribution axes[0, 2].hist(df_filtered['APG'], bins=30, edgecolor='black', alpha=0.7, color='green') axes[0, 2].set_xlabel('Assists Per Game') axes[0, 2].set_ylabel('Frequency') axes[0, 2].set_title('APG Distribution') axes[0, 2].axvline(df_filtered['APG'].mean(), color='red', linestyle='--', label=f'Mean: {df_filtered["APG"].mean():.1f}') axes[0, 2].axvline(df_filtered['APG'].median(), color='green', linestyle='--', label=f'Median: {df_filtered["APG"].median():.1f}') axes[0, 2].legend() # PPG vs RPG scatter axes[1, 0].scatter(df_filtered['PPG'], df_filtered['RPG'], alpha=0.6) axes[1, 0].set_xlabel('Points Per Game') axes[1, 0].set_ylabel('Rebounds Per Game') axes[1, 0].set_title('PPG vs RPG') z = np.polyfit(df_filtered['PPG'], df_filtered['RPG'], 1) p = np.poly1d(z) axes[1, 0].plot(df_filtered['PPG'], p(df_filtered['PPG']), "r--", alpha=0.8) # PPG vs APG scatter axes[1, 1].scatter(df_filtered['PPG'], df_filtered['APG'], alpha=0.6, color='orange') axes[1, 1].set_xlabel('Points Per Game') axes[1, 1].set_ylabel('Assists Per Game') axes[1, 1].set_title('PPG vs APG') z = np.polyfit(df_filtered['PPG'], df_filtered['APG'], 1) p = np.poly1d(z) axes[1, 1].plot(df_filtered['PPG'], p(df_filtered['PPG']), "r--", alpha=0.8) # RPG vs APG scatter axes[1, 2].scatter(df_filtered['RPG'], df_filtered['APG'], alpha=0.6, color='green') axes[1, 2].set_xlabel('Rebounds Per Game') axes[1, 2].set_ylabel('Assists Per Game') axes[1, 2].set_title('RPG vs APG') z = np.polyfit(df_filtered['RPG'], df_filtered['APG'], 1) p = np.poly1d(z) axes[1, 2].plot(df_filtered['RPG'], p(df_filtered['RPG']), "r--", alpha=0.8) plt.tight_layout() plt.savefig('pts_reb_ast_analysis.png', dpi=300) plt.close() # Calculate skewness and kurtosis print("Distribution Statistics:") print(f"PPG - Skewness: {stats.skew(df_filtered['PPG']):.3f}, " f"Kurtosis: {stats.kurtosis(df_filtered['PPG']):.3f}") print(f"RPG - Skewness: {stats.skew(df_filtered['RPG']):.3f}, " f"Kurtosis: {stats.kurtosis(df_filtered['RPG']):.3f}") print(f"APG - Skewness: {stats.skew(df_filtered['APG']):.3f}, " f"Kurtosis: {stats.kurtosis(df_filtered['APG']):.3f}") ``` ### Python: Position-Based Expectations Analysis ```python import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # Load data df = pd.read_csv('player_stats.csv') df['PPG'] = df['PTS'] / df['GP'] df['RPG'] = df['REB'] / df['GP'] df['APG'] = df['AST'] / df['GP'] # Filter to players with significant playing time df = df[df['MIN'] / df['GP'] >= 20].copy() # Define position categories position_map = { 'PG': 'Point Guard', 'SG': 'Shooting Guard', 'SF': 'Small Forward', 'PF': 'Power Forward', 'C': 'Center' } df['POSITION_NAME'] = df['POS'].map(position_map) # Calculate position benchmarks position_benchmarks = df.groupby('POSITION_NAME').agg({ 'PPG': ['mean', 'std', 'median', lambda x: x.quantile(0.25), lambda x: x.quantile(0.75)], 'RPG': ['mean', 'std', 'median', lambda x: x.quantile(0.25), lambda x: x.quantile(0.75)], 'APG': ['mean', 'std', 'median', lambda x: x.quantile(0.25), lambda x: x.quantile(0.75)] }) print("Position Benchmarks:") print(position_benchmarks) # Create box plots by position fig, axes = plt.subplots(1, 3, figsize=(15, 5)) positions_order = ['Point Guard', 'Shooting Guard', 'Small Forward', 'Power Forward', 'Center'] # PPG by position sns.boxplot(data=df, x='POSITION_NAME', y='PPG', order=positions_order, ax=axes[0], palette='Set2') axes[0].set_title('Points Per Game by Position', fontsize=12, fontweight='bold') axes[0].set_xlabel('Position') axes[0].set_ylabel('Points Per Game') axes[0].tick_params(axis='x', rotation=45) # RPG by position sns.boxplot(data=df, x='POSITION_NAME', y='RPG', order=positions_order, ax=axes[1], palette='Set2') axes[1].set_title('Rebounds Per Game by Position', fontsize=12, fontweight='bold') axes[1].set_xlabel('Position') axes[1].set_ylabel('Rebounds Per Game') axes[1].tick_params(axis='x', rotation=45) # APG by position sns.boxplot(data=df, x='POSITION_NAME', y='APG', order=positions_order, ax=axes[2], palette='Set2') axes[2].set_title('Assists Per Game by Position', fontsize=12, fontweight='bold') axes[2].set_xlabel('Position') axes[2].set_ylabel('Assists Per Game') axes[2].tick_params(axis='x', rotation=45) plt.tight_layout() plt.savefig('position_expectations.png', dpi=300) plt.close() # Calculate z-scores relative to position for pos in df['POSITION_NAME'].unique(): pos_mask = df['POSITION_NAME'] == pos df.loc[pos_mask, 'PPG_zscore'] = stats.zscore(df.loc[pos_mask, 'PPG']) df.loc[pos_mask, 'RPG_zscore'] = stats.zscore(df.loc[pos_mask, 'RPG']) df.loc[pos_mask, 'APG_zscore'] = stats.zscore(df.loc[pos_mask, 'APG']) # Identify position outliers (players exceeding expectations) df['total_zscore'] = df['PPG_zscore'] + df['RPG_zscore'] + df['APG_zscore'] top_performers = df.nlargest(20, 'total_zscore') print("\nTop 20 Players Relative to Position Expectations:") print(top_performers[['PLAYER', 'POSITION_NAME', 'PPG', 'RPG', 'APG', 'PPG_zscore', 'RPG_zscore', 'APG_zscore', 'total_zscore']]) # Create position comparison radar chart def create_position_profile(position_data, position_name): categories = ['PPG', 'RPG', 'APG'] values = [position_data['PPG'].mean(), position_data['RPG'].mean(), position_data['APG'].mean()] return values # Normalize values for radar chart (0-1 scale) max_ppg = df['PPG'].max() max_rpg = df['RPG'].max() max_apg = df['APG'].max() fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(projection='polar')) categories = ['Points', 'Rebounds', 'Assists'] N = len(categories) angles = [n / float(N) * 2 * np.pi for n in range(N)] angles += angles[:1] for pos in positions_order: pos_data = df[df['POSITION_NAME'] == pos] values = [ pos_data['PPG'].mean() / max_ppg, pos_data['RPG'].mean() / max_rpg, pos_data['APG'].mean() / max_apg ] values += values[:1] ax.plot(angles, values, 'o-', linewidth=2, label=pos) ax.fill(angles, values, alpha=0.15) ax.set_xticks(angles[:-1]) ax.set_xticklabels(categories) ax.set_ylim(0, 1) ax.set_title('Average Position Profiles (Normalized)', size=14, fontweight='bold', pad=20) ax.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1)) ax.grid(True) plt.tight_layout() plt.savefig('position_radar_chart.png', dpi=300) plt.close() ``` ### Python: Triple-Double Analysis ```python import pandas as pd import numpy as np import matplotlib.pyplot as plt from datetime import datetime # Load game-by-game data df = pd.read_csv('game_logs.csv') # Identify triple-doubles df['is_triple_double'] = ((df['PTS'] >= 10) & (df['REB'] >= 10) & (df['AST'] >= 10)) | \ ((df['PTS'] >= 10) & (df['REB'] >= 10) & (df['BLK'] >= 10)) | \ ((df['PTS'] >= 10) & (df['AST'] >= 10) & (df['STL'] >= 10)) | \ ((df['REB'] >= 10) & (df['AST'] >= 10) & (df['BLK'] >= 10)) # Count triple-doubles by player triple_double_counts = df[df['is_triple_double']].groupby('PLAYER').size().sort_values(ascending=False) print("Players with Most Triple-Doubles (Current Season):") print(triple_double_counts.head(10)) # Analyze triple-double composition td_df = df[df['is_triple_double']].copy() td_df['TD_Type'] = 'Other' td_df.loc[(td_df['PTS'] >= 10) & (td_df['REB'] >= 10) & (td_df['AST'] >= 10), 'TD_Type'] = 'PTS-REB-AST' td_df.loc[(td_df['PTS'] >= 10) & (td_df['REB'] >= 10) & (td_df['BLK'] >= 10), 'TD_Type'] = 'PTS-REB-BLK' td_df.loc[(td_df['PTS'] >= 10) & (td_df['AST'] >= 10) & (td_df['STL'] >= 10), 'TD_Type'] = 'PTS-AST-STL' td_type_counts = td_df['TD_Type'].value_counts() print("\nTriple-Double Types:") print(td_type_counts) # Calculate win percentage in triple-double games td_df['WIN'] = td_df['RESULT'].str.contains('W', na=False) win_pct = td_df['WIN'].mean() * 100 print(f"\nWin Percentage in Triple-Double Games: {win_pct:.1f}%") # Analyze near triple-doubles (missed by 1-2 in one category) df['near_triple_double'] = ((df['PTS'] >= 8) & (df['REB'] >= 8) & (df['AST'] >= 8)) & \ (~df['is_triple_double']) & \ (((df['PTS'] >= 10) & (df['REB'] >= 10)) | \ ((df['PTS'] >= 10) & (df['AST'] >= 10)) | \ ((df['REB'] >= 10) & (df['AST'] >= 10))) near_td_counts = df[df['near_triple_double']].groupby('PLAYER').size().sort_values(ascending=False) print("\nPlayers with Most Near Triple-Doubles:") print(near_td_counts.head(10)) # Visualize triple-double distribution over time if 'GAME_DATE' in df.columns: df['GAME_DATE'] = pd.to_datetime(df['GAME_DATE']) td_timeline = df[df['is_triple_double']].groupby('GAME_DATE').size() plt.figure(figsize=(12, 6)) plt.plot(td_timeline.index, td_timeline.cumsum(), linewidth=2) plt.xlabel('Date') plt.ylabel('Cumulative Triple-Doubles') plt.title('Triple-Doubles Over Time (Cumulative)') plt.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('triple_double_timeline.png', dpi=300) plt.close() # Player efficiency in triple-double games vs regular games players_with_tds = triple_double_counts[triple_double_counts >= 3].index comparison_data = [] for player in players_with_tds: player_games = df[df['PLAYER'] == player] td_games = player_games[player_games['is_triple_double']] regular_games = player_games[~player_games['is_triple_double']] if len(regular_games) > 0: comparison_data.append({ 'Player': player, 'TD_PPG': td_games['PTS'].mean(), 'Regular_PPG': regular_games['PTS'].mean(), 'TD_RPG': td_games['REB'].mean(), 'Regular_RPG': regular_games['REB'].mean(), 'TD_APG': td_games['AST'].mean(), 'Regular_APG': regular_games['AST'].mean(), 'TD_Count': len(td_games) }) comparison_df = pd.DataFrame(comparison_data) print("\nPerformance Comparison: Triple-Double Games vs Regular Games:") print(comparison_df) ``` ### R: PTS/REB/AST Statistical Analysis ```r library(tidyverse) library(ggplot2) library(corrplot) library(car) # Load player statistics player_stats <- read.csv('player_stats.csv') # Calculate per-game averages player_stats <- player_stats %>% mutate( PPG = PTS / GP, RPG = REB / GP, APG = AST / GP, MPG = MIN / GP, PTS_per36 = (PTS / MIN) * 36, REB_per36 = (REB / MIN) * 36, AST_per36 = (AST / MIN) * 36 ) # Filter to players with significant minutes player_stats_filtered <- player_stats %>% filter(MPG >= 20) # Summary statistics summary_stats <- player_stats_filtered %>% summarise( PPG_mean = mean(PPG, na.rm = TRUE), PPG_sd = sd(PPG, na.rm = TRUE), RPG_mean = mean(RPG, na.rm = TRUE), RPG_sd = sd(RPG, na.rm = TRUE), APG_mean = mean(APG, na.rm = TRUE), APG_sd = sd(APG, na.rm = TRUE) ) print("League Averages:") print(summary_stats) # Correlation analysis cor_matrix <- cor(player_stats_filtered[, c('PPG', 'RPG', 'APG')], use = "complete.obs") print("Correlation Matrix:") print(cor_matrix) # Visualize correlation matrix png('pts_reb_ast_correlation_r.png', width = 800, height = 600) corrplot(cor_matrix, method = "color", type = "upper", addCoef.col = "black", tl.col = "black", tl.srt = 45, title = "PTS/REB/AST Correlation Matrix", mar = c(0,0,1,0)) dev.off() # Position-based analysis position_stats <- player_stats_filtered %>% group_by(POS) %>% summarise( Count = n(), PPG_mean = mean(PPG, na.rm = TRUE), PPG_sd = sd(PPG, na.rm = TRUE), RPG_mean = mean(RPG, na.rm = TRUE), RPG_sd = sd(RPG, na.rm = TRUE), APG_mean = mean(APG, na.rm = TRUE), APG_sd = sd(APG, na.rm = TRUE) ) %>% arrange(desc(PPG_mean)) print("Statistics by Position:") print(position_stats) # ANOVA to test position differences ppg_anova <- aov(PPG ~ POS, data = player_stats_filtered) rpg_anova <- aov(RPG ~ POS, data = player_stats_filtered) apg_anova <- aov(APG ~ POS, data = player_stats_filtered) print("ANOVA Results - PPG by Position:") print(summary(ppg_anova)) print("ANOVA Results - RPG by Position:") print(summary(rpg_anova)) print("ANOVA Results - APG by Position:") print(summary(apg_anova)) # Create visualization: Distribution by position png('position_distributions_r.png', width = 1200, height = 400) par(mfrow = c(1, 3)) boxplot(PPG ~ POS, data = player_stats_filtered, main = "Points Per Game by Position", xlab = "Position", ylab = "PPG", col = rainbow(5)) boxplot(RPG ~ POS, data = player_stats_filtered, main = "Rebounds Per Game by Position", xlab = "Position", ylab = "RPG", col = rainbow(5)) boxplot(APG ~ POS, data = player_stats_filtered, main = "Assists Per Game by Position", xlab = "Position", ylab = "APG", col = rainbow(5)) dev.off() # Regression analysis: Predict points from rebounds and assists model <- lm(PPG ~ RPG + APG + MPG, data = player_stats_filtered) print("Regression Model: PPG ~ RPG + APG + MPG") print(summary(model)) # Check for multicollinearity vif_values <- vif(model) print("Variance Inflation Factors:") print(vif_values) # Identify double-double players player_stats_filtered <- player_stats_filtered %>% mutate( is_double_double = (PPG >= 10 & RPG >= 10) | (PPG >= 10 & APG >= 10) | (RPG >= 10 & APG >= 10) ) dd_count <- sum(player_stats_filtered$is_double_double, na.rm = TRUE) print(paste("Players averaging double-double:", dd_count)) # Create scatter plot matrix png('scatter_matrix_r.png', width = 1000, height = 1000) pairs(~ PPG + RPG + APG, data = player_stats_filtered, main = "Scatter Plot Matrix: PTS/REB/AST", pch = 19, col = alpha("blue", 0.3)) dev.off() ``` ### R: Time Series Analysis of Historical Leaders ```r library(tidyverse) library(lubridate) library(zoo) # Load historical game-by-game data game_logs <- read.csv('historical_game_logs.csv') # Convert date game_logs$GAME_DATE <- as.Date(game_logs$GAME_DATE) # Calculate cumulative statistics for career leaders career_totals <- game_logs %>% arrange(PLAYER, GAME_DATE) %>% group_by(PLAYER) %>% mutate( Career_PTS = cumsum(PTS), Career_REB = cumsum(REB), Career_AST = cumsum(AST), Games_Played = row_number() ) # Identify top 10 career scorers top_scorers <- career_totals %>% group_by(PLAYER) %>% summarise(Total_PTS = max(Career_PTS, na.rm = TRUE)) %>% arrange(desc(Total_PTS)) %>% head(10) print("Top 10 All-Time Scorers:") print(top_scorers) # Plot career progression for top scorers top_scorer_names <- top_scorers$PLAYER career_progression <- career_totals %>% filter(PLAYER %in% top_scorer_names) # Create career points progression plot png('career_points_progression.png', width = 1200, height = 800) ggplot(career_progression, aes(x = Games_Played, y = Career_PTS, color = PLAYER)) + geom_line(size = 1) + labs(title = "Career Points Progression - All-Time Leaders", x = "Games Played", y = "Career Points", color = "Player") + theme_minimal() + theme(legend.position = "right") dev.off() # Calculate rolling averages (last 10 games) game_logs <- game_logs %>% arrange(PLAYER, GAME_DATE) %>% group_by(PLAYER) %>% mutate( PPG_rolling = rollmean(PTS, k = 10, fill = NA, align = "right"), RPG_rolling = rollmean(REB, k = 10, fill = NA, align = "right"), APG_rolling = rollmean(AST, k = 10, fill = NA, align = "right") ) # Analyze consistency (coefficient of variation) player_consistency <- game_logs %>% group_by(PLAYER) %>% filter(n() >= 50) %>% # At least 50 games summarise( Games = n(), PPG_mean = mean(PTS, na.rm = TRUE), PPG_cv = sd(PTS, na.rm = TRUE) / mean(PTS, na.rm = TRUE), RPG_mean = mean(REB, na.rm = TRUE), RPG_cv = sd(REB, na.rm = TRUE) / mean(REB, na.rm = TRUE), APG_mean = mean(AST, na.rm = TRUE), APG_cv = sd(AST, na.rm = TRUE) / mean(AST, na.rm = TRUE) ) %>% arrange(PPG_cv) print("Most Consistent Scorers (Lowest Coefficient of Variation):") print(head(player_consistency, 10)) ``` ## Key Insights and Applications ### Statistical Relationships 1. **Points and Rebounds**: Weak positive correlation (r ≈ 0.2-0.3) - Big men who score close to the basket often grab rebounds - Perimeter scorers typically have lower rebounding numbers 2. **Points and Assists**: Weak negative to neutral correlation (r ≈ -0.1 to 0.1) - Primary scorers often have lower assist rates (shoot-first mentality) - Elite playmakers may sacrifice scoring opportunities to create for others 3. **Rebounds and Assists**: Weak positive correlation (r ≈ 0.1-0.2) - Point forwards and playmaking bigs can excel in both - Defensive rebounds often initiate fast breaks, leading to assists ### Usage in Player Evaluation 1. **Versatility Assessment**: Players who contribute across all three categories are especially valuable 2. **Role Definition**: Statistical profiles help identify optimal roles (scorer, facilitator, rebounder) 3. **Positional Value**: Compare players to position benchmarks rather than league-wide averages 4. **Efficiency Context**: Raw totals must be considered alongside shooting efficiency and turnover rates 5. **Pace Adjustment**: Use per-possession stats when comparing across different eras or teams ### Modern Trends 1. **Triple-Double Inflation**: Increased from ~20 per season (1980s-90s) to 100+ per season (2020s) 2. **Positionless Basketball**: Traditional position-based expectations are less rigid 3. **Three-Point Revolution**: Scoring distribution has shifted toward perimeter shooting 4. **Analytical Awareness**: Players and teams are more conscious of statistical milestones ## Related Metrics - **True Shooting Percentage (TS%)**: Measures scoring efficiency - **Rebound Rate (TRB%)**: Percentage of available rebounds grabbed - **Assist Percentage (AST%)**: Percentage of teammate field goals assisted - **Usage Rate (USG%)**: Percentage of team plays used while on court - **Player Efficiency Rating (PER)**: Comprehensive efficiency metric incorporating PTS/REB/AST ## References and Further Reading - Basketball-Reference.com: Comprehensive historical statistics - NBA Advanced Stats: Official league statistics and tracking data - Cleaning the Glass: Advanced analytics with playing time filters - "Basketball on Paper" by Dean Oliver: Statistical analysis fundamentals - NBA Stats API: Access to detailed play-by-play and tracking data --- *Last Updated: 2024 Season*

Discussion

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