{"cells":[{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"from IPython.display import Image\nImage(\"../input/document-images/banner.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### By: Elijah Hall\n\n* **Started: 11/27/2019**\n\n* **Completed: 1/2/2019**\n\n* **Updated: 1/19/2019**\n\nTo see the results in a PPT click [here](https://elijahhall.net/2020/01/03/nfl-1st-and-future-analytics-kaggle-competit/).\n\n<a id='Introduction'></a>\n# Introduction\n[Table of Contents](#TableContents) \n\n## Problem Description\nIn the NFL, 12 of the 31 stadiums have fields with synthetic turf. Recent investigations of lower limb injuries among football athletes have indicated significantly higher injury rates on synthetic turf compared with natural turf (Mack et al., 2018; Loughran et al., 2019). In conjunction with the epidemiologic investigations, biomechanical studies of football cleat-surface interactions have shown that synthetic turf surfaces do not release cleats as readily as natural turf and may contribute to the incidence of non-contact lower limb injuries (Kent et al., 2015). Given these differences in cleat-turf interactions, it has yet to be determined whether **player movement patterns and other measures of player performance differ across playing surfaces** and **how these may contribute to the incidence of lower limb injury**.\n\nBelow I **investigate the relationship between the playing surface and the injury and performance** of National Football League (NFL) athletes and **examine factors that may contribute to lower extremity injuries**. I also try to **characterize any differences in player movement between the playing surfaces** and **identify specific scenarios (e.g., field surface, weather, position, play type, etc.) that interact with player movement to present an elevated risk of injury**.\n\n## The Challenge\nNFL player tracking, also known as Next Gen Stats (NGS), is the capture of real time location data, speed and acceleration for every player, every play on every inch of the field. As part of this challenge, the NFL has provided full player tracking of on-field position for **250 players over two regular season schedules**. One hundred of the athletes in the study data set sustained one or more injuries during the study period that were identified as a **non-contact injury** of a type that may have turf interaction as a contributing factor to injury. The remaining 150 athletes serve as a representative sample of the larger NFL population that did not sustain a non-contact lower-limb injury during the study period. Details of the surface type and environmental parameters that may influence performance and outcome are also provided.\n\n## About The NFL\nThe National Football League is America's most popular sports league, comprised of 32 franchises that compete each year to win the Super Bowl, the world's biggest annual sporting event. Founded in 1920, the NFL developed the model for the successful modern sports league, including national and international distribution, extensive revenue sharing, competitive excellence, and strong franchises across the country.\n\nThe NFL is committed to advancing progress in the diagnosis, prevention and treatment of sports-related injuries. The NFL's ongoing health and safety efforts include support for independent medical research and engineering advancements and a commitment to work to better protect players and make the game safer, including enhancements to medical protocols and improvements to how our game is taught and played.\n\nAs more is learned, the league evaluates and changes rules to evolve the game and try to improve protections for players. Since 2002 alone, the NFL has made 50 rules changes intended to eliminate potentially dangerous tactics and reduce the risk of injuries.\n\n## The Data\n\n### Data Files\nThere are three files provided in the dataset, as described below:\n* **Injury Record**: The injury record file in .csv format contains information on 105 lower-limb injuries that occurred during regular season games over the two seasons. Injuries can be linked to specific records in a player history using the PlayerKey, GameID, and PlayKey fields.\n* **Play List**: – The play list file contains the details for the 267,005 player-plays that make up the dataset. Each play is indexed by PlayerKey, GameID, and PlayKey fields. Details about the game and play include the player’s assigned roster position, stadium type, field type, weather, play type, position for the play, and position group.\n* **Player Track Data**: player level data that describes the location, orientation, speed, and direction of each player during a play recorded at 10 Hz (i.e. 10 observations recorded per second).\n\n### Field and Key Definitions\nThe following provides a description of each field contained within the datasets and their corresponding formats and descriptions. **Key Variables** are designated in bold.\n"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"../input/documentation-images/table1.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\n### Injury Data File\n\nNote that there is not a PlayKey available for every injury. This indicates that the game in which the injury occurred is known, but the specific play in which the injury occurred was not noted at the time of injury."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"../input/documentation-images/table2.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\n* 102 Games\n* PlayKey 27% null but are handled later by assigning the last play participated in as the play of injury.\n* injury 46% knee and 40% ankle\n* field type 54% synthetic and 46% natural\n* Days Missed More than (DM_M) used as OHE variable, i.e. DM_M1, DM_M7 etc...\n    * Will be converted to ordinal later: \n        * DM_M1 = 0\n        * DM_M7 = 1\n        * DM_M28 = 4\n        * DM_M42 = 6\n        \n### Play List\nThe play list file contains information about each player-play in the dataset, to include the player’s assigned roster position, stadium type, field type, weather, play type, position for the play, and position group\n"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"../input/documentation-images/table3.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\n*Important Note: The GameID field is a unique identifier of player games but does not strictly reflect the order in which the games were played. The PlayerDay is an integer sequence that provides an accurate timeline for player game participation. In order to generate an accurate timeline of an individual player’s game participation, the PlayerDay variable should be used. The interval between days in the PlayerDay field for an individual player accurately reflects the interval in days between that player’s participation in games. Every player has a PlayerDay = 1 (note that this date is not the same for all players). Some players may have negative values for PlayerDay, which simply indicates participation in a game that occurred before their individually assigned PlayerDay = 1.\n\n### Player Track Data\nThe player track file in .csv format includes player position, direction, and orientation data for each player during the entire course of the play collected using the NGS system. This data is indexed by PlayKey (which includes information about the player and game), with the time variable providing a temporal index within an individual play."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"../input/documentation-images/table4.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\nWhen processing the player track data, it is recommended to calculate velocity using the x, y position data and use those calculated velocities for any analysis (although we have provided the speed variable reported by the NGS system). The origin for the x and y coordinates is defined as the corner of the home endzone and home sideline (see Figure 1). The angles defined by orientation and direction are referenced from the y-axis of the coordinate system.\n\n* Time is in tenths of seconds\n* Event timeline is 98% null and labeled when the new event occures, i.e. huddle, lineset, snap, pass...etc since there is a lot of transition time between plays we use this to trigger the start and stop of each plays action"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"../input/documentation-images/coordinate_diagram.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"\n\n## Investigation Questions\n<a id='ToTop'></a>\n### I. Areas of interest guided by problem statement with summary:\n\n* \"Player movement patterns and other measures of player performance differ across playing surfaces and how these may contribute to the incidence of lower limb injury.\" - ***Average speed does differ with a decrease on synthetic turf but players movements across turf types appear to be the same.***\n\n* \"Examine the effects that playing on synthetic turf versus natural turf can have on player movements and the factors that may contribute to lower extremity injuries\" - ***Factors that may contribute to injuries are the OS metric (os_met) which evaluates the interaction between orientation and speed, the ZigZag Ratio (my_met) which measures the ratio between the euclidean distance between start/stop positions and total distance traveled. ***\n\n* \"Characterize any differences in player movement between the playing surfaces and identify specific scenarios (e.g., field surface, weather, position, play type, etc.) that interact with player movement to present an elevated risk of injury\". \n\n### II. Hypotheses\n* **Hyothesis 1 (H01)**: Player movement patterns are the same on natural fields as synthetic.\n* **Hyothesis 2 (H02)**: Player movement patterns are the same between injured and not injured players.\n\n### III. Other Questions to look at throughout\n* What factors best explain injuries?\n* What are the key differences in the movements between field types?\n\n<a id='TableContents'></a>\n## Table of Contents:\n\n1) [Introduction](#Introduction) \n\n2) [EDA Conclusions](#EDAconclusions)\n\n3) [Hyothesis 1 ](#H01)\n\n4) [Hyothesis 2 ](#H01)\n\n5) [Modeling Conclusions](#MODELINGconclusions)\n\n6) [Final Conclusions](#FINALconclusions)\n\n# Exploritory Analysis"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"#import tensorflow as tf\nimport os\nimport matplotlib.pyplot as plt\nfrom matplotlib import animation\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nfrom IPython.display import HTML\nfrom scipy.stats import ttest_ind\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import confusion_matrix,classification_report,roc_auc_score,roc_curve,mean_squared_error,mean_absolute_error\nfrom sklearn.ensemble import GradientBoostingClassifier\nfrom xgboost import XGBClassifier\nfrom statsmodels.genmod.generalized_linear_model import GLM\nfrom statsmodels.genmod import families\nimport shap\nimport math\nimport sys\nimport urllib.request as urllib2","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"def cleanStadiumType(df):\n    df[\"StadiumType\"] = df[\"StadiumType\"].str.lower()\n    df.loc[df[\"StadiumType\"].isna(),\"StadiumType\"] = \"unk\"\n    df[\"StadiumType\"]=df[\"StadiumType\"].str.replace('domed','dome')\n    df.loc[df[\"StadiumType\"].str.contains('out'),\"StadiumType\"] = \"outdoor\"\n    df.loc[df[\"StadiumType\"].str.contains('our'),\"StadiumType\"] = \"outdoor\"\n    df.loc[df[\"StadiumType\"].str.contains('open'),\"StadiumType\"] = \"outdoor\"\n    df.loc[df[\"StadiumType\"].str.contains('bowl'),\"StadiumType\"] = \"outdoor\"\n    df.loc[df[\"StadiumType\"].str.contains('heinz'),\"StadiumType\"] = \"outdoor\"\n    df.loc[df[\"StadiumType\"].str.contains('oudoor'),\"StadiumType\"] = \"outdoor\"\n    df.loc[df[\"StadiumType\"].str.contains('closed'),\"StadiumType\"] = \"indoor\"\n    df.loc[df[\"StadiumType\"].str.contains('dome'),\"StadiumType\"] = \"indoor\"\n    df.loc[df[\"StadiumType\"].str.contains('roof'),\"StadiumType\"] = \"indoor\"\n    df.loc[df[\"StadiumType\"].str.contains('indoor'),\"StadiumType\"] = \"indoor\"\n    df.loc[df[\"StadiumType\"].str.contains('cloudy'),\"StadiumType\"] = \"unk\"\n    return(df)\n    \ndef cleanWeather(df):\n    df[\"Weather\"] = df[\"Weather\"].str.lower()\n    df[\"Weather\"] = df[\"Weather\"].fillna(\"unk\")\n    df.loc[df[\"Weather\"].str.contains('ear'),\"Weather\"] = \"clear\"\n    df.loc[df[\"Weather\"].str.contains('fair'),\"Weather\"] = \"clear\"\n    df.loc[df[\"Weather\"].str.contains('oud'),\"Weather\"] = \"cloudy\"\n    df.loc[df[\"Weather\"].str.contains('clou'),\"Weather\"] = \"cloudy\"\n    df.loc[df[\"Weather\"].str.contains('overcast'),\"Weather\"] = \"cloudy\"\n    df.loc[df[\"Weather\"].str.contains('hazy'),\"Weather\"] = \"cloudy\"\n    df.loc[df[\"Weather\"].str.contains('snow'),\"Weather\"] = \"snow\"\n    df.loc[df[\"Weather\"].str.contains('rain'),\"Weather\"] = \"rain\"\n    df.loc[df[\"Weather\"].str.contains('show'),\"Weather\"] = \"rain\"\n    df.loc[df[\"Weather\"].str.contains('sun'),\"Weather\"] = \"sunny\"\n    df.loc[df[\"Weather\"].str.contains('indoor'),\"Weather\"] = \"indoor\"\n    df.loc[df[\"Weather\"].str.contains('controlled climate'),\"Weather\"] = \"indoor\"\n    df.loc[df[\"Weather\"].str.contains('cold'),\"Weather\"] = \"unk\"\n    df.loc[df[\"Weather\"].str.contains('heat'),\"Weather\"] = \"unk\"\n    return df\n\ndef get_velocity(df):\n    vel = [math.sqrt((df.x.values[i+1] - df.x.values[i])**2 + (df.y.values[i+1] - df.y.values[i])**2)/.1 for i in range(0,(len(df.x)-1))]\n    df['velocity'] = np.concatenate((np.array([0]), np.array(vel))).flat\n    return df\n\ndef orientation_diff(df):\n    diff_o = [0]\n    for i in range(0,(len(df.o)-1)):\n        difference = df.o.values[i]-df.o.values[i+1]\n        while (difference < -180): difference += 360\n        while (difference > 180): difference -= 360\n        diff_o.append(difference)\n    df['diff_o'] = diff_o\n    return df\n\ndef get_diff_os(df,col = 'velocity'):\n    noise = (np.random.uniform(-1, 1,len(df)))*0.01\n    if 'diff_o' not in df.columns:\n        df = orientation_diff(df)\n    if 'velocity' not in df.columns:\n        df = get_velocity(df)\n    diff_os = np.multiply(np.array(df.diff_o.values + noise),np.array(df[col]))\n    df['diff_os'] = diff_os\n    return df\n\ndef ecdf(data):\n    \"\"\"Compute ECDF for a one-dimensional array of measurements.\"\"\"\n    # Number of data points: n\n    n = len(data)\n\n    # x-data for the ECDF: x\n    x = np.sort(data)\n\n    # y-data for the ECDF: y\n    y = np.arange(1, n+1) / n\n\n    return x, y\n\ndef diff_of_means(data_1, data_2):\n    \"\"\"Difference in means of two arrays.\"\"\"\n\n    # The difference of means of data_1, data_2: diff\n    diff = np.mean(data_1)-np.mean(data_2)\n\n    return diff\n\ndef permutation_sample(data1, data2):\n    \"\"\"Generate a permutation sample from two data sets.\"\"\"\n\n    # Concatenate the data sets: data\n    data = np.concatenate((data1,data2))\n\n    # Permute the concatenated array: permuted_data\n    permuted_data = np.random.permutation(data)\n\n    # Split the permuted array into two: perm_sample_1, perm_sample_2\n    perm_sample_1 = permuted_data[:len(data1)]\n    perm_sample_2 = permuted_data[len(data1):]\n\n    return perm_sample_1, perm_sample_2\n\ndef draw_perm_reps(data_1, data_2, func, size=1):\n    \"\"\"Generate multiple permutation replicates.\"\"\"\n\n    # Initialize array of replicates: perm_replicates\n    perm_replicates = np.empty(size)\n\n    for i in range(size):\n        # Generate permutation sample\n        perm_sample_1, perm_sample_2 = permutation_sample(data_1,data_2)\n\n        # Compute the test statistic\n        perm_replicates[i] = func(perm_sample_1,perm_sample_2)\n\n    return perm_replicates\n\ndef bootstrap_replicate_1d(data, func):\n    return func(np.random.choice(data, size=len(data)))\n\ndef draw_bs_reps(data, func, size=1):\n    \"\"\"Draw bootstrap replicates.\"\"\"\n\n    # Initialize array of replicates: bs_replicates\n    bs_replicates = np.empty(size)\n\n    # Generate replicates\n    for i in range(size):\n        bs_replicates[i] = bootstrap_replicate_1d(data,func)\n\n    return bs_replicates\n\ndef perm_test(list1,list2, size):\n    # Compute difference of mean impact force from experiment: empirical_diff_means\n    empirical_diff_means = diff_of_means(list1,list2)\n\n    # Draw 10,000 permutation replicates: perm_replicates\n    perm_replicates = draw_perm_reps(list1,list2,\n                                     diff_of_means, size=size)\n\n    # Compute p-value: p\n    p = np.sum(perm_replicates >= empirical_diff_means) / len(perm_replicates)\n    \n    if p > 0.5:\n        p_val = (1-p)\n        print('two-tail p-value =', (1-p)*2)\n    else:\n        p_val = p\n        print('two-tail p-value =', p*2)\n\n    return(empirical_diff_means, perm_replicates, p)\n    \ndef myFE(myList):\n    my_met_list = []\n    velocity_list = []\n    diff_os_list = []\n    os_met_list = []\n    for i in np.arange(0,len(myList)):\n        temp = playertrack[playertrack[\"PlayKey\"] == myList[i]]\n        crnt_events = temp.event.drop_duplicates().fillna(\"\")\n        strt_event = crnt_events[[i in init_event for i in crnt_events]]\n        if len(strt_event) > 0:\n            start = temp[temp[\"event\"] == np.array(strt_event)[0]]\n        else:\n            my_met_list.append(999.0)\n            os_met_list.append(999.0)\n        if len(strt_event) == 0:\n          continue\n        temp = temp.loc[start.index.values[0]: ,]\n        dist = np.sqrt(abs(temp.tail(1).x.values[0] - temp.head(1).x.values[0])*abs(temp.tail(1).y-temp.head(1).y.values[0]))\n        my_met = math.log(dist/temp.dis.sum())\n        temp = get_diff_os(temp,'velocity')\n        os_met = np.sum(np.sum(temp.diff_os > 34.22) + np.sum(temp.diff_os < -29.88)) # upper and lower limits selected to align with 2.5 and 97.5 percentiles of diff_os for all plays \n        velocity_list.append(np.array(temp.velocity.values))\n        diff_os_list.append(np.array(temp.diff_os.values))\n        my_met_list.append(my_met)\n        os_met_list.append(os_met)\n    player_df = pd.DataFrame({'PlayKey':myList,'my_met': my_met_list,'os_met': os_met_list, 'diff_os': diff_os_list, 'velocity': velocity_list})\n    return(player_df)\n\ndef bootTest(sample1, sample2):\n    # Compute difference of mean impact force from experiment: empirical_diff_means\n    empirical_diff_means = diff_of_means(sample1,sample2)\n\n    # Compute mean of all forces: mean_force\n    mean_all = np.mean(np.concatenate([sample1,sample2]))\n\n    # Generate shifted arrays\n    sample1_shifted = sample1 - np.mean(sample1) + mean_all\n    sample2_shifted = sample2 - np.mean(sample2) + mean_all \n\n    # Compute 10,000 bootstrap replicates from shifted arrays\n    bs_replicates_sample1 = draw_bs_reps(sample1_shifted, np.mean, 10000)\n    bs_replicates_sample2 = draw_bs_reps(sample2_shifted, np.mean, 10000)\n\n    # Get replicates of difference of means: bs_replicates\n    bs_replicates =  bs_replicates_sample1 - bs_replicates_sample2\n\n    # Compute and print p-value: p\n    p = np.sum(bs_replicates > empirical_diff_means) / 10000\n    print('p-value =', p)\n    \n    # Compute the 95% confidence interval: conf_int\n    conf_int = np.percentile(bs_replicates,[2.5,97.5])\n\n    # Print the confidence interval\n    print('95% confidence interval =', conf_int, 'games')\n    \n    return(bs_replicates, empirical_diff_means, conf_int, p)\n\ndef plotFieldTypeWeather(df):\n    syn = df[df[\"FieldType\"] == 'Synthetic'].groupby(\"Weather\").count()\n    syn = syn[\"PlayerKey\"]\n    nat = df[df[\"FieldType\"] == 'Natural'].groupby(\"Weather\").count()\n    nat = nat[\"PlayerKey\"]\n    nat = nat[syn.index.values]\n    N = len(nat)\n    ind = syn.index.values    # the x locations for the groups\n    width = 0.35       # the width of the bars: can also be len(x) sequence\n    p1 = plt.bar(x=ind, height = nat, width = width)\n    p2 = plt.bar(x=ind, height = syn, width = width, bottom=nat)\n    plt.ylabel('Count')\n    plt.title('Counts by FieldType and Weather')\n    plt.legend((p1[0], p2[0]), ('Natural', 'Synthetic'))\n    plt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"injury = pd.read_csv(\"../input/nfl-playing-surface-analytics/InjuryRecord.csv\")\nplaylist = pd.read_csv(\"../input/nfl-playing-surface-analytics/PlayList.csv\")\nplayertrack = pd.read_csv(\"../input/nfl-playing-surface-analytics/PlayerTrackData.csv\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"img = plt.imread(\"../input/field-background/field_background.png\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"## Injury PreProcessing\n#The DM_M# variables can be converted to discrete ordinal variables representing an at least time window. 0 being less than a week, 1 being at least 1 week but not 2, etc...\ninjury[\"DM_Sum\"] = injury[\"DM_M1\"] + injury[\"DM_M7\"]  + injury[\"DM_M28\"] + injury[\"DM_M42\"] \ninjury.loc[injury[\"DM_Sum\"] >2 ,\"DM_Sum\"] = injury.loc[injury[\"DM_Sum\"] >2 ,\"DM_Sum\"] + 2\ninjury.loc[injury[\"DM_Sum\"] >5 ,\"DM_Sum\"] = injury.loc[injury[\"DM_Sum\"] >5 ,\"DM_Sum\"] + 1\ninjury[\"DM_Sum\"] = injury[\"DM_Sum\"] -1\ninjury[\"BodyPart_num\"] = injury[\"BodyPart\"] + injury[\"DM_Sum\"].astype(str)\ninjury[\"DM_Sum_adj\"] = injury.DM_Sum + np.random.uniform(-0.5, 0.5,105)\ninjury[\"BodyPart\"] = injury[\"BodyPart\"].astype('category')\ninjury[\"BodyPart_cat\"] = injury[\"BodyPart\"].cat.codes\ninjury[\"BodyPart_cat\"] = injury.BodyPart_cat + np.random.uniform(-0.2, 0.2,105) + 1\n\nfound_PlayKey = ['33337-2-25', '45099-5-1', '36591-9-4', '45950-6-81', '39653-4-68', '38253-10-13', '38214-12-36', '43119-12-66', '35648-12-40', '40051-13-30', '39671-12-24', '43229-15-42', '46021-19-68', '38259-2-37', '45158-32-17', '36572-4-30', '43490-9-30', '36573-14-52', '46134-18-19', '47196-7-45', '45975-23-52', '47273-10-50', '40405-29-14', '44423-13-27', '31933-20-26', '47285-4-16', '37068-19-20', '36696-24-22']\ninjury.loc[injury.PlayKey.isna(),'PlayKey'] = found_PlayKey\n\nplaylist = cleanWeather(playlist)\nplaylist = cleanStadiumType(playlist)\nplaylist.loc[playlist.PlayType.isna(),'PlayType'] = 'unk'\nplaylist.loc[playlist.PlayType == '0','PlayType'] = 'unk'\nplaylist.loc[(playlist[\"Weather\"] == 'unk') & (playlist[\"StadiumType\"] == 'unk'), \"StadiumType\" ] = 'indoor'\nplaylist.loc[(playlist[\"Weather\"] == 'unk') & (playlist[\"StadiumType\"] == 'indoor'), \"Weather\" ] = 'indoor'\nplaylist.loc[(playlist[\"Weather\"] == 'indoor') & (playlist[\"StadiumType\"] == 'unk'), \"StadiumType\" ] = 'indoor'\nplaylist.loc[(playlist[\"Weather\"] == 'rain') & (playlist[\"StadiumType\"] == 'unk'), \"Weather\" ] = 'outdoor'\nplaylist.loc[(playlist[\"Weather\"] == 'cloudy') & (playlist[\"StadiumType\"] == 'unk'), \"StadiumType\" ] = 'outdoor'\nplaylist.loc[(playlist[\"Weather\"] != 'indoor') & (playlist[\"StadiumType\"] == 'unk'), \"StadiumType\" ] = 'outdoor'\n\ninjury = pd.merge(injury.drop_duplicates(),playlist.drop_duplicates(),'left',['PlayerKey','GameID','PlayKey'])\n### Get sample of non injured players\nnon_inj_sample = ['38274-11-30', '33337-3-17', '44424-11-43', '36554-15-2', '45061-1-10', '42398-15-18', '39715-1-8', '36579-10-2', '47784-13-25', '43489-11-60', '36572-30-27', '39794-29-50', '47220-4-9', '47273-12-12', '42370-16-13', '42344-21-62', '42413-10-15', '42448-16-2', '39836-22-38', '42432-3-50', '42413-11-8', '38325-25-44', '43523-17-3', '34243-8-28', '47282-12-45', '44506-4-6', '31266-8-7', '43229-7-12', '44546-22-58', '44527-16-13', '45962-16-53', '47282-1-11', '43505-14-46', '36555-8-50', '46430-11-4', '42549-9-4', '43672-11-6', '45962-6-15', '43483-24-16', '47278-13-9', '47287-4-54', '35648-10-22', '43535-8-58', '45061-20-7', '40335-31-38', '47307-5-21', '39956-9-21', '45099-1-1', '39731-9-6', '43490-20-43', '43490-30-26', '44418-17-15', '44480-12-6', '44451-14-40', '43119-11-3', '40474-21-25', '42588-2-49', '46038-3-52', '45953-17-43', '44511-20-47', '42346-6-47', '39583-16-31', '40345-11-2', '39038-17-74', '43672-3-6', '42352-21-36', '36757-7-14', '47287-3-54', '43483-20-23', '39680-31-11', '47282-14-40', '44506-9-12', '39653-16-48', '42399-19-44', '44158-8-40', '41558-28-52', '40345-3-3', '38259-5-49', '42344-21-14', '45158-30-13', '42359-10-19', '42406-10-42', '38274-7-42', '44527-1-55', '43050-23-30', '46098-15-30', '46066-6-2', '43656-27-50', '44158-16-41', '43505-2-37', '43050-15-4', '44037-26-66', '43050-23-26', '36572-32-22', '44629-30-42', '42549-24-55', '43540-2-1', '36555-18-27', '46038-9-17', '45927-21-53', '34230-22-15', '47287-15-12', '42404-4-54', '44449-29-10', '44511-21-7']\ninj_player = injury['PlayKey']\ninit_event = [\"ball_snap\", \"kickoff\",\"kickoff_play\", \"onside_kick\",\"free_kick\",\"free_kick_play\", \"snap_direct\"]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"players_df = pd.DataFrame({'PlayKey': np.concatenate((np.array(non_inj_sample),np.array(inj_player))),'injured': np.concatenate((np.zeros(len(non_inj_sample)), np.ones(len(inj_player))))})\nplayers_df2 = pd.merge(players_df.drop_duplicates(),playlist.drop_duplicates(),'left',['PlayKey']).drop(['PlayerKey'],axis=1)\nplayers_df2_features = myFE(players_df2.PlayKey)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"players_df3 = pd.merge(players_df2,players_df2_features,'left',['PlayKey'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The first step to generating the OS metric I need to calculate the difference of orientation as it relates to speed. I do this by first calculating the differnce of the orientation from time t and t+1 for every recorded point on every play. Then I multiply that value by my calculated velocity as a scaler instead of the given speed variable. This intermediate variable is called **diff_os**. Additionaly, I want to determine a threshold that might reflect an explicit definition of **extreme or unsafe** movement. To do this I calculate the 2.5 and 97.5 percentiles for all diff_os values which set the boundaries. Then the **os_met** is the count of observations, tenths of a second, where the boundaries are broken. Additionally, I want to look at the limits for injured v non injured."},{"metadata":{"trusted":true},"cell_type":"code","source":"Image(\"../input/document-images/diff_os_def.png\", width='500')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"Image(\"../input/document-images/os_met_def.png\", width='300')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Look at diff_os Limits"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"all_diff_os = np.hstack(np.array([np.array(players_df3.diff_os.values[i]) for i in range(0,len(players_df3.diff_os.values))]))\ndiff_os_limits = np.percentile(all_diff_os, [2.5,97.5])\nall_diff_os_inj = np.hstack(np.array([np.array(players_df3[players_df3.injured == 1].diff_os.values[i]) for i in range(0,len(players_df3[players_df3.injured == 1]))]))\ndiff_os_limits_inj = np.percentile(all_diff_os_inj, [2.5,97.5])\nall_diff_os_non_inj = np.hstack(np.array([np.array(players_df3[players_df3.injured == 0].diff_os.values[i]) for i in range(0,len(players_df3[players_df3.injured == 0]))]))\ndiff_os_limits_non_inj = np.percentile(all_diff_os_non_inj, [2.5,97.5])\n\nprint('diff_os limit for All:' + str(diff_os_limits))\nprint('diff_os limit for Injured:' + str(diff_os_limits_inj))\nprint('diff_os limit for Non Injured:' + str(diff_os_limits_non_inj))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Performance Inspection by Velocity \n\nIt is difficult to evaluate performance of players without adittional metrics such as who has the ball and points scored. However, we have speed. To try to investigate how performance is affected we can try to use my calculated **velocity** metric as a proxy for performance. Higher average velocity will therefore reflect higher average performance."},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"all_velocity = np.hstack(np.array([np.array(players_df3.velocity.values[i]) for i in range(0,len(players_df3))]))\nall_velocity_inj = np.hstack(np.array([np.array(players_df3[players_df3.injured == 1].velocity.values[i]) for i in range(0,len(players_df3[players_df3.injured == 1]))]))\nall_velocity_non_inj = np.hstack(np.array([np.array(players_df3[players_df3.injured == 0].velocity.values[i]) for i in range(0,len(players_df3[players_df3.injured == 0]))]))\nall_velocity_syn = np.hstack(np.array([np.array(players_df3[players_df3.FieldType == 'Synthetic'].velocity.values[i]) for i in range(0,len(players_df3[players_df3.FieldType == 'Synthetic']))]))\nall_velocity_nat = np.hstack(np.array([np.array(players_df3[players_df3.FieldType == 'Natural'].velocity.values[i]) for i in range(0,len(players_df3[players_df3.FieldType == 'Natural']))]))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.hist(all_velocity_syn, density=True,alpha=0.3)\nplt.xlim(0,15)\nplt.hist(all_velocity_nat[all_velocity_nat <15], density=True,alpha=0.3)\nplt.title('Velocity by Field Type')\nplt.legend(['Synthetic','Natural'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Permutation Difference of Means Test for Velocity on Synthetic v Natural Fields"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"perm_test(all_velocity_syn[all_velocity_syn <15],all_velocity_nat[all_velocity_nat <15],10000)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"print('Synthetic mean velocity = ' + str(round(np.mean(all_velocity_syn),2)))\nprint('Natural mean velocity = ' + str(round(np.mean(all_velocity_nat[all_velocity_nat <100]),2)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There is a significant difference between velocity on Synthetic v Natural field type. It is approximately a negative **10.2% drop in average velocity**, with mean velocity on Synthetic 2.02 and Natural of 2.25. Performing a Students T-Test we get a p-value of 0.000 meaning it is statistically significant with a confidence value of 0.05. Therefore, we reject the null hypothesis that velocity on Synthetic and Natural are sampled from the same parent distribution. \n\nThis answers the question about performance and **\"Performance drops on Synthetic Fields\"**\n\n## Temperature\n\nWe might also consider temperature and see if it is significantly different between injured players and non injured players. If it is it might indicate that tempurature could play a role in the risk of non-contact injuries."},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"all_temp_inj = players_df3[players_df3.injured == 1].Temperature\nall_temp_inj = all_temp_inj[all_temp_inj >-30]\nall_temp_non_inj = players_df3[players_df3.injured == 0].Temperature\nall_temp_non_inj = all_temp_non_inj[all_temp_non_inj >-30]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.hist(all_temp_inj,alpha=.2)\nplt.hist(all_temp_non_inj,alpha=.2)\nplt.title('Temperature by Injury')\nplt.legend(['Injured','Non Injured'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Students-T Test for Temperature of injured v non injured plays"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"stat, p = ttest_ind(all_temp_inj, all_temp_non_inj)\nprint('Statistics=%.3f, p=%.3f' % (stat, p))\n# interpret\nalpha = 0.05\nif p > alpha:\n    print('Temperature Injured & Non-injured Same distributions (fail to reject H0)')\nelse:\n    print('Temperature Injured & Non-injured Different distributions (reject H0)')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"all_temp_inj_syn = players_df3[(players_df3.injured == 1)&(players_df3.FieldType == 'Synthetic')].Temperature\nall_temp_inj_syn = all_temp_inj_syn[all_temp_inj_syn >-30]\nall_temp_inj_nat = players_df3[(players_df3.injured == 1)&(players_df3.FieldType == 'Natural')].Temperature\nall_temp_inj_nat = all_temp_inj_nat[all_temp_inj_nat >-30]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.hist(all_temp_inj_syn,density=True, alpha=.2)\nplt.hist(all_temp_inj_nat,density=True,alpha=.2)\nplt.title('Temperature by Field Type')\nplt.legend(['Synthetic','Natural'])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Students-T Test for Temperature of Synthetic v Natural Fields"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"stat, p = ttest_ind(all_temp_inj_syn, all_temp_inj_nat)\nprint('Statistics=%.3f, p=%.3f' % (stat, p))\n# interpret\nalpha = 0.05\nif p > alpha:\n    print('Temperature Synthetic & Natural Same distributions (fail to reject H0)')\nelse:\n    print('Temperature Synthetic & Natural Different distributions (reject H0)')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Temperature is not **significantly different** for injured v non injured as well as synthetic v natural field type. This doesn't necessarily rule it out as an important factor or that it doesn't impact risk of injuries.\n\n### Look at \"Last Play\" before Injury\nI want to look at the last play that a player was on before getting injured. This can tell me a few things like wether the player was injured in the beginning of the game or didn't play very much. This is hard to tell with the way plays are recorded. In this data the play count is relative to each player. This means a player can play only 10 plays and another could play 80 but you have no idea if the player with 10 played all his plays in the firt half or second half. Similarly you have no idea if a player was injured in the last play of the game or the 10th.\n\nThere is an average of 67 - 84 plays per game for the offense. It is not expected for every player to play every game due to substitutios. However there are some we expect to play every play or nearly every play such as QB's."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"last_play_inj = [PlayKey[-2:] for PlayKey in injury.PlayKey]\nlast_play_inj = [abs(int(i)) for i in last_play_inj]\ninjury['last_play'] = last_play_inj\nlast_play_inj = np.array([i for i in last_play_inj if (i > 0) ]) ","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"last_play = playlist.groupby('GameID').tail(1).PlayerGamePlay","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = plt.figure(figsize=(20,8))\nax = plt.subplot(121)\nax.hist(injury[injury.Surface == 'Synthetic'].last_play,density=True,alpha = .5)\nax.axvline(np.median(injury[injury.Surface == 'Synthetic'].last_play), color='blue', linestyle='dashed', linewidth=2)\nax.axvline(np.median(injury[injury.Surface == 'Natural'].last_play), color='orange', linestyle='dashed', linewidth=2)\nax.hist(injury[injury.Surface == 'Natural'].last_play,density=True,alpha = .5)\nax.set_title(r'last play Synthetic v Natural', fontsize=18)\nax.set_xlabel(r'last play', fontsize=16)\nax.set_ylabel('PDF', fontsize=16)\nax.legend(('Synthetic Median','Natural Median','Synthetic','Natural'), fontsize=14)\n\nax2 = plt.subplot(122)\nax2.axvline(np.median(last_play), color='blue', linestyle='dashed', linewidth=2)\nax2.axvline(last_play.mean(), color='lightblue', linestyle='dashed', linewidth=2)\nax2.hist(last_play, density=True,alpha = 0.5)\nax2.axvline(np.median(last_play_inj), color='orange', linestyle='dashed', linewidth=2)\nax2.axvline(last_play_inj.mean(), color='yellow', linestyle='dashed', linewidth=2)\nax2.hist(last_play_inj, density=True,alpha = 0.5)\nax2.set_title(r'last play for all vs inj', fontsize=18)\nax2.set_xlabel(r'last play', fontsize=16)\nax2.set_ylabel('PDF', fontsize=16)\nax2.legend(('all median','all mean','injured median','injured mean','all','injured'), fontsize=14)\n\nprint(\"Synthetic median last play = \" +str(np.median(injury[injury.Surface == 'Synthetic'].last_play)))\nprint(\"Natural median last play = \" +str(np.median(injury[injury.Surface == 'Natural'].last_play)))\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Permutation Difference of Means Test for Last Play on injured v non injured"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"test = perm_test(last_play,last_play_inj,10000)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Permutation Difference of Means Test for Last Play on Synthetic v Natural Fields"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"test = perm_test(injury[injury.Surface == 'Synthetic'].last_play,injury[injury.Surface == 'Natural'].last_play,10000)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The **Median** last play before injury is 23 and the median of last play of all other players in all other games is 49. Therefore, we can say that those prone to injury are more likely to be injured in the first half or first 20 plays of their game. This might suggest that players who play less often or positions substituted more often might be at higher risk. This might be expected if we consider the risk positions are wide receivers, running backs and their defensive counterparts. \n\nWhen we perform a permutation test on the difference of means we see the empirical difference has a p-value of around 0.0 which is < a significance level of 0.05. There **is significant evidence** to reject the null hypothesis that the Injured and All last plays are from the same parent distribution. This is expected and more of a validation of our assumptions. We naturaly assume that all injuries do not happen in the last few plays. Most might assume that injuries happen in a few key times such as the beginning and end of each half. Additionaly with added strain on the players body each play ran might increase the risk of injury. Therefore we assume injured players play less plays per game. \n\nThe more interesting question is directed at field type. The histograms between Synthetic and Natural appear to be similar enough to assume the risk of injury relative to play is similar. When we perform a permutation test on the difference of means we see the empirical difference has a p-value of around 0.089 which is > a significance level of 0.05 and **not statistically significant**. Additionally, the distribution seems to suggest that a survival analysis would be worth investigating. Synthetic median last play is 27.0 and Natural median last play is 18.5 meaning those more likely to be injured get injured earlier on natural vs synthetic. However, as stated before there **is not significant evidence** to refect the null hypothesis that the Synthetic and Natural last plays are from the same parent distribution."},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"# All Games PlayType by Natural v Synthetic Stacked Bar Chart\ndf = playlist[[\"FieldType\",\"PlayType\"]]\nsyn = df[df[\"FieldType\"] == 'Synthetic'].groupby(\"PlayType\").count()\n#syn = syn[\"PlayerKey\"].append(pd.Series([0], index=['snow']))\nnat = df[df[\"FieldType\"] == 'Natural'].groupby(\"PlayType\").count()\n#nat = nat[\"PlayerKey\"].append(pd.Series([0], index=['snow'])).append(pd.Series([0], index=['indoor']))\nstackplot_df = pd.DataFrame(nat)\nstackplot_df['synthetic'] = syn\nstackplot_df.columns = ['natural','synthetic']\n#stackplot_df=stackplot_df.loc[['clear', 'cloudy', 'indoor', 'rain', 'sunny', 'unk', 'snow']]\nstackplot_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"# Injured Games PlayType by Natural v Synthetic Stacked Bar Chart\ndf = injury[[\"FieldType\",\"PlayType\"]]\nsyn = df[df[\"FieldType\"] == 'Synthetic'].groupby(\"PlayType\").count()\n#syn = syn[\"PlayerKey\"].append(pd.Series([0], index=['snow']))\nnat = df[df[\"FieldType\"] == 'Natural'].groupby(\"PlayType\").count()\n#nat = nat[\"PlayerKey\"].append(pd.Series([0], index=['snow'])).append(pd.Series([0], index=['indoor']))\nstackplot_df = pd.DataFrame(nat)\nstackplot_df['synthetic'] = syn\nstackplot_df.columns = ['natural','synthetic']\n#stackplot_df=stackplot_df.loc[['clear', 'cloudy', 'indoor', 'rain', 'sunny', 'unk', 'snow']]\nstackplot_df","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-output":true,"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"#plot percentage stacked bars\nf, ax = plt.subplots(1, figsize=(15,8))\nbar_width = 1\nbar_l = [i for i in range(len(stackplot_df['natural']))] \ntick_pos = [i+(bar_width/2) for i in bar_l] \ntotals = [i+j for i,j in zip(stackplot_df['natural'], stackplot_df['synthetic'])]\nnatural_rel = [i / j * 100 if j > 0 else 0 for  i,j in zip(stackplot_df['natural'], totals)]\nsynthetic_rel = [i / j * 100 if j > 0 else 0 for  i,j in zip(stackplot_df['synthetic'], totals)]\nax.bar(bar_l, natural_rel, label='Natural', alpha=0.8, color='blue',width=bar_width, edgecolor='white')\nax.bar(bar_l, synthetic_rel, bottom=natural_rel,label='Synthetic', alpha=0.8,  color='red', width=bar_width, edgecolor='white')\nplt.xticks(tick_pos, stackplot_df.index.values)\nax.set_ylabel(\"Percentage\")\nax.set_xlabel(\"PlayType\")\nplt.xlim([min(tick_pos)-bar_width, max(tick_pos)+bar_width])\nplt.ylim(-10, 110)\nplt.setp(plt.gca().get_xticklabels(),rotation = 45, horizontalalignment='right')\nplt.legend(('Natural', 'Synthetic'))\nplt.title('PlayType by Natural v Syntehtic for Injuries')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = plt.figure(figsize=(20,10))\nax = plt.subplot(121)\nax.imshow(plt.imread(\"../input/document-images/playtypeBYfieldtype.png\"))\nax.axis('off')\nax2 = plt.subplot(122)\nax2.imshow(plt.imread(\"../input/document-images/playtypeBYfieldtype_injuries.png\"))\nax2.axis('off')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There are limited observations on injuries but for the two with >10 observations, Pass and Rush, we can reasonably compare the proportional difference. Visually we see that the **Pass** is nearly 60:40 on Natural v Synthetic Fields respectively but on injuries it is the reverse, 40:60. This suggests Synthetic fields have higher rates of injury on Pass Plays. There is a similar pattern on **Rush** plays with 60:40 in all plays and about 50:50 in injuries. "},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"# Injured Games PlayType by Natural v Synthetic Stacked Bar Chart\ndf = playlist\nsyn = df[df[\"FieldType\"] == 'Synthetic'].groupby(\"Weather\").count()\nsyn = syn[\"PlayerKey\"]\nnat = df[df[\"FieldType\"] == 'Natural'].groupby(\"Weather\").count()\nnat = nat[\"PlayerKey\"]\nstackplot_df = pd.DataFrame(nat)\nstackplot_df['synthetic'] = syn\nstackplot_df.columns = ['natural','synthetic']\nstackplot_df=stackplot_df.loc[['clear', 'cloudy', 'indoor', 'rain', 'sunny', 'unk', 'snow']]\nstackplot_df","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"## Injured Games Percentage Stacked Bar Plot \ndf = injury\nsyn = df[df[\"FieldType\"] == 'Synthetic'].groupby(\"Weather\").count()\nsyn = syn[\"PlayerKey\"].append(pd.Series([0], index=['snow']))\nnat = df[df[\"FieldType\"] == 'Natural'].groupby(\"Weather\").count()\nnat = nat[\"PlayerKey\"].append(pd.Series([0], index=['snow'])) #.append(pd.Series([0], index=['indoor']))\nstackplot_df = pd.DataFrame(nat)\nstackplot_df['synthetic'] = syn\nstackplot_df.columns = ['natural','synthetic']\nstackplot_df=stackplot_df.loc[['clear', 'cloudy', 'indoor', 'rain', 'sunny', 'unk', 'snow']]\nstackplot_df","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-output":true,"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"f, ax = plt.subplots(1, figsize=(15,8))\nbar_width = 1\nbar_l = [i for i in range(len(stackplot_df['natural']))] \ntick_pos = [i+(bar_width/2) for i in bar_l] \ntotals = [i+j for i,j in zip(stackplot_df['natural'], stackplot_df['synthetic'])]\nnatural_rel = [i / j * 100 if j > 0 else 0 for  i,j in zip(stackplot_df['natural'], totals)]\nsynthetic_rel = [i / j * 100 if j > 0 else 0 for  i,j in zip(stackplot_df['synthetic'], totals)]\nax.bar(bar_l, natural_rel, label='Natural', alpha=0.8, color='blue',width=bar_width, edgecolor='white')\nax.bar(bar_l, synthetic_rel, bottom=natural_rel,label='Synthetic', alpha=0.8,  color='red', width=bar_width, edgecolor='white')\nplt.xticks(tick_pos, stackplot_df.index.values)\nax.set_ylabel(\"Percentage\")\nax.set_xlabel(\"Weather\")\nplt.xlim([min(tick_pos)-bar_width, max(tick_pos)+bar_width])\nplt.ylim(-10, 110)\nplt.setp(plt.gca().get_xticklabels(), horizontalalignment='right')\nplt.legend(('Natural', 'Synthetic'))\nplt.title('Weather by Natural v Syntehtic for Injured Games')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = plt.figure(figsize=(20,10))\nax = plt.subplot(121)\nax.imshow(plt.imread(\"../input/document-images/Weather by Natural v Synthetic for all.png\"))\nax.axis('off')\nax2 = plt.subplot(122)\nax2.imshow(plt.imread(\"../input/document-images/Weather by Natural v Synthetic for inj.png\"))\nax2.axis('off')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**There are not enough observations in the Injured players across the various weather to walk away with any confident insights. The only real take away is that the most injuries happened in cloudy or sunny weather.**\n\n## Looking at Injuries by BodyPart\n\nBelow is a table by body part and the ordinal variable that reflects weeks out of the game where 0 is < 7 days and 1 is > 7 and < 14. For example Ankle0 reflects a minor ankel injury where the player was out for < 7 days and Ankle6 a player with a severe injury where the player was out > 42 days or 6 weeks."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"part_ct = injury[[\"BodyPart\",\"DM_Sum\"]].groupby(\"BodyPart\").count()\npart_ct[\"BodyPart\"]=part_ct.index.values\npart_ct.index = [1,2,3,4,5]\npart_mn = injury[[\"BodyPart\",\"DM_Sum\"]].groupby(\"BodyPart\").mean()\npart_mn[\"BodyPart\"]=part_mn.index.values\npart_mn.index = [1,2,3,4,5]\ntemp = injury[[\"BodyPart_num\",\"PlayerKey\"]].groupby([\"BodyPart_num\"]).count()\ntemp[\"BodyPart\"] = [i[:-1] for i in temp.index.values]\ntemp","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Below is a table reflecting the average, mean and mode, of body parts as well as total counts to give more context to the central tendencies reported. We can see that knee and ankle have large enough counts to have some confidence in the values reported. "},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"temp1 = pd.merge(part_mn, part_ct, 'left', 'BodyPart')\ntemp1.columns = [\"Mean\",\"BodyPart\",\"Count\"]\ntemp1['Mode'] = [0,6,1,1,1]\ntemp1","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"This table joins the previous tables to look at all perspectives together. Knee and ankle injuries are ditributed with similar patterns. Ankle injuries have slightly more counts of minor injuries > 0 and < 7 days, and knee injuries has slightly higher couns of mild injuries > 7 and < 14 days. This is reflected in the mean values. Foot injuries appear to be the most severe injuries with all counts > 28 days or 4 weeks. Toes are the least severe, 6 of 7 with minor or mild injuries."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"inj_by_part = pd.merge(temp,temp1,'left','BodyPart')\ninj_by_part['BP_DM_Sum'] = temp.index.values\ninj_by_part\ninj_by_part.columns = ['Count', 'BodyPart', 'Mean', 'BP_TTL','Mode', 'BP_DM_Sum']\ninj_by_part[[ 'BP_DM_Sum','BodyPart', 'Mean','Mode','BP_TTL','Count'  ]]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"These two visualizations help illustrate the observations from the table above."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"ax = sns.lmplot( x=\"BodyPart_cat\", y=\"DM_Sum_adj\", data=injury, fit_reg=False, hue='FieldType', legend=False,palette=\"Set1\")\nax.set(xlabel='Body Part', ylabel='Injury Severity')\nplt.legend(loc='upper center')\nplt.xticks([1,2,3,4,5], np.sort(np.array(injury[\"BodyPart\"].drop_duplicates())))\nplt.title('Injury Severity by Body Part and Field Type')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"../input/document-images/severity_likelihood.png\", width='500')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"As you can see from the plot above average Ankle and Knee injury severity is in the middle of the range but has the highest likelihood of all injuries. Foot injuries are the most severe but not as likely and toe injures are not severe and unlikely. There was only one observation of a heel injury."},{"metadata":{},"cell_type":"markdown","source":"## Inspect Base Rates of Injury\n\nOne of the first questions that came to mind is what is the base rate of injury on Synthetic v Natural fields? This answer was already informed by the problem description that Synthetic fields are showing higher injury rates for non-contact injuries. We see below that the number of injuries on synthetic fields is higher than natural, but we also know that synthetic fields only account for about 1/3 of the fields."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"num = injury[[\"Weather\",\"FieldType\"]].groupby(\"FieldType\").count()\nnum","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"pl = playlist[[\"PlayerKey\",\"GameID\",\"FieldType\"]] \n\nsyn_d = len(pl[pl[\"FieldType\"] == 'Synthetic'].drop_duplicates())\nnat_d = len(pl[pl[\"FieldType\"] == 'Natural'].drop_duplicates())\nnat_num = num[\"Weather\"].Natural\nsyn_num = num[\"Weather\"].Synthetic\n\n#print(\"Natrual Injury Base Rate = \" + str(round(nat_num/nat_d*100,2)) +\"%\")\n#print(\"Synthetic Injury Base Rate = \" + str(round(syn_num/syn_d*100,2)) +\"%\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"pl.drop_duplicates().groupby(\"FieldType\").count()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The potential for injury arises when a player plays in a game on either a Natural or Synthetic field. Each of these events are aggregated and we see **3311** observations of players playing in a game on Natural fields and **2401** observations of players playing on Synthetic Fields.\nThe relative base rates for these conditions are:\n* Natrual Injury Base Rate = 1.42%\n* Synthetic Injury Base Rate = 2.33%\n"},{"metadata":{},"cell_type":"markdown","source":"### Stadium Type analysis with Weather \n\nI also want to know what stadium type and weather might look like visualized together. The majority of stadium types can be generalized to indoors or outdoors. The specific differneces between variations didn't seem to have obviously different conditions. The exception would be stadiums with retractable roofs as seen in the table below which I classify as indoors since the roof is assumed to be closed in poor weather. I also cleaned the weather to make for more interpretable analysis with the categories defined earlier in the introduction."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"std_field = playlist[[\"PlayerKey\",\"StadiumType\",\"GameID\",\"Weather\",\"FieldType\"]].drop_duplicates()\nstd_field.groupby([\"StadiumType\",\"FieldType\"]).count()[[\"PlayerKey\"]]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"indoor = injury[injury[\"StadiumType\"] == 'indoor'].groupby(\"Weather\").count()\nindoor = indoor[\"PlayerKey\"].append(pd.Series([0], index=['rain']))\noutdoor = injury[injury[\"StadiumType\"] == 'outdoor'].groupby(\"Weather\").count()\noutdoor = outdoor[\"PlayerKey\"].append(pd.Series([0], index=['indoor']))\noutdoor = outdoor[indoor.index.values]\nN = len(indoor)\nind = indoor.index.values    # the x locations for the groups\nwidth = 0.35       # the width of the bars: can also be len(x) sequence\n\np1 = plt.bar(x=ind, height = indoor, width = width)\np2 = plt.bar(x=ind, height = outdoor, width = width)\nplt.ylabel('Count')\nplt.title('Counts by StadiumType and Weather')\nplt.legend((p1[0], p2[0]), ('indoor', 'outdoor'))\n\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"num = injury[[\"Weather\",\"StadiumType\"]].groupby(\"StadiumType\").count()\nnum","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"in_d = len(std_field[std_field[\"StadiumType\"] == 'indoor'].drop_duplicates())\nout_d = len(std_field[std_field[\"StadiumType\"] == 'outdoor'].drop_duplicates())\nin_num = num[\"Weather\"].indoor\nout_num = num[\"Weather\"].outdoor\n\n#print(\"Indoor Injury Base Rate = \" + str(round(in_num/in_d*100,2)) +\"%\")\n#print(\"Outdoor Injury Base Rate = \" + str(round(out_num/out_d*100,2)) +\"%\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"std_field.drop_duplicates().groupby(\"StadiumType\").count()[[\"PlayerKey\"]]","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"The potential for injury arises when a player plays in a game on either Indoors or Outdoors. Each of these events are aggregated and we see **4061** observations of players playing in a game on Outdoors and **1296** observations of players playing indoor, with only **355, about 7%,** unkown observations.\n\n**The majority of injuries happen outdoors when it is cloudy, likely causing more humidity which might have more of an impact to injuries on synthetic v natural turf. The relative base rates for these conditions are:\n* Indoor Injury Base Rate = **2.43%**\n* Outdoor Injury Base Rate = **1.64%**"},{"metadata":{},"cell_type":"markdown","source":"### Look at difference between my calculated velocity and the recorded speed metric\n\nAs mentioned before I calculated my own velocity metric however, I am not sure if the error or differnece between is from the x,y coordinate readings or the speedometer readings. **It should be noted that there is potential for measurement error when collecting the data which could have an impact to the conclusions made in further analysis.**\n\nOrientation is tricky and you can not use a simple **diff** function, because if the orientation of the player crosses the 0 degree mark it will look like they rotated 300+ degrees when we know this is not the case. Therefore, I created my own function, **orientation_diff()** to ensure this as expected.    "},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"inj_player = injury['PlayKey']\ncolors = [\"red\" if ft==\"Synthetic\" else \"blue\" for ft in injury['FieldType']]\ninit_event = [\"ball_snap\", \"kickoff\",\"kickoff_play\", \"onside_kick\",\"free_kick\",\"free_kick_play\", \"snap_direct\"]\ntemp = playertrack[playertrack[\"PlayKey\"] == inj_player[0]]\ncrnt_events = temp.event.drop_duplicates().fillna(\"\")\nstrt_event = crnt_events[[i in init_event for i in crnt_events]].values[0]\nstart = temp[temp[\"event\"] == strt_event]\ntemp = temp.loc[start.index.values[0]: , ]\ntemp = get_diff_os(temp,'velocity')\ndiff_os_example = temp","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = plt.figure(figsize=(30,20))\nax = plt.subplot(221)\nax.hist((temp.s-temp.velocity), density=True)\nax.set_xlabel('diff', fontsize = 16)\nax.set_ylabel('PDF', fontsize = 16)\nax.set_title('Difference between Recorded Speed(S) and Calculated Velocity', fontsize = 20)\nax2 = plt.subplot(222)\nax2.plot(temp.time,temp.s,label='Speed')\nax2.plot(temp.time,temp.velocity,label='Velocity')\nax2.set_xlabel('time', fontsize = 16)\nax2.set_ylabel('value', fontsize = 16)\nax2.set_title('Velocity v Speed', fontsize = 20)\nax2.legend(loc=\"upper right\", fontsize = 20)\nax3 = plt.subplot(212)\nax3.plot(temp.time,get_diff_os(temp,'s').diff_os,label='diff_os w/ Speed')\nax3.plot(temp.time,get_diff_os(temp,'velocity').diff_os,label='diff_os w/ Velocity')\nax3.set_xlabel('time', fontsize = 16)\nax3.set_ylabel('value', fontsize = 16)\nax3.set_title('diff_os for Velocity v Speed', fontsize = 20)\nax3.legend(loc=\"upper right\", fontsize = 20)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"In the plots above we see the histogram of the difference between the provided speed an my calculated velocity metrics. The distribution is not normal and it is centered slightly to the left of 0. We can see a test run of a single play to evaluate what this looks like on a case by case basis. In the top right we see speed and velocity ploted over the selected play. Below is the diff_os metric explained earlier. This difference is imposible to explain so instead I assume the X,Y coordinates are more accurate and default to my calculated velocity metric instead of speed.\n\n<a id='EDAconclusions'></a>\n## Conclusion EDA:\n<a href='#ToTop'>Back To Top</a>\n\n1) There is a significant difference between velocity on Synthetic v Natural field type. It is approximately a negative **10.2% drop in average velocity**, with mean velocity on Synthetic 2.02 and Natural of 2.25. Performing a Students T-Test we get a p-value of 0.000 meaning it is statistically significant with a confidence value of 0.05. Therefore, we reject the null hypothesis that velocity on Synthetic and Natural are sampled from the same parent distribution. **\"Performance drops on Synthetic Fields\"**\n\n2) Temperature is **not significantly different** for injured v non injured as well as synthetic v natural field type. This doesn't necessarily rule it out as an important factor or impact risk of injuries.\n\n3) The **Median** last play before injury is 23 and the median of last play of all other players in all other games is 49. Therefore, we can say that those prone to injury are more likely to be injured in the first half or first 20 plays of their game. When we perform a permutation test on the difference of means we see the empirical difference has a p-value of around 0.000 which is less than a significance level of 0.05. There **is significant evidence** to reject the null hypothesis that the Injured and All last plays are from the same parent distribution. The histograms between Synthetic and Natural appear to be similar enough to assume the risk of injury relative to play is similar. When we perform a permutation test on the difference of means we see the empirical difference has a p-value of around 0.089 which is > a significance level of 0.05. Additional the distribution seems to suggest that of a survival analysis would be worth investigating.Synthetic median last play = 27.0 and Natural median last play = 18.5 meaning those more likely to be injured get injured earlier on natural vs synthetic. However, as stated before there **is not significant evidence** to refect the null hypothesis that the Synthetic and Natural last plays are from the same parent distribution.\n\n4) There are limited observations on injuries but for the two with >10 observations Pass and Rush we can reasonably compare the proportional difference. Visually we see that the **Pass** is nearly 60:40 on Natural v Synthetic Fields respectively but on injuries it is the reverse, 40:60. This suggests Synthetic fields have higher rates of injury on Pass Plays. There is a similar pattern on **Rush** plays with 60:40 in all plays and about 50:50 in injuries. **There are not enough observations in the Injured players across the various weather to walk away with any confident insights.**\n\n5) Knee and ankle injuries are ditributed with similar patterns. Ankle injuries have slightly more counts of minor injuries > 0 and < 7 days, and knee injuries has slightly higher couns of mild injuries > 7 and < 14 days. This is reflected in the mean values. Foot injuries appear to be the most severe injuries with all counts > 28 days or 4 weeks. Toes are the least severe, 6 of 7 with minor or mild injuries. Ankle and Knee injury severity is in the middle of the range but has the highest likelihood of all injuries. Foot injuries are the most severe but not as likely and toe injures are not severe and unlikely. There was only one observation of a heel injury.\n\n6) Synthetic base rate of injury is almost 64% higher than Natural fields even though Synthetic fields only account for 1/3 of all NFL fields. The majority of injuries happen outdoors when it is cloudy, likely causing more humidity which might have more of an impact to injuries on synthetic v natural turf. The relative base rates are:\n    * Natrual Injury Base Rate = 1.42%\n    * Synthetic Injury Base Rate = 2.33%\n    * Indoor Injury Base Rate = 2.43%\n    * Outdoor Injury Base Rate = 1.64%\n\n7) Use Velocity not Speed to calculate movement metrics. There is a significant difference between velocity on Synthetic v Natural field type. It is approximately a negative 10.2% drop in average velocity. With mean velocity on Synthetic 2.02 and Natural of 2.25. Performing a Students T-Test we get a p-value of 0.000 meaning it is statistically significant with a confidence value of 0.05. Therefore we reject the null hypothesis that velocity on Synthetic and Natural are sampled from the same parent distribution. This gets to the question about performance. Without points or other metrics this is the best measure of performance. "},{"metadata":{},"cell_type":"markdown","source":"## H01: Player movement patterns are different on natural fields than synthetic.\n   \nTo evaluate player movements, I want to track player paths for each play and compare the spacial differences between field types visually.\n<a id='H01'>\n    \n<a href='#ToTop'>Back To Top</a>\n"},{"metadata":{},"cell_type":"markdown","source":"<img src=\"https://media.giphy.com/media/ZXkw4FZWsAcGjNJ5e1/giphy.gif\" width ='700'>\n<img src=\"https://media.giphy.com/media/Kg2AvSrfBUtA75nv2H/giphy.gif\">"},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"### Create GIF of player movements\nplt.cla\nfig = plt.figure(figsize=(30,10))\nax2 = fig.add_subplot(111)\n\nax2.imshow(img, zorder=0, extent=[0.1, 120, 0, 53.3])\nline, = ax2.plot([], [], color=colors[0], alpha = 0.6, lw=2)\n\ndef init():\n    line.set_data([], [])\n    return line,\n\ndef animate(t):\n    line.set_data(np.array(temp.x)[:t], np.array(temp.y)[:t])\n    return line,\n\nani = animation.FuncAnimation(fig, animate, init_func=init, frames=len(temp.x), interval=1, blit=True)\n#ani.save('inj_plays.gif', writer='pillow')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-output":true,"_kg_hide-input":true},"cell_type":"code","source":"### Create GIF of player diff_os\nplt.cla\nfig = plt.figure(figsize=(30,10))\nax = fig.add_subplot(121)\nax.set_ylim([-50, 50])\nax.set_xlim([0, len(temp.time)])\nax.set(xlabel='time', ylabel='diff_os')\n\nline = ax.plot([], [], color=colors[0], alpha = 0.2)[0]\n\ndef init():\n    line.set_data([], [])\n    return line,\n\ndef animate(t):\n    line.set_data(np.arange(0,len(temp.time))[:t], temp.diff_os[:t])\n    return line,\n\nani = animation.FuncAnimation(fig, animate, init_func=init, frames=len(temp.time), interval=1, blit=True)\n#ani.save('inj_plays2.gif', writer='pillow')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Above you can see the path ran by the player where he was injured. It is not obvious where he was injured but you can see the curviness of his route and that he accelerates and decelerates by the plot of his diff_os values. *Forgive me if they are out of sync*\n\nBelow we see the paths of Natural vs Synthetic paths and both together."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.cla\nfig = plt.figure(figsize=(30,10))\nax = fig.add_subplot(121)\nax.set_title('Injury Paths on Natural')\nax2 = fig.add_subplot(122)\nax2.set_title('Injury Paths on Synthetic')\nax.imshow(img,zorder=0,  extent=[0.1, 120, 0, 53.3])\nax.set_ylim(-10,60)\nax2.imshow(img,zorder=0,  extent=[0.1, 120, 0, 53.3])\nax2.set_ylim(-10,60)\n\nfor i in np.arange(0,len(inj_player)):\n    temp = playertrack[playertrack[\"PlayKey\"] == inj_player[i]]\n    crnt_events = temp.event.drop_duplicates().fillna(\"\")\n    strt_event = crnt_events[[i in init_event for i in crnt_events]].values[0]\n    start = temp[temp[\"event\"] == strt_event]\n    if len(temp[temp[\"event\"] == strt_event]) != 1:\n      continue\n    temp = temp.loc[start.index.values[0]: , [\"x\",\"y\"]]\n    if injury.FieldType[i] == 'Natural':\n        ax.plot(temp.x,temp.y, color='blue', alpha = 0.6)\n    else:\n        ax2.plot(temp.x,temp.y, color='red', alpha = 0.6)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### All injury Paths"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"../input/document-images/nfl_injury_paths.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"It is diffucult to see any visual difference between paths. You might see a bit more traffic over the center on natural fields vs synthetic. So maybe heat maps might show a different picture.\n\n## Heat Map of Field positions"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"custom_bucket_x = np.linspace(0, 120, 121)\ncustom_bucket_y = np.linspace(-10, 60, 71)\nplayertrack2 = playertrack\nplayertrack2['x_buk'] = pd.cut(playertrack['x'], custom_bucket_x)\nplayertrack2['y_buk'] = pd.cut(playertrack['y'], custom_bucket_y)\nheat_df = playertrack2[['x_buk','x','y_buk','y']].groupby(['y_buk','x_buk']).count()\nx_axis_labels = range(0,120)\ny_axis_labels = range(-10,60)\n\nplt.figure(figsize=(25,10))\nax = sns.heatmap(np.split(np.array(heat_df.x.values), 70),cmap=\"YlGnBu\",xticklabels=x_axis_labels, yticklabels=y_axis_labels,alpha = 0.5)\nax.imshow(img,zorder=0,  extent=[0.0,120.0, 63.0, 10.0],\n          aspect = ax.get_aspect())\n          #extent = ax.get_xlim() + ax.get_ylim())\nax.set_title('Field Traveled')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Above is the heatmap of most frequently traveled areas of the field. It appears to have two oval like shapes that naturally occur due to the way the game is played with most drives starting around the 20yard line and ending with decreasing probability with every yard as they approach the endzone.  "},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"playertrack3 = pd.merge(injury[['PlayKey']].drop_duplicates(),playertrack2[['PlayKey', 'x', 'y', 'x_buk','y_buk']].drop_duplicates(),'left',['PlayKey'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"heat_df = playertrack3[['x_buk','x','y_buk','y']].groupby(['y_buk','x_buk']).count().fillna(0)\nplt.figure(figsize=(25,10))\nax = sns.heatmap(np.split(np.sqrt(np.array(heat_df.x.values)), 70),cmap=sns.light_palette(\"red\"),xticklabels=x_axis_labels, yticklabels=y_axis_labels,alpha = 0.3)\nax.imshow(img,zorder=0,  extent=[0.0,120.0, 63.0, 10.0],\n          aspect = ax.get_aspect())\nax.set_title('Field Traveled Injuries')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":""},{"metadata":{},"cell_type":"markdown","source":"Above you can see the same heatmap for injury paths. Since there are only 105 the heatmap is more sparse but we can see that the paths are more spread out and doesn’t seem follow the same pattern as above.\n\nThis is hard to see the difference between injured players movements on field types. For this we might look at my two metrics \n\n* ZagZag Ratio: The euclidean distance between the first and last position over the total distance traveled for each play. This will give us an indication of more straight line vs zigzag runs. **(my_met)**"},{"metadata":{"trusted":true},"cell_type":"code","source":"Image(\"../input/document-images/my_met_def.png\", width = '500')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* An other indication might be directional changes. This will indicate more extreme player movements we will look at these patterns between field types and player types, **diff_os**. The number of extreme directional changes per play represented by the **os_met**. These metrics were defined earlier."},{"metadata":{},"cell_type":"markdown","source":"## diff_os Plot Example of Threshold"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.cla\nfig = plt.figure(figsize=(30,8))\nax = fig.add_subplot(121)\nax.set_ylim([-50, 50])\nax.set_xlim([0, len(diff_os_example.time)])\nax.set(xlabel='time', ylabel='diff_os')\nax.plot(np.arange(0,len(diff_os_example.time)), diff_os_example.diff_os, color=colors[0], alpha = 0.2)\nax.text(ax.get_xlim()[1]/2,ax.get_ylim()[1]-10,'Left Turn')\nax.text(ax.get_xlim()[1]/2,ax.get_ylim()[0]+10,'Right Turn')\nax.text(ax.get_xlim()[1]-40,diff_os_limits[1]+1,'Upper OS Risk Boundary')\nax.text(ax.get_xlim()[1]-40,diff_os_limits[0]-3,'Lower OS Lower Boundary')\nplt.axhline(diff_os_limits[1], color='black', linestyle='dashed', linewidth=1)\nplt.axhline(diff_os_limits[0], color='black', linestyle='dashed', linewidth=1)\nplt.title('diff_os plot example', Fontsize = 18)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## diff_os Plot All Plays"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"plt.cla\nplt.figure(figsize=(20,8))\nfor i in np.arange(0,len(players_df3)):\n    if players_df3.injured[i] == 1 :\n        plt.plot(np.array(players_df3.diff_os[i]), color='red',alpha=.2,label='Injured')\n    else:\n         plt.plot(np.array(players_df3.diff_os[i]), color='blue',alpha=.2,label='Not Injured')\nplt.legend(['Injured','Non Injured'], fontsize=16)\nplt.title('diff_os injured v non-injured', fontsize=36) \nplt.xlabel('time', fontsize=16)\nplt.ylabel('diff_os', fontsize=16)\nplt.xlim([0, 400])\nplt.ylim([-400, 400])\nplt.axhline(diff_os_limits[1], color='black', linestyle='dashed', linewidth=1)\nplt.axhline(diff_os_limits[0], color='black', linestyle='dashed', linewidth=1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"If we look at the diff_os plots above we can see the spikes represents extreme speed and orientation changes over time across all plays."},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"my_met_syn =  np.array(players_df3.loc[(players_df3['FieldType'] == 'Synthetic') & (players_df3['injured'] == 1.0), 'my_met'])\nos_met_syn =  np.array(players_df3.loc[(players_df3['FieldType'] == 'Synthetic') & (players_df3['injured'] == 1.0), 'os_met'])\nmy_met_nat =  np.array(players_df3.loc[(players_df3['FieldType'] == 'Natural') & (players_df3['injured'] == 1.0), 'my_met'])\nos_met_nat =  np.array(players_df3.loc[(players_df3['FieldType'] == 'Natural') & (players_df3['injured'] == 1.0), 'os_met'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = plt.figure(figsize=(30,10))\nax = plt.subplot(121)\nax.hist(os_met_syn,alpha=.3,density=True)\nax.axvline(np.mean(os_met_syn), color='blue', linestyle='dashed', linewidth=2)\nax.hist(os_met_nat,alpha=.3,density=True)\nax.axvline(np.mean(os_met_nat), color='orange', linestyle='dashed', linewidth=2)\nax.set_xlabel(r'value',fontsize = 16)\nax.set_ylabel('PDF',fontsize = 16)\nax.set_title('os_met Synthetic v Natural',fontsize = 18)\nax.legend(['Synthetic Mean', 'Natural Mean','Synthetic', 'Natural'],fontsize = 16)\n\nax2 = plt.subplot(122)\nax2.hist(my_met_syn,alpha=.3,density=True)\nax2.axvline(np.mean(my_met_syn), color='blue', linestyle='dashed', linewidth=2)\nax2.hist(my_met_nat,alpha=.3,density=True)\nax2.axvline(np.mean(my_met_nat), color='orange', linestyle='dashed', linewidth=2)\nax2.set_xlabel(r'value',fontsize = 16)\nax2.set_ylabel('PDF',fontsize = 16)\nax2.set_title('my_met Synthetic v Natural',fontsize = 18)\nax2.legend(['Synthetic Mean', 'Natural Mean','Synthetic', 'Natural'],fontsize = 16)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"print('os_met_nat: mean= %.3f stdv= %.3f size= %.3f' % (np.mean(os_met_nat), np.std(os_met_nat), len(os_met_nat)))\nprint('os_met_syn: mean= %.3f stdv= %.3f size= %.3f' % (np.mean(os_met_syn), np.std(os_met_syn), len(os_met_syn)))\nprint('my_met_nat: mean= %.3f stdv= %.3f size= %.3f' % (np.mean(my_met_nat), np.std(my_met_nat), len(my_met_nat)))\nprint('my_met_syn: mean= %.3f stdv= %.3f size= %.3f' % (np.mean(my_met_syn), np.std(my_met_syn), len(my_met_syn)))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Students-T Test for os_met and my_met on Synthetic v Natural Fields"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# compare samples\nstat, p = ttest_ind(os_met_nat,os_met_syn)\nprint('Statistics=%.3f, p=%.3f' % (stat, p))\n# interpret\nalpha = 0.05\nif p > alpha:\n    print('os_met Same distributions (fail to reject H0)')\nelse:\n    print('os_met Different distributions (reject H0)')\n\nstat, p = ttest_ind(my_met_nat,my_met_syn)\nprint('Statistics=%.3f, p=%.3f' % (stat, p))\n# interpret\nalpha = 0.05\nif p > alpha:\n    print('my_met Same distributions (fail to reject H0)')\nelse:\n    print('my_met Different distributions (reject H0)')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Permutation Difference of Means Test for os_met on Synthetic v Natural Fields"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"perm_test(os_met_nat,os_met_syn, 10000)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Permutation Difference of Means Test for my_met on Synthetic v Natural Fields"},{"metadata":{"trusted":true},"cell_type":"code","source":"perm_test(my_met_nat,my_met_syn, 10000)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"With a confidence level of alpha = .05, there is **not significant evidence** to reject the null hypothesis that movements on synthetic and natural fields, represented by the os_met and my_met, come from the same distribution.\n\n### Bootstrap Replicates Hypothesis Test: Differnece of Means Synthetic v Natural"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"boot_os_met = bootTest(os_met_nat,os_met_syn)\nboot_my_met = bootTest(my_met_nat,my_met_syn)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = plt.figure(figsize=(30,10))\nax = plt.subplot(121)\nax.axvline(boot_os_met[1], color='red', linestyle='dashed', linewidth=1)\nax.axvline(boot_os_met[2][0], color='black', linestyle='dashed', linewidth=1)\nax.axvline(boot_os_met[2][1], color='black', linestyle='dashed', linewidth=1)\nax.hist(boot_os_met[0], bins=50, density=True)\nax.text(boot_os_met[2][0]-2,ax.get_ylim()[1]*.5,'2.5%',fontsize=16)\nax.text(boot_os_met[2][1]+2,ax.get_ylim()[1]*.5,'2.5%',fontsize=16)\nax.text(boot_os_met[1]+0.2,ax.get_ylim()[1]*.95,'Empirical Diff: p-value= '+str(boot_os_met[3]),fontsize=12)\nax.set_title('Bootstrap Confidence Intervals and Empirical Difference of Means os_met',fontsize=18)\nax.set_xlabel(r'os_met diff of means',fontsize=16)\nax.set_ylabel('PDF',fontsize=16)\n\nax2 = plt.subplot(122)\nax2.axvline(boot_my_met[1], color='red', linestyle='dashed', linewidth=1)\nax2.axvline(boot_my_met[2][0], color='black', linestyle='dashed', linewidth=1)\nax2.axvline(boot_my_met[2][1], color='black', linestyle='dashed', linewidth=1)\nax2.hist(boot_my_met[0], bins=50, density=True)\nax2.text(boot_my_met[2][0]-.1,ax2.get_ylim()[1]*.5,'2.5%',fontsize=16)\nax2.text(boot_my_met[2][1]+.1,ax2.get_ylim()[1]*.5,'2.5%',fontsize=16)\nax2.text(boot_my_met[1]+0.002,ax2.get_ylim()[1]*.95,'Empirical Diff: p-value= '+str(boot_my_met[3]),fontsize=12)\nax2.set_title('Bootstrap Confidence Intervals and Empirical Difference of Means my_met',fontsize=18)\nax2.set_xlabel(r'my_met diff of means',fontsize=16)\nax2.set_ylabel('PDF',fontsize=16)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Look at non injured players \n\nCheck engineered features to try to answer if players movements are different between injured and non injured players. This is a random sample ignoring position. We might need a stratified sample of players from similar positions."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"os_met_list_non_inj = np.array(players_df3.loc[(players_df3['injured'] == 0.0), 'os_met'])\nos_met_list_inj = np.array(players_df3.loc[(players_df3['injured'] == 1.0), 'os_met'])\nmy_met_list_non_inj = np.array(players_df3.loc[(players_df3['injured'] == 0.0), 'my_met'])\nmy_met_list_inj = np.array(players_df3.loc[(players_df3['injured'] == 1.0), 'my_met'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = plt.figure(figsize=(30,10))\nax = plt.subplot(121)\nax.hist(os_met_list_non_inj,alpha=.3,density=True)\nax.axvline(np.mean(os_met_list_non_inj), color='blue', linestyle='dashed', linewidth=2)\nax.hist(os_met_list_inj,alpha=.3,density=True)\nax.axvline(np.mean(os_met_list_inj), color='orange', linestyle='dashed', linewidth=2)\nax.set_xlabel(r'value',fontsize = 16)\nax.set_ylabel('PDF',fontsize = 16)\nax.set_title('os_met Injured v Not Injured',fontsize = 18)\nax.legend(['Not Injured Mean','Injured Mean','Not Injured','Injured'],fontsize = 16)\n\nax2 = plt.subplot(122)\nax2.hist(my_met_list_non_inj,alpha=.3)\nax2.axvline(np.mean(my_met_list_non_inj), color='blue', linestyle='dashed', linewidth=2)\nax2.hist(my_met_list_inj,alpha=.3)\nax2.axvline(np.mean(my_met_list_inj), color='orange', linestyle='dashed', linewidth=2)\nax2.set_xlabel(r'value',fontsize = 16)\nax2.set_ylabel('PDF',fontsize = 16)\nax2.set_title('my_met Injured v Not Injured',fontsize = 18)\nax2.legend(['Not Injured Mean','Injured Mean','Not Injured','Injured'],fontsize = 16)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Students-T Test for os_met and my_met on injured v non injured"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"stat, p = ttest_ind(os_met_list_inj,os_met_list_non_inj)\nprint('Statistics=%.3f, p=%.3f' % (stat, p))\n# interpret\nalpha = 0.05\nif p > alpha:\n    print('os_met Same distributions (fail to reject H0)')\nelse:\n    print('os_met Different distributions (reject H0)')\n    \nstat, p = ttest_ind(my_met_list_inj,my_met_list_non_inj)\nprint('Statistics=%.3f, p=%.3f' % (stat, p))\n# interpret\nalpha = 0.05\nif p > alpha:\n    print('my_met Same distributions (fail to reject H0)')\nelse:\n    print('my_met Different distributions (reject H0)')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Permutation Difference of Means Test for os_met on injured v non injured"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"os_met_inj_perm_results = perm_test(os_met_list_inj,os_met_list_non_inj, 10000)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There is **little evidence to reject the null hypothesis** that the feature os_met for **both groups injured and non injured come from the same distribution.** \n\n### Permutation Difference of Means Test for my_met on injured v non injured"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"my_met_inj_perm_results = perm_test(my_met_list_inj,my_met_list_non_inj, 10000)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"There is **some evidence to reject the null hypothesis** that the feature my_met for **both groups injured and non injured come from the same distribution.**\n\n### Bootstrap Replicates Hypothesis Test: Differnece of Means Injured v Not Injured"},{"metadata":{"trusted":true},"cell_type":"code","source":"boot_os_met = bootTest(os_met_list_inj,os_met_list_non_inj)\nboot_my_met = bootTest(my_met_list_inj,my_met_list_non_inj)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig = plt.figure(figsize=(30,10))\nax = plt.subplot(121)\nax.axvline(boot_os_met[1], color='red', linestyle='dashed', linewidth=1)\nax.axvline(boot_os_met[2][0], color='black', linestyle='dashed', linewidth=1)\nax.axvline(boot_os_met[2][1], color='black', linestyle='dashed', linewidth=1)\nax.hist(boot_os_met[0], bins=50, density=True)\nax.text(boot_os_met[2][0]-2,ax.get_ylim()[1]*.5,'2.5%',fontsize=16)\nax.text(boot_os_met[2][1]+2,ax.get_ylim()[1]*.5,'2.5%',fontsize=16)\nax.text(boot_os_met[1]+0.2,ax.get_ylim()[1]*.95,'Empirical Diff: p-value= '+str(boot_os_met[3]),fontsize=12)\nax.set_title('Bootstrap Confidence Intervals and Empirical Difference of Means os_met',fontsize=18)\nax.set_xlabel(r'os_met diff of means',fontsize=16)\nax.set_ylabel('PDF',fontsize=16)\n\nax2 = plt.subplot(122)\nax2.axvline(boot_my_met[1], color='red', linestyle='dashed', linewidth=1)\nax2.axvline(boot_my_met[2][0], color='black', linestyle='dashed', linewidth=1)\nax2.axvline(boot_my_met[2][1], color='black', linestyle='dashed', linewidth=1)\nax2.hist(boot_my_met[0], bins=50, density=True)\nax2.text(boot_my_met[2][0]-.1,ax2.get_ylim()[1]*.5,'2.5%',fontsize=16)\nax2.text(boot_my_met[2][1]+.1,ax2.get_ylim()[1]*.5,'2.5%',fontsize=16)\nax2.text(boot_my_met[1]+0.002,ax2.get_ylim()[1]*.95,'Empirical Diff: p-value= '+str(boot_my_met[3]),fontsize=12)\nax2.set_title('Bootstrap Confidence Intervals and Empirical Difference of Means my_met',fontsize=18)\nax2.set_xlabel(r'my_met diff of means',fontsize=16)\nax2.set_ylabel('PDF',fontsize=16)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Even though there is not significant evidence to reject the null hypothesis there is some evidence to suggest a difference which might need larger samples to see. I also would have expected the opposite of the distribution on the right. The my_met measures the log-ratio of distance traveled and this suggests that injured players have less curvy routes which might mean sharper turns. \n\n## H02: Player movement patterns impact risk of injury.\n<a id='H02'></a> \n\nOne method for looking at factors that impact risk of injury is to build a model and look at the feature importance to help interpret their impact to the target variable. In this case we will build a model to predict if a player is injured or not, a binary prediction. There are both continuous and categorical features. I will look at two different models logistic regression, a classic model for binary predictions, and Gradient Boosted Decision Trees or XGBoost. The first assumes a linear relationship and the second a nonlinear relationship. Both are important to see when trying to build a ML model that best represents your data. Interpretation of these models are vary so I will use the **Shapley Additive Explanation** method used in the **SHAP** package which is model agnostic. First I will need to convert variabels to binary or OHE for modeling and perform a correlation analysis to help select proper features. A correlation analysis looks at the linear relationship between the target variable and the predictors or features. It however, doesn't inform you on the non-linear relationships. \n\n### Shapley Additive Explanations (SHAP)\n\nThis package removes the necessity of training a second model to try to interpret the first. This also prevents any loss of context that is not shared between the two models. I.e if you have a linear model and use a tree based model to look at variable importance. This method allows for both global and individual importance. You can inspect each prediction and the features that contributed to the final output.\n\nYou can review the documentation and papers in these resources:\n\n* [Paper](arxiv.org/abs/1705.07874): Lundberg, et al. “A Unified Approach to Interpreting Model Predictions.” ArXiv.org, 25 Nov. 2017, arxiv.org/abs/1705.07874.\n* [Documentation](https://shap.readthedocs.io/en/latest/)\n* [GitHub](https://github.com/slundberg/shap)\n\n<a href='#ToTop'>Back To Top</a>\n\n### Logistic Regression"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# Data preprocessing, convert categorical variabels to OHE, scale continuous variables \nplayers_df = players_df3\ncols = ['PlayKey','GameID', 'injured','StadiumType', 'FieldType', 'Temperature', 'Weather','PlayType', 'Position', 'PositionGroup', 'my_met', 'os_met']\n\ndf = pd.concat([players_df[cols] ,pd.get_dummies(players_df['Weather'], prefix='Weather',drop_first=True)],axis=1).drop(['Weather'],axis=1)\nfor col in ['StadiumType', 'FieldType', 'PlayType', 'Position', 'PositionGroup']:\n    df = pd.concat([df,pd.get_dummies(df[col], prefix=col,drop_first=True)],axis=1).drop([col],axis=1)\n    \nfeatures = ['Temperature', 'my_met', 'os_met']\n# Separating out the continuous features\ndf_scale= df.loc[:, features].values\n# Standardizing the features\ndf_scale = StandardScaler().fit_transform(df_scale)\ndf_scale = pd.DataFrame(df_scale)\ndf_scale.columns = np.array(['Temperature', 'my_met', 'os_met'])\n\ndf_scale = pd.concat([df.drop(['Temperature', 'my_met', 'os_met'],axis=1), df_scale], axis=1)\n\ndf_scale_bs = df_scale.sample(10000,replace=True) #boostrap sample for stable interpretatino of feature importance\n\nx_cols = ['Weather_cloudy', 'StadiumType_outdoor', 'FieldType_Synthetic', 'PlayType_Field Goal','PlayType_Kickoff','PlayType_Punt',\n          'PlayType_Punt Not Returned', 'PlayType_Punt Returned', 'PlayType_Rush','Position_DT','Position_OLB','Position_RB', 'Position_SS', \n          'Position_T', 'Position_TE', 'PositionGroup_DL', 'PositionGroup_LB','PositionGroup_OL', 'PositionGroup_TE', 'Temperature', 'my_met',\n          'os_met'] #select only relevent variables","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"corr = df_scale_bs[x_cols].corr()\nmask = np.zeros_like(corr, dtype=np.bool)\nmask[np.triu_indices_from(mask)] = True\nfig, ax = plt.subplots(figsize=(10,10))\nax = sns.heatmap( corr,mask=mask, vmin=-1, vmax=1, center=0, cmap=sns.diverging_palette(20, 220, n=200), square=True, cbar_kws={\"shrink\": 0.8} )\nax.set_xticklabels(ax.get_xticklabels(),rotation=45,horizontalalignment='right')\nax.set_title('Coorelation Heatmap for select Variables')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"X=df_scale_bs[x_cols]\ny=df_scale_bs['injured']\nX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)\n\nmodel = LogisticRegression(penalty=\"l2\", solver = 'liblinear')\nmodel.fit(X_train, y_train)\n\ny_pred = model.predict(X_test)\nLR_conf_mat = confusion_matrix(y_test, y_pred)\nprint(LR_conf_mat)\nprint('Accuracy of LR logistic regression classifier on test set: ' + str(round((LR_conf_mat[1][1] +LR_conf_mat[0][0]) / LR_conf_mat.sum(),4) ))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Using logistic regression we can see the accuracy is fairly good, near 70% accurate. Below we look more at the specific feature importance with SHAP."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"explainer = shap.LinearExplainer(model, X_train, feature_dependence = \"independent\")\nshap_values = explainer.shap_values(X_train)\nshap.summary_plot(shap_values, X_train, plot_type=\"bar\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Above you can see that os_met and my_met were not as significant as other such as FieldType_Synthetic which is the most important. This might simply mean that the linear relationship is not as strong as others. It should be noted however that the positions with the highest importance are OLB, SS, DT, RB, and TE. The most important Play_Types are punt, kickoff, and rush."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"shap.summary_plot(shap_values, X_train)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Below is the reciever operating characteristics curve (ROC) which is shows the relationship of the true positives rate to false positive rate. The area under the curve (AUC) represents the usefulness of the model and good it is at distinguishing the different classes, injured v not injured."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"logit_roc_auc = roc_auc_score(y_test, model.predict(X_test))\nfpr, tpr, thresholds = roc_curve(y_test, model.predict_proba(X_test)[:,1])\nplt.figure()\nplt.plot(fpr, tpr, label='Logistic Regression (area = %0.2f)' % logit_roc_auc)\nplt.plot([0, 1], [0, 1],'r--')\nplt.xlim([0.0, 1.0])\nplt.ylim([0.0, 1.05])\nplt.xlabel('False Positive Rate')\nplt.ylabel('True Positive Rate')\nplt.title('Receiver operating characteristic')\nplt.legend(loc=\"lower right\")\nplt.savefig('Log_ROC')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# Get the predictions and put them with the test data.\nX_output = X_test.copy()\n#X_output.loc[:,'predict'] = np.round(logreg.predict(X_output),2)\n\n# Randomly pick some observations\nS = X_output.sample(6)\nS\n\n# Initialize your Jupyter notebook with initjs(), otherwise you will get an error message.\nshap.initjs()\n\n# Write in a function\ndef shap_plot(j):\n    explainerModel = shap.LinearExplainer(model,X_train)\n    shap_values_Model = explainerModel.shap_values(S)\n    p = shap.force_plot(explainerModel.expected_value, shap_values_Model[j], S.iloc[[j]])\n    return(p)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Look at individual predictions and shapley values\n\nThis can be usefull to do individual anaysis on a specific player. Each observation has different values for their features and therefore different features have different levels of importance across each prediction. This can be used to help give prescriptive recomendations for teams or players to reduce future risk of injuries."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"shap_plot(0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"shap_plot(3)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Gradient Boosted Decision Tree (XGBoost)\n\nXGBoost is an ensamble of decision trees that is able to iteratively learn non-linear relationships between the features and the target variable."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"xgb_clf = XGBClassifier()\neval_set = [(X_val, y_val)]\nxgb_clf.fit(X_train, y_train, eval_metric=\"auc\", eval_set=eval_set, verbose=False)\n#xgb_clf.fit(X_train, y_train)\nscore = xgb_clf.score(X_val, y_val)\nprint('Accuracy of XGBoost classifier on test set: ' + str(round(score,2) ))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"shap_values = shap.TreeExplainer(xgb_clf).shap_values(X_train)\nshap.summary_plot(shap_values, X_train, plot_type=\"bar\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"shap.summary_plot(shap_values, X_train)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Look at some interesting dependecy plots"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"shap.dependence_plot('Temperature', shap_values, X_train)\nshap.dependence_plot('my_met', shap_values, X_train)\nshap.dependence_plot('os_met', shap_values, X_train)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"# Get the predictions and put them with the test data.\nX_output = X_test.copy()\n#X_output.loc[:,'predict'] = np.round(xgb_clf.predict(X_output),2)\n\n# Randomly pick some observations\nS = X_output.sample(6)\n\n# Initialize your Jupyter notebook with initjs(), otherwise you will get an error message.\nshap.initjs()\n\n# Write in a function\ndef shap_plot(j):\n    explainerModel = shap.TreeExplainer(xgb_clf)\n    shap_values_Model = explainerModel.shap_values(S)\n    p = shap.force_plot(explainerModel.expected_value, shap_values_Model[j], S.iloc[[j]])\n    return(p)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Look at individual predictions and shapley values"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"shap_plot(0)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"shap_plot(1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id='MODELINGconclusions'></a> \n## Conclusions from Modeling\n\nAccuracy of GLM logistic regression classifier is ~ 70%\n\nFieldType_Synthetic is the most important feature for a linear model. Inversely my features seem not to be as important. Each prediction has different inputs and therefore the various features have varying importance to each prediction. \n\nAccuracy of XGBoost classifier on test set is ~ 95%\n\nWe see that for this tree based model my_met and os_met are very important as well as FieldType_Synthetic and Temperature\n\n[Back To Top](#ToTop)"},{"metadata":{},"cell_type":"markdown","source":"<a id='FINALconclusions'></a> \n# Final Conclusions\n* Players movements across different fields were not statistically significant.\n* Players movements between injured and not injured players were not statistically significant, but appeared to have some evidence that warrants more investigation.\n* Performance drops on Synthetic Fields by about 10%\n* Even though Synthetic fields represent only about 1/3 of the fields their injury base rate is nearly double that of Natural Fields. The same is seen on indoor v outdoor as they are near proxies for field types. Base Rates of injury: \n    * Natural Injury Base Rate = 1.42%\n    * Synthetic Injury Base Rate = 2.33%\n    * Indoor Injury Base Rate = 2.43%\n    * Outdoor Injury Base Rate = 1.62%\n* Important feature for predicting injuries (XGBoost Accuracy = 95%): \n    * my_met, representing route curviness\n    * FieldType_Synthetic\n    * Temperature\n    * os_met, representing extreme orientation and speed changes \n\n# Proposed Rule/Policy Changes\n\nSince it appears it’s the combination of player movements with field type there might be some potential rules or regulation changes that the NFL can act on.\n\n1) Teams that want to convert or maintain Synthetic fields, must give justifiable reasons as to why. These reasons must reasonably outweigh the negative performance and risk of injury to players. \n\n2) Sponsors of cleats should provide proof that their cleats do not negatively impact player performance or increase risk of injury. This could be for a single cleat that works for all field types or for two separate cleats that perform best on respective field types. All evidence should be research based and subject to NFL approval to be removed if any signs develop that might contradict these conditions.\n\n3) Players can be educated about the risks of types of movements on specific field types to help prevent more risky movement patterns.\n\nTo see the results in a PPT click [here](https://elijahhall.net/2020/01/03/nfl-1st-and-future-analytics-kaggle-competit/).\n\n<a href='#ToTop'>Back To Top</a>\n\n\n## Additional Considerations\n* Annual costs for synthetic turf is about 2x that of natural turf. [Costs](https://www.safehealthyplayingfields.org/cost-grass-vs-synthetic-turf), [Forbes](https://www.forbes.com/sites/mikeozanian/2014/09/28/how-taxpayers-get-fooled-on-the-cost-of-an-artificial-turf-field/#24e38fbb5db2) \n\n* Lack of maintenence due to \"No maintence required\" selling point from sythetic turf manufacturers can increase hardness of the field and risk of injuries such as concussions.  [NYTimes](https://www.nytimes.com/2015/12/30/sports/football/concussion-report-highlights-field-maintenance.html)\n\nThe NFL has a much wider influence on the way football and other professional sports are played. Their position on synthetic vs natural turf will have broader implications than just the future NFL seasons. There is potential to affect college level football and the national conversation and decisions to use synthetic vs natural turf fields.\n\n## Asumptions\n\n* Injured players with unknown playkey were injured on the last play recorded in the game.\n* Orientation data is consistent throughout each game. Even with the NFL orientation data being off 90 degrees as long as this offset is constent then it will not affect my analysis since I use the difference in orientation.\n\n## My background\n\nI am a husband, father, veteran, and data scientist. I value the blessings I have had in my life. After serving 7 years in the United States Marine Corps I went back to school to earn a bachelors in business finance with a minor in economics. While studying full time I worked full time and managed family life with a new born child. I graduated and was accepted to Columbia University’s Masters of Science in [Applied Analytics Program](https://sps.columbia.edu/academics/masters/applied-analytics). I am a Data Science Consultant at Accenture in their Applied Intelligence practice. I am a part-time Associate Professor at Columbia University in the Applied Analytics program. I have had many hobbies and interests in my life, but I now count myself as a life long learner."}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.4","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}