Marcel法とMLを比較してみた — NPB選手成績予測システムを作った

に公開

はじめに

「来年、この選手はどのくらいの成績を残せるか?」

この問いに答えるため、NPB(日本プロ野球)の選手成績予測システムを作りました。

GitHub: https://github.com/yasumorishima/npb-prediction

予測手法は2種類用意しました。

  • Marcel法: 1980年代に考案されたシンプルな統計手法
  • LightGBM / XGBoost: 現代的な機械学習

結果として、投手ERA予測ではMarcel法がMLを上回り、打者OPS予測では両者がほぼ同等でした。


はじめての方へ:この記事で登場する用語

用語 意味
Marcel法 過去3年の成績を加重平均して翌年を予測するシンプルな手法。重みは直近ほど高い(5:4:3)
LightGBM / XGBoost 勾配ブースティング木モデル。現代的な機械学習アルゴリズムで特徴量から予測する
OPS 出塁率+長打率。打者の打撃力を表す総合指標
ERA(防御率) 9イニング当たりの自責点。投手の基本的な成績指標
MAE 平均絶対誤差。予測と実績のずれの平均。小さいほど精度が高い
バックテスト 過去のデータを使って予測モデルの精度を検証すること

データ取得

データソースは2つです。

pandas.read_html() でHTMLテーブルを取得しています。取得したのは以下のデータです。

ファイル 内容
打者成績 3,780行(2015-2025)
投手成績 3,773行(2015-2025)
順位表 132行(12球団×11年)
生年月日 2,479人
詳細打撃成績 4,538行(npb.jp)
支配下登録選手名鑑 7,866行(2018-2025、Marcel予測フィルタ用)

データの提供元:プロ野球データFreakNPB公式サイト


Marcel法とは

Tom Tangoが考案した成績予測手法です。実装はシンプルです。

ステップ1: 過去3年の加重平均

直近の成績ほど重要という考えのもと、5/4/3 の比率で加重平均します。

weight_map = {0: 5, 1: 4, 2: 3}  # 0=直近、1=2年前、2=3年前

ステップ2: リーグ平均への回帰

サンプルが少ない選手(出場機会が少ない)は、リーグ平均に引き戻します。

# 打者OPS: リーグ平均への回帰係数
regression = 1200 / (pa + 1200)
predicted = (1 - regression) * weighted + regression * league_avg

ステップ3: 年齢調整

ピーク年齢(27歳)からの距離に応じて成績を増減させます。

age_factor = 1.0 + (27 - age) * 0.003  # 27歳でピーク

ステップ4: 選手名鑑フィルタ(退団・MLB移籍選手の除外)

Marcel法はロースターに登録された選手を予測対象とします。しかし前年まで活躍していた選手がオフにMLB移籍・退団・引退した場合、そのままでは予測に「幽霊選手」として残ってしまいます。

これを防ぐため、baseball-data.comの年別支配下登録選手一覧(fetch_rosters.pyで取得)を使い、その年のNPBに在籍していない選手を予測から除外しています。

roster_names = set(df_roster[df_roster["year"] == target_year]["player"])
mh = mh[mh["player"].isin(roster_names)]  # 名鑑外の打者を除外
mp = mp[mp["player"].isin(roster_names)]  # 名鑑外の投手を除外

この処理により、例えば2021年の予測では秋山翔吾・筒香嘉智・菊池雄星(いずれもMLB移籍)が正しく除外され、各チームの予測精度が改善されます。


wOBA / wRC+ を自前算出

打者の総合得点貢献を測る指標 wOBA と、リーグ平均を100とした wRC+ を、NPBの公式データから自前で算出しました。

MLB(Baseball Savant等)と違い、NPBには公式のwOBA公開値がないため、各イベントに重みをつけて計算しています。

# wOBA計算(NPBリーグ環境に合わせた係数)
woba = (
    0.69 * BB + 0.72 * HBP +
    0.89 * H1B + 1.27 * H2B +
    1.62 * H3B + 2.10 * HR
) / (AB + BB + HBP + SF)

2024年 wRC+ Top3(自前算出)

選手 チーム wOBA wRC+
近藤健介 ソフトバンク .479 249
オースティン DeNA .478 248
サンタナ 楽天 .441 220

機械学習(LightGBM / XGBoost)

特徴量には年齢・過去成績に加え、先ほどのwOBA/wRC+も追加しました。

features = [
    'age', 'OPS_prev1', 'OPS_prev2', 'OPS_prev3',
    'woba', 'wrc_plus',  # セイバー指標追加
    'PA_prev1', 'PA_prev2'
]

LightGBMとXGBoostの両方を試しましたが、精度はほぼ同等でした。


