記事のシリーズ
- pybaseball基礎編① pybaseballのライブラリの概要とインストール
- pybaseball基礎編② シーズン単位のデータ取得
- pybaseball基礎編③ 試合単位のデータ取得
- pybaseball基礎編④ 打席単位のデータ取得
- pybaseball基礎編⑤ 1球単位のデータ取得【本記事】
- pybaseball基礎編⑥ 打球単位のデータ取得(予定)
- 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として扱いやすくするためのライブラリです。
データを利用・公開する場合は、利用元の最新の規約、データ提供条件、各サービスの利用条件を確認してください。
動作環境
本記事では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