2
0

More than 1 year has passed since last update.

【データ分析】平均的な打者が並ぶチームと、一部の強打者が居るチームを比較検証する(回帰分析・決定係数)

Posted at

0. 前置き

いわゆるビッグデータの分析や予測も面白いのですが、最近統計を改めて勉強し直して、決定係数とか単純な回帰の分かりやすさも良いなと感じ始めていました。

そこで、プロ野球のデータで昔から気になっていた、以下の2チームのどちらが得点力があるかを簡単に分析をしてみました。

  1. 1番から9番まで平均的な得点力の打者(いわゆるオールC)が並ぶチーム
  2. 各打者の得点力に大きくムラがあるチーム(例えば中軸だけオールA,Bでそれ以外はオールE,Fのチーム)

0.1 参考サイト

プロ野球データFreak様
いつもありがとうございます。

0.2 断り

間違ったことは書かないよう注意はしていますが、統計もプログラミングも素人に近いので、間違いや改善点等ありましたらご指摘お願いいたします。

0.3 使用するデータ

大きく以下の二つです。

  1. 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
  1. 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_と得点の分布.jpg

ということで、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')!

(3年分だけ抜粋)
OPS_hist.jpg

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

OPS_sdと平均得点及び平均OPSの分布.jpg

ということで、0.3と多少の相関はありそうだなという感じです。
また、標準偏差と平均の相関係数は低いことも確認できました。もしここにも相関があってしまうと、OPSの平均と得点平均の相関の影響で標準偏差と得点平均にも相関が出てしまっていた可能性が出てしまいます。

3.回帰分析と決定係数

最後に、以下の2条件で回帰分析を行なって自由度調整済み決定係数を比較してみます。

  1. 説明変数:OPSの平均 被説明変数:得点平均
  2. 説明変数: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')

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')

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人居るよりも、多少打力の低い選手がいても、スター選手がいるチームの方が総合的には得点力が上がりそうだということがわかりました。

そして、おそらく年棒も後者の方が高いんだろうなと思うので、ある意味理にかなってるなという感じです。

以上です。

2
0
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
2
0