A Study of NFL prediction models that I created in a past project used below regression models to help predict a season.

The data was trained from 2011 to 2018 and a test dataset was applied to 2019.

Model 2 was the best performing model (in terms of overall MSE and other predictor strength tests). Visuals were never created to help the general public visualize the overall error of such a model. Looking at it in terms of absolute error or mean error can be misleading when gambling on Win totals for individual teams.

Below is the gathered data for the models (Model 2 will be the model of focus for the visuals).

confidence %>% head() %>%  kbl(Caption='Basic Model Performance Data',digits = 2) %>% kable_styling(full_width = F)
Tm Wins prediction hi lo error abs_error
Arizona Cardinals 5 5.43 6.66 4.21 -0.43 0.43
Atlanta Falcons 7 7.23 8.46 6.01 -0.23 0.23
Baltimore Ravens 14 14.80 16.03 13.57 -0.80 0.80
Buffalo Bills 10 10.37 11.59 9.14 -0.37 0.37
Carolina Panthers 5 3.62 4.84 2.39 1.38 1.38
Chicago Bears 8 8.21 9.43 6.98 -0.21 0.21

Below column graphs will help to visualize the difference between ‘predictions’ and actual ‘Wins’ for the 2019 Model 2 test data:

The chunks in blue depict the number of wins a team outperformed their prediction. The chunks in red depict the number of wins a team underperformed their prediction. the purple are the overlap of the two.

This difference can be further visualized with the use of the scaled labels above the max of the two bars for each team. Red indicates a more severe inaccuracies in the prediction and green values indicates wins closer to prediction. A negative value in the labels indicates the team underperformed the prediction, while a positive label indicates that they outperformed the prediction.

##logo addin
logos_filter <- teams_colors_logos %>% distinct(team_name,.keep_all = T)
#plot_conf <- confidence %>% select(Tm,Wins,prediction,error) %>% as.data.frame() %>% reshape::melt(.,id='Tm')
plot_conf <- confidence %>% left_join(logos_filter,by=c('Tm'='team_name')) %>% 
  select(Tm,Wins,prediction,error,team_logo_wikipedia) %>% gather(.,key=var,value=val,c(Wins,prediction))

plot_conf2 <- confidence %>% left_join(logos_filter,by=c('Tm'='team_name')) %>% 
  select(Tm,Wins,prediction,error,team_logo_wikipedia)

### Plot 
plot_conf2 %>% ggplot(aes(x=reorder(Tm,Wins),y=Wins))+
  geom_col(aes(y=prediction,fill='Red'),position='identity',alpha=.5)+
  geom_col(aes(y=Wins,fill='Blue'),position='identity',alpha=.5,show.legend=F)+
  coord_flip()+
  scale_color_gradient2(name='Error',low='red',mid='green',high='red',aesthetics = c('color'),guide='colorbar')+
  geom_label(aes(y=ifelse(Wins>prediction,Wins,prediction),label=paste(round(error,2)),color=error),hjust=-.2,size=2)+
  #geom_label(aes(y=0,label=paste(round(error,2)),color=error),hjust=-.2,size=2)+
  scale_color_manual(name='legend',values=adjustcolor(c('Blue','Red'),alpha.f=.5),labels=c('Actual Wins','Predicted Wins'),aesthetics = c('fill','alpha'),guide='legend')+
  ggimage::geom_image(aes(x=reorder(Tm,Wins),image=team_logo_wikipedia),y=-.5,size=.04,inherit.aes = F)+
  theme(axis.title.y=element_blank(),
        axis.text.y=element_blank(),
        legend.justification = c(1,0),
        legend.position = c(1,0),
        plot.caption=element_text(face='italic')
  )+
  ggtitle('2019 NFL Predictions Against Actuals','Model 2 Data')+
  labs(caption='Data via: @nflfastR and Pro Football Reference')+
  guides(fill=guide_legend(override.aes=list(alpha=.5)))

Because the previous graph only shows teams in descending order of actual wins, it is difficult to compare the spread by itself.

Below will sort teams from the biggest underperformers (in games difference) to the biggest overperformers. This info can give us an easy visual of which teams may have odd situations causing the model to struggle with its prediction

plot_conf %>% ggplot(aes(x=reorder(Tm,error),y=error,fill=error))+
  geom_bar(stat='identity',alpha=.5,position='identity')+
  coord_flip()+
  ggimage::geom_image(aes(y=0,image=team_logo_wikipedia),hjust=.5)+
  scale_color_gradient2(name='Error',low='red',mid='green',high='red',aesthetics = c('fill'),guide='colorbar')+
  ggtitle('2019 Model 2 Projection Errors')+
  #scale_x_discrete(name='Tm')+
  labs(caption='Data via: @nflfastR and Pro Football Reference')+
  theme(axis.title.y=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        legend.justification = c(1,0),
        legend.position=c(1,0))