How Long Do Governments Survive?
Parliamentary cabinets fall when coalitions fracture, confidence votes fail, or snap elections are called. This project treats the life of a government as a survival process and asks what makes some durable and others collapse within months — through Kaplan-Meier curves, a Cox proportional-hazards model, a Weibull accelerated-failure model, and a competing-risks decomposition, each built from the likelihood up in pure NumPy.
// the question
Survival (or "event-history" / "duration") analysis answers a different question than ordinary regression: not whether a government changes, but how long until it does. The outcome pairs a duration with an event status — days a cabinet governed, and whether it terminated or was still in office at the data cutoff.
The wrinkle that breaks standard tools is right-censoring: the most recent cabinet in each country has not yet ended. We don't know its true lifespan, only that it exceeds what we have observed. Every method here is built to use censored cases correctly.
H1 each additional governing party raises the hazard of collapse · H2 wider left–right spread shortens survival · H3 minority governments fail faster · H4 discretionary collapse and mandatory elections are distinct competing risks.
// data & cleaning
Primary source: ParlGov — Parliaments and Governments Database (Döring & Manow), the standard cabinet-level dataset for EU/OECD democracies, 1900–2023. The loader aggregates the party-in-cabinet rows to one row per cabinet, derives the duration as the gap to the next cabinet's formation, marks the final cabinet in each country as censored, and engineers the coalition covariates.
def kaplan_meier(time, event):
"""Product-limit estimator with right-censoring."""
uniq = np.unique(time[event == 1])
n = len(time); surv = 1.0
T, S = [0.0], [1.0]
for t in uniq:
at_risk = np.sum(time >= t) # risk set still governing at t
d = np.sum((time == t) & event) # cabinets that fell exactly at t
surv *= (1 - d / at_risk) # product-limit update
T.append(t); S.append(surv)
return np.array(T), np.array(S)
| coalition type | n | events | share | median life (yrs) |
|---|---|---|---|---|
| Single-party majority | 209 | 139 | 13.9% | 2.15 |
| Minimal-winning coalition | 698 | 533 | 46.5% | 1.17 |
| Surplus coalition | 51 | 47 | 3.4% | 0.76 |
| Minority | 542 | 444 | 36.1% | 0.73 |
// non-parametric · Kaplan-Meier
The Kaplan-Meier estimator is the product-limit count of who is still at risk: at each termination time, multiply survival by the fraction of the risk set that made it through. No distribution assumed; censored cabinets stay in the risk set until they drop out. The curves separate exactly as theory predicts — single-party majorities are the most durable, minority and surplus coalitions the least.
// interactive · build a cabinet, predict its life
The widget below runs the fitted models in your browser. Drag the coalition sliders to watch the Weibull-predicted survival curve and median lifespan update live, or switch to the Kaplan-Meier tab to toggle the empirical curves by coalition type.
cabinet_survival.py and computed
client-side — no server, no libraries.// semiparametric · Cox proportional hazards
The Cox model never commits to a baseline-hazard shape, estimating covariate effects from the partial likelihood instead. I implemented it with Newton-Raphson and Efron's correction for tied termination times, with the analytic information matrix for standard errors. Hazard ratios above one mean faster collapse.
# Cox partial likelihood, Newton-Raphson with Efron ties
for t in death_times:
at_risk = time >= t
d = ((time == t) & event).sum()
S0 = risk[at_risk].sum()
S1 = (risk[at_risk, None] * X[at_risk]).sum(0)
...
for l in range(d): # Efron: discount tied deaths step by step
f = l / d
phi0 = S0 - f * Dsum0
z = (S1 - f * Dsum1) / phi0
grad -= z
hess -= (phi2 / phi0 - np.outer(z, z))
beta_new = beta - np.linalg.solve(hess, grad)
| term | coef | HR = eβ | std. err | z | p | 95% CI (HR) |
|---|---|---|---|---|---|---|
| # governing parties | +0.249 | 1.283 | 0.032 | 7.75 | 0.000* | 1.21–1.37 |
| ideological range | +0.072 | 1.075 | 0.020 | 3.69 | 0.000* | 1.03–1.12 |
| minority govt | +0.550 | 1.733 | 0.063 | 8.72 | 0.000* | 1.53–1.96 |
| surplus coalition | +0.476 | 1.610 | 0.153 | 3.12 | 0.002* | 1.19–2.17 |
| ENEP | +0.048 | 1.049 | 0.043 | 1.12 | 0.263 | 0.96–1.14 |
// parametric · Weibull AFT
The Weibull accelerated-failure-time model writes log(T) = Xβ + σW with an extreme-value error. I coded its censored log-likelihood — events contribute a density, censored cabinets a survival probability — and maximized it with Newton-Raphson plus a backtracking line search. It recovers a shape parameter of 1/σ ≈ 1.23 (a gently rising hazard: the longer a cabinet lasts, the more likely it is to fall), and every covariate sign agrees with the Cox model.
# Weibull AFT: log T = Xβ + σ·W, W ~ extreme-value (censored MLE)
def loglik(theta):
beta, sigma = theta[:-1], np.exp(theta[-1])
z = np.clip((y - Xc @ beta) / sigma, -30, 30)
ez = np.exp(z)
# events contribute a density, censored cases a survival probability
return np.sum(event * (-theta[-1] + z - ez) - (1 - event) * ez)
# maximized by Newton-Raphson + backtracking line search -> shape 1/σ = 1.23
// competing risks · how cabinets end
A government can end two very different ways: a discretionary collapse (coalition breakdown, lost confidence vote) or a technical termination at a mandatory election. Treating these as competing risks, the Aalen-Johansen estimator gives each cause its own cumulative-incidence curve — discretionary collapse dominates, and it accumulates fastest in exactly the fragile coalitions the Cox model flags.
// cross-country view
| model | log-lik | k | AIC | C-index |
|---|---|---|---|---|
| Cox PH (semiparametric) | -7200.6 | 5 | 14411.2 | 0.640 |
| Weibull AFT (parametric) | -1857.5 | 7 | 3728.9 | — |
// what this demonstrates
- Censored-data likelihood — correct handling of right-censoring in the KM estimator, the Weibull MLE, and the Aalen-Johansen cumulative incidence.
- Partial likelihood & tie handling — a from-scratch Cox model with Efron's approximation and an analytic information matrix.
- Four modeling philosophies — non-parametric, semiparametric, parametric, and competing-risks estimators side by side, assumptions made explicit.
- Reproducible & interactive — one NumPy pipeline produces every table and figure and exports the coefficients that power a dependency-free in-page dashboard.