0. 前置き
いわゆるビッグデータの分析や予測も面白いのですが、最近統計を改めて勉強し直して、決定係数とか単純な回帰の分かりやすさも良いなと感じ始めていました。
そこで、プロ野球のデータで昔から気になっていた、以下の2チームのどちらが得点力があるかを簡単に分析をしてみました。
- 1番から9番まで平均的な得点力の打者(いわゆるオールC)が並ぶチーム
- 各打者の得点力に大きくムラがあるチーム(例えば中軸だけオールA,Bでそれ以外はオールE,Fのチーム)
0.1 参考サイト
プロ野球データFreak様
いつもありがとうございます。
0.2 断り
間違ったことは書かないよう注意はしていますが、統計もプログラミングも素人に近いので、間違いや改善点等ありましたらご指摘お願いいたします。
0.3 使用するデータ
大きく以下の二つです。
- 2010年-2021年のセパ両リーグのチーム得点力
順位 | チーム | 試合 | 勝利 | 敗北 | 引分 | 打率 | 得点 | 安打 | 本塁打 | ... | 出塁率 | 長打率 | OPS | NOI | IsoD | IsoP | 得点平均 | 安打平均 | リーグ | 年 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 中日 | 144 | 79 | 62 | 3 | 0.259 | 539 | 1229 | 119 | ... | 0.327 | 0.388 | 0.715 | 0.456 | 0.068 | 0.13 | 3.74 | 8.53 | セリーグ | 10 |
2 | 阪神 | 144 | 78 | 63 | 3 | 0.29 | 740 | 1458 | 173 | ... | 0.345 | 0.449 | 0.794 | 0.495 | 0.055 | 0.159 | 5.14 | 10.13 | セリーグ | 10 |
3 | 巨人 | 144 | 79 | 64 | 1 | 0.266 | 711 | 1311 | 226 | ... | 0.329 | 0.458 | 0.787 | 0.482 | 0.063 | 0.192 | 4.94 | 9.1 | セリーグ | 10 |
4 | ヤクルト | 144 | 72 | 68 | 4 | 0.268 | 617 | 1304 | 124 | ... | 0.34 | 0.402 | 0.742 | 0.474 | 0.072 | 0.133 | 4.28 | 9.06 | セリーグ | 10 |
5 | 広島 | 144 | 58 | 84 | 2 | 0.263 | 596 | 1278 | 104 | ... | 0.324 | 0.383 | 0.707 | 0.452 | 0.061 | 0.121 | 4.14 | 8.88 | セリーグ | 10 |
- 2010年-2021年のセパ両リーグの100打席以上の打者の成績
背番号 | 選手名 | 打率 | 試合 | 打席数 | 打数 | 安打 | 本塁打 | 打点 | 盗塁 | ... | 三振 | 犠打 | 併殺打 | 出塁率 | 長打率 | OPS | RC27 | XR27 | 年 | チーム |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 青木 宣親 | 0.358 | 144 | 667 | 583 | 209 | 14 | 63 | 19 | ... | 61 | 0 | 10 | 0.435 | 0.509 | 0.944 | 887 | 830 | 10 | ヤクルト |
7 | 田中 浩康 | 0.3 | 140 | 637 | 516 | 155 | 4 | 54 | 4 | ... | 48 | 45 | 15 | 0.385 | 0.362 | 0.748 | 482 | 469 | 10 | ヤクルト |
6 | 宮本 慎也 | 0.276 | 129 | 517 | 468 | 129 | 4 | 39 | 2 | ... | 31 | 18 | 10 | 0.319 | 0.357 | 0.675 | 383 | 369 | 10 | ヤクルト |
9 | 飯原 誉士 | 0.27 | 130 | 492 | 430 | 116 | 15 | 48 | 8 | ... | 74 | 5 | 10 | 0.351 | 0.435 | 0.786 | 513 | 510 | 10 | ヤクルト |
2 | 相川 亮二 | 0.293 | 120 | 474 | 427 | 125 | 11 | 65 | 2 | ... | 70 | 6 | 8 | 0.34 | 0.419 | 0.759 | 514 | 513 | 10 | ヤクルト |
1. 強打者の定義
実際の打者がパワプロのような能力でわかれば良いのですが、当然そんなことはないので、データから取得する必要があります。
パッと思いつくのが打率、出塁率、長打率、OPSあたりだったので、これらの中で"得点力"と一番相関があるパラメータを調べてみました。
1.1 各指標と得点力の相関
つまりは、各年・各チームごとの打撃指標と得点の相関です。
fig, ax = plt.subplots(2,2, figsize=(12,8))
cols = ['打率', '出塁率', '長打率', 'OPS']
titles = ['batting average', 'on-base percentage', '']
for i in range(4):
target_col = cols[i]
corr = round(df_team_origin.loc[:,[target_col, '得点平均']].corr().iloc[0,1],3)
ax[i//2, i%2].scatter(df_team_origin.loc[:,target_col], df_team_origin.得点平均)
ax[i//2, i%2].set_title(f'{target_col} 相関係数:{corr}')
ということで、OPSを採用することとします。
(0.962ってかなり相関が強いですね..)
1.2 各年毎のOPSの打者の分布
参考までに、年毎に打者のOPSの分布と平均を調べてみました。低反発球の2011,2012はやはり低いですね。
years = df_hitter.年.unique()
years_count = len(years)
mean_list = []
sd_list = []
fig, ax = plt.subplots((years_count+2)//3,3, figsize=(16,(years_count//3+1)*3))
for i in range(years_count):
year_tmp = years[i]
df_tmp = df_hitter.loc[df_hitter.年 == year_tmp, 'OPS']
ops_mean_tmp = round(np.mean(df_tmp),3)
ops_sd_tmp = round(np.std(df_tmp),3)
mean_list.append(ops_mean_tmp), sd_list.append(ops_sd_tmp)
ax[i//3, i%3].hist(df_tmp, bins=[i/1000 for i in range(300,1200,100)], density=False)
ax[i//3, i%3].set_title(f'year:20{year_tmp}, 平均OPS:{ops_mean_tmp}, 標準偏差:{ops_sd_tmp}')
ax[i//3, i%3].axvline(ops_mean_tmp, color='r', linestyle='dashed')!
2. OPSの標準偏差
"平均的な得点力の打者が並ぶチーム"と"各打者の得点力に大きくムラがあるチーム"をどういうふうに比較すれば良いかですが、データのばらつきを示す標準偏差で比較していきます。
得点力は得点平均(チームの1試合あたりの平均得点)を採用し、それがチームOPSの標準偏差とどれぐらい相関があるかをみてみます。
df_hitter_OPS_var = df_hitter.pivot_table(
index = ['年', 'チーム'],
values = 'OPS',
aggfunc= [lambda x : x.var()**(0.5), 'mean']
).reset_index()
df_hitter_OPS_var.columns = ['年', 'チーム', 'OPS_sd', 'OPS_mean']
df = df_team_origin.loc[:,['年', 'チーム', '得点平均']].copy()
df = df.merge(df_hitter_OPS_var, on=['年', 'チーム'], how='left')
fig, ax = plt.subplots(1,2, figsize=(12,4))
ax[0].scatter(df.OPS_sd,df.得点平均)
ax[0].set_title('チームのOPS標準偏差と平均得点')
ax[0].set_ylabel('平均得点')
ax[1].scatter(df.OPS_sd,df.OPS_mean)
ax[1].set_title('チームのOPS標準偏差と平均OPS')
ax[1].set_xlabel('OPS 標準偏差')
ax[1].set_ylabel('平均OPS')
print(df.loc[:,['OPS_sd', '得点平均', 'OPS_mean']].corr())
fig.savefig('OPS_sdと平均得点及び平均OPSの分布.jpg')
OPS_sd | 得点平均 | OPS_mean | |
---|---|---|---|
OPS_sd | 1 | 0.32041 | 0.12962 |
得点平均 | 0.32041 | 1 | 0.83185 |
OPS_mean | 0.12962 | 0.83185 | 1 |
ということで、0.3と多少の相関はありそうだなという感じです。
また、標準偏差と平均の相関係数は低いことも確認できました。もしここにも相関があってしまうと、OPSの平均と得点平均の相関の影響で標準偏差と得点平均にも相関が出てしまっていた可能性が出てしまいます。
3.回帰分析と決定係数
最後に、以下の2条件で回帰分析を行なって自由度調整済み決定係数を比較してみます。
- 説明変数:OPSの平均 被説明変数:得点平均
- 説明変数:OPSの平均とOPSの標準偏差 被説明変数:得点平均
3.1 ケース1
reg_model1 = LinearRegression()
reg_model1.fit(df.loc[:,['OPS_mean']],df.得点平均)
# 予測
pre1 = reg_model1.predict(df.loc[:,[ 'OPS_mean']])
fig, ax = plt.subplots(1,1)
ax.scatter(pre1, df.得点平均)
ax.set_xlabel('predict')
ax.set_ylabel('true')
fig.savefig('OPS平均のみの場合の予測と正解のプロット.jpg')
決定係数も求めておきます。
SSE1 = ((df.得点平均 - pre1)**2).sum()
SST1 = ((df.得点平均 - df.得点平均.mean())**2).sum()
n = len(pre1)
k = 1
R2_1 = 1 - (SSE1/(n-k-1)) / (SST1/(n-1))
R2_1
0.699
まあまあですね。
3.2 ケース2
続いてケース2で、標準偏差を追加してみます。
reg_model2 = LinearRegression()
reg_model2.fit(df.loc[:,['OPS_mean', 'OPS_sd']],df.得点平均)
# 予測
pre2 = reg_model2.predict(df.loc[:,['OPS_mean', 'OPS_sd']])
fig, ax = plt.subplots(1,1)
ax.scatter(pre2, df.得点平均)
ax.set_xlabel('predict')
ax.set_ylabel('true')
fig.savefig('OPS平均とOPS_sdの予測と正解のプロット.jpg')
若干ばらつきが小さくなったように見えます。
同じく自由度調整済み決定係数を求めてみます。
SSE2 = ((df.得点平均 - pre2)**2).sum()
SST2 = ((df.得点平均 - df.得点平均.mean())**2).sum()
n = len(pre2)
k = 1
R2_2 = 1 - (SSE2/(n-k-1)) / (SST2/(n-1))
R2_2
0.736
先ほどよりも改善しています。
4. まとめ
可視化して回帰分析をして、決定係数で確認するプロセスを経て、一応OPSの標準偏差が得点力に影響がありそうだということがわかりました。
相関係数が正なことから、ばらつきが大きいチーム、つまり並みの選手が9人居るよりも、多少打力の低い選手がいても、スター選手がいるチームの方が総合的には得点力が上がりそうだということがわかりました。
そして、おそらく年棒も後者の方が高いんだろうなと思うので、ある意味理にかなってるなという感じです。
以上です。