This tutorial will go over some of the basics on how you can use nbashots
to access the NBA stats API and create some shot charts. I'll update this tutorial as I update or make changes to the package.
Now lets beginning by importing the libraries we will be using.
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import nbashots as nba # this will throw a warning if using matplotlib 1.5
%matplotlib inline
/home/savvas/miniconda/envs/py3/lib/python3.4/site-packages/matplotlib/__init__.py:872: UserWarning: axes.color_cycle is deprecated and replaced with axes.prop_cycle; please use the latter. warnings.warn(self.msg_depr % (key, alt_key))
The Player ID is one of the API paramter values we need in order to get the shooting data for a player. To get the Player ID for a given a player all you need to do is use the get_player_id()
function and pass in the player's name in "Last Name, First Name" format. Just note that this function returns the Player ID within a numpy
array as some players have the same name, so more than one ID may be returned.
Let's get the Player ID for Stephen Curry.
curry_id = nba.get_player_id("Curry, Stephen")[0]
curry_id
201939
There is another function, called get_all_player_ids()
, that returns a pandas DataFrame
containing a number of Player IDs depending on the value passed into the ids
parameter. The ids
parameter can accept 3 different strings, "shots" (the default value), "all_players", or "all_data".
When ids
is set to "shots", a DataFrame
containing only the Players (and their IDs) that have shooting data for our shot charts.
# "shots" is the defualt paramter value
players_with_shots = nba.get_all_player_ids("shots")
players_with_shots.head()
PERSON_ID | DISPLAY_LAST_COMMA_FIRST | |
---|---|---|
0 | 1505 | Abdul-Wahad, Tariq |
1 | 949 | Abdur-Rahim, Shareef |
2 | 101165 | Acker, Alex |
3 | 203112 | Acy, Quincy |
4 | 200801 | Adams, Hassan |
players_with_shots.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 1492 entries, 0 to 1491 Data columns (total 2 columns): PERSON_ID 1492 non-null int64 DISPLAY_LAST_COMMA_FIRST 1492 non-null object dtypes: int64(1), object(1) memory usage: 35.0+ KB
PERSON_ID
is the column containing the Player IDs and DISPLAY_LAST_COMMA_FIRST
is the columns conatining the Player names.
When ids
is set to "all_players", get_all_player_ids()
returns a DataFrame
with all Players and their IDs used in the NBA stats API.
all_players = nba.get_all_player_ids("all_players")
all_players.head()
PERSON_ID | DISPLAY_LAST_COMMA_FIRST | |
---|---|---|
0 | 76001 | Abdelnaby, Alaa |
1 | 76002 | Abdul-Aziz, Zaid |
2 | 76003 | Abdul-Jabbar, Kareem |
3 | 51 | Abdul-Rauf, Mahmoud |
4 | 1505 | Abdul-Wahad, Tariq |
all_players.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 4066 entries, 0 to 4065 Data columns (total 2 columns): PERSON_ID 4066 non-null int64 DISPLAY_LAST_COMMA_FIRST 4066 non-null object dtypes: int64(1), object(1) memory usage: 95.3+ KB
When ids
is set to "all_data", get_all_player_ids()
returns a DataFrame
with all the data from the API call.
all_data = nba.get_all_player_ids("all_data")
all_data.head()
PERSON_ID | DISPLAY_LAST_COMMA_FIRST | ROSTERSTATUS | FROM_YEAR | TO_YEAR | PLAYERCODE | TEAM_ID | TEAM_CITY | TEAM_NAME | TEAM_ABBREVIATION | TEAM_CODE | GAMES_PLAYED_FLAG | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 76001 | Abdelnaby, Alaa | 0 | 1990 | 1994 | HISTADD_alaa_abdelnaby | 0 | Y | ||||
1 | 76002 | Abdul-Aziz, Zaid | 0 | 1968 | 1977 | HISTADD_zaid_abdul-aziz | 0 | Y | ||||
2 | 76003 | Abdul-Jabbar, Kareem | 0 | 1969 | 1988 | HISTADD_kareem_abdul-jabbar | 0 | Y | ||||
3 | 51 | Abdul-Rauf, Mahmoud | 0 | 1990 | 2000 | HISTADD_mahmoud_abdul-rauf | 0 | Y | ||||
4 | 1505 | Abdul-Wahad, Tariq | 0 | 1997 | 2003 | tariq_abdul-wahad | 0 | Y |
The Team ID is another parameter value we may use in order to get shooting data for a whole team, or to get the shooting data when a player or team faces a specific team. To get a single Team ID (as an int
) use the get_team_id()
function and pass in the team name ONLY. Not the city, or the city and team name.
# getting the Knicks' Team ID
knicks_id = nba.get_team_id("Knicks")
knicks_id
1610612752
There is also a get_all_team_ids()
function that returns a DataFrame
with all teams and their IDs.
all_team_ids = nba.get_all_team_ids()
all_team_ids.head()
TEAM_ID | TEAM_NAME | |
---|---|---|
0 | 0 | |
1 | 1610612758 | Kings |
2 | 1610612763 | Grizzlies |
3 | 1610612760 | Thunder |
4 | 1610612752 | Knicks |
The Game ID is a parameter value we may use in order to get a player's or team's shooting data for a specific game. There are two ways you can do this. One way is to create a PlayerLog
object which allows access to a player's game logs. The other way is to create a TeamLog
object, which allows access to a team's game log. By getting the game logs we are then able to extract the Game ID for the specific game we want.
We can get Stephen Curry's game log by just passing his Player ID into the PlayerLog()
like so:
# Create a PlayerLog instance to get Curry's game logs
curry_logs = nba.PlayerLog(curry_id)
Note that PlayerLog()
has 4 parameters, player_id
, league_id
, season
, and season_type
, each representing the NBA stats API parameters needed to get the game logs data. league_id
has the default value of "00". season
has the default value of "2015-15". season_type
has the default value of "Regular Season". The parameter values are stored in a dictionary
and can be accessed as an attribute like so:
curry_logs.url_paramaters
{'LeagueID': '00', 'PlayerID': 201939, 'Season': '2015-16', 'SeasonType': 'Regular Season'}
To get the game logs all we need to do is call the get_game_logs()
method, which returns us a DataFrame
with the game logs data.
curry_logs_df = curry_logs.get_game_logs()
curry_logs_df.head()
SEASON_ID | Player_ID | Game_ID | GAME_DATE | MATCHUP | WL | MIN | FGM | FGA | FG_PCT | ... | DREB | REB | AST | STL | BLK | TOV | PF | PTS | PLUS_MINUS | VIDEO_AVAILABLE | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 22015 | 201939 | 0021500718 | 2016-01-31 | GSW @ NYK | W | 32 | 5 | 17 | 0.294 | ... | 4 | 4 | 4 | 3 | 0 | 3 | 1 | 13 | 10 | 1 |
1 | 22015 | 201939 | 0021500707 | 2016-01-30 | GSW @ PHI | W | 34 | 9 | 19 | 0.474 | ... | 3 | 5 | 6 | 1 | 2 | 8 | 3 | 23 | 8 | 1 |
2 | 22015 | 201939 | 0021500691 | 2016-01-27 | GSW vs. DAL | W | 32 | 4 | 11 | 0.364 | ... | 1 | 2 | 9 | 3 | 1 | 0 | 2 | 14 | 26 | 1 |
3 | 22015 | 201939 | 0021500675 | 2016-01-25 | GSW vs. SAS | W | 28 | 12 | 20 | 0.600 | ... | 1 | 2 | 3 | 5 | 0 | 1 | 0 | 37 | 23 | 1 |
4 | 22015 | 201939 | 0021500652 | 2016-01-22 | GSW vs. IND | W | 37 | 11 | 19 | 0.579 | ... | 8 | 10 | 12 | 1 | 0 | 2 | 1 | 39 | 22 | 1 |
5 rows × 27 columns
While you can get the Game ID you want from the DataFrame
above. If you know the date of the game whose ID you want, you can just pass it (as a string) into the get_game_id()
method. You can pass in the date in the numeric format of MM/DD/YY (like "01/09/16" or "01/09/2016") or the expanded Month Day, Year format (like "Jan 09, 2016" or "January 09, 2016").
# The next five examples all return the same Game ID
curry_logs.get_game_id("01/09/16")
'0021500556'
curry_logs.get_game_id("01/09/16")
'0021500556'
curry_logs.get_game_id("01/09/2016")
'0021500556'
curry_logs.get_game_id("Jan 09, 2016")
'0021500556'
curry_logs.get_game_id("January 09, 2016")
'0021500556'
Using TeamLogs()
is the same as using using PlayerLogs()
, but instead of passing in a Player ID you pass in a Team ID.
# Create a TeamLog object
knicks_logs = nba.TeamLog(knicks_id)
# Create a DataFrame containing the game logs data
knicks_logs_df = knicks_logs.get_game_logs()
knicks_logs_df.head()
Team_ID | Game_ID | GAME_DATE | MATCHUP | WL | MIN | FGM | FGA | FG_PCT | FG3M | ... | FT_PCT | OREB | DREB | REB | AST | STL | BLK | TOV | PF | PTS | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1610612752 | 0021500730 | 2016-02-02 | NYK vs. BOS | L | 240 | 31 | 82 | 0.378 | 6 | ... | 0.750 | 16 | 34 | 50 | 13 | 3 | 4 | 16 | 16 | 89 |
1 | 1610612752 | 0021500718 | 2016-01-31 | NYK vs. GSW | L | 240 | 33 | 90 | 0.367 | 6 | ... | 0.885 | 12 | 29 | 41 | 19 | 6 | 4 | 11 | 20 | 95 |
2 | 1610612752 | 0021500700 | 2016-01-29 | NYK vs. PHX | W | 240 | 36 | 84 | 0.429 | 7 | ... | 0.885 | 10 | 39 | 49 | 24 | 12 | 7 | 8 | 18 | 102 |
3 | 1610612752 | 0021500694 | 2016-01-28 | NYK @ TOR | L | 240 | 38 | 87 | 0.437 | 7 | ... | 0.500 | 12 | 28 | 40 | 20 | 6 | 8 | 9 | 19 | 93 |
4 | 1610612752 | 0021500679 | 2016-01-26 | NYK vs. OKC | L | 265 | 44 | 95 | 0.463 | 11 | ... | 0.767 | 9 | 33 | 42 | 24 | 9 | 7 | 9 | 21 | 122 |
5 rows × 24 columns
# Get the Game ID for a specific date
knicks_logs.get_game_id("1/10/16")
'0021500562'
Now that we know how to get the values for the player_id
, team_id
, and game_id
parameters lets use the Shots
class to get some shot chart data.
shots = nba.Shots()
There are a lot of parameters for Shots
, which I won't go over in detail right now (I plan on including a full description when I finish up the documentation). The paramaters and default values are as follows:
player_id=0
team_id=0
league_id="00"
season="2015-16"
season_type="Regular Season"
game_id=""
outcome=""
location=""
month=0, season_segment=""
date_from=""
date_to=""
opp_team_id=0
vs_conference=""
vs_division=""
position=""
rookie_year=""
game_segment=""
period=0
last_n_games=0
clutch_time=""
ahead_behind=""
point_diff=""
range_type=""
start_period=""
end_period=""
start_range=""
end_range=""
context_filter=""
context_measure="FGA"
If we call the method get_shots()
with the default parameter values, we get all the shots taken for the current season.
current_season_shots = shots.get_shots()
current_season_shots.head()
GRID_TYPE | GAME_ID | GAME_EVENT_ID | PLAYER_ID | PLAYER_NAME | TEAM_ID | TEAM_NAME | PERIOD | MINUTES_REMAINING | SECONDS_REMAINING | ... | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Shot Chart Detail | 0021500001 | 2 | 203083 | Andre Drummond | 1610612765 | Detroit Pistons | 1 | 11 | 41 | ... | Driving Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 1 | -17 | -6 | 1 | 0 |
1 | Shot Chart Detail | 0021500001 | 5 | 202694 | Marcus Morris | 1610612765 | Detroit Pistons | 1 | 11 | 21 | ... | Step Back Jump shot | 2PT Field Goal | Mid-Range | Right Side(R) | 8-16 ft. | 13 | 117 | 67 | 1 | 1 |
2 | Shot Chart Detail | 0021500001 | 6 | 200794 | Paul Millsap | 1610612737 | Atlanta Hawks | 1 | 11 | 0 | ... | Step Back Jump shot | 2PT Field Goal | In The Paint (Non-RA) | Right Side(R) | 8-16 ft. | 12 | 76 | 95 | 1 | 1 |
3 | Shot Chart Detail | 0021500001 | 7 | 203484 | Kentavious Caldwell-Pope | 1610612765 | Detroit Pistons | 1 | 10 | 44 | ... | Driving Floating Jump Shot | 2PT Field Goal | In The Paint (Non-RA) | Left Side(L) | 8-16 ft. | 8 | -68 | 51 | 1 | 1 |
4 | Shot Chart Detail | 0021500001 | 8 | 201143 | Al Horford | 1610612737 | Atlanta Hawks | 1 | 10 | 27 | ... | Jump Shot | 2PT Field Goal | Mid-Range | Left Side Center(LC) | 16-24 ft. | 20 | -117 | 164 | 1 | 0 |
5 rows × 21 columns
current_season_shots.tail()
GRID_TYPE | GAME_ID | GAME_EVENT_ID | PLAYER_ID | PLAYER_NAME | TEAM_ID | TEAM_NAME | PERIOD | MINUTES_REMAINING | SECONDS_REMAINING | ... | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
123025 | Shot Chart Detail | 0021500732 | 504 | 1626164 | Devin Booker | 1610612756 | Phoenix Suns | 4 | 0 | 35 | ... | Jump Shot | 3PT Field Goal | Above the Break 3 | Left Side Center(LC) | 24+ ft. | 25 | -114 | 228 | 1 | 0 |
123026 | Shot Chart Detail | 0021500732 | 507 | 203141 | Mirza Teletovic | 1610612756 | Phoenix Suns | 4 | 0 | 32 | ... | Jump Shot | 3PT Field Goal | Right Corner 3 | Right Side(R) | 24+ ft. | 22 | 227 | 0 | 1 | 1 |
123027 | Shot Chart Detail | 0021500732 | 520 | 203141 | Mirza Teletovic | 1610612756 | Phoenix Suns | 4 | 0 | 23 | ... | Jump Shot | 3PT Field Goal | Above the Break 3 | Right Side Center(RC) | 24+ ft. | 24 | 92 | 228 | 1 | 0 |
123028 | Shot Chart Detail | 0021500732 | 531 | 1626164 | Devin Booker | 1610612756 | Phoenix Suns | 4 | 0 | 14 | ... | Jump Shot | 3PT Field Goal | Above the Break 3 | Right Side Center(RC) | 24+ ft. | 25 | 155 | 208 | 1 | 0 |
123029 | Shot Chart Detail | 0021500732 | 533 | 200782 | PJ Tucker | 1610612756 | Phoenix Suns | 4 | 0 | 9 | ... | Jump Shot | 3PT Field Goal | Left Corner 3 | Left Side(L) | 24+ ft. | 23 | -237 | 2 | 1 | 0 |
5 rows × 21 columns
current_season_shots.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 123030 entries, 0 to 123029 Data columns (total 21 columns): GRID_TYPE 123030 non-null object GAME_ID 123030 non-null object GAME_EVENT_ID 123030 non-null int64 PLAYER_ID 123030 non-null int64 PLAYER_NAME 123030 non-null object TEAM_ID 123030 non-null int64 TEAM_NAME 123030 non-null object PERIOD 123030 non-null int64 MINUTES_REMAINING 123030 non-null int64 SECONDS_REMAINING 123030 non-null int64 EVENT_TYPE 123030 non-null object ACTION_TYPE 123030 non-null object SHOT_TYPE 123030 non-null object SHOT_ZONE_BASIC 123030 non-null object SHOT_ZONE_AREA 123030 non-null object SHOT_ZONE_RANGE 123030 non-null object SHOT_DISTANCE 123030 non-null int64 LOC_X 123030 non-null int64 LOC_Y 123030 non-null int64 SHOT_ATTEMPTED_FLAG 123030 non-null int64 SHOT_MADE_FLAG 123030 non-null int64 dtypes: int64(11), object(10) memory usage: 20.7+ MB
When both player_id
and team_id
are set to 0, the shots for the whole league are retrieved. So if we wanted to get all the shot chart data from last season we could either create another Shots
instance and call get_shots()
like so:
last_season_shots = nba.Shots(season="2014-15").get_shots()
Or we could use the update_params()
method, which accepts a dictionary containing key, value pairs that correspond with NBA stats API parameters.
# The dictionary that contains the current API parameters
shots.url_paramaters
{'AheadBehind': '', 'ClutchTime': '', 'ContextFilter': '', 'ContextMeasure': 'FGA', 'DateFrom': '', 'DateTo': '', 'EndPeriod': '', 'EndRange': '', 'GameID': '', 'GameSegment': '', 'LastNGames': 0, 'LeagueID': '00', 'Location': '', 'Month': 0, 'OpponentTeamID': 0, 'Outcome': '', 'Period': 0, 'PlayerID': 0, 'PointDiff': '', 'Position': '', 'RangeType': '', 'RookieYear': '', 'Season': '2015-16', 'SeasonSegment': '', 'SeasonType': 'Regular Season', 'StartPeriod': '', 'StartRange': '', 'TeamID': 0, 'VsConference': '', 'VsDivision': ''}
# update the 'Season' parameter to '2014-15'
shots.update_params({'Season':'2014-15'})
# the updated parameters
shots.url_paramaters
{'AheadBehind': '', 'ClutchTime': '', 'ContextFilter': '', 'ContextMeasure': 'FGA', 'DateFrom': '', 'DateTo': '', 'EndPeriod': '', 'EndRange': '', 'GameID': '', 'GameSegment': '', 'LastNGames': 0, 'LeagueID': '00', 'Location': '', 'Month': 0, 'OpponentTeamID': 0, 'Outcome': '', 'Period': 0, 'PlayerID': 0, 'PointDiff': '', 'Position': '', 'RangeType': '', 'RookieYear': '', 'Season': '2014-15', 'SeasonSegment': '', 'SeasonType': 'Regular Season', 'StartPeriod': '', 'StartRange': '', 'TeamID': 0, 'VsConference': '', 'VsDivision': ''}
last_season_shots = shots.get_shots()
last_season_shots.head()
GRID_TYPE | GAME_ID | GAME_EVENT_ID | PLAYER_ID | PLAYER_NAME | TEAM_ID | TEAM_NAME | PERIOD | MINUTES_REMAINING | SECONDS_REMAINING | ... | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Shot Chart Detail | 0021400001 | 2 | 203076 | Anthony Davis | 1610612740 | New Orleans Pelicans | 1 | 11 | 43 | ... | Jump Shot | 2PT Field Goal | Mid-Range | Center(C) | 16-24 ft. | 20 | 50 | 194 | 1 | 0 |
1 | Shot Chart Detail | 0021400001 | 4 | 202696 | Nikola Vucevic | 1610612753 | Orlando Magic | 1 | 11 | 31 | ... | Jump Bank Shot | 2PT Field Goal | Mid-Range | Center(C) | 16-24 ft. | 18 | -8 | 189 | 1 | 1 |
2 | Shot Chart Detail | 0021400001 | 7 | 203076 | Anthony Davis | 1610612740 | New Orleans Pelicans | 1 | 11 | 6 | ... | Jump Shot | 2PT Field Goal | Mid-Range | Left Side Center(LC) | 16-24 ft. | 18 | -131 | 127 | 1 | 0 |
3 | Shot Chart Detail | 0021400001 | 9 | 203901 | Elfrid Payton | 1610612753 | Orlando Magic | 1 | 10 | 54 | ... | Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 1 | -15 | 4 | 1 | 0 |
4 | Shot Chart Detail | 0021400001 | 25 | 203076 | Anthony Davis | 1610612740 | New Orleans Pelicans | 1 | 10 | 29 | ... | Dunk Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 0 | 0 | 1 | 1 | 1 |
5 rows × 21 columns
last_season_shots.tail()
GRID_TYPE | GAME_ID | GAME_EVENT_ID | PLAYER_ID | PLAYER_NAME | TEAM_ID | TEAM_NAME | PERIOD | MINUTES_REMAINING | SECONDS_REMAINING | ... | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
205545 | Shot Chart Detail | 0021401230 | 567 | 200797 | Ryan Hollins | 1610612758 | Sacramento Kings | 4 | 1 | 2 | ... | Alley Oop Dunk Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 0 | 0 | 1 | 1 | 1 |
205546 | Shot Chart Detail | 0021401230 | 568 | 203913 | Jabari Brown | 1610612747 | Los Angeles Lakers | 4 | 0 | 51 | ... | Jump Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 3 | -12 | 35 | 1 | 0 |
205547 | Shot Chart Detail | 0021401230 | 570 | 203463 | Ben McLemore | 1610612758 | Sacramento Kings | 4 | 0 | 41 | ... | Jump Shot | 3PT Field Goal | Above the Break 3 | Right Side Center(RC) | 24+ ft. | 27 | 115 | 249 | 1 | 0 |
205548 | Shot Chart Detail | 0021401230 | 572 | 203527 | Ryan Kelly | 1610612747 | Los Angeles Lakers | 4 | 0 | 30 | ... | Running Bank shot | 2PT Field Goal | In The Paint (Non-RA) | Center(C) | Less Than 8 ft. | 7 | 15 | 76 | 1 | 1 |
205549 | Shot Chart Detail | 0021401230 | 576 | 203135 | Robert Sacre | 1610612747 | Los Angeles Lakers | 4 | 0 | 0 | ... | Jump Shot | 2PT Field Goal | Mid-Range | Right Side Center(RC) | 16-24 ft. | 20 | 125 | 168 | 1 | 1 |
5 rows × 21 columns
last_season_shots.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 205550 entries, 0 to 205549 Data columns (total 21 columns): GRID_TYPE 205550 non-null object GAME_ID 205550 non-null object GAME_EVENT_ID 205550 non-null int64 PLAYER_ID 205550 non-null int64 PLAYER_NAME 205550 non-null object TEAM_ID 205550 non-null int64 TEAM_NAME 205550 non-null object PERIOD 205550 non-null int64 MINUTES_REMAINING 205550 non-null int64 SECONDS_REMAINING 205550 non-null int64 EVENT_TYPE 205550 non-null object ACTION_TYPE 205550 non-null object SHOT_TYPE 205550 non-null object SHOT_ZONE_BASIC 205550 non-null object SHOT_ZONE_AREA 205550 non-null object SHOT_ZONE_RANGE 205550 non-null object SHOT_DISTANCE 205550 non-null int64 LOC_X 205550 non-null int64 LOC_Y 205550 non-null int64 SHOT_ATTEMPTED_FLAG 205550 non-null int64 SHOT_MADE_FLAG 205550 non-null int64 dtypes: int64(11), object(10) memory usage: 34.5+ MB
To get the shot chart data for a team all we need is to pass in the Team's ID into the team_id
paramater in Shots
, and then call the get_shots()
method.
# Create a Shots object
knicks_shots = nba.Shots(team_id=knicks_id)
# Return the shot chart data as a DataFrame
knicks_shots_df = knicks_shots.get_shots()
knicks_shots_df.head()
GRID_TYPE | GAME_ID | GAME_EVENT_ID | PLAYER_ID | PLAYER_NAME | TEAM_ID | TEAM_NAME | PERIOD | MINUTES_REMAINING | SECONDS_REMAINING | ... | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Shot Chart Detail | 0021500012 | 2 | 201577 | Robin Lopez | 1610612752 | New York Knicks | 1 | 11 | 46 | ... | Dunk Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 0 | 0 | 1 | 1 | 1 |
1 | Shot Chart Detail | 0021500012 | 14 | 2546 | Carmelo Anthony | 1610612752 | New York Knicks | 1 | 10 | 22 | ... | Jump Shot | 2PT Field Goal | Mid-Range | Right Side(R) | 8-16 ft. | 14 | 91 | 115 | 1 | 0 |
2 | Shot Chart Detail | 0021500012 | 20 | 101181 | Jose Calderon | 1610612752 | New York Knicks | 1 | 9 | 50 | ... | Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 0 | -8 | 0 | 1 | 0 |
3 | Shot Chart Detail | 0021500012 | 29 | 2756 | Sasha Vujacic | 1610612752 | New York Knicks | 1 | 9 | 4 | ... | Step Back Jump shot | 3PT Field Goal | Left Corner 3 | Left Side(L) | 24+ ft. | 23 | -238 | 13 | 1 | 0 |
4 | Shot Chart Detail | 0021500012 | 37 | 204001 | Kristaps Porzingis | 1610612752 | New York Knicks | 1 | 8 | 33 | ... | Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 2 | -19 | 16 | 1 | 0 |
5 rows × 21 columns
Every Shots
instance has the method get_league_avg()
, that returns the league totals and averages for each shooting zone and area.
knicks_shots.get_league_avg()
GRID_TYPE | SHOT_ZONE_BASIC | SHOT_ZONE_AREA | SHOT_ZONE_RANGE | FGA | FGM | FG_PCT | |
---|---|---|---|---|---|---|---|
0 | League Averages | Above the Break 3 | Back Court(BC) | Back Court Shot | 38 | 2 | 0.053 |
1 | League Averages | Above the Break 3 | Center(C) | 24+ ft. | 6372 | 2202 | 0.346 |
2 | League Averages | Above the Break 3 | Left Side Center(LC) | 24+ ft. | 9845 | 3387 | 0.344 |
3 | League Averages | Above the Break 3 | Right Side Center(RC) | 24+ ft. | 9319 | 3276 | 0.352 |
4 | League Averages | Backcourt | Back Court(BC) | Back Court Shot | 252 | 8 | 0.032 |
5 | League Averages | In The Paint (Non-RA) | Center(C) | 8-16 ft. | 4023 | 1681 | 0.418 |
6 | League Averages | In The Paint (Non-RA) | Center(C) | Less Than 8 ft. | 11474 | 4451 | 0.388 |
7 | League Averages | In The Paint (Non-RA) | Left Side(L) | 8-16 ft. | 1300 | 530 | 0.408 |
8 | League Averages | In The Paint (Non-RA) | Right Side(R) | 8-16 ft. | 1131 | 466 | 0.412 |
9 | League Averages | Left Corner 3 | Left Side(L) | 24+ ft. | 4515 | 1690 | 0.374 |
10 | League Averages | Mid-Range | Center(C) | 16-24 ft. | 4415 | 1754 | 0.397 |
11 | League Averages | Mid-Range | Center(C) | 8-16 ft. | 1339 | 589 | 0.440 |
12 | League Averages | Mid-Range | Left Side Center(LC) | 16-24 ft. | 4708 | 1892 | 0.402 |
13 | League Averages | Mid-Range | Left Side(L) | 16-24 ft. | 3344 | 1355 | 0.405 |
14 | League Averages | Mid-Range | Left Side(L) | 8-16 ft. | 4546 | 1726 | 0.380 |
15 | League Averages | Mid-Range | Right Side Center(RC) | 16-24 ft. | 4893 | 1950 | 0.399 |
16 | League Averages | Mid-Range | Right Side(R) | 16-24 ft. | 2865 | 1158 | 0.404 |
17 | League Averages | Mid-Range | Right Side(R) | 8-16 ft. | 4592 | 1839 | 0.400 |
18 | League Averages | Restricted Area | Center(C) | Less Than 8 ft. | 39841 | 23671 | 0.594 |
19 | League Averages | Right Corner 3 | Right Side(R) | 24+ ft. | 4218 | 1581 | 0.375 |
To return the data as JSON, we can just call the to_json()
from the DataFrame
containing the data.
from pprint import pprint # to pretty print the JSON
# Return data as JSON from DataFrame using to_json()
pprint(knicks_shots.get_league_avg().to_json())
('{"GRID_TYPE":{"0":"League Averages","1":"League Averages","2":"League ' 'Averages","3":"League Averages","4":"League Averages","5":"League ' 'Averages","6":"League Averages","7":"League Averages","8":"League ' 'Averages","9":"League Averages","10":"League Averages","11":"League ' 'Averages","12":"League Averages","13":"League Averages","14":"League ' 'Averages","15":"League Averages","16":"League Averages","17":"League ' 'Averages","18":"League Averages","19":"League ' 'Averages"},"SHOT_ZONE_BASIC":{"0":"Above the Break 3","1":"Above the Break ' '3","2":"Above the Break 3","3":"Above the Break 3","4":"Backcourt","5":"In ' 'The Paint (Non-RA)","6":"In The Paint (Non-RA)","7":"In The Paint ' '(Non-RA)","8":"In The Paint (Non-RA)","9":"Left Corner ' '3","10":"Mid-Range","11":"Mid-Range","12":"Mid-Range","13":"Mid-Range","14":"Mid-Range","15":"Mid-Range","16":"Mid-Range","17":"Mid-Range","18":"Restricted ' 'Area","19":"Right Corner 3"},"SHOT_ZONE_AREA":{"0":"Back ' 'Court(BC)","1":"Center(C)","2":"Left Side Center(LC)","3":"Right Side ' 'Center(RC)","4":"Back Court(BC)","5":"Center(C)","6":"Center(C)","7":"Left ' 'Side(L)","8":"Right Side(R)","9":"Left ' 'Side(L)","10":"Center(C)","11":"Center(C)","12":"Left Side ' 'Center(LC)","13":"Left Side(L)","14":"Left Side(L)","15":"Right Side ' 'Center(RC)","16":"Right Side(R)","17":"Right ' 'Side(R)","18":"Center(C)","19":"Right ' 'Side(R)"},"SHOT_ZONE_RANGE":{"0":"Back Court Shot","1":"24+ ft.","2":"24+ ' 'ft.","3":"24+ ft.","4":"Back Court Shot","5":"8-16 ft.","6":"Less Than 8 ' 'ft.","7":"8-16 ft.","8":"8-16 ft.","9":"24+ ft.","10":"16-24 ' 'ft.","11":"8-16 ft.","12":"16-24 ft.","13":"16-24 ft.","14":"8-16 ' 'ft.","15":"16-24 ft.","16":"16-24 ft.","17":"8-16 ft.","18":"Less Than 8 ' 'ft.","19":"24+ ' 'ft."},"FGA":{"0":38,"1":6372,"2":9845,"3":9319,"4":252,"5":4023,"6":11474,"7":1300,"8":1131,"9":4515,"10":4415,"11":1339,"12":4708,"13":3344,"14":4546,"15":4893,"16":2865,"17":4592,"18":39841,"19":4218},"FGM":{"0":2,"1":2202,"2":3387,"3":3276,"4":8,"5":1681,"6":4451,"7":530,"8":466,"9":1690,"10":1754,"11":589,"12":1892,"13":1355,"14":1726,"15":1950,"16":1158,"17":1839,"18":23671,"19":1581},"FG_PCT":{"0":0.053,"1":0.346,"2":0.344,"3":0.352,"4":0.032,"5":0.418,"6":0.388,"7":0.408,"8":0.412,"9":0.374,"10":0.397,"11":0.44,"12":0.402,"13":0.405,"14":0.38,"15":0.399,"16":0.404,"17":0.4,"18":0.594,"19":0.375}}')
All we need to do to get a player's data is pass in his Player ID into Shots()
.
curry_shots_df = nba.Shots(curry_id).get_shots()
curry_shots_df.head()
GRID_TYPE | GAME_ID | GAME_EVENT_ID | PLAYER_ID | PLAYER_NAME | TEAM_ID | TEAM_NAME | PERIOD | MINUTES_REMAINING | SECONDS_REMAINING | ... | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Shot Chart Detail | 0021500003 | 6 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 1 | 10 | 56 | ... | Cutting Finger Roll Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 3 | 12 | 31 | 1 | 1 |
1 | Shot Chart Detail | 0021500003 | 9 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 1 | 10 | 29 | ... | Jump Shot | 3PT Field Goal | Above the Break 3 | Left Side Center(LC) | 24+ ft. | 26 | -176 | 195 | 1 | 0 |
2 | Shot Chart Detail | 0021500003 | 14 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 1 | 9 | 34 | ... | Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 2 | 20 | 9 | 1 | 0 |
3 | Shot Chart Detail | 0021500003 | 19 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 1 | 9 | 13 | ... | Jump Shot | 3PT Field Goal | Above the Break 3 | Left Side Center(LC) | 24+ ft. | 27 | -197 | 193 | 1 | 1 |
4 | Shot Chart Detail | 0021500003 | 36 | 201939 | Stephen Curry | 1610612744 | Golden State Warriors | 1 | 7 | 11 | ... | Running Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 0 | -4 | 8 | 1 | 1 |
5 rows × 21 columns
shot_chart()
function¶The shot_chart()
function lets you quickly create a shot by passing in the x and y values that represent the player's or team's shots. The x and y values are stored in the LOC_X
and LOC_Y
columns in the DataFrame
returned by get_shots()
.
Lets plot the Knick's and Curry's FGA.
# Set the size for our plots
plt.rcParams['figure.figsize'] = (12, 11)
# Pass in Curry's FGA coordinates to shot_charts()
nba.shot_chart(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season")
plt.show()
# Knicks FGA
nba.shot_chart(knicks_shots_df.LOC_X, knicks_shots_df.LOC_Y,
title="Knicks FGA 2015-16 Season")
plt.show()
Lets differentiate between made missed FGA
knicks_shots_df.head()
GRID_TYPE | GAME_ID | GAME_EVENT_ID | PLAYER_ID | PLAYER_NAME | TEAM_ID | TEAM_NAME | PERIOD | MINUTES_REMAINING | SECONDS_REMAINING | ... | 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 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | Shot Chart Detail | 0021500012 | 2 | 201577 | Robin Lopez | 1610612752 | New York Knicks | 1 | 11 | 46 | ... | Dunk Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 0 | 0 | 1 | 1 | 1 |
1 | Shot Chart Detail | 0021500012 | 14 | 2546 | Carmelo Anthony | 1610612752 | New York Knicks | 1 | 10 | 22 | ... | Jump Shot | 2PT Field Goal | Mid-Range | Right Side(R) | 8-16 ft. | 14 | 91 | 115 | 1 | 0 |
2 | Shot Chart Detail | 0021500012 | 20 | 101181 | Jose Calderon | 1610612752 | New York Knicks | 1 | 9 | 50 | ... | Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 0 | -8 | 0 | 1 | 0 |
3 | Shot Chart Detail | 0021500012 | 29 | 2756 | Sasha Vujacic | 1610612752 | New York Knicks | 1 | 9 | 4 | ... | Step Back Jump shot | 3PT Field Goal | Left Corner 3 | Left Side(L) | 24+ ft. | 23 | -238 | 13 | 1 | 0 |
4 | Shot Chart Detail | 0021500012 | 37 | 204001 | Kristaps Porzingis | 1610612752 | New York Knicks | 1 | 8 | 33 | ... | Layup Shot | 2PT Field Goal | Restricted Area | Center(C) | Less Than 8 ft. | 2 | -19 | 16 | 1 | 0 |
5 rows × 21 columns
We can also create kernel density estimate (kde) shot charts and hexbin shot charts by passing in the appropriate string to the kind
parameter. "kde" for a kde shot chart and "hex" for a hexbin shot chart. The default value for kind
is "scatter" which creates the type of shot charts above.
# Curry KDE plot
# Pass in Curry's FGA coordinates to shot_charts()
nba.shot_chart(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
kind="kde", title="Stephen Curry FGA 2015-16 Season")
plt.show()
# get a colormap for the KDE shot chart
cmap=plt.cm.gist_heat_r
# Now plot the Knicks KDE shot chart
nba.shot_chart(knicks_shots_df.LOC_X, knicks_shots_df.LOC_Y,
kind="kde", title="Knicks FGA 2015-16 Season",
cmap=cmap)
plt.show()
We can adjust the number of contour levels for a KDE plot by passing in an integer to n_levels
.
# Now plot the Knicks KDE shot chart with 20 contour levels
nba.shot_chart(knicks_shots_df.LOC_X, knicks_shots_df.LOC_Y,
kind="kde", title="Knicks FGA 2015-16 Season",
cmap=cmap, n_levels=20)
plt.show()
Let's make some hexbin plots.
nba.shot_chart(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
kind="hex", title="Stephen Curry FGA 2015-16 Season",
cmap=cmap)
plt.show()
We can set the number hexbins in the x-axis direction via the gridsize
parameter.
# Curry Hexbin plot with 26 hexbins across the x-axis
nba.shot_chart(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
kind="hex", title="Stephen Curry FGA 2015-16 Season",
cmap=cmap, gridsize=26)
plt.show()
nba.shot_chart_jointplot(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season")
plt.show()
We can also pass in the DataFrame
with our shots, and set x and y to the desired column names.
nba.shot_chart_jointplot(x="LOC_X", y="LOC_Y", data=knicks_shots_df,
color=cmap(.2), title="Knicks FGA 2015-16 Season")
plt.show()
To create a KDE jointplot, all we need to do is just set kind
to "kde".
nba.shot_chart_jointplot(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
kind="kde", title="Stephen Curry FGA 2015-16 Season")
plt.show()
The colormap for the above plot is extracted from the value passed to color
. When passing in a colormap for the jointplot make sure to set a proper color
value as it controls the color of the marginal plot.
# Passing in a cmap value and not changing the default color value
nba.shot_chart_jointplot(knicks_shots_df.LOC_X, knicks_shots_df.LOC_Y,
kind="kde", title="Knicks FGA 2015-16 Season",
cmap=cmap, n_levels=20)
plt.show()
# Passing a cmap value and a color value
# The color is based on cmap
nba.shot_chart_jointplot(knicks_shots_df.LOC_X, knicks_shots_df.LOC_Y,
kind="kde", title="Knicks FGA 2015-16 Season",
color=cmap(.2), cmap=cmap, n_levels=20)
plt.show()
To create a Hexbin jointplot, just set kind
to "hex".
# marginal plot color value is based off cmap
nba.shot_chart_jointplot(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
kind="hex", title="Stephen Curry FGA 2015-16 Season",
color=cmap(.2), cmap=cmap, gridsize=26)
plt.show()
We can plot the a KDE plot on the JointGrid
that shot_chart_jointplot()
retuns, by just calling on the JointGrid
's plot_joint()
method and passing in sns.kdeplot
.
grid = nba.shot_chart_jointplot(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season")
grid.plot_joint(sns.kdeplot, cmap=plt.cm.Oranges_r)
plt.show()
shot_chart_jointgrid()
¶shot_chart_jointgrid()
is just a wrapper over seaborn
's JointGrid
. It allows for a bit more flexibility than sot_chart_jointplot()
.
By defualt, shot_chart_jointgrid()
returns us a plot with a scatterplot on the joint plot and both KDE and histograms on the marginal plots.
nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season")
plt.show()
To change the type of joint plot, we can pass in "scatter" (default), "kde", or "hex" to joint_type
. To change the type of marginal plots, we can pass in "both" (default), "kde", or "hist" to marginal_type
. We can adjust the color of the different plot components using the joint_color
and marginal_color
paramters. For the joint plot, if a colormap isn't passed to cmap
, then the colormap for either the hexbin or kde plots will be based on joint_color
. However, if we set a colormap then that will dictate the color for the kde or hexbin joint plot. Now lets run through a few examples.
joint_type
¶# set the joint_type to kde
nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season",
joint_type="kde")
plt.show()
joint_color
¶# adjust joint_color
nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season",
joint_type="kde", joint_color="r")
plt.show()
# Using cmap instead of joint_color for kde
nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season",
joint_type="kde", cmap=cmap)
plt.show()
marginals_color
¶Here we pass in a color to marginals_color
to better match the joint plot's colormap.
nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season",
joint_type="kde", cmap=cmap,
marginals_color=cmap(.3))
plt.show()
marginal_type
¶Plotting just histograms on the marginal plots.
nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season",
joint_type="kde", cmap=cmap,
marginals_color=cmap(.3), marginals_type="hist")
plt.show()
Plotting KDE on marginal plots.
nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season",
joint_type="kde", cmap=cmap,
marginals_color=cmap(.3), marginals_type="kde")
plt.show()
We can get rid of the shading in the KDE marginal and joint plots by setting joint_kde_shade
and marginales_kde_shade
to False
.
nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season",
joint_type="kde", cmap=cmap,
marginals_color=cmap(.3), marginals_type="kde",
joint_kde_shade=False, marginals_kde_shade=False)
plt.show()
# Create the JointGrid object
grid = nba.shot_chart_jointgrid(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
title="Stephen Curry FGA 2015-16 Season",
joint_type="scatter", marginals_color=cmap(.3),
marginals_type="kde", joint_kde_shade=False)
grid.plot_joint(sns.kdeplot, cmap=plt.cm.OrRd_r)
plt.show()
The heatmap()
function allows us to plot a players FG%. All we need to do is pass in the x and y coordinates of the shot locations, and the an indicator that tells the function whether the FGA was made. The column that contains this information is called "SHOT_MADE_FLAG".
heatmap = nba.heatmap(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
curry_shots_df.SHOT_MADE_FLAG)
fig = plt.gcf()
fig.colorbar(heatmap)
plt.title("Curry FG% by Location, 2015-16 Season")
plt.show()
We can adjust the size of the boxes by adjusting the the number of bins along the x-axis via the bins
paramter. To increase the size of the bins, decrease the number of bins. (The default values is 20).
heatmap = nba.heatmap(curry_shots_df.LOC_X, curry_shots_df.LOC_Y,
curry_shots_df.SHOT_MADE_FLAG, bins=12)
fig = plt.gcf()
fig.colorbar(heatmap)
plt.title("Curry FG% by Location, 2015-16 Season")
plt.show()
img = nba.get_player_img(curry_id)
get_player_img()
allows returns a numpy array
, which allows us to plot the image using matplotlib.
type(img)
numpy.ndarray
plt.imshow(img)
<matplotlib.image.AxesImage at 0x7f6ed1461390>
OffsetImage
¶To plot the player's image we need to import OffsetImage
. It allows us to plot the image where ever we want on our shot chart.
from matplotlib.offsetbox import OffsetImage
# Create our JointGrid - jointplot() returns a JointGrid object
grid = nba.shot_chart_jointplot(curry_shots_df.LOC_X,
curry_shots_df.LOC_Y, kind="kde",
title="Stephen Curry FGA 2015-16 Season",
color=cmap(.2), cmap=cmap, n_levels=20)
# Create the OffsetImage object, also set the zoom
offset_img = OffsetImage(img, zoom=0.6)
# Pass in the x,y coordinates of where we want the
# image to be plotted
offset_img.set_offset((621, 584))
# Pass in the OffsetImage object to the add_artist() method
# from the joint plot Axes, in order to plot the image
grid.ax_joint.add_artist(offset_img)
plt.show()
bokeh_shot_chart()
¶Lets import what we need to plot our shot charts in the notebook.
# output_notebook lets use render the Javascript in the IPython/Jupyter notebook
from bokeh.plotting import output_notebook, show
# Load BokehJS to render chart in the notebook
output_notebook()
To create our interactive shot chart we can use bokeh_shot_chart()
. The function accepts the DataFrame
with the shot chart data as the first argument. We can also pass in string values, that represent columns in the DataFrame
, for the x and y parameters. The function, by default expects that the DataFrame
we pass in contain columns called "LOC_X" and "LOC_Y".
fig = nba.bokeh_shot_chart(curry_shots_df)
show(fig)
If we pass in columns that aren't in the DataFrame
, then nothing gets plotted except for the court.
fig = nba.bokeh_shot_chart(curry_shots_df, "z", "b")
show(fig)
ERROR:/home/savvas/miniconda/envs/py3/lib/python3.4/site-packages/bokeh/validation/check.py:E-1001 (BAD_COLUMN_NAME): Glyph refers to nonexistent column name: b, z [renderer: GlyphRenderer, ViewModel:GlyphRenderer, ref _id: e6b7b19a-44ec-4dd6-9fab-e7b8b61c3951]
Before creating a tooltip for our shot chart, lets set things up so we can add some color to differentiate between missed and made FGAs.
First we create two dictionaries. One dictionary is our colormap to differentiate between missed and made shots. The other dictionary contains the text indicating a missed or made shot.
# Create our dictionaries
# This dict provides the color indicating missed or made shots
colormap = {0: 'tomato', 1: '#1f77b4'}
# This dict provides the text
shot_outcome = {0: "MISSED", 1: "MADE"}
We then add two new columns to our DataFrame containing the values of our dictionaries. We do this by using the map
method from the SHOT_MADE_FLAG column and use lambda
to create an anonymous function that maps the values for each row (or shot) based on whether the SHOT_MADE_FLAG equals the keys in our dictionaries (0 for a missed shot and 1 for made shot).
# Add our new columns
# For each row (or shot) the new column value will be based
# on whether the SHOT_MADE_FLAG is 0 or 1
# If 0, then the color for that row will be tomato and its
# 'shot_outcome' will be 'Missed'
# If 1, then the color for that row will be #1f77b4 and its
# shot_outcome will be 'Made'
curry_shots_df['color'] = curry_shots_df.SHOT_MADE_FLAG.map(lambda x: colormap[x])
curry_shots_df['shot_outcome'] = curry_shots_df.SHOT_MADE_FLAG.map(lambda x: shot_outcome[x])
To indicate the information we want in our hover tool, we create a list of (field name, value) tuples. The field name is just the text label that will show up on the tooltip. The value is just the column name, prefixed with an '@', that contains the data that will show up on the tooltip. Read more on the hover tool here.
# The values prefixed by '@' are just column names from our DataFrame
# $index is just the index (row) value for each shot attempt,
# starting from 0
tooltips=[("Shot Index", "$index"), ("Shot Type", "@SHOT_TYPE"),
("Shot Outcome", "@shot_outcome"), ("Action Type", "@ACTION_TYPE"),
("Shot Distance", "@SHOT_DISTANCE ft")]
We can now add our hover tool by setting hover_tool
to True
and passing in the above list to tooltips
. To differentiate between missed and made FGAs we set fill_color
to the column name ("color") that contains the color values we mapped into our DataFrame
.
fig = nba.bokeh_shot_chart(curry_shots_df, fill_color="color",
hover_tool=True, tooltips=tooltips)
show(fig)
Here are some NBA stats oriented libraries for Python and R:
Python:
R:
Here are a few resources and blog posts (including a couple of my own) that make use of the NBA stats API: