Pressing Intensity
Pressing Intensity is a metric that quantifies the defensive pressure applied to ball carriers in soccer. This tutorial explains how to compute and visualize pressing intensity using the unravelsports package.
The Pressing Intensity metric measures:
Spatial pressure: How closely defenders surround the ball carrier
Velocity pressure: How fast defenders are moving toward the ball carrier
Coverage: How well defenders cover potential passing lanes
For the mathematical formulation and validation, see: Bekkers (2024): Pressing Intensity: An Intuitive Measure for Pressing in Soccer
Interactive Notebook
A comprehensive Jupyter notebook demonstrates the full workflow, including video generation:
-
Loading tracking data
Computing pressing intensity for match segments
Creating MP4 visualizations with matplotlib and mplsoccer
Example: 1. FC Köln vs. FC Bayern München (May 27th 2023)
Basic Usage
Step 1: Load Tracking Data
First, load your soccer tracking data:
from kloppy import sportec
from unravel.soccer import KloppyPolarsDataset
# Load tracking data
kloppy_dataset = sportec.load_open_tracking_data(
only_alive=True
)
# Convert to Polars format
polars_dataset = KloppyPolarsDataset(
kloppy_dataset=kloppy_dataset
)
Step 2: Initialize Model
Create a PressingIntensity model instance:
from unravel.soccer import PressingIntensity
model = PressingIntensity(dataset=polars_dataset)
Step 3: Compute Pressing Intensity
Compute pressing intensity for a specific time window:
import polars as pl
model.fit(
start_time=pl.duration(minutes=1, seconds=53),
end_time=pl.duration(minutes=2, seconds=32),
period_id=1,
method="teams",
ball_method="max",
orient="home_away",
speed_threshold=2.0,
)
# Access results
print(model.output)
Parameters Explained
Time Window
start_time: Start of the analysis window (Polars duration)end_time: End of the analysis window (Polars duration)period_id: Which period to analyze (1 for first half, 2 for second half)
import polars as pl
# First minute of the match
start_time = pl.duration(minutes=0, seconds=0)
end_time = pl.duration(minutes=1, seconds=0)
# Specific moment (e.g., 15:30 - 16:00)
start_time = pl.duration(minutes=15, seconds=30)
end_time = pl.duration(minutes=16, seconds=0)
# Or analyze entire period
start_time = None # Start from beginning
end_time = None # Until end of period
Method
Controls the matrix structure:
method="teams": Creates 11×11 matrix (ball-owning team × non-owning team)method="full": Creates 22×22 matrix (all players × all players)
Ball Method
How to handle the ball in the pressing intensity matrix:
ball_method="max": Merge ball with ball carrier using max(ball_tti, carrier_tti) - keeps 11×11 matrixball_method="include": Add ball as separate node (creates 11×12 or 22×23 matrix)ball_method="exclude": Ignore ball entirely
Orientation
Matrix orientation perspective:
orient="ball_owning": Rows = ball-owning team, Cols = non-owning teamorient="pressing": Rows = non-owning team, Cols = ball-owning team (transpose)orient="home_away": Rows = home team, Cols = away teamorient="away_home": Rows = away team, Cols = home team
Speed Threshold
Minimum speed (m/s) for a player to be considered actively pressing:
speed_threshold=2.0: Players moving faster than 2 m/s (filters out passive coverage)speed_threshold=None: All players included regardless of speed (default)
Output Format
The model stores results in model.output, a Polars DataFrame with one row per frame containing:
frame_id,period_id,timestamp: Frame identifierstime_to_intercept: List[List[float]] - TTI matrix (rows × columns)probability_to_intercept: List[List[float]] - PTI matrix (rows × columns)columns: List[str] - Object IDs for column playersrows: List[str] - Object IDs for row players
model.fit(
start_time=pl.duration(minutes=0),
end_time=pl.duration(minutes=1),
period_id=1,
method="teams"
)
# Access output DataFrame
print(model.output)
# Extract matrices for a specific frame
frame_data = model.output.filter(pl.col("frame_id") == 1000)
tti_matrix = np.array(frame_data["time_to_intercept"][0])
pti_matrix = np.array(frame_data["probability_to_intercept"][0])
Visualization
For complete visualization examples with heatmaps and video generation, see the Pressing Intensity Jupyter Notebook.
The notebook demonstrates:
Creating animated heatmaps of pressing intensity matrices
Overlaying pressing intensity on pitch visualizations
Generating MP4 videos with matplotlib and mplsoccer