DEV Community

Maria-Luise Volkmar
Maria-Luise Volkmar

Posted on • Originally published at datapulse.de

Europe's brain drain: the biggest loser flips when you normalize per 1,000 residents

Here is a question I could not answer from the headlines: which European countries are actually losing people the fastest, in absolute terms or per capita? Those are two different questions, and they give two different answers. So I pulled the open data and ran the numbers.

The headline figure

Across the 19 European countries in the 2024 dataset, 17 recorded a net loss of native-born residents. Only two were net positive. So the "brain drain" story is not a handful of outliers, it is the default state of the continent.

But the interesting part is who tops the ranking, because it depends entirely on how you measure.

Load the data yourself

The dataset is public on GitHub (CC BY 4.0). Every number below is reproducible with a few lines of pandas. No download, no API key, it reads the raw CSV straight from the repo:

import pandas as pd

url = (
    "https://raw.githubusercontent.com/DatapulseResearch/"
    "brain-drain-eu/main/data/net_migration_native_born_2024.csv"
)
df = pd.read_csv(url)

print(df.shape)          # (19, 3)
print(df.columns.tolist())  # ['country', 'net_migration', 'per_1000_residents']

# How many countries lost native-born residents?
losers = (df["net_migration"] < 0).sum()
print(f"{losers} of {len(df)} countries had a net loss")  # 17 of 19
Enter fullscreen mode Exit fullscreen mode

net_migration is the raw count for 2024 (negative means a net loss of native-born residents). per_1000_residents is the same flow normalized by population size.

The absolute ranking: Germany runs away with it

Sort by the raw count and one country dominates:

worst_absolute = df.sort_values("net_migration").head(5)
print(worst_absolute[["country", "net_migration"]])
Enter fullscreen mode Exit fullscreen mode
       country  net_migration
0      Germany         -91067
...
Enter fullscreen mode Exit fullscreen mode

Germany loses -91,067 native-born residents, far more than anyone else in absolute terms. If you stop reading here, the story writes itself: "Germany, Europe's biggest brain drain." Plenty of coverage did exactly that.

The counterintuitive finding: the ranking inverts per capita

Now normalize by population and sort by per_1000_residents. The leaderboard is unrecognizable:

worst_rate = df.sort_values("per_1000_residents").head(5)
print(worst_rate[["country", "per_1000_residents"]])
Enter fullscreen mode Exit fullscreen mode
      country  per_1000_residents
   Luxembourg               -2.35
      Belgium               -1.27
       Sweden               -1.23
...
Enter fullscreen mode Exit fullscreen mode

By rate, the hardest hit are Luxembourg (-2.35), Belgium (-1.27) and Sweden (-1.23). Germany, the absolute "winner," is nowhere near the top of this list, because -91,067 out of a large population is a modest per-capita rate. A big country losing a big number is not the same as a country bleeding a large share of its people.

And the only two net-positive countries, the ones actually gaining native-born residents, are Bulgaria (+0.88) and Lithuania (+2.67), both places long framed as classic emigration stories. That is the reversal that made me want to write this up.

You can see the whole flip in one frame by putting both ranks side by side:

df["rank_absolute"] = df["net_migration"].rank().astype(int)
df["rank_per_capita"] = df["per_1000_residents"].rank().astype(int)

flip = df.sort_values("net_migration")[
    ["country", "net_migration", "per_1000_residents",
     "rank_absolute", "rank_per_capita"]
]
print(flip.to_string(index=False))
Enter fullscreen mode Exit fullscreen mode

Germany sits at rank 1 by absolute loss and drops far down the per-capita rank. Luxembourg does the opposite. Same data, two honest stories, and the one you tell depends only on which column you sort.

Data and method

Takeaway for anyone working with data

This is a clean, small example of a trap that scales to almost any comparative dataset: the metric picks the winner. "Most X in total" and "highest rate of X" answer different questions, and a chart that only shows one of them is not wrong, it is just incomplete. When you see a country-ranking headline, the first thing worth asking is whether it was normalized, because normalizing here does not shade the story, it inverts it.

The data is right there in the repo. Clone it, re-sort it, and check whether your own intuition survives the per-capita column. Mine did not.

Top comments (0)