Introduction

library(knitr)
library(MASS)
dates <- c("2015-01-30","2015-02-06","2015-02-13","2015-02-20","2015-02-27","2015-03-06")
date.1 <- tail(dates,1)
forecast.matches <- read.csv(paste("forecasts_",date.1,".csv",sep=""),stringsAsFactors=F)
forecast.matches <- forecast.matches[is.na(forecast.matches$outcome)==F,]

This is the number 6 in a series of forecasts of football match outcomes, following on from my efforts last week and the previous weeks. The method of forecasts is unchanged from previous week. For next week I hope to add bookmaker prices in for reference purposes.

Loading the Data

As with previous weeks, the dataset is all English matches recorded on http://www.soccerbase.com, which goes back to 1877 and the very first football matches.  Experimentation will take place with adjusting the estimation sample size, since it is not necessarily useful to have all matches back to 1877 when forecasting matches in 2015.  The Elo ranks have been calculated since the very first matches, and hence historical information is retained, to the extent that it is useful in determining a team’s current strength, back throughout footballing history.

Forecast Model

The linear regression model is estimated here and reported:

res.eng <- read.csv(paste("historical_",date.1,".csv",sep=""))
model <- lm(outcome ~ E.1 + pts1 + pts.D + pts.D.2 + pld1 + pld.D + pld.D.2 + gs1 + gs.D + gs.D.2 
            + gd1 + gd.D + gd.D.2 
            + pos1 + pos.D + pos.D.2 + form1 + form.D + form.D.2 + tier1 + tier.D + tier.D.2 + season.d,
            data=res.eng)
summary(model)
## 
## Call:
## lm(formula = outcome ~ E.1 + pts1 + pts.D + pts.D.2 + pld1 + 
##     pld.D + pld.D.2 + gs1 + gs.D + gs.D.2 + gd1 + gd.D + gd.D.2 + 
##     pos1 + pos.D + pos.D.2 + form1 + form.D + form.D.2 + tier1 + 
##     tier.D + tier.D.2 + season.d, data = res.eng)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -1.0269 -0.3112  0.1153  0.3369  0.8964 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  3.310e-01  5.418e-03  61.093  < 2e-16 ***
## E.1          5.448e-01  6.053e-03  90.009  < 2e-16 ***
## pts1         9.700e-04  4.239e-04   2.288  0.02212 *  
## pts.D       -2.371e-03  3.077e-04  -7.704 1.32e-14 ***
## pts.D.2     -1.342e-05  6.471e-06  -2.074  0.03806 *  
## pld1        -1.628e-03  6.094e-04  -2.671  0.00756 ** 
## pld.D        2.229e-03  7.108e-04   3.136  0.00171 ** 
## pld.D.2     -4.029e-05  3.080e-05  -1.308  0.19088    
## gs1          4.764e-04  1.710e-04   2.786  0.00533 ** 
## gs.D        -2.492e-04  1.526e-04  -1.633  0.10247    
## gs.D.2      -6.539e-07  4.690e-06  -0.139  0.88910    
## gd1         -5.913e-04  2.407e-04  -2.457  0.01401 *  
## gd.D         2.451e-03  1.759e-04  13.934  < 2e-16 ***
## gd.D.2      -5.691e-06  2.343e-06  -2.429  0.01513 *  
## pos1         7.637e-04  3.005e-04   2.541  0.01105 *  
## pos.D       -1.841e-03  2.514e-04  -7.324 2.42e-13 ***
## pos.D.2      3.745e-05  1.171e-05   3.199  0.00138 ** 
## form1        6.144e-04  3.519e-04   1.746  0.08079 .  
## form.D       1.360e-03  2.960e-04   4.593 4.38e-06 ***
## form.D.2    -7.265e-05  2.996e-05  -2.425  0.01532 *  
## tier1        1.946e-03  7.655e-04   2.542  0.01101 *  
## tier.D      -3.169e-02  2.862e-03 -11.074  < 2e-16 ***
## tier.D.2    -5.274e-03  1.258e-03  -4.194 2.75e-05 ***
## season.d    -1.081e-03  3.082e-05 -35.062  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.3948 on 216015 degrees of freedom
##   (38216 observations deleted due to missingness)
## Multiple R-squared:  0.08512,    Adjusted R-squared:  0.08503 
## F-statistic: 873.9 on 23 and 216015 DF,  p-value: < 2.2e-16

