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

【pybaseball基礎編⑤】1球単位のデータ取得

pybaseballを使ってMLBのStatcastデータを1球単位で読み解く方法を解説します。投球結果、球種、球速、カウント、投球位置などをpandasで確認する方法を、Google Colabの実例を使って紹介します。

記事のシリーズ

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

はじめに

今回は、1球単位のデータを見ていきます。

Statcastでは、基本的に1球の投球が1行のデータとして記録されています。そのため1球単位まで理解すると、球種、球速、カウント、投球結果、投球位置などを細かく分析できます。

この記事ではGoogle Colab上で pybaseballを使い、1球を取り出す方法から、球種・球速・カウント・投球位置の確認、条件を組み合わせた抽出までを順番に解説します。


1. 「1球」とは?

Statcastでは pitch_number によって、1打席の中で何球目なのかを確認できます。

例えば次のようなデータなら、at_bat_number = 53 の同じ打席で、1~4球目が記録されています。


at_bat_number    pitch_number pitch_name     release_speed description

           53               1 Slider                  88.7 swinging_strike

           53               2 Sinker                  96.1 called_strike

           53               3 Slider                  88.8 blocked_ball

           53               4 Sinker                  95.4 swinging_strike

つまり、基本的には、

1行 = 1球

と考えるとStatcastデータを理解しやすくなります。

MLBの公式CSVドキュメントでは、

  • at_bat_numberは試合内の打席番号、
  • pitch_numberはその打席内の投球番号

と定義されています。

pybaseballの statcast()も、取得範囲の各投球を1行ずつ持つDataFrameを返します。


2. Statcastデータを取得する

例として2025年6月16日のデータを取得します。

!pip install -q git+https://github.com/jldbc/pybaseball.git

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
1449 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
1488 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
1555 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
1577 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
1620 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

3. 1球分のデータを取り出す

先頭の1行をDataFrameとして取り出すなら iloc[[0]] を使えます。

one_pitch = game_data.iloc[[0]].copy()
display(one_pitch)

出力結果

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
1449 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

4. 1球に必要な項目だけ表示する

pitch_columns = [
    "game_date",
    "inning",
    "inning_topbot",
    "at_bat_number",
    "pitch_number",
    "pitch_name",
    "release_speed",
    "release_spin_rate",
    "balls",
    "strikes",
    "type",
    "description",
    "events"
]

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

出力結果

index game_date inning inning_topbot at_bat_number pitch_number pitch_name release_speed release_spin_rate balls strikes type description events
1449 2025-06-16 00:00:00 9 Top 73 3 4-Seam Fastball 93.5 2309 0 2 X hit_into_play field_out
1488 2025-06-16 00:00:00 9 Top 73 2 Split-Finger 86.7 1351 0 1 S called_strike NaN
1555 2025-06-16 00:00:00 9 Top 73 1 4-Seam Fastball 93.0 2326 0 0 S foul NaN
1577 2025-06-16 00:00:00 9 Top 72 4 Split-Finger 85.9 1254 0 2 X hit_into_play field_out
1620 2025-06-16 00:00:00 9 Top 72 3 4-Seam Fastball 93.4 2154 0 2 S foul NaN
1709 2025-06-16 00:00:00 9 Top 72 2 4-Seam Fastball 94.0 2359 0 1 S foul NaN
1734 2025-06-16 00:00:00 9 Top 72 1 4-Seam Fastball 93.3 2176 0 0 S foul NaN
1829 2025-06-16 00:00:00 9 Top 71 3 Split-Finger 86.0 1375 1 1 X hit_into_play field_out
1850 2025-06-16 00:00:00 9 Top 71 2 4-Seam Fastball 93.2 2280 0 1 B ball NaN
1942 2025-06-16 00:00:00 9 Top 71 1 4-Seam Fastball 93.0 2213 0 0 S called_strike NaN
848 2025-06-16 00:00:00 8 Bot 70 1 4-Seam Fastball 95.3 2301 0 0 X hit_into_play field_out
870 2025-06-16 00:00:00 8 Bot 69 5 Slider 87.1 2225 3 1 B ball walk
877 2025-06-16 00:00:00 8 Bot 69 4 Curveball 84.1 2730 2 1 B ball NaN
898 2025-06-16 00:00:00 8 Bot 69 3 4-Seam Fastball 97.3 2502 1 1 B ball NaN
922 2025-06-16 00:00:00 8 Bot 69 2 4-Seam Fastball 96.4 2458 0 1 B ball NaN
951 2025-06-16 00:00:00 8 Bot 69 1 Curveball 83.2 2609 0 0 S swinging_strike NaN
960 2025-06-16 00:00:00 8 Bot 68 1 Slider 87.1 2332 0 0 X hit_into_play field_out
983 2025-06-16 00:00:00 8 Bot 67 5 4-Seam Fastball 96.4 2465 1 2 X hit_into_play field_out
1010 2025-06-16 00:00:00 8 Bot 67 4 Curveball 83.4 2686 1 2 S foul NaN
1047 2025-06-16 00:00:00 8 Bot 67 3 4-Seam Fastball 97.1 2446 0 2 B ball NaN

