corruption levels in European countries and Ukraine's ranking compared to other European countries
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
- I can describe corruption levels for European countries (above), where Denmark and Finland are the cleanest and Russia/Türkiye/Serbia the most corrupt in 2025.
- I cannot place Ukraine in that ranking: Ukraine is absent from the Eurostat Corruption Perceptions Index dataset and no other Eurostat/OECD dataset provides a comparable corruption level for it.
- Eurostat dataset: Corruption Perceptions Index
- Code Run 2Dataset: 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')]) - Code Run 1Dataset: sdg_16_50
Dataset filters for
sdg_16_50Filter Value(s) geo UA 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) - OECD dataset: Public Integrity Indicators
- OECD dataset: Perception of public sector integrity - Government at a glance indicators, 2025 edition
- OECD dataset: Trust, security and dignity - government at a glance indicators, 2025 edition
- Code Run 4Dataset: 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 22Code
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)) - Code Run 3Dataset: DSD_GOV_INT@DF_GOV_INT_2025
Dataset filters for
DSD_GOV_INT@DF_GOV_INT_2025Filter Value(s) CATEGORY INT 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: FalseCode
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) - Code Run 5Dataset: sdg_16_50
Images
cpi_2025_europe.svgcpi_trend.svgProgram output
saved cpi_2025_europe.svg saved cpi_trend.svg Ukraine rows in full dataset: 0Code
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())