Library · The Fiddler: Solutions · Chapter 147
Can You Set a Winning Baseball Lineup?
You manage the New York Frets and are rearranging the batting order. Eight of your nine batters are pure contact hitters. Each of them hits a single one time in three, moving every runner on base forward exactly one base; the team is so slow that nobody scores from first or second on a single, only from third. The other two times in three he is out, and the runners stay where they are. Your ninth batter is a slugger, who hits a home run one time in ten and strikes out otherwise. Where in the lineup should the slugger bat to score the most runs, on average, in the first inning?
The Fiddler, Zach Wissner-Gross, August 18, 2023(original post)
The official solution appeared in the post of August 25, 2023 and gives fourth for the main puzzle and third and fourth, with about runs a game, for the Extra Credit. Both agree with the answers below.
Solution
The trade-off. A home run scores the slugger and everyone on base, so he wants to bat after some contact hitters have reached base. But every batter ahead of him is also a chance for the inning to end before he comes up. Batting later buys more runners at the risk of not batting at all.
His first time up shows the trade-off cleanly. If he bats th, the contact hitters before him each single with chance . His home run, with chance , is worth run plus one for each runner on base, so this first trip to the plate is worth of the expected value of in the cases where he gets to bat at all.
Batting first or second, he always bats; the expected value is , or .
Batting third, he always bats, since two outs cannot end the inning; the two hitters ahead of him put on of a runner on average, so the value is .
Batting fourth, he bats only if at least one of the three hitters ahead of him singled. With singles among them, which happens with chance , he bats with runners on:
Batting fifth, he needs two singles among four hitters, and a fourth single scores a run on its own rather than adding a runner: .
The first trip to the plate is worth most from the fourth spot, by just of a home run’s worth over the third. That is a narrow margin, and it is not the whole story: a slugger batting early may come up a second time, and the contact hitters drive in runs of their own when they find the bases loaded.
The whole inning. To count everything, describe the inning by its state: the number of outs, which bases are occupied, and who is due up. From each state the next plate appearance leads to two others, with known chances and a known number of runs. Writing for the expected runs still to come from state , with once there are three outs. Hits do not add outs, so an inning can in principle go on for ever and the states can loop back on themselves; these equations are therefore solved as a linear system, one for each number of outs, over the combinations of runners and batter. Solving them with the slugger in each spot gives the first-inning expectation:
| slugger bats | 1 | 2 | 3 | 4 | 5 |
| runs | 0.1841 | 0.2107 | 0.2394 | 0.2401 | 0.2027 |
| slugger bats | 6 | 7 | 8 | 9 | |
| runs | 0.1562 | 0.1683 | 0.1742 | 0.1770 |
The first-trip advantage of runs survives, shrunk to by the other effects. The answer is fragile in the way such close calls are: with a home run rate of per cent instead of , the official write-up notes, the best spot moves to ninth.
The computation
The check does not solve equations. It plays the rules forward one plate appearance at a time, carrying the probability of every situation (inning, outs, runners, batter due up) and adding up the expected runs as they score, until the chance that the game is still going is below . The same function handles any number of innings and any set of sluggers.
import numpy as np
from itertools import combinations
def expected_runs(sluggers, innings):
"""Play forward one plate appearance at a time, carrying
the chance of every situation: inning, outs, runners
(bit 0 first base, bit 1 second, bit 2 third) and the
batter due up. Returns the expected runs scored."""
dist = np.zeros((innings, 3, 8, 9))
dist[0, 0, 0, 0] = 1.0 # first inning, batter 1
runs = 0.0
while dist.sum() > 1e-15:
new = np.zeros_like(dist)
for inn, outs, bases, i in zip(*np.nonzero(dist)):
p = dist[inn, outs, bases, i]
j = (i + 1) % 9
if i in sluggers: # home run, 1 in 10
on = bin(bases).count("1")
runs += p*0.1*(1 + on)
new[inn, outs, 0, j] += p*0.1
q = 0.9
else: # single, 1 in 3
runs += p/3*(bases >> 2) # from third
new[inn, outs, (bases << 1 | 1) & 7, j] += p/3
q = 2/3
if outs < 2: # an out, runners stay
new[inn, outs + 1, bases, j] += p*q
elif inn + 1 < innings: # third out: next inning
new[inn + 1, 0, 0, j] += p*q
dist = new
return runs
for k in range(9):
e = expected_runs({k}, 1)
print(f"slugger batting {k + 1}: {e:.5f} runs")
It prints
slugger batting 1: 0.18413 runs
slugger batting 2: 0.21073 runs
slugger batting 3: 0.23937 runs
slugger batting 4: 0.24014 runs
slugger batting 5: 0.20273 runs
slugger batting 6: 0.15624 runs
slugger batting 7: 0.16834 runs
slugger batting 8: 0.17425 runs
slugger batting 9: 0.17696 runs
Extra Credit
Now you have two sluggers and seven contact hitters, and you want the most runs over a nine-inning game. Where should the sluggers bat?
Solution
Over a game, each inning starts with whoever follows the last batter of the inning before, so the innings are linked. The inning equations above handle this with one more ingredient. For each possible leadoff batter , solving the same kind of linear system gives the expected runs in an inning that leads off, and the chance that the next inning is led off by . If is the row of chances for who leads off an inning, starting with batter for certain, the game’s expected runs are Doing this for each of the placements of the two sluggers gives the five best:
| sluggers bat | 3 and 4 | 2 and 3 | 3 and 7 | 4 and 8 | 3 and 8 |
|---|---|---|---|---|---|
| runs a game | 1.9977 | 1.9876 | 1.9843 | 1.9806 | 1.9799 |
Putting them back to back is best, but splitting them four places apart, so that the contact hitters can refill the bases after the first slugger clears them, is close behind. Over a whole game a single slugger is also best third rather than fourth, since being near the top of the order matters more when the order turns over several times.
The computation
The forward simulation above runs a full nine innings for each placement.
pairs = combinations(range(9), 2)
games = sorted(((expected_runs({a, b}, 9), a + 1, b + 1)
for a, b in pairs), reverse=True)
for e, a, b in games[:5]:
print(f"sluggers batting {a} and {b}: {e:.4f} runs")
It prints
sluggers batting 3 and 4: 1.9977 runs
sluggers batting 2 and 3: 1.9876 runs
sluggers batting 3 and 7: 1.9843 runs
sluggers batting 4 and 8: 1.9806 runs
sluggers batting 3 and 8: 1.9799 runs