Analyze V-Dem (Varieties of Democracy) cross-national panel data to measure democratic indices, detect backsliding, and run panel regressions linking institutional quality to economic outcomes.
Analyze V-Dem (Varieties of Democracy) cross-national panel data to measure democratic indices, detect backsliding, and run panel regressions linking institutional quality to economic outcomes.
The Varieties of Democracy (V-Dem) project provides the most comprehensive cross-national
dataset on democratic institutions, covering over 200 countries from 1789 to the present. This
skill covers the full analytical pipeline: loading the large CSV dataset efficiently, computing
democratic indices, detecting backsliding episodes, visualizing trends, and running panel
regressions.
Key Democratic Indices
Column
Description
v2x_polyarchy
Electoral democracy index
v2x_libdem
Liberal democracy index
v2x_partipdem
Participatory democracy index
v2x_delibdem
Deliberative democracy index
v2x_egaldem
Egalitarian democracy index
All indices are on a 0–1 scale where higher values indicate more democracy.
Download the V-Dem dataset from https://www.v-dem.net/data/the-v-dem-dataset/ (V-Dem Country-Year
Core dataset, CSV format, ~200 MB). Store the path as an environment variable or pass it directly.
"""
Load the V-Dem CSV efficiently using chunked reading and column pre-selection.
Parameters
----------
filepath : str
Path to the V-Dem CSV file (e.g. ``V-Dem-CY-Full+Others-v13.csv``).
years : tuple (start, end), optional
Inclusive year range filter, e.g. ``(2000, 2023)``.
countries : list of str, optional
Filter by ``country_text_id`` (ISO 3-letter codes) or ``country_name``.
extra_cols : list of str, optional
Additional V-Dem columns to retain beyond the defaults.
Returns
-------
pd.DataFrame
Filtered dataframe indexed by (country_name, year).
"""
"""
Identify country-years where a democratic index declined by ``threshold``
or more over the preceding ``window`` years.
Parameters
----------
df : pd.DataFrame
V-Dem dataframe (output of ``load_vdem``).
index_col : str
Which democracy index to track.
threshold : float
Minimum absolute decline to flag as backsliding (default 0.10).
window : int
Number of years over which to compute the decline (default 5).
Returns
-------
pd.DataFrame
Rows flagged as backsliding episodes, sorted by severity.
"""
"""
Plot time-series democracy trends for a list of countries.
Parameters
----------
df : pd.DataFrame
V-Dem dataframe.
countries : list of str
Country names to plot.
index_col : str
Democracy index column to plot.
title : str, optional
Plot title.
figsize : tuple
Matplotlib figure size.
save_path : str, optional
If provided, save the figure to this path.
Returns
-------
matplotlib.figure.Figure
"""
"""
Run a panel regression (Fixed Effects, Random Effects, or Pooled OLS).
Parameters
----------
df : pd.DataFrame
V-Dem dataframe with country_name and year columns.
outcome : str
Dependent variable column name.
treatment : str
Main independent variable.
controls : list of str, optional
Additional control variable columns.
model_type : str
One of ``"fe"`` (fixed effects), ``"re"`` (random effects), ``"pooled"``.
log_transform_treatment : bool
If True, log-transform the treatment variable (useful for GDP per capita).
Returns
-------
dict with keys ``"summary"``, ``"model"``, ``"rsquared"``.
"""
"""
Compute and visualize pairwise correlation between democracy indices.
Parameters
----------
df : pd.DataFrame
V-Dem dataframe.
indices : list of str, optional
Columns to correlate. Defaults to all five main V-Dem indices.
year : int, optional
Subset to a specific year. If None, uses all years.
method : str
Correlation method: ``"pearson"``, ``"spearman"``, or ``"kendall"``.
figsize : tuple
Figure size for the heatmap.
save_path : str, optional
Save figure if provided.
Returns
-------
pd.DataFrame
Correlation matrix.
"""
or
if
is
None
else
"year"
bool
1
True
".2f"
"RdYlGn"
1
1
True
0.5
str
if
else
"All Years"
f"V-Dem Index Correlations ({method.capitalize()}, {year_label})"
13
if
150
return
Example A: Democratic Backsliding in Eastern Europe (2000–2023)
This example tracks liberal democracy scores for Hungary, Poland, and Turkey, detects backsliding
episodes, and produces a publication-ready trend chart.
Example B: Does GDP Predict Democracy? Cross-National Panel Regression
This example runs a country fixed-effects panel regression asking whether higher GDP per capita
(Maddison project) is associated with higher liberal democracy scores.
# ── Example B ──────────────────────────────────────────────────────────────# --- Load broad panel (1950–2020) -------------------------------------------
df_panel = load_vdem(
filepath=VDEM_PATH,
years=(1950, 2020),
extra_cols=["e_pop"],
)
# Drop country-years with missing GDP or democracy score
df_panel = df_panel.dropna(subset=["v2x_libdem", "e_gdppc"])
print(f"Panel size: {len(df_panel)} obs, {df_panel['country_name'].nunique()} countries")
# --- Descriptive statistics --------------------------------------------------
desc = df_panel[["v2x_libdem", "v2x_polyarchy", "e_gdppc"]].describe().round(3)
print("\nDescriptive Statistics:")
print(desc.to_string())
# --- Fixed effects regression: GDP → Liberal Democracy ----------------------
result_fe = run_panel_regression(
df_panel,
outcome="v2x_libdem",
treatment="e_gdppc",
controls=[],
model_type="fe",
log_transform_treatment=True,
)
print("\n=== Fixed Effects: log(GDP per capita) → Liberal Democracy ===")
print(result_fe["summary"])
print(f"Within R²: {result_fe['rsquared']:.4f}")
# --- Pooled OLS for comparison -----------------------------------------------
result_ols = run_panel_regression(
df_panel,
outcome="v2x_libdem",
treatment="e_gdppc",
model_type="pooled",
log_transform_treatment=True,
)
print("\n=== Pooled OLS: log(GDP per capita) → Liberal Democracy ===")
print(result_ols["summary"])
# --- Scatter: GDP vs Democracy (cross-sectional, latest year) ----------------
df_2020 = df_panel[df_panel["year"] == 2020].dropna(subset=["e_gdppc", "v2x_libdem"])
fig, ax = plt.subplots(figsize=(10, 7))
scatter = ax.scatter(
np.log(df_2020["e_gdppc"]),
df_2020["v2x_libdem"],
c=df_2020["e_regionpol_6C"],
cmap="tab10",
alpha=0.7,
s=60,
edgecolors="white",
linewidth=0.4,
)
# Add regression line
x = np.log(df_2020["e_gdppc"])
y = df_2020["v2x_libdem"]
slope, intercept, r, p, se = stats.linregress(x.dropna(), y[x.notna()])
x_line = np.linspace(x.min(), x.max(), 100)
ax.plot(x_line, intercept + slope * x_line, "r--", linewidth=1.5, label=f"OLS (r={r:.2f})")
ax.set_xlabel("log(GDP per capita)", fontsize=12)
ax.set_ylabel("Liberal Democracy Index", fontsize=12)
ax.set_title("GDP per Capita vs. Liberal Democracy (2020)", fontsize=14)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig("gdp_democracy_scatter_2020.png", dpi=150)
plt.show()
# --- Compare all five V-Dem indices: correlation heatmap --------------------
corr = compare_indices_correlation(
df_panel,
method="spearman",
save_path="vdem_index_correlations.png",
)
print("\nSpearman Correlations among V-Dem Indices:")
print(corr.round(3).to_string())
The full dataset is ~200 MB; the load_vdem function handles memory efficiently via chunking.
Column availability varies by version; always check header.columns for your version.
Interpretation
Fixed effects absorb all time-invariant country characteristics (geography, culture, history).
The FE coefficient on log-GDP therefore captures within-country change, not cross-national
variation.
Backsliding threshold of 0.10 over 5 years is a common rule-of-thumb in the literature
(Lührmann & Lindberg 2019). Adjust based on your theoretical expectations.
V-Dem scores carry measurement uncertainty; the full dataset includes confidence intervals
for each estimate (columns ending in _codelow, _codehigh).