Using compdid for the Sequeira Application
Source:vignettes/sequeira-application.Rmd
sequeira-application.RmdThis guide uses the Sequeira (2016) application from Sant’Anna and Xu
(2026) to show how external researchers should use compdid
in a new empirical project. It is not a numerical replication guide for
Table 4. The package now uses more guarded defaults than the historical
paper scripts: generalized propensity score rows are normalized by
default, local multinomial-logit fits use more stable numerical
routines, and cross-fitted nuisance estimation is available as the main
front door. Those choices can move the application estimates relative to
the paper-final archived output.
If the goal is to reproduce the published paper numbers exactly, use the separate vignette:
vignette("sequeira-application-replication", package = "compdid")Data
The package ships the application data so users can work through the example without cloning the original replication repository.
The outcome variables are:
-
bp: whether a bribe was paid. -
lba: log transformed bribe amount. -
lba_value: log transformed bribe amount relative to shipment value. -
lba_tonnage: log transformed bribe amount relative to shipment tonnage.
The treatment indicator is tariff_change_2008, the
post-period indicator is post_2008, and clustered inference
in the paper uses hc_4digits.
Prepare The Analysis Data
The application needs a few deterministic transformations. The key point for new projects is to write preprocessing explicitly and keep the analysis sample fixed before fitting models.
prepare_sequeira <- function(data) {
data$lba_value[2783L] <- 0
data$monitor <- data$monitor - 1
data$psi <- 2 - data$psi
data$ltonnage <- log(data$tonnage + 1)
vars <- c(
"bp", "lba", "lba_value", "lba_tonnage",
"post_2008", "tariff_change_2008", "hc_4digits",
"lvalue_tonnage", "ltonnage", "lvalue_shipment_metical",
"tariff2007",
"differentiated", "agri", "perishable", "dfs", "day_w_arrival",
"monitor", "psi", "rsa", "term"
)
data[complete.cases(data[, vars]), vars]
}
app <- prepare_sequeira(sequeira_bribes)Recommended Front Door
For most new empirical work, start with comp_did() and
the default DML nuisance path. This avoids high-dimensional
local-polynomial smoothing and keeps the workflow close to the package’s
best-tested interface.
xvars <- c(
"lvalue_tonnage", "tariff2007",
"differentiated", "agri", "perishable", "dfs", "day_w_arrival",
"monitor", "psi", "rsa", "term"
)
xform <- reformulate(xvars)
fit_bp <- comp_did(
yname = "bp",
tname = "post_2008",
dname = "tariff_change_2008",
xformula = xform,
data = app,
nuisance_method = "dml",
K = 5,
stationary = TRUE,
stationarity_test = TRUE,
boot = FALSE,
inffunc = TRUE,
seed = 1234
)
summary(fit_bp)The non-stationary ATT is the compositional-change-robust target. The stationary fit and stationarity test are useful diagnostics when the researcher wants to compare the no-compositional-change restriction against the more robust target.
Clustered Inference
The paper clusters at the four-digit HS-code level. With
inffunc = TRUE, use the fitted influence functions and
drdid_cluster_bootstrap().
clustered_bp <- drdid_cluster_bootstrap(
nonstationary_fit = fit_bp$att,
stationary_fit = fit_bp$stationary,
group = app$hc_4digits,
nboot = 9999,
seed = 1234,
keep_boots = FALSE
)
clustered_bpMultiple Outcomes
A clean applied workflow makes the outcome-specific choices explicit. The example below mirrors the paper’s use of different scale variables across outcomes, while still using the modern DML front door.
outcome_specs <- list(
bp = c("lvalue_tonnage", "tariff2007"),
lba = c("lvalue_tonnage", "tariff2007"),
lba_value = c("ltonnage", "tariff2007"),
lba_tonnage = c("lvalue_shipment_metical", "tariff2007")
)
discrete_x <- c(
"differentiated", "agri", "perishable", "dfs", "day_w_arrival",
"monitor", "psi", "rsa", "term"
)
run_outcome <- function(y, continuous_x) {
xform <- reformulate(c(continuous_x, discrete_x))
fit <- comp_did(
yname = y,
tname = "post_2008",
dname = "tariff_change_2008",
xformula = xform,
data = app,
nuisance_method = "dml",
K = 5,
stationary = TRUE,
stationarity_test = TRUE,
boot = FALSE,
inffunc = TRUE,
seed = 1234
)
boot <- drdid_cluster_bootstrap(
nonstationary_fit = fit$att,
stationary_fit = fit$stationary,
group = app$hc_4digits,
nboot = 9999,
seed = 1234,
keep_boots = FALSE
)
list(fit = fit, cluster = boot)
}
fits <- Map(run_outcome, names(outcome_specs), outcome_specs)Local-Polynomial Nuisances
Local-polynomial nuisances can be useful in low-dimensional problems where the mixed continuous/discrete kernel structure is substantively important. They are also more expensive and more sensitive to bandwidth choices. If you use them, declare continuous and unordered covariates explicitly.
fit_bp_lp <- comp_did(
yname = "bp",
tname = "post_2008",
dname = "tariff_change_2008",
data = app,
continuous = c("lvalue_tonnage", "tariff2007"),
unordered = discrete_x,
nuisance_method = "loo",
stationary = TRUE,
stationarity_test = TRUE,
boot = FALSE,
inffunc = TRUE,
list_control_ps = list(
n_start = 3,
ps_min = 1e-2,
local_mode = "fast",
normalize_ps = TRUE
),
list_control_or = list(
n_start = 2,
bw_constrained = TRUE
)
)The default recommendation is to keep
normalize_ps = TRUE. Setting it to FALSE is an
advanced replication or sensitivity exercise, not a best-practice
default for new work.
Reporting
For a new application, report:
- the estimand, especially whether the non-stationary compositional-change robust ATT is the primary estimand;
- the nuisance method and learners or bandwidth controls;
- the cluster variable and number of bootstrap draws;
- seeds, package version, and any saved nuisance fitted values used for replication.
Do not expect this best-practice workflow to reproduce Table 4 numerically. That is intentional: the package’s current defaults target reliable use by new researchers. Numerical reproduction of the paper-final application is documented separately.