{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<h3 style='background:green; border:0; color:white'><center>Quick Navigation<center><h3>","metadata":{}},{"cell_type":"markdown","source":"## TABLE OF CONTENTS\n* [Introduction](#one)\n* [Landing areas prediction](#two)\n    - [Data Preparation](#a)\n    - [Model Building](#b)\n        - [Random Forest](#a)\n        - [Neural Network](#b)\n    - [Model Prediction](#c)\n* [Ranking returners](#three)\n* [Dynamic Voronoi](#four)\n* [Conclusion and future work](#five)","metadata":{}},{"cell_type":"markdown","source":"American Football is the most popular sport in USA, and nearly 96.4 million viewers each year watching this great and excited event in the USA. There are lots of fans and data scientists researching on it. \nSpecial team is a rarily research topic in the past years. With more and more data available, we as an ordinary people can analyze it. \nSpecial teams have the responsibility of **creating points, as well as protecting points and field position**. Therefore, it is worth researching on it.\n# **<font color=#ff0000 size=4 face=\"黑体\"> “ You are what your record says you are.” </font>** \n>                             - Former NFL Head Coach Bill Parcells authored the well-known quote","metadata":{}},{"cell_type":"markdown","source":"In this notebook, we will explore where is the landing area of the football after punting or kicking. We built some machine learning models to tackle this problem, and then we focus on ranking the best returners and drawing the important pitch control feature Voronoi area. There are so many excellent codes, models and participants in this Kaggle competition. We hope that after reading this notebook you will know how to tackle these problems:\n<h5>\n    <font color=#000000 size=2>\n1.     Where will the football land?<br>\n2.     Predicting with Machine Learning and NN. <br>\n3.     How to rank the Returners? <br>\n4.     How to draw dynamic Voronoi area?\n    </font>\n</h5>","metadata":{}},{"cell_type":"markdown","source":"# Part I : Where will the football land?","metadata":{}},{"cell_type":"markdown","source":"The first part of this notebook provides coaches and others a brand new angle to predict the landing areas after punting or kicking(kickoff). We divided the whole pitch into 13 areas: A1,A2,B1,B2,C1,C2,D1,D2,E1,E2 and F(endzone,F1 and F2). You can have a view in the following codes and a picture.","metadata":{}},{"cell_type":"code","source":"#Reading Data\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\npd.set_option(\"display.max_columns\",None)\n\ndf_games = pd.read_csv('../input/nfl-big-data-bowl-2022/games.csv')\ndf_plays = pd.read_csv('../input/nfl-big-data-bowl-2022/plays.csv')\ndf_players = pd.read_csv('../input/nfl-big-data-bowl-2022/players.csv')\ndf_pff = pd.read_csv('../input/nfl-big-data-bowl-2022/PFFScoutingData.csv')\n\ndef downcast(df, verbose=True):\n    start_mem = df.memory_usage().sum() / 1024**2\n    for col in df.columns:\n        dtype_name = df[col].dtype.name\n        if dtype_name == 'object':\n            pass\n        elif dtype_name == 'bool':\n            df[col] = df[col].astype('int8')\n        elif dtype_name.startswith('int') or (df[col].round() == df[col]).all():\n            df[col] = pd.to_numeric(df[col], downcast='integer')\n        else:\n            df[col] = pd.to_numeric(df[col], downcast='float')\n    end_mem = df.memory_usage().sum() / 1024**2\n    if verbose:\n        print('{:.1f}% Compressed'.format(100 * (start_mem - end_mem) / start_mem))    \n    return df\n\nseasons = [\"2018\", \"2019\", \"2020\"]\ndf_tracking = pd.DataFrame()\nfor s in seasons:\n    df_trackingTemp = pd.read_csv(\"../input/nfl-big-data-bowl-2022/tracking\"+s+\".csv\")\n    df_trackingTemp = downcast(df_trackingTemp,False)\n    df_tracking = df_tracking.append(df_trackingTemp)    \ndf_tracking.reset_index(inplace=True)\n\n# Landing areas definition\ndict_Y = {'None':0, 'OutOfBound':1, \n                 'A1':2,\n                 'B1':3,\n                 'C1':4,\n                 'D1':5,\n                 'E1':6,\n                 'F1':7,\n                 'A2':8,\n                 'B2':9,\n                 'C2':10,\n                 'D2':11,\n                 'E2':12,\n                 'F2':13,\n                }\n\n# Split the pitch into several areas\ndef convert_Y_to_area(df):\n    df['Y_final'] = dict_Y['None']\n    df.loc[(df['Y']<100),'Y_final'] = dict_Y['OutOfBound']\n    df.loc[(df['Y']>=100) & (df['Y']<=106),'Y_final'] = dict_Y['A1']\n    df.loc[(df['Y']>106) & (df['Y']<=108),'Y_final'] = dict_Y['B1']\n    df.loc[(df['Y']>108) & (df['Y']<=110),'Y_final'] = dict_Y['C1']\n    df.loc[(df['Y']>110) & (df['Y']<=111),'Y_final'] = dict_Y['D1']\n    df.loc[(df['Y']>111) & (df['Y']<=112),'Y_final'] = dict_Y['E1']\n    df.loc[(df['Y']>112),'Y_final'] = dict_Y['F1']\n    #df.loc[(df['Y']>112) & (df['Y']<=113),'Y_final'] = 'F1'\n    df.loc[(df['Y']>=200) & (df['Y']<=206),'Y_final'] = dict_Y['A2']\n    df.loc[(df['Y']>206) & (df['Y']<=208),'Y_final'] = dict_Y['B2']\n    df.loc[(df['Y']>208) & (df['Y']<=210),'Y_final'] = dict_Y['C2']\n    df.loc[(df['Y']>210) & (df['Y']<=211),'Y_final'] = dict_Y['D2']\n    df.loc[(df['Y']>211) & (df['Y']<=212),'Y_final'] = dict_Y['E2']\n    df.loc[(df['Y']>212),'Y_final'] = dict_Y['F2']\n    #df.loc[(df['Y']>212) & (df['Y']<=213),'Y_final'] = 'F2'\n    df.loc[(df['Y']>300),'Y_final'] = dict_Y['OutOfBound']    \n    return df\n\n# We only focus on the trackings of football\ndf_football = df_tracking.query('displayName==\"football\"')\n#Standardizing tracking data so its always in direction of kicking team vs raw on-field coordinates.\ndf_football.loc[df_football['playDirection'] == \"left\", 'x'] = 120-df_football.loc[df_football['playDirection'] == \"left\", 'x']\ndf_football.loc[df_football['playDirection'] == \"left\", 'y'] = 160/3-df_football.loc[df_football['playDirection'] == \"left\", 'y']\ndf_kickers = df_plays[~df_plays['kickerId'].isnull()]\ndf_plays_PK_ids = df_kickers.query('specialTeamsPlayType==\"Punt\" | specialTeamsPlayType==\"Kickoff\"')[['gameId','playId']]\ndf_PK_football_trackings = df_plays_PK_ids.merge(df_football,on=['gameId','playId'])\ndf_Y = df_PK_football_trackings[['x','y','event','frameId','gameId','playId']]\n# Now we transform x,y coordinate into classified area coordinate numbers\ndf_Y['Y'] = (((160/3/2) + df_Y['y']) / (160/3/2)).astype('int32')*100 + ((10+df_Y['x'])/10).astype('int32')\ndf_Y = convert_Y_to_area(df_Y)\n\ndef create_field_background(size=(10, 12)):\n    fig, ax = plt.subplots(1,1,figsize=size)    \n    ymin = 0\n    ymax = 120\n    half_w = 53.33/2\n    xmin = 0\n    xmax = 53.33    \n    ax.set_ylabel('')\n    ax.set_xticks([])\n    ax.set_yticks([])\n    ax.set_ylim([0,120])\n    #ax.legend()\n    ax.set_xlim([xmin,xmax])\n    ax.set_facecolor('green')\n    ax.patch.set_alpha(0.3)\n    x = 10\n    ax.text(x,  26,  \"A2\", fontsize=30, color=\"#ffffff\")\n    ax.text(x, 56, \"B2\", fontsize=30, color=\"red\")\n    ax.text(x, 76, \"C2\", fontsize=30, color=\"#ffffff\")\n    ax.text(x, 92, \"D2\", fontsize=30, color=\"#ff0000\")\n    ax.text(x, 103, \"E2\", fontsize=30, color=\"#ffffff\")\n    x = 38\n    ax.text(x,  26,  \"A1\", fontsize=30, color=\"#ffffff\")\n    ax.text(x, 56, \"B1\", fontsize=30, color=\"red\")\n    ax.text(x, 76, \"C1\", fontsize=30, color=\"#ffffff\")\n    ax.text(x, 92, \"D1\", fontsize=30, color=\"#ff0000\")\n    ax.text(x, 103, \"E1\", fontsize=30, color=\"#ffffff\")\n    ax.text(x-10, 112, \"F\", fontsize=30, color=\"#ffffff\")\n    ax.hlines([50,70,90,100,110], xmin, xmax, \"white\", linestyles='dashed') \n    ax.vlines([half_w], ymin, ymax-10, \"yellow\", linestyles='dashed') \n    plt.fill_between([0,53.3],[120,120],[110],facecolor=\"red\",alpha=0.3)\n    plt.fill_between([0,half_w],[110,110],[100,100],facecolor=\"#FF00FF\",alpha=0.3)\n    plt.fill_between([half_w,53.3],[110,110],[100,100],facecolor=\"blue\",alpha=0.3)\n    plt.fill_between([0,half_w],[100,100],[90,90],facecolor=\"#00FFFF\",alpha=0.3)\n    plt.fill_between([half_w,53.3],[100,100],[90,90],facecolor=\"green\",alpha=0.3)\n    plt.fill_between([0,half_w],[90,90],[70,70],facecolor=\"#D7FFEE\",alpha=0.3)\n    plt.fill_between([half_w,53.3],[90,90],[70,70],facecolor=\"gold\",alpha=0.3)\n    plt.fill_between([0,half_w],[70,70],[50,50],facecolor=\"#AAFFAA\",alpha=0.3)\n    plt.fill_between([half_w,53.3],[70,70],[50,50],facecolor=\"lime\",alpha=0.3)\n    plt.fill_between([0,53.3],[50,50],facecolor=\"azure\",alpha=0.3)    \n    ax.legend(bbox_to_anchor=(1,0),loc='lower right', fontsize=40) # 标签位置    \n    return fig,ax\n\ndef draw_event_landed_points(df,event_name,size=1):\n    fig,ax = create_field_background()\n    groups = df.query('event==@event_name').groupby('event')\n    groups.get_group(event_name).plot(x='y', y='x', ax=ax, style='.', label='Football', color='red', markersize=size)\n    ax.set_xlabel('')\n\ndraw_event_landed_points(df_Y,\"kick_received\",3)","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:33:09.040838Z","iopub.execute_input":"2022-01-05T17:33:09.041305Z","iopub.status.idle":"2022-01-05T17:35:27.476534Z","shell.execute_reply.started":"2022-01-05T17:33:09.041202Z","shell.execute_reply":"2022-01-05T17:35:27.47587Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### From the above graph, we can see clearly that most of kick_received happened downfield. The areas are E1,E2 and F(endzone), which means that the football after kickoff will land these areas mostly. We should avoid the football into B1 and B2. Now our purpose is to predict where is the ball landing areas, so that our coaches can guide their players in the match or as their reference.","metadata":{}},{"cell_type":"markdown","source":"# Data Preparation","metadata":{}},{"cell_type":"markdown","source":"### How can we know the landing area or can we predict the landing area by using machine learning method?\nFirstly, we mapped 13 areas into categories(1,2,3,...,12,13). Then we get the prediction number or the probabilities of these 13 areas. Therefore, the model will select the number(or area) with the greatest probablity as the prediction result in machine learning model. In the tracking data, there are some 'event' taht denote the football landing frame and the x,y coordinates, so we can know where the football landed and in which area. We select these events('kick_received', 'punt_land', 'punt_downed', 'out_of_bounds', 'fair_catch', 'touchback', 'kickoff_land') as the important landing denoting events. You can see the number(Y_final) in the following table.","metadata":{}},{"cell_type":"code","source":"df_temp = df_Y.query('event==\"punt_received\" | event==\"kick_received\" | \\\n                                event == \"punt_land\" | event == \"punt_downed\" | \\\n                                event==\"out_of_bounds\" | event==\"fair_catch\"| \\\n                                event ==\"touchback\" | event == \"kickoff_land\"')\ndf_temp = df_temp[['gameId','playId','frameId','Y_final']]\ndf_temp = df_temp.groupby(['gameId','playId'])\n\ndf_Y_final = pd.DataFrame()\nfor key,df_value in df_temp:\n    if (df_Y_final.empty):\n        df_Y_final = df_value.head(1)\n    else:\n        df_Y_final = df_Y_final.append(df_value.iloc[0])\n        \ndf_Y_final = df_Y_final.drop('frameId',axis=1)\ndf_Y_final","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:35:27.477851Z","iopub.execute_input":"2022-01-05T17:35:27.478072Z","iopub.status.idle":"2022-01-05T17:35:46.463372Z","shell.execute_reply.started":"2022-01-05T17:35:27.47804Z","shell.execute_reply":"2022-01-05T17:35:46.462665Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In the second step, we must select some features to predict the Y_final(or areas). The features include some tracking columns in the first event (such as 'ball_snap', 'kick_off', which means that a play began and the data in the same frameId will be our X data for training and predicting), the height,weight,age of a player(kicker or punter), some features in PFFScouting data.","metadata":{}},{"cell_type":"markdown","source":"#### The first event data","metadata":{}},{"cell_type":"code","source":"df_temp = df_Y_final.merge(df_plays,on=['gameId','playId'])\ndf_temp.rename({'kickerId':'nflId'},axis=1,inplace=True)\ndf_X_tracking = df_temp.merge(df_tracking,on = ['gameId','playId','nflId'])\ndf_X_tracking.drop('index',inplace=True,axis=1)\ndf_first_event = pd.DataFrame()\ngroups = df_X_tracking.groupby(['gameId','playId'],as_index=False)\nfor key,df_value in groups:    \n    df_track1 = df_value[df_value['event']!=\"None\"]\n    if (df_first_event.empty):\n        df_first_event = df_track1.head(1)#pd.DataFrame({},columns=df0['index'])\n    else:\n        df_first_event = df_first_event.append(df_track1.iloc[0])    \n    \nkeep_cols = ['gameId','playId','frameId','x','y','s','a','dis','o','dir','nflId','specialTeamsPlayType']\ndf_PK_tracking_all= df_first_event[keep_cols]","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:35:46.464565Z","iopub.execute_input":"2022-01-05T17:35:46.465735Z","iopub.status.idle":"2022-01-05T17:38:34.115667Z","shell.execute_reply.started":"2022-01-05T17:35:46.465692Z","shell.execute_reply":"2022-01-05T17:38:34.114878Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Players' height, weight and age","metadata":{}},{"cell_type":"code","source":"# convert height to meter\nh_ft_in = (df_players.height.str.contains('-'), 'height')\ndf_players.loc[h_ft_in] = df_players.loc[h_ft_in].str.split('-').str[0].astype(int)*12 \\\n    + df_players.loc[h_ft_in].str.split('-').str[1].astype(int)\ndf_players['height'] = df_players.height.astype(int) / 39.37\n# convert birthday to years old\ndf_players['birthDate'] = pd.to_datetime(df_players['birthDate'], infer_datetime_format = True) \ndf_players['birthDate'] = np.round((pd.Timestamp.now() - df_players['birthDate']).dt.days/365)\ndf_players = df_players.rename(columns={'birthDate': 'age'})\ndf_players['age'] = round(df_players['age'].fillna(df_players['age'].mean()), 2)\ndf_players_X = df_players[['nflId','height','weight','age']]\ndf_PK_tracking_all_players = df_PK_tracking_all.merge(df_players_X,on='nflId')","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:38:34.117504Z","iopub.execute_input":"2022-01-05T17:38:34.117784Z","iopub.status.idle":"2022-01-05T17:38:34.169286Z","shell.execute_reply.started":"2022-01-05T17:38:34.117748Z","shell.execute_reply":"2022-01-05T17:38:34.168622Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### PFFScouting data","metadata":{}},{"cell_type":"code","source":"df_X = df_pff[['gameId','playId','hangTime','kickType','kickDirectionActual','returnDirectionActual','kickoffReturnFormation','kickContactType']]\ndf_X = df_PK_tracking_all.merge(df_X,on=['gameId','playId'])\ndf_pkType = pd.get_dummies(df_X['specialTeamsPlayType'],prefix=\"pk\")\ndf_kickType = pd.get_dummies(df_X['kickType'],prefix=\"kickType\")\ndf_kick_direction = pd.get_dummies(df_X['kickDirectionActual'],prefix='kick_dire')\ndf_re_direction = pd.get_dummies(df_X['returnDirectionActual'],prefix=\"re_dire\")\ndf_formation = pd.get_dummies(df_X['kickoffReturnFormation'],prefix=\"formation\")\ndf_contackType = pd.get_dummies(df_X['kickContactType'],prefix=\"kickContactType\")\nPFF_temp_df = pd.concat([df_X[['gameId','playId','hangTime']],df_pkType,df_kickType,df_kick_direction,df_re_direction,df_formation,df_contackType],axis=1)\nPFF_temp_df['hangTime'] = round(PFF_temp_df['hangTime'].fillna(PFF_temp_df['hangTime'].mean()), 2)","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:38:34.170659Z","iopub.execute_input":"2022-01-05T17:38:34.171029Z","iopub.status.idle":"2022-01-05T17:38:34.206942Z","shell.execute_reply.started":"2022-01-05T17:38:34.170991Z","shell.execute_reply":"2022-01-05T17:38:34.206198Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### Merging X and Y ","metadata":{}},{"cell_type":"code","source":"df_X_Final = df_PK_tracking_all_players.merge(PFF_temp_df,on=['gameId','playId'])\ndf_train = df_X_Final.merge(df_Y_final,on=['gameId','playId'])","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:38:34.208092Z","iopub.execute_input":"2022-01-05T17:38:34.208343Z","iopub.status.idle":"2022-01-05T17:38:34.230203Z","shell.execute_reply.started":"2022-01-05T17:38:34.208309Z","shell.execute_reply":"2022-01-05T17:38:34.229523Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Machine learning models for training and predicting","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nfrom xgboost import XGBClassifier\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.linear_model import SGDClassifier\nfrom sklearn.svm import SVC, LinearSVC\nfrom sklearn.tree import DecisionTreeClassifier\nfrom sklearn.ensemble import RandomForestClassifier\nfrom sklearn.metrics import accuracy_score\n# cols_to_use = ['hangTime', 'x', 'y', 'weight','height','dis','dir','a','age']\ncols_to_use = ['x', 'y', 's', 'a', 'dis', 'o', 'dir',\n       'height', 'weight', 'age', 'hangTime', 'pk_Kickoff', 'pk_Punt',\n       'kickType_A', 'kickType_B', 'kickType_D', 'kickType_F', 'kickType_K',\n       'kickType_N', 'kickType_O', 'kickType_P', 'kickType_Q', 'kickType_R',\n       'kickType_S', 'kick_dire_C', 'kick_dire_L', 'kick_dire_R', 're_dire_C',\n       're_dire_L', 're_dire_R', 'formation_10-0-0', 'formation_5-0-4',\n       'formation_5-3-2', 'formation_6-0-3', 'formation_6-0-4',\n       'formation_6-2-2', 'formation_7-0-3', 'formation_7-1-2',\n       'formation_8-0-1', 'formation_8-0-2', 'formation_8-0-3',\n       'formation_8-1-0', 'formation_8-1-1', 'formation_9-0-0',\n       'formation_9-0-1', 'formation_9-1-0', 'kickContactType_BB',\n       'kickContactType_BC', 'kickContactType_BF', 'kickContactType_BOG',\n       'kickContactType_CC', 'kickContactType_CFFG', 'kickContactType_DEZ',\n       'kickContactType_ICC', 'kickContactType_KTB', 'kickContactType_KTC',\n       'kickContactType_KTF', 'kickContactType_MBC', 'kickContactType_MBDR',\n       'kickContactType_OOB']\nX = df_train[cols_to_use]\ny = df_train.Y_final\nX_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.3, random_state = 11)\nX_valid,X_test ,y_valid , y_test =train_test_split(X_valid,y_valid,test_size=0.5,random_state=42)\n\nmodels = {\n    \"XGBClassifier\": XGBClassifier(),\n    \"Decision Tree Classifier\": DecisionTreeClassifier(),\n    \"Random Forest Classifer\": RandomForestClassifier(random_state = 5)         \n}\n\nfor name, model in models.items():\n    model.fit(X_train, y_train)\n    print(name + \" trained\")\n    \nprint(\"-------------------------\", '\\n')\n\nfor name, model in models.items():\n    print(name)\n    predictions = model.predict(X_valid)\n    print(\"y_valid Accuracy: %.2f%%\" % (accuracy_score(y_valid, predictions, normalize=True) * 100.0))\n    predictions=model.predict(X_test)\n    print(\"y_test Accuracy: %.2f%%\" % (accuracy_score(y_test, predictions, normalize=True) * 100.0))","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:38:34.232103Z","iopub.execute_input":"2022-01-05T17:38:34.232627Z","iopub.status.idle":"2022-01-05T17:39:06.089862Z","shell.execute_reply.started":"2022-01-05T17:38:34.23257Z","shell.execute_reply":"2022-01-05T17:39:06.088976Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Nerual Network Model","metadata":{}},{"cell_type":"code","source":"from keras.layers import Dense,Input,Flatten,concatenate,Dropout,Lambda,BatchNormalization,LeakyReLU\nfrom keras.models import Model\nfrom keras.callbacks import Callback\nfrom  keras.callbacks import EarlyStopping,ModelCheckpoint\nfrom tensorflow.keras.utils import plot_model\nfrom sklearn.model_selection import train_test_split, KFold\nimport time\n\ndef get_model(x_tr,y_tr,x_val,y_val,x_test,y_test):\n    inp = Input(shape = (x_tr.shape[1],))\n    x = Dense(1024, input_dim=X.shape[1])(inp)\n    x = LeakyReLU(alpha=0.3)(x)\n    x = Dropout(0.2)(x)\n    x = BatchNormalization()(x)\n    x = Dense(512)(x)\n    x = LeakyReLU(alpha=0.2)(x)\n    x = Dropout(0.2)(x)\n    x = BatchNormalization()(x)\n    x = Dense(256)(x)\n    x = LeakyReLU(alpha=0.1)(x)\n    x = Dropout(0.2)(x)\n    x = BatchNormalization()(x)\n    \n    out = Dense(13, activation='softmax')(x)\n    model = Model(inp,out)\n\n    model.compile(optimizer='adam', loss='categorical_crossentropy',weighted_metrics=['accuracy'])    \n    bsz = 100\n    steps = x_tr.shape[0]/bsz\n    call_backs = EarlyStopping(monitor='val_loss', min_delta=0, patience=150, verbose=1, restore_best_weights=True)\n    check_point = ModelCheckpoint('/kaggle/working/best_model.tf',save_best_only=True, verbose=1, save_weights_only=True)\n\n    model.fit(x_tr, y_tr, epochs=100, batch_size=bsz,verbose=1,callbacks=[call_backs,check_point])\n\n    print('Starting the prediction of x_val')\n    y_pred = model.predict(x_val)\n    y_pred = model.predict(x_test)\n    argmax_pred = np.argmax(y_pred,axis=1)\n    argmax_true=np.argmax(y_test,axis=1)\n    comp = argmax_pred-argmax_true\n    error = np.count_nonzero(comp)\n    total = y_test.shape[0]\n    accuracy = 1- error/total\n    print(\"this is accuracy:\")\n    print(accuracy)\n\n    return model\n\n\nareas = df_train.Y_final\ny = np.zeros((areas.shape[0], 13))\nfor idx, target in enumerate(list(areas)):\n    y[idx][target-1] = 1\n\ns_time = time.time()\nfor k in range(1):\n    kfold = KFold(2, random_state = 42 + k, shuffle = True)    \n    for k_fold, (tr_inds, val_inds) in enumerate(kfold.split(areas)):\n        print(\"---------------------------------\")\n        print(\"The %dth fold: \"%(k_fold+1))\n        print(\"---------------------------------\")\n        tr_x,tr_y = X.iloc[tr_inds],y[tr_inds]\n        val_x,val_y = X.iloc[val_inds],y[val_inds]\n        val_x, test_x, val_y, test_y = train_test_split(val_x, val_y, test_size=0.01)\n        val_x, test_x2, val_y, test_y2 = train_test_split(val_x, val_y, test_size=0.5)\n        \n        tr_x = pd.concat([tr_x,test_x2],axis=0)      \n        tr_y =np.concatenate((tr_y,test_y2),axis=0)\n        \n        model = get_model(tr_x,tr_y,val_x,val_y,test_x,test_y)","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:39:06.091482Z","iopub.execute_input":"2022-01-05T17:39:06.091952Z","iopub.status.idle":"2022-01-05T17:40:50.948411Z","shell.execute_reply.started":"2022-01-05T17:39:06.091911Z","shell.execute_reply":"2022-01-05T17:40:50.947651Z"},"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_model(model=model,show_shapes=True)","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:40:50.949483Z","iopub.execute_input":"2022-01-05T17:40:50.950447Z","iopub.status.idle":"2022-01-05T17:40:51.977333Z","shell.execute_reply.started":"2022-01-05T17:40:50.9504Z","shell.execute_reply":"2022-01-05T17:40:51.976544Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Part II\n\n## Ranking Returners","metadata":{}},{"cell_type":"markdown","source":"As I have mentioned in the introduction, returners and tacklers are both quite important for a special teams, they will give their own team a spark, so in the part II, we evaluate returners and Tacklers based our calculation. Evaluating a player is uneasy,it has to consider many sides to fully evaluate them well.For returner, we value a returner through 4 index, one is a returner who bleak how many tacklers related to a returner, we measure by missedTackler, a returner needs **energy** to run, so we measure Enery index via player's weight or his BMI multiply his speed square; the third index we find how many returner returned opponent Touch Down Zone and win his team scores. Last but not least, how many yardage that returner has rushed, we also considered.","metadata":{}},{"cell_type":"markdown","source":"### Calculating each missedTackler","metadata":{}},{"cell_type":"code","source":"df_track = df_tracking[['gameId','playId','nflId','team','jerseyNumber']]\ndef count_missedTackler(row):\n    if row=='0':\n        return 0\n    count = len(row.split(';'))\n    return count\nkeep_cols = ['gameId','playId','missedTackler']\ndf_missedTackler = df_pff[keep_cols]\ndf_missedTackler['missedTackler'] = df_missedTackler['missedTackler'].fillna('0')\ndf_missedTackler['missedTackler_count'] = df_missedTackler['missedTackler'].apply(count_missedTackler)\ndf_missedTackler.drop('missedTackler',inplace=True,axis=1)","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:40:51.980783Z","iopub.execute_input":"2022-01-05T17:40:51.981008Z","iopub.status.idle":"2022-01-05T17:40:52.583897Z","shell.execute_reply.started":"2022-01-05T17:40:51.98098Z","shell.execute_reply":"2022-01-05T17:40:52.583131Z"},"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Returners ranking","metadata":{}},{"cell_type":"code","source":"keep_cols = ['gameId','playId','possessionTeam','specialTeamsPlayType','kickReturnYardage','kickerId','returnerId']\n# keep_cols = ['gameId','playId','possessionTeam','kickerId','returnerId','kickReturnYardage']\ndf_returner = df_plays[keep_cols]\ndf_returner = df_returner[~df_returner['returnerId'].isnull()]\ndf_returner[['nflId_returner','nflId2','nflId3']] = df_returner['returnerId'].str.split(';',expand=True)\ndf_returner.drop(['returnerId','nflId2','nflId3'],inplace=True,axis=1)\ndf_returner['nflId_returner'] = df_returner['nflId_returner'].astype(float)\ndf_returner['kickReturnYardage'] = df_returner['kickReturnYardage'].fillna(0)\nkeep_cols = ['gameId','playId','frameId','event','s','nflId','a']\ndf_track_returner = df_tracking[keep_cols]\ndf_returner = df_returner.rename(columns={'nflId_returner':'nflId'})\ndf_returner_names= df_returner.merge(df_players,on='nflId')\nreturner_features = df_track_returner.merge(df_returner_names,on=['gameId','playId','nflId'])","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:40:52.585064Z","iopub.execute_input":"2022-01-05T17:40:52.585603Z","iopub.status.idle":"2022-01-05T17:41:02.58581Z","shell.execute_reply.started":"2022-01-05T17:40:52.585544Z","shell.execute_reply":"2022-01-05T17:41:02.584993Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Returner's energy","metadata":{}},{"cell_type":"code","source":"returner_features=returner_features.rename(columns={'nflId':'nflId_returner'})\ndf_returner_features = df_missedTackler.merge(returner_features,on=['gameId','playId'])\ndf_returner_features['BMI'] = (df_returner_features['weight'] * 703) / (df_returner_features['height'] ** 2)\ndf1 = df_returner_features.groupby(['gameId','playId'],as_index=False)['s'].max()\ndf1.rename({'s':'Energy'},axis=1,inplace=True)\ndf_returner_features = df_returner_features.merge(df1,on=['gameId','playId']) \ndf_returner_features['Energy_BMI'] = (df_returner_features['BMI'] * 0.45359237) * (df_returner_features['Energy'] ** 2)\n# df_returner_features['Energy'] = (df_returner_features['weight'] * 0.45359237) * (df_returner_features['Energy'] ** 2)\ndf_returner_features.drop_duplicates('nflId_returner')","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:41:02.587324Z","iopub.execute_input":"2022-01-05T17:41:02.5876Z","iopub.status.idle":"2022-01-05T17:41:03.77813Z","shell.execute_reply.started":"2022-01-05T17:41:02.587545Z","shell.execute_reply":"2022-01-05T17:41:03.777443Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.preprocessing import StandardScaler\n\nfeature_columns = ['missedTackler_count','kickReturnYardage','Energy','Energy_BMI']\nscaler = StandardScaler()\ndf2 = scaler.fit_transform(df_returner_features[feature_columns])\ndf2 = pd.DataFrame(df2)\ndict_fc = {0:'missedTackler_count',1:'kickReturnYardage',2:'Energy',3:'Energy_BMI'}\nfor col in df2.columns:\n    df2 = df2.rename({col:dict_fc[col]},axis=1)\ndf_returner_features.drop(feature_columns,axis=1,inplace=True)\ndf_returner_features = pd.concat([df_returner_features,df2],axis=1)\nkeep_cols = ['nflId_returner','missedTackler_count','kickReturnYardage','Energy','Energy_BMI']\ndf_returner_rank = df_returner_features.groupby(['gameId','playId'],as_index=False).mean()[keep_cols]\ndf_returner_rank['returner_score']=df_returner_rank['missedTackler_count']*0.2 \\\n                                             +df_returner_rank['kickReturnYardage']*0.5 \\\n                                             +df_returner_rank['Energy']*0.3\n\ndf_returner_td = df_returner_features.query('event==\"touchdown\"')[['gameId','playId','frameId','event','nflId_returner']]\ndf_returner_td = df_returner_td.groupby('nflId_returner',as_index=False).count()\ndf_returner_td = df_returner_td[['nflId_returner','event','gameId','playId']]\ndf_returner_td.rename({'event':'td_count'},axis=1,inplace=True)\ndf_returner_td = df_returner_td[['nflId_returner','td_count']]\ndf_returner_rank = df_returner_rank.groupby(['nflId_returner'],as_index=False).mean()\ndf_returner_rank = df_returner_rank.merge(df_returner_td,how='outer',on='nflId_returner')\ndf_returner_rank['td_count'] = df_returner_rank['td_count'].fillna(0)\nscaler = StandardScaler()\nX3 = scaler.fit_transform(df_returner_rank[['nflId_returner','td_count']])\ndict_fc = {0:'nflId_returner',1:'td_count'}\nX3 = pd.DataFrame(X3)\nfor col in X3.columns:\n    X3 = X3.rename({col:dict_fc[col]},axis=1)\nX3.reset_index(inplace=True)\nX3.drop('nflId_returner',inplace=True,axis=1)\ndf_returner_rank.drop('td_count',inplace=True,axis=1)\ndf_returner_rank = pd.concat([df_returner_rank,X3],axis=1)\ndf_returner_rank.drop('index',inplace=True,axis=1)\ndf_returner_rank['returner_score'] = 100*(df_returner_rank['returner_score'] + (1+df_returner_rank['td_count']))/2","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:41:03.779509Z","iopub.execute_input":"2022-01-05T17:41:03.779784Z","iopub.status.idle":"2022-01-05T17:41:04.279198Z","shell.execute_reply.started":"2022-01-05T17:41:03.779749Z","shell.execute_reply":"2022-01-05T17:41:04.278402Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Ranking according to the score","metadata":{}},{"cell_type":"code","source":"df_returner_rank['rank_by_score'] = df_returner_rank['returner_score'].rank(ascending=False)\ndf_returner_rank = df_returner_rank.sort_values('rank_by_score',ascending=True)\ndf_returner_rank = df_returner_rank[['nflId_returner','rank_by_score','returner_score']]\ndf_returner_rank.rename({'nflId_returner':'nflId'},axis=1,inplace=True)\ndf_returner_rank = df_returner_rank.merge(df_players,on=['nflId'])","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:41:04.280504Z","iopub.execute_input":"2022-01-05T17:41:04.280806Z","iopub.status.idle":"2022-01-05T17:41:04.296302Z","shell.execute_reply.started":"2022-01-05T17:41:04.280773Z","shell.execute_reply":"2022-01-05T17:41:04.295522Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visulization","metadata":{}},{"cell_type":"code","source":"import plotly.express as px\nreturner_top_ten = df_returner_rank[0:50]\ncheck = returner_top_ten['rank_by_score'].value_counts().reset_index()\ncheck.rename({'rank_by_score':'cc'},axis=1,inplace=True)\ncheck.rename({'index':'rank_by_score'},axis=1,inplace=True)\ncheck = check.merge(returner_top_ten,on=['rank_by_score'])\ncheck = check[['returner_score','displayName']]\n\ncheck.columns = [\n    \n    'scores', \n    'playerName'\n]\n\ncheck = check.sort_values('scores')\n\nfig = px.bar(\n    check, \n    y='playerName', \n    x=\"scores\", \n    orientation='h', \n    title='Number of games for every date', \n    height=900, \n    width=800\n)\n\nfig.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:41:04.2992Z","iopub.execute_input":"2022-01-05T17:41:04.299442Z","iopub.status.idle":"2022-01-05T17:41:06.391944Z","shell.execute_reply.started":"2022-01-05T17:41:04.299412Z","shell.execute_reply":"2022-01-05T17:41:06.391252Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Part III :  Pitch control --Voronoi Visulization Animation\n\nWe firstly put voronoi and players' movement in a field together in NFL competition, each player has a control area , so in a field or a sport competition, a field control is also vital, so we visulize a play in a game.","metadata":{}},{"cell_type":"code","source":"import dateutil\nfrom math import radians\nfrom IPython.display import Video\n\nfrom matplotlib import animation\nfrom matplotlib.animation import FFMpegWriter\n\nimport matplotlib.patches as patches\nfrom scipy.spatial import Voronoi, voronoi_plot_2d\n\ndef voronoi_finite_polygons_2d(vor, radius=None):\n    \"\"\"\n    Reconstruct infinite voronoi regions in a 2D diagram to finite\n    regions.\n\n    Parameters\n    ----------\n    vor : Voronoi\n        Input diagram\n    radius : float, optional\n        Distance to 'points at infinity'.\n\n    Returns\n    -------\n    regions : list of tuples\n        Indices of vertices in each revised Voronoi regions.\n    vertices : list of tuples\n        Coordinates for revised Voronoi vertices. Same as coordinates\n        of input vertices, with 'points at infinity' appended to the\n        end.\n\n    \"\"\"\n\n    if vor.points.shape[1] != 2:\n        raise ValueError(\"Requires 2D input\")\n\n    new_regions = []\n    new_vertices = vor.vertices.tolist()\n\n    center = vor.points.mean(axis=0)\n    if radius is None:\n        radius = vor.points.ptp().max()*2\n\n    # Construct a map containing all ridges for a given point\n    all_ridges = {}\n    for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices):\n        all_ridges.setdefault(p1, []).append((p2, v1, v2))\n        all_ridges.setdefault(p2, []).append((p1, v1, v2))\n\n    # Reconstruct infinite regions\n    for p1, region in enumerate(vor.point_region):\n        vertices = vor.regions[region]\n\n        if all([v >= 0 for v in vertices]):\n            # finite region\n            new_regions.append(vertices)\n            continue\n\n        # reconstruct a non-finite region\n        ridges = all_ridges[p1]\n        new_region = [v for v in vertices if v >= 0]\n\n        for p2, v1, v2 in ridges:\n            if v2 < 0:\n                v1, v2 = v2, v1\n            if v1 >= 0:\n                # finite ridge: already in the region\n                continue\n\n            # Compute the missing endpoint of an infinite ridge\n\n            t = vor.points[p2] - vor.points[p1] # tangent\n            t /= np.linalg.norm(t)\n            n = np.array([-t[1], t[0]])  # normal\n\n            midpoint = vor.points[[p1, p2]].mean(axis=0)\n            direction = np.sign(np.dot(midpoint - center, n)) * n\n            far_point = vor.vertices[v2] + direction * radius\n\n            new_region.append(len(new_vertices))\n            new_vertices.append(far_point.tolist())\n\n        # sort region counterclockwise\n        vs = np.asarray([new_vertices[v] for v in new_region])\n        c = vs.mean(axis=0)\n        angles = np.arctan2(vs[:,1] - c[1], vs[:,0] - c[0])\n        new_region = np.array(new_region)[np.argsort(angles)]\n\n        # finish\n        new_regions.append(new_region.tolist())\n\n    return new_regions, np.asarray(new_vertices)\n\n\ndef calculate_dx_dy_arrow(x, y, angle, speed, multiplier):\n    if angle <= 90:\n        angle = angle\n        dx = np.sin(radians(angle)) * multiplier * speed\n        dy = np.cos(radians(angle)) * multiplier * speed\n        return dx, dy\n    if angle > 90 and angle <= 180:\n        angle = angle - 90\n        dx = np.sin(radians(angle)) * multiplier * speed\n        dy = -np.cos(radians(angle)) * multiplier * speed\n        return dx, dy\n    if angle > 180 and angle <= 270:\n        angle = angle - 180\n        dx = -(np.sin(radians(angle)) * multiplier * speed)\n        dy = -(np.cos(radians(angle)) * multiplier * speed)\n        return dx, dy\n    if angle > 270 and angle <= 360:\n        angle = 360 - angle\n        dx = -np.sin(radians(angle)) * multiplier * speed\n        dy = np.cos(radians(angle)) * multiplier * speed\n        return dx, dy\n\ndef create_football_field(linenumbers=True,\n                          endzones=True,\n                          highlight_line=False,\n                          highlight_line_number=55,\n                          highlight_first_down_line=False,\n                          yards_to_go=10,\n                          highlighted_name='Line of Scrimmage',\n                          fifty_is_los=False,\n                          figsize=(12, 6.33)):\n    \"\"\"\n    Function that plots the football field for viewing plays.\n    Allows for showing or hiding endzones.\n    \"\"\"\n    rect = patches.Rectangle((0, 0), 120, 53.3, linewidth=0.1,\n                             edgecolor='r', facecolor='darkgreen', zorder=0)\n\n    fig, ax = plt.subplots(1, figsize=figsize)\n    ax.add_patch(rect)\n\n    plt.plot([10, 10, 10, 20, 20, 30, 30, 40, 40, 50, 50, 60, 60, 70, 70, 80,\n              80, 90, 90, 100, 100, 110, 110, 120, 0, 0, 120, 120],\n             [0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3,\n              53.3, 0, 0, 53.3, 53.3, 0, 0, 53.3, 53.3, 53.3, 0, 0, 53.3],\n             color='white')\n    if fifty_is_los:\n        plt.plot([60, 60], [0, 53.3], color='gold')\n        plt.text(62, 50, '<- Player Yardline at Snap', color='gold')\n    # Endzones\n    if endzones:\n        ez1 = patches.Rectangle((0, 0), 10, 53.3,\n                                linewidth=0.1,\n                                edgecolor='r',\n                                facecolor='blue',\n                                alpha=0.2,\n                                zorder=0)\n        ez2 = patches.Rectangle((110, 0), 120, 53.3,\n                                linewidth=0.1,\n                                edgecolor='r',\n                                facecolor='blue',\n                                alpha=0.2,\n                                zorder=0)\n        ax.add_patch(ez1)\n        ax.add_patch(ez2)\n    plt.xlim(0, 120)\n    plt.ylim(-5, 58.3)\n    plt.axis('off')\n    if linenumbers:\n        for x in range(20, 110, 10):\n            numb = x\n            if x > 50:\n                numb = 120 - x\n            plt.text(x, 5, str(numb - 10),\n                     horizontalalignment='center',\n                     fontsize=20,  # fontname='Arial',\n                     color='white')\n            plt.text(x - 0.95, 53.3 - 5, str(numb - 10),\n                     horizontalalignment='center',\n                     fontsize=20,  # fontname='Arial',\n                     color='white', rotation=180)\n    if endzones:\n        hash_range = range(11, 110)\n    else:\n        hash_range = range(1, 120)\n\n    for x in hash_range:\n        ax.plot([x, x], [0.4, 0.7], color='white')\n        ax.plot([x, x], [53.0, 52.5], color='white')\n        ax.plot([x, x], [22.91, 23.57], color='white')\n        ax.plot([x, x], [29.73, 30.39], color='white')\n\n    if highlight_line:\n        hl = highlight_line_number + 10\n        plt.plot([hl, hl], [0, 53.3], color='yellow')\n        #plt.text(hl + 2, 50, '<- {}'.format(highlighted_name),\n        #         color='yellow')\n        \n    if highlight_first_down_line:\n        fl = hl + yards_to_go\n        plt.plot([fl, fl], [0, 53.3], color='yellow')\n        #plt.text(fl + 2, 50, '<- {}'.format(highlighted_name),\n        #         color='yellow')\n    return fig, ax\n","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:41:06.395922Z","iopub.execute_input":"2022-01-05T17:41:06.397729Z","iopub.status.idle":"2022-01-05T17:41:06.45488Z","shell.execute_reply.started":"2022-01-05T17:41:06.397686Z","shell.execute_reply":"2022-01-05T17:41:06.453876Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def animate_player_movement(weekNumber, playId, gameId):\n    weekData = df_tracking\n    playData = df_plays\n    \n    playHome = weekData.query('gameId==' + str(gameId) + ' and playId==' + str(playId) + ' and team == \"home\"')\n    playAway = weekData.query('gameId==' + str(gameId) + ' and playId==' + str(playId) + ' and team == \"away\"')\n    playFootball = weekData.query('gameId==' + str(gameId) + ' and playId==' + str(playId) + ' and team == \"football\"')\n    #playHome\n    \n    playHome['time'] = playHome['time'].apply(lambda x: dateutil.parser.parse(x).timestamp()).rank(method='dense')\n    playAway['time'] = playAway['time'].apply(lambda x: dateutil.parser.parse(x).timestamp()).rank(method='dense')\n    playFootball['time'] = playFootball['time'].apply(lambda x: dateutil.parser.parse(x).timestamp()).rank(method='dense')\n    \n    \n    \n    maxTime = int(playAway['time'].unique().max())\n    minTime = int(playAway['time'].unique().min())\n    \n    yardlineNumber = playData.query('gameId==' + str(gameId) + ' and playId==' + str(playId))['yardlineNumber'].item()\n    yardsToGo = playData.query('gameId==' + str(gameId) + ' and playId==' + str(playId))['yardsToGo'].item()\n    absoluteYardlineNumber = playData.query('gameId==' + str(gameId) + ' and playId==' + str(playId))['absoluteYardlineNumber'].item() - 10\n    playDir = playHome.sample(1)['playDirection'].item()\n    \n    if (absoluteYardlineNumber > 50):\n        yardlineNumber = 100 - yardlineNumber\n    if (absoluteYardlineNumber <= 50):\n        yardlineNumber = yardlineNumber\n        \n    if (playDir == 'left'):\n        yardsToGo = -yardsToGo\n    else:\n        yardsToGo = yardsToGo\n    \n    fig, ax = create_football_field(highlight_line=True, highlight_line_number=yardlineNumber, highlight_first_down_line=True, yards_to_go=yardsToGo)\n    playDesc = playData.query('gameId==' + str(gameId) + ' and playId==' + str(playId))['playDescription'].item()\n    plt.title(f'Game # {gameId} Play # {playId} \\n {playDesc}')\n    \n    def update_animation(time):\n        patch = []\n        if True:\n            ############################\n            # Home players' location\n            homeX = playHome.query('time == ' + str(time))['x']\n            homeY = playHome.query('time == ' + str(time))['y']\n            homeNum = playHome.query('time == ' + str(time))['jerseyNumber']\n            homeOrient = playHome.query('time == ' + str(time))['o']\n            homeDir = playHome.query('time == ' + str(time))['dir']\n            homeSpeed = playHome.query('time == ' + str(time))['s']\n            patch.extend(plt.plot(homeX, homeY, 'o',c='gold', ms=20, mec='white'))\n\n            # Home players' jersey number \n            for x, y, num in zip(homeX, homeY, homeNum):\n                patch.append(plt.text(x, y, int(num), va='center', ha='center', color='black', size='medium'))\n\n            # Home players' orientation\n            for x, y, orient in zip(homeX, homeY, homeOrient):\n                dx, dy = calculate_dx_dy_arrow(x, y, orient, 1, 1)\n                patch.append(plt.arrow(x, y, dx, dy, color='gold', width=0.5, shape='full'))\n\n            # Home players' direction\n            for x, y, direction, speed in zip(homeX, homeY, homeDir, homeSpeed):\n                dx, dy = calculate_dx_dy_arrow(x, y, direction, speed, 1)\n                patch.append(plt.arrow(x, y, dx, dy, color='black', width=0.25, shape='full'))\n\n\n            #########################\n            # Away players' location\n            awayX = playAway.query('time == ' + str(time))['x']\n            awayY = playAway.query('time == ' + str(time))['y']\n            awayNum = playAway.query('time == ' + str(time))['jerseyNumber']\n            awayOrient = playAway.query('time == ' + str(time))['o']\n            awayDir = playAway.query('time == ' + str(time))['dir']\n            awaySpeed = playAway.query('time == ' + str(time))['s']\n            patch.extend(plt.plot(awayX, awayY, 'o',c='orangered', ms=20, mec='white'))\n\n            # Away players' jersey number \n            for x, y, num in zip(awayX, awayY, awayNum):\n                patch.append(plt.text(x, y, int(num), va='center', ha='center', color='white', size='medium'))\n\n            # Away players' orientation\n            for x, y, orient in zip(awayX, awayY, awayOrient):\n                dx, dy = calculate_dx_dy_arrow(x, y, orient, 1, 1)\n                patch.append(plt.arrow(x, y, dx, dy, color='orangered', width=0.5, shape='full'))\n\n            # Away players' direction\n            for x, y, direction, speed in zip(awayX, awayY, awayDir, awaySpeed):\n                dx, dy = calculate_dx_dy_arrow(x, y, direction, speed, 1)\n                patch.append(plt.arrow(x, y, dx, dy, color='black', width=0.25, shape='full'))\n\n            # Football' location\n            footballX = playFootball.query('time == ' + str(time))['x']\n            footballY = playFootball.query('time == ' + str(time))['y']\n            patch.extend(plt.plot(footballX, footballY, 'o', c='black', ms=10, mec='white', data=playFootball.query('time == ' + str(time))['team']))\n\n        # Voronoi polygon \n        carrier_loc = (playHome.query('time == ' + str(time))[['x','y']]).values\n        notcarrier_loc = (playAway.query('time == ' + str(time))[['x','y']]).values\n        points = np.append(carrier_loc, notcarrier_loc, axis=0)\n        vor = Voronoi(points)\n        regions, vertices = voronoi_finite_polygons_2d(vor)\n        for i, region in enumerate(regions):\n            polygon = vertices[region]\n            patch.extend(plt.fill(*zip(*polygon), color='yellow', alpha=0.6))\n#             patch.extend(plt.fill(*zip(*polygon), alpha=0.8))\n        \n        return patch\n    \n    ims = [[]]\n    for time in np.arange(minTime, maxTime+1):\n        patch = update_animation(time)\n        ims.append(patch)\n        \n    anim = animation.ArtistAnimation(fig, ims, repeat=False)\n    \n    return anim\n\n\n\n\n# anim = animate_player_movement(1, 366, 2018090600)\n# anim = animate_player_movement(1, 1374, 2018122302)\nanim = animate_player_movement(1, 36, 2018111900)\n\nwriter = FFMpegWriter(fps=7)\nanim.save('animation_notrail.mp4', writer=writer)\nVideo(\"animation_notrail.mp4\")","metadata":{"execution":{"iopub.status.busy":"2022-01-05T17:41:06.459839Z","iopub.execute_input":"2022-01-05T17:41:06.460264Z","iopub.status.idle":"2022-01-05T17:41:29.316783Z","shell.execute_reply.started":"2022-01-05T17:41:06.460226Z","shell.execute_reply":"2022-01-05T17:41:29.315849Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Conclusion and Future work\n\nWe hope predicting landing area can guide our coaches to make thier strategies. However, this is initial work, and there are a lot of work to do. We did not finish Tackler's prediction, because there are a lot of interaction among players. We shoul not omit them, and we hope that Graph Neural Network can process both temporal and spatial info in NFL big data. So we hope we can provide more insightful suggestion in the near future.","metadata":{}}]}