How Much does Crowd Noise Affect NBA Players? (Season-Final)
Posted on August 11, 2020 in NBA-Basketball
Premise
Basketball is back in the bubble! But it's been drastically different with virtual fans and the lack of crowd noise. I was curious to see the effect that crowd noise had on the players, and the most "controlled" aspect of the game I could think of was free throws. Therefore, I wanted to see if player's free throw percentages improved due to the lack of crowd noise or not.
I used a matched pairs t-test to see whether a "bubble" effect existed on the NBA player's free throw percentage pre-bubble vs in the bubble. One of the disadvantages of the matched pairs t-test was that this treated the pre-bubble data as a single measurement point and the in-bubble data as another measurement point, but the data was actually binomial data.
Therefore, I also used a test of proportions to see whether any players deviated from their pre-bubble free throw shooting percentages
Load the data
I first loaded the box score data that was scraped from basketballreference.com using the code here
I then created dictionaries of each player's pre-bubble / bubble free throws made and free throw attempts. Thankfully no players in the NBA have the same name
import os
from os import path
import pickle
import pandas as pd
from scipy import stats
import numpy as np
import pylab
from statsmodels.stats.proportion import proportions_ztest
import matplotlib.pyplot as plt
def RepresentsInt(s):
try:
int(s)
return True
except ValueError:
return False
working_dir = os.getcwd() + "/ft_data"
# Get all pickle files that match basic box score in the 2020 season
pre_bubble_box_scores = dict()
bubble_box_scores = dict()
for file in os.listdir(working_dir):
if file.endswith(".pickle") and "basic_box_score" in file and "2020" in file:
with open(working_dir + "/" + file, 'rb') as handle:
b = pickle.load(handle)
if 'july' in file or 'august' in file or 'september' in file or 'october' in file:
bubble_box_scores.update(b)
else:
pre_bubble_box_scores.update(b)
pre_bubble_players_ft_attempts = dict()
pre_bubble_players_ft_made = dict()
debug = list()
for key, boxscore in pre_bubble_box_scores.items():
ft_attempts = boxscore['FTA']
ft_made = boxscore['FT']
for index, value in ft_attempts.items():
if RepresentsInt(value):
if int(value) != 0:
if index not in pre_bubble_players_ft_attempts.keys():
pre_bubble_players_ft_attempts[index] = int(ft_attempts[index])
pre_bubble_players_ft_made[index] = int(ft_made[index])
else:
pre_bubble_players_ft_attempts[index] = pre_bubble_players_ft_attempts[index] + int(ft_attempts[index])
pre_bubble_players_ft_made[index] = pre_bubble_players_ft_made[index] + int(ft_made[index])
bubble_players_ft_attempts = dict()
bubble_players_ft_made = dict()
for key, boxscore in bubble_box_scores.items():
ft_attempts = boxscore['FTA']
ft_made = boxscore['FT']
for index, value in ft_attempts.items():
if RepresentsInt(value):
if int(value) != 0:
if index not in bubble_players_ft_attempts.keys():
bubble_players_ft_attempts[index] = int(value)
bubble_players_ft_made[index] = int(ft_made[index])
else:
bubble_players_ft_attempts[index] = bubble_players_ft_attempts[index] + int(value)
bubble_players_ft_made[index] = bubble_players_ft_made[index] + int(ft_made[index])
Create the data frame
Next, I created a dataframe with the columns desired and ran through the players in the bubble to populate the the dataframe. I only took players who had taken at least 30 free throws in the pre-bubble as well as bubble.
- Player
- Pre-Bubble Free Throws Made
- Pre-Bubble Free Throws Attempted
- Bubble Free Throws Made
- Bubble Free Throws Attempted
- P-value of the difference between the two proportions
- Pre-bubble Free Throw %
- Bubble Free Throw %
- Differences of Pre-bubble vs Bubble
Analysis
Test of Proportions
We had a total of 49 players who at the end of the bubble had taken more than 30 free throws in the bubble. Only 5 of them had significantly different free throw percentages at a 0.05 level
- Russell Westbrook - Decrease of 23.5%
- Jerami Grant - Increase of 13.1%
- Eric Gordon - Increase of 12.65%
- Danilo Gallinari - Increase of 10%
- Bam Adebayo - Increase of 9.3%
ft_pct_df = pd.DataFrame(columns = ['player', 'pre-bubble-made', 'pre-bubble-att', 'bubble-made', 'bubble-att', 'p-val'])
for player in bubble_players_ft_attempts.keys():
if player in pre_bubble_players_ft_attempts.keys() and pre_bubble_players_ft_attempts[player] > 30 and bubble_players_ft_attempts[player] > 30:
count = np.array([pre_bubble_players_ft_made[player], bubble_players_ft_made[player]])
nobs = np.array([pre_bubble_players_ft_attempts[player], bubble_players_ft_attempts[player]])
stat, pval = proportions_ztest(count, nobs)
ft_pct_df = ft_pct_df.append({'player': player,
'pre-bubble-made': pre_bubble_players_ft_made[player],
'pre-bubble-att': pre_bubble_players_ft_attempts[player],
'bubble-made': bubble_players_ft_made[player],
'bubble-att': bubble_players_ft_attempts[player],
'p-val': pval}, ignore_index=True)
ft_pct_df['pre-ft-pct'] = (ft_pct_df['pre-bubble-made']/ft_pct_df['pre-bubble-att']).astype(float)
ft_pct_df['bubble-pct'] = (ft_pct_df['bubble-made']/ft_pct_df['bubble-att']).astype(float)
ft_pct_df['ft-pct-diff'] = (ft_pct_df['bubble-pct'] - ft_pct_df['pre-ft-pct']).astype(float)
ft_pct_df = ft_pct_df.reindex(ft_pct_df['ft-pct-diff'].abs().sort_values(ascending=False).index)
ft_pct_df.round(3)
player | pre-bubble-made | pre-bubble-att | bubble-made | bubble-att | p-val | pre-ft-pct | bubble-pct | ft-pct-diff | |
---|---|---|---|---|---|---|---|---|---|
23 | Russell Westbrook | 248 | 317 | 23 | 42 | 0.001 | 0.782 | 0.548 | -0.235 |
25 | Jerami Grant | 121 | 163 | 55 | 63 | 0.034 | 0.742 | 0.873 | 0.131 |
22 | Eric Gordon | 76 | 100 | 39 | 44 | 0.082 | 0.760 | 0.886 | 0.126 |
4 | Pascal Siakam | 194 | 248 | 36 | 53 | 0.109 | 0.782 | 0.679 | -0.103 |
20 | Danilo Gallinari | 213 | 243 | 42 | 43 | 0.051 | 0.877 | 0.977 | 0.100 |
45 | Kristaps Porziņģis | 158 | 202 | 35 | 40 | 0.182 | 0.782 | 0.875 | 0.093 |
15 | Bam Adebayo | 206 | 298 | 98 | 125 | 0.053 | 0.691 | 0.784 | 0.093 |
5 | Kyle Lowry | 230 | 267 | 54 | 69 | 0.107 | 0.861 | 0.783 | -0.079 |
46 | D.J. Augustin | 132 | 148 | 33 | 34 | 0.155 | 0.892 | 0.971 | 0.079 |
19 | Khris Middleton | 168 | 184 | 46 | 55 | 0.103 | 0.913 | 0.836 | -0.077 |
7 | Donovan Mitchell | 228 | 264 | 63 | 67 | 0.086 | 0.864 | 0.940 | 0.077 |
44 | Tim Hardaway | 112 | 141 | 27 | 31 | 0.326 | 0.794 | 0.871 | 0.077 |
3 | Jaylen Brown | 151 | 204 | 60 | 74 | 0.224 | 0.740 | 0.811 | 0.071 |
8 | Rudy Gobert | 213 | 343 | 26 | 47 | 0.371 | 0.621 | 0.553 | -0.068 |
43 | Luka Dončić | 345 | 459 | 61 | 89 | 0.192 | 0.752 | 0.685 | -0.066 |
42 | Carmelo Anthony | 113 | 134 | 30 | 33 | 0.334 | 0.843 | 0.909 | 0.066 |
36 | Ivica Zubac | 91 | 123 | 33 | 41 | 0.401 | 0.740 | 0.805 | 0.065 |
40 | CJ McCollum | 106 | 145 | 31 | 39 | 0.417 | 0.731 | 0.795 | 0.064 |
41 | Damian Lillard | 353 | 397 | 79 | 83 | 0.084 | 0.889 | 0.952 | 0.063 |
37 | Lou Williams | 244 | 280 | 35 | 43 | 0.306 | 0.871 | 0.814 | -0.057 |
30 | Montrezl Harrell | 207 | 315 | 41 | 68 | 0.396 | 0.657 | 0.603 | -0.054 |
26 | Paul Millsap | 95 | 115 | 45 | 58 | 0.427 | 0.826 | 0.776 | -0.050 |
33 | Kyle Kuzma | 96 | 130 | 33 | 42 | 0.539 | 0.738 | 0.786 | 0.047 |
6 | Norman Powell | 103 | 123 | 27 | 34 | 0.554 | 0.837 | 0.794 | -0.043 |
35 | Dwight Howard | 88 | 175 | 30 | 55 | 0.581 | 0.503 | 0.545 | 0.043 |
32 | LeBron James | 219 | 319 | 113 | 155 | 0.343 | 0.687 | 0.729 | 0.043 |
27 | Monte Morris | 48 | 59 | 41 | 48 | 0.577 | 0.814 | 0.854 | 0.041 |
39 | Ja Morant | 193 | 250 | 26 | 32 | 0.605 | 0.772 | 0.812 | 0.040 |
17 | Tyler Herro | 52 | 61 | 57 | 64 | 0.523 | 0.852 | 0.891 | 0.038 |
1 | Marcus Smart | 103 | 124 | 58 | 67 | 0.526 | 0.831 | 0.866 | 0.035 |
11 | Michael Porter | 29 | 37 | 30 | 40 | 0.726 | 0.784 | 0.750 | -0.034 |
47 | Tobias Harris | 143 | 177 | 24 | 31 | 0.663 | 0.808 | 0.774 | -0.034 |
18 | Giannis Antetokounmpo | 340 | 532 | 64 | 105 | 0.565 | 0.639 | 0.610 | -0.030 |
29 | Kawhi Leonard | 284 | 319 | 99 | 115 | 0.401 | 0.890 | 0.861 | -0.029 |
14 | Goran Dragić | 147 | 188 | 59 | 73 | 0.640 | 0.782 | 0.808 | 0.026 |
13 | Jae Crowder | 88 | 112 | 35 | 46 | 0.733 | 0.786 | 0.761 | -0.025 |
38 | Caris LeVert | 109 | 148 | 25 | 33 | 0.803 | 0.736 | 0.758 | 0.021 |
28 | Paul George | 165 | 187 | 57 | 66 | 0.690 | 0.882 | 0.864 | -0.019 |
16 | Duncan Robinson | 47 | 52 | 39 | 44 | 0.780 | 0.904 | 0.886 | -0.017 |
12 | Jimmy Butler | 397 | 473 | 177 | 207 | 0.602 | 0.839 | 0.855 | 0.016 |
34 | Alex Caruso | 58 | 76 | 27 | 36 | 0.879 | 0.763 | 0.750 | -0.013 |
2 | Kemba Walker | 171 | 198 | 75 | 88 | 0.798 | 0.864 | 0.852 | -0.011 |
48 | Joel Embiid | 293 | 360 | 53 | 66 | 0.836 | 0.814 | 0.803 | -0.011 |
0 | Jayson Tatum | 215 | 265 | 106 | 130 | 0.923 | 0.811 | 0.815 | 0.004 |
10 | Nikola Jokić | 204 | 251 | 72 | 89 | 0.938 | 0.813 | 0.809 | -0.004 |
9 | Jamal Murray | 139 | 155 | 72 | 80 | 0.938 | 0.897 | 0.900 | 0.003 |
31 | Anthony Davis | 338 | 403 | 165 | 196 | 0.922 | 0.839 | 0.842 | 0.003 |
24 | Daniel Theis | 77 | 100 | 27 | 35 | 0.986 | 0.770 | 0.771 | 0.001 |
21 | James Harden | 556 | 653 | 133 | 156 | 0.972 | 0.851 | 0.853 | 0.001 |
Matched Pairs T-Test
The effect size of the bubble on free throw percentage increased these 49 players free throw percentage on average by 0.8%. Note that this treats all players equally, regardless of how many free throws they've attempted because we group by each player.
The matched pair's t-test assumes that the observations are independent of one another and that the dependent variable should be approximately normally distributed. Plotting a kernal density estimator and a q-q plot, we see that we have approximate normality. From the q-q plot, we can see that Russell Westbrook was our lone outlier from normality.
A matched pair's t-test shows a p-value of .388, so we do not have enough evidence to reject the null hypothesis that the effect of the bubble on a player's free throw percentage is 0.
fig, ax = plt.subplots()
ax = ft_pct_df['ft-pct-diff'].plot.kde()
fig.suptitle('KDE of Free Throw Difference\n Pre-Bubble to Bubble')
plt.show()
stats.probplot(ft_pct_df['ft-pct-diff'], dist="norm", plot=pylab)
pylab.show()
print('P-value of Shapiro-Wilks Test for Normality = ' + str(round(stats.shapiro(ft_pct_df['ft-pct-diff'])[1],3)))
print('Effect size of the bubble = ' + str(round(ft_pct_df['ft-pct-diff'].mean(),3)))
pval = stats.ttest_rel(ft_pct_df['pre-ft-pct'], ft_pct_df['bubble-pct'])[1]
print('P-value of Matched Pairs T-Test = ' + str(round(pval,3)))
P-value of Shapiro-Wilks Test for Normality = 0.045
Effect size of the bubble = 0.008
P-value of Matched Pairs T-Test = 0.388
Conclusion
The data only shows that 5 players have a significant difference in their free throw percentage before the bubble and in the bubble. Using a matched pairs t-test, we can conclude that a player's free throw percentage does not increase in the bubble.
In an earlier version of the analysis performed on 8/10/2020, the matched t-test did show a significant effect of 2.6%. The fact that we do not see any effect at the end of the bubble could be due to a regression to the mean or the fact that players adjusted to playing in the bubble.