{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "**Table of Contents**\n", "\n", "
\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Super fun demo from moderndata!\n", "http://moderndata.plot.ly/nba-shots-analysis-using-plotly-shapes/" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import requests as r\n", " \n", "# Chrome's user-agent string, to simulate a browser visiting the webpage\n", "headers = {\n", " 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36'\n", "}\n", " \n", "# Stephen Curry's player id\n", "player_id = 201939\n", " \n", "# season details\n", "season = '2015-16'\n", "season_type = 'Regular Season'\n", " \n", "# request parameters\n", "req_params = {\n", " 'AheadBehind': '',\n", " 'ClutchTime': '',\n", " 'ContextFilter': '',\n", " 'ContextMeasure': 'FGA',\n", " 'DateFrom': '',\n", " 'DateTo': '',\n", " 'EndPeriod': '',\n", " 'EndRange': '',\n", " 'GameID': '',\n", " 'GameSegment': '',\n", " 'LastNGames': 0,\n", " 'LeagueID': '00',\n", " 'Location': '',\n", " 'Month': 0,\n", " 'OpponentTeamID': 0,\n", " 'Outcome': '',\n", " 'Period': 0,\n", " 'PlayerID': player_id,\n", " 'PointDiff': '',\n", " 'Position': '',\n", " 'RangeType': '',\n", " 'RookieYear': '',\n", " 'Season': season,\n", " 'SeasonSegment': '',\n", " 'SeasonType': season_type,\n", " 'StartPeriod': '',\n", " 'StartRange': '',\n", " 'TeamID': 0,\n", " 'VsConference': '',\n", " 'VsDivision': ''\n", "}\n", " \n", "res = r.get('http://stats.nba.com/stats/shotchartdetail', params=req_params, headers=headers)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Data Transformation for querying" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import pandas as pd\n", " \n", "res_json = res.json()\n", " \n", "# column names\n", "rows = res_json['resultSets'][0]['headers']\n", "# row content\n", "shots_data = res_json['resultSets'][0]['rowSet']\n", " \n", "shots_df = pd.DataFrame(shots_data, columns=rows)" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import numpy as np" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "array([u'Made Shot', u'Missed Shot'], dtype=object)" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "shots_df['EVENT_TYPE'].unique()\n", "\n", "np.array([u'Made Shot', u'Missed Shot'], dtype=object)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Shot locations" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# scatter chart of the *missed* shots by curry\n", "import plotly.graph_objs as go\n", "from plotly.offline import init_notebook_mode, iplot\n", "init_notebook_mode()\n", " \n", "shot_trace = go.Scatter(\n", " x = shots_df[shots_df['EVENT_TYPE'] == 'Missed Shot']['LOC_X'],\n", " y = shots_df[shots_df['EVENT_TYPE'] == 'Missed Shot']['LOC_Y'],\n", " mode = 'markers'\n", ")\n", " \n", "data = [shot_trace]\n", "layout = go.Layout(\n", " showlegend=False,\n", " height=600,\n", " width=600\n", ")\n", " \n", "fig = go.Figure(data=data, layout=layout)\n", "iplot(fig)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# repeat for made shots\n", "\n", "shot_trace = go.Scatter(\n", " x = shots_df[shots_df['EVENT_TYPE'] == 'Made Shot']['LOC_X'],\n", " y = shots_df[shots_df['EVENT_TYPE'] == 'Made Shot']['LOC_Y'],\n", " mode = 'markers'\n", ")\n", " \n", "data = [shot_trace]\n", "layout = go.Layout(\n", " showlegend=False,\n", " height=600,\n", " width=600\n", ")\n", " \n", "fig = go.Figure(data=data, layout=layout)\n", "iplot(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Create the court\n", "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.\n", "\n", "For reference to the court dimensions, we are using [this image](http://www.sportscourtdimensions.com/wp-content/uploads/2015/02/nba_court_dimensions_h.png) linked in the [post](http://savvastjortjoglou.com/nba-shot-sharts.html) by Savvas Tjortjoglou." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Outer Lines\n", "\n", "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.\n", "\n", "Here, the points (x0, y0) and (x1, y1) represents the bottom-left and top-right points of the rectangle." ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# list containing all the shapes\n", "court_shapes = []\n", " \n", "outer_lines_shape = dict(\n", " type='rect',\n", " xref='x',\n", " yref='y',\n", " x0='-250',\n", " y0='-47.5',\n", " x1='250',\n", " y1='422.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(outer_lines_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. basketball hoop\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# create the hoop\n", "hoop_shape = dict(\n", " type='circle',\n", " xref='x',\n", " yref='y',\n", " x0='7.5',\n", " y0='7.5',\n", " x1='-7.5',\n", " y1='-7.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(hoop_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basket Backboard\n", "\n", "The Backboard is a raised vertical board with a basket attached. It’s 72 inches (60 unit) wide." ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# backboard\n", "backboard_shape = dict(\n", " type='rect',\n", " xref='x',\n", " yref='y',\n", " x0='-30',\n", " y0='-7.5',\n", " x1='30',\n", " y1='-6.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " ),\n", " fillcolor='rgba(10, 10, 10, 1)'\n", ")\n", " \n", "court_shapes.append(backboard_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Outer box of three-second area\n", "\n", "It’s a rectangle with 16 ft. in width and 19 ft. in length." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "outer_three_sec_shape = dict(\n", " type='rect',\n", " xref='x',\n", " yref='y',\n", " x0='-80',\n", " y0='-47.5',\n", " x1='80',\n", " y1='143.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(outer_three_sec_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Inner box of three-second area\n", "\n", "It’s a rectangle with 12 ft. in width and 19 ft. in length." ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": true }, "outputs": [], "source": [ "inner_three_sec_shape = dict(\n", " type='rect',\n", " xref='x',\n", " yref='y',\n", " x0='-60',\n", " y0='-47.5',\n", " x1='60',\n", " y1='143.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(inner_three_sec_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Three-point line (left)\n", "\n", "The left side line of the Three-point line, 14 ft. in length.\n", "\n", "The points (x0, y0) and (x1, y1) represents the edges of the line." ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": true }, "outputs": [], "source": [ "left_line_shape = dict(\n", " type='line',\n", " xref='x',\n", " yref='y',\n", " x0='-220',\n", " y0='-47.5',\n", " x1='-220',\n", " y1='92.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(left_line_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Three-point line (right)\n", "\n", "The right side line of the Three-point line." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": true }, "outputs": [], "source": [ "right_line_shape = dict(\n", " type='line',\n", " xref='x',\n", " yref='y',\n", " x0='220',\n", " y0='-47.5',\n", " x1='220',\n", " y1='92.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(right_line_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Three-point arc\n", "\n", "The extreme point of the arc is 23.9 feet away from the origin.\n", "\n", "We are using the Curve Command (C) to draw the half circle (arc) path. You can learn more about SVG paths from this [tutorial](https://developer.mozilla.org/en/docs/Web/SVG/Tutorial/Paths) by Mozilla." ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": true }, "outputs": [], "source": [ "three_point_arc_shape = dict(\n", " type='path',\n", " xref='x',\n", " yref='y',\n", " path='M -220 92.5 C -70 300, 70 300, 220 92.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(three_point_arc_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Center circle\n", "\n", "This circle has a radius of 6 feets." ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "ename": "NameError", "evalue": "name 'center_circle_shape' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32m\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 13\u001b[0m )\n\u001b[0;32m 14\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 15\u001b[1;33m \u001b[0mcourt_shapes\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mappend\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcenter_circle_shape\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[1;31mNameError\u001b[0m: name 'center_circle_shape' is not defined" ] } ], "source": [ "center_circe_shape = dict(\n", " type='circle',\n", " xref='x',\n", " yref='y',\n", " x0='60',\n", " y0='482.5',\n", " x1='-60',\n", " y1='362.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(center_circle_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Restraining circe\n", "\n", "This circle has a radius of 2 feets." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": true }, "outputs": [], "source": [ "res_circle_shape = dict(\n", " type='circle',\n", " xref='x',\n", " yref='y',\n", " x0='20',\n", " y0='442.5',\n", " x1='-20',\n", " y1='402.5',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(res_circle_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Free-throw circle\n", "\n", "This circle has a radius of 6 feets." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": true }, "outputs": [], "source": [ "free_throw_circle_shape = dict(\n", " type='circle',\n", " xref='x',\n", " yref='y',\n", " x0='60',\n", " y0='200',\n", " x1='-60',\n", " y1='80',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1\n", " )\n", ")\n", " \n", "court_shapes.append(free_throw_circle_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Restricted area\n", "\n", "We are using the dash property to style the circle, it has a radius of 6 feet." ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": true }, "outputs": [], "source": [ "res_area_shape = dict(\n", " type='circle',\n", " xref='x',\n", " yref='y',\n", " x0='40',\n", " y0='40',\n", " x1='-40',\n", " y1='-40',\n", " line=dict(\n", " color='rgba(10, 10, 10, 1)',\n", " width=1,\n", " dash='dot'\n", " )\n", ")\n", " \n", "court_shapes.append(res_area_shape)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Chart the shots" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/html": [ "
" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "missed_shot_trace = go.Scatter(\n", " x = shots_df[shots_df['EVENT_TYPE'] == 'Missed Shot']['LOC_X'],\n", " y = shots_df[shots_df['EVENT_TYPE'] == 'Missed Shot']['LOC_Y'],\n", " mode = 'markers',\n", " name = 'Missed Shot',\n", " marker = dict(\n", " size = 5,\n", " color = 'rgba(255, 255, 0, .8)',\n", " line = dict(\n", " width = 1,\n", " color = 'rgb(0, 0, 0, 1)'\n", " )\n", " )\n", ")\n", " \n", "made_shot_trace = go.Scatter(\n", " x = shots_df[shots_df['EVENT_TYPE'] == 'Made Shot']['LOC_X'],\n", " y = shots_df[shots_df['EVENT_TYPE'] == 'Made Shot']['LOC_Y'],\n", " mode = 'markers',\n", " name = 'Made Shot',\n", " marker = dict(\n", " size = 5,\n", " color = 'rgba(0, 200, 100, .8)',\n", " line = dict(\n", " width = 1,\n", " color = 'rgb(0, 0, 0, 1)'\n", " )\n", " )\n", ")\n", " \n", "data = [missed_shot_trace, made_shot_trace]\n", " \n", "layout = go.Layout(\n", " title='Shots by Stephen Curry in NBA session 2015-16',\n", " showlegend=True,\n", " xaxis=dict(\n", " showgrid=False,\n", " range=[-300, 300]\n", " ),\n", " yaxis=dict(\n", " showgrid=False,\n", " range=[-100, 500]\n", " ),\n", " height=1200,\n", " width=1300,\n", " shapes=court_shapes\n", ")\n", " \n", "fig = go.Figure(data=data, layout=layout)\n", "iplot(fig)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Good stuff! let's save in plotly" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "u'https://plot.ly/~takanori/175'" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import plotly.plotly as py\n", "py.plot(fig, filename='stephen-curry')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python [Root]", "language": "python", "name": "Python [Root]" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.12" } }, "nbformat": 4, "nbformat_minor": 0 }