5. pitch_name で球種を見る

pitch_name はStatcastで判定された球種名です。球種ごとの投球数は 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

ここで注意したいのは、1日分の全MLBデータを取得している場合、この集計は特定の投手ではなく、取得した全投球を対象にしている点です。

投手を限定する場合は、MLB Player IDで絞り込みます。

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

display(pitcher_data["pitch_name"].value_counts())

出力結果

pitch_name count
Sweeper 10
4-Seam Fastball 9
Sinker 8
Split-Finger 1

6. release_speed で球速を見る

release_speed は投球速度で、単位はmphです。

例えばスライダーだけを取り出します。

slider_data = game_data[
    game_data["pitch_name"] == "Slider"
].copy()

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

出力結果

index pitch_name release_speed release_spin_rate
870 Slider 87.1 2225
960 Slider 87.1 2332
1684 Slider 88.2 2656
1865 Slider 90.0 2669
1136 Slider 86.9 2335
1210 Slider 87.6 2324
1323 Slider 88.2 2318
1688 Slider 87.4 2319
1735 Slider 87.7 2343
1941 Slider 87.7 2265
1127 Slider 88.3 2283
1145 Slider 86.9 2316
1406 Slider 88.8 2215
1493 Slider 88.7 2149
1835 Slider 84.2 2379
1 Slider 91.4 2952
31 Slider 91.2 2796
34 Slider 90.4 2663
59 Slider 90.5 2861
96 Slider 90.8 2698

平均球速も計算できます。

slider_avg = slider_data["release_speed"].mean()
print(f"Slider平均球速: {slider_avg:.1f} mph")

出力結果

Slider平均球速: 86.9 mph

平均値だけでなく、球種別・カウント別に見ることで、投球の特徴をより具体的に調べられます。


7. description で1球の結果を見る

1球単位の分析では descriptionが重要です。

代表的な値には、

  • swinging_strike(空振り)
  • called_strike(見逃しストライク)
  • foul(ファウル)
  • ball(ボール)
  • blocked_ball(捕手がブロックした投球)
  • hit_into_play(インプレーになった打球)

などがあります。

空振りだけを取り出すなら、次のように書けます。

swinging_strikes = game_data[
    game_data["description"] == "swinging_strike"
].copy()

display(
    swinging_strikes[
        [
            "game_date",
            "pitch_name",
            "release_speed",
            "balls",
            "strikes",
            "description"
        ]
    ].head(20)
)

出力結果

