Causal Inference for Computational Social Science

Do Coup Attempts Beget Coups? A V-Dem Country–Year Panel for Temporal Causal Inference

Author
Affiliation

Troy Cheng, Hongzhe Wang

Georgetown University

Published

July 23, 2025

Modified

August 12, 2025

1 Introduction

Understanding why regimes endure or break down is a core question in comparative politics. This project takes a design-based causal approach to ask whether conditions observed in year t make a coup termination of the regime more likely in year t+1. We leverage the Varieties of Democracy (V-Dem) country–year panel, which provides globally comparable annual indicators since the eighteenth century, and we align predictors to year t and outcomes to year t+1 to avoid temporal leakage. Our focus is intentionally narrow: do coup attempts in year t raise the short-run risk that a regime ends via a coup next year, once we account for country heterogeneity and time-varying turmoil?

Our project builds on three strands. First, work on coups documents their incidence, correlates, and consequences, showing that attempts arise from elite coordination problems and are shaped by military cohesion, repression, and institutional constraints (Powell & Thyne, 2011; Geddes, Wright, & Frantz, 2014). Second, theories of authoritarian politics emphasize power-sharing and ruler–elite bargaining, which structure both the incentives to attempt coups and the conditions under which they succeed (Svolik, 2012). Third, research on political instability and civil conflict demonstrates that mass contention and organized violence alter regime vulnerability and the payoff to elite defection, while global trends in the post–Cold War era contributed to a secular decline in coups (Goldstone et al., 2010; Hegre et al., 2013).

Methodologically, we follow the identification playbook that warns against post-treatment conditioning and collider bias and recommends design-based adjustments and transparent estimands (Pearl, 2009). We also heed guidance for rare-events logistic regression when interpreting odds ratios in sparse outcomes (King & Zeng, 2001). For implementation, we rely on the V-Dem country–year panel (Coppedge et al., various years) and estimate mixed-effects models using the glmmTMB framework (Brooks et al., 2017). To guard against separation in high-dimensional fixed-effects logit, we cross-check with bias-reduced estimation in the spirit of Firth (1993).

1.1 Data and variables

We use the Varieties of Democracy (V-Dem) country–year panel. The unit of analysis is the country–year. Three variables anchor measurement. First, v2regendtype encodes how a regime ends in a given year; we treat values 0, 1, and 2 as coup terminations (military coup, non-military coup, self-coup) and treat all other non-missing values—including “regime still exists” (13)—as non-coup. This choice lets us define an outcome for all country–years in year \(t+1\) and, crucially, avoids conditioning on whether a regime ends. Second, e_pt_coup_attempts records the count of coup attempts in year \(t\); we define the treatment as an indicator that at least one attempt occurred (\(A_t=1\) if >0, otherwise \(0\)). Coverage for attempts is strongest after 1950, so the effective sample reflects that period. Third, we include e_civil_war as a salient time-varying confounder that plausibly increases both the probability of attempts and the probability that a termination, if it occurs, takes the form of a coup. For descriptive context we also standardize and compare histname across adjacent years within a country to summarize institutional churn, but we do not condition on histname changes in the causal models.

The design aligns predictors to year \(t\) and defines the outcome for year \(t+1\) for every country–year: \(Y^*_{t+1}=1\) if v2regendtype_{t+1} ∈ {0,1,2} and \(0\) otherwise. This specification addresses a common source of bias: if one first restricts attention to country–years where the regime ends in \(t+1\) and then models “coup vs. non-coup” among those cases, the conditioning event acts as a collider for many time-varying forces (e.g., civil war, protest, elite fractures), inducing spurious associations with the coup outcome. By modeling the marginal probability of coup termination next year for the full panel, we avoid this collider while keeping the estimand directly interpretable.

Identification relies on absorbing stable cross-country differences with country effects (either fixed effects or a random intercept), absorbing global and period-specific shocks with year fixed effects, and adjusting for a time-varying confounder (civil war). Under temporal ordering (predictors at \(t\), outcome at \(t+1\)), conditional ignorability given \({U_i, W_t, C_t}\), overlap (within-country variation in attempts), and SUTVA, the contrasts we report can be interpreted as conditional average treatment effects of an attempt on the next-year probability of coup termination.

1.2 Identification and Estimand

Our identification strategy relies on temporal alignment and avoiding post-treatment conditioning. A common but problematic approach is to condition on the event that a regime ends in year \(t+1\) and then model the type of termination among those endings; doing so opens a collider path because time-varying forces (e.g., civil war, mass protest, elite splits) influence both whether a regime ends and how it ends. To prevent this bias, we do not condition on termination. Instead, we define the outcome for all country–years as \(Y^*_{t+1}=\mathbf{1}\{\text{coup termination in }t+1\}\) and model the marginal probability \(P\!\big(Y^*_{t+1}=1 \mid A_t,\ \text{adjustment}\big)\), where \(A_t=\mathbf{1}\{\text{at least one coup attempt in }t\}\).

We adjust for three ingredients that capture back-door paths emphasized in the literature on regime durability and coups. First, country heterogeneity \(U_i\) (political culture, state capacity, elite organizations) is absorbed via country fixed effects or a country random intercept. Second, global and period-specific shocks \(W_t\) (e.g., the long-run decline in coups) are absorbed via year fixed effects. Third, a time-varying confounder \(C_t\) (civil war) is included because it plausibly affects both the incidence of attempts and the likelihood of a coup termination. Under (i) temporal ordering (predictors at \(t\), outcomes at \(t+1\)); (ii) conditional ignorability given \(\{U_i, W_t, C_t\}\); (iii) overlap (within-country variation in attempts); and (iv) SUTVA, contrasts from our models recover a conditional average treatment effect.