The ordered logistic regression model is:

model.ord <- polr(as.factor(outcome) ~ E.1 + pts1 + pts.D + pts.D.2 + pld1 + pld.D + pld.D.2 + 
                    gs1 + gs.D + gs.D.2 + gd1 + gd.D + gd.D.2 + pos1 + pos.D + pos.D.2 + 
                    form1 + form.D + form.D.2 + tier1 + tier.D + tier.D.2 + season.d, 
                  data=res.eng, method = "logistic")
summary(model.ord)
## 
## Re-fitting to get Hessian
## Call:
## polr(formula = as.factor(outcome) ~ E.1 + pts1 + pts.D + pts.D.2 + 
##     pld1 + pld.D + pld.D.2 + gs1 + gs.D + gs.D.2 + gd1 + gd.D + 
##     gd.D.2 + pos1 + pos.D + pos.D.2 + form1 + form.D + form.D.2 + 
##     tier1 + tier.D + tier.D.2 + season.d, data = res.eng, method = "logistic")
## 
## Coefficients:
##               Value Std. Error   t value
## E.1       2.636e+00  3.019e-02  87.32128
## pts1      4.169e-03  2.084e-03   2.00048
## pts.D    -1.221e-02  1.522e-03  -8.02570
## pts.D.2  -7.482e-05  3.611e-05  -2.07207
## pld1     -9.114e-03  2.997e-03  -3.04071
## pld.D     1.268e-02  3.567e-03   3.55496
## pld.D.2  -2.313e-04  1.637e-04  -1.41305
## gs1       3.434e-03  8.512e-04   4.03401
## gs.D     -1.586e-03  7.583e-04  -2.09125
## gs.D.2   -8.177e-07  2.557e-05  -0.03198
## gd1      -3.311e-03  1.193e-03  -2.77448
## gd.D      1.331e-02  8.795e-04  15.13210
## gd.D.2    7.265e-06  1.638e-05   0.44352
## pos1      3.048e-03  1.466e-03   2.07952
## pos.D    -7.972e-03  1.240e-03  -6.42996
## pos.D.2   2.026e-04  5.945e-05   3.40719
## form1     2.794e-03  1.719e-03   1.62535
## form.D    7.078e-03  1.457e-03   4.85787
## form.D.2 -2.277e-04  1.495e-04  -1.52350
## tier1     9.442e-03  3.728e-03   2.53247
## tier.D   -1.744e-01  1.506e-02 -11.58488
## tier.D.2 -6.727e-03  6.882e-03  -0.97761
## season.d -5.401e-03  1.527e-04 -35.37495
## 
## Intercepts:
##       Value    Std. Error t value 
## 0|0.5   0.2401   0.0264     9.0975
## 0.5|1   1.4290   0.0266    53.7856
## 
## Residual Deviance: 428058.78 
## AIC: 428108.78 
## (38216 observations deleted due to missingness)

The Forecasts

FA Cup

fac.matches <- forecast.matches[forecast.matches$division=="English FA Cup",]
fac.matches <- fac.matches[order(fac.matches$date),]
fac.matches$id <- 1:NROW(fac.matches)
par(mar=c(9,4,4,5)+.1)
plot(fac.matches$id,fac.matches$outcome,xaxt="n",xlab="",ylim=range(0,1),
     main="Forecasts of Weekend facier League Matches",
     ylab="Probability of Outcome")
lines(fac.matches$id,fac.matches$Ph,col=2,pch=15,type="p")
lines(fac.matches$id,fac.matches$Pd,col=3,pch=16,type="p")
lines(fac.matches$id,fac.matches$Pa,col=4,pch=17,type="p")
legend("topleft",ncol=4,pch=c(1,15,16,17),col=c(1:4),
       legend=c("OLS","OL (home)","OL (draw)","OL (away)"),bty="n")
abline(h=0.5,lty=2)
abline(h=0.6,lty=3)
abline(h=0.7,lty=2)
abline(h=0.4,lty=3)
axis(1,at=fac.matches$id,labels=paste(fac.matches$team1,fac.matches$team2,sep=" v "),las=2,cex.axis=0.65)
for(i in 2:NROW(fac.matches)){
  if(fac.matches$date[i]!=fac.matches$date[i-1]) {
    lines(rep(c(i-0.5),2),c(0,1),lty=2)
  }
}

