Threshold-Sweep QCA (TS-QCA) is a framework for systematically
exploring how different threshold settings affect the results of
crisp-set Qualitative Comparative Analysis (QCA).
In crisp-set QCA, the researcher must choose thresholds to binarize:
Small changes in these thresholds may lead to substantial differences in truth tables and minimized solutions.
TS-QCA provides:
The TSQCA package implements four sweep methods:
| Method | What varies | What stays fixed | Purpose |
|---|---|---|---|
| CTS–QCA | One X threshold | Y + other Xs | Evaluate influence of a single condition |
| MCTS–QCA | Multiple X thresholds | Y | Explore combinations of X thresholds |
| OTS–QCA | Y threshold | All Xs | Assess robustness to Y calibration |
| DTS–QCA | X and Y thresholds | None | Full 2D sensitivity analysis |
Scope: This package focuses on sufficiency analysis—identifying condition combinations that are sufficient for an outcome. Necessity analysis (whether a condition is required for an outcome) involves different logical structures and evaluation metrics, and is planned for future versions.
TS-QCA assumes:
Example dataset structure:
library(TSQCA)
data("sample_data")
dat <- sample_data
str(dat)
#> 'data.frame': 80 obs. of 4 variables:
#> $ Y : int 8 4 5 7 2 2 7 5 3 8 ...
#> $ X1: int 7 2 6 8 8 9 8 8 1 5 ...
#> $ X2: int 7 5 8 4 0 5 8 4 4 2 ...
#> $ X3: int 1 6 6 5 3 4 5 3 6 8 ...Define outcome and conditions:
In real-world social science research, datasets often contain both binary variables (e.g., gender, yes/no responses) and continuous variables (e.g., sales, satisfaction scores). When using TSQCA with such mixed data, special attention is required.
sweep_listThe internal qca_bin() function uses the rule
x >= thr for binarization:
x = 0: 0 >= 1 → FALSE →
0 (preserved)x = 1: 1 >= 1 → TRUE →
1 (preserved)This ensures that binary variables remain unchanged during the binarization process.
Suppose your dataset has:
When using ctSweepM():
# CORRECT: Specify threshold explicitly for each variable
sweep_list <- list(
X1 = 1, # Binary variable: use threshold 1
X2 = 6:8, # Continuous: sweep thresholds
X3 = 6:8 # Continuous: sweep thresholds
)
res_mixed <- ctSweepM(
dat = dat,
Yvar = "Y",
Xvars = c("X1", "X2", "X3"),
sweep_list = sweep_list,
thrY = 7,
dir.exp = 1
)This explores 1 × 3 × 3 = 9 threshold combinations, treating X1 as a fixed binary condition while sweeping X2 and X3.
# WRONG: Using sweep range for binary variables
sweep_list <- list(
X1 = 6:8, # All values become 0 (since 0 < 6 and 1 < 6)
X2 = 6:8,
X3 = 6:8
)If you accidentally specify X1 = 6:8, both 0 and 1 will
fail the >= 6 condition, making all X1 values become 0.
This destroys the information in your binary variable.
Always examine your data structure before setting up threshold sweeps:
# Check variable ranges
summary(dat[, c("X1", "X2", "X3")])
# Identify binary variables (only 0 and 1)
sapply(dat[, c("X1", "X2", "X3")], function(x) {
unique_vals <- sort(unique(x))
if (length(unique_vals) == 2 && all(unique_vals == c(0, 1))) {
"Binary (use threshold = 1)"
} else {
paste("Continuous (range:", min(x), "-", max(x), ")")
}
})ctSweepS)CTS–QCA varies the threshold for one X condition, keeping the others fixed.
sweep_var <- "X3" # Condition (X) whose threshold is swept
sweep_range <- 6:9 # Candidate threshold values to evaluate
thrY <- 7 # Outcome (Y) threshold (fixed)
thrX_default <- 7 # Threshold for other X conditions (fixed)
res_cts <- ctSweepS(
dat = dat,
Yvar = "Y",
Xvars = c("X1", "X2", "X3"),
sweep_var = sweep_var,
sweep_range = sweep_range,
thrY = thrY,
thrX_default = thrX_default,
dir.exp = 1,
return_details = FALSE
)
head(res_cts)
#> threshold expression inclS covS
#> 1 6 No solution NA NA
#> 2 7 No solution NA NA
#> 3 8 No solution NA NA
#> 4 9 No solution NA NActSweepM)MCTS–QCA evaluates all combinations of thresholds for multiple X conditions.
res_mcts <- ctSweepM(
dat = dat,
Yvar = "Y",
Xvars = c("X1", "X2", "X3"),
sweep_vars = c("X2", "X3"),
sweep_range = 6:9,
thrY = 7,
thrX_default = 7,
dir.exp = 1,
return_details = FALSE
)
#> Error in ctSweepM(dat = dat, Yvar = "Y", Xvars = c("X1", "X2", "X3"), : unused arguments (sweep_vars = c("X2", "X3"), sweep_range = 6:9, thrX_default = 7)
head(res_mcts)
#> Error: object 'res_mcts' not foundotSweep)OTS–QCA varies only the threshold of Y, keeping X thresholds fixed.
res_ots <- otSweep(
dat = dat,
Yvar = "Y",
Xvars = c("X1", "X2", "X3"),
sweep_range = 6:9,
thrX = c(X1 = 7, X2 = 7, X3 = 7),
dir.exp = 1,
return_details = FALSE
)
head(res_ots)
#> thrY expression inclS covS
#> 1 6 No solution NA NA
#> 2 7 No solution NA NA
#> 3 8 No solution NA NA
#> 4 9 No solution NA NAdtSweep)DTS–QCA varies both X thresholds and Y thresholds, creating a full 2D grid.
sweep_list_dts_X <- list(
X1 = 6:8,
X2 = 6:8,
X3 = 6:8
)
sweep_range_dts_Y <- 6:8
res_dts <- dtSweep(
dat = dat,
Yvar = "Y",
Xvars = c("X1", "X2", "X3"),
sweep_list_X = sweep_list_dts_X,
sweep_range_Y = sweep_range_dts_Y,
dir.exp = 1,
return_details = FALSE
)
head(res_dts)
#> combo_id thrY thrX expression inclS covS
#> 1 1 6 X1=6, X2=6, X3=6 No solution NA NA
#> 2 1 7 X1=6, X2=6, X3=6 No solution NA NA
#> 3 1 8 X1=6, X2=6, X3=6 No solution NA NA
#> 4 2 6 X1=7, X2=6, X3=6 No solution NA NA
#> 5 2 7 X1=7, X2=6, X3=6 No solution NA NA
#> 6 2 8 X1=7, X2=6, X3=6 No solution NA NAEach sweep result contains:
inclS),covS).General guidance:
TSQCA provides a structured and reproducible way to evaluate
how threshold choices influence QCA results.
Using CTS, MCTS, OTS, and DTS sweeps, researchers can:
For more information on TS-QCA methodology, see:
sessionInfo()
#> R version 4.4.1 (2024-06-14 ucrt)
#> Platform: x86_64-w64-mingw32/x64
#> Running under: Windows 11 x64 (build 26200)
#>
#> Matrix products: default
#>
#>
#> locale:
#> [1] LC_COLLATE=C LC_CTYPE=Japanese_Japan.utf8
#> [3] LC_MONETARY=Japanese_Japan.utf8 LC_NUMERIC=C
#> [5] LC_TIME=Japanese_Japan.utf8
#>
#> time zone: Asia/Tokyo
#> tzcode source: internal
#>
#> attached base packages:
#> [1] stats graphics grDevices utils datasets methods base
#>
#> other attached packages:
#> [1] QCA_3.23 admisc_0.39 TSQCA_0.1.2
#>
#> loaded via a namespace (and not attached):
#> [1] cli_3.6.5 knitr_1.50 rlang_1.1.6 xfun_0.52
#> [5] otel_0.2.0 promises_1.5.0 shiny_1.12.1 jsonlite_2.0.0
#> [9] xtable_1.8-4 htmltools_0.5.9 httpuv_1.6.16 sass_0.4.10
#> [13] lpSolve_5.6.23 rmarkdown_2.29 evaluate_1.0.4 jquerylib_0.1.4
#> [17] fastmap_1.2.0 yaml_2.3.10 lifecycle_1.0.4 compiler_4.4.1
#> [21] Rcpp_1.1.0 rstudioapi_0.17.1 later_1.4.4 digest_0.6.39
#> [25] R6_2.6.1 magrittr_2.0.4 bslib_0.9.0 declared_0.25
#> [29] tools_4.4.1 mime_0.13 venn_1.12 cachem_1.1.0