nesticot commited on
Commit
ee0d83f
·
verified ·
1 Parent(s): 25249d0

Update functions/PitchPlotFunctions.py

Browse files
Files changed (1) hide show
  1. functions/PitchPlotFunctions.py +44 -23
functions/PitchPlotFunctions.py CHANGED
@@ -282,6 +282,8 @@ class PitchPlotFunctions:
282
  ax:plt.Axes,
283
  n_std:float=3.0,
284
  facecolor:str='none',
 
 
285
  **kwargs):
286
  """
287
  Create a plot of the covariance confidence ellipse of *x* and *y*.
@@ -293,6 +295,10 @@ class PitchPlotFunctions:
293
  The axes object to draw the ellipse into.
294
  n_std : float
295
  The number of standard deviations to determine the ellipse's radiuses.
 
 
 
 
296
  **kwargs
297
  Forwarded to `~matplotlib.patches.Ellipse`
298
  Returns
@@ -310,7 +316,7 @@ class PitchPlotFunctions:
310
  ell_radius_x = np.sqrt(1 + pearson)
311
  ell_radius_y = np.sqrt(1 - pearson)
312
  ellipse = Ellipse((0, 0), width=ell_radius_x * 2, height=ell_radius_y * 2,
313
- facecolor=facecolor,linewidth=2,linestyle='--', **kwargs)
314
 
315
 
316
  # Calculating the standard deviation of x from
@@ -339,7 +345,7 @@ class PitchPlotFunctions:
339
  return ax.add_patch(ellipse)
340
 
341
 
342
- def break_plot_big(self, df: pl.DataFrame, ax: plt.Axes, sport_id: int):
343
  """
344
  Plots a big break plot for the given DataFrame on the provided axis.
345
 
@@ -347,6 +353,7 @@ class PitchPlotFunctions:
347
  df (pl.DataFrame): The DataFrame containing pitch data.
348
  ax (plt.Axes): The matplotlib axis to plot on.
349
  sport_id (int): The sport ID to determine the plot title.
 
350
  """
351
  # Set font properties for different elements of the plot
352
  font_properties = {'size': 10}
@@ -355,26 +362,32 @@ class PitchPlotFunctions:
355
 
356
  # Get unique pitch types sorted by 'prop' and 'pitch_type'
357
  label_labels = df.sort(by=['prop', 'pitch_type'], descending=[False, True])['pitch_type'].unique()
358
- j = 0
359
  dict_colour, dict_pitch = self.pitch_colours()
360
  custom_theme, colour_palette = self.sns_custom_theme()
361
 
362
- # Loop through each pitch type and plot confidence ellipses
363
- for label in label_labels:
364
- subset = df.filter(pl.col('pitch_type') == label)
365
- if len(subset) > 4:
366
- try:
367
- if df['pitcher_hand'][0] == 'R':
368
- self.confidence_ellipse(subset['hb']* 1, subset['ivb'], ax=ax, edgecolor=dict_colour[label], n_std=2, facecolor=dict_colour[label], alpha=0.2)
369
- if df['pitcher_hand'][0] == 'L':
370
- self.confidence_ellipse(subset['hb'] * 1, subset['ivb'], ax=ax, edgecolor=dict_colour[label], n_std=2, facecolor=dict_colour[label], alpha=0.2)
371
- except ValueError:
372
- return
373
- j += 1
374
- else:
375
- j += 1
 
 
 
 
 
 
 
376
 
377
- # Plot scatter plot of pitch data
378
  if df['pitcher_hand'][0] == 'R':
379
  sns.scatterplot(ax=ax, x=df['hb'] * 1, y=df['ivb'] * 1, hue=df['pitch_type'], palette=dict_colour, ec='black', alpha=1, zorder=2, s=35)
380
  if df['pitcher_hand'][0] == 'L':
@@ -572,7 +585,7 @@ class PitchPlotFunctions:
572
  )
573
  return df
574
 
575
- def final_plot(self, df: pl.DataFrame, pitcher_id: str, plot_picker: str, sport_id: int,game_type: list = ['R']):
576
  """
577
  Creates a final plot with player headshot, bio, logo, and pitch movement plots.
578
 
@@ -581,6 +594,7 @@ class PitchPlotFunctions:
581
  pitcher_id (str): The ID of the pitcher.
582
  plot_picker (str): The type of plot to create ('short_form_movement', 'long_form_movement', 'release_point').
583
  sport_id (int): The sport ID to determine the plot title.
 
