.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "gallery/pitch_plots/plot_scatter.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_gallery_pitch_plots_plot_scatter.py: ======= Scatter ======= This example shows how to plot a scatter chart. .. GENERATED FROM PYTHON SOURCE LINES 8-32 .. code-block:: Python import numpy as np from matplotlib import colormaps import matplotlib.pyplot as plt from matplotlib.colors import ListedColormap from mplsoccer import (VerticalPitch, Pitch, create_transparent_cmap, FontManager, arrowhead_marker, Sbopen) # get data for a Sevilla versus Barcelona match with a high amount of shots parser = Sbopen() df, related, freeze, tactics = parser.event(9860) # subset the barcelona shots df_shots_barca = df[(df.type_name == 'Shot') & (df.team_name == 'Barcelona')].copy() # subset the barca open play passes df_pass_barca = df[(df.type_name == 'Pass') & (df.team_name == 'Barcelona') & (~df.sub_type_name.isin(['Throw-in', 'Corner', 'Free Kick', 'Kick Off']))].copy() # setup a mplsoccer FontManager to download google fonts (SigmarOne-Regular) fm_rubik = FontManager('https://raw.githubusercontent.com/google/fonts/main/ofl/' 'rubikmonoone/RubikMonoOne-Regular.ttf') .. GENERATED FROM PYTHON SOURCE LINES 33-42 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 `_. .. GENERATED FROM PYTHON SOURCE LINES 42-62 .. code-block:: Python 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, y=80, s='Barcelona shots\nversus Sevilla', size=30, # here i am using a downloaded font from google fonts instead of passing a fontdict fontproperties=fm_rubik.prop, color=pitch.line_color, va='center', ha='center') .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_001.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 63-68 Shot map Barcelona using cmap ----------------------------- An alternative is to use colors to represent the quality of shots. In this example, we will also pass the expected goals to the c argument and use a matplotlib colormap to map the expected goals to colors .. GENERATED FROM PYTHON SOURCE LINES 68-87 .. code-block:: Python fig, ax = pitch.draw(figsize=(12, 10)) sc = pitch.scatter(df_shots_barca.x, df_shots_barca.y, # size varies between 100 and 1900 (points squared) s=(df_shots_barca.shot_statsbomb_xg * 1900) + 100, cmap='plasma_r', # reverse magma colormap so darker = higher expected goals edgecolors='#383838', # give the markers a charcoal border c=df_shots_barca.shot_statsbomb_xg, # color for scatter in hex format # for other markers types see: https://matplotlib.org/api/markers_api.html marker='*', ax=ax) txt = ax.text(x=40, y=80, s='Barcelona shots\nversus Sevilla', size=30, # here i am using a downloaded font from google fonts instead of passing a fontdict fontproperties=fm_rubik.prop, color='#b94b75', va='center', ha='center') # comment below sets this as the thumbnail in the docs # sphinx_gallery_thumbnail_path = 'gallery/pitch_plots/images/sphx_glr_plot_scatter_002' .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_002.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 88-93 Shot map Barcelona using cmap for edges --------------------------------------- It's possible to use cmaps for the edgecolors for emphasis by mapping the expected goals values to colors and using these as edgecolors. You could use the same technique to assign fewer colors to the scatter. .. GENERATED FROM PYTHON SOURCE LINES 93-117 .. code-block:: Python # get the cmap as 10 colors (n_colors can be anything) cmap = colormaps.get_cmap('Greys') # reversed plasma N_COLORS = 10 cmap = cmap(np.linspace(0.5, 1, N_COLORS)) # from half-way (0.5) to end (1) of grey colormap cmap = ListedColormap(cmap, name='Greys') # convert the statsbomb xg to colors edgecolors = cmap(df_shots_barca.shot_statsbomb_xg) fig, ax = pitch.draw(figsize=(12, 10)) sc = pitch.scatter(df_shots_barca.x, df_shots_barca.y, s=1000, edgecolors=edgecolors, # give the markers a charcoal border linewidths=1.2, # for fun making the edges slightly thicker c='#b94b75', # color for scatter in hex format # for other markers types see: https://matplotlib.org/api/markers_api.html marker='*', ax=ax) txt = ax.text(x=40, y=80, s='Barcelona shots\nversus Sevilla', size=30, # here i am using a downloaded font from google fonts instead of passing a fontdict fontproperties=fm_rubik.prop, color=pitch.line_color, va='center', ha='center') .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_003.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_003.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 118-124 Shot map Barcelona using transparency cmap ------------------------------------------ I included a helper function in mplsoccer to create a transparent cmap from either a color or a cmap. Here we create a cmap from a color (can use cmap='viridis' for example instead) and vary the transparency from 0.5 to 1 as the expected goals increase .. GENERATED FROM PYTHON SOURCE LINES 124-143 .. code-block:: Python cmap = create_transparent_cmap(color='#b94b75', n_segments=100, alpha_start=0.5, alpha_end=1) fig, ax = pitch.draw(figsize=(12, 10)) sc = pitch.scatter(df_shots_barca.x, df_shots_barca.y, # size varies between 100 and 1900 (points squared) s=(df_shots_barca.shot_statsbomb_xg * 1900) + 100, cmap=cmap, # reverse magma colormap so darker = higher expected goals edgecolors='#383838', # give the markers a charcoal border c=df_shots_barca.shot_statsbomb_xg, # color for scatter in hex format # for other markers types see: https://matplotlib.org/api/markers_api.html marker='*', ax=ax) txt = ax.text(x=40, y=80, s='Barcelona shots\nversus Sevilla', size=30, # here i am using a downloaded font from google fonts instead of passing a fontdict fontproperties=fm_rubik.prop, color=pitch.line_color, va='center', ha='center') .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_004.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_004.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 144-152 Shot map Barcelona using hatch ------------------------------ Another method popularized by `@petermckeever `_. is to use hatch patterns to show where something was not-successful versus successful. There are lots of different hatch patterns. See set_hatch: https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.Patch.html This is typically combined with the highlight-text package by `@danzn1 `_. .. GENERATED FROM PYTHON SOURCE LINES 152-186 .. code-block:: Python # filter goals / non-shot goals df_goals_barca = df_shots_barca[df_shots_barca.outcome_name == 'Goal'].copy() df_non_goal_shots_barca = df_shots_barca[df_shots_barca.outcome_name != 'Goal'].copy() fig, ax = pitch.draw(figsize=(12, 10)) # plot non-goal shots with hatch sc1 = pitch.scatter(df_non_goal_shots_barca.x, df_non_goal_shots_barca.y, # size varies between 100 and 1900 (points squared) s=(df_non_goal_shots_barca.shot_statsbomb_xg * 1900) + 100, edgecolors='#606060', # give the markers a charcoal border c='None', # no facecolor for the markers hatch='///', # the all important hatch (triple diagonal lines) # for other markers types see: https://matplotlib.org/api/markers_api.html marker='o', ax=ax) # plot goal shots with a color sc2 = pitch.scatter(df_goals_barca.x, df_goals_barca.y, # size varies between 100 and 1900 (points squared) s=(df_goals_barca.shot_statsbomb_xg * 1900) + 100, edgecolors='#606060', # give the markers a charcoal border c='#b94b75', # color for scatter in hex format # for other markers types see: https://matplotlib.org/api/markers_api.html marker='o', ax=ax) txt = ax.text(x=40, y=80, s='Barcelona shots\nversus Sevilla', size=30, # here i am using a downloaded font from google fonts instead of passing a fontdict fontproperties=fm_rubik.prop, color=pitch.line_color, va='center', ha='center') .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_005.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_005.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 187-191 Shot map Barcelona using footballs ---------------------------------- I also included a football marker in mplsoccer, which in this context could also be used to show goals/ non-goals .. GENERATED FROM PYTHON SOURCE LINES 191-226 .. code-block:: Python # filter goals / non-shot goals df_goals_barca = df_shots_barca[df_shots_barca.outcome_name == 'Goal'].copy() df_non_goal_shots_barca = df_shots_barca[df_shots_barca.outcome_name != 'Goal'].copy() fig, ax = pitch.draw(figsize=(12, 10)) # plot non-goal shots with hatch sc1 = pitch.scatter(df_non_goal_shots_barca.x, df_non_goal_shots_barca.y, # size varies between 100 and 1900 (points squared) s=(df_non_goal_shots_barca.shot_statsbomb_xg * 1900) + 100, edgecolors='#b94b75', # give the markers a charcoal border c='None', # no facecolor for the markers hatch='///', # the all important hatch (triple diagonal lines) # for other markers types see: https://matplotlib.org/api/markers_api.html marker='o', ax=ax) # plot goal shots with a football marker # 'edgecolors' sets the color of the pentagons and edges, 'c' sets the color of the hexagons sc2 = pitch.scatter(df_goals_barca.x, df_goals_barca.y, # size varies between 100 and 1900 (points squared) s=(df_goals_barca.shot_statsbomb_xg * 1900) + 100, edgecolors='#b94b75', linewidths=0.6, c='white', marker='football', ax=ax) txt = ax.text(x=40, y=80, s='Barcelona shots\nversus Sevilla', size=30, # here i am using a downloaded font from google fonts instead of passing a fontdict fontproperties=fm_rubik.prop, color=pitch.line_color, va='center', ha='center') .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_006.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_006.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 227-237 Cropping to important areas --------------------------- One under-used technique is to crop the pitch edges where there is likely to be fewer shots. If you look at the StatsBomb shot maps this is a subtle technique they use. It means you reduce the amount of white space and you zoom into the areas where more shots are taken. The disadvantage of this approach is that sometimes people misinterpret the pitch and think the areas towards the edges are the edges of the pitch. You might also miss some shots near the half-way line. .. GENERATED FROM PYTHON SOURCE LINES 237-277 .. code-block:: Python pitch = VerticalPitch(pad_top=0.5, # only a small amount of space at the top of the pitch pad_bottom=-20, # reduce the area displayed at the bottom of the pitch pad_left=-15, # reduce the area displayed on the left of the pitch pad_right=-15, # reduce the area displayed on the right of the pitch half=True, # half of a pitch goal_type='line') # filter goals / non-shot goals df_goals_barca = df_shots_barca[df_shots_barca.outcome_name == 'Goal'].copy() df_non_goal_shots_barca = df_shots_barca[df_shots_barca.outcome_name != 'Goal'].copy() fig, ax = pitch.draw(figsize=(12, 10)) # plot non-goal shots with hatch sc1 = pitch.scatter(df_non_goal_shots_barca.x, df_non_goal_shots_barca.y, # size varies between 100 and 1900 (points squared) s=(df_non_goal_shots_barca.shot_statsbomb_xg * 1900) + 100, edgecolors='#606060', # give the markers a charcoal border c='None', # no facecolor for the markers hatch='///', # the all important hatch (triple diagonal lines) # for other markers types see: https://matplotlib.org/api/markers_api.html marker='o', ax=ax) # plot goal shots with a color sc2 = pitch.scatter(df_goals_barca.x, df_goals_barca.y, # size varies between 100 and 1900 (points squared) s=(df_goals_barca.shot_statsbomb_xg * 1900) + 100, edgecolors='#606060', # give the markers a charcoal border c='#b94b75', # color for scatter in hex format # for other markers types see: https://matplotlib.org/api/markers_api.html marker='o', ax=ax) txt = ax.text(x=40, y=85, s='Barcelona shots\nversus Sevilla', size=30, # here i am using a downloaded font from google fonts instead of passing a fontdict fontproperties=fm_rubik.prop, color=pitch.line_color, va='center', ha='center') .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_007.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_007.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 278-281 Plot the chart again with a title --------------------------------- We will use mplsoccer's grid function to plot a pitch with a title and endnote axes. .. GENERATED FROM PYTHON SOURCE LINES 281-320 .. code-block:: Python fig, axs = pitch.grid(figheight=10, title_height=0.08, endnote_space=0, # Turn off the endnote/title axis. I usually do this after # I am happy with the chart layout and text placement axis=False, title_space=0, grid_height=0.82, endnote_height=0.05) # plot non-goal shots with hatch sc1 = pitch.scatter(df_non_goal_shots_barca.x, df_non_goal_shots_barca.y, # size varies between 100 and 1900 (points squared) s=(df_non_goal_shots_barca.shot_statsbomb_xg * 1900) + 100, edgecolors='#606060', # give the markers a charcoal border c='None', # no facecolor for the markers hatch='///', # the all important hatch (triple diagonal lines) # for other markers types see: https://matplotlib.org/api/markers_api.html marker='o', ax=axs['pitch']) # plot goal shots with a color sc2 = pitch.scatter(df_goals_barca.x, df_goals_barca.y, # size varies between 100 and 1900 (points squared) s=(df_goals_barca.shot_statsbomb_xg * 1900) + 100, edgecolors='#606060', # give the markers a charcoal border c='#b94b75', # color for scatter in hex format # for other markers types see: https://matplotlib.org/api/markers_api.html marker='o', ax=axs['pitch']) # endnote text axs['endnote'].text(1, 0.5, '@your_twitter_handle', color=pitch.line_color, va='center', ha='right', fontsize=15, fontproperties=fm_rubik.prop) # title text title1 = axs['title'].text(0.5, 0.7, "Barcelona shots", color=pitch.line_color, va='center', ha='center', fontproperties=fm_rubik.prop, fontsize=30) title2 = axs['title'].text(0.5, 0.25, "versus Sevilla", color=pitch.line_color, va='center', ha='center', fontproperties=fm_rubik.prop, fontsize=20) .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_008.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_008.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 321-335 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. .. GENERATED FROM PYTHON SOURCE LINES 335-351 .. code-block:: Python 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, 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, ax=ax, marker=arrowhead_marker) title1 = fig.text(x=0.5, y=0.94, s='Barcelona Open play passes', va='center', ha='center', size=30, color=pitch.line_color, fontproperties=fm_rubik.prop, ) title2 = fig.text(x=0.5, y=0.9, s='Larger marker = greater pass distance', va='center', ha='center', size=15, color=pitch.line_color, fontproperties=fm_rubik.prop) plt.show() # If you are using a Jupyter notebook you do not need this line .. image-sg:: /gallery/pitch_plots/images/sphx_glr_plot_scatter_009.png :alt: plot scatter :srcset: /gallery/pitch_plots/images/sphx_glr_plot_scatter_009.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 1.035 seconds) .. _sphx_glr_download_gallery_pitch_plots_plot_scatter.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_scatter.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_scatter.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_scatter.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_