精度比較(2025年バックテスト)

2015〜2024年のデータで学習し、2025年の実績を予測するバックテストを実施しました。Marcel法と機械学習の比較は、同じ選手セット(打者PA≥100・投手IP≥30)で統一しています。

打者 OPS MAE(2025年、n=172選手)

手法 OPS MAE
Marcel法 .063
XGBoost .063
LightGBM .066

ほぼ同等(差は0.001以下)。

投手 ERA MAE(2025年、n=145選手)

手法 ERA MAE
Marcel法 0.78
XGBoost 0.93
LightGBM 0.92

Marcel法が上回りました(差は約0.14)。

投手ERA予測ではMarcel法が明確に上回りました。打者OPS予測では両者がほぼ同等でした。シンプルな手法がMLと同等以上の精度を出せることを、NPBデータで確認できました。

なぜこうなるのか、考えられる理由は以下です。

  • 選手の真の実力は1〜2年程度しか変化しない
  • MLは過学習しやすく、サンプル数が限られると精度が落ちる
  • シンプルな加重平均が、実際の成績変化の分布にフィットしている

選手ストーリー: 予測が当たるケースと外れるケース

バックテストの数値だけでは伝わりにくいので、具体的な選手で見てみます。

オースティン(DeNA)— 同じ選手で大外れと的中の両方

2022-2023年のケガによる離脱からの復活劇を追うと、Marcel法の強みと弱みが両方見えます。

2024年予測(2021-2023データで予測):

OPS PA
Marcel予測 .818 145
2024実績 .983 445
誤差 .165

ケガで2022年38打席・2023年54打席しか立てなかったため、Marcel法はリーグ平均方向に大きく回帰。結果としてOPS .983の大復活を予測できませんでした。

2025年予測(2022-2024データで予測):

OPS PA
Marcel予測 .842 213
2025実績 .834 246
誤差 .008

ところが翌年の予測では誤差わずか .008。2024年の好成績がデータに入ったことで、リーグ平均への回帰が「高すぎる成績を適度に引き下げる」方向に作用し、結果的に非常に的確な予測となりました。

同じ選手・同じ手法でも、データの状況次第で精度が大きく変わるという好例です。

筒香嘉智(DeNA)— 復活を予測できなかったケース

2019年までのNPB時代はOPS .900前後の強打者でしたが、MLB挑戦を経て2024年にNPBへ復帰。2024年は168打席でOPS .683と苦しみました。

OPS PA
Marcel予測 .656 168
2025実績 .876 257
誤差 .220

Marcel法は2024年の不振に引っ張られ、OPS .656と低めに予測。しかし2025年の筒香選手は20本塁打を放ちOPS .876と復活を遂げました。

Marcel法の限界が見えるケースです。直近の成績に重みを置く設計上、前年までのトレンドを大きく上回るような成績変化には対応しにくいと考えられます。


ピタゴラス勝率

チームの強さを「得点と失点の比率」で予測する手法です。

勝率 ≈ 得点^k / (得点^k + 失点^k)

指数 k はMLBでは 1.83 が標準ですが、NPBでの最適値を探したところ k=1.72 でした。

指数 MAE 対象
NPB最適(k=1.72) 3.20勝 全12球団(2015-2025)
MLB標準(k=1.83) 3.32勝 同上

FastAPIで推論APIを構築

予測をAPIとして提供できるようにしました。pip install -r requirements.txtuvicorn api:app --reload で起動し、http://localhost:8000/docs からSwagger UIで試せます。

エンドポイント一覧

パス 内容
GET /predict/hitter/{name} 打者の翌年予測(Marcel + ML)
GET /predict/pitcher/{name} 投手の翌年予測(Marcel + ML)
GET /predict/team/{name} チームのピタゴラス勝率
GET /sabermetrics/{name} wOBA / wRC+ / wRAA
GET /rankings/hitters 打者ランキング
GET /rankings/pitchers 投手ランキング
GET /pythagorean 全チームのピタゴラス勝率

レスポンス例(牧秀悟 翌年予測)

{
  "player": "牧 秀悟",
  "team": "DeNA",
  "marcel": { "OPS": 0.834, "AVG": 0.295, "HR": 22.9, "RBI": 81.4 },
  "ml": { "pred_OPS": 0.874 }
}

Docker対応もしており、docker compose up --build で一発起動できます。

Raspberry Pi + Tailscale Funnelで外部公開

ローカルで動かすだけでなく、Raspberry Pi 5にDockerごとデプロイして常時稼働させています。さらにTailscale Funnelを使ってインターネットに公開しました。

# RPiでFunnelを有効化(1コマンドで完了)
sudo tailscale funnel --bg 8000

