############################################################################## # Set color to make the lines more transparent when fewer passes are made MIN_TRANSPARENCY = 0.3 color = np.array(to_rgba('white')) color = np.tile(color, (len(passes_between), 1)) c_transparency = passes_between.pass_count / passes_between.pass_count.max() c_transparency = (c_transparency * (1 - MIN_TRANSPARENCY)) + MIN_TRANSPARENCY color[:, 3] = c_transparency ############################################################################## # Plotting pitch = Pitch( pitch_type='statsbomb', pitch_color='#22312b', line_color='#c7d5cc', ) fig, ax = pitch.draw(figsize=(16, 11), constrained_layout=True, tight_layout=False) pass_lines = pitch.lines(passes_between.x, passes_between.y, passes_between.x_end, passes_between.y_end, lw=passes_between.width, color=color, zorder=1, ax=ax) pass_nodes = pitch.scatter(average_locs_and_count.x, average_locs_and_count.y,
import cmasher as cmr import matplotlib.pyplot as plt import numpy as np from PIL import Image from highlight_text import ax_text from mplsoccer import Pitch, VerticalPitch, add_image, FontManager from mplsoccer.statsbomb import read_event, read_lineup, EVENT_SLUG, LINEUP_SLUG ############################################################################## # Single Pitch # ------------ # You can also use grid to plot a single pitch with an endnote and title axis. # The defaults setup a pitch with a 2.5% # (of the figure height/ width) border around the sides. pitch = Pitch() fig, axs = pitch.grid() ############################################################################## # Multiple Pitches side by side # ----------------------------- # Next up let's plot 3 pitches side-by-side. pitch = VerticalPitch() fig, axs = pitch.grid(figheight=15, ncols=3) ############################################################################## # Grid of Pitches # --------------- # Here's how to plot a grid of pitches pitch = Pitch(linewidth=4) fig, axs = pitch.grid(nrows=3, ncols=4, # number of rows/ columns
============ Pitch Basics ============ First we import the Pitch classes and matplotlib """ import matplotlib.pyplot as plt from mplsoccer import Pitch, VerticalPitch ############################################################################## # Draw a pitch on a new axis # -------------------------- # Let's plot on a new axis first. pitch = Pitch() # specifying figure size (width, height) fig, ax = pitch.draw(figsize=(8, 4)) ############################################################################## # Draw on an existing axis # ------------------------ # mplsoccer also plays nicely with other matplotlib figures. To draw a pitch on an # existing matplotlib axis specify an ``ax`` in the ``draw`` method. fig, axs = plt.subplots(nrows=1, ncols=2) pitch = Pitch() pie = axs[0].pie(x=[5, 15]) pitch.draw(ax=axs[1]) ##############################################################################
# Show the away data df_away.head() ############################################################################## # Show the home data df_home.head() ############################################################################## # Show the ball data df_ball.head() ############################################################################## # Plot the animation # First set up the figure, the axis pitch = Pitch(pitch_type='metricasports', goal_type='line', pitch_width=68, pitch_length=105) fig, ax = pitch.draw(figsize=(16, 10.4)) # then setup the pitch plot markers we want to animate marker_kwargs = {'marker': 'o', 'markeredgecolor': 'black', 'linestyle': 'None'} ball, = ax.plot([], [], ms=6, markerfacecolor='w', zorder=3, **marker_kwargs) away, = ax.plot([], [], ms=10, markerfacecolor='#b94b75', **marker_kwargs) # red/maroon home, = ax.plot([], [], ms=10, markerfacecolor='#7f63b8', **marker_kwargs) # purple # animation function def animate(i): """ Function to animate the data. Each frame it sets the data for the players and the ball.""" # set the ball data with the x and y positions for the ith frame ball.set_data(df_ball.iloc[i, 3], df_ball.iloc[i, 4]) # get the frame id for the ith frame
'connectionstyle': 'angle3,angleA=0,angleB=-90', 'color': FONTCOLOR } font_kwargs = { 'fontsize': 14, 'ha': 'center', 'va': 'bottom', 'fontweight': 'bold', 'fontstyle': 'italic', 'c': FONTCOLOR } for idx, pt in enumerate(pitch_types): if pt in ['tracab', 'metricasports', 'custom', 'skillcorner']: pitch = Pitch(pitch_type=pt, pitch_length=105, pitch_width=68, **pitch_kwargs) else: pitch = Pitch(pitch_type=pt, **pitch_kwargs) pitch.draw(axes[idx]) xmin, xmax, ymin, ymax = pitch.extent if pitch.dim.aspect != 1: TEXT = 'data coordinates \n are square (1:1) \n scale up to a real-pitch size' axes[idx].annotate(TEXT, xy=(xmin, ymin), xytext=(0 + (xmax - xmin) / 2, ymin), **font_kwargs) axes[idx].xaxis.set_ticks([xmin, xmax]) axes[idx].yaxis.set_ticks([ymin, ymax]) axes[idx].tick_params(labelsize=15) if pt == 'skillcorner':
coord['team'], player_size, player_opacity) st.image(draw.compose_image(sensitivity)) with o_col3: draw = PitchDraw(image, original=False) for pid, coord in dfCoords.iterrows(): draw.draw_circle(coord[['x', 'y']].values, coord['team'], 2, player_opacity) draw.draw_text(coord[['x', 'y']] + 0.5, f"{pid}", coord['team']) st.image(draw.compose_image(sensitivity)) st.markdown(get_table_download_link( dfCoords[['team', 'x', 'y']]), unsafe_allow_html=True) #mplsoccer based output viz _, mpl_output, _ = st.beta_columns( (1, 2, 1)) #temporary, as fig size doesn't seem to be working from mplsoccer import Pitch pitch = Pitch(pitch_type='statsbomb', pitch_color='#22312b') fig, ax = pitch.draw() pitch.scatter(dfCoords.x, dfCoords.y, c=dfCoords.team, ax=ax, s=120, edgecolors='white') with mpl_output: st.pyplot(fig)
############################################################################## # Rotated markers # --------------- # I also included a method for rotating markers in mplsoccer. # # Warning: The rotation angle is in degrees and assumes the original marker is pointing upwards ↑. # If it's not you will have to modify the rotation degrees. # Rotates the marker in degrees, clockwise. 0 degrees is facing the # direction of play (left to right). # In a horizontal pitch, 0 degrees is this way →, in a vertical pitch, 0 degrees is this way ↑ # # We are going to plot pass data as an arrowhead marker with the # arrow facing in the direction of the pass # The marker size is going to relate to the pass distance, # so larger markers mean the pass was longer. pitch = Pitch() fig, ax = pitch.draw(figsize=(14, 12)) angle, distance = pitch.calculate_angle_and_distance(df_pass_barca.x, df_pass_barca.y, df_pass_barca.end_x, df_pass_barca.end_y, standardized=False, degrees=True) sc = pitch.scatter( df_pass_barca.x, df_pass_barca.y, rotation_degrees=angle, c='#b94b75', # color for scatter in hex format edgecolors='#383838', alpha=0.9, s=(distance / distance.max()) * 900,
############################################################################## # Boolean mask for filtering the dataset by team team1, team2 = df.team_name.unique() mask_team1 = (df.type_name == 'Pass') & (df.team_name == team1) ############################################################################## # Filter dataset to only include one teams passes and get boolean mask for the completed passes df_pass = df.loc[mask_team1, ['x', 'y', 'end_x', 'end_y', 'outcome_name']] mask_complete = df_pass.outcome_name.isnull() ############################################################################## # Setup the pitch and number of bins pitch = Pitch(pitch_type='statsbomb', line_zorder=2, line_color='#c7d5cc', pitch_color='#22312b') bins = (6, 4) ############################################################################## # Plotting using a single color and length fig, ax = pitch.draw(figsize=(16, 11), constrained_layout=True, tight_layout=False) fig.set_facecolor('#22312b') # plot the heatmap - darker colors = more passes originating from that square bs_heatmap = pitch.bin_statistic(df_pass.x, df_pass.y, statistic='count', bins=bins) hm = pitch.heatmap(bs_heatmap, ax=ax, cmap='Blues')
# Get the StatsBomb logo and Fonts LOGO_URL = 'https://raw.githubusercontent.com/statsbomb/open-data/master/img/statsbomb-logo.jpg' sb_logo = Image.open(urlopen(LOGO_URL)) # a FontManager object for using a google font (default Robotto) fm = FontManager() # path effects path_eff = [path_effects.Stroke(linewidth=3, foreground='black'), path_effects.Normal()] ############################################################################## # Plot the percentages # setup a mplsoccer pitch pitch = Pitch(line_zorder=2, line_color='black', pad_top=20) # mplsoccer calculates the binned statistics usually from raw locations, such as pressure events # for this example we will create a binned statistic dividing # the pitch into thirds for one point (0, 0) # we will fill this in a loop later with each team's statistics from the dataframe bin_statistic = pitch.bin_statistic([0], [0], statistic='count', bins=(3, 1)) GRID_HEIGHT = 0.8 CBAR_WIDTH = 0.03 fig, axs = pitch.grid(nrows=4, ncols=5, figheight=20, # leaves some space on the right hand side for the colorbar grid_width=0.88, left=0.025, endnote_height=0.06, endnote_space=0, # Turn off the endnote/title axis. I usually do this after # I am happy with the chart layout and text placement
df_total = pd.DataFrame(df[pressure_cols].sum()) df_total.columns = ['total'] df_total = df_total.T df_total = df_total.divide(df_total.sum(axis=1), axis=0) * 100 ############################################################################## # Calculate the percentages for each team and sort so that the teams which press higher are last df[pressure_cols] = df[pressure_cols].divide(df[pressure_cols].sum(axis=1), axis=0) * 100. df.sort_values(['Att 3rd', 'Def 3rd'], ascending=[True, False], inplace=True) ############################################################################## # Plot the percentages # setup a mplsoccer pitch pitch = Pitch(line_zorder=2, line_color='black') # mplsoccer calculates the binned statistics usually from raw locations, such as pressure events # for this example we will create a binned statistic dividing # the pitch into thirds for one point (0, 0) # we will fill this in a loop later with each team's statistics from the dataframe bin_statistic = pitch.bin_statistic([0], [0], statistic='count', bins=(3, 1)) # Plot fig, axes = pitch.draw(figsize=(16, 9), ncols=5, nrows=4, tight_layout=False, constrained_layout=True) axes = axes.ravel() teams = df['Squad'].values
kwargs = { 'related_event_df': False, 'shot_freeze_frame_df': False, 'tactics_lineup_df': False, 'warn': False } df_false9 = read_event(f'{EVENT_SLUG}/69249.json', **kwargs)['event'] df_before_false9 = read_event(f'{EVENT_SLUG}/69251.json', **kwargs)['event'] # filter messi's actions (starting positions) df_false9 = df_false9.loc[df_false9.player_id == 5503, ['x', 'y']] df_before_false9 = df_before_false9.loc[df_before_false9.player_id == 5503, ['x', 'y']] ############################################################################## # plotting pitch = Pitch(pitch_type='statsbomb', pitch_color='#22312b', stripe=False, line_zorder=2) fig, ax = pitch.draw( figsize=(16, 9), nrows=1, ncols=2, ) pitch.hexbin(df_before_false9.x, df_before_false9.y, ax=ax[0], cmap='Blues') pitch.hexbin(df_false9.x, df_false9.y, ax=ax[1], cmap='Blues') TITLE_STR1 = 'Messi in the game directly before \n playing in the false 9 role' TITLE_STR2 = 'The first Game Messi \nplayed in the false 9 role' title1 = ax[0].set_title(TITLE_STR1, fontsize=25, pad=20) title2 = ax[1].set_title(TITLE_STR2, fontsize=25, pad=20)
'related_event_df': False, 'shot_freeze_frame_df': False, 'tactics_lineup_df': False, 'warn': False } df = pd.concat([ read_event(f'{EVENT_SLUG}/{file}', **kwargs)['event'] for file in match_files ]) # filter chelsea pressure events mask_chelsea_pressure = (df.team_name == 'Chelsea FCW') & (df.type_name == 'Pressure') df = df.loc[mask_chelsea_pressure, ['x', 'y']] ############################################################################## # Plot the heatmaps # setup pitch pitch = Pitch(pitch_type='statsbomb', line_zorder=2, line_color='white') # draw fig, ax = pitch.draw(figsize=(16, 9)) bin_statistic = pitch.bin_statistic(df.x, df.y, statistic='count', bins=(25, 25)) bin_statistic['statistic'] = gaussian_filter(bin_statistic['statistic'], 1) pcm = pitch.heatmap(bin_statistic, ax=ax, cmap='hot', edgecolors='#22312b') cbar = fig.colorbar(pcm, ax=ax) TITLE_STR = 'Location of pressure events - 3 home games for Chelsea FC Women' title = fig.suptitle(TITLE_STR, x=0.4, y=0.98, fontsize=23)
fig = plt.figure(figsize=FIGSIZE) fm_rubik = FontManager(('https://github.com/google/fonts/blob/main/ofl/rubikmonoone/' 'RubikMonoOne-Regular.ttf?raw=true')) # layout specifications PAD = 1 pitch_spec = {'pad_left': PAD, 'pad_right': PAD, 'pad_bottom': PAD, 'pad_top': PAD, 'pitch_color': 'None'} pitch_width, pitch_length = 80, 105 pitch_width3, pitch_length3 = 60, 105 pitch_length4, pitch_width4 = 120, 68 pitch_length6, pitch_width6 = 85, 68 # define pitches (top left, top middle, top right, bottom left, bottom middle, bottom right) pitch1 = Pitch(pitch_type='custom', pitch_width=pitch_width, pitch_length=pitch_length, line_color='#b94e45', **pitch_spec) pitch2 = Pitch(pitch_type='statsbomb', **pitch_spec) pitch3 = Pitch(pitch_type='custom', pitch_width=pitch_width3, pitch_length=pitch_length3, line_color='#56ae6c', **pitch_spec) pitch4 = VerticalPitch(pitch_type='custom', pitch_length=pitch_length4, pitch_width=pitch_width4, line_color='#bc7d39', **pitch_spec) pitch5 = VerticalPitch(pitch_type='statsbomb', **pitch_spec) pitch6 = VerticalPitch(pitch_type='custom', pitch_length=pitch_length6, pitch_width=pitch_width6, line_color='#677ad1', **pitch_spec) TITLE_HEIGHT = 0.1 # title axes are 10% of the figure height # width of pitch axes as percent of the figure width TOP_WIDTH = 0.27 BOTTOM_WIDTH = 0.18
# Load an image of Messi # ###################### # load the image IMAGE_URL = 'https://upload.wikimedia.org/wikipedia/commons/b/b8/Messi_vs_Nigeria_2018.jpg' image = Image.open(urlopen(IMAGE_URL)) ############################################################################## # Plotting an image over a pitch # ############################## # # To plot images you use ``Axes.imshow()`` in matplotlib. # We are going to draw a pitch and then overlay ontop an image of Messi on a new axis. # draw the pitch pitch = Pitch(line_zorder=2) fig, ax = pitch.draw(figsize=(16, 9), tight_layout=False) # add an image ax_image = add_image(image, fig, left=0.55, bottom=0.2, width=0.2, alpha=0.9, interpolation='hanning') ############################################################################## # Photo from: https://en.wikipedia.org/wiki/Lionel_Messi#/media/File:Messi_vs_Nigeria_2018.jpg; # License: https://creativecommons.org/licenses/by-sa/3.0/; # Creator: Кирилл Венедиктов ############################################################################## # More control over the images and axis # ##################################### #
""" =========== Quick start =========== """ from mplsoccer import Pitch pitch = Pitch(pitch_color='grass', line_color='white', stripe=True) fig, ax = pitch.draw()
# Now creating a DataFrame and Storing the DataFrame in a dictionary with match id as Key df = json_normalize(data, sep='_').assign(match_id=filename[-9:-5]) # In[13]: # A dataframe of only Shots shots = df[df['type_name'] == 'Shot'].set_index('id') shots.head() # In[15]: # fig,ax = createPitch(pitchLengthX,pitchWidthY,'yards','gray') pitch = Pitch( pitch_type='custom', # example plotting a tracab pitch pitch_length=pitchLengthX, pitch_width=pitchWidthY, axis=True, label=True) # showing axis labels is optional fig, ax = pitch.draw(figsize=(16, 11), constrained_layout=False, tight_layout=True) # Now lets plot the shots for i, shot in shots.iterrows(): x = shot['location'][0] y = shot['location'][1] goal = shot['shot_outcome_name'] == 'Goal' team_name = shot['team_name'] circleSize = 2 #circleSize=np.sqrt(shot['shot_statsbomb_xg'])*12
# the pass comet lines when using capstyle='round' ALPHA_PITCH_LINE = 0.3 ALPHA_PASS_LINE = 0.15 # The colors are borrowed from mplcyberpunk. Try some of the following alternatives # '#08F7FE' (teal/cyan), '#FE53BB' (pink), '#F5D300' (yellow), # '#00ff41' (matrix green), 'r' (red), '#9467bd' (viloet) BACKGROUND_COLOR = '#212946' PASS_COLOR = '#FE53BB' LINE_COLOR = '#08F7FE' # plot as initial pitch and the lines with alpha=1 # I have used grid to get a title and endnote axis automatically, but you could you pitch.draw() pitch = Pitch(line_color=LINE_COLOR, pitch_color=BACKGROUND_COLOR, linewidth=LINEWIDTH, line_alpha=1, goal_alpha=1, goal_type='box') fig, ax = pitch.grid(grid_height=0.9, title_height=0.06, axis=False, endnote_height=0.04, title_space=0, endnote_space=0) fig.set_facecolor(BACKGROUND_COLOR) pitch.lines( df_pass.x, df_pass.y, df_pass.end_x, df_pass.end_y, capstyle='butt', # cut-off the line at the end-location.
############################################################################## # Filter the dataframes to only include Messi's events and the starting positions df_false9 = df_false9.loc[df_false9.player_id == 5503, ['x', 'y']] df_before_false9 = df_before_false9.loc[df_before_false9.player_id == 5503, ['x', 'y']] ############################################################################## # View a dataframe df_false9.head() ############################################################################## # Plotting Messi's first game as a False-9 pitch = Pitch(pitch_type='statsbomb', pitch_color='grass', stripe=True) fig, ax = pitch.draw(figsize=(16, 11)) # plotting ax.set_title('The first Game Messi played in the false 9 role', fontsize=30, pad=20) # plot the kernel density estimation pitch.kdeplot(df_false9.x, df_false9.y, ax=ax, cmap='plasma', linewidths=3) # annotate pitch.annotate('6-2 thrashing \nof Real Madrid', (25, 10), color='white', fontsize=25, ha='center',
# I created a function to calculate the maximum dimensions you can get away with while # having a set figure size. Let's use this to create the largest pitch possible # with a 16:9 figure aspect ratio. FIGWIDTH = 16 FIGHEIGHT = 9 NROWS = 1 NCOLS = 1 # here we want the maximum side in proportion to the figure dimensions # (height in this case) to take up all of the image MAX_GRID = 1 # pitch with minimal padding (2 each side) pitch = Pitch(pad_top=2, pad_bottom=2, pad_left=2, pad_right=2, pitch_color='#22312b') # calculate the maximum grid_height/ width GRID_WIDTH, GRID_HEIGHT = pitch.calculate_grid_dimensions(figwidth=FIGWIDTH, figheight=FIGHEIGHT, nrows=NROWS, ncols=NCOLS, max_grid=MAX_GRID, space=0) # plot using the mplsoccer grid function fig, ax = pitch.grid(figheight=FIGHEIGHT, grid_width=GRID_WIDTH, grid_height=GRID_HEIGHT,