pathrs = "G:/Runshop/"
setwd(pathrs)
pacman::p_load(rmarkdown, data.table, tidyverse, magrittr, janitor, lubridate, scales, matrixStats, gt, patchwork)
source("G:/Colossus/codes/R/Runshop/Runshop_functions.R")
`%notin%` = Negate(`%in%`)College Football – BettorIQ – Analysis of Qual Signal
Import dependencies
Import historical BettorIQ data
Rows: 38,792
Columns: 233
$ week <dbl> 9, 1, 3, 1, 1, 1, 2, 1, 1, 13, 3, 5, 12, 1, 3, …
$ season <dbl> 2013, 2014, 2014, 2015, 2016, 2017, 2017, 2018,…
$ week_qualify <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Yes"…
$ date <dttm> 2013-10-26, 2014-08-27, 2014-09-13, 2015-09-05…
$ time <dttm> 1899-12-31 20:00:00, 1899-12-31 19:00:00, 1899…
$ team <chr> "ABILENE CHR", "ABILENE CHR", "ABILENE CHR", "A…
$ opponent <chr> "NEW MEXICO ST", "GEORGIA ST", "TROY", "FRESNO …
$ away <chr> "ABILENE CHR", "ABILENE CHR", "ABILENE CHR", "A…
$ home <chr> "NEW MEXICO ST", "GEORGIA ST", "TROY", "FRESNO …
$ team_concat <chr> "ABILENE CHR41573", "ABILENE CHR41878", "ABILEN…
$ opp_concat <chr> "NEW MEXICO ST41573", "GEORGIA ST41878", "TROY4…
$ team_fbs <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ opp_fbs <chr> "YES", "YES", "YES", "YES", "YES", "YES", "YES"…
$ fbs <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ team_qual_rating <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ opp_qual_rating <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 0.2, -1.9, NA, …
$ qual_edge <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ qual_edge_cat <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ side_qualify_basic <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ side_qualify_combined <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ team_total_rating <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 0, 0, NA, NA, N…
$ opp_total_rating <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 1.5, 3.0, NA, N…
$ combined_total_edge <dbl> NA, NA, NA, NA, NA, NA, NA, NA, 1.5, 3.0, NA, N…
$ basic_total_qualify <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ combined_total_qualify <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ original_h_a <chr> "Away", "Away", "Away", "Away", "Away", "Away",…
$ h_a <chr> "Away", "Away", "Away", "Away", "Away", "Away",…
$ q1awayscore <dbl> 3, 3, 7, 7, 0, 7, 0, 7, 0, 0, 7, 0, 0, 0, 3, 0,…
$ q2awayscore <dbl> 19, 13, 0, 0, 7, 0, 0, 13, 10, 7, 0, 3, 7, 3, 0…
$ q3awayscore <dbl> 0, 14, 21, 0, 7, 0, 10, 7, 7, 0, 3, 7, 0, 6, 7,…
$ q4awayscore <dbl> 7, 7, 10, 6, 7, 7, 0, 0, 14, 0, 3, 13, 8, 0, 7,…
$ otawaysore <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ q1homescore <dbl> 7, 7, 7, 14, 7, 7, 10, 17, 17, 7, 0, 7, 14, 7, …
$ q2homescore <dbl> 14, 14, 14, 7, 21, 7, 7, 21, 21, 14, 10, 16, 14…
$ q3homescore <dbl> 0, 0, 7, 10, 0, 10, 14, 10, 10, 7, 0, 15, 14, 1…
$ q4homescore <dbl> 13, 17, 7, 3, 9, 14, 7, 7, 3, 17, 7, 17, 13, 7,…
$ othomescore <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ awascore <dbl> 29, 37, 38, 13, 21, 14, 10, 27, 31, 7, 13, 23, …
$ homscore <dbl> 34, 38, 35, 34, 37, 38, 38, 55, 51, 45, 17, 55,…
$ ot <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA…
$ neutral <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ openspr <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ openfav <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ opentot <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ openhml <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ spread <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ favored <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ team_score <dbl> 29, 37, 38, 13, 21, 14, 10, 27, 31, 7, 13, 23, …
$ opp_score <dbl> 34, 38, 35, 34, 37, 38, 38, 55, 51, 45, 17, 55,…
$ margin <dbl> -5, -1, 3, -21, -16, -24, -28, -28, -20, -38, -…
$ spread_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ fave_dog_op <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ cover_margin_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ats_w_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ats_l_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ats_p_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ spread_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ spread_cl_qual <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ fave_dog_cl <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ cover_margin_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ats_w_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ats_l_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ ats_p_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ total <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ total_points <dbl> 63, 75, 73, 47, 58, 52, 48, 82, 82, 52, 30, 78,…
$ totals_week_qualify <chr> NA, "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "…
$ total_cover_margin_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ over_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ under_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ push_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ total_cover_margin_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ over_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ under_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ push_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ homeml <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ team_ml_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ team_ml_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ opp_ml_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ opp_ml_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ openh1spr <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ openh1fav <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ openh1sprodds <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
$ openh1tot <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ openh1totou <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ openh1totodds <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
$ openh1ml <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ half1spr <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ half1fav <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ half1sprodds <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
$ half1tot <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ half1totou <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ half1totodds <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
$ half1ml <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, …
$ x1h_team_score <dbl> 22, 16, 7, 7, 7, 7, 0, 20, 10, 7, 7, 3, 7, 3, 3…
$ x1h_opp_score <dbl> 21, 21, 21, 21, 28, 14, 17, 38, 38, 21, 10, 23,…
$ x1h_margin <dbl> 1, -5, -14, -14, -21, -7, -17, -18, -28, -14, -…
$ x1h_margin_qual <chr> NA, "2H TRAIL", "2H TRAIL", "2H TRAIL", "2H TRA…
$ x1h_spread_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_cover_margin_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_ats_w_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_ats_l_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_ats_p_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_spread_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_cover_margin_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_ats_w_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_ats_l_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_ats_p_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_total_points <dbl> 43, 37, 28, 28, 35, 21, 17, 58, 48, 28, 17, 26,…
$ x1h_total_margin_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_over_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_under_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_push_op <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_total_margin_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_over_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_under_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x1h_push_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ half2spr <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ half2fav <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ half2sprodds <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
$ half2tot <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ half2totou <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ half2totodds <dbl> 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.…
$ half2ml <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, …
$ x2h_team_score <dbl> 7, 21, 31, 6, 14, 7, 10, 7, 21, 0, 6, 20, 8, 6,…
$ x2h_opp_score <dbl> 13, 17, 14, 13, 9, 24, 21, 17, 13, 24, 7, 32, 2…
$ x2h_margin <dbl> -6, 4, 17, -7, 5, -17, -11, -10, 8, -24, -1, -1…
$ x2h_spread_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_fave_dog <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_cover_margin_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_ats_w_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_ats_l_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_ats_p_cl <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_total_points <dbl> 20, 38, 45, 19, 23, 31, 31, 24, 34, 24, 13, 52,…
$ x2h_total_margin <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_over <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_under <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ x2h_push <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ conf <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,…
$ desc <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ confchamp <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ bowl <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
$ hfdowns <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afdowns <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hratt <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ aratt <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hrush <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ arush <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpcomp <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apcomp <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpatt <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apatt <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpass <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apass <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hint <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ aint <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpen <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpenyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apen <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apenyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfum <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfumlost <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afum <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afumlost <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hsack <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hsackyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ asack <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ asackyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ htime <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
$ atime <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00,…
$ hpuntret <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpuntretyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apuntret <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apuntretyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hintret <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hintretyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ aintret <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ aintretyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hkoret <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hkoretyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ akoret <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ akoretyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ h3downc <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ h3downa <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ a3downc <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ a3downa <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ h4downc <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ h4downa <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ a4downc <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ a4downa <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpunt <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpuntyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpuntin20 <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hpunttb <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apunt <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apuntyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apuntin20 <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ apunttb <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hkickoff <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hkickoffyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hkickofftb <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ akickoff <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ akickoffyds <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ akickofftb <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfg20a <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfg20m <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfg30a <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfg30m <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfg40a <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfg40m <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfg50a <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hfg50m <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afg20a <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afg20m <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afg30a <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afg30m <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afg40a <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afg40m <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afg50a <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ afg50m <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hepm <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hepa <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ aepm <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ aepa <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ hwin <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ hloss <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ awin <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ aloss <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ temp <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ cond <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ wind <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA,…
$ dir <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ outdoor <dbl> 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,…
$ surface <chr> "G", "FT", "FT", "FT", "FT", "FT", "FT", "FT", …
Data pre-processing
setDT(biq_data)
biq_data = biq_data[!is.na(time)]
biq_data[, ko_time := strftime(time, format = "%H:%M:%S")]
biq_data[, date_time := as.POSIXct(paste(date, ko_time))]
biq_data[, year := year(date_time)]
setorder(biq_data, "date_time", "original_h_a")
# check corresponding team and opponent names are spelled and formatted consistently
biq_data[team %notin% opponent, unique(team)]character(0)
biq_data[opponent %notin% team, unique(opponent)][1] "UMASS"
# only discrepancy is U MASS, so coerce for both team and opponent fields
biq_data[team=="U MASS", team := "UMASS"]
biq_data[opponent=="U MASS", opponent := "UMASS"]
# create unique match-up identifier based on kick-off time and team names
biq_data[, match_id := paste0(date_time, ifelse(original_h_a=="Home", paste0(team, opponent), paste0(opponent, team)))]
biq_data[, duplicate_check := uniqueN(team), by = .(match_id)]
biq_data[duplicate_check!=2] # 0 rowsEmpty data.table (0 rows and 238 cols): week,season,week_qualify,date,time,team...
# only keep home team row per match-up (home ordered first)
biq_data = biq_data[!duplicated(match_id, fromLast = T)]
cat("Number matches remaining: ", comma(biq_data[, .N]))Number matches remaining: 17,965
# From Matt: "Just a heads up to filter out those rows in which the data looks funky / off. Specifically, I'd filter the "OPENTOT" column and remove, or tag, those rows that are either "0", "blank" or any outlier number that doesn't make sense - for example, any "OPENTOT" value below 20 or above 200 are likely errors, as no football total would ever fall outside those boundaries."
biq_data[, summary(opentot)] Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
0.00 47.00 53.00 49.32 58.50 90.00 359
biq_data = biq_data[!is.na(opentot) & opentot != 0 & opentot %between% c(20, 200)]
## also remove rows with other missing lines/spreads
opening_prices = c("spread_op", "cover_margin_op", "opentot", "total_cover_margin_op")
closing_prices = c("spread_cl", "cover_margin_cl", "total", "total_cover_margin_cl")
biq_data[, lapply(.SD, function(x) sum(is.na(x))), .SDcols = opening_prices] spread_op cover_margin_op opentot total_cover_margin_op
1: 0 0 0 1
biq_data[, lapply(.SD, function(x) sum(is.na(x))), .SDcols = closing_prices] spread_cl cover_margin_cl total total_cover_margin_cl
1: 0 0 0 1
biq_data = biq_data[!is.na(spread_op) & !is.na(opentot) & !is.na(cover_margin_op) & !is.na(total_cover_margin_op)]
cat("Number matches remaining: ", comma(biq_data[, .N]))Number matches remaining: 15,945
# Keep game-weeks 0 through 16
biq_data = biq_data[week %in% 0:16]
cat("Number matches remaining: ", comma(biq_data[, .N]))Number matches remaining: 15,175
Simple descriptive analysis
Spreads:
- Sense-checking error distribution on lines: overall symmetric and mean-zero
- Spread errors decreasing across seasons (markets becoming more efficient); but errors not significantly decreasing over gameweeks within a season
Call:
lm(formula = op_spread_error ~ year + week, data = biq_data)
Residuals:
Min 1Q Median 3Q Max
-64.14 -10.17 -0.01 10.39 62.65
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 104.644109 40.560552 2.580 0.00989 **
year -0.052063 0.020156 -2.583 0.00981 **
week 0.004166 0.032781 0.127 0.89886
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 15.69 on 15172 degrees of freedom
Multiple R-squared: 0.0004396, Adjusted R-squared: 0.0003079
F-statistic: 3.336 on 2 and 15172 DF, p-value: 0.03559
Call:
lm(formula = spread_error ~ year + week, data = biq_data)
Residuals:
Min 1Q Median 3Q Max
-71.225 -10.142 -0.009 10.376 63.175
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 70.196764 40.269292 1.743 0.0813 .
year -0.034976 0.020012 -1.748 0.0805 .
week 0.004657 0.032545 0.143 0.8862
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 15.57 on 15172 degrees of freedom
Multiple R-squared: 0.0002017, Adjusted R-squared: 6.994e-05
F-statistic: 1.531 on 2 and 15172 DF, p-value: 0.2164
Totals:
- Totals lines seem quite well calibrated on average (quite symmetric, with mean-zero error)
- Generally, actual totals are increasing both across and within seasons
- Opening totals errors are higher in later game-weeks, on average (more line-up uncertainty/fixture congestion/injuries?)
- Closing totals errors are higher in later game-weeks, on average. No significant association with year
Call:
lm(formula = actual_total ~ year + week, data = biq_data)
Residuals:
Min 1Q Median 3Q Max
-51.821 -12.585 -1.053 11.380 89.498
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -341.94665 46.99842 -7.276 3.61e-13 ***
year 0.19644 0.02336 8.411 < 2e-16 ***
week 0.15589 0.03798 4.104 4.08e-05 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 18.18 on 15172 degrees of freedom
Multiple R-squared: 0.005906, Adjusted R-squared: 0.005775
F-statistic: 45.07 on 2 and 15172 DF, p-value: < 2.2e-16
Call:
lm(formula = op_totals_error ~ year + week, data = biq_data)
Residuals:
Min 1Q Median 3Q Max
-54.467 -11.606 -0.779 10.665 97.828
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 42.52393 43.28158 0.982 0.32587
year -0.02143 0.02151 -0.996 0.31915
week 0.10687 0.03498 3.055 0.00225 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 16.74 on 15172 degrees of freedom
Multiple R-squared: 0.0006668, Adjusted R-squared: 0.0005351
F-statistic: 5.062 on 2 and 15172 DF, p-value: 0.006345
Call:
lm(formula = totals_error ~ year + week, data = biq_data)
Residuals:
Min 1Q Median 3Q Max
-75.558 -11.377 -0.819 10.468 98.681
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 5.328234 42.656165 0.125 0.9006
year -0.002796 0.021198 -0.132 0.8951
week 0.087095 0.034474 2.526 0.0115 *
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 16.5 on 15172 degrees of freedom
Multiple R-squared: 0.0004206, Adjusted R-squared: 0.0002889
F-statistic: 3.192 on 2 and 15172 DF, p-value: 0.04111
Line movement analysis: opening vs closing
- Seems to be less spread movement (from open to close) for home favourites vs. home dogs, on average
- Spread movement somewhat increases over gameweeks and across seasons for home dogs
- Totals movements are higher earlier in the season, seem to stabilise by GW10.
- Totals movements have been higher past few years vs pre-2013.
Call:
lm(formula = spread_change_pct ~ year + week, data = biq_data[favored ==
"H"])
Residuals:
Min 1Q Median 3Q Max
-6.9793 -0.1017 0.0122 0.1226 8.0448
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -5.385869 1.643821 -3.276 0.00106 **
year 0.002672 0.000817 3.271 0.00107 **
week -0.002304 0.001313 -1.755 0.07931 .
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.5029 on 9506 degrees of freedom
Multiple R-squared: 0.001406, Adjusted R-squared: 0.001196
F-statistic: 6.692 on 2 and 9506 DF, p-value: 0.001247
Call:
lm(formula = spread_change_pct ~ year + week, data = biq_data[favored ==
"A"])
Residuals:
Min 1Q Median 3Q Max
-7.3559 -0.0935 0.0521 0.2041 10.0262
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 13.721593 2.899046 4.733 2.27e-06 ***
year -0.006861 0.001440 -4.763 1.95e-06 ***
week 0.001043 0.002406 0.434 0.665
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.6798 on 5581 degrees of freedom
Multiple R-squared: 0.004066, Adjusted R-squared: 0.003709
F-statistic: 11.39 on 2 and 5581 DF, p-value: 1.154e-05
Call:
lm(formula = total_change_pct ~ year + week, data = biq_data[favored ==
"H"])
Residuals:
Min 1Q Median 3Q Max
-0.29439 -0.03176 -0.00070 0.02877 0.34809
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -1.211e+00 1.579e-01 -7.666 1.95e-14 ***
year 6.047e-04 7.849e-05 7.704 1.45e-14 ***
week -4.071e-04 1.261e-04 -3.227 0.00125 **
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.04832 on 9506 degrees of freedom
Multiple R-squared: 0.007108, Adjusted R-squared: 0.0069
F-statistic: 34.03 on 2 and 9506 DF, p-value: 1.88e-15
Call:
lm(formula = total_change_pct ~ year + week, data = biq_data[favored ==
"A"])
Residuals:
Min 1Q Median 3Q Max
-58.693 -0.015 0.015 0.050 0.577
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 2.736247 4.327599 0.632 0.527
year -0.001381 0.002150 -0.642 0.521
week 0.003850 0.003591 1.072 0.284
Residual standard error: 1.015 on 5581 degrees of freedom
Multiple R-squared: 0.0002735, Adjusted R-squared: -8.478e-05
F-statistic: 0.7634 on 2 and 5581 DF, p-value: 0.4662
Qual ratings data
- Qualitative ratings are recorded from 2019-2023. Higher numbers represent stronger qualitative edges and the ratings are compared or combined for each match-up.
- Generally ratings seem quite well calibrated (spreads/totals monotonic in edge score)
- Only simple correlational analysis for now; need to decompose individual edge ratings more and blend with other datasets provided
Rows: 3,461
Columns: 11
$ team_qual_rating <dbl> 3.8, 8.8, 3.9, 2.0, 3.6, 5.9, 3.0, 5.3, -4.3, 5…
$ opp_qual_rating <dbl> -4.3, 6.0, 7.6, -10.9, 4.0, -2.1, 6.5, 3.8, 1.2…
$ qual_edge <dbl> 8.1, 2.8, -3.7, 12.9, -0.4, 8.0, -3.5, 1.5, -5.…
$ qual_edge_cat <dbl> 8, 3, -4, 13, 0, 8, -4, 2, -6, 2, 9, 6, 7, 0, 7…
$ side_qualify_basic <chr> "Yes", "Yes", "Yes", NA, "Yes", "Yes", "Yes", "…
$ side_qualify_combined <chr> "Yes", NA, NA, "Yes", NA, "Yes", NA, NA, NA, NA…
$ team_total_rating <dbl> -0.9, 1.0, 0.2, 3.9, 1.3, 1.4, -3.7, 4.7, 2.6, …
$ opp_total_rating <dbl> -1.000000e+00, -2.700000e+00, 1.000000e+00, 2.4…
$ combined_total_edge <dbl> -1.9, -1.7, 1.2, 6.3, 5.0, 3.6, -2.1, 2.5, 2.6,…
$ basic_total_qualify <chr> NA, NA, NA, "OVER", NA, NA, "UNDER", "OVER", NA…
$ combined_total_qualify <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
| home_qual_edge_quintile | favored | number_selections | avg_home_win_rate | avg_spread_op | avg_spread_cl | avg_op_error | avg_cl_error | avg_spread_change_pct |
|---|---|---|---|---|---|---|---|---|
| (-18,-4.6] | H | 229.0 | 67.2% | −10.4 | 10.4 | −3.1 | −3.2 | −0.6% |
| (-18,-4.6] | A | 211.0 | 21.8% | 9.7 | 10.4 | −3.1 | −2.3 | −13.6% |
| (-18,-4.6] | E | 4.0 | 50.0% | 1.0 | 0.0 | 2.0 | 1.0 | 0.0% |
| (-4.6,-1.4] | H | 258.0 | 79.5% | −11.9 | 12.0 | 0.4 | 0.3 | 2.4% |
| (-4.6,-1.4] | A | 187.0 | 28.9% | 8.3 | 9.0 | −1.6 | −0.9 | −16.5% |
| (-1.4,1.7] | H | 276.0 | 74.6% | −12.5 | 12.5 | 0.3 | 0.3 | 2.6% |
| (-1.4,1.7] | A | 163.0 | 29.4% | 9.5 | 10.3 | 1.7 | 2.5 | −9.9% |
| (-1.4,1.7] | E | 2.0 | 0.0% | 0.0 | 0.0 | −2.0 | −2.0 | 0.0% |
| (1.7,4.86] | A | 162.0 | 38.3% | 8.6 | 9.3 | 1.7 | 2.3 | −18.6% |
| (1.7,4.86] | H | 278.0 | 78.8% | −12.9 | 13.4 | 0.6 | 0.2 | −6.1% |
| (1.7,4.86] | E | 3.0 | 66.7% | 0.5 | 0.0 | 11.2 | 10.7 | 0.0% |
| (4.86,17.3] | H | 327.0 | 80.7% | −13.9 | 14.5 | 2.1 | 1.4 | −4.5% |
| (4.86,17.3] | A | 115.0 | 33.0% | 8.0 | 8.3 | −1.7 | −1.4 | −20.6% |
| (4.86,17.3] | E | 2.0 | 100.0% | 1.5 | 0.0 | 14.0 | 12.5 | 0.0% |
| NA | A | 516.0 | 30.6% | 8.6 | 9.6 | −1.7 | −0.7 | −19.5% |
| NA | H | 725.0 | 76.4% | −12.7 | 12.9 | −0.3 | −0.4 | −0.4% |
| NA | E | 3.0 | 100.0% | −1.3 | 0.0 | 1.7 | 3.0 | 0.0% |
| combined_total_edge_quintile | number_selections | avg_total_op | avg_total_cl | avg_total_score_actual | avg_totals_error_cl | avg_totals_change_pct |
|---|---|---|---|---|---|---|
| (-16.3,-3.2] | 444.0 | 53.0 | 52.1 | 51.7 | −0.3 | 2.2% |
| (-3.2,-0.5] | 444.0 | 54.2 | 53.3 | 54.1 | 0.8 | 2.0% |
| (-0.5,1.76] | 444.0 | 55.5 | 55.0 | 55.1 | 0.1 | 1.3% |
| (1.76,4.3] | 445.0 | 55.1 | 54.7 | 56.1 | 1.4 | 1.2% |
| (4.3,15.6] | 444.0 | 55.3 | 55.2 | 55.7 | 0.6 | 0.5% |
| NA | 1,240.0 | 56.0 | 56.1 | 56.0 | −0.2 | 0.1% |