Skip to content

Instantly share code, notes, and snippets.

@nickwan
Created September 11, 2025 03:47
Show Gist options
  • Save nickwan/ce5caea600dacfe5f48c2911bfab0c1b to your computer and use it in GitHub Desktop.
Save nickwan/ce5caea600dacfe5f48c2911bfab0c1b to your computer and use it in GitHub Desktop.
Baseball Savant Pitch Trajectory a la Alan Nathan
def get_xz_at_y(row, y_target=0.0):
"""
Given a row of Baseball Savant pitch data and a target y (distance from plate),
return the (x, z) location of the pitch at that y.
Converted this via Gemini and the Alan Nathan spreadsheet
https://view.officeapps.live.com/op/view.aspx?src=https%3A%2F%2Fbaseball.physics.illinois.edu%2FTrajectoryCalculator-new-3D-May2021.xlsx&wdOrigin=BROWSELINK
If you're using pandas and numpy, you should be good to go. Sorry, I'm not good at dependencies and docs GIGGLECHAD
Parameters
----------
row : pd.Series
Must contain release_pos_x, release_pos_y, release_pos_z,
vx0, vy0, vz0, ax, ay, az.
y_target : float
Distance from plate in feet (default 0 = front of home plate).
Returns
-------
(x, z) : tuple of floats
"""
# Initial conditions
x0, y0, z0 = row["release_pos_x"], row["release_pos_y"], row["release_pos_z"]
vx0, vy0, vz0 = row["vx0"], row["vy0"], row["vz0"]
ax, ay, az = row["ax"], row["ay"], row["az"]
# Solve quadratic: y(t) = y_target
# (1/2)ay t^2 + vy0 t + (y0 - y_target) = 0
coeffs = [0.5 * ay, vy0, y0 - y_target]
roots = np.roots(coeffs)
# Select the positive, real root (time going forward)
t_candidates = [r for r in roots if np.isreal(r) and r >= 0]
if not t_candidates:
return None, None # no valid solution
t = min(t_candidates) # earliest valid time
# We did this on 9/10/2025 and the randos in chat are unstabledeus hectorhsc trevin_flick queuemina jasminsc16 et al
# Compute x, z at time t
x = x0 + vx0 * t + 0.5 * ax * t**2
z = z0 + vz0 * t + 0.5 * az * t**2
return float(x), float(z)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment