--- title: "Getting started with qcaERT" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with qcaERT} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", eval = FALSE ) ``` 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`. ## Start from a QCA analysis The usual workflow still begins with `QCA`. In this example, the `LR` data are calibrated, minimized, and then passed to qcaERT. ```{r setup-lr} 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. ## Choose a robustness test 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()` | ## Test inclusion and frequency cutoffs `incl.test()` and `ncut.test()` are the simplest robustness tests. They move one truth table cutoff below and above the baseline value. ```{r incl-ncut} 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. ## Test calibration thresholds `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. ```{r calib} 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$bounds ``` `conditions` 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`. ```{r calib-outcome} 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 ) ``` ## Test cases and samples `loo.test()` removes cases one at a time. `subsample.test()` runs repeated subsamples. ```{r cases-samples} 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$summary ``` ## Test alternative analysis sets `altset.test()` samples alternative analysis settings. It can vary calibration thresholds, `incl.cut`, and `n.cut` in the same run. ```{r altsets} 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$summary ``` Use `calib.test()` when you want directional threshold bounds. Use `altset.test()` when you want sampled compound perturbations. ## Compare theory specifications `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"`. ```{r theory} 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 ``` The 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. ## Test clusters `cluster.test()` starts from an existing truth table and compares selected configurations across groups. ```{r clusters} 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$units ``` `cluster.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`. ## Plot selected results Plotting is optional and requires `ggplot2`. ```{r plots} 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`. ## Next 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.