Quantcast
Channel: Plotly – Modern Data
Viewing all articles
Browse latest Browse all 48

NBA shots analysis using Plotly shapes

$
0
0

In this post, we will analyse the shots by Stephen Curry, ‘Top Scorer’ of the NBA season 2015-16.

You can create SVG shapes like line, circle, rectangle, and path using Plotly’s shapes feature. With the help of the shapes, we will create the basketball court and plot all his shots on it.

Data Collection

We will collect the necessary data from NBA Stats.

import requests as r

# Chrome's user-agent string, to simulate a browser visiting the webpage
headers = {
  'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36'
}

# Stephen Curry's player id
player_id = 201939

# season details
season = '2015-16'
season_type = 'Regular Season'

# request parameters
req_params = {
 'AheadBehind': '',
 'ClutchTime': '',
 'ContextFilter': '',
 'ContextMeasure': 'FGA',
 'DateFrom': '',
 'DateTo': '',
 'EndPeriod': '',
 'EndRange': '',
 'GameID': '',
 'GameSegment': '',
 'LastNGames': 0,
 'LeagueID': '00',
 'Location': '',
 'Month': 0,
 'OpponentTeamID': 0,
 'Outcome': '',
 'Period': 0,
 'PlayerID': player_id,
 'PointDiff': '',
 'Position': '',
 'RangeType': '',
 'RookieYear': '',
 'Season': season,
 'SeasonSegment': '',
 'SeasonType': season_type,
 'StartPeriod': '',
 'StartRange': '',
 'TeamID': 0,
 'VsConference': '',
 'VsDivision': ''
}

res = r.get('http://stats.nba.com/stats/shotchartdetail', params=req_params, headers=headers)

Data Transformation

After collecting the necessary data, the next step is to transform the data in proper format for querying.

Using pandas we can create a DataFrame object from the JSON response content.

import pandas as pd

res_json = res.json()

# column names
rows = res_json['resultSets'][0]['headers']
# row content
shots_data = res_json['resultSets'][0]['rowSet']

shots_df = pd.DataFrame(shots_data, columns=rows)

We can see that there are two unique values (‘Made Shot’, ‘Missed Shot’) for the column ‘EVENT_TYPE’ in the DataFrame. They represent the shots that made (or missed) it to the basket.

shots_df['EVENT_TYPE'].unique()
>> array([u'Made Shot', u'Missed Shot'], dtype=object)

A row in the DataFrame represents a single shot and the columns ‘LOC_X’ and ‘LOC_Y’ represents the location of that shot.

Shot Locations

Let’s start with creating a scatter chart of all the ‘missed’ shots by Stephen Curry.

import plotly.graph_objs as go
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode()

shot_trace = go.Scatter(
    x = shots_df[shots_df['EVENT_TYPE'] == 'Missed Shot']['LOC_X'],
    y = shots_df[shots_df['EVENT_TYPE'] == 'Missed Shot']['LOC_Y'],
    mode = 'markers'
)

data = [shot_trace]
layout = go.Layout(
    showlegend=False,
    height=600,
    width=600
)

fig = go.Figure(data=data, layout=layout)
iplot(fig)

We can see that most of the shots are near the hoop and three-point arc (line).

Similarly, you can create the chart for all the successful shots using the following Pandas selection syntax.

shots_df[shots_df['EVENT_TYPE'] == 'Made Shot']

It will select all the shot with ‘EVENT_TYPE’ equal to ‘Made Shot’.

Creating the court

The X-axis and Y-axis of our court chart will range from -300 to 300 and -100 to 500 respectively, 10 units on the chart scale is equal to 1 feet.

For reference to the court dimensions, we are using this image linked in the post by Savvas Tjortjoglou.

1. Outer Lines

The boundary of the court looks like a rectangle of the size 50(ft.) X 94(ft.), we are drawing just the half (47 ft.) of it in length.

Here, the points (x0, y0) and (x1, y1) represents the bottom-left and top-right points of the rectangle.

# list containing all the shapes
court_shapes = []

