見出し画像

DHあり vs なし、東京ヤクルトスワローズの得点力はどう変わる? ~シミュレーションによる得点を比較~ part③

前回に引き続き、東京ヤクルトスワローズの得点力がDHの有無によってどのように変化するのかを、Pythonを用いたシミュレーションで評価、検証します。前回の記事(Part②)では、指定した打線での9イニングの攻撃をシミュレーションし、実際の打撃指標に基づいた得点計算を行える仕組みを構築しました。
part③の本記事では、part②で作成したコードを元に、指定した打線で試合を繰り返しシミュレーションすることで、1試合あたりの平均得点を算出します。さらに、スワローズだけでなくセ・リーグの他球団についても同様にシミュレーションを実施し、DH制がチームにどれほど有利(または不利)に働くのかを比較・考察します。


1. 分析方法

前回の記事(part②)では、任意の打線を指定した際の1試合(9イニング)ので攻撃をシミュレーションしましたが、確率指標によって打撃結果が決定する都合上、試行回数が少ないと算出される得点にばらつきが生じ得ます(例えば、1試合単位での打線がうまく繋がる上振れの場合や、その逆の下振れの場合など)。そこで、試行回数を増やし、より多くの試合をシミュレーションすることで、1試合あたりの平均得点を算出し、このようなばらつきを低減した結果を取得します。

今回は、東京ヤクルトスワローズのDHの有無による1試合あたりの平均得点を比較するために、以下の打順を指定します。

  • DHなしの打順:2024年の開幕戦スタメン打順
    (1番塩見選手、2番西川選手、3番オスナ選手、4番村上選手、5番山田選手、6番サンタナ選手、7番中村選手、8番長岡選手、9番ピッチャー)

  • DHありの打線:2024年の交流戦ビジター初戦のスタメン打順
    (1番丸山選手、2番山田選手、3番長岡選手、4番村上選手、5番サンタナ選手、6番青木選手、7番オスナ選手、8番松本選手、9番山崎選手)

2. 実装

基本的な処理はPart②のコードと同じですが、繰り返し計算を行う都合上、1試合分の攻撃シミュレーションを関数化しました。

import pandas as pd
import numpy as np
import sys

# 打撃指標取得
df = pd.read_excel("C:/big6stats/ys_2024打撃成績_20250123.xlsx")
pitcher = ['ピッチャー', 0.100, 0.000, 0.000, 0.000, 0.020, 0.880]
df_pitcher = pd.DataFrame([pitcher], columns=df.columns)
df = pd.concat([df, df_pitcher], ignore_index=True)
#print(df)

# 打線指定
players = ['塩見泰隆', '西川遥輝', 'オスナ', '村上宗隆', '山田哲人', 'サンタナ', '中村悠平', '長岡秀樹', 'ピッチャー']
#players = ['丸山和郁', '山田哲人', '長岡秀樹', '村上宗隆', 'サンタナ', '青木宣親', 'オスナ', '松本直樹', '山崎晃大朗']

# 打線の人数チェック
if len(players) != 9:
    print("指定した打線の人数が9人ではありません。")
    sys.exit()

# 指定した打者がdfに存在するかチェック
df_players = df['選手名'].tolist()
for player in players:
    if player not in df_players:
        print(f"選手名:{player} のデータが存在しません。")
        sys.exit()

print(f"\n{players}")

stats = pd.DataFrame(columns=df.columns)
for player in players:
    stats = pd.concat([stats, df[df['選手名'].isin([player])]], ignore_index=True)
#print(stats)

