LoginSignup
0
0

k-means法を用いた野球データの可視化

Posted at

はじめに

対象読者
・AIについて学習をはじめた方
・機械学習の手法について学んでいる方
・k-means法を試してみたい方

動作環境

・Windows10
・CPU:CORE i7
・GPU:RTX3060ti
・メモリ:32GB
・Python:3.8.10

背景

この記事を作成するきっかけは、AIに興味をもち、G検定ジェネラリストの資格勉強をはじめたことでした。勉強をはじめた頃は、集中力もなく長続きしなそうだったので、趣味(野球)を題材としてAIの手法を試すことにより楽しく学習できるのではないかと思い、実装を試すことに至りました。
私自身AIについて初心者、また野球データを取り扱うのは初めてですが、この記事をきっかけに趣味とAIが簡単に結び付けられると思っていただければ幸いです。

試したこと

MLBでのチームごとのバッターとピッチャーのデータを用いて、チームの特徴を視覚的にわかりやすくしてみました。手法としてk-means法を用いました。
手順としては以下の流れで行いました。
①データの準備
②データの読み込み
③データの標準化
④k-means法の実行

①データの準備

以下のサイトから2023年のチームごとのバッターのデータとして"OPS"をピッチャーのデータとして"WHIP"を用いました。
https://www.baseball-reference.com/leagues/majors/2023.shtml

ちなみにOPSとは、出塁率と長打率を足し合わせたものです。高ければ良い指標です。
WHIPとは、ピッチャーが与四球と被安打をどれだけ許したかを示す指標です。低ければ良い指標です。
野球が好きな方は、どちらも見かける機会が増えたかと思います。
ここでは、OPSが高いと良い指標、WHIPが低いと良い指標と覚えていただければ大丈夫です。

②データの読み込み

上記のサイトからShare&Exportをクリックして、エクセルファイルをダウンロードしました。
エクセルダウンロード.PNG

そこから、列を抽出してリスト化しました。

mlb_batting_and_pitching.py
#データの読み込み
input_book = pd.ExcelFile('sportsref_download.xlsx')
input_sheet_name = input_book.sheet_names
batting_data = input_book.parse(input_sheet_name[0])
pitching_data = input_book.parse(input_sheet_name[1])

#打撃指標:ops
ops=batting_data.iloc[0:30,20]
#投手指標:whip
whip=pitching_data.iloc[0:30,29]

df=pd.DataFrame([ops,whip])
dfT=df.T

#散布図
plt.xlabel('ops')
plt.ylabel('whip')
plt.title('')
plt.scatter(dfT["OPS"], dfT["WHIP"])
plt.show()

リスト化してそのままグラフ化したものが以下の画像となります。
横軸がOPS、縦軸がWHIPとなっています。

どうでしょうか。このグラフは、横軸と縦軸の使っている単位も違い、ぱっと見でチームの特徴をつかむのは難しいかと思います。

ここから、k-means法を用いることでクラスタリングさせグラフに色付けをすることで見やすいグラフにしていきましょう!

とその前に、k-means法のロジックの中で点と点の距離を計算する過程があります。なので、標準化をしてOPSとWHIPのスケールを統一してk-means法を適用できる準備をしていきましょう!

ops_whip.png

③データの標準化

データの標準化をしたコードが以下のものとなります。
scikit-learnのStandardScalerを用いて標準化しました。

mlb_batting_and_pitching.py
#標準化
sc = StandardScaler()
dfT_sc = sc.fit_transform(dfT)
dfT_sc = pd.DataFrame(dfT_sc, columns=dfT.columns)

#標準化した散布図
plt.xlabel('ops')
plt.ylabel('whip')
plt.title('')
plt.scatter(dfT_sc["OPS"], dfT_sc["WHIP"])
plt.show()'

以下の画像が標準化したグラフです。
先ほどのグラフと少し違い、OPSとWHIPのスケールが統一されたものとなります。これで下準備が整ったので、次ステップでk-means法を実行していきます。

ops_whip_before.png

④k-means法の実行

k-means法を実行したコードが以下のものとなります。
標準化と同様に、scikit-learnのKmeansを用いてk-means法を実行しています。引数のクラスター数に3を指定しています。グラフを描写する箇所では、クラスタリングされた結果をもとに色付けを行っています。