index game_date pitch_name release_speed balls strikes description
951 2025-06-16 00:00:00 Curveball 83.2 0 0 swinging_strike
1142 2025-06-16 00:00:00 4-Seam Fastball 97.4 1 2 swinging_strike
1189 2025-06-16 00:00:00 4-Seam Fastball 98.2 1 1 swinging_strike
1653 2025-06-16 00:00:00 Curveball 83.3 2 2 swinging_strike
1574 2025-06-16 00:00:00 Cutter 93.8 0 2 swinging_strike
1618 2025-06-16 00:00:00 4-Seam Fastball 97.8 0 1 swinging_strike
1219 2025-06-16 00:00:00 Changeup 90.0 1 1 swinging_strike
1350 2025-06-16 00:00:00 Sinker 95.4 1 2 swinging_strike
1493 2025-06-16 00:00:00 Slider 88.7 0 0 swinging_strike
1510 2025-06-16 00:00:00 4-Seam Fastball 94.0 3 2 swinging_strike
1622 2025-06-16 00:00:00 4-Seam Fastball 93.1 2 1 swinging_strike
1727 2025-06-16 00:00:00 4-Seam Fastball 92.7 1 0 swinging_strike
42 2025-06-16 00:00:00 4-Seam Fastball 99.3 3 2 swinging_strike
84 2025-06-16 00:00:00 4-Seam Fastball 98.0 0 0 swinging_strike
644 2025-06-16 00:00:00 Sweeper 87.0 0 2 swinging_strike
173 2025-06-16 00:00:00 4-Seam Fastball 97.8 2 2 swinging_strike
300 2025-06-16 00:00:00 Slider 91.6 0 0 swinging_strike
346 2025-06-16 00:00:00 4-Seam Fastball 98.5 0 0 swinging_strike
479 2025-06-16 00:00:00 4-Seam Fastball 96.4 0 0 swinging_strike
697 2025-06-16 00:00:00 Sweeper 84.3 3 2 swinging_strike

events との違い

description はその1球の結果、eventsはその打席の結果を見るための項目です。

例えば、4球で三振になった打席なら、途中の投球では events = NaNでも、最後の投球では events = strikeout になることがあります。

したがって、eventsが空欄だからといって「ファウルだった」「ボールだった」と判断することはできません。1球の結果を知りたい場合は、まずdescription や type を確認しましょう。


8. type でボール・ストライク・インプレーを見る

Statcastの type は投球結果の大分類で、B = Ball、S = Strike、X = In Play です。

strikes_data = game_data[
    game_data["type"] == "S"
].copy()

display(
    strikes_data[
        [
            "pitch_name",
            "release_speed",
            "balls",
            "strikes",
            "description"
        ]
    ].head(20)
)

出力結果

index pitch_name release_speed balls strikes description
1488 Split-Finger 86.7 0 1 called_strike
1555 4-Seam Fastball 93.0 0 0 foul
1620 4-Seam Fastball 93.4 0 2 foul
1709 4-Seam Fastball 94.0 0 1 foul
1734 4-Seam Fastball 93.3 0 0 foul
1942 4-Seam Fastball 93.0 0 0 called_strike
951 Curveball 83.2 0 0 swinging_strike
1010 Curveball 83.4 1 2 foul
1067 4-Seam Fastball 96.7 0 1 foul
1090 4-Seam Fastball 97.5 0 0 foul
1615 4-Seam Fastball 97.4 0 2 foul_tip
1684 Slider 88.2 0 1 called_strike
1751 4-Seam Fastball 97.7 0 0 called_strike
1865 Slider 90.0 0 1 foul
1921 4-Seam Fastball 96.9 0 0 foul
1142 4-Seam Fastball 97.4 1 2 swinging_strike
1189 4-Seam Fastball 98.2 1 1 swinging_strike
1269 Curveball 82.3 0 0 foul
1323 Slider 88.2 2 2 foul
1414 4-Seam Fastball 96.6 1 2 foul

description の細かな種類をまとめて「ストライク系の投球」として確認したいときに便利です。


9. カウントごとの投球を見る

balls と strikes は、その投球が行われる直前のカウントです。

count_data = game_data[
    [
        "at_bat_number",
        "pitch_number",
        "pitch_name",
        "balls",
        "strikes",
        "description"
    ]
].copy()

display(count_data.head(30))

出力結果

