{"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":"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\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 read-only \"../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# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2023-01-24T17:12:23.942992Z","iopub.execute_input":"2023-01-24T17:12:23.943405Z","iopub.status.idle":"2023-01-24T17:12:23.960835Z","shell.execute_reply.started":"2023-01-24T17:12:23.943370Z","shell.execute_reply":"2023-01-24T17:12:23.959909Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Importing Packages","metadata":{}},{"cell_type":"markdown","source":"For storage and manipulation of data we use numpy and pandas. For visualisations we use matplotlib.","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as patches\nfrom matplotlib.markers import MarkerStyle\nimport matplotlib.animation as animation\nimport seaborn as sns\nimport cv2\nimport subprocess\n\n#Credit to Dino Wun for the idea to use these packages for video rendering in the notebook. https://www.kaggle.com/code/dinowun/eda-simplified-nfl-1st-and-future-pcd-b\n#!pip3 install moviepy \n#from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip\nimport IPython\nfrom IPython.display import Video,display","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:23.963407Z","iopub.execute_input":"2023-01-24T17:12:23.963808Z","iopub.status.idle":"2023-01-24T17:12:23.971102Z","shell.execute_reply.started":"2023-01-24T17:12:23.963771Z","shell.execute_reply":"2023-01-24T17:12:23.969584Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Reading the data for analysis.","metadata":{}},{"cell_type":"code","source":"baseline_helms_df = pd.read_csv('/kaggle/input/nfl-player-contact-detection/train_baseline_helmets.csv')\nplayer_tracking_df = pd.read_csv('/kaggle/input/nfl-player-contact-detection/train_player_tracking.csv')\nvideo_metadata_df = pd.read_csv('/kaggle/input/nfl-player-contact-detection/train_video_metadata.csv')\nlabels_df = pd.read_csv('/kaggle/input/nfl-player-contact-detection/train_labels.csv')","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:23.974710Z","iopub.execute_input":"2023-01-24T17:12:23.975110Z","iopub.status.idle":"2023-01-24T17:12:37.955955Z","shell.execute_reply.started":"2023-01-24T17:12:23.975075Z","shell.execute_reply":"2023-01-24T17:12:37.954291Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Initial Data Analysis","metadata":{}},{"cell_type":"markdown","source":"**Checking the head for each dataframe**","metadata":{}},{"cell_type":"code","source":"baseline_helms_df.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:37.962583Z","iopub.execute_input":"2023-01-24T17:12:37.963531Z","iopub.status.idle":"2023-01-24T17:12:37.980484Z","shell.execute_reply.started":"2023-01-24T17:12:37.963475Z","shell.execute_reply":"2023-01-24T17:12:37.979198Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"player_tracking_df.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:37.982661Z","iopub.execute_input":"2023-01-24T17:12:37.983561Z","iopub.status.idle":"2023-01-24T17:12:38.017153Z","shell.execute_reply.started":"2023-01-24T17:12:37.983509Z","shell.execute_reply":"2023-01-24T17:12:38.016005Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"video_metadata_df.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:38.020345Z","iopub.execute_input":"2023-01-24T17:12:38.021056Z","iopub.status.idle":"2023-01-24T17:12:38.034904Z","shell.execute_reply.started":"2023-01-24T17:12:38.021016Z","shell.execute_reply":"2023-01-24T17:12:38.033632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_df.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:38.036445Z","iopub.execute_input":"2023-01-24T17:12:38.036823Z","iopub.status.idle":"2023-01-24T17:12:38.055208Z","shell.execute_reply.started":"2023-01-24T17:12:38.036788Z","shell.execute_reply":"2023-01-24T17:12:38.054118Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Checking Shape of dataframes**","metadata":{}},{"cell_type":"code","source":"baseline_helms_df.shape","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:38.056586Z","iopub.execute_input":"2023-01-24T17:12:38.056971Z","iopub.status.idle":"2023-01-24T17:12:38.063624Z","shell.execute_reply.started":"2023-01-24T17:12:38.056938Z","shell.execute_reply":"2023-01-24T17:12:38.062373Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"player_tracking_df.shape","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:38.065146Z","iopub.execute_input":"2023-01-24T17:12:38.065467Z","iopub.status.idle":"2023-01-24T17:12:38.075969Z","shell.execute_reply.started":"2023-01-24T17:12:38.065438Z","shell.execute_reply":"2023-01-24T17:12:38.074858Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"video_metadata_df.shape","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:38.077238Z","iopub.execute_input":"2023-01-24T17:12:38.077742Z","iopub.status.idle":"2023-01-24T17:12:38.092172Z","shell.execute_reply.started":"2023-01-24T17:12:38.077671Z","shell.execute_reply":"2023-01-24T17:12:38.090807Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_df.shape","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:38.093715Z","iopub.execute_input":"2023-01-24T17:12:38.094113Z","iopub.status.idle":"2023-01-24T17:12:38.104871Z","shell.execute_reply.started":"2023-01-24T17:12:38.094076Z","shell.execute_reply":"2023-01-24T17:12:38.103624Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Checking for NaN values**","metadata":{}},{"cell_type":"code","source":"baseline_helms_df.isna().sum()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:38.109778Z","iopub.execute_input":"2023-01-24T17:12:38.110163Z","iopub.status.idle":"2023-01-24T17:12:38.782072Z","shell.execute_reply.started":"2023-01-24T17:12:38.110130Z","shell.execute_reply":"2023-01-24T17:12:38.780885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"player_tracking_df.isna().sum()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:38.783620Z","iopub.execute_input":"2023-01-24T17:12:38.783969Z","iopub.status.idle":"2023-01-24T17:12:39.049577Z","shell.execute_reply.started":"2023-01-24T17:12:38.783941Z","shell.execute_reply":"2023-01-24T17:12:39.048110Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"video_metadata_df.isna().sum()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:39.051348Z","iopub.execute_input":"2023-01-24T17:12:39.051715Z","iopub.status.idle":"2023-01-24T17:12:39.062372Z","shell.execute_reply.started":"2023-01-24T17:12:39.051661Z","shell.execute_reply":"2023-01-24T17:12:39.060910Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"labels_df.isna().sum()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:39.064122Z","iopub.execute_input":"2023-01-24T17:12:39.064513Z","iopub.status.idle":"2023-01-24T17:12:39.870236Z","shell.execute_reply.started":"2023-01-24T17:12:39.064478Z","shell.execute_reply":"2023-01-24T17:12:39.868909Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Baseline Helmets Dataframe","metadata":{}},{"cell_type":"markdown","source":"We start by taking a look at the baseline helmets file, this gives information about the imperfect baseline predictions for helmet boxes and player assignments from both sideline and end views","metadata":{}},{"cell_type":"markdown","source":"Its nice to see the view expressed as a pie chart to see the percentages of each view ","metadata":{}},{"cell_type":"code","source":"plt.rcParams[\"figure.figsize\"] = (7,12)\nplt.rcParams.update({'font.size': 16})","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:39.874091Z","iopub.execute_input":"2023-01-24T17:12:39.874509Z","iopub.status.idle":"2023-01-24T17:12:39.880338Z","shell.execute_reply.started":"2023-01-24T17:12:39.874471Z","shell.execute_reply":"2023-01-24T17:12:39.878915Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"views = baseline_helms_df[\"view\"].value_counts()\ncolours = sns.color_palette('pastel')[0:3]\ndef label_pie(pct, vals):\n    absolute = int(pct/100.*np.sum(vals)) #getting interger value from percentage and number of values\n    return \"{:.1f}%\\n({:d})\".format(pct, absolute)\n\nplt.pie(views,labels=baseline_helms_df[\"view\"].unique(), colors = colours, autopct=lambda pct: label_pie(pct,views))\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:39.881909Z","iopub.execute_input":"2023-01-24T17:12:39.882328Z","iopub.status.idle":"2023-01-24T17:12:40.517995Z","shell.execute_reply.started":"2023-01-24T17:12:39.882288Z","shell.execute_reply":"2023-01-24T17:12:40.516247Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"figs, ((ax1,ax2),(ax3,ax4)) = plt.subplots(2, 2, figsize=(20,15))\nax1.hist(baseline_helms_df[\"left\"], bins = 100)\nax2.hist(baseline_helms_df[\"width\"], bins = 50)\nax3.hist(baseline_helms_df[\"top\"], bins = 100)\nax4.hist(baseline_helms_df[\"height\"], bins = 50)","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:40.520262Z","iopub.execute_input":"2023-01-24T17:12:40.521321Z","iopub.status.idle":"2023-01-24T17:12:42.256364Z","shell.execute_reply.started":"2023-01-24T17:12:40.521247Z","shell.execute_reply":"2023-01-24T17:12:42.255176Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#From https://www.kaggle.com/code/dariussingh/player-contact-detection-eda#EDA\ndef play_video(video_path: str):\n    frac = 0.75\n    display(\n        Video(data=video_path,embed=True, height=int(720*frac), width=int(1280*frac))\n           )","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:42.257571Z","iopub.execute_input":"2023-01-24T17:12:42.257910Z","iopub.status.idle":"2023-01-24T17:12:42.264112Z","shell.execute_reply.started":"2023-01-24T17:12:42.257879Z","shell.execute_reply":"2023-01-24T17:12:42.262992Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path='/kaggle/input/nfl-player-contact-detection/train/58174_001792_Endzone.mp4'\nplay_video(path)","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:42.265646Z","iopub.execute_input":"2023-01-24T17:12:42.266099Z","iopub.status.idle":"2023-01-24T17:12:42.515824Z","shell.execute_reply.started":"2023-01-24T17:12:42.266056Z","shell.execute_reply":"2023-01-24T17:12:42.514933Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Credit for this function goes to Darius Singh. https://www.kaggle.com/code/dariussingh/player-contact-detection-eda#EDA   \nThis function takes the video path and the baseline helmet boxes as a string and pandas DataFrame respectively. Verbose = True is used to help with readability of certain sections of code.","metadata":{}},{"cell_type":"code","source":" def annotate_video_with_helmets(video_path: str, baseline_boxes:pd.DataFrame, verbose=True) -> str:\n    \"\"\"\n    Annotates a video with baseline model boxes and labels.\n    \"\"\"\n    video_name = video_path.split('/')[-1] #Splits the video path string on the last [-1] forward slash\n    play_name = video_name.split('.')[0] #Splits the video name from \".mp4\" by splitting everything before the \".\"\n    HELMET_COLOR = (0, 0, 0) #Setting helmet box colour to black (R,G,B)\n    baseline_boxes = baseline_boxes.query(\"video==@video_name\").copy() #Creates a copy of the dataframe that only contains the boxes for the specific video_path supplied.\n    \n    # verbose\n    if verbose==True:\n        print(f\"Running for {video_name}\") #Displaying the video being annotated.\n    \n    # VideoCapture Object\n    cap  = cv2.VideoCapture(video_path) #Opens a video file.\n    width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) #width of frame\n    height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) #height of frame\n    fps = cap.get(cv2.CAP_PROP_FPS)  #fps of video\n    \n    # VideoWriter Object - Object to write video files\n    output_path = \"labeled_\" + video_name #Specifying name of labelled video file\n    tmp_output_path = \"tmp_\" + output_path #Temporary output path\n    out = cv2.VideoWriter(tmp_output_path, #VideoWriter takes the filepath, fourcc code, fps and framesize.\n                          cv2.VideoWriter_fourcc(*'MP4V'), #This VideoWriter function creates a fourcc code, used to identify data formats.\n                          fps, (width, height)) #Specifying the fps, height and width of video output.\n    \n    #Checking the cap object is ready to read frames from the video.  \n    if cap.isOpened() == False:\n        print('Error opening file')\n        \n    # read until video is complete\n    frame  = 1\n    while(cap.isOpened()):\n        ret, img = cap.read() #Reads frames of the video, returning ret (boolean of whether frame was read successfully) and img (the actual frame that is read)\n        \n        if ret==True:\n            #This section is adding nice visual details to the video\n            # add play_name text\n            cv2.putText(img, play_name, #The putText function takes the image to be annotated, the text to annotate the image, the coordinates of the bottom left corner of the text   \n                       (int(0.01*width), int(0.05*height)), # as well as the font type, font scale, colour and thickness in pixels\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                       )\n            \n            # add frame counter\n            cv2.putText(img, \"Frame: \"+str(frame),\n                       (int(0.75*width), int(0.05*height)),\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                       )\n\n            # adding helmet bounding boxes and player tags\n            bbox_set = baseline_boxes.query(\"frame==@frame\") #Getting the helmet bounding boxes for the frame most recently read\n            for i, annot in enumerate(zip(np.array(bbox_set[['player_label','left','width','top','height']]))): #enumerate gives a counter on iteration through a loop, zip combines iterables into tuples.\n                player_label, bbox_left, bbox_width, bbox_top, bbox_height = annot[0] #Getting the dimensions for the player helmet box\n                # add helmet bbox\n                cv2.rectangle(img, #Draws a rectangle on an image\n                              (bbox_left,bbox_top), (bbox_left+bbox_width, bbox_top+bbox_height), #starting coordinates of rectangle and nd coordinates of rectangle\n                              HELMET_COLOR, #colour of bounding box\n                              1\n                             )\n                # add player label\n                cv2.putText(img, player_label, #Adding text next to the helmet bounding boxes for the player labels\n                       (bbox_left+10, bbox_top+10),\n                        cv2.FONT_HERSHEY_SIMPLEX,\n                        1,\n                        HELMET_COLOR,\n                        1\n                       )\n            \n            frame += 1\n            out.write(img) #writing image to the video writer object\n                \n        # break the loop   \n        else: \n            break\n        \n        \n    out.release()\n    \n    # Not all browsers support the codec, we will re-load the file at tmp_output_path\n    # and convert to a codec that is more broadly readable using ffmpeg\n    if os.path.exists(output_path):\n        os.remove(output_path) #checking if the output path already exists, if it does delete it.\n    subprocess.run(\n        [\n            \"ffmpeg\",\n            \"-i\",\n            tmp_output_path,\n            \"-crf\",\n            \"18\",\n            \"-preset\",\n            \"veryfast\",\n            \"-hide_banner\",\n            \"-loglevel\",\n            \"error\",\n            \"-vcodec\",\n            \"libx264\",\n            output_path,\n        ]\n    )\n    os.remove(tmp_output_path)\n    \n    return output_path","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:42.517308Z","iopub.execute_input":"2023-01-24T17:12:42.518035Z","iopub.status.idle":"2023-01-24T17:12:42.539546Z","shell.execute_reply.started":"2023-01-24T17:12:42.517996Z","shell.execute_reply":"2023-01-24T17:12:42.538512Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = annotate_video_with_helmets(path,baseline_helms_df)\nplay_video(path)","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:12:42.540963Z","iopub.execute_input":"2023-01-24T17:12:42.541296Z","iopub.status.idle":"2023-01-24T17:13:03.918347Z","shell.execute_reply.started":"2023-01-24T17:12:42.541266Z","shell.execute_reply":"2023-01-24T17:13:03.917026Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Player Tracking Data","metadata":{}},{"cell_type":"markdown","source":"Credit to Darius Singh for these visualisation functions. https://www.kaggle.com/code/dariussingh/player-contact-detection-eda#EDA","metadata":{}},{"cell_type":"code","source":"def create_football_field(fig, ax, line_color='black', field_color='white'):\n    \"\"\"\n    Function that plots the football field for viewing players.\n    \"\"\"\n    \n    # set field dimensions\n    plt.xlim(0,120)\n    plt.ylim(0,53.3)\n    \n    # adding rectangles to the field\n    for i in range(12):\n        rect = patches.Rectangle((10*i,0), 10, 53.3, linewidth=1, edgecolor=line_color, facecolor=field_color) #Creates a rectangle anchored at point (x,y)\n        ax.add_patch(rect)\n    \n    # configure axes\n    ax.tick_params( \n        axis='both',\n        which='both',\n        direction='in',\n        pad=-40,\n        length = 5,\n        bottom=True,\n        top=True,\n        labeltop=True,\n        labelbottom=True,\n        left=False,\n        right=False,\n        labelleft=False,\n        labelright=False,\n        color=line_color) \n    \n    # set ticks on the side of the field\n    ax.set_xticks([i for i in range(10,111)])\n    \n    # setting yard marking\n    label_set = []\n    for i in range(1,10):\n        if i<=5:\n            label_set += [\" \" for j in range(9)] + [str(i*10)]\n        else:\n            label_set += [\" \" for j in range(9)] + [str((10-i)*10)]\n    label_set =  [\" \"] + label_set + [\" \" for j in range(10)]\n    ax.set_xticklabels(label_set, fontsize=20, color=line_color)\n    \n    \n    return fig, ax","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:13:03.919893Z","iopub.execute_input":"2023-01-24T17:13:03.920411Z","iopub.status.idle":"2023-01-24T17:13:03.935664Z","shell.execute_reply.started":"2023-01-24T17:13:03.920376Z","shell.execute_reply":"2023-01-24T17:13:03.933218Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def populate_field(play_name:str, step:np.int64, player_tracking_data:pd.DataFrame, home_color:str='purple', away_color:str='orange'):\n    \"\"\"\n    populates the field with player tracking data of a play_name and step.\n    \"\"\"\n    # subset data to current play and step \n    step_info = player_tracking_data.query(\"game_play==@play_name and step==@step \").copy()\n    \n    # create new field\n    fig, ax= plt.subplots(figsize=(12, 5.33)) #12 subplots because of 12 sections of a football field\n    fig, ax  = create_football_field(fig, ax)\n    \n    # set title\n    ax.set_title(f'Player tracking data for {play_name} at step {step}')\n    \n    # populate field with players\n    for row in step_info.iterrows():\n        if row[1]['team'] == 'home':\n            color = home_color\n        else:\n            color = away_color\n        marker1 = MarkerStyle(r'$\\spadesuit$')  \n        marker1._transform.rotate_deg(360-row[1]['orientation'])\n        ax.scatter(row[1]['x_position'], row[1]['y_position'], marker=marker1, s=150, color=color)    \n    \n    plt.close()\n    return fig, ax","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:13:03.937978Z","iopub.execute_input":"2023-01-24T17:13:03.938616Z","iopub.status.idle":"2023-01-24T17:13:03.955841Z","shell.execute_reply.started":"2023-01-24T17:13:03.938554Z","shell.execute_reply":"2023-01-24T17:13:03.954752Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, ax = populate_field(play_name='58174_001792', step=1, player_tracking_data=player_tracking_df)\nfig","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:13:03.957254Z","iopub.execute_input":"2023-01-24T17:13:03.957642Z","iopub.status.idle":"2023-01-24T17:13:07.561613Z","shell.execute_reply.started":"2023-01-24T17:13:03.957607Z","shell.execute_reply":"2023-01-24T17:13:07.560484Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The following code creates and displays a video for the player tracking data at each step","metadata":{}},{"cell_type":"code","source":"# arguments\nplay_name = '58174_001792'\ndata = player_tracking_df.copy()\n\n# total number of frames at 10hz\nframes = len(data.query(\"game_play==@play_name\")['step'].unique())\n\n# frequency modifier factor [min_val:1, max_val:10] (reduce this for smoother tracking but longer rendering time)\nfreq_mod_fac = 5\n# to reduce rendering time we change the frequency to (10/freq_mod_fac)Hz\nframes = int(frames/freq_mod_fac)\ninterval_ms = 100*freq_mod_fac\n\n\n# initialize figure\nfig, ax = plt.subplots(figsize=(12, 5.33))\n#fig, ax = create_football_field(fig, ax)\n\ndef animate(i:int, play_name:str, player_tracking_data:pd.DataFrame, frames, home_color:str='purple', away_color:str='orange'):\n    \"\"\"\n    Function to animate player tracking data\n    \"\"\"\n    # create fresh field\n    ax.clear()\n    create_football_field(fig, ax)\n    \n    # find appropriate\n    play_info = player_tracking_data.query(\"game_play==@play_name\").copy() #getting play name \n    step_list = np.linspace(play_info['step'].min(), play_info['step'].max(), frames) #getting evenly spaced step for the number of frames in the play \n    step = int(step_list[i])\n    \n    # subset data to step info \n    step_info = play_info.query(\"step==@step\").copy()\n    \n    # iterate step info to populate field\n    for row in step_info.iterrows(): #iterrows iterates over a dataframe\n        if row[1]['team'] == 'home':\n            color = home_color\n        else:\n            color = away_color\n        marker1 = MarkerStyle(r'$\\spadesuit$')\n        marker1._transform.rotate_deg(360-row[1]['orientation'])\n        ax.scatter(row[1]['x_position'], row[1]['y_position'], marker=marker1, s=150, color=color)\n    \n    # set axis title\n    ax.set_title(f'Tracking data for {play_name} at step {step}')\n\n# animate\nanim = animation.FuncAnimation(fig, animate, fargs=(play_name, data, frames),frames=frames, repeat=False, interval=interval_ms)\n\n# embed html video to notebook\nvideo = anim.to_html5_video()\nhtml = IPython.display.HTML(video)\ndisplay(html)\nplt.close()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:13:07.563453Z","iopub.execute_input":"2023-01-24T17:13:07.563891Z","iopub.status.idle":"2023-01-24T17:14:55.643321Z","shell.execute_reply.started":"2023-01-24T17:13:07.563858Z","shell.execute_reply":"2023-01-24T17:14:55.641982Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Credit to Dino Wun for inspiration for these visualisations.  https://www.kaggle.com/code/dinowun/eda-simplified-nfl-1st-and-future-pcd-b  ","metadata":{}},{"cell_type":"code","source":"plt.rcParams[\"figure.figsize\"] = (15,8)","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:14:55.645372Z","iopub.execute_input":"2023-01-24T17:14:55.645879Z","iopub.status.idle":"2023-01-24T17:14:55.651334Z","shell.execute_reply.started":"2023-01-24T17:14:55.645831Z","shell.execute_reply":"2023-01-24T17:14:55.650468Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"team=player_tracking_df['team'].value_counts() #getting value counts for team values\nsns.barplot(x=team.index,y=team.values,data=player_tracking_df) #creating a barplot where x is the team (home/away) and y is the count","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:14:55.652788Z","iopub.execute_input":"2023-01-24T17:14:55.653392Z","iopub.status.idle":"2023-01-24T17:14:55.980888Z","shell.execute_reply.started":"2023-01-24T17:14:55.653356Z","shell.execute_reply":"2023-01-24T17:14:55.979816Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Count of player tracking data by position**","metadata":{}},{"cell_type":"code","source":"position = player_tracking_df['position'].value_counts().to_frame().reset_index().rename(columns={\"index\":\"position\",\"position\":\"count\"})  #value counts to get count for each position, to_frame converts series to dataframe, reset_index resets index of Dataframe,\n                                                                                                                    #rename axes label index is now position and position is now count\nsns.barplot(x='position',y='count',data=position)","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:14:55.982366Z","iopub.execute_input":"2023-01-24T17:14:55.982957Z","iopub.status.idle":"2023-01-24T17:14:56.516738Z","shell.execute_reply.started":"2023-01-24T17:14:55.982918Z","shell.execute_reply":"2023-01-24T17:14:56.515582Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"\n**Plot of speed, distance, acceleration, orientation of players**","metadata":{}},{"cell_type":"code","source":"fig, axs = plt.subplots(2,2,figsize=(15,10))\nplt.suptitle(\"Speed (yards/second), Distance (yards), Acceleration (yards/second^2.) and Orientation (Degrees) of Players\")\n\nsns.kdeplot(data=player_tracking_df, x=\"speed\",ax=axs[0,0])\nsns.kdeplot(data=player_tracking_df, x=\"distance\",ax=axs[0,1])\nsns.kdeplot(data=player_tracking_df, x=\"acceleration\",ax=axs[1,0])\nsns.kdeplot(data=player_tracking_df, x=\"orientation\",ax=axs[1,1])\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:14:56.523115Z","iopub.execute_input":"2023-01-24T17:14:56.523533Z","iopub.status.idle":"2023-01-24T17:15:17.598320Z","shell.execute_reply.started":"2023-01-24T17:14:56.523495Z","shell.execute_reply":"2023-01-24T17:15:17.596959Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Labels Analysis","metadata":{}},{"cell_type":"markdown","source":"The labels dataframe contains data for all possible combinations of players with eachother and the ground for each 0.1 second timestamp within the play.","metadata":{}},{"cell_type":"markdown","source":"We want to visualise probaby the step, the player id combinations (subplot, player 1 id and player 2 id, proportion of contact-non contact (i.e. pie chart), ","metadata":{}},{"cell_type":"markdown","source":"**Maybe come back and tidy this up, get interquartile range and work out how to index in a series**","metadata":{}},{"cell_type":"code","source":"plt.rcParams[\"figure.figsize\"] = (8,6)\nminimum = labels_df['step'].min()\nmaximum = labels_df['step'].max()\nquantiles = labels_df['step'].quantile([0.25,0.5,0.75])\nax = sns.boxplot(data=labels_df, x='step',orient='h')\nprint(f\" Minimum value is {minimum} \\n Maximum value is {maximum}\")\nprint(f\" The Quartiles are: \\n{quantiles}\")","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:15:17.599592Z","iopub.execute_input":"2023-01-24T17:15:17.599942Z","iopub.status.idle":"2023-01-24T17:15:18.142062Z","shell.execute_reply.started":"2023-01-24T17:15:17.599910Z","shell.execute_reply":"2023-01-24T17:15:18.140873Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"contacts = labels_df[\"contact\"].value_counts()\n\nplt.pie(contacts,labels=labels_df[\"contact\"].unique(), colors = colours, autopct=lambda pct: label_pie(pct,contacts))\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:15:18.143758Z","iopub.execute_input":"2023-01-24T17:15:18.144087Z","iopub.status.idle":"2023-01-24T17:15:18.324586Z","shell.execute_reply.started":"2023-01-24T17:15:18.144056Z","shell.execute_reply":"2023-01-24T17:15:18.322710Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"As we can see the vast majority of player-player and player-ground combinations do not result in a contact during the play, so the challenge will be correctly fitting whichever model we chose to be able to predict the contacts accurately.","metadata":{}},{"cell_type":"code","source":"true = labels_df.index[labels_df['contact'] == 1]\ncontact_true = labels_df[[\"contact\",\"step\"]].loc[true]\ncontact_true.reset_index()\ncontact_true = contact_true[\"step\"].value_counts()\ncontact_true.columns = [\"step\",\"count of contacts\"]\ncontact_true.head()","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:15:18.332824Z","iopub.execute_input":"2023-01-24T17:15:18.333529Z","iopub.status.idle":"2023-01-24T17:15:18.386440Z","shell.execute_reply.started":"2023-01-24T17:15:18.333451Z","shell.execute_reply":"2023-01-24T17:15:18.385231Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"sns.kdeplot(data=contact_true)","metadata":{"execution":{"iopub.status.busy":"2023-01-24T17:15:18.388581Z","iopub.execute_input":"2023-01-24T17:15:18.389091Z","iopub.status.idle":"2023-01-24T17:15:18.577143Z","shell.execute_reply.started":"2023-01-24T17:15:18.389045Z","shell.execute_reply":"2023-01-24T17:15:18.575861Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Interestingly there is a peak of contacts around the start of the play that then tails off to a roughly consistent amount for the rest of the play until near the end","metadata":{}}]}