Bookmaker prices return for our FA Cup matches:

clean.data <- function(data) {
  require(zoo)
  colnames(data)[1] <- "Date.Time"
  colnames(data)[-1] <- gsub("^.*?\\d+_(\\w+)[.]href.*?$","\\1",colnames(data)[-1])
  data <- data[,-NCOL(data)]
  data$Date.Time <- as.Date(substring(data$Date.Time,2),"%Y-%m-%d")
  data <- data[is.na(data$Date.Time)==F,]
  data <- data[order(data$Date.Time),]
  for(i in colnames(data)[nchar(colnames(data))==2]) {
    data[,i] <- gsub("<div class=.*?>(.*?)</div>","\\1",data[,i])
    data[,i] <- gsub("<div class=.*?>(.*?)\\s+$","\\1",data[,i])
    data[,i] <- gsub(">","",data[,i])
    data[,i] <- gsub("^\\s+|\\s+$","",data[,i])
    #need to turn fractional odds into decimal odds
    numerator <- gsub("(\\d+)/(\\d+)$","\\1",data[,i])
    denominator <- gsub("(\\d+)/(\\d+)$","\\2",data[,i])
    data[,i] <- as.numeric(numerator)+1
    data[numerator!=denominator,i] <- as.numeric(numerator[numerator!=denominator])/as.numeric(denominator[numerator!=denominator])+1
    data[,i] <- na.locf(data[,i],na.rm=F)
  }
  return(data)
}

fac.bk <- data.frame()
fac.loc <- paste("/home/readejj/Dropbox/Research/Data for Ideas/Betting/football/",date.1,"/fa-cup",sep="")
fac.bk.matches <- dir(fac.loc,pattern="*.csv")
for(i in fac.bk.matches) {
  temp <- read.csv(paste(fac.loc,i,sep="/"),stringsAsFactors=F)
  temp <- clean.data(temp)
  temp$event <- gsub("[.]csv","",i)
  fac.bk <- rbind(fac.bk,temp)
}
## Loading required package: zoo
## 
## Attaching package: 'zoo'
## 
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
## Warning: NAs introduced by coercion
fac.bk <- fac.bk[order(fac.bk$event,fac.bk$Date.Time),]
fac.bk$mean <- rowMeans(fac.bk[colnames(fac.bk)[nchar(colnames(fac.bk))==2]],na.rm=T)
fac.bk$event <- gsub("man-utd","man utd",fac.bk$event)
fac.bk$event <- gsub("man-city","man city",fac.bk$event)
fac.bk$event <- gsub("aston-villa","aston villa",fac.bk$event)
fac.bk$event <- gsub("west-brom","west brom",fac.bk$event)
fac.bk$event <- gsub("west-ham","west ham",fac.bk$event)
fac.bk$event <- gsub("crystal-palace","c palace",fac.bk$event)
fac.bk$team1 <- gsub("^(.*?)-v-(.*?)-(.*?)$","\\1",fac.bk$event)
fac.bk$team2 <- gsub("^(.*?)-v-(.*?)-(.*?)$","\\2",fac.bk$event)
fac.bk$event <- gsub("^(.*?)-v-(.*?)-(.*?)$","\\3",fac.bk$event)
fac.bk$event.1 <- c(fac.bk$event[-1],NA)
fac.bk.h <- fac.bk[(fac.bk$event!=fac.bk$event.1 | is.na(fac.bk$event.1)==T) & fac.bk$event==fac.bk$team1,]
fac.bk.d <- fac.bk[fac.bk$event!=fac.bk$event.1 & regexpr("draw",fac.bk$event)>-1,]
fac.bk.a <- fac.bk[fac.bk$event!=fac.bk$event.1 & fac.bk$event==fac.bk$team2,]
fac.matches$team1 <- tolower(fac.matches$team1)
fac.matches$team2 <- tolower(fac.matches$team2)
fac.matches <- merge(fac.matches,fac.bk.h[,c("team1","team2","mean")],by=c("team1","team2"),all.x=T)
colnames(fac.matches) <- gsub("mean","bk.h",colnames(fac.matches))
fac.matches <- merge(fac.matches,fac.bk.d[,c("team1","team2","mean")],by=c("team1","team2"),all.x=T)
colnames(fac.matches) <- gsub("mean","bk.d",colnames(fac.matches))
fac.matches <- merge(fac.matches,fac.bk.a[,c("team1","team2","mean")],by=c("team1","team2"),all.x=T)
colnames(fac.matches) <- gsub("mean","bk.a",colnames(fac.matches))

