ジャッジ選手と村上選手のホームラン数の経過をWeb scrapingで図化した話
概要と注意事項
こんにちは。
今回はGISでもリモセンネタでもなく、Web scrapingの話です。
手法の適用分野としては様々なのですが、勉強用としてよく題材になるほど大量のデータが公開されていて、比較馴染みのある野球のデータを題材にして試行してみました。
2022年は日米ともに歴史的な勢いでホームランを量産している選手がいるので、その比較をゲーム数を揃えてやってみようというものです。
また参考までに、アメリカンリーグMVPを取った大谷選手の昨年の記録と、2001年にMLB最多ホームラン数を記録したボンズ選手の記録も比較してみようと思います。
単にデータの比較をWeb scrapingでやってみたというものであり、選手の優劣を論じるものではないのでご注意下さい。
データは日本時間2022/9/16時点のものです、以降もホームラン数は増えると思いますが、シーズン最終結果ではないのでご注意下さい。
何故野球データ?
本来であれば業務に即したデータがいいはずです。
この手のデータであれば、国内だと気象データや水文データなどになると思います。ただ何らかの集計をすると、意図しない評価を導く可能性があるので、現状では避けています。平時の過去のデータであれば問題が少ないと思われますので、今後検討します。
野球データサイトとそのデータの内容(MLB)
MLBのデータとしてはsavantのデータを使用します。
よくぞここまでデータを集めるな、そして惜しげもなく公開するな、というサイトです。さすがアメリカ。さすがopen data大国。
選手名でも、チーム名でもいいので選手を選んみます。
例えば、ニューヨーク・ヤンキースのジャッジ選手はここにあります。
一番上には安打の方向がでていますが、この1つをクリックすると、そのビデオまで出てきます。何なんでしょう、この充実ぶり。
さて多くが、集計値ではありますが、StatsのGame Logsから年別の試合ごとの集計値を見ることができるようになっています。今回はこれを使います。
ちなみに大谷選手はここにあります。
今年一番遠くまでホームランが飛んだ時の動画はこれ(16号)でしょうか。
ボンズ選手は既に引退しているのと活躍期間が前なので少しフォーマットが違うようですが、ちゃんとデータが入っています。
イチロー選手の2004年(MLB年間最多安打記録更新年)のデータもここにあります。
野球データサイトとそのデータの内容(NPB)
翻って日本国内ですが、何年か前までは毎試合別の細かいデータも網羅したサイトがあったのですが、現在は更新されていないようです。
そこでそれが載っているチームのサイトのデータを使うことにしました。東京ヤクルトスワローズではここになります。
データのフォーマットがMPBとNPBでは異なるので、そこは統一しました。
データ処理方法、使用言語、ライブラリ
データなどは、ウェブサイトを開いて、表計算ソフトにコピペで済むのですが、それだと毎日見ていないといけないのと、勉強にならないので、全てコードで済ますことにしました。
今回も使用言語にはPythonを使っています。データの図化についてもPythonを使うことにしました。
データ処理をシステム化するわけではないのと、あくまで個人の勉強でもあるので、コードは必ずしも洗練されてないかと思いますが、そこはご容赦だください。
ライブラリとしては、一般的なものの他、pandasやselenium、図化のためにmatplotlib、plotlyを使っています。
StatsのGame Logsはマウスで選択して表を選ぶような構造になっています。そのため単にurlを参照するわけではデータが取れません。
そこでseleniumを使ってChromeをプログラムから操作するようにしました。
文字ばかりになってしまったので、ここで結果の図の1つを示しておきます。静的なものです。動的な図は次の機会に紹介します。
比較はしないと言いましたが、ジャッジ選手と村上選手のペースが途中から同じになっています。年間試合数がMLBでは162、NPBでは143なので今後どうなるでしょうかね?
大谷選手の去年のペースが、オールスターまで(83試合消化)はすごかったこと、ボンズ選手の2001年の成績が40試合目くらいから、ずっとリードを保っていたことがわかります。
コード(の一部)
まず、ニューヨーク・ヤンキースのジャッジ選手の2022年の打撃成績から。
Game数と累積ホームラン数(ACCHR)は追加しています。
import numpy as np
import pandas as pd
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
#Chromeのバージョンとselenium で使うChromeのバージョンがあわないので常にインストール
driver = webdriver.Chrome(ChromeDriverManager().install())
#urlを指定してデータを入手 ここも選手コートなどで実装できるがそこは略
urlJudge='https://baseballsavant.mlb.com/savant-player/aaron-judge-592450?stats=gamelogs-r-visuals-mlb&season=2022'
driver.get(urlJudge)
#Chromeが開いてすぐにアクセスすると、うまくいかないので少し待つ
wait = WebDriverWait(driver, 5)
wait.until(EC.presence_of_all_elements_located)
#CSS_SELECTORの"#tab_gamelogs"はChromeのDeveloperツールで調べる
gamelogs=driver.find_element(By.CSS_SELECTOR,"#tab_gamelogs")
gamelogs.click()
wait.until(EC.presence_of_all_elements_located)
#表形式で取り出す
stats=driver.find_element(By.CSS_SELECTOR,'#div_gamelogs').get_attribute("outerHTML")
#ここからはpandasによるデータ整理
dfJudge = pd.read_html(stats,header=0)[0]
dfJudge=dfJudge.dropna()
dfJudge.reset_index(inplace=True,drop=True)
#ゲーム数を1からに揃え、累積ホームラン数を集計する
dfJudge["Game"] = range(1, len(dfJudge.index) + 1)
dfJudge["ACCHR"] = np.cumsum(dfJudge["HR"])
dfJudge
基本urlを変えるだけで大谷選手の2021年の成績も見られます(表示は略)。ボンズ選手の成績はurlを指定するだけでは取れませんでした。
成績年はドロップダウンで選ぶのですが、そこがurlだけでは取れないようです。それ以外は同じです。
#urlを指定
urlBonds='https://baseballsavant.mlb.com/savant-player/barry-bonds-111188?stats=gamelogs-r-hitting-mlb&season=2001'
driver.get(urlBonds)
wait = WebDriverWait(driver, 5)
wait.until(EC.presence_of_all_elements_located)
gamelogs=driver.find_element(By.CSS_SELECTOR,"#tab_gamelogs")
gamelogs.click()
wait.until(EC.presence_of_all_elements_located)
#成績年はドロップダウンリストで選ぶ2001はリストの7番目なのでoption:nth-child(7)
gamelogs_year = driver.find_element(By.CSS_SELECTOR,'#gamelogs-season-mlb > option:nth-child(7)')
wait.until(EC.presence_of_all_elements_located)
stats=driver.find_element(By.CSS_SELECTOR,'#div_gamelogs').get_attribute("outerHTML")
dfBonds = pd.read_html(stats,header=0)[0]
dfBonds=dfBonds.dropna()
dfBonds.reset_index(inplace=True,drop=True)
dfBonds["Game"] = range(1, len(dfBonds.index) + 1)
dfBonds["ACCHR"] = np.cumsum(dfBonds["HR"])
dfBonds
さて、ヤクルトの村上選手の記録の方ですが、こちらは簡単で、url指定だけでデータを取ることができます。列名が日本語なので英語に揃えるなど調整しておきます。
#村上選手のStatsのurl
urlMurakami = 'https://www.yakult-swallows.co.jp/players/detail/1700084'
dfMurakami = pd.read_html(urlMurakami,header=0)[2]
dfMurakami.columns=['Date','AVG','PA','AB','H','2B','3B','HR','RBI','R','SB'] # 列の名前を変更
dfMurakami["Game"] = range(1, len(dfMurakami.index) + 1)
dfMurakami["ACCHR"] = np.cumsum(dfMurakami["HR"])
dfMurakami
これで4つの表ができたのでGameの列をキーにして各データフレームを結合します。まだシーズン途中なので、試合数が一番多いボンズ選手(当時)のデータフレームを全部入れる形での結合にしています。
NaNになっているのは、まだゲーム数が150試合まで行っていないためです。
静的なグラフを書く
先程示した図をここで書きます。
図にはmatplotlibライブラリを使います。
import matplotlib.pyplot as plt # matplotlibライブラリの取り込み
plt.rcParams['font.family'] = 'Meiryo' # グラフで日本語を使う設定
#図の設定と表示
plt.figure(figsize=(8,6))
plt.plot(dfMLB_NPB['Game'],dfMLB_NPB['HR_Judge'],label='ジャッジ',color='Navy')
plt.plot(dfMLB_NPB['Game'],dfMLB_NPB['HR_Murakami'],label='村上',color='Lime')
plt.plot(dfMLB_NPB['Game'],dfMLB_NPB['HR_Ohtani2021'],label='大谷2021',color='Red')
plt.plot(dfMLB_NPB['Game'],dfMLB_NPB['HR_Bonds2001'],label='ボンズ2001',color='Orange')
plt.xlim(0,165)
plt.ylim(0,80)
plt.title('ジャッジと村上選手のHR数経過 22/9/16現在(大谷選手は2021年,ボンズ選手は2001年)') #title
plt.xlabel('出場試合数') # X軸のラベル
plt.ylabel('HR数') # Y軸のラベル
plt.legend(loc = 'best') # 凡例表記
plt.grid('true')
plt.savefig('HR_matplot.png')#画像で出力する場合
plt.show()
動的なグラフを書く
これでも、十分よいのですが、時間軸が入っているようなデータの表現、プレゼンなど時間が限られている場合の表現、などでは動いている方が説明がしやすくなります。今回の投稿は長くなったので次回に回しますが、動いているところをYoutubeの動画にしたので、雰囲気を味わって下さい。
動いているだけでなく、属性も並列で見ることができるようになっています。
まとめ
今回は、
データの公開サイトからデータを入手する(Web scraping)
データ加工
静的な図化
までをPythonで行う方法記載しました。
データがこのような形で公開されていることに何より感謝です。
次回は最後の動画作成のところを紹介します。
終わりに
ここまでご覧いただきありがとうございました。
今回の投稿は以上です。
いつもは北海道に本拠地を置くNPOに所属し、環境保全を主な題材としてGISやリモセンに関する仕事をしています。
コンサベーションGISコンソーシアムジャパン の活動もその1つです。
この分野の仕事や活動でなにかお困りのある方、ご相談ごとのある方など、是非コメントをお寄せいいただくか、上記WEBサイト掲載のメールアドレスまでメールをお寄せください。