Simple Football Predictor: Week 7 Predictions
By: Nathaniel Selevan
Tags: Football, Logistic Regression, Data Science, Linear Transformation, Python

Unfortunately, the simple predictor only correctly predicted half of last week’s games. Below see what improvements were made to improve these results, exploring the data, and this week’s predictions.

Week 6 Results:

Week 6 Results

Last week was a rough one for the predictor, predicting only 50% of the games correctly. 14 games however, is a small sample size so its possible we were just unlucky this time around. Hopefully, with this week’s improvements, we will see better results. Again, for games in the 60–65% probability range, in the long run, we only expect to be correct 60–65% of the time.

Exploratory Data Analysis:

Our data contains 459 NFL games from weeks 4–17 of the 2018 and 2019 season and weeks 4–6 of the current season. The data contains the following columns:

# Column Non-Null Count Dtype

--- ------ -------------- -----

0 ID 459 non-null object

1 Season 459 non-null int64

2 Home 459 non-null object

3 Away 459 non-null object

4 Home_Game 459 non-null float64

5 Away_Game 459 non-null float64

6 3_wk_avg_points_for_home 459 non-null float64

7 3_wk_avg_points_against_home 459 non-null float64

8 3_wk_avg_points_for_away 459 non-null float64

9 3_wk_avg_points_against_away 459 non-null float64

10 Home_win 459 non-null category

11 Home_win_margin 459 non-null int64

ID — matchup ID number created by yours truly in the format of 18W8M8 with the first digits representing the season (in this example the 2018 season), the next two the week number (week 8), and finally the last two representing the matchup number (the 8th match of the week) Season — The season that the matchup took place Home — The home team Away — The away team Home_Game — what game of the season is it for the home team Away_Game — what game of the season is if for the road team 3_wk_avg_points_for_home — the average points scored by the home team in their last 3 games 3_wk_avg_points_against_home — the average points allowed by the home team in their last 3 games 3_wk_avg_points_for_away — the average points scored by the away team in their last 3 games 3_wk_avg_points_against_away — the average points allowed by the away team in their last 3 games Home_win — whether or not the home team won Home_win_margin — the margin of victory by the home team, in the event of a road victory this field would be negative. This is will not be used in this article, its existence is for a future article predicting who will win against the spread

Below are the descriptive statistics for the 5 columns to be used in our model: the four scoring statistics and the whether the home team won

Descriptive Statistics (using Python’s Pandas library)

Some interesting observations on our data:

The mean for Home_win is .5577 meaning that the home team was victorious in 55.77% of all matchups.

The statistics for the points are pretty consistent across the four fields, with the average team scoring and allowing around 23 points in their last three games.

I was little shocked to see that 25% of teams were failing to score or allow more than 20 points a game over the last 3. I thought that the 25th percentile would be a bit a higher.

Given the sample size we could assume the data for the 4 scoring values would be normal. From the below histograms and Kernel Density Estimation (KDE), we see that our data is close to normal, but not quite there, with a larger sample size I believe a data will eventually be normal

Histograms and KDEs fitted with the normal distribution, created using the seaborn library in Python

Another interesting question to ask is if any of our features are correlated. The below correlation heatmap shows no large correlations in our features

correlation matrix created using the seaborn heatmap in

The below scatter plots visualize the lack of correlation in a different way (I was originally excited to share a new Python visualization library called Plotly where you can make cool interactive plots and graphs. Unfortunately, I was unable to embed the interactive version to Medium, and had to share a static version instead.)

To point out the extremes, on the first plot the team allowing 44.3 points a game and scoring only 5.3 points a game is the 2019 Miami Dolphins heading into their week 4 game (a 30–10 loss to the Chargers). On the other extreme the 2019 Ravens heading into week 13, averaged victory of 45 to 8.67 the 3 weeks prior, they beat the 49ers that week 20–17.

On the visiting team graph, the 2018 bills were averaging deficits of 34.33–6.67 heading into their week 10 matchup (a 41–10 victory against the Jets, because of course the Jets would get blown out in that game). On the flip side the Saints averaged victory of 43.33–12.67 heading into their 2018 week 13 matchup (a 13–10 loss to Cowboys, because why not). The 2018 Rams were the most fun team heading into their week 13 matchup, averaging a deficit of 42.33–41.67 (a 30–16 victory of the Lions).

Scatter plot of home teams’ average points for and against showing no correlation. Plot created using the plotly express library in Python

Scatter plot of away teams’ average points for and against showing no correlation. Plot created using the plotly express library in Python

Improvements and Feature Transformations

There are two improvements made to the model this week. The first is the addition of the 2018 season to the dataset. The second explained in great detail below is testing out some feature transformations.

Feature transformations is when you apply some sort of mathematical transformation to one of more of the features (columns being used to predict the outcome) in an effort to increase its predictive capabilities. One of the most common ways of transforming a feature is to square its value, this way the higher the value the greater the marginal effect on the predicted outcome.

For this week I will be testing 3 possible transformations:

Squaring each of the scoring averages: As mentioned above, this will further reward teams for scoring a lot, and further punish them for allowing too many points Regressing the scoring to the mean: This is the opposite of the above transformation. Since the quality of opponents each team faces in their 3 week average vary some teams benefit from playing weaker opponents, and their averages might reflect outlier games. To regress towards the mean, we find the average scoring rate for each season, and calculate a weighted average between the total scoring rate and the the teams actual average. For this model I will be regressing by 30%. The reason for this value is that I’ve seen it used in a book on hockey analytics written by Rob Vollman. For example, if a team came in averaging 38 points for in the last 3 games, but the season average was 23, we would change their value to 23 x .3 + 38 x .7 = 33.5. This transformations will assume that extra high or low values are outliers, and will move them slightly closer to the mean. Average scoring per season as follows: 2018–23.34, 2019–22.81, 2020–25.39 Average margin of victory or defeat: This transformation will add a feature consisting of each teams’ points for minus their points against.

