College Football – BettorIQ – Analysis of Qual Signal

Published

June 10, 2024

Import dependencies

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%`)

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 rows
Empty 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%