corruption levels in European countries and Ukraine's ranking compared to other European countries

Model: DeepSeek V4.1 Flash Checked on:

Corruption levels in Europe (Corruption Perceptions Index, Eurostat)

Important caveat on Ukraine first: The Eurostat Corruption Perceptions Index dataset1 — the only corruption‑level dataset available in Eurostat or OECD — does not include Ukraine. Across the whole series (2012–2025, 44 reporting entities) there are zero observations for Ukraine23. I also checked the OECD alternatives: Ukraine appears in the codelists of the OECD Public Integrity Indicators4 and the Government at a Glance integrity tables56, but the latter return no data for Ukraine (they cover only ~33 OECD members)78, and the Public Integrity Indicators measure institutional/anti‑corruption frameworks rather than a corruption level. So I cannot provide Ukraine's ranking relative to other European countries from the available data.

What the data does show: European countries, CPI 2025

The CPI score runs 0–100, where a higher score = less corrupt. Latest‑year (2025) ranking of the 39 European countries covered19:

Rank Country Score Rank Country Score
1 🇩🇰 Denmark 89 21 🇪🇸 Spain 55
2 🇫🇮 Finland 88 22 🇨🇾 Cyprus 55
3 🇳🇴 Norway 81 23 🇵🇱 Poland 53
4 🇸🇪 Sweden 80 24 🇮🇹 Italy 53
5 🇨🇭 Switzerland 80 25 🇬🇷 Greece 50
6 🇳🇱 Netherlands 78 26 🇲🇹 Malta 49
7 🇱🇺 Luxembourg 78 27 🇸🇰 Slovakia 48
8 🇩🇪 Germany 77 28 🇭🇷 Croatia 47
9 🇮🇸 Iceland 77 29 🇲🇪 Montenegro 46
10 🇪🇪 Estonia 76 30 🇷🇴 Romania 45
11 🇮🇪 Ireland 76 31 🇽🇰 Kosovo 43
12 🇬🇧 United Kingdom 70 32 🇭🇺 Hungary 40
13 🇧🇪 Belgium 69 33 🇧🇬 Bulgaria 40
14 🇦🇹 Austria 69 34 🇲🇰 North Macedonia 40
15 🇫🇷 France 66 35 🇦🇱 Albania 39
16 🇱🇹 Lithuania 65 36 🇧🇦 Bosnia & Herzegovina 34
17 🇱🇻 Latvia 60 37 🇷🇸 Serbia 33
18 🇨🇿 Czechia 59 38 🇹🇷 Türkiye 31
19 🇸🇮 Slovenia 58 39 🇷🇺 Russia 22
20 🇵🇹 Portugal 56

For reference, the aggregates were EU‑27 average = 62 and euro area (EA21) = 63 in 202519. The Nordic countries and Switzerland top the list; the Western Balkans, Türkiye and Russia sit at the bottom.

I generated two charts during the analysis2: a horizontal bar chart of the 2025 scores (cpi_2025_europe.svg) and a 2012–2025 trend line for selected countries plus the EU average (cpi_trend.svg).