par(mar=c(9,4,4,5)+.1)
plot(fac.matches$id,fac.matches$Ph,xaxt="n",xlab="",ylim=range(0,1),col=2,pch=15,type="p",
     main="Forecasts of Weekend facier League Matches",
     ylab="Probability of Outcome")
lines(fac.matches$id,1/fac.matches$bk.h,col=2,pch=0,type="p")
lines(fac.matches$id,fac.matches$Pd,col=3,pch=16,type="p")
lines(fac.matches$id,1/fac.matches$bk.d,col=3,pch=1,type="p")
lines(fac.matches$id,fac.matches$Pa,col=4,pch=17,type="p")
lines(fac.matches$id,1/fac.matches$bk.a,col=4,pch=2,type="p")
legend("topleft",ncol=4,pch=c(1,15,16,17),col=c(1:4),
       legend=c("OLS","OL (home)","OL (draw)","OL (away)"),bty="n")
abline(h=0.5,lty=2)
abline(h=0.6,lty=3)
abline(h=0.7,lty=2)
abline(h=0.4,lty=3)
axis(1,at=fac.matches$id,labels=paste(fac.matches$team1,fac.matches$team2,sep=" v "),las=2,cex.axis=0.65)

They suggest little difference between my model forecasts and average bookmaker prices.

Premier League

prem.matches <- forecast.matches[forecast.matches$division=="English Premier",]
prem.matches <- prem.matches[order(prem.matches$date),]
prem.matches$id <- 1:NROW(prem.matches)
par(mar=c(9,4,4,5)+.1)
plot(prem.matches$id,prem.matches$outcome,xaxt="n",xlab="",ylim=range(0,1),
     main="Forecasts of Weekend Premier League Matches",
     ylab="Probability of Outcome")
lines(prem.matches$id,prem.matches$Ph,col=2,pch=15,type="p")
lines(prem.matches$id,prem.matches$Pd,col=3,pch=16,type="p")
lines(prem.matches$id,prem.matches$Pa,col=4,pch=17,type="p")
legend("topleft",ncol=4,pch=c(1,15,16,17),col=c(1:4),
       legend=c("OLS","OL (home)","OL (draw)","OL (away)"),bty="n")
abline(h=0.5,lty=2)
abline(h=0.6,lty=3)
abline(h=0.7,lty=2)
abline(h=0.4,lty=3)
axis(1,at=prem.matches$id,labels=paste(prem.matches$team1,prem.matches$team2,sep=" v "),las=2,cex.axis=0.65)

if(NROW(prem.matches)>1) {
  for(i in 2:NROW(prem.matches)){
    if(prem.matches$date[i]!=prem.matches$date[i-1]) {
      lines(rep(c(i-0.5),2),c(0,1),lty=2)
    }
  }
}

The coloured dots are forecasts from the ordered logistic regression model; the black circles are the forecasts from a simple OLS linear probability model.  Hence the black circles are essentially a probability of a home win occurring (given the ordinal variable defined to capture all three outcomes), whereas the red squares are the probability of a home win, the green solid circles are the probability of a draw, and the blue triangles the probability of an away win.  The home bias in football is notable in that the majority of red squares lie above blue triangles.

Unlike previous weeks, I am unable to provide bookmaker odds. Hopefully this will return next week.

Championship

Next, our Championship forecasts:

champ.matches <- forecast.matches[forecast.matches$division=="English Championship",]
champ.matches <- champ.matches[order(champ.matches$date),]
champ.matches$id <- 1:NROW(champ.matches)
par(mar=c(9,4,4,5)+.1)
plot(champ.matches$id,champ.matches$outcome,xaxt="n",xlab="",ylim=range(0,1),
     main="Forecasts of Weekend Championship Matches",
     ylab="Probability of Outcome")
lines(champ.matches$id,champ.matches$Ph,col=2,pch=15,type="p")
lines(champ.matches$id,champ.matches$Pd,col=3,pch=16,type="p")
lines(champ.matches$id,champ.matches$Pa,col=4,pch=17,type="p")
legend("topleft",ncol=4,pch=c(1,15,16,17),col=c(1:4),
       legend=c("OLS","OL (home)","OL (draw)","OL (away)"),bty="n")
