Update functions/pitch_summary_functions.py
Browse files
functions/pitch_summary_functions.py
CHANGED
|
@@ -261,7 +261,7 @@ def velocity_kdes(df: pl.DataFrame, ax: plt.Axes, gs: gridspec.GridSpec, gs_x: l
|
|
| 261 |
ax_top[-1].spines['right'].set_visible(False)
|
| 262 |
ax_top[-1].spines['left'].set_visible(False)
|
| 263 |
ax_top[-1].set_xticks(list(range(math.floor(df['start_speed'].min() / 5) * 5, math.ceil(df['start_speed'].max() / 5) * 5, 5)))
|
| 264 |
-
ax_top[-1].set_xlabel('Velocity (mph)')
|
| 265 |
|
| 266 |
### TJ STUFF+ ROLLING ###
|
| 267 |
def tj_stuff_roling(df: pl.DataFrame, window: int, ax: plt.Axes):
|
|
@@ -370,6 +370,71 @@ def tj_stuff_roling_game(df: pl.DataFrame, window: int, ax: plt.Axes):
|
|
| 370 |
ax.set_title(f"{window} Game Rolling tjStuff+", fontdict=font_properties_titles)
|
| 371 |
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
|
| 372 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
def break_plot(df: pl.DataFrame, ax: plt.Axes):
|
| 374 |
"""
|
| 375 |
Plot the pitch breaks for different pitch types.
|
|
@@ -433,15 +498,15 @@ def break_plot(df: pl.DataFrame, ax: plt.Axes):
|
|
| 433 |
# Add text annotations for glove side and arm side
|
| 434 |
if df['pitcher_hand'][0] == 'R':
|
| 435 |
ax.text(-24.5, -24.5, s='β Glove Side', fontstyle='italic', ha='left', va='bottom',
|
| 436 |
-
bbox=dict(facecolor='white', edgecolor='black'), fontsize=
|
| 437 |
ax.text(24.5, -24.5, s='Arm Side β', fontstyle='italic', ha='right', va='bottom',
|
| 438 |
-
bbox=dict(facecolor='white', edgecolor='black'), fontsize=
|
| 439 |
elif df['pitcher_hand'][0] == 'L':
|
| 440 |
ax.invert_xaxis()
|
| 441 |
ax.text(24.5, -24.5, s='β Arm Side', fontstyle='italic', ha='left', va='bottom',
|
| 442 |
-
bbox=dict(facecolor='white', edgecolor='black'), fontsize=
|
| 443 |
ax.text(-24.5, -24.5, s='Glove Side β', fontstyle='italic', ha='right', va='bottom',
|
| 444 |
-
bbox=dict(facecolor='white', edgecolor='black'), fontsize=
|
| 445 |
|
| 446 |
# Set aspect ratio and format axis ticks
|
| 447 |
ax.set_aspect('equal', adjustable='box')
|
|
|
|
| 261 |
ax_top[-1].spines['right'].set_visible(False)
|
| 262 |
ax_top[-1].spines['left'].set_visible(False)
|
| 263 |
ax_top[-1].set_xticks(list(range(math.floor(df['start_speed'].min() / 5) * 5, math.ceil(df['start_speed'].max() / 5) * 5, 5)))
|
| 264 |
+
ax_top[-1].set_xlabel('Velocity (mph)', fontdict=font_properties_axes)
|
| 265 |
|
| 266 |
### TJ STUFF+ ROLLING ###
|
| 267 |
def tj_stuff_roling(df: pl.DataFrame, window: int, ax: plt.Axes):
|
|
|
|
| 370 |
ax.set_title(f"{window} Game Rolling tjStuff+", fontdict=font_properties_titles)
|
| 371 |
ax.xaxis.set_major_locator(MaxNLocator(integer=True))
|
| 372 |
|
| 373 |
+
|
| 374 |
+
def pitch_usage(df: pl.DataFrame, ax: plt.Axes):
|
| 375 |
+
"""
|
| 376 |
+
Plot pitch usage as a population pyramid: left is % vs LHH, right is % vs RHH.
|
| 377 |
+
|
| 378 |
+
Parameters
|
| 379 |
+
----------
|
| 380 |
+
df : pl.DataFrame
|
| 381 |
+
The DataFrame containing pitch data.
|
| 382 |
+
ax : plt.Axes
|
| 383 |
+
The axis to plot on.
|
| 384 |
+
"""
|
| 385 |
+
# Get unique pitch types sorted by total usage
|
| 386 |
+
usage = (
|
| 387 |
+
df.group_by(['pitch_type', 'batter_hand'])
|
| 388 |
+
.agg(pl.col('is_pitch').sum().alias('count'))
|
| 389 |
+
.pivot(values='count', index='pitch_type', columns='batter_hand')
|
| 390 |
+
.fill_null(0)
|
| 391 |
+
)
|
| 392 |
+
|
| 393 |
+
# Calculate total pitches for each hand
|
| 394 |
+
total_lhh = usage['L'].sum() if 'L' in usage.columns else 0
|
| 395 |
+
total_rhh = usage['R'].sum() if 'R' in usage.columns else 0
|
| 396 |
+
|
| 397 |
+
# Calculate proportions (0-1), then scale to percent (0-100)
|
| 398 |
+
usage = usage.with_columns([
|
| 399 |
+
(pl.col('L') / total_lhh * 100).alias('L_prop') if total_lhh > 0 else pl.lit(0).alias('L_prop'),
|
| 400 |
+
(pl.col('R') / total_rhh * 100).alias('R_prop') if total_rhh > 0 else pl.lit(0).alias('R_prop')
|
| 401 |
+
])
|
| 402 |
+
|
| 403 |
+
# Sort pitch types by total usage
|
| 404 |
+
usage = usage.with_columns(
|
| 405 |
+
(pl.col('L') + pl.col('R')).alias('total')
|
| 406 |
+
).sort('total', descending=False)
|
| 407 |
+
|
| 408 |
+
pitch_types = usage['pitch_type'].to_list()
|
| 409 |
+
y_pos = np.arange(len(pitch_types))
|
| 410 |
+
|
| 411 |
+
# Plot bars: LHH to left (negative), RHH to right (positive)
|
| 412 |
+
ax.barh(y_pos, -usage['L_prop'].to_numpy(), color=[dict_colour.get(pt, '#888888') for pt in pitch_types], alpha=1, label='vs LHH')
|
| 413 |
+
ax.barh(y_pos, usage['R_prop'].to_numpy(), color=[dict_colour.get(pt, '#888888') for pt in pitch_types], alpha=1, label='vs RHH')
|
| 414 |
+
|
| 415 |
+
# Add counts on bars
|
| 416 |
+
for i, pt in enumerate(pitch_types):
|
| 417 |
+
if 'L' in usage.columns and usage['L'][i] > 0:
|
| 418 |
+
ax.text(-usage['L_prop'][i] - 4, i, f'{(usage["L_prop"][i]/100):.1%}', va='center', ha='right', fontsize=14)
|
| 419 |
+
if 'R' in usage.columns and usage['R'][i] > 0:
|
| 420 |
+
ax.text(usage['R_prop'][i] + 4, i, f'{(usage["R_prop"][i]/100):.1%}', va='center', ha='left', fontsize=14)
|
| 421 |
+
|
| 422 |
+
ax.set_yticks(y_pos)
|
| 423 |
+
ax.set_yticklabels([])
|
| 424 |
+
ax.set_xlabel('Usage (%)', fontdict=font_properties_axes)
|
| 425 |
+
ax.set_title('Pitch Usage', fontdict=font_properties_titles)
|
| 426 |
+
ax.axvline(0, color='black', linewidth=1)
|
| 427 |
+
# ax.get_legend().remove()
|
| 428 |
+
ax.set_xlim(-100, 100)
|
| 429 |
+
ax.set_xticks(np.arange(-100, 101, 20))
|
| 430 |
+
ax.set_xticklabels([f"{abs(x)}%" for x in np.arange(-100, 101, 20)], fontdict=font_properties)
|
| 431 |
+
# ax.grid(axis='x', linestyle='--', alpha=0.5)
|
| 432 |
+
ax.text(-98, -0.49, s='vs LHH', fontstyle='italic', ha='left', va='bottom',
|
| 433 |
+
bbox=dict(facecolor='white', edgecolor='black'), fontsize=16, zorder=3)
|
| 434 |
+
ax.text(98, -0.49, s='vs RHH', fontstyle='italic', ha='right', va='bottom',
|
| 435 |
+
bbox=dict(facecolor='white', edgecolor='black'), fontsize=16, zorder=3)
|
| 436 |
+
|
| 437 |
+
|
| 438 |
def break_plot(df: pl.DataFrame, ax: plt.Axes):
|
| 439 |
"""
|
| 440 |
Plot the pitch breaks for different pitch types.
|
|
|
|
| 498 |
# Add text annotations for glove side and arm side
|
| 499 |
if df['pitcher_hand'][0] == 'R':
|
| 500 |
ax.text(-24.5, -24.5, s='β Glove Side', fontstyle='italic', ha='left', va='bottom',
|
| 501 |
+
bbox=dict(facecolor='white', edgecolor='black'), fontsize=16, zorder=3)
|
| 502 |
ax.text(24.5, -24.5, s='Arm Side β', fontstyle='italic', ha='right', va='bottom',
|
| 503 |
+
bbox=dict(facecolor='white', edgecolor='black'), fontsize=16, zorder=3)
|
| 504 |
elif df['pitcher_hand'][0] == 'L':
|
| 505 |
ax.invert_xaxis()
|
| 506 |
ax.text(24.5, -24.5, s='β Arm Side', fontstyle='italic', ha='left', va='bottom',
|
| 507 |
+
bbox=dict(facecolor='white', edgecolor='black'), fontsize=16, zorder=3)
|
| 508 |
ax.text(-24.5, -24.5, s='Glove Side β', fontstyle='italic', ha='right', va='bottom',
|
| 509 |
+
bbox=dict(facecolor='white', edgecolor='black'), fontsize=16, zorder=3)
|
| 510 |
|
| 511 |
# Set aspect ratio and format axis ticks
|
| 512 |
ax.set_aspect('equal', adjustable='box')
|