2022年のプロ野球野手データをクラスタリングしたよ。

概要

2022年度のプロ野球データから規定打席到達者の主成分分析およびクラスタリングを行ったよ。

  1. データ

  2. 主成分分析

  3. クラスタリング

の流れで紹介します。

1. データ

いつものごとくNPB公式からシーズン成績を拝借しました。


こんな感じでエクセルにまとめました。

2. 主成分分析

データを見る

上のデータを使って主成分分析行います。
まずデータのインポート

import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import sklearn
from sklearn.decomposition import PCA
import csv
import numpy as np
from sklearn.cluster import KMeans
import japanize_matplotlib
%matplotlib inline

df = pd.read_excel("./2022規程打者.xlsx")
df.head()

とりあえず変数同士の相関係数をヒートマップで見てみます。

cor = df.corr()
sns.heatmap(cor)


2022年度規定到達打者のヒートマップ

本塁打や塁打と長打率に正の相関があることや試合数と打席が相関することは当然ですね。もっと詳しくデータを見た記事も後に出したい。

実装

#データの標準化
df2 = df.iloc[:, 2:].apply(lambda x: (x - x.mean()) / x.std(), axis=0)
df2


#principle conponent
pca = PCA(n_components=10)
pca.fit(df2)

寄与率のグラフを見てどこまで使うか決めます。(Elbow plotっていうらしい)

# 寄与率
contriburion_ratio = pd.DataFrame(pca.explained_variance_ratio_)
plt.plot(contriburion_ratio)
plt.xlabel("PC")


PC4くらいまでは十分な情報含んでる。

可視化の観点からPC3まで使って三次元プロットします。

#pcaの結果をデータフレームへ
pca_result = pca.transform(df2)
pca_result = pd.DataFrame(pca_result)
pca_result.columns = ['PC1', 'PC2', 'PC3'] 
pca_result

クラスタリング

実装

データフレームができてれば一瞬ですね。方法はK-means、クラスター数は適当に3に設定します。

kmeans = KMeans(n_clusters=3).fit_predict(pca_result)
kmeans

可視化

クラスタリング結果を可視化します。
まず前処理

#可視化用のデータ
player_name = df['選手']
pca_result["name"] = player_name
pca_result["cluster"] = kmeans
pca_result.head()


こんな感じのデータフレームを作る
import matplotlib.pyplot as plt
from matplotlib.text import Annotation
from mpl_toolkits.mplot3d.axes3d import Axes3D
from PIL import Image # アニメーション用
from matplotlib import animation # アニメーション用
from io import BytesIO # アニメーション用
from mpl_toolkits.mplot3d import proj3d

# クラスの継承
class Annotation3D(Annotation):
    '''Annotate the point xyz with text s'''

    def __init__(self, s, xyz, *args, **kwargs):
        Annotation.__init__(self,s, xy=(0,0), *args, **kwargs)
        self._verts3d = xyz

    def draw(self, renderer):
        xs3d, ys3d, zs3d = self._verts3d
        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)
        self.xy=(xs,ys)
        Annotation.draw(self, renderer)

# annotate3Dを定義
def annotate3D(ax, s, *args, **kwargs):
    '''add anotation text s to to Axes3d ax'''

    tag = Annotation3D(s, *args, **kwargs)
    ax.add_artist(tag)

# plot_3Dを定義
color = ["red", "blue", "green", "orange"] # クラスタリング用の色
def plot_3D(data, angle = 10):
    # 3d plot用Figure
    fig = plt.figure(num=None, figsize=(12, 12), dpi=150) #アニメーションgifを作る際はdpiを72にする
    ax = fig.add_subplot(projection="3d")
    for i in range(len(data.index)):
        ax.scatter(data.iloc[i,0], data.iloc[i,1],data.iloc[i,2], c=color[int(data.iloc[i,4])]) # プロットの座標を指定
        annotate3D(ax, s=str(data.iloc[i,3]), xyz=(data.iloc[i,0], data.iloc[i,1],data.iloc[i,2]),
        fontsize=6,
        xytext=(-3,3),
        textcoords='offset points', ha='right',va='bottom') # annotation
    ax.view_init(30, angle)
    ax.set_xlim(data.describe().at['min', 'PC1'], data.describe().at['max', 'PC1'])
    ax.set_ylim(data.describe().at['min', 'PC2'], data.describe().at['max', 'PC2'])
    ax.set_zlim(data.describe().at['min', 'PC3'], data.describe().at['max', 'PC3'])
    ax.set_xlabel('PC1 ' + str(round(pca.explained_variance_ratio_[0]*100, 1)) + "%") # 軸に寄与率を表示
    ax.set_ylabel('PC2 ' + str(round(pca.explained_variance_ratio_[1]*100, 1)) + "%")
    ax.set_zlabel('PC3 ' + str(round(pca.explained_variance_ratio_[2]*100, 1)) + "%")
    buf = BytesIO()
    fig.savefig(buf, bbox_inches='tight', pad_inches=0.0)
    return Image.open(buf)
#二つ目の引数がangle
plot_3D(pca_result, 60)
plt.show()


綺麗に分かれてますね。

結果解釈

赤いクラスターはPC1が大きいグループ、PC1が低いがPC2が高いグループが青クラスターって感じですね。
それぞれの固有ベクトルを見てみましょう。

component = pd.DataFrame(pca.components_)

fig = plt.figure(figsize=(12, 20))
for i in range(len(component.index)):
    ax = fig.add_subplot(3,1,i+1)
    ax.bar(df.iloc[:, 2:].columns, component.iloc[i])
    ax.set_xticks(df.iloc[:, 2:].columns)
    ax.set_xticklabels(df.iloc[:, 2:].columns, rotation = 270, fontsize=10)
    ax.set_title('PC' +str(i+1) + 'の固有ベクトル', fontsize=10)
    ax.set_ylabel('ベクトルの大きさ', fontsize=10)
    #plt.xticks(rotation = '0')
    plt.tick_params(labelsize=10)
    ax.plot()

plt.show()


PC1は総合的な打力、盗塁や犠打が少ない方が高くなるあたり4番打者向きな特徴ですね。
PC2はPC1と反対に本塁打が少なく盗塁が多い方が高くなるため1番打者向き、PC3も似たような傾向ですが打率が高く、選球眼がいいような印象ですね。

まとめ

以上2022年度の打者データを用いてクラスタリングを行ってみました!
今回用いたデータをいろいろ可視化して遊んでみた記事も出したいと考えているのでそちらもよろしくお願いします。

参考にしたサイトってかほぼパクリ

【Python】Python歴3年、野球ファン歴10年の俺がプロ野球選手を主成分分析してクラスタリングする【機械学習】

https://qiita.com/yanami/items/efb6bdd2806f966ae313

この記事が気に入ったらサポートをしてみませんか?