📍 Recipe: Drawing a Pass Map#

This example demonstrates how to visualize a player’s passes using Flow to process StatsBomb data and mplsoccer to draw the pitch.

🧰 What You’ll Learn#

  • How to filter for completed passes

  • How to extract pass start and end locations

  • How to use .assign() to extract nested data as an alternative to using .select() + .rename()

  • How to use .split_array() to extract the location array into multiple fields

  • How to optimize the flow for better performance

  • How to plot them using the penaltyblog Pitch class

Imports#

[1]:
from IPython.display import HTML
from penaltyblog.matchflow import Flow, where_equals, get_field
from penaltyblog.viz import Pitch
import plotly.io as pio

Load the Data#

[2]:
# Load events for a StatsBomb match
match_id = 22912  # Champions League Final 2018/2019

flow = Flow.statsbomb.events(match_id)

Extract the Passes#

[3]:
passes = (
    flow.filter(where_equals("player.name", "Harry Kane"))
    .filter(where_equals("type.name", "Pass"))
    .filter(lambda r: get_field(r, "pass.outcome.name") is None)
    .split_array("location", ["start_x", "start_y"])
    .split_array("pass.end_location", ["end_x", "end_y"])
    .select("start_x", "start_y", "end_x", "end_y")
    .rename(start_x="x", start_y="y", end_x="x2", end_y="y2")
    .dropna()
)

for pass_ in passes.head(3):
    print(pass_)
/Users/martin/repos/penaltyblog/venv/lib/python3.13/site-packages/statsbombpy/api_client.py:21: NoAuthWarning: credentials were not supplied. open data access only
  warnings.warn(
{'x': 60.0, 'y': 40.0, 'x2': 43.4, 'y2': 40.3}
{'x': 39.6, 'y': 63.4, 'x2': 39.8, 'y2': 55.0}
{'x': 86.5, 'y': 47.8, 'x2': 75.6, 'y2': 55.6}

Plotting the Passes#

[4]:
pitch = Pitch(
    provider="statsbomb",
    orientation="horizontal",
    view="full",
    theme="night",
    show_axis=False,
    show_legend=False,
    width=400,
    height=400,
    title="Harry Kane – Completed Passes",
)

pitch.plot_arrows(passes)
# NOTE: normally we'd just call `pitch.show()` here, but since
# we're exporting to HTML docs, we need to use `HTML` to export
# the plot
# pitch.show()
HTML(pio.to_html(pitch.fig, include_plotlyjs="cdn"))
/Users/martin/repos/penaltyblog/venv/lib/python3.13/site-packages/statsbombpy/api_client.py:21: NoAuthWarning:

credentials were not supplied. open data access only

[4]: