Replication File: Dirichlet Monte Carlo Perturbations
Authors: Salvatore Barbaro and David Bustamante Lazo
(2026, manuscript submitted for publication)
This README focuses exclusively on the main function and its internal subfunctions. Detailed descriptions of the code's functionality are included as comments within the provided main R-file.
This repository implements the function run_one_election() to estimate Condorcet cycle probabilities in ranked-choice elections using Dirichlet Monte Carlo perturbations of empirically observed preference distributions.
The function evaluates all 3-candidate subsets (triplets) in an election and computes the probability of observing a majority cycle under sampling uncertainty.
We used the same CSES project data as provided in the replication files in Barbaro and Kurella (2025).
- Main Replication R-File:
All_Elections.R
- Datasets:
- CSES project data:
rankings.RData - Political Behaviour Weights (PBW):
resW.RData
- CSES project data:
All replications use a fixed seed value for reproducibility:
Seed Value: 55234 (the main authors' zip code).
- Linux operating system
- R version: 4.5.2 "[Not] Part in a Rumble"
- Required R Libraries:
dplyrmagrittrtidyversetidyrstringrdata.tableMCMCpack
The results have been tested WITH LINUX ONLY!
Urgent: If you run this script on Windows, you must adjust the function. In particular, do NOT use mclapply.
Alternatives:
- Run the (outcommented) Timing1 function without parallelization (it will take longer).
- Instead of
mclapply, recode, under your own responsability, the function using thefuture.applylibrary available in CRAN:
This function executes the complete Condorcet-cycle simulation pipeline for a single election.
df: a data.frame containing respondent-level rankings (rows = individuals, columns = candidates)N: number of Dirichlet draws per tripletphi: precision scaling parameter for the Dirichlet distributionseed: random seed for reproducibilitypseudocount: unused placeholder argumentord_levels: vector of the 13 admissible ranking orderings for three candidates (including ties)
The function returns a list containing:
ranking_all: empirical ranking frequencies for each tripletvec_list: 13-category count vectors for each tripletprop_list: normalized proportions for each tripletperturbations: Dirichlet draws for each tripletoutcomes_by_triplet: Condorcet outcomes for each drawoverall_table: aggregated outcome countscycle_rate_by_triplet: cycle probability per tripletoverall_cycle_rate: global cycle probabilitybaseline_outcome: Condorcet outcome computed from empirical proportions (no perturbation)
All triplets are generated by combining the candidate columns.
For an election with m candidates, the number of triplets equals choose(m, 3).
Within each triplet, candidates are relabeled locally as A, B, and C to standardize processing.
Each respondent’s ranking over A, B, C is converted into one of 13 canonical ordering strings:
$A \sim B \sim C$ $A \sim B \succ C$ $A \sim C \succ B$ $A \succ B \sim C$ $A \succ B \succ C$ $A \succ C \succ B$ $B \sim C \succ A$ $B \succ A \sim C$ $B \succ A \succ C$ $B \succ C \succ A$ $C \succ A \sim B$ $C \succ A \succ B$ $C \succ B \succ A$
The encoding logic works as follows:
- Candidates tied at the same rank are joined with
$\sim$ . - The preferences of a candidate over another one are joined with
$\succ$ .
For every respondent, the reported ranking is mapped to exactly one canonical ordering string.
For each triplet:
- Ordering strings are tabulated.
- A fixed-length vector of length 13 is created.
- The order of entries matches
ord_levelsexactly.
Missing ordering types receive zero counts.
Outputs:
vec_list: raw frequency countsprop_list: normalized proportions (counts divided by total)
Observed counts (frequencies) are scaled by the precision parameter:
α = φ · c
For each triplet, N draws are generated from a Dirichlet distribution using these alpha parameters.
Each draw represents a plausible population preference distribution consistent with the observed data.
Column ordering is enforced to match ord_levels to ensure stable indexing.
For each of the 13 ordering types, contributions to pairwise contests are precomputed:
-
$A$ vs$B$ -
$A$ vs$C$ -
$B$ vs$C$
Rules:
- Strict preference contributes 1.
- Strict loss contributes 0.
- Tie contributes 0.5.
Index sets (idx) store which ordering columns imply wins, losses, or ties for each candidate pair.
These index sets allow fast vectorized computation of pairwise majority shares from each Dirichlet draw.
For every Dirichlet draw:
- Pairwise vote shares are computed using the index sets.
- Pairwise winners are determined.
- A final outcome is assigned:
- "A", "B", "C" if a Condorcet winner exists
- "Tie" if majority relations are unresolved
- "Cycle" if a majority cycle occurs
This classification is performed independently for every triplet and every draw.
The function computes:
cycle_rate_by_triplet: fraction of draws producing cycles for each tripletoverall_cycle_rate: fraction of cycles across all triplets and all drawsoverall_table: total counts of each outcomebaseline_outcome: Condorcet outcome derived directly from empirical proportions
A single election can be analyzed, e.g., by calling:
res = run_one_election(elections[[Switzerland_2011]])
This runs the full Dirichlet perturbation and Condorcet classification pipeline for the first election in the list.
The returned object res is a list containing all intermediate and final results.
Key outputs:
-
res$overall_cycle_rate
The overall probability of observing a Condorcet cycle across all triplets and all Dirichlet draws. -
res$cycle_rate_by_triplet
A vector containing the cycle probability for each individual triplet. -
res$overall_table
A frequency table of all outcomes ("A", "B", "C", "Tie", "Cycle") aggregated across draws and triplets. -
res$baseline_outcome
The Condorcet outcome is computed directly from the empirical preference proportions (without Dirichlet perturbation).