├── .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 | Screenshot 2023-10-24 at 4 24 35 PM 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 | Screenshot 2023-10-24 at 4 26 17 PM 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 | Screenshot 2023-10-24 at 4 29 23 PM 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 | Screenshot 2023-10-24 at 4 36 10 PM 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 | Screenshot 2023-10-24 at 5 16 26 PM 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 | Screenshot 2023-10-24 at 4 38 19 PM 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 | Screenshot 2023-10-24 at 4 42 16 PM 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 | Screenshot 2023-10-24 at 4 44 09 PM 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 | Screenshot 2023-10-24 at 4 52 25 PM 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. --------------------------------------------------------------------------------