Our estimand is the average treatment effect of a coup attempt in year \(t\) on the probability of a coup termination in year \(t+1\):

\[ \mathrm{ATE} \;=\; \mathbb{E}\!\left[\,Y^*_{t+1}(1) - Y^*_{t+1}(0)\,\right]. \]

We estimate this quantity using logit specifications with (a) country and year fixed effects and clustered standard errors, and (b) a mixed-effects logit with a country random intercept and year fixed effects. For interpretability, we report both odds ratios (OR) and average risk differences via g-computation, i.e., predicting \(P(Y^*_{t+1}=1)\) for the same observations under \(A_t=1\) and \(A_t=0\) and averaging the difference.

The estimand is short-run and conditional: the effect of an attempt in \(t\) on the probability of a coup termination in \(t+1\), averaging within countries after absorbing country and year structure and controlling for civil war. It does not capture slower dynamics (e.g., how attempts reshape elite coalitions over several years) or feedback loops between conflict and coup risk. Coding accuracy and coverage in the underlying data further bound external validity. For these reasons we emphasize risk differences—in percentage points—as the primary quantity for substantive interpretation, with odds ratios as complementary evidence about direction and proportional change.

1.3 Estimation methodology

We estimate three specifications. (1) A logistic regression with country fixed effects and year fixed effects; standard errors are clustered by country. (2) A mixed-effects logistic model with a country random intercept and year fixed effects, which partially pools information across countries. (3) The mixed-effects model augmented with the civil-war indicator as a time-varying covariate. Alongside odds ratios for the attempt coefficient with 95% confidence intervals, we report average risk differences via g-computation: we predict \(P(Y^*_{t+1}=1)\) for each observation twice—once setting \(A_t=1\) and once setting \(A_t=0\)—and average the difference. Because coup terminations are rare and high-dimensional fixed effects can induce quasi-separation, we view penalized or shrinkage alternatives (e.g., Firth-penalized logit; mixed models estimated via glmmTMB) as robustness checks rather than primary estimates.

2 Load Packages

library(reticulate)
library(tidyverse)
library(stringr)
library(tidyr)
library(stats)
library(sandwich)
library(lmtest)
library(lme4)

3 Import Vdem Data using its R Package

# Install V-Dem data package:
# install.packages("devtools")
# devtools::install_github("vdeminstitute/vdemdata")

library(vdemdata)

4 Measurement strategy and temporal alignment

V-Dem provides several ways to represent regime continuity and rupture. In this study we adopt a state-centered measure. Specifically, within each country we standardize histname and mark a regime change in year t when the standardized value differs from year t−1. This definition treats leadership rotations and intra-regime phases as continuity, while capturing foundational transformations (refounding, mergers, dissolutions). To prepare a prediction task that avoids temporal leakage, we also shift the indicator forward by one year so that subsequent models use information from year t to predict change at t+1.

4.1 Construct within-country change and a t→t+1 target

The block below (i) standardizes histname, (ii) flags a within-country change in year t, and (iii) builds an aligned indicator for change next year. This alignment is crucial because all covariates we will use reside at year t.

# Standardize `histname` to avoid spurious changes due to case/whitespace
vdem_change <- vdem |>
  arrange(country_id, year) |>
  mutate(histname_std = str_squish(str_to_lower(histname)))

# Define within-country regime change this year by adjacent-year difference in `histname`
vdem_change <- vdem_change |>
  group_by(country_id) |>
  mutate(
    regime_change_histname = as.integer(!is.na(histname_std) & histname_std != lag(histname_std))
  ) |>
  ungroup() |>
  mutate(regime_change_histname = replace_na(regime_change_histname, 0L))

# Prediction-oriented: construct Y_{t+1} (use year-t features to predict change at t+1)
vdem_change <- vdem_change |>
  group_by(country_id) |>
  mutate(y_t1_histname = replace_na(lead(regime_change_histname, 1), 0L)) |>
  ungroup()

Next, we report the base rate of within-country changes to calibrate rarity. We also spot-check a handful of flagged rows to verify that detected changes correspond to visible histname shifts. These are sanity checks only; they are not part of the causal design.

# Overall rate of regime change (baseline rate)
mean(vdem_change$regime_change_histname, na.rm = TRUE)
[1] 0.02583026
# Spot-check a few rows with a change (sanity check)
vdem_change |>
  filter(regime_change_histname == 1) |>
  select(country_name, year, histname) |>
  head(10)
country_name year histname
Mexico 1821 Mexican Empire
Mexico 1823 Provisional Government
Mexico 1824 United states of Mexico
Mexico 1835 Mexican Republic [Including Texas]
Mexico 1837 Mexican Republic
Mexico 1846 United Mexican States
Mexico 1863 Second Mexican Empire
Mexico 1867 United Mexican States
Suriname 1954 Suriname [Dutch colony with self-rule]
Suriname 1975 Republic of Suriname [independent state]

4.2 Descriptive priors from v2regendtype

While histname identifies whether institutional rupture occurs, v2regendtype indicates how a regime ends in a given year. To keep the temporal alignment, we define regend_t1 as the next year’s end-type code. This lets us later build an outcome for all country–years: whether the regime ends via a coup at t+1. Before moving to causal models, it is informative to examine the marginal distribution of end types and the distribution among observations that precede a histname change. These summaries serve as descriptive priors about mechanisms and base rates; they are not used as conditioning variables in the causal stage.

