-
Notifications
You must be signed in to change notification settings - Fork 0
/
runsim.py
482 lines (391 loc) · 19 KB
/
runsim.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
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
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
from __future__ import division
import csv
import math
import random
import sys
import os
import itertools
import traceback
import tkinter as tk
'''
ncaasim is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
'''
class Team:
def __init__(self, name, predictors, known_wins):
self.name = name
self.predictors = predictors
self.num_round1_wins = 0
self.num_round2_wins = 0
self.num_round3_wins = 0
self.num_round4_wins = 0
self.num_round5_wins = 0
self.num_championship_wins = 0
self.known_wins = known_wins
def __str__(self):
return self.name
# Error handling decorator
# http://stackoverflow.com/questions/6666882/tkinter-python-catching-exceptions
class safe:
def __init__(self, function):
self.function = function
def __call__(self, *args):
try:
return self.function(*args)
except Exception as e:
# make a popup here with your exception information.
# might want to use traceback module to parse the exception info
friendly_error = '''
Warning - program encountered errors:
'''
friendly_error += str(e)
friendly_error += "\n\t\t" + str(sys.exc_info()[0])
friendly_error += '''
Troubleshooting tips:
* For "Permission Denied" errors, please close all input and output
files and try again
* For other errors, check your settings.csv file, and make sure it
is saved in CSV (not Excel) format.
'''
tex.insert(tk.END, friendly_error)
tex.see(tk.END) # Scroll if necessary
top.update_idletasks()
class Match:
def __init__(self, team1, team2, known_winner=None):
self.team1 = team1
self.team2 = team2
if known_winner is None:
# Calculate team1's win probability
team1_predictors = self.team1.predictors
team2_predictors = self.team2.predictors
predictor_differences = []
for idx, predictor in enumerate(team1_predictors):
predictor_differences.append(predictor - team2_predictors[idx])
exponent = 0
exponent += constant
for idx in range(num_predictors):
# If predictor is continuous
if type(predictor_coefficients[idx]) is not dict:
exponent += predictor_coefficients[idx] * predictor_differences[idx]
else: # Predictor is categorical
difference_string = str(predictor_differences[idx])
if difference_string == "0.0":
difference_string = "0"
else:
difference_string = difference_string.rstrip('.0')
exponent += predictor_coefficients[idx][difference_string]
odds_team1_wins = math.e ** exponent
self.probability_team_one_wins = odds_team1_wins / (1.0 + odds_team1_wins)
# Now pick a winner!
rand_num = random.random()
if rand_num <= self.probability_team_one_wins:
self.winner = self.team1
else:
self.winner = self.team2
else:
self.winner = known_winner
if self.winner == self.team1:
self.probability_team_one_wins = 1
else:
self.probability_team_one_wins = 0
def __str__(self):
if not self.winner:
self.winner = "Unknown"
return "Match: "+self.team1.name+" vs. "+self.team2.name+"\nProbability former team wins is " + \
str(self.probability_team_one_wins) + "\nWinner is: " + str(self.winner)+"\n"
def cbc(tex, newtext):
return lambda : callback(tex, newtext)
def callback(tex, newtext):
tex.insert(tk.END, newtext)
tex.see(tk.END) # Scroll if necessary
def main():
b = tk.Button(bop, text='Run', command=lambda: runcalcs(tex, top))
welcome = '''
Welcome to ncaasim!
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
See https://github.com/tongning/ncaasim for source code.
=========================================================================
* Before you begin, ensure you have your inputs saved in settings.csv, and
that settings.csv is stored in the same folder as this program.
* Click Run to launch.\n'''
tex.insert(tk.END, welcome)
tex.see(tk.END) # Scroll if necessary
top.update_idletasks()
b.pack()
tk.Button(bop, text='Exit', command=top.destroy).pack()
top.mainloop()
@safe
def runcalcs(tex, top):
running_message = '''
Running...
'''
tex.insert(tk.END, running_message)
tex.see(tk.END) # Scroll if necessary
top.update_idletasks()
global constant
global num_predictors
global predictor_coefficients
matches = [None] * 63
predictor_coefficients = []
all_csv_rows = []
teams = []
non_playing_teams = []
with open('settings.csv', 'r') as csvfile:
csv_reader = csv.reader(csvfile, delimiter=',', quotechar='|')
year_row = next(csv_reader)
tournament_year = int(year_row[1])
num_runs_row = next(csv_reader)
num_runs = int(num_runs_row[1])
round_of_row = next(csv_reader)
round_of = int(round_of_row[1])
num_predictors_row = next(csv_reader)
num_predictors = int(num_predictors_row[1])
constant_row = next(csv_reader)
constant = float(constant_row[1])
print("constant is "+str(constant))
for i in range(num_predictors):
predictor_coefficient_row = next(csv_reader)
print(predictor_coefficient_row)
if "categorical!" not in predictor_coefficient_row[0].lower():
predictor_coefficients.append(float(predictor_coefficient_row[1]))
else:
# Pass a dictionary defining the proper constant to add to y-hat for each possible difference, rather
# than a coefficient to multiply the difference by
level_diff_dict = {}
# Read horizontally in column until no more level-constant pairs can be found
col_index = 1
while True:
try:
level_diff = str(predictor_coefficient_row[col_index])
level_diff_constant = float(predictor_coefficient_row[col_index + 1])
# Add pair to the dictionary
level_diff_dict[level_diff] = level_diff_constant
col_index += 2
except IndexError:
break
predictor_coefficients.append(level_diff_dict)
# Skip a heading row
next(csv_reader)
# Create team objects
finished_bracket_teams = False
while True:
try:
team_row = next(csv_reader)
except StopIteration:
break
print(team_row)
if 'not on bracket' in team_row[0].lower() or team_row[1] == '':
finished_bracket_teams = True
continue
team_name = team_row[1]
print(team_name)
team_known_wins = int(team_row[2])
team_predictor_values = []
column_num = 3
try:
while team_row[column_num] != '':
team_predictor_values.append(float(team_row[column_num]))
column_num += 1
except (IndexError, ValueError):
pass
new_team = Team(team_name, team_predictor_values, team_known_wins)
if not finished_bracket_teams:
teams.append(new_team)
else:
non_playing_teams.append(new_team)
# Error check
# Make sure "Round of" value is valid - 64, 32, 16, 8, or 2
valid_round_ofs = [64, 32, 16, 8, 2]
if round_of not in valid_round_ofs:
print("Error: The ROUND OF value specified in your CSV is invalid.")
print("Value should be 64, 32, 16, 8, or 2.")
print("Please double-check your CSV and relaunch this program.")
print("RUN FAILED - INPUT ERROR")
input()
sys.exit()
for index, j in enumerate(range(num_runs)):
# Run through first round matches, # 0-31
team1_idx = 0
team2_idx = 1
match_num = 0
while team2_idx < len(teams):
# Does either team already have 1 or more wins so far?
# If so, they've already won round 1 match; force winner
if index == 0:
tex.insert(tk.END, "\t"+ str(teams[team1_idx]) + " vs. " + str(teams[team2_idx]) + "\n")
if teams[team1_idx].known_wins >= 1: # We already know team 1 won this
matches[match_num] = Match(teams[team1_idx], teams[team2_idx], teams[team1_idx])
elif teams[team2_idx].known_wins >= 1: # We already know team 2 won this
matches[match_num] = Match(teams[team1_idx], teams[team2_idx], teams[team2_idx])
else: # Determine winner by probability
matches[match_num] = Match(teams[team1_idx], teams[team2_idx])
matches[match_num].winner.num_round1_wins += 1
team1_idx += 2
team2_idx += 2
match_num += 1
if index == 0:
tex.insert(tk.END, "\n\t"+ "Running...Please be patient; program may "
"appear to hang.\n")
# Run through second round matches, # 32-47
tex.see(tk.END) # Scroll if necessary
top.update_idletasks()
for idx in range(32, 48):
# Does either team already have 2 or more wins so far?
# If so, they've already won round 2 match; force winner
if matches[2*idx-64].winner.known_wins >= 2:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64].winner)
elif matches[2*idx-64+1].winner.known_wins >= 2:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64+1].winner)
else:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner)
matches[idx].winner.num_round2_wins += 1
# Run through third round matches, # 48-55
for idx in range(48, 56):
# Does either team already have 3 or more wins so far?
# If so, they've already won round 3 match; force winner
if matches[2*idx-64].winner.known_wins >= 3:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64].winner)
elif matches[2*idx-64+1].winner.known_wins >= 3:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64+1].winner)
else:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner)
matches[idx].winner.num_round3_wins += 1
# Run through fourth round matches, # 56-59
for idx in range(56, 60):
# Does either team already have 4 or more wins so far?
# If so, they've already won round 4 match; force winner
if matches[2*idx-64].winner.known_wins >= 4:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64].winner)
elif matches[2*idx-64+1].winner.known_wins >= 4:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64+1].winner)
else:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner)
matches[idx].winner.num_round4_wins += 1
# 60 and 61
for idx in range(60, 62):
# Does either team already have 5 or more wins so far?
# If so, they've already won round 5 match; force winner
if matches[2*idx-64].winner.known_wins >= 5:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64].winner)
elif matches[2*idx-64+1].winner.known_wins >= 5:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64+1].winner)
else:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner)
matches[idx].winner.num_round5_wins += 1
# Championship, # 62
for idx in range(62, 63):
# Does either team already have 6 or more wins so far?
# If so, they've already won championship match; force winner
if matches[2*idx-64].winner.known_wins >= 6:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64].winner)
elif matches[2*idx-64+1].winner.known_wins >= 6:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner, matches[2*idx-64+1].winner)
else:
matches[idx] = Match(matches[2*idx-64].winner, matches[2*idx-64+1].winner)
matches[idx].winner.num_championship_wins += 1
with open('out.csv', "w", newline='') as out_file:
writer = csv.writer(out_file, delimiter=',')
for team in teams:
writer.writerow([team, str(team.num_round1_wins/num_runs), str(team.num_round2_wins/num_runs),
str(team.num_round3_wins/num_runs), str(team.num_round4_wins/num_runs),
str(team.num_round5_wins/num_runs), str(team.num_championship_wins/num_runs)])
# Generate all possible pairings
# Join the non-playing teams into the teams list
teams = teams + non_playing_teams
combinations = itertools.combinations(teams, 2)
all_matches = []
for combination in combinations:
all_matches.append(Match(combination[0], combination[1]))
with open('all_matches.csv', "w", newline='') as out_file:
writer = csv.writer(out_file, delimiter=',')
writer.writerow(["First team", "Second team", "Probability First Team Wins"])
for match in all_matches:
writer.writerow([match.team1, match.team2, match.probability_team_one_wins])
kaggle_generated = True
try:
with open('Teams.csv', 'r') as teamfile:
teamfile_reader = csv.reader(teamfile, delimiter=',', quotechar='|')
# Skip header row
next(teamfile_reader)
# Read teams into dictionary, using name as key and id as value
team_ids_dict = {}
while True:
try:
team_row = next(teamfile_reader)
except StopIteration:
break
team_ids_dict[team_row[1].lower()] = team_row[0]
# Now create the output file
with open('kaggle_output.csv', "w", newline='') as kaggle_file:
writer = csv.writer(kaggle_file, delimiter=',')
writer.writerow(["id","pred"])
team_not_found_err = '''
I tried to generate Kaggle output, but team
%s was not listed in Teams.csv, so I could not
find the team ID. Feel free to ignore this if you do
not want Kaggle output.
'''
print(team_ids_dict)
for match in all_matches:
if str(match.team1).lower() not in team_ids_dict:
raise LookupError(team_not_found_err % match.team1)
break
if str(match.team2).lower() not in team_ids_dict:
raise LookupError(team_not_found_err % match.team2)
break
team1_id = team_ids_dict[str(match.team1).lower()]
team2_id = team_ids_dict[str(match.team2).lower()]
if int(team1_id) > int(team2_id):
writer.writerow([str(tournament_year)+"_"+team2_id+"_"+team1_id,str(1-match.probability_team_one_wins)])
else:
writer.writerow([str(tournament_year)+"_"+team1_id+"_"+team2_id,str(match.probability_team_one_wins)])
except (EnvironmentError, LookupError) as error:
kaggle_generated = False
finishtext = '''
Warning: Unable to generate Kaggle output.
%s
'''
tex.insert(tk.END, finishtext % error)
tex.see(tk.END) # Scroll if necessary
top.update_idletasks()
pass
finishtext = '''
Finished!
To verify that you've listed the teams in the correct order,
the first-round matches are listed above. Make sure these
are correct!!
* Probabilities of progression have been saved to out.csv
* Probabilities of all matchups have been saved to all_matches.csv'''
if kaggle_generated:
finishtext += '''
* Kaggle submission file saved to kaggle_output.csv'''
else:
finishtext += '''
* Kaggle submission file NOT generated due to errors.'''
os.remove('kaggle_output.csv')
tex.insert(tk.END, finishtext)
tex.see(tk.END) # Scroll if necessary
top.update_idletasks()
if __name__ == "__main__":
# GUI code
# http://stackoverflow.com/questions/14879916/python-tkinter-make-any-output-appear-in-a-text-box-on-gui-not-in-the-shell
top = tk.Tk()
top.resizable(width='FALSE', height='FALSE')
top.wm_title("ncaasim")
tex = tk.Text(master=top)
tex.pack(side=tk.RIGHT)
bop = tk.Frame()
bop.pack(side=tk.LEFT)
main()