By phase and outcome
The same pick-one-see-what-it-does reference By set piece gives set pieces, for the three questions the raw qualifiers can’t answer directly: did the danger continue, did the team keep the ball, and what exactly happened. All three are derived heuristics, not fields the provider gives you – see each entry’s caveats.
Second phases
- What it detects
Whether a corner/free-kick delivery was cleared immediately, produced a shot straight off the ball, or stayed alive (a knockdown, blocked clearance, loose ball) long enough for a second-phase shot.
- Requirements
cornerorfree_kickonly – inferred from event sequencing (time gaps, who touches the ball, clearance distance), so there’s no equivalent for pass-based types without a meaningful “phase” concept.
Compute it
from wa_setpieces import second_phases, second_phase_summary
second_phases(events, "corner") # one row per corner, classified
second_phase_summary(events, "corner") # per-team roll-up
# thresholds are tunable:
from wa_setpieces.core.phases import second_phases as _second_phases
_second_phases(events, "corner", clear_safe_x=40, max_gap_seconds=6, max_total_seconds=15)
- Where it’s used
set_piece_added_value()(By value model) andplot_second_phase()in the Gallery.
Retention
- What it detects
Whether the team that took the set piece still had the ball some seconds later (default 8s) – a broader question than the raw pass
outcomeflag, which only checks whether the very first pass found a teammate.- Requirements
Any type except
penalty(a penalty is a single shot, not a restart with a meaningful “possession after” question).
Compute it
from wa_setpieces import retention_detail, retention_rate
retention_detail(events, "corner") # per-delivery: outcome vs. retained
retention_rate(events, "throw_in") # per-team retention rate
retention_rate(events, "corner", window_seconds=5)
- Where it’s used
set_piece_report’sretention_ratecolumn, whichteam_rating()(By value model) uses for the four types with no xT path.
Outcomes
- What it detects
One discrete
delivery_outcomeper corner/free-kick delivery –short_corner,direct_shot,second_phase_shot,aerial_duel,cleared,first_touch_won,first_touch_lostorno_action– checked in that priority order, for a shot-map-style scatter.- Requirements
cornerorfree_kickonly – builds on second-phase detection above.
Compute it
from wa_setpieces import delivery_outcomes, outcome_summary
delivery_outcomes(events, "corner") # one row per delivery
outcome_summary(events, "corner") # per-team counts of each category
Note
Don’t confuse delivery_outcome (this classified result) with the
raw pass/shot outcome flag By metric’s team/player counts
use – both were named category/outcome_category in earlier
versions and were unified precisely because the overlap was
confusing.
- Where it’s used
plot_set_piece_outcomes()(a shot-map scatter, colored by category) andplot_outcome_flow()(the same categories as a Sankey-style funnel down to goal/no goal), both in the Gallery; the aerial-duel entry below.
Aerial duels
- What it detects
When a delivery’s outcome is
"aerial_duel", who actually won the header – from the raw Opta Aerial event’s ownoutcomeflag (Opta logs one Aerial event per team involved).- Requirements
cornerorfree_kickonly, same as the outcome classification it builds on.
Compute it
from wa_setpieces import aerial_duel_summary
team_summary, player_summary = aerial_duel_summary(events, "corner")
# team_summary: duels_involved, duels_won, win_rate, per team
# player_summary: duels_won per player with at least one *identified* win
# (a loss identifies the winning team but not the winning player --
# only the loser's own event is what's being read)
- Where it’s used
plot_aerial_duel_win_rate()in the Gallery.