Featured image of post 【pybaseball基礎編⑦】投球単位のデータ取得

【pybaseball基礎編⑦】投球単位のデータ取得

pybaseballを使ってMLBのStatcastデータから投球ごとの球種、球速、回転数、変化量、リリース位置、投球位置を取得し、Google Colabとpandasで分析する方法を解説します。

記事のシリーズ

  1. pybaseball基礎編① pybaseballのライブラリの概要とインストール
  2. pybaseball基礎編② シーズン単位のデータ取得
  3. pybaseball基礎編③ 試合単位のデータ取得
  4. pybaseball基礎編④ 打席単位のデータ取得
  5. pybaseball基礎編⑤ 1球単位のデータ取得
  6. pybaseball基礎編⑥ 打球単位のデータ取得
  7. pybaseball基礎編⑦ 投球単位のデータ取得【本記事】

はじめに

これまでのシリーズでは、pybaseballの基本、シーズン・試合・打席単位のデータ取得、1球単位のデータ、打球データを順番に見てきました。

今回は、投手が投げた1球ごとの投球データを詳しく見ていきます。

Statcastでは、球種や球速だけでなく、回転数、リリース位置、ホームプレート付近の位置、変化量に関係する値など、投球を分析するための情報を確認できます。

この記事ではGoogle Colabを使い、pybaseball でデータを取得して、次のような分析を行います。

  • 球種ごとの投球数と平均球速
  • 回転数やリリース位置の確認
  • 投球位置の散布図
  • 特定投手の投球データの抽出
  • 欠損値やデータの扱い方

コードは短い期間のデータから始め、実行結果を確認しながら進めます。


1. 「投球単位」とは?

Statcastのデータは、基本的に1球につき1行で構成されています。pybaseballの公式ドキュメントでも、statcast() がクエリ対象の各投球について1行ずつ返すと説明されています。

例えば、1打席が5球で終わった場合、その打席には最大5行の投球データがあります。

1打席
 ├─ 1球目:フォーシーム
 ├─ 2球目:スライダー
 ├─ 3球目:ボール
 ├─ 4球目:ファウル
 └─ 5球目:三振

投球単位の分析では、打席の結果だけでなく、その1球がどんな球だったのかに注目します。


2. Statcastデータを取得する

まずは1日分のデータを取得します。

from pybaseball import statcast

game_data = statcast(
    start_dt="2025-06-16",
    end_dt="2025-06-16"
)

display(game_data.head())

出力結果

index pitch_type game_date release_speed release_pos_x release_pos_z player_name batter pitcher events description spin_dir spin_rate_deprecated break_angle_deprecated break_length_deprecated zone des game_type stand p_throws home_team
1411 FF 2025-06-16 00:00:00 93.5 -2.44 5.35 Yates, Kirby 642180 489446 field_out hit_into_play <NA> <NA> <NA> <NA> 2 Tyler Wade pops out to third baseman Max Muncy. R L R LAD
1467 FS 2025-06-16 00:00:00 86.7 -2.47 5.17 Yates, Kirby 642180 489446 NaN called_strike <NA> <NA> <NA> <NA> 13 NaN R L R LAD
1550 FF 2025-06-16 00:00:00 93.0 -2.46 5.21 Yates, Kirby 642180 489446 NaN foul <NA> <NA> <NA> <NA> 3 NaN R L R LAD
1570 FS 2025-06-16 00:00:00 85.9 -2.39 5.18 Yates, Kirby 578428 489446 field_out hit_into_play <NA> <NA> <NA> <NA> 9 Jose Iglesias lines out to shortstop Mookie Betts. R R R LAD
1650 FF 2025-06-16 00:00:00 93.4 -2.35 5.38 Yates, Kirby 578428 489446 NaN foul <NA> <NA> <NA> <NA> 11 NaN R R R LAD

start_dt と end_dt は、取得する期間の開始日と終了日です。日付は YYYY-MM-DD 形式で指定します。


3. 投球データに使う主な列

投球分析でよく使う列を抜き出してみましょう。

pitch_columns = [
    "game_date",
    "game_pk",
    "inning",
    "at_bat_number",
    "pitch_number",
    "pitcher",
    "batter",
    "pitch_type",
    "pitch_name",
    "release_speed",
    "release_spin_rate",
    "spin_axis",
    "release_pos_x",
    "release_pos_z",
    "release_extension",
    "pfx_x",
    "pfx_z",
    "plate_x",
    "plate_z",
    "balls",
    "strikes",
    "description",
    "events"
]

available_columns = [
    col for col in pitch_columns
    if col in game_data.columns
]

display(game_data[available_columns].head(20))

列名はデータ仕様や取得データの時期によって変わる可能性があるため、available_columns のように存在する列を確認してから選ぶと、列が見つからない場合のエラーを防ぎやすくなります。