abline(h=0.5,lty=2)
abline(h=0.6,lty=3)
abline(h=0.7,lty=2)
axis(1,at=champ.matches$id,labels=paste(champ.matches$team1,champ.matches$team2,sep=" v "),las=2,cex.axis=0.65)
for(i in 2:NROW(champ.matches)){
  if(champ.matches$date[i]!=champ.matches$date[i-1]) {
    lines(rep(c(i-0.5),2),c(0,1),lty=2)
  }
}

League One

Next, our League One forecasts:

lg1.matches <- forecast.matches[forecast.matches$division=="English League One",]
lg1.matches <- lg1.matches[order(lg1.matches$date),]
lg1.matches$id <- 1:NROW(lg1.matches)
par(mar=c(9,4,4,5)+.1)
plot(lg1.matches$id,lg1.matches$outcome,xaxt="n",xlab="",ylim=range(0,1),
     main="Forecasts of Weekend League One Matches",
     ylab="Probability of Outcome")
lines(lg1.matches$id,lg1.matches$Ph,col=2,pch=15,type="p")
lines(lg1.matches$id,lg1.matches$Pd,col=3,pch=16,type="p")
lines(lg1.matches$id,lg1.matches$Pa,col=4,pch=17,type="p")
legend("topleft",ncol=4,pch=c(1,15,16,17),col=c(1:4),
       legend=c("OLS","OL (home)","OL (draw)","OL (away)"),bty="n")
abline(h=0.5,lty=2)
abline(h=0.6,lty=3)
abline(h=0.7,lty=2)
axis(1,at=lg1.matches$id,labels=paste(lg1.matches$team1,lg1.matches$team2,sep=" v "),las=2,cex.axis=0.65)
for(i in 2:NROW(lg1.matches)){
  if(lg1.matches$date[i]!=lg1.matches$date[i-1]) {
    lines(rep(c(i-0.5),2),c(0,1),lty=2)
  }
}

League Two

Next, our League Two forecasts:

lg2.matches <- forecast.matches[forecast.matches$division=="English League Two",]
lg2.matches <- lg2.matches[order(lg2.matches$date),]
lg2.matches$id <- 1:NROW(lg2.matches)
par(mar=c(9,4,4,5)+.1)
plot(lg2.matches$id,lg2.matches$outcome,xaxt="n",xlab="",ylim=range(0,1),
     main="Forecasts of Weekend League Two Matches",
     ylab="Probability of Outcome")
lines(lg2.matches$id,lg2.matches$Ph,col=2,pch=15,type="p")
lines(lg2.matches$id,lg2.matches$Pd,col=3,pch=16,type="p")
lines(lg2.matches$id,lg2.matches$Pa,col=4,pch=17,type="p")
legend("topleft",ncol=4,pch=c(1,15,16,17),col=c(1:4),
       legend=c("OLS","OL (home)","OL (draw)","OL (away)"),bty="n")
abline(h=0.5,lty=2)
abline(h=0.6,lty=3)
abline(h=0.7,lty=2)
axis(1,at=lg2.matches$id,labels=paste(lg2.matches$team1,lg2.matches$team2,sep=" v "),las=2,cex.axis=0.65)

for(i in 2:NROW(lg2.matches)){
  if(lg2.matches$date[i]!=lg2.matches$date[i-1]) {
    lines(rep(c(i-0.5),2),c(0,1),lty=2)
  }
}

Football Conference

Next, our Football Conference forecasts:

conf.matches <- forecast.matches[forecast.matches$division=="Football Conference",]
conf.matches$id <- 1:NROW(conf.matches)
par(mar=c(9,4,4,5)+.1)
plot(conf.matches$id,conf.matches$outcome,xaxt="n",xlab="",ylim=range(0,1),
     main="Forecasts of Weekend Football Conference Matches",
     ylab="Probability of Outcome")
lines(conf.matches$id,conf.matches$Ph,col=2,pch=15,type="p")
lines(conf.matches$id,conf.matches$Pd,col=3,pch=16,type="p")
lines(conf.matches$id,conf.matches$Pa,col=4,pch=17,type="p")
legend("topleft",ncol=4,pch=c(1,15,16,17),col=c(1:4),
       legend=c("OLS","OL (home)","OL (draw)","OL (away)"),bty="n")
