qcaERT adds robustness checks to a normal QCA workflow. It is meant for the moment after calibration, truth table construction, and minimization, when the main question becomes:
How much does this solution depend on my thresholds, cutoffs, cases, sample, or grouping structure?
This vignette gives a short path through the package. For a map from
robustness questions to functions, see ?qcaERT_tests.
The usual workflow still begins with QCA. In this
example, the LR data are calibrated, minimized, and then
passed to qcaERT.
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)
tt <- truthTable(
data = dat,
outcome = outcome,
conditions = conditions,
incl.cut = 0.8,
n.cut = 1
)
sol <- minimize(tt, include = "?", dir.exp = dir_exp)
sol.df(intermediate = sol, solution = "intermediate")sol.df() is not a robustness test. It is a compact table
builder for QCA minimization output.
Each qcaERT function answers a different robustness question.
| Question | Function |
|---|---|
| Do calibration thresholds matter? | calib.test() |
| Does the inclusion cutoff matter? | incl.test() |
| Does the frequency cutoff matter? | ncut.test() |
| Do individual cases drive the solution? | loo.test() |
| Is the solution stable across subsamples? | subsample.test() |
| What if several analysis choices vary together? | altset.test() |
| Do theoretically motivated condition sets lead to different solutions? | theory.test() |
| Do results differ across groups or clusters? | cluster.test() |
| How do I turn QCA solutions into a table? | sol.df() |
incl.test() and ncut.test() are the
simplest robustness tests. They move one truth table cutoff below and
above the baseline value.
incl_out <- incl.test(
data = dat,
outcome = outcome,
conditions = conditions,
incl.cut = 0.8,
n.cut = 1,
step = 0.02,
max_steps = 5,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
incl_out
as.data.frame(incl_out)
incl_out$diagnostics
ncut_out <- ncut.test(
data = dat,
outcome = outcome,
conditions = conditions,
n.cut = 1,
incl.cut = 0.8,
step = 1,
max_steps = 3,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
ncut_out
as.data.frame(ncut_out)The printed object is the quick view. as.data.frame()
returns the clean table. diagnostics contains the more
detailed internal table.
calib.test() perturbs one calibration anchor at a time.
Use calib_spec as the calibration specification: it records
the raw column, calibration type, method, and thresholds in one place. A
good default is to let qcaERT compute the perturbation size from the
threshold spacing instead of choosing a raw number by hand.
calib_spec <- list(
DEV = list(raw = "DEV", type = "fuzzy", thresholds = thresholds$DEV),
URB = list(raw = "URB", type = "fuzzy", thresholds = thresholds$URB),
LIT = list(raw = "LIT", type = "fuzzy", thresholds = thresholds$LIT),
IND = list(raw = "IND", type = "fuzzy", thresholds = thresholds$IND),
STB = list(raw = "STB", type = "fuzzy", thresholds = thresholds$STB)
)
calib_spec_outcome <- calib_spec
calib_spec_outcome$SURV <- list(
raw = "SURV",
type = "fuzzy",
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$boundsconditions names the full condition set used in the QCA
model. test.conditions names the subset whose calibration
thresholds should be perturbed. If test.conditions is not
supplied, qcaERT tests every condition in conditions.
Outcome calibration is requested explicitly. The outcome is not added
to conditions; instead, use
test.outcome = TRUE and include the outcome in
calib_spec.
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
)loo.test() removes cases one at a time.
subsample.test() runs repeated subsamples.
loo_out <- loo.test(
data = dat,
outcome = outcome,
conditions = conditions,
cases = 1:5,
incl.cut = 0.8,
n.cut = 1,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
loo_out
as.data.frame(loo_out)
subsample_out <- subsample.test(
data = dat,
outcome = outcome,
conditions = conditions,
sample_prop = 0.8,
reps = 25,
seed = 123,
incl.cut = 0.8,
n.cut = 1,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
subsample_out
subsample_out$summaryaltset.test() samples alternative analysis settings. It
can vary calibration thresholds, incl.cut, and
n.cut in the same run.
altset_out <- altset.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,
calib_max_steps = 5,
incl.cut = 0.8,
incl_step = 0.02,
incl_max_steps = 5,
n.cut = 1,
ncut_step = 1,
ncut_max_steps = 2,
n_draws = 50,
seed = 123,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
altset_out
as.data.frame(altset_out)
altset_out$summaryUse calib.test() when you want directional threshold
bounds. Use altset.test() when you want sampled compound
perturbations.
theory.test() compares several theoretically motivated
condition sets under the same outcome, truth-table cutoffs, solution
setting, exclusion handling, and the same settings for
which_M and i_mode. Each theory gets its own
truth table; exclusions are recomputed separately when
exclude_mode = "recompute".
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$pairwiseThe model table compares fit and truth-table diagnostics by theory and solution_type. The solutions table extracts the selected terms. The pairwise table compares selected solution memberships across theories.
cluster.test() starts from an existing truth table and
compares selected configurations across groups.
cluster_data <- dat
cluster_data$region <- ifelse(seq_len(nrow(cluster_data)) %% 2 == 0, "A", "B")
cluster_data$unit <- rownames(cluster_data)
cluster_out <- cluster.test(
data = cluster_data,
tt = tt,
cluster_id = "region",
unit_id = "unit",
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
cluster_out
as.data.frame(cluster_out)
cluster_out$results$clusters
cluster_out$results$unitscluster.test() is one of the structured-result
exceptions in the package: results is a list with
overview, clusters, and units.
The other structured-result exception is theory.test(),
whose results component contains models,
solutions, and pairwise.
Plotting is optional and requires ggplot2.
plot(incl_out, solution_type = "conservative")
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 plotting details, see ?qcaERT_plots.
Read:
vignette("qcaERT-result-objects", package = "qcaERT")
for the common returned-object structure.vignette("qcaERT-calibration", package = "qcaERT") for
calibration specifications, scale-aware threshold moves, and alternative
sets.?qcaERT_conventions for the package-wide argument and
output conventions.