出力結果

index game_date game_pk inning at_bat_number pitch_number pitcher batter pitch_type pitch_name release_speed release_spin_rate spin_axis release_pos_x release_pos_z release_extension pfx_x pfx_z plate_x plate_z balls
1411 2025-06-16 00:00:00 777490 9 73 3 489446 642180 FF 4-Seam Fastball 93.5 2309 229 -2.44 5.35 6.1 -0.96 1.28 -0.1236243215667922 3.256601567619987 0
1467 2025-06-16 00:00:00 777490 9 73 2 489446 642180 FS Split-Finger 86.7 1351 257 -2.47 5.17 6.0 -0.97 0.87 -0.2766508753703635 1.350055118752824 0
1550 2025-06-16 00:00:00 777490 9 73 1 489446 642180 FF 4-Seam Fastball 93.0 2326 233 -2.46 5.21 6.1 -0.9 1.27 0.3305527045795539 2.8401170752894838 0
1570 2025-06-16 00:00:00 777490 9 72 4 489446 578428 FS Split-Finger 85.9 1254 264 -2.39 5.18 6.2 -0.87 -0.23 0.2986480050714284 1.359024034526068 0
1650 2025-06-16 00:00:00 777490 9 72 3 489446 578428 FF 4-Seam Fastball 93.4 2154 229 -2.35 5.38 5.9 -0.99 1.15 -0.0399089501307835 3.4196585344346118 0
1687 2025-06-16 00:00:00 777490 9 72 2 489446 578428 FF 4-Seam Fastball 94.0 2359 226 -2.5 5.34 5.9 -1.03 1.15 -0.1408786429155342 3.4708462830100206 0
1741 2025-06-16 00:00:00 777490 9 72 1 489446 578428 FF 4-Seam Fastball 93.3 2176 229 -2.39 5.18 6.3 -0.97 1.19 0.0297634899928171 2.258311964976208 0
1823 2025-06-16 00:00:00 777490 9 71 3 489446 630105 FS Split-Finger 86.0 1375 257 -2.59 5.16 5.9 -0.86 0.61 0.370591581027924 1.3671351605609767 1
1858 2025-06-16 00:00:00 777490 9 71 2 489446 630105 FF 4-Seam Fastball 93.2 2280 233 -2.56 5.26 6.0 -0.91 1.2 1.2552807496902143 2.799829922368259 0
1913 2025-06-16 00:00:00 777490 9 71 1 489446 630105 FF 4-Seam Fastball 93.0 2213 230 -2.54 5.22 6.3 -0.93 1.11 0.321323719692403 2.55692342942573 0
853 2025-06-16 00:00:00 777490 8 70 1 688158 605141 FF 4-Seam Fastball 95.3 2301 216 -1.74 5.47 5.9 -0.32 1.19 -0.393615151383056 3.0147805374936176 0
865 2025-06-16 00:00:00 777490 8 69 5 688158 660271 SL Slider 87.1 2225 160 -2.38 5.28 5.9 0.28 0.18 -2.240474406898455 2.094120206332674 3
877 2025-06-16 00:00:00 777490 8 69 4 688158 660271 CU Curveball 84.1 2730 39 -1.71 5.59 6.0 0.92 -0.86 2.060671564552802 0.8537048319164913 2
914 2025-06-16 00:00:00 777490 8 69 3 688158 660271 FF 4-Seam Fastball 97.3 2502 218 -2.06 5.44 5.8 -0.53 1.3 -0.4583749270656911 3.9101669562079975 1
918 2025-06-16 00:00:00 777490 8 69 2 688158 660271 FF 4-Seam Fastball 96.4 2458 216 -2.06 5.43 5.7 -0.52 1.24 -0.1567795340519512 4.236033624652384 0
939 2025-06-16 00:00:00 777490 8 69 1 688158 660271 CU Curveball 83.2 2609 42 -1.84 5.58 6.1 0.66 -1.04 0.7604812299790533 1.1388906812474766 0
964 2025-06-16 00:00:00 777490 8 68 1 688158 669242 SL Slider 87.1 2332 89 -2.25 5.2 6.1 0.47 -0.32 0.092538646095987 1.3888309414066669 0
997 2025-06-16 00:00:00 777490 8 67 5 688158 624424 FF 4-Seam Fastball 96.4 2465 228 -2.19 5.33 5.8 -0.64 1.29 -0.5381142567806368 2.2198248180781737 1
1014 2025-06-16 00:00:00 777490 8 67 4 688158 624424 CU Curveball 83.4 2686 46 -1.83 5.54 6.0 0.93 -0.84 0.4049509964475222 1.7151008643813692 1
1030 2025-06-16 00:00:00 777490 8 67 3 688158 624424 FF 4-Seam Fastball 97.1 2446 226 -2.04 5.43 5.7 -0.63 1.2 0.4916163852080379 4.131930739456839 0

