Esempio n. 1
0
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,
Esempio n. 2
0
                              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)
Esempio n. 3
0
#
# 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]}'
Esempio n. 4
0
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)
Esempio n. 5
0
##############################################################################
# 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.
Esempio n. 6
0
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()
Esempio n. 7
0
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,
Esempio n. 8
0
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)
Esempio n. 9
0
     '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,
Esempio n. 10
0
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)
Esempio n. 11
0
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
Esempio n. 12
0
                                                  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
Esempio n. 14
0
# 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,
Esempio n. 15
0
# 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',
Esempio n. 16
0
    '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()
Esempio n. 17
0
}
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
Esempio n. 18
0
# 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,