4.3 Summarize end-type distributions

The block below (i) maps numeric end-type codes to labels, (ii) reports the overall distribution, (iii) aligns to t+1 by creating regend_t1, and (iv) summarizes end-types among rows that precede a histname change (excluding 13 = still exists for that table). Two counts at the end verify how many t+1 change rows exist and how many have a non-13 label.

# Build a codebook (map numeric codes to labels)
regend_map <- tibble::tibble(
  v2regendtype = 0:13,
  regend_label = c(
    "Military coup d’état", # 0
    "Coup by non-military groups", # 1
    "Self-coup (autogolpe)", # 2
    "Assassination of leader (non-coup)", # 3
    "Natural death of leader", # 4
    "Loss in civil war", # 5
    "Loss in inter-state war", # 6
    "Foreign intervention (non inter-state loss)", # 7
    "Popular uprising", # 8
    "Liberalization/democratization guided by leaders", # 9
    "Other directed transformation under leaders", # 10
    "Liberalization/democratization w/o leader guidance", # 11
    "Other process (not 1–11)", # 12
    "Regime still exists" # 13
  )
)

# Overall distribution (not conditioning on change)
overall_dist <- vdem_change |>
  filter(!is.na(v2regendtype)) |>
  count(v2regendtype, name = "n") |>
  left_join(regend_map, by = "v2regendtype") |>
  mutate(p = n / sum(n)) |>
  arrange(v2regendtype)

print(overall_dist)
# A tibble: 14 × 4
   v2regendtype     n regend_label                                             p
          <dbl> <int> <chr>                                                <dbl>
 1            0  2135 Military coup d’état                               0.0771 
 2            1  1400 Coup by non-military groups                        0.0506 
 3            2  1026 Self-coup (autogolpe)                              0.0370 
 4            3   303 Assassination of leader (non-coup)                 0.0109 
 5            4   371 Natural death of leader                            0.0134 
 6            5   748 Loss in civil war                                  0.0270 
 7            6  3183 Loss in inter-state war                            0.115  
 8            7  1528 Foreign intervention (non inter-state loss)        0.0552 
 9            8  1593 Popular uprising                                   0.0575 
10            9  3091 Liberalization/democratization guided by leaders   0.112  
11           10  5195 Other directed transformation under leaders        0.188  
12           11   627 Liberalization/democratization w/o leader guidance 0.0226 
13           12   239 Other process (not 1–11)                           0.00863
14           13  6256 Regime still exists                                0.226  
# Align with the t → t+1 design: take next year's end type (regend_t1)
vdem_change <- vdem_change |>
  group_by(country_id) |>
  mutate(regend_t1 = dplyr::lead(v2regendtype, 1)) |>
  ungroup()

# Among rows that precede a histname change at t+1; exclude code=13 ("still exists")
change_event_dist <- vdem_change |>
  filter(y_t1_histname == 1, !is.na(regend_t1), regend_t1 != 13) |>
  count(regend_t1, name = "n") |>
  left_join(regend_map, by = c("regend_t1" = "v2regendtype")) |>
  mutate(p = n / sum(n)) |>
  arrange(desc(p))

print(change_event_dist)
# A tibble: 13 × 4
   regend_t1     n regend_label                                             p
       <dbl> <int> <chr>                                                <dbl>
 1        10   176 Other directed transformation under leaders        0.268  
 2         6   116 Loss in inter-state war                            0.177  
 3         9    72 Liberalization/democratization guided by leaders   0.110  
 4         7    65 Foreign intervention (non inter-state loss)        0.0991 
 5         0    54 Military coup d’état                               0.0823 
 6         8    44 Popular uprising                                   0.0671 
 7         1    38 Coup by non-military groups                        0.0579 
 8         2    36 Self-coup (autogolpe)                              0.0549 
 9         5    26 Loss in civil war                                  0.0396 
10        11    12 Liberalization/democratization w/o leader guidance 0.0183 
11         4     7 Natural death of leader                            0.0107 
12         3     5 Assassination of leader (non-coup)                 0.00762
13        12     5 Other process (not 1–11)                           0.00762
# Simple checks for alignment and coverage
n_change_rows <- sum(vdem_change$y_t1_histname == 1, na.rm = TRUE)
n_labeled_events <- sum(vdem_change$y_t1_histname == 1 & !is.na(vdem_change$regend_t1) & vdem_change$regend_t1 != 13)
cat("# of t+1 change rows:", n_change_rows, "\n")
# of t+1 change rows: 721 
cat("# of t+1 rows with a non-13 end-type label:", n_labeled_events, "\n")
# of t+1 rows with a non-13 end-type label: 656 

Two patterns matter for what follows. First, “regime still exists” (code 13) is common, whereas coup endings {0,1,2} are rare. This anchors prior beliefs and alerts us to class imbalance. Second, among observations that precede a histname change, the mix of termination mechanisms provides context for the later coup-vs-non-coup outcome. Importantly, these are descriptive. In the causal analysis we will not condition on “ending” at t+1, precisely to avoid collider bias. Instead, we will define an outcome for all rows—coup termination next year—and ask whether attempts at year t increase that probability after appropriate adjustment.

5 From measurement to identification

Building on the measurement choices above, we now move from descriptive context to a design-based causal analysis. The key move is to define an outcome for all country–years—“coup termination next year”—and to align the treatment at year \(t\). This avoids conditioning on whether a regime ends in \(t+1\), which would otherwise introduce selection bias.

5.1 Outcome and treatment

