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 figheight=25, # the figure height in inches bottom=0.025, # starts 2.5% in from the figure bottom # grid takes up 83% of the figure height # I calculated this so most of the figure is pitches # 1 - (bottom + endnote_height + endnote_space + # title_height + title_space) - 0.025 [space at top] grid_height=0.83,
pitch_to='custom', length_to=105, width_to=68) x_relative, y_relative = opta_to_custom.transform(x, y) # plotting to show that events can move from their pitch markings # without relative scaling to the pitch markings # first setup the figure FIGWIDTH = 16 FIGHEIGHT = 6 fig = plt.figure(figsize=(FIGWIDTH, FIGHEIGHT)) opta_pitch = VerticalPitch(pitch_type='opta', line_color='black', half=True, line_zorder=3) ax_opta = fig.add_axes( (0.05, 0.05, 0.4, 0.4 * FIGWIDTH / FIGHEIGHT / opta_pitch.ax_aspect)) opta_pitch.draw(ax=ax_opta) custom_pitch = VerticalPitch(pitch_type='custom', line_color='black', half=True, pitch_length=105, pitch_width=68, line_zorder=3) ax_custom = fig.add_axes( (0.55, 0.05, 0.4, 0.4 * FIGWIDTH / FIGHEIGHT / custom_pitch.ax_aspect)) custom_pitch.draw(ax=ax_custom)
# # See the docs for more info: https://cmasher.readthedoavorite cs.io/. cmap_dict = cmr.cm.cmap_cd all_cmap_dict = {} for cmap_type_key in cmap_dict: for key, cmap in cmap_dict[cmap_type_key].items(): if key[-2:] != '_r': all_cmap_dict[key] = cmap ############################################################################## # Cmasher kdeplot # --------------- # Here's all the cmasher colormaps plotted as a grid so you can find your # favorite. pitch = VerticalPitch(line_color='#cfcfcf', line_zorder=2, pitch_color='#122c3d') fig, axs = pitch.grid( nrows=11, ncols=4, space=0.1, figheight=40, title_height=0, endnote_height=0, # no title/ endnote grid_width=0.9, grid_height=0.98, bottom=0.01, left=0.05) cmap_names = list(all_cmap_dict.keys()) for idx, ax in enumerate(axs['pitch'].flat): cmap_name = f'cmr.{cmap_names[idx]}'
SHOT_ID = '8bb8bbc2-68a6-4c01-93de-53a194e7a1cf' df_freeze_frame = df_freeze[df_freeze.id == SHOT_ID].copy() df_shot_event = df_event[df_event.id == SHOT_ID].dropna(axis=1, how='all').copy() ############################################################################## # Location dataset df = pd.concat([df_shot_event[['x', 'y']], df_freeze_frame[['x', 'y']]]) x = df.x.values y = df.y.values teams = np.concatenate([[True], df_freeze_frame.player_teammate.values]) ############################################################################## # Plotting # draw plot pitch = VerticalPitch(half=True) fig, ax = pitch.draw(figsize=(8, 6.2)) # Plot Voronoi team1, team2 = pitch.voronoi(x, y, teams) t1 = pitch.polygon(team1, ax=ax, fc='#c34c45', ec='white', lw=3, alpha=0.4) t2 = pitch.polygon(team2, ax=ax, fc='#6f63c5', ec='white', lw=3, alpha=0.4) # Plot players sc1 = pitch.scatter(x[teams], y[teams], ax=ax, c='#c34c45', s=150) sc2 = pitch.scatter(x[~teams], y[~teams], ax=ax, c='#6f63c5', s=150)
############################################################################## # Pitch orientation # ----------------- # There are four basic pitch orientations. # To get vertical pitches use the VerticalPitch class. # To get half pitches use the half=True argument. # # Horizontal full pitch = Pitch(half=False) fig, ax = pitch.draw() ############################################################################## # Vertical full pitch = VerticalPitch(half=False) fig, ax = pitch.draw() ############################################################################## # Horizontal half pitch = Pitch(half=True) fig, ax = pitch.draw() ############################################################################## # Vertical half pitch = VerticalPitch(half=True) fig, ax = pitch.draw() ############################################################################## # You can also adjust the pitch orientations with the ``pad_left``, ``pad_right``, # ``pad_bottom`` and ``pad_top`` arguments to make arbitrary pitch shapes.
import matplotlib.pyplot as plt from mplsoccer import Pitch, VerticalPitch from mplsoccer.statsbomb import read_event, EVENT_SLUG from mplsoccer.utils import FontManager # get data for a Sevilla versus Barcelona match with a high amount of shots kwargs = {'related_event_df': False, 'shot_freeze_frame_df': False, 'tactics_lineup_df': False, 'warn': False} df = read_event(f'{EVENT_SLUG}/9860.json', **kwargs)['event'] # setup the mplsoccer StatsBomb Pitches # note not much padding around the pitch so the marginal axis are tight to the pitch # if you are using a different goal type you will need to increase the padding to see the goals pitch = Pitch(pad_top=0.05, pad_right=0.05, pad_bottom=0.05, pad_left=0.05, line_zorder=2) vertical_pitch = VerticalPitch(half=True, pad_top=0.05, pad_right=0.05, pad_bottom=0.05, pad_left=0.05, line_zorder=2) # setup a mplsoccer FontManager to download google fonts (Roboto-Regular / SigmarOne-Regular) fm = FontManager() fm_rubik = FontManager(('https://github.com/google/fonts/blob/main/ofl/rubikmonoone/' 'RubikMonoOne-Regular.ttf?raw=true')) ############################################################################## # Subset the shots for each team and move Barcelona's shots to the other side of the pitch. # subset the shots df_shots = df[df.type_name == 'Shot'].copy() # subset the shots for each team team1, team2 = df_shots.team_name.unique() df_team1 = df_shots[df_shots.team_name == team1].copy()
df_shot = (df.loc[(df.type_name == 'Shot') & (df.team_name == TEAM1), ['id', 'outcome_name', 'shot_statsbomb_xg']].rename( {'id': 'pass_assisted_shot_id'}, axis=1)) df_pass = df_pass.merge(df_shot, how='left').drop('pass_assisted_shot_id', axis=1) mask_goal = df_pass.outcome_name == 'Goal' ############################################################################## # This example shows how to plot all passes leading to shots from a team using a colormap (cmap). # Setup the pitch pitch = VerticalPitch(pitch_type='statsbomb', pitch_color='#22312b', line_color='#c7d5cc', half=True, pad_top=2) fig, axs = pitch.grid(endnote_height=0.03, endnote_space=0, figheight=12, title_height=0.08, title_space=0, axis=False, grid_height=0.82) fig.set_facecolor('#22312b') # Plot the completed passes pitch.lines(df_pass.x, df_pass.y, df_pass.end_x,
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']] ############################################################################## # Create a custom colormap. # Note see the `custom colormaps # <https://mplsoccer.readthedocs.io/en/latest/gallery/pitch_plots/plot_cmap.html>`_ # example for more ideas. flamingo_cmap = LinearSegmentedColormap.from_list("Flamingo - 10 colors", ['#e3aca7', '#c03a1d'], N=10) ############################################################################## # Plot Messi's first game as a false-9. pitch = VerticalPitch(line_color='#000009', line_zorder=2, pitch_color='white') fig, ax = pitch.draw(figsize=(4.4, 6.4)) hexmap = pitch.hexbin(df_false9.x, df_false9.y, ax=ax, edgecolors='#f4f4f4', gridsize=(8, 8), cmap=flamingo_cmap) ############################################################################## # Load a custom font. URL = 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Regular.ttf?raw=true' URL2 = 'https://github.com/google/fonts/blob/main/apache/roboto/static/Roboto-Bold.ttf?raw=true' robotto_regular = FontManager(URL) robboto_bold = FontManager(URL2)
'RubikMonoOne-Regular.ttf?raw=true')) ############################################################################## # Shot map Barcelona # ------------------ # First let's plot Barcelona's shots with the scatter marker size varying # by the expected goals amount. The maximum of 1 (100% expected chance of scoring) # has been given size 1000 (points**2). # By multiplying the expected goals amount by 900 and adding 100 # we essentially get a size that varies between 100 and 1000. # For choosing color schemes, I really like this website # `iWantHue <https://medialab.github.io/iwanthue/>`_. pitch = VerticalPitch( pad_bottom=0.5, # pitch extends slightly below halfway line half=True, # half of a pitch goal_type='box', goal_alpha=0.8) # control the goal transparency fig, ax = pitch.draw(figsize=(12, 10)) sc = pitch.scatter( df_shots_barca.x, df_shots_barca.y, # size varies between 100 and 1000 (points squared) s=(df_shots_barca.shot_statsbomb_xg * 900) + 100, c='#b94b75', # color for scatter in hex format edgecolors='#383838', # give the markers a charcoal border # for other markers types see: https://matplotlib.org/api/markers_api.html marker='h', ax=ax) txt = ax.text( x=40,
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']] ############################################################################## # Create a custom colormap. # Note see the `custom colormaps # <https://mplsoccer.readthedocs.io/en/latest/gallery/pitch_plots/plot_cmap.html>`_ # example for more ideas. flamingo_cmap = LinearSegmentedColormap.from_list("Flamingo - 100 colors", ['#e3aca7', '#c03a1d'], N=100) ############################################################################## # Plot Messi's first game as a false-9. pitch = VerticalPitch(line_color='#000009', line_zorder=2) fig, ax = pitch.draw(figsize=(4.4, 6.4)) kde = pitch.kdeplot(df_false9.x, df_false9.y, ax=ax, # shade using 100 levels so it looks smooth shade=True, levels=100, # shade the lowest area so it looks smooth # so even if there are no events it gets some color shade_lowest=True, cut=4, # extended the cut so it reaches the bottom edge cmap=flamingo_cmap) ############################################################################## # Load a custom font. URL = 'https://github.com/googlefonts/roboto/blob/main/src/hinted/Roboto-Regular.ttf?raw=true' URL2 = 'https://github.com/google/fonts/blob/main/apache/roboto/static/Roboto-Bold.ttf?raw=true' robotto_regular = FontManager(URL)
df_wyscout['coordinates_x'] = x_std df_wyscout['coordinates_y'] = y_std ############################################################################## # Add the last name to the dataframes df_statsbomb['last_name'] = df_statsbomb.player_name.str.split(' ').str[-1] df_wyscout['last_name'] = df_wyscout.player_name.str.split(' ').str[-1] ############################################################################## # Plot the standardized data # -------------------------- pitch = VerticalPitch(pitch_type='statsbomb', half=True, pad_left=-10, pad_right=-10, pad_bottom=-20) fig, ax = pitch.draw(figsize=(16, 9)) fm = FontManager() # a mplsoccer fontmanager with the default Robotto font # subset portugals shots for both data providers mask_portugal_sb = df_statsbomb.team_name == 'Portugal' mask_shot_sb = df_statsbomb.type_name == 'Shot' mask_portugal_wyscout = df_wyscout.team_name == 'Portugal' mask_shot_wyscout = df_wyscout.event_type == 'SHOT' df_wyscout_portugal = df_wyscout[mask_shot_wyscout & mask_portugal_wyscout].copy() df_statsbomb_portugal = df_statsbomb[mask_shot_sb & mask_portugal_sb].copy() # plotting the shots as a scatter plot with a legend
axis=1) mask_goal = df_pass.outcome_name == 'Goal' ############################################################################## # View the pass dataframe. df_pass ############################################################################## # Plotting # Setup the pitch pitch = VerticalPitch(pitch_type='statsbomb', pitch_color='#22312b', line_color='#c7d5cc', half=True, pad_top=2) fig, ax = pitch.draw(figsize=(16, 11), tight_layout=True) # Plot the completed passes pitch.lines(df_pass.x, df_pass.y, df_pass.end_x, df_pass.end_y, lw=10, transparent=True, comet=True, cmap='jet', label='pass leading to shot', ax=ax)
# 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 # calculate the horizontal space between axes (and figure sides) in percent of the figure width TOP_SPACE = (1 - (TOP_WIDTH * 3)) / 4 BOTTOM_SPACE = (1 - (BOTTOM_WIDTH * 3)) / 4 # calculate the height of the pitch axes in percent of the figure height
# strings for team names team1 = df_shot_event.team_name.iloc[0] team2 = list(set(df_event.team_name.unique()) - {team1})[0] # subset the team shooting, and the opposition (goalkeeper/ other) df_team1 = df_freeze_frame[df_freeze_frame.team_name == team1] df_team2_goal = df_freeze_frame[(df_freeze_frame.team_name == team2) & ( df_freeze_frame.player_position_name == 'Goalkeeper')] df_team2_other = df_freeze_frame[(df_freeze_frame.team_name == team2) & ( df_freeze_frame.player_position_name != 'Goalkeeper')] ############################################################################## # Plotting # Setup the pitch pitch = VerticalPitch(half=True, goal_type='box', pad_bottom=-20) # We will use mplsoccer's grid function to plot a pitch with a title axis. fig, axs = pitch.grid( figheight=8, endnote_height=0, # no endnote title_height=0.1, title_space=0.02, # Turn off the endnote/title axis. I usually do this after # I am happy with the chart layout and text placement axis=False, grid_height=0.83) # Plot the players sc1 = pitch.scatter(df_team1.x, df_team1.y,
# fontmanager for google font (robotto) robotto_regular = FontManager() path_eff = [ path_effects.Stroke(linewidth=3, foreground='black'), path_effects.Normal() ] ############################################################################## # Plot positional heatmap # ----------------------- # setup pitch pitch = VerticalPitch(pitch_type='statsbomb', line_zorder=2, pitch_color='#22312b', line_color='white') # draw fig, ax = pitch.draw(figsize=(4.125, 6)) bin_statistic = pitch.bin_statistic_positional(df.x, df.y, statistic='count', positional='full', normalize=True) pitch.heatmap_positional(bin_statistic, ax=ax, cmap='coolwarm', edgecolors='#22312b') pitch.scatter(df.x, df.y, c='white', s=2, ax=ax) labels = pitch.label_heatmap(bin_statistic, color='#f4edf0',
'tactics_lineup_df': False, 'warn': False } df = read_event(f'{EVENT_SLUG}/9860.json', **kwargs)['event'] # setup the mplsoccer StatsBomb Pitches # note not much padding around the pitch so the marginal axis are tight to the pitch # if you are using a different goal type you will need to increase the padding to see the goals pitch = Pitch(pad_top=0.05, pad_right=0.05, pad_bottom=0.05, pad_left=0.05, line_zorder=2) vertical_pitch = VerticalPitch(half=True, pad_top=0.05, pad_right=0.05, pad_bottom=0.05, pad_left=0.05, line_zorder=2) # setup a mplsoccer FontManager to download google fonts (Roboto-Regular / SigmarOne-Regular) fm = FontManager() fm_rubik = FontManager( ('https://github.com/google/fonts/blob/main/ofl/rubikmonoone/' 'RubikMonoOne-Regular.ttf?raw=true')) ############################################################################## # Subset the shots for each team and move Barcelona's shots to the other side of the pitch. # subset the shots df_shots = df[df.type_name == 'Shot'].copy()
} 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 = VerticalPitch(pitch_type='statsbomb', line_zorder=2, pitch_color='#22312b', line_color='white') # draw fig, ax = pitch.draw( figsize=(16, 9), ncols=3, nrows=1, ) # heatmap specified by (nx, ny) for horizontal pitch bins = [(6, 5), (1, 5), (6, 1)] for i, bin_dimension in enumerate(bins): bin_statistic = pitch.bin_statistic(df.x, df.y, statistic='count', bins=bin_dimension) # draw
# 16 by 9: three vertical pitches # ------------------------------- # Three vertical pitches fits nicely in the 16:9 aspect ratio. # Here we plot with a title and endnote axis too. FIGWIDTH = 16 FIGHEIGHT = 9 NROWS = 1 NCOLS = 3 SPACE = 0.09 MAX_GRID = 0.95 pitch = VerticalPitch(pad_top=1, pad_bottom=1, pad_left=1, pad_right=1, pitch_color='grass', stripe=True, line_color='white') GRID_WIDTH, GRID_HEIGHT = pitch.calculate_grid_dimensions(figwidth=FIGWIDTH, figheight=FIGHEIGHT, nrows=NROWS, ncols=NCOLS, max_grid=MAX_GRID, space=SPACE) TITLE_HEIGHT = 0.1 ENDNOTE_HEIGHT = MAX_GRID - (GRID_HEIGHT + TITLE_HEIGHT) fig, ax = pitch.grid(figheight=FIGHEIGHT,