Featured image of post 【pybaseball基礎編④】打席単位のデータ取得

【pybaseball基礎編④】打席単位のデータ取得

pybaseballを使ってMLBのStatcastデータを打席単位で読み解く方法を解説します。

記事のシリーズ

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

はじめに

前回の「pybaseball基礎編③」では、game_pkを使ってMLBの1試合分のStatcastデータを取得しました。

Statcastのデータは基本的に1球につき1行で記録されています。

そのため、1打席を分析するには、複数の投球データを「同じ打席」という単位でまとめて考える必要があります。

そこで今回は、at_bat_numberとpitch_numberを中心に、打席単位でStatcastデータを見る方法を解説します。

この記事では、以下の内容を扱います。

  • 1球単位のデータと打席単位のデータの違い
  • at_bat_numberの意味
  • pitch_numberの意味
  • 1打席分の投球を取り出す方法
  • eventsとdescriptionの違い
  • 初球・最終球を確認する方法
  • 1打席に何球投げられたかを集計する方法
  • 大谷翔平選手の打席を例に分析する方法
  • 打席単位のデータ分析で注意したいポイント

1. Statcastデータは「1球=1行」

まず、前回の記事で取得したgame_dataを確認します。

from pybaseball import statcast_single_game

game_data = statcast_single_game(game_pk=777490)

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
202 FF 2025-06-16 00:00:00 93.5 -2.44 5.35 Wade, Tyler 642180 489446 field_out hit_into_play NaN NaN NaN NaN 2 Tyler Wade pops out to third baseman Max Muncy. R L R LAD
215 FS 2025-06-16 00:00:00 86.7 -2.47 5.17 Wade, Tyler 642180 489446 NaN called_strike NaN NaN NaN NaN 13 NaN R L R LAD
221 FF 2025-06-16 00:00:00 93.0 -2.46 5.21 Wade, Tyler 642180 489446 NaN foul NaN NaN NaN NaN 3 NaN R L R LAD
231 FS 2025-06-16 00:00:00 85.9 -2.39 5.18 Iglesias, Jose 578428 489446 field_out hit_into_play NaN NaN NaN NaN 9 Jose Iglesias lines out to shortstop Mookie Betts. R R R LAD
241 FF 2025-06-16 00:00:00 93.4 -2.35 5.38 Iglesias, Jose 578428 489446 NaN foul NaN NaN NaN NaN 11 NaN R R R LAD

Statcastのデータでは、基本的に1回の投球が1行として記録されています。

例えば、ある打席で3球投げられた場合、概念的には次のようになります。

at_bat_number pitch_number pitch_name description
10 1 4-Seam Fastball called_strike
10 2 Slider ball
10 3 Changeup hit_into_play

3行ありますが、これは3打席ではありません。

同じat_bat_numberなので、1つの打席に3球投げられたことを表しています。

Baseball SavantのStatcast CSV仕様では、at_bat_numberは「その試合における打席番号」、pitch_numberは「その打席における投球番号」と定義されています。(Statcast Search CSV Documentation)


2. at_bat_numberとは?

at_bat_numberは、その試合の中で何番目の打席かを表す番号です。

例えば、

at_bat_number = 25

なら、その試合の25番目の打席という意味です。

重要なのは、at_bat_numberが投球番号ではないことです。

同じ打席に5球投げられた場合、

at_bat_number
25
25
25
25
25

のように、5球すべて同じat_bat_numberになります。

一方、pitch_numberは、

1
2
3
4
5

となります。

つまり、

at_bat_number
    ↓
「何番目の打席?」

pitch_number
    ↓
「その打席の何球目?」

となります。


3. pitch_numberとは?

pitch_numberは、その打席の中で何球目に投じられた球なのかを表します。

例えば、

at_bat_number pitch_number pitch_name
12 1 4-Seam Fastball
12 2 Slider
12 3 Changeup
12 4 Curveball

なら、

12番目の打席で、4球の投球があった

ということになります。

4. 打席単位でデータを見る

例えば、大谷翔平選手の打撃データをohtani_dataとして取得しているとします。

