Calibration robustness needs a little more structure than the cutoff and case tests. qcaERT has to know which raw columns created the calibrated conditions, which calibration method was used, and which thresholds may be perturbed.
This vignette focuses on calib.test() and
altset.test().
The examples below use 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)Calibration-family functions use calib_spec: a named
list with one entry per set you want qcaERT to be able to recalibrate.
This structure keeps the raw source, calibration type, calibration
method, thresholds, and extra QCA::calibrate() arguments
together.
calib_spec <- list(
DEV = list(
raw = "DEV",
type = "fuzzy",
method = "direct",
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
)Each entry contains:
raw: the raw column name in raw.datatype: "crisp" or "fuzzy"thresholds: the baseline calibration thresholdsmethod: optional, passed to
QCA::calibrate()calibrate: optional list of extra
QCA::calibrate() argumentsFor condition-only runs, calib_spec must contain exactly
one entry for every condition in conditions. When
test.outcome = TRUE, it must contain the conditions plus
one entry named by outcome. This keeps the recalibration
structure the same across the calibration-family functions.
conditions is the full condition set in the QCA
model.
test.conditions is the subset whose thresholds should be
perturbed.
calib.test(
raw.data = LR,
calib.data = dat,
outcome = "SURV",
conditions = c("DEV", "URB", "LIT", "IND", "STB"),
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 = rep("1", 5),
progress = TRUE
)If test.conditions is not supplied, qcaERT tests all
conditions in conditions.
The outcome is not a condition and should not be placed in
conditions. To test outcome calibration, keep the QCA model
unchanged and request the outcome explicitly with
test.outcome = TRUE.
Use test.conditions = NULL when you want to test only
the outcome calibration.
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
)
calib_outcome
as.data.frame(calib_outcome)qcaERT uses anchor names that match the calibration method.
| Calibration type | Thresholds | Anchor names |
|---|---|---|
| Crisp | one threshold | T |
| Fuzzy direct, three thresholds | c(E, C, I) |
E, C, I |
| Fuzzy direct, six thresholds | c(E1, C1, I1, I2, C2, E2) |
E1, C1, I1, I2,
C2, E2 |
| Fuzzy indirect | ordered cutpoints | T1, T2, … |
By default, anchors_to_test = NULL means qcaERT uses all
anchors implied by each tested condition.
calib.test(
raw.data = LR,
calib.data = dat,
outcome = "SURV",
conditions = conditions,
calib_spec = calib_spec,
test.conditions = "DEV",
anchors_to_test = c("E1", "C1", "I1", "I2", "C2", "E2"),
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
)Raw columns can live on very different scales. A one-unit move may be tiny for an economic measure and huge for an index. For that reason, qcaERT can compute the threshold step automatically.
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
)With unit_step = NULL, qcaERT derives a step from the
threshold geometry for each condition.
unit_step_divisor = 10 means the function uses roughly
one-tenth of the relevant threshold spacing as the move size.
Use a manually supplied unit_step only when you have a
substantive reason to define the perturbation size yourself.
qcaERT supports the calibration-perturbation cases used in the ordinary QCA workflow:
For indirect calibration, qcaERT treats thresholds as ordered
cutpoints. Anchor names are positional: T1,
T2, and so on.
This scope is deliberate. calib.test() and
altset.test() perturb explicit calibration thresholds; they
are not general multi-value calibration robustness tools and they do not
rediscover thresholds during the perturbation search.
calib_spec_indirect <- list(
A = list(
raw = "A_raw",
type = "fuzzy",
method = "indirect",
thresholds = c(10, 20, 30, 40)
)
)The same output convention applies: results is the clean
table, diagnostics keeps the path detail, and
bounds summarizes the tested threshold bounds.
calib.test() asks: how far can one anchor move in one
direction before the monitored solution changes?
altset.test() asks: what happens across sampled
alternative analysis settings?
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$summaryThe same calibration specification is used in both functions. This is
intentional: the meaning of conditions,
test.conditions, and test.outcome should not
change from one sibling function to another.
altset_outcome <- altset.test(
raw.data = LR,
calib.data = dat,
outcome = outcome,
conditions = conditions,
calib_spec = calib_spec_outcome,
test.conditions = c("DEV", "URB"),
test.outcome = TRUE,
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 = 456,
solution = "all",
dir.exp = dir_exp,
progress = TRUE
)
as.data.frame(altset_outcome)If ggplot2 is installed, plot.calib_test()
can show interval, heatmap, and trace views.
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")Trace plots require an existing condition-anchor-direction path. If the requested path is unavailable, the error message lists the available paths.
See ?qcaERT_plots for the plotting API and
?qcaERT_conventions for the package-wide conventions.