4. 球種を確認する:pitch_type と pitch_name

投球分析の入口として、まず球種を確認します。

  • pitch_type:球種コード
  • pitch_name:球種名
display(
    game_data[
        ["pitch_type", "pitch_name"]
    ].drop_duplicates().sort_values("pitch_name")
)

出力結果

index pitch_type pitch_name
1411 FF 4-Seam Fastball
1177 CH Changeup
877 CU Curveball
1410 FC Cutter
386 KC Knuckle Curve
1514 SI Sinker
865 SL Slider
1758 SV Slurve
1467 FS Split-Finger
380 ST Sweeper
1952 NaN NaN

球種ごとの投球数を集計するには、value_counts() を使います。

pitch_counts = game_data["pitch_name"].value_counts(dropna=False)
display(pitch_counts)

出力結果

pitch_name count
4-Seam Fastball 616
Slider 307
Sinker 306
Cutter 219
Changeup 144
Sweeper 143
Curveball 116
Knuckle Curve 58
Split-Finger 40
NaN 10
Slurve 1

この集計で分かるのは、指定した取得期間における球種別の投球数です。


5. release_speed で球速を分析する

release_speed は投球速度を表す項目です。単位はmphです。

display(
    game_data[
        ["pitch_name", "release_speed"]
    ].head(20)
)

出力結果

index pitch_name release_speed
1411 4-Seam Fastball 93.5
1467 Split-Finger 86.7
1550 4-Seam Fastball 93.0
1570 Split-Finger 85.9
1650 4-Seam Fastball 93.4
1687 4-Seam Fastball 94.0
1741 4-Seam Fastball 93.3
1823 Split-Finger 86.0
1858 4-Seam Fastball 93.2
1913 4-Seam Fastball 93.0
853 4-Seam Fastball 95.3
865 Slider 87.1
877 Curveball 84.1
914 4-Seam Fastball 97.3
918 4-Seam Fastball 96.4
939 Curveball 83.2
964 Slider 87.1
997 4-Seam Fastball 96.4
1014 Curveball 83.4
1030 4-Seam Fastball 97.1

球速の平均、中央値、最小値、最大値などは describe() で確認できます。

display(game_data["release_speed"].describe())

出力結果

index release_speed
count 1950.0
mean 90.1703076923077
std 5.9872453287528185
min 72.7
25% 85.6
50% 91.2
75% 95.3
max 101.9

球種ごとの平均球速を計算する

pitch_speed_summary = (
    game_data
    .groupby("pitch_name", dropna=False)
    .agg(
        pitches=("release_speed", "count"),
        avg_speed=("release_speed", "mean"),
        max_speed=("release_speed", "max")
    )
    .reset_index()
    .sort_values("avg_speed", ascending=False)
)

display(pitch_speed_summary)

出力結果

index pitch_name pitches avg_speed max_speed
0 4-Seam Fastball 616 95.23863636363636 101.5
5 Sinker 306 95.159477124183 101.9
3 Cutter 219 89.36164383561645 95.7
6 Slider 307 86.89869706840392 92.4
1 Changeup 144 86.46805555555555 93.6
8 Split-Finger 40 84.77000000000001 93.1
4 Knuckle Curve 58 84.64827586206897 87.2
7 Slurve 1 82.8 82.8
9 Sweeper 143 81.07062937062938 88.8
2 Curveball 116 80.78017241379311 86.6
10 NaN 0 <NA> <NA>

pitches は球速が欠損していない投球数、avg_speed は平均球速、max_speed は最大球速です。


6. 回転数:release_spin_rate

release_spin_rate は投球の回転数を表す項目です。一般にrpm(1分あたりの回転数)で扱われます。

display(
    game_data[
        [
            "pitch_name",
            "release_speed",
            "release_spin_rate"
        ]
    ].head(20)
)

出力結果

index pitch_name release_speed release_spin_rate
1411 4-Seam Fastball 93.5 2309
1467 Split-Finger 86.7 1351
1550 4-Seam Fastball 93.0 2326
1570 Split-Finger 85.9 1254
1650 4-Seam Fastball 93.4 2154
1687 4-Seam Fastball 94.0 2359
1741 4-Seam Fastball 93.3 2176
1823 Split-Finger 86.0 1375
1858 4-Seam Fastball 93.2 2280
1913 4-Seam Fastball 93.0 2213
853 4-Seam Fastball 95.3 2301
865 Slider 87.1 2225
877 Curveball 84.1 2730
914 4-Seam Fastball 97.3 2502
918 4-Seam Fastball 96.4 2458
939 Curveball 83.2 2609
964 Slider 87.1 2332
997 4-Seam Fastball 96.4 2465
1014 Curveball 83.4 2686
1030 4-Seam Fastball 97.1 2446