We map v2regendtype_{t+1} ∈ {0,1,2} to \(Y^{\star}_{t+1}=1\) (coup termination next year) and all other non-missing codes—including “still exists” (13)—to \(0\). The treatment is \(A_t=\mathbb{1}\{\text{attempts}_t>0\}\), where \(\text{attempts}_t\) refers to the V-Dem count e_pt_coup_attempts in year \(t\). Because attempt coverage is strongest post-1950, we retain rows where both \(Y^{\star}_{t+1}\) and \(A_t\) are observed and record a weakly informative prior centered at the empirical base rate.

# Y*_{t+1}: does next year end via a coup?
# 1 if regend_t1 ∈ {0,1,2}; 0 for all other non-missing (including "still exists"=13 and non-coup endings)
y_star <- with(vdem_change, ifelse(
  regend_t1 %in% c(0,1,2), 1L,
  ifelse(!is.na(regend_t1), 0L, NA_integer_)
))

# Align with A_t: `e_pt_coup_attempts` is mostly available post-1950; drop rows with NA in attempts
attempts <- vdem_change$e_pt_coup_attempts
keep <- !is.na(y_star) & !is.na(attempts)

y_star <- as.integer(y_star[keep])
x_attempt <- as.integer(attempts[keep] > 0)  # 1 = at least one attempt in year t; 0 = no attempt
N_star <- length(y_star)

# Weakly informative prior centered at the sample mean (can be swapped for a training split later)
p0_star <- mean(y_star)
m_star <- 20
alpha_star <- m_star * p0_star
beta_star <- m_star * (1 - p0_star)

# Quick check: base rate and treated/control counts
mean(y_star); table(x_attempt)
[1] 0.1796597
x_attempt
    0     1 
10280   418 

The printed base rate mean(y_star) and the table of attempt=1/0 confirm the outcome is rare and that both treatment arms are populated.

5.2 Sanity check: prior vs. observed vs. posterior

As a pre-model check, we compare the prior mean, observed mean, and conjugate posterior mean of \(Y^{\star}_{t+1}\) within each group \(A_t \in \{0,1\}\). This is a raw association check; it is not our causal estimate.

import numpy as np
rng = np.random.default_rng(0)

y_star = np.array(r.y_star, dtype="int64")
x_attempt = np.array(r.x_attempt, dtype="int64")
alpha = float(r.alpha_star); beta = float(r.beta_star)

def group_stats(mask, name):
    y = y_star[mask]; n=len(y); s=int(y.sum())
    prior_mean = alpha/(alpha+beta)
    post_mean  = (alpha+s)/(alpha+beta+n)
    obs_mean   = y.mean()
    return {"group": name, "n": n,
            "prior_mean": round(float(prior_mean),4),
            "obs_mean":   round(float(obs_mean),4),
            "post_mean":  round(float(post_mean),4)}

out = [group_stats(x_attempt==1, "attempt=1"),
       group_stats(x_attempt==0, "attempt=0")]
print(out)
[{'group': 'attempt=1', 'n': 418, 'prior_mean': 0.1797, 'obs_mean': 0.4258, 'post_mean': 0.4146}, {'group': 'attempt=0', 'n': 10280, 'prior_mean': 0.1797, 'obs_mean': 0.1696, 'post_mean': 0.1697}]
# Posterior intervals and difference
def post_draws(mask, size=40000):
    y = y_star[mask]; n=len(y); s=int(y.sum())
    return rng.beta(alpha+s, beta+(n-s), size=size)

d1, d0 = post_draws(x_attempt==1), post_draws(x_attempt==0)
diff = d1 - d0
print({"diff_mean": float(diff.mean()),
       "Pr(diff>0)": float((diff>0).mean()),
       "diff_ci95": tuple(np.round(np.quantile(diff,[0.025,0.975]),4))})
{'diff_mean': 0.24487812242561358, 'Pr(diff>0)': 1.0, 'diff_ci95': (np.float64(0.1983), np.float64(0.2924))}

The posterior mean in attempt=1 exceeds that in attempt=0 with a high Pr(diff>0), reflecting the unadjusted association that we will revisit after controlling for country and time.

5.3 Identification: avoid conditioning on endings & adjust back-door paths

Conditioning on “ends in \(t+1\)” creates a collider between time-varying drivers and the outcome. We therefore model the marginal probability \(P\!\big(Y^{\star}_{t+1}=1 \mid A_t,\ \text{adjustment}\big)\) and block back-door paths by adjusting for country heterogeneity \(U_i\), year shocks \(W_t\), and a time-varying confounder \(C_t\) (civil war).

from graphviz import Digraph
g = Digraph("dag_bad", format="svg"); g.attr(rankdir="LR")
g.node("A","A_t\n(coup attempts)", shape="ellipse")
g.node("C","C_t\n(conflicts, protest,\nrepression, shocks)", shape="ellipse")
g.node("U","U_i\n(country trait)", shape="ellipse")
g.node("E","E_{t+1}\n(any regime end)", shape="box")
g.node("Y","Y_{t+1}\n(coup vs not | E=1)", shape="box")
# edges
g.edge("A","E"); g.edge("C","E"); g.edge("U","E")
g.edge("C","Y"); g.edge("U","Y")
# conditioning (visual hint)
g.edge("A","Y", style="dashed", label="selection bias via E")
g.render("dag_bad", cleanup=True)
'dag_bad.svg'

dag_bad A A_t (coup attempts) E E_{t+1} (any regime end) A->E Y Y_{t+1} (coup vs not | E=1) A->Y selection bias via E C C_t (conflicts, protest, repression, shocks) C->E C->Y U U_i (country trait) U->E U->Y

