0
1

古典野球データ主義者がStatCastに抗ってみる

Posted at

トラックマンをはじめとするデータ解析ツールが一般的になって久しい今日この頃。

あらゆる野球人が成績(結果)よりも動き(過程)に重きを置くようになり、一つ一つのプレーに対する考え方、選手の評価方法が大きく変わりつつあります。




でも、昨今のこういう流れを何だかポジティブに受け入れられていない自分がいるように思えてならない...。


いや、正直なところそれが本音です。


だって私が子供のころから追っかけてきたのは、
投手なら(勝/敗/防御率)、野手なら(打率/HR/打点/盗塁)というお決まりのセットです。

これに嫌というほど触れてきたから、2010年の金本知憲(.241/16本/45打点)とか2011年の荒木雅博(.263/2本/24打点)みたいな成績を見ると、言葉では表現できない「良さ」を感じて涎が止まらなくなるのです。


それなのに、急に平均回転数だの打球速度だの、バレル%だのスイートスポット%だの、xOBPだのxwOBAだのと攻め立てられると、保守派の私はただただオロオロすることしかできないのです。


とはいっても、動作解析の隆盛がとどまることを知らない現代野球についていくためには、いつまでもStatCast的な指標から目を背けるわけにはいきません。

そこで、どの指標がどの成績と強い結びつきをもっているのか、あるいは全然関わりがないのかを、自分なりに明らかにしてみようと考えました。


もちろん、「この分析を通じてセイバーメトリクス関連の知見を深めたい」みたいな崇高な目標意識はありません。

一口に言えば、「StatCastなんて意味ねえ!! 結局今まで通り試合での成績だけ見てればええんや!!!」 っていうような結論がほしくて、やってみるだけです。

-理論編- 

野球というスポーツの一つの特徴として、身体能力が成績に反映されづらいという側面があります。

これは、ある意味では素晴らしい側面だと思います。
生まれ持った身体能力では劣っていても、野球という競技においては大活躍できる可能性が十分にあるのですから。


昨今の動作解析偏重の風潮というのは、こうした野球の魅力を形作っていた特徴を揺るがすものです。

だからこそ私は、StatCastで収集されたデータがどれほど成績に影響を及ぼしているのか、どこまで信頼に足る数値なのかをはっきりさせたいというわけです。

今回の検証では、必要なデータを取得・整理して、データの標準化を行ったうえで回帰分析を実行し、各説明変数の重み(パラメータ)を算出します。目的変数は打率と本塁打数、説明変数はStatCastで示される動作解析系の数値とします。

打率および本塁打数とStatCast系の指標の関係性を知りたい、願わくばあまり関係がないという結果がほしい、というのがスタート時点での考えです。

-取得編- 

案の定ですが、NPBに関してはStatCast系の数値が網羅されたサイトはなく、そもそも個人成績をまとめてCSVファイルでローカルに落とし込めるようなシステムがありません。

私は日本の野球を追いかけ続けてきたので、せっかくならNPBで検証したいと思っていましたが、個人レベルで今回のような分析を行うのはちょっと厳しそうです。

仕方なく、MLBの公式サイトからデータを取得することにします。

MLB公式サイト

ほしいデータは、MLB.com → STATS → Statcast Leaders からダウンロードできます。
さらに今回はデータに含める指標を自分で選択したいので、Cusom Leaderboard のページに移ります。

スクリーンショット 2024-05-18 104517.png

今回取得する指標は以下の通りです。

変数名 内容 備考
player_name 名前
player_id 選手ID
year 年度 2023年
player_age 年齢 最年長はミゲルカブレラ(40)
ab 打数
pa 打席数
hit 安打
single 単打
double 二塁打
triple 三塁打
home_run 本塁打
strikeout 三振
walk 四球
k_percent 三振率
bb_percent 四球率
battinng_avg 打率
slg_percent 長打率
on_base_percent 出塁率
on_base_plus_slg OPS
babip インプレー打率 本塁打を除くフェアゾーンに飛んだ打球が安打になった割合
rbi 打点
xba 打率の期待値
xslg 長打率の期待値
woba 得点貢献度 1打席あたりにどれだけチームの得点増加に貢献したかを表す指標。安打や四球など出塁を伴う要素に得点価値を加重して算出
xwoba 得点貢献度の期待値
xobp 出塁率の期待値
xiso (長打率ー打率)の期待値
exit_velocity_avg 平均打球速度
launch_angle_avg 平均打球角度
sweet_spot_percent スイートスポット率 打ち出し角度が8度から32度の割合
barrel_batted_rate バレルゾーン率 得点に繋がりやすい打球速度と打球角度の組み合わせ(バレルゾーン)に入る割合
hard_hit_percent ハードヒット率 打ち出し速度が95mph以上の割合
avg_best_speed 打球速度の上位50%
avg_hyper_speed 調整打球速度 88マイル未満の打球は全て88マイルとし、それ以上はそのままにした平均打球速度
whiff_percent 空振り率
swing_percent スイング率