これだけで固定のHTTPS URLが発行され、外部からSwagger UIを操作できます。VPNサービスのTailscaleが証明書管理・ポートフォワード・NAT越えをすべて肩代わりしてくれるため、ルーター設定なしで公開できるのが手軽でした。

チーム編成シミュレーション

v0.3.0で /simulate/team/{team} エンドポイントを追加しました。選手を入れ替えて勝数の変化をシミュレーションできます。

GET /simulate/team/DeNA?year=2025&add=山川&remove=宮﨑

wRAAベースで得点を加減算し、ピタゴラス勝率を再計算する仕組みです。


Streamlitダッシュボード

APIの全機能をブラウザで操作できるStreamlitダッシュボードも用意しました。

pip install -r requirements.txt
streamlit run streamlit_app.py

7ページ構成で、打者/投手予測・ランキング・ピタゴラス順位表・チーム勝率をインタラクティブに操作できます。チャートはPlotlyで、NPB12球団のチームカラーに対応しています。日本語/英語の2言語切り替えに対応しています。

打者ランキング: wOBA / wRC+ ソート追加

OPS/AVG/HR/RBIに加え、wOBA(打席あたりの得点貢献度)とwRC+(リーグ平均=100の打撃力)でもソートできるようになりました。

投手ランキング: FIP / K% / BB% 等の高度な指標追加

ERA/WHIPに加えて、FIP(味方の守備に左右されない投手の真の実力)や、K%(三振率)、BB%(四球率)、K-BB%(三振率−四球率)、K/9(9イニングあたり奪三振)、BB/9HR/9 でもソートできます。

FIP = (13×被本塁打 + 3×(四球+死球) - 2×奪三振) / 投球回 + 定数C

FIPが防御率より低い投手は「味方の守備に恵まれず、実力以上に打たれている」可能性があり、逆にFIPが防御率より高い投手は「守備に助けられている」可能性があると考えられます。

予測ページ: 計算式の解説

打者予測ではwOBA/wRC+/wRAAの数値カードとwRC+推移グラフを表示します。投手予測ではFIP/K%/BB%/K-BB%/K9/BB9/HR9の数値カードを表示し、K/9・BB/9・HR/9にはリーグ平均との差分も表示します。各指標には計算式と基準値の解説expander付きです。

レーダーチャートも更新し、打者はHR/AVG/OBP/SLG/wOBA/wRC+の6軸、投手はERA/WHIP/奪三振/K9/BB9/HR9/FIPの7軸で描画します(OPS・勝利・投球回は除外し、セイバー指標を重視した構成)。

トップページ: wRC+ / FIP 順のTOP3 + 先発/リリーフ別

トップページの打者TOP3をwRC+順(OPSから変更)、投手TOP3をFIP順(ERAから変更)に変更しました。いずれも守備の影響を受けにくい指標で、より純粋な打撃力・投球力を反映します。

また投手を先発(投球回100以上)とリリーフ(投球回20〜99)に分けてそれぞれFIP順でTOP3を表示するようにしました。投球回フィルターによる分類のため、怪我等で投球回が少ない先発投手が混入する場合があります。

さらに、カード内の棒グラフとレーダーチャートの指標を統一し、打者カードにwOBA/wRC+、投手カードにFIP/K9/BB9/HR9を追加しました。


現在の課題と今後の改善案

新外国人・新人選手の扱い

Marcel法は過去3年のNPBデータを必要とするため、新外国人・新人・長期離脱からの復帰選手は計算対象外になります。現在の実装では、これらの選手を**「リーグ平均の貢献(wRAA=0)」**として暗黙的に扱っています。

ダッシュボードでは計算外選手をチームカードのバッジとexpanderで可視化しました。さらに予測幅(信頼区間)を実装し、不確実性をグラフ上に表示しています。

✅ 実装済み: 予測幅(信頼区間)

計算外選手はwRAA=0(リーグ平均と同等の貢献)として計算されますが、実際の初年度成績には大きなばらつきがあります。

考え方:

  1. 歴史的にNPB外国人選手の初年度wRAAは -15点〜+25点 のばらつきがある
  2. 野球統計の経験則: 10点の得失点差 ≈ 1勝(ピタゴラス勝率から導出)
  3. → 計算外選手1人あたりの不確実性 ≈ ±1.5勝 と設定
予測幅 = 計算外人数 × 1.5勝
例: 計算外3名のチーム → 予測70勝なら「67〜74勝」と表示

グラフのオレンジのエラーバーが予測幅です。計算外選手が多いチームほど幅が広く、「データのない選手の活躍次第で順位が大きく変わりうる」という示唆を数値で表現しました。

