I realized at the last moment that I hadn't seeded my NCAA Men's Basketball tournament brackets. Usually I like to have some data driven analysis to back up my sports predictions, but
- I didn't have time.
- This is March Madness
So I figured I'd make my choices randomly. I still wanted my choices have some shot at being right, so I decided to use the d20 I had in my backpack to weight things in favor of the higher seeded teams. As there are 16 levels of seeding, simply dividing the the 11 through 20 rolls up based on seeding wouldn't work. (I only had have the faces to work with because at least 50 percent of the time, the favored team should win.) I went with rolling twice, both because this gave me enough discrete intervals to account for 16 seeding slots and, since summing the dice starts to approximate the normal distribution I'd account for the superlinear drop off in probability of upset as the difference in seeding increased. Using two d20 rolls (denoted 2d20
), I even get a reasonable value for probability of a 16 seed upsettig a 1, 1 in 400. A 15 over a 2 is only 1 in 100, though, is a fair bit too low, I believe.
Still, it's a good start if you need to seed your bracket quickly.
I wrote up a quick Python script to create the table.
Here's the table. You roll twice for each game, and if the sum of the rolls is greater than or equal to the Upset Roll value for the given difference in seeding, you select the lower rank team as winner. Otherwise, advance the winner.
Seed Difference: 2d20 Upset Roll (this value or higher)
0: 21
1: 22
2: 23
3: 24
4: 26
5: 27
6: 28
7: 30
8: 31
9: 32
10: 34
11: 35
12: 36
13: 37
14: 39
15: 40
Here's the bracket I put together.
And here's the script:
"""Print out the table for building your NCAA Men's Basketball bracket via d20 rolls."""
number_of_seeds = 16
max_seed_difference = 16 - 1 #16 over 1 is a double crit
die_size = 40 #2d20 in this case. So the probability intervals are non linear
def upset_roll( seed_difference ):
"""Return the roll required for an upset"""
interval = float(die_size-1)/float(max_seed_difference)/2.0
half_die_size = die_size/2
#Round down because I feel guilty for the nonlinear intervals
return int( interval * seed_difference ) + half_die_size+1
print( "{}: {}".format( "Seed Difference","Upset Roll (this value or higher)"))
for difference in range(0,16):
print( "{}: {}".format(difference, upset_roll(difference) ) )