index at_bat_number pitch_number pitch_name balls strikes description
1449 73 3 4-Seam Fastball 0 2 hit_into_play
1488 73 2 Split-Finger 0 1 called_strike
1555 73 1 4-Seam Fastball 0 0 foul
1577 72 4 Split-Finger 0 2 hit_into_play
1620 72 3 4-Seam Fastball 0 2 foul
1709 72 2 4-Seam Fastball 0 1 foul
1734 72 1 4-Seam Fastball 0 0 foul
1829 71 3 Split-Finger 1 1 hit_into_play
1850 71 2 4-Seam Fastball 0 1 ball
1942 71 1 4-Seam Fastball 0 0 called_strike
848 70 1 4-Seam Fastball 0 0 hit_into_play
870 69 5 Slider 3 1 ball
877 69 4 Curveball 2 1 ball
898 69 3 4-Seam Fastball 1 1 ball
922 69 2 4-Seam Fastball 0 1 ball
951 69 1 Curveball 0 0 swinging_strike
960 68 1 Slider 0 0 hit_into_play
983 67 5 4-Seam Fastball 1 2 hit_into_play
1010 67 4 Curveball 1 2 foul
1047 67 3 4-Seam Fastball 0 2 ball
1067 67 2 4-Seam Fastball 0 1 foul
1090 67 1 4-Seam Fastball 0 0 foul
1568 66 1 4-Seam Fastball 0 0 hit_into_play
1615 65 3 4-Seam Fastball 0 2 foul_tip
1684 65 2 Slider 0 1 called_strike
1751 65 1 4-Seam Fastball 0 0 called_strike
1783 64 3 4-Seam Fastball 0 2 hit_into_play
1865 64 2 Slider 0 1 foul
1921 64 1 4-Seam Fastball 0 0 foul
1136 63 1 Slider 0 0 hit_into_play

例えば2ストライクから投げられた球だけなら、

two_strike_pitches = game_data[
    game_data["strikes"] == 2
].copy()

display(
    two_strike_pitches[
        [
            "pitch_name",
            "release_speed",
            "balls",
            "strikes",
            "description",
            "events"
        ]
    ].head(20)
)

出力結果

index pitch_name release_speed balls strikes description events
1449 4-Seam Fastball 93.5 0 2 hit_into_play field_out
1577 Split-Finger 85.9 0 2 hit_into_play field_out
1620 4-Seam Fastball 93.4 0 2 foul NaN
983 4-Seam Fastball 96.4 1 2 hit_into_play field_out
1010 Curveball 83.4 1 2 foul NaN
1047 4-Seam Fastball 97.1 0 2 ball NaN
1615 4-Seam Fastball 97.4 0 2 foul_tip strikeout
1783 4-Seam Fastball 97.0 0 2 hit_into_play field_out
1142 4-Seam Fastball 97.4 1 2 swinging_strike strikeout
1308 4-Seam Fastball 98.3 2 2 hit_into_play single
1323 Slider 88.2 2 2 foul NaN
1408 Curveball 81.7 1 2 ball NaN
1414 4-Seam Fastball 96.6 1 2 foul NaN
1653 Curveball 83.3 2 2 swinging_strike strikeout
1688 Slider 87.4 2 2 foul NaN
1735 Slider 87.7 1 2 ball NaN
1452 Cutter 92.8 0 2 hit_into_play single
1574 Cutter 93.8 0 2 swinging_strike strikeout
1194 Changeup 88.7 1 2 hit_into_play field_out
1350 Sinker 95.4 1 2 swinging_strike strikeout

これだけでも、「2ストライクでどの球種を選びやすいか」という分析につなげられます。


10. 1球の投球位置を見る

代表的な項目が plate_x、plate_z、zone です。

location_data = game_data[
    [
        "pitch_name",
        "plate_x",
        "plate_z",
        "zone",
        "description"
    ]
].copy()

display(location_data.head(20))

出力結果

index pitch_name plate_x plate_z zone description
1449 4-Seam Fastball -0.1236243215667922 3.256601567619987 2 hit_into_play
1488 Split-Finger -0.2766508753703635 1.350055118752824 13 called_strike
1555 4-Seam Fastball 0.3305527045795539 2.8401170752894838 3 foul
1577 Split-Finger 0.2986480050714284 1.359024034526068 9 hit_into_play
1620 4-Seam Fastball -0.0399089501307835 3.4196585344346118 11 foul
1709 4-Seam Fastball -0.1408786429155342 3.4708462830100206 11 foul
1734 4-Seam Fastball 0.0297634899928171 2.258311964976208 5 foul
1829 Split-Finger 0.370591581027924 1.3671351605609767 14 hit_into_play
1850 4-Seam Fastball 1.2552807496902143 2.799829922368259 12 ball
1942 4-Seam Fastball 0.321323719692403 2.55692342942573 6 called_strike
848 4-Seam Fastball -0.393615151383056 3.0147805374936176 1 hit_into_play
870 Slider -2.240474406898455 2.094120206332674 13 ball
877 Curveball 2.060671564552802 0.8537048319164913 14 ball
898 4-Seam Fastball -0.4583749270656911 3.9101669562079975 11 ball
922 4-Seam Fastball -0.1567795340519512 4.236033624652384 11 ball
951 Curveball 0.7604812299790533 1.1388906812474766 14 swinging_strike
960 Slider 0.092538646095987 1.3888309414066669 14 hit_into_play
983 4-Seam Fastball -0.5381142567806368 2.2198248180781737 4 hit_into_play
1010 Curveball 0.4049509964475222 1.7151008643813692 9 foul
1047 4-Seam Fastball 0.4916163852080379 4.131930739456839 12 ball

