immigration and migration inflows to Europe over the past 10 years trend percentage change

Model: Kimi K2.6 Checked on:

Over the past decade, total immigration inflows to the European Union (EU27) have risen sharply, climbing from around 3.19 million in 2014 to 5.68 million in 2024—an overall increase of 78.3%1. The trajectory was uneven, driven by policy shifts, the COVID-19 pandemic, and the humanitarian response to Russia’s invasion of Ukraine.

Annual trend and percentage change

Year-over-year growth rates fluctuated considerably1:

The net absolute gain between 2014 and 2024 was approximately 2.50 million additional immigrants per year1.

Composition of inflows (intra-EU vs. extra-EU)

A breakdown by origin is available from 2020 onward21. Extra-EU migration consistently accounted for the larger share:

Intra-EU mobility remained relatively stable (roughly 1.3–1.6 million annually), whereas extra-EU inflows were the primary driver of volatility, especially the 2022 spike and subsequent partial retreat1.

Visual summary

The trend line and the 2020–2024 origin breakdown are illustrated in the charts generated during the analysis1.

  1. Dataset: migr_imm12prv

    Images

    eu_immigration_trend.svg
    eu_immigration_breakdown.svg

    Dataset filters for migr_imm12prv

    FilterValue(s)
    freqA
    geoEU27_2020
    unitNR
    sinceTimePeriod2014

    Program output

    Total Immigration to EU27 (annual)
    time
    2014    3187841
    2015    4097244
    2016    3735536
    2017    3766413
    2018    3936055
    2019    4206293
    2020    3279673
    2021    4132213
    2022    6997284
    2023    6011360
    2024    5684984
    
    Year-over-year % change
    time
    2015    28.5
    2016    -8.8
    2017     0.8
    2018     4.5
    2019     6.9
    2020   -22.0
    2021    26.0
    2022    69.3
    2023   -14.1
    2024    -5.4
    
    Overall change 2014 to 2024: 78.3%
    Absolute change: 2,497,143
    
    Charts saved: eu_immigration_trend.svg and eu_immigration_breakdown.svg
    

    Code

    import pandas as pd
    import matplotlib.pyplot as plt
    
    # Read data
    df = pd.read_csv(DATASET_FILE)
    
    # Pivot to get years as rows
    total = df[df['partner'] == 'TOTAL'][['time', 'value']].sort_values('time').set_index('time')['value']
    intra = df[df['partner'] == 'EU27_2020_FOR'][['time', 'value']].sort_values('time').set_index('time')['value']
    extra = df[df['partner'] == 'NEU27_2020_FOR'][['time', 'value']].sort_values('time').set_index('time')['value']
    
    # Calculate YoY % change for total
    pct_change = total.pct_change() * 100
    
    # Calculate overall change 2014 vs 2024
    overall_change = (total.loc[2024] - total.loc[2014]) / total.loc[2014] * 100
    
    print("Total Immigration to EU27 (annual)")
    print(total.to_string())
    print("\nYear-over-year % change")
    print(pct_change.dropna().round(1).to_string())
    print(f"\nOverall change 2014 to 2024: {overall_change:.1f}%")
    print(f"Absolute change: {total.loc[2024] - total.loc[2014]:,.0f}")
    
    # Create chart
    fig, ax1 = plt.subplots(figsize=(10, 6))
    
    # Plot total immigration line
    years_total = total.index.astype(int)
    ax1.plot(years_total, total.values/1e6, marker='o', color='#1f77b4', linewidth=2, label='Total immigration')
    ax1.set_xlabel('Year')
    ax1.set_ylabel('Number of immigrants (millions)', color='#1f77b4')
    ax1.tick_params(axis='y', labelcolor='#1f77b4')
    ax1.set_ylim(0, 8)
    
    # Add annotation for 2022 spike
    ax1.annotate('2022 spike:\n+69.3% YoY', xy=(2022, total.loc[2022]/1e6), xytext=(2020, 7.5),
                 arrowprops=dict(arrowstyle='->', color='red'), fontsize=10, color='red')
    
    # Add secondary axis for percentage change? Or just a bar chart for the components
    # Create a second plot for the breakdown
    fig2, ax2 = plt.subplots(figsize=(10, 5))
    
    breakdown_years = intra.index.astype(int)
    width = 0.35
    ax2.bar(breakdown_years - width/2, intra.values/1e6, width, label='Intra-EU (from other EU)', color='#2ca02c')
    ax2.bar(breakdown_years + width/2, extra.values/1e6, width, label='Extra-EU (from outside EU)', color='#d62728')
    ax2.set_xlabel('Year')
    ax2.set_ylabel('Number of immigrants (millions)')
    ax2.set_title('Immigration to EU27 by Origin (2020-2024)')
    ax2.legend()
    ax2.set_ylim(0, 6)
    
    # Add values on top of bars
    for i, (x, v) in enumerate(zip(breakdown_years, intra.values/1e6)):
        ax2.text(x - width/2, v + 0.1, f'{v:.1f}M', ha='center', va='bottom', fontsize=8)
    for i, (x, v) in enumerate(zip(breakdown_years, extra.values/1e6)):
        ax2.text(x + width/2, v + 0.1, f'{v:.1f}M', ha='center', va='bottom', fontsize=8)
    
    fig.savefig('eu_immigration_trend.svg', format='svg')
    fig2.savefig('eu_immigration_breakdown.svg', format='svg')
    
    print("\nCharts saved: eu_immigration_trend.svg and eu_immigration_breakdown.svg")
    
  2. Eurostat dataset: Immigration by broad group of country of previous residence