まず、打席番号と投球番号を確認します。

OHTANI_ID = 660271

ohtani_data = game_data[
    game_data["batter"].astype(str) == str(OHTANI_ID)
].copy()

ohtani_pitches = (
    ohtani_data[
        [
            "game_date",
            "inning",
            "at_bat_number",
            "pitch_number",
            "pitch_name",
            "release_speed",
            "events",
            "description"
        ]
    ]
    .sort_values(
        ["at_bat_number", "pitch_number"]
    )
)

display(ohtani_pitches)

出力結果

index game_date inning at_bat_number pitch_number pitch_name release_speed events description
296 2025-06-16 00:00:00 1 6 1 Slider 89.0 NaN ball
286 2025-06-16 00:00:00 1 6 2 4-Seam Fastball 99.5 NaN called_strike
276 2025-06-16 00:00:00 1 6 3 Slider 90.6 NaN called_strike
266 2025-06-16 00:00:00 1 6 4 Slider 90.4 NaN ball
253 2025-06-16 00:00:00 1 6 5 Slider 91.4 NaN foul
239 2025-06-16 00:00:00 1 6 6 Slider 91.3 NaN ball
229 2025-06-16 00:00:00 1 6 7 Slider 92.4 strikeout swinging_strike
75 2025-06-16 00:00:00 3 22 1 Slider 92.1 NaN blocked_ball
73 2025-06-16 00:00:00 3 22 2 Slider 91.5 double hit_into_play
31 2025-06-16 00:00:00 4 36 1 4-Seam Fastball 99.5 NaN foul
30 2025-06-16 00:00:00 4 36 2 4-Seam Fastball 98.3 single hit_into_play
217 2025-06-16 00:00:00 6 53 1 Slider 88.7 NaN swinging_strike
208 2025-06-16 00:00:00 6 53 2 Sinker 96.1 NaN called_strike
195 2025-06-16 00:00:00 6 53 3 Slider 88.8 NaN blocked_ball
192 2025-06-16 00:00:00 6 53 4 Sinker 95.4 strikeout swinging_strike
121 2025-06-16 00:00:00 8 69 1 Curveball 83.2 NaN swinging_strike
118 2025-06-16 00:00:00 8 69 2 4-Seam Fastball 96.4 NaN ball
113 2025-06-16 00:00:00 8 69 3 4-Seam Fastball 97.3 NaN ball
111 2025-06-16 00:00:00 8 69 4 Curveball 84.1 NaN ball
107 2025-06-16 00:00:00 8 69 5 Slider 87.1 walk ball

ここでポイントになるのが、

.sort_values(
    ["at_bat_number", "pitch_number"]
)

です。

これは、

  1. 打席の順番
  2. その打席の中での投球順

という順番でデータを並べています。

Statcastのデータ取得処理でも、pybaseballはgame_date、game_pk、at_bat_number、pitch_numberなどを使ってデータを並べる処理を行っています。(pybaseball GitHub)


5. 1打席だけを取り出す

すべての打席を見ると行数が多くなるので、まず1つの打席だけ取り出してみましょう。

例えば、

target_at_bat = 53

one_pa = ohtani_data[
    ohtani_data["at_bat_number"] == target_at_bat
].copy()

display(
    one_pa[
        [
            "game_date",
            "inning",
            "at_bat_number",
            "pitch_number",
            "pitch_name",
            "release_speed",
            "events",
            "description"
        ]
    ].sort_values("pitch_number")
)

これで、指定した打席の投球だけを確認できます。

例えば、

index game_date inning at_bat_number pitch_number pitch_name release_speed events description
217 2025-06-16 00:00:00 6 53 1 Slider 88.7 NaN swinging_strike
208 2025-06-16 00:00:00 6 53 2 Sinker 96.1 NaN called_strike
195 2025-06-16 00:00:00 6 53 3 Slider 88.8 NaN blocked_ball
192 2025-06-16 00:00:00 6 53 4 Sinker 95.4 strikeout swinging_strike

