{"metadata":{"kernelspec":{"name":"ir","display_name":"R","language":"R"},"language_info":{"name":"R","codemirror_mode":"r","pygments_lexer":"r","mimetype":"text/x-r-source","file_extension":".r","version":"4.0.5"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"In this notebook, I studied kickoff data, specifically looking at returned and allowed field position for teams across the NFL. I started by loading in desrired packages and csv files for my analysis. Then, I explored the structure and content of each data frame. I cleaned, filtered, and joined multiple data frames to form seasonal data used to calculate my new kickoff metric.\n\nOnce the data was filtered and cleaned, I created a series of functions that determined the strength of the opponenet on a kickoff. Then I calculated the average field position after a kickoff for each team on offense and defense and adjusted for the strength of the opponent. Finally, I created two functions to calculate the offensive and defensive tKP (Team Kickoff Performance) Metric which is the percent away from the mean average adjusted field position after a kickoff.\n\nThroughout the process I created graphs and tables to inform my decisions and conduct further analysis.","metadata":{}},{"cell_type":"code","source":"# Loading in packages\nlibrary(tidyverse)\nlibrary(patchwork)\nlibrary(skimr)\nlibrary(psych)\n\n# Loading in the data\ngames <- read.csv(\"../input/nfl-big-data-bowl-2022/games.csv\")\nplays <- read.csv(\"../input/nfl-big-data-bowl-2022/plays.csv\")\nplayers <-read.csv(\"../input/nfl-big-data-bowl-2022/players.csv\")\nPFFScoutingData <- read.csv(\"../input/nfl-big-data-bowl-2022/PFFScoutingData.csv\")\ntracking2020 <-read.csv(\"../input/nfl-big-data-bowl-2022/tracking2020.csv\")","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:37:30.456685Z","iopub.execute_input":"2022-01-06T04:37:30.494734Z","iopub.status.idle":"2022-01-06T04:39:22.083121Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Returning distinct specialTeamsPlayType\nunique(plays$specialTeamsPlayType)\n\n# Filtering Play data to include only Kickoffs\nkickoff_plays <- filter(plays, specialTeamsPlayType == \"Kickoff\")\n\n# Joining kickoff_plays and PFFScoutingData  \nkickoffs <- left_join(kickoff_plays, PFFScoutingData, by = c(\"playId\", \"gameId\"))\nsapply(kickoffs, n_distinct)\nhead(kickoffs)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:22.085718Z","iopub.execute_input":"2022-01-06T04:39:22.087240Z","iopub.status.idle":"2022-01-06T04:39:22.180626Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I explored what types of plays were present in the plays data frame which informed my decision to study kickoffs. Then, I joined plays and PFFScoutingData.","metadata":{}},{"cell_type":"code","source":"# Exploring Kickoff Results\nunique(kickoffs$specialTeamsResult)\n\nkickoff_result = kickoffs$specialTeamsResult\nggplot(kickoffs, aes(x = factor(kickoff_result))) +\n  geom_bar(stat = \"count\", width = .7)\n\nftable(kickoff_result)\nround(prop.table(table(kickoff_result)), digits = 4)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:22.183155Z","iopub.execute_input":"2022-01-06T04:39:22.184573Z","iopub.status.idle":"2022-01-06T04:39:22.904203Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Once I narrowed down my analysis to kickoff plays only, I found that toucbacks and returns were the most common results of a kickoff play.","metadata":{}},{"cell_type":"code","source":"# Exploring Returned Kickoffs\nkickoff_plays_returned_raw <- filter(kickoffs, specialTeamsResult == \"Return\")\nView(t(colSums(is.na(kickoff_plays_returned_raw))))\n\nkickoff_plays_returned_raw %>%\n  filter(is.na(kickReturnYardage))\n\n# Cleaning Data\nkickoff_plays_returned <- filter(kickoff_plays_returned_raw, kickReturnYardage != \"NA\" & is.na(penaltyCodes) & yardlineNumber == \"35\" & kickType != \"K\")","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:22.906699Z","iopub.execute_input":"2022-01-06T04:39:22.908127Z","iopub.status.idle":"2022-01-06T04:39:22.966932Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I further narrowed down my dataset to include only returned kickoffs for further analysis. I then cleaned the data to have no NA returnYardage values, no penaltys before, during, or after the play, and to not include kickoffs after a safety. ","metadata":{}},{"cell_type":"code","source":"# Exploring Kick Type\nunique(kickoff_plays_returned$kickType)\n\nkickoff_type <- kickoff_plays_returned$kickType\np1 <- ggplot(kickoff_plays_returned, aes(x = factor(kickoff_type))) +\n        ggtitle(\"Frequency\") +\n        geom_bar(stat = \"count\", width = .7)\nftable(kickoff_type)\nround(prop.table(table(kickoff_type)), digits = 4)\n\nkick_yards <- kickoff_plays_returned$kickLength\np2 <- ggplot(kickoff_plays_returned, aes(x = factor(kickoff_type), y = kick_yards)) +\n  ggtitle(\"Kick Length\") +\n  geom_point() +\n  stat_summary(fun=mean, geom=\"point\", colour = \"red\", shape = 20) +\n  ylim(-20,120)\n\nopponent_return_yards <- kickoff_plays_returned$kickReturnYardage\np3 <- ggplot(kickoff_plays_returned, aes(x = factor(kickoff_type), y = opponent_return_yards)) +\n  ggtitle(\"Return Length\") +\n  geom_point() +\n  stat_summary(fun=mean, geom=\"point\", colour = \"red\", shape = 20) +\n  ylim(-20,120)\n\np1+p2+p3","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:47:04.800806Z","iopub.execute_input":"2022-01-06T04:47:04.802489Z","iopub.status.idle":"2022-01-06T04:47:05.663632Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I graphed kick type against frequency, kick length, and return length. For the kick length and return length graphs, red dots indicate the mean for each respective kickoff type. \"D\" (Deep Normal Kicks) were extremely dominate in frequency and uniform in kick length making them a goo dcandite for evaluting a teams kickoff performance. ","metadata":{}},{"cell_type":"code","source":"# Joining kickoffs with games\ngame_kickoff_plays_returned_raw <- left_join(kickoff_plays_returned, games, by = c(\"gameId\"))\n\n# Filter for Deep Kickoffs \nd_kickoffs_returned <- filter(game_kickoff_plays_returned_raw, kickType == \"D\")\n\n# Adding kickoff_team and return_team columns to d_kickoffs_returned\nadd_role_cols <- function(df) {\n  teams <- df %>%\n    select(homeTeamAbbr, visitorTeamAbbr)\n  kickoff_team <- df %>% \n    select(possessionTeam)\n  val <- data.frame(kickoff_team, return_team = NA)\n  num <- nrow(df)\n  for (i in 1:num) {\n    if (val[i,1] == teams[i,1]) {\n      val[i,2] <- teams[i,2]\n    } else {\n      val[i,2] <- teams[i,1]\n    }\n  }\n  colnames(val)[1] <- \"kickoff_team\"\n  return(val)\n}\n\nnew_cols <- add_role_cols(d_kickoffs_returned)\nd_kickoffs_returned <- cbind(d_kickoffs_returned, new_cols)\n\n# Creating Season Specific dfs for future testing\nd_kickoffs_returned_18 <- filter(d_kickoffs_returned, season == '2018')\n\nd_kickoffs_returned_19 <- filter(d_kickoffs_returned, season == '2019')\n\nd_kickoffs_returned_20 <- filter(d_kickoffs_returned, season == '2020')","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:24.439715Z","iopub.execute_input":"2022-01-06T04:39:24.441176Z","iopub.status.idle":"2022-01-06T04:39:24.712296Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"I filted on \"D\" kickoffs, added columns specifying the kickoff and return team for a play, and created seasonal data frames for further analysis.","metadata":{}},{"cell_type":"code","source":"###### tOKP (Team Offensive Kickoff Performance) ###### \n\n# Function for finding the average yards allowed (defensive) for all teams in a season given a df (season data)\navg_ya <- function(df) {\n  teams <- unique(df$kickoff_team)\n  val <- data.frame(teams, avg_ya = NA)\n  j <- 0\n  for (i in teams) {\n    team <- df %>% \n      filter(kickoff_team == i)\n    avg_ya <- mean(team$kickReturnYardage)\n    j <- j + 1\n    val[j,2] <- avg_ya\n  }\n  return(val)\n}\n\n# Function for creating bins for measuring the strength of defense on the kickoff (based on average yards allowed) given a df (season data)\nsod_bins <- function(df) {\n  val <- avg_ya(df)\n  bin <- cut(val$avg_ya, 5, labels = c(1,2,3,4,5))\n  val <- cbind(val, bin)\n  return(val)\n}\n\n# Function for converting bin value to weight value given a df (season data)\nsod_weight <- function(df) {\n  bins <- sod_bins(df)\n  teams <- bins$teams\n  weight <- data.frame(teams, val = NA)\n  num <- nrow(bins)\n  for (i in 1:num) {\n    if (bins$bin[i] == 1) {\n      weight$val[i] <- 1.1\n    } else if (bins$bin[i] == 2) {\n      weight$val[i] <- 1.05\n    } else if (bins$bin[i] == 3) {\n      weight$val[i] <- 1\n    } else if (bins$bin[i] == 4) {\n      weight$val[i] <- .95\n    } else {\n      weight$val[i] <- .9\n    }\n  }\n  return(weight)\n}\n\n# Function to find the weight value given a team and a df (weights data)\nfind_weight <- function(team, weights) {\n  val <- weights %>% \n    filter(teams == team)\n  val = val[,2]\n  return(val)\n}\n\n# Function for finding adjusted average field position returned after kickoff for each team\nadj_avg_fpr <- function(df) {\n  teams <- unique(df$return_team)\n  val <- data.frame(teams, adj_avg_fpr = NA)\n  count <- 0\n  weights <- sod_weight(df)\n  for (i in teams) {\n    team <- df %>% \n      filter(return_team == i)\n    num <- nrow(team)\n    adj_fp <- 0\n    for (j in 1:num) {\n      kickoff_team <- team$kickoff_team[j]\n      weight <- find_weight(kickoff_team, weights)\n      adj_fp <- adj_fp + weight*(100 - df$yardlineNumber[j] - df$kickLength[j] + df$kickReturnYardage[j])\n    }\n    adj_avg_fpr <- adj_fp/num\n    count <- count + 1\n    val[count,2] <- adj_avg_fpr\n  }\n  return(val)\n}\n\n# Function for calculating the tOKP\ntokp <- function(df) {\n  adj_avg_fpr <- adj_avg_fpr(df)\n  league_adj_avg_fpr <- mean(adj_avg_fpr$adj_avg_fpr)\n  tokp_diff <- adj_avg_fpr\n  tokp_diff[,2] <- tokp_diff[,2] - league_adj_avg_fpr\n  tokp <- tokp_diff\n  tokp[,2] <- round((tokp[,2]/league_adj_avg_fpr)*100,3)\n  colnames(tokp)[2] <- \"tokp\"\n  return(tokp)\n}\n","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:24.714831Z","iopub.execute_input":"2022-01-06T04:39:24.716329Z","iopub.status.idle":"2022-01-06T04:39:24.774854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"###### tDKP (Team Defensive Kickoff Performance) ###### \n\n# Function for finding the average yards returned (offensive) for all teams in a season given a df (season data)\navg_yr <- function(df) {\n  teams <- unique(df$return_team)\n  val <- data.frame(teams, avg_yr = NA)\n  j <- 0\n  for (i in teams) {\n    team <- df %>% \n      filter(return_team == i)\n    avg_yr <- mean(team$kickReturnYardage)\n    j <- j + 1\n    val[j,2] <- avg_yr\n  }\n  return(val)\n}\n\n# Function for creating bins for measuring the strength of offense on the kickoff (based on average yards returned) given a df (season data)\nsoo_bins <- function(df) {\n  val <- avg_yr(df)\n  bin <- cut(val$avg_yr, 5, labels = c(5,4,3,2,1))\n  val <- cbind(val, bin)\n  return(val)\n}\n\n# Function for converting bin value to weight value given a df (season data)\nsoo_weight <- function(df) {\n  bins <- soo_bins(df)\n  teams <- bins$teams\n  weight <- data.frame(teams, val = NA)\n  num <- nrow(bins)\n  for (i in 1:num) {\n    if (bins$bin[i] == 1) {\n      weight$val[i] <- 1.1\n    } else if (bins$bin[i] == 2) {\n      weight$val[i] <- 1.05\n    } else if (bins$bin[i] == 3) {\n      weight$val[i] <- 1\n    } else if (bins$bin[i] == 4) {\n      weight$val[i] <- .95\n    } else {\n      weight$val[i] <- .9\n    }\n  }\n  return(weight)\n}\n\n# Function for finding adjusted average field position allowed after kickoff for each team\nadj_avg_fpa <- function(df) {\n  teams <- unique(df$kickoff_team)\n  val <- data.frame(teams, adj_avg_fpa = NA)\n  count <- 0\n  weights <- soo_weight(df)\n  for (i in teams) {\n    team <- df %>% \n      filter(kickoff_team == i)\n    num <- nrow(team)\n    adj_fp <- 0\n    for (j in 1:num) {\n      return_team <- team$return_team[j]\n      weight <- find_weight(return_team, weights)\n      adj_fp <- adj_fp + weight*(100 - df$yardlineNumber[j] - df$kickLength[j] + df$kickReturnYardage[j])\n    }\n    adj_avg_fpa <- adj_fp/num\n    count <- count + 1\n    val[count,2] <- adj_avg_fpa\n  }\n  return(val)\n}\n\n# Function for calculating the tDKP\ntdkp <- function(df) {\n  adj_avg_fpa <- adj_avg_fpa(df)\n  league_adj_avg_fpa <- mean(adj_avg_fpa$adj_avg_fpa)\n  tdkp_diff <- adj_avg_fpa\n  tdkp_diff[,2] <- tdkp_diff[,2] - league_adj_avg_fpa\n  tdkp <- tdkp_diff\n  tdkp[,2] <- round((tdkp[,2]/league_adj_avg_fpa)*100,3)\n  colnames(tdkp)[2] <- \"tdkp\"\n  return(tdkp)\n}\n","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:24.777318Z","iopub.execute_input":"2022-01-06T04:39:24.778729Z","iopub.status.idle":"2022-01-06T04:39:24.800830Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Calculating tDKP and tOKP for all seasons provided\ntokp_18 = tokp(d_kickoffs_returned_18)\ntokp_19 = tokp(d_kickoffs_returned_19)\ntokp_20 = tokp(d_kickoffs_returned_20)\n\ntdkp_18 = tdkp(d_kickoffs_returned_18)\ntdkp_19 = tdkp(d_kickoffs_returned_19)\ntdkp_20 = tdkp(d_kickoffs_returned_20)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:24.803185Z","iopub.execute_input":"2022-01-06T04:39:24.804566Z","iopub.status.idle":"2022-01-06T04:39:31.926477Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A key difference about tOKP and tDKP is that positive values indiciate out performing league averages for tOKP whereas negative values indicate out performing league averages for tDKP","metadata":{}},{"cell_type":"code","source":"# Viewing tOKP's and summary statistics for each season\nView(tokp_18)\ndescribe(tokp_18$tokp)\n\nView(tokp_19)\ndescribe(tokp_19$tokp)\n\nView(tokp_20)\ndescribe(tokp_20$tokp)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:31.929124Z","iopub.execute_input":"2022-01-06T04:39:31.930625Z","iopub.status.idle":"2022-01-06T04:39:32.060196Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# tOKP Plots\nggplot(tokp_18, aes(x = teams, y = tokp)) +\ngeom_bar(stat = \"identity\") +\nggtitle(\"2018 Season tOKP\") +\nylim(-20,20)\n\nggplot(tokp_19, aes(x = teams, y = tokp)) +\ngeom_bar(stat = \"identity\") +\nggtitle(\"2019 Season tOKP\") +\nylim(-20,20)\n\nggplot(tokp_20, aes(x = teams, y = tokp)) +\ngeom_bar(stat = \"identity\") +\nggtitle(\"2020 Season tOKP\") +\nylim(-20,20)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:32.062968Z","iopub.execute_input":"2022-01-06T04:39:32.064581Z","iopub.status.idle":"2022-01-06T04:39:32.863673Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In the 2018 the majority of teams had positive tOKP's, whereas in 2019 and 2020 there was an approximately even split. This could be due to extreme under performance from some teams resulting in a low league average. \n\nIn 2018, Arizona, Chicago, Jacksonville, and New Orleans had the most effective offensive performances accross the league. In contrast, Detroit, Dallas, Indianpolis and New York had the least effective offensive performances accross the league.\n\nIn 2019, Los Angeles Rams, Los Angeles Chargers, Jacksonville, and Carolina had the most effective offensive performances accross the league. In contrast, Minnesota, New York, Seattle and Houston had the least effective offensive performances accross the league.\n\nIn 2020, New York, Houston, Dallas, and Los Angeles Chargers had the most effective offensive performances accross the league. In contrast, Miami, Las Vegas, Carolina and Denver had the least effective offensive performances accross the league. ","metadata":{}},{"cell_type":"code","source":"# Viewing tDKP's for each season\nView(tdkp_18)\ndescribe(tdkp_18$tdkp)\n\nView(tdkp_19)\ndescribe(tdkp_19$tdkp)\n\nView(tdkp_20)\ndescribe(tdkp_20$tdkp)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:56:04.058901Z","iopub.execute_input":"2022-01-06T04:56:04.062131Z","iopub.status.idle":"2022-01-06T04:56:04.274215Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# tDKP Plots\nggplot(tdkp_18, aes(x = teams, y = tdkp)) +\n  geom_bar(stat = \"identity\") +\n  ggtitle(\"2018 Season tDKP\") +\n  ylim(-20,20)\n\nggplot(tdkp_19, aes(x = teams, y = tdkp)) +\n  geom_bar(stat = \"identity\") +\n  ggtitle(\"2019 Season tDKP\") +\n  ylim(-20,20)\n\nggplot(tdkp_20, aes(x = teams, y = tdkp)) +\n  geom_bar(stat = \"identity\") +\n  ggtitle(\"2020 Season tDKP\") +\n  ylim(-20,20)","metadata":{"execution":{"iopub.status.busy":"2022-01-06T04:39:32.983125Z","iopub.execute_input":"2022-01-06T04:39:32.984575Z","iopub.status.idle":"2022-01-06T04:39:33.755341Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In 2018, Carolina, Jacksonville, Los Angeles Chargers, and Oakland had the most effective defensive performances accross the league. In contrast, Buffal, Chicago, Indianpolis and Los Angeles Rams had the least effective defensive performances accross the league.\n\nIn 2019, Indianpolis, Cincinati, Houston, and Minnesota had the most effective defensive performances accross the league. In contrast, Tampa Bay, Baltimore, New York and Cleveland had the least effective defensive performances accross the league.\n\nIn 2020, New York, Cincinati, Jacksonville, and Miami had the most effective defensive performances accross the league. In contrast, Tennessee, Los Angeles Chargers, Green Bay and Atlanta had the least effective defensive performances accross the league. ","metadata":{}}]}