This case study builds upon the analysis of the Steph Curry dataset, originally introduced in Chapter 15 of Murachβs 'Python for Data Science'. The objective is to extend the initial analysis by incorporating additional functions and substrings in order to conduct a deeper exploratory analysis and uncover new insights.ΒΆ
The four steps of the study, as given in the Exercise 15-1 on page 530, are:ΒΆ
1. Get the dataΒΆ
2. Clean the dataΒΆ
3. Prepare and analyze the dataΒΆ
4. Plot the dataΒΆ
1. Get the Data,ΒΆ
Necessary modules:ΒΆ
InΒ [2]:
import pandas as pd
import seaborn as sns
import json
from urllib import request
Loading the json dataset into a dictionary and building a pandas dataframe:ΒΆ
InΒ [3]:
shots_url = 'https://www.murach.com/python_analysis/shots.json'
shots = request.urlretrieve(shots_url, filename='shots.json')
with open('shots.json') as jsonData:
shots = json.load(jsonData)
shots.keys() # displaying the dataset keys
Out[3]:
dict_keys(['resource', 'parameters', 'resultSets'])
InΒ [4]:
columnHeaders = shots['resultSets'][0]['headers'] # building columns and rows
columnHeaders = [x.lower() for x in columnHeaders]
rows = shots['resultSets'][0]['rowSet']
shots = pd.DataFrame(data=rows, columns=columnHeaders) # building the DataFrame
shots.head() # the first five rows of the data frame
Out[4]:
| grid_type | game_id | game_event_id | player_id | player_name | team_id | team_name | period | minutes_remaining | seconds_remaining | ... | shot_zone_area | shot_zone_range | shot_distance | loc_x | loc_y | shot_attempted_flag | shot_made_flag | game_date | htm | vtm | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Shot Chart Detail | 0020900015 | 4 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 1 | 11 | 25 | ... | Right Side Center(RC) | 24+ ft. | 26 | 99 | 249 | 1 | 0 | 20091028 | GSW | HOU |
| 1 | Shot Chart Detail | 0020900015 | 17 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 1 | 9 | 31 | ... | Left Side Center(LC) | 16-24 ft. | 18 | -122 | 145 | 1 | 1 | 20091028 | GSW | HOU |
| 2 | Shot Chart Detail | 0020900015 | 53 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 1 | 6 | 2 | ... | Center(C) | 8-16 ft. | 14 | -60 | 129 | 1 | 0 | 20091028 | GSW | HOU |
| 3 | Shot Chart Detail | 0020900015 | 141 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 2 | 9 | 49 | ... | Left Side(L) | 16-24 ft. | 19 | -172 | 82 | 1 | 0 | 20091028 | GSW | HOU |
| 4 | Shot Chart Detail | 0020900015 | 249 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 2 | 2 | 19 | ... | Left Side Center(LC) | 16-24 ft. | 16 | -68 | 148 | 1 | 0 | 20091028 | GSW | HOU |
5 rows Γ 24 columns
2. Clean the DataΒΆ
Drop columns that are not needed for the analysis:ΒΆ
InΒ [5]:
shots = shots.drop(columns=['grid_type','game_event_id','player_id','player_name',
'team_id','team_name','minutes_remaining','seconds_remaining',
'htm','vtm'])
InΒ [6]:
shots.head() # verifying columns were dropped
Out[6]:
| game_id | period | event_type | action_type | shot_type | shot_zone_basic | shot_zone_area | shot_zone_range | shot_distance | loc_x | loc_y | shot_attempted_flag | shot_made_flag | game_date | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0020900015 | 1 | Missed Shot | Jump Shot | 3PT Field Goal | Above the Break 3 | Right Side Center(RC) | 24+ ft. | 26 | 99 | 249 | 1 | 0 | 20091028 |
| 1 | 0020900015 | 1 | Made Shot | Step Back Jump shot | 2PT Field Goal | Mid-Range | Left Side Center(LC) | 16-24 ft. | 18 | -122 | 145 | 1 | 1 | 20091028 |
| 2 | 0020900015 | 1 | Missed Shot | Jump Shot | 2PT Field Goal | In The Paint (Non-RA) | Center(C) | 8-16 ft. | 14 | -60 | 129 | 1 | 0 | 20091028 |
| 3 | 0020900015 | 2 | Missed Shot | Jump Shot | 2PT Field Goal | Mid-Range | Left Side(L) | 16-24 ft. | 19 | -172 | 82 | 1 | 0 | 20091028 |
| 4 | 0020900015 | 2 | Missed Shot | Jump Shot | 2PT Field Goal | Mid-Range | Left Side Center(LC) | 16-24 ft. | 16 | -68 | 148 | 1 | 0 | 20091028 |
Display the value counts for the period column:ΒΆ
InΒ [7]:
shots.period.value_counts()
Out[7]:
period 3 3541 1 3501 2 2502 4 2209 5 90 6 3 Name: count, dtype: int64
Drop the rows for periods 5 and 6:ΒΆ
InΒ [8]:
shots = shots.query('period < 5')
shots.period.value_counts()
Out[8]:
period 3 3541 1 3501 2 2502 4 2209 Name: count, dtype: int64
3. Prepare and Analyze the DataΒΆ
Display the number of unique values for the action_type column:ΒΆ
InΒ [9]:
shots.action_type.nunique()
Out[9]:
51
Display the value counts for the action_type column:ΒΆ
InΒ [10]:
shots.action_type.value_counts()
Out[10]:
action_type Jump Shot 5802 Pullup Jump shot 1694 Step Back Jump shot 755 Driving Layup Shot 635 Layup Shot 537 Floating Jump shot 387 Driving Finger Roll Layup Shot 324 Running Jump Shot 209 Driving Reverse Layup Shot 165 Running Layup Shot 131 Cutting Layup Shot 119 Jump Bank Shot 108 Reverse Layup Shot 102 Fadeaway Jump Shot 90 Turnaround Jump Shot 89 Driving Floating Jump Shot 83 Running Bank shot 61 Running Finger Roll Layup Shot 51 Running Pull-Up Jump Shot 44 Finger Roll Layup Shot 36 Driving Bank shot 36 Driving Floating Bank Jump Shot 34 Running Reverse Layup Shot 33 Driving Jump shot 32 Cutting Finger Roll Layup Shot 28 Turnaround Fadeaway shot 26 Pullup Bank shot 20 Turnaround Bank shot 20 Putback Layup Shot 11 Hook Shot 10 Running Hook Shot 10 Driving Hook Shot 9 Slam Dunk Shot 7 Driving Dunk Shot 7 Tip Shot 7 Tip Layup Shot 6 Running Dunk Shot 6 Dunk Shot 5 Fadeaway Bank shot 4 Turnaround Fadeaway Bank Jump Shot 3 Step Back Bank Jump Shot 3 Turnaround Hook Shot 3 Driving Slam Dunk Shot 2 Jump Hook Shot 2 Cutting Dunk Shot 1 Driving Bank Hook Shot 1 Alley Oop Layup shot 1 Running Bank Hook Shot 1 Hook Bank Shot 1 Running Slam Dunk Shot 1 Putback Dunk Shot 1 Name: count, dtype: int64
Add substrings such as βJumpβ and βLayupβ to the list of common substrings that identify each action type. Then, continue to add substrings and to test this code until youβre sure that it provides all the values for the action_type column:ΒΆ
InΒ [12]:
commonSubstrings = ['Jump','Layup','Fadeaway','Hook','Tip','Dunk','Bank']
actions = shots[shots.action_type.str.contains('|'.join(commonSubstrings))].action_type.to_list()
shots.query('action_type not in @actions').action_type # querying to ensure the list is empty and all values are provided
Out[12]:
Series([], Name: action_type, dtype: object)
Define a function named get_label() that takes a row as input and loops through the common substrings list. In the loop, check if the substring is in the rowβs action_type column. If so, return the substring:ΒΆ
InΒ [13]:
def get_label(row):
for a in commonSubstrings:
if a in row.action_type:
return a
Apply this function to every row in the shots DataFrame and assign the result to the shot_type column:ΒΆ
InΒ [14]:
shots['shot_type'] = shots.apply(lambda x: get_label(x), axis=1)
InΒ [19]:
shots.shot_type.value_counts() # checking the shot_type column values
Out[19]:
shot_type Jump 9335 Layup 2179 Bank 137 Hook 35 Fadeaway 30 Dunk 30 Tip 7 Name: count, dtype: int64
4. Plot the DataΒΆ
Use Seaborn to create a count plot that shows the counts for each shot type:ΒΆ
InΒ [37]:
sns.catplot(data=shots, kind='count', x='shot_type', hue = 'shot_type', order=shots['shot_type'].value_counts().index, aspect = 1.5)
Out[37]:
<seaborn.axisgrid.FacetGrid at 0x168b48610>
Use the draw_court() function and the Seaborn displot() method to create a KDE plot with subplots for the βJumpβ and βLayupβ types:ΒΆ
InΒ [21]:
# SOURCE: http://savvastjortjoglou.com/nba-shot-sharts.html
from matplotlib.patches import Circle, Rectangle, Arc
def draw_court(ax=None, color='black', lw=2, outer_lines=False):
# If an axes object isn't provided to plot onto, just get current one
if ax is None:
ax = plt.gca()
# Create the various parts of an NBA basketball court
# Create the basketball hoop
# Diameter of a hoop is 18" so it has a radius of 9", which is a value
# 7.5 in our coordinate system
hoop = Circle((0, 0), radius=7.5, linewidth=lw, color=color, fill=False)
# Create backboard
backboard = Rectangle((-30, -7.5), 60, -1, linewidth=lw, color=color)
# The paint
# Create the outer box 0f the paint, width=16ft, height=19ft
outer_box = Rectangle((-80, -47.5), 160, 190, linewidth=lw, color=color,
fill=False)
# Create the inner box of the paint, widt=12ft, height=19ft
inner_box = Rectangle((-60, -47.5), 120, 190, linewidth=lw, color=color,
fill=False)
# Create free throw top arc
top_free_throw = Arc((0, 142.5), 120, 120, theta1=0, theta2=180,
linewidth=lw, color=color, fill=False)
# Create free throw bottom arc
bottom_free_throw = Arc((0, 142.5), 120, 120, theta1=180, theta2=0,
linewidth=lw, color=color, linestyle='dashed')
# Restricted Zone, it is an arc with 4ft radius from center of the hoop
restricted = Arc((0, 0), 80, 80, theta1=0, theta2=180, linewidth=lw,
color=color)
# Three point line
# Create the side 3pt lines, they are 14ft long before they begin to arc
corner_three_a = Rectangle((-220, -47.5), 0, 140, linewidth=lw,
color=color)
corner_three_b = Rectangle((220, -47.5), 0, 140, linewidth=lw, color=color)
# 3pt arc - center of arc will be the hoop, arc is 23'9" away from hoop
# I just played around with the theta values until they lined up with the
# threes
three_arc = Arc((0, 0), 475, 475, theta1=22, theta2=158, linewidth=lw,
color=color)
# Center Court
center_outer_arc = Arc((0, 422.5), 120, 120, theta1=180, theta2=0,
linewidth=lw, color=color)
center_inner_arc = Arc((0, 422.5), 40, 40, theta1=180, theta2=0,
linewidth=lw, color=color)
# List of the court elements to be plotted onto the axes
court_elements = [hoop, backboard, outer_box, inner_box, top_free_throw,
bottom_free_throw, restricted, corner_three_a,
corner_three_b, three_arc, center_outer_arc,
center_inner_arc]
if outer_lines:
# Draw the half court line, baseline and side out bound lines
outer_lines = Rectangle((-250, -47.5), 500, 470, linewidth=lw,
color=color, fill=False)
court_elements.append(outer_lines)
# Add the court elements onto the axes
for element in court_elements:
ax.add_patch(element)
return ax
InΒ [26]:
# KDE plot of Steph Curry's 'Jump' and 'Layup" shots
graph = sns.displot(data=shots.query('shot_type in ["Jump","Layup"]'), kind='kde', x='loc_x', y='loc_y', col='shot_type', col_wrap=2)
for i, ax in enumerate(graph.axes.flat):
ax = draw_court(ax, outer_lines=True)
ax.set_xlim(-300,300)
ax.set_ylim(-100, 500)