Expected Assisted Goals (xAG) 101
What xAG measures (and what it doesn’t), the key blindspots to watch out for, how it's different from expected assists (xA), and how to pull tons of xAG data with Python.
Hi friend,
Welcome to The Python Football Review #003!
We’ve already dissected xG (chance creation) and xGOT (shot execution).
Today we slide one pass earlier in the move: Expected Assisted Goals (xAG).
By the end of this issue, you’ll know:
what xAG measures
the biggest blindspots to watch for
how it differs from expected assists (yes they are different)
how professionals uncover hidden creative talent using it
how to pull 8 seasons of xAG data with a few lines of Python code (templates included)
Enjoy!
First things first… “Aren’t assists good enough?”
Raw assists matter, but they’re messy:
Finisher‑dependent: A defence‑splitting through‑ball earns zero credit if the striker skies it.
Team‑dependent: Creators at goal‑rich clubs stack assists; equally gifted players on low‑scoring sides look anonymous.
Analysts needed a way to rate the quality of the pass itself, independent of who finishes—or whether anyone finishes at all.
Enter Expected Assisted Goals (xAG).
1 — What is xAG?
Think of xAG as xG but for the passer.
xAG equals the xG of the shot that immediately follows a completed pass.
So it’s a probability between 0 and 1 that the shot resulting from the pass would transform into a goal.
0.00 → a pass that virtually never leads to a goal
1.00 → a pass that would become an assist for a goal every single time
Add up every pass’s xAG and you get how many assists a player should have given the quality of chances he supplies—regardless of finishing luck.
In short, xAG adds quality to the raw quantity of passes leading to shots.
It removes finishing noise and levels the field across teams, turning raw pass counts into a continuous, chance‑quality metric.
2 —xAG blind spots
No stat is perfect—xAG included. Two common traps you should be aware of:
The Messi Dribble Tax
A five‑yard square ball to Messi in the middle of the pitch that he turns into a wonder‑goal by dancing his way through 5 defenders still hands the passer the full xAG. Great for Busquets’ résumé; not great for isolating creative genius.
The Pre‑assist Disappearing Act
A deep-lying Pirlo laser that breaks the press, followed by one extra square pass of the receiver, earns 0 xAG for Pirlo. The metric only credits the final ball before the shot.
Now there are other metrics such as xA (expected assists) and xT (expected threat) that can plug those gaps—but that’s a story for another issue.
Bottom line: use xAG, but never in isolation. It captures far more nuance than raw assists, yet still needs context from video, xA, or xT to tell the full creative story.
“But Martin, isn’t xAG just expected assists—xA?”
If only it were that simple, my friend. The two are related but not the same:
xAG gives the passer the xG of the very next shot. No shot, no xAG.
xA values every completed pass, shot or no shot, so long as it historically leads to goals often enough.
xA fixes the “no‑shot, no credit” problem—but in doing so it introduces its own quirks (a story for another newsletter).
For now, just remember: xAG and xA measure the same creative impulse from two different angles. Don’t mix them up.
All right—so how do the pros actually use xAG?
3 — How professionals use xAG
Just as xG separates luck from finishing skill, xAG tells a richer story than raw assists ever can.
By rewarding pass quality, it has become the go‑to metric for judging creative players—especially midfielders whose reputations hinge on the final ball.
Rating creators over time
Over large samples xAG drifts toward a player’s assist total, but the gap between the two is where the insight hides:
A simple roll‑up therefore is Assists − xAG.
Negative across a season? Bad luck or blunt finishing.
Positive? Visionary passer or ruthless finishers.
Let’s do a mini case study and uncover Europe’s top under‑credited architects.
Who’s been most let down by team‑mates since 2017‑18?
Here are the top five “should‑have” creators as of 2 May 2025 (you will get to replicate this analysis with our templates below):
Serge Gnabry’s Bayern teammates hardly lack fire‑power, yet he “deserved” roughly 12 more assists (-11.9 xGA) on top of his 33 from 191 games. Further down we find creators at less glamorous clubs—Lorenzo Pellegrini (Roma) with -11.8 xGA, Junya Ito (Reims) with -11.4 xGA, Jesus Navas (Sevilla) with -11.2 xGA—whose teammates squandered double‑digit chances.
Sort by Assists-xGA per 90 minutes and hidden gems pop even faster (if you can define a player playing for Europe’s top 5 leagues as “hidden”).
Remi Oudin (Lecce) −0.17 Assists–minus-xAG per 90 minutes ≈ 7 “missing” assists per season (that’s 0.17x38)
Lucas Pérez (Deportivo La Coruna) ≈ 6 “missing” assists
Angelo Fulgini (Lens) ≈ 6 “missing” assists
Of course, those players hardly unknown—each plays in a top league—but the exercise shows how quickly a global database can surface creators whose numbers lag behind their service.
And that’s exactly what recruitment departments do. Filter your database for high xAG/90, low Assists/90. Swap their wasteful strikers for clinical finishers and the numbers explode.
Hot‑streak beneficiaries
Now for the over‑performers—usually stars on elite, clinical sides.
Top of the pile is Thomas Müller: Bayern’s finishers converted 25.8 more goals than his xAG implied. He’s followed by a surprise name, Paul Pogba (+16.4), then Jadon Sancho in his Dortmund days (+14.9), Florian Wirtz (+13.7) and Mohamed Salah (+13.3).
So to recap, xAG lets clubs spot two very different stories:
Underrated creators—high xAG, few assists—who might explode with better finishers.
Hot‑streak beneficiaries—assists far above xAG—whose numbers could cool when the shooting luck fades (or if paired with less skillful finishers).
And finally, here’s how to get 8 seasons of data with a few lines of Python code.
4 — Getting xAG data in Python
If you’ve been reading The Python Football Review for a while, you know the drill: Opta’s advanced data—shared free on FBref—is the easiest starting point.
Rather than scraping it by hand, we’ll lean on Pieter Robberechts’ (@p_robberechts) community wrapper soccerdata
and wrangle the results with polars
.
Before we begin, here’s a download link to the code you are about to read.
To start coding, just go to Google Colab (zero setup—just your Gmail) and open a new notebook. You can either import the code (that you just downloaded), or type it yourself by following the instructions you are about to read.
So the first thing you’ll need to do is to install soccerdata
, then import it alongside polars
(for data wrangling).
Next, define your study window with sd.FBref
. For this demo we’ll pull the last eight seasons across Europe’s top‑five leagues.
Next we call read_player_season_stats(stat_type="standard")
The raw DataFrame arrives with 33 columns; we keep only the essentials—position, matches, minutes, assists, and xAG—rename them for sanity, and convert to Polars.
Et voilà—we now have our clean analysis-ready data.
We continue the analysis by grouping by league / team / player, aggregating totals, and creating our key metrics:
Assists − xAG
Assists − xAG per 90 minutes
Sort ascending Assists − xAG to spot under‑credited creators.
Add a minimum‑xAG filter (e.g., ≥ 5) to avoid tiny‑sample noise and sort ascending Assists − xAG per 90 minutes to spot under‑credited creators.
Sort descending Assists − xAG to see who’s thriving on clinical team‑mates.
Within minutes you’re staring at clean tables showing who deserved more assists and who benefited from red‑hot finishers.
Boom—that’s xAG 101.
If you found this issue useful, please spread the word! You now know more about xAG than most football fans: what it measures, how professionals use it, why it isn’t the same as xA, and—crucially—how to pull eight seasons of data with a handful of Python lines.
I’m still experimenting with the format of the newsletter, so your feedback is super welcome—would you prefer shorter content, longer deep dives, more Python, or more football concepts? Or does this format hit the mark?
My aim is to build a truly practical newsletter together with you.
Until next week,
Martin
The Python Football Review
Really enjoyed reading this. I've often been confused about the difference between XAG and XA. Thanks for explaining it. And for the code too. I enjoy the deep dives.
Hey! I saw your post pop up on my homepage and wanted to show some support. If you get a chance, I’d really appreciate a little love on my latest newsletter too always happy to boost each other!