球種ごとの平均回転数を集計する例です。

spin_summary = (
    game_data
    .groupby("pitch_name", dropna=False)
    .agg(
        pitches=("release_spin_rate", "count"),
        avg_spin=("release_spin_rate", "mean"),
        median_spin=("release_spin_rate", "median")
    )
    .reset_index()
    .sort_values("avg_spin", ascending=False)
)

display(spin_summary)

出力結果

index pitch_name pitches avg_spin median_spin
7 Slurve 1 2821 2821
4 Knuckle Curve 58 2716.4310344827586 2654
2 Curveball 116 2677.137931034483 2617.5
9 Sweeper 143 2570.5384615384614 2547
3 Cutter 219 2526.7397260273974 2594
6 Slider 307 2480.889250814332 2519
0 4-Seam Fastball 616 2370.6347402597403 2369
5 Sinker 306 2196.843137254902 2235.5
1 Changeup 144 2031.0972222222222 2015
8 Split-Finger 40 1022.4 1003
10 NaN 0 <NA> <NA>

7. 回転軸:spin_axis

spin_axis は投球の回転軸に関する項目です。

display(
    game_data[
        [
            "pitch_name",
            "release_spin_rate",
            "spin_axis"
        ]
    ].head(20)
)

出力結果

index pitch_name release_spin_rate spin_axis
1411 4-Seam Fastball 2309 229
1467 Split-Finger 1351 257
1550 4-Seam Fastball 2326 233
1570 Split-Finger 1254 264
1650 4-Seam Fastball 2154 229
1687 4-Seam Fastball 2359 226
1741 4-Seam Fastball 2176 229
1823 Split-Finger 1375 257
1858 4-Seam Fastball 2280 233
1913 4-Seam Fastball 2213 230
853 4-Seam Fastball 2301 216
865 Slider 2225 160
877 Curveball 2730 39
914 4-Seam Fastball 2502 218
918 4-Seam Fastball 2458 216
939 Curveball 2609 42
964 Slider 2332 89
997 4-Seam Fastball 2465 228
1014 Curveball 2686 46
1030 4-Seam Fastball 2446 226

spin_axisは、2D X-Z平面における0度から360度で表される回転軸(の角度)。180度は純粋なバックスピンのファストボール(ストレート)を、0度は純粋なトップスピンの縦カーブ(12-6カーブ)を表します


8. リリース位置:release_pos_x と release_pos_z

投球が投手の手を離れた位置を調べるときは、次の項目を確認します。

  • release_pos_x:リリース位置の左右方向
  • release_pos_z:リリース位置の高さ方向
display(
    game_data[
        [
            "pitcher",
            "pitch_name",
            "release_pos_x",
            "release_pos_z"
        ]
    ].head(20)
)

出力結果

index pitcher pitch_name release_pos_x release_pos_z
1411 489446 4-Seam Fastball -2.44 5.35
1467 489446 Split-Finger -2.47 5.17
1550 489446 4-Seam Fastball -2.46 5.21
1570 489446 Split-Finger -2.39 5.18
1650 489446 4-Seam Fastball -2.35 5.38
1687 489446 4-Seam Fastball -2.5 5.34
1741 489446 4-Seam Fastball -2.39 5.18
1823 489446 Split-Finger -2.59 5.16
1858 489446 4-Seam Fastball -2.56 5.26
1913 489446 4-Seam Fastball -2.54 5.22
853 688158 4-Seam Fastball -1.74 5.47
865 688158 Slider -2.38 5.28
877 688158 Curveball -1.71 5.59
914 688158 4-Seam Fastball -2.06 5.44
918 688158 4-Seam Fastball -2.06 5.43
939 688158 Curveball -1.84 5.58
964 688158 Slider -2.25 5.2
997 688158 4-Seam Fastball -2.19 5.33
1014 688158 Curveball -1.83 5.54
1030 688158 4-Seam Fastball -2.04 5.43

これらの値を使うと、投手ごとのリリース位置の特徴を調べられます。


9. release_extension でリリースの伸びを確認する

release_extension は、投手がマウンド前方へどの程度伸びて投球をリリースしたかに関係する項目です。一般にフィートで表されます。

display(
    game_data[
        [
            "pitch_name",
            "release_speed",
            "release_extension"
        ]
    ].head(20)
)

出力結果

