├── .gitignore
├── Custom League
├── README.md
├── UpdateAHL.py
├── UpdateCFB.py
├── UpdateKHL.py
├── custom_league_game_probability_model.py
└── game_data.csv
├── LICENSE
├── NBA
├── Output CSV Data
│ ├── 2023-02-15_games.csv
│ ├── best_performances.csv
│ ├── biggest_upsets.csv
│ ├── miami_heat_game_log.csv
│ ├── most_consistent_teams.csv
│ ├── power_rankings.csv
│ ├── toronto_raptors_game_log.csv
│ └── toronto_raptors_vs_philadelphia_76ers_game_probabilities.csv
└── nba_game_probability_model.py
├── NHL
├── Output CSV Data
│ ├── 2023-02-11_games.csv
│ ├── best_performances.csv
│ ├── biggest_upsets.csv
│ ├── colorado_avalanche_game_log.csv
│ ├── colorado_avalanche_vs_boston_bruins_game_probabilities.csv
│ ├── most_consistent_teams.csv
│ └── power_rankings.csv
└── nhl_game_probability_model.py
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/Custom League/README.md:
--------------------------------------------------------------------------------
1 | ## Custom Leagues
2 | With the Custom Leagues option, you can get game probabilities from any custom league. In order to do this, you must have a file named 'game_data.csv' in the same directory as the Python file, that contains data about all of the games. An example of the formatting has been provided in this folder. The Python script will read the CSV and compute the probabilities.
3 |
4 | For the input data, only the home and visitor team names and scores are needed for each game. More columns may be included in the input CSV, but they will not be read by the program. The 4 required column headings must be spelled correctly.
5 |
--------------------------------------------------------------------------------
/Custom League/UpdateAHL.py:
--------------------------------------------------------------------------------
1 | from selenium import webdriver
2 | from selenium.webdriver.chrome.service import Service
3 | from selenium.webdriver.chrome.options import Options
4 | from selenium.webdriver.common.by import By
5 | from selenium.webdriver.support.ui import WebDriverWait
6 | from selenium.webdriver.support import expected_conditions as EC
7 | from selenium.common.exceptions import NoSuchElementException, ElementClickInterceptedException, TimeoutException
8 | from webdriver_manager.chrome import ChromeDriverManager
9 | import time
10 | import csv
11 |
12 | # Browser options
13 | options = Options()
14 | options.add_argument("--headless")
15 | options.add_argument("--no-sandbox")
16 | options.add_argument("--disable-dev-shm-usage")
17 | options.add_argument("--disable-gpu")
18 | options.add_argument("--disable-software-rasterizer")
19 | options.add_argument("--window-size=1920,1080")
20 |
21 | # Set up ChromeDriver
22 | service = Service(ChromeDriverManager().install())
23 | driver = webdriver.Chrome(service=service, options=options)
24 |
25 | # URL for AHL results in English
26 | url = 'https://www.flashscore.ca/hockey/usa/ahl/results/'
27 | driver.get(url)
28 |
29 | # Close the cookie banner if it appears
30 | try:
31 | cookie_banner = WebDriverWait(driver, 10).until(
32 | EC.element_to_be_clickable((By.CSS_SELECTOR, "#onetrust-accept-btn-handler"))
33 | )
34 | cookie_banner.click()
35 | print("Cookie banner closed.")
36 | except NoSuchElementException:
37 | print("Cookie banner not found, continuing.")
38 |
39 | # Click the "Show more games" button until it's unavailable
40 | while True:
41 | try:
42 | load_more_button = WebDriverWait(driver, 5).until(
43 | EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), 'Show more games')]"))
44 | )
45 | load_more_button.click()
46 | time.sleep(2) # Wait after clicking
47 | except (NoSuchElementException, TimeoutException):
48 | print("All matches loaded or button is unavailable.")
49 | break
50 | except ElementClickInterceptedException:
51 | print("Element not clickable, retrying.")
52 | time.sleep(2)
53 |
54 | # Parse match data
55 | matches = driver.find_elements(By.CSS_SELECTOR, "div.event__match")
56 |
57 | # Collect match data
58 | match_data = []
59 |
60 | for match in matches:
61 | # Match time
62 | match_time = match.find_element(By.CSS_SELECTOR, "div.event__time").text.strip()
63 |
64 | # Team names
65 | home_team = match.find_element(By.CSS_SELECTOR, "div.event__participant--home").text.strip()
66 | away_team = match.find_element(By.CSS_SELECTOR, "div.event__participant--away").text.strip()
67 |
68 | # Scores
69 | scores = match.find_elements(By.CSS_SELECTOR, "div.event__score")
70 | if len(scores) >= 2:
71 | home_score = scores[0].text.strip()
72 | away_score = scores[1].text.strip()
73 | else:
74 | home_score = "N/A"
75 | away_score = "N/A"
76 |
77 | # Clean team names by removing "@" if present
78 | home_team_cleaned = home_team.replace('@', '').strip()
79 | away_team_cleaned = away_team.replace('@', '').strip()
80 |
81 | match_info = {
82 | 'Home Team': home_team_cleaned,
83 | 'Home Score': home_score,
84 | 'Visitor Score': away_score,
85 | 'Visiting Team': away_team_cleaned
86 | }
87 | match_data.append(match_info)
88 |
89 | # Save the collected match data to a CSV file
90 | csv_file_path = 'Custom League/game_data.csv' # Updated file name for clarity
91 | with open(csv_file_path, mode='w', newline='', encoding='utf-8') as csv_file:
92 | fieldnames = ['Home Team', 'Home Score', 'Visitor Score', 'Visiting Team']
93 | writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
94 |
95 | writer.writeheader() # Write the header
96 | for data in match_data:
97 | writer.writerow(data) # Write each row
98 |
99 | print(f"Match data saved to {csv_file_path}")
100 |
101 | # Close the browser
102 | driver.quit()
103 |
--------------------------------------------------------------------------------
/Custom League/UpdateCFB.py:
--------------------------------------------------------------------------------
1 | from selenium import webdriver
2 | from selenium.webdriver.chrome.service import Service
3 | from selenium.webdriver.chrome.options import Options
4 | from selenium.webdriver.common.by import By
5 | from selenium.webdriver.support.ui import WebDriverWait
6 | from selenium.webdriver.support import expected_conditions as EC
7 | from selenium.common.exceptions import NoSuchElementException, ElementClickInterceptedException, TimeoutException
8 | from webdriver_manager.chrome import ChromeDriverManager
9 | import time
10 | import csv
11 |
12 | # Browser options
13 | options = Options()
14 | options.add_argument("--headless")
15 | options.add_argument("--no-sandbox")
16 | options.add_argument("--disable-dev-shm-usage")
17 | options.add_argument("--disable-gpu")
18 | options.add_argument("--disable-software-rasterizer")
19 | options.add_argument("--window-size=1920,1080")
20 |
21 | # Set up ChromeDriver
22 | service = Service(ChromeDriverManager().install())
23 | driver = webdriver.Chrome(service=service, options=options)
24 |
25 | # URL for NCAA results in English
26 | url = 'https://www.flashscore.ca/football/usa/ncaa/results/'
27 | driver.get(url)
28 |
29 | # Close the cookie banner if it appears
30 | try:
31 | cookie_banner = WebDriverWait(driver, 10).until(
32 | EC.element_to_be_clickable((By.CSS_SELECTOR, "#onetrust-accept-btn-handler"))
33 | )
34 | cookie_banner.click()
35 | print("Cookie banner closed.")
36 | except NoSuchElementException:
37 | print("Cookie banner not found, continuing.")
38 |
39 | # Click the "Show more games" button until it's unavailable
40 | while True:
41 | try:
42 | load_more_button = WebDriverWait(driver, 5).until(
43 | EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), 'Show more games')]"))
44 | )
45 | load_more_button.click()
46 | time.sleep(2) # Wait after clicking
47 | except (NoSuchElementException, TimeoutException):
48 | print("All matches loaded or button is unavailable.")
49 | break
50 | except ElementClickInterceptedException:
51 | print("Element not clickable, retrying.")
52 | time.sleep(2)
53 |
54 | # Parse match data
55 | matches = driver.find_elements(By.CSS_SELECTOR, "div.event__match")
56 |
57 | # Collect match data
58 | match_data = []
59 |
60 | for match in matches:
61 | # Match time
62 | match_time = match.find_element(By.CSS_SELECTOR, "div.event__time").text.strip()
63 |
64 | # Team names
65 | home_team = match.find_element(By.CSS_SELECTOR, "div.event__participant--home").text.strip()
66 | away_team = match.find_element(By.CSS_SELECTOR, "div.event__participant--away").text.strip()
67 |
68 | # Scores
69 | scores = match.find_elements(By.CSS_SELECTOR, "div.event__score")
70 | if len(scores) >= 2:
71 | home_score = scores[0].text.strip()
72 | away_score = scores[1].text.strip()
73 | else:
74 | home_score = "N/A"
75 | away_score = "N/A"
76 |
77 | # Clean team names by removing "@" if present
78 | home_team_cleaned = home_team.replace('@', '').strip()
79 | away_team_cleaned = away_team.replace('@', '').strip()
80 |
81 | match_info = {
82 | 'Home Team': home_team_cleaned,
83 | 'Home Score': home_score,
84 | 'Visitor Score': away_score,
85 | 'Visiting Team': away_team_cleaned
86 | }
87 | match_data.append(match_info)
88 |
89 | # Save the collected match data to a CSV file
90 | csv_file_path = 'Custom League/game_data.csv' # Updated file name for clarity
91 | with open(csv_file_path, mode='w', newline='', encoding='utf-8') as csv_file:
92 | fieldnames = ['Home Team', 'Home Score', 'Visitor Score', 'Visiting Team']
93 | writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
94 |
95 | writer.writeheader() # Write the header
96 | for data in match_data:
97 | writer.writerow(data) # Write each row
98 |
99 | print(f"Match data saved to {csv_file_path}")
100 |
101 | # Close the browser
102 | driver.quit()
103 |
--------------------------------------------------------------------------------
/Custom League/UpdateKHL.py:
--------------------------------------------------------------------------------
1 | from selenium import webdriver
2 | from selenium.webdriver.chrome.service import Service
3 | from selenium.webdriver.chrome.options import Options
4 | from selenium.webdriver.common.by import By
5 | from selenium.webdriver.support.ui import WebDriverWait
6 | from selenium.webdriver.support import expected_conditions as EC
7 | from selenium.common.exceptions import NoSuchElementException, ElementClickInterceptedException, TimeoutException
8 | from webdriver_manager.chrome import ChromeDriverManager
9 | import time
10 | import csv
11 |
12 | # Browser options
13 | options = Options()
14 | options.add_argument("--headless")
15 | options.add_argument("--no-sandbox")
16 | options.add_argument("--disable-dev-shm-usage")
17 | options.add_argument("--disable-gpu")
18 | options.add_argument("--disable-software-rasterizer")
19 | options.add_argument("--window-size=1920,1080")
20 |
21 | # Set up ChromeDriver
22 | service = Service(ChromeDriverManager().install())
23 | driver = webdriver.Chrome(service=service, options=options)
24 |
25 | # URL for KHL results in English
26 | url = 'https://www.flashscore.ca/hockey/russia/khl/results/'
27 | driver.get(url)
28 |
29 | # Close the cookie banner if it appears
30 | try:
31 | cookie_banner = WebDriverWait(driver, 10).until(
32 | EC.element_to_be_clickable((By.CSS_SELECTOR, "#onetrust-accept-btn-handler"))
33 | )
34 | cookie_banner.click()
35 | print("Cookie banner closed.")
36 | except NoSuchElementException:
37 | print("Cookie banner not found, continuing.")
38 |
39 | # Click the "Show more games" button until it's unavailable
40 | while True:
41 | try:
42 | load_more_button = WebDriverWait(driver, 5).until(
43 | EC.element_to_be_clickable((By.XPATH, "//a[contains(text(), 'Show more games')]"))
44 | )
45 | load_more_button.click()
46 | time.sleep(2) # Wait after clicking
47 | except (NoSuchElementException, TimeoutException):
48 | print("All matches loaded or button is unavailable.")
49 | break
50 | except ElementClickInterceptedException:
51 | print("Element not clickable, retrying.")
52 | time.sleep(2)
53 |
54 | # Parse match data
55 | matches = driver.find_elements(By.CSS_SELECTOR, "div.event__match")
56 |
57 | # Collect match data
58 | match_data = []
59 |
60 | for match in matches:
61 | # Match time
62 | match_time = match.find_element(By.CSS_SELECTOR, "div.event__time").text.strip()
63 |
64 | # Team names
65 | home_team = match.find_element(By.CSS_SELECTOR, "div.event__participant--home").text.strip()
66 | away_team = match.find_element(By.CSS_SELECTOR, "div.event__participant--away").text.strip()
67 |
68 | # Scores
69 | scores = match.find_elements(By.CSS_SELECTOR, "div.event__score")
70 | if len(scores) >= 2:
71 | home_score = scores[0].text.strip()
72 | away_score = scores[1].text.strip()
73 | else:
74 | home_score = "N/A"
75 | away_score = "N/A"
76 |
77 | match_info = {
78 | 'Home Team': home_team,
79 | 'Home Score': home_score,
80 | 'Visitor Score': away_score,
81 | 'Visiting Team': away_team
82 | }
83 | match_data.append(match_info)
84 |
85 | # Save the collected match data to a CSV file
86 | csv_file_path = 'Custom League/game_data.csv'
87 | with open(csv_file_path, mode='w', newline='', encoding='utf-8') as csv_file:
88 | fieldnames = ['Home Team', 'Home Score', 'Visitor Score', 'Visiting Team']
89 | writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
90 |
91 | writer.writeheader() # Write the header
92 | for data in match_data:
93 | writer.writerow(data) # Write each row
94 |
95 | print(f"Match data saved to {csv_file_path}")
96 |
97 | # Close the browser
98 | driver.quit()
99 |
--------------------------------------------------------------------------------
/Custom League/custom_league_game_probability_model.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import numpy as np
3 | import matplotlib.pyplot as plt
4 | from scipy.stats import pearsonr
5 | from scipy.optimize import curve_fit
6 | from sklearn.metrics import log_loss
7 | import os
8 | import time
9 |
10 | class Team():
11 | def __init__(self, name):
12 | self.name = name
13 | self.team_game_list = []
14 | self.apd = 0
15 | self.opponent_power = []
16 | self.schedule = 0
17 | self.power = 0
18 | self.prev_power = 0
19 | self.points_for = 0
20 | self.points_against = 0
21 | self.wins = 0
22 | self.losses = 0
23 | self.ties = 0
24 | self.pct = 0
25 |
26 | def read_games(self):
27 | print(f'--{self.name.upper()} GAME LIST--')
28 | for game in self.team_game_list:
29 | print(f'{game.home_team.name} {game.home_score}-{game.away_score} {game.away_team.name}')
30 |
31 | def calc_apd(self):
32 | point_differential = 0
33 | for game in self.team_game_list:
34 | if self == game.home_team:
35 | point_differential += game.home_score - game.away_score
36 | else:
37 | point_differential += game.away_score - game.home_score
38 | apd = point_differential / len(self.team_game_list)
39 |
40 | return apd
41 |
42 | def calc_sched(self):
43 | self.opponent_power = []
44 | for game in self.team_game_list:
45 | if self == game.home_team:
46 | self.opponent_power.append(game.away_team.prev_power)
47 | else:
48 | self.opponent_power.append(game.home_team.prev_power)
49 |
50 | return sum(self.opponent_power) / len(self.opponent_power)
51 |
52 | def calc_power(self):
53 | return self.calc_sched() + self.apd
54 |
55 | def calc_win_pct(self):
56 | win_percentage = self.wins/(self.wins + self.losses)
57 | return win_percentage
58 |
59 | def calc_consistency(self):
60 | performance_list = []
61 | for game in self.team_game_list:
62 | if self == game.home_team:
63 | performance_list.append(game.away_team.power + game.home_score - game.away_score)
64 | else:
65 | performance_list.append(game.away_team.power + game.home_score - game.away_score)
66 |
67 | variance = np.var(performance_list)
68 | return variance
69 |
70 | class Game():
71 | def __init__(self, home_team, away_team, home_score, away_score):
72 | self.home_team = home_team
73 | self.away_team = away_team
74 | self.home_score = home_score
75 | self.away_score = away_score
76 |
77 | def retrieve_league_data():
78 | input_df = pd.read_csv(f'{os.path.dirname(__file__)}/game_data.csv')
79 | input_df.index += 1
80 | return input_df
81 |
82 | def game_team_object_creation(games_metadf):
83 | total_game_list = []
84 | team_list = []
85 |
86 | for index, row in games_metadf.iterrows():
87 | try:
88 | row['Home Score'] = float(row['Home Score'])
89 | row['Visitor Score'] = float(row['Visitor Score'])
90 |
91 | team_in_list = False
92 | for team in team_list:
93 | if team.name == row['Home Team']:
94 | team_in_list = True
95 | home_team_obj = team
96 | if team_in_list == False:
97 | home_team_obj = Team(row['Home Team'])
98 | team_list.append(home_team_obj)
99 |
100 | team_in_list = False
101 | for team in team_list:
102 | if team.name == row['Visiting Team']:
103 | team_in_list = True
104 | away_team_obj = team
105 | if team_in_list == False:
106 | away_team_obj = Team(row['Visiting Team'])
107 | team_list.append(away_team_obj)
108 |
109 | game_obj = Game(home_team_obj, away_team_obj, row['Home Score'], row['Visitor Score'])
110 |
111 | home_team_obj.team_game_list.append(game_obj)
112 | away_team_obj.team_game_list.append(game_obj)
113 | home_team_obj.points_for += game_obj.home_score
114 | away_team_obj.points_against += game_obj.home_score
115 | home_team_obj.points_against += game_obj.away_score
116 | away_team_obj.points_for += game_obj.away_score
117 |
118 | if game_obj.home_score > game_obj.away_score:
119 | home_team_obj.wins += 1
120 | away_team_obj.losses += 1
121 | elif game_obj.home_score < game_obj.away_score:
122 | home_team_obj.losses += 1
123 | away_team_obj.wins += 1
124 | else:
125 | home_team_obj.ties += 1
126 | away_team_obj.ties += 1
127 |
128 | total_game_list.append(game_obj)
129 | except ValueError:
130 | pass
131 |
132 | return team_list, total_game_list
133 |
134 | def assign_power(team_list, iterations):
135 | for team in team_list:
136 | team.apd = team.calc_apd()
137 | team.pct = team.calc_win_pct()
138 |
139 | for iteration in range(iterations):
140 | # print(f'ITERATION {iteration+1}')
141 | for team in team_list:
142 | team.schedule = team.calc_sched()
143 | team.power = team.calc_power()
144 | # print(f'{team.name}\t\tAPD: {team.calc_apd():.2f}\tSCHEDULE: {team.schedule:.2f}\t\tPOWER: {team.power:.2f}')
145 | for team in team_list:
146 | team.prev_power = team.power
147 |
148 | def prepare_power_rankings(team_list):
149 | power_df = pd.DataFrame()
150 | for team in team_list:
151 | power_df = pd.concat([power_df, pd.DataFrame([{
152 | 'Team': team.name,
153 | 'POWER': round(team.power, 2),
154 | 'Record': f'{team.wins}-{team.losses}-{team.ties}',
155 | 'Win%': f"{team.calc_win_pct():.3f}",
156 | 'Avg PTS Diff': round(team.calc_apd(), 2),
157 | 'Avg PTS For': f"{team.points_for/len(team.team_game_list):.2f}",
158 | 'Avg PTS Against': f"{team.points_against/len(team.team_game_list):.2f}",
159 | 'Strength of Schedule': f"{team.schedule:.3f}"
160 | }])], ignore_index=True)
161 | power_df.sort_values(by=['POWER'], inplace=True, ascending=False)
162 | power_df = power_df.reset_index(drop=True)
163 | power_df.index += 1
164 |
165 | return power_df
166 |
167 | def logistic_regression(total_game_list):
168 | xpoints = [] # Rating differential (Home - Away)
169 | ypoints = [] # Home Win/Loss Boolean (Win = 1, Tie = 0.5, Loss = 0)
170 |
171 | for game in total_game_list:
172 | xpoints.append(game.home_team.power - game.away_team.power)
173 |
174 | if game.home_score > game.away_score:
175 | ypoints.append(1)
176 | elif game.home_score < game.away_score:
177 | ypoints.append(0)
178 | else:
179 | ypoints.append(0.5)
180 |
181 | parameters, covariates = curve_fit(lambda t, param: 1/(1+np.exp((t)/param)), [-x for x in xpoints], ypoints) # Regression only works if parameter is positive.
182 | param = -parameters[0]
183 |
184 | return xpoints, ypoints, param
185 |
186 | def model_performance(xpoints, ypoints, param):
187 | x_fitted = np.linspace(np.min(xpoints)*1.25, np.max(xpoints)*1.25, 100)
188 | y_fitted = 1/(1+np.exp((x_fitted)/param))
189 |
190 | ypoints_logloss_adj = []
191 | xpoints_logloss_adj = []
192 | for yindex in range(len(ypoints)):
193 | if ypoints[yindex] != 0.5:
194 | ypoints_logloss_adj.append(ypoints[yindex])
195 | xpoints_logloss_adj.append(xpoints[yindex])
196 |
197 | r, p = pearsonr(xpoints, ypoints)
198 | print(f'Pearson Correlation of Independent and Dependent Variables: {r:.3f}')
199 | print(f'Log Loss of the Cumulative Distribution Function (CDF): {log_loss(ypoints_logloss_adj, 1/(1+np.exp((xpoints_logloss_adj)/param))):.3f}')
200 | print(f'Regressed Sigmoid: 1/(1+exp((x)/{param:.3f}))')
201 | print(f'Precise Parameter: {param}')
202 |
203 | plt.plot(xpoints, ypoints, 'o', color='grey')
204 | plt.plot(x_fitted, y_fitted, color='black', alpha=1, label=f'CDF (Log Loss = {log_loss(ypoints_logloss_adj, 1/(1+np.exp((xpoints_logloss_adj)/param))):.3f})')
205 | plt.legend()
206 | plt.title('Logistic Regression of Team Rating Difference vs Game Result')
207 | plt.xlabel('Rating Difference')
208 | plt.ylabel('Win Probability')
209 | plt.show()
210 |
211 | def calc_prob(team, opponent, param):
212 | return 1/(1+np.exp((team.power-opponent.power)/param))
213 |
214 | def calc_spread(team, opponent, param, lower_bound_spread, upper_bound_spread):
215 | if lower_bound_spread == '-inf':
216 | if upper_bound_spread == 'inf':
217 | return 1
218 | return 1/(1+np.exp((upper_bound_spread-(team.power-opponent.power))/param))
219 | elif upper_bound_spread == 'inf':
220 | return 1 - 1/(1+np.exp((lower_bound_spread-(team.power-opponent.power))/param))
221 | else:
222 | return 1/(1+np.exp((upper_bound_spread-(team.power-opponent.power))/param)) - 1/(1+np.exp((lower_bound_spread-(team.power-opponent.power))/param))
223 |
224 | def download_csv_option(df, filename):
225 | valid = False
226 | while valid == False:
227 | user_input = input('Would you like to download this as a CSV? (Y/N): ')
228 | if user_input.lower() in ['y', 'yes', 'y.', 'yes.']:
229 | valid = True
230 | elif user_input.lower() in ['n', 'no', 'n.', 'no.']:
231 | return
232 | else:
233 | print(f'Sorry, I could not understand "{user_input}". Please enter Y or N: ')
234 |
235 | if not os.path.exists(f'{os.path.dirname(__file__)}/Output CSV Data'):
236 | os.makedirs(f'{os.path.dirname(__file__)}/Output CSV Data')
237 | df.to_csv(f'{os.path.dirname(__file__)}/Output CSV Data/{filename}.csv')
238 | print(f'{filename}.csv has been downloaded to the following directory: {os.path.dirname(__file__)}/Output CSV Data')
239 | return
240 |
241 | def custom_game_selector(param, team_list):
242 | valid = False
243 | while valid == False:
244 | home_team_input = input('Enter the home team: ')
245 | for team in team_list:
246 | if home_team_input.strip().lower() == team.name.lower().replace('é','e'):
247 | home_team = team
248 | valid = True
249 | if valid == False:
250 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
251 |
252 | valid = False
253 | while valid == False:
254 | away_team_input = input('Enter the away team: ')
255 | for team in team_list:
256 | if away_team_input.strip().lower() == team.name.lower().replace('é','e'):
257 | away_team = team
258 | valid = True
259 | if valid == False:
260 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
261 |
262 | game_probability_df = pd.DataFrame(columns = ['', home_team.name, away_team.name])
263 |
264 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Rating', home_team.name:f'{home_team.power:.3f}', away_team.name:f'{away_team.power:.3f}'}])], ignore_index = True)
265 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Record', home_team.name:f'{home_team.wins}-{home_team.losses}-{home_team.ties}', away_team.name:f'{away_team.wins}-{away_team.losses}-{away_team.ties}'}])], ignore_index = True)
266 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Point PCT', home_team.name:f'{home_team.pct:.3f}', away_team.name:f'{away_team.pct:.3f}'}])], ignore_index = True)
267 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Win Probability', home_team.name:f'{calc_prob(home_team, away_team, param)*100:.2f}%', away_team.name:f'{(calc_prob(away_team, home_team, param))*100:.2f}%'}])], ignore_index = True)
268 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Win by 1-5 Points', home_team.name:f'{calc_spread(home_team, away_team, param, 0, 5.5)*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 0, 5.5)*100:.2f}%'}])], ignore_index = True)
269 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Win by 6-10 Points', home_team.name:f'{calc_spread(home_team, away_team, param, 5.5, 10.5)*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 5.5, 10.5)*100:.2f}%'}])], ignore_index = True)
270 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Win by 11-15 Points', home_team.name:f'{calc_spread(home_team, away_team, param, 10.5, 15.5)*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 10.5, 15.5)*100:.2f}%'}])], ignore_index = True)
271 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Win by 16-20 Points', home_team.name:f'{calc_spread(home_team, away_team, param, 15.5, 20.5)*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 15.5, 20.5)*100:.2f}%'}])], ignore_index = True)
272 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame([{'':'Win by 21+ Points', home_team.name:f'{calc_spread(home_team, away_team, param, 20.5, "inf")*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 20.5, "inf")*100:.2f}%'}])], ignore_index = True)
273 | game_probability_df = game_probability_df.set_index('')
274 |
275 | return home_team, away_team, game_probability_df
276 |
277 | def get_upsets(total_game_list):
278 | upset_df = pd.DataFrame(columns = ['Home Team', 'Home points', 'Away points', 'Away Team', 'xGD', 'GD', 'Upset Rating'])
279 |
280 | for game in total_game_list:
281 | expected_score_diff = game.home_team.power - game.away_team.power #home - away
282 | actaul_score_diff = game.home_score - game.away_score
283 | upset_rating = actaul_score_diff - expected_score_diff #Positive score is an upset by the home team. Negative scores are upsets by the visiting team.
284 |
285 | upset_df = pd.concat([upset_df, pd.DataFrame([{'Home Team':game.home_team.name, 'Home points':int(game.home_score), 'Away points':int(game.away_score), 'Away Team':game.away_team.name,'xGD':f'{expected_score_diff:.2f}', 'GD':int(actaul_score_diff), 'Upset Rating':f'{abs(upset_rating):.2f}'}])], ignore_index = True)
286 |
287 | upset_df = upset_df.sort_values(by=['Upset Rating'], ascending=False)
288 | upset_df = upset_df.reset_index(drop=True)
289 | upset_df.index += 1
290 | return upset_df
291 |
292 | def get_best_performances(total_game_list):
293 | performance_df = pd.DataFrame(columns = ['Team', 'Opponent', 'GF', 'GA', 'xGD', 'Performance'])
294 |
295 | for game in total_game_list:
296 | performance_df = pd.concat([performance_df, pd.DataFrame([{'Team':game.home_team.name, 'Opponent':game.away_team.name, 'GF':int(game.home_score), 'GA':int(game.away_score), 'xGD':f'{game.home_team.power-game.away_team.power:.2f}', 'Performance':round(game.away_team.power+game.home_score-game.away_score,2)}])], ignore_index = True)
297 | performance_df = pd.concat([performance_df, pd.DataFrame([{'Team':game.away_team.name, 'Opponent':game.home_team.name, 'GF':int(game.away_score), 'GA':int(game.home_score), 'xGD':f'{game.away_team.power-game.home_team.power:.2f}', 'Performance':round(game.home_team.power+game.away_score-game.home_score,2)}])], ignore_index = True)
298 |
299 | performance_df = performance_df.sort_values(by=['Performance'], ascending=False)
300 | performance_df = performance_df.reset_index(drop=True)
301 | performance_df.index += 1
302 | return performance_df
303 |
304 | def get_team_consistency(team_list):
305 | consistency_df = pd.DataFrame(columns = ['Team', 'Rating', 'Consistency (z-Score)'])
306 |
307 | for team in team_list:
308 | consistency_df = pd.concat([consistency_df, pd.DataFrame([{'Team':team.name, 'Rating':f'{team.power:.2f}', 'Consistency (z-Score)':team.calc_consistency()}])], ignore_index = True)
309 |
310 | consistency_df['Consistency (z-Score)'] = consistency_df['Consistency (z-Score)'].apply(lambda x: (x-consistency_df['Consistency (z-Score)'].mean())/-consistency_df['Consistency (z-Score)'].std())
311 |
312 | consistency_df = consistency_df.sort_values(by=['Consistency (z-Score)'], ascending=False)
313 | consistency_df = consistency_df.reset_index(drop=True)
314 | consistency_df.index += 1
315 | consistency_df['Consistency (z-Score)'] = consistency_df['Consistency (z-Score)'].apply(lambda x: f'{x:.2f}')
316 | return consistency_df
317 |
318 | def team_game_log(team_list):
319 | valid = False
320 | while valid == False:
321 | input_team = input('Enter a team: ')
322 | for team_obj in team_list:
323 | if input_team.strip().lower() == team_obj.name.lower().replace('é','e'):
324 | team = team_obj
325 | valid = True
326 | if valid == False:
327 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
328 |
329 | game_log_df = pd.DataFrame(columns = ['Opponent', 'GF', 'GA', 'Performance'])
330 | for game in team.team_game_list:
331 | if team == game.home_team:
332 | points_for = game.home_score
333 | opponent = game.away_team
334 | points_against = game.away_score
335 | else:
336 | points_for = game.away_score
337 | opponent = game.home_team
338 | points_against = game.home_score
339 | game_log_df = pd.concat([game_log_df, pd.DataFrame([{'Opponent':opponent.name, 'GF':int(points_for), 'GA':int(points_against), 'Performance':round(opponent.power + points_for - points_against,2)}])], ignore_index = True)
340 |
341 | game_log_df.index += 1
342 | return team, game_log_df
343 |
344 | def get_team_prob_breakdown(team_list, param):
345 | valid = False
346 | while valid == False:
347 | input_team = input('Enter a team: ')
348 | for team_obj in team_list:
349 | if input_team.strip().lower() == team_obj.name.lower().replace('é','e'):
350 | team = team_obj
351 | valid = True
352 | if valid == False:
353 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
354 |
355 | prob_breakdown_df = pd.DataFrame()
356 | for opp_team in team_list:
357 | if opp_team is not team:
358 | prob_breakdown_df = pd.concat([prob_breakdown_df, pd.DataFrame([{'Opponent': opp_team.name,
359 | 'Record': f'{opp_team.wins}-{opp_team.losses}-{opp_team.ties}',
360 | 'Win%': f'{opp_team.calc_win_pct():.3f}',
361 | 'Win Probability':f'{calc_prob(team, opp_team, param)*100:.2f}%',
362 | 'Lose by 21+': f'{calc_spread(team, opp_team, param, "-inf", -20.5)*100:.2f}%',
363 | 'Lose by 16-20': f'{calc_spread(team, opp_team, param, -20.5, -15.5)*100:.2f}%',
364 | 'Lose by 11-15': f'{calc_spread(team, opp_team, param, -15.5, -10.5)*100:.2f}%',
365 | 'Lose by 6-10': f'{calc_spread(team, opp_team, param, -10.5, -5.5)*100:.2f}%',
366 | 'Lose by 1-5': f'{calc_spread(team, opp_team, param, -5.5, 0)*100:.2f}%',
367 | 'Win by 1-5': f'{calc_spread(team, opp_team, param, 0, 5.5)*100:.2f}%',
368 | 'Win by 6-10': f'{calc_spread(team, opp_team, param, 5.5, 10.5)*100:.2f}%',
369 | 'Win by 10-15': f'{calc_spread(team, opp_team, param, 10.5, 15.5)*100:.2f}%',
370 | 'Win by 16-20': f'{calc_spread(team, opp_team, param, 15.5, 20.5)*100:.2f}%',
371 | 'Win by 21+': f'{calc_spread(team, opp_team, param, 20.5, "inf")*100:.2f}%'}])], ignore_index = True)
372 |
373 | prob_breakdown_df = prob_breakdown_df.set_index('Opponent')
374 | prob_breakdown_df = prob_breakdown_df.sort_values(by=['Win%'], ascending=False)
375 | return team, prob_breakdown_df
376 |
377 | def extra_menu(total_game_list, team_list, param):
378 | while True:
379 | print("""--EXTRAS MENU--
380 | 1. Biggest Upsets
381 | 2. Best Performances
382 | 3. Most Consistent Teams
383 | 4. Team Game Logs
384 | 5. Team Probability Big Board
385 | 6. Exit to Main Menu""")
386 |
387 | valid = False
388 | while valid == False:
389 | user_option = input('Enter a menu option: ')
390 | try:
391 | user_option = int(user_option)
392 | if user_option >= 1 and user_option <= 6:
393 | print()
394 | valid = True
395 | else:
396 | raise ValueError
397 | except ValueError:
398 | print(f'Your option "{user_option}" is invalid.', end=' ')
399 |
400 | if user_option == 1:
401 | upsets = get_upsets(total_game_list)
402 | print(upsets)
403 | download_csv_option(upsets, 'biggest_upsets')
404 | elif user_option == 2:
405 | performances = get_best_performances(total_game_list)
406 | print(performances)
407 | download_csv_option(performances, 'best_performances')
408 | elif user_option == 3:
409 | consistency = get_team_consistency(team_list)
410 | print(consistency)
411 | download_csv_option(consistency, 'most_consistent_teams')
412 | elif user_option == 4:
413 | team, game_log = team_game_log(team_list)
414 | print(game_log)
415 | download_csv_option(game_log, f'{team.name.replace(" ", "_").lower()}_game_log')
416 | elif user_option == 5:
417 | team, team_probabilities = get_team_prob_breakdown(team_list, param)
418 | print(team_probabilities)
419 | download_csv_option(team_probabilities, f'{team.name.replace(" ", "_").lower()}_game_log')
420 | elif user_option == 6:
421 | pass
422 |
423 | return
424 |
425 | def menu(power_df, xpoints, ypoints, param, computation_time, total_game_list, team_list):
426 | while True:
427 | print("""--MAIN MENU--
428 | 1. View Power Rankings
429 | 2. Custom Game Selector
430 | 3. View Model Performance
431 | 4. View Program Performance
432 | 5. Extra Options
433 | 6. Quit""")
434 |
435 | valid = False
436 | while valid == False:
437 | user_option = input('Enter a menu option: ')
438 | try:
439 | user_option = int(user_option)
440 | if user_option >= 1 and user_option <= 6:
441 | print()
442 | valid = True
443 | else:
444 | raise ValueError
445 | except ValueError:
446 | print(f'Your option "{user_option}" is invalid.', end=' ')
447 |
448 | if user_option == 1:
449 | print(power_df)
450 | download_csv_option(power_df, 'power_rankings')
451 | elif user_option == 2:
452 | home_team, away_team, custom_game_df = custom_game_selector(param, team_list)
453 | print(custom_game_df)
454 | download_csv_option(custom_game_df, f'{home_team.name.replace(" ", "_").lower()}_vs_{away_team.name.replace(" ", "_").lower()}_game_probabilities')
455 | elif user_option == 3:
456 | model_performance(xpoints, ypoints, param)
457 | elif user_option == 4:
458 | print(f'Computation Time: {computation_time:.2f} seconds')
459 | print(f'Games Scraped: {len(total_game_list)}')
460 | print(f'Rate: {len(total_game_list)/computation_time:.1f} games/second')
461 | elif user_option == 5:
462 | extra_menu(total_game_list, team_list, param)
463 | elif user_option == 6:
464 | return
465 |
466 | input('Press ENTER to continue\t\t')
467 | print()
468 |
469 | def main():
470 | start_time = time.time()
471 |
472 | games_metadf = retrieve_league_data()
473 | iterations = 10
474 | team_list, total_game_list = game_team_object_creation(games_metadf)
475 | assign_power(team_list, iterations)
476 | power_df = prepare_power_rankings(team_list)
477 | xpoints, ypoints, param = logistic_regression(total_game_list)
478 |
479 | computation_time = time.time()-start_time
480 | menu(power_df, xpoints, ypoints, param, computation_time, total_game_list, team_list)
481 |
482 | if __name__ == '__main__':
483 | main()
484 |
--------------------------------------------------------------------------------
/Custom League/game_data.csv:
--------------------------------------------------------------------------------
1 | Home Team,Home Score,Visitor Score,Visiting Team
2 | Team 25,7,2,Team 5
3 | Team 25,8,3,Team 11
4 | Team 2,10,0,Team 16
5 | Team 16,0,6,Team 32
6 | Team 13,7,2,Team 30
7 | Team 31,7,3,Team 5
8 | Team 25,5,3,Team 8
9 | Team 14,0,3,Team 5
10 | Team 6,6,4,Team 15
11 | Team 2,0,1,Team 9
12 | Team 26,3,3,Team 25
13 | Team 20,2,10,Team 11
14 | Team 15,6,1,Team 28
15 | Team 19,0,2,Team 10
16 | Team 9,4,1,Team 1
17 | Team 20,7,8,Team 4
18 | Team 16,4,5,Team 3
19 | Team 20,9,2,Team 21
20 | Team 2,6,5,Team 5
21 | Team 24,10,5,Team 4
22 | Team 24,5,0,Team 26
23 | Team 29,7,9,Team 24
24 | Team 14,2,10,Team 8
25 | Team 13,10,0,Team 31
26 | Team 22,10,5,Team 20
27 | Team 14,5,2,Team 25
28 | Team 1,0,7,Team 19
29 | Team 10,5,10,Team 29
30 | Team 6,4,4,Team 26
31 | Team 22,2,9,Team 24
32 | Team 7,5,10,Team 11
33 | Team 31,4,5,Team 13
34 | Team 32,4,2,Team 5
35 | Team 24,5,8,Team 13
36 | Team 10,3,7,Team 21
37 | Team 22,4,5,Team 28
38 | Team 10,8,0,Team 26
39 | Team 5,5,3,Team 7
40 | Team 28,1,4,Team 30
41 | Team 15,8,7,Team 25
42 | Team 5,2,2,Team 19
43 | Team 10,5,10,Team 31
44 | Team 16,0,8,Team 31
45 | Team 6,2,4,Team 26
46 | Team 21,5,2,Team 5
47 | Team 27,9,2,Team 25
48 | Team 14,5,0,Team 8
49 | Team 22,3,2,Team 25
50 | Team 27,9,2,Team 26
51 | Team 19,7,10,Team 20
52 | Team 24,6,1,Team 9
53 | Team 15,10,3,Team 1
54 | Team 26,5,4,Team 15
55 | Team 9,4,2,Team 23
56 | Team 7,3,2,Team 21
57 | Team 13,3,0,Team 6
58 | Team 25,6,6,Team 24
59 | Team 18,7,1,Team 7
60 | Team 30,9,2,Team 14
61 | Team 6,4,5,Team 26
62 | Team 16,1,1,Team 21
63 | Team 10,2,2,Team 14
64 | Team 11,9,9,Team 10
65 | Team 10,8,7,Team 8
66 | Team 23,2,4,Team 14
67 | Team 16,7,7,Team 24
68 | Team 31,10,7,Team 21
69 | Team 21,4,8,Team 7
70 | Team 16,5,0,Team 4
71 | Team 9,2,5,Team 20
72 | Team 14,10,9,Team 5
73 | Team 13,10,2,Team 3
74 | Team 18,4,1,Team 8
75 | Team 24,6,3,Team 14
76 | Team 5,6,1,Team 23
77 | Team 15,10,1,Team 4
78 | Team 9,3,2,Team 11
79 | Team 26,4,6,Team 5
80 | Team 16,7,3,Team 11
81 | Team 30,10,1,Team 3
82 | Team 22,6,4,Team 11
83 | Team 2,5,3,Team 8
84 | Team 12,5,10,Team 13
85 | Team 6,5,6,Team 24
86 | Team 15,7,0,Team 26
87 | Team 23,2,0,Team 18
88 | Team 13,10,6,Team 15
89 | Team 5,2,0,Team 2
90 | Team 10,8,2,Team 32
91 | Team 27,9,3,Team 9
92 | Team 20,8,3,Team 4
93 | Team 10,1,7,Team 21
94 | Team 9,9,1,Team 6
95 | Team 22,10,1,Team 11
96 | Team 8,7,9,Team 24
97 | Team 6,10,3,Team 16
98 | Team 16,5,10,Team 19
99 | Team 14,9,6,Team 7
100 | Team 30,1,7,Team 21
101 | Team 6,7,7,Team 19
102 | Team 32,9,9,Team 10
103 | Team 12,2,10,Team 10
104 | Team 12,4,5,Team 19
105 | Team 7,7,2,Team 11
106 | Team 30,10,6,Team 14
107 | Team 12,3,2,Team 4
108 | Team 1,5,9,Team 28
109 | Team 16,2,5,Team 7
110 | Team 26,6,6,Team 25
111 | Team 16,9,1,Team 31
112 | Team 16,1,6,Team 15
113 | Team 17,1,9,Team 8
114 | Team 5,10,10,Team 21
115 | Team 22,3,8,Team 10
116 | Team 17,0,9,Team 27
117 | Team 23,2,1,Team 11
118 | Team 26,3,9,Team 5
119 | Team 19,2,4,Team 4
120 | Team 9,3,1,Team 20
121 | Team 29,4,0,Team 6
122 | Team 25,4,4,Team 22
123 | Team 18,4,6,Team 27
124 | Team 26,4,8,Team 19
125 | Team 3,7,3,Team 30
126 | Team 12,9,4,Team 16
127 | Team 7,3,8,Team 28
128 | Team 16,0,4,Team 6
129 | Team 32,3,4,Team 29
130 | Team 4,5,2,Team 23
131 | Team 30,3,1,Team 24
132 | Team 30,9,2,Team 31
133 | Team 2,7,3,Team 4
134 | Team 19,5,10,Team 9
135 | Team 9,4,5,Team 6
136 | Team 26,9,10,Team 8
137 | Team 3,3,1,Team 12
138 | Team 1,1,7,Team 4
139 | Team 9,9,10,Team 3
140 | Team 5,1,8,Team 13
141 | Team 23,0,3,Team 8
142 | Team 21,4,9,Team 17
143 | Team 11,7,1,Team 3
144 | Team 32,2,2,Team 11
145 | Team 3,0,2,Team 17
146 | Team 12,7,3,Team 27
147 | Team 17,8,10,Team 18
148 | Team 23,2,6,Team 19
149 | Team 15,7,10,Team 32
150 | Team 6,0,2,Team 3
151 | Team 6,1,2,Team 13
152 | Team 22,2,8,Team 8
153 | Team 20,3,5,Team 28
154 | Team 11,6,10,Team 29
155 | Team 10,5,9,Team 3
156 | Team 14,0,6,Team 15
157 | Team 4,7,6,Team 26
158 | Team 32,9,5,Team 3
159 | Team 16,9,5,Team 27
160 | Team 19,3,6,Team 24
161 | Team 17,5,10,Team 11
162 | Team 23,1,2,Team 24
163 | Team 2,0,1,Team 12
164 | Team 6,8,2,Team 12
165 | Team 2,4,6,Team 23
166 | Team 25,3,1,Team 26
167 | Team 12,0,9,Team 27
168 | Team 32,6,1,Team 25
169 | Team 16,4,3,Team 15
170 | Team 14,10,4,Team 13
171 | Team 17,0,8,Team 11
172 | Team 21,8,4,Team 29
173 | Team 5,8,6,Team 21
174 | Team 28,5,1,Team 31
175 | Team 19,7,0,Team 13
176 | Team 24,6,6,Team 15
177 | Team 10,5,5,Team 9
178 | Team 12,10,9,Team 8
179 | Team 16,8,7,Team 29
180 | Team 9,10,8,Team 15
181 | Team 18,6,9,Team 29
182 | Team 22,9,7,Team 25
183 | Team 11,2,0,Team 25
184 | Team 8,0,1,Team 15
185 | Team 3,7,6,Team 2
186 | Team 3,5,10,Team 27
187 | Team 15,3,6,Team 9
188 | Team 10,2,9,Team 22
189 | Team 29,5,2,Team 31
190 | Team 27,5,7,Team 17
191 | Team 25,0,8,Team 20
192 | Team 23,5,4,Team 27
193 | Team 7,9,1,Team 28
194 | Team 13,3,9,Team 12
195 | Team 13,3,3,Team 27
196 | Team 8,6,7,Team 18
197 | Team 30,1,5,Team 2
198 | Team 11,0,10,Team 32
199 | Team 8,3,2,Team 13
200 | Team 8,5,4,Team 6
201 | Team 4,3,0,Team 5
202 | Team 19,5,6,Team 18
203 | Team 9,1,4,Team 28
204 | Team 27,0,9,Team 31
205 | Team 12,0,8,Team 19
206 | Team 1,1,3,Team 19
207 | Team 5,1,0,Team 32
208 | Team 23,10,8,Team 28
209 | Team 29,6,3,Team 25
210 | Team 4,3,9,Team 6
211 | Team 19,8,0,Team 14
212 | Team 25,3,9,Team 31
213 | Team 14,5,5,Team 23
214 | Team 2,7,3,Team 13
215 | Team 29,10,5,Team 14
216 | Team 9,5,3,Team 3
217 | Team 5,1,9,Team 23
218 | Team 13,7,2,Team 19
219 | Team 2,1,3,Team 32
220 | Team 28,0,3,Team 31
221 | Team 10,3,2,Team 22
222 | Team 2,1,8,Team 14
223 | Team 3,8,4,Team 17
224 | Team 24,5,2,Team 22
225 | Team 12,10,10,Team 21
226 | Team 9,6,7,Team 12
227 | Team 26,3,7,Team 30
228 | Team 22,3,9,Team 25
229 | Team 8,2,2,Team 32
230 | Team 19,0,2,Team 24
231 | Team 13,7,7,Team 6
232 | Team 30,0,6,Team 16
233 | Team 3,1,0,Team 17
234 | Team 5,3,1,Team 25
235 | Team 22,1,8,Team 13
236 | Team 15,7,1,Team 8
237 | Team 7,10,1,Team 24
238 | Team 13,5,0,Team 16
239 | Team 24,1,5,Team 32
240 | Team 27,5,4,Team 14
241 | Team 6,0,9,Team 18
242 | Team 29,8,2,Team 13
243 | Team 25,10,7,Team 22
244 | Team 10,8,8,Team 23
245 | Team 18,10,9,Team 27
246 | Team 23,7,10,Team 5
247 | Team 21,4,0,Team 27
248 | Team 13,4,9,Team 14
249 | Team 31,10,8,Team 6
250 | Team 23,2,0,Team 16
251 | Team 32,7,9,Team 10
252 | Team 31,6,3,Team 16
253 | Team 8,1,8,Team 18
254 | Team 4,0,5,Team 16
255 | Team 16,1,8,Team 17
256 | Team 18,7,5,Team 3
257 | Team 15,3,5,Team 30
258 | Team 28,4,5,Team 8
259 | Team 16,7,1,Team 8
260 | Team 11,8,5,Team 23
261 | Team 4,2,0,Team 32
262 | Team 8,0,4,Team 10
263 | Team 14,0,8,Team 29
264 | Team 32,7,4,Team 18
265 | Team 12,0,2,Team 4
266 | Team 30,7,0,Team 28
267 | Team 10,6,10,Team 9
268 | Team 27,5,3,Team 14
269 | Team 9,8,3,Team 15
270 | Team 3,5,5,Team 17
271 | Team 17,0,6,Team 8
272 | Team 20,6,5,Team 16
273 | Team 2,7,3,Team 12
274 | Team 20,7,2,Team 2
275 | Team 19,9,0,Team 17
276 | Team 28,2,4,Team 12
277 | Team 32,8,3,Team 4
278 | Team 18,10,6,Team 32
279 | Team 4,8,4,Team 8
280 | Team 22,2,2,Team 24
281 | Team 4,1,8,Team 23
282 | Team 28,1,4,Team 7
283 | Team 18,8,7,Team 25
284 | Team 13,5,7,Team 17
285 | Team 16,6,1,Team 14
286 | Team 26,3,8,Team 19
287 | Team 5,0,5,Team 7
288 | Team 31,3,7,Team 13
289 | Team 30,8,4,Team 6
290 | Team 11,8,4,Team 22
291 | Team 19,0,7,Team 26
292 | Team 26,6,9,Team 13
293 | Team 25,0,1,Team 9
294 | Team 32,2,9,Team 15
295 | Team 16,2,7,Team 1
296 | Team 25,5,9,Team 11
297 | Team 29,8,2,Team 19
298 | Team 3,10,1,Team 31
299 | Team 27,3,10,Team 30
300 | Team 18,8,6,Team 22
301 | Team 16,1,7,Team 6
302 | Team 12,3,8,Team 13
303 | Team 3,2,7,Team 18
304 | Team 26,3,9,Team 15
305 | Team 18,9,8,Team 25
306 | Team 10,1,6,Team 9
307 | Team 13,9,6,Team 27
308 | Team 21,3,3,Team 18
309 | Team 9,4,7,Team 12
310 | Team 26,3,10,Team 9
311 | Team 28,9,10,Team 16
312 | Team 25,5,6,Team 1
313 | Team 1,9,7,Team 2
314 | Team 27,2,3,Team 4
315 | Team 2,6,5,Team 17
316 | Team 30,1,1,Team 14
317 | Team 30,9,2,Team 5
318 | Team 23,9,4,Team 10
319 | Team 4,5,6,Team 11
320 | Team 31,1,4,Team 32
321 | Team 9,8,4,Team 7
322 | Team 25,2,1,Team 23
323 | Team 4,8,8,Team 30
324 | Team 18,0,5,Team 12
325 | Team 6,3,4,Team 21
326 | Team 8,4,2,Team 30
327 | Team 21,5,4,Team 7
328 | Team 19,6,0,Team 26
329 | Team 13,4,0,Team 22
330 | Team 9,3,4,Team 6
331 | Team 4,8,2,Team 29
332 | Team 26,8,1,Team 19
333 | Team 28,9,9,Team 18
334 | Team 18,7,1,Team 21
335 | Team 27,1,8,Team 24
336 | Team 24,7,6,Team 22
337 | Team 1,5,9,Team 16
338 | Team 18,4,3,Team 21
339 | Team 21,6,8,Team 26
340 | Team 1,8,10,Team 8
341 | Team 19,10,9,Team 17
342 | Team 17,10,0,Team 26
343 | Team 27,0,1,Team 17
344 | Team 16,7,2,Team 1
345 | Team 27,8,4,Team 1
346 | Team 1,2,4,Team 2
347 | Team 3,3,7,Team 31
348 | Team 24,5,5,Team 32
349 | Team 10,9,10,Team 2
350 | Team 28,5,0,Team 5
351 | Team 26,2,6,Team 22
352 | Team 14,2,0,Team 4
353 | Team 22,7,7,Team 32
354 | Team 30,5,10,Team 23
355 | Team 13,1,7,Team 11
356 | Team 20,7,3,Team 31
357 | Team 31,9,1,Team 1
358 | Team 31,4,1,Team 20
359 | Team 31,2,6,Team 14
360 | Team 5,1,4,Team 2
361 | Team 3,1,0,Team 20
362 | Team 1,1,4,Team 22
363 | Team 22,7,9,Team 30
364 | Team 23,5,4,Team 1
365 | Team 29,9,0,Team 9
366 | Team 30,0,6,Team 21
367 | Team 5,9,3,Team 24
368 | Team 9,3,6,Team 32
369 | Team 9,0,10,Team 11
370 | Team 27,0,8,Team 9
371 | Team 13,1,5,Team 28
372 | Team 23,8,8,Team 22
373 | Team 17,5,7,Team 28
374 | Team 11,5,1,Team 2
375 | Team 1,3,9,Team 21
376 | Team 6,5,9,Team 31
377 | Team 2,1,1,Team 24
378 | Team 22,6,2,Team 16
379 | Team 19,4,0,Team 4
380 | Team 21,7,0,Team 2
381 | Team 21,5,0,Team 19
382 | Team 24,7,5,Team 22
383 | Team 14,4,7,Team 27
384 | Team 18,3,4,Team 28
385 | Team 19,3,5,Team 14
386 | Team 26,6,4,Team 21
387 | Team 5,7,3,Team 25
388 | Team 15,5,10,Team 32
389 | Team 24,1,0,Team 13
390 | Team 27,1,0,Team 9
391 | Team 12,0,7,Team 10
392 | Team 15,8,8,Team 12
393 | Team 30,2,3,Team 24
394 | Team 3,5,10,Team 15
395 | Team 6,5,2,Team 22
396 | Team 12,6,9,Team 25
397 | Team 4,4,2,Team 12
398 | Team 4,1,7,Team 16
399 | Team 24,4,4,Team 12
400 | Team 26,0,0,Team 31
401 | Team 3,9,0,Team 8
402 | Team 18,3,6,Team 28
403 | Team 3,6,9,Team 20
404 | Team 27,10,7,Team 1
405 | Team 5,7,1,Team 9
406 | Team 15,10,4,Team 17
407 | Team 17,10,7,Team 7
408 | Team 9,8,5,Team 26
409 | Team 22,6,10,Team 21
410 | Team 2,6,10,Team 30
411 | Team 28,8,3,Team 21
412 | Team 17,4,1,Team 28
413 | Team 29,8,10,Team 22
414 | Team 13,7,2,Team 24
415 | Team 14,0,1,Team 13
416 | Team 16,6,7,Team 12
417 | Team 10,1,5,Team 13
418 | Team 4,6,7,Team 11
419 | Team 13,8,5,Team 5
420 | Team 25,4,4,Team 30
421 | Team 2,7,3,Team 25
422 | Team 5,0,5,Team 27
423 | Team 1,2,3,Team 7
424 | Team 10,3,8,Team 31
425 | Team 1,2,6,Team 4
426 | Team 1,3,10,Team 11
427 | Team 5,9,8,Team 28
428 | Team 16,0,10,Team 28
429 | Team 23,7,6,Team 10
430 | Team 19,8,6,Team 16
431 | Team 28,8,2,Team 16
432 | Team 3,4,6,Team 30
433 | Team 30,5,8,Team 14
434 | Team 3,0,5,Team 5
435 | Team 13,2,9,Team 22
436 | Team 32,1,1,Team 1
437 | Team 26,7,4,Team 27
438 | Team 28,3,8,Team 32
439 | Team 9,8,8,Team 27
440 | Team 11,3,5,Team 31
441 | Team 18,6,7,Team 24
442 | Team 25,7,0,Team 11
443 | Team 22,10,4,Team 4
444 | Team 13,10,1,Team 16
445 | Team 1,0,9,Team 8
446 | Team 2,9,7,Team 14
447 | Team 32,10,3,Team 7
448 | Team 21,9,3,Team 2
449 | Team 25,6,9,Team 24
450 | Team 10,8,6,Team 8
451 | Team 16,10,0,Team 19
452 | Team 25,5,3,Team 24
453 | Team 20,0,8,Team 31
454 | Team 6,7,0,Team 14
455 | Team 9,9,6,Team 32
456 | Team 28,3,10,Team 23
457 | Team 13,9,3,Team 2
458 | Team 18,5,9,Team 22
459 | Team 7,4,8,Team 3
460 | Team 26,0,10,Team 15
461 | Team 1,3,0,Team 19
462 | Team 8,8,2,Team 26
463 | Team 2,10,9,Team 11
464 | Team 28,9,4,Team 19
465 | Team 4,1,3,Team 26
466 | Team 8,3,4,Team 5
467 | Team 7,8,0,Team 2
468 | Team 19,5,9,Team 30
469 | Team 23,2,1,Team 30
470 | Team 21,7,9,Team 11
471 | Team 22,7,7,Team 24
472 | Team 10,0,1,Team 17
473 | Team 29,7,5,Team 21
474 | Team 9,0,0,Team 15
475 | Team 27,0,3,Team 25
476 | Team 2,5,5,Team 25
477 | Team 32,6,1,Team 24
478 | Team 1,8,6,Team 3
479 | Team 9,1,0,Team 8
480 | Team 12,1,4,Team 1
481 | Team 29,5,10,Team 13
482 | Team 23,8,0,Team 4
483 | Team 21,4,0,Team 27
484 | Team 11,9,1,Team 12
485 | Team 30,10,2,Team 12
486 | Team 21,9,0,Team 2
487 | Team 27,6,10,Team 12
488 | Team 13,1,10,Team 25
489 | Team 15,4,3,Team 26
490 | Team 28,4,6,Team 10
491 | Team 2,5,4,Team 22
492 | Team 5,1,1,Team 22
493 | Team 32,5,7,Team 4
494 | Team 23,10,3,Team 13
495 | Team 22,2,2,Team 20
496 | Team 29,5,2,Team 10
497 | Team 31,1,8,Team 9
498 | Team 1,0,1,Team 24
499 | Team 2,1,0,Team 23
500 | Team 29,0,7,Team 20
501 | Team 16,1,6,Team 8
502 | Team 1,10,8,Team 9
503 | Team 26,8,4,Team 31
504 | Team 22,6,3,Team 2
505 | Team 31,8,10,Team 13
506 | Team 13,4,5,Team 10
507 | Team 26,3,3,Team 4
508 | Team 9,2,2,Team 10
509 | Team 14,10,10,Team 7
510 | Team 10,7,3,Team 31
511 | Team 5,4,5,Team 13
512 | Team 24,7,1,Team 25
513 | Team 15,6,7,Team 21
514 | Team 17,7,3,Team 29
515 | Team 20,1,5,Team 16
516 | Team 22,9,1,Team 6
517 | Team 24,6,8,Team 13
518 | Team 22,10,9,Team 6
519 | Team 19,2,2,Team 2
520 | Team 5,1,7,Team 1
521 | Team 3,0,10,Team 31
522 | Team 25,8,5,Team 26
523 | Team 17,3,10,Team 27
524 | Team 24,2,0,Team 29
525 | Team 14,10,10,Team 11
526 | Team 29,9,4,Team 24
527 | Team 11,4,4,Team 20
528 | Team 5,7,7,Team 32
529 | Team 29,7,2,Team 17
530 | Team 27,1,4,Team 30
531 | Team 8,6,5,Team 32
532 | Team 26,5,5,Team 27
533 | Team 15,5,10,Team 31
534 | Team 32,10,5,Team 30
535 | Team 25,6,4,Team 7
536 | Team 22,6,3,Team 3
537 | Team 9,7,9,Team 2
538 | Team 25,6,8,Team 2
539 | Team 22,8,0,Team 10
540 | Team 1,10,3,Team 28
541 | Team 2,3,9,Team 25
542 | Team 28,6,1,Team 1
543 | Team 13,4,1,Team 19
544 | Team 30,1,7,Team 24
545 | Team 17,10,1,Team 30
546 | Team 5,8,2,Team 30
547 | Team 15,3,4,Team 18
548 | Team 21,8,3,Team 11
549 | Team 20,2,6,Team 25
550 | Team 30,9,3,Team 6
551 | Team 32,7,5,Team 18
552 | Team 25,2,9,Team 6
553 | Team 9,3,6,Team 21
554 | Team 9,7,2,Team 18
555 | Team 31,6,2,Team 14
556 | Team 17,1,3,Team 32
557 | Team 31,10,5,Team 7
558 | Team 3,5,4,Team 15
559 | Team 6,7,10,Team 20
560 | Team 12,8,1,Team 14
561 | Team 17,8,4,Team 12
562 | Team 21,0,6,Team 8
563 | Team 16,0,2,Team 18
564 | Team 27,9,5,Team 5
565 | Team 9,9,0,Team 16
566 | Team 14,1,2,Team 30
567 | Team 17,6,3,Team 10
568 | Team 28,9,10,Team 3
569 | Team 28,8,9,Team 6
570 | Team 30,4,2,Team 20
571 | Team 26,1,4,Team 8
572 | Team 25,6,10,Team 2
573 | Team 30,7,2,Team 26
574 | Team 26,3,5,Team 3
575 | Team 11,6,1,Team 2
576 | Team 19,7,8,Team 10
577 | Team 9,7,1,Team 29
578 | Team 27,9,7,Team 19
579 | Team 12,9,5,Team 18
580 | Team 26,3,9,Team 24
581 | Team 24,5,6,Team 28
582 | Team 2,1,2,Team 10
583 | Team 4,1,10,Team 26
584 | Team 16,5,1,Team 12
585 | Team 7,9,4,Team 4
586 | Team 4,3,5,Team 27
587 | Team 15,3,7,Team 18
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Andrew De Rango
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NBA/Output CSV Data/2023-02-15_games.csv:
--------------------------------------------------------------------------------
1 | ,Date,Start (EST),Home Team,Home Win Prob,Visitor Win Prob,Visiting Team,Arena
2 | 872,"Wed, Feb 15, 2023",7:00p,Charlotte Hornets,62.62%,37.38%,San Antonio Spurs,Spectrum Center
3 | 873,"Wed, Feb 15, 2023",7:00p,Indiana Pacers,40.94%,59.06%,Chicago Bulls,Gainbridge Fieldhouse
4 | 874,"Wed, Feb 15, 2023",7:30p,Atlanta Hawks,42.03%,57.97%,New York Knicks,State Farm Arena
5 | 875,"Wed, Feb 15, 2023",7:30p,Boston Celtics,85.50%,14.50%,Detroit Pistons,TD Garden
6 | 876,"Wed, Feb 15, 2023",7:30p,Brooklyn Nets,55.05%,44.95%,Miami Heat,Barclays Center
7 | 877,"Wed, Feb 15, 2023",7:30p,Philadelphia 76ers,41.62%,58.38%,Cleveland Cavaliers,Wells Fargo Center
8 | 878,"Wed, Feb 15, 2023",8:00p,Memphis Grizzlies,61.64%,38.36%,Utah Jazz,FedEx Forum
9 | 879,"Wed, Feb 15, 2023",8:00p,Oklahoma City Thunder,77.91%,22.09%,Houston Rockets,Paycom Center
10 | 880,"Wed, Feb 15, 2023",9:00p,Denver Nuggets,60.97%,39.03%,Dallas Mavericks,Ball Arena
11 | 881,"Wed, Feb 15, 2023",10:00p,Los Angeles Lakers,38.23%,61.77%,New Orleans Pelicans,Crypto.com Arena
12 |
--------------------------------------------------------------------------------
/NBA/Output CSV Data/miami_heat_game_log.csv:
--------------------------------------------------------------------------------
1 | Opponent,Record,PCT,Win Probability,Lose by 21+,Lose by 16-20,Lose by 11-15,Lose by 6-10,Lose by 1-5,Win by 1-5,Win by 6-10,Win by 10-15,Win by 16-20,Win by 21+
2 | Boston Celtics,37-15,0.712,31.72%,11.34%,8.96%,13.35%,16.59%,18.05%,13.83%,8.02%,4.65%,2.52%,2.69%
3 | Denver Nuggets,36-16,0.692,37.68%,8.95%,7.42%,11.67%,15.65%,18.64%,15.59%,9.62%,5.79%,3.21%,3.47%
4 | Milwaukee Bucks,35-17,0.673,43.80%,7.08%,6.09%,10.03%,14.36%,18.64%,17.04%,11.25%,7.07%,4.01%,4.43%
5 | Philadelphia 76ers,33-17,0.660,39.72%,8.27%,6.95%,11.11%,15.24%,18.71%,16.12%,10.17%,6.20%,3.46%,3.77%
6 | Memphis Grizzlies,32-20,0.615,39.11%,8.47%,7.09%,11.28%,15.37%,18.69%,15.96%,10.00%,6.08%,3.38%,3.68%
7 | Brooklyn Nets,31-20,0.608,44.12%,7.00%,6.03%,9.95%,14.28%,18.62%,17.10%,11.34%,7.14%,4.06%,4.48%
8 | Cleveland Cavaliers,32-22,0.593,34.84%,10.00%,8.11%,12.46%,16.14%,18.44%,14.80%,8.86%,5.24%,2.87%,3.08%
9 | Sacramento Kings,29-21,0.580,42.10%,7.55%,6.44%,10.47%,14.74%,18.70%,16.68%,10.80%,6.70%,3.78%,4.14%
10 | Dallas Mavericks,28-25,0.528,49.34%,5.75%,5.08%,8.64%,13.02%,18.16%,17.99%,12.69%,8.33%,4.86%,5.47%
11 | New York Knicks,28-25,0.528,45.47%,6.65%,5.77%,9.60%,13.97%,18.54%,17.36%,11.69%,7.44%,4.26%,4.72%
12 | Los Angeles Clippers,29-26,0.527,53.20%,4.97%,4.46%,7.74%,12.04%,17.60%,18.43%,13.65%,9.27%,5.53%,6.33%
13 | Minnesota Timberwolves,28-26,0.519,51.82%,5.23%,4.67%,8.05%,12.40%,17.82%,18.30%,13.31%,8.92%,5.28%,6.01%
14 | Utah Jazz,27-26,0.509,47.00%,6.28%,5.49%,9.21%,13.60%,18.41%,17.63%,12.09%,7.78%,4.49%,5.01%
15 | Phoenix Suns,27-26,0.509,48.33%,5.97%,5.25%,8.89%,13.28%,18.28%,17.84%,12.43%,8.09%,4.70%,5.26%
16 | Atlanta Hawks,26-26,0.500,51.74%,5.25%,4.69%,8.07%,12.42%,17.83%,18.29%,13.29%,8.90%,5.27%,5.99%
17 | Golden State Warriors,26-26,0.500,52.23%,5.15%,4.61%,7.96%,12.29%,17.76%,18.34%,13.41%,9.02%,5.35%,6.10%
18 | New Orleans Pelicans,26-27,0.491,47.20%,6.23%,5.45%,9.16%,13.55%,18.40%,17.67%,12.14%,7.83%,4.52%,5.04%
19 | Portland Trail Blazers,25-26,0.490,49.98%,5.61%,4.97%,8.49%,12.86%,18.08%,18.08%,12.85%,8.48%,4.97%,5.60%
20 | Washington Wizards,24-26,0.480,50.57%,5.49%,4.87%,8.35%,12.71%,18.00%,18.15%,13.00%,8.62%,5.07%,5.73%
21 | Los Angeles Lakers,25-28,0.472,55.43%,4.56%,4.13%,7.24%,11.46%,17.19%,18.60%,14.18%,9.83%,5.94%,6.88%
22 | Chicago Bulls,24-27,0.471,49.30%,5.76%,5.09%,8.65%,13.03%,18.17%,17.99%,12.68%,8.32%,4.85%,5.46%
23 | Oklahoma City Thunder,24-27,0.471,46.98%,6.29%,5.49%,9.22%,13.61%,18.42%,17.63%,12.09%,7.78%,4.48%,5.00%
24 | Indiana Pacers,24-29,0.453,57.00%,4.29%,3.90%,6.89%,11.04%,16.87%,18.67%,14.54%,10.23%,6.25%,7.30%
25 | Toronto Raptors,23-30,0.434,48.17%,6.01%,5.28%,8.92%,13.31%,18.30%,17.82%,12.39%,8.05%,4.67%,5.23%
26 | Orlando Magic,20-32,0.385,61.43%,3.60%,3.32%,5.97%,9.86%,15.83%,18.68%,15.47%,11.42%,7.21%,8.64%
27 | Charlotte Hornets,15-38,0.283,70.42%,2.44%,2.30%,4.27%,7.45%,13.13%,17.67%,16.82%,13.95%,9.58%,12.39%
28 | San Antonio Spurs,14-38,0.269,79.02%,1.55%,1.49%,2.84%,5.19%,9.91%,15.17%,16.84%,16.18%,12.53%,18.29%
29 | Houston Rockets,13-38,0.255,73.51%,2.10%,1.99%,3.73%,6.63%,12.04%,16.97%,17.02%,14.81%,10.56%,14.16%
30 | Detroit Pistons,13-39,0.250,72.34%,2.22%,2.11%,3.93%,6.94%,12.46%,17.26%,16.97%,14.49%,10.18%,13.45%
31 |
--------------------------------------------------------------------------------
/NBA/Output CSV Data/most_consistent_teams.csv:
--------------------------------------------------------------------------------
1 | ,Team,Rating,Consistency (z-Score)
2 | 1,Indiana Pacers,-1.74,1.52
3 | 2,Miami Heat,0.30,1.39
4 | 3,Utah Jazz,1.17,1.21
5 | 4,Oklahoma City Thunder,1.18,1.15
6 | 5,Minnesota Timberwolves,-0.23,1.14
7 | 6,Los Angeles Lakers,-1.28,0.94
8 | 7,Houston Rockets,-7.11,0.90
9 | 8,Denver Nuggets,3.96,0.88
10 | 9,Portland Trail Blazers,0.31,0.79
11 | 10,Cleveland Cavaliers,4.85,0.54
12 | 11,Philadelphia 76ers,3.33,0.46
13 | 12,Orlando Magic,-3.08,0.43
14 | 13,Toronto Raptors,0.83,0.37
15 | 14,Dallas Mavericks,0.49,0.23
16 | 15,Sacramento Kings,2.61,0.19
17 | 16,Washington Wizards,0.13,0.03
18 | 17,Chicago Bulls,0.50,-0.12
19 | 18,Detroit Pistons,-6.68,-0.18
20 | 19,Los Angeles Clippers,-0.63,-0.22
21 | 20,New York Knicks,1.62,-0.37
22 | 21,New Orleans Pelicans,1.11,-0.45
23 | 22,Atlanta Hawks,-0.20,-0.46
24 | 23,Charlotte Hornets,-6.00,-0.50
25 | 24,Milwaukee Bucks,2.11,-0.83
26 | 25,Boston Celtics,5.87,-1.05
27 | 26,Golden State Warriors,-0.35,-1.08
28 | 27,Memphis Grizzlies,3.52,-1.17
29 | 28,Brooklyn Nets,2.02,-1.44
30 | 29,Phoenix Suns,0.79,-1.92
31 | 30,San Antonio Spurs,-9.33,-2.38
32 |
--------------------------------------------------------------------------------
/NBA/Output CSV Data/power_rankings.csv:
--------------------------------------------------------------------------------
1 | ,Team,POWER,Record,PCT,Avg PTS Diff,Avg PTS For,Avg PTS Against,Strength of Schedule
2 | 1,Boston Celtics,5.87,37-15,0.712,6.19,118.2,112.0,-0.323
3 | 2,Cleveland Cavaliers,4.85,32-22,0.593,4.67,111.6,106.9,0.180
4 | 3,Denver Nuggets,3.96,36-16,0.692,4.17,117.0,112.8,-0.217
5 | 4,Memphis Grizzlies,3.52,32-20,0.615,3.73,116.5,112.8,-0.214
6 | 5,Philadelphia 76ers,3.33,33-17,0.660,3.78,114.5,110.7,-0.449
7 | 6,Sacramento Kings,2.61,29-21,0.580,3.14,119.5,116.4,-0.525
8 | 7,Milwaukee Bucks,2.11,35-17,0.673,2.31,114.1,111.8,-0.196
9 | 8,Brooklyn Nets,2.02,31-20,0.608,2.14,114.2,112.1,-0.120
10 | 9,New York Knicks,1.62,28-25,0.528,1.72,114.3,112.5,-0.097
11 | 10,Oklahoma City Thunder,1.18,24-27,0.471,0.84,116.6,115.8,0.338
12 | 11,Utah Jazz,1.17,27-26,0.509,1.23,117.8,116.5,-0.054
13 | 12,New Orleans Pelicans,1.11,26-27,0.491,1.19,114.8,113.6,-0.074
14 | 13,Toronto Raptors,0.83,23-30,0.434,0.43,113.1,112.6,0.398
15 | 14,Phoenix Suns,0.79,27-26,0.509,0.83,112.7,111.8,-0.042
16 | 15,Chicago Bulls,0.5,24-27,0.471,0.2,114.3,114.1,0.307
17 | 16,Dallas Mavericks,0.49,28-25,0.528,0.47,112.3,111.8,0.021
18 | 17,Portland Trail Blazers,0.31,25-26,0.490,0.57,114.3,113.7,-0.261
19 | 18,Miami Heat,0.3,29-24,0.547,0.26,108.4,108.1,0.037
20 | 19,Washington Wizards,0.13,24-26,0.480,-0.04,113.0,113.0,0.174
21 | 20,Atlanta Hawks,-0.2,26-26,0.500,0.0,116.5,116.5,-0.204
22 | 21,Minnesota Timberwolves,-0.23,28-26,0.519,0.15,115.1,115.0,-0.377
23 | 22,Golden State Warriors,-0.35,26-26,0.500,-0.35,118.0,118.3,-0.000
24 | 23,Los Angeles Clippers,-0.63,29-26,0.527,0.09,110.5,110.5,-0.721
25 | 24,Los Angeles Lakers,-1.28,25-28,0.472,-1.19,116.9,118.1,-0.093
26 | 25,Indiana Pacers,-1.74,24-29,0.453,-2.04,115.1,117.1,0.294
27 | 26,Orlando Magic,-3.08,20-32,0.385,-3.42,110.9,114.3,0.346
28 | 27,Charlotte Hornets,-6.0,15-38,0.283,-6.6,112.0,118.6,0.608
29 | 28,Detroit Pistons,-6.68,13-39,0.250,-7.42,112.6,120.1,0.744
30 | 29,Houston Rockets,-7.11,13-38,0.255,-7.24,109.6,116.9,0.124
31 | 30,San Antonio Spurs,-9.33,14-38,0.269,-9.73,112.5,122.2,0.403
32 |
--------------------------------------------------------------------------------
/NBA/Output CSV Data/toronto_raptors_game_log.csv:
--------------------------------------------------------------------------------
1 | ,Date,Opponent,GF,GA,Performance
2 | 1,"Wed, Oct 19, 2022",Cleveland Cavaliers,108,105,7.85
3 | 2,"Fri, Oct 21, 2022",Brooklyn Nets,105,109,-1.98
4 | 3,"Sat, Oct 22, 2022",Miami Heat,109,112,-2.7
5 | 4,"Mon, Oct 24, 2022",Miami Heat,98,90,8.3
6 | 5,"Wed, Oct 26, 2022",Philadelphia 76ers,119,109,13.33
7 | 6,"Fri, Oct 28, 2022",Philadelphia 76ers,90,112,-18.67
8 | 7,"Mon, Oct 31, 2022",Atlanta Hawks,139,109,29.8
9 | 8,"Wed, Nov 2, 2022",San Antonio Spurs,143,100,33.67
10 | 9,"Fri, Nov 4, 2022",Dallas Mavericks,110,111,-0.51
11 | 10,"Sun, Nov 6, 2022",Chicago Bulls,113,104,9.5
12 | 11,"Mon, Nov 7, 2022",Chicago Bulls,97,111,-13.5
13 | 12,"Wed, Nov 9, 2022",Houston Rockets,116,109,-0.11
14 | 13,"Fri, Nov 11, 2022",Oklahoma City Thunder,113,132,-17.82
15 | 14,"Sat, Nov 12, 2022",Indiana Pacers,104,118,-15.74
16 | 15,"Mon, Nov 14, 2022",Detroit Pistons,115,111,-2.68
17 | 16,"Wed, Nov 16, 2022",Miami Heat,112,104,8.3
18 | 17,"Sat, Nov 19, 2022",Atlanta Hawks,122,124,-2.2
19 | 18,"Wed, Nov 23, 2022",Brooklyn Nets,98,112,-11.98
20 | 19,"Sat, Nov 26, 2022",Dallas Mavericks,105,100,5.49
21 | 20,"Mon, Nov 28, 2022",Cleveland Cavaliers,100,88,16.85
22 | 21,"Wed, Nov 30, 2022",New Orleans Pelicans,108,126,-16.89
23 | 22,"Fri, Dec 2, 2022",Brooklyn Nets,105,114,-6.98
24 | 23,"Sat, Dec 3, 2022",Orlando Magic,121,108,9.92
25 | 24,"Mon, Dec 5, 2022",Boston Celtics,110,116,-0.13
26 | 25,"Wed, Dec 7, 2022",Los Angeles Lakers,126,113,11.72
27 | 26,"Fri, Dec 9, 2022",Orlando Magic,109,113,-7.08
28 | 27,"Sun, Dec 11, 2022",Orlando Magic,99,111,-15.08
29 | 28,"Wed, Dec 14, 2022",Sacramento Kings,123,124,1.61
30 | 29,"Fri, Dec 16, 2022",Brooklyn Nets,116,119,-0.98
31 | 30,"Sun, Dec 18, 2022",Golden State Warriors,110,126,-16.35
32 | 31,"Mon, Dec 19, 2022",Philadelphia 76ers,101,104,0.33
33 | 32,"Wed, Dec 21, 2022",New York Knicks,113,106,8.62
34 | 33,"Fri, Dec 23, 2022",Cleveland Cavaliers,118,107,15.85
35 | 34,"Tue, Dec 27, 2022",Los Angeles Clippers,113,124,-11.63
36 | 35,"Thu, Dec 29, 2022",Memphis Grizzlies,106,119,-9.48
37 | 36,"Fri, Dec 30, 2022",Phoenix Suns,113,104,9.79
38 | 37,"Mon, Jan 2, 2023",Indiana Pacers,114,122,-9.74
39 | 38,"Wed, Jan 4, 2023",Milwaukee Bucks,101,104,-0.89
40 | 39,"Fri, Jan 6, 2023",New York Knicks,108,112,-2.38
41 | 40,"Sun, Jan 8, 2023",Portland Trail Blazers,117,105,12.31
42 | 41,"Tue, Jan 10, 2023",Charlotte Hornets,132,120,6.0
43 | 42,"Thu, Jan 12, 2023",Charlotte Hornets,124,114,4.0
44 | 43,"Sat, Jan 14, 2023",Atlanta Hawks,103,114,-11.2
45 | 44,"Mon, Jan 16, 2023",New York Knicks,123,121,3.62
46 | 45,"Tue, Jan 17, 2023",Milwaukee Bucks,122,130,-5.89
47 | 46,"Thu, Jan 19, 2023",Minnesota Timberwolves,126,128,-2.23
48 | 47,"Sat, Jan 21, 2023",Boston Celtics,104,106,3.87
49 | 48,"Sun, Jan 22, 2023",New York Knicks,125,116,10.62
50 | 49,"Wed, Jan 25, 2023",Sacramento Kings,113,95,20.61
51 | 50,"Fri, Jan 27, 2023",Golden State Warriors,117,129,-12.35
52 | 51,"Sat, Jan 28, 2023",Portland Trail Blazers,123,105,18.31
53 | 52,"Mon, Jan 30, 2023",Phoenix Suns,106,114,-7.21
54 | 53,"Wed, Feb 1, 2023",Utah Jazz,128,131,-1.83
55 |
--------------------------------------------------------------------------------
/NBA/Output CSV Data/toronto_raptors_vs_philadelphia_76ers_game_probabilities.csv:
--------------------------------------------------------------------------------
1 | ,Toronto Raptors,Philadelphia 76ers
2 | Rating,0.832,3.331
3 | Record,23-30,33-17
4 | Point PCT,0.434,0.660
5 | Win Probability,41.48%,58.52%
6 | Win by 1-5 Points,16.54%,18.71%
7 | Win by 6-10 Points,10.64%,14.87%
8 | Win by 11-15 Points,6.57%,10.64%
9 | Win by 16-20 Points,3.69%,6.57%
10 | Win by 21+ Points,4.04%,7.73%
11 |
--------------------------------------------------------------------------------
/NBA/nba_game_probability_model.py:
--------------------------------------------------------------------------------
1 | import requests
2 | from bs4 import BeautifulSoup
3 | import pandas as pd
4 | import numpy as np
5 | import matplotlib.pyplot as plt
6 | from scipy.stats import pearsonr
7 | from scipy.optimize import curve_fit
8 | from sklearn.metrics import log_loss
9 | import os
10 | import time
11 | from datetime import date, datetime
12 |
13 | class Team():
14 | def __init__(self, name):
15 | self.name = name
16 | self.team_game_list = []
17 | self.apd = 0
18 | self.opponent_power = []
19 | self.schedule = 0
20 | self.power = 0
21 | self.prev_power = 0
22 | self.points_for = 0
23 | self.points_against = 0
24 | self.wins = 0
25 | self.losses = 0
26 | self.pct = 0
27 |
28 | def read_games(self):
29 | print(f'--{self.name.upper()} GAME LIST--')
30 | for game in self.team_game_list:
31 | print(f'{game.home_team.name} {game.home_score}-{game.away_score} {game.away_team.name}')
32 |
33 | def calc_apd(self):
34 | point_differential = 0
35 | for game in self.team_game_list:
36 | if self == game.home_team:
37 | point_differential += game.home_score - game.away_score
38 | else:
39 | point_differential += game.away_score - game.home_score
40 | apd = point_differential / len(self.team_game_list)
41 |
42 | return apd
43 |
44 | def calc_sched(self):
45 | self.opponent_power = []
46 | for game in self.team_game_list:
47 | if self == game.home_team:
48 | self.opponent_power.append(game.away_team.prev_power)
49 | else:
50 | self.opponent_power.append(game.home_team.prev_power)
51 |
52 | return sum(self.opponent_power) / len(self.opponent_power)
53 |
54 | def calc_power(self):
55 | return self.calc_sched() + self.apd
56 |
57 | def calc_pct(self):
58 | point_percentage = self.wins/(self.wins + self.losses)
59 | return point_percentage
60 |
61 | def calc_consistency(self):
62 | performance_list = []
63 | for game in self.team_game_list:
64 | if self == game.home_team:
65 | performance_list.append(game.away_team.power + game.home_score - game.away_score)
66 | else:
67 | performance_list.append(game.away_team.power + game.home_score - game.away_score)
68 |
69 | variance = np.var(performance_list)
70 | return variance
71 |
72 | class Game():
73 | def __init__(self, home_team, away_team, home_score, away_score, date):
74 | self.home_team = home_team
75 | self.away_team = away_team
76 | self.home_score = home_score
77 | self.away_score = away_score
78 | self.date = date
79 |
80 | def game_team_object_creation(games_metadf):
81 | total_game_list = []
82 | team_list = []
83 |
84 | for index, row in games_metadf.iterrows():
85 | try:
86 | row['Home Score'] = float(row['Home Score'])
87 | row['Visitor Score'] = float(row['Visitor Score'])
88 |
89 | team_in_list = False
90 | for team in team_list:
91 | if team.name == row['Home Team']:
92 | team_in_list = True
93 | home_team_obj = team
94 | if team_in_list == False:
95 | home_team_obj = Team(row['Home Team'])
96 | team_list.append(home_team_obj)
97 |
98 | team_in_list = False
99 | for team in team_list:
100 | if team.name == row['Visiting Team']:
101 | team_in_list = True
102 | away_team_obj = team
103 | if team_in_list == False:
104 | away_team_obj = Team(row['Visiting Team'])
105 | team_list.append(away_team_obj)
106 |
107 | game_obj = Game(home_team_obj, away_team_obj, row['Home Score'], row['Visitor Score'], row['Date'])
108 |
109 | home_team_obj.team_game_list.append(game_obj)
110 | away_team_obj.team_game_list.append(game_obj)
111 | home_team_obj.points_for += game_obj.home_score
112 | away_team_obj.points_against += game_obj.home_score
113 | home_team_obj.points_against += game_obj.away_score
114 | away_team_obj.points_for += game_obj.away_score
115 |
116 | if game_obj.home_score > game_obj.away_score:
117 | home_team_obj.wins += 1
118 | away_team_obj.losses += 1
119 | else:
120 | home_team_obj.losses += 1
121 | away_team_obj.wins += 1
122 |
123 | total_game_list.append(game_obj)
124 | except ValueError:
125 | pass
126 |
127 | return team_list, total_game_list
128 |
129 | def scrape_nba_data():
130 | season_months = ['october', 'november', 'december', 'january', 'february', 'march', 'april']
131 |
132 | for month in season_months:
133 | url = f'https://www.basketball-reference.com/leagues/NBA_2025_games-{month}.html'
134 | soup = BeautifulSoup(requests.get(url).text, 'lxml')
135 | url_table = soup.find('table', id='schedule')
136 |
137 | if month == season_months[0]:
138 | headers = ['Date', 'Start (EST)', 'Visiting Team', 'Visitor Score', 'Home Team', 'Home Score', 'Box Score Links', 'OT', 'Attendance', 'LOG', 'Arena', 'Notes']
139 | scraped_df = pd.DataFrame(columns = headers)
140 |
141 | for url_row in url_table.find_all('tr')[1:]:
142 | row = [url_row.find('th').text]
143 | row.extend([i.text for i in url_row.find_all('td')])
144 | scraped_df.loc[len(scraped_df)] = row
145 |
146 | scraped_df.index += 1
147 | return scraped_df
148 |
149 | def assign_power(team_list, iterations):
150 | for team in team_list:
151 | team.apd = team.calc_apd()
152 | team.pct = team.calc_pct()
153 |
154 | for iteration in range(iterations):
155 | # print(f'ITERATION {iteration+1}')
156 | for team in team_list:
157 | team.schedule = team.calc_sched()
158 | team.power = team.calc_power()
159 | # print(f'{team.name}\t\tAPD: {team.calc_apd():.2f}\tSCHEDULE: {team.schedule:.2f}\t\tPOWER: {team.power:.2f}')
160 | for team in team_list:
161 | team.prev_power = team.power
162 |
163 | def prepare_power_rankings(team_list):
164 | frames = []
165 | for team in team_list:
166 | frame = pd.DataFrame({
167 | 'Team': [team.name],
168 | 'POWER': [round(team.power, 2)],
169 | 'Record': [f'{team.wins}-{team.losses}'],
170 | 'PCT': [f"{team.calc_pct():.3f}"],
171 | 'Avg PTS Diff': [round(team.calc_apd(), 2)],
172 | 'Avg PTS For': [f"{team.points_for/len(team.team_game_list):.1f}"],
173 | 'Avg PTS Against': [f"{team.points_against/len(team.team_game_list):.1f}"],
174 | 'Strength of Schedule': [f"{team.schedule:.3f}"]
175 | })
176 | frames.append(frame)
177 |
178 | power_df = pd.concat(frames, ignore_index=True)
179 | power_df.sort_values(by=['POWER'], inplace=True, ascending=False)
180 | power_df.reset_index(drop=True, inplace=True)
181 |
182 | return power_df
183 |
184 | def logistic_regression(total_game_list):
185 | xpoints = [] # Rating differential (Home - Away)
186 | ypoints = [] # Home Win/Loss Boolean (Win = 1, Tie = 0.5, Loss = 0)
187 |
188 | for game in total_game_list:
189 | xpoints.append(game.home_team.power - game.away_team.power)
190 |
191 | if game.home_score > game.away_score:
192 | ypoints.append(1)
193 | elif game.home_score < game.away_score:
194 | ypoints.append(0)
195 | else:
196 | ypoints.append(0.5)
197 |
198 | parameters, covariates = curve_fit(lambda t, param: 1/(1+np.exp((t)/param)), [-x for x in xpoints], ypoints) # Regression only works if parameter is positive.
199 | param = -parameters[0]
200 |
201 | return xpoints, ypoints, param
202 |
203 | def model_performance(xpoints, ypoints, param):
204 | x_fitted = np.linspace(np.min(xpoints)*1.25, np.max(xpoints)*1.25, 100)
205 | y_fitted = 1/(1+np.exp((x_fitted)/param))
206 |
207 | r, p = pearsonr(xpoints, ypoints)
208 | print(f'Pearson Correlation of Independent and Dependent Variables: {r:.3f}')
209 | print(f'Log Loss of the Cumulative Distribution Function (CDF): {log_loss(ypoints, 1/(1+np.exp((xpoints)/param))):.3f}')
210 | print(f'Regressed Sigmoid: 1/(1+exp((x)/{param:.3f}))')
211 | print(f'Precise Parameter: {param}')
212 |
213 | plt.plot(xpoints, ypoints, 'o', color='grey')
214 | plt.plot(x_fitted, y_fitted, color='black', alpha=1, label=f'CDF (Log Loss = {log_loss(ypoints, 1/(1+np.exp((xpoints)/param))):.3f})')
215 | plt.legend()
216 | plt.title('Logistic Regression of Team Rating Difference vs Game Result')
217 | plt.xlabel('Rating Difference')
218 | plt.ylabel('Win Probability')
219 | plt.show()
220 |
221 | def calc_prob(team, opponent, param):
222 | return 1/(1+np.exp((team.power-opponent.power)/param))
223 |
224 | def calc_spread(team, opponent, param, lower_bound_spread, upper_bound_spread):
225 | if lower_bound_spread == '-inf':
226 | if upper_bound_spread == 'inf':
227 | return 1
228 | return 1/(1+np.exp((upper_bound_spread-(team.power-opponent.power))/param))
229 | elif upper_bound_spread == 'inf':
230 | return 1 - 1/(1+np.exp((lower_bound_spread-(team.power-opponent.power))/param))
231 | else:
232 | return 1/(1+np.exp((upper_bound_spread-(team.power-opponent.power))/param)) - 1/(1+np.exp((lower_bound_spread-(team.power-opponent.power))/param))
233 |
234 | def download_csv_option(df, filename):
235 | valid = False
236 | while valid == False:
237 | user_input = input('Would you like to download this as a CSV? (Y/N): ')
238 | if user_input.lower() in ['y', 'yes', 'y.', 'yes.']:
239 | valid = True
240 | elif user_input.lower() in ['n', 'no', 'n.', 'no.']:
241 | return
242 | else:
243 | print(f'Sorry, I could not understand "{user_input}". Please enter Y or N: ')
244 |
245 | if not os.path.exists(f'{os.path.dirname(__file__)}/Output CSV Data'):
246 | os.makedirs(f'{os.path.dirname(__file__)}/Output CSV Data')
247 | df.to_csv(f'{os.path.dirname(__file__)}/Output CSV Data/{filename}.csv')
248 | print(f'{filename}.csv has been downloaded to the following directory: {os.path.dirname(__file__)}/Output CSV Data')
249 | return
250 |
251 | def get_todays_games(param, team_list, games_metadf):
252 | month_dict = {
253 | '01': 'Jan',
254 | '02': 'Feb',
255 | '03': 'Mar',
256 | '04': 'Apr',
257 | '05': 'May',
258 | '06': 'Jun',
259 | '07': 'Jul',
260 | '08': 'Aug',
261 | '09': 'Sep',
262 | '10': 'Oct',
263 | '11': 'Nov',
264 | '12': 'Dec'}
265 |
266 | todays_date = f"{datetime.today().strftime('%A')[:3]}, {month_dict[str(date.today()).split('-')[1]]} {int(str(date.today()).split('-')[2])}, {str(date.today()).split('-')[0]}"
267 |
268 | today_games_df = games_metadf[games_metadf['Date'] == todays_date]
269 | today_games_df = today_games_df.reindex(columns = today_games_df.columns.tolist() + ['Home Win Prob','Visitor Win Prob'])
270 | today_games_df = today_games_df[['Date', 'Start (EST)', 'Home Team', 'Home Win Prob', 'Visitor Win Prob', 'Visiting Team', 'Arena']]
271 | team_name_obj_dict = {}
272 |
273 | for index, row in today_games_df.iterrows():
274 | for team in team_list:
275 | if row['Visiting Team'] == team.name:
276 | visiting_team_obj = team
277 | visiting_team_name = team.name
278 | if row['Home Team'] == team.name:
279 | home_team_obj = team
280 | home_team_name = team.name
281 |
282 | team_name_obj_dict[home_team_name] = home_team_obj
283 | team_name_obj_dict[visiting_team_name] = visiting_team_obj
284 |
285 | if not today_games_df.empty:
286 | today_games_df['Home Win Prob'] = today_games_df.apply(lambda row: f"{calc_prob(team_name_obj_dict[row['Home Team']], team_name_obj_dict[row['Visiting Team']], param)*100:.2f}%", axis=1)
287 | today_games_df['Visitor Win Prob'] = today_games_df.apply(lambda row: f"{calc_prob(team_name_obj_dict[row['Visiting Team']], team_name_obj_dict[row['Home Team']], param)*100:.2f}%", axis=1)
288 |
289 | return date.today(), today_games_df
290 |
291 | def custom_game_selector(param, team_list):
292 | valid = False
293 | while valid == False:
294 | home_team_input = input('Enter the home team: ')
295 | for team in team_list:
296 | if home_team_input.strip().lower() == team.name.lower().replace('é','e'):
297 | home_team = team
298 | valid = True
299 | if valid == False:
300 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
301 |
302 | valid = False
303 | while valid == False:
304 | away_team_input = input('Enter the away team: ')
305 | for team in team_list:
306 | if away_team_input.strip().lower() == team.name.lower().replace('é','e'):
307 | away_team = team
308 | valid = True
309 | if valid == False:
310 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
311 |
312 | game_probability_df = pd.DataFrame(columns = ['', home_team.name, away_team.name])
313 |
314 | data_frames = []
315 | data_frames.append(pd.DataFrame({'': ['Rating', 'Record', 'Point PCT', 'Win Probability'],
316 | home_team.name: [f'{home_team.power:.3f}', f'{home_team.wins}-{home_team.losses}', f'{home_team.pct:.3f}', f'{calc_prob(home_team, away_team, param)*100:.2f}%'],
317 | away_team.name: [f'{away_team.power:.3f}', f'{away_team.wins}-{away_team.losses}', f'{away_team.pct:.3f}', f'{(calc_prob(away_team, home_team, param))*100:.2f}%']}))
318 | for points_range in [(0, 5.5), (5.5, 10.5), (10.5, 15.5), (15.5, 20.5), (20.5, float('inf'))]:
319 | data_frames.append(pd.DataFrame({'': [f'Win by {points_range[0]}-{points_range[1]} Points'],
320 | home_team.name: [f'{calc_spread(home_team, away_team, param, points_range[0], points_range[1])*100:.2f}%'],
321 | away_team.name: [f'{calc_spread(away_team, home_team, param, points_range[0], points_range[1])*100:.2f}%']}))
322 | game_probability_df = pd.concat(data_frames, ignore_index=True).set_index('')
323 |
324 | return home_team, away_team, game_probability_df
325 |
326 | def get_upsets(total_game_list):
327 | frames = []
328 | for game in total_game_list:
329 | expected_score_diff = game.home_team.power - game.away_team.power
330 | actaul_score_diff = game.home_score - game.away_score
331 | upset_rating = actaul_score_diff - expected_score_diff
332 |
333 | frame = pd.DataFrame({
334 | 'Home Team': [game.home_team.name],
335 | 'Home points': [int(game.home_score)],
336 | 'Away points': [int(game.away_score)],
337 | 'Away Team': [game.away_team.name],
338 | 'Date': [game.date],
339 | 'xGD': [f'{expected_score_diff:.2f}'],
340 | 'GD': [int(actaul_score_diff)],
341 | 'Upset Rating': [f'{abs(upset_rating):.2f}']
342 | })
343 | frames.append(frame)
344 |
345 | upset_df = pd.concat(frames, ignore_index=True)
346 | upset_df.sort_values(by=['Upset Rating'], inplace=True, ascending=False)
347 | upset_df.reset_index(drop=True, inplace=True)
348 | upset_df.index += 1
349 |
350 | return upset_df
351 |
352 |
353 | def get_best_performances(total_game_list):
354 | performance_df = pd.DataFrame(columns = ['Team', 'Opponent', 'PF', 'PA', 'Date', 'xGD', 'Performance'])
355 |
356 | for game in total_game_list:
357 | data_to_append = [{'Team': game.home_team.name, 'Opponent': game.away_team.name, 'PF': int(game.home_score), 'PA': int(game.away_score), 'Date': game.date, 'xGD': f'{game.home_team.power - game.away_team.power:.2f}', 'Performance': round(game.away_team.power + game.home_score - game.away_score, 2)},
358 | {'Team': game.away_team.name, 'Opponent': game.home_team.name, 'PF': int(game.away_score), 'PA': int(game.home_score), 'Date': game.date, 'xGD': f'{game.away_team.power - game.home_team.power:.2f}', 'Performance': round(game.home_team.power + game.away_score - game.home_score, 2)}]
359 | performance_df = pd.concat([performance_df, pd.DataFrame(data_to_append)], ignore_index=True)
360 |
361 |
362 | performance_df = performance_df.sort_values(by=['Performance'], ascending=False)
363 | performance_df = performance_df.reset_index(drop=True)
364 | performance_df.index += 1
365 | return performance_df
366 |
367 | def get_team_consistency(team_list):
368 | consistency_df = pd.DataFrame(columns = ['Team', 'Rating', 'Consistency (z-Score)'])
369 |
370 | for team in team_list:
371 | data_to_append = {'Team': team.name, 'Rating': f'{team.power:.2f}', 'Consistency (z-Score)': team.calc_consistency()}
372 | consistency_df = pd.concat([consistency_df, pd.DataFrame(data_to_append, index=[0])], ignore_index=True)
373 |
374 | consistency_df['Consistency (z-Score)'] = consistency_df['Consistency (z-Score)'].apply(lambda x: (x-consistency_df['Consistency (z-Score)'].mean())/-consistency_df['Consistency (z-Score)'].std())
375 |
376 | consistency_df = consistency_df.sort_values(by=['Consistency (z-Score)'], ascending=False)
377 | consistency_df = consistency_df.reset_index(drop=True)
378 | consistency_df.index += 1
379 | consistency_df['Consistency (z-Score)'] = consistency_df['Consistency (z-Score)'].apply(lambda x: f'{x:.2f}')
380 | return consistency_df
381 |
382 | def team_game_log(team_list):
383 | valid = False
384 | while valid == False:
385 | input_team = input('Enter a team: ')
386 | for team_obj in team_list:
387 | if input_team.strip().lower() == team_obj.name.lower().replace('é','e'):
388 | team = team_obj
389 | valid = True
390 | if valid == False:
391 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
392 |
393 | game_log_df = pd.DataFrame(columns = ['Date', 'Opponent', 'PF', 'PA', 'Performance'])
394 | for game in team.team_game_list:
395 | if team == game.home_team:
396 | points_for = game.home_score
397 | opponent = game.away_team
398 | points_against = game.away_score
399 | else:
400 | points_for = game.away_score
401 | opponent = game.home_team
402 | points_against = game.home_score
403 | data_to_append = {'Date': game.date, 'Opponent': opponent.name, 'PF': int(points_for), 'PA': int(points_against), 'Performance': round(opponent.power + points_for - points_against, 2)}
404 | game_log_df = pd.concat([game_log_df, pd.DataFrame(data_to_append, index=[0])], ignore_index=True)
405 |
406 | game_log_df.index += 1
407 | return team, game_log_df
408 |
409 | def get_team_prob_breakdown(team_list, param):
410 | valid = False
411 | while valid == False:
412 | input_team = input('Enter a team: ')
413 | for team_obj in team_list:
414 | if input_team.strip().lower() == team_obj.name.lower().replace('é','e'):
415 | team = team_obj
416 | valid = True
417 | if valid == False:
418 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
419 |
420 | prob_breakdown_df = pd.DataFrame()
421 | for opp_team in team_list:
422 | if opp_team is not team:
423 | data_to_append = {
424 | 'Opponent': opp_team.name,
425 | 'Record': f'{opp_team.wins}-{opp_team.losses}',
426 | 'PCT': f'{opp_team.calc_pct():.3f}',
427 | 'Win Probability': f'{calc_prob(team, opp_team, param) * 100:.2f}%',
428 | 'Lose by 21+': f'{calc_spread(team, opp_team, param, "-inf", -20.5) * 100:.2f}',
429 | 'Lose by 16-20': f'{calc_spread(team, opp_team, param, -20.5, -15.5) * 100:.2f}',
430 | 'Lose by 11-15': f'{calc_spread(team, opp_team, param, -15.5, -10.5) * 100:.2f}',
431 | 'Lose by 6-10': f'{calc_spread(team, opp_team, param, -10.5, -5.5) * 100:.2f}',
432 | 'Lose by 1-5': f'{calc_spread(team, opp_team, param, -5.5, 0) * 100:.2f}',
433 | 'Win by 1-5': f'{calc_spread(team, opp_team, param, 0, 5.5) * 100:.2f}',
434 | 'Win by 6-10': f'{calc_spread(team, opp_team, param, 5.5, 10.5) * 100:.2f}',
435 | 'Win by 10-15': f'{calc_spread(team, opp_team, param, 10.5, 15.5) * 100:.2f}',
436 | 'Win by 16-20': f'{calc_spread(team, opp_team, param, 15.5, 20.5) * 100:.2f}',
437 | 'Win by 21+': f'{calc_spread(team, opp_team, param, 20.5, "inf") * 100:.2f}'
438 | }
439 | prob_breakdown_df = pd.concat([prob_breakdown_df, pd.DataFrame(data_to_append, index=[0])], ignore_index=True)
440 |
441 |
442 | prob_breakdown_df = prob_breakdown_df.set_index('Opponent')
443 | prob_breakdown_df = prob_breakdown_df.sort_values(by=['PCT'], ascending=False)
444 | return team, prob_breakdown_df
445 |
446 | def extra_menu(total_game_list, team_list, param):
447 | while True:
448 | print("""--EXTRAS MENU--
449 | 1. Biggest Upsets
450 | 2. Best Performances
451 | 3. Most Consistent Teams
452 | 4. Team Game Logs
453 | 5. Team Probability Big Board
454 | 6. Exit to Main Menu""")
455 |
456 | valid = False
457 | while valid == False:
458 | user_option = input('Enter a menu option: ')
459 | try:
460 | user_option = int(user_option)
461 | if user_option >= 1 and user_option <= 6:
462 | print()
463 | valid = True
464 | else:
465 | raise ValueError
466 | except ValueError:
467 | print(f'Your option "{user_option}" is invalid.', end=' ')
468 |
469 | if user_option == 1:
470 | upsets = get_upsets(total_game_list)
471 | print(upsets)
472 | download_csv_option(upsets, 'biggest_upsets')
473 | elif user_option == 2:
474 | performances = get_best_performances(total_game_list)
475 | print(performances)
476 | download_csv_option(performances, 'best_performances')
477 | elif user_option == 3:
478 | consistency = get_team_consistency(team_list)
479 | print(consistency)
480 | download_csv_option(consistency, 'most_consistent_teams')
481 | elif user_option == 4:
482 | team, game_log = team_game_log(team_list)
483 | print(game_log)
484 | download_csv_option(game_log, f'{team.name.replace(" ", "_").lower()}_game_log')
485 | elif user_option == 5:
486 | team, team_probabilities = get_team_prob_breakdown(team_list, param)
487 | print(team_probabilities)
488 | download_csv_option(team_probabilities, f'{team.name.replace(" ", "_").lower()}_game_log')
489 | elif user_option == 6:
490 | pass
491 |
492 | return
493 |
494 | def menu(power_df, today_games_df, xpoints, ypoints, param, computation_time, total_game_list, team_list, date):
495 | while True:
496 | print("""--MAIN MENU--
497 | 1. View Power Rankings
498 | 2. View Today's Games
499 | 3. Custom Game Selector
500 | 4. View Model Performance
501 | 5. View Program Performance
502 | 6. Extra Options
503 | 7. Quit""")
504 |
505 | valid = False
506 | while valid == False:
507 | user_option = input('Enter a menu option: ')
508 | try:
509 | user_option = int(user_option)
510 | if user_option >= 1 and user_option <= 7:
511 | print()
512 | valid = True
513 | else:
514 | raise ValueError
515 | except ValueError:
516 | print(f'Your option "{user_option}" is invalid.', end=' ')
517 |
518 | if user_option == 1:
519 | print(power_df)
520 | download_csv_option(power_df, 'power_rankings')
521 | elif user_option == 2:
522 | if not today_games_df.empty:
523 | print(today_games_df)
524 | download_csv_option(today_games_df, f'{date}_games')
525 | else:
526 | print('There are no games today!\n')
527 | elif user_option == 3:
528 | home_team, away_team, custom_game_df = custom_game_selector(param, team_list)
529 | print(custom_game_df)
530 | download_csv_option(custom_game_df, f'{home_team.name.replace(" ", "_").lower()}_vs_{away_team.name.replace(" ", "_").lower()}_game_probabilities')
531 | elif user_option == 4:
532 | model_performance(xpoints, ypoints, param)
533 | elif user_option == 5:
534 | print(f'Computation Time: {computation_time:.2f} seconds')
535 | print(f'Games Scraped: {len(total_game_list)}')
536 | print(f'Rate: {len(total_game_list)/computation_time:.1f} games/second')
537 | elif user_option == 6:
538 | extra_menu(total_game_list, team_list, param)
539 | elif user_option == 7:
540 | return
541 |
542 | input('Press ENTER to continue\t\t')
543 | print()
544 |
545 | def main():
546 | start_time = time.time()
547 |
548 | games_metadf = scrape_nba_data()
549 | iterations = 25
550 | team_list, total_game_list = game_team_object_creation(games_metadf)
551 | assign_power(team_list, iterations)
552 | power_df = prepare_power_rankings(team_list)
553 | xpoints, ypoints, param = logistic_regression(total_game_list)
554 | date, today_games_df = get_todays_games(param, team_list, games_metadf)
555 |
556 | computation_time = time.time()-start_time
557 | menu(power_df, today_games_df, xpoints, ypoints, param, computation_time, total_game_list, team_list, date)
558 |
559 | if __name__ == '__main__':
560 | main()
561 |
--------------------------------------------------------------------------------
/NHL/Output CSV Data/2023-02-11_games.csv:
--------------------------------------------------------------------------------
1 | ,GameID,Game State,Home Team,Home Goals,Away Goals,Away Team,Pre-Game Home Win Probability,Pre-Game Away Win Probability,Home Record,Away Record
2 | 1,2022020838,END 1st,Boston Bruins,0,1,Washington Capitals,70.52%,29.48%,39-7-5,27-20-6
3 | 2,2022020839,Pre-Game,Florida Panthers,0,0,Colorado Avalanche,48.63%,51.37%,26-22-6,27-19-4
4 | 3,2022020840,Pre-Game,Toronto Maple Leafs,0,0,Columbus Blue Jackets,79.40%,20.60%,32-13-8,15-33-4
5 | 4,2022020841,Pre-Game,Carolina Hurricanes,0,0,New York Rangers,52.48%,47.52%,34-9-8,30-14-8
6 | 5,2022020842,Pre-Game,St. Louis Blues,0,0,Arizona Coyotes,55.24%,44.76%,23-25-3,17-28-7
7 | 6,2022020843,Pre-Game,Minnesota Wild,0,0,New Jersey Devils,36.25%,63.75%,27-20-4,34-13-4
8 | 7,2022020844,Pre-Game,Winnipeg Jets,0,0,Chicago Blackhawks,75.03%,24.97%,32-19-1,16-29-5
9 | 8,2022020845,Pre-Game,Los Angeles Kings,0,0,Pittsburgh Penguins,42.48%,57.52%,28-18-7,26-16-9
10 | 9,2022020832,Final,Detroit Red Wings,5,2,Vancouver Canucks,54.02%,45.98%,23-20-8,21-28-4
11 | 10,2022020833,Final,Buffalo Sabres,2,7,Calgary Flames,51.02%,48.98%,26-21-4,25-18-10
12 | 11,2022020834,Final,Ottawa Senators,3,6,Edmonton Oilers,38.77%,61.23%,24-24-3,30-18-5
13 | 12,2022020835,Final,Montréal Canadiens,4,3,New York Islanders,31.53%,68.47%,21-27-4,27-23-6
14 | 13,2022020836,Final,Philadelphia Flyers,1,2,Nashville Predators,47.43%,52.57%,22-22-10,25-19-6
15 | 14,2022020837,Final,Dallas Stars,1,3,Tampa Bay Lightning,51.83%,48.17%,30-14-10,34-16-2
16 |
--------------------------------------------------------------------------------
/NHL/Output CSV Data/biggest_upsets.csv:
--------------------------------------------------------------------------------
1 | ,Home Team,Home Goals,Away Goals,Away Team,Date,xGD,GD,Upset Rating
2 | 1,Colorado Avalanche,7,0,Ottawa Senators,2023-01-14,0.52,7,6.48
3 | 2,New York Rangers,1,5,Columbus Blue Jackets,2022-10-23,1.80,-4,5.80
4 | 3,Washington Capitals,9,2,Montréal Canadiens,2022-12-31,1.35,7,5.65
5 | 4,Carolina Hurricanes,0,4,Arizona Coyotes,2022-11-23,1.57,-4,5.57
6 | 5,Buffalo Sabres,6,0,Los Angeles Kings,2022-12-13,0.54,6,5.46
7 | 6,Arizona Coyotes,5,0,St. Louis Blues,2023-01-26,-0.34,5,5.34
8 | 7,Minnesota Wild,6,1,Winnipeg Jets,2022-11-23,-0.28,5,5.28
9 | 8,Calgary Flames,1,5,Chicago Blackhawks,2023-01-26,1.25,-4,5.25
10 | 9,Detroit Red Wings,2,8,New York Rangers,2022-11-10,-0.89,-6,5.11
11 | 10,Seattle Kraken,2,7,Edmonton Oilers,2022-12-30,0.08,-5,5.08
12 | 11,Winnipeg Jets,5,0,Colorado Avalanche,2022-11-29,0.10,5,4.90
13 | 12,Toronto Maple Leafs,2,6,Ottawa Senators,2023-01-27,0.89,-4,4.89
14 | 13,Winnipeg Jets,0,4,Philadelphia Flyers,2023-01-28,0.83,-4,4.83
15 | 14,Vancouver Canucks,5,1,Pittsburgh Penguins,2022-10-28,-0.77,4,4.77
16 | 15,Carolina Hurricanes,7,2,Edmonton Oilers,2022-11-10,0.25,5,4.75
17 | 16,San Jose Sharks,1,7,Edmonton Oilers,2023-01-13,-1.26,-6,4.74
18 | 17,Winnipeg Jets,1,4,Columbus Blue Jackets,2022-12-02,1.74,-3,4.74
19 | 18,Buffalo Sabres,0,4,Philadelphia Flyers,2023-01-09,0.73,-4,4.73
20 | 19,Vegas Golden Knights,1,5,Vancouver Canucks,2022-11-26,0.71,-4,4.71
21 | 20,Toronto Maple Leafs,7,0,Anaheim Ducks,2022-12-13,2.29,7,4.71
22 | 21,New York Rangers,2,5,Chicago Blackhawks,2022-12-03,1.70,-3,4.70
23 | 22,San Jose Sharks,5,1,Ottawa Senators,2022-11-21,-0.68,4,4.68
24 | 23,Edmonton Oilers,8,2,Arizona Coyotes,2022-12-07,1.33,6,4.67
25 | 24,Arizona Coyotes,6,3,Toronto Maple Leafs,2022-12-29,-1.63,3,4.63
26 | 25,Pittsburgh Penguins,6,1,Los Angeles Kings,2022-10-20,0.40,5,4.60
27 | 26,Carolina Hurricanes,2,6,New York Islanders,2022-10-28,0.59,-4,4.59
28 | 27,Montréal Canadiens,4,1,Winnipeg Jets,2023-01-17,-1.53,3,4.53
29 | 28,Florida Panthers,5,2,Boston Bruins,2022-11-23,-1.52,3,4.52
30 | 29,Seattle Kraken,1,5,Florida Panthers,2022-12-03,0.51,-4,4.51
31 | 30,Minnesota Wild,5,1,Tampa Bay Lightning,2023-01-04,-0.49,4,4.49
32 | 31,Pittsburgh Penguins,6,2,Tampa Bay Lightning,2022-10-15,-0.44,4,4.44
33 | 32,Washington Capitals,5,1,Tampa Bay Lightning,2022-11-11,-0.39,4,4.39
34 | 33,New York Islanders,7,1,Anaheim Ducks,2022-10-15,1.64,6,4.36
35 | 34,Vegas Golden Knights,1,5,Los Angeles Kings,2023-01-07,0.34,-4,4.34
36 | 35,Chicago Blackhawks,1,7,New York Rangers,2022-12-18,-1.70,-6,4.30
37 | 36,Anaheim Ducks,2,0,Dallas Stars,2023-01-04,-2.30,2,4.30
38 | 37,Anaheim Ducks,1,6,San Jose Sharks,2022-12-09,-0.72,-5,4.28
39 | 38,Winnipeg Jets,5,1,Dallas Stars,2022-11-08,-0.28,4,4.28
40 | 39,Buffalo Sabres,8,3,Detroit Red Wings,2022-10-31,0.73,5,4.27
41 | 40,Buffalo Sabres,1,4,Arizona Coyotes,2022-11-08,1.26,-3,4.26
42 | 41,Arizona Coyotes,6,3,Colorado Avalanche,2022-12-27,-1.26,3,4.26
43 | 42,New York Rangers,0,4,Washington Capitals,2022-12-27,0.25,-4,4.25
44 | 43,Toronto Maple Leafs,1,5,Seattle Kraken,2023-01-05,0.23,-4,4.23
45 | 44,Philadelphia Flyers,5,2,New Jersey Devils,2022-10-13,-1.18,3,4.18
46 | 45,New Jersey Devils,2,5,Detroit Red Wings,2022-10-15,1.18,-3,4.18
47 | 46,Montréal Canadiens,6,2,Arizona Coyotes,2022-10-20,-0.17,4,4.17
48 | 47,New York Islanders,5,1,Pittsburgh Penguins,2022-12-27,-0.15,4,4.15
49 | 48,Vegas Golden Knights,6,2,Washington Capitals,2023-01-21,-0.11,4,4.11
50 | 49,Boston Bruins,6,0,Philadelphia Flyers,2023-01-16,1.89,6,4.11
51 | 50,Toronto Maple Leafs,5,0,Los Angeles Kings,2022-12-08,0.90,5,4.10
52 | 51,Detroit Red Wings,4,1,Toronto Maple Leafs,2023-01-12,-1.09,3,4.09
53 | 52,Ottawa Senators,5,0,Montréal Canadiens,2023-01-28,0.91,5,4.09
54 | 53,Columbus Blue Jackets,1,6,Detroit Red Wings,2022-11-19,-0.92,-5,4.08
55 | 54,Arizona Coyotes,4,1,Vegas Golden Knights,2023-01-22,-1.07,3,4.07
56 | 55,Detroit Red Wings,7,4,Tampa Bay Lightning,2022-12-21,-1.03,3,4.03
57 | 56,San Jose Sharks,5,2,Minnesota Wild,2022-12-22,-1.02,3,4.02
58 | 57,Boston Bruins,0,3,Seattle Kraken,2023-01-12,1.01,-3,4.01
59 | 58,Vancouver Canucks,1,5,St. Louis Blues,2022-12-19,0.01,-4,4.01
60 | 59,Dallas Stars,0,4,Toronto Maple Leafs,2022-12-06,0.01,-4,4.01
61 | 60,Vegas Golden Knights,2,5,San Jose Sharks,2022-11-15,1.00,-3,4.00
62 | 61,Edmonton Oilers,2,5,Vancouver Canucks,2022-12-23,0.97,-3,3.97
63 | 62,Seattle Kraken,6,1,Vancouver Canucks,2023-01-25,1.05,5,3.95
64 | 63,Florida Panthers,7,2,Montréal Canadiens,2022-12-29,1.07,5,3.93
65 | 64,Washington Capitals,0,4,Colorado Avalanche,2022-11-19,-0.08,-4,3.92
66 | 65,Colorado Avalanche,3,5,Anaheim Ducks,2023-01-26,1.92,-2,3.92
67 | 66,Calgary Flames,6,2,Florida Panthers,2022-11-29,0.08,4,3.92
68 | 67,New Jersey Devils,7,1,Columbus Blue Jackets,2022-10-30,2.09,6,3.91
69 | 68,Columbus Blue Jackets,5,2,Philadelphia Flyers,2022-11-10,-0.91,3,3.91
70 | 69,New York Islanders,5,1,Florida Panthers,2022-12-23,0.09,4,3.91
71 | 70,Seattle Kraken,5,1,Buffalo Sabres,2022-10-25,0.14,4,3.86
72 | 71,Tampa Bay Lightning,6,2,Seattle Kraken,2022-12-13,0.16,4,3.84
73 | 72,Carolina Hurricanes,4,1,Boston Bruins,2023-01-29,-0.84,3,3.84
74 | 73,Seattle Kraken,1,5,Carolina Hurricanes,2022-10-17,-0.17,-4,3.83
75 | 74,Philadelphia Flyers,1,4,Chicago Blackhawks,2023-01-19,0.81,-3,3.81
76 | 75,Philadelphia Flyers,5,1,St. Louis Blues,2022-11-08,0.19,4,3.81
77 | 76,Montréal Canadiens,0,4,San Jose Sharks,2022-11-29,-0.24,-4,3.76
78 | 77,Minnesota Wild,0,3,St. Louis Blues,2023-01-08,0.74,-3,3.74
79 | 78,Vancouver Canucks,6,2,San Jose Sharks,2022-12-27,0.29,4,3.71
80 | 79,Edmonton Oilers,2,6,Dallas Stars,2022-11-05,-0.32,-4,3.68
81 | 80,Florida Panthers,3,0,Carolina Hurricanes,2022-11-09,-0.68,3,3.68
82 | 81,Minnesota Wild,0,4,Seattle Kraken,2022-11-03,-0.32,-4,3.68
83 | 82,Ottawa Senators,7,5,Boston Bruins,2022-10-18,-1.68,2,3.68
84 | 83,Arizona Coyotes,2,5,Anaheim Ducks,2023-01-24,0.66,-3,3.66
85 | 84,Minnesota Wild,3,7,New York Rangers,2022-10-13,-0.34,-4,3.66
86 | 85,Vegas Golden Knights,1,5,New York Rangers,2022-12-07,-0.36,-4,3.64
87 | 86,New York Islanders,4,7,St. Louis Blues,2022-12-06,0.64,-3,3.64
88 | 87,Florida Panthers,5,1,Detroit Red Wings,2022-12-08,0.36,4,3.64
89 | 88,Toronto Maple Leafs,2,4,Arizona Coyotes,2022-10-17,1.63,-2,3.63
90 | 89,Colorado Avalanche,2,6,Toronto Maple Leafs,2022-12-31,-0.37,-4,3.63
91 | 90,Calgary Flames,2,5,St. Louis Blues,2022-12-16,0.63,-3,3.63
92 | 91,St. Louis Blues,1,5,Los Angeles Kings,2022-10-31,-0.38,-4,3.62
93 | 92,Los Angeles Kings,6,3,Edmonton Oilers,2023-01-09,-0.60,3,3.60
94 | 93,Calgary Flames,6,3,Tampa Bay Lightning,2023-01-21,-0.59,3,3.59
95 | 94,San Jose Sharks,5,3,Dallas Stars,2023-01-18,-1.58,2,3.58
96 | 95,Anaheim Ducks,1,6,Nashville Predators,2022-12-30,-1.42,-5,3.58
97 | 96,Nashville Predators,6,2,St. Louis Blues,2022-10-27,0.42,4,3.58
98 | 97,Seattle Kraken,2,4,Montréal Canadiens,2022-12-06,1.57,-2,3.57
99 | 98,Montréal Canadiens,2,7,Buffalo Sabres,2022-11-22,-1.43,-5,3.57
100 | 99,Toronto Maple Leafs,5,1,Washington Capitals,2023-01-29,0.45,4,3.55
101 | 100,New Jersey Devils,3,6,Washington Capitals,2022-10-24,0.54,-3,3.54
102 | 101,Montréal Canadiens,5,2,Vancouver Canucks,2022-11-09,-0.53,3,3.53
103 | 102,St. Louis Blues,4,7,Montréal Canadiens,2022-10-29,0.52,-3,3.52
104 | 103,Ottawa Senators,4,1,Buffalo Sabres,2022-11-16,-0.52,3,3.52
105 | 104,Montréal Canadiens,2,5,Anaheim Ducks,2022-12-15,0.48,-3,3.48
106 | 105,Philadelphia Flyers,6,2,Arizona Coyotes,2023-01-05,0.53,4,3.47
107 | 106,Philadelphia Flyers,0,3,San Jose Sharks,2022-10-23,0.47,-3,3.47
108 | 107,New York Rangers,6,2,Florida Panthers,2023-01-23,0.53,4,3.47
109 | 108,New Jersey Devils,5,1,Washington Capitals,2022-11-26,0.54,4,3.46
110 | 109,Vancouver Canucks,1,5,Florida Panthers,2022-12-01,-0.54,-4,3.46
111 | 110,Detroit Red Wings,3,0,New York Islanders,2022-11-05,-0.45,3,3.45
112 | 111,Washington Capitals,5,2,Toronto Maple Leafs,2022-12-17,-0.45,3,3.45
113 | 112,New York Islanders,3,0,New York Rangers,2022-10-26,-0.44,3,3.44
114 | 113,Ottawa Senators,5,2,Washington Capitals,2022-10-20,-0.44,3,3.44
115 | 114,Seattle Kraken,2,5,Calgary Flames,2023-01-27,0.43,-3,3.43
116 | 115,Vegas Golden Knights,0,4,Dallas Stars,2023-01-16,-0.58,-4,3.42
117 | 116,St. Louis Blues,5,2,Nashville Predators,2023-01-19,-0.42,3,3.42
118 | 117,Arizona Coyotes,4,3,Boston Bruins,2022-12-09,-2.42,1,3.42
119 | 118,Colorado Avalanche,4,1,Dallas Stars,2022-11-26,-0.38,3,3.38
120 | 119,Winnipeg Jets,5,1,Ottawa Senators,2022-12-20,0.62,4,3.38
121 | 120,Ottawa Senators,1,5,Winnipeg Jets,2023-01-21,-0.62,-4,3.38
122 | 121,Vancouver Canucks,2,6,New York Islanders,2023-01-03,-0.63,-4,3.37
123 | 122,New Jersey Devils,3,5,St. Louis Blues,2023-01-05,1.37,-2,3.37
124 | 123,Vancouver Canucks,4,1,Los Angeles Kings,2022-11-18,-0.37,3,3.37
125 | 124,Buffalo Sabres,1,4,Florida Panthers,2023-01-16,0.37,-3,3.37
126 | 125,Arizona Coyotes,2,7,Dallas Stars,2022-11-03,-1.64,-5,3.36
127 | 126,Chicago Blackhawks,2,7,Winnipeg Jets,2022-11-27,-1.64,-5,3.36
128 | 127,Columbus Blue Jackets,4,9,Buffalo Sabres,2022-12-07,-1.64,-5,3.36
129 | 128,Seattle Kraken,5,1,Nashville Predators,2022-11-08,0.64,4,3.36
130 | 129,Columbus Blue Jackets,3,1,Calgary Flames,2022-12-09,-1.35,2,3.35
131 | 130,San Jose Sharks,2,5,Chicago Blackhawks,2022-10-15,0.34,-3,3.34
132 | 131,Ottawa Senators,4,8,Seattle Kraken,2023-01-07,-0.66,-4,3.34
133 | 132,New York Islanders,3,0,Edmonton Oilers,2022-11-23,-0.34,3,3.34
134 | 133,Seattle Kraken,2,5,Vegas Golden Knights,2022-10-15,0.33,-3,3.33
135 | 134,Carolina Hurricanes,4,0,Florida Panthers,2022-12-30,0.68,4,3.32
136 | 135,Dallas Stars,3,6,Edmonton Oilers,2022-12-21,0.32,-3,3.32
137 | 136,Colorado Avalanche,4,1,Carolina Hurricanes,2022-11-12,-0.31,3,3.31
138 | 137,Anaheim Ducks,4,3,Toronto Maple Leafs,2022-10-30,-2.29,1,3.29
139 | 138,Vegas Golden Knights,5,2,Winnipeg Jets,2022-10-20,-0.29,3,3.29
140 | 139,Florida Panthers,5,2,Washington Capitals,2022-11-15,-0.28,3,3.28
141 | 140,Winnipeg Jets,1,4,Minnesota Wild,2022-12-27,0.28,-3,3.28
142 | 141,Columbus Blue Jackets,5,3,Florida Panthers,2022-11-20,-1.28,2,3.28
143 | 142,Ottawa Senators,6,2,Arizona Coyotes,2022-10-22,0.74,4,3.26
144 | 143,Dallas Stars,5,1,Florida Panthers,2023-01-08,0.75,4,3.25
145 | 144,Anaheim Ducks,4,3,Carolina Hurricanes,2022-12-06,-2.23,1,3.23
146 | 145,Winnipeg Jets,0,3,Pittsburgh Penguins,2022-11-19,0.23,-3,3.23
147 | 146,Washington Capitals,4,1,Seattle Kraken,2022-12-09,-0.23,3,3.23
148 | 147,Dallas Stars,3,6,New York Rangers,2022-10-29,0.22,-3,3.22
149 | 148,Detroit Red Wings,3,0,Nashville Predators,2022-11-23,-0.22,3,3.22
150 | 149,New York Islanders,1,4,Nashville Predators,2022-12-02,0.22,-3,3.22
151 | 150,Calgary Flames,1,4,Nashville Predators,2022-11-03,0.21,-3,3.21
152 | 151,Pittsburgh Penguins,6,2,St. Louis Blues,2022-12-03,0.79,4,3.21
153 | 152,Buffalo Sabres,4,7,Vegas Golden Knights,2022-11-10,0.20,-3,3.20
154 | 153,Calgary Flames,5,2,Washington Capitals,2022-12-03,-0.20,3,3.20
155 | 154,Winnipeg Jets,2,5,Washington Capitals,2022-12-11,0.18,-3,3.18
156 | 155,Vancouver Canucks,1,5,Washington Capitals,2022-11-29,-0.82,-4,3.18
157 | 156,Chicago Blackhawks,4,2,Florida Panthers,2022-10-25,-1.18,2,3.18
158 | 157,Washington Capitals,4,1,Winnipeg Jets,2022-12-23,-0.18,3,3.18
159 | 158,Calgary Flames,4,1,Pittsburgh Penguins,2022-10-25,-0.16,3,3.16
160 | 159,Columbus Blue Jackets,5,3,Nashville Predators,2022-10-20,-1.14,2,3.14
161 | 160,Carolina Hurricanes,4,1,New Jersey Devils,2022-12-20,-0.14,3,3.14
162 | 161,Dallas Stars,5,1,Nashville Predators,2022-10-15,0.88,4,3.12
163 | 162,Columbus Blue Jackets,4,1,Chicago Blackhawks,2022-12-31,-0.10,3,3.10
164 | 163,San Jose Sharks,3,7,Calgary Flames,2022-12-20,-0.91,-4,3.09
165 | 164,Vancouver Canucks,1,5,Buffalo Sabres,2022-10-22,-0.91,-4,3.09
166 | 165,Vegas Golden Knights,2,5,New York Islanders,2022-12-17,0.09,-3,3.09
167 | 166,Los Angeles Kings,0,4,Dallas Stars,2023-01-19,-0.92,-4,3.08
168 | 167,Buffalo Sabres,6,2,St. Louis Blues,2022-11-23,0.92,4,3.08
169 | 168,Edmonton Oilers,5,2,Seattle Kraken,2023-01-17,-0.08,3,3.08
170 | 169,Anaheim Ducks,3,2,New York Rangers,2022-11-23,-2.08,1,3.08
171 | 170,Pittsburgh Penguins,4,6,San Jose Sharks,2023-01-28,1.07,-2,3.07
172 | 171,New Jersey Devils,1,4,Dallas Stars,2022-12-13,0.07,-3,3.07
173 | 172,Anaheim Ducks,5,4,Seattle Kraken,2022-10-12,-2.06,1,3.06
174 | 173,Vegas Golden Knights,5,2,Pittsburgh Penguins,2023-01-05,-0.06,3,3.06
175 | 174,Winnipeg Jets,4,1,New York Rangers,2022-10-14,-0.06,3,3.06
176 | 175,Washington Capitals,1,4,Pittsburgh Penguins,2022-11-09,0.05,-3,3.05
177 | 176,Tampa Bay Lightning,2,4,Detroit Red Wings,2022-12-06,1.03,-2,3.03
178 | 177,Ottawa Senators,1,5,New Jersey Devils,2022-11-19,-0.97,-4,3.03
179 | 178,Ottawa Senators,2,5,Los Angeles Kings,2022-12-06,0.01,-3,3.01
180 | 179,Calgary Flames,4,1,New York Islanders,2023-01-06,-0.01,3,3.01
181 | 180,Vancouver Canucks,1,5,Winnipeg Jets,2022-12-17,-1.00,-4,3.00
182 | 181,St. Louis Blues,6,2,Anaheim Ducks,2022-11-19,1.00,4,3.00
183 | 182,Ottawa Senators,0,3,Nashville Predators,2023-01-09,-0.02,-3,2.98
184 | 183,Winnipeg Jets,4,0,St. Louis Blues,2022-10-24,1.02,4,2.98
185 | 184,New York Islanders,0,2,Arizona Coyotes,2022-11-10,0.98,-2,2.98
186 | 185,Edmonton Oilers,3,4,Anaheim Ducks,2022-12-17,1.98,-1,2.98
187 | 186,Edmonton Oilers,0,2,St. Louis Blues,2022-10-22,0.98,-2,2.98
188 | 187,Columbus Blue Jackets,4,3,Carolina Hurricanes,2023-01-07,-1.95,1,2.95
189 | 188,Nashville Predators,6,4,New Jersey Devils,2023-01-26,-0.95,2,2.95
190 | 189,Toronto Maple Leafs,4,1,Tampa Bay Lightning,2022-12-20,0.06,3,2.94
191 | 190,Anaheim Ducks,1,7,Boston Bruins,2023-01-08,-3.07,-6,2.93
192 | 191,Montréal Canadiens,2,6,Florida Panthers,2023-01-19,-1.07,-4,2.93
193 | 192,Edmonton Oilers,2,5,Seattle Kraken,2023-01-03,-0.08,-3,2.92
194 | 193,Ottawa Senators,4,2,Dallas Stars,2022-10-24,-0.90,2,2.90
195 | 194,Chicago Blackhawks,5,2,Columbus Blue Jackets,2022-12-23,0.10,3,2.90
196 | 195,Arizona Coyotes,3,1,Florida Panthers,2022-11-01,-0.90,2,2.90
197 | 196,Philadelphia Flyers,2,6,Toronto Maple Leafs,2023-01-08,-1.10,-4,2.90
198 | 197,Vancouver Canucks,4,2,Colorado Avalanche,2023-01-05,-0.90,2,2.90
199 | 198,Los Angeles Kings,4,2,Toronto Maple Leafs,2022-10-29,-0.90,2,2.90
200 | 199,Philadelphia Flyers,1,5,Dallas Stars,2022-11-13,-1.11,-4,2.89
201 | 200,Ottawa Senators,4,0,Columbus Blue Jackets,2023-01-03,1.12,4,2.88
202 | 201,Pittsburgh Penguins,6,2,Arizona Coyotes,2022-10-13,1.13,4,2.87
203 | 202,Buffalo Sabres,6,3,Pittsburgh Penguins,2022-11-02,0.13,3,2.87
204 | 203,Boston Bruins,5,1,Colorado Avalanche,2022-12-03,1.16,4,2.84
205 | 204,Seattle Kraken,1,4,Tampa Bay Lightning,2023-01-16,-0.16,-3,2.84
206 | 205,Colorado Avalanche,0,4,Boston Bruins,2022-12-07,-1.16,-4,2.84
207 | 206,Los Angeles Kings,4,2,Tampa Bay Lightning,2022-10-25,-0.84,2,2.84
208 | 207,Minnesota Wild,3,6,Colorado Avalanche,2022-10-17,-0.18,-3,2.82
209 | 208,Arizona Coyotes,0,4,Washington Capitals,2023-01-19,-1.18,-4,2.82
210 | 209,Detroit Red Wings,7,5,Winnipeg Jets,2023-01-10,-0.82,2,2.82
211 | 210,Detroit Red Wings,2,6,New Jersey Devils,2022-10-25,-1.18,-4,2.82
212 | 211,Detroit Red Wings,1,5,New Jersey Devils,2023-01-04,-1.18,-4,2.82
213 | 212,New Jersey Devils,2,4,Florida Panthers,2022-12-17,0.82,-2,2.82
214 | 213,Detroit Red Wings,5,1,Anaheim Ducks,2022-10-23,1.19,4,2.81
215 | 214,Carolina Hurricanes,3,5,Nashville Predators,2023-01-05,0.81,-2,2.81
216 | 215,Montréal Canadiens,3,2,Toronto Maple Leafs,2023-01-21,-1.80,1,2.80
217 | 216,Montréal Canadiens,4,3,Toronto Maple Leafs,2022-10-12,-1.80,1,2.80
218 | 217,Washington Capitals,3,0,Calgary Flames,2022-11-25,0.20,3,2.80
219 | 218,Edmonton Oilers,6,3,Pittsburgh Penguins,2022-10-24,0.20,3,2.80
220 | 219,Detroit Red Wings,3,6,Ottawa Senators,2022-12-17,-0.21,-3,2.79
221 | 220,Philadelphia Flyers,1,4,Ottawa Senators,2022-11-12,-0.21,-3,2.79
222 | 221,Pittsburgh Penguins,1,4,Winnipeg Jets,2023-01-13,-0.23,-3,2.77
223 | 222,Edmonton Oilers,5,2,Minnesota Wild,2022-12-09,0.24,3,2.76
224 | 223,Philadelphia Flyers,5,3,Colorado Avalanche,2022-12-05,-0.73,2,2.73
225 | 224,New Jersey Devils,4,6,New York Islanders,2022-12-09,0.73,-2,2.73
226 | 225,Toronto Maple Leafs,4,1,Winnipeg Jets,2023-01-19,0.27,3,2.73
227 | 226,Winnipeg Jets,1,4,Toronto Maple Leafs,2022-10-22,-0.27,-3,2.73
228 | 227,Florida Panthers,4,0,Columbus Blue Jackets,2022-12-13,1.28,4,2.72
229 | 228,Anaheim Ducks,3,2,Vegas Golden Knights,2022-12-28,-1.72,1,2.72
230 | 229,Dallas Stars,4,1,Winnipeg Jets,2022-10-17,0.28,3,2.72
231 | 230,Calgary Flames,3,6,Buffalo Sabres,2022-10-20,-0.29,-3,2.71
232 | 231,Calgary Flames,1,4,Colorado Avalanche,2023-01-18,-0.29,-3,2.71
233 | 232,Dallas Stars,5,0,Anaheim Ducks,2022-12-01,2.30,5,2.70
234 | 233,Edmonton Oilers,2,3,Columbus Blue Jackets,2023-01-25,1.70,-1,2.70
235 | 234,Boston Bruins,2,3,Los Angeles Kings,2022-12-15,1.69,-1,2.69
236 | 235,Chicago Blackhawks,5,4,Seattle Kraken,2022-10-23,-1.68,1,2.68
237 | 236,Ottawa Senators,3,2,Boston Bruins,2022-12-27,-1.68,1,2.68
238 | 237,Vegas Golden Knights,4,1,Ottawa Senators,2022-11-23,0.33,3,2.67
239 | 238,Chicago Blackhawks,2,5,San Jose Sharks,2023-01-01,-0.34,-3,2.66
240 | 239,Washington Capitals,1,3,Philadelphia Flyers,2023-01-14,0.65,-2,2.65
241 | 240,Philadelphia Flyers,5,3,Washington Capitals,2023-01-11,-0.65,2,2.65
242 | 241,Detroit Red Wings,3,1,Washington Capitals,2022-11-03,-0.64,2,2.64
243 | 242,New York Rangers,4,1,Vegas Golden Knights,2023-01-27,0.36,3,2.64
244 | 243,Toronto Maple Leafs,5,2,Buffalo Sabres,2022-11-19,0.37,3,2.63
245 | 244,Columbus Blue Jackets,3,6,Arizona Coyotes,2022-10-25,-0.38,-3,2.62
246 | 245,St. Louis Blues,3,5,Chicago Blackhawks,2023-01-21,0.62,-2,2.62
247 | 246,Tampa Bay Lightning,6,3,Washington Capitals,2022-11-13,0.39,3,2.61
248 | 247,New Jersey Devils,5,2,Edmonton Oilers,2022-11-21,0.39,3,2.61
249 | 248,Pittsburgh Penguins,4,1,Ottawa Senators,2023-01-20,0.39,3,2.61
250 | 249,Anaheim Ducks,1,5,Ottawa Senators,2022-11-25,-1.40,-4,2.60
251 | 250,New York Rangers,1,2,Montréal Canadiens,2023-01-15,1.60,-1,2.60
252 | 251,Edmonton Oilers,1,3,Los Angeles Kings,2022-11-16,0.60,-2,2.60
253 | 252,Dallas Stars,4,5,San Jose Sharks,2022-11-11,1.58,-1,2.58
254 | 253,Nashville Predators,0,3,Washington Capitals,2022-10-29,-0.42,-3,2.58
255 | 254,Seattle Kraken,4,1,New York Islanders,2023-01-01,0.42,3,2.58
256 | 255,San Jose Sharks,4,3,Toronto Maple Leafs,2022-10-27,-1.57,1,2.57
257 | 256,Vegas Golden Knights,3,1,Toronto Maple Leafs,2022-10-24,-0.56,2,2.56
258 | 257,Philadelphia Flyers,2,5,Calgary Flames,2022-11-21,-0.44,-3,2.56
259 | 258,New York Islanders,5,2,Philadelphia Flyers,2022-11-26,0.45,3,2.55
260 | 259,Chicago Blackhawks,4,3,Buffalo Sabres,2023-01-17,-1.54,1,2.54
261 | 260,Chicago Blackhawks,3,7,Washington Capitals,2022-12-13,-1.46,-4,2.54
262 | 261,Winnipeg Jets,5,2,Florida Panthers,2022-12-06,0.46,3,2.54
263 | 262,Chicago Blackhawks,3,2,Colorado Avalanche,2023-01-12,-1.54,1,2.54
264 | 263,San Jose Sharks,4,7,Detroit Red Wings,2022-11-17,-0.47,-3,2.53
265 | 264,Florida Panthers,4,3,Boston Bruins,2023-01-28,-1.52,1,2.52
266 | 265,Ottawa Senators,3,1,Buffalo Sabres,2023-01-01,-0.52,2,2.52
267 | 266,Carolina Hurricanes,5,2,Minnesota Wild,2023-01-19,0.49,3,2.51
268 | 267,Pittsburgh Penguins,1,4,Toronto Maple Leafs,2022-11-26,-0.50,-3,2.50
269 | 268,Toronto Maple Leafs,2,4,Pittsburgh Penguins,2022-11-11,0.50,-2,2.50
270 | 269,Pittsburgh Penguins,2,5,Toronto Maple Leafs,2022-11-15,-0.50,-3,2.50
271 | 270,Florida Panthers,2,5,Seattle Kraken,2022-12-11,-0.51,-3,2.49
272 | 271,Buffalo Sabres,4,1,Ottawa Senators,2022-10-13,0.52,3,2.48
273 | 272,Boston Bruins,7,3,Florida Panthers,2022-12-19,1.52,4,2.48
274 | 273,Detroit Red Wings,1,4,Vegas Golden Knights,2022-12-03,-0.53,-3,2.47
275 | 274,Philadelphia Flyers,3,1,New York Islanders,2022-11-29,-0.45,2,2.45
276 | 275,Minnesota Wild,4,1,Detroit Red Wings,2022-12-14,0.55,3,2.45
277 | 276,Edmonton Oilers,7,4,Nashville Predators,2022-11-01,0.56,3,2.44
278 | 277,Minnesota Wild,1,4,Dallas Stars,2022-12-29,-0.56,-3,2.44
279 | 278,Columbus Blue Jackets,5,3,San Jose Sharks,2023-01-21,-0.44,2,2.44
280 | 279,Nashville Predators,3,6,Edmonton Oilers,2022-12-13,-0.56,-3,2.44
281 | 280,Columbus Blue Jackets,2,6,Washington Capitals,2023-01-05,-1.56,-4,2.44
282 | 281,Montréal Canadiens,0,4,Seattle Kraken,2023-01-09,-1.57,-4,2.43
283 | 282,Buffalo Sabres,2,3,Montréal Canadiens,2022-10-27,1.43,-1,2.43
284 | 283,New York Islanders,0,3,Carolina Hurricanes,2022-12-10,-0.59,-3,2.41
285 | 284,New York Islanders,2,5,Carolina Hurricanes,2023-01-21,-0.59,-3,2.41
286 | 285,Tampa Bay Lightning,4,1,Calgary Flames,2022-11-17,0.59,3,2.41
287 | 286,Philadelphia Flyers,1,4,Pittsburgh Penguins,2022-11-25,-0.60,-3,2.40
288 | 287,Edmonton Oilers,7,3,Chicago Blackhawks,2023-01-28,1.60,4,2.40
289 | 288,Ottawa Senators,4,6,Vancouver Canucks,2022-11-08,0.38,-2,2.38
290 | 289,Chicago Blackhawks,2,5,St. Louis Blues,2022-11-16,-0.62,-3,2.38
291 | 290,St. Louis Blues,1,4,Calgary Flames,2023-01-12,-0.63,-3,2.37
292 | 291,Dallas Stars,4,0,Arizona Coyotes,2023-01-21,1.64,4,2.36
293 | 292,Vancouver Canucks,5,2,Chicago Blackhawks,2023-01-24,0.64,3,2.36
294 | 293,Winnipeg Jets,4,0,Chicago Blackhawks,2022-11-05,1.64,4,2.36
295 | 294,New York Rangers,2,3,San Jose Sharks,2022-10-20,1.36,-1,2.36
296 | 295,Columbus Blue Jackets,1,5,Colorado Avalanche,2022-11-05,-1.64,-4,2.36
297 | 296,St. Louis Blues,2,5,New York Islanders,2022-11-03,-0.64,-3,2.36
298 | 297,Philadelphia Flyers,1,4,Washington Capitals,2022-12-07,-0.65,-3,2.35
299 | 298,Boston Bruins,3,4,Vegas Golden Knights,2022-12-05,1.35,-1,2.35
300 | 299,Toronto Maple Leafs,5,2,New York Islanders,2023-01-23,0.65,3,2.35
301 | 300,Dallas Stars,5,2,New York Islanders,2022-11-19,0.66,3,2.34
302 | 301,San Jose Sharks,2,5,Los Angeles Kings,2022-11-25,-0.66,-3,2.34
303 | 302,Los Angeles Kings,4,2,Vegas Golden Knights,2022-12-27,-0.34,2,2.34
304 | 303,Tampa Bay Lightning,4,1,Florida Panthers,2022-12-10,0.67,3,2.33
305 | 304,Los Angeles Kings,1,4,Seattle Kraken,2022-10-13,-0.67,-3,2.33
306 | 305,Ottawa Senators,5,2,San Jose Sharks,2022-12-03,0.68,3,2.32
307 | 306,Nashville Predators,4,1,San Jose Sharks,2022-10-07,0.70,3,2.30
308 | 307,Montréal Canadiens,3,2,Pittsburgh Penguins,2022-10-17,-1.30,1,2.30
309 | 308,Boston Bruins,6,1,Chicago Blackhawks,2022-11-19,2.70,5,2.30
310 | 309,Montréal Canadiens,5,4,Pittsburgh Penguins,2022-11-12,-1.30,1,2.30
311 | 310,Calgary Flames,5,3,Colorado Avalanche,2022-10-13,-0.29,2,2.29
312 | 311,Toronto Maple Leafs,5,6,St. Louis Blues,2023-01-03,1.29,-1,2.29
313 | 312,Detroit Red Wings,3,0,Montréal Canadiens,2022-10-14,0.71,3,2.29
314 | 313,Vancouver Canucks,0,3,Minnesota Wild,2022-12-10,-0.72,-3,2.28
315 | 314,St. Louis Blues,5,2,Columbus Blue Jackets,2022-10-15,0.72,3,2.28
316 | 315,Vegas Golden Knights,4,0,Anaheim Ducks,2022-10-28,1.72,4,2.28
317 | 316,Columbus Blue Jackets,3,5,Anaheim Ducks,2023-01-19,0.28,-2,2.28
318 | 317,Chicago Blackhawks,2,0,Arizona Coyotes,2023-01-06,-0.28,2,2.28
319 | 318,New York Islanders,1,4,New Jersey Devils,2022-10-20,-0.73,-3,2.27
320 | 319,Buffalo Sabres,6,3,Detroit Red Wings,2022-12-29,0.73,3,2.27
321 | 320,Colorado Avalanche,6,3,Detroit Red Wings,2023-01-16,0.73,3,2.27
322 | 321,St. Louis Blues,2,5,Minnesota Wild,2022-12-31,-0.74,-3,2.26
323 | 322,Vancouver Canucks,5,2,Columbus Blue Jackets,2023-01-27,0.74,3,2.26
324 | 323,Montréal Canadiens,1,5,Tampa Bay Lightning,2022-12-17,-1.74,-4,2.26
325 | 324,Chicago Blackhawks,4,3,Calgary Flames,2023-01-08,-1.25,1,2.25
326 | 325,Edmonton Oilers,6,4,Carolina Hurricanes,2022-10-20,-0.25,2,2.25
327 | 326,Edmonton Oilers,5,3,Tampa Bay Lightning,2023-01-19,-0.24,2,2.24
328 | 327,Minnesota Wild,5,3,Edmonton Oilers,2022-12-01,-0.24,2,2.24
329 | 328,Nashville Predators,1,3,Philadelphia Flyers,2022-10-22,0.23,-2,2.23
330 | 329,Carolina Hurricanes,3,4,Vancouver Canucks,2023-01-15,1.22,-1,2.22
331 | 330,Columbus Blue Jackets,6,4,Montréal Canadiens,2022-11-17,-0.21,2,2.21
332 | 331,Winnipeg Jets,4,2,Tampa Bay Lightning,2023-01-06,-0.21,2,2.21
333 | 332,Detroit Red Wings,4,2,Ottawa Senators,2022-12-31,-0.21,2,2.21
334 | 333,New York Rangers,3,1,Toronto Maple Leafs,2022-12-15,-0.21,2,2.21
335 | 334,Los Angeles Kings,2,4,Philadelphia Flyers,2022-12-31,0.20,-2,2.20
336 | 335,Anaheim Ducks,3,2,Detroit Red Wings,2022-11-15,-1.19,1,2.19
337 | 336,Tampa Bay Lightning,5,2,Nashville Predators,2022-12-08,0.81,3,2.19
338 | 337,New Jersey Devils,1,2,Philadelphia Flyers,2022-12-15,1.18,-1,2.18
339 | 338,Florida Panthers,5,3,Minnesota Wild,2023-01-21,-0.18,2,2.18
340 | 339,Washington Capitals,2,3,Arizona Coyotes,2022-11-05,1.18,-1,2.18
341 | 340,Tampa Bay Lightning,5,2,Los Angeles Kings,2023-01-28,0.84,3,2.16
342 | 341,Calgary Flames,1,2,Montréal Canadiens,2022-12-01,1.15,-1,2.15
343 | 342,New York Rangers,5,3,Carolina Hurricanes,2023-01-03,-0.15,2,2.15
344 | 343,Montréal Canadiens,2,1,Calgary Flames,2022-12-12,-1.15,1,2.15
345 | 344,Boston Bruins,3,4,Buffalo Sabres,2022-12-31,1.15,-1,2.15
346 | 345,New York Rangers,3,1,Tampa Bay Lightning,2022-10-11,-0.14,2,2.14
347 | 346,Pittsburgh Penguins,3,1,Buffalo Sabres,2022-12-10,-0.13,2,2.13
348 | 347,Nashville Predators,1,4,Dallas Stars,2022-10-13,-0.88,-3,2.12
349 | 348,Boston Bruins,5,1,Detroit Red Wings,2022-10-27,1.88,4,2.12
350 | 349,Montréal Canadiens,1,5,New Jersey Devils,2022-11-15,-1.89,-4,2.11
351 | 350,Calgary Flames,5,3,Minnesota Wild,2022-12-07,-0.11,2,2.11
352 | 351,Philadelphia Flyers,3,6,New York Rangers,2022-12-17,-0.89,-3,2.11
353 | 352,Columbus Blue Jackets,6,5,Los Angeles Kings,2022-12-11,-1.11,1,2.11
354 | 353,Vancouver Canucks,1,4,Colorado Avalanche,2023-01-20,-0.90,-3,2.10
355 | 354,Washington Capitals,2,4,Minnesota Wild,2023-01-17,0.10,-2,2.10
356 | 355,New York Islanders,1,3,Florida Panthers,2022-10-13,0.09,-2,2.09
357 | 356,San Jose Sharks,2,5,Calgary Flames,2022-12-18,-0.91,-3,2.09
358 | 357,Dallas Stars,5,2,Los Angeles Kings,2022-11-01,0.92,3,2.08
359 | 358,New York Islanders,5,2,San Jose Sharks,2022-10-18,0.92,3,2.08
360 | 359,Nashville Predators,6,3,Montréal Canadiens,2023-01-03,0.93,3,2.07
361 | 360,Boston Bruins,2,1,Anaheim Ducks,2022-10-20,3.07,1,2.07
362 | 361,Edmonton Oilers,2,4,Buffalo Sabres,2022-10-18,0.06,-2,2.06
363 | 362,Seattle Kraken,3,4,St. Louis Blues,2022-10-19,1.06,-1,2.06
364 | 363,Seattle Kraken,4,5,Vancouver Canucks,2022-10-27,1.05,-1,2.05
365 | 364,Columbus Blue Jackets,2,6,Carolina Hurricanes,2023-01-12,-1.95,-4,2.05
366 | 365,Vancouver Canucks,6,5,Seattle Kraken,2022-12-22,-1.05,1,2.05
367 | 366,Tampa Bay Lightning,2,3,Philadelphia Flyers,2022-10-18,1.04,-1,2.04
368 | 367,Anaheim Ducks,2,6,Edmonton Oilers,2023-01-11,-1.98,-4,2.02
369 | 368,Minnesota Wild,2,3,San Jose Sharks,2022-11-13,1.02,-1,2.02
370 | 369,Los Angeles Kings,2,5,New Jersey Devils,2023-01-14,-0.99,-3,2.01
371 | 370,Chicago Blackhawks,2,1,Los Angeles Kings,2022-11-03,-1.01,1,2.01
372 | 371,New York Rangers,2,5,Boston Bruins,2022-11-03,-0.99,-3,2.01
373 | 372,Colorado Avalanche,2,4,Buffalo Sabres,2022-12-15,-0.00,-2,2.00
374 | 373,Winnipeg Jets,7,4,Vancouver Canucks,2023-01-08,1.00,3,2.00
375 | 374,Buffalo Sabres,4,6,Colorado Avalanche,2022-12-01,0.00,-2,2.00
376 | 375,Vancouver Canucks,8,5,Anaheim Ducks,2022-11-03,1.01,3,1.99
377 | 376,St. Louis Blues,2,5,Winnipeg Jets,2022-12-08,-1.02,-3,1.98
378 | 377,Arizona Coyotes,5,4,New York Islanders,2022-12-16,-0.98,1,1.98
379 | 378,Edmonton Oilers,3,4,St. Louis Blues,2022-12-15,0.98,-1,1.98
380 | 379,Nashville Predators,5,3,Los Angeles Kings,2023-01-21,0.03,2,1.97
381 | 380,Philadelphia Flyers,1,4,Tampa Bay Lightning,2022-12-01,-1.04,-3,1.96
382 | 381,Minnesota Wild,4,6,Pittsburgh Penguins,2022-11-17,-0.05,-2,1.95
383 | 382,New Jersey Devils,3,4,Nashville Predators,2022-12-01,0.95,-1,1.95
384 | 383,Arizona Coyotes,2,4,San Jose Sharks,2023-01-10,-0.06,-2,1.94
385 | 384,Seattle Kraken,5,2,St. Louis Blues,2022-12-20,1.06,3,1.94
386 | 385,Carolina Hurricanes,1,3,Toronto Maple Leafs,2022-11-06,-0.06,-2,1.94
387 | 386,Vegas Golden Knights,4,1,Arizona Coyotes,2022-11-17,1.07,3,1.93
388 | 387,Montréal Canadiens,4,3,Nashville Predators,2023-01-12,-0.93,1,1.93
389 | 388,Vegas Golden Knights,5,2,Arizona Coyotes,2022-12-21,1.07,3,1.93
390 | 389,Detroit Red Wings,3,4,Columbus Blue Jackets,2023-01-14,0.92,-1,1.92
391 | 390,Los Angeles Kings,3,2,Dallas Stars,2023-01-03,-0.92,1,1.92
392 | 391,Colorado Avalanche,2,3,St. Louis Blues,2022-11-14,0.92,-1,1.92
393 | 392,Buffalo Sabres,4,5,Vancouver Canucks,2022-11-15,0.91,-1,1.91
394 | 393,Columbus Blue Jackets,5,4,Philadelphia Flyers,2022-11-15,-0.91,1,1.91
395 | 394,Toronto Maple Leafs,4,1,Detroit Red Wings,2023-01-07,1.09,3,1.91
396 | 395,Toronto Maple Leafs,5,2,Philadelphia Flyers,2022-11-02,1.10,3,1.90
397 | 396,Buffalo Sabres,2,4,Winnipeg Jets,2023-01-12,-0.10,-2,1.90
398 | 397,New York Islanders,1,3,Minnesota Wild,2023-01-12,-0.10,-2,1.90
399 | 398,Colorado Avalanche,3,4,Vancouver Canucks,2022-11-23,0.90,-1,1.90
400 | 399,New York Rangers,2,3,Detroit Red Wings,2022-11-06,0.89,-1,1.89
401 | 400,Arizona Coyotes,1,4,Pittsburgh Penguins,2023-01-08,-1.13,-3,1.87
402 | 401,Carolina Hurricanes,3,5,New Jersey Devils,2023-01-10,-0.14,-2,1.86
403 | 402,Tampa Bay Lightning,3,2,Boston Bruins,2023-01-26,-0.85,1,1.85
404 | 403,Florida Panthers,5,3,Ottawa Senators,2022-10-29,0.16,2,1.84
405 | 404,St. Louis Blues,5,4,Washington Capitals,2022-11-17,-0.83,1,1.83
406 | 405,Vegas Golden Knights,4,2,Florida Panthers,2023-01-12,0.17,2,1.83
407 | 406,Chicago Blackhawks,4,3,Detroit Red Wings,2022-10-21,-0.81,1,1.81
408 | 407,Philadelphia Flyers,5,2,Anaheim Ducks,2023-01-17,1.19,3,1.81
409 | 408,Anaheim Ducks,1,4,Philadelphia Flyers,2023-01-02,-1.19,-3,1.81
410 | 409,Buffalo Sabres,6,3,San Jose Sharks,2022-12-04,1.20,3,1.80
411 | 410,Toronto Maple Leafs,2,1,Boston Bruins,2022-11-05,-0.79,1,1.79
412 | 411,Columbus Blue Jackets,1,3,Montréal Canadiens,2022-11-23,-0.21,-2,1.79
413 | 412,Vancouver Canucks,2,5,Tampa Bay Lightning,2023-01-18,-1.21,-3,1.79
414 | 413,Tampa Bay Lightning,5,2,St. Louis Blues,2022-11-25,1.22,3,1.78
415 | 414,Florida Panthers,2,4,Pittsburgh Penguins,2022-12-15,-0.23,-2,1.77
416 | 415,Washington Capitals,2,5,Boston Bruins,2022-10-12,-1.24,-3,1.76
417 | 416,Ottawa Senators,2,4,New York Islanders,2022-11-14,-0.24,-2,1.76
418 | 417,Minnesota Wild,4,1,Montréal Canadiens,2022-11-01,1.25,3,1.75
419 | 418,Columbus Blue Jackets,4,3,Vancouver Canucks,2022-10-18,-0.74,1,1.74
420 | 419,New York Islanders,3,0,Chicago Blackhawks,2022-12-04,1.26,3,1.74
421 | 420,Arizona Coyotes,2,5,Buffalo Sabres,2022-12-17,-1.26,-3,1.74
422 | 421,Arizona Coyotes,2,1,Los Angeles Kings,2022-12-23,-0.73,1,1.73
423 | 422,Seattle Kraken,3,1,Pittsburgh Penguins,2022-10-29,0.27,2,1.73
424 | 423,Anaheim Ducks,5,4,San Jose Sharks,2023-01-06,-0.72,1,1.72
425 | 424,Vegas Golden Knights,2,3,St. Louis Blues,2022-11-12,0.72,-1,1.72
426 | 425,San Jose Sharks,4,5,Anaheim Ducks,2022-11-05,0.72,-1,1.72
427 | 426,St. Louis Blues,5,3,San Jose Sharks,2022-11-10,0.28,2,1.72
428 | 427,San Jose Sharks,5,6,Anaheim Ducks,2022-11-01,0.72,-1,1.72
429 | 428,Detroit Red Wings,2,3,Montréal Canadiens,2022-11-08,0.71,-1,1.71
430 | 429,New York Rangers,3,5,New Jersey Devils,2022-11-28,-0.29,-2,1.71
431 | 430,Montréal Canadiens,5,4,Philadelphia Flyers,2022-11-19,-0.70,1,1.70
432 | 431,Tampa Bay Lightning,5,3,Buffalo Sabres,2022-11-05,0.30,2,1.70
433 | 432,St. Louis Blues,1,4,Dallas Stars,2022-11-28,-1.30,-3,1.70
434 | 433,Carolina Hurricanes,5,3,Buffalo Sabres,2022-11-04,0.31,2,1.69
435 | 434,New York Rangers,2,3,Ottawa Senators,2022-12-02,0.68,-1,1.68
436 | 435,Dallas Stars,5,6,Calgary Flames,2023-01-14,0.67,-1,1.67
437 | 436,Vegas Golden Knights,2,4,Seattle Kraken,2022-11-25,-0.33,-2,1.67
438 | 437,Anaheim Ducks,2,1,Arizona Coyotes,2023-01-28,-0.66,1,1.66
439 | 438,Minnesota Wild,4,2,Ottawa Senators,2022-12-18,0.34,2,1.66
440 | 439,Edmonton Oilers,4,2,New York Islanders,2023-01-05,0.34,2,1.66
441 | 440,Nashville Predators,2,1,New York Rangers,2022-11-12,-0.66,1,1.66
442 | 441,Ottawa Senators,2,4,Minnesota Wild,2022-10-27,-0.34,-2,1.66
443 | 442,Seattle Kraken,8,5,San Jose Sharks,2022-11-23,1.34,3,1.66
444 | 443,Boston Bruins,4,0,San Jose Sharks,2023-01-22,2.35,4,1.65
445 | 444,Toronto Maple Leafs,2,3,New York Islanders,2022-11-21,0.65,-1,1.65
446 | 445,Chicago Blackhawks,1,4,Vegas Golden Knights,2022-12-15,-1.35,-3,1.65
447 | 446,Minnesota Wild,4,1,Chicago Blackhawks,2022-12-16,1.36,3,1.64
448 | 447,Vancouver Canucks,2,5,New Jersey Devils,2022-11-01,-1.36,-3,1.64
449 | 448,Anaheim Ducks,2,6,New Jersey Devils,2023-01-13,-2.37,-4,1.63
450 | 449,St. Louis Blues,4,3,Calgary Flames,2023-01-10,-0.63,1,1.63
451 | 450,Los Angeles Kings,4,1,Anaheim Ducks,2022-12-20,1.38,3,1.62
452 | 451,Calgary Flames,3,4,Vancouver Canucks,2022-12-14,0.62,-1,1.62
453 | 452,Calgary Flames,3,2,Carolina Hurricanes,2022-10-22,-0.60,1,1.60
454 | 453,Ottawa Senators,3,0,Anaheim Ducks,2022-12-12,1.40,3,1.60
455 | 454,Nashville Predators,2,1,Winnipeg Jets,2023-01-24,-0.60,1,1.60
456 | 455,Pittsburgh Penguins,4,5,Detroit Red Wings,2022-12-28,0.59,-1,1.59
457 | 456,New York Rangers,4,1,Arizona Coyotes,2022-11-13,1.42,3,1.58
458 | 457,New York Islanders,1,4,Boston Bruins,2023-01-18,-1.43,-3,1.57
459 | 458,Florida Panthers,2,4,Edmonton Oilers,2022-11-12,-0.43,-2,1.57
460 | 459,New York Rangers,5,3,New York Islanders,2022-12-22,0.44,2,1.56
461 | 460,Toronto Maple Leafs,3,4,Vegas Golden Knights,2022-11-08,0.56,-1,1.56
462 | 461,Nashville Predators,4,3,Edmonton Oilers,2022-12-19,-0.56,1,1.56
463 | 462,Dallas Stars,5,6,Minnesota Wild,2022-12-04,0.56,-1,1.56
464 | 463,Florida Panthers,4,5,St. Louis Blues,2022-11-26,0.55,-1,1.55
465 | 464,New York Islanders,2,0,Detroit Red Wings,2023-01-27,0.45,2,1.55
466 | 465,Buffalo Sabres,1,3,New Jersey Devils,2022-11-25,-0.45,-2,1.55
467 | 466,Detroit Red Wings,2,1,Minnesota Wild,2022-10-29,-0.55,1,1.55
468 | 467,Arizona Coyotes,4,3,Detroit Red Wings,2023-01-17,-0.54,1,1.54
469 | 468,Arizona Coyotes,5,4,Philadelphia Flyers,2022-12-11,-0.53,1,1.53
470 | 469,Vegas Golden Knights,2,3,Detroit Red Wings,2023-01-19,0.53,-1,1.53
471 | 470,Dallas Stars,2,0,Washington Capitals,2022-10-27,0.47,2,1.53
472 | 471,Colorado Avalanche,4,5,Los Angeles Kings,2022-12-29,0.53,-1,1.53
473 | 472,Montréal Canadiens,5,4,St. Louis Blues,2023-01-07,-0.52,1,1.52
474 | 473,Pittsburgh Penguins,2,1,Dallas Stars,2022-12-12,-0.51,1,1.51
475 | 474,Tampa Bay Lightning,4,2,Minnesota Wild,2023-01-24,0.49,2,1.51
476 | 475,Colorado Avalanche,3,1,Nashville Predators,2022-12-17,0.50,2,1.50
477 | 476,Nashville Predators,3,5,Buffalo Sabres,2023-01-14,-0.50,-2,1.50
478 | 477,Colorado Avalanche,5,3,Nashville Predators,2022-11-10,0.50,2,1.50
479 | 478,Minnesota Wild,2,1,Carolina Hurricanes,2022-11-19,-0.49,1,1.49
480 | 479,Columbus Blue Jackets,3,6,Pittsburgh Penguins,2022-10-22,-1.51,-3,1.49
481 | 480,Pittsburgh Penguins,4,1,Columbus Blue Jackets,2022-12-06,1.51,3,1.49
482 | 481,Florida Panthers,3,5,New York Rangers,2023-01-01,-0.53,-2,1.47
483 | 482,Colorado Avalanche,5,2,Chicago Blackhawks,2022-10-12,1.54,3,1.46
484 | 483,Washington Capitals,3,4,Ottawa Senators,2022-12-29,0.44,-1,1.44
485 | 484,New York Rangers,3,4,New York Islanders,2022-11-08,0.44,-1,1.44
486 | 485,Seattle Kraken,2,3,Calgary Flames,2022-12-28,0.43,-1,1.43
487 | 486,Dallas Stars,5,2,San Jose Sharks,2022-12-31,1.58,3,1.42
488 | 487,Pittsburgh Penguins,2,4,New Jersey Devils,2022-12-30,-0.58,-2,1.42
489 | 488,Washington Capitals,2,3,Nashville Predators,2023-01-06,0.42,-1,1.42
490 | 489,Tampa Bay Lightning,5,3,New York Islanders,2022-10-22,0.58,2,1.42
491 | 490,St. Louis Blues,1,0,Nashville Predators,2022-12-12,-0.42,1,1.42
492 | 491,St. Louis Blues,2,1,Ottawa Senators,2023-01-16,-0.40,1,1.40
493 | 492,Montréal Canadiens,1,4,New York Rangers,2023-01-05,-1.60,-3,1.40
494 | 493,Ottawa Senators,5,4,Pittsburgh Penguins,2023-01-18,-0.39,1,1.39
495 | 494,Calgary Flames,3,2,Winnipeg Jets,2022-11-12,-0.39,1,1.39
496 | 495,Dallas Stars,2,3,Colorado Avalanche,2022-11-21,0.38,-1,1.38
497 | 496,St. Louis Blues,3,1,Chicago Blackhawks,2022-12-29,0.62,2,1.38
498 | 497,Dallas Stars,2,3,Buffalo Sabres,2023-01-23,0.38,-1,1.38
499 | 498,Philadelphia Flyers,4,3,Florida Panthers,2022-10-27,-0.37,1,1.37
500 | 499,Buffalo Sabres,3,4,Florida Panthers,2022-10-15,0.37,-1,1.37
501 | 500,Los Angeles Kings,4,6,Winnipeg Jets,2022-10-27,-0.63,-2,1.37
502 | 501,Colorado Avalanche,4,5,Florida Panthers,2023-01-10,0.36,-1,1.36
503 | 502,Colorado Avalanche,6,3,Columbus Blue Jackets,2022-11-04,1.64,3,1.36
504 | 503,Edmonton Oilers,3,4,Calgary Flames,2022-10-15,0.35,-1,1.35
505 | 504,Los Angeles Kings,1,0,Minnesota Wild,2022-11-08,-0.35,1,1.35
506 | 505,Minnesota Wild,6,7,Los Angeles Kings,2022-10-15,0.35,-1,1.35
507 | 506,Chicago Blackhawks,5,8,Seattle Kraken,2023-01-14,-1.68,-3,1.32
508 | 507,Nashville Predators,2,1,Minnesota Wild,2022-11-15,-0.32,1,1.32
509 | 508,Ottawa Senators,1,3,New York Rangers,2022-11-30,-0.68,-2,1.32
510 | 509,Seattle Kraken,0,1,Minnesota Wild,2022-11-11,0.32,-1,1.32
511 | 510,Carolina Hurricanes,2,3,Colorado Avalanche,2022-11-17,0.31,-1,1.31
512 | 511,Los Angeles Kings,2,5,Boston Bruins,2023-01-05,-1.69,-3,1.31
513 | 512,Seattle Kraken,4,3,New Jersey Devils,2023-01-19,-0.31,1,1.31
514 | 513,Los Angeles Kings,3,5,New York Rangers,2022-11-22,-0.70,-2,1.30
515 | 514,New Jersey Devils,1,3,Boston Bruins,2022-12-28,-0.70,-2,1.30
516 | 515,Pittsburgh Penguins,3,2,New York Rangers,2022-12-20,-0.29,1,1.29
517 | 516,New York Rangers,4,3,New Jersey Devils,2022-12-12,-0.29,1,1.29
518 | 517,Winnipeg Jets,5,6,Vegas Golden Knights,2022-12-13,0.29,-1,1.29
519 | 518,Vegas Golden Knights,2,1,Winnipeg Jets,2022-10-30,-0.29,1,1.29
520 | 519,New York Islanders,5,4,Colorado Avalanche,2022-10-29,-0.28,1,1.28
521 | 520,Dallas Stars,4,5,Winnipeg Jets,2022-11-25,0.28,-1,1.28
522 | 521,Los Angeles Kings,5,3,Arizona Coyotes,2022-12-01,0.73,2,1.27
523 | 522,Arizona Coyotes,3,5,Ottawa Senators,2023-01-12,-0.74,-2,1.26
524 | 523,Anaheim Ducks,1,4,Minnesota Wild,2022-11-09,-1.74,-3,1.26
525 | 524,Tampa Bay Lightning,4,1,Montréal Canadiens,2022-12-28,1.74,3,1.26
526 | 525,Anaheim Ducks,1,4,Minnesota Wild,2022-12-21,-1.74,-3,1.26
527 | 526,Los Angeles Kings,4,3,Calgary Flames,2022-12-22,-0.25,1,1.25
528 | 527,Florida Panthers,4,6,Dallas Stars,2022-11-17,-0.75,-2,1.25
529 | 528,Ottawa Senators,2,1,New York Islanders,2023-01-25,-0.24,1,1.24
530 | 529,Minnesota Wild,2,1,Edmonton Oilers,2022-12-12,-0.24,1,1.24
531 | 530,Tampa Bay Lightning,2,3,Edmonton Oilers,2022-11-08,0.24,-1,1.24
532 | 531,Boston Bruins,3,1,Dallas Stars,2022-10-25,0.77,2,1.23
533 | 532,Nashville Predators,5,4,New York Islanders,2022-11-17,-0.22,1,1.22
534 | 533,New York Rangers,2,1,Dallas Stars,2023-01-12,-0.22,1,1.22
535 | 534,Nashville Predators,2,1,Calgary Flames,2023-01-16,-0.21,1,1.21
536 | 535,Winnipeg Jets,4,3,Carolina Hurricanes,2022-11-21,-0.21,1,1.21
537 | 536,Ottawa Senators,1,2,Philadelphia Flyers,2022-11-05,0.21,-1,1.21
538 | 537,Columbus Blue Jackets,0,4,Boston Bruins,2022-10-28,-2.80,-4,1.20
539 | 538,Colorado Avalanche,2,3,Vegas Golden Knights,2023-01-02,0.19,-1,1.19
540 | 539,Minnesota Wild,3,2,Buffalo Sabres,2023-01-28,-0.18,1,1.18
541 | 540,Washington Capitals,6,4,Vancouver Canucks,2022-10-17,0.82,2,1.18
542 | 541,Florida Panthers,2,4,New Jersey Devils,2022-12-21,-0.82,-2,1.18
543 | 542,Montréal Canadiens,2,5,Dallas Stars,2022-10-22,-1.82,-3,1.18
544 | 543,Florida Panthers,3,4,Los Angeles Kings,2023-01-27,0.17,-1,1.17
545 | 544,Los Angeles Kings,5,4,Florida Panthers,2022-11-05,-0.17,1,1.17
546 | 545,Philadelphia Flyers,3,5,Winnipeg Jets,2023-01-22,-0.83,-2,1.17
547 | 546,Arizona Coyotes,2,3,Montréal Canadiens,2022-12-19,0.17,-1,1.17
548 | 547,New York Rangers,2,3,Colorado Avalanche,2022-10-25,0.16,-1,1.16
549 | 548,Chicago Blackhawks,0,3,Carolina Hurricanes,2022-11-14,-1.85,-3,1.15
550 | 549,Chicago Blackhawks,1,4,Tampa Bay Lightning,2023-01-03,-1.85,-3,1.15
551 | 550,Boston Bruins,3,1,Tampa Bay Lightning,2022-11-29,0.85,2,1.15
552 | 551,Carolina Hurricanes,3,0,Chicago Blackhawks,2022-12-27,1.85,3,1.15
553 | 552,Edmonton Oilers,2,3,Washington Capitals,2022-12-05,0.15,-1,1.15
554 | 553,Tampa Bay Lightning,3,5,Boston Bruins,2022-11-21,-0.85,-2,1.15
555 | 554,Los Angeles Kings,2,4,Carolina Hurricanes,2022-12-03,-0.85,-2,1.15
556 | 555,Washington Capitals,5,4,Edmonton Oilers,2022-11-07,-0.15,1,1.15
557 | 556,New Jersey Devils,4,5,Carolina Hurricanes,2023-01-01,0.14,-1,1.14
558 | 557,Seattle Kraken,1,2,Colorado Avalanche,2023-01-21,0.14,-1,1.14
559 | 558,Buffalo Sabres,3,4,Pittsburgh Penguins,2022-12-09,0.13,-1,1.13
560 | 559,Boston Bruins,4,1,Philadelphia Flyers,2022-11-17,1.89,3,1.11
561 | 560,Washington Capitals,2,3,Vegas Golden Knights,2022-11-01,0.11,-1,1.11
562 | 561,Winnipeg Jets,2,3,Buffalo Sabres,2023-01-26,0.10,-1,1.10
563 | 562,New York Rangers,3,4,Edmonton Oilers,2022-11-26,0.10,-1,1.10
564 | 563,Florida Panthers,5,3,Arizona Coyotes,2023-01-03,0.90,2,1.10
565 | 564,Montréal Canadiens,2,4,Los Angeles Kings,2022-12-10,-0.90,-2,1.10
566 | 565,Calgary Flames,3,2,Vegas Golden Knights,2022-10-18,-0.09,1,1.09
567 | 566,Philadelphia Flyers,5,3,Columbus Blue Jackets,2022-12-20,0.91,2,1.09
568 | 567,New York Islanders,2,1,Vegas Golden Knights,2023-01-28,-0.09,1,1.09
569 | 568,Florida Panthers,3,2,New York Islanders,2022-10-23,-0.09,1,1.09
570 | 569,New Jersey Devils,1,2,Toronto Maple Leafs,2022-11-23,0.08,-1,1.08
571 | 570,Buffalo Sabres,6,3,Anaheim Ducks,2023-01-21,1.92,3,1.08
572 | 571,Colorado Avalanche,4,2,St. Louis Blues,2023-01-28,0.92,2,1.08
573 | 572,Tampa Bay Lightning,5,4,Dallas Stars,2022-11-15,-0.08,1,1.08
574 | 573,Columbus Blue Jackets,2,4,Detroit Red Wings,2022-12-04,-0.92,-2,1.08
575 | 574,St. Louis Blues,3,5,Buffalo Sabres,2023-01-24,-0.92,-2,1.08
576 | 575,Carolina Hurricanes,5,4,Dallas Stars,2022-12-17,-0.07,1,1.07
577 | 576,Dallas Stars,2,3,Carolina Hurricanes,2023-01-25,0.07,-1,1.07
578 | 577,Edmonton Oilers,2,3,Colorado Avalanche,2023-01-07,0.06,-1,1.06
579 | 578,Anaheim Ducks,4,5,Seattle Kraken,2022-11-27,-2.06,-1,1.06
580 | 579,Tampa Bay Lightning,4,3,Toronto Maple Leafs,2022-12-03,-0.06,1,1.06
581 | 580,Carolina Hurricanes,4,1,Columbus Blue Jackets,2022-10-12,1.95,3,1.05
582 | 581,Tampa Bay Lightning,4,1,Columbus Blue Jackets,2022-12-15,1.95,3,1.05
583 | 582,Columbus Blue Jackets,2,5,Tampa Bay Lightning,2022-10-14,-1.95,-3,1.05
584 | 583,Tampa Bay Lightning,6,3,Columbus Blue Jackets,2023-01-10,1.95,3,1.05
585 | 584,Seattle Kraken,2,3,Winnipeg Jets,2022-11-13,0.04,-1,1.04
586 | 585,Edmonton Oilers,5,3,Vancouver Canucks,2022-10-12,0.97,2,1.03
587 | 586,Nashville Predators,3,4,Los Angeles Kings,2022-10-18,0.03,-1,1.03
588 | 587,Vancouver Canucks,2,4,Edmonton Oilers,2023-01-21,-0.97,-2,1.03
589 | 588,Seattle Kraken,3,2,New York Rangers,2022-11-17,-0.02,1,1.02
590 | 589,Winnipeg Jets,3,2,Anaheim Ducks,2022-11-17,2.02,1,1.02
591 | 590,Nashville Predators,2,3,Ottawa Senators,2022-12-10,0.02,-1,1.02
592 | 591,St. Louis Blues,1,3,Edmonton Oilers,2022-10-26,-0.98,-2,1.02
593 | 592,Columbus Blue Jackets,1,2,Dallas Stars,2022-12-19,-2.02,-1,1.02
594 | 593,New York Rangers,1,3,Boston Bruins,2023-01-19,-0.99,-2,1.01
595 | 594,Detroit Red Wings,1,2,Philadelphia Flyers,2023-01-21,0.01,-1,1.01
596 | 595,New Jersey Devils,3,0,Chicago Blackhawks,2022-12-06,1.99,3,1.01
597 | 596,Toronto Maple Leafs,3,2,Dallas Stars,2022-10-20,-0.01,1,1.01
598 | 597,St. Louis Blues,3,1,Anaheim Ducks,2022-11-21,1.00,2,1.00
599 | 598,Winnipeg Jets,4,2,Vancouver Canucks,2022-12-29,1.00,2,1.00
600 | 599,San Jose Sharks,2,4,Vegas Golden Knights,2022-10-25,-1.00,-2,1.00
601 | 600,New York Islanders,4,3,Calgary Flames,2022-11-07,0.01,1,0.99
602 | 601,Tampa Bay Lightning,3,4,Carolina Hurricanes,2022-11-03,-0.01,-1,0.99
603 | 602,Los Angeles Kings,2,3,Ottawa Senators,2022-11-27,-0.01,-1,0.99
604 | 603,Winnipeg Jets,4,2,St. Louis Blues,2023-01-30,1.02,2,0.98
605 | 604,Winnipeg Jets,5,2,Anaheim Ducks,2022-12-04,2.02,3,0.98
606 | 605,Chicago Blackhawks,2,4,Nashville Predators,2022-12-21,-1.04,-2,0.96
607 | 606,Seattle Kraken,3,2,Winnipeg Jets,2022-12-18,0.04,1,0.96
608 | 607,Edmonton Oilers,1,2,Winnipeg Jets,2022-12-31,-0.04,-1,0.96
609 | 608,Washington Capitals,3,2,Pittsburgh Penguins,2023-01-26,0.05,1,0.95
610 | 609,Boston Bruins,5,2,Vancouver Canucks,2022-11-13,2.06,3,0.94
611 | 610,Pittsburgh Penguins,4,3,Vegas Golden Knights,2022-12-01,0.06,1,0.94
612 | 611,San Jose Sharks,3,2,Arizona Coyotes,2022-12-13,0.06,1,0.94
613 | 612,Dallas Stars,2,3,New Jersey Devils,2023-01-27,-0.07,-1,0.93
614 | 613,Colorado Avalanche,3,2,Washington Capitals,2023-01-24,0.08,1,0.92
615 | 614,Toronto Maple Leafs,2,3,New Jersey Devils,2022-11-17,-0.08,-1,0.92
616 | 615,Florida Panthers,4,5,Calgary Flames,2022-11-19,-0.08,-1,0.92
617 | 616,New York Rangers,6,4,St. Louis Blues,2022-12-05,1.08,2,0.92
618 | 617,Detroit Red Wings,2,4,Toronto Maple Leafs,2022-11-28,-1.09,-2,0.91
619 | 618,Washington Capitals,4,5,Buffalo Sabres,2023-01-03,-0.09,-1,0.91
620 | 619,Colorado Avalanche,3,4,Winnipeg Jets,2022-10-19,-0.10,-1,0.90
621 | 620,Chicago Blackhawks,2,3,Montréal Canadiens,2022-11-25,-0.11,-1,0.89
622 | 621,Tampa Bay Lightning,2,1,New York Rangers,2022-12-29,0.14,1,0.86
623 | 622,Colorado Avalanche,2,3,Seattle Kraken,2022-10-21,-0.14,-1,0.86
624 | 623,Buffalo Sabres,3,4,Seattle Kraken,2023-01-10,-0.14,-1,0.86
625 | 624,Buffalo Sabres,1,3,Boston Bruins,2022-11-12,-1.15,-2,0.85
626 | 625,Colorado Avalanche,1,2,New York Rangers,2022-12-09,-0.16,-1,0.84
627 | 626,Pittsburgh Penguins,2,1,Calgary Flames,2022-11-23,0.16,1,0.84
628 | 627,Carolina Hurricanes,3,2,Seattle Kraken,2022-12-15,0.17,1,0.83
629 | 628,Philadelphia Flyers,3,2,Vancouver Canucks,2022-10-15,0.17,1,0.83
630 | 629,Buffalo Sabres,6,5,Minnesota Wild,2023-01-07,0.18,1,0.82
631 | 630,Detroit Red Wings,4,5,Los Angeles Kings,2022-10-17,-0.19,-1,0.81
632 | 631,Los Angeles Kings,4,3,Detroit Red Wings,2022-11-12,0.19,1,0.81
633 | 632,Vegas Golden Knights,2,3,Colorado Avalanche,2022-10-22,-0.19,-1,0.81
634 | 633,New York Islanders,3,4,Washington Capitals,2023-01-16,-0.19,-1,0.81
635 | 634,Boston Bruins,4,2,Columbus Blue Jackets,2022-12-17,2.80,2,0.80
636 | 635,Philadelphia Flyers,3,4,Los Angeles Kings,2023-01-24,-0.20,-1,0.80
637 | 636,Vegas Golden Knights,2,3,Buffalo Sabres,2022-12-19,-0.20,-1,0.80
638 | 637,Toronto Maple Leafs,3,2,New York Rangers,2023-01-25,0.21,1,0.79
639 | 638,Pittsburgh Penguins,4,3,Anaheim Ducks,2023-01-16,1.79,1,0.79
640 | 639,St. Louis Blues,2,4,Tampa Bay Lightning,2023-01-14,-1.22,-2,0.78
641 | 640,Pittsburgh Penguins,7,6,Florida Panthers,2023-01-24,0.23,1,0.77
642 | 641,Seattle Kraken,3,2,Washington Capitals,2022-12-01,0.23,1,0.77
643 | 642,St. Louis Blues,4,6,Carolina Hurricanes,2022-12-01,-1.23,-2,0.77
644 | 643,Montréal Canadiens,4,6,Vegas Golden Knights,2022-11-05,-1.24,-2,0.76
645 | 644,Montréal Canadiens,1,3,Minnesota Wild,2022-10-25,-1.25,-2,0.75
646 | 645,Calgary Flames,6,5,Los Angeles Kings,2022-11-14,0.25,1,0.75
647 | 646,Chicago Blackhawks,1,3,New York Islanders,2022-11-01,-1.26,-2,0.74
648 | 647,Minnesota Wild,5,4,Anaheim Ducks,2022-12-03,1.74,1,0.74
649 | 648,Edmonton Oilers,4,3,Vegas Golden Knights,2022-11-19,0.26,1,0.74
650 | 649,Vegas Golden Knights,3,4,Edmonton Oilers,2023-01-14,-0.26,-1,0.74
651 | 650,Pittsburgh Penguins,2,3,Seattle Kraken,2022-11-05,-0.27,-1,0.73
652 | 651,Colorado Avalanche,1,0,New York Islanders,2022-12-19,0.28,1,0.72
653 | 652,Buffalo Sabres,3,2,New York Islanders,2023-01-19,0.28,1,0.72
654 | 653,San Jose Sharks,3,4,Vancouver Canucks,2022-11-27,-0.29,-1,0.71
655 | 654,New Jersey Devils,4,3,New York Rangers,2023-01-07,0.29,1,0.71
656 | 655,San Jose Sharks,5,6,Vancouver Canucks,2022-12-07,-0.29,-1,0.71
657 | 656,Buffalo Sabres,5,6,Tampa Bay Lightning,2022-11-28,-0.30,-1,0.70
658 | 657,Vegas Golden Knights,5,4,Nashville Predators,2022-12-31,0.31,1,0.69
659 | 658,Ottawa Senators,4,5,Vegas Golden Knights,2022-11-03,-0.33,-1,0.67
660 | 659,New York Rangers,4,3,Minnesota Wild,2023-01-10,0.34,1,0.66
661 | 660,Los Angeles Kings,3,4,Vegas Golden Knights,2022-10-11,-0.34,-1,0.66
662 | 661,Washington Capitals,3,1,Montréal Canadiens,2022-10-15,1.35,2,0.65
663 | 662,New Jersey Devils,2,1,San Jose Sharks,2022-10-22,1.65,1,0.65
664 | 663,Calgary Flames,1,2,Edmonton Oilers,2022-12-27,-0.35,-1,0.65
665 | 664,San Jose Sharks,3,4,New Jersey Devils,2023-01-16,-1.65,-1,0.65
666 | 665,Vegas Golden Knights,1,3,Boston Bruins,2022-12-11,-1.35,-2,0.65
667 | 666,Calgary Flames,2,3,Edmonton Oilers,2022-10-29,-0.35,-1,0.65
668 | 667,Vancouver Canucks,3,2,Arizona Coyotes,2022-12-03,0.36,1,0.64
669 | 668,Detroit Red Wings,2,3,Florida Panthers,2023-01-06,-0.36,-1,0.64
670 | 669,Anaheim Ducks,2,3,Calgary Flames,2022-12-23,-1.63,-1,0.63
671 | 670,Florida Panthers,4,3,Philadelphia Flyers,2022-10-19,0.37,1,0.63
672 | 671,Anaheim Ducks,2,3,Chicago Blackhawks,2022-11-12,-0.38,-1,0.62
673 | 672,Winnipeg Jets,3,2,Calgary Flames,2023-01-03,0.39,1,0.61
674 | 673,Edmonton Oilers,3,4,New Jersey Devils,2022-11-03,-0.39,-1,0.61
675 | 674,Chicago Blackhawks,4,5,Edmonton Oilers,2022-11-30,-1.60,-1,0.60
676 | 675,Vancouver Canucks,3,4,Nashville Predators,2022-11-05,-0.40,-1,0.60
677 | 676,Carolina Hurricanes,3,2,Washington Capitals,2022-10-31,0.40,1,0.60
678 | 677,Chicago Blackhawks,5,6,Edmonton Oilers,2022-10-27,-1.60,-1,0.60
679 | 678,Chicago Blackhawks,3,5,Pittsburgh Penguins,2022-11-20,-1.41,-2,0.59
680 | 679,Montréal Canadiens,2,4,Boston Bruins,2023-01-24,-2.59,-2,0.59
681 | 680,Boston Bruins,6,3,Arizona Coyotes,2022-10-15,2.42,3,0.58
682 | 681,Edmonton Oilers,4,3,Florida Panthers,2022-11-28,0.43,1,0.57
683 | 682,Calgary Flames,4,5,Seattle Kraken,2022-11-01,-0.43,-1,0.57
684 | 683,Boston Bruins,3,1,Calgary Flames,2022-11-10,1.44,2,0.56
685 | 684,Columbus Blue Jackets,3,4,Washington Capitals,2023-01-31,-1.56,-1,0.56
686 | 685,Pittsburgh Penguins,3,4,Carolina Hurricanes,2022-12-22,-0.44,-1,0.56
687 | 686,Washington Capitals,1,0,Columbus Blue Jackets,2023-01-08,1.56,1,0.56
688 | 687,Ottawa Senators,2,3,Washington Capitals,2022-12-22,-0.44,-1,0.56
689 | 688,Carolina Hurricanes,3,2,Pittsburgh Penguins,2022-12-18,0.44,1,0.56
690 | 689,Pittsburgh Penguins,2,3,Carolina Hurricanes,2022-11-29,-0.44,-1,0.56
691 | 690,Carolina Hurricanes,2,1,Pittsburgh Penguins,2023-01-14,0.44,1,0.56
692 | 691,New Jersey Devils,1,0,Colorado Avalanche,2022-10-28,0.45,1,0.55
693 | 692,Toronto Maple Leafs,3,2,Washington Capitals,2022-10-13,0.45,1,0.55
694 | 693,Washington Capitals,4,3,Los Angeles Kings,2022-10-22,0.45,1,0.55
695 | 694,Buffalo Sabres,4,3,Chicago Blackhawks,2022-10-29,1.54,1,0.54
696 | 695,Washington Capitals,1,2,Dallas Stars,2022-12-15,-0.47,-1,0.53
697 | 696,Winnipeg Jets,3,2,Montréal Canadiens,2022-11-03,1.53,1,0.53
698 | 697,Detroit Red Wings,3,2,San Jose Sharks,2023-01-24,0.47,1,0.53
699 | 698,San Jose Sharks,3,4,Philadelphia Flyers,2022-12-29,-0.47,-1,0.53
700 | 699,San Jose Sharks,1,2,Carolina Hurricanes,2022-10-14,-1.51,-1,0.51
701 | 700,Carolina Hurricanes,5,4,San Jose Sharks,2023-01-27,1.51,1,0.51
702 | 701,Edmonton Oilers,5,3,Montréal Canadiens,2022-12-03,1.50,2,0.50
703 | 702,San Jose Sharks,3,4,Tampa Bay Lightning,2022-10-29,-1.50,-1,0.50
704 | 703,Nashville Predators,2,3,Colorado Avalanche,2022-12-23,-0.50,-1,0.50
705 | 704,Boston Bruins,5,3,Florida Panthers,2022-10-17,1.52,2,0.48
706 | 705,Vancouver Canucks,7,6,Montréal Canadiens,2022-12-05,0.53,1,0.47
707 | 706,Detroit Red Wings,4,3,Arizona Coyotes,2022-11-25,0.54,1,0.46
708 | 707,Vegas Golden Knights,2,1,Philadelphia Flyers,2022-12-09,0.54,1,0.46
709 | 708,Florida Panthers,4,3,Vancouver Canucks,2023-01-14,0.54,1,0.46
710 | 709,Columbus Blue Jackets,2,3,Vegas Golden Knights,2022-11-28,-1.45,-1,0.45
711 | 710,Minnesota Wild,3,2,Philadelphia Flyers,2023-01-26,0.55,1,0.45
712 | 711,Minnesota Wild,3,4,Toronto Maple Leafs,2022-11-25,-0.55,-1,0.45
713 | 712,Anaheim Ducks,3,5,Florida Panthers,2022-11-06,-1.55,-2,0.45
714 | 713,Toronto Maple Leafs,3,1,San Jose Sharks,2022-11-30,1.57,2,0.43
715 | 714,Colorado Avalanche,2,1,Montréal Canadiens,2022-12-21,1.43,1,0.43
716 | 715,Tampa Bay Lightning,5,3,Arizona Coyotes,2022-12-31,1.57,2,0.43
717 | 716,Boston Bruins,4,3,New York Islanders,2022-12-13,1.43,1,0.43
718 | 717,Arizona Coyotes,2,3,New York Rangers,2022-10-30,-1.42,-1,0.42
719 | 718,New Jersey Devils,2,1,Pittsburgh Penguins,2023-01-22,0.58,1,0.42
720 | 719,Nashville Predators,2,1,Anaheim Ducks,2022-11-29,1.42,1,0.42
721 | 720,Winnipeg Jets,2,1,Nashville Predators,2022-12-15,0.60,1,0.40
722 | 721,Carolina Hurricanes,3,2,Calgary Flames,2022-11-26,0.60,1,0.40
723 | 722,Calgary Flames,3,2,Vancouver Canucks,2022-12-31,0.62,1,0.38
724 | 723,New Jersey Devils,4,2,Anaheim Ducks,2022-10-18,2.37,2,0.37
725 | 724,Washington Capitals,4,3,Detroit Red Wings,2022-12-19,0.64,1,0.36
726 | 725,Winnipeg Jets,2,1,Arizona Coyotes,2023-01-15,1.36,1,0.36
727 | 726,Columbus Blue Jackets,2,3,New York Islanders,2022-11-25,-1.36,-1,0.36
728 | 727,San Jose Sharks,1,2,New York Rangers,2022-11-19,-1.36,-1,0.36
729 | 728,New York Islanders,2,1,Columbus Blue Jackets,2022-12-29,1.36,1,0.36
730 | 729,Arizona Coyotes,2,3,Winnipeg Jets,2022-10-28,-1.36,-1,0.36
731 | 730,New York Islanders,4,3,Columbus Blue Jackets,2022-11-12,1.36,1,0.36
732 | 731,Chicago Blackhawks,1,3,Winnipeg Jets,2022-12-09,-1.64,-2,0.36
733 | 732,Chicago Blackhawks,3,4,Minnesota Wild,2022-10-30,-1.36,-1,0.36
734 | 733,Washington Capitals,3,2,Philadelphia Flyers,2022-11-23,0.65,1,0.35
735 | 734,New Jersey Devils,3,2,Vegas Golden Knights,2023-01-24,0.65,1,0.35
736 | 735,San Jose Sharks,2,4,Boston Bruins,2023-01-07,-2.35,-2,0.35
737 | 736,Calgary Flames,4,3,Columbus Blue Jackets,2023-01-23,1.35,1,0.35
738 | 737,Vegas Golden Knights,1,0,Chicago Blackhawks,2022-10-13,1.35,1,0.35
739 | 738,Boston Bruins,4,3,Minnesota Wild,2022-10-22,1.34,1,0.34
740 | 739,Los Angeles Kings,3,2,San Jose Sharks,2022-12-17,0.66,1,0.34
741 | 740,New York Islanders,1,2,Dallas Stars,2023-01-10,-0.66,-1,0.34
742 | 741,Toronto Maple Leafs,5,4,Calgary Flames,2022-12-10,0.66,1,0.34
743 | 742,Los Angeles Kings,4,3,San Jose Sharks,2023-01-11,0.66,1,0.34
744 | 743,Florida Panthers,2,3,Tampa Bay Lightning,2022-10-21,-0.67,-1,0.33
745 | 744,Los Angeles Kings,8,9,Seattle Kraken,2022-11-29,-0.67,-1,0.33
746 | 745,Seattle Kraken,3,2,Los Angeles Kings,2022-11-19,0.67,1,0.33
747 | 746,San Jose Sharks,2,3,Nashville Predators,2022-10-08,-0.70,-1,0.30
748 | 747,New Jersey Devils,3,4,Boston Bruins,2022-12-23,-0.70,-1,0.30
749 | 748,Pittsburgh Penguins,5,6,Boston Bruins,2022-11-01,-1.29,-1,0.29
750 | 749,New Jersey Devils,4,2,Arizona Coyotes,2022-11-12,1.71,2,0.29
751 | 750,St. Louis Blues,4,5,Toronto Maple Leafs,2022-12-27,-1.29,-1,0.29
752 | 751,Vancouver Canucks,4,5,Vegas Golden Knights,2022-11-21,-0.71,-1,0.29
753 | 752,Montréal Canadiens,3,4,Detroit Red Wings,2023-01-26,-0.71,-1,0.29
754 | 753,Boston Bruins,2,1,Pittsburgh Penguins,2023-01-02,1.29,1,0.29
755 | 754,Minnesota Wild,4,3,Vancouver Canucks,2022-10-20,0.72,1,0.28
756 | 755,Vegas Golden Knights,5,4,St. Louis Blues,2022-12-23,0.72,1,0.28
757 | 756,Colorado Avalanche,3,2,Philadelphia Flyers,2022-12-13,0.73,1,0.27
758 | 757,Toronto Maple Leafs,3,2,Vancouver Canucks,2022-11-12,1.27,1,0.27
759 | 758,Toronto Maple Leafs,5,4,Florida Panthers,2023-01-17,0.73,1,0.27
760 | 759,Detroit Red Wings,4,5,Buffalo Sabres,2022-11-30,-0.73,-1,0.27
761 | 760,New Jersey Devils,3,2,Calgary Flames,2022-11-08,0.74,1,0.26
762 | 761,Calgary Flames,3,4,New Jersey Devils,2022-11-05,-0.74,-1,0.26
763 | 762,Nashville Predators,4,3,Arizona Coyotes,2022-11-21,0.76,1,0.24
764 | 763,Pittsburgh Penguins,5,4,Vancouver Canucks,2023-01-10,0.77,1,0.23
765 | 764,Anaheim Ducks,2,4,Tampa Bay Lightning,2022-10-26,-2.22,-2,0.22
766 | 765,Vancouver Canucks,2,3,Carolina Hurricanes,2022-10-24,-1.22,-1,0.22
767 | 766,Seattle Kraken,3,1,Columbus Blue Jackets,2023-01-28,1.78,2,0.22
768 | 767,Boston Bruins,4,3,Toronto Maple Leafs,2023-01-14,0.79,1,0.21
769 | 768,Tampa Bay Lightning,5,4,Vancouver Canucks,2023-01-12,1.21,1,0.21
770 | 769,Columbus Blue Jackets,1,3,New York Rangers,2023-01-16,-1.80,-2,0.20
771 | 770,Nashville Predators,2,3,Tampa Bay Lightning,2022-11-19,-0.81,-1,0.19
772 | 771,Dallas Stars,4,2,Montréal Canadiens,2022-12-23,1.82,2,0.18
773 | 772,Philadelphia Flyers,2,3,New Jersey Devils,2022-12-03,-1.18,-1,0.18
774 | 773,Tampa Bay Lightning,4,3,Ottawa Senators,2022-11-01,0.82,1,0.18
775 | 774,San Jose Sharks,3,4,Florida Panthers,2022-11-03,-0.83,-1,0.17
776 | 775,Boston Bruins,3,2,Carolina Hurricanes,2022-11-25,0.84,1,0.16
777 | 776,Carolina Hurricanes,5,4,Los Angeles Kings,2023-01-31,0.85,1,0.15
778 | 777,New York Islanders,2,1,Montréal Canadiens,2023-01-14,1.15,1,0.15
779 | 778,Nashville Predators,2,1,Columbus Blue Jackets,2023-01-17,1.14,1,0.14
780 | 779,Toronto Maple Leafs,2,1,Nashville Predators,2023-01-11,0.87,1,0.13
781 | 780,Nashville Predators,2,3,Dallas Stars,2022-12-27,-0.88,-1,0.12
782 | 781,New York Rangers,1,0,Philadelphia Flyers,2022-11-01,0.89,1,0.11
783 | 782,Dallas Stars,3,2,Detroit Red Wings,2022-12-10,1.11,1,0.11
784 | 783,Toronto Maple Leafs,3,2,Ottawa Senators,2022-10-15,0.89,1,0.11
785 | 784,Toronto Maple Leafs,4,3,Philadelphia Flyers,2022-12-22,1.10,1,0.10
786 | 785,Dallas Stars,4,3,Ottawa Senators,2022-12-08,0.90,1,0.10
787 | 786,Ottawa Senators,3,2,Montréal Canadiens,2022-12-14,0.91,1,0.09
788 | 787,Montréal Canadiens,4,5,Ottawa Senators,2023-01-31,-0.91,-1,0.09
789 | 788,St. Louis Blues,2,3,Colorado Avalanche,2022-12-11,-0.92,-1,0.08
790 | 789,New York Rangers,6,4,Anaheim Ducks,2022-10-17,2.08,2,0.08
791 | 790,Minnesota Wild,2,1,Arizona Coyotes,2023-01-14,1.08,1,0.08
792 | 791,Minnesota Wild,4,3,Arizona Coyotes,2022-11-27,1.08,1,0.08
793 | 792,Dallas Stars,6,4,Chicago Blackhawks,2022-11-23,1.92,2,0.08
794 | 793,Boston Bruins,3,1,St. Louis Blues,2022-11-07,2.07,2,0.07
795 | 794,Boston Bruins,3,2,Winnipeg Jets,2022-12-22,1.06,1,0.06
796 | 795,Detroit Red Wings,0,1,Carolina Hurricanes,2022-12-13,-1.04,-1,0.04
797 | 796,Philadelphia Flyers,3,4,Carolina Hurricanes,2022-10-29,-1.04,-1,0.04
798 | 797,Carolina Hurricanes,6,5,Philadelphia Flyers,2022-12-23,1.04,1,0.04
799 | 798,New Jersey Devils,4,3,Ottawa Senators,2022-11-10,0.97,1,0.03
800 | 799,Calgary Flames,3,2,Arizona Coyotes,2022-12-05,0.97,1,0.03
801 | 800,Los Angeles Kings,2,1,Chicago Blackhawks,2022-11-10,1.01,1,0.01
802 | 801,Chicago Blackhawks,1,2,Los Angeles Kings,2023-01-22,-1.01,-1,0.01
803 |
--------------------------------------------------------------------------------
/NHL/Output CSV Data/colorado_avalanche_game_log.csv:
--------------------------------------------------------------------------------
1 | Opponent,Record,PCT,Win Probability,Lose by 5+,Lose by 4,Lose by 3,Lose by 2,Lose by 1,Win by 1,Win by 2,Win by 3,Win by 4,Win by 5+
2 | Boston Bruins,38-7-5,0.810,31.48%,9.54%,7.59%,11.70%,15.42%,24.27%,17.13%,6.47%,3.70%,2.00%,2.18%
3 | Carolina Hurricanes,33-9-8,0.740,44.76%,5.64%,4.85%,8.18%,12.36%,24.20%,21.95%,9.70%,5.96%,3.37%,3.78%
4 | New Jersey Devils,32-13-4,0.694,42.45%,6.17%,5.24%,8.74%,12.93%,24.47%,21.25%,9.12%,5.52%,3.10%,3.45%
5 | Toronto Maple Leafs,31-12-8,0.686,43.82%,5.85%,5.00%,8.41%,12.59%,24.32%,21.68%,9.47%,5.78%,3.25%,3.64%
6 | Tampa Bay Lightning,32-15-1,0.677,44.88%,5.62%,4.83%,8.16%,12.33%,24.19%,21.99%,9.73%,5.98%,3.38%,3.80%
7 | Dallas Stars,28-13-10,0.647,43.59%,5.90%,5.04%,8.46%,12.65%,24.35%,21.61%,9.41%,5.73%,3.23%,3.61%
8 | Seattle Kraken,29-15-5,0.643,47.62%,5.06%,4.40%,7.53%,11.63%,23.75%,22.72%,10.42%,6.52%,3.73%,4.22%
9 | New York Rangers,27-14-8,0.633,47.26%,5.13%,4.45%,7.61%,11.73%,23.82%,22.63%,10.33%,6.45%,3.68%,4.16%
10 | Winnipeg Jets,32-19-1,0.625,48.33%,4.93%,4.29%,7.38%,11.45%,23.62%,22.90%,10.61%,6.67%,3.82%,4.34%
11 | Vegas Golden Knights,29-18-4,0.608,53.24%,4.08%,3.62%,6.35%,10.21%,22.50%,23.90%,11.85%,7.72%,4.53%,5.23%
12 | Minnesota Wild,27-17-4,0.604,53.03%,4.12%,3.64%,6.39%,10.26%,22.56%,23.87%,11.80%,7.68%,4.50%,5.19%
13 | Edmonton Oilers,28-18-4,0.600,48.92%,4.82%,4.20%,7.25%,11.30%,23.51%,23.04%,10.76%,6.79%,3.90%,4.44%
14 | Los Angeles Kings,28-18-7,0.594,58.87%,3.28%,2.95%,5.28%,8.80%,20.83%,24.58%,13.26%,9.07%,5.48%,6.49%
15 | Pittsburgh Penguins,24-16-9,0.582,52.20%,4.25%,3.75%,6.56%,10.47%,22.77%,23.72%,11.59%,7.49%,4.37%,5.03%
16 | Buffalo Sabres,26-19-4,0.571,49.96%,4.63%,4.06%,7.02%,11.04%,23.29%,23.27%,11.02%,7.01%,4.05%,4.62%
17 | Calgary Flames,24-17-9,0.570,54.82%,3.84%,3.42%,6.04%,9.81%,22.07%,24.15%,12.25%,8.09%,4.78%,5.56%
18 | Washington Capitals,27-20-6,0.566,51.41%,4.38%,3.86%,6.72%,10.67%,22.96%,23.57%,11.39%,7.32%,4.25%,4.88%
19 | Nashville Predators,24-18-6,0.562,58.33%,3.35%,3.01%,5.38%,8.93%,21.00%,24.54%,13.13%,8.93%,5.38%,6.36%
20 | New York Islanders,25-22-5,0.529,54.67%,3.86%,3.44%,6.07%,9.85%,22.12%,24.13%,12.21%,8.05%,4.75%,5.52%
21 | Detroit Red Wings,21-19-8,0.521,61.96%,2.89%,2.62%,4.74%,8.04%,19.75%,24.70%,14.00%,9.86%,6.08%,7.32%
22 | Florida Panthers,24-22-6,0.519,56.09%,3.66%,3.26%,5.80%,9.49%,21.70%,24.31%,12.57%,8.39%,4.99%,5.83%
23 | Ottawa Senators,24-23-3,0.510,58.64%,3.31%,2.97%,5.32%,8.85%,20.90%,24.56%,13.20%,9.01%,5.44%,6.43%
24 | Philadelphia Flyers,21-21-9,0.500,62.05%,2.88%,2.61%,4.73%,8.02%,19.72%,24.70%,14.02%,9.89%,6.10%,7.34%
25 | St. Louis Blues,23-25-3,0.480,64.94%,2.55%,2.33%,4.25%,7.32%,18.61%,24.63%,14.68%,10.67%,6.72%,8.24%
26 | Vancouver Canucks,20-26-3,0.439,64.74%,2.57%,2.35%,4.28%,7.36%,18.69%,24.64%,14.63%,10.62%,6.68%,8.17%
27 | Montréal Canadiens,20-27-4,0.431,72.38%,1.82%,1.68%,3.13%,5.58%,15.40%,23.52%,16.08%,12.85%,8.66%,11.27%
28 | San Jose Sharks,15-25-11,0.402,69.11%,2.12%,1.95%,3.61%,6.34%,16.88%,24.18%,15.53%,11.87%,7.74%,9.78%
29 | Arizona Coyotes,16-28-6,0.380,70.02%,2.03%,1.87%,3.47%,6.12%,16.47%,24.03%,15.70%,12.14%,7.99%,10.17%
30 | Anaheim Ducks,16-29-5,0.370,78.40%,1.32%,1.23%,2.33%,4.25%,12.47%,21.44%,16.65%,14.68%,10.68%,14.97%
31 | Chicago Blackhawks,15-29-4,0.354,73.80%,1.69%,1.57%,2.93%,5.26%,14.74%,23.13%,16.28%,13.29%,9.09%,12.02%
32 | Columbus Blue Jackets,15-32-4,0.333,75.09%,1.58%,1.47%,2.76%,4.98%,14.12%,22.73%,16.42%,13.68%,9.51%,12.75%
33 |
--------------------------------------------------------------------------------
/NHL/Output CSV Data/colorado_avalanche_vs_boston_bruins_game_probabilities.csv:
--------------------------------------------------------------------------------
1 | ,Colorado Avalanche,Boston Bruins
2 | Rating,0.373,1.529
3 | Record,27-18-3,38-7-5
4 | Point PCT,0.594,0.810
5 | Win Probability,31.48%,68.52%
6 | Win by 1 Goal,17.13%,24.27%
7 | Win by 2 Goals,6.47%,15.42%
8 | Win by 3 Goals,3.70%,11.70%
9 | Win by 4 Goals,2.00%,7.59%
10 | Win by 5+ Goals,2.18%,9.54%
11 |
--------------------------------------------------------------------------------
/NHL/Output CSV Data/most_consistent_teams.csv:
--------------------------------------------------------------------------------
1 | ,Team,Rating,Consistency (z-Score)
2 | 1,New Jersey Devils,0.83,1.69
3 | 2,Nashville Predators,-0.13,1.68
4 | 3,Boston Bruins,1.53,1.33
5 | 4,Calgary Flames,0.09,1.26
6 | 5,Vegas Golden Knights,0.18,1.25
7 | 6,Tampa Bay Lightning,0.68,1.21
8 | 7,Los Angeles Kings,-0.16,0.98
9 | 8,Carolina Hurricanes,0.69,0.95
10 | 9,Pittsburgh Penguins,0.24,0.86
11 | 10,Minnesota Wild,0.19,0.84
12 | 11,Vancouver Canucks,-0.53,0.40
13 | 12,New York Islanders,0.09,0.38
14 | 13,Colorado Avalanche,0.37,0.12
15 | 14,Dallas Stars,0.76,0.06
16 | 15,Philadelphia Flyers,-0.36,0.05
17 | 16,Columbus Blue Jackets,-1.27,-0.05
18 | 17,Toronto Maple Leafs,0.74,-0.07
19 | 18,Florida Panthers,0.01,-0.13
20 | 19,Chicago Blackhawks,-1.17,-0.17
21 | 20,New York Rangers,0.54,-0.27
22 | 21,St. Louis Blues,-0.54,-0.32
23 | 22,San Jose Sharks,-0.82,-0.43
24 | 23,Montréal Canadiens,-1.06,-0.63
25 | 24,Seattle Kraken,0.51,-0.80
26 | 25,Detroit Red Wings,-0.35,-0.84
27 | 26,Edmonton Oilers,0.44,-0.96
28 | 27,Arizona Coyotes,-0.89,-0.97
29 | 28,Buffalo Sabres,0.37,-1.06
30 | 29,Ottawa Senators,-0.15,-1.55
31 | 30,Anaheim Ducks,-1.54,-1.57
32 | 31,Washington Capitals,0.29,-1.61
33 | 32,Winnipeg Jets,0.47,-1.65
34 |
--------------------------------------------------------------------------------
/NHL/Output CSV Data/power_rankings.csv:
--------------------------------------------------------------------------------
1 | ,Team,POWER,Record,PCT,Avg Goal Differential,GF/Game,GA/Game,Strength of Schedule
2 | 1,Boston Bruins,1.53,38-7-5,0.810,1.56,3.74,2.18,-0.031
3 | 2,New Jersey Devils,0.83,32-13-4,0.694,0.82,3.49,2.67,0.009
4 | 3,Dallas Stars,0.76,28-13-10,0.647,0.78,3.39,2.61,-0.028
5 | 4,Toronto Maple Leafs,0.74,31-12-8,0.686,0.73,3.39,2.67,0.016
6 | 5,Carolina Hurricanes,0.69,33-9-8,0.740,0.66,3.36,2.70,0.025
7 | 6,Tampa Bay Lightning,0.68,32-15-1,0.677,0.71,3.65,2.94,-0.030
8 | 7,New York Rangers,0.54,27-14-8,0.633,0.57,3.20,2.63,-0.035
9 | 8,Seattle Kraken,0.51,29-15-5,0.643,0.53,3.61,3.08,-0.016
10 | 9,Winnipeg Jets,0.47,32-19-1,0.625,0.56,3.19,2.63,-0.086
11 | 10,Edmonton Oilers,0.44,28-18-4,0.600,0.5,3.74,3.24,-0.063
12 | 11,Buffalo Sabres,0.37,26-19-4,0.571,0.41,3.78,3.37,-0.033
13 | 12,Colorado Avalanche,0.37,27-18-3,0.594,0.38,3.15,2.77,-0.002
14 | 13,Washington Capitals,0.29,27-20-6,0.566,0.26,3.13,2.87,0.025
15 | 14,Pittsburgh Penguins,0.24,24-16-9,0.582,0.16,3.29,3.12,0.079
16 | 15,Minnesota Wild,0.19,27-17-4,0.604,0.27,3.15,2.88,-0.078
17 | 16,Vegas Golden Knights,0.18,29-18-4,0.608,0.24,3.14,2.90,-0.055
18 | 17,New York Islanders,0.09,25-22-5,0.529,0.08,2.85,2.77,0.017
19 | 18,Calgary Flames,0.09,24-17-9,0.570,0.1,3.14,3.04,-0.015
20 | 19,Florida Panthers,0.01,24-22-6,0.519,-0.1,3.42,3.52,0.105
21 | 20,Nashville Predators,-0.13,24-18-6,0.562,-0.08,2.85,2.94,-0.044
22 | 21,Ottawa Senators,-0.15,24-23-3,0.510,-0.16,3.02,3.18,0.013
23 | 22,Los Angeles Kings,-0.16,28-18-7,0.594,-0.19,3.26,3.45,0.028
24 | 23,Detroit Red Wings,-0.35,21-19-8,0.521,-0.31,3.02,3.33,-0.040
25 | 24,Philadelphia Flyers,-0.36,21-21-9,0.500,-0.39,2.78,3.18,0.034
26 | 25,Vancouver Canucks,-0.53,20-26-3,0.439,-0.57,3.37,3.94,0.040
27 | 26,St. Louis Blues,-0.54,23-25-3,0.480,-0.57,3.06,3.63,0.025
28 | 27,San Jose Sharks,-0.82,15-25-11,0.402,-0.76,3.08,3.84,-0.060
29 | 28,Arizona Coyotes,-0.89,16-28-6,0.380,-0.92,2.62,3.54,0.031
30 | 29,Montréal Canadiens,-1.06,20-27-4,0.431,-1.08,2.63,3.71,0.019
31 | 30,Chicago Blackhawks,-1.17,15-29-4,0.354,-1.21,2.46,3.67,0.041
32 | 31,Columbus Blue Jackets,-1.27,15-32-4,0.333,-1.31,2.57,3.88,0.046
33 | 32,Anaheim Ducks,-1.54,16-29-5,0.370,-1.6,2.50,4.10,0.056
34 |
--------------------------------------------------------------------------------
/NHL/nhl_game_probability_model.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import numpy as np
3 | import matplotlib.pyplot as plt
4 | from scipy.stats import pearsonr
5 | from scipy.optimize import curve_fit
6 | from sklearn.metrics import log_loss
7 | from tqdm import tqdm
8 | import requests
9 | import json
10 | import os
11 | import time
12 |
13 | class Team():
14 | def __init__(self, name):
15 | self.name = name
16 | self.team_game_list = []
17 | self.agd = 0
18 | self.opponent_power = []
19 | self.schedule = 0
20 | self.power = 0
21 | self.prev_power = 0
22 | self.iteration_powers = []
23 | self.goals_for = 0
24 | self.goals_against = 0
25 | self.record = '0-0-0'
26 | self.pct = 0
27 |
28 | def read_games(self):
29 | print(f'--{self.name.upper()} GAME LIST--')
30 | for game in self.team_game_list:
31 | print(f'{game.home_team.name} {game.home_score}-{game.away_score} {game.away_team.name}')
32 |
33 | def calc_agd(self):
34 | goal_differential = 0
35 | for game in self.team_game_list:
36 | if self == game.home_team:
37 | goal_differential += game.home_score - game.away_score
38 | else:
39 | goal_differential += game.away_score - game.home_score
40 | agd = goal_differential / len(self.team_game_list)
41 |
42 | return agd
43 |
44 | def calc_sched(self):
45 | self.opponent_power = []
46 | for game in self.team_game_list:
47 | if self == game.home_team:
48 | self.opponent_power.append(game.away_team.prev_power)
49 | else:
50 | self.opponent_power.append(game.home_team.prev_power)
51 |
52 | return sum(self.opponent_power) / len(self.opponent_power)
53 |
54 | def calc_power(self):
55 | return self.calc_sched() + self.agd
56 |
57 | def calc_pct(self):
58 | wins = int(self.record[:self.record.find('-')])
59 | losses = int(self.record[len(str(wins))+1:][:self.record[len(str(wins))+1:].find('-')])
60 | otl = int(self.record[len(str(losses))+len(str(wins))+2:])
61 | point_percentage = (wins*2+otl)/(len(self.team_game_list)*2)
62 | return point_percentage
63 |
64 | def calc_consistency(self):
65 | performance_list = []
66 | for game in self.team_game_list:
67 | if self == game.home_team:
68 | performance_list.append(game.away_team.power + game.home_score - game.away_score)
69 | else:
70 | performance_list.append(game.away_team.power + game.home_score - game.away_score)
71 |
72 | variance = np.var(performance_list)
73 | return variance
74 |
75 | class Game():
76 | def __init__(self, home_team, away_team, home_score, away_score, date):
77 | self.home_team = home_team
78 | self.away_team = away_team
79 | self.home_score = home_score
80 | self.away_score = away_score
81 | self.date = date
82 |
83 | def game_team_object_creation(games_metadf):
84 | total_game_list = []
85 | team_list = []
86 |
87 | for index, row in games_metadf.iterrows():
88 | try:
89 | row['Home Goals'] = float(row['Home Goals'])
90 | row['Away Goals'] = float(row['Away Goals'])
91 |
92 | team_in_list = False
93 | for team in team_list:
94 | if team.name == row['Home Team']:
95 | team_in_list = True
96 | home_team_obj = team
97 | if team_in_list == False:
98 | home_team_obj = Team(row['Home Team'])
99 | team_list.append(home_team_obj)
100 |
101 | team_in_list = False
102 | for team in team_list:
103 | if team.name == row['Away Team']:
104 | team_in_list = True
105 | away_team_obj = team
106 | if team_in_list == False:
107 | away_team_obj = Team(row['Away Team'])
108 | team_list.append(away_team_obj)
109 |
110 | game_obj = Game(home_team_obj, away_team_obj, row['Home Goals'], row['Away Goals'], row['Date'])
111 |
112 | home_team_obj.team_game_list.append(game_obj)
113 | away_team_obj.team_game_list.append(game_obj)
114 | home_team_obj.goals_for += game_obj.home_score
115 | away_team_obj.goals_against += game_obj.home_score
116 | home_team_obj.goals_against += game_obj.away_score
117 | away_team_obj.goals_for += game_obj.away_score
118 | home_team_obj.record = row['Home Record']
119 | away_team_obj.record = row['Away Record']
120 | total_game_list.append(game_obj)
121 | except ValueError:
122 | pass
123 |
124 | return team_list, total_game_list
125 |
126 | def scrape_nhl_data():
127 | data = []
128 | team_id_dict = {}
129 |
130 | team_metadata = requests.get("https://api.nhle.com/stats/rest/en/team").json()
131 | for team in tqdm(team_metadata['data'], desc='Scraping Games', dynamic_ncols=True, colour='Green', bar_format='{l_bar}{bar:30}{r_bar}{bar:-10b}'):
132 |
133 | if team['fullName'] in ['Atlanta Thrashers', 'Hartford Whalers', 'Minnesota North Stars', 'Quebec Nordiques', 'Winnipeg Jets (1979)', 'Colorado Rockies', 'Ottawa Senators (1917)', 'Hamilton Tigers', 'Pittsburgh Pirates', 'Philadelphia Quakers', 'Detroit Cougars', 'Montreal Wanderers', 'Quebec Bulldogs', 'Montreal Maroons', 'New York Americans', 'St. Louis Eagles', 'Oakland Seals', 'Atlanta Flames', 'Kansas City Scouts', 'Cleveland Barons', 'Detroit Falcons', 'Brooklyn Americans', 'California Golden Seals', 'Toronto Arenas', 'Toronto St. Patricks', 'NHL']:
134 | continue
135 |
136 | team_id_dict[team['id']] = team['fullName']
137 |
138 | game_metadata = requests.get(f"https://api-web.nhle.com/v1/club-schedule-season/{team['triCode']}/20242025").json()
139 |
140 | for game in game_metadata['games']:
141 | if game['gameType'] == 2 and game['gameState'] == 'OFF':
142 | data.append({'GameID':game['id'], 'Date':game['gameDate'], 'Home Team':game['homeTeam']['id'], 'Home Goals':game['homeTeam']['score'], 'Away Goals':game['awayTeam']['score'], 'Away Team':game['awayTeam']['id'], "FinalState":game['gameOutcome']['lastPeriodType']})
143 |
144 | scraped_df = pd.DataFrame(data)
145 | scraped_df['Home Team'] = scraped_df['Home Team'].replace(team_id_dict)
146 | scraped_df['Away Team'] = scraped_df['Away Team'].replace(team_id_dict)
147 | scraped_df = scraped_df.drop_duplicates(subset='GameID')
148 | scraped_df = scraped_df.sort_values(by=['GameID'])
149 | scraped_df = calculate_records(scraped_df) # Adds home and away record columns
150 | return scraped_df, team_id_dict
151 |
152 | def calculate_records(df):
153 | # Combine unique teams from both 'Home Team' and 'Away Team' columns
154 | all_teams = pd.concat([df['Home Team'], df['Away Team']]).unique()
155 |
156 | # Initialize records dictionary for all teams
157 | records = {team: {'wins': 0, 'losses': 0, 'ot_losses': 0} for team in all_teams}
158 |
159 | for index, row in df.iterrows():
160 | home_team = row['Home Team']
161 | away_team = row['Away Team']
162 | home_goals = row['Home Goals']
163 | away_goals = row['Away Goals']
164 | final_state = row['FinalState']
165 |
166 | if home_goals > away_goals:
167 | records[home_team]['wins'] += 1
168 | if final_state == 'REG':
169 | records[away_team]['losses'] += 1
170 | else:
171 | records[away_team]['ot_losses'] += 1
172 | elif home_goals < away_goals:
173 | if final_state == 'REG':
174 | records[home_team]['losses'] += 1
175 | else:
176 | records[home_team]['ot_losses'] += 1
177 | records[away_team]['wins'] += 1
178 | else:
179 | print(f'Critical Error: Found Tie | Information: {home_team} {home_goals}-{away_goals} {away_team}') # should never happen
180 | return
181 |
182 | df.loc[index, 'Home Record'] = f"{records[home_team]['wins']}-{records[home_team]['losses']}-{records[home_team]['ot_losses']}"
183 | df.loc[index, 'Away Record'] = f"{records[away_team]['wins']}-{records[away_team]['losses']}-{records[away_team]['ot_losses']}"
184 |
185 | return df
186 |
187 | def assign_power(team_list, epochs):
188 | for team in team_list:
189 | team.agd = team.calc_agd()
190 | team.pct = team.calc_pct()
191 |
192 | for epochs in range(epochs):
193 | # print(f'EPOCH {epochs+1}')
194 | for team in team_list:
195 | team.schedule = team.calc_sched()
196 | team.power = team.calc_power()
197 | team.iteration_powers.append(team.power)
198 | # print(f'{team.name}\t\tAGD: {team.calc_agd():.2f}\tSCHEDULE: {team.schedule:.2f}\t\tPOWER: {team.power:.2f}')
199 | for team in team_list:
200 | team.prev_power = team.power
201 |
202 | def prepare_power_rankings(team_list):
203 | power_df = pd.DataFrame()
204 | for team in team_list:
205 | power_df = pd.concat([power_df, pd.DataFrame.from_dict([{'Team':team.name, 'POWER':round(team.power,2), 'Record':team.record, 'PCT':f"{team.calc_pct():.3f}",'Avg Goal Differential':round(team.calc_agd(),2), 'GF/Game':f"{team.goals_for/len(team.team_game_list):.2f}", 'GA/Game':f"{team.goals_against/len(team.team_game_list):.2f}", 'Strength of Schedule':f"{team.schedule:.3f}"}])], ignore_index=True)
206 | power_df.sort_values(by=['POWER'], inplace=True, ascending=False)
207 | power_df = power_df.reset_index(drop=True)
208 | power_df.index += 1
209 |
210 | return power_df
211 |
212 | def logistic_regression(total_game_list):
213 | xpoints = [] # Rating differential (Home - Away)
214 | ypoints = [] # Home Win/Loss Boolean (Win = 1, Tie = 0.5, Loss = 0)
215 |
216 | for game in total_game_list:
217 | xpoints.append(game.home_team.power - game.away_team.power)
218 |
219 | if game.home_score > game.away_score:
220 | ypoints.append(1)
221 | elif game.home_score < game.away_score:
222 | ypoints.append(0)
223 | else:
224 | ypoints.append(0.5)
225 |
226 | parameters, covariates = curve_fit(lambda t, param: 1/(1+np.exp((t)/param)), [-x for x in xpoints], ypoints) # Regression only works if parameter is positive.
227 | param = -parameters[0]
228 |
229 | return xpoints, ypoints, param
230 |
231 | def model_performance(xpoints, ypoints, param):
232 | x_fitted = np.linspace(np.min(xpoints)*1.25, np.max(xpoints)*1.25, 100)
233 | y_fitted = 1/(1+np.exp((x_fitted)/param))
234 |
235 | r, p = pearsonr(xpoints, ypoints)
236 | print(f'Pearson Correlation of Independent and Dependent Variables: {r:.3f}')
237 | print(f'Log Loss of the Cumulative Distribution Function (CDF): {log_loss(ypoints, 1/(1+np.exp((xpoints)/param))):.3f}')
238 | print(f'Regressed Sigmoid: 1/(1+exp((x)/{param:.3f}))')
239 | print(f'Precise Parameter: {param}')
240 |
241 | plt.plot(xpoints, ypoints, 'o', color='grey')
242 | plt.plot(x_fitted, y_fitted, color='black', alpha=1, label=f'CDF (Log Loss = {log_loss(ypoints, 1/(1+np.exp((xpoints)/param))):.3f})')
243 | plt.legend()
244 | plt.title('Logistic Regression of Team Rating Difference vs Game Result')
245 | plt.xlabel('Rating Difference')
246 | plt.ylabel('Win Probability')
247 | plt.show()
248 |
249 | def calc_prob(team, opponent, param):
250 | return 1/(1+np.exp((team.power-opponent.power)/param))
251 |
252 | def calc_spread(team, opponent, param, lower_bound_spread, upper_bound_spread):
253 | if lower_bound_spread == '-inf':
254 | if upper_bound_spread == 'inf':
255 | return 1
256 | return 1/(1+np.exp((upper_bound_spread-(team.power-opponent.power))/param))
257 | elif upper_bound_spread == 'inf':
258 | return 1 - 1/(1+np.exp((lower_bound_spread-(team.power-opponent.power))/param))
259 | else:
260 | return 1/(1+np.exp((upper_bound_spread-(team.power-opponent.power))/param)) - 1/(1+np.exp((lower_bound_spread-(team.power-opponent.power))/param))
261 |
262 | def download_csv_option(df, filename):
263 | valid = False
264 | while valid == False:
265 | user_input = input('Would you like to download this as a CSV? (Y/N): ')
266 | if user_input.lower() in ['y', 'yes', 'y.', 'yes.']:
267 | valid = True
268 | elif user_input.lower() in ['n', 'no', 'n.', 'no.']:
269 | return
270 | else:
271 | print(f'Sorry, I could not understand "{user_input}". Please enter Y or N: ')
272 |
273 | if not os.path.exists(f'{os.path.dirname(__file__)}/Output CSV Data'):
274 | os.makedirs(f'{os.path.dirname(__file__)}/Output CSV Data')
275 | df.to_csv(f'{os.path.dirname(__file__)}/Output CSV Data/{filename}.csv')
276 | print(f'{filename}.csv has been downloaded to the following directory: {os.path.dirname(__file__)}/Output CSV Data')
277 | return
278 |
279 |
280 | def get_todays_games(param, team_list, team_id_dict):
281 | today_schedule = requests.get("https://api-web.nhle.com/v1/schedule/now").json()
282 |
283 | today_games_df = pd.DataFrame(columns = ['GameID', 'Game State', 'Home Team', 'Home Goals', 'Away Goals', 'Away Team', 'Pre-Game Home Win Probability', 'Pre-Game Away Win Probability', 'Home Record', 'Away Record'])
284 |
285 | try:
286 | date = today_schedule['gameWeek'][0]['date']
287 | for games in today_schedule['gameWeek'][0]['games']:
288 | for team in team_list:
289 | if team.name == team_id_dict[games['homeTeam']['id']]:
290 | home_team_obj = team
291 | elif team.name == team_id_dict[games['awayTeam']['id']]:
292 | away_team_obj = team
293 |
294 | home_win_prob = calc_prob(home_team_obj, away_team_obj, param)
295 | away_win_prob = 1-home_win_prob
296 |
297 | if games['gameState'] == 'OFF': # final
298 | today_games_df = pd.concat([today_games_df, pd.DataFrame.from_dict([{'GameID':games['id'], 'Game State':'Final', 'Home Team':team_id_dict[games['homeTeam']['id']], 'Home Goals':games['homeTeam']['score'], 'Away Goals':games['awayTeam']['score'], 'Away Team':team_id_dict[games['awayTeam']['id']], 'Pre-Game Home Win Probability':f'{home_win_prob*100:.2f}%', 'Pre-Game Away Win Probability':f'{away_win_prob*100:.2f}%', 'Home Record':home_team_obj.record, 'Away Record':away_team_obj.record}])], ignore_index=True)
299 | elif games['gameState'] == 'FUT': # pre-game
300 | today_games_df = pd.concat([today_games_df, pd.DataFrame.from_dict([{'GameID':games['id'], 'Game State':'Pre-Game', 'Home Team':team_id_dict[games['homeTeam']['id']], 'Home Goals':0, 'Away Goals':0, 'Away Team':team_id_dict[games['awayTeam']['id']], 'Pre-Game Home Win Probability':f'{home_win_prob*100:.2f}%', 'Pre-Game Away Win Probability':f'{away_win_prob*100:.2f}%', 'Home Record':home_team_obj.record, 'Away Record':away_team_obj.record}])], ignore_index=True)
301 | else: # in progress
302 | try:
303 | today_games_df = pd.concat([today_games_df, pd.DataFrame.from_dict([{'GameID':games['id'], 'Game State':f"Period {games['periodDescriptor']['number']}", 'Home Team':team_id_dict[games['homeTeam']['id']], 'Home Goals':games['homeTeam']['score'], 'Away Goals':games['awayTeam']['score'], 'Away Team':team_id_dict[games['awayTeam']['id']], 'Pre-Game Home Win Probability':f'{home_win_prob*100:.2f}%', 'Pre-Game Away Win Probability':f'{away_win_prob*100:.2f}%', 'Home Record':home_team_obj.record, 'Away Record':away_team_obj.record}])], ignore_index=True)
304 | except KeyError:
305 | today_games_df = pd.concat([today_games_df, pd.DataFrame.from_dict([{'GameID':games['id'], 'Game State':f"Period {games['periodDescriptor']['number']}", 'Home Team':team_id_dict[games['homeTeam']['id']], 'Home Goals':0, 'Away Goals':0, 'Away Team':team_id_dict[games['awayTeam']['id']], 'Pre-Game Home Win Probability':f'{home_win_prob*100:.2f}%', 'Pre-Game Away Win Probability':f'{away_win_prob*100:.2f}%', 'Home Record':home_team_obj.record, 'Away Record':away_team_obj.record}])], ignore_index=True)
306 |
307 | today_games_df.index += 1
308 |
309 | except IndexError:
310 | today_games_df = None
311 | date = None
312 |
313 | return date, today_games_df
314 |
315 | def custom_game_selector(param, team_list):
316 | valid = False
317 | while valid == False:
318 | home_team_input = input('Enter the home team: ')
319 | for team in team_list:
320 | if home_team_input.strip().lower() == team.name.lower().replace('é','e'):
321 | home_team = team
322 | valid = True
323 | if valid == False:
324 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
325 |
326 | valid = False
327 | while valid == False:
328 | away_team_input = input('Enter the away team: ')
329 | for team in team_list:
330 | if away_team_input.strip().lower() == team.name.lower().replace('é','e'):
331 | away_team = team
332 | valid = True
333 | if valid == False:
334 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
335 |
336 | game_probability_df = pd.DataFrame(columns = ['', home_team.name, away_team.name])
337 |
338 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Rating', home_team.name:f'{home_team.power:.3f}', away_team.name:f'{away_team.power:.3f}'}])], ignore_index=True)
339 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Record', home_team.name:f'{home_team.record}', away_team.name:f'{away_team.record}'}])], ignore_index=True)
340 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Point PCT', home_team.name:f'{home_team.pct:.3f}', away_team.name:f'{away_team.pct:.3f}'}])], ignore_index=True)
341 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Win Probability', home_team.name:f'{calc_prob(home_team, away_team, param)*100:.2f}%', away_team.name:f'{(calc_prob(away_team, home_team, param))*100:.2f}%'}])], ignore_index=True)
342 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Win by 1 Goal', home_team.name:f'{calc_spread(home_team, away_team, param, 0, 1.5)*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 0, 1.5)*100:.2f}%'}])], ignore_index=True)
343 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Win by 2 Goals', home_team.name:f'{calc_spread(home_team, away_team, param, 1.5, 2.5)*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 1.5, 2.5)*100:.2f}%'}])], ignore_index=True)
344 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Win by 3 Goals', home_team.name:f'{calc_spread(home_team, away_team, param, 2.5, 3.5)*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 2.5, 3.5)*100:.2f}%'}])], ignore_index=True)
345 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Win by 4 Goals', home_team.name:f'{calc_spread(home_team, away_team, param, 3.5, 4.5)*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 3.5, 4.5)*100:.2f}%'}])], ignore_index=True)
346 | game_probability_df = pd.concat([game_probability_df, pd.DataFrame.from_dict([{'':'Win by 5+ Goals', home_team.name:f'{calc_spread(home_team, away_team, param, 4.5, "inf")*100:.2f}%', away_team.name:f'{calc_spread(away_team, home_team, param, 4.5, "inf")*100:.2f}%'}])], ignore_index=True)
347 | game_probability_df = game_probability_df.set_index('')
348 |
349 | return home_team, away_team, game_probability_df
350 |
351 | def get_upsets(total_game_list):
352 | upset_df = pd.DataFrame(columns = ['Home Team', 'Home Goals', 'Away Goals', 'Away Team', 'Date', 'xGD', 'GD', 'Upset Rating'])
353 |
354 | for game in total_game_list:
355 | expected_score_diff = game.home_team.power - game.away_team.power #home - away
356 | actaul_score_diff = game.home_score - game.away_score
357 | upset_rating = actaul_score_diff - expected_score_diff #Positive score is an upset by the home team. Negative scores are upsets by the visiting team.
358 |
359 | upset_df = pd.concat([upset_df, pd.DataFrame.from_dict([{'Home Team':game.home_team.name, 'Home Goals':int(game.home_score), 'Away Goals':int(game.away_score), 'Away Team':game.away_team.name, 'Date':game.date,'xGD':f'{expected_score_diff:.2f}', 'GD':int(actaul_score_diff), 'Upset Rating':f'{abs(upset_rating):.2f}'}])], ignore_index=True)
360 |
361 | upset_df = upset_df.sort_values(by=['Upset Rating'], ascending=False)
362 | upset_df = upset_df.reset_index(drop=True)
363 | upset_df.index += 1
364 | return upset_df
365 |
366 | def get_best_performances(total_game_list):
367 | performance_df = pd.DataFrame(columns = ['Team', 'Opponent', 'GF', 'GA', 'Date', 'xGD', 'Performance'])
368 |
369 | for game in total_game_list:
370 | performance_df = pd.concat([performance_df, pd.DataFrame.from_dict([{'Team':game.home_team.name, 'Opponent':game.away_team.name, 'GF':int(game.home_score), 'GA':int(game.away_score), 'Date':game.date, 'xGD':f'{game.home_team.power-game.away_team.power:.2f}', 'Performance':round(game.away_team.power+game.home_score-game.away_score,2)}])], ignore_index = True)
371 | performance_df = pd.concat([performance_df, pd.DataFrame.from_dict([{'Team':game.away_team.name, 'Opponent':game.home_team.name, 'GF':int(game.away_score), 'GA':int(game.home_score), 'Date':game.date, 'xGD':f'{game.away_team.power-game.home_team.power:.2f}', 'Performance':round(game.home_team.power+game.away_score-game.home_score,2)}])], ignore_index = True)
372 |
373 | performance_df = performance_df.sort_values(by=['Performance'], ascending=False)
374 | performance_df = performance_df.reset_index(drop=True)
375 | performance_df.index += 1
376 | return performance_df
377 |
378 | def get_team_consistency(team_list):
379 | consistency_df = pd.DataFrame(columns = ['Team', 'Consistency Rating', 'Consistency (z-Score)'])
380 |
381 | for team in team_list:
382 | consistency_df = pd.concat([consistency_df, pd.DataFrame.from_dict([{'Team':team.name, 'Consistency Rating':f'{team.power:.2f}', 'Consistency (z-Score)':team.calc_consistency()}])], ignore_index = True)
383 |
384 | consistency_df['Consistency (z-Score)'] = consistency_df['Consistency (z-Score)'].apply(lambda x: (x-consistency_df['Consistency (z-Score)'].mean())/-consistency_df['Consistency (z-Score)'].std())
385 |
386 | consistency_df = consistency_df.sort_values(by=['Consistency (z-Score)'], ascending=False)
387 | consistency_df = consistency_df.reset_index(drop=True)
388 | consistency_df.index += 1
389 | consistency_df['Consistency (z-Score)'] = consistency_df['Consistency (z-Score)'].apply(lambda x: f'{x:.2f}')
390 | return consistency_df
391 |
392 | def team_game_log(team_list):
393 | valid = False
394 | while valid == False:
395 | input_team = input('Enter a team: ')
396 | for team_obj in team_list:
397 | if input_team.strip().lower() == team_obj.name.lower().replace('é','e'):
398 | team = team_obj
399 | valid = True
400 | if valid == False:
401 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
402 |
403 | game_log_df = pd.DataFrame(columns = ['Date', 'Opponent', 'GF', 'GA', 'Performance'])
404 | for game in team.team_game_list:
405 | if team == game.home_team:
406 | goals_for = game.home_score
407 | opponent = game.away_team
408 | goals_against = game.away_score
409 | else:
410 | goals_for = game.away_score
411 | opponent = game.home_team
412 | goals_against = game.home_score
413 |
414 | game_log_df = pd.concat([game_log_df, pd.DataFrame.from_dict([{'Date':game.date, 'Opponent':opponent.name, 'GF':int(goals_for), 'GA':int(goals_against), 'Performance':round(opponent.power + goals_for - goals_against,2)}])], ignore_index = True)
415 |
416 | game_log_df.index += 1
417 | return team, game_log_df
418 |
419 | def get_team_prob_breakdown(team_list, param):
420 | valid = False
421 | while valid == False:
422 | input_team = input('Enter a team: ')
423 | for team_obj in team_list:
424 | if input_team.strip().lower() == team_obj.name.lower().replace('é','e'):
425 | team = team_obj
426 | valid = True
427 | if valid == False:
428 | print('Sorry, I am not familiar with this team. Maybe check your spelling?')
429 |
430 | prob_breakdown_df = pd.DataFrame(columns = ['Opponent', 'Record', 'PCT', 'Win Probability', 'Lose by 5+', 'Lose by 4', 'Lose by 3', 'Lose by 2', 'Lose by 1', 'Win by 1', 'Win by 2', 'Win by 3', 'Win by 4', 'Win by 5+'])
431 | for opp_team in team_list:
432 | if opp_team is not team:
433 | prob_breakdown_df = pd.concat([prob_breakdown_df, pd.DataFrame.from_dict([{'Opponent': opp_team.name,
434 | 'Record': opp_team.record,
435 | 'PCT': f'{opp_team.calc_pct():.3f}',
436 | 'Win Probability':f'{calc_prob(team, opp_team, param)*100:.2f}%',
437 | 'Lose by 5+': f'{calc_spread(team, opp_team, param, "-inf", -4.5)*100:.2f}%',
438 | 'Lose by 4': f'{calc_spread(team, opp_team, param, -4.5, -3.5)*100:.2f}%',
439 | 'Lose by 3': f'{calc_spread(team, opp_team, param, -3.5, -2.5)*100:.2f}%',
440 | 'Lose by 2': f'{calc_spread(team, opp_team, param, -2.5, -1.5)*100:.2f}%',
441 | 'Lose by 1': f'{calc_spread(team, opp_team, param, -1.5, 0)*100:.2f}%',
442 | 'Win by 1': f'{calc_spread(team, opp_team, param, 0, 1.5)*100:.2f}%',
443 | 'Win by 2': f'{calc_spread(team, opp_team, param, 1.5, 2.5)*100:.2f}%',
444 | 'Win by 3': f'{calc_spread(team, opp_team, param, 2.5, 3.5)*100:.2f}%',
445 | 'Win by 4': f'{calc_spread(team, opp_team, param, 3.5, 4.5)*100:.2f}%',
446 | 'Win by 5+': f'{calc_spread(team, opp_team, param, 4.5, "inf")*100:.2f}%'}])], ignore_index = True)
447 |
448 | prob_breakdown_df = prob_breakdown_df.set_index('Opponent')
449 | prob_breakdown_df = prob_breakdown_df.sort_values(by=['PCT'], ascending=False)
450 | return team, prob_breakdown_df
451 |
452 | def get_power_convergence(team_list):
453 | power_convergence = pd.DataFrame(columns = ['Team', 'Iteration', 'Power'])
454 |
455 | for team in team_list:
456 | for i in range(len(team.iteration_powers)):
457 | power_convergence = pd.concat([power_convergence, pd.DataFrame.from_dict([{'Team':team.name, 'Iteration':i+1, 'Power':team.iteration_powers[i]}])], ignore_index=True)
458 |
459 | plt.figure(figsize=(10, 6))
460 | for team in team_list:
461 | plt.plot(power_convergence[power_convergence['Team'] == team.name]['Iteration'], power_convergence[power_convergence['Team'] == team.name]['Power'], label=team.name)
462 | plt.title('Power Convergence Over Iterations')
463 | plt.xlabel('Iteration')
464 | plt.ylabel('Power')
465 | plt.legend()
466 | plt.grid()
467 | plt.show()
468 |
469 | return power_convergence
470 |
471 | def extra_menu(total_game_list, team_list, param):
472 | while True:
473 | print("""--EXTRAS MENU--
474 | 1. Biggest Upsets
475 | 2. Best Performances
476 | 3. Most Consistent Teams
477 | 4. Team Game Logs
478 | 5. Team Probability Big Board
479 | 6. Iterative Power Convergence
480 | 7. Exit to Main Menu""")
481 |
482 | valid = False
483 | while valid == False:
484 | user_option = input('Enter a menu option: ')
485 | try:
486 | user_option = int(user_option)
487 | if user_option >= 1 and user_option <= 7:
488 | print()
489 | valid = True
490 | else:
491 | raise ValueError
492 | except ValueError:
493 | print(f'Your option "{user_option}" is invalid.', end=' ')
494 |
495 | if user_option == 1:
496 | upsets = get_upsets(total_game_list)
497 | print(upsets)
498 | download_csv_option(upsets, 'biggest_upsets')
499 | elif user_option == 2:
500 | performances = get_best_performances(total_game_list)
501 | print(performances)
502 | download_csv_option(performances, 'best_performances')
503 | elif user_option == 3:
504 | consistency = get_team_consistency(team_list)
505 | print(consistency)
506 | download_csv_option(consistency, 'most_consistent_teams')
507 | elif user_option == 4:
508 | team, game_log = team_game_log(team_list)
509 | print(game_log)
510 | download_csv_option(game_log, f'{team.name.replace(" ", "_").lower()}_game_log')
511 | elif user_option == 5:
512 | team, team_probabilities = get_team_prob_breakdown(team_list, param)
513 | print(team_probabilities)
514 | download_csv_option(team_probabilities, f'{team.name.replace(" ", "_").lower()}_prob_breakdown')
515 | elif user_option == 6:
516 | power_convergence = get_power_convergence(team_list)
517 | print(power_convergence)
518 | download_csv_option(power_convergence, 'iterative_power_convergence')
519 | elif user_option == 7:
520 | pass
521 |
522 | return
523 |
524 | def menu(power_df, today_games_df, xpoints, ypoints, param, computation_time, total_game_list, team_list, date):
525 | while True:
526 | print("""--MAIN MENU--
527 | 1. View Power Rankings
528 | 2. View Today's Games
529 | 3. Custom Game Selector
530 | 4. View Model Performance
531 | 5. View Program Performance
532 | 6. Extra Options
533 | 7. Quit""")
534 |
535 | valid = False
536 | while valid == False:
537 | user_option = input('Enter a menu option: ')
538 | try:
539 | user_option = int(user_option)
540 | if user_option >= 1 and user_option <= 7:
541 | print()
542 | valid = True
543 | else:
544 | raise ValueError
545 | except ValueError:
546 | print(f'Your option "{user_option}" is invalid.', end=' ')
547 |
548 | if user_option == 1:
549 | print(power_df)
550 | download_csv_option(power_df, 'power_rankings')
551 | elif user_option == 2:
552 | if today_games_df is not None:
553 | print(today_games_df)
554 | download_csv_option(today_games_df, f'{date}_games')
555 | else:
556 | print('There are no games today!')
557 | elif user_option == 3:
558 | home_team, away_team, custom_game_df = custom_game_selector(param, team_list)
559 | print(custom_game_df)
560 | download_csv_option(custom_game_df, f'{home_team.name.replace(" ", "_").lower()}_vs_{away_team.name.replace(" ", "_").lower()}_game_probabilities')
561 | elif user_option == 4:
562 | model_performance(xpoints, ypoints, param)
563 | elif user_option == 5:
564 | print(f'Computation Time: {computation_time:.2f} seconds')
565 | print(f'Games Scraped: {len(total_game_list)}')
566 | print(f'Rate: {len(total_game_list)/computation_time:.1f} games/second')
567 | elif user_option == 6:
568 | extra_menu(total_game_list, team_list, param)
569 | elif user_option == 7:
570 | return
571 |
572 | input('Press ENTER to continue\t\t')
573 | print()
574 |
575 | def main():
576 | start_time = time.time()
577 |
578 | games_metadf, team_id_dict = scrape_nhl_data()
579 | epochs = 5
580 | team_list, total_game_list = game_team_object_creation(games_metadf)
581 | assign_power(team_list, epochs)
582 | power_df = prepare_power_rankings(team_list)
583 | xpoints, ypoints, param = logistic_regression(total_game_list)
584 | date, today_games_df = get_todays_games(param, team_list, team_id_dict)
585 |
586 | computation_time = time.time()-start_time
587 | menu(power_df, today_games_df, xpoints, ypoints, param, computation_time, total_game_list, team_list, date)
588 |
589 | if __name__ == '__main__':
590 | main()
591 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Game Outcome Prediction Model
2 |
3 | This repository presents a real-time machine learning-based predictive modeling framework designed to estimate the probabilities of game outcomes based on historical scores and team performance metrics for any head-to-head sport. The core of the model revolves around the Iterative Convergence Power Algorithm (ICPA), which performs a convergence-driven iterative refinement process to optimize team strength estimators. These refined team strength estimators are then employed as features in a novel variant of a logistic regression model.
4 |
5 | The model effectively predicts various game outcomes, including the probabilities of each team winning, as well as the likelihood of specific goal spreads and margins of victory or defeat. By integrating advanced statistical algorithms with unique machine learning adaptations and real-time data processing, this framework provides an elegant and robust solution for analyzing various match outcomes and the competitive landscape of the league.
6 |
7 | ## Iterative Convergence Power Algorithm
8 |
9 | The Iterative Convergence Power Algorithm (ICPA) is an elegant but powerful method designed to refine team strength estimators through a convergence-based iterative process. It operates by evaluating each team's historical performance in relation to their opponents, adjusting the power values assigned to teams based on their game outcomes. The algorithm iteratively updates these estimators, utilizing feedback from previous iterations to enhance accuracy and stability. This continuous refinement allows the model to dynamically adapt to the changing competitive landscape, ensuring that the team strengths reflect the most current performance data. By serving as robust features for a novel variant of logistic regression, the ICPA plays a crucial role in predicting game outcomes, enabling precise estimations of win probabilities, goal spreads, and margins of victory or defeat. Its ability to converge upon stable and reliable team strength assessments makes it a key component of the predictive modeling framework.
10 |
11 | ## Scope
12 |
13 | While primarily focused on the NHL and NBA, the model also includes native support for additional leagues, such as the KHL, AHL, and CFB, allowing for versatile applications across various competitive contexts. Users can seamlessly integrate their own league data by inputting a spreadsheet of game scores, enabling the model to adapt to new datasets and making it a flexible tool for analyzing different leagues and competitions. By harnessing the power of historical performance data and employing sophisticated statistical techniques, this model serves as an advanced tool for sports analysts, data scientists, and enthusiasts looking to gain insights into game outcome probabilities across multiple leagues.
14 |
15 | ## Specifications
16 |
17 | ### Feature Availability
18 |
19 | | Feature | NHL | NBA | CFB | KHL | AHL | Custom Leagues |
20 | |----------------------------------------------------|:-------:|:-------:|:-------:|:-------:|:-------:|:--------------:|
21 | | Team Power Rankings | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
22 | | Game Probabilities for Today's Games | ✓ | ✓ | | | | |
23 | | Live Scores for Today's Games | ✓ | | | | | |
24 | | Game Probabilities a Game Between any 2 Teams | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
25 | | View Biggest Single-Game Upsets | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
26 | | View Best Single-Game Team Performances | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
27 | | Most Consistent Teams | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
28 | | Team Game Logs | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
29 | | Win Probability for a Team Against all Other Teams | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
30 | | View Model Accuracy | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
31 | | Download CSV's | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
32 |
33 | ### Data Sources
34 |
35 | | League | Data Source(s) |
36 | |----------------|-------------------------------------------------------------------------------|
37 | | NHL | [NHL API](https://api-web.nhle.com/v1/schedule/now) |
38 | | NBA | [Basketball Reference](https://www.basketball-reference.com/) |
39 | | CFB | [FlashScore](https://www.flashscore.ca/football/usa/ncaa/results/) |
40 | | KHL | [FlashScore](https://www.flashscore.ca/hockey/russia/khl/results/) |
41 | | AHL | [FlashScore](https://www.flashscore.ca/hockey/usa/ahl/results/) |
42 | | Custom Leagues | User-uploaded CSV |
43 |
44 |
45 | ### Model Accuracy
46 |
47 | Here are the log loss values for the model over the past 3 seasons for the NHL and NBA.
48 | | Season | NHL | NBA |
49 | |----------------|-------|-------|
50 | | 2023 | 0.639 | 0.631 |
51 | | 2022 | 0.633 | 0.623 |
52 | | 2021 | 0.638 | 0.639 |
53 |
54 | ## Featues
55 | The following is a brief explanation of some of the core features. Note that the CSV's outputted by the program are included in the files for each of the leagues in this repo.
56 |
57 | ### Live Scraping
58 | The program scrapes data from the NHL API every time it is run, so the data is updated automatically. The time it takes to scrape all game data is up to 3 seconds and depends on how late in the season it is.
59 |
60 | ### Power Rankings
61 | The following is an image of the final power rankings for the 2022-23 NHL season. Shows the team's POWER ranking, record, goal differential, strength of schedule, etc.
62 |
63 |
64 | ### Today's Games
65 | The following is an image of the game probabilities for all the games being played today. The home and away scores are updated live.
66 |
67 |
68 | ### Custom Game Selector
69 | The following is an example of the output of the custom game selector. The user enters a home and away team, and the program shows the probability of each team winning and the probability of them winning by _n_ goals based on the model.
70 |
71 |
72 | ### Biggest Upsets
73 | This returns a CSV of every game played in the season, sorted from the biggest upset to the smallest. The quantification of an upset is done by subtracting the actual goal differential of the game by the expected goal differential, based on the model. Below, it seems that the biggest upset of the 2022-23 season was the Colorado Avalanche's 7-0 victory over the Ottawa Senators on January 14th, 2023. They were expected to win by 0.69 goals but won by 7.
74 |
75 |
76 | ### Best Performances
77 | This returns a CSV of every team's games, their opponent, the score, and the team's performance. Their performance is determined by the goal differential of the game, adjusting for the strength of their opponent in that game. Below, it appears that the Boston Bruins' 7-0 victory over the Buffalo Sabres on March 19th, 2023 was the best team performance of the season while Montreal's 9-2 loss to Washington
78 | on New Year's Eve 2022 was the worst.
79 |
80 |
81 | ### Team Consistency Ratings
82 | The option shows how consistent teams were. This is defined as how much their actual performances differed from their expected performances, or how good the model was at predicting scores for their games. From the image below, it seems that the Minnesota Wild were the most consistent team, and the Detroit Red Wings were the least.
83 |
84 |
85 | ### Team Game Logs
86 | This option shows every game that a team has played, and how it affected their POWER score. This value can also be seen as how well they performed in that game, hence the column title "Performance". The program prompts the user for which team's game log to display.
87 |
88 |
89 | ### Team Probability Big Board
90 | Entering this option prompts the user to enter a team. Then, a table is returned where each row is a different team, and the table shows the probability that the entered team will beat the team in the given row and the probability of the team betting the team in the row by _n_ goals. For the example below, the program suggests that the Toronto Maple Leafs have a 75.33% chance of beating the Montreal Canadiens, and that the probability of TOR beating MTL by 5+ goals is 10.64%.
91 |
92 |
93 | ### View Model Performance
94 | Returns a Matplotlib plot encapsulating the model's predictions and accuracy. The grey dots represent true instances of games, while the black line is the model's prediction of the probability that the home team will win. The function representing the sigmoid is then printed to the terminal, which can be useful to employ the model elsewhere and in debugging. This is the result for the 2022-23 NHL season:
95 |
96 |
97 | ## Update Log
98 | **February 1, 2023**: Addition of NHL Game Probability Model
99 | **February 3, 2023**: Addition of NBA Game Probability Model
100 | **February 3, 2023**: Addition of Custom League Probability Model
101 | **April 14, 2023**: Pandas deprecated ```pd.DataFrame.append()```, replaced with ```concat()```
102 | **November 10, 2023**: NHL API was shut down and replaced. The API lost an endpoint with all games in a season, so now the program has to loop through each team's schedule and make 30x more API calls which increased runtime from ~1s to ~15s.
103 | **November 4, 2024**: Added custom league support for the KHL, AHL, and CFB.
--------------------------------------------------------------------------------