Spaces:
Running
Running
Update functions/PitchPlotFunctions.py
Browse files- 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=
|
| 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 |
-
#
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
self.confidence_ellipse(
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 640 |
else:
|
| 641 |
-
ax_legend.legend(handles=legend_handles, bbox_to_anchor=(0.1, 0, 0.8, 0.7), ncol=
|
| 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')
|