# シミュレーション関数
def game(bat_stats):
    score = 0
    current_batter_idx = 0  # 現在の打者のインデックス

    for inning in range(9):
        outs = 0
        bases = {'1st': False, '2nd': False, '3rd': False}  # 走者状況の管理

        #print(f"\n=== {inning + 1}回の攻撃 ===")

        while outs < 3:
            batter = stats.iloc[current_batter_idx]
            
            probabilities = [
                batter['single%'],
                batter['double%'],
                batter['triple%'],
                batter['hr%'],
                batter['walk%'],
                batter['out%']
            ]
            
            outcomes = ['single', 'double', 'triple', 'hr', 'walk', 'out']
            result = np.random.choice(outcomes, p=probabilities)
            #print(f"{current_batter_idx + 1}番打者 {batter['選手名']}: {result}")

            if result == 'out':
                outs += 1
                #print(f"凡退。 アウトカウント: {outs}")
            else:
                runs = 0
                if result == 'single':
                    if bases['3rd']:
                        runs += 1
                    bases['3rd'] = bases['2nd']
                    bases['2nd'] = bases['1st']
                    bases['1st'] = True
                elif result == 'double':
                    if bases['3rd']:
                        runs += 1
                    if bases['2nd']:
                        runs += 1
                    bases['3rd'] = bases['1st']
                    bases['2nd'] = True
                    bases['1st'] = False
                elif result == 'triple':
                    runs += sum(bases.values())
                    bases['3rd'] = True
                    bases['2nd'] = False
                    bases['1st'] = False
                elif result == 'hr':
                    runs += sum(bases.values()) + 1
                    bases['3rd'] = False
                    bases['2nd'] = False
                    bases['1st'] = False
                elif result == 'walk':
                    if bases['1st']:
                        if bases['2nd']:
                            if bases['3rd']:
                                runs += 1
                            bases['3rd'] = True
                        bases['2nd'] = True
                    bases['1st'] = True

                score += runs
                #print(f"走者状況: 1塁={bases['1st']}, 2塁={bases['2nd']}, 3塁={bases['3rd']}, 追加得点: {runs}")
            current_batter_idx = (current_batter_idx + 1) % 9

    #print(f"\n最終得点: {score}")
    return score
    

N = 10000
total_score = []
for _ in range(N):
    score = game(stats)
    total_score.append(score)
average_score = np.mean(total_score)
print(f"1試合平均得点(N={N}): {average_score:.2f}")

3. 結果

今回は、指定した打順で10,000試合シミュレーションを行い、1試合あたりの平均得点を求めました。結果は以下のように、ターミナルにテキストとして表示するようにしました。

DHなしの場合の出力結果

さらに、スワローズ以外のセ・リーグ他5球団に関しても、DHなし、DHありの打線を指定し、それぞれシミュレーションを実施した結果が以下のようになりました(スワローズ同様に、DHなしの打順は2024年の開幕戦スタメン打順、DHありの打順は2024年の交流戦ビジター初戦のスタメン打順に設定し、ピッチャーの打撃指標は固定したまま、N=10,000試合のシミュレーションを実施しました)。

4. 考察

簡単にはなりますが、最後に考察を記します。
あらかじめ想定されていたことではありますが、基本的にはDHを導入することにより、1試合あたりの平均得点が増加していることが確認できます。当然ですが、高確率でアウトになるピッチャーを打線に含めるよりも、より出塁や長打を期待できる野手を組み込んだ方が得点力は高まります。具体的な数値として今回のシミュレーション結果では、DHの導入により1試合あたり約1点弱ほど(全平均で0.80点)の得点の増加が見られました。

特に、読売ジャイアンツ、横浜DeNAベイスターズ、中日ドラゴンズでは、DHの導入により1試合あたり約1点の得点増加が確認され、DHの有無による違いが明確に表れています。さらに、ジャイアンツはDH導入後に平均得点が4点台に、ベイスターズは5点台にまで増加し、打線の厚みが増し、セ・リーグの中でも非常に強力な打線を構成できていることが分かります。
また、広島東洋カープは、DHの導入による得点の増加がダントツの1.78点となり、他球団と比べても特に顕著な変化が見られました。ただし、カープに関しては、DHなしの得点が1.62点と元々が他球団と比較して低く算出されているため、この得点の伸びはDH制そのものの影響のみならず、DHなしの打線における打順や選手起用の影響も考えられるかと思います。

さて、、、本記事のテーマでもあるスワローズに目を向けると、、、
まさかのDHの導入によって得点が減少する結果となり、これはセ・リーグ6球団で唯一の事例となっています。

この結果だけを見ると、DHなしの状態、つまりピッチャーを打線に含めた方が得点を挙げられるということになってしまいますが、前述のように、一般的かつ定性的な議論でも、DH導入により得点は増加することが見込まれ、これは違和感のある結果と言わざるを得ません。
DHなしの打線での得点が4.35点と、元々比較的攻撃力のある打線を構成できており、非常にポテンシャルに優れているだけに、DHありの打線ではあまり効果的に得点を伸ばすことができず、攻撃力の向上に繋げられてはいなかったと本結果から結論付けます。
ただし、これは裏を返せば、DHありの打線での打順や選手起用の工夫により、得点増加の余地が大きく残されているとも言え、DH制を活かしたより効果的な打線の組み方の検討については今後の課題としたいと思います。

なお、全体を通して今回のシミュレーション結果の解釈において注意すべき点に関しても触れておきます。
まず、今回のシミュレーションでは、野球のルールや試合の仕組みをPythonで再現することにより得点を算出できる仕組みを構築しましたが、モデル化の都合上、実際の野球におけるすべての事象をモデルに組み込めているわけではありません。基本的なモデルとして、安打と四死球以外はアウトとして扱い、1塁打につき1進塁するモデルを考えましたが、実際のプレーでは、1塁打で2進塁以上する場合(例えば、ランナー1塁でライト線へのシングルヒットにより、ランナー1, 3塁になる場合など)や、打者はアウトになるも走者は進塁する場合(内野ゴロの間の進塁やタッチアップなど)が考えられます。また、その他考慮できていない事象として、送りバントやダブルプレー等も挙げられ、これらの要素が1試合当たりの得点の増減にどのような影響を及ぼすかはより詳細な検討が必要かと思います。

さらに、シミュレーションにおいては各選手の打撃指標を利用しましたが、今回使用した指標は2024シーズン全体を通しての成績に基づいたものである点にも注意する必要があります。特に本記事では、DHの有無の比較として、特定の試合(開幕戦と交流戦ビジター初戦)の打順を考慮対象としましたが、このような打順は各チームの首脳陣が、チーム戦略や選手の直近の活躍、コンディション等を加味して決定したという側面が大きいかと思います。ケガや好不調含め、その時その時のチームや選手の短期的な状況、短期的な視点を踏まえて組まれた打順に対して、シーズン通して均した成績を利用している点も注意が必要であると感じます。

(いずれにしても、スワローズのみ今回のシミュレーション結果が他球団と大きく異なった点に関しては、なにかしら再考の余地があるようには思えます。)

4. まとめ

本記事では、東京ヤクルトスワローズを中心に、DHの有無が得点力にどのような変化をもたらすのかを、Pythonを用いたシミュレーションによって評価、検証しました。シミュレーションの結果、多くの球団でDHの導入により得点力が向上し、全平均としてDHを導入することで1試合あたり0.80点の得点増加が見られました。
一方で、東京ヤクルトスワローズに関しては、実際にDHを導入した打線では、むしろ得点が減少するという予想外の結果が得られました。

先に述べたように、本シミュレーションにはモデル化の誤差等も含まれるため、実際の試合と完璧に一致する結果が得られるわけではありません。しかし、DHの有無による得点の違いを具体的な数値として算出する試みはこれまであまり多くないと思いますし、その意味でも今回の分析は興味深いものとなりました。
特に、東京ヤクルトスワローズにおいてDHの導入が得点の増加に繋がらなかった点は、さらなる検討の余地があると感じます。今後は、より実際の試合に近いモデルを構築し、シミュレーションの精度を向上させる工夫を重ねるとともに、東京ヤクルトスワローズのDH制を活かしたより効果的な打線の組み方についても検討していきたいと思います。

参考文献

いいなと思ったら応援しよう!