584
  """
585
  # Set the theme for seaborn plots
586
  sns.set_theme(style="whitegrid", rc=self.sns_custom_theme()[0])
@@ -602,7 +616,7 @@ class PitchPlotFunctions:
602
 
603
  # Plot player headshot, bio, and logo
604
  self.player_headshot(pitcher_id=pitcher_id, ax=ax_headshot, sport_id=sport_id)
605
- self.player_bio(pitcher_id=pitcher_id, ax=ax_bio, start_date=start_date, end_date=end_date, batter_hand=batter_hand,game_type=game_type)
606
  self.plot_logo(pitcher_id=pitcher_id, ax=ax_logo)
607
 
608
  # Create subplot for the main plot
@@ -617,7 +631,7 @@ class PitchPlotFunctions:
617
 
618
  # Plot the selected pitch movement plot
619
  if plot_picker == 'short_form_movement':
620
- self.break_plot_big(df, ax_main_plot, sport_id=sport_id)
621
  elif plot_picker == 'long_form_movement':
622
  self.break_plot_big_long(df, ax_main_plot, sport_id=sport_id)
623
  elif plot_picker == 'release_point':
@@ -634,11 +648,18 @@ class PitchPlotFunctions:
634
  # Create custom legend handles with circles
635
  legend_handles = [mlines.Line2D([], [], color=color, marker='o', linestyle='None', markersize=5, label=label) for color, label in zip(ordered_colors, items_in_order)]
636
 
 
 
 
 
 
 
637
  # Add legend to ax_legend
 
638
  if len(items_in_order) <= 5:
639
- ax_legend.legend(handles=legend_handles, bbox_to_anchor=(0.1, 0, 0.8, 0.7), ncol=5, fancybox=True, loc='center', fontsize=10, framealpha=1.0, markerscale=2, prop={'size': 10})
640
  else:
641
- ax_legend.legend(handles=legend_handles, bbox_to_anchor=(0.1, 0, 0.8, 0.7), ncol=5, fancybox=True, loc='center', fontsize=10, framealpha=1.0, markerscale=2, prop={'size': 10})
642
 
643
  # Add footer text
644
  ax_footer.text(x=0.075, y=0, s='By: Thomas Nestico\n @TJStats', fontname='Calibri', ha='left', fontsize=12, va='bottom')
 
282
  ax:plt.Axes,
283
  n_std:float=3.0,
284
  facecolor:str='none',
285
+ linestyle:str='--',
286
+ linewidth:float=2,
287
  **kwargs):
288
  """
289
  Create a plot of the covariance confidence ellipse of *x* and *y*.
 
295
  The axes object to draw the ellipse into.
296
  n_std : float
297
  The number of standard deviations to determine the ellipse's radiuses.
298
+ linestyle : str
299
+ The line style for the ellipse edge (default: '--').
300
+ linewidth : float
301
+ The line width for the ellipse edge (default: 2).
302
  **kwargs
303
  Forwarded to `~matplotlib.patches.Ellipse`
304
  Returns
 
316
  ell_radius_x = np.sqrt(1 + pearson)
317
  ell_radius_y = np.sqrt(1 - pearson)
318
  ellipse = Ellipse((0, 0), width=ell_radius_x * 2, height=ell_radius_y * 2,
319
+ facecolor=facecolor, linewidth=linewidth, linestyle=linestyle, **kwargs)
320
 
321
 
322
  # Calculating the standard deviation of x from
 
345
  return ax.add_patch(ellipse)
346
 
347
 
348
+ def break_plot_big(self, df: pl.DataFrame, ax: plt.Axes, sport_id: int, compare_df: pl.DataFrame = None):
349
  """
350
  Plots a big break plot for the given DataFrame on the provided axis.
351
 
 
353
  df (pl.DataFrame): The DataFrame containing pitch data.
354
  ax (plt.Axes): The matplotlib axis to plot on.
355
  sport_id (int): The sport ID to determine the plot title.
356
+ compare_df (pl.DataFrame): Optional DataFrame for comparison data (draws only ellipses, no points).
357
  """
358
  # Set font properties for different elements of the plot
359
  font_properties = {'size': 10}
 
362
 
363
  # Get unique pitch types sorted by 'prop' and 'pitch_type'
364
  label_labels = df.sort(by=['prop', 'pitch_type'], descending=[False, True])['pitch_type'].unique()
 
365
  dict_colour, dict_pitch = self.pitch_colours()
366
  custom_theme, colour_palette = self.sns_custom_theme()
367
 
