0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

WBCメキシコ戦、サンドバル投手を調べる

Last updated at Posted at 2023-03-20

メキシコ戦、サンドバル投手を調べる

ここ見れば一発だけど、一応python勉強がてら。
https://baseballsavant.mlb.com/savant-player/patrick-sandoval-663776?stats=statcast-r-pitching-mlb


データ取得 / 2021年データ

!pip install pybaseball
from pybaseball import statcast
df = statcast(start_dt='2022-03-30', end_dt='2022-12-31')
df_663776 = df[df['pitcher'] == 663776]


球種

# df_663776のpitch_typeカラムに含まれるユニークな球種を確認する
unique_pitch_types = df_663776['pitch_type'].unique()

# 確認した球種を表示する
print(unique_pitch_types)

結果、

['CH' 'SI' 'FF' 'SL' 'CU' nan]

表示された球種は以下のようになります:

  • CH:チェンジアップ(Changeup) - 速球よりも遅い速度で投げられ、バッターのタイミングを狂わせる目的がある。
  • SI:シンカー(Sinker) - 速球の一種で、ボールが下に落ちる動きがあります。グラウンドボールを誘発することが目的。
  • FF:フォーシーム・ファストボール(Four-seam Fastball) - 最も一般的な速球で、ボールが高速で直線的に飛びます。
  • SL:スライダー(Slider) - 水平方向に大きく曲がる変化球で、速球と同じような投げ方をするが、速度が遅く、横方向にスライドするような動きがあります。
  • CU:カーブ(Curveball) - 上から下に大きく落ちる変化球で、バッターのタイミングを狂わせる効果があります。
  • nanは、その球種が不明または記録されていないことを示しています。

投球コース

import matplotlib.pyplot as plt

# データを pitch_type ごとにグループ分けする
grouped = df_663776.groupby('pitch_type')

# pitch_type ごとに、'plate_x' を X 軸、'plate_z' を Y 轴とした散布図を作成する
for pitch_type, data in grouped:
    plt.scatter(data['plate_x'], data['plate_z'], label=pitch_type)

# 凡例を表示する
# plt.legend()

plt.xlim(-3, 3)
plt.ylim(-2, 6)

plt.xlabel('Plate X')
plt.ylabel('Plate Z')
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0)

# 罫線
plt.grid(which='both', linestyle='--', color='gray', alpha=0.5)



# グラフを表示する
plt.show()


# 投球コース 右打者・左打者

fig, axs = plt.subplots(1, 2, figsize=(15, 6))

# vs左打者
df_663776_L = df_663776[df_663776['stand'] == 'L']
grouped_L = df_663776_L.groupby('pitch_type')

for pitch_type, data in grouped_L:
    axs[0].scatter(data['plate_x'], data['plate_z'], label=pitch_type)

axs[0].set_xlim(-3, 3)
axs[0].set_ylim(-2, 6)
axs[0].set_xlabel('Plate X')
axs[0].set_ylabel('Plate Z')
axs[0].legend()
axs[0].grid(which='both', linestyle='--', color='gray', alpha=0.5)
axs[0].set_title("vs left-handed batter\n(the batter is on the right side of the graph)")

# vs右打者
df_663776_R = df_663776[df_663776['stand'] == 'R']
grouped_R = df_663776_R.groupby('pitch_type')

for pitch_type, data in grouped_R:
    axs[1].scatter(data['plate_x'], data['plate_z'], label=pitch_type)

axs[1].set_xlim(-3, 3)
axs[1].set_ylim(-2, 6)
axs[1].set_xlabel('Plate X')
axs[1].set_ylabel('Plate Z')
axs[1].legend()
axs[1].grid(which='both', linestyle='--', color='gray', alpha=0.5)
axs[1].set_title("vs Right-handed batter\n(the batter is on the left side of the graph)")

plt.show()


球種コース / 左打者