なお、外れ値をなるべく減らすため、今回は2023シーズンに250打席以上立っている選手に限定してデータを抽出しました。

-整理編- 

公式サイトからダウンロードしたファイルを見てみると、姓名が別々のカラムに入ってたので、player_nameとしてスプレッドシートで統合しました。

スクリーンショット 2024-05-18 105246.png

このまま分析に移ってもよいのですが、各種成績の期待値(x○○系)と実際の成績の関連性が気になったので、少しだけ確認してみます。


Bigqueryにデータフレームを読みこませて、適当にSQLを投げて一部選手の成績を抽出しました。

以下は打率・長打率・出塁率が期待値より高い選手を期待値との乖離順で表示させたものです。

スクリーンショット 2024-05-11 101758.png
スクリーンショット 2024-05-11 102018.png
スクリーンショット 2024-05-11 102144.png

打率・出塁率で1位に来ているのはホセ・アルトゥーベです。ある意味めちゃめちゃイメージ通りですね。あのフィジカルでメジャートップレベルの打撃成績を維持しているのは、Statcastのデータから見ても驚く結果のようです。

長打率で1位なのはフェルナンド・タティス・Jrでした。身体能力の塊みたいな選手ですが、Statcast指標から予測される成績は意外と大したことないようですね。

逆に、各成績が期待値より低い選手のランキングも見てみます。

スクリーンショット 2024-06-18 115711.png
スクリーンショット 2024-06-18 115816.png
スクリーンショット 2024-06-18 115905.png

打率・出塁率ではウラディミール・ゲレーロ・ジュニアが目立ちます。
2018年に全米プロスペクト1位、2021年には堂々のホームラン王を獲得した圧倒的スケールの持ち主ですが、2023シーズンは打率.264、26本塁打と不完全燃焼な印象でした。

長打率ではアーロン・ジャッジが1位。
2023シーズンは6割を超える素晴らしい長打率を記録しましたが、期待値が.712と異常だったのでこういう結果になりました。可哀そうというべきかどうなのか・・・。

これだけ見てると、Statcastデータをもとに算出された期待値というのはあてにならないなと思ってしまいますね。まあ、乖離が大きい選手を抽出しているせいなんですが。

-分析編- 

今回は、ひとまずRStudioで重回帰分析もやってみることにします。

重回帰分析を行う前の準備段階として、以下の記事を参考にしてデータフレームの標準化を行いました。

Rでdata frameを標準化するメモ(関数作った&パッケージ化した)

今回使用するデータフレームには数値型ではない値(選手の氏名など)が含まれているため、そのままscale関数を適用しようとすると以下のエラーが表示されます。

Error in colMeans(x, na.rm = TRUE) : 'x' must be numeric

だから列を限定してscale関数を適用させるのですが、これをやるとデータフレームとしての構造が崩れてしまい、このあとやりたい操作(lm関数による回帰分析)ができなくなってしまいます。

なので、次の手順で標準化および回帰分析をすることになります。

①指定した列のみscale関数を適用させたものを新しい変数に格納する
②このままだとただの列のまとまりなので、cbind関数を適用して標準化していない列といま作った変数を結合する
③データフレームの状態を確認したうえで、lm関数により重回帰分析を行う

具体的には以下のようになります。

#元のデータはbatterdfに格納
batterdf.sc <- scale(batterdf[5:36]) #5列目から36列を標準化する
batterdf.new_sc <- cbind(batterdf[1:4], batterdf.sc) #標準化されてない部分と結合してデータフレームに戻す
head(batterdf.new_sc) #データフレームになっていることを確認する

