{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load in \n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the \"../input/\" directory.\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# Any results you write to the current directory are saved as output.","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"from IPython.display import Image\nImage(\"/kaggle/input/nflanalyticsimages/breakawaybanner.JPG\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"This report identifies high risk factors for cleat-surface interaction injuries. I created novel player movement metrics using next gen stats and then created a machine learning model to predict the surface. As expected, this model shows that there are significant performance differences between synthetic and natural turf.  What surprised me is that it showed that weather, specifically wet or dry fields, are equally important.  The combination of surface type and weather, which I call surface conditions, play a key role in non contact injuries.  \n\nThe primary cause is simply the sport of football. The skill position players make very sharp cuts at high speeds, which are dangerous on all surfaces. Even if we replaced all synthetic fields with grass, that would only prevent 27% of the injuries. Turf design is currently constrained by the opposite objectives of high traction for performance and the need to not \"catch\" cleats to prevent injuries.  The way to escape that constraint can be found in a 50 year old idea -- breakaway cleats.  Whether ski boot style bindings or sheer pins on individual spikes, breakaway cleats would allow greater performance and greater safety.  If turf can ignore the \"catch\" requirement, they could focus on other areas such as concussion prevention.  "},{"metadata":{"_kg_hide-input":true,"trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom IPython.display import HTML, Image\nimport warnings\nimport plotly\nimport plotly.express as px\n\npd.set_option(\"display.max_columns\", 100)\nth_props = [('font-size', '13px'), ('background-color', 'white'), \n            ('color', '#666666')]\ntd_props = [('font-size', '15px'), ('background-color', 'white')]\nstyles = [dict(selector=\"td\", props=td_props), dict(selector=\"th\", \n            props=th_props)]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"play = pd.read_csv('../input/nfl-playing-surface-analytics/PlayList.csv')\ninj = pd.read_csv('../input/nfl-playing-surface-analytics/InjuryRecord.csv')\noneplayer = pd.read_parquet('/kaggle/input/breakaway-cleats-loading-track-data/oneplayer.parq')\ntrack = pd.read_parquet('/kaggle/input/breakaway-cleats-loading-track-data/track.parq')\ninjtrack = pd.read_parquet('/kaggle/input/breakaway-cleats-loading-track-data/injtrack.parq')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"<a id='ia'></a>\n<div class=\"h2\"> Analysis </div>\nWhen I analyzed the injury data, I was concerned about the low number of samples from a statistically point of view. To avoid drawing false conclusions on small sample sizes, I grouped data.  I simplified weather into wet or dry surface.  Roster positions were divided into skill positions and lineman. The adjusted injury counts are what the injury count would be if 100% of the games were played under that condition. \n\nThe Analysis shows that there are 3 large risk factors for turf/cleat injuries -- Weather, Field Type, and Roster Position.  All three factors indicate support the theory that surfaces are currently at a delicate balancing point between too much and too little traction."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"df = inj.groupby(['Surface'])['PlayerKey'].count().reset_index()\ndf.columns = ['Surface','InjuryCount']\ndf['InjuryCount'] = np.where(df.Surface==\"Natural\",df.InjuryCount*32/20,df.InjuryCount*32/12)\nfig = px.bar(df, x='Surface', y='InjuryCount',\n                 width=800, height=400)\nfig.update_layout(\n    title=\"Synthetic Fields have 2x injuries of Natural Fields\",\n    xaxis_title=\"Surface Type\",\n    yaxis_title=\"Adjusted Injury Count\",\n    font=dict(\n        family=\"Courier New, monospace\",\n        size=18\n    )\n)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Weatherdict = {\"10% Chance of Rain\": \"Dry\",\n               \"30% Chance of Rain\": \"Dry\",\n              \"Clear\":\"Dry\",\n\"Clear Skies\":\"Dry\",\n\"Clear and Cool\":\"Dry\",\n\"Clear and Sunny\":\"Dry\",\n\"Clear and cold\":\"Dry\",\n\"Clear and sunny\":\"Dry\",\n\"Clear and warm\":\"Dry\",\n\"Clear skies\":\"Dry\",\n\"Clear to Partly Cloudy\":\"Dry\",\n\"Cloudy\":\"Dry\",\n\"Cloudy and Cool\":\"Dry\",\n\"Cloudy and cold\":\"Dry\",\n\"Cloudy with periods of rain, thunder possible. Winds shifting to WNW, 10-20 mph.\":\"Dry\",\n\"Cloudy, 50% change of rain\":\"Dry\",\n\"Cloudy, Rain\":\"Wet\",\n\"Cloudy, chance of rain\":\"Dry\",\n\"Cloudy, fog started developing in 2nd quarter\":\"Dry\",\n\"Cloudy, light snow accumulating 1-3\":\"Dry\",\n\"Cold\":\"Dry\",\n\"Controlled Climate\":\"Dry\",\n\"Coudy\":\"Dry\",\n\"Fair\":\"Dry\",\n\"Hazy\":\"Dry\",\n\"Heat Index 95\":\"Dry\",\n\"Heavy lake effect snow\":\"Wet\",\n\"Indoor\":\"Dry\",\n\"Indoors\":\"Dry\",\n\"Light Rain\":\"Wet\",\n\"Mostly Cloudy\":\"Dry\",\n\"Mostly Coudy\":\"Dry\",\n\"Mostly Sunny\":\"Dry\",\n\"Mostly Sunny Skies\":\"Dry\",\n\"Mostly cloudy\":\"Dry\",\n\"Mostly sunny\":\"Dry\",\n\"N/A (Indoors)\":\"Dry\",\n\"N/A Indoor\":\"Dry\",\n\"Overcast\":\"Dry\",\n\"Partly Cloudy\":\"Dry\",\n\"Partly Clouidy\":\"Dry\",\n\"Partly Sunny\":\"Dry\",\n\"Partly clear\":\"Dry\",\n\"Partly cloudy\":\"Dry\",\n\"Partly sunny\":\"Dry\",\n\"Party Cloudy\":\"Dry\",\n\"Rain\":\"Wet\",\n\"Rain Chance 40%\":\"Dry\",\n\"Rain likely, temps in low 40s.\":\"Wet\",\n\"Rain shower\":\"Wet\",\n\"Rainy\":\"Wet\",\n\"Scattered Showers\":\"Wet\",\n\"Showers\":\"Wet\",\n\"Snow\":\"Wet\",\n\"Sun & clouds\":\"Dry\",\n\"Sunny\":\"Dry\",\n\"Sunny Skies\":\"Dry\",\n\"Sunny and clear\":\"Dry\",\n\"Sunny and cold\":\"Dry\",\n\"Sunny and warm\":\"Dry\",\n\"Sunny, Windy\":\"Dry\",\n\"Sunny, highs to upper 80s\":\"Dry\",\n\"cloudy\":\"Dry\",\n}\nRosterdict1 = {\"Cornerback\":\"Fast\",\n\"Defensive Lineman\":\"Slow\",\n\"Linebacker\":\"Medium\",\n\"Offensive Lineman\":\"Slow\",\n\"Running Back\":\"Fast\",\n\"Safety\":\"Medium\",\n\"Tight End\":\"Medium\",\n\"Wide Receiver\":\"Fast\"}\nRosterdict2 = {\n\"Cornerback\":\"Defense\",\n\"Defensive Lineman\":\"Defense\",\n\"Linebacker\":\"Defense\",\n\"Offensive Lineman\":\"Offense\",\n\"Running Back\":\"Offense\",\n\"Safety\":\"Defense\",\n\"Tight End\":\"Offense\",\n\"Wide Receiver\":\"Offense\"}\nRosterdict3={\n\"Cornerback\":\"Defender\",\n\"Defensive Lineman\":\"Blocker\",\n\"Linebacker\":\"Defender\",\n\"Offensive Lineman\":\"Blocker\",\n\"Running Back\":\"Catcher\",\n\"Safety\":\"Defender\",\n\"Tight End\":\"Catcher\",\n\"Wide Receiver\":\"Catcher\"}\nRosterdict4={\n\"Cornerback\":\"Skill\",\n\"Defensive Lineman\":\"Lineman\",\n\"Linebacker\":\"Skill\",\n\"Offensive Lineman\":\"Lineman\",\n\"Running Back\":\"Skill\",\n\"Safety\":\"Skill\",\n\"Tight End\":\"Skill\",\n\"Wide Receiver\":\"Skill\"}\nBodydict={\n\"Ankle\":\"Joint\",\n\"Foot\":\"Feet\",\n\"Heel\":\"Feet\",\n\"Knee\":\"Joint\",\n\"Toes\":\"Feet\"}\n\nRosterEncode1 = {\"Fast\":2,\n\"Medium\":1,\n\"Slow\": 0}\n\nRosterEncode = {\"Skill\":0,\n\"Lineman\":1}\n\nTurfEncode = {'Synthetic':1,'Natural':0}\nWeatherEncode = {\"Dry\":0,\"Wet\":1}\n\n\nplay['Dry']=play['Weather']\nplay = play.replace({\"Dry\": Weatherdict})\n\nplay['Dry'] = np.where(play['Dry'].isin([\"Dry\",'Wet']),play['Dry'],'Wet')\nplay['Roster']=play['RosterPosition']\nplay = play.replace({\"Roster\": Rosterdict4})\nplay['Roster1']=play['RosterPosition']\nplay = play.replace({\"Roster2\": Rosterdict1})\n\n\n\n\nplay['IsDry'] = play['Dry']\nplay['Turf'] = play['FieldType']\n\n\nplay = play.replace({'Roster':RosterEncode})\nplay = play.replace({'Roster1':RosterEncode1})\nplay = play.replace({'IsDry':WeatherEncode})\nplay = play.replace({'Turf':TurfEncode})\n\nplay = play[play.Roster.isin([0,1])].copy()\n\nplay['IsPass'] = np.where(play['PlayType'].isin(['Rush','Pass']),play['PlayType'],2)\nplay['IsPass'] = np.where(play['PlayType']=='Pass',0,play.IsPass)\nplay['IsPass'] = np.where(play['PlayType']=='Rush',1,play.IsPass)\n\nplay['Dry']=play['Weather']\nplay = play.replace({\"Dry\": Weatherdict})\n\nplay['Dry'] = np.where(play['Dry'].isin([\"Dry\",'Wet']),play['Dry'],'Wet')\nplay['Roster']=play['RosterPosition']\nplay = play.replace({\"Roster\": Rosterdict4})\nplay['Roster1']=play['RosterPosition']\nplay = play.replace({\"Roster2\": Rosterdict1})\n\nplay['IsDry'] = play['Dry']\nplay['Turf'] = play['FieldType']\n\nplay['RosterType'] = play.Roster\nplay = play.replace({'Roster':RosterEncode})\nplay = play.replace({'Roster1':RosterEncode1})\nplay = play.replace({'IsDry':WeatherEncode})\nplay = play.replace({'Turf':TurfEncode})\n\nplay = play[play.Roster.isin([0,1])].copy()\n\nplay['IsPass'] = np.where(play['PlayType'].isin(['Rush','Pass']),play['PlayType'],2)\nplay['IsPass'] = np.where(play['PlayType']=='Pass',0,play.IsPass)\nplay['IsPass'] = np.where(play['PlayType']=='Rush',1,play.IsPass)\n\nplay['PlayKey'] = play.PlayKey.fillna('0-0-0')\nid_array = play.PlayKey.str.split('-', expand=True).to_numpy()\nplay['PlayerKey'] = id_array[:,0].astype(int)\nplay['GameID'] = id_array[:,1].astype(int)\nplay['PlayKey'] = id_array[:,2].astype(int)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"inj = pd.read_csv('../input/nfl-playing-surface-analytics/InjuryRecord.csv')\ninj['injGameKey'] = inj.GameID.fillna('0-0')\nid_array = inj.injGameKey.str.split('-', expand=True).to_numpy()\ninj['GameID'] = id_array[:,1].astype(int)\ninj['PlayerKey'] = id_array[:,0].astype(int)\ninj['injKey'] = inj.PlayKey.fillna('0-0-0')\nid_array = inj.injKey.str.split('-', expand=True).to_numpy()\ninj['PlayKey'] = id_array[:,2].astype(int)\ninjplay = inj[inj['PlayKey']>0]\ninjplay = injplay.merge(play, on=['PlayerKey','GameID','PlayKey'])\ngame = play[['GameID','PlayerKey','Dry','Roster','Turf']].drop_duplicates()\ninjgame = inj.merge(game, on=['GameID','PlayerKey'],how='left')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"df = injplay.groupby(['RosterType'])['PlayerKey'].count().reset_index()\ndf.columns = ['RosterType','InjuryCount']\nfig = px.bar(df, x='RosterType', y='InjuryCount',\n                 width=800, height=400)\nfig.update_layout(\n    title=\"Skill Positions have 5x injuries of Lineman\",\n    xaxis_title=\"\",\n    yaxis_title=\"Injury Count\",\n    font=dict(\n        family=\"Courier New, monospace\",\n        size=18\n    )\n)\nfig.show()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"df = injgame.groupby(['Dry','Surface'])['PlayerKey'].count().reset_index()\ndf.columns = ['Dry','FieldType','InjuryCount']\ndf2= play.groupby(['Dry','FieldType'])['PlayerKey'].count().reset_index()\ndf =df.merge(df2)\n#Adjustment factor = number of plays/number of plays under that condition\ndf['AdjustedCount']=df['InjuryCount']*len(play)/df['PlayerKey']\nfig = px.bar(df, x='FieldType', y='AdjustedCount', color='Dry',barmode='group' ,\n                 width=800, height=400)\nfig.update_layout(\n    title=\"Rain makes synthetic better and natural worse\",\n    xaxis_title=\"Weather\",\n    yaxis_title=\"Adjusted Injury Count\",\n    font=dict(\n        family=\"Courier New, monospace\",\n        size=18\n    )\n)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### <a id='st'></a>\n<div class=\"h2\"> Statistics </div>\nIn this section, I looked at performance under the 4 types of surface conditions (wet/dry, natural/synthetic).  There are significant differences in speed and change of direction between the 4 groups.  In the graphs below, I looked at max values for each play."},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Play_trk=play.merge(track,on=['PlayerKey','GameID','PlayKey'])\nPlay_trk['DryTurf']=Play_trk['Dry'].astype(str)+Play_trk['FieldType'].astype(str)\ndfMax = Play_trk.groupby(['GameID','PlayKey']).max().reset_index()\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = px.box(dfMax, x=\"DryTurf\", y=\"dis\",\n                 width=800, height=500)\nfig.update_layout(\n    title=\"Differences in Max Speed\",\n    xaxis_title=\"Surface Type + Weather\",\n    yaxis_title=\"Max Speed\",\n    font=dict(\n        family=\"Courier New, monospace\",\n        size=18\n    )\n)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"\nfig = px.box(dfMax, x=\"DryTurf\", y=\"Delta_Dir_2\",\n                 width=800, height=600)\nfig.update_layout(\n    title=\"Differences in Max Change of Direction\",\n    xaxis_title=\"Surface Type + Weather\",\n    yaxis_title=\"Max Change of Direction\",\n    font=dict(\n        family=\"Courier New, monospace\",\n        size=18\n    )\n)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"fig = px.box(dfMax[dfMax.Delta_Dis_2<0.2], x=\"DryTurf\", y=\"Delta_Dis_2\",\n                 width=800, height=600)\nfig.update_layout(\n    title=\"Differences in Max Acceleration\",\n    xaxis_title=\"Surface Type + Weather\",\n    yaxis_title=\"Max Acceleration\",\n    font=dict(\n        family=\"Courier New, monospace\",\n        size=18\n    )\n)\nfig.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### <a id='ml'></a>\n<div class=\"h2\"> Machine Learning Model </div>\n**Variables**\n1. Calculated the differences in dis,direction,orientation, and angle (direction-orientation) from one row to the other. I used dis as a proxy for speed because speed appeared to have some data quality issues.\n2. Rolling mean and standard deviation over 5 and 10 rows (0.5 to 1 second).  This time period was picked because it correlates with approximately the time it takes an athlete to perform a cut.\n\n**Initial Results**\n\nThe first model showed that playerkey was by far the most important variable. In other words, the best way to know whether a surface is natural or synthetic is to focus on the actions of an individual player on both.  For the next models, I used only the data from one player.\n\n**Models Created**\n1. Predict Turf given next gen stats (AUC = 0.94).  The most important feature by far was weather. \n2. Predict Weather given next gen stats (AUC = 0.88). The most important feature was turf.\n\nDetails can be found in the referenced notebooks.\n\n\n"},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"/kaggle/input/breakaway-cleats-lightgbm/shap.png\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"Image(\"/kaggle/input/breakaway-cleats-lightgbm/roc.png\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"#### <a id='c'></a>\n<div class=\"h2\"> Conclusion </div>\nChanges in surface conditions impact the quality of the play and increase the risk of injury.  It is impossible to create a perfect field and control the weather.  It is possible to design a cleat that will prevent non contact injuries."}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":1}