outer_lines_shape = dict(
  type='rect',
  xref='x',
  yref='y',
  x0='-250',
  y0='-47.5',
  x1='250',
  y1='422.5',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(outer_lines_shape)

2. basketball hoop

We will draw it using a circle shape. The center of the circle is at the origin of the graph, with the radius being 7.5 unit.

hoop_shape = dict(
  type='circle',
  xref='x',
  yref='y',
  x0='7.5',
  y0='7.5',
  x1='-7.5',
  y1='-7.5',
  line=dict(
    color='rgba(10, 10, 10, 1)',
    width=1
  )
)

court_shapes.append(hoop_shape)

3. Basket Backboard

The Backboard is a raised vertical board with a basket attached. It’s 72 inches (60 unit) wide.

backboard_shape = dict(
  type='rect',
  xref='x',
  yref='y',
  x0='-30',
  y0='-7.5',
  x1='30',
  y1='-6.5',
  line=dict(
    color='rgba(10, 10, 10, 1)',
    width=1
  ),
  fillcolor='rgba(10, 10, 10, 1)'
)

court_shapes.append(backboard_shape)

4. Outer box of three-second area

It’s a rectangle with 16 ft. in width and 19 ft. in length.

outer_three_sec_shape = dict(
  type='rect',
  xref='x',
  yref='y',
  x0='-80',
  y0='-47.5',
  x1='80',
  y1='143.5',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(outer_three_sec_shape)

5. Inner box of three-second area

It’s a rectangle with 12 ft. in width and 19 ft. in length.

inner_three_sec_shape = dict(
  type='rect',
  xref='x',
  yref='y',
  x0='-60',
  y0='-47.5',
  x1='60',
  y1='143.5',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(inner_three_sec_shape)

6. Three-point line (left)

The left side line of the Three-point line, 14 ft. in length.

The points (x0, y0) and (x1, y1) represents the edges of the line.

left_line_shape = dict(
  type='line',
  xref='x',
  yref='y',
  x0='-220',
  y0='-47.5',
  x1='-220',
  y1='92.5',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(left_line_shape)

7. Three-point line (right)

The right side line of the Three-point line.

right_line_shape = dict(
  type='line',
  xref='x',
  yref='y',
  x0='220',
  y0='-47.5',
  x1='220',
  y1='92.5',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(right_line_shape)

8. Three-point arc

The extreme point of the arc is 23.9 feet away from the origin.

We are using the Curve Command (C) to draw the half circle (arc) path. You can learn more about SVG paths from this tutorial by Mozilla.

three_point_arc_shape = dict(
  type='path',
  xref='x',
  yref='y',
  path='M -220 92.5 C -70 300, 70 300, 220 92.5',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(three_point_arc_shape)

9. Center circle

This circle has a radius of 6 feets.

center_circe_shape = dict(
  type='circle',
  xref='x',
  yref='y',
  x0='60',
  y0='482.5',
  x1='-60',
  y1='362.5',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(center_circle_shape)

10. Restraining circe

This circle has a radius of 2 feets.

res_circle_shape = dict(
  type='circle',
  xref='x',
  yref='y',
  x0='20',
  y0='442.5',
  x1='-20',
  y1='402.5',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(res_circle_shape)

11. Free-throw circle

This circle has a radius of 6 feets.

free_throw_circle_shape = dict(
  type='circle',
  xref='x',
  yref='y',
  x0='60',
  y0='200',
  x1='-60',
  y1='80',
  line=dict(
      color='rgba(10, 10, 10, 1)',
      width=1
  )
)

court_shapes.append(free_throw_circle_shape)

12. Restricted area

We are using the dash property to style the circle, it has a radius of 6 feet.

res_area_shape = dict(
  type='circle',
  xref='x',
  yref='y',
  x0='40',
  y0='40',
  x1='-40',
  y1='-40',
  line=dict(
    color='rgba(10, 10, 10, 1)',
    width=1,
    dash='dot'
  )
)

court_shapes.append(res_area_shape)

That’s the basketball court outline created with the help of Plotly shapes and annotations.

Charting the shots

Now that we have all the shapes for the court, we will plot all the shots on it as a scatter plot.

We have made two traces for the ‘missed’ and ‘made’ type shots, with different colors to help us identify.

missed_shot_trace = go.Scatter(
    x = shots_df[shots_df['EVENT_TYPE'] == 'Missed Shot']['LOC_X'],
    y = shots_df[shots_df['EVENT_TYPE'] == 'Missed Shot']['LOC_Y'],
    mode = 'markers',
    name = 'Missed Shot',
    marker = dict(
        size = 5,
        color = 'rgba(255, 255, 0, .8)',
        line = dict(
            width = 1,
            color = 'rgb(0, 0, 0, 1)'
        )
    )
)

made_shot_trace = go.Scatter(
    x = shots_df[shots_df['EVENT_TYPE'] == 'Made Shot']['LOC_X'],
    y = shots_df[shots_df['EVENT_TYPE'] == 'Made Shot']['LOC_Y'],
    mode = 'markers',
    name = 'Made Shot',
    marker = dict(
        size = 5,
        color = 'rgba(0, 200, 100, .8)',
        line = dict(
            width = 1,
            color = 'rgb(0, 0, 0, 1)'
        )
    )
)

data = [missed_shot_trace, made_shot_trace]

layout = go.Layout(
    title='Shots by Stephen Curry in NBA session 2015-16',
    showlegend=True,
    xaxis=dict(
        showgrid=False,
        range=[-300, 300]
    ),
    yaxis=dict(
        showgrid=False,
        range=[-100, 500]
    ),
    height=600,
    width=650,
    shapes=court_shapes
)

fig = go.Figure(data=data, layout=layout)
iplot(fig)

You can toggle and view the distribution of different shot types by clicking on the legend.


Viewing all articles
Browse latest Browse all 48

Trending Articles