Health spending divergence: fast-growing vs stagnating EU health systems

Between 2000 and 2023, EU health spending trajectories diverged dramatically. Newer members like Romania, Poland, and Malta more than doubled their real spending, while Southern European countries barely grew — Italy +21%, Greece +26%.

This notebook examines health spending across all 27 EU member states using three WHO datasets:

Throughout, countries are grouped as fast growers (Malta, Ireland, Romania, Poland, Slovakia — red), slow growers (Italy, Greece, Portugal — blue), and other EU (grey) for context.

Setup and data loading
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import seaborn as sns
import numpy as np

sns.set_theme(style='whitegrid', palette='colorblind')
plt.rcParams['figure.figsize'] = (14, 6)
plt.rcParams['figure.dpi'] = 100

# Load data
spend = pd.read_csv('data/EU_health_spend.csv')
purpose = pd.read_csv('data/EU_spend_purpose.csv')
life = pd.read_csv('data/EU_US_lifeExp.csv')

# Harmonize Netherlands naming
spend['country_name'] = spend['country_name'].replace('Netherlands (Kingdom of the)', 'Netherlands')
purpose['country_name'] = purpose['country_name'].replace('Netherlands (Kingdom of the)', 'Netherlands')

# Reference groups
FAST_GROWERS = ['Malta', 'Ireland', 'Romania', 'Poland', 'Slovakia']
SLOW_GROWERS = ['Italy', 'Greece', 'Portugal']

def group_label(country):
    if country in FAST_GROWERS:
        return 'Fast growers'
    elif country in SLOW_GROWERS:
        return 'Slow growers'
    return 'Other EU'

def group_color(country):
    if country in FAST_GROWERS:
        return '#e74c3c'
    elif country in SLOW_GROWERS:
        return '#2980b9'
    return '#555555'

# Prepare CHE data (used throughout)
che = spend[spend['expenditure_type'] == 'Current Health Expenditure (CHE)'].copy()

# Compute total growth 2000-2023 for sorting
growth_00_23 = {}
for c in che['country_name'].unique():
    d = che[che['country_name'] == c]
    v0 = d[d['year'] == 2000]['value'].values
    v1 = d[d['year'] == 2023]['value'].values
    if len(v0) and len(v1):
        growth_00_23[c] = (v1[0] / v0[0] - 1) * 100

ALL_COUNTRIES = sorted(growth_00_23, key=growth_00_23.get, reverse=True)

# Small multiples layout
SM_NCOLS = 5
SM_NROWS = int(np.ceil(len(ALL_COUNTRIES) / SM_NCOLS))

print(f'Spend: {len(spend)} rows, Purpose: {len(purpose)} rows, Life exp: {len(life)} rows')
print(f'{len(ALL_COUNTRIES)} EU countries, sorted by total growth 2000-2023')
print(f'\nFast growers: {FAST_GROWERS}')
print(f'Slow growers: {SLOW_GROWERS}')
Spend: 2172 rows, Purpose: 1581 rows, Life exp: 67 rows
27 EU countries, sorted by total growth 2000-2023

Fast growers: ['Malta', 'Ireland', 'Romania', 'Poland', 'Slovakia']
Slow growers: ['Italy', 'Greece', 'Portugal']

1. The divergence: total health spending over time

How have EU countries’ spending trajectories diverged since 2000?

Show code
# Index to 2000 = 100 for all countries
che_idx = che[che['country_name'].isin(ALL_COUNTRIES)].copy()
base_2000 = che_idx[che_idx['year'] == 2000].set_index('country_name')['value']
che_idx = che_idx[che_idx['country_name'].isin(base_2000.index)]
che_idx['indexed'] = che_idx.apply(lambda r: r['value'] / base_2000[r['country_name']] * 100, axis=1)

fig, axes = plt.subplots(SM_NROWS, SM_NCOLS, figsize=(20, SM_NROWS * 3), sharex=True, sharey=True)