368
+ # Draw comparison ellipses first (if comparison data provided) - dashed lines, no fill
369
+ if compare_df is not None and len(compare_df) > 0:
370
+ compare_labels = compare_df['pitch_type'].unique()
371
+ for label in compare_labels:
372
+ subset = compare_df.filter(pl.col('pitch_type') == label)
373
+ if len(subset) > 4:
374
+ try:
375
+ # Draw comparison ellipses with dashed lines and lighter color
376
+ self.confidence_ellipse(
377
+ subset['hb'] * 1,
378
+ subset['ivb'],
379
+ ax=ax,
380
+ edgecolor=dict_colour.get(label, '#888888'),
381
+ n_std=2,
382
+ facecolor='none', # No fill for comparison
383
+ alpha=0.8,
384
+ linestyle='--',
385
+ linewidth=2.5
386
+ )
387
+ except ValueError:
388
+ pass
389
 
390
+ # Plot scatter plot of pitch data (no ellipses for original data)
391
  if df['pitcher_hand'][0] == 'R':
392
  sns.scatterplot(ax=ax, x=df['hb'] * 1, y=df['ivb'] * 1, hue=df['pitch_type'], palette=dict_colour, ec='black', alpha=1, zorder=2, s=35)
393
  if df['pitcher_hand'][0] == 'L':
 
585
  )
586
  return df
587
 
588
+ def final_plot(self, df: pl.DataFrame, pitcher_id: str, plot_picker: str, sport_id: int, game_type: list = ['R'], compare_df: pl.DataFrame = None):
589
  """
590
  Creates a final plot with player headshot, bio, logo, and pitch movement plots.
591
 
 
594
  pitcher_id (str): The ID of the pitcher.
595
  plot_picker (str): The type of plot to create ('short_form_movement', 'long_form_movement', 'release_point').
596
  sport_id (int): The sport ID to determine the plot title.
597
+ compare_df (pl.DataFrame): Optional DataFrame for comparison data (draws only ellipses).
598
  """
599
  # Set the theme for seaborn plots
600
  sns.set_theme(style="whitegrid", rc=self.sns_custom_theme()[0])
 
616
 
617
  # Plot player headshot, bio, and logo
618
  self.player_headshot(pitcher_id=pitcher_id, ax=ax_headshot, sport_id=sport_id)
619
+ self.player_bio(pitcher_id=pitcher_id, ax=ax_bio, start_date=start_date, end_date=end_date, batter_hand=batter_hand, game_type=game_type)
620
  self.plot_logo(pitcher_id=pitcher_id, ax=ax_logo)
621
 
622
  # Create subplot for the main plot
 
631
 
632
  # Plot the selected pitch movement plot
633
  if plot_picker == 'short_form_movement':
634
+ self.break_plot_big(df, ax_main_plot, sport_id=sport_id, compare_df=compare_df)
635
  elif plot_picker == 'long_form_movement':
636
  self.break_plot_big_long(df, ax_main_plot, sport_id=sport_id)
637
  elif plot_picker == 'release_point':
 
648
  # Create custom legend handles with circles
649
  legend_handles = [mlines.Line2D([], [], color=color, marker='o', linestyle='None', markersize=5, label=label) for color, label in zip(ordered_colors, items_in_order)]
650
 
651
+ # Add comparison indicator to legend if comparison data is present
652
+ if compare_df is not None and len(compare_df) > 0:
653
+ # Add a dashed line legend entry to indicate comparison ellipses
654
+ compare_handle = mlines.Line2D([], [], color='gray', linestyle='--', linewidth=2, label='Comparison (dashed ellipse)')
655
+ legend_handles.append(compare_handle)
656
+
657
  # Add legend to ax_legend
658
+ ncols = min(len(legend_handles), 6)
659
  if len(items_in_order) <= 5:
660
+ ax_legend.legend(handles=legend_handles, bbox_to_anchor=(0.1, 0, 0.8, 0.7), ncol=ncols, fancybox=True, loc='center', fontsize=10, framealpha=1.0, markerscale=2, prop={'size': 10})
661
  else:
662
+ ax_legend.legend(handles=legend_handles, bbox_to_anchor=(0.1, 0, 0.8, 0.7), ncol=ncols, fancybox=True, loc='center', fontsize=10, framealpha=1.0, markerscale=2, prop={'size': 10})
663
 
664
  # Add footer text
665
  ax_footer.text(x=0.075, y=0, s='By: Thomas Nestico\n @TJStats', fontname='Calibri', ha='left', fontsize=12, va='bottom')