index pitch_name release_speed release_extension
1437 4-Seam Fastball 93.5 6.1
1492 Split-Finger 86.7 6.0
1526 4-Seam Fastball 93.0 6.1
1568 Split-Finger 85.9 6.2
1629 4-Seam Fastball 93.4 5.9
1685 4-Seam Fastball 94.0 5.9
1759 4-Seam Fastball 93.3 6.3
1787 Split-Finger 86.0 5.9
1857 4-Seam Fastball 93.2 6.0
1925 4-Seam Fastball 93.0 6.3
841 4-Seam Fastball 95.3 5.9
856 Slider 87.1 5.9
895 Curveball 84.1 6.0
904 4-Seam Fastball 97.3 5.8
932 4-Seam Fastball 96.4 5.7
942 Curveball 83.2 6.1
971 Slider 87.1 6.1
996 4-Seam Fastball 96.4 5.8
1011 Curveball 83.4 6.0
1038 4-Seam Fastball 97.1 5.7

球種ごとの平均値を確認する場合は、次のように集計できます。

extension_summary = (
    game_data
    .groupby("pitch_name", dropna=False)
    .agg(
        pitches=("release_extension", "count"),
        avg_extension=("release_extension", "mean")
    )
    .reset_index()
    .sort_values("avg_extension", ascending=False)
)

display(extension_summary)

出力結果

index pitch_name pitches avg_extension
8 Split-Finger 40 6.8675
0 4-Seam Fastball 616 6.567532467532468
7 Slurve 1 6.5
5 Sinker 306 6.455555555555556
1 Changeup 144 6.430555555555555
6 Slider 307 6.429967426710098
4 Knuckle Curve 58 6.362068965517241
3 Cutter 219 6.358447488584475
9 Sweeper 143 6.353846153846154
2 Curveball 116 6.314655172413793
10 NaN 0 <NA>

リリース位置やエクステンションは、投手の投げ方を比較する材料になります。


10. 変化量に関係する項目:pfx_x と pfx_z

Statcastには、投球の変化に関係する項目として pfx_x と pfx_z があります。

  • pfx_x:水平方向の変化に関係する値
  • pfx_z:垂直方向の変化に関係する値
display(
    game_data[
        [
            "pitch_name",
            "pfx_x",
            "pfx_z"
        ]
    ].head(20)
)

出力結果

index pitch_name pfx_x pfx_z
1437 4-Seam Fastball -0.96 1.28
1492 Split-Finger -0.97 0.87
1526 4-Seam Fastball -0.9 1.27
1568 Split-Finger -0.87 -0.23
1629 4-Seam Fastball -0.99 1.15
1685 4-Seam Fastball -1.03 1.15
1759 4-Seam Fastball -0.97 1.19
1787 Split-Finger -0.86 0.61
1857 4-Seam Fastball -0.91 1.2
1925 4-Seam Fastball -0.93 1.11
841 4-Seam Fastball -0.32 1.19
856 Slider 0.28 0.18
895 Curveball 0.92 -0.86
904 4-Seam Fastball -0.53 1.3
932 4-Seam Fastball -0.52 1.24
942 Curveball 0.66 -1.04
971 Slider 0.47 -0.32
996 4-Seam Fastball -0.64 1.29
1011 Curveball 0.93 -0.84
1038 4-Seam Fastball -0.63 1.2

球種別の平均値を計算する例です。

movement_summary = (
    game_data
    .groupby("pitch_name", dropna=False)
    .agg(
        pitches=("pfx_x", "count"),
        avg_pfx_x=("pfx_x", "mean"),
        avg_pfx_z=("pfx_z", "mean")
    )
    .reset_index()
)

display(movement_summary)

出力結果

index pitch_name pitches avg_pfx_x avg_pfx_z
0 4-Seam Fastball 616 -0.43576298701298705 1.3729220779220779
1 Changeup 144 -1.0579166666666666 0.3015277777777778
2 Curveball 116 0.7072413793103449 -0.8094827586206897
3 Cutter 219 0.22095890410958904 0.6886301369863014
4 Knuckle Curve 58 0.8550000000000001 -1.0636206896551723
5 Sinker 306 -0.8955882352941177 0.5690196078431373
6 Slider 307 0.22019543973941366 0.1557328990228013
7 Slurve 1 0.93 -0.89
8 Split-Finger 40 -0.46575 0.05700000000000001
9 Sweeper 143 0.6359440559440559 0.044125874125874126
10 NaN 0 <NA> <NA>

説明画像


11. ホームプレート付近の位置:plate_x と plate_z

plate_x と plate_z は、ホームプレート付近での投球位置を分析する際に使われます。

  • plate_x:左右方向の位置
  • plate_z:高さ方向の位置
display(
    game_data[
        [
            "pitch_name",
            "plate_x",
            "plate_z",
            "sz_top",
            "sz_bot"
        ]
    ].head(20)
)

出力結果