Figure 1. Conditioning on \(E_{t+1}=1\) opens a collider.

#| label: dag_good
from graphviz import Digraph
g = Digraph("dag_good", format="svg"); g.attr(rankdir="LR")
g.node("A","A_t\n(coup attempts)", shape="ellipse")
g.node("C","C_t\n(conflicts, protest,\nrepression, shocks)", shape="ellipse")
g.node("U","U_i\n(country trait)", shape="ellipse")
g.node("T","W_t\n(year FE)", shape="ellipse")
g.node("Y","Y*_{t+1}\n(coup end next year?)", shape="box")
# edges
for s in ["A","C","U","T"]:
    g.edge(s,"Y")
g.edge("C","A"); g.edge("U","A") # Confounding induces association between A and Y
g.render("dag_good", cleanup=True)
'dag_good.svg'

dag_good A A_t (coup attempts) Y Y*_{t+1} (coup end next year?) A->Y C C_t (conflicts, protest, repression, shocks) C->A C->Y U U_i (country trait) U->A U->Y T W_t (year FE) T->Y

Figure 2. Modeling \(Y^{\star}_{t+1}\) avoids selection on \(E\) and adjusts for \(U_i\), \(W_t\), and \(C_t\).

5.4 Estimand and identifying conditions

Estimand (ATE).

\[ \mathrm{ATE} \;=\; \mathbb{E}\!\left[\,Y^{\star}_{t+1}(1)\;-\;Y^{\star}_{t+1}(0)\,\right]. \]

Identification requires:
(i) temporal ordering (predictors at \(t\), outcome at \(t+1\));
(ii) conditional ignorability given \(\{U_i,\, W_t,\, C_t\}\);
(iii) overlap (within-country variation in \(A_t\)); and
(iv) SUTVA.

5.5 Estimation strategy and reporting

We estimate the conditional effect of \(A_t\) using three specifications:

  1. Fixed-effects logit with country and year fixed effects and country-clustered standard errors.
  2. Random-intercept logit with year fixed effects (partial pooling across countries).
  3. The same mixed-effects model adding civil war as \(C_t\).

For interpretation, we report odds ratios (OR) and average risk differences via g-computation—i.e., predict \(P\!\big(Y^{\star}_{t+1}=1\big)\) under \(A_t=1\) and under \(A_t=0\) for the same rows, then average the difference.

6 Causal estimates

We now report within-country contrasts of the effect of a coup attempt in year \(t\) on the probability that the regime ends via a coup in year \(t+1\). All models align predictors at \(t\) and outcomes at \(t+1\), avoid conditioning on termination itself, and are read as conditional effects under our adjustment set.


6.1 Model 1: Fixed-effects logit (country & year FE) + g-computed risk difference

Country fixed effects absorb time-invariant heterogeneity (\(U_i\)) and year fixed effects absorb common shocks (\(W_t\)). The coefficient on attempt is therefore identified from within-country, over-time variation.

6.1.1 Prepare the panel frame

df_star <- vdem_change |>
  mutate(
    y_star = ifelse(regend_t1 %in% c(0,1,2), 1L,
             ifelse(!is.na(regend_t1), 0L, NA_integer_)),
    attempt = case_when(
      !is.na(e_pt_coup_attempts) & e_pt_coup_attempts > 0 ~ 1L,
      !is.na(e_pt_coup_attempts) & e_pt_coup_attempts == 0 ~ 0L,
      TRUE ~ NA_integer_
    )
  ) |>
  filter(!is.na(y_star), !is.na(attempt)) |>
  select(country_id, year, y_star, attempt)

summary(df_star$y_star); table(df_star$attempt)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.0000  0.0000  0.0000  0.1797  0.0000  1.0000 

    0     1 
10280   418 

6.1.2 Fixed-effects logit (country & year FE; robust SE)

fit_fe <- glm(
  y_star ~ attempt + factor(country_id) + factor(year),
  data = df_star, family = binomial()
)

Vcl <- sandwich::vcovCL(fit_fe, cluster = ~ country_id)
est <- lmtest::coeftest(fit_fe, vcov. = Vcl)["attempt", ]

b <- unname(est[1]); se <- unname(est[2]); p <- unname(est[4]); z <- qnorm(0.975)
ci <- b + c(-1,1)*z*se
OR <- exp(c(b, ci))  # OR, OR_low, OR_high

print(list(
  logit_coef_attempt = round(b, 4),
  se_robust = round(se, 4),
  p_value = round(p, 4),
  OR_attempt = round(OR[1], 3),
  OR_95CI = round(OR[-1], 3)
))
$logit_coef_attempt
[1] -0.3557

$se_robust
[1] 0.1599

$p_value
[1] 0.0261

$OR_attempt
[1] 0.701

$OR_95CI
[1] 0.512 0.959

6.1.3 G-computation: average risk difference

df1 <- transform(df_star, attempt = 1L)
df0 <- transform(df_star, attempt = 0L)
p1 <- predict(fit_fe, newdata = df1, type = "response")
p0 <- predict(fit_fe, newdata = df0, type = "response")
risk_diff <- mean(p1 - p0)
print(list(risk_diff = round(risk_diff, 4)))
$risk_diff
[1] -0.0243

