\n",
"\n",
"# Baseball Simulation\n",
"\n",
"The [538 Riddler for March 22, 2019](https://fivethirtyeight.com/features/can-you-turn-americas-pastime-into-a-game-of-yahtzee/) asks us to simulate baseball using probabilities from a 19th century dice game called *Our National Ball Game*. The Riddler description of the rules said *you can assume some standard baseball things* but left many things unspecified, so I [looked up](http://baseballgames.dreamhosters.com/BbDiceHome.htm) the original rules of *Our National Ball Game*, which are shown below and which, it turns out, contradict some of the *standard baseball things* assumed by 538. I'll go with the rules as stated below.\n",
"\n",
"\n",
"|RULES FOR PLAYING \"OUR NATIONAL BALL GAME\"|DICE ROLL OUTCOMES|\n",
"|-----|-----|\n",
"| ![](http://baseballgames.dreamhosters.com/bbOurNationalBallGameMcGillDelanyHoag86diE2.jpg) | ![](http://baseballgames.dreamhosters.com/bbOurNationalBallGameMcGillDelanyHoag86ddE.jpg) |\n",
"\n",
"*Note: There is a great history of dice baseball [here](https://baseballgames.dreamhosters.com/BbDiceHome.htm).*",
"\n",
"# Design Choices\n",
"\n",
"\n",
"- Exactly one thing happens to each batter. I'll call that an **event**.\n",
"- To clarify: the dice roll `1,1` has probability 1/36, whereas `1,2` has probability 2/36, because it also represents `2,1`.\n",
"- The \"One Strike\" dice roll is not an event; it is only *part* of an event. From the probability of a \"One Strike\" dice roll, 7/36, I compute the probability of three strikes in a row, `(7/36)**3 == 0.00735`, and call that a strikeout event. \n",
"- I'll represent events with the following 11 one letter **event codes**:\n",
" - `1`, `2`, `3`, `4`: one-, two-, three-, and four-base (home run) hits. Runners advance same number of bases.\n",
" - `B`: base on balls. Runners advance only if forced.\n",
" - `D`: double play. Batter and runner nearest home are out; others advance one base.\n",
" - `E`: error. Batter reaches first and all runners advance one base.\n",
" - `F`, `K`, `O`: fly out, strikeout, foul out. Batter is out, runners do not advance.\n",
" - `S`: called \"out at first\" in rules, but actually a sacrifice. Batter is out, runners advance one base.\n",
"\n",
"\n",
"# Implementation"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import random\n",
"from statistics import mean, stdev\n",
"from collections import Counter\n",
"from itertools import islice"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"event_codes = {\n",
" '1': 'single', '2': 'double', '3': 'triple', '4': 'home run',\n",
" 'B': 'base on balls', 'D': 'double play', 'E': 'error',\n",
" 'F': 'fly out', 'K': 'strikeout', 'O': 'foul out', 'S': 'out at first'}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"I'll define the function `inning` to simulate a half inning and return the number of runs scored. Design choices for `inning`:\n",
"\n",
"- I'll keep track of runners with a set of occupied bases; `runners = {1, 3}` means runners on first and third.\n",
"- I'll keep track of the number of `runs` and `outs` in an inning, and return the number of `runs` when there are three `outs`.\n",
"- Each event follows four steps. If `runners = {1, 3}` and the event is `'2'` (a double), then the steps are:\n",
" - The batter steps up to the plate. The plate is represented as base `0`, so now `runners = {0, 1, 3}`.\n",
" - Check if the event causes runner(s) to be out, and if the inning is over. In this case, no.\n",
" - Advance each runner according to `advance(r, e)`. In this case, `runners = {2, 3, 5}`.\n",
" - Remove the runners who have `scored` and increment `runs` accordingly. In this case, runner `5` has scored, so we increment `runs` by 1 and end up with `runners = {2, 3}`.\n",
"- I want `inning` to be easily **testable**: I want to say `assert 2 = inning('1KO4F')`.\n",
"- I also want `inning` to be capable of simulating many independent random innings. So the interface is to accept an *iterable* of event codes. That could be string, or a generator, as provided by `event_stream()`.\n",
"- I want `inning` to be **loggable**: calling `inning(events, verbose=True)` should produce printed output for each event.\n",
"- `advance(r, e)` says that a runner advances `e` bases on an `e` base hit; one base on an error, sacrifice, or double play; and one base on a base on balls only if forced.\n",
"- A runner on base `r` is `forced` if all the lower-numbered bases have runners.\n",
"- `ONBG` is defined as a generator of random events with the probabilities from \"Our National Ball Game\"."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"def inning(events, verbose=True) -> int:\n",
" \"\"\"Simulate a half inning based on events, and return number of runs scored.\"\"\"\n",
" outs = runs = 0 # Inning starts with no outs and no runs,\n",
" runners = set() # and with nobody on base\n",
" def out(r) -> int: runners.remove(r); return 1\n",
" def forced(r) -> bool: return all(b in runners for b in range(r))\n",
" def advance(r, e) -> int: \n",
" return int(e if e in '1234' else (e in 'ESD' or (e == 'B' and forced(r))))\n",
" for e in events:\n",
" if verbose: show(outs, runs, runners, e)\n",
" runners.add(batter) # Batter steps up to the plate\n",
" if e == 'D' and len(runners) > 1: # Double play: batter and lead runner out\n",
" outs += out(batter) + out(max(runners))\n",
" elif e in 'DSKOF': # Batter is out\n",
" outs += out(batter) \n",
" if outs >= 3: # If inning is over: return runs scored\n",
" return runs \n",
" runners = {r + advance(r, e) for r in runners} # Runners advance\n",
" runs += len(runners & scored) # Tally runs\n",
" runners = runners - scored # Remove runners who scored\n",
" \n",
"def event_stream(events, strikes=0):\n",
" \"\"\"A generator of random baseball events.\"\"\"\n",
" while True:\n",
" yield 'K' if (random.random() < strikes ** 3) else random.choice(events)\n",
"\n",
"def show(outs, runs, runners, event):\n",
" \"\"\"Print a representation of the current state of play.\"\"\"\n",
" bases = ''.join(b if int(b) in runners else '-' for b in '321')\n",
" print(f'{outs} outs {runs} runs {bases} {event} ({event_codes[event]})')\n",
" \n",
"ONBG = event_stream('2111111EEBBOOSSSSSSSFFFFFD334', 7/36) # Our National Ball Game\n",
"batter = 0 # The batter is not yet at first base\n",
"scored = {4, 5, 6, 7} # Runners in these positions have scored"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Examples and Tests\n",
"\n",
"Let's peek at some random innings:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 outs 0 runs --- 3 (triple)\n",
"0 outs 0 runs 3-- F (fly out)\n",
"1 outs 0 runs 3-- S (out at first)\n",
"2 outs 1 runs --- F (fly out)\n"
]
},
{
"data": {
"text/plain": [
"1"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"inning(ONBG)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 outs 0 runs --- F (fly out)\n",
"1 outs 0 runs --- S (out at first)\n",
"2 outs 0 runs --- S (out at first)\n"
]
},
{
"data": {
"text/plain": [
"0"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"inning(ONBG)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's also test some historic innings. I'll take some of the Red Sox innings from their 2004 playoff series against the Yankees."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 outs 0 runs --- K (strikeout)\n",
"1 outs 0 runs --- 2 (double)\n",
"1 outs 0 runs -2- O (foul out)\n",
"2 outs 0 runs -2- 1 (single)\n",
"2 outs 0 runs 3-1 2 (double)\n",
"2 outs 1 runs 32- 1 (single)\n",
"2 outs 2 runs 3-1 4 (home run)\n",
"2 outs 5 runs --- K (strikeout)\n"
]
}
],
"source": [
"# 7th inning in game 1: 5 runs (Homer by Varitek)\n",
"# (But not a perfect reproduction, because our simulation doesn't have passed balls.)\n",
"assert 5 == inning('K2O1214K')"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 outs 0 runs --- S (out at first)\n",
"1 outs 0 runs --- S (out at first)\n",
"2 outs 0 runs --- 2 (double)\n",
"2 outs 0 runs -2- 1 (single)\n",
"2 outs 0 runs 3-1 1 (single)\n",
"2 outs 1 runs -21 4 (home run)\n",
"2 outs 4 runs --- F (fly out)\n"
]
}
],
"source": [
"# 4th inning in game 6: 4 runs (Homer by Bellhorn)\n",
"assert 4 == inning('SS2114F')"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0 outs 0 runs --- S (out at first)\n",
"1 outs 0 runs --- 1 (single)\n",
"1 outs 0 runs --1 B (base on balls)\n",
"1 outs 0 runs -21 B (base on balls)\n",
"1 outs 0 runs 321 4 (home run)\n",
"1 outs 4 runs --- B (base on balls)\n",
"1 outs 4 runs --1 F (fly out)\n",
"2 outs 4 runs --1 S (out at first)\n"
]
}
],
"source": [
"# 2nd inning in game 7: 4 runs (Grand Slam by Damon)\n",
"assert 4 == inning('S1BB4BFS')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"That looks good to me.\n",
"\n",
"# Simulation\n",
"\n",
"Now, simulate a hundred thousand innings, and then sample from them to simulate a hundred thousand nine-inning games (for one team), and show histograms of the results, labelled with statistics:"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"def simulate(N=100000, inning=inning, events=ONBG) -> None:\n",
" innings = [inning(events=events, verbose=False) for _ in range(N)]\n",
" games = [sum(random.sample(innings, 9)) for _ in range(N)]\n",
" hist(innings, 'Runs/inning (for one team)')\n",
" hist(games, 'Runs/game (for one team)')\n",
" \n",
"def hist(nums, title): \n",
" \"\"\"Plot a histogram and show some statistics.\"\"\"\n",
" plt.hist(nums, ec='black', bins=max(nums)-min(nums), align='left')\n",
" plt.xlabel(title)\n",
" plt.title(f'μ: {mean(nums):.2f}, σ: {stdev(nums):.2f}, max: {max(nums)}')\n",
" plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": false
},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"