mlb_batting_and_pitching.py
#k-means法の実行
km = KMeans(n_clusters=3, max_iter=30)
kmh = km.fit_predict(dfT_sc[["OPS", "WHIP"]])
dfT_sc['kmh'] = kmh
dfT_sc.head()

#k-means法を用いた散布図
plt.xlabel("OPS")
plt.ylabel("WHIP")
plt.title('')
for i in np.sort(dfT_sc['kmh'].unique()):
  plt.scatter(dfT_sc[dfT_sc['kmh']==i]["OPS"], dfT_sc[dfT_sc['kmh']==i]["WHIP"], label=f'cluster{i}')
plt.legend()
plt.show()

k-means法を実行したグラフが以下の画像です。
色分けをすることで、ぱっと見でチームの特徴がわかりやすくなったのではないでしょうか。

私は、以下のように考察できました!
緑のグループ:バッター、ピッチャー共に強いチーム
橙のグループ:ピッチャーは強いけどバッターにもうちょっと頑張ってほしいチーム
青のグループ:バッター、ピッチャーともにもうちょっと頑張ってほしいチーム

実際に、緑のグループの端にあるブレーブスとドジャースは共に地区1位となっており強い要因が明確ですね!ドジャースには大谷選手、山本選手が入団したこともあり注目度大ですね。
一方で、青のグループの端にあるアスレチックスとロッキーズは共に地区最下位となっておりまだまだ課題がありそうです。。。頑張ってほしい!

今回は、クラスター数を3つに定めて上記のような考察をしてみました。クラスター数やデータを変えることで違った野球の見方ができるかもしれません!

whip_ops_kmeans_add_team.PNG

まとめ

k-means法を用いてラベリングをすることで視覚的にデータの特徴をつかみやすくなることがわかりました。
この記事を読んで身近なデータや趣味のデータを分析するきっかけになっていただけると幸いです!
最後まで、目を通していただきありがとうございました:pray:

参考文献

・野球データ
https://www.baseball-reference.com/leagues/majors/2023.shtml
・mlb公式サイト
https://www.mlb.com/standings/2023
・k-means法のコード
https://www.tech-teacher.jp/blog/clustering-python/
https://smart-hint.com/ml/k-means/

おまけ

コードをまとめたものを以下に載せておきます。

mlb_batting_and_pitching.py
from matplotlib import pyplot as plt
import numpy as np
import pandas as pd
from sklearn.cluster import KMeans
from sklearn.discriminant_analysis import StandardScaler

#データの読み込み
input_book = pd.ExcelFile('sportsref_download.xlsx')
input_sheet_name = input_book.sheet_names
batting_data = input_book.parse(input_sheet_name[0])
pitching_data = input_book.parse(input_sheet_name[1])

#打撃指標:ops
ops=batting_data.iloc[0:30,20]
#投手指標:whip
whip=pitching_data.iloc[0:30,29]

df=pd.DataFrame([ops,whip])
dfT=df.T

#散布図
plt.xlabel('ops')
plt.ylabel('whip')
plt.title('')
plt.scatter(dfT["OPS"], dfT["WHIP"])
plt.show()

#標準化
sc = StandardScaler()
dfT_sc = sc.fit_transform(dfT)
dfT_sc = pd.DataFrame(dfT_sc, columns=dfT.columns)

#標準化した散布図
plt.xlabel('ops')
plt.ylabel('whip')
plt.title('')
plt.scatter(dfT_sc["OPS"], dfT_sc["WHIP"])
plt.show()

#k-means法の実行
km = KMeans(n_clusters=3, max_iter=30)
kmh = km.fit_predict(dfT_sc[["OPS", "WHIP"]])
dfT_sc['kmh'] = kmh
dfT_sc.head()

#k-means法を用いた散布図
plt.xlabel("OPS")
plt.ylabel("WHIP")
plt.title('')
for i in np.sort(dfT_sc['kmh'].unique()):
   plt.scatter(dfT_sc[dfT_sc['kmh']==i]["OPS"], dfT_sc[dfT_sc['kmh']==i]["WHIP"], label=f'cluster{i}')
plt.legend()
plt.show()
0
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
0
0