最後の行の出力結果は以下の通りです。
スクリーンショット 2024-05-25 110300.png

ちゃんとデータフレームになっているようです。標準化もしっかりされてます。
この状態で、lm関数による回帰分析を実行します。

冒頭で紹介したように、この分析における目的変数は打率と本塁打数、説明変数はStatCastで示される動作解析系の数値と設定しているので、以下のように記述します。

HR.lm <- lm (home_run~exit_velocity_avg+launch_angle_avg+sweet_spot_percent+barrel_batted_rate+hard_hit_percent
             +avg_best_speed+avg_hyper_speed+whiff_percent+swing_percent,data=batterdf.new_sc)
summary(HR.lm)

AVG.lm <- lm (batting_avg~exit_velocity_avg+launch_angle_avg+sweet_spot_percent+barrel_batted_rate+hard_hit_percent
              +avg_best_speed+avg_hyper_speed+whiff_percent+swing_percent,data=batterdf.new_sc)
summary(AVG.lm)

本塁打数が目的変数の場合の各指標の重みは以下の通り。
スクリーンショット 2024-06-02 114222.png
スクリーンショット 2024-06-02 114033.png


打率が目的変数の場合の各指標の重みは以下の通り。

スクリーンショット 2024-06-02 114205.png
スクリーンショット 2024-06-02 114047.png


Rだけだと味気ないので、せっかくだからPythonライブラリを使った重回帰分析もやってみます。

こちらはGoogleColaboratoryで動かしてみます。環境構築不要でデータ分析できるのは良いですよね。

基本的には株式会社キカガクの学習講座に従って、以下の手順で進めます。

①データセット読み込み
②外れ値除去
③学習用データセットとテスト用データセットの分割
④学習用データセットを標準化
⑤モデル構築
⑥検証、可視化

本塁打数を目的変数とした場合の操作を記載します。

①データセット読み込み

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt

df = pd.read_csv("/content/mlb2023batters.csv")

②外れ値除去

hrdf = df.reindex(columns=['exit_velocity_avg', 'launch_angle_avg', 'sweet_spot_percent', 'barrel_batted_rate', 'hard_hit_percent', 'avg_best_speed', 'avg_hyper_speed', 'whiff_percent', 'swing_percent', 'home_run'])
h_mean = hrdf.mean() #平均値
h_sigma = hrdf.std() #標準偏差
h_cols = hrdf.columns #列の取得

_df = hrdf
for col in h_cols:
  # 3σ 法の上限値の設定
  low = h_mean[col] - 3 * h_sigma[col]
  high = h_mean[col] + 3 * h_sigma[col]
  # 条件で絞込
  _df = _df[(_df[col] >= low) & (_df[col] <= high)]

③学習用データセットとテスト用データセットの分割

