import requests
import json
= "BUF"
team = 20222023
season = "https://api-web.nhle.com/v1/club-schedule-season/" + str(team) + "/" + str(season)
link print(link)
= json.loads(requests.get(link).text) # pull request, get data (.text), and convert JSON to a Python dict
sched = sched["games"][0] # look at the first game
game1 print(json.dumps(game1, indent=4)) # print the new dictionary object
print("")
= game1["gameDate"]
game1_date print("The date of the Sabres first game this season is: " + str(game1_date))
= game1["id"]
game1_ID print("The ID of the Sabres first game this season is: " + str(game1_ID))
In hockey (and some other sports), a hat trick is three goals scored by the same player in a single game. How common are hat tricks in the NHL?
We’ll again turn to the NHL’s stats database, with API documentation help from here and here.
First, let’s just look at the 2022-2023 season for one team. How about the Buffalo Sabres? We’ll need to loop through every Sabres game from this season and find games with hat tricks.
Let’s look at the data structure. Here’s the first Sabres game that season.
https://api-web.nhle.com/v1/club-schedule-season/BUF/20222023
{
"id": 2022010006,
"season": 20222023,
"gameType": 1,
"gameDate": "2022-09-25",
"venue": {
"default": "Capital One Arena"
},
"neutralSite": false,
"startTimeUTC": "2022-09-25T18:00:00Z",
"easternUTCOffset": "-04:00",
"venueUTCOffset": "-04:00",
"venueTimezone": "US/Eastern",
"gameState": "FINAL",
"gameScheduleState": "OK",
"tvBroadcasts": [
{
"id": 320,
"market": "H",
"countryCode": "US",
"network": "NBCSWA",
"sequenceNumber": 1
},
{
"id": 324,
"market": "N",
"countryCode": "US",
"network": "NHLN",
"sequenceNumber": 1
}
],
"awayTeam": {
"id": 7,
"placeName": {
"default": "Buffalo"
},
"abbrev": "BUF",
"logo": "https://assets.nhle.com/logos/nhl/svg/BUF_light.svg",
"darkLogo": "https://assets.nhle.com/logos/nhl/svg/BUF_dark.svg",
"awaySplitSquad": false,
"score": 4
},
"homeTeam": {
"id": 15,
"placeName": {
"default": "Washington"
},
"abbrev": "WSH",
"logo": "https://assets.nhle.com/logos/nhl/svg/WSH_light.svg",
"darkLogo": "https://assets.nhle.com/logos/nhl/svg/WSH_dark.svg",
"homeSplitSquad": false,
"score": 3
},
"periodDescriptor": {
"periodType": "OT"
},
"gameOutcome": {
"lastPeriodType": "OT"
},
"winningGoalie": {
"playerId": 8480045,
"firstInitial": {
"default": "U."
},
"lastName": {
"default": "Luukkonen"
}
},
"winningGoalScorer": {
"playerId": 8476994,
"firstInitial": {
"default": "V."
},
"lastName": {
"default": "Hinostroza"
}
},
"gameCenterLink": "/gamecenter/buf-vs-wsh/2022/09/25/2022010006"
}
The date of the Sabres first game this season is: 2022-09-25
The ID of the Sabres first game this season is: 2022010006
Unfortunately, this data structure doesn’t show us the goals scored by each player. To find that, we’ll need to cross reference a different database.
= "https://api-web.nhle.com/v1/score/" + str(game1_date)
link = json.loads(requests.get(link).text)["games"] # pull request, get data (.text), and convert JSON to a Python dict
box_scores
= next(item for item in box_scores if item['id'] == game1_ID) # find the Sabres game using the game ID from our previous bit of code
game1_score
print(json.dumps(game1_score, indent=4)) # print the new dictionary object
{
"id": 2022010006,
"season": 20222023,
"gameType": 1,
"gameDate": "2022-09-25",
"venue": {
"default": "Capital One Arena"
},
"startTimeUTC": "2022-09-25T18:00:00Z",
"easternUTCOffset": "-04:00",
"venueUTCOffset": "-04:00",
"tvBroadcasts": [
{
"id": 320,
"market": "H",
"countryCode": "US",
"network": "NBCSWA",
"sequenceNumber": 1
},
{
"id": 324,
"market": "N",
"countryCode": "US",
"network": "NHLN",
"sequenceNumber": 1
}
],
"gameState": "FINAL",
"gameScheduleState": "OK",
"awayTeam": {
"id": 7,
"name": {
"default": "Sabres"
},
"abbrev": "BUF",
"score": 4,
"sog": 28,
"logo": "https://assets.nhle.com/logos/nhl/svg/BUF_light.svg"
},
"homeTeam": {
"id": 15,
"name": {
"default": "Capitals"
},
"abbrev": "WSH",
"score": 3,
"sog": 27,
"logo": "https://assets.nhle.com/logos/nhl/svg/WSH_light.svg"
},
"gameCenterLink": "/gamecenter/buf-vs-wsh/2022/09/25/2022010006",
"clock": {
"timeRemaining": "03:45",
"secondsRemaining": 225,
"running": false,
"inIntermission": false
},
"neutralSite": false,
"venueTimezone": "US/Eastern",
"period": 4,
"periodDescriptor": {
"number": 4,
"periodType": "OT"
},
"gameOutcome": {
"lastPeriodType": "OT",
"otPeriods": 1
},
"goals": [
{
"period": 1,
"periodDescriptor": {
"number": 1,
"periodType": "REG"
},
"timeInPeriod": "04:05",
"playerId": 8477511,
"name": {
"default": "A. Mantha"
},
"mugshot": "https://assets.nhle.com/mugs/nhl/20222023/WSH/8477511.png",
"teamAbbrev": "WSH",
"goalsToDate": 1,
"awayScore": 0,
"homeScore": 1,
"strength": "PP",
"highlightClip": 6335818566112
},
{
"period": 2,
"periodDescriptor": {
"number": 2,
"periodType": "REG"
},
"timeInPeriod": "04:25",
"playerId": 8481528,
"name": {
"default": "D. Cozens"
},
"mugshot": "https://assets.nhle.com/mugs/nhl/20222023/BUF/8481528.png",
"teamAbbrev": "BUF",
"goalsToDate": 1,
"awayScore": 1,
"homeScore": 1,
"strength": "PP",
"highlightClip": 6335819727112
},
{
"period": 2,
"periodDescriptor": {
"number": 2,
"periodType": "REG"
},
"timeInPeriod": "09:16",
"playerId": 8482896,
"name": {
"default": "T. Kozak"
},
"mugshot": "https://assets.nhle.com/mugs/nhl/20222023/BUF/8482896.png",
"teamAbbrev": "BUF",
"goalsToDate": 1,
"awayScore": 2,
"homeScore": 1,
"strength": "EV",
"highlightClip": 6335819725112
},
{
"period": 2,
"periodDescriptor": {
"number": 2,
"periodType": "REG"
},
"timeInPeriod": "09:42",
"playerId": 8477839,
"name": {
"default": "C. Sheary"
},
"mugshot": "https://assets.nhle.com/mugs/nhl/20222023/WSH/8477839.png",
"teamAbbrev": "WSH",
"goalsToDate": 1,
"awayScore": 2,
"homeScore": 2,
"strength": "EV",
"highlightClip": 6335818367112
},
{
"period": 3,
"periodDescriptor": {
"number": 3,
"periodType": "REG"
},
"timeInPeriod": "05:43",
"playerId": 8481441,
"name": {
"default": "J. Snively"
},
"mugshot": "https://assets.nhle.com/mugs/nhl/20222023/WSH/8481441.png",
"teamAbbrev": "WSH",
"goalsToDate": 1,
"awayScore": 2,
"homeScore": 3,
"strength": "EV",
"highlightClip": 6335819254112
},
{
"period": 3,
"periodDescriptor": {
"number": 3,
"periodType": "REG"
},
"timeInPeriod": "18:55",
"playerId": 8482097,
"name": {
"default": "J. Quinn"
},
"mugshot": "https://assets.nhle.com/mugs/nhl/20222023/BUF/8482097.png",
"teamAbbrev": "BUF",
"goalsToDate": 1,
"awayScore": 3,
"homeScore": 3,
"strength": "PP",
"highlightClip": 6335820218112
},
{
"period": 4,
"periodDescriptor": {
"number": 4,
"periodType": "OT"
},
"timeInPeriod": "01:15",
"playerId": 8476994,
"name": {
"default": "V. Hinostroza"
},
"mugshot": "https://assets.nhle.com/mugs/nhl/20222023/BUF/8476994.png",
"teamAbbrev": "BUF",
"goalsToDate": 1,
"awayScore": 4,
"homeScore": 3,
"strength": "EV",
"highlightClip": 6335820905112
}
]
}
This data structure has info on each goal scored this game. Let’s figure out a way to tally the number of goals scored by each player. We’ll use the Counter
function from the collections
library.
import collections
= [i["playerId"] for i in game1_score['goals']] # extract player IDs for each goal scored
scorers = dict(collections.Counter(scorers)) # count how many goals are scored by each player
scorers_table
print("goals scored by player ID")
print(scorers_table)
goals scored by player ID
{8477511: 1, 8481528: 1, 8482896: 1, 8477839: 1, 8481441: 1, 8482097: 1, 8476994: 1}
Now that we’ve got the basics, we can set up a loop to go through all Sabres games in the 2022-2023 season.
= "BUF"
team = 20222023
season
= "https://api-web.nhle.com/v1/club-schedule-season/" + str(team) + "/" + str(season)
link = json.loads(requests.get(link).text) # pull request, get data (.text), and convert JSON to a Python dict
sched
print(team + " games with hat tricks...")
print("date:{{player_ID: [number_of_goals, player_name]}}")
for game in sched["games"]: # loop through every focal team game in the schedule
= game["gameDate"]
gameDATE = game["id"]
gameID
= "https://api-web.nhle.com/v1/score/" + str(gameDATE)
link2 = json.loads(requests.get(link2).text)["games"] # pull request, get data (.text), and convert JSON to a Python dict
box_scores = next(item for item in box_scores if item['id'] == gameID) # find the game using the game ID from our previous bit of code
score
= [d for d in score['goals'] if d['teamAbbrev'] in team] # only get goals scored by the team
team_goals
= [i["playerId"] for i in team_goals] # extract player IDs for each goal scored
scorers = dict(collections.Counter(scorers))
scorers_table
= dict((k, v) for k, v in scorers_table.items() if v >= 3) # get scorers with more than 3 goals
hat_tricks_scorers
for key in hat_tricks_scorers: # loop through hat trick scorers
= next(item for item in team_goals if item['playerId']== key)['name']['default'] # get player name based on player ID
player = [hat_tricks_scorers[key]] # convert dict value to list
hat_tricks_scorers[key] += [player] # add player name as a value to player ID key
hat_tricks_scorers[key]
if bool(hat_tricks_scorers): # only return results if there was a hat trick
print(gameDATE + ":" + str(hat_tricks_scorers))
BUF games with hat tricks...
date:{{player_ID: [number_of_goals, player_name]}}
2022-10-20:{8477949: [3, 'A. Tuch']}
2022-10-31:{8479420: [3, 'T. Thompson']}
2022-12-07:{8479420: [5, 'T. Thompson']}
2022-12-29:{8473449: [3, 'K. Okposo']}
2023-01-03:{8479420: [3, 'T. Thompson']}
2023-02-23:{8479420: [3, 'T. Thompson']}
2023-02-26:{8481528: [3, 'D. Cozens']}
2023-04-01:{8477949: [3, 'A. Tuch']}
Tage Thompson had 4 hat tricks that season, impressive!
We can expand this to look at all teams in the 2022-2023 season. It will take a little while to run this code. We’ll use the datetime
and dateutil
libraries to loop through every day in that season. Also, let’s only look at regular season games ("gameType": 2
). Lastly, we also need to ignore shootout goals (encoded by the periodDescriptor
value), since these aren’t counted toward a hat trick.
This code will take a few minutes to run.
from datetime import date, timedelta
import dateutil.parser as dateparser
= 20222023
season
# get start and end date of season
= json.loads(requests.get("https://api-web.nhle.com/v1/standings-season").text)["seasons"] # pull request, get data (.text), and convert JSON to a Python dict
standings
= dateparser.parse(next(item for item in standings if item["id"] == season)["standingsStart"])
start_date = dateparser.parse(next(item for item in standings if item["id"] == season)["standingsEnd"])
end_date
= timedelta(days=1)
delta
= {}
all_hat_tricks
while start_date.date() <= end_date.date():
= start_date.strftime("%Y-%m-%d")
date
= "https://api-web.nhle.com/v1/score/" + str(date)
link2 = json.loads(requests.get(link2).text)["games"] # pull request, get data (.text), and convert JSON to a Python dict
box_scores
= {} # empty dictionary to contain all hat trick scorers on this date
hat_tricks_scorers_date
if bool(box_scores): # only look at dates with games
for game in box_scores: # loop through each game on each date
if game['gameType'] == 2: # only look at regular season games
= [d for d in game["goals"] if d['periodDescriptor']['periodType'] != "SO"] # ignore shootout goals
goals = [i["playerId"] for i in goals] # extract player IDs for each goal scored
scorers = dict(collections.Counter(scorers))
scorers_table = dict((k, v) for k, v in scorers_table.items() if v >= 3) # get scorers with more than 3 goals
hat_tricks_scorers if bool(hat_tricks_scorers): # if there was a hat trick in this game
for key in hat_tricks_scorers: # loop through hat trick scorers
= next(item for item in goals if item['playerId'] == key)['name']['default'] # get player name based on player ID
player = next(item for item in goals if item['playerId'] == key)['teamAbbrev'] # get player name based on player ID
team = [hat_tricks_scorers[key]] # convert dict value to list
hat_tricks_scorers[key] += [player] # add player name as a value to player ID key
hat_tricks_scorers[key] += [team] # add team abbreviation as a value to player ID key
hat_tricks_scorers[key] = hat_tricks_scorers[key] # update the date dictionary
hat_tricks_scorers_date[key] if bool(hat_tricks_scorers_date): # if there was a hat trick on this date, update the main hat tricks dictionary
= hat_tricks_scorers_date
all_hat_tricks[date]
+= delta # increment date by 1
start_date
print("hat tricks were scored during " + str(len(all_hat_tricks)) + " games in the 2022-2023 NHL season")
hat tricks were scored during 66 games in the 2022-2023 NHL season
Let’s convert our messy all_hat_tricks
dictionary to a cleaner data frame.
import pandas as pd
= []
df_data for i in all_hat_tricks: # loop through all dates
for j in all_hat_tricks[i]: # loop through all hat tricks each date
1], all_hat_tricks[i][j][2], all_hat_tricks[i][j][0]]) # write data for each hat trick to list of list
df_data.append([i, j, all_hat_tricks[i][j][
= pd.DataFrame(df_data, columns=['date', 'player_ID', 'player_name', 'team', 'goals']) # convert list of lists to dataframe
all_hat_tricks_df
print(all_hat_tricks_df.head(5)) # print first five rows
date player_ID player_name team goals
0 2022-10-12 8478402 C. McDavid EDM 3
1 2022-10-20 8480830 A. Svechnikov CAR 3
2 2022-10-20 8477949 A. Tuch BUF 3
3 2022-10-22 8470794 J. Pavelski DAL 3
4 2022-10-27 8478402 C. McDavid EDM 3
Nice, that should be easier to work with. How many hat tricks were there in the 2022-2023 season?
print("Number of hat tricks in the 2022-2023 season: " + str(len(all_hat_tricks_df)))
Number of hat tricks in the 2022-2023 season: 96
How about a quick plot?
import plotly.express as px
'hat_tricks'] = range(1, 1 + len(all_hat_tricks_df))
all_hat_tricks_df[
= px.line(all_hat_tricks_df, x='date', y="hat_tricks",
fig = "NHL 2022-2023 Season Hat Tricks",
title ="plotly_dark",
template='hv') # line_shape will plot lines as steps
line_shape="")
fig.update_xaxes(title_text="number of hat tricks")
fig.update_yaxes(title_text='cyan', line_width=3)
fig.update_traces(line_color
# reduce margins for better viewing on mobile
=dict(l=20, r=20, b=20))
fig.update_layout(margin
fig.show()
I wonder which NHL season had the most hat tricks per game played. To answer this question, we can loop over all NHL seasons and extract the hat trick data.
This code will take a few hours to run since we’re doing a lot of API queries.
from datetime import date, timedelta
import dateutil.parser as dateparser
# get start and end date of season
= json.loads(requests.get("https://api-web.nhle.com/v1/standings-season").text)["seasons"] # pull request, get data (.text), and convert JSON to a Python dict
standings
# data structures to store results
=[]
every_hat_trick=[]
hat_tricks_by_season
for season in standings:
= season['id']
season_id
if season_id != 20232024: # skip current season
= dateparser.parse(season["standingsStart"])
start_date = dateparser.parse(season["standingsEnd"])
end_date
= timedelta(days=1)
delta
= 0 # counter for the number of games in the season
season_games = 0 # counter for the number of hat tricks
season_hat_tricks
while start_date.date() <= end_date.date():
= start_date.strftime("%Y-%m-%d")
date
= "https://api-web.nhle.com/v1/score/" + str(date)
link2 = json.loads(requests.get(link2).text)["games"] # pull request, get data (.text), and convert JSON to a Python dict
box_scores
= {} # empty dictionary to contain all hat trick scorers on this date
hat_tricks_scorers_date
if bool(box_scores): # only look at dates with games
for game in box_scores: # loop through each game on each date
if game['gameType'] == 2: # only look at regular season games
+= 1 # increment the number of games for the season
season_games = [d for d in game["goals"] if d['periodDescriptor']['periodType'] != "SO"] # ignore shootout goals
goals = [i["playerId"] for i in goals] # extract player IDs for each goal scored
scorers = dict(collections.Counter(scorers)) # count the number of goals per player
scorers_table = dict((k, v) for k, v in scorers_table.items() if v >= 3) # get scorers with more than 3 goals
hat_tricks_scorers if bool(hat_tricks_scorers): # if there was a hat trick in this game
for key in hat_tricks_scorers: # loop through hat trick scorers
= next(item for item in goals if item['playerId'] == key)['name']['default'] # get player name based on player ID
player = next(item for item in goals if item['playerId'] == key)['teamAbbrev'] # get player name based on player ID
team = game['id']
game_id = [season_id, game_id, team, key, player, hat_tricks_scorers[key]]
temp # add hat trick data to list
every_hat_trick.append(temp) += 1 # increment number of hat tricks for the season
season_hat_tricks
+= delta # increment date by 1
start_date
# add new season data to list
hat_tricks_by_season.append([season_id, season_games, season_hat_tricks])
print(str(season_id) + ", games = " + str(season_games) + ", hat tricks = " + str(season_hat_tricks))
19171918, games = 36, hat tricks = 37
19181919, games = 27, hat tricks = 16
19191920, games = 48, hat tricks = 35
19201921, games = 48, hat tricks = 20
19211922, games = 48, hat tricks = 25
19221923, games = 48, hat tricks = 17
19231924, games = 48, hat tricks = 5
19241925, games = 90, hat tricks = 18
19251926, games = 126, hat tricks = 18
19261927, games = 220, hat tricks = 16
19271928, games = 220, hat tricks = 24
19281929, games = 220, hat tricks = 4
19291930, games = 220, hat tricks = 35
19301931, games = 220, hat tricks = 20
19311932, games = 192, hat tricks = 18
19321933, games = 216, hat tricks = 17
19331934, games = 216, hat tricks = 19
19341935, games = 216, hat tricks = 13
19351936, games = 192, hat tricks = 11
19361937, games = 192, hat tricks = 16
19371938, games = 192, hat tricks = 11
19381939, games = 168, hat tricks = 14
19391940, games = 168, hat tricks = 7
19401941, games = 168, hat tricks = 6
19411942, games = 168, hat tricks = 12
19421943, games = 150, hat tricks = 23
19431944, games = 150, hat tricks = 43
19441945, games = 150, hat tricks = 22
19451946, games = 150, hat tricks = 19
19461947, games = 180, hat tricks = 26
19471948, games = 180, hat tricks = 15
19481949, games = 180, hat tricks = 7
19491950, games = 210, hat tricks = 10
19501951, games = 210, hat tricks = 13
19511952, games = 210, hat tricks = 14
19521953, games = 210, hat tricks = 10
19531954, games = 210, hat tricks = 11
19541955, games = 210, hat tricks = 15
19551956, games = 210, hat tricks = 10
19561957, games = 210, hat tricks = 14
19571958, games = 210, hat tricks = 16
19581959, games = 210, hat tricks = 21
19591960, games = 210, hat tricks = 15
19601961, games = 210, hat tricks = 21
19611962, games = 210, hat tricks = 14
19621963, games = 210, hat tricks = 14
19631964, games = 210, hat tricks = 13
19641965, games = 210, hat tricks = 12
19651966, games = 210, hat tricks = 22
19661967, games = 210, hat tricks = 18
19671968, games = 444, hat tricks = 41
19681969, games = 456, hat tricks = 41
19691970, games = 456, hat tricks = 36
19701971, games = 546, hat tricks = 52
19711972, games = 546, hat tricks = 53
19721973, games = 624, hat tricks = 66
19731974, games = 624, hat tricks = 66
19741975, games = 720, hat tricks = 85
19751976, games = 720, hat tricks = 89
19761977, games = 720, hat tricks = 59
19771978, games = 720, hat tricks = 69
19781979, games = 680, hat tricks = 73
19791980, games = 840, hat tricks = 81
19801981, games = 840, hat tricks = 133
19811982, games = 840, hat tricks = 139
19821983, games = 840, hat tricks = 108
19831984, games = 840, hat tricks = 113
19841985, games = 840, hat tricks = 113
19851986, games = 840, hat tricks = 114
19861987, games = 840, hat tricks = 97
19871988, games = 840, hat tricks = 113
19881989, games = 840, hat tricks = 119
19891990, games = 840, hat tricks = 88
19901991, games = 840, hat tricks = 71
19911992, games = 880, hat tricks = 101
19921993, games = 1008, hat tricks = 112
19931994, games = 1092, hat tricks = 91
19941995, games = 624, hat tricks = 48
19951996, games = 1066, hat tricks = 93
19961997, games = 1066, hat tricks = 74
19971998, games = 1066, hat tricks = 64
19981999, games = 1107, hat tricks = 56
19992000, games = 1148, hat tricks = 53
20002001, games = 1230, hat tricks = 93
20012002, games = 1230, hat tricks = 57
20022003, games = 1230, hat tricks = 75
20032004, games = 1230, hat tricks = 46
20052006, games = 1230, hat tricks = 79
20062007, games = 1230, hat tricks = 71
20072008, games = 1230, hat tricks = 73
20082009, games = 1230, hat tricks = 65
20092010, games = 1230, hat tricks = 67
20102011, games = 1230, hat tricks = 76
20112012, games = 1230, hat tricks = 55
20122013, games = 720, hat tricks = 32
20132014, games = 1230, hat tricks = 56
20142015, games = 1230, hat tricks = 49
20152016, games = 1230, hat tricks = 67
20162017, games = 1230, hat tricks = 59
20172018, games = 1271, hat tricks = 81
20182019, games = 1271, hat tricks = 97
20192020, games = 1082, hat tricks = 67
20202021, games = 868, hat tricks = 60
20212022, games = 1312, hat tricks = 102
20222023, games = 1312, hat tricks = 96
Phew, finally done. Let’s answer our question about which season has the highest rate of hat tricks.
= pd.DataFrame(hat_tricks_by_season, columns=['season_long', 'games', 'hat_tricks']) # convert list of lists to dataframe
hat_tricks_by_season_df
'hat_tricks_per_game'] = round(hat_tricks_by_season_df['hat_tricks'] / hat_tricks_by_season_df['games'], 3) # calculate number of hat tricks per game
hat_tricks_by_season_df[
'temp'] = hat_tricks_by_season_df['season_long'].astype(str)
hat_tricks_by_season_df[
'temp1'] = hat_tricks_by_season_df['temp'].str[:-4]
hat_tricks_by_season_df['temp2'] = hat_tricks_by_season_df['temp'].str[-4:]
hat_tricks_by_season_df[
'season'] = hat_tricks_by_season_df[['temp1', 'temp2']].apply(lambda row: '-'.join(row.values.astype(str)), axis=1)
hat_tricks_by_season_df[
= px.bar(hat_tricks_by_season_df, y='hat_tricks_per_game', x=str('season'),
fig ="plotly_dark",
template='season',
hover_name= "NHL Hat Trick Rate by Season")
title
="hat tricks per game")
fig.update_yaxes(title_text="cyan")
fig.update_traces(marker_color
# reduce margins for better viewing on mobile
=dict(l=20, r=20, b=20))
fig.update_layout(margin
fig.show()
The NHL’s early seasons were the golden age of hat tricks. On average, there was actually more than one hat trick per game during the very first season! The rate of hat tricks declined drastically in the 1920s and 30s.
So what caused this decline? Probably many factors. First, improved goalie equipment coupled with rule changes led to a decline in scoring. According to hockey-reference.com, teams averaged 4.75 goals per game during the 1917-1918 NHL season. Compare that to only 3.18 goals per game in 2022-2023 season.
Second, and perhaps more importantly, team rosters have gotten larger. In the early years of the NHL, only 9 skaters per team were allowed to play during any given game. Todays teams have twice as many skaters per game. More players on the team means less ice time for any given player and fewer opportunities to rack up goals.
A complete analysis of hat trick rates would require consideration of these factors and more.
One last question. How many times has more than one hat trick been scored in an NHL game?
= pd.DataFrame(every_hat_trick, columns=['season_id', "game_id", 'team', 'player_id', 'player', 'goals']) # convert list of lists to dataframe
every_hat_trick_df
print("there have been " + str(len(every_hat_trick_df.index)) + " hat tricks in NHL history")
print("")
= dict(collections.Counter(every_hat_trick_df['game_id'])) # count hat tricks for each game
games_hts = dict((k, v) for k, v in games_hts.items() if v >= 2) # get only games with multiple hat tricks
multi_ht_games
print("there have been " + str(len(multi_ht_games)) + " games with multiple hat tricks")
print("")
= dict((k, v) for k, v in games_hts.items() if v >= 3) # get only games with more than 3 hat tricks
three_ht_games
print("there have been " + str(len(three_ht_games)) + " games with more than 3 hat tricks")
print("")
# find the game with the most hat tricks
= max(games_hts.values())
max_hts = sum(value == max_hts for value in games_hts.values()) # how many games have the max number of hat tricks?
n_games_max
= dict((k, v) for k, v in games_hts.items() if v >= max_hts) # get only games with multiple hat tricks
max_ht_games
print("the maximum number of hat tricks scored in an NHL game is " + str(max_hts))
print("there have been " + str(n_games_max) + " games with " + str(max_hts) + " hat tricks")
= 1
c for i in max_ht_games:
print("game " + str(c) + ":")
= every_hat_trick_df.loc[every_hat_trick_df['game_id'] == i]
max_hts_df print(max_hts_df)
+= 1 c
there have been 5086 hat tricks in NHL history
there have been 239 games with multiple hat tricks
there have been 18 games with more than 3 hat tricks
the maximum number of hat tricks scored in an NHL game is 4
there have been 3 games with 4 hat tricks
game 1:
season_id game_id team player_id player goals
4 19171918 1917020003 TAN 8447832 H. Meeking 3
5 19171918 1917020003 TAN 8445873 C. Denneny 3
6 19171918 1917020003 TAN 8448013 R. Noble 3
7 19171918 1917020003 SEN 8445874 C. Denneny 3
game 2:
season_id game_id team player_id player goals
59 19191920 1919020011 MTL 8448154 D. Pitre 3
60 19191920 1919020011 MTL 8447289 N. Lalonde 6
61 19191920 1919020011 MTL 8445496 O. Cleghorn 3
62 19191920 1919020011 TSP 8448013 R. Noble 3
game 3:
season_id game_id team player_id player goals
76 19191920 1919020042 MTL 8448154 D. Pitre 3
77 19191920 1919020042 MTL 8445496 O. Cleghorn 3
78 19191920 1919020042 MTL 8447289 N. Lalonde 4
79 19191920 1919020042 MTL 8445314 H. Cameron 4
Wow, three games with four hat tricks each!
Let’s look at the first game with four hat tricks. We can look at the box score for this game where the Toronto Arenas (TAN) blew out the Ottawa Senators (SEN) by a score of 11-4. However, one Ottawa player still managed to score a hat trick.
Ottawa’s hat trick was scored by a player named “C. Denneny.” Weirdly, there was also a Toronto hat trick scorer named “C. Denneny.” As it turns out, Corb Denneny (Toronto) and Cy Denneny (Ottawa) were brothers! Both are now members of the Hockey Hall of Fame.
I wonder how many times brothers or relatives have scored hat tricks in the same game.
print("the following games have multiple hat tricks scored by players with the same last name:")
print("")
for i in multi_ht_games:
= every_hat_trick_df[every_hat_trick_df['game_id'] == i] # get data for multi hat trick games
temp = temp['player'].str[3:].tolist() # convert player column to list
temp2 = set(temp2) # create set
temp2_set if len(temp2) != len(temp2_set): # if lengths are not equal, list contains duplicate values
= every_hat_trick_df.loc[every_hat_trick_df['game_id'] == i]
brother_hts print(brother_hts)
the following games have multiple hat tricks scored by players with the same last name:
game 1:
season_id game_id team player_id player goals
4 19171918 1917020003 TAN 8447832 H. Meeking 3
5 19171918 1917020003 TAN 8445873 C. Denneny 3
6 19171918 1917020003 TAN 8448013 R. Noble 3
7 19171918 1917020003 SEN 8445874 C. Denneny 3
game 2:
season_id game_id team player_id player goals
17 19171918 1917020013 SEN 8445844 J. Darragh 3
18 19171918 1917020013 SEN 8445874 C. Denneny 3
19 19171918 1917020013 TAN 8445873 C. Denneny 3
game 3:
season_id game_id team player_id player goals
112 19211922 1921020018 MTL 8445497 S. Cleghorn 4
113 19211922 1921020018 MTL 8445496 O. Cleghorn 4
game 4:
season_id game_id team player_id player goals
127 19211922 1921020041 TSP 8445873 C. Denneny 3
128 19211922 1921020041 SEN 8445874 C. Denneny 4
game 5:
season_id game_id team player_id player goals
557 19461947 1946020149 CHI 8445062 D. Bentley 4
558 19461947 1946020149 NYR 8449363 G. Warwick 3
559 19461947 1946020149 CHI 8445063 M. Bentley 3
game 6:
season_id game_id team player_id player goals
1757 19801981 1980020621 QUE 8451689 P. Stastny 3
1758 19801981 1980020621 QUE 8451688 A. Stastny 3
game 7:
season_id game_id team player_id player goals
1761 19801981 1980020636 QUE 8451689 P. Stastny 4
1762 19801981 1980020636 QUE 8451688 A. Stastny 3
1763 19801981 1980020636 QUE 8450815 J. Richard 3
There have been seven NHL games where two players with the same last name both scored hat tricks! Turns out our brothers from the previous code block, Corb Denneny and Cy Denneny, have actually scored hat tricks together three separate times.
Odie and Sprague Cleghorn are another pair of brothers who scored a hat trick in the same game, but this time they both played for the same team. In 1946, brother Dough and Max Bentley achieved a similar feat while both playing for the Chicago Blackhawks.
And lastly, brothers Peter and Anton Šťastný of the Quebec Nordiques scored hat tricks together twice in the 1980-1981 season. This was their first year in the NHL, having just defected to Canada from communist Czechoslovakia.
This is one of my favorite things about digging through sports databases. You can find all manner of strange statistical anomalies and reveal fascinating human stories that might have otherwise been lost to time.