記事のシリーズ
- pybaseball基礎編① pybaseballのライブラリの概要とインストール
- pybaseball基礎編② シーズン単位のデータ取得
- pybaseball基礎編③ 試合単位のデータ取得
- pybaseball基礎編④ 打席単位のデータ取得
- pybaseball基礎編⑤ 1球単位のデータ取得
- pybaseball基礎編⑥ 打球単位のデータ取得
- 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 は、打者のストライクゾーン上端・下端に関係する項目です。打者によってストライクゾーンの高さが異なるため、投球位置を分析するときはこれらの値も参考になります。

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()


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として扱いやすくするためのライブラリです。
データを利用・公開する場合は、利用元の最新の規約、データ提供条件、各サービスの利用条件を確認してください。
動作環境
本記事では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