"""Modern country-year panel: V-Dem v16 (institutions, outcomes) + Maddison 2023 (population, GDP per capita). Variables fixed in research/preregistration.md ยง3 and the codebook readings recorded in research/measurement.md: K items: v2clrspct, v2stcritrecadm, v2cltrnslw, v2stfisccap, v2svstterr, v2elembcap D: Maddison `pop` (thousands) ; e_area (km2, 1886+) Outcomes (events in year t): Y4 civil war onset = e_civil_war 0->1 ; Y5 coup = e_coups >= 1 ; Y6 irregular regime end = last year of a regime (v2regidnr changes) whose v2regendtype is in {0,1,2,3,5,6,7,8} Covariates: Maddison gdppc, v2x_polyarchy, e_regionpol_6C Crosswalk: ISO3 (`country_text_id` == `countrycode`); unmatched countries are listed in data/metadata/crosswalk_vdem_maddison.csv and kept without Maddison values (no substitution from e_pop). """ from __future__ import annotations from pathlib import Path import numpy as np import pandas as pd import pyreadr ROOT = Path(__file__).resolve().parents[3] VDEM = ROOT / "data" / "raw" / "vdem_v16_rdata" / "vdem_v16.RData" MADD = ROOT / "data" / "raw" / "maddison_2023_xlsx" / "mpd2023_web.xlsx" META = ROOT / "data" / "metadata" K_ITEMS = ["v2clrspct", "v2stcritrecadm", "v2cltrnslw", "v2stfisccap", "v2svstterr", "v2elembcap"] IRREGULAR_END = {0, 1, 2, 3, 5, 6, 7, 8} KEEP = ["country_name", "country_text_id", "country_id", "year", "e_regionpol_6C", "e_regionpol", "v2x_polyarchy", "e_civil_war", "e_miinterc", "e_coups", "e_pt_coup", "v2regendtype", "v2regint", "v2regdur", "e_pop", "e_area", "e_gdppc", "v2x_regime", "v2x_rule", "v2regidnr"] + K_ITEMS def build() -> dict[str, pd.DataFrame]: vd = pyreadr.read_r(VDEM)["vdem"] cols = [c for c in KEEP if c in vd.columns] v = vd[cols].copy() v["year"] = v.year.astype(int) md = pd.read_excel(MADD, sheet_name="Full data") md = md.rename(columns={"countrycode": "country_text_id", "pop": "maddison_pop", "gdppc": "maddison_gdppc"}) md["year"] = md.year.astype(int) m = v.merge(md[["country_text_id", "year", "maddison_pop", "maddison_gdppc"]], on=["country_text_id", "year"], how="left") # crosswalk audit vset, mset = set(v.country_text_id.unique()), set(md.country_text_id.unique()) cw = pd.DataFrame({"code": sorted(vset | mset)}) cw["in_vdem"] = cw.code.isin(vset); cw["in_maddison"] = cw.code.isin(mset) names = v.drop_duplicates("country_text_id").set_index("country_text_id").country_name cw["vdem_name"] = cw.code.map(names) META.mkdir(parents=True, exist_ok=True) cw.to_csv(META / "crosswalk_vdem_maddison.csv", index=False) m = m.sort_values(["country_text_id", "year"]).reset_index(drop=True) g = m.groupby("country_text_id") # outcomes in year t cw_prev = g.e_civil_war.shift(1) m["y4_civilwar_onset"] = np.where(m.e_civil_war.isna() | cw_prev.isna(), np.nan, ((m.e_civil_war == 1) & (cw_prev == 0)).astype(float)) m["y5_coup"] = np.where(m.e_coups.isna(), np.nan, (m.e_coups >= 1).astype(float)) # v2regendtype is carried on every year of a regime (how that regime eventually ended; 13 = still exists). # The event is placed in the regime's LAST year: v2regidnr changes between t and t+1 (within country). next_id = g.v2regidnr.shift(-1) last_year = m.v2regidnr.notna() & next_id.notna() & (m.v2regidnr != next_id) m["regime_end_year"] = last_year.astype(float) m["y6_irregular_regime_end"] = np.where(m.v2regendtype.isna() | next_id.isna(), np.nan, (last_year & m.v2regendtype.isin(IRREGULAR_END)).astype(float)) m["log_pop"] = np.log(m.maddison_pop.where(m.maddison_pop > 0)) m["log_area"] = np.log(m.e_area.where(m.e_area > 0)) m["log_gdppc"] = np.log(m.maddison_gdppc.where(m.maddison_gdppc > 0)) return {"panel": m, "crosswalk": cw}