abline(h=0.5,lty=2)
abline(h=0.6,lty=3)
abline(h=0.7,lty=2)
axis(1,at=conf.matches$id,labels=paste(conf.matches$team1,conf.matches$team2,sep=" v "),las=2,cex.axis=0.65)
for(i in 2:NROW(conf.matches)){
  if(conf.matches$date[i]!=conf.matches$date[i-1]) {
    lines(rep(c(i-0.5),2),c(0,1),lty=2)
  }
}

List of all forecasts

For transparency, all forecasts are also listed as a table:

kable(forecast.matches[order(forecast.matches$date,forecast.matches$division),
                       c("date","division","team1","outcome","team2","Ph","Pd","Pa")])
date division team1 outcome team2 Ph Pd Pa
1 2015-03-06 English Championship Fulham 0.3823206 Bournemouth 0.2370610 0.2679382 0.4950009
59 2015-03-07 Conference North Hednesford 0.6071943 Boston Utd 0.4699708 0.2743591 0.2556701
62 2015-03-07 Conference North Stalybridge 0.4845479 Barrow 0.3321200 0.2880443 0.3798356
66 2015-03-07 Conference North Tamworth 0.7304738 Bradford PA 0.6213436 0.2221048 0.1565516
46 2015-03-07 Conference South Sutton Utd 0.6969378 Hayes & Y 0.5777477 0.2401833 0.1820690
3 2015-03-07 English Championship Cardiff 0.5181393 Charlton 0.3643979 0.2886654 0.3469367
4 2015-03-07 English Championship Derby 0.5760513 Birmingham 0.4554792 0.2776000 0.2669208
5 2015-03-07 English Championship Nottm Forest 0.4360652 Middlesbro 0.2810444 0.2810265 0.4379291
6 2015-03-07 English Championship Huddersfield 0.5403995 Rotherham 0.3896455 0.2873625 0.3229921
7 2015-03-07 English Championship Wigan 0.5468318 Leeds 0.3976625 0.2866461 0.3156914
8 2015-03-07 English Championship Ipswich 0.5851810 Brentford 0.4453436 0.2796474 0.2750090
9 2015-03-07 English Championship Millwall 0.2957922 Norwich 0.1754919 0.2358705 0.5886375
10 2015-03-07 English Championship Blackpool 0.3629449 Sheff Wed 0.2182727 0.2600153 0.5217120
11 2015-03-07 English Championship Wolves 0.4344636 Watford 0.2761498 0.2799177 0.4439326
101 2015-03-07 English FA Cup Aston Villa 0.4821551 West Brom 0.3255087 0.2875739 0.3869174
102 2015-03-07 English FA Cup Bradford 0.6151810 Reading 0.4840204 0.2708794 0.2451002
12 2015-03-07 English League One Crawley 0.2910730 Bristol C 0.1713633 0.2330432 0.5955935
13 2015-03-07 English League One Yeovil 0.4450451 Oldham 0.2882923 0.2825199 0.4291878
14 2015-03-07 English League One Rochdale 0.7550132 Colchester 0.6541294 0.2071669 0.1387037
15 2015-03-07 English League One Crewe 0.5070725 Scunthorpe 0.3506702 0.2887304 0.3605995
16 2015-03-07 English League One Peterborough 0.7061433 Leyton Orient 0.5851238 0.2372774 0.1775988
17 2015-03-07 English League One Sheff Utd 0.6863388 Fleetwood 0.5676428 0.2440578 0.1882994
18 2015-03-07 English League One Barnsley 0.6291734 Walsall 0.4930932 0.2684628 0.2384440
19 2015-03-07 English League One Gillingham 0.5380465 Doncaster 0.3892466 0.2873944 0.3233589
20 2015-03-07 English League One Swindon 0.6997759 Notts Co 0.5918036 0.2345906 0.1736058
21 2015-03-07 English League One Coventry 0.5075364 Port Vale 0.3541445 0.2887587 0.3570968
22 2015-03-07 English League One MK Dons 0.3359636 Preston 0.1965082 0.2488596 0.5546322
23 2015-03-07 English League Two Stevenage 0.6130424 Newport Co 0.4783948 0.2723116 0.2492936
24 2015-03-07 English League Two Carlisle 0.5796086 Exeter 0.4385611 0.2809129 0.2805259
25 2015-03-07 English League Two Hartlepool 0.3695381 Burton 0.2282773 0.2644217 0.5073011
26 2015-03-07 English League Two Tranmere 0.4740229 Dag & Red 0.3168038 0.2867653 0.3964310
27 2015-03-07 English League Two Luton 0.7236880 Morecambe 0.6069295 0.2283177 0.1647528
28 2015-03-07 English League Two AFC W’bledon 0.4609074 York 0.3023807 0.2849330 0.4126863
29 2015-03-07 English League Two Plymouth 0.4631433 Northampton 0.3043457 0.2852198 0.4104345
30 2015-03-07 English League Two Bury 0.7484961 Oxford 0.6359073 0.2156038 0.1484889
31 2015-03-07 English League Two Shrewsbury 0.7411982 Cambridge U 0.6302982 0.2181336 0.1515682
32 2015-03-07 English League Two Accrington 0.5239505 Portsmouth 0.3689970 0.2885397 0.3424634
33 2015-03-07 English League Two Cheltenham 0.4174368 Mansfield 0.2596383 0.2755556 0.4648061
34 2015-03-07 English League Two Southend 0.5802296 Wycombe 0.4331992 0.2818526 0.2849481
2 2015-03-07 English Premier QPR 0.3537208 Tottenham 0.2098379 0.2559544 0.5342077
35 2015-03-07 Football Conference Braintree 0.4903936 Gateshead 0.3345075 0.2881844 0.3773081
36 2015-03-07 Football Conference Wrexham 0.5369709 Chester 0.3876262 0.2875206 0.3248532
37 2015-03-07 Football Conference Alfreton 0.5228892 Kidderminster 0.3763636 0.2882330 0.3354034
38 2015-03-07 Football Conference Barnet 0.6129419 Forest Green 0.4842797 0.2708121 0.2449081
39 2015-03-07 Football Conference Southport 0.6582698 Nuneaton 0.5344089 0.2558872 0.2097038
40 2015-03-07 Football Conference Halifax 0.7309076 Dartford 0.6328425 0.2169901 0.1501674
41 2015-03-07 Football Conference Macclesfield 0.7346834 Aldershot 0.6246752 0.2206371 0.1546877
42 2015-03-07 Football Conference Dover 0.6542938 Lincoln 0.5349289 0.2557134 0.2093577
43 2015-03-07 Football Conference Bristol R 0.6919771 Eastleigh 0.5690529 0.2435246 0.1874224
44 2015-03-07 Football Conference Woking 0.5534278 Grimsby 0.4048360 0.2858873 0.3092767
45 2015-03-07 Football Conference Torquay 0.7537630 Telford 0.6564784 0.2060556 0.1374660
88 2015-03-07 Ryman Premier Lewes 0.4202453 Kingstonian 0.2669972 0.2776205 0.4553823
105 2015-03-08 English FA Cup Liverpool 0.8290591 Blackburn 0.7321966 0.1675718 0.1002316
103 2015-03-08 Football Conference Welling 0.4162036 Altrincham 0.2627488 0.2764525 0.4607987
109 2015-03-09 English FA Cup Man Utd 0.5597195 Arsenal 0.4131189 0.2848767 0.3020044
110 2015-03-10 English Championship Reading 0.5264255 Brighton 0.3694896 0.2885232 0.3419872
111 2015-03-10 English League One Coventry 0.4280955 Bradford 0.2715492 0.2787999 0.4496508
112 2015-03-10 English League One Yeovil 0.2736797 Bristol C 0.1600024 0.2247672 0.6152304
113 2015-03-10 Football Conference Aldershot 0.5355632 Halifax 0.3870669 0.2875627 0.3253704
114 2015-03-10 Football Conference Wrexham 0.6400000 Southport 0.5134136 0.2625897 0.2239967
115 2015-03-10 Football Conference Gateshead 0.8018084 Welling 0.7036170 0.1826777 0.1137053
116 2015-03-10 Football Conference Kidderminster 0.5067374 Eastleigh 0.3517764 0.2887427 0.3594808
117 2015-03-10 Football Conference Torquay 0.5586778 Chester 0.4211171 0.2837677 0.2951152
130 2015-03-10 Football Conference Macclesfield 0.8096579 Telford 0.7155054 0.1764752 0.1080195
133 2015-03-11 English Championship Blackburn 0.6667812 Bolton 0.5429120 0.2529987 0.2040893