Analyzing Home Field Advantage at The Super Bowl in R

Richard Cotton and Paul Sabin, a Wharton lecturer and sports analytics expert, analyze whether home field advantage matters in the Super Bowl using R. They examine the Eagles vs. Chiefs matchup and walk through the data and code to test the claim.
Chapters
Hi there.
Data Scamps and Data Champs, this is Richie. Now, we are just days away from one of the biggest events in the US sporting calendar. So we've got a. Well, it's like 20, 23 all over again. So we've got the Philadelphia Eagles going head to head with the Kansas City Chiefs and it's going to be a fun game, I hope. Now, I find that knowing a bit about the analytics makes me appreciate the games a bit more because you understand what's going on sort of underneath the hood, how people are making decisions. And so data science is incredibly important in sports, both for the team trying to get an edge on the field and for anyone gambling on the event and trying to get a better chance of winning some money. And today we're looking at the specific example of does the team who is playing at home have an advantage? So it's going to be some fun analytics. And beyond that, I'm also very excited that we're doing the analysis in R today. It must be a good year or so since we've had an R based code along. And yes, I know Python is much more popular these days, but R still remains my favorite programming language for data analysis. So let's get started. Our guest is Paul Sabin. He is a lecturer in statistics and data science and also a senior sports analytics fellow at the Wharton School. So Paul leads sports analytics research in the Wharton Sports analytics and Business Initiative. He also teaches data science, so he's built and led NFL football analytics teams. He's got a sports analytics consultancy called Sabin Analytics. And previously Paul was an adjunct professor at the Sports Analytic in Sports analytics at Virginia Tech. He was also VP of football analytics at suports. And he was also a sports data scientist at espn. So very well qualified. I think you spent a lot of time thinking about football analytics. Please take it away, Paul.
All right, yeah, thanks. Thanks everyone for letting me be here. And I know, you know, some, some of you might be normally Python users, but I will say in sports analytics the R is sort of the traditional language and more of the, more, some of the more modern stuff with the bigger data sets, you know, Python is more dominant, but in some of the traditional data sets which we're going to be working on today, R is, is pretty standard. Standard. So we're going to get started and we're going to just quickly install two packages that aren't native to the notebook here. And the first one is the most important, which is the NFL Fast Star package. And what this allows us to do is to Essentially access all NFL data that's publicly available that's out there. And then the second, this is broom mix, which I just like to use to analyze some linear mixed model results. So, so you know, it's not required for maybe some people, but I like, I like its functionality. So I'm just going to quickly run those, I kind of set that up so you can follow along and they'll install pretty quickly. All right, so we're going to be talking today about sort of what the role of home field advantage is. And you know, if you're trying to predict the super bowl, you need to first understand how good the teams are. And in order to understand how good the teams are, you need to analyze how they've done this season and you could not analyze how they've done the season without understanding the role that other things besides how good the teams are, such as whether they played at home and perhaps if they had some referee advantage. So if you, you've been following along, there's a lot of people who claim that the Kansas City Chiefs get bias and favoritism from the refs. And so we're going to, time permitting, talk about that today. So a quick background, we're going to quickly build a, a power rating model which essentially will estimate how good each team is in the NFL on a points per game basis versus average. Then we are going to look at the estimates of home field advantage across time and then time permitting, we will look at the role that referees play play in deciding games. So first we're going to set up and we're going to load these packages that I've listed here. So first is tidyverse. If you're familiar with R, it's just sort of the common version of R. Everyone uses Tidyverse. So you just need to load them all by library each of them. Now that we've installed both the Brew Mixed and NFL Fast Star. Packages, okay, LME4 is going to allow us to do linear mixed models and then splines will allow us to run non linear effects in our, our modeling process. Okay, so we'll just quickly load those, which is pretty fast. Okay. So the first thing we need to do is we need to create a, determine the data set that we want to look at. So what I would like to do is I would like to look about 20 years into the past in order to understand NFL trends and to understand, you know, long term home field advantage effects or long term referee impacts. So what I'm going to do is I'm first going to define the current year that we're going to look at, which is although it's 2025, it's referred to as a 2024 season. And all the data, since most of the football season happens before the new year, so we just refer to it as that year. And I'm going to define a historical seasons horizon of 20 years and then I will define a vector of all the years that I want to look at. So a vector of integers which will be from the current year minus the number of historical seasons, but then I have to add one. Okay. Since it's inclusive all the way to the current year, so that will give me a vector of 2005, 2006, all the way to 2024. Okay. All right. One tricky thing that when you work with sports data, if you're not an expert in sports, you're not familiar, familiar with the sport, especially in American sports, less, less common. So in other sports is the fact that teams change names or they change cities. And instead of making everyone figure this out on their own, I will tell you that in NFL Fast Star, there's two main data sets that we're going to be looking at. One is what we call the play by play data. And the play by play data tells you what happens on every individual play in an NFL game. And the other is what I'll just call schedule data. Or you could think about it as the metadata for each game. So who's playing in the game, when the game was, what time of day, another, what stadium it was in, who was the referee, who were the coaches, things like that. So the tricky thing in NFL Fast Star is that in these two different data sets, it handles team teams that change names differently. And the metadata for the games, it will use the current team name for the teams. So for example, the St. Louis Rams in 2016 moved to be the Los Angeles Rams. And so in the metadata for the games, it will refer to the team abbreviation of LA to refer to the Rams for all historical data, including before they moved to Los Angeles, but in the play by play data, because it happens as of that, that game and it's saved off, you know, during the game, it will still use the old abbreviation of STL for St. Louis for the Rams before 2016, similarly for the San Diego Chargers, which are now the Los Angeles Chargers, or the Oakland Raiders, which are now the Las Vegas Raiders. And instead of making everybody figure that out on their own, I am going to just show everyone some quick code on how this can happen. First what I do. And instead of Typing this out, I figured this would be slow. I'm going to create a data frame here which will define the three teams and the years. Their old abbreviations. So STL for St. Louis, San Diego and Oakland, their new team abbreviations, LAC and LV and the year in which they moved from the old location to the new location. And then I create this function and whenever, you know, I could just manually do this, but it's easier to do with a function because say there's different columns I'll want to change in the future. So I input the data set. I'm worried about this team conversion data frame here and then the column name that I want to change. If it's the called team, it'll be called team. Or maybe it's called opponent. All I'd want to change the opponent's name. Essentially all I do is I look and I take. They rename the team. The column that is called that I input, I call it Team Rename. I join on the team conversion here and then I rename it and I say if the season that we're looking at is after the moving season and it's not missing, sometimes we have missing data for the name, then I will give it a new abbreviation. If not, I will give it the old abbreviation. And then I will make these two columns null. And then I will essentially add the new abbreviation to the old abbreviation. If the season was before the move season. And then I'll rename the column back. So I'll take anything that's called that rename variable that I called anything, it's called team rename. And then I replace team rename here with the column name that I inputted in the function and I return. Okay, so I know it looks like a lot. I will run this really fast and all this is doing is just saving the function off. But then we can now apply this function in the future. So that's just sort of some data input and some preparation for some of the analysis we'll be doing. Okay, so next step is I'm going to load the data that we're interested. So in NFL fastr you can do this via the load schedules and the load play by play or load P by p function. Okay, so first I'm going to do that and actually I should be more clear in the load schedules. It you can we can type NFL schedule and I can load it by doing NFL and then NF I could write NFL fast start. But it's actually there's a sub package of NFL Fastral Cart NFL Read R& in R, if you want to specify a package that you're calling a function specifically from that package to make sure there's no confusion, then you can do it this way. I think you don't need it in this case, but I just wanted to demonstrate that. So you could do load schedules and I could give it the years that I want or it, if I want it to just load all schedules, I will, I can leave it blank and it will load the all the entire history of the NFL. And it might seem like a lot of data, but it's really not. Since the NFL has only been around for about 60 years, there's 200 and so games a year, 200, 300 games a year. So you know, that's, that's in the order of like, you know, 10 to 12,000 games. So relatively speaking, not a huge data set. But on the flip side, the play by play data set is a little bit bigger since each game has around 150 to 200 plays in it. We will not want to load all of the seasons historically for that. So I will load the vector, right? I defined the seasons that I wanted and I will load just those seasons that I'm interested in. And like I said, so you multiply 150 rows by the number of games in each season and that becomes a much bigger data set than the metadata that we had before. Okay. The next step that I want to do is I will want to create variables for home and away. And although there are variables for home and away, so say I, if I want to look at what these two data sets look like, I can do head NFL play by play. So you can kind of look at what that looks like. It'll load here for a second. And in fact, I'm going to make a new chunk here for in a minute while that loads. All right. And it might take a second to load because it is, it is a bit of data. Like I said, it's 150 rows for each game. And we have 20 seasons and you know, 250 to 270 or so games a season. And so you can see that, you know, that's a fairly large data set for the play by play data. It's actually taking a little bit longer than I was testing before. So I'm going to ad lib here for a minute. But we'll look at the play by play data here and what the columns are, and then essentially what we're going to want to do when that loads is we're going to want to create. Here it is. We're going to want to create a new column that indicates home and away. Now, you might look at this and you might say to yourselves, well, there's a column that says home team. There's a column that says away team. So why do I need something that says home and away? Well, the interesting thing is in NFL data and in sports data, typically there's always a team that's called the home team. There's always a team that's called the away team. And if you want this is a very large data set. There's a lot of information in NFL fast off for each play. But sometimes there's games that are not at any team's stadium. So an example of this would be the international game. So in the NFL, increasingly each year they play more and more games overseas. So this year, you know, there was a game or two in, in Germany. There's always a couple games in England. Some years there's games been games in Mexico. This last year the NFL had its first game in Brazil. And so although those games are not at either team's home stadium, they still have to name one team, the home team and one team, the away team. So they know which team gets to call the coin flip. So they know which team gets to wear the dark jerseys and which team gets to wear the light jerseys. So what we want is we want a column that will denote not just home and away, but but home, away and neutral. And we want that for the team who has possession of the ball. So if we scroll over here, you will see besides home team, you will see a POS team which stands for possessing team, the team that has the ball. And you will see a def team which stands for the team that is the on defense on that play. And so each row of this data set is a play in the NFL. So you'll see the game ID here denotes the year, the week that it was played in the two teams, the home in the away, whether it was a regular season game or a playoff game. All right, you'll see another column for a week and then you'll see this possessing team and defensive team. You'll see the yard line in terms of 0 to 100, so how far away they are from scoring a touchdown. And you'll see the quarter and half seconds remaining, which half it is. You'll see lots of information. You'll see the down. So whether it's first or second down, these ones, it's like a kickoff, so it doesn't have a Valid down and you'll see the time remaining. Lots of, lots of different things. You'll see interestingly enough, this description column, that's the raw data, everything else in this data set is actually derivatives from what is contained in this description column. Okay. Okay, so I want a variable and I'm going to call it, and this will be helpful for our modeling purposes. Okay, I'm going to create a new variable. I'm going to call it possessing team site indicator and I want this to just say neutral if it's neutral or home or. Sorry, I want this, this one I'm going to be a. It's an indicator value. So we want it to be zero if it's a neutral game, a positive one if it's a home game and a negative one if it's an away game for the team that has the ball. Okay. And the reason why I'm doing this is that so when we fit our linear model we will essentially it'll be a linear effect, essentially that home is one unit and change in that coefficient from neutral or a two unit change from being away. So in R this is similar, you can do a. It's similar to a SQL function where you can say this case when statement 9 say location. If this is a separate variable called location different than home and away and it tells you if it's a neutral site location or not. So if location is equal to capital N neutral, which is how they denote it in the data, I'm going to give it a zero and that's for both teams. Both teams will get zeros because if it's neutral, both teams are neutral. Otherwise if the possessing team is the same as the home team, I'm going to give this value a1. If not I will say if the possessing team is equal to the away team, I'm going to give it a value of negative one. And then just to cover my bases, if there's anything that's not covered by those, which I think there will be, this should cover everything. But just in case there's not just good data practices to cover. You know, I said this is essentially if location then is neutral than zero. If else possessing team is equal to home team you get a one. If else possessing team is equal to away team, a negative one. And then this is kind of like my L statement when I say true. So every other value that is not covered by those previous ones, I'm going to give it a missing value, an NA value of real NA value and then that's though that's All I need. But if I want to print this off or I want it to make it look pretty, 0, 1 and negative 1 doesn't look very pretty. So I'm going to create a separate variable which we'll just call possessing team site. And this will be the same thing. So I'm just going to go ahead and copy and paste. But Instead of the 1, 0, negative 1, I will just make it a text string. Okay. Which will be. I'll just call it neutral if there's neutral home if it's home and away if it's away. Like I said, the reason why I'm doing this is because in the data it says who's the home team? And then a separate column says if it's a neutral site game. And this way I have just one column that says if the possessing team is home, away or neutral. Right. And then I have it in numerical format here. 1, 0 and negative 1. Okay, so I'm going to quickly, I'm going to run that and of course, as always, I'm going to get an error when I'm live coding. So let me figure that out. What I do here. Neutral team 18. Oh, that's why. Here we go. This should be a tilde and that's what you get. All right, so let me rerun that. Okay. Oh, that's great. I had this working. Oh, sorry. And then this one needs to be a character because these are characters, not real numbers. All right, sorry, I, I had this right on my notes, but here we go. All right, now what I'm going to do is I am going to convert the team names. So in order to join the play by play data with the metadata for each game, I need to make sure the team names always match. And like I said, in the play by play data, before the teams changed cities, they had the old team names. So I had this function which I created and now I'm just going to feed the play by play data into the function. I'm going to tell it, I want to change the possessing team's name based on this team conversion data frame that I defined. And then I'm going to tell it to change the defensive team name if it's before the team, the year that they moved to the current name of the team. So essentially this means that the St. Louis Rams in 2009, I will now in the play by play call them the Los Angeles Rams so that I can join it on the metadata. It's the same franchise, really. I just want to make sure I don't have any missing or data that I can't join. Okay. So that shouldn't take very long, but it's a large data set, so here we go. Okay. You can also look if you're curious about the columns, you can use the glimpse function or I use head. Sometimes a glimpse will just show you kind of sequentially what, what the data types look like. All right, so now I want to motivate a little bit what we we're doing here. So the NFL has really adopted analytics in the last several number of years. And actually they hired a former professor who decided sports analytics about six or seven years ago. His name is Michael Lopez and he, he runs the NFL's analytics department. And. But before he was hired by the NFL, he actually wrote this research paper which analyzed home field advantage across every major professional sport in America. And what they found was that the NFL had the second highest home field advantage in the professional sports in America after only the NBA. But some people have estimated that over the years that the home field advantage in the NFL has gotten less and less. And so that when you're modeling the impact of how good teams are, you should give less credit or you should discount the home team winning a little bit less because home field doesn't matter as much as it used to. That's sort of been a prevailing thought in recent years. Okay. So I also have studied home field advantage in other sports and college basketball. And you can actually detect that in certain stadiums, when the opposing team has to shoot free throws in the second half facing the students, they tend to statistically significantly shoot worse free throw percentages than they did in the first half. And you can go to my blog, I've linked it here, savement analytics.com blog and I've done some of that research here, but I just wanted to motivate that. So we want to see if this is true. Has home field advantage in the NFL gotten less impactful over time? Because if so, that'll impact how good we analyze the teams to be from this season. So what we're going to do here is first we need to prep the data for each game and then we need to create a power rating, right, A model. And in that model we're estimate how good each team is and we'll estimate the effect of home field advantage. And then we're going to look at that model and see if home field advantage has changed over time. And then we're if we have, time permitting, if not. I have the rest of this already coded and I don't Think we'll get to this. But if you're interested, you can follow along after this and you can look at my analysis and the code for it to see if what the causes of home field advantage are. So some people think that referees tend to call more penalties against the away team than the home team to please, sort of subconsciously to please the home crowd. And we're going to look to see if that's true. And then if you, if you want to keep going, I do an analysis at the end about the Kansas City Chiefs and whether or not they do get more penalty calls than other teams. Okay, so if we want to roll up the data. Well, first in the play by play data, we're going to want to filter out some information and this is going to get a little bit lengthy. But the first thing we're going to do is we want. The nice thing about R is this pipe structure. So I'm going to pipe several different things on top of each other on these bullet points. So the first bullet point says I want to ignore all quarterback kneels, spikes and kickoff plays. And so this is going to come from the play type variable. And I want to ignore those three kinds of play types and they're called QBneel, they're called QB spike, and they're called kickoff. Now here what I'm doing is this actually says I want to only include those. So now I want to do the opposite, which is everything but those plays. Now it's common in NFL analytics to filter out plays which we call our garbage time. Garbage time is plays where there is the game's pretty much over. Okay. And so in NFL Fast Star there's some models that are included, including a win probability model which says given the current score of the game, how much time is left, who has the ball, what's each team's chances of winning. And so that's the win probability value column. So I'm going to just say I only want to keep plays where the win Probability is between 0.05 and 0.95. So between 5% and 95% chance of either team winning. Okay. Other than that, I'm going to call that. That's garbage time. The game is really different in that teams aren't really playing the same style. I also want to ignore any plays where there is no possessing team listed. Okay. That could be like, you know, some odd plays or you know, maybe like sometimes in the playback play it'll just be like team so and so call the timeout and then it won't list a possessing team because it wasn't really a play. And then I will also ignore plays where the expected points added variable is missing. And expected points added is, is a model which estimates, well, it's, it comes from expected points, which is a model which estimates the expected number of points the the current team with the ball is going to score and it's based on the next team to score. So if a team that has the balls backed up on their own end zone and then that means the other team's more likely to score, then their expected points will be negative. Or if they're about to score a touchdown, their expected points will be, you know, above six because a touchdown is usually about seven points. So EPA then is the difference between your expected points on the previous play or on the current play and on the next play. So if you have a positive epa, that means you've improved your team situation and you're more likely to score than you were before the last play. So we only, we're going to use this EPA variable to denote essentially it's like scoring every play, essentially the point value of every play. And we only want to include games or plays that have that those values because that's how we're going to determine success in a game. All right, and then I'm going to group by several variables. Game id okay, Season possessing team, defensive team possessing team, the site that we defined earlier, home away neutral. Right. And then whether or 1, 0, negative 1 and then home away neutral here. Okay. And then I'm going to these, these, if you're not familiar with R, these are pipes essentially just says it's pushing through the current data to the next function. And then I'm going to summarize and so I want to get in each game, right? I've grouped by the game within each season and the team. So for each team, for each game I'm going to summarize or essentially calculate the total expected points added for each team's offense. I'm going to calculate the average per play expected points average for each offense and then I'm going to calculate how many plays each offense or each team had when they had the ball. Right? And that'll be the number of plays. And the last step I want to do is that I will want to. One step is I want to standardize for the number of plays in a game. And the reason why I do this is that if one team had more EPA in a game just because they played at a faster tempo, so they had more plays, you don't want to reward them just because they played up tempo. You want every team to sort of be evaluated on the same sort of what we call tempo neutral game. So all I'm doing here is one, I'm going to make sure. And I'm going to make sure that I'm going to adjust each season to have an average EPA per game of zero. Okay? The model tries to do this. It tries to adjust for that offense is easier now than it used to be because we just. Because mathematically you can't say if a team last year is better than a team this year because they never play each other. So because the game changes, we can only evaluate teams against other teams in the same season. So I'm just going to do one last thing where I will subtract off a weighted average within each season so that the weighted average is zero, essentially for EPA per play. And then I will wait by the number of plays. And if I want to make this faster, I'll just copy and paste. Then I'll make this EPA per game. All right? And then the last thing I do is with this tempo neutral thing where I'm going to define an EPA per 70 plays. And the reason why I chose 70 is that's about the average number of plays for each team on offense in a game. And it's not exactly, but it's, you know, it's close. It's like 69, typically 68, 69, 70 is a nice round number. So then I'm just going to take the EPA per play, multiply it by 70. So then I get essentially get sort of a possessing possession neutral estimate for how efficient the team was in each game. Then I'm going to run that. That should actually run pretty quickly. There we go. So now I, now I have that. Or if I want to, at least if you want to look at what that my data set looks like now, I'll just put it here so we can quickly see what a quick. I don't know why it takes longer now when I just ran it already, but. Should not take longer. That's weird. All right, so here is the. The data. So now we have one row for each team for each game. So you see Arizona offense, the Giants on defense in this game. And the next row is the Giants on offense and Arizona on defense. You'll see here Arizona was away, so they were minus one. Giants were home, so they were plus one. And if we scroll over, we can see. Oh, sorry, let me. I forgot to do a group by statement here. So this is why I check my data where I group by the season. Sorry. So each, each team has a EPA versus average in that season. Okay. And it's actually always best to then ungroup at the end. So I will do that. Here, let me stop that and run it again. All right, so then we'll, we'll let that run and then the next step we're going to do is we're going to want to account for other things that might impact the outcome of a game besides home and away. It's actually known that there's more offense when you play indoors. So I'm going to have a roof variable to see if it's indoors. And there's also better offense, depending on the surface, sometimes if it's an artificial turf or not. And I'm going to keep the referee and the rest variables as well. All right, so here we can look. We scroll over and we'll see that the EPA per play and EPA per game for each team and then per 70 typically, right. If a team has more expected points than the other team, they won the game. This won't correspond necessarily to the points in the game, but it should correspond to the points versus average. So if the Giants here had positive 4.6 EPA versus average, that means they scored 4.6 more points on offense than the average for the NFL that year. And then the Arizona team here with a negative 1.5 means that they scored 1 1/2 points less than the NFL average that year. So pretty much as long as one team's EPA per game and the game was higher than the other, that means they won the game and it should actually pretty much be a one to one relationship. Okay. So the next step is I'm going to join on the schedule data, okay. Which we've set up a lot of data information to do that for. So I'm going to left join, add the schedule data. Okay. And then I'm only. But I don't want to add every variable in the schedule data. So I will select only certain variables. Okay. Game id, because I need that to join. I need the home team to be able to join, the away team to be able to join. I need, and then I want to keep this metadata such as the roofs, the surface that was played on. So like grass or turf, who the referee was for the game, how many days rest the home team had coming in, and how many days rest the away team had. And then I'm going to join by the game id. Okay. And then I will create a new Variable, which is instead of home team rest, which is what the schedule will tell me, I want to know if the possessing team had rest. So I'm simply just going to say if the possessing team is equal to the home team listed, then you get the home rest, otherwise you get the away rest. And likewise for the defensive team. If I want to know how much rest the defense had, I will just call this opponent rest. And then I will do the same thing, but then I will switch the if else. So if the possessive team is equal to the home team, then the opponent's rest is the away rest and vice versa. And then lastly, well, not lastly, but I will then take the team rest and look at the difference and I'll call that the team rest effect, which is I'll take the team rest minus the opponent's rest. And then I'm going to look and whether I'm going to create a new variable of whether or not the game was indoors. The variable roof will tell you if it was a dome or if it was open or a closed retractable roof. And so I essentially just say I want to know if it's a dome or if the roof says closed. And that I'm going to say is an indoors. So it'll be an indicator of one sorry, and a zero otherwise. And then I will create another variable called grass should let me know if it's on a grass surface. You think these things don't matter, but actually over the long run they do. And I will look to see if the surface name has the word grass in it. This is a string. It'll say like different kinds of grass or different kinds of artificial turf. I essentially will just say if the word grass is in the name of the surface, I'm going to give it a one and a zero otherwise. Okay? And then so now I will have this new, these new variables on my EPA per game data set and I'll let that run. And then the next step, which was probably one of the last things that we're going to do today, is we need to create this power ranking. Okay? And so I'm going to do this. First I need to create a unique team identifier for each season because I don't want the giants to have one power rating for all 20 years. I want the Giants 2024 to be different than the Giants 2021 and vice versa. So I'm going to create these new variables called team season, which is just going to concatenate the possessing team, underscore with the season and that'll be a new text string and the defensive team season, which will be a concatenation of the defensive team underscore season. And now here, and now you can see this, the first few rows of this, right? And you can see all these new variables that we created. Arizona 2005, New York Giants 2005, whether or not it was on the grass, right? So this game was on the grass, this game, this game was not, whether or not was indoors, etc. And the team rest effect, if both teams had seven days of rest, then no team had an advantage. But if the other team played on Monday Night Football and the other team, the other, this team played on Sunday, then the, then the Sunday team had an extra day of rest, which would be an advantage. Okay, so we are going to just do a linear mixed effects model which will be good for estimating team effects and it actually shrinks the, the team effects. Essentially it's if you're, if you follow Bayesian analysis or a mixed effects model is kind of like a frequencist version of a Bayesian prior where you say I assume every team is zero, which will be average. And then I'm going to then have some variance around that. So some teams will be above average, some teams will be below. And I'm going to try to predict the expected points added per 70 plays in each game. And I'm going to have an effect for the team, sorry, an effect for the team and an effect for the defense. So each team will have an offensive component and a defensive component. I'm going to adjust for other variables such as the whether it was indoors or it was a grass or some season variable. You know, maybe season to season is different. I'm going to put a nonlinear effect on the team rest. I'm going to use a spline, but you could use a polynomial expansion as well. And then I'm going to have a variable for an interaction between season and home field. Okay, so I'm going to start. This is from the LMER stands for linear mixed effects Regression. Okay. And I want this EPA per 70 to be my value that I'm predicting. And then I'm going to first, right? And I need to tell the data set here, I'm going to first give it this nonlinear effect for team rest. I'm going to give it three degrees of freedom, which would be kind of similar to doing a polynomial of expansion of like x x squared and X cubed. And then I'm going to have this indicator for indoors, an indicator for it feels on the grass. And then these random effects for the offenses. So team season and a random effect for the defenses. And then I want a effect for each season. But I have to be careful. Since season is a number, I have to tell it to treat it like a discrete value. So I'll do factor season and I want the interaction between this discrete season and home field. So then I will do possessing team site indicator. Okay. And then I will run this mixed effects model. Just take a second. Once that runs, what I will want to do is I will want to look at the ratings for each team and I want to extract out each team's estimates for offense and defense. And the way this works is that because in the regression I have to, I have an effect for defense and it's additive. What I really want is I want offense minus defense and that'll be an estimate of how many points above average you scored. But since I can't do that, it's offense plus defense. And so what I will want to do is here I will want to take the negative value of the defense and that will mean that a good defense is a positive value. So if you have a really good offense and it's minus a really good defense, then you essentially will have a. It would be like a wash, right? It looks like it ran into an error. It was running before just fine. So I'm going to do is just to get this to work. I will decrease the number of years. Maybe it's a memory issue. Since I'm doing this live call and I'll just do since 2020 or maybe 2018 and I'll have to rerun everything. Above. Sorry about that. I'm just going to talk through this code. Once I get that, I will pull the random effects out. I will only keep the ones, the variables that say defensive team or offensive team season. I'm going to rename team season to be offense, defense to be defensive team, season to be defense. I'm going to pull out because it's the team name, underscore season. I'm going to extract out using this regular expression everything before the underscore which will be the team name. And then I will remove the underscore. Then I'll extract out using a regular expression the four digits which will be the season. In here is where I say if it's a defense, I'm going to take the minus of that estimate and then I'm going to, I'll do this pivot wider statement which essentially takes my data from long format to Wide format. And then I will estimate the, the total power rating of the team. So their offensive strength plus their defensive strength, that'll get me an overall power rating. While I wait for that to run. I had preloaded this so I have this for each season until you'll get something like this, you know, Chicago 2018 offense plus defense. And you can sort when you do this on your own and it'll tell you the overall rating for these things. If we filter to the season and we look at 2024, you can look at this year's power ratings and it actually thinks Baltimore and Detroit were the strongest teams, followed by Buffalo and that Philadelphia and Kansas City are a little bit further down. I will caution a few things. Baltimore and Detroit. Kansas City won a lot of games really closely. So power ratings kind of hurt you. And they say if you win close games it's some of it's due to luck. Maybe you don't believe that because Kansas City has won the super bowl two times in a row and so maybe have they have some ability to win close games. But typically over the long run it's shown that close games tend to go on either side. Baltimore, Detroit had a lot of blowouts. So did Buffalo. Washington came on strong late. They beat Detroit in the playoffs. Minnesota played well all year and Philadelphia played really well all year too. But they had an injury to their quarterback in the last part few weeks of the season. So that might have hurt them. So we actually see Philadelphia and Camp Kansas City here aren't even estimated to be the two best teams. Kansas City had a buy. You know, there's randomness in the playoffs. So here we are with Philadelphia and Kansas City. And Kansas City was. Is estimated to be about 2 points worse per game than Philadelphia. After you account for home field, let's see if we're. Yeah, we'll run up to here. I'll see if I can run this again. And so but you actually, if you look at the betting models, you'll see here, I've just pulled it out here. If you look at the betting models, you'll see that Philadelphia's is actually Kansas City is favored by a point and a half. And that's usually in these power rating models you'll get pretty close to what the betting models say if there's not injuries. I'm not accounting for injuries here. I'm just treating like every team's healthy. So of course part of that might be injuries, but part of it also, you know, Kansas City has had a receiver that's been Hurt all year is now back Hollywood Brown. And I think the betting markets are saying hey, Kansas City is a two time champion and so we're going to give them a little boost beyond what the power rating would say. Typically the betting markets and the power ratings are pretty close unless we have some of these extra extenuating circumstances like we have here. Just to show you, since we're short on time, if you wanted to look at the home effects each year, I will just show you what that looks like. And you would get code that looks like this. Essentially you would take the coefficients from the model and you want to look just at the possessing team and values. But remember, this is the home field per year. So I'm going to pull out the season for these. And then the important thing to remember is I said my variable was a 1 if it was home, 0 if it was neutral. And so I said if it's home versus neutral that's the coefficient. But really the home field advantage is home versus away because if someone's home the other team is away and so it'll be double actually the home versus neutral effect. And that's just for the offense. But then you have to factor in the defensive side of the ball. So really the home field advantage is like four times this coefficient. So that's essentially what I'm doing here is I'm multiplying the coefficient by 4 and then I'm just making this table look a little prettier and I'm saying year by year, what does the home field advantage look like on the road versus neutral? And then I plot that and I'll show you the code for the plot. It's just a ggplot if you're familiar with that and I look at that by season and you'll actually see that over time it's somewhat year to year, that's somewhat inconsistent. And that's because in the NFL you only have currently 272 games a year. And so due to that smallish sample size, it's hard to detect home field over time like each within each season. So what I do here is I actually take the average home field advantage that our model estimates each season and I plot that as a dotted black line. And so that's sort of the overall home field effect. And you'll notice it's a little bouncy, but mostly it bounces around this black line. But the reason why people think it's gone down over time is because if you look at 2019 and 2020, it was actually negative road Teams actually won more games than they lost. 2020, there was no fans in the stands due to Covid. In 2023, we had a bounce back year in home field advantage. But you could see that of the last 1, 2, 3, 4, 5 seasons, only one of them was above the historical average for home field advantage. And so that's why some people think it's gone down over time. But it is a pretty small sample to look at. So if you're familiar with ggplot, all you would do is just, you would give it the season in the x axis and this effect for home versus away. You plot those lines and then I would give it the new data set for the average and then I would plot that plotted dotted black line, the dotted red line at 0 and do it here. That's really all the time we have. We're short on time. I'll make sure that they post the entire code base in its full extent. If you want to go through this, I further talk about penalties and you can look and look at yourself. I give you some helps and hints on how to analyze penalties and whether or not home teams get more penalties. And then at the end in the code I, I show you whether or not the. I build a model to estimate the referees that give the most penalty yards and the least per game and then whether or not the Chiefs get favorable refereeing. And if we can see that in the data, probably you have an opinion on that already. You're not going to be swayed one way or the other. But I'll let you look at the data as well. All right, so I'm going to open it up for questions. I know we didn't get through everything, but hopefully if you have extra time after this, you can, you can go through and I've given you hopefully a lot of fun and interesting ways to look at this data set. Yeah.
Thank you, Paul. That was very cool. Actually, I just want to make sure I've understood one of the conclusions correctly because I was just, I just checked the, the betting odds. Now it does look like most of the sort of betting sites are giving sort of, they're putting Kansas City Chiefs as like the likely winners. But you're saying from the power model actually the Philadelphia Eagles are the stronger team at the moment. Is that correct?
In a typical power rating, the Eagles and every power rating, the Eagles are actually higher this season. You can look at like ESPN's FPI or other ones, but the betting models do slightly favor Kansas City. But it's really close a point and A half is like a 53, 47% advantage. And so. And the reason why they're doing that is it seems to be that the bettors are giving Kansas City a boost because they have won two times in a row, and so that they have sort of that extra, you know, my power rating doesn't impact her in previous seasons, so they're giving them a boost for these previous seasons. But just based on this year alone, you would say that the Eagles should be favored. But of course, other seasons probably impact this one as well. So.
All right, so it's not quite a free money unlock there. It's like, it is very close to court on this.
No.
All right, cool. So, yeah, for anyone in the audience, please do ask questions in the chat. We've got a couple of minutes for that. In the meantime, I actually have a question for you about how you create that power model. So you use this BS function for creating a spline. Do you want to talk us through, like, how you, how you decide what goes into the model and why you use splines?
Yeah. So, I mean, there's other things that you could do if I had more time. It's actually shown that teams that play like in Denver, that aren't used to playing in Denver, they actually, that's an additional home field advantage for Denver, and that has been shown in the data. So you could do things like calculate the difference in elevation from where they're used to playing to factor, hey, if you played poorly in Denver, it might be because of the elevation, not because you're a bad team. And so those are other things you consider. I use the spline just because the difference between 0 extra days rest versus your opponent and 1 is somewhat significant, but it's. It's more significant than the difference between three and four extra days, which sometimes you do get. If some team played is playing on a Thursday and the. And they played the previous Sunday, but the other team had a buy the week before, so they haven't played in 11 days. Right. And that's. That's a. That's. It's not a linear effect. So every additional day is not incrementally the same helpfulness. So I do that spline. So we have the diminishing returns effect. You could do a log transformation, a spline, or a power expansion as well.
Okay, yeah, that makes sense. Like, certainly, like the day after the gym, I'm always knackered. But like, difference between like four and five days after the gym, not so much. Okay, cool. All right. So I know we're going to wrap up shortly, but let's do one question for the audience. So Ghost Daddy, very nice name says career wise. If I'm interested in, in the sports analytics, where do I get started like.
From zero I get this question a lot. So do what stuff I'm doing. I'll write about it, publish it and then if you're interested, you know there's NFL is a big data competition every year. They just announced their finalists. One of my students at UPenn or 2. The students I work with at UPENN were announced as the finalists and they get, they get one, they get a cash prize of like $12,000 which when you're a student seems pretty nice but also they fly them out to Indianapolis and they get to present their research to the or their competition evaluation to people that work in the NFL. And typically if you get that far, the people that work in the NFL want to give you a job. So doing these kinds of competitions, if you do well is also another great way NFL does one other data companies, stats bomb. If you're into soccer, there's a stat Staffletes is a hockey one. They do like a big data cup. There's, there's several of them out there. The NBA will do a hackathon. These type of things actually do get you noticed. Or presenting at conferences. There's sports analytics conferences you can look up and you can submit to present at those.
That's very cool. Yeah, I like that. And certainly, I mean even now, I would say a 12,000 cash prize, I would not say no to that. So very, very nice. Okay, so thank you once again, Paul. Before everyone dashes off, I'm going to say we've got a few more sessions coming up this week so come back on Wednesday for creating your first app with Java. So if you're interested in all sort of software development side of things, very useful to attend. On Thursday we've got a session on assessing your organization's AI maturity so you can find out whether your company is any good at using AI on or not. And then next Tuesday we've got session on creating AI agents for financial reporting. So please do come back for those. Hope to see you all again soon. Thank you to Ghost Daddy for asking question. Thank you to everyone who showed up today and yeah, thanks.
More from Richie

#359 My Best Friend is AI with Valerie Tiberius, Professor of Philosophy at University of Minnesota

How AI Agents Will Work While You Sleep | Ruslan Salakhutdinov, Professor at Carnegie Mellon

#358 How AI Agents Will Work While You Sleep | Ruslan Salakhutdinov, Professor at Carnegie Mellon







