Spaces:
Runtime error
Runtime error
Commit
Β·
fc2b782
1
Parent(s):
79347f6
Add results
Browse files- Source/Build/update.py +28 -0
- Source/Data/results.csv +3 -0
- Source/Predict/predict.py +41 -11
- Templates/index.html +65 -36
Source/Build/update.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import nfl_data_py.nfl_data_py as nfl
|
| 2 |
import build
|
| 3 |
import datetime as dt
|
|
@@ -6,10 +7,20 @@ import pandas as pd
|
|
| 6 |
pd.set_option('chained_assignment',None)
|
| 7 |
pd.set_option('display.max_columns',None)
|
| 8 |
import os
|
|
|
|
| 9 |
|
| 10 |
current_directory = os.path.dirname(os.path.abspath(__file__))
|
| 11 |
parent_directory = os.path.dirname(current_directory)
|
| 12 |
data_directory = os.path.join(parent_directory, 'Data')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
# get current season
|
| 15 |
year = dt.datetime.now().year
|
|
@@ -20,3 +31,20 @@ current_season = year if month in [8,9,10,11,12] else year-1
|
|
| 20 |
build.build_gbg_data(get_seasons=[current_season])
|
| 21 |
build.add_odds_data()
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from operator import index
|
| 2 |
import nfl_data_py.nfl_data_py as nfl
|
| 3 |
import build
|
| 4 |
import datetime as dt
|
|
|
|
| 7 |
pd.set_option('chained_assignment',None)
|
| 8 |
pd.set_option('display.max_columns',None)
|
| 9 |
import os
|
| 10 |
+
import pickle as pkl
|
| 11 |
|
| 12 |
current_directory = os.path.dirname(os.path.abspath(__file__))
|
| 13 |
parent_directory = os.path.dirname(current_directory)
|
| 14 |
data_directory = os.path.join(parent_directory, 'Data')
|
| 15 |
+
pickle_directory = os.path.join(parent_directory, 'Pickles')
|
| 16 |
+
|
| 17 |
+
# get team abbreviations
|
| 18 |
+
file_path = os.path.join(pickle_directory, 'team_name_to_abbreviation.pkl')
|
| 19 |
+
with open(file_path, 'rb') as f:
|
| 20 |
+
team_name_to_abbreviation = pkl.load(f)
|
| 21 |
+
file_path = os.path.join(pickle_directory, 'team_abbreviation_to_name.pkl')
|
| 22 |
+
with open(file_path, 'rb') as f:
|
| 23 |
+
team_abbreviation_to_name = pkl.load(f)
|
| 24 |
|
| 25 |
# get current season
|
| 26 |
year = dt.datetime.now().year
|
|
|
|
| 31 |
build.build_gbg_data(get_seasons=[current_season])
|
| 32 |
build.add_odds_data()
|
| 33 |
|
| 34 |
+
# get winners
|
| 35 |
+
pbp = build.get_pbp_data([2023])
|
| 36 |
+
pbp = pbp.drop_duplicates(subset='game_id')
|
| 37 |
+
pbp[['season','week','away','home']] = pbp['game_id'].str.split('_', expand=True)
|
| 38 |
+
games = pbp[['game_id','away_score','home_score','season','week','away','home']]
|
| 39 |
+
games[['away_score','home_score','season','week']] = games[['away_score','home_score','season','week']].astype(int)
|
| 40 |
+
|
| 41 |
+
games['away_team'] = games['away'].map(team_abbreviation_to_name)
|
| 42 |
+
games['home_team'] = games['home'].map(team_abbreviation_to_name)
|
| 43 |
+
|
| 44 |
+
games['total'] = games['away_score'] + games['home_score']
|
| 45 |
+
games['winner'] = [a if a_s>h_s else h if h_s>a_s else 'Tie' for a,h,a_s,h_s in games[['away_team','home_team','away_score','home_score']].values]
|
| 46 |
+
|
| 47 |
+
file_path = os.path.join(data_directory, 'results.csv')
|
| 48 |
+
games[['game_id','total','winner']].to_csv(file_path, index=False)
|
| 49 |
+
|
| 50 |
+
|
Source/Data/results.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cba191ba24d4a23d9ebd2720203deb51546e3a121a335320bb50db8bcb6084b9
|
| 3 |
+
size 584
|
Source/Predict/predict.py
CHANGED
|
@@ -16,6 +16,9 @@ pickle_directory = os.path.join(parent_directory, 'Pickles')
|
|
| 16 |
file_path = os.path.join(data_directory, 'gbg_this_year.csv')
|
| 17 |
gbg = pd.read_csv(file_path, low_memory=False)
|
| 18 |
|
|
|
|
|
|
|
|
|
|
| 19 |
# get team abbreviations
|
| 20 |
file_path = os.path.join(pickle_directory, 'team_name_to_abbreviation.pkl')
|
| 21 |
with open(file_path, 'rb') as f:
|
|
@@ -77,41 +80,68 @@ def get_one_week(home,away,season,week):
|
|
| 77 |
|
| 78 |
def predict(home,away,season,week,total):
|
| 79 |
# finish preparing data
|
| 80 |
-
|
| 81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
data = get_one_week(home_abbrev,away_abbrev,season,week)
|
| 83 |
data['Total Score Close'] = total
|
| 84 |
matrix = xgb.DMatrix(data.astype(float).values)
|
| 85 |
|
|
|
|
|
|
|
|
|
|
| 86 |
# moneyline
|
| 87 |
-
model = '
|
| 88 |
file_path = os.path.join(model_directory, f'{model}.json')
|
| 89 |
xgb_ml = xgb.Booster()
|
| 90 |
xgb_ml.load_model(file_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
try:
|
| 92 |
ml_predicted_proba = xgb_ml.predict(matrix)[0][1]
|
| 93 |
-
print(xgb_ml.predict(matrix))
|
| 94 |
winner_proba = max([ml_predicted_proba, 1-ml_predicted_proba]).item()
|
| 95 |
-
moneyline = {'Winner': [home if ml_predicted_proba>0.
|
| 96 |
-
'Probabilities':[winner_proba]
|
|
|
|
| 97 |
except:
|
| 98 |
moneyline = {'Winner': 'NA',
|
| 99 |
-
'Probabilities':['N/A']
|
|
|
|
| 100 |
|
| 101 |
# over/under
|
| 102 |
model = 'xgboost_OU_no_odds_60.8%'
|
| 103 |
file_path = os.path.join(model_directory, f'{model}.json')
|
| 104 |
xgb_ou = xgb.Booster()
|
| 105 |
xgb_ou.load_model(file_path)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 106 |
try:
|
| 107 |
ou_predicted_proba = xgb_ou.predict(matrix)[0][1]
|
| 108 |
ou_proba = max([ou_predicted_proba, 1-ou_predicted_proba]).item()
|
|
|
|
| 109 |
over_under = {'Over/Under': ['Over' if ou_predicted_proba>0.5 else 'Under'],
|
| 110 |
-
'Probability': [ou_proba]
|
|
|
|
| 111 |
except:
|
| 112 |
over_under = {'Over/Under': 'N/A',
|
| 113 |
-
'Probability': ['N/A']
|
|
|
|
| 114 |
|
| 115 |
-
|
| 116 |
-
game_id = str(season) + '_' + str(week) + '_' + away_abbrev + '_' + home_abbrev
|
| 117 |
return game_id, moneyline, over_under
|
|
|
|
| 16 |
file_path = os.path.join(data_directory, 'gbg_this_year.csv')
|
| 17 |
gbg = pd.read_csv(file_path, low_memory=False)
|
| 18 |
|
| 19 |
+
file_path = os.path.join(data_directory, 'results.csv')
|
| 20 |
+
results = pd.read_csv(file_path, low_memory=False)
|
| 21 |
+
|
| 22 |
# get team abbreviations
|
| 23 |
file_path = os.path.join(pickle_directory, 'team_name_to_abbreviation.pkl')
|
| 24 |
with open(file_path, 'rb') as f:
|
|
|
|
| 80 |
|
| 81 |
def predict(home,away,season,week,total):
|
| 82 |
# finish preparing data
|
| 83 |
+
if len(home)>4:
|
| 84 |
+
home_abbrev = team_name_to_abbreviation[home]
|
| 85 |
+
else:
|
| 86 |
+
home_abbrev = home
|
| 87 |
+
|
| 88 |
+
if len(away)>4:
|
| 89 |
+
away_abbrev = team_name_to_abbreviation[away]
|
| 90 |
+
else:
|
| 91 |
+
away_abbrev = away
|
| 92 |
+
|
| 93 |
data = get_one_week(home_abbrev,away_abbrev,season,week)
|
| 94 |
data['Total Score Close'] = total
|
| 95 |
matrix = xgb.DMatrix(data.astype(float).values)
|
| 96 |
|
| 97 |
+
# create game id
|
| 98 |
+
game_id = str(season) + '_0' + str(week) + '_' + away_abbrev + '_' + home_abbrev
|
| 99 |
+
|
| 100 |
# moneyline
|
| 101 |
+
model = 'xgboost_ML_no_odds_71.4%'
|
| 102 |
file_path = os.path.join(model_directory, f'{model}.json')
|
| 103 |
xgb_ml = xgb.Booster()
|
| 104 |
xgb_ml.load_model(file_path)
|
| 105 |
+
|
| 106 |
+
try:
|
| 107 |
+
moneyline_result = results.loc[results['game_id']==game_id, 'winner'].item()
|
| 108 |
+
except:
|
| 109 |
+
moneyline_result = 'N/A'
|
| 110 |
+
|
| 111 |
try:
|
| 112 |
ml_predicted_proba = xgb_ml.predict(matrix)[0][1]
|
|
|
|
| 113 |
winner_proba = max([ml_predicted_proba, 1-ml_predicted_proba]).item()
|
| 114 |
+
moneyline = {'Winner': [home if ml_predicted_proba>0.5 else away if ml_predicted_proba<0.5 else 'Toss-Up'],
|
| 115 |
+
'Probabilities':[winner_proba],
|
| 116 |
+
'Result': moneyline_result}
|
| 117 |
except:
|
| 118 |
moneyline = {'Winner': 'NA',
|
| 119 |
+
'Probabilities':['N/A'],
|
| 120 |
+
'Result': moneyline_result}
|
| 121 |
|
| 122 |
# over/under
|
| 123 |
model = 'xgboost_OU_no_odds_60.8%'
|
| 124 |
file_path = os.path.join(model_directory, f'{model}.json')
|
| 125 |
xgb_ou = xgb.Booster()
|
| 126 |
xgb_ou.load_model(file_path)
|
| 127 |
+
|
| 128 |
+
try:
|
| 129 |
+
result = results.loc[results['game_id']==game_id, 'total'].item()
|
| 130 |
+
over_under_result = 'Over' if float(result)>float(total) else 'Under'
|
| 131 |
+
except:
|
| 132 |
+
over_under_result = 'N/A'
|
| 133 |
+
|
| 134 |
try:
|
| 135 |
ou_predicted_proba = xgb_ou.predict(matrix)[0][1]
|
| 136 |
ou_proba = max([ou_predicted_proba, 1-ou_predicted_proba]).item()
|
| 137 |
+
|
| 138 |
over_under = {'Over/Under': ['Over' if ou_predicted_proba>0.5 else 'Under'],
|
| 139 |
+
'Probability': [ou_proba],
|
| 140 |
+
'Result': over_under_result}
|
| 141 |
except:
|
| 142 |
over_under = {'Over/Under': 'N/A',
|
| 143 |
+
'Probability': ['N/A'],
|
| 144 |
+
'Result': over_under_result}
|
| 145 |
|
| 146 |
+
print(moneyline)
|
|
|
|
| 147 |
return game_id, moneyline, over_under
|
Templates/index.html
CHANGED
|
@@ -82,11 +82,13 @@
|
|
| 82 |
position: relative;
|
| 83 |
width: 100%;
|
| 84 |
text-align: center;
|
|
|
|
|
|
|
|
|
|
| 85 |
}
|
| 86 |
.winner-image {
|
| 87 |
-
width: 50px;
|
| 88 |
height: auto;
|
| 89 |
-
margin:
|
| 90 |
transition: opacity 0.3s ease;
|
| 91 |
}
|
| 92 |
.overlay {
|
|
@@ -95,7 +97,7 @@
|
|
| 95 |
left: 0;
|
| 96 |
width: 100%;
|
| 97 |
height: 100%;
|
| 98 |
-
background-color: rgba(0, 0, 0, 0.
|
| 99 |
color: white;
|
| 100 |
display: flex;
|
| 101 |
justify-content: center;
|
|
@@ -117,23 +119,25 @@
|
|
| 117 |
}
|
| 118 |
.over-under-text {
|
| 119 |
display: inline-block;
|
| 120 |
-
margin:
|
|
|
|
|
|
|
| 121 |
}
|
| 122 |
.over {
|
| 123 |
-
|
| 124 |
}
|
| 125 |
.under {
|
| 126 |
-
|
| 127 |
}
|
| 128 |
.na {
|
| 129 |
-
|
| 130 |
}
|
| 131 |
.over-under-wrapper .overlay {
|
| 132 |
position: absolute;
|
| 133 |
top: 0;
|
| 134 |
left: 0;
|
| 135 |
width: 100%;
|
| 136 |
-
background-color: rgba(0, 0, 0, 0.
|
| 137 |
color: white;
|
| 138 |
display: flex;
|
| 139 |
justify-content: center;
|
|
@@ -259,36 +263,36 @@
|
|
| 259 |
|
| 260 |
<script>
|
| 261 |
async function fetchGames() {
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
|
|
|
| 289 |
}
|
| 290 |
-
|
| 291 |
-
});
|
| 292 |
}
|
| 293 |
|
| 294 |
fetchGames();
|
|
@@ -341,6 +345,18 @@
|
|
| 341 |
winnerImg.className = 'winner-image hidden';
|
| 342 |
wrapperDiv.appendChild(winnerImg);
|
| 343 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 344 |
setTimeout(() => {
|
| 345 |
winnerImg.classList.remove('hidden');
|
| 346 |
}, 10);
|
|
@@ -370,6 +386,18 @@
|
|
| 370 |
|
| 371 |
overUnderDiv.appendChild(textDiv);
|
| 372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
setTimeout(() => {
|
| 374 |
overUnderDiv.classList.remove('hidden');
|
| 375 |
}, 10);
|
|
@@ -380,6 +408,7 @@
|
|
| 380 |
overUnderDiv.appendChild(overUnderOverlayDiv);
|
| 381 |
|
| 382 |
overUnderCell.appendChild(overUnderDiv);
|
|
|
|
| 383 |
});
|
| 384 |
}
|
| 385 |
});
|
|
|
|
| 82 |
position: relative;
|
| 83 |
width: 100%;
|
| 84 |
text-align: center;
|
| 85 |
+
display: flex;
|
| 86 |
+
justify-content: center;
|
| 87 |
+
align-items: center;
|
| 88 |
}
|
| 89 |
.winner-image {
|
|
|
|
| 90 |
height: auto;
|
| 91 |
+
margin: 0;
|
| 92 |
transition: opacity 0.3s ease;
|
| 93 |
}
|
| 94 |
.overlay {
|
|
|
|
| 97 |
left: 0;
|
| 98 |
width: 100%;
|
| 99 |
height: 100%;
|
| 100 |
+
background-color: rgba(0, 0, 0, 0.9);
|
| 101 |
color: white;
|
| 102 |
display: flex;
|
| 103 |
justify-content: center;
|
|
|
|
| 119 |
}
|
| 120 |
.over-under-text {
|
| 121 |
display: inline-block;
|
| 122 |
+
margin: 0;
|
| 123 |
+
margin-right: 2px;
|
| 124 |
+
font-weight: bold;
|
| 125 |
}
|
| 126 |
.over {
|
| 127 |
+
color: green;
|
| 128 |
}
|
| 129 |
.under {
|
| 130 |
+
color: red;
|
| 131 |
}
|
| 132 |
.na {
|
| 133 |
+
color: white;
|
| 134 |
}
|
| 135 |
.over-under-wrapper .overlay {
|
| 136 |
position: absolute;
|
| 137 |
top: 0;
|
| 138 |
left: 0;
|
| 139 |
width: 100%;
|
| 140 |
+
background-color: rgba(0, 0, 0, 0.9);
|
| 141 |
color: white;
|
| 142 |
display: flex;
|
| 143 |
justify-content: center;
|
|
|
|
| 263 |
|
| 264 |
<script>
|
| 265 |
async function fetchGames() {
|
| 266 |
+
const response = await fetch('/get_games');
|
| 267 |
+
const pulled_games = await response.json();
|
| 268 |
+
const table = document.getElementById('gameTable');
|
| 269 |
+
const columns = ['Date','Away Team', 'Home Team'];
|
| 270 |
+
|
| 271 |
+
pulled_games.forEach((game) => {
|
| 272 |
+
const row = table.insertRow(-1);
|
| 273 |
+
|
| 274 |
+
columns.forEach((column) => {
|
| 275 |
+
const cell = row.insertCell(-1);
|
| 276 |
+
if (column === 'Away Team' || column === 'Home Team') {
|
| 277 |
+
const img = document.createElement('img');
|
| 278 |
+
img.src = `/Static/${game[column]}.webp`;
|
| 279 |
+
img.alt = game[column];
|
| 280 |
+
img.width = 50;
|
| 281 |
+
cell.appendChild(img);
|
| 282 |
+
} else {
|
| 283 |
+
cell.textContent = game[column];
|
| 284 |
+
}
|
| 285 |
+
});
|
| 286 |
|
| 287 |
+
for (let i = 0; i < 3; i++) {
|
| 288 |
+
const cell = row.insertCell(-1);
|
| 289 |
+
if (i<1) {
|
| 290 |
+
const input = document.createElement('input');
|
| 291 |
+
input.type = 'text';
|
| 292 |
+
cell.appendChild(input);
|
| 293 |
+
}
|
| 294 |
}
|
| 295 |
+
});
|
|
|
|
| 296 |
}
|
| 297 |
|
| 298 |
fetchGames();
|
|
|
|
| 345 |
winnerImg.className = 'winner-image hidden';
|
| 346 |
wrapperDiv.appendChild(winnerImg);
|
| 347 |
|
| 348 |
+
const winnerEmojiDiv = document.createElement('div');
|
| 349 |
+
winnerEmojiDiv.className = 'emoji';
|
| 350 |
+
if (moneyline.Winner === moneyline.Result) {
|
| 351 |
+
winnerEmojiDiv.textContent = 'β
';
|
| 352 |
+
} else {
|
| 353 |
+
winnerEmojiDiv.textContent = 'β';
|
| 354 |
+
}
|
| 355 |
+
if (moneyline.Result === 'N/A') {
|
| 356 |
+
winnerEmojiDiv.textContent = '';
|
| 357 |
+
}
|
| 358 |
+
wrapperDiv.appendChild(winnerEmojiDiv);
|
| 359 |
+
|
| 360 |
setTimeout(() => {
|
| 361 |
winnerImg.classList.remove('hidden');
|
| 362 |
}, 10);
|
|
|
|
| 386 |
|
| 387 |
overUnderDiv.appendChild(textDiv);
|
| 388 |
|
| 389 |
+
const overEmojiDiv = document.createElement('div');
|
| 390 |
+
overEmojiDiv.className = 'emoji';
|
| 391 |
+
if (data.over_unders[index]['Over/Under'] === data.over_unders[index]['Result']) {
|
| 392 |
+
overEmojiDiv.textContent = 'β
';
|
| 393 |
+
} else {
|
| 394 |
+
overEmojiDiv.textContent = 'β';
|
| 395 |
+
}
|
| 396 |
+
if (data.over_unders[index]['Result'] === 'N/A') {
|
| 397 |
+
overEmojiDiv.textContent = '';
|
| 398 |
+
}
|
| 399 |
+
overUnderDiv.appendChild(overEmojiDiv);
|
| 400 |
+
|
| 401 |
setTimeout(() => {
|
| 402 |
overUnderDiv.classList.remove('hidden');
|
| 403 |
}, 10);
|
|
|
|
| 408 |
overUnderDiv.appendChild(overUnderOverlayDiv);
|
| 409 |
|
| 410 |
overUnderCell.appendChild(overUnderDiv);
|
| 411 |
+
|
| 412 |
});
|
| 413 |
}
|
| 414 |
});
|