qcaERT

qcaERT provides enhanced robustness tests for Qualitative Comparative Analysis (QCA). It is designed for the moment after calibration, truth table construction, and minimization, when the question becomes:

How much does this QCA solution depend on my thresholds, cutoffs, cases, sample, or grouping structure?

The package is built as a family of sibling tools. They share common argument conventions, returned-object structure, print behavior, and data-frame coercion.

The strongest coverage is the QCA package workflow built around QCA::calibrate(), QCA::truthTable(), QCA::minimize(), and QCA::findRows(), especially crisp-set and fuzzy-set analyses. qcaERT is not intended to cover every possible QCA variant.

What qcaERT helps with

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_tests

Installation

qcaERT is currently in development. From a local source checkout, install it with:

install.packages("devtools")
devtools::install()

Then load it normally:

library(qcaERT)
library(QCA)

Plotting is optional and uses ggplot2:

install.packages("ggplot2")

A compact LR workflow

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$diagnostics

Compare 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$pairwise

For 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$bounds

Here, 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
)

Reading qcaERT results

Most qcaERT robustness functions return an S3 object with:

Use:

print(incl_out)
as.data.frame(incl_out)
incl_out$diagnostics

For 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:

For cluster_test objects, as.data.frame() returns results$overview.

theory.test() contains:

For theory_test objects, as.data.frame() returns results$models.

Plotting

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_plots

Learn more

?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")

Status

qcaERT is release-ready.