Jupyterとseaborn、pitchpxを使ったMLB野球データの可視化
前回の記事(といっても4ヶ月近く経っちゃいましたがw)の続編かつ、BPStudy#103「Baseball Play Study 2016」の技術的な補足資料となります.
野球Hack!(野球プログラミング)2016年の新作となります.
【はじめに】ライセンスについて
絶対質問が来る&コメントが入るので先に書いちゃいます.
- 使用ライブラリ(Jupyter,seabornなど)のライセンスは各ライブラリのページもしくはPyPIのページで確認ができます.
- 今回使用する野球データ(MLBAM)のライセンスは、 個人利用程度であれば問題ありません!!!
- ただし、商用利用、大量に取得&スクレイピングはNG
- この辺の解説は以下のリンクを参照ください&認識齟齬があればコメントを.
- http://gd2.mlb.com/components/copyright.txt ※データのコピーライトです
- MLBの野球データのライセンスについて(MLBAM) ※日本語訳とその解釈
岩隈久志のノーヒットノーランを投球から探ってみる
ドジャースと大型契約間近にまさかの破談→マリナーズに出戻りした岩隈さんですが、昨年の8/12(日本時間8/13)に日米通じて初のノーヒットノーランをキメました.
ノーヒットノーランを決めた試合は他の試合とどう違うのか?が少し気になったので可視化してみることにしました.
前提条件
- ノーヒットノーランを決めた8/12先発試合(9回完投無安打無得点)
- ノーヒットノーランを決める前の試合(8/7、7回3失点、勝利投手)
- ノーヒットノーラン後の試合(8/18、7回2失点、勝利投手)
これらの投球データを、
- 球速
- ボールの回転数
- 変化球の種類
- 投球の結果(ストライク/ボール/ボールインプレイ)
毎に可視化し、違いがないか見てみることします.
なお、
- 対戦チーム・打者との相性
- 球場(ホームかアウェイか)
- 捕手の差
については可視化の条件から外しています(というより間に合わなかった)
使用データ
メジャーリーグ公式の一球速報データを利用します.
具体的には、MLBAM(MLB Advanced Media)が配信している一球速報データを活用します.
これは野球データ分析の(マニアックな)書籍、「Analyzing Baseball Data With R」でも紹介されています.
データを手に入れる&可視化する
用意するモノ
PyDataでお馴染みのライブラリに加え、MLBAMの一球速報データを取得するライブラリ、「pitchpx」を使います.
- PC(筆者はMacbook Pro 13inch Retinaを使用)
- Python 3.3.x以上が動く環境、推奨は3.5以上
- pyenv + virtualenvで環境構築がオススメ.
- Pythonライブラリ(いずれもpipで入るversionでOK)
- Jupyter notebook
- pandas
- matplotlib
- seaborn
- pitchpx
PyData環境
Pythonのデータ分析・可視化でお馴染みのライブラリを使います.
この辺の詳細については世の中に沢山ノウハウや本が出ているので、そちらを見ていただけると幸いです.
pitchpx
MLBAMの一球速報データ、試合の結果、打席情報、審判の一覧など、ありとあらゆる野球のデータセット(CSV)を得るためのライブラリでコマンドラインで利用可能です.
このライブラリ自体は、 筆者(@shinyorke )自身が必要に駆られて開発、PyPIで公開しています!!!
使い方は後述します.
事前準備
Python 3.3.x以上が動く環境がある前提で解説します.
pipコマンドで必要なライブラリのインストールを行ってください.
$ pip install jupyter matplotlib pandas seaborn pitchpx
特にエラーがなく終了していれば成功です.
可視化してみる
データの取得
pitchpxのヘルプコマンドで使い方を確認します.
$ pitchpx --help
多分こんな感じで出てくると思います.
Options:
-s, --start TEXT Start Day(YYYYMMDD) [required]
-e, --end TEXT End Day(YYYYMMDD) [required]
-o, --out TEXT Output directory(default:".") [required]
--help Show this message and exit.
平たく言えば、日付と出力先を指定してもらえればOKです.
というわけで、CURRENT Directoryの下にdataというDirectoryを作った上でデータを取得します.
$ pitchpx -s 20150807 -e 20150807 -o ./data
$ pitchpx -s 20150812 -e 20150812 -o ./data
$ pitchpx -s 20150818 -e 20150818 -o ./data
なお、
$ pitchpx -s 20150807 -e 20150818 -o ./data
でもデータを取れますが、大量のデータが取れて後で大変になるので避けたほうが賢明です.
※この辺は直したい...
これで./data下にcsvが吐かれているハズです.
Jupyter notebook起動&初期処理
可視化と分析はJupyter notebookを使います.
$ Jupyter notebook
これでブラウザが立ち上がるので、適当にnotebookを作成します.
と同時に必要なライブラリをimportしましょう.
%matplotlib inline
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
from collections import OrderedDict
pandasでデータを読み込む
csvファイル名を指定してDataframeを作ります.
投球データ(mlbam_pitch_{yyyymmdd}.csv)を読み取った後、queryで岩隈が投げているデータのみ取得します.
# ノーヒットノーランを決めた日(8/12)前後の岩隈の試合データを取得する
iwakuma_pitch_data= OrderedDict()
for day in (20150807, 20150812, 20150818):
pitch= pd.read_csv('./data/mlbam_pitch_{day}.csv'.format(day=day))
iwakuma_pitch_data[day] = pitch.query('pit_box_name=="Iwakuma"')
球速×球種毎に結果を可視化
球速・球種毎の投球結果(ストライク・ボール・ボールインプレイ)を可視化します.
ちなみに「ボールインプレイ」はフィールドにボールが飛んだことを表す結果です(フライ、ゴロ、ヒットなど、ルール上のボールインプレイとは少し違います).
"""
球種と球種の略称(この後も使います)
{
'CH': 'Change-up',
'CU': 'Curveball',
'EP': 'Ephuus',
'FA': 'Fastball',
'FC': 'Cut Fastball',
'FF': 'four-seam Fastball',
'FO': 'Forkball',
'FS': 'Split-finger Fastball',
'FT': 'two-seam Fastball',
'KC': 'Knuckle Curve',
'KN': 'Knuckleball',
'SC': 'Screwball',
'SI': 'Sinker',
'SL': 'Slider',
'UN': 'Unknown'
}
"""
# X:球種, Y:球速(初速)毎に投球結果(S:Strike, B:Ball, X:Ball in play)を可視化
axarray = {day: None for day in iwakuma_pitch_data.keys()}
f, (axarray) = plt.subplots(1, len(iwakuma_pitch_data.keys()), sharey=True, figsize=(24,6))
for i, day in enumerate(iwakuma_pitch_data.keys()):
axarray[i].set_title('Hisashi Iwakuma({day})'.format(day=day))
sns.stripplot(x="pitch_type", y="start_speed", data=iwakuma_pitch_data[day], hue="pitch_res", ax=axarray[i], split=True, jitter=True)
こんな結果が得られるはずです.
左から順に、
- ノーヒットノーラン前の試合(8/7)
- ノーヒットノーラン(8/12)
- ノーヒットノーラン後の試合(8/18)
です.
ゴロを打たせる名手、通称「ゴロくま」さんらしく、ストライク(S)とインプレイ(X)が大変目立っています.
が、ノーノーの日は少しばかりボール(B)が多かったみたいです.
なお、ノーノー後の試合だけXとBの色が逆転しているのは謎です.
投球回毎の球速・ボール回転数の平均
次は投球回毎の球速(今回は初速データを使用)・ボール回転数の平均と推移を見てみます.
まずは球速の平均から.
# X:投球回, Y:初速の平均の遷移を球種毎に可視化
axarray = {day: None for day in iwakuma_pitch_data.keys()}
f, (axarray) = plt.subplots(1, len(iwakuma_pitch_data.keys()), sharey=True, figsize=(24,6))
for i, day in enumerate(iwakuma_pitch_data.keys()):
axarray[i].set_title('Hisashi Iwakuma({day})'.format(day=day))
sns.pointplot(x="inning_number", y="start_speed", hue="pitch_type", ax=axarray[i], data=iwakuma_pitch_data[day], dodge=True, palette="Set1")
結果はこんな感じに.
ノーノーの日はそれなりの球速が出ていますが、ノーノーの前後とくにノーノー後は多少球速が落ちている感じがします.
続いてボールの回転数も同じように見てみます.
コード上はy軸が変わるだけです.
# X:投球回, Y:ボール回転数(Spin Rate)の平均の遷移を球種毎に可視化
axarray = {day: None for day in iwakuma_pitch_data.keys()}
f, (axarray) = plt.subplots(1, len(iwakuma_pitch_data.keys()), sharey=True, figsize=(24,6))
for i, day in enumerate(iwakuma_pitch_data.keys()):
axarray[i].set_title('Hisashi Iwakuma({day})'.format(day=day))
sns.pointplot(x="inning_number", y="spin_rate", hue="pitch_type", ax=axarray[i], data=iwakuma_pitch_data[day], dodge=True, palette="Set2")
結果はこちら.
球種毎に回転数の違いがある(まっすぐ系は回転する、スライダー・チェンジアップは回転しない)以外、結果にばらつきがありますね.
回転数そのものを見る意味はあまり無いのかも(この辺りはちょっとわかんない).
野村スコープ風に可視化してみる
最後に、ボールを投げた位置を元に、野村スコープ風の可視化をしてみます.
まず、matplotlibのplotにmarker/label/colorをつけるため定義用のクラスをちょろっと書き込みます.
# -*- coding: utf-8 -*-
class PitchPxConst(object):
# 球種はこのリンクを参照 http://pitchfx.texasleaguers.com/
PITCH_TYPES = {
'FA': 'Fastball',
'FF': 'four-seam Fastball',
'FT': 'two-seam Fastball',
'FC': 'Cut Fastball',
'FS': 'Split-finger Fastball',
'FO': 'Forkball',
'SI': 'Sinker',
'SL': 'Slider',
'CU': 'Curveball',
'KC': 'Knuckle Curve',
'EP': 'Ephuus',
'CH': 'Change-up',
'SC': 'Screwball',
'KN': 'Knuckleball',
'UN': 'Unknown',
}
# markers(matplotlib)
PITCH_TYPES_MARKERS = {
'FA': '.',
'FF': 'o',
'FT': ',',
'FC': 'p',
'FS': 'v',
'FO': 'v',
'SI': '>',
'SL': '<',
'CU': '+',
'KC': ',',
'EP': 'D',
'CH': 'o',
'SC': 'H',
'KN': '*',
'UN': 'x',
}
# colors(matplotlib)
PITCH_TYPES_COLORS = {
'FA': 'red',
'FF': 'red',
'FT': 'red',
'FC': 'red',
'FS': 'red',
'FO': 'blue',
'SI': 'red',
'SL': 'blue',
'CU': 'green',
'KC': 'yellow',
'EP': 'green',
'CH': 'green',
'SC': 'yellow',
'KN': 'white',
'UN': 'black',
}
このクラスを読み込んで以下のコードを実行します.
from pitchpx.const import PitchPxConst
# 一試合の投球を可視化(pz:高さ, px:幅)
axarray = {day: None for day in iwakuma_pitch_data.keys()}
f, (axarray) = plt.subplots(1, len(iwakuma_pitch_data.keys()), sharey=True, figsize=(24,6))
# TODO ストライクゾーンを描きたかったが断念(誰かおしえて)
for i, day in enumerate(iwakuma_pitch_data.keys()):
axarray[i].set_title('Hisashi Iwakuma({day})'.format(day=day))
datasets = iwakuma_pitch_data[day]
for k, v in PitchPxConst.PITCH_TYPES.items():
ds = datasets.query('pitch_type=="{type}"'.format(type=k))
if ds.empty:
continue
ds.plot(
kind='scatter',
x='px',
y='pz',
label=PitchPxConst.PITCH_TYPES[k],
color=PitchPxConst.PITCH_TYPES_COLORS[k],
marker=PitchPxConst.PITCH_TYPES_MARKERS[k],
xlim=(-3.0, 3.0),
ylim=(-1.0, 6.0),
ax=axarray[i]
)
結果はこちらです.
うーん...これは対戦打者毎に可視化&ストライクゾーンが無いと見難いですね.
結論
- ノーノーの日は球速が出ていた、他の日と傾向が少し違う
- 一方、ボールが多めでやや荒れ気味だった
- でも明確な違いが見当たらない
- 対戦打者やチームも見ないとわからない
- ホームとアウェイの差は?
- matplotlibの使い方をようやっと覚えてきた
- pandasとseaborn素晴らしい!
- seabornあれば綺麗でカッコイイ可視化ができるんだ(^O^)