To choose which transformation to use we will test 8 models:

The original model with no transformations transformation 1 only transformation 2 only transformation 3 only transformations 1 and 2, we will regress towards the mean prior to squaring. transformations 1 and 3, we will take margin of victory prior to the squaring transformations 2 and 3, we will take margin of victory prior to the regression towards the mean all 3 transformations: margin of victory will be from original data, scoring averages will be regressed prior to squaring

To test each model we divide our data into a training set and a testing set, I used a 70:30 split with 70% of the games being used to train the model and 30% to test which model is better. The reason for dividing the data is because it is important to grade each model on how it performs on new data (the test data).

To grade which model is best, we use four measures: Accuracy, Precision, Recall and F1

Accuracy is the percentage of games correctly predicted

Precision is the percentage of games predicted to be won by the home team that are actually won by the home team (true positives / (true positives + false positives))

Recall is percentage of games where the home team won that are actually predicted by the model (true positives / (true positives + False Negatives))

F1 is a combination of precision and recall, it is calculated as 2 x ((precision x recall) / (precision + recall)).

In all these cases the higher the measure the better. However, because precision, recall, and F1 don’t care about the cases where the road team winning is correctly predicted (true negatives) we will use accuracy as our primary measure. Below are the confusion matrices and measurements for all 8 models (confusion matrices created using seaborn library in conjunction with the confusion_matrix function from sklearn.metrics)

Model 1:

Accuracy: 60%; Precision: 64%; Recall: 74%: F1: .69

Model 2:

Accuracy: 63%; Precision: 66%; Recall: 77%: F1: .71

Model 3:

Accuracy: 60%; Precision: 64%; Recall: 74%: F1: .69

Model 4:

Accuracy: 60%; Precision: 64%; Recall: 74%: F1: .69

Model 5

Accuracy: 63%; Precision: 66%; Recall: 77%: F1: .71

Model 6:

Accuracy: 62%; Precision: 66%; Recall: 76%: F1: .70

Model 7:

Accuracy: 60%; Precision: 64%; Recall: 74%: F1: .69

Model 8

Accuracy: 64%; Precision: 67%; Recall: 76%: F1: .71

Model 8 has the highest accuracy though it has a lower recall than Model 2. The two models were very close, with model 2 correctly predicting 1 more home winner, and model 8 predicting 2 more road winners. Because we said we would use accuracy as our primary measure, we should have used Model 8 for our predictions (though this time we will use the whole dataset to train the model)

However, once I ran the final model I noticed that in the coefficients a higher margin of victory for the road team led to a higher chance of victory for the home team, which doesn’t make sense, and requires further observation, and running the tests again next week when we add the 2017 data. So for this week I will use model 2

Original Model 8 coefficients

Regressed Home Points For Squared: 0.0046038605300748586

Regressed Home Points Against Squared: -0.0035808292146099663

Regressed Away Points For Squared: -0.002043253767257121

Regressed Away Points Against Squared: 0.0014994114766728964

Home Margin: -0.09475156125993649

Away Margin 0.01393347432542924

Model 2 coefficients:

Home Points For Squared: 0.0011185954531353428

Home Points Against Squared: -0.0005942483683362089

Away Points For Squared: -0.001188458879721672

Away Points Against Squared: 0.0006683984124776526

We see that the points scored both home and away have about equal effects, same with points allowed. I don’t always include the intercept (the starting advantage that the home team has) in my report, but I will note that the intercept for this model is .28501796 because I looked ahead at the predictions and saw something that surprised me.

Predictions:

Week 7 predictions

Some quick observations on these predictions:

I noted the intercept above for a reason, and looking at these predictions you can see that I believe it gave too much of a home field advantage.

11 out of 14 games are predicted in favor of the home team, normally, I would chalk that up to a weird coincidence, but then I saw the Jets given a 50.22% chance of victory and I figured something has to be off about the model because no one in their right mind would ever believe the Jets should ever be given more than a 25% chance to win any game this year.

On second glance at the confusion matrices above, I noticed that all the models performed weakly when the road the team was the victor, predicting it correctly less than half the time, further showing our model’s bias for the home team.

Furthermore, looking at the test data above, the model only predicted a road victory in around 30% of the games, when in reality the road team wins around 45% of the time, another indicator of strong home bias in the model.

Maybe its the premise that games can be predicted by each teams recent points scored and allowed totals. If that’s the case, then luckily starting week 9 I plan on adding more features to the model, a couple each week. I’m also going to rerun all the test models next week once we include the 2017 season, maybe that might make things make more sense.

Given the strong bias towards the home team, I think its fair to say that the 3 games where the road team is predicted, should be viewed as pretty confident predictions.

At 67.75% chance of victory, the Los Angeles Chargers are the greatest favorites this week.

Besides for the 2017 season data being incorporated next week, we will also introduce a Linear Regression to see if we could predict the margin of victory, hopefully, it will pass the snuff test better than this week’s predictions.

All game data was taken from ESPN.com and pro-footballreference.com

I am currently enrolled in the Applied Data Science Professional Certificate program at the Thayer School of Engineering at Dartmouth College. Please reach out to met via LinkedIn (https://www.linkedin.com/in/nathanielselevan/) or comment below with all questions, comments, job opportunities, project suggestions, or to act as a mentor. All likes and comments will be well appreciated.