The package CausalClustering implements methods for
designing cluster-randomized experiments under network interference. In
this setting, one unit’s treatment may affect neighboring units. The
choice of experimental clusters therefore affects both statistical
precision and spillover bias.
The package is designed for experiments where the user observes an
experimental network and wants to choose clusters before randomizing
treatment. The central input is an adjacency matrix W; the
central output is a vector of cluster labels.
The main workflow is:
xi, or provide a calibration
grid/range;causal_clustering_algorithm();This package implements methods from:
Viviano, D., Lei, L., Imbens, G., Karrer, B., Schrijvers, O., & Shi, L. (2026). Causal clustering: design of cluster experiments under network interference. Manuscript, May 28, 2026.
Install the development version from GitHub with:
Load the package with:
Some features require optional packages:
The SDP engine requires Matrix and sdpt3r.
If sdpt3r is not available from CRAN in your R setup,
install it from GitHub:
We first illustrate the main estimator on a simulated network. The goal is to choose experimental clusters that trade off variance and spillover bias.
library(CausalClustering)
sim <- simulate_network_data(
parameters_graph = list(
n = 60,
type_graph = "geometric",
neighb = 2
)
)
W <- sim$WThe object W is the adjacency matrix used by the
clustering algorithm. Nonzero entries indicate edges in the experimental
network.
We now run the causal clustering algorithm for a single calibration value.
fit <- causal_clustering_algorithm(
W = W,
xi = 1,
min_k = 2,
max_k = 12,
engine = "sdp",
methods = available_discretization_methods(),
seed = 123
)After fitting, we can inspect the selected design.
The entry selected_k is the number of clusters selected
by the algorithm. The entry selected_method records which
discretization method was selected after rounding the relaxation and
evaluating the realized objective.
For a candidate clustering, the package evaluates the objective
xi * variance + bias^2.
The variance component is the normalized sum of squared cluster sizes. The bias component is the average fraction of each unit’s neighbors assigned to a different cluster.
The objective can also be evaluated directly for a user-supplied clustering.
W_small <- matrix(
c(
0, 1, 1, 0,
1, 0, 0, 1,
1, 0, 0, 1,
0, 1, 1, 0
),
nrow = 4,
byrow = TRUE
)
clusters <- c(1, 1, 2, 2)
components <- clustering_objective_components(W_small, clusters)
objective <- compute_causal_clustering_objective(
W = W_small,
xi = 1,
clusters = clusters
)
components
objective
all.equal(objective, components$variance + components$bias^2)The calibration parameter xi controls the relative
weight on the variance component. Larger values of xi place
more weight on variance; smaller values place more weight on
spillover-bias reduction.
If the calibration is uncertain, pass a grid or range. The same public function automatically uses the endpoint-regret algorithm.
fit_grid <- causal_clustering_algorithm(
W = W,
xi_grid = c(0.2, 0.5, 1, 2),
min_k = 2,
max_k = 12,
engine = "sdp",
methods = available_discretization_methods(),
seed = 123
)
fit_grid$algorithm
fit_grid$xi_range
fit_grid$selected_k
fit_grid$selected_method
fit_grid$objective
fit_grid$rho_values
fit_grid$componentsThe package also accepts calibration,
calibration_grid, and calibration_range. These
are converted internally by
xi = 1 / calibration.
The relaxation output must be rounded into a feasible clustering. The supported rounding methods are:
They are currently:
c(
"kmeans",
"hierarchical",
"spectral_norm_kmeans",
"spectral_unnorm_kmeans",
"spectral_norm_hierarchical",
"spectral_unnorm_hierarchical"
)When several methods are supplied, the package tries all requested methods and keeps the candidate with the smallest realized objective.
With engine = "sdp", fitted objects may contain an SDP
lower bound and an approximation certificate.
The argument k_constraint controls how the SDP
relaxation is solved.
fit_k <- causal_clustering_algorithm(
W = W,
xi = 1,
min_k = 2,
max_k = 12,
engine = "sdp",
k_constraint = TRUE,
gamma_bar = 10,
box_constraints = TRUE,
methods = available_discretization_methods(),
seed = 123
)With k_constraint = FALSE, the algorithm solves one
relaxation and rounds it over the candidate K grid. With
k_constraint = TRUE, it solves a K-specific SDP relaxation
for each candidate K and can impose additional cluster-size and box
constraints.
The package can be used without an SDP solver by setting
engine = "spectral". This is useful for exploratory
computation. The spectral engine is available for scalar calibration
values.
fit_spectral <- causal_clustering_algorithm(
W = W,
xi = 1,
min_k = 2,
max_k = 12,
engine = "spectral",
methods = available_discretization_methods(),
seed = 123
)
fit_spectral$selected_k
fit_spectral$selected_method
fit_spectral$objective
fit_spectral$componentsThe spectral engine evaluates the same realized objective after rounding, but it does not provide the SDP lower bound or approximation certificate.
The package also includes baseline clustering procedures.
epsilon_fit <- cluster_epsilon_net(W)
spectral_fit <- cluster_spectral(W, num_clusters = 5, seed = 123)If igraph is installed, Louvain clustering is also
available.
These baselines can be evaluated using the same objective functions.
The function run_single_network() simulates or accepts a
network, runs the causal clustering method over a sequence of
xi values, and compares it with baseline clusterings.
out <- run_single_network(
seed = 123,
parameters_graph = list(
type_graph = "geometric",
n = 100,
neighb = 2
),
xi_seq = seq(0.1, 5, by = 0.5),
min_k = 2,
max_k = 20,
include_louvain = TRUE,
engine = "spectral",
objective_type = "squared",
methods = available_discretization_methods()
)
head(out$results)The plotting functions summarize the objective path and the selected number of clusters.