Pythonでプロ野球個人成績の年度間相関を算出してみた

# はじめに
Pythonの学習を兼ねて、過去シーズンのプロ野球個人打撃成績ごと（打率・HR・打点・OPSなど）の年度間相関を算出します。
学習ついでに指標ごとの年度間相関を比較して、次シーズンの成績予測の有効性（指標が選手の実力を反映しているか）を考察します。

# 年度間相関とは
元々、2種類のデータ間の関連性（相関関係）の強さを示す係数＝相関係数という指標があります。
異なる年度間のデータについて相関係数を算出したものが年度間相関です。
-1～1まであり、1に近いほど相関が強く、0に近づくほど相関が弱いと言うことができます。
-1に近いと負（マイナス）の相関があるということになります。

# 分析について
今回は2012年以降で規定打席に到達したプロ野球選手の主要な個人成績の**連続する2年間の相関係数＝年度間相関**を算出します。
選手の実力を反映している成績は年度間相関が強く、逆に運に左右されやすい成績は年度間相関が低くなると予想できます。

# 前提
* Python 3.8
* プロ野球（NPB) の2012～2023年の打者の年間個人成績を使用します。
今回は既にcsvファイルに出力したものを用意しておきます。
* 成績データは「データで楽しむプロ野球」様のデータを使用しています。(https://baseballdata.jp/)

# 指標ごとの年度間相関を算出
```python
import sys

import numpy as np
from numpy.core.fromnumeric import sort


def main():
    # 成績リスト取得
    list = get_stats_csv(143*3.1)

    # 年度間相関を算出
    get_year_corr(list)


# CSVファイルから年度別個人成績を取得
def get_stats_csv(qualified=0):
    # CSVファイルヘッダー項目
    csv_header = ['year', 'team', 'name', 'num', 'age', 'avg', 'game', 'pa', 'ab', 'hit', 'dou', 'tri', 'hr', 'rbi', 'run', 'so', 'bb', 'hbp', 'sc', 'sf', 'sb', 'cs', 'sbp', 'dp', 'obp', 'slg', 'ops', 'risp', 'err', 'rab', 'rhit']
    
    list = []
    # CSVファイルを読み込み
    with open('hbp.batter_chnc.csv', 'r',encoding="utf-8_sig") as f:
        for row in csv.DictReader(f, csv_header):
            # 1行目はヘッダー
            if row['pa'] == 'pa':
                continue

            qual_pa = qualified
            # 2020年は120試合制
            if row['year'] == '2020':
                qual_pa = 120*3.1
            # 2012～2014年は144試合制
            elif row['year'] in ['2012', '2013', '2014']:
                qual_pa = 144*3.1

            # 設定した最低打席数以上の選手のみリスト追加
            if int(row['pa']) < qual_pa:
                continue

            # 特定の指標は計算
            row = cal_ad_stat(row)
            list.append(row)

    return list


# 特定の指標は計算
def cal_ad_stat(data):
    # 三振率
    data['k_par'] = int(data['so']) / int(data['pa'])

    # 四球率
    data['bb_par'] = int(data['bb']) / int(data['pa'])

    return data


# 年度間相関を算出
def get_year_corr(list):
    # 出力する指標一覧
    stat_name_list = ['avg', 'hr', 'rbi', 'obp', 'slg', 'ops', 'risp', 'k_par', 'bb_par', 'hr_par', 'dp', 'rma']

    stat_list = []
    # 指標ごとに年度間相関を計算
    for key in list[0]:
        if key not in stat_name_list:
            continue

        # 少数にできないときはスキップ
        if str(list[0][key]).isdecimal() == False:
            try:
                float(list[0][key])
            except:
                continue

        # 前年と翌年の成績リストを作成
        bef_list, aft_list = create_arr_year(list, key)
        print(len(bef_list), len(aft_list))

        # 指標ごとの年度間相関係数を算出
        correlation = np.corrcoef(bef_list, aft_list)
        stat_list.append({'key': key, 'corr': round(correlation[0, 1], 3)})

    for row in sorted(stat_list, key=lambda x: x['corr'], reverse=True):
        print(row)

    return


# 前年データと翌年データのリストを作成
def create_arr_year(list, stat):
    bef_list = []
    aft_list = []
    for i in list:
        for j in list:
            bef_name = i['name'].replace('　', '').replace(' ', '')
            aft_name = j['name'].replace('　', '').replace(' ', '')
            if bef_name == aft_name and int(i['year']) == int(j['year'])-1:
                bef_list.append(float(i[stat]))
                aft_list.append(float(j[stat]))
                continue
    
    return bef_list, aft_list


if __name__ == "__main__":
    main()

```

# 結果
|指標|年度別相関|
| ---- | ---- |
|本塁打|0.780|
|三振率|0.778|
|四球率|0.769|
|打点|0.660|
|長打率|0.656|
|OPS|0.609|
|出塁率|0.592|
|併殺|0.507|
|打率|0.347|
|得点圏打率|0.172|

# ざっくり考察
本塁打、三振率、四球率などの相手守備の影響を受けない指標は年度間相関が高くなり、打率や得点圏打率など相手守備の影響を受ける指標は年度間相関が低くなるという結果が見てとれました。
打率や得点圏打率については運の影響が大きく、選手の来シーズンの成績予想が難しいと言えそうです。
これらの結果については個人的にはイメージ通りの結果です。

面白かったのは、運の影響が大きいと言われることが多い打点について、年度間相関が高いという結果が得られたことです。
ただ、この結果については実力を反映しているというよりは、打順や他の打者の影響も受ける部分が多いのかなと思います。

# 終わりに
年度間相関の算出や考察については改善の余地がまだまだあるので、気が向いたらまた勉強がてらに触れてみたいと思います。