The goal of this analysis is to develop and evaluate a vector autoregression model to forecast point differential and all associated predictors. The data to be modeled is college basketball score differentials based on data downloaded from sports-reference.com. Each college basketball team has a page containing a csv file of their game logs for the season. Game logs are downloaded for 351 teams between the 2012-2013 and 2016-2017 seasons. All regular season and post-season games are included. Fields such as points for and points again are condensed to differentials (for each variable we use n_for - n_against
). The data is then scaled and centered as it will be for the actual model building process.
Next we want to try to determine the variables that correlate well with one another (as well as with the target variable - point differential). Most variables, to varying degrees, correlate predictably with point differential. For example, teams that turn over the ball less frequently tend to have higher point differentials as do teams that score more three pointers than their opponents. Offensive rebounding and free throw shooting have the weakest correlations with point differential.
Basketball has some different properties than stock data (which we typically use for these projects) particularly in scheduling. The market has 252 trading days per year but the best two teams in the country play 40 games. Additionally, investors, for the most part, make projections based on market factors that transcend years while basketball teams gain and lose players and coaching talent annually. So instead of training a VAR model on the whole dataset, the models are trained separately on 10 team-seasons for the first 20 games per season. The teams are randomly selected, but include Virginia Tech because go Hokies!
library(vars)
library(forecast)
# Generate dataframes and ts objects for each team/season combo
teams <- c(sample(game_data$team, 9), 'virginia tech')
team_dfs <- list()
team_ts <- list()
for(i in seq(1,length(teams))){
team_dfs[[i]] <- game_data%>%
filter(team == teams[i] & g <= 20 & year == 2015)%>%
dplyr::select(-g, -team, -win, -year, -loc.away, -orb.d, -ft.d)
team_ts[[i]] <- ts(team_dfs[[i]])
}
autoplot_lists <- list()
for(i in 1:10){
autoplot_lists[[i]] <- autoplot(team_ts[[i]][,'point.d'])+
theme_yaz()+
labs(title = teams[i], y = 'Point Differential', x = 'Games Played')+
geom_hline(yintercept = 0, linetype = 'dashed')
}
library(gridExtra)
do.call("grid.arrange", c(autoplot_lists, ncol=5, top = 'Time Series of Point Differential By Game'))

All of the teams’ point differentials represent stationary white noise time series. That means no differencing is required!
acf_list <- list()
for(i in 1:10){
acf_list[[i]] <- ggAcf(team_ts[[i]][,'point.d'])+
theme_yaz()+
labs(title = teams[i], y = 'ACF', x = 'Lag')
}
do.call("grid.arrange", c(acf_list, ncol=5, top = 'AutoCorrelations by Team'))

The VARselect
function output calculates several information criteria to determine the optimal lag point for a given set of variables. In this case, we’ll use all variables in the above graphic except for free throw differential and offensive rebound differential. Number of lags (\(p\)) is selected using Final Prediction Error. For most teams, the preferred lag is \(p = 1\), but some use \(p = 2\), but for some reason, using \(p = 2\) resulted in null forecasts so \(p = 1\) was applited to all teams.
var.mods <- list()
for(i in 1:10){var.mods[[i]] <- VAR(team_ts[[i]], p = 1, type = 'const')}
The forecast plots are presented below looking five games into the future. The confidence intervals of the projections are narrow at first and grow as the forecast gets farther from the final game of the dataset, but that’s OK because a model can be retrained on a daily basis during the season. While this is a good start, the individual predictor variables are not all strong enough predictors of one another to feed consistent, trustworthy forecasts. My next step will be to try some other approaches like a naiive model or simple exponential smoothing that can rely on just the individual time series.
fcast.plots <- list()
for(i in 1:10){
fcast.plots[[i]] <- autoplot(forecast(var.mods[[i]], h = 5))+
theme_yaz()+
labs(x = 'Games Played', title = teams[i])
}
fcast.plots
[[1]]
[[2]]
[[3]]
[[4]]
[[5]]
[[6]]
[[7]]
[[8]]
[[9]]
[[10]]










LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpUaGUgZ29hbCBvZiB0aGlzIGFuYWx5c2lzIGlzIHRvIGRldmVsb3AgYW5kIGV2YWx1YXRlIGEgdmVjdG9yIGF1dG9yZWdyZXNzaW9uIG1vZGVsIHRvIGZvcmVjYXN0IHBvaW50IGRpZmZlcmVudGlhbCBhbmQgYWxsIGFzc29jaWF0ZWQgcHJlZGljdG9ycy4gVGhlIGRhdGEgdG8gYmUgbW9kZWxlZCBpcyBjb2xsZWdlIGJhc2tldGJhbGwgc2NvcmUgZGlmZmVyZW50aWFscyBiYXNlZCBvbiBkYXRhIGRvd25sb2FkZWQgZnJvbSBzcG9ydHMtcmVmZXJlbmNlLmNvbS4gRWFjaCBjb2xsZWdlIGJhc2tldGJhbGwgdGVhbSBoYXMgYSBwYWdlIGNvbnRhaW5pbmcgYSBjc3YgZmlsZSBvZiB0aGVpciBnYW1lIGxvZ3MgZm9yIHRoZSBzZWFzb24uIEdhbWUgbG9ncyBhcmUgZG93bmxvYWRlZCBmb3IgMzUxIHRlYW1zIGJldHdlZW4gdGhlIDIwMTItMjAxMyBhbmQgMjAxNi0yMDE3IHNlYXNvbnMuIEFsbCByZWd1bGFyIHNlYXNvbiBhbmQgcG9zdC1zZWFzb24gZ2FtZXMgYXJlIGluY2x1ZGVkLiBGaWVsZHMgc3VjaCBhcyBwb2ludHMgZm9yIGFuZCBwb2ludHMgYWdhaW4gYXJlIGNvbmRlbnNlZCB0byBkaWZmZXJlbnRpYWxzIChmb3IgZWFjaCB2YXJpYWJsZSB3ZSB1c2UgYG5fZm9yIC0gbl9hZ2FpbnN0YCkuIFRoZSBkYXRhIGlzIHRoZW4gc2NhbGVkIGFuZCBjZW50ZXJlZCBhcyBpdCB3aWxsIGJlIGZvciB0aGUgYWN0dWFsIG1vZGVsIGJ1aWxkaW5nIHByb2Nlc3MuDQoNCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmdhbWVfZGF0YS5yYXcgPC0gcmVhZF9jc3YoJ0M6L1VzZXJzL2pvc2h5L0Rlc2t0b3AvY2JiLXN0YXRzL2NsZWFuZWRfZ2FtZXMuY3N2JyklPiUNCiAgbXV0YXRlKGxvYy5hd2F5ID0gaWZlbHNlKGxvY2F0aW9uID09ICdhd2F5JywgMSwgMCksDQogICAgICAgICBsb2MuaG9tZSA9IGlmZWxzZShsb2NhdGlvbiA9PSAnaG9tZScsIDEsIDApLA0KICAgICAgICAgd2luID0gaWZlbHNlKG91dGNvbWUgPT0gJ1cnLCAxLCAwKSwNCiAgICAgICAgIHllYXIgPSB5ZWFyKERhdGUpKQ0KDQpnYW1lX2RhdGEgPC0gZ2FtZV9kYXRhLnJhdyU+JQ0KICBtdXRhdGUocG9pbnQuZCA9IHBvaW50c19mb3IgLSBwb2ludHNfYWdhaW5zdCwNCiAgICAgICAgIGZnLmQgPSBGR3BjdC5mb3IgLSBGR3BjdC5hZ2FpbnN0LA0KICAgICAgICAgdGhyZWUuZCA9IGAzUHBjdC5mb3JgIC0gYDNQcGN0LmFnYWluc3RgLA0KICAgICAgICAgZnQuZCA9IEZUcGN0LmZvciAtIEZUcGN0LmFnYWluc3QsDQogICAgICAgICBvcmIuZCA9IE9SQi5mb3IgLSBPUkIuYWdhaW5zdCwNCiAgICAgICAgIGRyYi5kID0gKFRSQi5mb3IgLSBPUkIuZm9yKSAtIChUUkIuYWdhaW5zdCAtIE9SQi5hZ2FpbnN0KSwNCiAgICAgICAgIGFzdC5kID0gQVNULmZvciAtIEFTVC5hZ2FpbnN0LA0KICAgICAgICAgc3RsLmQgPSBTVEwgLSBTVEwuYWdhaW5zdCwNCiAgICAgICAgIGJsay5kID0gQkxLIC0gQkxLLmFnYWluc3QsDQogICAgICAgICB0b3YuZCA9IFRPViAtIFRPVi5hZ2FpbnN0LA0KICAgICAgICAgcGYuZCA9IFBGIC0gUEYuYWdhaW5zdCklPiUNCiAgZHBseXI6OnNlbGVjdChwb2ludC5kLCBmZy5kLCB0aHJlZS5kLCBmdC5kLCANCiAgICAgICAgIG9yYi5kLCBkcmIuZCwgYXN0LmQsIHN0bC5kLCBibGsuZCwgdG92LmQsIHBmLmQpJT4lDQogIHNjYWxlKHNjYWxlID0gVCwgY2VudGVyID0gVCklPiUNCiAgZGF0YS5mcmFtZSgpJT4lDQogIGJpbmRfY29scyhnYW1lX2RhdGEucmF3JT4lDQogICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoZyA9IEcsIHRlYW0sIGxvYy5hd2F5LCBsb2MuaG9tZSwgd2luLCB5ZWFyKSkNCg0KaGVhZChnYW1lX2RhdGEsIDEwMCkNCmBgYA0KDQpOZXh0IHdlIHdhbnQgdG8gdHJ5IHRvIGRldGVybWluZSB0aGUgdmFyaWFibGVzIHRoYXQgY29ycmVsYXRlIHdlbGwgd2l0aCBvbmUgYW5vdGhlciAoYXMgd2VsbCBhcyB3aXRoIHRoZSB0YXJnZXQgdmFyaWFibGUgLSBwb2ludCBkaWZmZXJlbnRpYWwpLiBNb3N0IHZhcmlhYmxlcywgdG8gdmFyeWluZyBkZWdyZWVzLCBjb3JyZWxhdGUgcHJlZGljdGFibHkgd2l0aCBwb2ludCBkaWZmZXJlbnRpYWwuIEZvciBleGFtcGxlLCB0ZWFtcyB0aGF0IHR1cm4gb3ZlciB0aGUgYmFsbCBsZXNzIGZyZXF1ZW50bHkgdGVuZCB0byBoYXZlIGhpZ2hlciBwb2ludCBkaWZmZXJlbnRpYWxzIGFzIGRvIHRlYW1zIHRoYXQgc2NvcmUgbW9yZSB0aHJlZSBwb2ludGVycyB0aGFuIHRoZWlyIG9wcG9uZW50cy4gT2ZmZW5zaXZlIHJlYm91bmRpbmcgYW5kIGZyZWUgdGhyb3cgc2hvb3RpbmcgaGF2ZSB0aGUgd2Vha2VzdCBjb3JyZWxhdGlvbnMgd2l0aCBwb2ludCBkaWZmZXJlbnRpYWwuIA0KDQpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIHJlc3VsdHMgPSAnaGlkZScsIGVjaG8gPSBUUlVFfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh5YXp0aGVtZSkNCnBtIDwtIEdHYWxseTo6Z2dwYWlycyhnYW1lX2RhdGElPiVkcGx5cjo6c2VsZWN0KC1nLCAtdGVhbSwgLXdpbiwgLXllYXIsIC1sb2MuYXdheSksDQogICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAnRGlzdHJpYnV0aW9uIGFuZCBSZWxhdGlvbnNoaXBzIG9mIFZhcmlhYmxlcycpKw0KICB5YXp0aGVtZTo6dGhlbWVfeWF6KCkNCnBtDQpgYGANCg0KQmFza2V0YmFsbCBoYXMgc29tZSBkaWZmZXJlbnQgcHJvcGVydGllcyB0aGFuIHN0b2NrIGRhdGEgKHdoaWNoIHdlIHR5cGljYWxseSB1c2UgZm9yIHRoZXNlIHByb2plY3RzKSBwYXJ0aWN1bGFybHkgaW4gc2NoZWR1bGluZy4gVGhlIG1hcmtldCBoYXMgMjUyIHRyYWRpbmcgZGF5cyBwZXIgeWVhciBidXQgdGhlIGJlc3QgdHdvIHRlYW1zIGluIHRoZSBjb3VudHJ5IHBsYXkgNDAgZ2FtZXMuIEFkZGl0aW9uYWxseSwgaW52ZXN0b3JzLCBmb3IgdGhlIG1vc3QgcGFydCwgbWFrZSBwcm9qZWN0aW9ucyBiYXNlZCBvbiBtYXJrZXQgZmFjdG9ycyB0aGF0IHRyYW5zY2VuZCB5ZWFycyB3aGlsZSBiYXNrZXRiYWxsIHRlYW1zIGdhaW4gYW5kIGxvc2UgcGxheWVycyBhbmQgY29hY2hpbmcgdGFsZW50IGFubnVhbGx5LiBTbyBpbnN0ZWFkIG9mIHRyYWluaW5nIGEgVkFSIG1vZGVsIG9uIHRoZSB3aG9sZSBkYXRhc2V0LCB0aGUgbW9kZWxzIGFyZSB0cmFpbmVkIHNlcGFyYXRlbHkgb24gMTAgdGVhbS1zZWFzb25zIGZvciB0aGUgZmlyc3QgMjAgZ2FtZXMgcGVyIHNlYXNvbi4gVGhlIHRlYW1zIGFyZSByYW5kb21seSBzZWxlY3RlZCwgYnV0IGluY2x1ZGUgVmlyZ2luaWEgVGVjaCBiZWNhdXNlIGdvIEhva2llcyENCg0KYGBge3IsIGZpZy53aWR0aD0xMSwgZmlnLmhlaWdodD01LCBlY2hvID0gVFJVRX0NCmxpYnJhcnkodmFycykNCmxpYnJhcnkoZm9yZWNhc3QpDQojIEdlbmVyYXRlIGRhdGFmcmFtZXMgYW5kIHRzIG9iamVjdHMgZm9yIGVhY2ggdGVhbS9zZWFzb24gY29tYm8NCnRlYW1zIDwtIGMoc2FtcGxlKGdhbWVfZGF0YSR0ZWFtLCA5KSwgJ3ZpcmdpbmlhIHRlY2gnKQ0KDQp0ZWFtX2RmcyA8LSBsaXN0KCkNCnRlYW1fdHMgPC0gbGlzdCgpDQoNCmZvcihpIGluIHNlcSgxLGxlbmd0aCh0ZWFtcykpKXsNCiAgdGVhbV9kZnNbW2ldXSA8LSBnYW1lX2RhdGElPiUNCiAgICBmaWx0ZXIodGVhbSA9PSB0ZWFtc1tpXSAmIGcgPD0gMjAgJiB5ZWFyID09IDIwMTUpJT4lDQogICAgZHBseXI6OnNlbGVjdCgtZywgLXRlYW0sIC13aW4sIC15ZWFyLCAtbG9jLmF3YXksIC1vcmIuZCwgLWZ0LmQpDQogIHRlYW1fdHNbW2ldXSA8LSB0cyh0ZWFtX2Rmc1tbaV1dKQ0KfQ0KDQphdXRvcGxvdF9saXN0cyA8LSBsaXN0KCkNCg0KZm9yKGkgaW4gMToxMCl7DQogIGF1dG9wbG90X2xpc3RzW1tpXV0gPC0gYXV0b3Bsb3QodGVhbV90c1tbaV1dWywncG9pbnQuZCddKSsNCiAgICB0aGVtZV95YXooKSsNCiAgICBsYWJzKHRpdGxlID0gdGVhbXNbaV0sIHkgPSAnUG9pbnQgRGlmZmVyZW50aWFsJywgeCA9ICdHYW1lcyBQbGF5ZWQnKSsNCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICdkYXNoZWQnKQ0KfSAgDQoNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KDQpkby5jYWxsKCJncmlkLmFycmFuZ2UiLCBjKGF1dG9wbG90X2xpc3RzLCBuY29sPTUsIHRvcCA9ICdUaW1lIFNlcmllcyBvZiBQb2ludCBEaWZmZXJlbnRpYWwgQnkgR2FtZScpKQ0KYGBgDQoNCkFsbCBvZiB0aGUgdGVhbXMnIHBvaW50IGRpZmZlcmVudGlhbHMgcmVwcmVzZW50IHN0YXRpb25hcnkgd2hpdGUgbm9pc2UgdGltZSBzZXJpZXMuIFRoYXQgbWVhbnMgbm8gZGlmZmVyZW5jaW5nIGlzIHJlcXVpcmVkIQ0KYGBge3IsIGZpZy53aWR0aD0xMSwgZmlnLmhlaWdodD01LCBlY2hvID0gVFJVRX0NCmFjZl9saXN0IDwtIGxpc3QoKQ0KDQpmb3IoaSBpbiAxOjEwKXsNCiAgYWNmX2xpc3RbW2ldXSA8LSBnZ0FjZih0ZWFtX3RzW1tpXV1bLCdwb2ludC5kJ10pKw0KICAgIHRoZW1lX3lheigpKw0KICAgIGxhYnModGl0bGUgPSB0ZWFtc1tpXSwgeSA9ICdBQ0YnLCB4ID0gJ0xhZycpDQp9ICANCg0KZG8uY2FsbCgiZ3JpZC5hcnJhbmdlIiwgYyhhY2ZfbGlzdCwgbmNvbD01LCB0b3AgPSAnQXV0b0NvcnJlbGF0aW9ucyBieSBUZWFtJykpDQpgYGANCg0KVGhlIGBWQVJzZWxlY3RgIGZ1bmN0aW9uIG91dHB1dCBjYWxjdWxhdGVzIHNldmVyYWwgaW5mb3JtYXRpb24gY3JpdGVyaWEgdG8gZGV0ZXJtaW5lIHRoZSBvcHRpbWFsIGxhZyBwb2ludCBmb3IgYSBnaXZlbiBzZXQgb2YgdmFyaWFibGVzLiBJbiB0aGlzIGNhc2UsIHdlJ2xsIHVzZSBhbGwgdmFyaWFibGVzIGluIHRoZSBhYm92ZSBncmFwaGljIGV4Y2VwdCBmb3IgZnJlZSB0aHJvdyBkaWZmZXJlbnRpYWwgYW5kIG9mZmVuc2l2ZSByZWJvdW5kIGRpZmZlcmVudGlhbC4gTnVtYmVyIG9mIGxhZ3MgKCRwJCkgaXMgc2VsZWN0ZWQgdXNpbmcgRmluYWwgUHJlZGljdGlvbiBFcnJvci4gRm9yIG1vc3QgdGVhbXMsIHRoZSBwcmVmZXJyZWQgbGFnIGlzICRwID0gMSQsIGJ1dCBzb21lIHVzZSAkcCA9IDIkLCBidXQgZm9yIHNvbWUgcmVhc29uLCB1c2luZyAkcCA9IDIkIHJlc3VsdGVkIGluIG51bGwgZm9yZWNhc3RzIHNvICRwID0gMSQgd2FzIGFwcGxpdGVkIHRvIGFsbCB0ZWFtcy4NCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KdmFyLm1vZHMgPC0gbGlzdCgpDQpmb3IoaSBpbiAxOjEwKXt2YXIubW9kc1tbaV1dIDwtIFZBUih0ZWFtX3RzW1tpXV0sIHAgPSAxLCB0eXBlID0gJ2NvbnN0Jyl9DQpgYGANCg0KVGhlIGZvcmVjYXN0IHBsb3RzIGFyZSBwcmVzZW50ZWQgYmVsb3cgbG9va2luZyBmaXZlIGdhbWVzIGludG8gdGhlIGZ1dHVyZS4gVGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIG9mIHRoZSBwcm9qZWN0aW9ucyBhcmUgbmFycm93IGF0IGZpcnN0IGFuZCBncm93IGFzIHRoZSBmb3JlY2FzdCBnZXRzIGZhcnRoZXIgZnJvbSB0aGUgZmluYWwgZ2FtZSBvZiB0aGUgZGF0YXNldCwgYnV0IHRoYXQncyBPSyBiZWNhdXNlIGEgbW9kZWwgY2FuIGJlIHJldHJhaW5lZCBvbiBhIGRhaWx5IGJhc2lzIGR1cmluZyB0aGUgc2Vhc29uLiBXaGlsZSB0aGlzIGlzIGEgZ29vZCBzdGFydCwgdGhlIGluZGl2aWR1YWwgcHJlZGljdG9yIHZhcmlhYmxlcyBhcmUgbm90IGFsbCBzdHJvbmcgZW5vdWdoIHByZWRpY3RvcnMgb2Ygb25lIGFub3RoZXIgdG8gZmVlZCBjb25zaXN0ZW50LCB0cnVzdHdvcnRoeSBmb3JlY2FzdHMuIE15IG5leHQgc3RlcCB3aWxsIGJlIHRvIHRyeSBzb21lIG90aGVyIGFwcHJvYWNoZXMgbGlrZSBhIG5haWl2ZSBtb2RlbCBvciBzaW1wbGUgZXhwb25lbnRpYWwgc21vb3RoaW5nIHRoYXQgY2FuIHJlbHkgb24ganVzdCB0aGUgaW5kaXZpZHVhbCB0aW1lIHNlcmllcy4gDQpgYGB7ciwgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoID0gNiwgZWNobyA9IFRSVUV9DQpmY2FzdC5wbG90cyA8LSBsaXN0KCkNCmZvcihpIGluIDE6MTApew0KICBmY2FzdC5wbG90c1tbaV1dIDwtIGF1dG9wbG90KGZvcmVjYXN0KHZhci5tb2RzW1tpXV0sIGggPSA1KSkrDQogICAgdGhlbWVfeWF6KCkrDQogICAgbGFicyh4ID0gJ0dhbWVzIFBsYXllZCcsIHRpdGxlID0gdGVhbXNbaV0pDQp9DQoNCmZjYXN0LnBsb3RzDQpgYGANCg0K