のようなデータになります。

この場合、

  • 1球目:空振りストライク
  • 2球目:見逃しストライク
  • 3球目:捕手が止めたボール
  • 4球目:空振り三振

という流れを確認できます。


6. eventsとdescriptionの違い

打席分析では、eventsとdescriptionの違いを理解しておくことが重要です。

Baseball SavantのCSV仕様では、

  • events → 打席の結果
  • description → その投球の結果

として定義されています。(Statcast Search CSV Documentation)

例えば、

description = ball
description = called_strike
description = swinging_strike
description = hit_into_play

などは、その1球がどうなったかを見るための情報です。

一方、

events = single
events = double
events = home_run
events = strikeout
events = walk
events = field_out

などは、打席の最終的な結果を見るための情報です。


7. 打席の最後の球を取り出す

打席結果を確認するときには、最後の投球を見る方法も便利です。

例えば、

last_pitch = (
    one_pa
    .sort_values("pitch_number")
    .iloc[-1]
)

display(
    last_pitch[
        [
            "pitch_number",
            "pitch_name",
            "release_speed",
            "events",
            "description"
        ]
    ]
)

出力結果

pitch_number	4
pitch_name	Sinker
release_speed	95.4
events	strikeout
description	swinging_strike

これで、その打席の最後の投球を確認できます。

eventsに打席結果が記録されていれば、

single
double
home_run
strikeout
walk
field_out

など、打席の結果を確認できます。


8. 1打席に何球投げられたか調べる

次に、打席ごとの投球数を集計してみます。

ここではat_bat_numberごとに行数を数えます。

pa_pitch_count = (
    ohtani_data
    .groupby("at_bat_number")
    .size()
    .reset_index(name="pitch_count")
)

display(pa_pitch_count)

例えば、

index at_bat_number pitch_count
0 6 7
1 22 2
2 36 2
3 53 4
4 69 5

のようになります。

これだけでも、

「この試合では、何球粘った打席があったのか?」

といった分析ができます。


9. 打席結果も一緒にまとめる

さらに、打席ごとの最終結果をまとめてみましょう。

pa_summary = (
    ohtani_data
    .sort_values(["at_bat_number", "pitch_number"])
    .groupby("at_bat_number")
    .agg(
        pitch_count=("pitch_number", "count"),
        result=("events", "last")
    )
    .reset_index()
)

display(pa_summary)

例えば、

index at_bat_number pitch_count result
0 6 7 strikeout
1 22 2 double
2 36 2 single
3 53 4 strikeout
4 69 5 walk

のような表を作れます。

これによって、**「何球投げられた打席で、どんな結果になったのか」**を比較できます。


10. 初球を分析する

打席分析では、初球を見るのも面白いポイントです。

各打席のpitch_number == 1を抽出してみます。

first_pitches = (
    ohtani_data[
        ohtani_data["pitch_number"] == 1
    ]
    [
        [
            "game_date",
            "inning",
            "at_bat_number",
            "pitch_name",
            "release_speed",
            "description"
        ]
    ]
    .sort_values("at_bat_number")
)

display(first_pitches)

出力結果

index game_date inning at_bat_number pitch_name release_speed description
296 2025-06-16 00:00:00 1 6 Slider 89.0 ball
75 2025-06-16 00:00:00 3 22 Slider 92.1 blocked_ball
31 2025-06-16 00:00:00 4 36 4-Seam Fastball 99.5 foul
217 2025-06-16 00:00:00 6 53 Slider 88.7 swinging_strike
121 2025-06-16 00:00:00 8 69 Curveball 83.2 swinging_strike

これで、

大谷翔平選手の各打席で、初球にどんな球が投げられたのか

を確認できます。

さらに球種ごとに集計することもできます。

first_pitch_type = (
    ohtani_data[
        ohtani_data["pitch_number"] == 1
    ]
    .groupby("pitch_name")
    .size()
    .reset_index(name="count")
    .sort_values("count", ascending=False)
)

display(first_pitch_type)

出力結果