index pitch_name plate_x plate_z sz_top sz_bot
1437 4-Seam Fastball -0.1236243215667922 3.256601567619987 3.2 1.56
1492 Split-Finger -0.2766508753703635 1.350055118752824 3.16208227902198 1.58270217663964
1526 4-Seam Fastball 0.3305527045795539 2.8401170752894838 3.2 1.56
1568 Split-Finger 0.2986480050714284 1.359024034526068 3.23 1.41
1629 4-Seam Fastball -0.0399089501307835 3.4196585344346118 3.23 1.41
1685 4-Seam Fastball -0.1408786429155342 3.4708462830100206 3.23 1.41
1759 4-Seam Fastball 0.0297634899928171 2.258311964976208 3.23 1.41
1787 Split-Finger 0.370591581027924 1.3671351605609767 3.37 1.56
1857 4-Seam Fastball 1.2552807496902143 2.799829922368259 3.30420653495766 1.52572839349602
1925 4-Seam Fastball 0.321323719692403 2.55692342942573 3.30501371861494 1.52638514822459
841 4-Seam Fastball -0.393615151383056 3.0147805374936176 3.29 1.55
856 Slider -2.240474406898455 2.094120206332674 3.63695276076573 1.66667954858272
895 Curveball 2.060671564552802 0.8537048319164913 3.65026508979165 1.64738611119713
904 4-Seam Fastball -0.4583749270656911 3.9101669562079975 3.6468729455183 1.7089441312831
932 4-Seam Fastball -0.1567795340519512 4.236033624652384 3.65941738906449 1.65672764868447
942 Curveball 0.7604812299790533 1.1388906812474766 3.57 1.65
971 Slider 0.092538646095987 1.3888309414066669 3.38 1.54
996 4-Seam Fastball -0.5381142567806368 2.2198248180781737 3.32 1.61
1011 Curveball 0.4049509964475222 1.7151008643813692 3.32 1.61
1038 4-Seam Fastball 0.4916163852080379 4.131930739456839 3.52467505174426 1.65959100906599

sz_top と sz_bot は、打者のストライクゾーン上端・下端に関係する項目です。打者によってストライクゾーンの高さが異なるため、投球位置を分析するときはこれらの値も参考になります。

plate_x, plate_z, sz_top, sz_botのイメージ


12. 投球位置を散布図で可視化する

plate_x と plate_z を使って、投球位置の散布図を作成してみましょう。

import matplotlib.pyplot as plt

plot_data = game_data[
    ["plate_x", "plate_z"]
].dropna()

plt.figure(figsize=(7, 7))
plt.scatter(
    plot_data["plate_x"],
    plot_data["plate_z"],
    alpha=0.5
)

plt.xlabel("Plate X (feet)")
plt.ylabel("Plate Z (feet)")
plt.title("Pitch Locations")
plt.grid(True)
plt.show()

出力結果

この図では、投球位置がどのあたりに集まっているかを確認できます。

ただし、上の図は投球位置の分布を示す散布図であり、ストライクゾーンを厳密に描いたものではありません。ストライクゾーンを図に追加する場合は、打者ごとの sz_top と sz_bot や、座標の定義を考慮する必要があります。


13. 球種ごとの投球位置を比べる

例えば、球種ごとに投球位置を比較することもできます。

selected_pitch_names = ["4-Seam Fastball", "Slider"]

selected_data = game_data[
    game_data["pitch_name"].isin(selected_pitch_names)
].copy()

for pitch_name in selected_pitch_names:
    subset = selected_data[
        selected_data["pitch_name"] == pitch_name
    ][["plate_x", "plate_z"]].dropna()

    if subset.empty:
        print(f"{pitch_name}: 該当データがありません")
        continue

    plt.figure(figsize=(6, 6))
    plt.scatter(
        subset["plate_x"],
        subset["plate_z"],
        alpha=0.5
    )
    plt.xlabel("Plate X (feet)")
    plt.ylabel("Plate Z (feet)")
    plt.title(f"Pitch Locations: {pitch_name}")
    plt.grid(True)
    plt.show()

4-Seam Fastball

Slider


14. 特定の投手だけを取得する

日付範囲の全投球を取得した後に、pitcher のIDで絞り込む方法もあります。

pitcher_id = 660271  # 例:分析対象の投手IDに置き換える

pitcher_data = game_data[
    game_data["pitcher"] == pitcher_id
].copy()

display(
    pitcher_data[
        [
            "game_date",
            "pitch_name",
            "release_speed",
            "release_spin_rate",
            "spin_axis",
            "plate_x",
            "plate_z"
        ]
    ].head(30)
)

出力結果

