Visualising NBA shot charts in Tableau

In my last post I produced some NBA shot charts in R using data scraped from and ggplot2. This time I extracted all shot location data available for 490 players and linked it to a Tableau dashboard.

The first dashboard shows each shot attempted during the 2014-15 NBA Regular Season. On the right, it is possible to select team, player, shot type, shot zone and shot range. The table above the chart is also updated in line with the filter selection (click on the image to open the dashboard on a new window).

shot chart Tableau

Another way to visualise this data is to group shots by shot zone. The dashboard below displays shot accuracy for each zone of the court. Note how the “Above the Break 3” area is located in the 2-point region. This is due to the fact that I averaged the X and Y coordinates of all shots taken from “Above the Break 3”. Shots taken from the left and right bring the average location inside the 2-point region.

accuracy shot chart

The half court image is the same I used in my last post. This is how you we add the image to the plot:
Map -> Background Image -> Add Image…

tableau map 1

Select the NBA court jpeg file and click Edit. The X field limits should be -250 to 250 and Y field limits should be -52 to 418.

tableau map 2

In order to scrape and save the shot data using R, I created a function that extract data for a player and ran it in a loop. This will take some time to run depending on the memory you have available (when I first ran this code it took about 40 minutes). Once we have the data in a readable format it is time to link it to Tableau.

## load the file players.csv from my NBA Shot Chart project repo in my GitHub page.
players <- read.csv("players.csv", header = TRUE)

## create function 

AllShotData <- function(playerID) {
# Shot Data
shotURL <- paste("",playerID,"&PlusMinus=N&Position=&Rank=N&RookieYear=&Season=2014-15&SeasonSegment=&SeasonType=Regular+Season&TeamID=0&VsConference=&VsDivision=&mode=Advanced&showDetails=0&showShots=1&showZones=0", sep = "")
 # import from JSON
  shotData <- fromJSON(file = shotURL, method="C")
 # unlist shot data, save into a data frame
  shots <- data.frame(matrix(unlist(shotData$resultSets[[1]][[3]]), ncol=21, byrow = TRUE))
 # shot data headers
  colnames(shots) <- shotData$resultSets[[1]][[2]]
 # covert x and y coordinates into numeric
  shots$LOC_X <- as.numeric(as.character(shots$LOC_X))
  shots$LOC_Y <- as.numeric(as.character(shots$LOC_Y))
  shotsSHOT_DISTANCE <- as.numeric(as.character(shots$SHOT_DISTANCE))
# return table

## Execute shot data function in loop
shots.output <- data.frame()
  for (i in players$player_id) {
    shots.output <- rbind(shots.output, AllShotData(i));
  }, error=function(e){cat("ERROR in series", i,":", conditionMessage(e), "\n") })

# View and save shot data
save(shots.output, file = "Shot Location Data.RData")

Feel free to comment or ask any questions in the comment section below.

10 thoughts on “Visualising NBA shot charts in Tableau

    • Go to the NBA shot chart project repo on my GitHub page and download the file players.csv
      Load the file into R and save it into a data frame object called players.

      players <- read.csv('players.csv', header = TRUE)

      Re run the code and it should work.


      • I’ve downloaded the file from GitHub but I’m unsure how to load in a csv file. Could you help me out?

        Thank you


      • This file is a list of all players and playerIDs with existing shot data and can be used as a reference for player id’s. If you want to run charts for Kevin Durant for example, search for his name on the file and you will find the id 201142.


    • It should work still. NBA may have changed the tables structure since then.
      I am working on player performance data for each game (game logs) and using the following code:


      #### Player game logs URL: one record per player per game played ####
      gameLogsURL <- paste("")

      #### Import game logs data from JSON ####
      # use jsonlite::fromJSON to handle NULL values
      gameLogsData <- jsonlite::fromJSON(gameLogsURL, simplifyDataFrame = TRUE)
      # Save into a data frame and add column names
      gameLogs <- data.frame(gameLogsData$resultSets$rowSet)
      colnames(gameLogs) <- gameLogsData$resultSets$headers[[1]]

      It works fine.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s