Skip to main content

baseball-field-viz

A Python library for drawing baseball fields and spray charts from Statcast data.

Companion to savant-extras — together they provide a seamless MLB analysis pipeline.

Installation

pip install baseball-field-viz

Why this library?

pybaseball's built-in spraychart() doesn't support overlaying heatmaps (e.g. seaborn kdeplot). This library gives you a matplotlib Axes object directly, so you can layer any plot on top.

from baseball_field_viz import draw_field
import seaborn as sns

fig, ax = plt.subplots(figsize=(10, 10))
draw_field(ax)
sns.kdeplot(data=df_t, x="x", y="y", ax=ax, cmap="Reds", fill=True, alpha=0.6)

Quick Start

import matplotlib.pyplot as plt
from baseball_field_viz import draw_field, spraychart, transform_coords

# Spray chart (one-liner)
fig, ax = plt.subplots(figsize=(10, 10))
spraychart(ax, df, color_by="events", title="Ohtani 2025 - Batted Balls")
plt.show()

Functions

transform_coords(df)

Converts Statcast hc_x/hc_y (screen pixel coordinates) to real field coordinates in feet (home plate as origin).

from baseball_field_viz import transform_coords

df_t = transform_coords(df)
# Adds 'x', 'y' columns (feet)
# x = 2.5 * (hc_x - 125.42)   ← left/right (positive = right)
# y = 2.5 * (198.27 - hc_y)   ← depth (positive = outfield)

draw_field(ax, foul_distance=330, outfield_distance=340)

Draw a baseball field on a matplotlib Axes.

from baseball_field_viz import draw_field

fig, ax = plt.subplots(figsize=(10, 10))
draw_field(ax)
# Now overlay any plot: scatter, kdeplot, histplot, etc.

spraychart(ax, df, color_by="events", title=None)

Draw a spray chart in one call. Internally calls transform_coords and draw_field.

from baseball_field_viz import spraychart

fig, ax = plt.subplots(figsize=(10, 10))
spraychart(ax, df, color_by="events", title="Player - Season")
plt.show()

color_by options:

Value Effect
"events" (default) home_run=red, triple=orange, double=blue, single=green, other=gray
Any column name Categorical coloring with auto palette

Full Example with Heatmap

from pybaseball import statcast
import duckdb
import matplotlib.pyplot as plt
import seaborn as sns
from baseball_field_viz import draw_field, transform_coords

# Fetch data
df_raw = statcast(start_dt="2025-03-01", end_dt="2025-10-31")
con = duckdb.connect()
df = con.execute("""
    SELECT * FROM df_raw
    WHERE batter = 660271  -- mlbam_id (e.g. Shohei Ohtani = 660271)
      AND hc_x IS NOT NULL AND hc_y IS NOT NULL
      AND game_type = 'R'
""").df()

df_t = transform_coords(df)

# Hits vs outs heatmap
fig, axs = plt.subplots(1, 2, figsize=(16, 8))

hits = df_t[df_t["events"].isin(["home_run", "double", "triple", "single"])]
outs = df_t[~df_t["events"].isin(["home_run", "double", "triple", "single"])]

draw_field(axs[0])
sns.kdeplot(data=hits, x="x", y="y", ax=axs[0], cmap="Reds", fill=True, alpha=0.6)
axs[0].set_xlim(-350, 350); axs[0].set_ylim(-50, 400)
axs[0].set_title("Hits Heatmap")

draw_field(axs[1])
sns.kdeplot(data=outs, x="x", y="y", ax=axs[1], cmap="Blues", fill=True, alpha=0.6)
axs[1].set_xlim(-350, 350); axs[1].set_ylim(-50, 400)
axs[1].set_title("Outs Heatmap")

plt.tight_layout()
plt.show()

Strike Zone (v0.2.0)

draw_strike_zone(ax, sz_top=3.5, sz_bot=1.5, color="black", lw=2)

Draw a strike zone rectangle on a matplotlib Axes (plate_x / plate_z coordinate system).

from baseball_field_viz import draw_strike_zone

fig, ax = plt.subplots(figsize=(6, 6))
draw_strike_zone(ax, sz_top=3.5, sz_bot=1.5)

pitch_zone_chart(ax, df, color_by="pitch_type", sz_top=None, sz_bot=None, title=None)

Plot pitch locations with strike zone overlay. Uses plate_x / plate_z columns (Statcast standard). If sz_top/sz_bot are not provided, the mean of df["sz_top"]/df["sz_bot"] is used automatically.

from pybaseball import statcast
from baseball_field_viz import pitch_zone_chart

df = statcast(start_dt="2025-03-01", end_dt="2025-10-31")
df_pitcher = df[df["pitcher"] == 592789]  # mlbam_id: Yoshinobu Yamamoto

fig, ax = plt.subplots(figsize=(6, 6))
pitch_zone_chart(ax, df_pitcher, color_by="pitch_type", title="Yamamoto 2025 - Pitch Locations")
plt.show()

The strike zone is automatically sized from per-pitch Hawk-Eye measurements (sz_top/sz_bot) in the Statcast data, reflecting each batter's actual stance.

Requirements

  • Python 3.9+
  • matplotlib >= 3.5
  • numpy >= 1.21
  • pandas >= 1.3

License

MIT

Metadata

Release files for baseball-field-viz 0.2.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for baseball-field-viz 0.2.5
File Size Uploaded
baseball_field_viz-0.2.5.tar.gz 8.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for baseball-field-viz 0.2.5
File Interpreter ABI Platform
baseball_field_viz-0.2.5-py3-none-any.whl Python 3 none any Details

Total release size: 16.9 kB

Release files / baseball_field_viz-0.2.5.tar.gz

Download URL baseball_field_viz-0.2.5.tar.gz
Size 8.4 kB
Tags Source
SHA-256 checksum
How to use checksums
a775a50e8ab0e370b962a2af86e93cd02e7df09a549cd74d1934d94b1c4a1871
BLAKE2b-256 checksum
How to use checksums
a4e5b1f90ffc6ba1c31e63d731966d8825dc5fe7a23b146b97ce1eaa5c4412c2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release files / baseball_field_viz-0.2.5-py3-none-any.whl

Download URL baseball_field_viz-0.2.5-py3-none-any.whl
Size 8.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d502187d335e067857dc409d5fc84c223175cae9a315eab8a516716ae2d1e374
BLAKE2b-256 checksum
How to use checksums
d90c720f79fda30ddf5a4b2551f0990e20fb376d3057871570bc51cda49cdf54
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 8, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.5 This release

2 release files

0.2.4

2 release files

0.2.3

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page