index game_date pitch_name release_speed release_spin_rate spin_axis plate_x plate_z
885 2025-06-16 00:00:00 Sinker 95.4 1874 225 -0.1695595860585503 2.2304112714559383
914 2025-06-16 00:00:00 Sinker 95.6 1962 220 -0.4756752424391601 1.3862145162754338
921 2025-06-16 00:00:00 Sweeper 83.9 2340 65 0.9363134480379516 1.7602100938017309
954 2025-06-16 00:00:00 4-Seam Fastball 98.9 2358 221 0.9118426292581976 1.922892090823876
973 2025-06-16 00:00:00 Sweeper 87.0 2446 62 1.0806296877409023 0.7912220985274341
995 2025-06-16 00:00:00 4-Seam Fastball 99.1 2371 222 1.1248701065402666 1.8938951025794135
1005 2025-06-16 00:00:00 Split-Finger 91.3 1357 239 -0.0170783317440392 0.6047896477181882
1047 2025-06-16 00:00:00 Sweeper 86.4 2394 61 -0.1861927462977922 2.5143031558050755
1075 2025-06-16 00:00:00 Sinker 98.8 1871 224 0.8785047285603955 1.7336137402643477
1083 2025-06-16 00:00:00 Sweeper 84.2 2401 60 0.647502509823766 1.10626925406747
1109 2025-06-16 00:00:00 Sweeper 88.3 2488 67 1.425338067285291 1.8556138135588724
1140 2025-06-16 00:00:00 4-Seam Fastball 99.9 2304 212 1.2226062133202922 2.678904548991989
1201 2025-06-16 00:00:00 Sweeper 88.4 2547 63 1.894538654310912 2.504215080587014
1217 2025-06-16 00:00:00 Sweeper 86.5 2415 65 1.0148383942928958 2.3959374827012003
1277 2025-06-16 00:00:00 4-Seam Fastball 99.1 2198 205 0.9475308927840076 2.533982824652222
1317 2025-06-16 00:00:00 Sweeper 86.5 2297 81 0.1952660023521355 2.9590111259633938
1349 2025-06-16 00:00:00 Sinker 98.0 2009 225 0.3308838098056545 1.4767999079905925
1409 2025-06-16 00:00:00 Sweeper 88.8 2416 76 -0.3789707947839915 2.535471839978888
1439 2025-06-16 00:00:00 4-Seam Fastball 100.2 2254 215 1.2500478642410382 1.097568978614687
1480 2025-06-16 00:00:00 Sinker 97.4 2073 225 0.7520744472146288 1.7166070206434445
1549 2025-06-16 00:00:00 4-Seam Fastball 98.6 2226 215 1.500700400182047 1.4361960267355165
1588 2025-06-16 00:00:00 4-Seam Fastball 98.3 2379 197 2.041691681232951 0.7763450125428075
1647 2025-06-16 00:00:00 4-Seam Fastball 99.1 2129 220 -0.4071909593778892 2.591659938309836
1715 2025-06-16 00:00:00 4-Seam Fastball 98.3 2223 219 0.2217549381264476 2.771397696101639
1747 2025-06-16 00:00:00 Sinker 98.5 2029 237 1.5440339506822047 0.3177014080011138
1799 2025-06-16 00:00:00 Sweeper 86.4 2417 74 -0.1057505305017472 1.635473110938701
1882 2025-06-16 00:00:00 Sinker 98.1 2004 214 -0.0472076008891768 1.4784173660575293
1900 2025-06-16 00:00:00 Sinker 97.6 2063 219 -0.2184482079188072 2.3561262521570008

pitcher は投手のMLB Player IDです。名前の文字列ではなくIDをキーにすることで、集計対象を明確にできます。

期間を指定して特定投手のデータを直接取得したい場合は、statcast_pitcher() も利用できます。

from pybaseball import statcast_pitcher

pitcher_data = statcast_pitcher(
    start_dt="2025-06-01",
    end_dt="2025-06-30",
    player_id=660271
)

display(pitcher_data.head())

出力結果

index pitch_type game_date release_speed release_pos_x release_pos_z player_name batter pitcher events description spin_dir spin_rate_deprecated break_angle_deprecated break_length_deprecated zone des game_type stand p_throws home_team
0 ST 2025-06-28 82.6 -2.45 5.48 Ohtani, Shohei 679845 660271 field_out hit_into_play NaN NaN NaN NaN 6 Nick Loftin pops out to catcher Dalton Rushing in foul territory. R R R KC
1 FF 2025-06-28 98.6 -2.19 5.68 Ohtani, Shohei 679845 660271 NaN foul NaN NaN NaN NaN 12 NaN R R R KC
2 SL 2025-06-28 87.9 -2.03 6.08 Ohtani, Shohei 679845 660271 NaN foul NaN NaN NaN NaN 13 NaN R R R KC
3 SL 2025-06-28 87.5 -2.04 6.04 Ohtani, Shohei 679845 660271 NaN swinging_strike NaN NaN NaN NaN 14 NaN R R R KC
4 ST 2025-06-28 81.7 -2.38 5.69 Ohtani, Shohei 679845 660271 NaN called_strike NaN NaN NaN NaN 14 NaN R R R KC