index pitch_name count
2 Slider 3
0 4-Seam Fastball 1
1 Curveball 1

11. 打席単位の分析で便利な列

ここまで使った列を整理しておきます。

列 意味 打席分析での用途
game_date 試合日 試合の識別
inning イニング 打席が発生した場面
at_bat_number 試合内の打席番号 打席の識別
pitch_number 打席内の投球番号 投球順の確認
pitch_name 球種 球種分析
release_speed 投球速度 球速分析
events 打席結果 打席結果の分析
description その投球の結果 ボール・ストライク・空振りなどの分析
launch_speed 打球速度 打球分析
launch_angle 打球角度 打球分析
hit_distance_sc Statcastによる推定飛距離 打球の飛距離分析

at_bat_numberとpitch_numberは、「打席」と「投球」を結びつけるための基本的な列です。


12. 打席単位で分析すると何が見えてくるのか

打席単位でデータを整理できるようになると、分析の幅が一気に広がります。

例えば、

初球の傾向

初球にストレートが多い
初球に変化球が多い

打席の長さ

平均何球で打席が終わるか
長い打席がどれくらいあるか

最終球

どの球種で打席が終わることが多いか

打席結果

single
double
home_run
walk
strikeout
field_out

などを調べられます。

さらに次のステップでは、

「その打席の中で、どの球に対してスイングしたのか?」

「どのカウントでどの球種が投げられたのか?」

「打球になった球の速度や角度はどうだったのか?」

といった、より細かい分析につなげられます。


13. 今回覚えておきたいコード

この記事のポイントをまとめると、まずは次のコードを覚えておけば十分です。

打席の投球順に並べる

ohtani_pitches = (
    ohtani_data
    .sort_values(
        ["at_bat_number", "pitch_number"]
    )
)

display(ohtani_pitches)

1打席を抽出する

target_at_bat = 53

one_pa = ohtani_data[
    ohtani_data["at_bat_number"] == target_at_bat
].sort_values("pitch_number")

display(one_pa)

打席ごとの投球数を数える

pa_pitch_count = (
    ohtani_data
    .groupby("at_bat_number")
    .size()
    .reset_index(name="pitch_count")
)

display(pa_pitch_count)

打席ごとの結果をまとめる

pa_summary = (
    ohtani_data
    .sort_values(["at_bat_number", "pitch_number"])
    .groupby("at_bat_number")
    .agg(
        pitch_count=("pitch_number", "count"),
        result=("events", "last")
    )
    .reset_index()
)

display(pa_summary)

まとめ

今回は、pybaseballで取得したStatcastデータを打席単位で分析する方法を紹介しました。

特に重要なのは、次の2つです。

at_bat_number
    ↓
その試合の何番目の打席か

pitch_number
    ↓
その打席の何球目か

Statcastのデータは1球=1行ですが、at_bat_numberを使うことで複数の投球を1つの打席としてまとめて見ることができます。

また、

events
    ↓
打席の結果

description
    ↓
その投球の結果

という違いも重要です。

この違いを理解すると、

「1打席で何球投げられたか」

「初球は何だったか」

「最後の球は何だったか」

「どの球種で打席が終わったか」

といった分析ができるようになります。

そして、複数試合をまとめて分析するときには、

groupby(["game_pk", "at_bat_number"])

のように、試合を識別するgame_pkと打席番号を組み合わせることも重要です。

ここまでできれば、単に「試合のデータを取得する」段階から、1つ1つの打席を時系列で分析する段階へ進めます。


次回の記事

次回の「pybaseball基礎編⑤」では、さらに細かく1球単位のデータ取得・分析に進みます。

例えば、

  • 1球ごとのボール・ストライクを確認する
  • 投球ごとの球種を調べる
  • 投球速度を比較する
  • pitch_numberを使って投球順を分析する
  • descriptionから投球結果を確認する
  • 1打席の中で投球がどのように変化したかを見る

といった内容を扱う予定です。

打席単位の分析から1球単位の分析へ進むことで、Statcastデータの構造をさらに詳しく理解できるようになります。


データ参照元

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