Home Field Advantage: How Weather Impacts NFL Games
By: Clay Myers
Date: 3/5/2021
Introduction
There are numerous theories as to why NFL teams win more often at home, from weather differences, to rowdy hometown fans, to referee preferences, and everything in between. In this project, I examine the notion of home field advantage in the NFL, with an eye especially towards hometown weather.
Dataset
The data for this project was used with permission from http://www.nflsavant.com/. The title of the data file is “CSV of All NFL Weather From 1960 to 2013”. The data comprises of 11,192 observations of NFL regular season games from 1960-2013, and contains the following variables: id, hometeam, homescore, awayteam, awayscore, temperature (at the time of the game), windchill, humidity, windmph, weather, and date of game. See below for some basic descriptive statistics of each numerical variable.
The dataset is inclusive of current and past NFL teams dating back to 1960. The weather variable is a written description of temperature, windchill, and windmph.
Above and to the left of each graph, click the “Code” button in order to examine the Python code that created each visualization.
Code
import wget
URL = "http://www.nflsavant.com/dump/weather_20131231.csv"
path = "C:Anaconda3/"
wget.download(URL, path + 'NFL_Weather_Final.csv')
import pandas as pd
import numpy as np
import seaborn as sns
import joypy as joypy
from matplotlib import cm
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import matplotlib.style as style
filename = "NFL_Weather_Final.csv"
df = pd.read_csv(path + filename)
df.describe()
Can The Dolphins Play in Cold Weather?
As we begin our analysis, let’s first start by examining some conventional wisdom. There is a cliche surrounding the Miami Dolphins, as well as other NFL teams based in Florida, that these teams cannot win in cold weather. Before the 2020 season and Tom Brady’s departure to Tampa Bay, we’ve seen the Dolphins travel to New England once a year to play the Patriots, oftentimes in the bitter cold months of November and December. Traditionally, the Dolphins are not very successful in these campaigns, so this notion has been top of mind in recent years. An argument can be made that Miami’s inability to win in New England is more a function of the Patriot’s prowess and less a function of the chilly New England weather. After all, some players on Miami could be from northern states, and thus, might already be used to the cold climate. While this dataset only reaches to 2013, we can still examine historically how Miami has fared in colder weather, not merely localized to games against Brady’s New England.
Code
df['HomeWin'] = np.where(df['home_score'] > df['away_score'], 1 , 0)
df['WinningTeam'] = np.where(df['home_score'] > df['away_score'], df['home_team'] , df['away_team'])
df['LosingTeam'] = np.where(df['home_score'] > df['away_score'], df['away_team'] , df['home_team'])
# Remove Ties
df = df[df.home_score != df.away_score]
# Remove Extraneous Columns
del df['wind_chill']
del df['humidity']
del df['wind_mph']
del df['weather']
df['DolphinWin'] = np.where(df.WinningTeam == 'Miami Dolphins', 1 , 0)
bins = [-7, 33, 55, 75, 96]
names = [ '<32'u'\N{DEGREE SIGN}F''', '32-55'u'\N{DEGREE SIGN}F''', '55-75'u'\N{DEGREE SIGN}F''', '>75'u'\N{DEGREE SIGN}F''']
df['Temperature_Bins'] = pd.cut(df['temperature'], bins, labels=names)
df_dolphins = df[(df.WinningTeam == 'Miami Dolphins') | (df.LosingTeam == 'Miami Dolphins') ]
count_series = df_dolphins.groupby(['Temperature_Bins', 'DolphinWin']).size()
new_df = count_series.to_frame(name = 'size').reset_index()
# 'Welcome to Miami: Dolphins Wins and Losses, 1960-2013'
my_colors = {0:'#f26a24',
1:'#008e97'}
ax1 = new_df.pivot("Temperature_Bins", "DolphinWin", "size").plot(kind='bar', color=my_colors)
ax1.set_title('Welcome to Miami: Dolphins Wins & Losses, 1960-2013', fontsize = 16, pad=20)
ax1.set_xlabel('Gameday Temperature', fontsize=16)
ax1.set_ylabel('Games', fontsize=16)
plt.xticks(rotation=0,fontsize=14)
plt.yticks(fontsize=14)
ax1.legend(bbox_to_anchor=(1, .9), fontsize=14, title = 'Result', frameon=False, labels = 'LW')
ax1.yaxis.set_major_locator(plt.MaxNLocator(6))
txt="Source: NFL Savant"
plt.figtext(0.7, -0.15, txt, wrap=True, horizontalalignment='center', fontsize=10)
style.use('fivethirtyeight')
plt.show()
As the above graph demonstrates, there is statistical backing behind Miami’s inability to play in the cold. We can see that the Dolphins have a losing record playing in games under 55 degrees, while their winning percentage skyrockets from 55 degrees and onward.
Can The Packers Play In Warm Weather?
If Miami can’t win with the same efficiency in cold weather, can we see a reverse effect for teams based in colder cities? Let’s take a look at how the Green Bay Packers, playing in the frozen tundra of Lambeau Field, fare in warmer weather.
Code
df['PackerWin'] = np.where(df.WinningTeam == 'Green Bay Packers', 1 , 0)
df_packers = df[(df.WinningTeam == 'Green Bay Packers') | (df.LosingTeam == 'Green Bay Packers') ]
count_series1 = df_packers.groupby(['Temperature_Bins', 'PackerWin']).size()
new_df1 = count_series1.to_frame(name = 'size').reset_index()
# 'The Frozen Tundra: Packers Wins and Losses, 1960-2013'
my_colors = { 1:'#ffb612',
0:'#203731'}
ax1 = new_df1.pivot("Temperature_Bins", "PackerWin", "size").plot(kind='bar', color=my_colors)
ax1.set_title('The Frozen Tundra: Packers Wins & Losses, 1960-2013', fontsize = 16, pad=20)
ax1.set_xlabel('Gameday Temperature' , fontsize=16)
ax1.set_ylabel('Games', fontsize=16)
plt.xticks(rotation=0,fontsize=14)
plt.yticks(fontsize=14)
ax1.legend(bbox_to_anchor=(1.20, .9), fontsize=14, title = 'Result', frameon=False, labels = 'LW')
ax1.yaxis.set_major_locator(plt.MaxNLocator(6))
txt="Source: NFL Savant"
plt.figtext(0.7, -0.15, txt, wrap=True, horizontalalignment='center', fontsize=10)
style.use('fivethirtyeight')
plt.show()
We can indeed see this opposite effect happen with the Green Bay Packers. The Packers have a losing record in games with temperatures above 75 degrees, but steadily perform better and better as the temperature drops. While temperature effects might be a proxy for home games, which teams generally win more of (see “NFL Home and Away Winning Percentages by Team” later in this article), the effects of temperature on winning percentage are still quite pronounced for both the Miami Dolphins and the Green Bay Packers nonetheless.
Does Weather Impact Total Points Scored?
Since we’ve identified some teams may perform better in certain conditions, how does temperature universally impact the amount of points scored per game? One might imagine that a blisteringly cold evening or a hot and humid afternoon would impact the morale of the players, the on-field conditions, or the overall player stamina. Using a joyplot, I plotted multiple lines detailing the total score distribution (final home score plus final away score), grouped by temperature range.
Code
df['total_score'] = df['home_score'] + df['away_score']
# 'Total Points Scored in NFL Games by Gameday Temperature, 1960-2013'
fig, axes = joypy.joyplot(df, column=[ 'total_score'], by='Temperature_Bins', colormap=cm.Blues_r, fill=False, figsize=(10,6) )
plt.axvline(df.total_score.mean(), color='grey', linestyle='solid')
plt.text(42.9, 1.0, 'Mean = 41.5', rotation=0, fontsize=12)
plt.title('Total Points Scored in NFL Games by Gameday Temperature, 1960-2013', fontsize=20, pad=20)
style.use('fivethirtyeight')
plt.vlines(df.total_score.mean(), 1, -1)
txt="Source: NFL Savant"
plt.figtext(0.7, -0.05, txt, wrap=True, horizontalalignment='center', fontsize=10)
plt.xlabel('Total Points Scored', fontsize=16)
plt.show()
As we can see, there isn’t much difference between the distribution of total points scored up to 75 degrees. However, games played in temperatures over 75 degrees see a lower distribution of total points scored. One might hypothesize that the heat slows players down and depletes them of energy, resulting in slower, low-scoring games.
How About Era?
Moving away from temperature for a moment, we might wonder how the game has changed since 1960. While temperature differences provide noticeable outcomes, it’s important to establish if the game has remained consistent over the course of the last few decades in terms of scoring patterns.
In addition to providing weather and home field statistics, the dataset in use also provides a look into how the game has changed offensively. By plotting a joyplot of total points scored in NFL games by year of play, we can produce the below graph.
df['date'] = pd.to_datetime(df['date'], format = '%m/%d/%Y')Code
df['Year'] = df.date.dt.year
# 'Total Points Scored in NFL Games, 1960-2013'
decades = [int(y) if y%10==0 or y == 2013 else None for y in df.Year.unique()]
fig, axes = joypy.joyplot(df, column=[ 'total_score'], by='Year', colormap=cm.autumn_r, labels=decades, fill=False, figsize=(10,6) )
plt.axvline(df.total_score.mean(), color='grey', linestyle='solid')
plt.text(42.9, 1.05, 'Mean = 41.5 ', rotation=0, fontsize=12)
plt.title('Total Points Scored in NFL Games, 1960-2013', fontsize=22, pad=20)
style.use('fivethirtyeight')
plt.vlines(df.total_score.mean(), 1, -1)
txt="Source: NFL Savant"
plt.figtext(0.7, -0.02, txt, wrap=True, horizontalalignment='center', fontsize=10)
plt.xlabel('Total Points Scored', fontsize=14)
plt.show()
We can see some slight increases from the dataset mean both in the early 1960s, as well as in the mid 2000s and onward. While total points scored from 2013-2020 is beyond the scope of this dataset, it is reasonable to hypothesize this trend of increased scoring has continued to the present, especially given the NFL’s propensity to introduce offense-friendly rules, like expanded defensive pass interference and roughing the passer penalties.
Maybe Watch These Games From Home
Returning to our discussion of temperature, there have been quite a few games played under extreme temperature conditions. While the Packers may prefer to play in cooler climates, and Florida teams may prefer sunshine, there likely are not any players that enjoy playing in 96 degree heat or -7 degree cold…and even fewer fans that enjoy spectating.
y = df.sort_values(by=['temperature'], ascending=False).reset_index()Code
def pick_colors_according_to_city(this_data):
colors=[]
city = this_data.home_team
for each in this_data.home_team:
if each == 'Dallas Cowboys':
colors.append('#002244')
elif each == 'Tampa Bay Buccaneers':
colors.append('#d50a0a')
elif each == 'Arizona Cardinals':
colors.append('#ffb612')
elif each == 'Chicago Bears':
colors.append('#c83803')
elif each == 'Green Bay Packers':
colors.append('#203731')
elif each == 'Denver Broncos':
colors.append('#002244')
elif each == 'Cincinnati Bengals':
colors.append('#FB4F14')
else:
colors.append('black')
return colors
# 'Temperatures and Cities of Hottest and Coldest NFL Games ' '('u'\N{DEGREE SIGN}F''), 1960-2013'
y = df.sort_values(by=['temperature'], ascending=False).reset_index()
bottom1 = 0
top1 = 4
d1 = y.loc[bottom1:top1]
my_colors1 = pick_colors_according_to_city(d1)
bottom2 = 11091
top2 = 11095
d2 = y.loc[bottom2:top2]
my_colors2 = pick_colors_according_to_city(d2)
Dallas = mpatches.Patch(color='#002244', label='Dallas')
Tampa_Bay = mpatches.Patch(color='#d50a0a', label='Tampa Bay')
Arizona = mpatches.Patch(color='#ffb612', label='Arizona')
Chicago = mpatches.Patch(color='#c83803', label='Chicago')
Green_Bay = mpatches.Patch(color='#203731', label='Green Bay')
Denver = mpatches.Patch(color='#002244', label='Denver')
Cincinnati = mpatches.Patch(color='#FB4F14', label='Cincinnati')
fig = plt.figure(figsize=(10, 6))
fig.suptitle('Temperatures & Cities of Hottest and Coldest NFL Games ' '('u'\N{DEGREE SIGN}F''), 1960-2013', fontsize=16,
fontweight='bold')
ax1 = fig.add_subplot(2, 1, 1)
ax1.bar(d1.id, d1.temperature, label='Count', color=my_colors1)
ax1.legend(fontsize=14)
ax1.legend(handles=[Dallas, Tampa_Bay, Arizona],fontsize=14)
ax1.spines['right'].set_visible(False)
ax1.spines['top'].set_visible(False)
ax1.set_ylim([90,97])
ax1.axes.xaxis.set_visible(False)
ax1.set_title('Hottest 5 Games', size=14)
x = ['90'''u'\N{DEGREE SIGN}F''', '92'''u'\N{DEGREE SIGN}F''', '94'''u'\N{DEGREE SIGN}F''', '96'''u'\N{DEGREE SIGN}F''']
l = [90, 92, 94, 96]
ax1.set_yticks(l)
ax1.set_yticklabels(x)
ax2 = fig.add_subplot(2, 1, 2)
y = ['-2'''u'\N{DEGREE SIGN}F''', '-3'''u'\N{DEGREE SIGN}F''', '-4'''u'\N{DEGREE SIGN}F''', '-5'''u'\N{DEGREE SIGN}F''',
'-6'''u'\N{DEGREE SIGN}F''', '-7'''u'\N{DEGREE SIGN}F''']
L = [-2, -3, -4, -5, -6, -7]
ax2.set_yticks(L)
ax2.set_yticklabels(y)
ax2.bar(d2.id, d2.temperature, label='Count', color=my_colors2)
ax2.legend(fontsize=14)
ax2.legend(handles=[Chicago, Green_Bay, Denver, Cincinnati],fontsize=14)
ax2.spines['right'].set_visible(False)
ax2.spines['top'].set_visible(False)
ax2.axes.xaxis.set_visible(False)
ax2.set_ylim([-7,-2])
ax2.set_title('Coldest 5 Games', size=14)
fig.subplots_adjust(hspace = 0.35)
txt="Source: NFL Savant"
plt.figtext(0.7, 0.01, txt, wrap=True, horizontalalignment='center', fontsize=10)
plt.show()
With little surprise, the coldest NFL game took place in Green Bay, Wisconsin, on January 20, 2008. The New York Giants traveled to meet the Green Bay Packers and emerged victorious, capturing a 23-20 win over Green Bay. The wind chill was a blustery -27 degrees. It seems the Green Bay Packers had no such home field advantage that day; even the residents of Green Bay cannot easily acclimate to temperatures that frigid.
Just five years later, the New York Giants were participants in the hottest game on record during this time period as well, traveling to Dallas to play the Cowboys on September 8, 2013. Facing a temperature of 96 degrees, Dallas bested New York by a score of 36-31. Perhaps training in the hot Texas summer aided the Cowboys in conquering both the heat and the New York Giants.
Playing At Mile High
Which NFL team has the best home winning record? While home teams fare better than away teams in general, there is a big difference across stadiums and locales. The notorious Philadelphia Eagle fans, for example, try to trash-talk the opposing team into submission. The Seattle Seahawks are home to the famous “12th Man”. However, the team with the best home winning percentage might come as a bit of a surprise to the casual observer.
df_home_wins = df[(df.HomeWin == 1)]Code
homepercent = (df_home_wins['home_team'].value_counts())/(df['home_team'].value_counts())
home_df = homepercent.to_frame(name = 'HomeWinPercent').reset_index()
df_away_wins = df[(df.HomeWin == 0)]
awaypercent = (df_away_wins['away_team'].value_counts())/(df['away_team'].value_counts())
away_df = awaypercent.to_frame(name = 'AwayWinPercent').reset_index()
df5 = pd.merge(away_df, home_df)
df5.columns=['Team', 'AwayPercent','HomePercent']
# Drop past teams
df5 = df5[df5.Team != 'Boston Patriots']
df5 = df5[df5.Team != 'Baltimore Colts']
df5 = df5[df5.Team != 'Houston Oilers']
df5 = df5[df5.Team != 'Los Angeles Raiders']
df5 = df5[df5.Team != 'Los Angeles Rams']
df5 = df5[df5.Team != 'Phoenix Cardinals']
df5 = df5[df5.Team != 'St. Louis Cardinals']
df5 = df5[df5.Team != 'Tennessee Oilers']
# 'NFL Home and Away Winning Percentage by Team, 1960-2013'
fig, ax = plt.subplots(1, figsize=(10, 6))
ax.scatter(x = df5['HomePercent'], y = df5['AwayPercent'], c=df5['HomePercent'], s = 200, cmap='Greens', edgecolors='black')
ax.set_xlabel("Home Winning Percentage")
ax.set_ylabel("Away Winning Percentage")
ax.set_title('NFL Home and Away Winning Percentage by Team, 1960-2013', fontsize=20, pad=20)
x = ['50%', '55%', '60%', '65%', '70%']
l = [0.50, 0.55, 0.60, 0.65, 0.70]
ax.set_xticks(l)
ax.set_xticklabels(x)
y = ['30%', '35%', '40%', '45%', '50%']
L = [0.30, 0.35, 0.40, 0.45, 0.50]
ax.set_yticks(L)
ax.set_yticklabels(y)
plt.annotate("Denver Broncos", (0.695531-0.01, 0.463557+0.01))
ax.xaxis.grid(True)
ynew = 0.5
ax.axhline(ynew, color="gray")
ax.yaxis.grid(True)
xnew = 0.5
ax.axvline(xnew, color="gray")
txt="Source: NFL Savant"
plt.figtext(0.7, -0.05, txt, wrap=True, horizontalalignment='center', fontsize=10)
style.use('fivethirtyeight')
plt.show()
The Denver Broncos boast the best home winning percentage in the NFL, winning about 69.5% of their games at home. While their fans are not known to be especially boisterous, the stadium is 5,280 feet above sea level. The high altitude presents many challenges to opponents, but the Broncos players are more acclimated to play in such an environment.
Final Thoughts
While home field advantage is a well-documented phenomena, it manifests itself in a myriad of ways. There are many additional factors to consider when thinking about home field advantage besides merely temperature and altitude, and all of these factors are worth examining in their own right. The untraditional 2020 season also provides an interesting case study regarding the absence of fans (or the absence of most fans) and player performance.
Works Cited
Willman, D. (2021). CSV of All NFL Weather From 1960 to 2013. NFL Savant. Retrieved February 24, 2021, from
http://www.nflsavant.com/about.php.