導入
最近の野球の戦略として「無死1塁では送りバントをせずに打つ方が良い」と言われますが、本当に正しいのかどうか気になったので調べてみました。おそらく同じようなことは既にやられているとは思いますが、自分の勉強も兼ねてやってみることにしました。
方法
モンテカルロ法的な何かを使って計算してみました。言語はPythonです。コードは末尾に記載してあります。
打線の仮定
以下のような打線を仮定しました。
・すべての打者が均一
・打席当たりの安打の確率は30%、安打の内3分の1が二塁打になる
・打席当たりの出塁する確率が35%
・打席当たりの凡打の確率が65%、併殺が生じるケースの場合(例:無死or一死1塁)、凡打の50%が併殺となる
・本塁生還率:例えば、ランナー2塁のケースでは、2塁打では100%得点し、単打では50%の確率で得点になる
・ランナー3塁の場合、凡打のうちおよそ10%がスクイズ・犠飛扱い(アウトが1つ増え、得点が1増える)
他にも、ケースに応じて細々と条件をつけていますが、説明は割愛します。詳細はコードを参照してください(見づらくて申し訳ないですが…)。
送りバントの扱いについて
送りバントの扱いは以下の通りです。
・無死or一死1塁、または無死12塁の際に、アウトを1つ増やす代わりにランナーをそれぞれ進める(前者は一死or二死2塁になり、後者は一死23塁になる)。成功率は100%。
打者の仮定の部分で本塁打・三塁打が発生しないと仮定したのもそうですが、送りバントは必ず成功する扱いであるため、送りバント戦法が有利に働くように設定しています。
結果1
横軸が攻撃イニング数、縦軸がイニング当たりの得点期待値です。また、赤線が強行策をとった場合、青線が送りバント策をとった場合の値の変動を示しています。図の通り、打席当たりの安打確率が平均して3割程度見込める打線であれば、強硬策をとる方が得点期待値が高くなることが分かります。
結果2
打席当たりの安打確率を25%、打席当たりの出塁する確率を30%に変えた場合の計算を行ってみました。その他の条件は前の計算と同じです。図の通り、送りバント策をとる方がわずかに得点期待値が高くなっています。
まとめ
当然と言えば当然ですが、安打が出る確率に応じて有効な戦略が変化することが分かりました。様々な条件を試していないので一概には言えませんが、打席当たりの安打確率が25%(または、打席当たりの出塁確率が30%)くらいのところに分岐点があるような気がします。
後は、高校野球等のトーナメント戦だと試行イニング数が少なすぎるので、どちらの戦略が有効に働くとも言い切れない部分があると思います。逆に言えば、最善の戦略が様々であることによって、チームごとのカラーが出る、という面白さもあるとは思いますが。
今後について
打者ごとに厳密に打率等を設定できるように改良したいのと、盗塁等の要素も含めていければいいかなと考えています。
コード
以下にコードを示します。初学者ゆえ非常に冗長&煩雑です…こちらについても改良はしたいです。
以下の計算は、結果2のものについてです。
# coding: utf-8
# average_Hitting=0.25
# average_go_on_base=0.30
import numpy as np
import random
import matplotlib.pyplot as plt
random.seed(0)
def HitResult(p,runner,out,R):
#no runner
if runner == 0:
if p < 0.:
R = R+1 #homerun
elif p < 0.:
runner = 3 #triple
elif p < 0.083333:
runner = 2 #double
elif p < 0.30:
runner = 1 #single
else:
out = out+1 #out
#runner on base1
elif runner == 1:
if p < 0.:
runner = 0
R = R+2 #two run homerun
elif p < 0.:
runner = 3
R = R+1 #clutch triple
elif p < 0.041666:
runner = 2
R = R+1 #clutch double
elif p < 0.083333:
runner = 23 #double
elif p < 0.166666:
runner = 13 #single with base3
elif p < 0.3:
runner = 12 #single or walk
elif p < 0.65:
out = out+1 #out
else:
runner = 0
out = out+2 #double play
#runner on base2
elif runner == 2:
if p < 0.:
runner = 0
R = R+2 #two run homerun
elif p < 0.:
runner = 3 #clutch triple
R = R+1
elif p < 0.083333:
R = R+1 #clutch double
elif p < 0.166666:
runner = 1
R = R+1 #clutch single
elif p < 0.25:
runner = 13 #single
elif p < 0.30:
runner = 12 #walk
elif p < 0.9:
out = out+1 #out
else:
runner = 0
out = out+2 #double play
#runner on base3
elif runner == 3:
if p < 0.:
runner = 0
R = R+2 #two run homerun
elif p < 0.:
runner = 3
R = R+1 #clutch triple
elif p < 0.083333:
runner = 2
R = R+1 #clutch double
elif p < 0.25:
runner = 1
R = R+1 #clutch single
elif p < 0.30:
runner = 13 #walk
elif p < 0.45:
if out <= 1:
runner = 0
R = R+1
out = out+1 #sacrifice out
elif p < 0.9:
out = out+1 #out
else:
runner = 0
out = out+2 #double play
#runner on base1 and base2
elif runner == 12:
if p < 0.:
runner = 0
R = R+3 #three run homerun
elif p < 0.:
runner = 3
R = R+2 #2 clutches triple
elif p < 0.041666:
runner = 2
R = R+2 #2 clutches double
elif p < 0.083333:
runner = 23
R = R+1 #clutch double
elif p < 0.138888:
runner = 13
R = R+1 #clutch single with base3
elif p < 0.194444:
runner = 12
R = R+1 #clutch single
elif p < 0.30:
runner = 123 #single or walk
elif p < 0.65:
out = out+1 #out
elif p < 0.825:
runner = 2
out = out+2 #double play through base3
elif p < 0.99:
runner = 3
out = out+2 #double play through base2
else:
runner = 0
out = out+3 #triple play
#runner on base1 and base3
elif runner == 13:
if p < 0.:
runner = 0
R = R+3 #three run homerun
elif p < 0.:
runner = 3
R = R+2 #2 clutches triple
elif p < 0.041666:
runner = 2
R = R+2 #2 clutches double
elif p < 0.083333:
runner = 23
R = R+1 #clutch double
elif p < 0.166666:
runner = 13
R = R+1 #clutch single with base3
elif p < 0.25:
runner = 12
R = R+1 #clutch single
elif p < 0.30:
runner = 123 #walk
elif p < 0.45:
if out <= 1:
runner = 1
R = R+1
out = out+1 #sacrifice out
elif p < 0.52:
if out <= 1:
R = R+1
runner = 1
out = out+1 #double play failure
elif p < 0.68:
out = out+1 #out
elif p < 0.84:
runner = 12
out = out+1 #out on home
elif p < 0.99:
if out == 0:
R = R+1
out = out+2 #double play
else:
out = out+3 #triple play
#runner on base2 and base3
elif runner == 23:
if p < 0.:
runner = 0
R = R+3 #three run homerun
elif p < 0.:
runner = 3
R = R+2 #2 clutches triple
elif p < 0.083333:
runner = 2
R = R+2 #2 clutches double
elif p < 0.166666:
runner = 1
R = R+2 #2 clutches single
elif p < 0.25:
runner = 13
R = R+1 #clutch single
elif p < 0.30:
runner = 123 #walk
elif p < 0.45:
if out <= 1:
runner = 2
R = R+1
out = out+1 #sacrifice out
elif p < 0.9:
out = out+1 #out
elif p < 0.995:
runner = 2
out = out+2 #double play
else:
runner = 0
out = out+3 #triple play
#runner on all bases
else:
if p < 0.:
runner = 0
R = R+4 #four run homerun
elif p < 0.:
runner = 3
R = R+3 #3 clutches triple
elif p < 0.041666:
runner = 2
R = R+3 #3 clutches double
elif p < 0.083333:
runner = 23
R = R+2 #2 clutches double
elif p < 0.138888:
runner = 13
R = R+2 #2 clutches single with base3
elif p < 0.194444:
runner = 12
R = R+2 #2 clutches single
elif p < 0.30:
R = R+1 #clutch single or clutch walk
elif p < 0.45:
if out <= 1:
runner = 12
R = R+1
out = out+1 #sacrifice out
elif p < 0.675:
out = out+1 #out
elif p < 0.99:
runner = 23
out = out+2 #double play through home
else:
runner = 0
out = out+3 #triple play
return runner,out,R
def BuntResult(p,runner,out,R):
#no runner
if runner == 0:
if p < 0.:
R = R+1 #homerun
elif p < 0.:
runner = 3 #triple
elif p < 0.1:
runner = 2 #double
elif p < 0.35:
runner = 1 #single
else:
out = out+1 #out
#runner on base1
elif runner == 1:
if out == 2:
if p < 0.:
runner = 0
R = R+2 #two run homerun
elif p < 0.:
runner = 3
R = R+1 #clutch triple
elif p < 0.041666:
runner = 2
R = R+1 #clutch double
elif p < 0.083333:
runner = 23 #double
elif p < 0.166666:
runner = 13 #single with base3
elif p < 0.30:
runner = 12 #single
elif p < 0.65:
out = out+1 #out
else:
runner = 0
out = out+2 #double play
else:
if p < 1.:
runner = 2
out = out+1 #sacrifice bunt
elif p < 999:
out = out+1 #bunt failure
else:
runner = 0
out = out+2 #double play
#runner on base2
elif runner == 2:
if out == 0:
if p < 1.:
runner = 3
out = out+1 #sacrifice bunt
elif p < 999:
out = out+1 #bunt failure
else:
runner = 1
out = out+1 #bunt failure
else:
if p < 0.:
runner = 0
R = R+2 #two run homerun
elif p < 0.:
runner = 3 #clutch triple
R = R+1
elif p < 0.083333:
R = R+1 #clutch double
elif p < 0.166666:
runner = 1
R = R+1 #clutch single
elif p < 0.25:
runner = 13 #single
elif p < 0.30:
runner = 12 #walk
elif p < 0.9:
out = out+1 #out
else:
runner = 0
out = out+2 #double play
#runner on base3
elif runner == 3:
if p < 0.:
runner = 0
R = R+2 #two run homerun
elif p < 0.:
runner = 3
R = R+1 #clutch triple
elif p < 0.083333:
runner = 2
R = R+1 #clutch double
elif p < 0.25:
runner = 1
R = R+1 #clutch single
elif p < 0.30:
runner = 13 #walk
elif p < 0.45:
if out <= 1:
runner = 0
R = R+1
out = out+1 #sacrifice out
elif p < 0.9:
out = out+1 #out
else:
runner = 0
out = out+2 #double play
#runner on base1 and base2
elif runner == 12:
if out == 0:
if p < 1.:
runner = 23
out = out+1 #sacrifice bunt
elif p < 999:
out = out+1 #bunt failure
else:
runner = 2
out = out+2 #bunt failure (double play)
else:
if p < 0.:
runner = 0
R = R+3 #three run homerun
elif p < 0.:
runner = 3
R = R+2 #2 clutches triple
elif p < 0.041666:
runner = 2
R = R+2 #2 clutches double
elif p < 0.083333:
runner = 23
R = R+1 #clutch double
elif p < 0.138888:
runner = 13
R = R+1 #clutch single with base3
elif p < 0.194444:
runner = 12
R = R+1 #clutch single
elif p < 0.30:
runner = 123 #single or walk
elif p < 0.65:
out = out+1 #out
elif p < 0.825:
runner = 2
out = out+2 #double play through base3
elif p < 0.99:
runner = 3
out = out+2 #double play through base2
else:
runner = 0
out = out+3 #triple play
#runner on base1 and base3
elif runner == 13:
if p < 0.:
runner = 0
R = R+3 #three run homerun
elif p < 0.:
runner = 3
R = R+2 #2 clutches triple
elif p < 0.041666:
runner = 2
R = R+2 #2 clutches double
elif p < 0.083333:
runner = 23
R = R+1 #clutch double
elif p < 0.166666:
runner = 13
R = R+1 #clutch single with base3
elif p < 0.25:
runner = 12
R = R+1 #clutch single
elif p < 0.30:
runner = 123 #walk
elif p < 0.45:
if out <= 1:
runner = 1
R = R+1
out = out+1 #sacrifice out
elif p < 0.52:
if out <= 1:
R = R+1
runner = 1
out = out+1 #double play failure
elif p < 0.68:
out = out+1 #out
elif p < 0.84:
runner = 12
out = out+1 #out on home
elif p < 0.99:
if out == 0:
R = R+1
out = out+2 #double play
else:
out = out+3 #triple play
#runner on base2 and base3
elif runner == 23:
if p < 0.:
runner = 0
R = R+3 #three run homerun
elif p < 0.:
runner = 3
R = R+2 #2 clutches triple
elif p < 0.083333:
runner = 2
R = R+2 #2 clutches double
elif p < 0.166666:
runner = 1
R = R+2 #2 clutches single
elif p < 0.25:
runner = 13
R = R+1 #clutch single
elif p < 0.30:
runner = 123 #walk
elif p < 0.45:
if out <= 1:
runner = 2
R = R+1
out = out+1 #sacrifice out
elif p < 0.9:
out = out+1 #out
elif p < 0.995:
runner = 2
out = out+2 #double play
else:
runner = 0
out = out+3 #triple play
#runner on all bases
else:
if p < 0.:
runner = 0
R = R+4 #four run homerun
elif p < 0.:
runner = 3
R = R+3 #3 clutches triple
elif p < 0.041666:
runner = 2
R = R+3 #3 clutches double
elif p < 0.083333:
runner = 23
R = R+2 #2 clutches double
elif p < 0.138888:
runner = 13
R = R+2 #2 clutches single with base3
elif p < 0.194444:
runner = 12
R = R+2 #2 clutches single
elif p < 0.30:
R = R+1 #clutch single or clutch walk
elif p < 0.45:
if out <= 1:
runner = 12
R = R+1
out = out+1 #sacrifice out
elif p < 0.65:
out = out+1 #out
elif p < 0.99:
runner = 23
out = out+2 #double play through home
else:
runner = 0
out = out+3 #triple play
return runner,out,R
# Initial condition
inning = 1
max_inning = 10000
runner = 0
out = 0
R = 0
sum_num1 = 0
sum_num2 = 0
sum_num3 = 0
sum_num4 = 0
sum_num5 = 0
sum_num6 = 0
sum_num7 = 0
sum_num8 = 0
sum_num9 = 0
sum_num10 = 0
R_array_Hit1 = np.zeros((max_inning+1,1))
R_array_Hit2 = np.zeros((max_inning+1,1))
R_array_Hit3 = np.zeros((max_inning+1,1))
R_array_Hit4 = np.zeros((max_inning+1,1))
R_array_Hit5 = np.zeros((max_inning+1,1))
R_array_Hit6 = np.zeros((max_inning+1,1))
R_array_Hit7 = np.zeros((max_inning+1,1))
R_array_Hit8 = np.zeros((max_inning+1,1))
R_array_Hit9 = np.zeros((max_inning+1,1))
R_array_Hit10 = np.zeros((max_inning+1,1))
Exp_R_Hit1 = np.zeros((max_inning+1,1))
Exp_R_Hit2 = np.zeros((max_inning+1,1))
Exp_R_Hit3 = np.zeros((max_inning+1,1))
Exp_R_Hit4 = np.zeros((max_inning+1,1))
Exp_R_Hit5 = np.zeros((max_inning+1,1))
Exp_R_Hit6 = np.zeros((max_inning+1,1))
Exp_R_Hit7 = np.zeros((max_inning+1,1))
Exp_R_Hit8 = np.zeros((max_inning+1,1))
Exp_R_Hit9 = np.zeros((max_inning+1,1))
Exp_R_Hit10 = np.zeros((max_inning+1,1))
# inning_array = np.zeros((max_inning+1,1))
# Simulation in case of hitting
for i in range(1,11):
for inning in range(1,max_inning+1):
while True:
p = random.random()
runner,out,R = HitResult(p,runner,out,R)
if out >= 3:
break
if i == 1:
R_array_Hit1[inning,0] = R
sum_num1 = sum_num1 + R
Exp_R_Hit1[inning,0] = float(sum_num1)/inning
elif i == 2:
R_array_Hit2[inning,0] = R
sum_num2 = sum_num2 + R
Exp_R_Hit2[inning,0] = float(sum_num2)/inning
elif i == 3:
R_array_Hit3[inning,0] = R
sum_num3 = sum_num3 + R
Exp_R_Hit3[inning,0] = float(sum_num3)/inning
elif i == 4:
R_array_Hit4[inning,0] = R
sum_num4 = sum_num4 + R
Exp_R_Hit4[inning,0] = float(sum_num4)/inning
elif i == 5:
R_array_Hit5[inning,0] = R
sum_num5 = sum_num5 + R
Exp_R_Hit5[inning,0] = float(sum_num5)/inning
elif i == 6:
R_array_Hit6[inning,0] = R
sum_num6 = sum_num6 + R
Exp_R_Hit6[inning,0] = float(sum_num6)/inning
elif i == 7:
R_array_Hit7[inning,0] = R
sum_num7 = sum_num7 + R
Exp_R_Hit7[inning,0] = float(sum_num7)/inning
elif i == 8:
R_array_Hit8[inning,0] = R
sum_num8 = sum_num8 + R
Exp_R_Hit8[inning,0] = float(sum_num8)/inning
elif i == 9:
R_array_Hit9[inning,0] = R
sum_num9 = sum_num9 + R
Exp_R_Hit9[inning,0] = float(sum_num9)/inning
else:
R_array_Hit10[inning,0] = R
sum_num10 = sum_num10 + R
Exp_R_Hit10[inning,0] = float(sum_num10)/inning
#Initialize conditions
runner = 0
out = 0
R = 0
inning = inning+1
i = i+1
# Initial condition
inning = 1
max_inning = 10000
runner = 0
out = 0
R = 0
sum_num1 = 0
sum_num2 = 0
sum_num3 = 0
sum_num4 = 0
sum_num5 = 0
sum_num6 = 0
sum_num7 = 0
sum_num8 = 0
sum_num9 = 0
sum_num10 = 0
R_array_Bunt1 = np.zeros((max_inning+1,1))
R_array_Bunt2 = np.zeros((max_inning+1,1))
R_array_Bunt3 = np.zeros((max_inning+1,1))
R_array_Bunt4 = np.zeros((max_inning+1,1))
R_array_Bunt5 = np.zeros((max_inning+1,1))
R_array_Bunt6 = np.zeros((max_inning+1,1))
R_array_Bunt7 = np.zeros((max_inning+1,1))
R_array_Bunt8 = np.zeros((max_inning+1,1))
R_array_Bunt9 = np.zeros((max_inning+1,1))
R_array_Bunt10 = np.zeros((max_inning+1,1))
Exp_R_Bunt1 = np.zeros((max_inning+1,1))
Exp_R_Bunt2 = np.zeros((max_inning+1,1))
Exp_R_Bunt3 = np.zeros((max_inning+1,1))
Exp_R_Bunt4 = np.zeros((max_inning+1,1))
Exp_R_Bunt5 = np.zeros((max_inning+1,1))
Exp_R_Bunt6 = np.zeros((max_inning+1,1))
Exp_R_Bunt7 = np.zeros((max_inning+1,1))
Exp_R_Bunt8 = np.zeros((max_inning+1,1))
Exp_R_Bunt9 = np.zeros((max_inning+1,1))
Exp_R_Bunt10 = np.zeros((max_inning+1,1))
# inning_array = np.zeros((max_inning+1,1))
# Simulation in case of bunt
for i in range(1,11):
for inning in range(1,max_inning+1):
while True:
p = random.random()
runner,out,R = BuntResult(p,runner,out,R)
if out >= 3:
break
if i == 1:
R_array_Bunt1[inning,0] = R
sum_num1 = sum_num1 + R
Exp_R_Bunt1[inning,0] = float(sum_num1)/inning
elif i == 2:
R_array_Bunt2[inning,0] = R
sum_num2 = sum_num2 + R
Exp_R_Bunt2[inning,0] = float(sum_num2)/inning
elif i == 3:
R_array_Bunt3[inning,0] = R
sum_num3 = sum_num3 + R
Exp_R_Bunt3[inning,0] = float(sum_num3)/inning
elif i == 4:
R_array_Bunt4[inning,0] = R
sum_num4 = sum_num4 + R
Exp_R_Bunt4[inning,0] = float(sum_num4)/inning
elif i == 5:
R_array_Bunt5[inning,0] = R
sum_num5 = sum_num5 + R
Exp_R_Bunt5[inning,0] = float(sum_num5)/inning
elif i == 6:
R_array_Bunt6[inning,0] = R
sum_num6 = sum_num6 + R
Exp_R_Bunt6[inning,0] = float(sum_num6)/inning
elif i == 7:
R_array_Bunt7[inning,0] = R
sum_num7 = sum_num7 + R
Exp_R_Bunt7[inning,0] = float(sum_num7)/inning
elif i == 8:
R_array_Bunt8[inning,0] = R
sum_num8 = sum_num8 + R
Exp_R_Bunt8[inning,0] = float(sum_num8)/inning
elif i == 9:
R_array_Bunt9[inning,0] = R
sum_num9 = sum_num9 + R
Exp_R_Bunt9[inning,0] = float(sum_num9)/inning
else:
R_array_Bunt10[inning,0] = R
sum_num10 = sum_num10 + R
Exp_R_Bunt10[inning,0] = float(sum_num10)/inning
#Initialize conditions
runner = 0
out = 0
R = 0
inning = inning+1
i = i+1
ave_Exp_R_Hit = np.zeros((max_inning+1,1))
ave_Exp_R_Bunt = np.zeros((max_inning+1,1))
all_Exp_R_Hit = np.array([])
all_Exp_R_Hit = np.hstack((Exp_R_Hit1,Exp_R_Hit2,Exp_R_Hit3,Exp_R_Hit4,Exp_R_Hit5,Exp_R_Hit6,Exp_R_Hit7,Exp_R_Hit8,Exp_R_Hit9,Exp_R_Hit10))
all_Exp_R_Bunt = np.array([])
all_Exp_R_Bunt = np.hstack((Exp_R_Bunt1,Exp_R_Bunt2,Exp_R_Bunt3,Exp_R_Bunt4,Exp_R_Bunt5,Exp_R_Bunt6,Exp_R_Bunt7,Exp_R_Bunt8,Exp_R_Bunt9,Exp_R_Bunt10))
for inning in range(1,max_inning+1):
ave_Exp_R_Hit[inning,0] = np.average(all_Exp_R_Hit[inning,:])
ave_Exp_R_Bunt[inning,0] = np.average(all_Exp_R_Bunt[inning,:])
print "Exp_RunScore_Hitting",ave_Exp_R_Hit[max_inning,0]
print "Exp_RunScore_Bunt",ave_Exp_R_Bunt[max_inning,0]
plt.xlabel("Number of innings")
plt.ylabel("Expected Runs Score per inning")
plt.axis([0,max_inning,0.0,2])
plt.plot(Exp_R_Hit1,'r',alpha=0.2)
plt.plot(Exp_R_Hit2,'r',alpha=0.2)
plt.plot(Exp_R_Hit3,'r',alpha=0.2)
plt.plot(Exp_R_Hit4,'r',alpha=0.2)
plt.plot(Exp_R_Hit5,'r',alpha=0.2)
plt.plot(Exp_R_Hit6,'r',alpha=0.2)
plt.plot(Exp_R_Hit7,'r',alpha=0.2)
plt.plot(Exp_R_Hit8,'r',alpha=0.2)
plt.plot(Exp_R_Hit9,'r',alpha=0.2)
plt.plot(Exp_R_Hit10,'r',alpha=0.2)
plt.plot(ave_Exp_R_Hit,'r',label="Hitting")
plt.plot(Exp_R_Bunt1,'b',alpha=0.2)
plt.plot(Exp_R_Bunt2,'b',alpha=0.2)
plt.plot(Exp_R_Bunt3,'b',alpha=0.2)
plt.plot(Exp_R_Bunt4,'b',alpha=0.2)
plt.plot(Exp_R_Bunt5,'b',alpha=0.2)
plt.plot(Exp_R_Bunt6,'b',alpha=0.2)
plt.plot(Exp_R_Bunt7,'b',alpha=0.2)
plt.plot(Exp_R_Bunt8,'b',alpha=0.2)
plt.plot(Exp_R_Bunt9,'b',alpha=0.2)
plt.plot(Exp_R_Bunt10,'b',alpha=0.2)
plt.plot(ave_Exp_R_Bunt,'b',label="Bunt")
plt.legend()
plt.grid(True)
plt.savefig('ex_v2_2_Simuresult.png')
'''
plt.hist(R_array_Hit,bins=10)
plt.hist(R_array_Bunt,bins=10)
plt.show()
'''
quit()