Python 動くグラフ (2021 MLB American League / Home Run 争い)

前回、[pybaseball / MLBデータ分析ライブラリ](https://hide.ac/articles/-JZTuB_RS) にて、MLBの触れるデータを確認した。

今回は試しに、2021年 MLB アメリカンリーグ 本塁打争いのデータを確認してみた。

まあ、以下のようなのが作ってみたかったのです。

<iframe width="560" height="315" src="https://www.youtube.com/embed/cO40ILurYRk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>


<br>備忘録として流れを記載

----

# pybaseballライブラリを使ってMLBのStatcastデータを取得する。

<pre><code>!pip install pybaseball
from pybaseball import statcast
df = statcast(start_dt='2021-04-01', end_dt='2021-12-31')</code></pre>

Pythonを使う。pybaseballライブラリを使って、MLBのStatcastデータを取得する。

pip installコマンドを使ってpybaseballライブラリをインストール。

ライブラリがインストールされたら、statcast()関数を使って、指定した期間のStatcastデータを取得。この場合、2021年4月1日から2021年12月31日の期間のデータをリクエスト。この関数は、指定した期間のStatcastデータを含むPandasのデータフレームを返す。

注意することとして、pybaseballが提供するStatcastデータは、レギュラーシーズンのゲームのみを含む。プレーオフやオールスターゲームのデータは含まれない。

3月のデータがあったのでオープン戦の結果は入ってそう。

---


# 大谷のホームラン数で確認

<pre><code>player_hr = df[(df['batter'] == 660271) & (df['events'] == 'home_run')]
print(len(player_hr))</code></pre>

このコードでは、Pandasデータフレームdfから打者IDが660271で、イベントがホームランのデータを抽出している。それらのデータを含む新しいデータフレームplayer_hrが作成される。

>ID 660271 は大谷です<br>[https://baseballsavant.mlb.com/savant-player/shohei-ohtani-660271](https://baseballsavant.mlb.com/savant-player/shohei-ohtani-660271)<br>URLにIDが載ってます

その後、len()関数を使ってplayer_hrデータフレームの行数（すなわち、ホームランの発生した試合数）をカウントしている。その結果をprint()関数を使って出力。

結果は、46
>2021年 大谷のホームラン数と一致。OK<br>![](https://pbs.twimg.com/media/Fl65Vu-aMAAsDAJ?format=png&name=900x900)

------

# 大谷のホームランをグラフ化

<pre><code>player_hr = df[(df['batter'] == 660271) & (df['events'] == 'home_run')]
player_hr_by_date = player_hr.groupby('game_date').size()</code></pre>

このコードでは、まずPandasデータフレームdfから、打者IDが660271で、イベントがホームランのデータを抽出。それらのデータを含む新しいデータフレームplayer_hrが作成される。

次に、groupby()メソッドを使って、player_hrデータフレームをゲーム日ごとにグループ化。size()関数を使って、各ゲーム日ごとの行数（すなわち、ホームランの発生した試合数）をカウント。その結果を、新しいデータフレームplayer_hr_by_dateに格納。

これで、ゲーム日ごとにホームランの発生した試合数が、新しいデータフレームplayer_hr_by_dateに格納される。

<pre><code>import matplotlib.pyplot as plt

plt.plot(player_hr_by_date.cumsum())
plt.show()
</code></pre>

このコードでは、Matplotlibライブラリを使って、データフレームplayer_hr_by_dateのグラフを描画。

plot()関数を使ってデータフレームのデータを折れ線グラフとして描画。また、cumsum()関数を使って、各ゲーム日ごとのホームランの発生した試合数を累積和にしている。

最後に、show()関数を使って、グラフを表示。

このコードを実行すると、指定した期間内で、各ゲーム日ごとにホームランを打った試合数を累積和としたグラフが描画される。

![](https://pbs.twimg.com/media/Fl68MbnaMAAC_xl?format=png&name=small)

---

# 選手を追加

2021年ホームラン数Top8に入った選手のデータを追加してみました。

| ID     | Player            |     
| ------ | ----------------- | 
| 660271 | Shohei Ohtani     |    
| 665489 | Vladimir Guerrero |    
| 521692 | Salvador Pérez    |    
| 543760 | Marcus Semien     |     
| 571745 | Mitch Haniger     |      
| 592450 | Aaron Judge       |     
| 664040 | Brandon Lowe      |     
| 621566 | Matt Olson        |     

<pre><code># 選手のID　
player_ids = [660271, 665489, 521692, 543760, 571745, 592450, 664040, 621566]

# 選手ごとのホームラン数を日付ごとに集計　
player_hrs = {}
for player_id in player_ids:
  player_hr = df[(df['batter'] == player_id) & (df['events'] == 'home_run')]
  player_hrs[player_id] = player_hr.groupby('game_date').size()
</code></pre>

このコードでは複数の選手のIDを含むリストplayer_idsを定義している。次にこのリストを使って選手ごとにホームランの発生した試合数を日付ごとに集計。

for文を使ってplayer_idsリストを一つずつ取り出している。取り出した選手IDを使って、Pandasデータフレームdfから打者ID、イベントがホームランのデータを抽出。それらのデータを含む新しいデータフレームを作成。

次に、groupby()メソッドを使ってこのデータフレームをゲーム日ごとにグループ化。そしてsize()関数を使って各ゲーム日ごとの行数（すなわち、ホームランの発生した試合数）をカウント。その結果を辞書player_hrsのキーが選手のID、値がゲーム日ごとのホームランの発生した試合数を持つSeriresオブジェクトとして格納。

実行すると辞書player_hrsには、各選手のゲーム日ごとのホームランの発生した試合数がSeriresオブジェクトとして格納。

<pre><code>import matplotlib.pyplot as plt

# プロットを重ねるため、プロットを初期化　
plt.clf()

# 選手ごとにプロット　
for player_id, player_hr in player_hrs.items():
  plt.plot(player_hr.cumsum())

plt.show()</code></pre>

このコードでは、Matplotlibライブラリを使って、複数の選手のゲーム日ごとのホームランの発生した試合数をプロットしている。

まずclf()関数を使ってプロットを初期化。これにより前回のプロットが重なることを防ぐ。

次に、for文を使って辞書player_hrsから選手のIDとその選手のゲーム日ごとのホームランの発生した試合数を取り出している。そして、plot()関数を使ってこのデータを折れ線グラフとして描画している。またcumsum()関数を使って各ゲーム日ごとのホームランの発生した試合数を累積和にして描画している。

最後にshow()関数を使ってプロットを表示。

このコードを実行すると、複数の選手のゲーム日ごとにホームランを打った試合数を累積和としたグラフが重ねて描画される。

![](https://pbs.twimg.com/media/Fl7AMwPaYAAXFmo?format=png&name=small)

グラフにレジェンドを追加するのは、以下です<br>
plt.legend()

---------

# 累積値のデータを作成

csvファイルは試しに実行しただけ。

<pre><code>import pandas as pd

# データフレームを作成
df_hrs = pd.DataFrame(player_hrs)

# df_hrs データフレームを累積値にする
df_hrs_cumsum = df_hrs.cumsum()

# CSV ファイルに保存
df_hrs_cumsum.to_csv('player_hrs_cumsum.csv', index=True)

</code></pre>

このコードでは辞書player_hrsからPandasデータフレームを作成。辞書のキーがカラム名、値がデータとして、新しいデータフレームdf_hrsを作成。

次に、データフレームdf_hrsを累積値にして、新しいデータフレームdf_hrs_cumsumを作成。

最後にデータフレームdf_hrs_cumsumをCSVファイルplayer_hrs_cumsum.csvとして保存。引数index=Trueを指定することで、データフレームのインデックスも保存される。

このコードを実行すると、カラム名が選手のID、行名がゲーム日を表すインデックスを持つ、CSVファイルが作成される。各カラムには、その選手のゲーム日ごとにホームランを打った試合数を累積値として保存される。


<pre><code>import pandas as pd

# CSV ファイルを読み込む
df = pd.read_csv('player_hrs_cumsum.csv')

# データフレームを表示する
df

</code></pre>

このコードでは、Pandasのread_csv()関数を使ってCSVファイルplayer_hrs_cumsum.csvを読み込んでいる。この関数はCSVファイルをPandasデータフレームとして読み込むことができる。

読み込んだデータフレームを行名がゲーム日を表すインデックスを持つ、カラム名が選手のIDを持つデータフレームとして、変数dfに格納。

最後に、変数dfを表示している。これにより、カラム名が選手のID、行名がゲーム日を表すインデックスを持つ、CSVファイルを読み込んだデータフレームが表示される。

![](https://pbs.twimg.com/media/Fl7Ef9paYAUE1KV?format=png&name=small)

--------

# NaNを0へ

<pre><code>df = df.fillna(0)

# データフレームを表示する 
df</code></pre>

このコードでは、Pandasのfillna()メソッドを使ってデータフレームdfのNaN（Not a Number）を指定した値で埋めている。

fillna()メソッドは、データフレームのNaNを任意の値で埋めることができるメソッド。引数に埋める値を指定できる。

このコードでは、fillna()メソッドを使ってデータフレームdfのNaNを0で埋めている。これによりデータフレームdf内に含まれるすべてのNaNが0で埋められる。

このコードを実行すると、データフレームdfのすべてのNaNが0で埋められる。

![](https://pbs.twimg.com/media/Fl7FtbQaYAECbtN?format=png&name=small)

-------

# game_dateカラムを日付型に変換

する意味なかったかも、むしろ不要

<pre><code>df["game_date"] = pd.to_datetime(df["game_date"])
df.set_index("game_date", inplace=True)
</code></pre>

このコードでは、Pandasのto_datetime()関数を使ってデータフレームdfのgame_dateカラムを日付型に変換している。

to_datetime()関数は、日付文字列やタイムスタンプを日付型のデータに変換することができる関数。引数に変換したい日付文字列やタイムスタンプを指定する。

このコードでは、to_datetime()関数を使ってデータフレームdfのgame_dateカラムを日付型に変換している。これによりgame_dateカラムは日付型のデータになる。

次に、Pandasのset_index()メソッドを使ってデータフレームdfのインデックスをgame_dateカラムに設定している。引数inplace=Trueを指定することでデータフレームを直接書き換えることができる。

このコードを実行するとデータフレームdfのgame_dateカラムが日付型に変換され、データフレームdfのインデックスがgame_dateカラムに設定される。

これにより、データフレームdfはゲーム日を表す日付型のインデックスを持つカラム名が選手のIDを持つデータフレームになる。

-----

# 前日の値を引き継ぐようにした

4/1からでも良かったけど。過剰に行った

<pre><code>df_after_3_15 = df[df.index >= "2021-03-15"]

# 各列を取得する　
columns = df_after_3_15.columns

# 各列ごとに処理を行う　
for column in columns:
    values = df_after_3_15[column]
    for i in range(1, len(values)):
        if values[i] == 0:
            values[i] = values[i - 1]

# 変更したデータを、元のデータフレームに戻す　
df_after_3_15[column] = values
</code></pre>

このコードでは、データフレームdfから2021年3月15日以降のデータを取り出して新しいデータフレームdf_after_3_15を作成している。

次に、データフレームdf_after_3_15の各カラムを取り出しそれぞれに対して処理を行っている。

処理は、各カラムのデータを取り出しそれを1つずつ繰り返し処理している。1番目のデータは何もしないが、2番目以降のデータは前のデータと同じ値に更新している。

これにより、2番目以降のデータが前のデータと同じ値に更新される。

最後に、変更したデータを元のデータフレームdf_after_3_15に戻している。

このコードを実行するとデータフレームdf_after_3_15の各カラムの2番目以降のデータが前のデータと同じ値に更新される。

<pre><code>print(df_after_3_15)
</code></pre>

![](https://pbs.twimg.com/media/Fl7I-tNaAAM4E9O?format=png&name=small)


-----
# カラム名を変更 

<pre><code>print(df_after_3_15.columns)
</code></pre>

>Index(['660271', '665489', '521692', '543760', '571745', '592450', '664040',
       '621566'],
      dtype='object')

print(df_after_3_15.columns)で、現在のカラム名を表示した

<pre><code>df_after_3_15.columns =  ['Shohei Ohtani', 'Vladimir Guerrero Jr.', 'Salvador Pérez', 'Marcus Semien', 'Mitch Haniger', 'Aaron Judge', 'Brandon Lowe', 'Matt Olson']
</code></pre>

df_after_3_15.columnsに新しいカラム名のリストを代入することでカラム名を変更

<pre><code>print(df_after_3_15.columns)
</code></pre>

>Index(['Shohei Ohtani', 'Vladimir Guerrero Jr.', 'Salvador Pérez',
       'Marcus Semien', 'Mitch Haniger', 'Aaron Judge', 'Brandon Lowe',
       'Matt Olson'],
      dtype='object')

      

これでOK

----------
# Bar Chart Race 作成

<pre><code>!pip install bar_chart_race

import pandas as pd
import bar_chart_race as bcr

bcr.bar_chart_race(df=df_after_3_15, n_bars=8)
</code></pre>

<iframe width="560" height="315" src="https://dwqte-viaaa-aaaai-qaufq-cai.raw.ic0.app/embed/2224-3949" title="DSocial video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>




---
# 追加確認

## 4/1に0追加

<pre><code>import numpy as np
import pandas as pd

# 全選手に0というデータを追加する　
new_row = np.zeros(8)

# データフレームに追加する　
df_after_3_15.loc['2021-04-01'] = new_row

# インデックスを日付に変換する　
df_after_3_15.index = pd.to_datetime(df_after_3_15.index)

# データフレームを昇順に並べ替える　
df_after_3_15.sort_index(inplace=True)

# 追加されたことを確認する　
print(df_after_3_15)

</code></pre>

![](https://pbs.twimg.com/media/Fl7NE9-aMAMH7p1?format=png&name=small)

このコードは、すべての値が0に設定された新しい行をdf_after_3_15データフレームに追加する。新しい行はインデックス'2021-04-01'に追加される。

次に、pandasライブラリのto_datetime関数を使用して、データフレームのインデックスを日付型オブジェクトに変換。

最後に、sort_index関数を使用してデータフレームをインデックスの昇順で並び替え。inplaceパラメータがTrueに設定されているため、データフレームはインプレースで並び替えられ、元のデータフレームが修正される。

このコードを実行すると、df_after_3_15データフレームにはインデックス'2021-04-01'に新しい行が含まれ、インデックスの昇順で並び替えられる。

---------

## グラフ

<pre><code>import matplotlib.pyplot as plt

# 折れ線グラフを作成する　
plt.plot(df_after_3_15)

# グラフにレジェンドを追加する　
plt.legend(df_after_3_15.columns)

# グラフを表示する　
plt.show()
</code></pre>

![](https://pbs.twimg.com/media/Fl7ODmqaYAAlRQ0?format=png&name=small)

このコードは、matplotlib.pyplotライブラリのplot関数を使用して、df_after_3_15データフレームのデータを折れ線グラフで表示。

その後、legend関数を使用してデータフレームの列名をラベルとしてプロットに凡例を追加する。

最後に、show関数を使用してプロットを表示。

このコードを実行すると、df_after_3_15データフレームのデータを示す折れ線グラフと列名を示す凡例が表示される。

---------

データ追加後、Bar Chat Race 反応しなかった。