for i, country in enumerate(ALL_COUNTRIES):
    ax = axes.flat[i]
    d = che_idx[che_idx['country_name'] == country]
    color = group_color(country)
    ax.plot(d['year'], d['indexed'], color=color, linewidth=2)
    ax.axhline(100, color='grey', linestyle='--', alpha=0.3, linewidth=0.5)
    ax.axvspan(2008, 2013, alpha=0.06, color='red')
    ax.axvline(2020, color='grey', linestyle=':', alpha=0.3)
    growth_pct = growth_00_23.get(country, 0)
    ax.set_title(f'{country} ({growth_pct:+.0f}%)', fontsize=9, fontweight='bold', color=color)
    ax.tick_params(labelsize=7)

for j in range(len(ALL_COUNTRIES), SM_NROWS * SM_NCOLS):
    axes.flat[j].set_visible(False)

fig.suptitle('Total health expenditure growth, indexed to 2000 = 100 (constant 2023 USD)',
             fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
Figure 1: Total health expenditure growth indexed to 2000 for all EU countries, sorted by total growth. Red = fast growers, blue = slow growers.

2. Did the eurozone crisis cause the stagnation?

Southern Europe was hit hard by austerity after 2008. Let’s see how spending changed in distinct periods.

Show code
periods = [
    ('Pre-crisis\n2000\u20132008', 2000, 2008),
    ('Crisis\n2008\u20132013', 2008, 2013),
    ('Recovery\n2013\u20132019', 2013, 2019),
    ('COVID+\n2019\u20132023', 2019, 2023),
]

che_pivot = che[che['country_name'].isin(ALL_COUNTRIES)].pivot(
    index='year', columns='country_name', values='value')

period_growth = []
for label, y_start, y_end in periods:
    for country in ALL_COUNTRIES:
        try:
            v_start = che_pivot.loc[y_start, country]
            v_end = che_pivot.loc[y_end, country]
            growth = (v_end / v_start - 1) * 100
        except KeyError:
            growth = np.nan
        period_growth.append({'period': label, 'country': country, 'growth': growth})

pg = pd.DataFrame(period_growth)

pg_wide = pg.pivot(index='country', columns='period', values='growth')
pg_wide = pg_wide[[p[0] for p in periods]]
pg_wide['Total\n2000\u20132023'] = pg_wide.index.map(lambda c: growth_00_23.get(c, np.nan))
pg_wide = pg_wide.reindex(ALL_COUNTRIES)

fig, ax = plt.subplots(figsize=(10, 9))
sns.heatmap(pg_wide, annot=True, fmt='.0f', cmap='RdBu_r', center=0,
            linewidths=0.4, linecolor='white', ax=ax, annot_kws={'size': 8},
            cbar_kws={'label': 'Growth (%)', 'shrink': 0.5, 'aspect': 30})
ax.set_title('Health spending growth by period (%, constant 2023 USD)',
             fontsize=13, fontweight='bold')
ax.set_ylabel('')
ax.set_xlabel('')
ax.tick_params(axis='y', labelsize=9)
ax.tick_params(axis='x', labelsize=9)

for i, label in enumerate(ax.get_yticklabels()):
    label.set_color(group_color(ALL_COUNTRIES[i]))
    label.set_fontweight('bold' if ALL_COUNTRIES[i] in FAST_GROWERS + SLOW_GROWERS else 'normal')

plt.tight_layout()
plt.show()
Figure 2: Health spending growth by period for all EU countries, sorted by total growth.
Show code
pg_table = pg_wide.copy()
pg_table['group'] = pg_table.index.map(group_label)
pg_table.round(1)
Table 1: Growth rates by period for all EU countries
period Pre-crisis\n2000–2008 Crisis\n2008–2013 Recovery\n2013–2019 COVID+\n2019–2023 Total\n2000–2023 group
country
Malta 44.8 24.2 65.8 21.2 261.3 Fast growers
Ireland 111.9 9.8 13.1 27.0 234.1 Fast growers
Romania 98.8 0.8 43.8 8.4 212.6 Fast growers
Poland 65.5 15.6 29.4 23.5 205.8 Fast growers
Slovakia 110.9 13.8 11.4 12.4 200.5 Fast growers
Cyprus 57.5 4.6 30.1 37.8 195.2 Other EU
Estonia 83.9 3.3 32.2 13.5 185.0 Other EU
Lithuania 79.4 -3.4 42.0 15.4 184.2 Other EU
Bulgaria 71.9 14.1 17.2 23.0 182.8 Other EU
Latvia 91.1 -12.8 44.5 14.6 176.1 Other EU
Czechia 55.2 16.0 24.0 14.0 154.2 Other EU
Sweden 36.0 40.5 15.8 7.3 137.4 Other EU
Slovenia 40.3 1.4 19.4 18.4 101.0 Other EU
Belgium 41.1 14.4 13.0 6.2 93.6 Other EU
Spain 57.9 -0.7 17.8 3.6 91.5 Other EU
Finland 47.8 11.7 1.8 13.5 90.7 Other EU
Netherlands 40.5 12.0 8.1 6.2 80.7 Other EU
Luxembourg 40.1 -10.6 21.7 8.3 65.1 Other EU
Austria 24.3 7.3 13.5 7.5 62.7 Other EU
Denmark 30.6 7.8 12.6 2.5 62.5 Other EU
Hungary 36.8 -1.3 10.5 8.1 61.3 Other EU
France 25.9 11.0 7.0 5.6 57.9 Other EU
Croatia 40.6 -25.2 22.9 20.5 55.8 Other EU
Germany 14.1 10.2 18.5 3.3 54.0 Other EU
Portugal 21.6 -9.8 15.5 12.2 42.1 Slow growers
Greece 62.2 -30.5 1.4 10.4 26.3 Slow growers
Italy 20.5 -4.8 3.0 2.2 20.8 Slow growers

3. Government vs private spending share over time

How has the split between government and private health spending evolved? Each panel shows the proportion stacked to 100%.

Show code
pvt = spend[spend['expenditure_type'] == 'Domestic Private Health Expenditure (PVT-D)'].copy()
pvt = pvt.rename(columns={'value': 'pvt'})[['country_name', 'year', 'pvt']]
stacked = merged.merge(pvt, on=['country_name', 'year'])
stacked['govt_pct'] = stacked['gghe'] / stacked['che'] * 100
stacked['pvt_pct'] = stacked['pvt'] / stacked['che'] * 100

fig, axes = plt.subplots(SM_NROWS, SM_NCOLS, figsize=(20, SM_NROWS * 3), sharex=True, sharey=True)

for i, country in enumerate(ALL_COUNTRIES):
    ax = axes.flat[i]
    d = stacked[stacked['country_name'] == country].sort_values('year')
    color = group_color(country)
    ax.fill_between(d['year'], 0, d['govt_pct'], alpha=0.7, label='Government', color=color)
    ax.fill_between(d['year'], d['govt_pct'], d['govt_pct'] + d['pvt_pct'], alpha=0.3, label='Private', color=color)
    ax.set_ylim(0, 100)
    gs_2023 = d[d['year'] == 2023]['govt_pct'].values
    gs_label = f' ({gs_2023[0]:.0f}% govt)' if len(gs_2023) else ''
    ax.set_title(f'{country}{gs_label}', fontsize=9, fontweight='bold', color=color)
    ax.tick_params(labelsize=7)
    if i == 0:
        ax.legend(fontsize=7, loc='lower left')

for j in range(len(ALL_COUNTRIES), SM_NROWS * SM_NCOLS):
    axes.flat[j].set_visible(False)

fig.suptitle('Government vs private share of health spending (% of total)',
             fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
Figure 3: Government vs private health spending shares over time, normalized to 100%.

4. Year-over-year growth rates

Where exactly did growth accelerate or collapse? This shows the annual % change.

Show code
che_yoy = che[che['country_name'].isin(ALL_COUNTRIES)].sort_values(['country_name', 'year']).copy()
che_yoy['yoy'] = che_yoy.groupby('country_name')['value'].pct_change() * 100

fig, axes = plt.subplots(SM_NROWS, SM_NCOLS, figsize=(20, SM_NROWS * 3), sharex=True, sharey=True)

for i, country in enumerate(ALL_COUNTRIES):
    ax = axes.flat[i]
    d = che_yoy[che_yoy['country_name'] == country]
    color = group_color(country)
    ax.plot(d['year'], d['yoy'], color=color, linewidth=1.5, alpha=0.85)
    ax.axhline(0, color='black', linewidth=0.5)
    ax.axvspan(2008, 2013, alpha=0.06, color='red')
    ax.axvline(2020, color='grey', linestyle=':', alpha=0.3)
    ax.set_title(country, fontsize=9, fontweight='bold', color=group_color(country))
    ax.tick_params(labelsize=7)

for j in range(len(ALL_COUNTRIES), SM_NROWS * SM_NCOLS):
    axes.flat[j].set_visible(False)

fig.suptitle('Year-over-year change in total health expenditure (%)', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
Figure 4: Annual growth rates of total health expenditure for all EU countries.

5. What are they spending on? Purpose breakdown (2016–2023)

The purpose data only covers 2016–2023, but it lets us see structural differences in what these countries prioritize.

Show code
purp_2023 = purpose[purpose['year'] == 2023].copy()che_2023 = che[che['year'] == 2023][['country_name', 'value']].rename(columns={'value': 'che'})purp_2023 = purp_2023.merge(che_2023, on='country_name')purp_2023['share'] = purp_2023['value'] / purp_2023['che'] * 100main_purposes = [    'Curative care',    'Long-term care (health)',    'Medical goods (non-specified by function)',    'Preventive care',    'Rehabilitative care',    'Governance, and health system and financing administration',]short_labels = {    'Curative care': 'Curative care',    'Long-term care (health)': 'Long-term care',    'Medical goods (non-specified by function)': 'Medical goods',    'Preventive care': 'Preventive care',    'Rehabilitative care': 'Rehabilitative care',    'Governance, and health system and financing administration': 'Governance',}# ISO 2-letter codesISO_CODES = {    'Austria': 'AT', 'Belgium': 'BE', 'Bulgaria': 'BG', 'Croatia': 'HR',    'Cyprus': 'CY', 'Czechia': 'CZ', 'Denmark': 'DK', 'Estonia': 'EE',    'Finland': 'FI', 'France': 'FR', 'Germany': 'DE', 'Greece': 'GR',    'Hungary': 'HU', 'Ireland': 'IE', 'Italy': 'IT', 'Latvia': 'LV',    'Lithuania': 'LT', 'Luxembourg': 'LU', 'Malta': 'MT', 'Netherlands': 'NL',    'Poland': 'PL', 'Portugal': 'PT', 'Romania': 'RO', 'Slovakia': 'SK',    'Slovenia': 'SI', 'Spain': 'ES', 'Sweden': 'SE',}purp_all = purp_2023[(purp_2023['country_name'].isin(ALL_COUNTRIES)) &                      (purp_2023['spending_purpose'].isin(main_purposes))].copy()purp_all['purpose_short'] = purp_all['spending_purpose'].map(short_labels)purp_all['iso'] = purp_all['country_name'].map(ISO_CODES)purpose_order = ['Curative care', 'Long-term care', 'Medical goods',                 'Preventive care', 'Rehabilitative care', 'Governance']purpose_colors = sns.color_palette('Set2', len(purpose_order))fig, axes = plt.subplots(2, 3, figsize=(20, 10))for idx, (purp, color) in enumerate(zip(purpose_order, purpose_colors)):    ax = axes.flat[idx]    sub = purp_all[purp_all['purpose_short'] == purp].sort_values('share', ascending=False)    bars = ax.bar(range(len(sub)), sub['share'].values, color=color, width=0.8, edgecolor='white', linewidth=0.3)    ax.set_xticks(range(len(sub)))    ax.set_xticklabels(sub['iso'].values, fontsize=7, fontweight='bold')    ax.set_title(purp, fontsize=12, fontweight='bold', color=color)    ax.set_ylabel('% of CHE', fontsize=9)    ax.tick_params(axis='y', labelsize=8)fig.suptitle('Health spending by purpose \u2014 2023 (% of total expenditure)',             fontsize=14, fontweight='bold')plt.tight_layout()plt.show()
Figure 5
Show code
purp_all['group'] = purp_all['country_name'].map(group_label)
group_avg = purp_all.groupby(['group', 'purpose_short'])['share'].mean().unstack()
group_avg = group_avg[purpose_order].round(1)
group_avg['Total shown'] = group_avg.sum(axis=1).round(1)
group_avg
Table 2: Average spending shares by group (% of CHE)
purpose_short Curative care Long-term care Medical goods Preventive care Rehabilitative care Governance Total shown
group
Fast growers 53.8 11.7 21.8 1.8 2.5 3.4 95.0
Other EU 52.1 14.1 18.3 3.3 4.0 2.7 94.5
Slow growers 58.3 5.8 22.3 3.3 2.3 1.9 93.9

7. How spending purposes shifted (2016 → 2023)

Are these countries restructuring? Who shifted toward long-term care or away from medical goods?

Show code
def purpose_shares(year):
    p = purpose[purpose['year'] == year].copy()
    c = che[che['year'] == year][['country_name', 'value']].rename(columns={'value': 'che'})
    p = p.merge(c, on='country_name')
    p['share'] = p['value'] / p['che'] * 100
    return p

p2016 = purpose_shares(2016)
p2023_s = purpose_shares(2023)

p_change = p2016[['country_name', 'spending_purpose', 'share']].merge(
    p2023_s[['country_name', 'spending_purpose', 'share']],
    on=['country_name', 'spending_purpose'],
    suffixes=('_2016', '_2023')
)
p_change['change_pp'] = p_change['share_2023'] - p_change['share_2016']

pc_all = p_change[(p_change['country_name'].isin(ALL_COUNTRIES)) &
                   (p_change['spending_purpose'].isin(main_purposes))].copy()
pc_all['purpose_short'] = pc_all['spending_purpose'].map(short_labels)

pivot_change = pc_all.pivot(index='country_name', columns='purpose_short', values='change_pp')
pivot_change = pivot_change.reindex(ALL_COUNTRIES)[purpose_order]

fig, ax = plt.subplots(figsize=(10, 9))
sns.heatmap(pivot_change, annot=True, fmt='+.1f', cmap='RdBu_r', center=0,
            linewidths=0.4, linecolor='white', ax=ax, annot_kws={'size': 8},
            cbar_kws={'label': 'Change in share (pp)', 'shrink': 0.5, 'aspect': 30})
ax.set_title('Change in spending purpose shares, 2016 \u2192 2023 (percentage points)',
             fontsize=13, fontweight='bold')
ax.set_ylabel('')
ax.set_xlabel('')
ax.tick_params(axis='y', labelsize=9)
ax.tick_params(axis='x', labelsize=9)

for label in ax.get_yticklabels():
    country = label.get_text()
    label.set_color(group_color(country))
    label.set_fontweight('bold' if country in FAST_GROWERS + SLOW_GROWERS else 'normal')

plt.tight_layout()
plt.show()
Figure 6: Change in spending purpose shares from 2016 to 2023 (percentage points) for all EU countries.

8. Life expectancy: did faster spending growth translate to better outcomes?

Show code
from matplotlib.lines import Line2D

all_combined = []
for country in ALL_COUNTRIES:
    le = life[life['country'] == country]
    le_change_val = None
    le_2023_val = None
    from_year = None
    for start_year in [2002, 2007]:
        v_start = le[le['year'] == start_year]['lifeExp'].values
        v_end = le[le['year'] == 2023]['lifeExp'].values
        if len(v_start) and len(v_end):
            le_change_val = v_end[0] - v_start[0]
            le_2023_val = v_end[0]
            from_year = start_year
            break
    if le_2023_val is None:
        v_end = le[le['year'] == 2023]['lifeExp'].values
        if len(v_end):
            le_2023_val = v_end[0]
    if country in growth_00_23:
        all_combined.append({
            'country': country, 'group': group_label(country),
            'che_growth': growth_00_23[country],
            'le_2023': le_2023_val, 'le_change': le_change_val, 'from_year': from_year,
        })

all_combined = pd.DataFrame(all_combined)

fig, axes = plt.subplots(1, 2, figsize=(16, 7))

for ax_idx, (col, ylabel, title) in enumerate([
    ('le_change', 'Life expectancy change (years)', 'Spending growth vs life expectancy gain'),
    ('le_2023', 'Life expectancy 2023', 'Spending growth vs current life expectancy'),
]):
    ax = axes[ax_idx]
    subset = all_combined.dropna(subset=[col])
    for _, row in subset.iterrows():
        color = group_color(row['country'])
        marker = 'o' if row['group'] == 'Fast growers' else ('s' if row['group'] == 'Slow growers' else 'D')
        size = 100 if row['group'] != 'Other EU' else 50
        ax.scatter(row['che_growth'], row[col], color=color, marker=marker, s=size,
                   zorder=10 if row['group'] != 'Other EU' else 5,
                   edgecolors='white', linewidth=0.8)
        fw = 'bold' if row['group'] != 'Other EU' else 'normal'
        ax.annotate(row['country'], (row['che_growth'], row[col]),
                    textcoords='offset points', xytext=(6, 3), fontsize=7, fontweight=fw, color=color)
    ax.set_xlabel('Health spending growth 2000\u20132023 (%)', fontsize=11)
    ax.set_ylabel(ylabel, fontsize=11)
    ax.set_title(title, fontsize=13, fontweight='bold')

legend_elements = [
    Line2D([0], [0], marker='o', color='w', markerfacecolor='#e74c3c', markersize=10, label='Fast growers'),
    Line2D([0], [0], marker='s', color='w', markerfacecolor='#2980b9', markersize=10, label='Slow growers'),
    Line2D([0], [0], marker='D', color='w', markerfacecolor='#555555', markersize=7, label='Other EU'),
]
axes[1].legend(handles=legend_elements, loc='lower right', fontsize=10)

plt.tight_layout()
plt.show()

print('\nData behind the chart:')
all_combined[['country', 'group', 'che_growth', 'le_2023', 'le_change', 'from_year']].round(1)

Data behind the chart:
(a) Spending growth vs life expectancy for all EU countries with available data.
country group che_growth le_2023 le_change from_year
0 Malta Fast growers 261.3 83.4 NaN NaN
1 Ireland Fast growers 234.1 82.8 5.1 2002.0
2 Romania Fast growers 212.6 76.5 5.2 2002.0
3 Poland Fast growers 205.8 78.4 3.7 2002.0
4 Slovakia Fast growers 200.5 NaN NaN NaN
5 Cyprus Other EU 195.2 81.6 NaN NaN
6 Estonia Other EU 185.0 78.9 NaN NaN
7 Lithuania Other EU 184.2 77.4 NaN NaN
8 Bulgaria Other EU 182.8 75.8 3.7 2002.0
9 Latvia Other EU 176.1 75.6 NaN NaN
10 Czechia Other EU 154.2 79.9 NaN NaN
11 Sweden Other EU 137.4 83.4 3.3 2002.0
12 Slovenia Other EU 101.0 82.0 5.3 2002.0
13 Belgium Other EU 93.6 82.4 4.1 2002.0
14 Spain Other EU 91.5 84.0 4.2 2002.0
15 Finland Other EU 90.7 81.6 3.3 2002.0
16 Netherlands Other EU 80.7 81.9 3.4 2002.0
17 Luxembourg Other EU 65.1 83.4 NaN NaN
18 Austria Other EU 62.7 81.8 2.9 2002.0
19 Denmark Other EU 62.5 81.8 4.6 2002.0
20 Hungary Other EU 61.3 76.6 4.1 2002.0
21 France Other EU 57.9 82.9 3.3 2002.0
22 Croatia Other EU 55.8 78.6 3.7 2002.0
23 Germany Other EU 54.0 81.1 2.4 2002.0
24 Portugal Slow growers 42.1 82.4 5.1 2002.0
25 Greece Slow growers 26.3 81.8 3.5 2002.0
26 Italy Slow growers 20.8 83.4 3.2 2002.0
(b)
Figure 7

9. Summary of key findings

What the data shows — and what it doesn’t.

Show code
summary = []
for country in ALL_COUNTRIES:
    d_che = che[che['country_name'] == country]
    v2000 = d_che[d_che['year'] == 2000]['value'].values
    v2008 = d_che[d_che['year'] == 2008]['value'].values
    v2013 = d_che[d_che['year'] == 2013]['value'].values
    v2023 = d_che[d_che['year'] == 2023]['value'].values

    if not (len(v2000) and len(v2023)):
        continue
    v2000, v2008, v2013, v2023 = v2000[0], v2008[0], v2013[0], v2023[0]

    gs = merged[merged['country_name'] == country]
    gs_2000 = gs[gs['year'] == 2000]['govt_share'].values
    gs_2023 = gs[gs['year'] == 2023]['govt_share'].values

    le_row = life[life['country'] == country]
    le_2023 = le_row[le_row['year'] == 2023]['lifeExp'].values
    le_2023 = le_2023[0] if len(le_2023) else None

    ltc = purp_2023[(purp_2023['country_name'] == country) &
                     (purp_2023['spending_purpose'] == 'Long-term care (health)')]
    ltc_share = ltc['share'].values[0] if len(ltc) else None

    summary.append({
        'Country': country,
        'Group': group_label(country),
        'CHE 2000 (bn)': round(v2000 / 1e9, 1),
        'CHE 2023 (bn)': round(v2023 / 1e9, 1),
        'Growth 00-23': f'{(v2023/v2000 - 1)*100:.0f}%',
        'Crisis 08-13': f'{(v2013/v2008 - 1)*100:+.0f}%',
        'Govt% 2000': f'{gs_2000[0]:.0f}%' if len(gs_2000) else 'n/a',
        'Govt% 2023': f'{gs_2023[0]:.0f}%' if len(gs_2023) else 'n/a',
        'LTC% 2023': f'{ltc_share:.0f}%' if ltc_share else 'n/a',
        'LE 2023': round(le_2023, 1) if le_2023 else 'n/a',
    })

summary_df = pd.DataFrame(summary).set_index('Country')
summary_df
Table 3: Summary comparison of all EU countries
Group CHE 2000 (bn) CHE 2023 (bn) Growth 00-23 Crisis 08-13 Govt% 2000 Govt% 2023 LTC% 2023 LE 2023
Country
Malta Fast growers 0.5 2.0 261% +24% 72% 66% 21% 83.4
Ireland Fast growers 10.9 36.3 234% +10% 78% 77% 22% 82.8
Romania Fast growers 6.4 20.0 213% +1% 79% 76% 6% 76.5
Poland Fast growers 19.0 58.0 206% +16% 68% 77% 8% 78.4
Slovakia Fast growers 3.3 9.8 201% +14% 87% 79% 2% n/a
Cyprus Other EU 0.9 2.8 195% +5% 41% 77% 5% 81.6
Estonia Other EU 1.1 3.1 185% +3% 76% 76% 11% 78.9
Lithuania Other EU 2.1 5.8 184% -3% 67% 65% 8% 77.4
Bulgaria Other EU 2.9 8.1 183% +14% 60% 63% 5% 75.8
Latvia Other EU 1.1 3.1 176% -13% 51% 59% 5% 75.6
Czechia Other EU 11.4 28.9 154% +16% 89% 84% 14% 79.9
Sweden Other EU 27.5 65.3 137% +41% 86% 86% 28% 83.4
Slovenia Other EU 3.2 6.4 101% +1% 71% 73% 12% 82.0
Belgium Other EU 36.0 69.6 94% +14% 75% 74% 23% 82.4
Spain Other EU 78.0 149.4 91% -1% 71% 73% 10% 84.0
Finland Other EU 16.2 30.9 91% +12% 76% 81% 21% 81.6
Netherlands Other EU 62.8 113.5 81% +12% 69% 68% 29% 81.9
Luxembourg Other EU 3.0 5.0 65% -11% 83% 87% 18% 83.4
Austria Other EU 35.1 57.1 63% +7% 73% 77% 14% 81.8
Denmark Other EU 23.8 38.7 63% +8% 83% 83% 21% 81.8
Hungary Other EU 8.5 13.6 61% -1% 69% 73% 4% 76.6
France Other EU 222.6 351.6 58% +11% 73% 68% 16% 82.9
Croatia Other EU 3.9 6.0 56% -25% 85% 84% 3% 78.6
Germany Other EU 345.0 531.5 54% +10% 78% 79% 21% 81.1
Portugal Slow growers 20.4 29.0 42% -10% 70% 61% 5% 82.4
Greece Slow growers 16.2 20.4 26% -30% 62% 51% 2% 81.8
Italy Slow growers 160.3 193.7 21% -5% 73% 73% 10% 83.4