qcaERT provides enhanced robustness tests for Qualitative Comparative Analysis (QCA). It is designed for the stage after calibration, truth table construction, and minimization, once the analyst needs to assess how much a QCA solution depends on thresholds, cutoffs, cases, samples, or grouping structures.
Building on the workflow supported by the QCA package (Dusa, 2019), qcaERT treats robustness evaluation as an auditable process through a series of comprehensive diagnostics.
| Concern | Use |
|---|---|
| Calibration thresholds may be fragile | calib.test() |
| Inclusion cutoff may be fragile | incl.test() |
| Frequency cutoff may be fragile | ncut.test() |
| Individual cases may drive the solution | loo.test() |
| Sample composition may matter | subsample.test() |
| Several analytic choices may vary together | altset.test() |
| Theoretical condition-set specifications should be compared | theory.test() |
| Results may differ across groups or clusters | cluster.test() |
| QCA minimization output needs a clean table or chart | sol.df(), sol.chart() |
Inside R, the same map is available with:
?qcaERT_testsqcaERT can be installed directly from CRAN:
install.packages("qcaERT")For the latest version, install from GitHub with:
install.packages("remotes")
remotes::install_github("marisguia/qcaERT")To cite qcaERT in publications, use:
Marisguia, B. A. H. (2026). qcaERT: Enhanced Robustness Tests for Qualitative Comparative Analysis. R package version 0.1.2. https://CRAN.R-project.org/package=qcaERT. doi:10.32614/CRAN.package.qcaERT.
Inside R, citation information is available with:
citation(package = "qcaERT")This example uses the LR data from the QCA
package.
library(QCA)
library(qcaERT)
data(LR)
conditions <- c("DEV", "URB", "LIT", "IND", "STB")
outcome <- "SURV"
dir_exp <- rep("1", length(conditions))
thresholds <- list(
DEV = findTh(LR$DEV, groups = 7),
URB = findTh(LR$URB, groups = 4),
LIT = findTh(LR$LIT, groups = 4),
IND = findTh(LR$IND, groups = 4),
STB = findTh(LR$STB, groups = 4),
SURV = findTh(LR$SURV, groups = 4)
)
dat <- LR
dat$DEV <- calibrate(LR$DEV, type = "fuzzy", thresholds = thresholds$DEV)
dat$URB <- calibrate(LR$URB, type = "fuzzy", thresholds = thresholds$URB)
dat$LIT <- calibrate(LR$LIT, type = "fuzzy", thresholds = thresholds$LIT)
dat$IND <- calibrate(LR$IND, type = "fuzzy", thresholds = thresholds$IND)
dat$STB <- calibrate(LR$STB, type = "fuzzy", thresholds = thresholds$STB)
dat$SURV <- calibrate(LR$SURV, type = "fuzzy", thresholds = thresholds$SURV)Run a regular QCA analysis:
tt <- truthTable(
dat,
outcome = outcome,
conditions = conditions,
incl.cut = 0.8,
n.cut = 1,
complete = TRUE,
show.cases = TRUE
)
sol <- minimize(tt, include = "", details = TRUE, show.cases = FALSE)
solution_table <- sol.df(conservative = sol, solution = "conservative")
solution_table
sol.chart(solution_table)Then check robustness.
incl_out <- incl.test(
data = dat,
outcome = outcome,
conditions = conditions,
incl.cut = 0.8,
step = 0.05,
max_steps = 4,
n.cut = 1,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
incl_out
as.data.frame(incl_out)
incl_out$diagnosticsCompare theoretically motivated condition sets under the same analytic settings:
theories <- list(
development = c("DEV", "URB", "LIT"),
industrial = c("DEV", "URB", "IND"),
broad = c("DEV", "URB", "LIT", "IND", "STB")
)
dir_exp_theories <- list(
development = c("1", "1", "1"),
industrial = c("1", "1", "1"),
broad = c("1", "1", "1", "1", "1")
)
theory_out <- theory.test(
data = dat,
outcome = outcome,
theories = theories,
incl.cut = 0.8,
n.cut = 1,
solution = "all",
dir.exp = dir_exp_theories,
progress = TRUE
)
theory_out
as.data.frame(theory_out)
theory_out$results$solutions
theory_out$results$pairwiseFor calibration robustness, define calib_spec and let
qcaERT compute scale-aware perturbation steps. calib_spec
records the raw column, calibration type, method, thresholds, and any
extra QCA::calibrate() arguments in one place.
calib_spec <- list(
DEV = list(raw = "DEV", type = "fuzzy", method = "direct", thresholds = thresholds$DEV),
URB = list(raw = "URB", type = "fuzzy", method = "direct", thresholds = thresholds$URB),
LIT = list(raw = "LIT", type = "fuzzy", method = "direct", thresholds = thresholds$LIT),
IND = list(raw = "IND", type = "fuzzy", method = "direct", thresholds = thresholds$IND),
STB = list(raw = "STB", type = "fuzzy", method = "direct", thresholds = thresholds$STB)
)
calib_spec_outcome <- calib_spec
calib_spec_outcome$SURV <- list(
raw = "SURV",
type = "fuzzy",
method = "direct",
thresholds = thresholds$SURV
)
calib_out <- calib.test(
raw.data = LR,
calib.data = dat,
outcome = outcome,
conditions = conditions,
calib_spec = calib_spec,
test.conditions = c("DEV", "URB"),
unit_step = NULL,
unit_step_divisor = 10,
max_steps = 5,
incl.cut = 0.8,
n.cut = 1,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
calib_out
as.data.frame(calib_out)
calib_out$boundsHere, conditions defines the full QCA model, while
test.conditions selects which calibrated conditions are
perturbed. If test.conditions is omitted, all model
conditions are tested.
To test the outcome calibration, keep the outcome out of
conditions and ask for it explicitly:
calib_outcome <- calib.test(
raw.data = LR,
calib.data = dat,
outcome = outcome,
conditions = conditions,
calib_spec = calib_spec_outcome,
test.conditions = NULL,
test.outcome = TRUE,
unit_step = NULL,
unit_step_divisor = 10,
max_steps = 5,
incl.cut = 0.8,
n.cut = 1,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)Most qcaERT robustness functions return an S3 object with:
diagnostics: detailed/internal resultsresults: clean resultssettings: the settings used to run the analysisbaseline,
bounds, by_direction, by_case,
by_run, by_draw, or summaryUse:
print(incl_out)
as.data.frame(incl_out)
incl_out$diagnosticsFor incl.test(), ncut.test(), and
calib.test(), result_shape controls the layout
of the clean table when solution = "all". The default
"wide" layout keeps one row per tested path with
solution-type-specific columns; "long" returns one row per
tested path and solution type, with a solution_type
column.
cluster.test() and theory.test() are the
deliberate structured-result exceptions. cluster.test()
contains three tables:
overviewclustersunitsFor cluster_test objects, as.data.frame()
returns results$overview.
theory.test() contains:
modelssolutionspairwiseFor theory_test objects, as.data.frame()
returns results$models.
If ggplot2 is installed, calib.test(),
incl.test(), and theory.test() results can be
plotted directly.
plot(incl_out, solution_type = "conservative")
plot(incl_out, solution_type = "conservative", type = "trace", direction = "lower")
plot(calib_out, solution_type = "conservative")
plot(calib_out, solution_type = "conservative", type = "heatmap")
plot(calib_out, solution_type = "conservative", type = "trace", set = "DEV", anchor = "E1", direction = "lower")
plot(theory_out, solution_type = "conservative")For direct six-threshold fuzzy calibration, anchors are
E1, C1, I1, I2,
C2, and E2. For indirect calibration, anchors
are T1, T2, and so on.
See:
?qcaERT_plotsFor comprehensive guidance, worked examples, and interpretation of qcaERT diagnostics, read Marisguia (2026), A Guide to qcaERT: Robustness Diagnostics in QCA. The citable Version 1.0 is archived on Zenodo.
?qcaERT
?qcaERT_tests
?qcaERT_conventions
?qcaERT_plots
vignette("qcaERT-overview", package = "qcaERT")
vignette("qcaERT-result-objects", package = "qcaERT")
vignette("qcaERT-calibration", package = "qcaERT")
news(package = "qcaERT")
citation(package = "qcaERT")qcaERT is available from CRAN: https://CRAN.R-project.org/package=qcaERT