# vs Left batter
pitch_types_L = [pt for pt in df_663776_L['pitch_type'].unique() if pt != 'CS']
fig, axs = plt.subplots(len(pitch_types_L)//2, 2, figsize=(15, 15))  # 3x2のグリッドに変更

for i, pitch_type in enumerate(pitch_types_L):
    df_663776_L_cs = df_663776_L[df_663776_L['pitch_type'] == pitch_type]
    axs[i//2, i%2].scatter(df_663776_L_cs['plate_x'], df_663776_L_cs['plate_z'], label=pitch_type)
    axs[i//2, i%2].set_xlim(-3, 3)
    axs[i//2, i%2].set_ylim(-2, 6)
    axs[i//2, i%2].set_xlabel('Plate X')
    axs[i//2, i%2].set_ylabel('Plate Z')
    axs[i//2, i%2].legend()
    axs[i//2, i%2].grid(which='both', linestyle='--', color='gray', alpha=0.5)
    axs[i//2, i%2].set_title(pitch_type)

    # ストライクゾーン
    x = [-0.88, 0.88, 0.88, -0.88, -0.88]
    y = [1.51, 1.51, 3.4, 3.4, 1.51]

    # すべてのグラフに対してストライクゾーンを表示
    axs[i//2, i%2].fill(x, y, color='r', alpha=0.3)

plt.show()


球種コース / 右打者

# vs Right batter
pitch_types_R = [pt for pt in df_663776_R['pitch_type'].unique() if pt != 'CS']
fig, axs = plt.subplots(len(pitch_types_R)//2 + len(pitch_types_R)%2, 2, figsize=(15, 15))  # グリッドのサイズを変更

for i, pitch_type in enumerate(pitch_types_R):
    df_663776_R_cs = df_663776_R[df_663776_R['pitch_type'] == pitch_type]
    axs[i//2, i%2].scatter(df_663776_R_cs['plate_x'], df_663776_R_cs['plate_z'], label=pitch_type)
    axs[i//2, i%2].set_xlim(-3, 3)
    axs[i//2, i%2].set_ylim(-2, 6)
    axs[i//2, i%2].set_xlabel('Plate X')
    axs[i//2, i%2].set_ylabel('Plate Z')
    axs[i//2, i%2].legend()
    axs[i//2, i%2].grid(which='both', linestyle='--', color='gray', alpha=0.5)
    axs[i//2, i%2].set_title(pitch_type)

    # ストライクゾーン
    x = [-0.88, 0.88, 0.88, -0.88, -0.88]
    y = [1.51, 1.51, 3.4, 3.4, 1.51]

    # すべてのグラフに対してストライクゾーンを表示
    axs[i//2, i%2].fill(x, y, color='r', alpha=0.3)

plt.show()


投球内訳

import pandas as pd

total_counts = df_663776['pitch_type'].value_counts()
left_counts = df_663776_L['pitch_type'].value_counts()
right_counts = df_663776_R['pitch_type'].value_counts()

pitch_counts_table = pd.DataFrame({'Total': total_counts, 'Left Batter': left_counts, 'Right Batter': right_counts})
pitch_counts_table.fillna(0, inplace=True)  # NaNを0に置き換える
pitch_counts_table = pitch_counts_table.astype(int)  # カウントを整数に変換する

print(pitch_counts_table)

結果、


投球内訳 / 円グラフ

import matplotlib.pyplot as plt

fig, axs = plt.subplots(1, 2, figsize=(12, 6))

# vs Left batter
df_663776_L['pitch_type'].value_counts().plot(kind='pie', ax=axs[0], autopct='%.1f%%')
axs[0].set_title('vs Left batter')
axs[0].set_ylabel('')  # y軸ラベルを削除

# vs Right batter
df_663776_R['pitch_type'].value_counts().plot(kind='pie', ax=axs[1], autopct='%.1f%%')
axs[1].set_title('vs Right batter')
axs[1].set_ylabel('')  # y軸ラベルを削除

plt.show()


import numpy as np
import matplotlib.pyplot as plt

pitch_types_L = [pt for pt in df_663776_L['pitch_type'].unique() if not pd.isna(pt)]
pitch_types_R = [pt for pt in df_663776_R['pitch_type'].unique() if not pd.isna(pt)]
pitch_types = sorted(set(pitch_types_L) | set(pitch_types_R))

fig, axs = plt.subplots(len(pitch_types), 2, figsize=(12, len(pitch_types) * 4))

for i, pitch_type in enumerate(pitch_types):
    df_663776_L_pt = df_663776_L[df_663776_L['pitch_type'] == pitch_type]
    df_663776_R_pt = df_663776_R[df_663776_R['pitch_type'] == pitch_type]

    axs[i, 0].scatter(df_663776_L_pt['plate_x'], df_663776_L_pt['plate_z'], label=pitch_type)
    axs[i, 1].scatter(df_663776_R_pt['plate_x'], df_663776_R_pt['plate_z'], label=pitch_type)

    axs[i, 0].set_xlim(-3, 3)
    axs[i, 0].set_ylim(-2, 6)
    axs[i, 1].set_xlim(-3, 3)
    axs[i, 1].set_ylim(-2, 6)

    axs[i, 0].set_xlabel('Plate X')
    axs[i, 0].set_ylabel('Plate Z')
    axs[i, 1].set_xlabel('Plate X')
    axs[i, 1].set_ylabel('Plate Z')

    axs[i, 0].legend()
    axs[i, 1].legend()

    axs[i, 0].grid(which='both', linestyle='--', color='gray', alpha=0.5)
    axs[i, 1].grid(which='both', linestyle='--', color='gray', alpha=0.5)

    axs[i, 0].set_title(f"Left Batter - {pitch_type}")
    axs[i, 1].set_title(f"Right Batter - {pitch_type}")

    x = [-0.88, 0.88, 0.88, -0.88, -0.88]
    y = [1.51, 1.51, 3.4, 3.4, 1.51]

    axs[i, 0].fill(x, y, color='r', alpha=0.3)
    axs[i, 1].fill(x, y, color='r', alpha=0.3)

plt.tight_layout()
plt.show()


球速、回転

import pandas as pd

# 全投手データのrelease_speedとspin rateの平均値と標準偏差を算出する
df_mean_all = df.groupby('pitch_type')[['release_speed', 'release_spin_rate']].mean()
df_std_all = df.groupby('pitch_type')[['release_speed', 'release_spin_rate']].std()

# Sandoval投手データのrelease_speedとspin rateの平均値と標準偏差を算出する
df_mean_663776 = df_663776.groupby('pitch_type')[['release_speed', 'release_spin_rate']].mean()
df_std_663776 = df_663776.groupby('pitch_type')[['release_speed', 'release_spin_rate']].std()

# Sandoval投手データに存在するpitch_typeだけを取り出す
valid_pitch_types = df_mean_663776.index

# Sandoval投手データに存在するpitch_typeだけの全投手データを取り出す
df_mean_all = df_mean_all.loc[valid_pitch_types]
df_std_all = df_std_all.loc[valid_pitch_types]

# 指定された順序で全投手データとSandoval投手データの平均値と標準偏差を横に結合する
df_comparison = pd.concat([df_mean_all['release_speed'], df_mean_663776['release_speed'], df_mean_all['release_spin_rate'], df_mean_663776['release_spin_rate'], df_std_all['release_speed'], df_std_663776['release_speed'], df_std_all['release_spin_rate'], df_std_663776['release_spin_rate']], axis=1)
df_comparison.columns = ['mean_release_speed_all', 'mean_release_speed_663776', 'mean_spin_rate_all', 'mean_spin_rate_663776', 'std_release_speed_all', 'std_release_speed_663776', 'std_spin_rate_all', 'std_spin_rate_663776']

# 少数第二位まで表示する
pd.options.display.float_format = "{:.2f}".format

# 指定された形式で2個ずつ表示する
for i in range(0, len(df_comparison.columns), 2):
    display(df_comparison.iloc[:, i:i+2])

!


全体との比較

import matplotlib.pyplot as plt

# ボックスプロットを描画する
fig, ax = plt.subplots(figsize=(10, 5))

# 全投手のSLの球種のrelease_spin_rateをボックスプロットで表示する
df[df['pitch_type'] == 'SL'].boxplot(column='release_spin_rate', by='pitch_type', ax=ax, positions=[1])

#  sandovalのSLの球種のrelease_spin_rateをボックスプロットで表示する
df_663776[df_663776['pitch_type'] == 'SL'].boxplot(column='release_spin_rate', by='pitch_type', ax=ax, positions=[2])

# 全投手のSLの球種のrelease_spin_rateの平均値を表示する
mean_all = df[df['pitch_type'] == 'SL']['release_spin_rate'].mean()
ax.text(1.1, mean_all, f"mean = {mean_all:.2f}", ha='left', va='bottom')

# sandovalのSLの球種のrelease_spin_rateの平均値を表示する
mean_663776 = df_663776[df_663776['pitch_type'] == 'SL']['release_spin_rate'].mean()
ax.text(2.1, mean_663776, f"mean = {mean_663776:.2f}", ha='left', va='bottom')

# グラフのタイトルを設定する
ax.set_title("release_spin_rate by pitch_type (all vs. sandoval)")

# 軸のラベルを設定する
ax.set_xlabel("SL_pitch_type")
ax.set_ylabel("release_spin_rate")

# X軸のラベルを設定する
ax.set_xticklabels(['All', 'sandoval'])

# グラフを表示する
plt.show()

import matplotlib.pyplot as plt

# ボックスプロットを描画する
fig, ax = plt.subplots(figsize=(10, 5))

# 全投手のSLの球種のrelease_speedをボックスプロットで表示する
df[df['pitch_type'] == 'SL'].boxplot(column='release_speed', by='pitch_type', ax=ax, positions=[1])

#  sandovalのSLの球種のrelease_speedをボックスプロットで表示する
df_663776[df_663776['pitch_type'] == 'SL'].boxplot(column='release_speed', by='pitch_type', ax=ax, positions=[2])

# 全投手のSLの球種のrelease_speedの平均値を表示する
mean_all = df[df['pitch_type'] == 'SL']['release_speed'].mean()
ax.text(1.1, mean_all, f"mean = {mean_all:.2f}", ha='left', va='bottom')

# sandovalのSLの球種のrelease_speedの平均値を表示する
mean_663776 = df_663776[df_663776['pitch_type'] == 'SL']['release_speed'].mean()
ax.text(2.1, mean_663776, f"mean = {mean_663776:.2f}", ha='left', va='bottom')

# グラフのタイトルを設定する
ax.set_title("release_speed by pitch_type (all vs. sandoval)")

# 軸のラベルを設定する
ax.set_xlabel("SL_pitch_type")
ax.set_ylabel("rrelease_speed")

# X軸のラベルを設定する
ax.set_xticklabels(['All', 'sandoval'])

# グラフを表示する
plt.show()


打たれた打球。全体と663776で比較

import matplotlib.pyplot as plt

# pitcher 663776の結果を取り出し、df_663776に格納
df_663776 = df[df["pitcher"] == 663776]

# df_663776 = df_663776.dropna(subset=['hc_x', 'hc_y']) で除外すると三振など除外されてしまう
df_663776 = df_663776.fillna({'hc_x': 0, 'hc_y': 0})

# df = df.dropna(subset=['hc_x', 'hc_y']) すると三振など除外されてしまう
df = df.fillna({'hc_x': 0, 'hc_y': 0})

# 全体をプロット
plt.scatter(df['hc_x'], df['hc_y'], color='gray')

# pitcher 663776の結果を赤色でプロット
plt.scatter(df_663776['hc_x'], df_663776['hc_y'], color='red')

plt.xlabel('X coordinate')
plt.ylabel('Y coordinate')
plt.gca().invert_yaxis()

# フェア線を引く
plt.plot([125, 250], [210, 85], 'k-', lw=3)
plt.plot([125, 0], [210, 85], 'k-', lw=3)

plt.show()


import matplotlib.pyplot as plt

# 右打者と左打者のデータを取り出す
df_663776_R = df_663776[df_663776["stand"] == "R"]
df_663776_L = df_663776[df_663776["stand"] == "L"]

df_R = df[df["stand"] == "R"]
df_L = df[df["stand"] == "L"]

# グラフを2つ並べて表示する
fig, axs = plt.subplots(1, 2, figsize=(12, 6))

# 右打者のグラフを表示
axs[0].scatter(df_R['hc_x'], df_R['hc_y'], color='gray')
axs[0].scatter(df_663776_R['hc_x'], df_663776_R['hc_y'], color='red')
axs[0].set_title('vs Right batter')
axs[0].set_xlabel('X coordinate')
axs[0].set_ylabel('Y coordinate')
axs[0].invert_yaxis()

# 左打者のグラフを表示
axs[1].scatter(df_L['hc_x'], df_L['hc_y'], color='gray')
axs[1].scatter(df_663776_L['hc_x'], df_663776_L['hc_y'], color='red')
axs[1].set_title('vs Left batter')
axs[1].set_xlabel('X coordinate')
axs[1].set_ylabel('Y coordinate')
axs[1].invert_yaxis()

# フェア線を引く
for ax in axs:
    ax.plot([125, 250], [210, 85], 'k-', lw=3)
    ax.plot([125, 0], [210, 85], 'k-', lw=3)

plt.show()


サンドバルのみ

import matplotlib.pyplot as plt

# Sandoval (663776)のデータを取り出す
df_663776 = df[df["pitcher"] == 663776]

# home_runのデータを取り出す
df_hr = df_663776[df_663776['events'] == 'home_run']
# doubleのデータを取り出す
df_2b = df_663776[df_663776['events'] == 'double']
# tripleのデータを取り出す
df_3b = df_663776[df_663776['events'] == 'triple']
# singleのデータを取り出す
df_1b = df_663776[df_663776['events'] == 'single']
# hit_by_pitchのデータを取り出す
df_hbp = df_663776[df_663776['events'] == 'hit_by_pitch']

# outイベントのデータを取り出す
df_out = df_663776[~df_663776['events'].isin(['home_run', 'double', 'triple', 'single', 'hit_by_pitch'])]

# plotする
plt.scatter(df_hr['hc_x'], df_hr['hc_y'], color='r', label='home_run')
plt.scatter(df_2b['hc_x'], df_2b['hc_y'], color='b', label='double')
plt.scatter(df_3b['hc_x'], df_3b['hc_y'], color='g', label='triple')
plt.scatter(df_1b['hc_x'], df_1b['hc_y'], color='y', label='single')

# plt.scatter(df_hbp['hc_x'], df_hbp['hc_y'], color='c', label='hit_by_pitch')
plt.scatter(df_out['hc_x'], df_out['hc_y'], color='gray', label='out', zorder=0)
plt.xlabel('X coordinate')
plt.ylabel('Y coordinate')
plt.gca().invert_yaxis()
plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left', borderaxespad=0)

# フェア線を引く
plt.plot([125, 250], [210, 85], 'k-', lw=3)
plt.plot([125, 0], [210,85], 'k-', lw=3)

plt.title("Pitcher Sandoval, direction of the batted ball 2021")

plt.show()


# Sandoval (663776)のデータを取り出す
df_663776 = df[df["pitcher"] == 663776]

# 右打者と左打者のデータに分割
df_663776_R = df_663776[df_663776['stand'] == 'R']
df_663776_L = df_663776[df_663776['stand'] == 'L']

# Figureを作成し、左右のグラフを描画
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 6))

# 各グラフに対して、イベントごとにプロットを行う関数を定義
def plot_events(ax, df, title):
    # 各イベントに対して、プロットを行う
    for event, color in zip(['home_run', 'double', 'triple', 'single', 'out'], ['r', 'b', 'g', 'y', 'gray']):
        if event == 'out':
            event_data = df[~df['events'].isin(['home_run', 'double', 'triple', 'single', 'hit_by_pitch'])]
        else:
            event_data = df[df['events'] == event]
        event_data = event_data[(event_data['hc_x'] != 0) & (event_data['hc_y'] != 0)] # 座標(0, 0)のデータを除外
        ax.scatter(event_data['hc_x'], event_data['hc_y'], color=color, label=event, zorder=2 if event == 'out' else 3)

    # 軸ラベル、タイトル、凡例、フェアラインを設定
    ax.set_xlabel('X coordinate')
    ax.set_ylabel('Y coordinate')
    ax.invert_yaxis()
    ax.set_title(title)
    ax.plot([125, 250], [210, 85], 'k-', lw=3)
    ax.plot([125, 0], [210, 85], 'k-', lw=3)

# 各グラフに対して、イベントごとにプロットを行う関数を呼び出し
plot_events(ax1, df_663776_R, "Sandoval - Right-handed batters")
plot_events(ax2, df_663776_L, "Sandoval - Left-handed batters")

# 左右のグラフのレジェンドを結合して1つのレジェンドを作成
handles, labels = ax1.get_legend_handles_labels()
fig.legend(handles, labels, bbox_to_anchor=(0.5, 0), loc='upper center', ncol=5, borderaxespad=0)

plt.show()


左打者、右打者に対する被打率と被本塁打を計算し、表示するコードを以下に示します

公式と打率が少しずれた

exclude_events = ['walk', 'hit_by_pitch', 'sac_fly', 'sac_bunt', 'sac_fly_double_play', 'sac_bunt_double_play', 'catcher_interf', 'wild_pitch', 'game_advisory', 'pickoff_3b', 'caught_stealing_3b', 'pickoff_caught_stealing_2b', 'caught_stealing_home', 'sac_fly_double_play', 'wild_pitch', 'pickoff_1b', 'pickoff_caught_stealing_home', 'pickoff_caught_stealing_3b', 'pickoff_2b', 'caught_stealing_2b', 'other_out']

ba_R = calc_batting_average(df_663776_R, exclude_events)
ba_L = calc_batting_average(df_663776_L, exclude_events)

print(f"対右打者 被打率: {ba_R:.3f}")
print(f"対左打者 被打率: {ba_L:.3f}")

# 右打者の被本塁打数
hr_count_R = len(df_663776_R[df_663776_R['events'] == 'home_run'])
print(f"\n対右打者 被本塁打数: {hr_count_R}")

# 左打者の被本塁打数
hr_count_L = len(df_663776_L[df_663776_L['events'] == 'home_run'])
print(f"対左打者 被本塁打数: {hr_count_L}")

結果、

対右打者 被打率: 0.270
対左打者 被打率: 0.149

対右打者 被本塁打数: 9
対左打者 被本塁打数: 0

--

観戦のため、寝る

0
2
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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?