plate_x は捕手側から見たホームプレート通過時の横方向の位置、plate_z は縦方向の位置、zone は投球ゾーンを表します。

2026年以降の注意点

Baseball Savantの公式ドキュメントでは、plate_x と plate_z の定義について2026年から変更があると説明されています。2025年まではプレート手前側を基準としていましたが、2026年以降はABS(Automated Ball-Strike)システムとの整合を取るため、プレート中央付近を基準とする定義になっています。

そのため、2025年以前と2026年以降の投球位置を比較するときは、定義変更を意識する必要があります。過去データと現在データを完全に同じ基準だと考えて単純比較しないようにしましょう。


11. 1球単位の分析でよく使う項目

列名 意味 主な用途
game_pk 試合ID 試合の識別
at_bat_number 試合内の打席番号 打席の識別
pitch_number 打席内の投球番号 何球目かを確認
pitch_name 球種名 球種分析
release_speed 投球速度(mph) 球速分析
release_spin_rate 投球の回転数 スピン分析
balls 投球前のボール数 カウント分析
strikes 投球前のストライク数 カウント分析
type Ball / Strike / In Play 大分類
description その投球の結果 空振り・ファウルなど
events 打席の結果 三振・安打など
plate_x 横方向の投球位置 コース分析
plate_z 縦方向の投球位置 コース分析
zone 投球ゾーン ゾーン分析
launch_speed 打球速度 打球分析
launch_angle 打球角度 打球分析

12. 1球単位のデータからできる分析

1球単位まで細かくすると、分析テーマが大きく広がります。

投手側

  • 球種ごとの投球割合
  • 球種ごとの平均球速
  • 球種ごとの回転数
  • 初球に投げる球種
  • カウント別の球種選択
  • 2ストライクから選択する球種
  • 空振りを取った球種
  • ストライクゾーン内外の投球割合

打者側

  • どの球種を多く見ているか
  • どの球種で空振りが多いか
  • 初球への反応
  • 2ストライク後の結果
  • 球速帯別の反応
  • コース別の反応

投手と打者の組み合わせ

「特定の投手が特定の打者に対して、どの球種をどのカウントで投げたか」という対戦分析も可能です。


まとめ

今回は pybaseballを使ってMLBのStatcastデータを1球単位で取得・確認する方法を解説しました。

ポイントは次のとおりです。

  • Statcastでは基本的に1球の投球が1行として記録される
  • pitch_number で打席内の何球目かを確認できる
  • pitch_name で球種を確認できる
  • release_speed で球速を確認できる
  • balls と strikes で投球前のカウントを確認できる
  • type でBall・Strike・In Playの大分類を確認できる
  • description でその1球の結果を確認できる
  • events は打席全体の結果を表すため、打席途中では空欄になることがある
  • plate_x、plate_z、zone を使うと投球位置も分析できる
  • 2026年以降は plate_x・plate_z の定義変更に注意が必要
  • 長期間のデータを取得するとデータ量が大きくなるため、最初は短い期間から試すと扱いやすい

1球単位まで扱えるようになると、単なる選手成績の確認から、「どの球種を、どのカウントで、どのコースに投げたのか?」という、より具体的な投球分析へ進めます。


次回の記事

次回は、

【pybaseball基礎編⑥】打球単位のデータ取得

を予定しています。

基礎編⑤では投球そのものを1球ずつ確認しました。基礎編⑥では、

  • 打球速度(launch_speed)
  • 打球角度(launch_angle)
  • 打球距離
  • ゴロ・ライナー・フライなどの打球タイプ
  • どのような打球が発生したのか

といった、バットに当たった後のデータを中心に見ていきます。


データ参照元

本記事では、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 によって設計されています。