Spaces:
Running
Running
| from __future__ import annotations | |
| import pandas as pd | |
| def _safe_quantile(series: pd.Series, q: float) -> float: | |
| valid = pd.to_numeric(series, errors="coerce").dropna() | |
| if valid.empty: | |
| return 0.0 | |
| return float(valid.quantile(q)) | |
| def batter_summary(df: pd.DataFrame) -> pd.DataFrame: | |
| if df.empty or "player_name" not in df.columns: | |
| return pd.DataFrame() | |
| rows: list[dict] = [] | |
| for player_name, group in df.groupby("player_name"): | |
| launch_speed = pd.to_numeric(group["launch_speed"], errors="coerce") if "launch_speed" in group.columns else pd.Series(dtype=float) | |
| launch_angle = pd.to_numeric(group["launch_angle"], errors="coerce") if "launch_angle" in group.columns else pd.Series(dtype=float) | |
| xwoba = pd.to_numeric(group["xwoba"], errors="coerce") if "xwoba" in group.columns else pd.Series(dtype=float) | |
| hard_hit_rate = float((launch_speed >= 95).mean()) if len(launch_speed.dropna()) else 0.0 | |
| barrel_like_rate = float(((launch_speed >= 98) & (launch_angle.between(26, 30))).mean()) if len(launch_speed.dropna()) else 0.0 | |
| rows.append( | |
| { | |
| "player_name": player_name, | |
| "events": int(len(group)), | |
| "ev_avg": float(launch_speed.mean()) if len(launch_speed.dropna()) else 0.0, | |
| "ev90": _safe_quantile(launch_speed, 0.90), | |
| "la_avg": float(launch_angle.mean()) if len(launch_angle.dropna()) else 0.0, | |
| "hard_hit_rate": hard_hit_rate, | |
| "barrel_like_rate": barrel_like_rate, | |
| "xwoba_avg": float(xwoba.mean()) if len(xwoba.dropna()) else 0.0, | |
| } | |
| ) | |
| out = pd.DataFrame(rows) | |
| return out.sort_values(["ev90", "xwoba_avg"], ascending=False).reset_index(drop=True) |