Coup attempts in year t are associated with a lower chance that the regime ends via a coup in t+1: OR = 0.701 (95% CI 0.512–0.959, p = 0.026). G-computation implies an average risk difference of −2.43 percentage points (attempt 1 vs. 0). Interpreted within countries over time (absorbing year shocks), attempts do not increase next-year coup-termination risk and may slightly reduce it—reversing the positive raw association. The glm.fit warning about probabilities near 0/1 reflects near-separation in a rare outcome; we use clustered SEs and corroborate with the random-intercept model below.

6.2 Model 2: Random intercepts for countries + year FE

A country random intercept partially pools country baselines rather than estimating a separate dummy for each, which can stabilize estimates when outcomes are rare and FE suffer (quasi-)separation.

ctrl <- glmerControl(
  optimizer = "bobyqa",
  optCtrl   = list(maxfun = 2e5),
  calc.derivs = FALSE
)

system.time(
  fit_re <- glmer(
    y_star ~ attempt + factor(year) + (1 | country_id),
    data = df_star, family = binomial(),
    control = ctrl, nAGQ = 0, verbose = 1L
  )
)
start par. =  1 fn =  5796.455 
At return
eval:  51 fn:      5321.7059 par:  3.80426
   user  system elapsed 
   2.48    0.03    2.48 
b  <- fixef(fit_re)["attempt"]
se <- sqrt(vcov(fit_re)["attempt","attempt"])
z  <- qnorm(0.975); ci <- b + c(-1,1)*z*se
OR <- exp(c(b, ci))

print(list(
  logit_coef_attempt = round(b, 4),
  se = round(se, 4),
  OR_attempt = round(OR[1], 3),
  OR_95CI = round(OR[-1], 3)
))
$logit_coef_attempt
attempt 
-0.3181 

$se
[1] 0.1362

$OR_attempt
attempt 
  0.728 

$OR_95CI
            
0.557 0.950 
# g-computation(average risk difference)
df1 <- transform(df_star, attempt = 1L)
df0 <- transform(df_star, attempt = 0L)
p1 <- predict(fit_re, newdata = df1, type = "response", allow.new.levels = TRUE)
p0 <- predict(fit_re, newdata = df0, type = "response", allow.new.levels = TRUE)
risk_diff_re <- mean(p1 - p0)
print(list(risk_diff_re = round(risk_diff_re, 4)))
$risk_diff_re
[1] -0.0224

Allowing a random intercept for countries (with year fixed effects) yields OR = 0.728 with 95% CI 0.557–0.950. The g-computed average risk difference is −2.24 percentage points (attempt 1 vs. 0). This mirrors Model 1: within countries and net of period shocks, coup attempts do not raise the next-year probability that a regime ends via a coup. Partial pooling across countries also alleviates the near-separation concern from the high-dimensional FE logit, and the estimate remains negative and statistically different from 1.

6.3 Model 3: Adding a time-varying confounder: civil war

Civil war plausibly affects both the incidence of attempts and the probability of a coup termination, so we include a binary \(C_t\) to further block back-door paths.

6.3.1 Build the confounder and prepare data

# Rebuild a dataframe on the original vdem_change that includes the conflict variable
df_star2 <- vdem_change |>
  mutate(
    # Outcome: does next year end via a coup?
    # 1 if regend_t1 ∈ {0,1,2}; 0 for all other non-missing (including "still exists" = 13 and non-coup endings)
    y_star = ifelse(regend_t1 %in% c(0,1,2), 1L,
             ifelse(!is.na(regend_t1), 0L, NA_integer_)),
    # Treatment: was there at least one coup attempt in year t?
    attempt = case_when(
      !is.na(e_pt_coup_attempts) & e_pt_coup_attempts > 0 ~ 1L,
      !is.na(e_pt_coup_attempts) & e_pt_coup_attempts == 0 ~ 0L,
      TRUE ~ NA_integer_
    ),
    # Key time-varying confounder C_t — civil war in year t (treat missing as 0)
    any_conflict = as.integer(replace_na(e_civil_war, 0) > 0)
  ) |>
  # Align with the availability of attempts; drop rows with NA in y_star or attempt
  filter(!is.na(y_star), !is.na(attempt)) |>
  select(country_id, year, y_star, attempt, any_conflict)

# Quick check: counts of 0/1 (including NA if any)
table(df_star2$any_conflict, useNA = "ifany")

    0     1 
10131   567 

6.3.2 Random intercepts + year FE with civil war

ctrl <- glmerControl(
  optimizer = "bobyqa",
  optCtrl = list(maxfun = 2e5),
  calc.derivs = FALSE
)

fit_re_c <- glmer(
  y_star ~ attempt + any_conflict + factor(year) + (1 | country_id),
  data = df_star2,
  family  = binomial(),
  control = ctrl,
  nAGQ = 0,
  verbose = 1L
)
start par. =  1 fn =  5786.886 
At return
eval:  51 fn:      5316.1653 par:  3.79377
# OR and 95% CI for attempt (adjusted)
bA  <- fixef(fit_re_c)["attempt"]
seA <- sqrt(vcov(fit_re_c)["attempt","attempt"])
ciA <- bA + c(-1, 1) * qnorm(0.975)
OR_A <- exp(c(bA, ciA))

# OR and 95% CI for civil war (any_conflict) — sanity check: typically > 1
bC <- fixef(fit_re_c)["any_conflict"]
seC <- sqrt(vcov(fit_re_c)["any_conflict","any_conflict"])
ciC <- bC + c(-1, 1) * qnorm(0.975)
OR_C <- exp(c(bC, ciC))