x = _df.iloc[:, :-1] #各選手のStatcastの数値
t = _df.iloc[:, -1 #各選手のHR数

from sklearn.model_selection import train_test_split
x_train, x_test, t_train, t_test = train_test_split(x, t, test_size=0.4, random_state=1)

④学習用データセットを標準化

from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(x_train, t_train)

x_train2 = scaler.transform(x_train)
x_test2 = scaler.transform(x_test)

⑤モデル構築

model = LinearRegression().fit(x_train2, t_train)

⑥検証、可視化

model.score(x_test2, t_test) #決定係数は約0.59
np.set_printoptions(precision=5, suppress=True) #有効数字5桁、指数表示禁止
model.coef_

各指標の重みとグラフ化した図は以下の通り。

スクリーンショット 2024-06-20 145536.png

スクリーンショット 2024-06-02 113305.png

打率が目的変数の場合についても同じ操作を行いました。
分析結果は以下の通り。

スクリーンショット 2024-06-20 145615.png
スクリーンショット 2024-06-02 112811.png

-考察編-

まず、本塁打数を目的変数とした場合の分析結果を見てみましょう。

各指標の重みをみると、やはりバレル率の高さが一際目を引きます。

が、そもそもバレルというのは打球角度と打球速度の組み合わせとそれに対応する結果を統計的に調査し、そのなかで圧倒的に長打になりやすい組み合わせとして規定された指標です。
言うなれば、成績ドリブンな指標であるわけです。

なので、バレル率と本塁打数の間に強い相関があるのはある意味当たり前の話。

それ以外の指標でいうと、平均打球速度の係数が割と大きくなっています。

ですが、こちらも結果をそのまま受け入れるわけにはいきません。

打球速度の指標というのはStatcast開始当初から存在するわけですが、全体の平均をとってしまうと速度にバラつきがある選手とずっと同じくらいの選手が同じものとして扱われてしまうため、選手の価値を測る指標としては疑問視されてきたんですね。

そのなかで色々と試行錯誤が重ねられて、現在は速度上位50%の打球だけで平均をとるEV50(avg_best_speed)、88mph以下の打球は全て88mphだとみなして平均をとるAdjuted EV(avg_hyper_speed)という2つの指標がLeaderboardに加えられています。

この辺の詳しい話は以下の記事を参照してください。

打球分類ごとのEscape Velocity~フライボール革命の幹~

まあてなわけで、全ての打球速度の平均をとっただけの値というのは、もはやあまり意味がないものとしてみなされているんですよね。

今回の分析においては、平均打球速度こそ割と相関がありそうな感じで出てますが、EV50やAdjusted EVに関しては係数が小さな値になっており、あまりつながりがないように見えます。

こうなってくると、打球速度だけでホームランをよく打つ選手か、そうでないかを見極めるのは難しいという話になってきますね。

ただ唯一信頼して良さそうなのは、空振り率の重みです。
空振り率と本塁打数の間には負の相関があり、空振りが多い選手は比較的HRが少ないという結論は。とりあえず間違いなさそうですね。


続いて、打率を目的変数にとった場合の分析もみてみます。

まず相関が強そうな指標として気になるのは、スイートスポット率です。

この指標は打ち出し角度が8度から32度の割合を表しています。
ゴロよりフライ性の打球の方が価値は高いという考えが広く浸透していますが、今回の分析ではそれを裏付けるような結果となりました。

そして最も係数が大きいのは先ほど紹介したAdjusted EVです。
平均打球速度だけで見るとあまり関係がなさそうですが、速い打球の比重を上げてみると強い相関がみられました。これはなかなか重要な発見になるかもしれません。

また、こちらでも空振り率とは強い負の相関がみられます。
空振りの多い選手は打率が低い、その逆もしかり、というのはイメージ通りですね。


以上の考察を踏まえて、ひとまず次の2つの結論を出しました。

1.「空振りが多い選手はダメ!バットに当てることからすべてが始まる」
2.「全ての解析データをそのまま示すタイプの指標はあてにならない。人間側での調整が必要」

1に関しては、読んで字のごとくです。
バットに当て、ボールを前に飛ばさなければ何も始まりません。それができていない段階で理想的な打球角度やら打球速度やらを探求するのはナンセンスです。

2の方についても、ある意味当たり前といえば当たり前のことなのかもしれません。
全ての打球速度の平均をとったExitVerocityAvgや全ての打球角度の平均をとったLaunchAngleAvgを見るだけでは、その選手の価値や能力は判断できないのです。

EV50・Adjusted EVやスイートスポット率のように、特定の範囲の数値に重きを置いてその割合を計測したり、バレル率のように複数の指標を組み合わせたりすることによって、真に価値ある打球をどのくらい放っているかが明瞭になってきます。

Statcastで取得したデータをそのまま受け取るのではなく、試合での成績とのつながりが分かりやすい形に加工し・調整を行って、初めて動作解析系の数値は生き生きとその選手の特性を語ってくれるのです。

-結語-

素人がとりあえずやってみた野球データ分析、いかがだったでしょうか。

正直、己の知識不足のために踏み込めてない領域がかなりあるので、新たな発見といえるような成果を出すところまでは到達できてません。

とはいえ、個人的にはやってよかったと思います。
実際に分析をして記事にまとめるとなればくまなく調べるほかないので、元々興味がなかったStatcastの項目や最近導入されたセイバーメトリクス系の指標についてもかなり知識が深まりましたからね。

本当は投手成績も含めてもっと広い範囲を調査したかったのですが、キリがないのでかなり限定的になってしまいました。

気が向いたら、違うテーマでまたやろうかな…。

ていうか、NPBの成績で分析させてくれ!!早く公式に動作解析系の数値を掲載して、CSVでダウンロードできるようにしてくれ!!!


以上、古典野球データ主義者がStatCastに抗ってみるコーナーでした。

それではまた。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1