nesticot commited on
Commit
e87fba1
Β·
verified Β·
1 Parent(s): d7e7811

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=12, zorder=3)
437
  ax.text(24.5, -24.5, s='Arm Side β†’', fontstyle='italic', ha='right', va='bottom',
438
- bbox=dict(facecolor='white', edgecolor='black'), fontsize=12, zorder=3)
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=12, zorder=3)
443
  ax.text(-24.5, -24.5, s='Glove Side β†’', fontstyle='italic', ha='right', va='bottom',
444
- bbox=dict(facecolor='white', edgecolor='black'), fontsize=12, zorder=3)
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')