Chapter 8: Key Takeaways
Quick Reference Card
What is xA?
Expected Assists (xA) measures the quality of chances a player creates for teammates by summing the xG values of shots resulting from their passes.
- Value range: Typically 0-0.4 per key pass
- Interpretation: Higher xA = better quality chances created
- Relationship: xA of a pass = xG of the resulting shot
Core Formulas
Basic xA Calculation
xA_player = Σ(xG of shots from player's key passes)
xA per 90 Minutes
xA_90 = xA_total / (minutes / 90)
Key Pass Rate
KP_rate = key_passes / total_passes × 100
Assist vs xA Difference
Δ_assist = actual_assists - xA
Positive = overperformance; Negative = underperformance
Typical Values
| Metric | Average | Good | Elite |
|---|---|---|---|
| xA per 90 (AM) | 0.12 | 0.20 | 0.30+ |
| xA per 90 (Winger) | 0.15 | 0.22 | 0.35+ |
| Key passes per 90 | 1.5 | 2.5 | 3.5+ |
| xA per key pass | 0.08 | 0.12 | 0.16+ |
Pass Type xG Values
| Pass Type | Typical xG | Notes |
|---|---|---|
| Through ball | 0.15-0.25 | Highest quality, lowest volume |
| Cutback | 0.12-0.20 | High quality from byline |
| Regular penalty area pass | 0.08-0.12 | Standard chance |
| Cross | 0.04-0.08 | Lower quality but high volume |
| Set piece delivery | 0.05-0.10 | Variable by situation |
Essential Python Code
Calculate xA from Events
def calculate_xa(shots_df, passes_df):
"""Calculate xA for all players."""
xa_data = []
for _, shot in shots_df.iterrows():
kp_id = shot.get('shot_key_pass_id')
if pd.notna(kp_id):
kp = passes_df[passes_df['id'] == kp_id]
if len(kp) > 0:
xa_data.append({
'player': kp.iloc[0]['player'],
'xA': shot['shot_statsbomb_xg'],
'assist': shot['shot_outcome'] == 'Goal'
})
xa_df = pd.DataFrame(xa_data)
return xa_df.groupby('player').agg({
'xA': 'sum',
'assist': 'sum'
}).rename(columns={'assist': 'assists'})
Key Pass Identification
# Using StatsBomb data
key_passes = passes_df[passes_df['pass_shot_assist'] == True]
# Manual linkage (if needed)
def is_key_pass(pass_row, shots_df, time_threshold=10):
"""Check if pass led to a shot within time threshold."""
same_team_shots = shots_df[
(shots_df['team'] == pass_row['team']) &
(shots_df['minute'] >= pass_row['minute']) &
(shots_df['minute'] <= pass_row['minute'] + time_threshold/60)
]
return len(same_team_shots) > 0
Pass Type Classification
def classify_key_pass(pass_row):
"""Classify pass type for analysis."""
if pass_row.get('pass_through_ball'):
return 'through_ball'
elif pass_row.get('pass_cross'):
return 'cross'
elif pass_row.get('pass_cut_back'):
return 'cutback'
else:
return 'regular'
Shot-Creating Actions
def calculate_sca(events_df, n_preceding=2):
"""Calculate Shot-Creating Actions."""
shots = events_df[events_df['type'] == 'Shot']
sca_counts = {}
for shot_idx in shots.index:
shot = events_df.loc[shot_idx]
preceding = events_df[
(events_df.index < shot_idx) &
(events_df['team'] == shot['team'])
].tail(n_preceding)
for _, action in preceding.iterrows():
player = action['player']
if player:
sca_counts[player] = sca_counts.get(player, 0) + 1
return sca_counts
Key Concepts
Shot-Creating Actions (SCA) vs xA
| Metric | What It Counts | Best For |
|---|---|---|
| xA | Only the final pass (key pass) | Direct creativity |
| SCA | Last 2 actions before shot | Broader involvement |
| GCA | Last 2 actions before goal | Goal contribution |
xA Interpretation Guidelines
Individual Shot Level: - xA = xG of shot created - Single passes range from 0.02 to 0.50+ xA
Match Level: - Sum all key passes by player - Top creators: 0.3-0.6 xA per match
Season Level: - Total xA correlates with actual assists (r ≈ 0.7) - Over/underperformance regresses to mean
Common Pitfalls
1. Ignoring Sample Size
- Problem: Judging creativity from 5 key passes
- Solution: Require 20+ key passes for reliable conclusions
2. Not Accounting for Position
- Problem: Comparing CAM xA to CB xA
- Solution: Use positional percentiles
3. Set Piece Inflation
- Problem: Corner/FK takers have inflated xA
- Solution: Separate open play vs. set piece xA
4. Ignoring Teammate Quality
- Problem: Playing with clinical finishers boosts assist totals
- Solution: Focus on xA, which removes conversion dependency
5. Confusing xA with Passing Quality
- Problem: Thinking xA measures pass difficulty
- Solution: Remember xA = shot quality, not pass quality
Applications Summary
| Application | Key Metric | What It Shows |
|---|---|---|
| Player scouting | xA per 90 | Creativity rate |
| Style analysis | Pass type breakdown | How they create |
| Team tactics | Team xA distribution | Creation patterns |
| Partnership | Passer-shooter xA | Combination effectiveness |
| Luck/skill | Assists vs xA | Teammate conversion |
Pass Types and Values
Through Balls
- Definition: Pass into space behind defense
- Characteristics: High risk, high reward
- Typical success rate: 30-40%
- When successful: Generates 0.15-0.25 xG
Crosses
- Definition: Pass from wide to central penalty area
- Characteristics: High volume, lower per-pass value
- Typical success rate: 20-30%
- When shot results: Generates 0.04-0.08 xG
Cutbacks
- Definition: Pass from byline backward into box
- Characteristics: High quality when executed
- Typical success rate: 50-60%
- When shot results: Generates 0.12-0.20 xG
Quick Reference: Evaluation Steps
For Individual Players:
- Calculate total xA and xA per 90
- Compare to positional benchmarks
- Analyze pass type breakdown
- Check assists vs. xA (luck factor)
- Consider team/tactical context
For Team Analysis:
- Sum team xA per match
- Compare creation methods (cross vs. through ball)
- Analyze set piece vs. open play split
- Identify key creative players
- Assess tactical style signature
One-Page Summary
xA = Sum of xG from shots you create
- xA measures creativity independent of teammate finishing
- Different pass types have different typical xG values
- Through balls > Cutbacks > Regular > Crosses for xA per pass
- SCA broadens the view to all shot-contributing actions
- Context matters: position, team style, set pieces
- Sample size: need 20+ key passes for stability
- Assists will regress toward xA over time
- Compare to positional benchmarks not raw totals