{"cells":[{"metadata":{},"cell_type":"markdown","source":"# March Madness\n\nThis analysis originally began as an in depth look at expected wins in the men’s and women’s NCAA basketball competition and made use of the data made available in the 2020 March Madness competition being hosted on Kaggle in collaboration with Google Cloud, and can be found here. For an in depth analysis of college basketball data, this analysis might be of interest.\n\nIt has since morphed (from the 7th March, 2020) to incorporate additional professional sports, however this analysis will be built iteratively, meaning additional sports will be added over time. I hope you keep coming back to check in which additional sports have been added to the analysis.\n\nThis tournament is a knockout tournament where the loser is immediately eliminated from the tournament. Since it is mostly played in march, hence it has been accorded the title of March Madness. The first edition took place in 1939 and has been regularly held since then. the Women’s Championship was inaugurated in the 1981–82 season.\n\n![](https://upload.wikimedia.org/wikipedia/en/thumb/2/28/March_Madness_logo.svg/440px-March_Madness_logo.svg.png)"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pylab as plt\nimport matplotlib as mpl\nfrom matplotlib.patches import Circle, Rectangle, Arc\nimport seaborn as sns\nplt.style.use('seaborn-dark-palette')\nmypal = plt.rcParams['axes.prop_cycle'].by_key()['color'] # Grab the color pal\nimport os\nimport gc\n\nMENS_DIR = '../input/google-cloud-ncaa-march-madness-2020-division-1-mens-tournament'\nWOMENS_DIR = '../input/google-cloud-ncaa-march-madness-2020-division-1-womens-tournament'","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Loss Metric & Sample Submission\nLog Loss is the metric we will be evaluated on for the tournament prediction challenge. This metric provides a stronger punishment that are overly confident and wrong."},{"metadata":{"trusted":true},"cell_type":"code","source":"def logloss(true_label, predicted, eps=1e-15):\n    p = np.clip(predicted, eps, 1 - eps)\n    if true_label == 1:\n        return -np.log(p)\n    return -np.log(1 - p)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(f'Confident Wrong Prediction: \\t\\t {logloss(1, 0.01):0.4f}')\nprint(f'Confident Correct Prediction: \\t\\t {logloss(0, 0.01):0.4f}')\nprint(f'Non-Confident Wrong Prediction: \\t {logloss(1, 0.49):0.4f}')\nprint(f'Non-Confident Correct Prediction: \\t {logloss(0, 0.49):0.4f}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Your submission will have a prediction for every possible combination of tournament teams. Stage 1 (not final) will be graded your score will be based on 2015-2019. It's possible to cheat and get a perfect score.. but don't do that. In Stage 2 you will be graded on the outcomes of the yet to be played 2020 tournament.\n- `ID` is in the format SSSS_XXXX_YYYY, where SSSS is the four digit season number, XXXX is the four-digit TeamID of the lower-ID team, and YYYY is the four-digit TeamID of the higher-ID team. Read more here: https://www.kaggle.com/c/march-madness-analytics-2020/data"},{"metadata":{"trusted":true},"cell_type":"code","source":"Mss = pd.read_csv(f'{MENS_DIR}/MSampleSubmissionStage1_2020.csv')\nWss = pd.read_csv(f'{WOMENS_DIR}/WSampleSubmissionStage1_2020.csv')\nMss.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Team Data\n**MTeams & WTeams**\n\nTeam name and Team ID, first and last D1 Season. Sorting by the `FirstD1Season` column we can see some of the newest teams in D1 basketball. Welcome to D1 Merrimack! Cool mascot."},{"metadata":{},"cell_type":"markdown","source":"# Format\n\nThe male edition tournament comprises of **68** teams that compete in **7** rounds for the National Championship Title. However, the number of Teams in the Women’s edition is **64**.\n\n![](https://cdn-images-1.medium.com/max/800/1*TaaEJ3zTwhuU67QPqqrkaA.png)\n\n---\n\n# Selection\n\nThe selection procedure takes place by two methods:\n\n![](https://cdn-images-1.medium.com/max/800/1*s7gpAnvzL-mQ0lKlzc8xXQ.png)\n\n## 1. Automatic\n\n32 Teams get selected in this way.\n\n-   Men’s Division 1 Team comprises of **353** Teams.\n\n![](https://cdn-images-1.medium.com/max/800/1*DBT72cUKGLIvXmjO7mBgyQ.png)\n\n-   Each one of those teams belongs to **32** [conferences](https://en.wikipedia.org/wiki/List_of_NCAA_conferences).\n\n![](https://cdn-images-1.medium.com/max/800/1*rq4HBtMnQeGsiI7hOmBfIA.png)\n\n-   Each of those conferences conducts a tournament and if a time wins the tournament, they get selected for the NCAA.\n\n  \n\n## 2. At Large\n\nThe second selection process is called ‘At Large’ where The NCAA selection committee convenes at the final days of the regular season and decides which 36 teams which are not the Automatic qualifiers can be sent to the playoffs. This selection is based on multiple stats and rankings.\n\n---\n\n## Selection Sunday\n\nThese “at-large” teams are announced in a nationally televised event on the Sunday preceding the [“First Four” play-in games](https://en.wikipedia.org/wiki/NCAA_Men%27s_Division_I_Basketball_Opening_Round_game \"NCAA Men's Division I Basketball Opening Round game\"). This Sunday is called ‘Selection Sunday and is on March 15.\n\n## Seeding\n\nAfter all the 68(64 in case of Women), have been decided, the selection committee ranks them in a process called seeding where each team gets a ranking from 1 to 68. Then **First Four** play-in games are contested between teams holding the four lowest-seeded automatic bids and the four lowest-seeded at-large bids.\n\nThe Teams are then split into 4 regions of 16 Teams each. Each team is now ranked from 1 to 16 in each region. After the [First Four](https://en.wikipedia.org/wiki/First_Four \"First Four\"), the tournament occurs during the course of three weekends, at pre-selected neutral sites across the United States. Here, the first round matches are determined by pitting the top team in the region with the lowest-seeded team in that region and so on. This ranking is the team’s seed.\n\n# March Madness Begins\n![](https://www.ncaa.com/sites/default/files/public/styles/original/public-s3/images/2020/02/12/kelly-campbell-depaul-2020-ncaa.jpg?itok=suRzKxqw)\n\n## First Round\n\nThe First round consisting of 64 teams playing in 32 games over the course of a week. From here 32 teams emerge as winners and go on to the second round.\n\n## Sweet Sixteen\n\nNext, the sweet sixteen round takes place, which sees the elimination of 16 teams. Rest of the 16 teams move forward.\n\n## Elite Eight\n\nThe next fight is for the Elite Eight as only 8 teams remain in the competition.\n\n## Final Four\n\nThe penultimate round of the tournament where the 4 teams contest to reserve a place in the finals. Four teams, one from each region (East, South, Midwest, and West), compete in a preselected location for the national championship."},{"metadata":{"trusted":true},"cell_type":"code","source":"MTeams = pd.read_csv(f'{MENS_DIR}/MDataFiles_Stage1/MTeams.csv')\nMTeams.sort_values('FirstD1Season', ascending=False).head(5)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Womens' data does not contain years joined :(\nWTeams = pd.read_csv(f'{WOMENS_DIR}/WDataFiles_Stage1/WTeams.csv')\nWTeams.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Seasons Data\n## MSeasons.csv & WSeasons.csv\nThese files identify the different seasons included in the historical data, along with certain season-level properties.\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"MSeasons = pd.read_csv(f'{MENS_DIR}/MDataFiles_Stage1/MSeasons.csv')\nWSeasons = pd.read_csv(f'{WOMENS_DIR}/WDataFiles_Stage1/WSeasons.csv')\nMSeasons.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Tourney Seed Data\n**MNCAATourneySeeds.csv & WNCAATourneySeeds.csv**\nThis file identifies the seeds for all teams in each NCAA® tournament, for all seasons of historical data."},{"metadata":{"trusted":true},"cell_type":"code","source":"MNCAATourneySeeds = pd.read_csv(f'{MENS_DIR}/MDataFiles_Stage1/MNCAATourneySeeds.csv')\nWNCAATourneySeeds = pd.read_csv(f'{WOMENS_DIR}/WDataFiles_Stage1/WNCAATourneySeeds.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# As Lets join this with the teams data to see some of the past matchups\nWNCAATourneySeeds.merge(WTeams, validate='many_to_one').head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Regular Season Results\n**MRegularSeasonCompactResults.csv & WRegularSeasonCompactResults.csv**\n\nThese files identify the game-by-game NCAA® tournament results for all seasons of historical data."},{"metadata":{"trusted":true},"cell_type":"code","source":"MRegularSeasonCompactResults = pd.read_csv(f'{MENS_DIR}/MDataFiles_Stage1/MRegularSeasonCompactResults.csv')\nWRegularSeasonCompactResults = pd.read_csv(f'{WOMENS_DIR}/WDataFiles_Stage1/WRegularSeasonCompactResults.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# We have the team the won, lost and the score.\nMRegularSeasonCompactResults.head(5)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"We can join our regular season results on the team names to more clearly identify the games."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Lets Add the winning and losing team names to the results\nMRegularSeasonCompactResults = \\\n    MRegularSeasonCompactResults \\\n    .merge(MTeams[['TeamName', 'TeamID']],\n           left_on='WTeamID',\n           right_on='TeamID',\n           validate='many_to_one') \\\n    .drop('TeamID', axis=1) \\\n    .rename(columns={'TeamName': 'WTeamName'}) \\\n    .merge(MTeams[['TeamName', 'TeamID']],\n           left_on='LTeamID',\n           right_on='TeamID') \\\n    .drop('TeamID', axis=1) \\\n    .rename(columns={'TeamName': 'LTeamName'})\n\nWRegularSeasonCompactResults = \\\n    WRegularSeasonCompactResults \\\n    .merge(WTeams[['TeamName', 'TeamID']],\n           left_on='WTeamID',\n           right_on='TeamID',\n           validate='many_to_one') \\\n    .drop('TeamID', axis=1) \\\n    .rename(columns={'TeamName': 'WTeamName'}) \\\n    .merge(WTeams[['TeamName', 'TeamID']],\n           left_on='LTeamID',\n           right_on='TeamID') \\\n    .drop('TeamID', axis=1) \\\n    .rename(columns={'TeamName': 'LTeamName'})","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"WRegularSeasonCompactResults.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"WRegularSeasonCompactResults['Score_Diff'] = WRegularSeasonCompactResults['WScore'] - WRegularSeasonCompactResults['LScore']\nMRegularSeasonCompactResults['Score_Diff'] = MRegularSeasonCompactResults['WScore'] - MRegularSeasonCompactResults['LScore']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.style.use('fivethirtyeight')\nMRegularSeasonCompactResults['Score_Diff'] \\\n    .plot(kind='hist',\n          bins=90,\n          figsize=(15, 5),\n          label='Mens',\n          alpha=0.5)\nWRegularSeasonCompactResults['Score_Diff'] \\\n    .plot(kind='hist',\n          bins=105,\n          figsize=(15, 5),\n          label='Womens',\n          alpha=0.5)\nplt.title('Score Differential')\nplt.xlim(0,60)\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.style.use('fivethirtyeight')\nfig, axs = plt.subplots(1, 2, figsize=(15, 5))\nWRegularSeasonCompactResults['counter'] = 1\nWRegularSeasonCompactResults.groupby('WTeamName')['counter'] \\\n    .count() \\\n    .sort_values() \\\n    .tail(20) \\\n    .plot(kind='barh',\n          title='⬆️ Most Regular Season Wins (Womens)',\n          figsize=(15, 8),\n          xlim=(400, 680),\n          color=mypal[0],\n          ax=axs[0])\nWRegularSeasonCompactResults.groupby('WTeamName')['counter'] \\\n    .count() \\\n    .sort_values(ascending=False) \\\n    .tail(20) \\\n    .plot(kind='barh',\n          title='⬇️ Least Regular Season Wins (Womens)',\n          figsize=(15, 8),\n          xlim=(0, 150),\n          color=mypal[1],\n          ax=axs[1])\nplt.tight_layout()\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"plt.style.use('fivethirtyeight')\nfig, axs = plt.subplots(1, 2, figsize=(15, 5))\nMRegularSeasonCompactResults['counter'] = 1\nMRegularSeasonCompactResults.groupby('WTeamName')['counter'] \\\n    .count() \\\n    .sort_values() \\\n    .tail(20) \\\n    .plot(kind='barh',\n          title='⬆️ Most Regular Season Wins (Mens)',\n          figsize=(15, 8),\n          xlim=(600, 920),\n          color=mypal[2],\n         ax=axs[0])\nMRegularSeasonCompactResults.groupby('WTeamName')['counter'] \\\n    .count() \\\n    .sort_values(ascending=False) \\\n    .tail(20) \\\n    .plot(kind='barh',\n          title='⬇️ Least Regular Season Wins (Mens)',\n          figsize=(15, 8),\n          xlim=(0, 150),\n          color=mypal[3],\n          ax=axs[1])\naxs[1].set_ylabel('')\nplt.tight_layout()\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Event Data\n\nEach MEvents & WEvents file lists the play-by-play event logs for more than 99.5% of games from that season.\nEach event is assigned to either a team or a single one of the team's players.\nThus if a basket is made by one player and an assist is credited to a second player,\nthat would show up as two separate records. The players are listed by PlayerID within the xPlayers.csv file.\n\nMens Event Files:\n- MEvents2015.csv, MEvents2016.csv, MEvent2017.csv, MEvents2018.csv, MEvents2019.csv\n\nWomens Event Files:\n- WEvents2015.csv, WEvents2016.csv, WEvents2017.csv, WEvents2018.csv, WEvents2019.csv\n\nWe can read in all files and combine into one huge dataframe, one for womens and one for mens."},{"metadata":{"trusted":true},"cell_type":"code","source":"mens_events = []\nfor year in [2015, 2016, 2017, 2018, 2019]:\n    mens_events.append(pd.read_csv(f'{MENS_DIR}/MEvents{year}.csv'))\nMEvents = pd.concat(mens_events)\nprint(MEvents.shape)\nMEvents.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"womens_events = []\nfor year in [2015, 2016, 2017, 2018, 2019]:\n    womens_events.append(pd.read_csv(f'{WOMENS_DIR}/WEvents{year}.csv'))\nWEvents = pd.concat(womens_events)\nprint(WEvents.shape)\nWEvents.head()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"del mens_events\ndel womens_events\ngc.collect()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Common Event Types"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Event Types\nplt.style.use('fivethirtyeight')\nMEvents['counter'] = 1\nMEvents.groupby('EventType')['counter'] \\\n    .sum() \\\n    .sort_values(ascending=False) \\\n    .plot(kind='bar',\n          figsize=(15, 5),\n         color=mypal[2],\n         title='Event Type Frequency (Mens)')\nplt.xticks(rotation=0)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Event Types\nplt.style.use('fivethirtyeight')\nWEvents['counter'] = 1\nWEvents.groupby('EventType')['counter'] \\\n    .sum() \\\n    .sort_values(ascending=False) \\\n    .plot(kind='bar',\n          figsize=(15, 5),\n         color=mypal[3],\n         title='Event Type Frequency (Womens)')\nplt.xticks(rotation=0)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Area of Event\nWe are told that the `Area` feature describes the 13 \"areas\" of the court, as follows: 1=under basket; 2=in the paint; 3=inside right wing; 4=inside right; 5=inside center; 6=inside left; 7=inside left wing; 8=outside right wing; 9=outside right; 10=outside center; 11=outside left; 12=outside left wing; 13=backcourt.\n\nWe can map these values to their names."},{"metadata":{"trusted":true},"cell_type":"code","source":"area_mapping = {0: np.nan,\n                1: 'under basket',\n                2: 'in the paint',\n                3: 'inside right wing',\n                4: 'inside right',\n                5: 'inside center',\n                6: 'inside left',\n                7: 'inside left wing',\n                8: 'outside right wing',\n                9: 'outside right',\n                10: 'outside center',\n                11: 'outside left',\n                12: 'outside left wing',\n                13: 'backcourt'}\n\nMEvents['Area_Name'] = MEvents['Area'].map(area_mapping)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"MEvents.groupby('Area_Name')['counter'].sum() \\\n    .sort_values() \\\n    .plot(kind='barh',\n          figsize=(15, 8),\n          title='Frequency of Event Area')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(15, 8))\nfor i, d in MEvents.loc[~MEvents['Area_Name'].isna()].groupby('Area_Name'):\n    d.plot(x='X', y='Y', style='.', label=i, ax=ax, title='Visualizing Event Areas')\n    ax.legend()\nplt.legend(bbox_to_anchor=(1.04,1), loc=\"upper left\")\nax.set_xticks([])\nax.set_yticks([])\nax.set_xlabel('')\nax.set_xlim(0, 100)\nax.set_ylim(0, 100)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Plotting X, Y Data\nThis is some of the most exciting data provided, but after looking there are some things to consider.\n- X, Y points are not available for all games- so this is not a complete sample\n- The X/Y position is provided for fouls, turnovers, and field-goal attempts (either 2-point or 3-point). No X/Y data for other events."},{"metadata":{"trusted":true},"cell_type":"code","source":"# Normalize X, Y positions for court dimentions\n# Court is 50 feet wide and 94 feet end to end.\nMEvents['X_'] = (MEvents['X'] * (94/100))\nMEvents['Y_'] = (MEvents['Y'] * (50/100))\n\nWEvents['X_'] = (WEvents['X'] * (94/100))\nWEvents['Y_'] = (WEvents['Y'] * (50/100))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# NCAA Court Plot"},{"metadata":{"trusted":true},"cell_type":"code","source":"def create_ncaa_full_court(ax=None, three_line='mens', court_color='#dfbb85',\n                           lw=3, lines_color='black', lines_alpha=0.5,\n                           paint_fill='blue', paint_alpha=0.4,\n                           inner_arc=False):\n    \"\"\"\n    Version 2020.2.19\n    Creates NCAA Basketball Court\n    Dimensions are in feet (Court is 97x50 ft)\n    Created by: Rob Mulla / https://github.com/RobMulla\n\n    * Note that this function uses \"feet\" as the unit of measure.\n    * NCAA Data is provided on a x range: 0, 100 and y-range 0 to 100\n    * To plot X/Y positions first convert to feet like this:\n    ```\n    Events['X_'] = (Events['X'] * (94/100))\n    Events['Y_'] = (Events['Y'] * (50/100))\n    ```\n    \n    ax: matplotlib axes if None gets current axes using `plt.gca`\n\n\n    three_line: 'mens', 'womens' or 'both' defines 3 point line plotted\n    court_color : (hex) Color of the court\n    lw : line width\n    lines_color : Color of the lines\n    lines_alpha : transparency of lines\n    paint_fill : Color inside the paint\n    paint_alpha : transparency of the \"paint\"\n    inner_arc : paint the dotted inner arc\n    \"\"\"\n    if ax is None:\n        ax = plt.gca()\n\n    # Create Pathes for Court Lines\n    center_circle = Circle((94/2, 50/2), 6,\n                           linewidth=lw, color=lines_color, lw=lw,\n                           fill=False, alpha=lines_alpha)\n    hoop_left = Circle((5.25, 50/2), 1.5 / 2,\n                       linewidth=lw, color=lines_color, lw=lw,\n                       fill=False, alpha=lines_alpha)\n    hoop_right = Circle((94-5.25, 50/2), 1.5 / 2,\n                        linewidth=lw, color=lines_color, lw=lw,\n                        fill=False, alpha=lines_alpha)\n\n    # Paint - 18 Feet 10 inches which converts to 18.833333 feet - gross!\n    left_paint = Rectangle((0, (50/2)-6), 18.833333, 12,\n                           fill=paint_fill, alpha=paint_alpha,\n                           lw=lw, edgecolor=None)\n    right_paint = Rectangle((94-18.83333, (50/2)-6), 18.833333,\n                            12, fill=paint_fill, alpha=paint_alpha,\n                            lw=lw, edgecolor=None)\n    \n    left_paint_boarder = Rectangle((0, (50/2)-6), 18.833333, 12,\n                           fill=False, alpha=lines_alpha,\n                           lw=lw, edgecolor=lines_color)\n    right_paint_boarder = Rectangle((94-18.83333, (50/2)-6), 18.833333,\n                            12, fill=False, alpha=lines_alpha,\n                            lw=lw, edgecolor=lines_color)\n\n    left_arc = Arc((18.833333, 50/2), 12, 12, theta1=-\n                   90, theta2=90, color=lines_color, lw=lw,\n                   alpha=lines_alpha)\n    right_arc = Arc((94-18.833333, 50/2), 12, 12, theta1=90,\n                    theta2=-90, color=lines_color, lw=lw,\n                    alpha=lines_alpha)\n    \n    leftblock1 = Rectangle((7, (50/2)-6-0.666), 1, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    leftblock2 = Rectangle((7, (50/2)+6), 1, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    ax.add_patch(leftblock1)\n    ax.add_patch(leftblock2)\n    \n    left_l1 = Rectangle((11, (50/2)-6-0.666), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    left_l2 = Rectangle((14, (50/2)-6-0.666), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    left_l3 = Rectangle((17, (50/2)-6-0.666), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    ax.add_patch(left_l1)\n    ax.add_patch(left_l2)\n    ax.add_patch(left_l3)\n    left_l4 = Rectangle((11, (50/2)+6), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    left_l5 = Rectangle((14, (50/2)+6), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    left_l6 = Rectangle((17, (50/2)+6), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    ax.add_patch(left_l4)\n    ax.add_patch(left_l5)\n    ax.add_patch(left_l6)\n    \n    rightblock1 = Rectangle((94-7-1, (50/2)-6-0.666), 1, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    rightblock2 = Rectangle((94-7-1, (50/2)+6), 1, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    ax.add_patch(rightblock1)\n    ax.add_patch(rightblock2)\n\n    right_l1 = Rectangle((94-11, (50/2)-6-0.666), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    right_l2 = Rectangle((94-14, (50/2)-6-0.666), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    right_l3 = Rectangle((94-17, (50/2)-6-0.666), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    ax.add_patch(right_l1)\n    ax.add_patch(right_l2)\n    ax.add_patch(right_l3)\n    right_l4 = Rectangle((94-11, (50/2)+6), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    right_l5 = Rectangle((94-14, (50/2)+6), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    right_l6 = Rectangle((94-17, (50/2)+6), 0.166, 0.666,\n                           fill=True, alpha=lines_alpha,\n                           lw=0, edgecolor=lines_color,\n                           facecolor=lines_color)\n    ax.add_patch(right_l4)\n    ax.add_patch(right_l5)\n    ax.add_patch(right_l6)\n    \n    # 3 Point Line\n    if (three_line == 'mens') | (three_line == 'both'):\n        # 22' 1.75\" distance to center of hoop\n        three_pt_left = Arc((6.25, 50/2), 44.291, 44.291, theta1=-78,\n                            theta2=78, color=lines_color, lw=lw,\n                            alpha=lines_alpha)\n        three_pt_right = Arc((94-6.25, 50/2), 44.291, 44.291,\n                             theta1=180-78, theta2=180+78,\n                             color=lines_color, lw=lw, alpha=lines_alpha)\n\n        # 4.25 feet max to sideline for mens\n        ax.plot((0, 11.25), (3.34, 3.34),\n                color=lines_color, lw=lw, alpha=lines_alpha)\n        ax.plot((0, 11.25), (50-3.34, 50-3.34),\n                color=lines_color, lw=lw, alpha=lines_alpha)\n        ax.plot((94-11.25, 94), (3.34, 3.34),\n                color=lines_color, lw=lw, alpha=lines_alpha)\n        ax.plot((94-11.25, 94), (50-3.34, 50-3.34),\n                color=lines_color, lw=lw, alpha=lines_alpha)\n        ax.add_patch(three_pt_left)\n        ax.add_patch(three_pt_right)\n\n    if (three_line == 'womens') | (three_line == 'both'):\n        # womens 3\n        three_pt_left_w = Arc((6.25, 50/2), 20.75 * 2, 20.75 * 2, theta1=-85,\n                              theta2=85, color=lines_color, lw=lw, alpha=lines_alpha)\n        three_pt_right_w = Arc((94-6.25, 50/2), 20.75 * 2, 20.75 * 2,\n                               theta1=180-85, theta2=180+85,\n                               color=lines_color, lw=lw, alpha=lines_alpha)\n\n        # 4.25 inches max to sideline for mens\n        ax.plot((0, 8.3), (4.25, 4.25), color=lines_color,\n                lw=lw, alpha=lines_alpha)\n        ax.plot((0, 8.3), (50-4.25, 50-4.25),\n                color=lines_color, lw=lw, alpha=lines_alpha)\n        ax.plot((94-8.3, 94), (4.25, 4.25),\n                color=lines_color, lw=lw, alpha=lines_alpha)\n        ax.plot((94-8.3, 94), (50-4.25, 50-4.25),\n                color=lines_color, lw=lw, alpha=lines_alpha)\n\n        ax.add_patch(three_pt_left_w)\n        ax.add_patch(three_pt_right_w)\n\n    # Add Patches\n    ax.add_patch(left_paint)\n    ax.add_patch(left_paint_boarder)\n    ax.add_patch(right_paint)\n    ax.add_patch(right_paint_boarder)\n    ax.add_patch(center_circle)\n    ax.add_patch(hoop_left)\n    ax.add_patch(hoop_right)\n    ax.add_patch(left_arc)\n    ax.add_patch(right_arc)\n    \n    if inner_arc:\n        left_inner_arc = Arc((18.833333, 50/2), 12, 12, theta1=90,\n                             theta2=-90, color=lines_color, lw=lw,\n                       alpha=lines_alpha, ls='--')\n        right_inner_arc = Arc((94-18.833333, 50/2), 12, 12, theta1=-90,\n                        theta2=90, color=lines_color, lw=lw,\n                        alpha=lines_alpha, ls='--')\n        ax.add_patch(left_inner_arc)\n        ax.add_patch(right_inner_arc)\n\n    # Restricted Area Marker\n    restricted_left = Arc((6.25, 50/2), 8, 8, theta1=-90,\n                        theta2=90, color=lines_color, lw=lw,\n                        alpha=lines_alpha)\n    restricted_right = Arc((94-6.25, 50/2), 8, 8,\n                         theta1=180-90, theta2=180+90,\n                         color=lines_color, lw=lw, alpha=lines_alpha)\n    ax.add_patch(restricted_left)\n    ax.add_patch(restricted_right)\n    \n    # Backboards\n    ax.plot((4, 4), ((50/2) - 3, (50/2) + 3),\n            color=lines_color, lw=lw*1.5, alpha=lines_alpha)\n    ax.plot((94-4, 94-4), ((50/2) - 3, (50/2) + 3),\n            color=lines_color, lw=lw*1.5, alpha=lines_alpha)\n    ax.plot((4, 4.6), (50/2, 50/2), color=lines_color,\n            lw=lw, alpha=lines_alpha)\n    ax.plot((94-4, 94-4.6), (50/2, 50/2),\n            color=lines_color, lw=lw, alpha=lines_alpha)\n\n    # Half Court Line\n    ax.axvline(94/2, color=lines_color, lw=lw, alpha=lines_alpha)\n\n    # Boarder\n    boarder = Rectangle((0.3,0.3), 94-0.4, 50-0.4, fill=False, lw=3, color='black', alpha=lines_alpha)\n    ax.add_patch(boarder)\n    \n    # Plot Limit\n    ax.set_xlim(0, 94)\n    ax.set_ylim(0, 50)\n    ax.set_facecolor(court_color)\n    ax.set_xticks([])\n    ax.set_yticks([])\n    ax.set_xlabel('')\n    return ax\n\n\nfig, ax = plt.subplots(figsize=(15, 8.5))\ncreate_ncaa_full_court(ax, three_line='both', paint_alpha=0.4)\nplt.show()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, ax = plt.subplots(figsize=(15, 7.8))\nms = 10\nax = create_ncaa_full_court(ax, paint_alpha=0.1)\nMEvents.query('EventType == \"turnover\"') \\\n    .plot(x='X_', y='Y_', style='X',\n          title='Turnover Locations (Mens)',\n          c='red',\n          alpha=0.3,\n         figsize=(15, 9),\n         label='Steals',\n         ms=ms,\n         ax=ax)\nax.set_xlabel('')\nax.get_legend().remove()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"COURT_COLOR = '#dfbb85'\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 4))\n# Where are 3 pointers made from? (This is really cool)\nWEvents.query('EventType == \"made3\"') \\\n    .plot(x='X_', y='Y_', style='.',\n          color='blue',\n          title='3 Pointers Made (Womens)',\n          alpha=0.01, ax=ax1)\nax1 = create_ncaa_full_court(ax1, lw=0.5, three_line='womens', paint_alpha=0.1)\nax1.set_facecolor(COURT_COLOR)\nWEvents.query('EventType == \"miss3\"') \\\n    .plot(x='X_', y='Y_', style='.',\n          title='3 Pointers Missed (Womens)',\n          color='red',\n          alpha=0.01, ax=ax2)\nax2.set_facecolor(COURT_COLOR)\nax2 = create_ncaa_full_court(ax2, lw=0.5, three_line='womens', paint_alpha=0.1)\nax1.get_legend().remove()\nax2.get_legend().remove()\nax1.set_xticks([])\nax1.set_yticks([])\nax2.set_xticks([])\nax2.set_yticks([])\nax1.set_xlabel('')\nax2.set_xlabel('')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"COURT_COLOR = '#dfbb85'\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 4))\n# Where are 3 pointers made from? (This is really cool)\nWEvents.query('EventType == \"made2\"') \\\n    .plot(x='X_', y='Y_', style='.',\n          color='blue',\n          title='2 Pointers Made (Womens)',\n          alpha=0.01, ax=ax1)\nax1.set_facecolor(COURT_COLOR)\nax1 = create_ncaa_full_court(ax1, lw=0.5, three_line='womens', paint_alpha=0.1)\nWEvents.query('EventType == \"miss2\"') \\\n    .plot(x='X_', y='Y_', style='.',\n          title='2 Pointers Missed (Womens)',\n          color='red',\n          alpha=0.01, ax=ax2)\nax2.set_facecolor(COURT_COLOR)\nax2 = create_ncaa_full_court(ax2, lw=0.5, three_line='womens', paint_alpha=0.1)\nax1.get_legend().remove()\nax2.get_legend().remove()\nax1.set_xticks([])\nax1.set_yticks([])\nax2.set_xticks([])\nax2.set_yticks([])\nax1.set_xlabel('')\nax2.set_xlabel('')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## PlayerIDs\nThere is an issue when trying to read in lines where the player name has a comma. We can use `error_bad_lines` to get past this, but ideally the data would be cleaned to remove the comma or a different delimiter would be used."},{"metadata":{"_kg_hide-output":true,"trusted":true},"cell_type":"code","source":"MPlayers = pd.read_csv(f'{MENS_DIR}/MPlayers.csv', error_bad_lines=False)\nWPlayers = pd.read_csv(f'{WOMENS_DIR}/WPlayers.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"MPlayers.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# Merge Player name onto events\nMEvents = MEvents.merge(MPlayers,\n              how='left',\n              left_on='EventPlayerID',\n              right_on='PlayerID')\n\nWEvents = WEvents.merge(WPlayers,\n              how='left',\n              left_on='EventPlayerID',\n              right_on='PlayerID')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Common Events by Player"},{"metadata":{"trusted":true},"cell_type":"code","source":"MEvents.loc[MEvents['PlayerID'] == 2825].groupby('EventType')['EventID'].count() \\\n    .sort_values() \\\n    .plot(kind='barh',\n          figsize=(15, 5),\n          title='Zion Williamson event type count',\n          color=mypal[1])\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"markdown","source":"# Plotting Specific Players' Made/Missed Shots\nNow that we have player names in the event data, lets single out specific players. Starting with one of the most exciting players of the last decade.\n\n![](https://thenypost.files.wordpress.com/2018/11/zion-williamson-duke-freshman-scouting-comparables.jpg?quality=80&strip=all&w=618&h=410&crop=1)"},{"metadata":{"trusted":true},"cell_type":"code","source":"ms = 10 # Marker Size\nFirstName = 'Zion'\nLastName = 'Williamson'\nfig, ax = plt.subplots(figsize=(15, 8))\nax = create_ncaa_full_court(ax)\nMEvents.query('FirstName == @FirstName and LastName == @LastName and EventType == \"made2\"') \\\n    .plot(x='X_', y='Y_', style='o',\n          title='Shots (Zion Williamson)',\n          alpha=0.5,\n         figsize=(15, 8),\n         label='Made 2',\n         ms=ms,\n         ax=ax)\nplt.legend()\nMEvents.query('FirstName == @FirstName and LastName == @LastName and EventType == \"miss2\"') \\\n    .plot(x='X_', y='Y_', style='X',\n          alpha=0.5, ax=ax,\n         label='Missed 2',\n         ms=ms)\nplt.legend()\nMEvents.query('FirstName == @FirstName and LastName == @LastName and EventType == \"made3\"') \\\n    .plot(x='X_', y='Y_', style='o',\n          c='brown',\n          alpha=0.5,\n         figsize=(15, 8),\n         label='Made 3', ax=ax,\n         ms=ms)\nplt.legend()\nMEvents.query('FirstName == @FirstName and LastName == @LastName and EventType == \"miss3\"') \\\n    .plot(x='X_', y='Y_', style='X',\n          c='green',\n          alpha=0.5, ax=ax,\n         label='Missed 3',\n         ms=ms)\nax.set_xlabel('')\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Next lets look at Katie Lou Samuelson. She is known to be a 3-point shooter. As such, we can see her shots mostly come from outside the 3-point line.\n\n![](https://imagesvc.timeincapp.com/v3/fan/image?url=https://highposthoops.com/wp-content/uploads/getty-images/2018/10/951142340.jpeg?&w=618&h=410&crop=1)"},{"metadata":{"trusted":true},"cell_type":"code","source":"ms = 10 # Marker Size\nFirstName = 'Katie Lou'\nLastName = 'Samuelson'\nfig, ax = plt.subplots(figsize=(15, 8))\nax = create_ncaa_full_court(ax, three_line='womens')\nWEvents.query('FirstName == @FirstName and LastName == @LastName and EventType == \"made2\"') \\\n    .plot(x='X_', y='Y_', style='o',\n          title='Shots (Katie Lou Samuelson)',\n          alpha=0.5,\n         figsize=(15, 8),\n         label='Made 2',\n         ms=ms,\n         ax=ax)\nplt.legend()\nWEvents.query('FirstName == @FirstName and LastName == @LastName and EventType == \"miss2\"') \\\n    .plot(x='X_', y='Y_', style='X',\n          alpha=0.5, ax=ax,\n         label='Missed 2',\n         ms=ms)\nplt.legend()\nWEvents.query('FirstName == @FirstName and LastName == @LastName and EventType == \"made3\"') \\\n    .plot(x='X_', y='Y_', style='o',\n          c='brown',\n          alpha=0.5,\n         figsize=(15, 8),\n         label='Made 3', ax=ax,\n         ms=ms)\nplt.legend()\nWEvents.query('FirstName == @FirstName and LastName == @LastName and EventType == \"miss3\"') \\\n    .plot(x='X_', y='Y_', style='X',\n          c='green',\n          alpha=0.5, ax=ax,\n         label='Missed 3',\n         ms=ms)\nax.set_xlabel('')\nplt.legend()\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Shot Heatmap\nWe can plot a heatmap of where shots occur on the court. Interesting observation when comparing the mens to womens game is that many of the shots for mens come from directly under the hoop, while the hot spots for women shots come more frequently from the left and right of the hoop."},{"metadata":{"trusted":true},"cell_type":"code","source":"N_bins = 100\nshot_events = MEvents.loc[MEvents['EventType'].isin(['miss3','made3','miss2','made2']) & (MEvents['X_'] != 0)]\nfig, ax = plt.subplots(figsize=(15, 7))\nax = create_ncaa_full_court(ax,\n                            paint_alpha=0.0,\n                            three_line='mens',\n                            court_color='black',\n                            lines_color='white')\n_ = plt.hist2d(shot_events['X_'].values + np.random.normal(0, 0.1, shot_events['X_'].shape), # Add Jitter to values for plotting\n           shot_events['Y_'].values + np.random.normal(0, 0.1, shot_events['Y_'].shape),\n           bins=N_bins, norm=mpl.colors.LogNorm(),\n               cmap='plasma')\n\n# Plot a colorbar with label.\ncb = plt.colorbar()\ncb.set_label('Number of shots')\n\nax.set_title('Shot Heatmap (Mens)')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"N_bins = 100\nshot_events = WEvents.loc[WEvents['EventType'].isin(['miss3','made3','miss2','made2']) & (WEvents['X_'] != 0)]\nfig, ax = plt.subplots(figsize=(15, 7))\nax = create_ncaa_full_court(ax, three_line='womens', paint_alpha=0.0,\n                            court_color='black',\n                            lines_color='white')\n_ = plt.hist2d(shot_events['X_'].values + np.random.normal(0, 0.2, shot_events['X_'].shape),\n           shot_events['Y_'].values + np.random.normal(0, 0.2, shot_events['Y_'].shape),\n           bins=N_bins, norm=mpl.colors.LogNorm(),\n               cmap='plasma')\n\n# Plot a colorbar with label.\ncb = plt.colorbar()\ncb.set_label('Number of shots')\n\nax.set_title('Shot Heatmap (Womens)')\nplt.show()","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}