TalentOrigin

Which countries produce more elite soccer players than their population and economy would predict, and what actually drives the gap.

About

Why I built this

I have played soccer for 14 years. Every World Cup I end up thinking about the teams that are not there.

Not the favorites. The countries that have never qualified once, or have not been back in decades. I wanted to know whether you could look at a country and tell if it was ever going to produce enough talent to get there. And if the answer was no, I wanted to know what was actually holding it back.

That question turns into a measurable one: given a country's population, wealth, and history, how many elite players should it be producing? Once you have that number, the interesting countries are the ones that miss it in either direction. Countries producing far more than expected are doing something right. Countries producing far less have something in the way.

What it actually does

The project takes 41,734 real players and sorts them by the country they were born in and the five year window they were born into. For each of those groups it counts how many became elite players, then divides by the country's population to get a fair rate instead of just rewarding big countries.

Then it trains models to predict that rate from things a country cannot easily change overnight: how many people live there, how wealthy they are, how urban, and how much talent they produced in the previous generation. The gap between what the model predicts and what actually happened is the part worth looking at.

How it works

Player records come from Transfermarkt and get matched to World Bank country data, which sounds simple and was not, since the two sources name countries differently. A player counts as elite if they played in any country's first tier league. I checked that definition against real World Cup rosters from 2006 through 2026 and it covers 99.9 percent of actual World Cup players.

Two earlier definitions got thrown out first. One relied on international caps, which silently recorded zero for every retired player. The other only counted the top five European leagues, which mostly measured where a player happened to be born.

Models train on birth cohorts from 1985 to 1995 and get scored on the 2000 cohort, which they never see during training. The split is by time rather than at random, because a random split would let a model learn from the future to predict the past.

Built with

Four models, all trained against the same data and the same metrics so the comparison is fair: linear regression as the baseline, a random forest, gradient boosting, and a small neural network with the training loop written by hand. Every run is logged in MLflow.

Results

Model comparison

Four architectures, identical data, scored on a birth cohort none of them trained on. The last two rows are not models at all. They are dumb rules included to show what the trained models actually have to beat.

Ranked by mean absolute error rather than RMSE. The largest values in this dataset come from very small countries, where one or two players swing the per million rate enormously, and RMSE would push the models to chase that noise.

Map

Who beat their prediction

Blue means a country produced more elite talent than the model expected. Red means less. Hover any country for its numbers, or switch to the table below the map.

Drag to pan, scroll to zoom, double click to reset. Grey countries are not in the 2000 cohort.

Outliers

The countries worth asking about

Produced more than expected

Produced less than expected

These are patterns, not causes. Nothing here isolates why a country over or under performs, and the data records where a player was born rather than where they were actually developed.

Drivers

What the models leaned on

How much each input mattered to the selected model.

Tree models report importance, which has no direction and adds up to one. Linear regression reports coefficients, which do have direction, so a red bar there means the input pushes the prediction down.

Finding

More complexity did not help

What I would fix with more time