Note
Go to the end to download the full example code.
Expected Threat (xT) for corners and free kicks
wa_setpieces.XTModel implements Karun Singh’s grid-based xT
method: fit a grid of zone values from data, then value any pass as
xT[end_zone] - xT[start_zone].
Important
The grid below is fit on a single match, purely to demonstrate the
mechanism – it is far too little data for a trustworthy grid. Fit on a
full season (concatenate many matches’ events) for real analysis, then
reuse it with to_csv() /
from_csv().
from pathlib import Path
from wa_setpieces import load_matches
from wa_setpieces.viz.plots import plot_xt_grid
from wa_setpieces.core.xt import XTModel, set_piece_delivery_xt, set_piece_xt_summary
try:
_here = Path(__file__).resolve().parent
except NameError:
_here = Path.cwd()
DATA = _here.parent / "tests" / "data" / "sample_match.json"
events = load_matches(DATA)
Fit the grid and look at it directly – value should climb steadily towards the opponent’s goal (x=100 in each event’s own attacking frame).
model = XTModel.fit(events, x_bins=8, y_bins=6)
fig, ax = plot_xt_grid(model)

xT added by every corner delivery (NaN where the corner didn’t find a teammate – there’s no reliable end location to value):
set_piece_delivery_xt(events, "corner", model)[
["eventId", "contestantId", "outcome", "xt_start", "xt_end", "xt_added"]
]
Rolled up per team, for both corners and free kicks:
set_piece_xt_summary(events, "corner", model)
set_piece_xt_summary(events, "free_kick", model)