print(list(
  OR_attempt_adj   = round(OR_A[1], 3),
  OR95_attempt_adj = round(OR_A[-1], 3),
  OR_conflict      = round(OR_C[1], 3),
  OR95_conflict    = round(OR_C[-1], 3)
))
$OR_attempt_adj
attempt 
  0.714 

$OR95_attempt_adj
            
0.101 5.068 

$OR_conflict
any_conflict 
       1.387 

$OR95_conflict
            
0.195 9.847 

6.3.3 Risk difference after adjusting for civil war (g-computation)

df1 <- transform(df_star2, attempt = 1L)
df0 <- transform(df_star2, attempt = 0L)
p1 <- predict(fit_re_c, newdata = df1, type = "response", allow.new.levels = TRUE)
p0 <- predict(fit_re_c, newdata = df0, type = "response", allow.new.levels = TRUE)
risk_diff_adj <- mean(p1 - p0)
print(list(risk_diff_adj = round(risk_diff_adj, 4)))
$risk_diff_adj
[1] -0.0236

Adjusting for civil war hardly changes the attempt effect. The odds ratio for attempt is 0.714 with a very wide 95% CI [0.101, 5.068]; the g-computed risk difference is −0.0236 (≈ −2.4 percentage points). The coefficient on any_conflict points in the expected direction—OR = 1.387—but its 95% CI [0.195, 9.847] is extremely imprecise, reflecting the rarity of civil-war years. In short, once we account for country heterogeneity and year shocks, attempts are not associated with a higher probability of coup termination next year; if anything, the estimated effect remains small and negative.

7 Main findings

A simple split (before any adjustment) suggests that years with a coup attempt are followed by more coup terminations. However, once we (i) align time to avoid conditioning on endings and (ii) compare within countries over time using country and year effects, that pattern reverses. In the fixed-effects logit, the attempt coefficient implies OR ≈ 0.70 (95% CI ≈ 0.51–0.96) and a g-computed risk difference of about −2.4 percentage points. A random-intercept logit with year fixed effects yields a very similar result (OR ≈ 0.73; risk difference ≈ −2.2 pp). Adding civil war as a time-varying confounder leaves the point estimate essentially unchanged (OR ≈ 0.71; risk difference ≈ −2.4 pp), though uncertainty widens because civil-war years are rare.

Taken together, the evidence indicates that coup attempts do not make a coup termination next year more likely; if anything, the estimated short-run effect is small and negative (≈ −2–3 pp). The contrast with the naive split is consistent with composition and timing effects—attempts occur in particular countries and periods—which inflate the raw association. Our estimates should be read as conditional average effects under the stated adjustment set (country heterogeneity, year shocks, and civil war).

8 Limitations

Two caveats shape how the results should be read. First, the fixed-effects logit exhibits numerical (quasi-)separation—expected in rare-event panels with many country and year dummies. Although the mixed-effects specification and the g-computed risk differences point in the same direction, finite-sample bias and optimistic standard errors remain possible. Second, both coup attempts and civil war are infrequent. Rarity inflates uncertainty and widens intervals once civil war is included, so effect magnitudes should be interpreted with caution rather than as precise point estimates.

Beyond these statistical issues, several substantive limitations apply. The key variables—e_pt_coup_attempts and v2regendtype—are assembled from historical sources and may contain misclassification or uneven coverage (attempts are most reliably coded after 1950). By design, we code “still exists” as a non-coup outcome and treat missing civil-war values as zero to align samples; if these choices down-weight near-collapses or mask unrecorded conflict, estimates may be attenuated. Identification also rests on the assumption that adjusting for country heterogeneity, year shocks, and civil war blocks the major back-door paths. Other time-varying forces—mass protest, repression severity, elite splits, or economic crises—could still confound both attempts and coup terminations. Our estimand is short-run (effects of attempts in year t on coup termination in t+1), so longer lags, anticipation, and feedback dynamics are not modeled, nor are regional spillovers that could violate SUTVA.

These caveats suggest practical robustness checks rather than invalidate the design: penalized/Firth or rare-events logit (or weakly informative Bayesian priors) to mitigate separation; sensitivity to alternative codings of termination and conflict; richer adjustment sets or double-robust estimators to hedge against residual confounding; and dynamic or spatial extensions (event-history models, regional contagion terms) to probe timing and interference. Even with these limitations, the qualitative pattern—that time alignment and adjustment for country and period effects overturn the naïve positive association—remains stable across specifications.

9 Robustness

To probe whether our main results are driven by separation in the high-dimensional fixed-effects logit or by optimizer idiosyncrasies in the mixed model, we ran two complementary checks. First, a bias-reduced fixed-effects logit (Firth-type, brglm2) produces an odds ratio for attempt of 0.711 (95% CI 0.546–0.927), essentially identical to the baseline FE estimate despite separation warnings. Second, re-estimating the random-intercept logit with year fixed effects and civil war using glmmTMB yields an odds ratio of 0.712 (95% CI 0.544–0.933) and an average risk difference of −0.0235. Taken together, these exercises indicate that the small, slightly negative association between attempts and next-year coup termination is not an artifact of complete separation or a particular optimizer; it appears stable across estimators and implementations(details see Appendix).

10 Reference

Brooks, M. E., Kristensen, K., van Benthem, K. J., Magnusson, A., Berg, C. W., Nielsen, A., Skaug, H. J., Mächler, M., & Bolker, B. M. (2017). glmmTMB balances speed and flexibility among packages for zero-inflated generalized linear mixed modeling. The R Journal, 9(2), 378–400. https://journal.r-project.org/archive/2017/RJ-2017-066/index.html

Coppedge, M., Gerring, J., Knutsen, C. H., Lindberg, S. I., Teorell, J., Altman, D., Angiolillo, F., Bernhard, M., Cornell, A., Fish, M. S., Fox, L., Gastaldi, L., Gjerløw, H., Glynn, A., Good God, A., Grahn, S., Hicken, A., Kinzelbach, K., Krusell, J., … Ziblatt, D. (2025). V-Dem [Country–Year/Country–Date] Dataset (Version 15) [Data set]. Varieties of Democracy (V-Dem) Project. https://doi.org/10.23696/vdemds25

Djuve, V. L., Knutsen, C. H., & Wig, T. (2019). Patterns of Regime Breakdown since the French Revolution. Comparative Political Studies, 001041401987995. https://doi.org/10.1177/0010414019879953

Firth, D. (1993). Bias reduction of maximum likelihood estimates. Biometrika, 80(1), 27–38. https://doi.org/10.1093/biomet/80.1.27

Geddes, B., Wright, J., & Frantz, E. (2014). Autocratic breakdown and regime transitions: A new data set. Perspectives on Politics, 12(2), 313–331. https://doi.org/10.1017/S1537592714000851

Goldstone, J. A., Bates, R. H., Epstein, D. L., Gurr, T. R., Lustik, M. B., Marshall, M. G., Ulfelder, J., & Woodward, M. (2010). A global model for forecasting political instability. American Journal of Political Science, 54(1), 190–208. https://doi.org/10.1111/j.1540-5907.2009.00426.x

Hegre, H., Karlsen, J., Nygård, H. M., Strand, H., & Urdal, H. (2013). Predicting armed conflict, 2010–2050. International Studies Quarterly, 57(2), 250–270. https://doi.org/10.1111/isqu.12007

King, G., & Zeng, L. (2001). Logistic regression in rare events data. Political Analysis, 9(2), 137–163. https://doi.org/10.1093/oxfordjournals.pan.a004868

Pearl, J. (2009). Causality: Models, reasoning, and inference (2nd ed.). Cambridge University Press. https://doi.org/10.1017/CBO9780511803161

Pemstein, D., Marquardt, K. L., Tzelgov, E., Wang, Y.-t., Medzihorsky, J., Krusell, J., Miri, F., & von Römer, J. (2025). The V-Dem measurement model: Latent variable analysis for cross-national and cross-temporal expert-coded data (V-Dem Working Paper No. 21, 10th ed.). Varieties of Democracy Institute, University of Gothenburg.

Powell, J. M., & Thyne, C. L. (2011). Global instances of coups from 1950 to present: A new dataset. Journal of Peace Research, 48(2), 249–259. https://doi.org/10.1177/0022343310397436

Svolik, M. W. (2012). The politics of authoritarian rule. Cambridge University Press. https://doi.org/10.1017/CBO9781139176040

11 Appendix: Robustness checks

11.1 Bias-reduced fixed-effects logit (Firth)

library(brglm2)

fit_fe_firth <- glm(
  y_star ~ attempt + factor(country_id) + factor(year),
  data = df_star,
  family = binomial("logit"),
  method = "brglmFit",  # <- from brglm2
  type = "AS_mean" # mean-bias reduction (Firth-like)
)

co <- summary(fit_fe_firth)$coef["attempt", ]
b <- unname(co[1]); se <- unname(co[2]); z <- qnorm(0.975)
ci <- b + c(-1,1)*z*se
OR <- exp(c(b, ci))

print(list(
  OR_attempt_firth = round(OR[1], 3),
  OR95_attempt_firth = round(OR[-1], 3)
))
$OR_attempt_firth
[1] 0.711

$OR95_attempt_firth
[1] 0.546 0.927

In our run, the Firth FE logit yields OR_attempt_firth ≈ 0.711 with a 95% CI of 0.546–0.927. This is very close to the baseline FE estimate despite separation warnings, indicating that the fixed-effects result is not driven by complete separation or small-sample bias.

11.2 Mixed-effects logit via glmmTMB (optimizer check)

library(glmmTMB)

# Same specification as the RE + year FE + civil war model
fit_tmb <- glmmTMB(
  y_star ~ attempt + any_conflict + factor(year) + (1 | country_id),
  data = df_star2,
  family = binomial()
)

co <- summary(fit_tmb)$coefficients$cond["attempt", ]
b <- unname(co["Estimate"]); se <- unname(co["Std. Error"]); z <- qnorm(0.975)
ci <- b + c(-1,1)*z*se
OR <- exp(c(b, ci))
print(list(
  OR_attempt_tmb   = round(OR[1], 3),
  OR95_attempt_tmb = round(OR[-1], 3)
))
$OR_attempt_tmb
[1] 0.712

$OR95_attempt_tmb
[1] 0.544 0.933
# g-computation: average risk difference under attempt=1 vs 0 for the same rows
df1 <- transform(df_star2, attempt = 1L)
df0 <- transform(df_star2, attempt = 0L)
p1  <- predict(fit_tmb, newdata = df1, type = "response", allow.new.levels = TRUE)
p0  <- predict(fit_tmb, newdata = df0, type = "response", allow.new.levels = TRUE)
risk_diff_tmb <- mean(p1 - p0)
print(list(risk_diff_tmb = round(risk_diff_tmb, 4)))
$risk_diff_tmb
[1] -0.0235

With glmmTMB, we obtain OR_attempt_tmb ≈ 0.712 (95% CI 0.544–0.933) and an average risk difference of −0.0235. These values line up with the main mixed-effects model, suggesting the small, slightly negative association is not an optimizer artifact.