Bottom line

  1. Eurostat dataset: Corruption Perceptions Index
  2. Dataset: sdg_16_50

    Program output

    total rows: 616 n geos: 44
    ['AL', 'AT', 'BA', 'BE', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EA21', 'EE', 'EL', 'ES', 'EU27_2020', 'FI', 'FR', 'HR', 'HU', 'IE', 'IS', 'IT', 'JP', 'KR', 'LT', 'LU', 'LV', 'ME', 'MK', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'RS', 'RU', 'SE', 'SI', 'SK', 'TR', 'UK', 'US', 'XK']
    U-codes: ['UK', 'US']
    

    Code

    
    import pandas as pd
    df = pd.read_csv(DATASET_FILE)
    geos = sorted(df['geo'].unique())
    print("total rows:", len(df), "n geos:", len(geos))
    print(geos)
    # any geo whose code starts with U
    print("U-codes:", [g for g in geos if g.startswith('U')])
    
  3. Dataset: sdg_16_50

    Dataset filters for sdg_16_50

    FilterValue(s)
    geoUA

    Program output

    UA rows: 0
    Any geo containing U: []
    (0, 5)
    

    Code

    
    import pandas as pd
    df = pd.read_csv(DATASET_FILE)
    print("UA rows:", (df['geo']=='UA').sum())
    print("Any geo containing U:", [g for g in df['geo'].unique() if 'U' in g])
    print(df.shape)
    
  4. OECD dataset: Public Integrity Indicators
  5. OECD dataset: Perception of public sector integrity - Government at a glance indicators, 2025 edition
  6. OECD dataset: Trust, security and dignity - government at a glance indicators, 2025 edition
  7. Dataset: sdg_16_50

    Program output

    2025 values:
          geo  value
           DK     89
           FI     88
           NO     81
           CH     80
           SE     80
           LU     78
           NL     78
           DE     77
           IS     77
           IE     76
           EE     76
           JP     71
           UK     70
           AT     69
           BE     69
           FR     66
           LT     65
           US     64
           KR     63
         EA21     63
    EU27_2020     62
           LV     60
           CZ     59
           SI     58
           PT     56
           ES     55
           CY     55
           IT     53
           PL     53
           EL     50
           MT     49
           SK     48
           HR     47
           ME     46
           RO     45
           XK     43
           HU     40
           BG     40
           MK     40
           AL     39
           BA     34
           RS     33
           TR     31
           RU     22
    
    EU27_2020 2025: [62]
    EA21 2025: [63]
    
    European countries ranking 2025 (higher score = less corrupt):
     rank geo  value
        1  DK     89
        2  FI     88
        3  NO     81
        4  SE     80
        5  CH     80
        6  NL     78
        7  LU     78
        8  DE     77
        9  IS     77
       10  EE     76
       11  IE     76
       12  UK     70
       13  BE     69
       14  AT     69
       15  FR     66
       16  LT     65
       17  LV     60
       18  CZ     59
       19  SI     58
       20  PT     56
       21  ES     55
       22  CY     55
       23  PL     53
       24  IT     53
       25  EL     50
       26  MT     49
       27  SK     48
       28  HR     47
       29  ME     46
       30  RO     45
       31  XK     43
       32  HU     40
       33  BG     40
       34  MK     40
       35  AL     39
       36  BA     34
       37  RS     33
       38  TR     31
       39  RU     22
    

    Code

    
    import pandas as pd
    df = pd.read_csv(DATASET_FILE)
    df['time'] = df['time'].astype(int)
    
    # Aggregates vs countries
    aggs = ['EA21','EU27_2020']
    non_european = ['JP','KR','US']
    
    # Latest year snapshot
    latest = df[df['time']==2025].copy()
    print("2025 values:")
    print(latest.sort_values('value', ascending=False)[['geo','value']].to_string(index=False))
    print()
    print("EU27_2020 2025:", latest.loc[latest.geo=='EU27_2020','value'].values)
    print("EA21 2025:", latest.loc[latest.geo=='EA21','value'].values)
    
    # Full 2025 ranking of European COUNTRIES (exclude aggregates & non-European)
    eur = latest[~latest['geo'].isin(aggs+non_european)].copy()
    eur = eur.sort_values('value', ascending=False).reset_index(drop=True)
    eur['rank'] = range(1, len(eur)+1)
    print("\nEuropean countries ranking 2025 (higher score = less corrupt):")
    print(eur[['rank','geo','value']].to_string(index=False))
    
  8. Dataset filters for DSD_GOV_INT@DF_GOV_INT_2025

    FilterValue(s)
    CATEGORYINT

    Program output

    shape: (432, 10)
    measures: <ArrowStringArray>
    ['TRUST_BIR', 'TRUST_FAV']
    Length: 2, dtype: str
    n areas: 33
    ['AUS', 'AUT', 'BEL', 'CAN', 'CHE', 'CHL', 'COL', 'CRI', 'CZE', 'DEU', 'DNK', 'ESP', 'EST', 'FIN', 'FRA', 'GBR', 'GRC', 'IRL', 'ISL', 'ITA', 'JPN', 'KOR', 'LUX', 'LVA', 'MEX', 'NLD', 'NOR', 'NZL', 'OECD_REP', 'PRT', 'SVK', 'SVN', 'SWE']
    UKR present: False
    

    Code

    
    import pandas as pd
    df = pd.read_csv(DATASET_FILE)
    print("shape:", df.shape)
    print("measures:", df['MEASURE'].unique())
    print("n areas:", df['REF_AREA'].nunique())
    print(sorted(df['REF_AREA'].unique()))
    print("UKR present:", 'UKR' in df['REF_AREA'].values)
    
  9. Dataset: sdg_16_50

    Images

    cpi_2025_europe.svg
    cpi_trend.svg

    Program output

    saved cpi_2025_europe.svg
    saved cpi_trend.svg
    Ukraine rows in full dataset: 0
    

    Code

    
    import pandas as pd
    import matplotlib
    matplotlib.use('Agg')
    import matplotlib.pyplot as plt
    
    df = pd.read_csv(DATASET_FILE)
    df['time'] = df['time'].astype(int)
    
    aggs = ['EA21','EU27_2020']
    non_european = ['JP','KR','US']
    
    latest = df[df['time']==2025].copy()
    eur = latest[~latest['geo'].isin(aggs+non_european)].copy().sort_values('value')
    
    eu_avg = latest.loc[latest.geo=='EU27_2020','value'].iloc[0]
    
    fig, ax = plt.subplots(figsize=(10, 11))
    colors = ['#2b8cbe' if v>=eu_avg else '#e34a33' for v in eur['value']]
    bars = ax.barh(eur['geo'], eur['value'], color=colors)
    ax.axvline(eu_avg, color='black', linestyle='--', linewidth=1.2, label=f'EU27 average ({eu_avg})')
    ax.set_xlabel('Corruption Perceptions Index score (2025)  —  higher = less corrupt')
    ax.set_title('Corruption Perceptions Index 2025\nEuropean countries covered by Eurostat')
    ax.set_xlim(0, 100)
    for b, v in zip(bars, eur['value']):
        ax.text(v+0.7, b.get_y()+b.get_height()/2, str(v), va='center', fontsize=8)
    ax.legend(loc='lower right')
    plt.tight_layout()
    plt.savefig('cpi_2025_europe.svg')
    plt.close()
    print("saved cpi_2025_europe.svg")
    
    # Time series: EU average and a few illustrative countries + note Ukraine absence
    fig, ax = plt.subplots(figsize=(11,6))
    for geo in ['EU27_2020','DK','DE','FR','PL','HU','BG','RU']:
        sub = df[df.geo==geo].sort_values('time')
        ax.plot(sub['time'], sub['value'], marker='o', ms=3, label=geo)
    ax.set_xlabel('Year'); ax.set_ylabel('CPI score (higher = less corrupt)')
    ax.set_title('Corruption Perceptions Index over time (2012–2025)')
    ax.legend(ncol=2, fontsize=8); ax.grid(alpha=0.3)
    plt.tight_layout(); plt.savefig('cpi_trend.svg'); plt.close()
    print("saved cpi_trend.svg")
    
    # Confirm no Ukraine in any year
    print("Ukraine rows in full dataset:", (df.geo=='UA').sum())