diff --git "a/build.ipynb" "b/build.ipynb" new file mode 100644--- /dev/null +++ "b/build.ipynb" @@ -0,0 +1,4990 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "cd16fdae", + "metadata": {}, + "outputs": [], + "source": [ + "from __future__ import annotations\n", + "import os\n", + "from datetime import date\n", + "from pathlib import Path\n", + "\n", + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from pybaseball import statcast\n", + "from sklearn.preprocessing import StandardScaler\n", + "from sklearn.cluster import KMeans\n", + "from sklearn.neighbors import NearestNeighbors\n", + "\n", + "import plotly.express as px\n", + "import plotly.graph_objects as go" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4c2c0387", + "metadata": {}, + "outputs": [], + "source": [ + "pd.set_option(\"display.max_columns\", 200)\n", + "CACHE_DIR = Path(\"data/cache\")\n", + "CACHE_DIR.mkdir(parents=True, exist_ok=True)\n", + "ARTIFACTS_DIR = Path(\"artifacts\")\n", + "ARTIFACTS_DIR.mkdir(parents=True, exist_ok=True)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "11bb8296", + "metadata": {}, + "outputs": [], + "source": [ + "def default_window():\n", + " \"\"\"Return a recent season window (approx opening day to today).\"\"\"\n", + " today = date.today()\n", + " start = date(today.year if today.month >= 3 else today.year - 1, 3, 1)\n", + " return start.isoformat(), today.isoformat()\n", + "\n", + "\n", + "def cache_path(start: str, end: str) -> Path:\n", + " return CACHE_DIR / f\"statcast_{start}_{end}.parquet\"" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "cc0b9aee", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "('2025-03-01', '2025-10-30')" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def load_statcast(start_date: str, end_date: str, force: bool = False) -> pd.DataFrame:\n", + " \"\"\"Download Statcast data with simple caching to Parquet.\"\"\"\n", + " cp = cache_path(start_date, end_date)\n", + " if cp.exists() and not force:\n", + " print(f\"Loading cached: {cp}\")\n", + " return pd.read_parquet(cp)\n", + " print(\"Downloading from Statcast (pybaseball)... window size affects duration.\")\n", + " df = statcast(start_dt=start_date, end_dt=end_date)\n", + " if \"pitch_type\" in df.columns:\n", + " df = df[df[\"pitch_type\"].notna()]\n", + " df.to_parquet(cp, index=False)\n", + " print(f\"Cached to: {cp}\")\n", + " return df\n", + "\n", + "\n", + "start_date, end_date = default_window()\n", + "start_date, end_date" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "db1cf23c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading cached: data/cache/statcast_2025-03-01_2025-10-30.parquet\n" + ] + }, + { + "data": { + "text/plain": [ + "((752024, 118),\n", + " pitch_type game_date release_speed release_pos_x release_pos_z \\\n", + " 0 SL 2025-10-29 86.7 -2.63 5.58 \n", + " 1 SL 2025-10-29 87.0 -2.55 5.6 \n", + " 2 SL 2025-10-29 87.2 -2.58 5.59 \n", + " \n", + " player_name batter pitcher events description spin_dir \\\n", + " 0 Hoffman, Jeff 606192 656546 strikeout swinging_strike \n", + " 1 Hoffman, Jeff 606192 656546 None blocked_ball \n", + " 2 Hoffman, Jeff 606192 656546 None ball \n", + " \n", + " spin_rate_deprecated break_angle_deprecated break_length_deprecated \\\n", + " 0 \n", + " 1 \n", + " 2 \n", + " \n", + " zone des game_type stand p_throws \\\n", + " 0 14 Teoscar Hernández strikes out swinging. W R R \n", + " 1 14 Ball In Dirt W R R \n", + " 2 14 Ball W R R \n", + " \n", + " home_team away_team type hit_location bb_type balls strikes game_year \\\n", + " 0 LAD TOR S 2 None 2 2 2025 \n", + " 1 LAD TOR B None 1 2 2025 \n", + " 2 LAD TOR B None 0 2 2025 \n", + " \n", + " pfx_x pfx_z plate_x plate_z on_3b on_2b on_1b outs_when_up inning \\\n", + " 0 0.25 0.13 0.63 1.1 669257 2 9 \n", + " 1 0.22 0.07 1.22 0.09 669257 2 9 \n", + " 2 0.13 0.2 0.82 1.34 669257 2 9 \n", + " \n", + " inning_topbot hc_x hc_y tfs_deprecated tfs_zulu_deprecated umpire \\\n", + " 0 Bot \n", + " 1 Bot \n", + " 2 Bot \n", + " \n", + " sv_id vx0 vy0 vz0 ax ay az \\\n", + " 0 7.310843 -126.234364 -4.940849 1.600881 19.561186 -30.09391 \n", + " 1 8.567988 -126.485626 -7.272101 1.046798 20.184028 -30.388937 \n", + " 2 7.933229 -126.959588 -4.65206 0.265795 19.239855 -29.350702 \n", + " \n", + " sz_top sz_bot hit_distance_sc launch_speed launch_angle \\\n", + " 0 3.43 1.59 \n", + " 1 3.35 1.59 \n", + " 2 3.37 1.59 \n", + " \n", + " effective_speed release_spin_rate release_extension game_pk fielder_2 \\\n", + " 0 87.8 2674 6.4 813022 672386 \n", + " 1 87.6 2811 6.3 813022 672386 \n", + " 2 88.4 2804 6.4 813022 672386 \n", + " \n", + " fielder_3 fielder_4 fielder_5 fielder_6 fielder_7 fielder_8 \\\n", + " 0 665489 643396 676391 665926 664702 662139 \n", + " 1 665489 643396 676391 665926 664702 662139 \n", + " 2 665489 643396 676391 665926 664702 662139 \n", + " \n", + " fielder_9 release_pos_y estimated_ba_using_speedangle \\\n", + " 0 680718 54.08 \n", + " 1 680718 54.24 \n", + " 2 680718 54.09 \n", + " \n", + " estimated_woba_using_speedangle woba_value woba_denom babip_value \\\n", + " 0 0.0 0.0 1 0 \n", + " 1 \n", + " 2 \n", + " \n", + " iso_value launch_speed_angle at_bat_number pitch_number pitch_name \\\n", + " 0 0 73 5 Slider \n", + " 1 73 4 Slider \n", + " 2 73 3 Slider \n", + " \n", + " home_score away_score bat_score fld_score post_away_score \\\n", + " 0 1 6 1 6 6 \n", + " 1 1 6 1 6 6 \n", + " 2 1 6 1 6 6 \n", + " \n", + " post_home_score post_bat_score post_fld_score if_fielding_alignment \\\n", + " 0 1 1 6 Standard \n", + " 1 1 1 6 Standard \n", + " 2 1 1 6 Standard \n", + " \n", + " of_fielding_alignment spin_axis delta_home_win_exp delta_run_exp \\\n", + " 0 Standard 89 -0.001 -0.216 \n", + " 1 Standard 25 0.0 0.033 \n", + " 2 Standard 30 0.0 0.009 \n", + " \n", + " bat_speed swing_length estimated_slg_using_speedangle \\\n", + " 0 66.1 8.5 \n", + " 1 \n", + " 2 \n", + " \n", + " delta_pitcher_run_exp hyper_speed home_score_diff bat_score_diff \\\n", + " 0 0.216 -5 -5 \n", + " 1 -0.033 -5 -5 \n", + " 2 -0.009 -5 -5 \n", + " \n", + " home_win_exp bat_win_exp age_pit_legacy age_bat_legacy age_pit \\\n", + " 0 0.001 0.001 32 32 32 \n", + " 1 0.001 0.001 32 32 32 \n", + " 2 0.001 0.001 32 32 32 \n", + " \n", + " age_bat n_thruorder_pitcher n_priorpa_thisgame_player_at_bat \\\n", + " 0 33 1 3 \n", + " 1 33 1 3 \n", + " 2 33 1 3 \n", + " \n", + " pitcher_days_since_prev_game batter_days_since_prev_game \\\n", + " 0 \n", + " 1 \n", + " 2 \n", + " \n", + " pitcher_days_until_next_game batter_days_until_next_game \\\n", + " 0 \n", + " 1 \n", + " 2 \n", + " \n", + " api_break_z_with_gravity api_break_x_arm api_break_x_batter_in \\\n", + " 0 2.84 -0.25 -0.25 \n", + " 1 2.9 -0.22 -0.22 \n", + " 2 2.73 -0.13 -0.13 \n", + " \n", + " arm_angle attack_angle attack_direction swing_path_tilt \\\n", + " 0 21.292185 -28.318897 34.361479 \n", + " 1 \n", + " 2 \n", + " \n", + " intercept_ball_minus_batter_pos_x_inches \\\n", + " 0 43.225687 \n", + " 1 \n", + " 2 \n", + " \n", + " intercept_ball_minus_batter_pos_y_inches \n", + " 0 45.809537 \n", + " 1 \n", + " 2 )" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_raw = load_statcast(start_date, end_date, force=False)\n", + "df_raw.shape, df_raw.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "55548f5d", + "metadata": {}, + "outputs": [], + "source": [ + "INCHES_PER_FOOT = 12.0\n", + "\n", + "def _safe_rate(num, den):\n", + " return np.divide(\n", + " num, den, out=np.full_like(num, np.nan, dtype=float), where=den > 0\n", + " )\n", + "\n", + "\n", + "def signed_arm_side(hb_in_raw: pd.Series, p_throws: pd.Series) -> pd.Series:\n", + " \"\"\"\n", + " Convert Statcast pfx_x (catcher-right positive) into 'arm-side positive' for both RHP and LHP.\n", + " For RHP: arm-side = +pfx_x.\n", + " For LHP: arm-side = -pfx_x (flip sign).\n", + " \"\"\"\n", + " handed = p_throws.fillna(\"R\").str.upper().str[0]\n", + " sign = np.where(handed == \"R\", 1.0, -1.0)\n", + " return -hb_in_raw * sign\n", + "\n", + "\n", + "def engineer_pitch_features(df: pd.DataFrame) -> pd.DataFrame:\n", + " \"\"\"\n", + " Aggregates to (player_name, pitch_type, p_throws) with handedness-aware XY:\n", + " X = hb_as_in (Arm-Side + / Glove-Side -), Y = ivb_in (Ride + / Drop -)\n", + " Also computes CSW, Whiff, GB, Zone%.\n", + " \"\"\"\n", + " cols = [\n", + " \"pitch_type\",\n", + " \"player_name\",\n", + " \"game_date\",\n", + " \"events\",\n", + " \"description\",\n", + " \"p_throws\",\n", + " \"stand\",\n", + " \"release_pos_x\",\n", + " \"release_pos_z\",\n", + " \"pfx_x\",\n", + " \"pfx_z\",\n", + " \"release_speed\",\n", + " \"release_spin_rate\",\n", + " \"plate_x\",\n", + " \"plate_z\",\n", + " \"zone\",\n", + " ]\n", + " have = [c for c in cols if c in df.columns]\n", + " df = df[have].copy()\n", + "\n", + " # outcomes\n", + " df[\"is_called_strike\"] = (df[\"description\"] == \"called_strike\").astype(int)\n", + " df[\"is_swing\"] = (\n", + " df[\"description\"]\n", + " .isin([\"swinging_strike\", \"swinging_strike_blocked\", \"foul\", \"hit_into_play\"])\n", + " .astype(int)\n", + " )\n", + " df[\"is_whiff\"] = (\n", + " df[\"description\"]\n", + " .isin([\"swinging_strike\", \"swinging_strike_blocked\"])\n", + " .astype(int)\n", + " )\n", + " df[\"is_ball\"] = (df[\"description\"] == \"ball\").astype(int)\n", + " df[\"is_in_play\"] = (df[\"description\"] == \"hit_into_play\").astype(int)\n", + " # quick GB proxy (refine later with launch angle)\n", + " df[\"is_gb\"] = (\n", + " df[\"events\"]\n", + " .isin([\"groundout\", \"field_error\", \"single\", \"double\", \"triple\"])\n", + " .astype(int)\n", + " )\n", + "\n", + " # movement proxies in inches\n", + " df[\"hb_in_raw\"] = df[\"pfx_x\"] * INCHES_PER_FOOT # + = break to catcher's right\n", + " df[\"ivb_in\"] = df[\"pfx_z\"] * INCHES_PER_FOOT # + = ride, − = drop\n", + " df[\"hb_as_in\"] = signed_arm_side(\n", + " df[\"hb_in_raw\"], df.get(\"p_throws\")\n", + " ) # + = arm-side, − = glove-side\n", + "\n", + " grp = df.groupby([\"player_name\", \"pitch_type\", \"p_throws\"], as_index=False)\n", + " agg = grp.agg(\n", + " n=(\"pitch_type\", \"size\"),\n", + " velo=(\"release_speed\", \"mean\"),\n", + " spin=(\"release_spin_rate\", \"mean\"),\n", + " ivb_in=(\"ivb_in\", \"mean\"),\n", + " hb_as_in=(\"hb_as_in\", \"mean\"),\n", + " rel_height=(\"release_pos_z\", \"mean\"),\n", + " rel_side=(\"release_pos_x\", \"mean\"),\n", + " cs=(\"is_called_strike\", \"sum\"),\n", + " swings=(\"is_swing\", \"sum\"),\n", + " whiffs=(\"is_whiff\", \"sum\"),\n", + " inplay=(\"is_in_play\", \"sum\"),\n", + " gb=(\"is_gb\", \"sum\"),\n", + " )\n", + "\n", + " agg[\"csw\"] = _safe_rate(agg[\"cs\"] + agg[\"whiffs\"], agg[\"n\"])\n", + " agg[\"whiff_rate\"] = _safe_rate(agg[\"whiffs\"], agg[\"swings\"])\n", + " agg[\"gb_rate\"] = _safe_rate(agg[\"gb\"], agg[\"inplay\"])\n", + " # rough in-zone proxy\n", + " agg[\"zone_pct\"] = _safe_rate(agg[\"cs\"] + agg[\"inplay\"], agg[\"n\"])\n", + "\n", + " keep = [\n", + " \"player_name\",\n", + " \"pitch_type\",\n", + " \"p_throws\",\n", + " \"n\",\n", + " \"velo\",\n", + " \"spin\",\n", + " \"ivb_in\",\n", + " \"hb_as_in\",\n", + " \"rel_height\",\n", + " \"rel_side\",\n", + " \"csw\",\n", + " \"whiff_rate\",\n", + " \"gb_rate\",\n", + " \"zone_pct\",\n", + " ]\n", + " out = agg[keep].dropna(subset=[\"velo\", \"ivb_in\", \"hb_as_in\"])\n", + " return out" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "bba4c7b2", + "metadata": {}, + "outputs": [], + "source": [ + "ARCH_FEATURES = [\n", + " \"velo\",\n", + " \"ivb_in\",\n", + " \"hb_as_in\",\n", + " \"rel_height\",\n", + " \"rel_side\",\n", + " \"spin\",\n", + " \"csw\",\n", + " \"whiff_rate\",\n", + " \"gb_rate\",\n", + " \"zone_pct\",\n", + "]\n", + "\n", + "\n", + "def fit_kmeans(df_feat: pd.DataFrame, k: int = 8, random_state: int = 42):\n", + " df = df_feat.dropna(subset=ARCH_FEATURES).copy()\n", + " X = df[ARCH_FEATURES].values\n", + " scaler = StandardScaler()\n", + " Xs = scaler.fit_transform(X)\n", + " km = KMeans(n_clusters=k, n_init=20, random_state=random_state)\n", + " labels = km.fit_predict(Xs)\n", + " df[\"cluster\"] = labels\n", + "\n", + " nn = NearestNeighbors(n_neighbors=6, metric=\"euclidean\")\n", + " nn.fit(Xs)\n", + " return df, scaler, km, nn" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "f3210171", + "metadata": {}, + "outputs": [], + "source": [ + "# --- Replacement: cluster tags using XY + functional flavor ---\n", + "def _mag_label(v, q25, q75, small=\"Subtle\", mid=\"Moderate\", big=\"Heavy\"):\n", + " if v >= q75:\n", + " return big\n", + " if v <= q25:\n", + " return small\n", + " return mid\n", + "\n", + "\n", + "def _side_label(hb_as):\n", + " return \"Arm-Side\" if hb_as >= 0 else \"Glove-Side\"\n", + "\n", + "\n", + "def _vert_label(ivb):\n", + " return \"Ride\" if ivb >= 0 else \"Drop\"\n", + "\n", + "\n", + "def xy_cluster_tags(df_with_clusters: pd.DataFrame) -> dict[int, str]:\n", + " \"\"\"\n", + " Returns {cluster_id: label} like:\n", + " Slider: Glove-Side • Moderate Sweep, Subtle Ride • Strike-Throwing\n", + " Changeup: Arm-Side • Subtle Ride, Heavy Run • Grounder-First\n", + " \"\"\"\n", + " df = df_with_clusters.copy()\n", + "\n", + " q_abs_ivb25 = np.nanquantile(np.abs(df[\"ivb_in\"]), 0.25)\n", + " q_abs_ivb75 = np.nanquantile(np.abs(df[\"ivb_in\"]), 0.75)\n", + " q_abs_hb25 = np.nanquantile(np.abs(df[\"hb_as_in\"]), 0.25)\n", + " q_abs_hb75 = np.nanquantile(np.abs(df[\"hb_as_in\"]), 0.75)\n", + "\n", + " q_wh75 = np.nanquantile(df[\"whiff_rate\"], 0.75)\n", + " q_gb75 = np.nanquantile(df[\"gb_rate\"], 0.75)\n", + " q_zn75 = np.nanquantile(df[\"zone_pct\"], 0.75)\n", + "\n", + " q_wh50 = np.nanquantile(df[\"whiff_rate\"], 0.50)\n", + " q_gb50 = np.nanquantile(df[\"gb_rate\"], 0.50)\n", + " q_zn50 = np.nanquantile(df[\"zone_pct\"], 0.50)\n", + "\n", + " tags = {}\n", + " for c, sub in df.groupby(\"cluster\"):\n", + " row = sub.mean(numeric_only=True)\n", + " dom_pt = (\n", + " sub[\"pitch_type\"].mode().iloc[0]\n", + " if not sub[\"pitch_type\"].mode().empty\n", + " else \"Pitch\"\n", + " )\n", + "\n", + " side = _side_label(row[\"hb_as_in\"])\n", + " vert = _vert_label(row[\"ivb_in\"])\n", + "\n", + " mag_side = _mag_label(\n", + " abs(row[\"hb_as_in\"]), q_abs_hb25, q_abs_hb75\n", + " ) # \"Subtle/Moderate/Heavy\"\n", + " mag_vert = _mag_label(abs(row[\"ivb_in\"]), q_abs_ivb25, q_abs_ivb75)\n", + "\n", + " # Functional flavor\n", + " flavor = []\n", + " if row[\"whiff_rate\"] >= q_wh75:\n", + " flavor.append(\"Whiff-First\")\n", + " if row[\"gb_rate\"] >= q_gb75:\n", + " flavor.append(\"Grounder-First\")\n", + " if row[\"zone_pct\"] >= q_zn75:\n", + " flavor.append(\"Strike-Throwing\")\n", + " if not flavor:\n", + " diffs = {\n", + " \"Whiff-First\": row[\"whiff_rate\"] - q_wh50,\n", + " \"Grounder-First\": row[\"gb_rate\"] - q_gb50,\n", + " \"Strike-Throwing\": row[\"zone_pct\"] - q_zn50,\n", + " }\n", + " flavor.append(max(diffs, key=diffs.get))\n", + "\n", + " # Compose label: \": , \"\n", + " # Examples:\n", + " # \"Slider: Glove-Side • Heavy Sweep, Subtle Drop • Strike-Throwing\"\n", + " # \"Changeup: Arm-Side • Moderate Run, Subtle Drop • Grounder-First\"\n", + " side_noun = \"Sweep\" if side == \"Glove-Side\" else \"Run\"\n", + " vert_noun = \"Ride\" if vert == \"Ride\" else \"Drop\"\n", + " shape = f\"{side} • {mag_side} {side_noun}, {mag_vert} {vert_noun}\"\n", + " tags[c] = f\"{dom_pt}: {shape} • \" + \" / \".join(flavor)\n", + "\n", + " return tags" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "74c47e88", + "metadata": {}, + "outputs": [], + "source": [ + "def nearest_comps(\n", + " row: pd.Series, df_fit: pd.DataFrame, scaler, nn, within_pitch_type=True, k=6\n", + "):\n", + " X_all = df_fit[ARCH_FEATURES].values\n", + " xq = scaler.transform(row[ARCH_FEATURES].values.reshape(1, -1))\n", + " dists, idxs = nn.kneighbors(xq, n_neighbors=k)\n", + " comps = df_fit.iloc[idxs[0]].copy()\n", + " if within_pitch_type:\n", + " comps = comps[comps[\"pitch_type\"] == row[\"pitch_type\"]]\n", + " return comps[\n", + " [\n", + " \"player_name\",\n", + " \"pitch_type\",\n", + " \"p_throws\",\n", + " \"velo\",\n", + " \"ivb_in\",\n", + " \"hb_as_in\",\n", + " \"whiff_rate\",\n", + " \"gb_rate\",\n", + " \"cluster_name\",\n", + " ]\n", + " ].head(k - 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "ebcbaea2", + "metadata": {}, + "outputs": [], + "source": [ + "def movement_scatter_xy(\n", + " df: pd.DataFrame, color=\"pitch_type\", facet_by_handedness=False\n", + "):\n", + " df_plot = df.copy()\n", + " if facet_by_handedness:\n", + " fig = px.scatter(\n", + " df_plot,\n", + " x=\"hb_as_in\",\n", + " y=\"ivb_in\",\n", + " color=color,\n", + " facet_col=\"p_throws\",\n", + " hover_data=[\n", + " \"player_name\",\n", + " \"pitch_type\",\n", + " \"p_throws\",\n", + " \"velo\",\n", + " \"whiff_rate\",\n", + " \"gb_rate\",\n", + " \"csw\",\n", + " ],\n", + " )\n", + " else:\n", + " fig = px.scatter(\n", + " df_plot,\n", + " x=\"hb_as_in\",\n", + " y=\"ivb_in\",\n", + " color=color,\n", + " hover_data=[\n", + " \"player_name\",\n", + " \"pitch_type\",\n", + " \"p_throws\",\n", + " \"velo\",\n", + " \"whiff_rate\",\n", + " \"gb_rate\",\n", + " \"csw\",\n", + " ],\n", + " )\n", + "\n", + " fig.update_layout(\n", + " xaxis_title=\"Horizontal: Arm-Side (+) | Glove-Side (−)\",\n", + " yaxis_title=\"Vertical: Ride (+) | Drop (−)\",\n", + " legend_title_text=color,\n", + " )\n", + " # quadrant guides (for every subplot if faceted)\n", + " for ax in fig.select_yaxes():\n", + " fig.add_hline(\n", + " y=0,\n", + " line_dash=\"dot\",\n", + " row=ax.anchor.split(\"y\")[-1] if facet_by_handedness else None,\n", + " col=None,\n", + " )\n", + " for ax in fig.select_xaxes():\n", + " fig.add_vline(\n", + " x=0,\n", + " line_dash=\"dot\",\n", + " row=ax.anchor.split(\"x\")[-1] if facet_by_handedness else None,\n", + " col=None,\n", + " )\n", + "\n", + " # helpful annotations (single-plot case)\n", + " if not facet_by_handedness:\n", + " fig.add_annotation(\n", + " x=1,\n", + " y=0,\n", + " xref=\"paper\",\n", + " yref=\"paper\",\n", + " xanchor=\"left\",\n", + " yanchor=\"top\",\n", + " text=\"AS (+)\",\n", + " showarrow=False,\n", + " font=dict(size=10),\n", + " )\n", + " fig.add_annotation(\n", + " x=0,\n", + " y=0,\n", + " xref=\"paper\",\n", + " yref=\"paper\",\n", + " xanchor=\"right\",\n", + " yanchor=\"top\",\n", + " text=\"GS (−)\",\n", + " showarrow=False,\n", + " font=dict(size=10),\n", + " )\n", + " fig.add_annotation(\n", + " x=0.5,\n", + " y=1,\n", + " xref=\"paper\",\n", + " yref=\"paper\",\n", + " xanchor=\"center\",\n", + " yanchor=\"bottom\",\n", + " text=\"Ride (+)\",\n", + " showarrow=False,\n", + " font=dict(size=10),\n", + " )\n", + " fig.add_annotation(\n", + " x=0.5,\n", + " y=0,\n", + " xref=\"paper\",\n", + " yref=\"paper\",\n", + " xanchor=\"center\",\n", + " yanchor=\"top\",\n", + " text=\"Drop (−)\",\n", + " showarrow=False,\n", + " font=dict(size=10),\n", + " )\n", + " return fig\n", + "\n", + "\n", + "def radar_quality(row: pd.Series):\n", + " cats = [\"csw\", \"whiff_rate\", \"gb_rate\", \"zone_pct\"]\n", + " vals = [row[c] for c in cats]\n", + " fig = go.Figure(data=go.Scatterpolar(r=vals, theta=cats, fill=\"toself\"))\n", + " fig.update_layout(\n", + " polar=dict(radialaxis=dict(visible=True, range=[0, 1])), showlegend=False\n", + " )\n", + " return fig\n", + "\n", + "\n", + "def xy_blurb(row: pd.Series) -> str:\n", + " side = \"Arm-Side\" if row[\"hb_as_in\"] >= 0 else \"Glove-Side\"\n", + " vert = \"Ride\" if row[\"ivb_in\"] >= 0 else \"Drop\"\n", + " return (\n", + " f\"{row['pitch_type']} ({row['p_throws']}): {side}, {vert}. \"\n", + " f\"Velo {row['velo']:.1f} mph | AS {row['hb_as_in']:.1f}\\\" | \"\n", + " f\"Ride {row['ivb_in']:.1f}\\\" | CSW {row['csw']:.2f} | \"\n", + " f\"Whiff {row['whiff_rate']:.2f} | GB {row['gb_rate']:.2f} | Zone {row['zone_pct']:.2f}\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "a3d31501", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading cached: data/cache/statcast_2025-03-01_2025-10-30.parquet\n" + ] + } + ], + "source": [ + "start_date, end_date = default_window()\n", + "# For a fast first run, you can narrow the window, e.g.:\n", + "# start_date, end_date = \"2024-04-01\", \"2024-04-07\"\n", + "\n", + "df_raw = load_statcast(start_date, end_date, force=False)\n", + "df_feat = engineer_pitch_features(df_raw)\n", + "\n", + "df_fit, scaler, km, nn = fit_kmeans(df_feat, k=8)\n", + "cluster_names = xy_cluster_tags(df_fit)\n", + "df_fit[\"cluster_name\"] = df_fit[\"cluster\"].map(cluster_names)\n", + "\n", + "# Save artifacts\n", + "df_feat.to_parquet(ARTIFACTS_DIR / \"pitch_features.parquet\", index=False)\n", + "df_fit.to_parquet(ARTIFACTS_DIR / \"pitch_features_clusters.parquet\", index=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "a8b637de", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Yesavage, Trey'" + ] + }, + "execution_count": 38, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pitchers = sorted(df_fit[\"player_name\"].unique().tolist())\n", + "SELECTED_PITCHER = pitchers[1071] if pitchers else None\n", + "SELECTED_PITCHER" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "id": "cbcd0c40", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
player_namepitch_typep_throwsnvelospinivb_inhb_as_inrel_heightrel_sidecswwhiff_rategb_ratezone_pctclustercluster_name
4680Yesavage, TreyFFR27094.3162962261.15925919.4786674.4817787.049444-0.4441110.2629630.1666670.2777780.3333337FF: Arm-Side • Moderate Run, Moderate Ride • S...
4681Yesavage, TreyFSR17983.8106151526.6592187.39039111.7204477.175978-0.4801680.4581010.5862070.2000000.2849166CH: Arm-Side • Moderate Run, Moderate Ride • W...
4682Yesavage, TreySLR20988.4947372212.53114.7041153.7113887.129378-0.5848330.3253590.3773580.4000000.3014351SL: Glove-Side • Moderate Sweep, Subtle Drop •...
\n", + "
" + ], + "text/plain": [ + " player_name pitch_type p_throws n velo spin \\\n", + "4680 Yesavage, Trey FF R 270 94.316296 2261.159259 \n", + "4681 Yesavage, Trey FS R 179 83.810615 1526.659218 \n", + "4682 Yesavage, Trey SL R 209 88.494737 2212.5311 \n", + "\n", + " ivb_in hb_as_in rel_height rel_side csw whiff_rate \\\n", + "4680 19.478667 4.481778 7.049444 -0.444111 0.262963 0.166667 \n", + "4681 7.390391 11.720447 7.175978 -0.480168 0.458101 0.586207 \n", + "4682 4.704115 3.711388 7.129378 -0.584833 0.325359 0.377358 \n", + "\n", + " gb_rate zone_pct cluster \\\n", + "4680 0.277778 0.333333 7 \n", + "4681 0.200000 0.284916 6 \n", + "4682 0.400000 0.301435 1 \n", + "\n", + " cluster_name \n", + "4680 FF: Arm-Side • Moderate Run, Moderate Ride • S... \n", + "4681 CH: Arm-Side • Moderate Run, Moderate Ride • W... \n", + "4682 SL: Glove-Side • Moderate Sweep, Subtle Drop •... " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "customdata": [ + [ + "Yesavage, Trey", + "FF", + "R", + 94.3162962962963, + 0.16666666666666666, + 0.2777777777777778, + 0.26296296296296295 + ] + ], + "hovertemplate": "pitch_type=%{customdata[1]}
hb_as_in=%{x}
ivb_in=%{y}
player_name=%{customdata[0]}
p_throws=%{customdata[2]}
velo=%{customdata[3]}
whiff_rate=%{customdata[4]}
gb_rate=%{customdata[5]}
csw=%{customdata[6]}", + "legendgroup": "FF", + "marker": { + "color": "#636efa", + "symbol": "circle" + }, + "mode": "markers", + "name": "FF", + "orientation": "v", + "showlegend": true, + "type": "scatter", + "x": [ + 4.481777777777777 + ], + "xaxis": "x", + "y": [ + 19.478666666666665 + ], + "yaxis": "y" + }, + { + "customdata": [ + [ + "Yesavage, Trey", + "FS", + "R", + 83.81061452513967, + 0.5862068965517241, + 0.2, + 0.4581005586592179 + ] + ], + "hovertemplate": "pitch_type=%{customdata[1]}
hb_as_in=%{x}
ivb_in=%{y}
player_name=%{customdata[0]}
p_throws=%{customdata[2]}
velo=%{customdata[3]}
whiff_rate=%{customdata[4]}
gb_rate=%{customdata[5]}
csw=%{customdata[6]}", + "legendgroup": "FS", + "marker": { + "color": "#EF553B", + "symbol": "circle" + }, + "mode": "markers", + "name": "FS", + "orientation": "v", + "showlegend": true, + "type": "scatter", + "x": [ + 11.720446927374303 + ], + "xaxis": "x", + "y": [ + 7.390391061452514 + ], + "yaxis": "y" + }, + { + "customdata": [ + [ + "Yesavage, Trey", + "SL", + "R", + 88.49473684210527, + 0.37735849056603776, + 0.4, + 0.3253588516746411 + ] + ], + "hovertemplate": "pitch_type=%{customdata[1]}
hb_as_in=%{x}
ivb_in=%{y}
player_name=%{customdata[0]}
p_throws=%{customdata[2]}
velo=%{customdata[3]}
whiff_rate=%{customdata[4]}
gb_rate=%{customdata[5]}
csw=%{customdata[6]}", + "legendgroup": "SL", + "marker": { + "color": "#00cc96", + "symbol": "circle" + }, + "mode": "markers", + "name": "SL", + "orientation": "v", + "showlegend": true, + "type": "scatter", + "x": [ + 3.711387559808612 + ], + "xaxis": "x", + "y": [ + 4.704114832535885 + ], + "yaxis": "y" + } + ], + "layout": { + "annotations": [ + { + "font": { + "size": 10 + }, + "showarrow": false, + "text": "AS (+)", + "x": 1, + "xanchor": "left", + "xref": "paper", + "y": 0, + "yanchor": "top", + "yref": "paper" + }, + { + "font": { + "size": 10 + }, + "showarrow": false, + "text": "GS (−)", + "x": 0, + "xanchor": "right", + "xref": "paper", + "y": 0, + "yanchor": "top", + "yref": "paper" + }, + { + "font": { + "size": 10 + }, + "showarrow": false, + "text": "Ride (+)", + "x": 0.5, + "xanchor": "center", + "xref": "paper", + "y": 1, + "yanchor": "bottom", + "yref": "paper" + }, + { + "font": { + "size": 10 + }, + "showarrow": false, + "text": "Drop (−)", + "x": 0.5, + "xanchor": "center", + "xref": "paper", + "y": 0, + "yanchor": "top", + "yref": "paper" + } + ], + "legend": { + "title": { + "text": "pitch_type" + }, + "tracegroupgap": 0 + }, + "margin": { + "t": 60 + }, + "shapes": [ + { + "line": { + "dash": "dot" + }, + "type": "line", + "x0": 0, + "x1": 1, + "xref": "x domain", + "y0": 0, + "y1": 0, + "yref": "y" + }, + { + "line": { + "dash": "dot" + }, + "type": "line", + "x0": 0, + "x1": 0, + "xref": "x", + "y0": 0, + "y1": 1, + "yref": "y domain" + } + ], + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + }, + "xaxis": { + "anchor": "y", + "domain": [ + 0, + 1 + ], + "title": { + "text": "Horizontal: Arm-Side (+) | Glove-Side (−)" + } + }, + "yaxis": { + "anchor": "x", + "domain": [ + 0, + 1 + ], + "title": { + "text": "Vertical: Ride (+) | Drop (−)" + } + } + } + }, + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "fill": "toself", + "r": [ + 0.26296296296296295, + 0.16666666666666666, + 0.2777777777777778, + 0.3333333333333333 + ], + "theta": [ + "csw", + "whiff_rate", + "gb_rate", + "zone_pct" + ], + "type": "scatterpolar" + } + ], + "layout": { + "polar": { + "radialaxis": { + "range": [ + 0, + 1 + ], + "visible": true + } + }, + "showlegend": false, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + } + } + }, + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FF (R): Arm-Side, Ride. Velo 94.3 mph | AS 4.5\" | Ride 19.5\" | CSW 0.26 | Whiff 0.17 | GB 0.28 | Zone 0.33\n" + ] + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "fill": "toself", + "r": [ + 0.4581005586592179, + 0.5862068965517241, + 0.2, + 0.2849162011173184 + ], + "theta": [ + "csw", + "whiff_rate", + "gb_rate", + "zone_pct" + ], + "type": "scatterpolar" + } + ], + "layout": { + "polar": { + "radialaxis": { + "range": [ + 0, + 1 + ], + "visible": true + } + }, + "showlegend": false, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + } + } + }, + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "FS (R): Arm-Side, Ride. Velo 83.8 mph | AS 11.7\" | Ride 7.4\" | CSW 0.46 | Whiff 0.59 | GB 0.20 | Zone 0.28\n" + ] + }, + { + "data": { + "application/vnd.plotly.v1+json": { + "config": { + "plotlyServerURL": "https://plot.ly" + }, + "data": [ + { + "fill": "toself", + "r": [ + 0.3253588516746411, + 0.37735849056603776, + 0.4, + 0.3014354066985646 + ], + "theta": [ + "csw", + "whiff_rate", + "gb_rate", + "zone_pct" + ], + "type": "scatterpolar" + } + ], + "layout": { + "polar": { + "radialaxis": { + "range": [ + 0, + 1 + ], + "visible": true + } + }, + "showlegend": false, + "template": { + "data": { + "bar": [ + { + "error_x": { + "color": "#2a3f5f" + }, + "error_y": { + "color": "#2a3f5f" + }, + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "barpolar": [ + { + "marker": { + "line": { + "color": "#E5ECF6", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "baxis": { + "endlinecolor": "#2a3f5f", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "#2a3f5f" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "choropleth" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "contour" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "contourcarpet" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmap" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "heatmapgl" + } + ], + "histogram": [ + { + "marker": { + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "histogram" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2d" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "histogram2dcontour" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolar" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterpolargl" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + }, + "colorscale": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "#EBF0F8" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "#C8D4E3" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowcolor": "#2a3f5f", + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 0, + "ticks": "" + } + }, + "colorscale": { + "diverging": [ + [ + 0, + "#8e0152" + ], + [ + 0.1, + "#c51b7d" + ], + [ + 0.2, + "#de77ae" + ], + [ + 0.3, + "#f1b6da" + ], + [ + 0.4, + "#fde0ef" + ], + [ + 0.5, + "#f7f7f7" + ], + [ + 0.6, + "#e6f5d0" + ], + [ + 0.7, + "#b8e186" + ], + [ + 0.8, + "#7fbc41" + ], + [ + 0.9, + "#4d9221" + ], + [ + 1, + "#276419" + ] + ], + "sequential": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ], + "sequentialminus": [ + [ + 0, + "#0d0887" + ], + [ + 0.1111111111111111, + "#46039f" + ], + [ + 0.2222222222222222, + "#7201a8" + ], + [ + 0.3333333333333333, + "#9c179e" + ], + [ + 0.4444444444444444, + "#bd3786" + ], + [ + 0.5555555555555556, + "#d8576b" + ], + [ + 0.6666666666666666, + "#ed7953" + ], + [ + 0.7777777777777778, + "#fb9f3a" + ], + [ + 0.8888888888888888, + "#fdca26" + ], + [ + 1, + "#f0f921" + ] + ] + }, + "colorway": [ + "#636efa", + "#EF553B", + "#00cc96", + "#ab63fa", + "#FFA15A", + "#19d3f3", + "#FF6692", + "#B6E880", + "#FF97FF", + "#FECB52" + ], + "font": { + "color": "#2a3f5f" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "#E5ECF6", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "#E5ECF6", + "polar": { + "angularaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "radialaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "yaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + }, + "zaxis": { + "backgroundcolor": "#E5ECF6", + "gridcolor": "white", + "gridwidth": 2, + "linecolor": "white", + "showbackground": true, + "ticks": "", + "zerolinecolor": "white" + } + }, + "shapedefaults": { + "line": { + "color": "#2a3f5f" + } + }, + "ternary": { + "aaxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "baxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + }, + "bgcolor": "#E5ECF6", + "caxis": { + "gridcolor": "white", + "linecolor": "white", + "ticks": "" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + }, + "yaxis": { + "automargin": true, + "gridcolor": "white", + "linecolor": "white", + "ticks": "", + "title": { + "standoff": 15 + }, + "zerolinecolor": "white", + "zerolinewidth": 2 + } + } + } + } + }, + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SL (R): Arm-Side, Ride. Velo 88.5 mph | AS 3.7\" | Ride 4.7\" | CSW 0.33 | Whiff 0.38 | GB 0.40 | Zone 0.30\n" + ] + } + ], + "source": [ + "if SELECTED_PITCHER:\n", + " df_p = df_fit[df_fit[\"player_name\"] == SELECTED_PITCHER].sort_values(\"pitch_type\")\n", + " try:\n", + " display(df_p)\n", + " except NameError:\n", + " print(df_p)\n", + "\n", + " try:\n", + " display(movement_scatter_xy(df_p, color=\"pitch_type\"))\n", + " except NameError:\n", + " movement_scatter_xy(df_p, color=\"pitch_type\").show()\n", + "\n", + " for _, row in df_p.iterrows():\n", + " try:\n", + " display(radar_quality(row))\n", + " except NameError:\n", + " radar_quality(row).show()\n", + " print(xy_blurb(row))\n", + "else:\n", + " print(\"No pitchers found. Try a different date window.\")" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "b4349e0e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Nearest comps — FF (FF: Arm-Side • Moderate Run, Moderate Ride • Strike-Throwing):\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
player_namepitch_typep_throwsveloivb_inhb_as_inwhiff_rategb_ratecluster_name
4680Yesavage, TreyFFR94.31629619.4786674.4817780.1666670.277778FF: Arm-Side • Moderate Run, Moderate Ride • S...
3292Pivetta, NickFFR93.77473219.656362.5789290.2257600.219731FF: Arm-Side • Moderate Run, Moderate Ride • S...
4490Wells, TylerFFR92.810417.783045.008320.2031250.227273FF: Arm-Side • Moderate Run, Moderate Ride • S...
1205Fairbanks, PeteFFR97.31330717.2353820.2599610.2164180.304348FF: Arm-Side • Moderate Run, Moderate Ride • S...
4349Verlander, JustinFFR93.93063118.5886988.8779360.1514080.297674FF: Arm-Side • Moderate Run, Moderate Ride • S...
\n", + "
" + ], + "text/plain": [ + " player_name pitch_type p_throws velo ivb_in hb_as_in \\\n", + "4680 Yesavage, Trey FF R 94.316296 19.478667 4.481778 \n", + "3292 Pivetta, Nick FF R 93.774732 19.65636 2.578929 \n", + "4490 Wells, Tyler FF R 92.8104 17.78304 5.00832 \n", + "1205 Fairbanks, Pete FF R 97.313307 17.235382 0.259961 \n", + "4349 Verlander, Justin FF R 93.930631 18.588698 8.877936 \n", + "\n", + " whiff_rate gb_rate cluster_name \n", + "4680 0.166667 0.277778 FF: Arm-Side • Moderate Run, Moderate Ride • S... \n", + "3292 0.225760 0.219731 FF: Arm-Side • Moderate Run, Moderate Ride • S... \n", + "4490 0.203125 0.227273 FF: Arm-Side • Moderate Run, Moderate Ride • S... \n", + "1205 0.216418 0.304348 FF: Arm-Side • Moderate Run, Moderate Ride • S... \n", + "4349 0.151408 0.297674 FF: Arm-Side • Moderate Run, Moderate Ride • S... " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Nearest comps — FS (CH: Arm-Side • Moderate Run, Moderate Ride • Whiff-First):\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
player_namepitch_typep_throwsveloivb_inhb_as_inwhiff_rategb_ratecluster_name
4681Yesavage, TreyFSR83.8106157.39039111.7204470.5862070.200000CH: Arm-Side • Moderate Run, Moderate Ride • W...
265Bautista, FélixFSR88.90614510.5144138.8015640.5057470.227273CH: Arm-Side • Moderate Run, Moderate Ride • W...
\n", + "
" + ], + "text/plain": [ + " player_name pitch_type p_throws velo ivb_in hb_as_in \\\n", + "4681 Yesavage, Trey FS R 83.810615 7.390391 11.720447 \n", + "265 Bautista, Félix FS R 88.906145 10.514413 8.801564 \n", + "\n", + " whiff_rate gb_rate cluster_name \n", + "4681 0.586207 0.200000 CH: Arm-Side • Moderate Run, Moderate Ride • W... \n", + "265 0.505747 0.227273 CH: Arm-Side • Moderate Run, Moderate Ride • W... " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Nearest comps — SL (SL: Glove-Side • Moderate Sweep, Subtle Drop • Whiff-First):\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
player_namepitch_typep_throwsveloivb_inhb_as_inwhiff_rategb_ratecluster_name
4682Yesavage, TreySLR88.4947374.7041153.7113880.3773580.400000SL: Glove-Side • Moderate Sweep, Subtle Drop •...
4351Verlander, JustinSLR87.0661375.482226-4.0796180.3089890.346774SL: Glove-Side • Moderate Sweep, Subtle Drop •...
4491Wells, TylerSLR86.8177426.224516-4.1129030.2500000.300000SL: Glove-Side • Moderate Sweep, Subtle Drop •...
268Bautista, FélixSLR85.4775860.848276-5.8241380.3636360.285714SL: Glove-Side • Moderate Sweep, Subtle Drop •...
785Church, MarcSLR87.1813565.162034-2.2128810.3076920.222222SL: Glove-Side • Moderate Sweep, Subtle Drop •...
\n", + "
" + ], + "text/plain": [ + " player_name pitch_type p_throws velo ivb_in hb_as_in \\\n", + "4682 Yesavage, Trey SL R 88.494737 4.704115 3.711388 \n", + "4351 Verlander, Justin SL R 87.066137 5.482226 -4.079618 \n", + "4491 Wells, Tyler SL R 86.817742 6.224516 -4.112903 \n", + "268 Bautista, Félix SL R 85.477586 0.848276 -5.824138 \n", + "785 Church, Marc SL R 87.181356 5.162034 -2.212881 \n", + "\n", + " whiff_rate gb_rate cluster_name \n", + "4682 0.377358 0.400000 SL: Glove-Side • Moderate Sweep, Subtle Drop •... \n", + "4351 0.308989 0.346774 SL: Glove-Side • Moderate Sweep, Subtle Drop •... \n", + "4491 0.250000 0.300000 SL: Glove-Side • Moderate Sweep, Subtle Drop •... \n", + "268 0.363636 0.285714 SL: Glove-Side • Moderate Sweep, Subtle Drop •... \n", + "785 0.307692 0.222222 SL: Glove-Side • Moderate Sweep, Subtle Drop •... " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "if SELECTED_PITCHER:\n", + " for _, row in df_p.iterrows():\n", + " comps = nearest_comps(row, df_fit, scaler, nn, within_pitch_type=True, k=6)\n", + " print(f\"\\nNearest comps — {row['pitch_type']} ({row['cluster_name']}):\")\n", + " try:\n", + " display(comps)\n", + " except NameError:\n", + " print(comps.to_string(index=False))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}