✅ 実装済み: NPB1〜2年目選手のデータ年数バッジ

「計算対象外」とは別の問題として、NPBデータが1〜2年しかない選手も予測の不確実性が高くなります。Marcel法のリーグ平均への回帰は、データが少ないほど強く働くからです。

# データ1年(60IP)の投手の場合: weighted_ip = 60×5 = 300
# proj_ERA = (選手ERA×300 + リーグ平均ERA×600) / 900
#           ≈ 選手1/3 + リーグ平均2/3
  • データ1年のみの選手 → 予測値の約2/3がリーグ平均に引き寄せられる
  • データ2年のみの選手 → 予測値の約半分がリーグ平均に引き寄せられる

「前年Aチームに在籍、今季Bチームに移籍した2年目外国人投手」のように、「NPBには1年いるけどデータは少ない」という選手の予測値が平均寄りに出ても、それは能力の限界ではなくデータ不足による補正です。

この問題を可視化するため、ダッシュボードの選手名横に「NPB1年」「NPB2年」バッジを自動表示するようにしました(対象: 打者125名・投手180名)。バッジがついている選手の予測値は参考程度に留め、前年成績や他の情報と合わせて判断してください。

✅ 実装済み: 選手名の表記揺れ正規化

NPBのデータでは、同一選手が年度によって異なる漢字表記で記録される場合があります。たとえば 宮﨑 敏郎(﨑=U+FA11 の互換漢字)と 宮崎 敏郎(崎=U+5D0E)が混在し、そのまま処理すると別の選手として扱われてしまう問題がありました。

wRC+の複数年推移グラフで「一部の選手だけグラフが表示されない」原因がこれでした。

データパイプライン(sabermetrics.py)の保存前に正規化を適用しています:

VARIANT_MAP = str.maketrans("﨑髙濵澤邊齋齊國島嶋櫻", "崎高浜沢辺斎斉国島島桜")

def normalize_player_name(name: str) -> str:
    return str(name).replace("\u3000", " ").strip().lstrip("*").strip().translate(VARIANT_MAP)
  • CJK互換漢字 → 標準字(﨑→崎髙→高 など)に変換
  • データソース由来の先頭 * を除去
  • 全角スペース → 半角スペースに統一

この正規化により、宮崎敏郎選手のwRC+推移が1年分→11年分として正しく表示されるようになりました。

残る課題

アプローチ 内容 難易度
過去実績平均 歴代NPB外国人選手の初年度成績を集計して割り当て ★★☆
出身リーグ変換係数 MLB・KBO → NPB の換算係数を適用 ★★★
ドラフト順位別平均 1位・2位以下で初年度期待値を変える ★★☆

計算外選手が多いチームほど予測の不確実性が高く、「モデルの下位予測でも、記録のない選手たちの活躍次第で順位は十分に変わりうる」という情報自体が重要な示唆です。

Marcel法のもう一つの限界: 若手の急成長

Marcel法の年齢調整は +0.3%/年(27歳ピーク基準) と非常に小さく、急激な成長には追いつきません。

23〜26歳の選手が「殻を破る」ような場合、モデルは過去3年の平均に引き戻すため、実際の成績を大幅に下回る予測になります。新外国人・新人と同様に、若手ブレイク候補が多いチームほどモデルの予測は保守的に出る傾向があります。この限界は信頼区間(オレンジの予測幅)には現在反映されていないため、実際の順位との差がさらに広がる可能性もあります。

まとめ

項目 内容
データ baseball-data.com + npb.jp(2015-2025、5種類)
Marcel法精度(2025) 打者OPS MAE=.063 / 投手ERA MAE=0.78
ML精度(2025) 打者OPS MAE=.063 / 投手ERA MAE=0.92
ピタゴラス勝率 NPB最適 k=1.72、MAE=3.20勝
API FastAPI 8エンドポイント、Docker対応
ダッシュボード Streamlit 7ページ、Plotlyチャート、日英対応

投手ERA予測ではMarcel法がMLを上回り、打者OPS予測では両者がほぼ同等でした。「最新のAIが必ずしも勝つわけではない」という事実をNPBデータで確認できたのが一番の収穫でした。

オースティン選手のように2024年は誤差.165の大外れ、翌2025年は誤差.008の的中と、同じ選手でもデータ次第で精度が大きく変わります。筒香選手の復活のように予測が追いつかないケースも含め、予測モデルの限界を理解した上で活用することが大切だと感じています。

GitHub: https://github.com/yasumorishima/npb-prediction

データ出典: プロ野球データFreak / NPB公式サイト


関連記事

GitHubで編集を提案

Discussion