Shotcharts Revisited – From NBA Stats to Feature Service in Less Than 20 Lines of Code


View larger map

A guest post by Gregory Brunner

About two years ago, I wrote about creating shotcharts in ArcGIS using Python and arcpy. In the post, I demonstrated how to scrape the data from stats.nba.com and create a shots feature class from the data. I then shared the resulting shotchart as several web maps in ArcGIS Online. What I was unable to do at the time was automate the creation of the feature service and web maps that I shared in that post. Recent enhancements to the ArcGIS API for Python allow me to automate the process of sharing the shot data as a hosted feature service in ArcGIS Online and design web maps using the shot data. What I really like is that I can do this with less code than I wrote for my original blog post! In my previous post, I had to scrape the shot data, create a feature class, add fields to the feature class, and then add the shot data to the feature class. At that point, I would manually create a hosted feature service from the shot feature class. With recent enhancements to the ArcGIS API for Python, I can do this all in Python and I can to this in less than 20 lines of code! In this post, I will demonstrate how to to scrape data from the NBA stats site, put it into a spatial dataframe, share the spatial dataframe as a hosted feature service, and design web maps that use the hosted feature service. I will try to do this in as little code as possible!

Python Packages

In this post, I will use arcgis, pandas, and requests. After importing these packages, I will log into my ArcGIS Online account in order to save the shot data to a hosted feature service.

import arcgis
from arcgis.gis import GIS
from arcgis import SpatialDataFrame

import pandas as pd
import requests

from IPython.display import display

gis = GIS("https://www.arcgis.com", "gregb")

Getting the Shot Data

In my original post I looked at shots taken by Russell Westbrook from the 2014 – 2015 regular season. Here I will look at Russell Westbrook’s shots taken during the 2016 – 2017 regular season. I originally showed how to do this with urllib. Here, I will use requests similar to how Savvas Tjortjoglou does in How to Create NBA Shot Charts in Python.

player_id= '201566' #Russell Westbrook
season = '2016-17'  #MVP Season of 2016-17
seasontype="Regular+Season" #or use "Playoffs" for shots taken in playoffs

I will form the NBA stats request URL using Russell Westbrook’s NBA.com player ID and use requests to get the data.

nba_call_url = 'http://stats.nba.com/stats/shotchartdetail?AheadBehind=&CFID=&CFPARAMS=&ClutchTime=&Conference=&ContextFilter=&ContextMeasure=FGM&DateFrom=&DateTo=&Division=&EndPeriod=10&EndRange=28800&GameEventID=&GameID=&GameSegment=&GroupID=&GroupQuantity=5&LastNGames=0&LeagueID=00&Location=&Month=0&OpponentTeamID=0&Outcome=&PORound=0&Period=0&PlayerID=%s&PlayerPosition=&PointDiff=&Position=&RangeType=0&RookieYear=&Season=%s&SeasonSegment=&SeasonType=%s&ShotClockRange=&StartPeriod=1&StartRange=0&StarterBench=&TeamID=0&VsConference=&VsDivision=' % (player_id, season, seasontype)
response = requests.get(nba_call_url, headers={'User-Agent': "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.82 Safari/537.36"})

I will push the response JSON into a pandas dataframe.

shots = response.json()['resultSets'][0]['rowSet']
headers = response.json()['resultSets'][0]['headers']
shot_df = pd.DataFrame(shots, columns=headers)

From DataFrame to SpatialDataFrame

In order to publish the shot data to ArcGIS Online as a shotchart, I will convert the pandas dataframe to an arcgis spatial dataframe. In addition to passing the shot_df to SpatialDataFrame, I will pass the geometries associated with each row in the dataframe.

shot_coords = shot_df.iloc[:,17:19].values.tolist()
sdf = SpatialDataFrame(shot_df,geometry=[arcgis.geometry.Geometry({'x':-r[0], 'y':r[1], 
                    'spatialReference':{'wkid':3857}}) for r in shot_coords])

From SpatialDataFrame to Feature Service

Now that the shots are in a spatial dataframe, I will publish the spatial dataframe to ArcGIS Online.

shot_chart = gis.content.import_data(sdf)

Voila! I have the shots as a hosted feature service! It took less than 20 lines of code (including import statements) and to go from the NBA stats response to a hosted feature service only took 7 lines!

display(shot_chart)

From Feature Service to Web Maps

Even though I have the shots as a feature service, I am not done. I can use the ArcGIS API for Python to visualize the shots. I will create three maps: one that shows the shots without any renderer, one the that shows the shots symbolized by whether it was made or missed, and one that shows all the shots taken as a heat map.

For the basketball court, I will use the court feature class that I have in ArcGIS Online.

court_tiles = gis.content.search("Basketball Court", outside_org=False, item_type="Feature")
court_layers = court_tiles[1].layers

The court is broken into two feature services: One for the court outline and one for the court lines. I need to add them separately to the web map.

Shot Locations

Now that I have the basketball court layers and the shots as a feature service, I can display them on my map. I will set the shot chart renderer to “None” so that the default point symbology is applied to the shot_chart layer.

chart1 = gis.map((0.002,0), zoomlevel=17)
display(chart1)
chart1.add_layer(court_layers[1])
chart1.add_layer(court_layers[0])
chart1.add_layer(shot_chart,{"renderer":"None"})
chart1.basemap='dark-gray'


View Web Map

Shots Made and Missed

I want to be able to differentiate between shots made and shots missed. I can do that by changing the renderer when I add the shot_chart layer to the map. I will use the “ClassedColorRenderer” and apply the class colors based on the values in the “SHOT_MADE_FLAG” field.

chart2 = gis.map((0.002,0), zoomlevel=17)
display(chart2)
chart2.add_layer(court_layers[1])
chart2.add_layer(court_layers[0])
chart2.add_layer(shot_chart, {"renderer":"ClassedColorRenderer",
               "field_name": "SHOT_MADE_FLAG"})
chart2.basemap='dark-gray'


View Web Map

Shots missed are in dark gray. Shots made are in white. I assume that this is the default “ClassedColorRenderer”. I need to do some more investigating to figure out how to apply my desired color map.

Shots Taken as a Heat Map

I also want to see the shots taken as a heat map. I will use the “HeatmapRenderer” to do that.

chart3 = gis.map((0.002,0), zoomlevel=17)
display(chart3)
chart3.add_layer(court_layers[1])
chart3.add_layer(court_layers[0])
chart3.add_layer(shot_chart, {"renderer":"HeatmapRenderer",
               "opacity": 0.75})
chart3.basemap='dark-gray'


View Web Map

Again, I assume the orange-to-red color ramp is the default heat map renderer. I need to learn a little bit more about the API if I want to change the heat map color ramp.

Conclusions

I thought it would be fun to revisit my post on mapping shotcharts with ArcGIS and Python and see how the code changes with the introduction of the ArcGIS API for Python. Using the trick to convert the pandas dataframe to ArcGIS spatial dataframe makes it very easy to go from NBA stats as JSON to a hosted feature service. It also reduces the amount of code I need to write. I no longer need to create a feature class with arcpy, add fields to the feature class, then add the data to the feature class. I can now go from the stats as JSON to hosted feature service in only a few lines of code! I am still learning some of the ins-and-outs of the API (like applying renderers to the MapView), but if you have any questions, don’t hesitate to ask!