660271 はコード例です。実際に分析する投手のMLB Player IDに置き換えてください。選手IDは playerid_lookup() で調べられます。

from pybaseball import playerid_lookup

playerid_lookup("ohtani", "shohei")

出力結果

index name_last name_first key_mlbam key_retro key_bbref key_fangraphs mlb_played_first mlb_played_last
0 ohtani shohei 660271 ohtas001 ohtansh01 19755 2018.0 2026.0

15. 球種ごとの球速・回転数・変化量をまとめる

複数の指標をまとめて集計すると、球種ごとの特徴を比較しやすくなります。

pitch_summary = (
    game_data
    .groupby("pitch_name", dropna=False)
    .agg(
        pitches=("pitch_name", "size"),
        avg_speed=("release_speed", "mean"),
        avg_spin=("release_spin_rate", "mean"),
        avg_extension=("release_extension", "mean"),
        avg_pfx_x=("pfx_x", "mean"),
        avg_pfx_z=("pfx_z", "mean")
    )
    .reset_index()
    .sort_values("pitches", ascending=False)
)

display(pitch_summary)

出力結果

index pitch_name pitches avg_speed avg_spin avg_extension avg_pfx_x avg_pfx_z
0 4-Seam Fastball 616 95.23863636363636 2370.6347402597403 6.567532467532468 -0.43576298701298705 1.3729220779220779
6 Slider 307 86.89869706840392 2480.889250814332 6.429967426710098 0.22019543973941366 0.1557328990228013
5 Sinker 306 95.159477124183 2196.843137254902 6.455555555555556 -0.8955882352941177 0.5690196078431373
3 Cutter 219 89.36164383561645 2526.7397260273974 6.358447488584475 0.22095890410958904 0.6886301369863014
1 Changeup 144 86.46805555555555 2031.0972222222222 6.430555555555555 -1.0579166666666666 0.3015277777777778
9 Sweeper 143 81.07062937062938 2570.5384615384614 6.353846153846154 0.6359440559440559 0.044125874125874126
2 Curveball 116 80.78017241379311 2677.137931034483 6.314655172413793 0.7072413793103449 -0.8094827586206897
4 Knuckle Curve 58 84.64827586206897 2716.4310344827586 6.362068965517241 0.8550000000000001 -1.0636206896551723
8 Split-Finger 40 84.77000000000001 1022.4 6.8675 -0.46575 0.05700000000000001
10 NaN 10 <NA> <NA> <NA> <NA> <NA>
7 Slurve 1 82.8 2821 6.5 0.93 -0.89

ここでは球種ごとに投球数、平均球速、平均回転数、平均エクステンション、平均変化量に関係する値をまとめています。

これは指定期間の集計です。投手同士を比較する場合は、同じ期間・同じ条件でデータを取得し、投球数も合わせて確認しましょう。


まとめ

今回は、pybaseball を使ってMLBのStatcastデータから投球単位のデータを取得し、主な項目を確認する方法を解説しました。

本記事で扱った代表的な項目は次のとおりです。

列名 内容
pitch_type 球種コード
pitch_name 球種名
release_speed 投球速度
release_spin_rate 回転数
spin_axis 回転軸に関する項目
release_pos_x / release_pos_z リリース位置
release_extension リリースエクステンション
pfx_x / pfx_z 変化に関係する項目
plate_x / plate_z ホームプレート付近の投球位置
balls / strikes 投球時点のボール・ストライクカウント
description その1球の結果・説明
events 打席の結果

球速、回転数、変化量、リリース位置、投球位置、カウント、球種などを組み合わせ、対象期間と投球数を確認しながら分析しましょう。

ここまでの基礎編を通して、シーズン・試合・打席・1球・打球・投球という異なる粒度でStatcastデータを扱う流れを学びました。これらを組み合わせることで、より具体的なMLBデータ分析へ進めます。


データ参照元

本記事では、MLBのStatcastデータをpybaseballから取得して分析しています。

Statcastのデータ項目については、MLBのBaseball Savantが公開しているStatcast Search CSV Documentationを参照しています。

Statcast Search CSV Documentation

pybaseballは、Baseball SavantのStatcastデータをPythonから取得し、pandasのDataFrameとして扱いやすくするためのライブラリです。

pybaseball GitHub

データを利用・公開する場合は、利用元の最新の規約、データ提供条件、各サービスの利用条件を確認してください。


動作環境

本記事ではGoogle Colabを利用しています。

Pythonとpybaseballのバージョンは、以下のコードで確認できます。

import sys
import pybaseball

print("Python:", sys.version)
print("pybaseball:", pybaseball.__version__)

出力結果

Python: 3.13.15 (main, Aug  6 2026, 11:06:22) [GCC 13.3.0]
pybaseball: 2.2.7

Hugo で構築されています。
テーマ Stack は Jimmy によって設計されています。