ThSQCA Tutorial (English)

knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
options(width = 100)

Quick start (5 minutes)

ThSQCA runs a crisp-set QCA analysis many times, once for each threshold setting you specify, and collects the solutions in one table. This section gets you from zero to a first result. Later sections explain each step.

The examples use small simulated data sets, so that every number in this tutorial can be reproduced exactly. The first one mimics a marketing survey: three evaluation scores (0 to 10) and a loyalty score. The data-generating code is shown so that you can see how the structure was built.

make_demo_data <- function(n = 400, seed = 2026) {
  set.seed(seed)
  clip <- function(x) pmin(10, pmax(0, round(x)))
  QUA <- clip(rnorm(n, 6, 2))   # quality evaluation
  SER <- clip(rnorm(n, 6, 2))   # service evaluation
  ENV <- clip(rnorm(n, 6, 2))   # store environment evaluation
  # Loyalty is high when quality and service are both high, or (a little
  # less strongly) when quality and environment are both high.
  core <- pmax(pmin(QUA, SER), pmin(QUA, ENV) - 1)
  LOY  <- clip(core + rnorm(n, 0, 0.7))
  data.frame(LOY, QUA, SER, ENV)
}
demo <- make_demo_data()
head(demo)

Now run a sweep of the outcome threshold. We treat a case as a member of the outcome set when LOY >= t, and we let t take the values 5, 6, 7 and 8. The condition thresholds are held fixed at 7.

library(ThSQCA)

res <- otSweep(
  dat         = demo,
  outcome     = "LOY",
  conditions  = c("QUA", "SER", "ENV"),
  sweep_range = 5:8,                          # outcome thresholds to try
  thrX        = c(QUA = 7, SER = 7, ENV = 7)  # fixed condition thresholds
)
summary(res)

Read the table from top to bottom. Each row is one complete QCA analysis:

That is the whole idea: the sufficiency structure you report can depend on the target level of the outcome. A single-threshold analysis would show only one of these rows. Reporting all four rows shows where the structure is stable and where it changes.

The remaining sections show how to prepare your data, how to sweep the other thresholds, how to choose a solution type, how to read the output, and how to report the results.

What ThS-QCA does, and what it does not do

In crisp-set QCA, the outcome and each condition must be turned into 0/1 membership using a threshold. Threshold-Sweep QCA (ThS-QCA) treats these thresholds as an explicit analytical dimension instead of a fixed preprocessing input. It records the sufficiency solution obtained at each threshold setting.

ThSQCA implements four sweeps:

| Sweep | What varies | What stays fixed | Function | |-------|-------------|------------------|----------| | CTS (single) | One condition threshold | Outcome threshold and the other conditions | ctSweepS() | | CTS (multiple) | Several condition thresholds (a grid) | Outcome threshold | ctSweepM() | | OTS | Outcome threshold | All condition thresholds | otSweep() | | DTS | Outcome threshold and condition thresholds | Nothing | dtSweep() |

What ThSQCA does not do. It does not reimplement QCA. For every threshold setting, it binarizes the data and then calls QCA::truthTable() and QCA::minimize(). ThSQCA only loops over the settings, collects the results, and reports them. Section "Reporting your results" shows how to check any single cell directly against the QCA package.

Scope. The sweeps address sufficiency: which combinations of conditions are sufficient for the outcome at each threshold setting.

Tip: The Sweep Builder web tool generates ready-to-run ThSQCA code from your variable names and thresholds.

Preparing your data

ThS-QCA works on raw scores and binarizes them itself, so no calibration step is needed before a sweep.

Binary (0/1) variables

If a condition is already binary (for example an indicator for a customer segment), do not sweep it. Give it the threshold 1, which leaves 0 as 0 and 1 as 1. Any larger threshold would turn every value into 0 and destroy the variable.

# X1 is binary (0/1); X2 and X3 are 0-10 scores.
res_mixed <- ctSweepM(
  dat        = dat,
  outcome    = "Y",
  conditions = c("X1", "X2", "X3"),
  sweep_list = list(X1 = 1,      # binary: fixed threshold 1, not swept
                    X2 = 6:8,    # scores: swept
                    X3 = 6:8),
  thrY       = 7
)

This explores 1 x 3 x 3 = 9 threshold combinations. A quick way to spot binary variables before you set up a sweep:

sapply(dat[, c("X1", "X2", "X3")], function(x) all(x %in% c(0, 1)))

Conditions that are already fuzzy sets (pre_calibrated)

Sometimes a condition has a theoretically grounded fuzzy calibration that you do not want to sweep. List it in pre_calibrated. Such a variable is passed to QCA::truthTable() as it is, without binarization, and needs no entry in thrX. The other conditions are still binarized at their thresholds.

d <- demo
d$ENV_fz <- pmin(1, pmax(0, (d$ENV - 2.5) / 6))   # example calibration (0 to 1)

res_mix <- otSweep(
  dat            = d,
  outcome        = "LOY",
  conditions     = c("QUA", "SER", "ENV_fz"),
  sweep_range    = 5:8,
  thrX           = c(QUA = 7, SER = 7),           # no entry for ENV_fz
  pre_calibrated = "ENV_fz"
)

Points to remember:

Sweeping membership scores

Membership scores can be swept like any other numeric variable. Leave them out of pre_calibrated and give thresholds on the 0 to 1 scale. A case is then a member when its membership score is at least the threshold, so the sweep asks how the sufficiency structure changes as the membership criterion becomes stricter (for example 0.4, 0.6, 0.8). This applies to the conditions and to the outcome alike.

fz <- function(x) pmin(1, pmax(0, (x - 2.5) / 6))   # example calibration (0 to 1)
d_fz <- data.frame(LOY_fz = fz(demo$LOY), QUA_fz = fz(demo$QUA),
                   SER_fz = fz(demo$SER), ENV_fz = fz(demo$ENV))

res_fz <- otSweep(
  dat         = d_fz,
  outcome     = "LOY_fz",
  conditions  = c("QUA_fz", "SER_fz", "ENV_fz"),
  sweep_range = c(0.4, 0.6, 0.8),                    # outcome membership criteria
  thrX        = c(QUA_fz = 0.6, SER_fz = 0.6, ENV_fz = 0.6)
)

Which approach to use is a design decision, and both are defensible:

The four sweeps

Each subsection follows the same pattern: when to use the sweep, a minimal call, and how to read the result. All calls use the demo data from the quick start. The compute chunks hide the console output so that only the summary table is shown.

OTS: sweep the outcome threshold (otSweep)

Use it when the outcome is measured on a graded scale and you want to know how the solution changes as the target level of the outcome rises. The quick start already showed this sweep. The call is repeated here with the two optional arguments you will use most often, incl.cut and n.cut.

res_ots <- otSweep(
  dat         = demo,
  outcome     = "LOY",
  conditions  = c("QUA", "SER", "ENV"),
  sweep_range = 5:8,
  thrX        = c(QUA = 7, SER = 7, ENV = 7),
  incl.cut    = 0.8,   # consistency cutoff for the truth table
  n.cut       = 1      # minimum number of cases per configuration
)
summary(res_ots)

How to read it. The solution changes between LOY >= 6 and LOY >= 7 (two configurations become one), and it disappears at LOY >= 8. In the vocabulary of the method, the sweep has a threshold transition between 6 and 7, and a further one between 7 and 8.

By default otSweep() returns the complex solution (include = ""). In this data set all eight combinations of the three conditions are observed, so the complex, parsimonious and intermediate solutions coincide. The choice of solution type matters when some combinations are unobserved; the section "Choosing a solution type" below uses a second data set for that.

CTS (single): sweep one condition threshold (ctSweepS)

Use it when you want to know how sensitive the solution is to a single calibration decision, such as "what counts as a high service evaluation".

res_cts <- ctSweepS(
  dat          = demo,
  outcome      = "LOY",
  conditions   = c("QUA", "SER", "ENV"),
  sweep_var    = "SER",   # the condition whose threshold is swept
  sweep_range  = 5:9,     # candidate thresholds for SER
  thrY         = 7,       # fixed outcome threshold (LOY >= 7)
  thrX_default = 7        # fixed threshold for the other conditions
)
summary(res_cts)

How to read it.

sum(demo$QUA >= 7 & demo$SER >= 9)

CTS (multiple): sweep several condition thresholds (ctSweepM)

Use it when you want to explore the joint space of several calibration decisions at once. The function evaluates every combination of the candidate thresholds you supply, so the number of cells grows quickly (here 3 x 3 x 3 = 27).

res_mcts <- ctSweepM(
  dat        = demo,
  outcome    = "LOY",
  conditions = c("QUA", "SER", "ENV"),
  sweep_list = list(QUA = 6:8, SER = 6:8, ENV = 6:8),  # candidates per condition
  thrY       = 7                                        # fixed outcome threshold
)
summary(res_mcts)

How to read it. With 27 rows, look for regions rather than single rows.

DTS: sweep outcome and condition thresholds together (dtSweep)

Use it when you want the fullest picture: the target level of the outcome and the criteria for the conditions vary at the same time. The result is a two-dimensional map, with one row per combination.

res_dts <- dtSweep(
  dat           = demo,
  outcome       = "LOY",
  conditions    = c("QUA", "SER", "ENV"),
  sweep_list_X  = list(QUA = 6:7, SER = 6:7, ENV = 6:7),  # condition candidates
  sweep_range_Y = 6:8                                     # outcome candidates
)
summary(res_dts)

How to read it. There are 24 cells (8 condition combinations by 3 outcome thresholds).

In this data set, the higher the target level of the outcome, the stricter the criteria for the conditions had to be before a configuration was consistent enough. To look for solutions at LOY >= 8, you could extend the condition thresholds upward (for example QUA and SER in 8:9).

Choosing a solution type

QCA can minimize the truth table in three ways, which differ in how they treat logical remainders: combinations of conditions that do not occur in the data. All sweep functions accept the same two arguments as QCA::minimize().

| Solution type | include | dir.exp | Logical remainders | |---------------|-----------|-----------|--------------------| | Complex (default) | "" | NULL | Not used | | Parsimonious | "?" | NULL | Any remainder may be used if it shortens the formula | | Intermediate | "?" | c(1, 1, ...) | Only remainders consistent with your directional expectations |

dir.exp states, for each condition, whether its presence (1) or absence (0) is expected to contribute to the outcome.

The first data set has no remainders, so the three types give the same answer. To see the difference we need data in which some combinations are missing. The second data set is again simulated. It describes 60 business-to-business software accounts: RNW is renewal intention, and TRU (trust in the vendor), PRC (price fairness) and SUP (support quality) are the conditions, all on 0 to 10 scales. The three conditions share a common factor, so that accounts tend to be high on all of them or low on all of them, and some combinations do not occur.

make_demo_data2 <- function(n = 60, seed = 89, rho = 0.85) {
  set.seed(seed)
  clip <- function(x) pmin(10, pmax(0, round(x)))
  L   <- rnorm(n)                                   # common factor
  mk  <- function() clip(6 + 2 * (rho * L + sqrt(1 - rho^2) * rnorm(n)))
  TRU <- mk(); PRC <- mk(); SUP <- mk()
  # Renewal is high when trust is high and either price or support is high.
  RNW <- clip(pmin(TRU, pmax(PRC, SUP)) + rnorm(n, 0, 0.7))
  data.frame(RNW, TRU, PRC, SUP)
}
demo2 <- make_demo_data2()
head(demo2)

The truth table has remainders

Take the target RNW >= 5 and the criterion 7 or more for every condition. The truth table, built directly with the QCA package, shows which of the eight combinations occur.

library(QCA)
bin2 <- data.frame(
  RNW = as.integer(demo2$RNW >= 5),
  TRU = as.integer(demo2$TRU >= 7),
  PRC = as.integer(demo2$PRC >= 7),
  SUP = as.integer(demo2$SUP >= 7)
)
tt2 <- truthTable(bin2, outcome = "RNW", conditions = c("TRU", "PRC", "SUP"),
                  incl.cut = 0.8, n.cut = 1, show.cases = FALSE)
tt2

Six rows are listed. Rows 3 (TRU PRC SUP = 0 1 0) and 5 (1 0 0) are missing because no account has that combination: they are the logical remainders. Five of the observed rows meet the consistency cutoff (OUT = 1), and only the combination low on all three conditions (row 1) does not.

Three solutions from the same data

Run the same OTS sweep three times, changing only include and dir.exp.

thr2  <- c(TRU = 7, PRC = 7, SUP = 7)
cond2 <- c("TRU", "PRC", "SUP")
run2  <- function(...) {
  otSweep(dat = demo2, outcome = "RNW", conditions = cond2, sweep_range = 5:8,
          thrX = thr2, incl.cut = 0.8, n.cut = 1, ...)
}

res_cx <- run2()                                     # complex
res_ps <- run2(include = "?")                        # parsimonious
res_im <- run2(include = "?", dir.exp = c(1, 1, 1))  # intermediate

data.frame(
  thrY         = 5:8,
  complex      = res_cx$summary$expression,
  parsimonious = res_ps$summary$expression,
  intermediate = res_im$summary$expression
)

(The chunk hides one warning, which the next section explains.)

How to read the comparison:

Which solution type should I report?

| Solution | When it fits | Strength | Caution | |----------|--------------|----------|---------| | Complex | Exploration; you want no assumptions about unobserved cases | Every claim rests on observed cases | Formulas can be long | | Parsimonious | Checking which conditions survive maximal simplification | Shortest formulas | Relies on remainders that may be implausible | | Intermediate | Theory-driven reporting | Uses only plausible remainders | Needs a justified dir.exp |

Ragin (2008) recommends the intermediate solution for reporting, together with the parsimonious solution to show which conditions are core. Whichever you choose, state the solution type, incl.cut, n.cut and dir.exp in your methods section. Because the sweep repeats the analysis at every threshold, the chosen type applies to all rows.

Multiple equivalent solutions

Look again at the parsimonious result at RNW >= 5: the table showed TRU + SUP, but the n_solutions column of the sweep summary reads 2. The shortening of TRU*PRC can go two ways: to TRU (assuming row 5 is sufficient) or to PRC (assuming the other remainder, PRC alone, is sufficient). Both choices fit the observed data equally well, so QCA returns two equivalent models. Sweeps signal this with a warning and with the n_solutions column.

extract_mode

The argument extract_mode controls what the sweep table shows.

| extract_mode | What the expression column contains | |----------------|----------------------------------------| | "first" (default) | Model M1 only. Check n_solutions to see whether others exist. | | "all" | All models, for example M1: ...; M2: ... | | "essential" | Terms common to all models, plus extra columns for the rest |

res_all <- run2(include = "?", extract_mode = "all")
summary(res_all)

At RNW >= 5 the two models are TRU + SUP and PRC + SUP. The warning printed above says that fit measures are those of M1. Here M2 has the same values (inclS 0.967, covS 0.690), but in general you should check each model.

res_ess <- run2(include = "?", extract_mode = "essential")
res_ess$summary[, c("thrY", "expression", "selective_terms", "unique_terms", "n_solutions")]

Essential, selective and unique terms

| Type | Definition | In this example (RNW >= 5) | |------|------------|------------------------------| | Essential prime implicants | Present in every model | SUP | | Selective prime implicants | Present in some but not all models | TRU, PRC | | Unique terms | Present in only one model | M1: TRU; M2: PRC |

A careful report says that SUP appears in every equivalent model, and that the second term is TRU or PRC, depending on which unobserved combination is assumed sufficient. Two cautions:

For the details of every model, write the full report:

generate_report(res_all, "ots_multiple_report.md", dat = demo2, format = "full")

In the full report, the section for RNW >= 5 lists the number of solutions, both models, the essential and selective terms, the unique terms and the raw QCA output. Its per-term table and configuration chart follow M1, and a note says so.

Understanding the output

Each sweep returns an object with three parts:

names(res_ots)

The columns of the summary table are:

| Column | Meaning | |--------|---------| | thrY, threshold, thrX | The threshold setting for that row | | expression | The minimized solution (* is AND, + is OR, ~ is negation) | | inclS | Solution consistency: how consistently the cases covered by the solution are also members of the outcome set | | covS | Solution coverage: the share of the outcome set that the solution accounts for | | n_solutions | Number of equivalent minimal solutions found |

Four points help avoid common misreadings.

  1. Consistency comes first. A consistency of at least 0.8 is a common minimum for calling a configuration sufficient, and it is the default cutoff you set with incl.cut. Coverage says how much of the outcome set is accounted for. It does not rank solutions; use it to describe how much of the outcome the solution explains.
  2. Stable versus changing. Compare expression across rows. Rows with the same expression form a stable region. A change of expression between adjacent rows marks a threshold transition. A solution that changes with small threshold shifts is less robust.
  3. "No solution" is a technical result. It means that, under the analysis settings you chose (consistency cutoff, frequency cutoff and the thresholds of that row), no configuration met the sufficiency criterion. It does not mean that no route to the outcome exists.
  4. Coverage is relative to a changing set. When the outcome threshold rises, the outcome set shrinks, so covS at LOY >= 5 and covS at LOY >= 7 are proportions of different sets. A larger covS in a later row does not mean that the solution has become "better". Likewise, covS is not the share of customers or cases that a solution "captures" in the population.

Changing a threshold changes which cases belong to the sets, so the sufficiency structure and its fit change with it. These tables describe that dependence. They do not show that manipulating a condition would change the outcome.

Negated outcomes

QCA usually analyses what is sufficient for the presence of the outcome. The absence of the outcome can be analysed too, for example the combinations sufficient for low loyalty or for non-renewal. Prefix the outcome with a tilde:

# Presence: cases with LOY >= threshold
res_pos <- otSweep(dat = demo, outcome = "LOY",  conditions = c("QUA", "SER", "ENV"),
                   sweep_range = 4:6, thrX = c(QUA = 7, SER = 7, ENV = 7))

# Absence: cases with LOY < threshold
res_neg <- otSweep(dat = demo, outcome = "~LOY", conditions = c("QUA", "SER", "ENV"),
                   sweep_range = 4:6, thrX = c(QUA = 7, SER = 7, ENV = 7))

In the solution of a ~ analysis, ~QUA means that quality is below its threshold. The formulas for LOY and ~LOY are not mirror images of each other, so both need to be analysed and interpreted on their own.

All four sweep functions accept a ~ outcome. The stored settings record it:

res_neg$params$negate_outcome   # TRUE
res_neg$params$outcome          # "~LOY"

Reporting your results

A sweep yields many rows, so decide in advance which parts go into the main text and which into a supplement.

What to report

Report enough for a reader to reproduce every row.

c(R = R.version.string,
  QCA = as.character(packageVersion("QCA")),
  ThSQCA = as.character(packageVersion("ThSQCA")))

Generating the report

generate_report() writes a Markdown file. The "simple" format suits a main text or an appendix. The "full" format includes truth tables and fit measures for every setting, essential and selective terms, and configuration charts, and suits supplementary material.

generate_report(res_ots, "ots_report_simple.md", dat = demo, format = "simple")
generate_report(res_ots, "ots_report_full.md",   dat = demo, format = "full")

Solution formulas in the report use your own outcome name (for example -> LOY). Optional arguments control what is included:

generate_report(res_ots, "r.md", dat = demo, include_chart = FALSE)         # no charts
generate_report(res_ots, "r.md", dat = demo, chart_symbol_set = "latex")    # LaTeX symbols
generate_report(res_ots, "r.md", dat = demo, include_raw_output = FALSE)    # omit QCA output

The report also ends with a short snippet of QCA code that reproduces a cell directly, which is the subject of the next subsection.

Configuration charts

Reports contain Fiss-style configuration charts (conditions in rows, solution terms in columns). The chart functions can also be used on their own, starting from path strings:

paths <- c("A*B*~C", "A*D", "B*E")
cat(config_chart_from_paths(paths))

Symbol sets are "unicode" (default), "ascii" for maximum compatibility, and "latex" for PDF output ($\bullet$ for presence, $\otimes$ for absence).

cat(config_chart_from_paths(paths, symbol_set = "ascii"))

When a threshold has several equivalent solutions, a chart with one block per solution is available:

solutions <- list(c("A*B", "C*D"), c("A*B", "C*E"))
cat(config_chart_multi_solutions(solutions))

Check a cell directly with QCA

Because ThSQCA calls the QCA package for every cell, any row can be reproduced with QCA alone. The example reproduces the row LOY >= 7 of the OTS sweep, in which all thresholds are 7.

bin <- function(x, t) as.integer(x >= t)
d7 <- data.frame(
  LOY = bin(demo$LOY, 7), QUA = bin(demo$QUA, 7),
  SER = bin(demo$SER, 7), ENV = bin(demo$ENV, 7)
)
tt  <- truthTable(d7, outcome = "LOY", conditions = c("QUA", "SER", "ENV"),
                  incl.cut = 0.8, n.cut = 1, show.cases = FALSE)
sol <- minimize(tt)
sol

The solution matches the LOY >= 7 row of the sweep (QUA*SER). Before you publish, do this for at least the rows you discuss in the text, and compare the formulas, the inclS and covS values and the number of models (M1, M2, ...). For solution types other than complex, pass the same include and dir.exp to minimize().

Example wording

The following paragraph illustrates a careful description of the OTS result. It is written for the simulated data and should be adapted to your study.

We examined how the sufficient configurations for high loyalty change with the outcome threshold (LOY >= 5 to 8), holding the condition thresholds at 7 (consistency cutoff 0.8, frequency cutoff 1, complex solution). At LOY >= 5 and LOY >= 6, two configurations met the criterion: quality together with service, and quality together with store environment. At LOY >= 7, only the first remained. At LOY >= 8, no configuration met the consistency cutoff under these settings. These patterns describe how the sufficiency structure depends on the definition of the outcome; they are not evidence that improving any condition would raise loyalty.

Some habits make the wording safer:

Core and peripheral conditions (Fiss, 2011)

Fiss (2011) refined QCA configuration tables by distinguishing two kinds of condition within a configuration (a term of the intermediate solution):

| Type | Definition | Symbol | |------|------------|--------| | Core condition | Also part of the parsimonious solution | large symbol | | Peripheral condition | Eliminated in the parsimonious solution, so it appears in the intermediate solution only | small symbol |

A condition that survives even the most aggressive simplification (the parsimonious solution) rests on stronger evidence than one that appears only once theoretical expectations are applied. The four-symbol set is:

● = core condition present       ⊗ = core condition absent
⊙ = peripheral condition present ⊘ = peripheral condition absent
(blank) = the condition does not matter

How ThSQCA applies the definition. In Fiss's own solution tables the classification is made configuration by configuration: solutions are grouped by their core conditions, and the same condition can be core in one configuration and peripheral in another. compute_fiss_core() follows this. For each term of the intermediate solution it looks for the parsimonious terms contained in it (every condition of the parsimonious term has the same status in the intermediate term). The conditions of those parsimonious terms are core; the other conditions of the term are peripheral. With the parsimonious solution ~A*E + A*B, for example, the intermediate term ~A*~B*C*E contains ~A*E, so ~A and E are core, while ~B and C are peripheral even though B appears in the other parsimonious term.

Two situations are not covered by Fiss (2011). ThSQCA treats both conservatively and says so in a warning:

If you want to report core and peripheral conditions relative to one particular parsimonious solution, state that choice in the text.

For LaTeX output the symbols are $\bullet$, $\otimes$, $\odot$ and $\oslash$.

compute_fiss_core() works on results of otSweep() and ctSweepS() (results of ctSweepM() and dtSweep() are not supported yet). It needs an intermediate-solution sweep with the logical remainders enabled and the details stored:

  1. include = "?",
  2. dir.exp specified,
  3. return_details = TRUE (the default).
res_i <- otSweep(
  dat         = demo,
  outcome     = "LOY",
  conditions  = c("QUA", "SER", "ENV"),
  sweep_range = 6:8,
  thrX        = c(QUA = 7, SER = 7, ENV = 7),
  include     = "?",
  dir.exp     = c(1, 1, 1)
)

# For every threshold, compare each term of the stored intermediate solution
# with the parsimonious term(s) contained in it.
res_fiss <- compute_fiss_core(res_i, conditions = c("QUA", "SER", "ENV"))

print_fiss_summary(res_fiss, thr_key = "7")           # one threshold
cat(generate_fiss_chart(res_fiss, symbol_set = "unicode"))
cat(generate_fiss_chart(res_fiss, symbol_set = "latex"))

generate_report(res_fiss, "fiss_report.md", dat = demo, format = "full",
                include_fiss_core = TRUE)

print_fiss_summary() lists, term by term, which conditions are core and which are peripheral. When the parsimonious and intermediate solutions are identical, every condition is core and none is peripheral. For the second data set at RNW >= 6, the parsimonious solution is TRU + ~PRC*SUP and the intermediate solution TRU*PRC + ~PRC*SUP: in TRU*PRC, TRU is core and PRC peripheral; in ~PRC*SUP, both conditions are core.

Practical tips and frequently asked questions

Start small, then expand. Test a call with one threshold, then a short range, and only then the full grid. The number of QCA analyses grows quickly:

| Function | Number of analyses | Example | |----------|--------------------|---------| | otSweep() | one per outcome threshold | 5 thresholds: 5 | | ctSweepS() | one per threshold of the swept condition | 5 thresholds: 5 | | ctSweepM() | product of the candidate counts | 3 x 3 x 3: 27 | | dtSweep() | outcome thresholds x condition grid | 3 x (3 x 3 x 3): 81 |

A grid with five conditions and five candidates each has 3,125 cells; reduce the number of swept conditions first.

Why do I see a solution at one threshold but "No solution" at the next? Consistency depends on how the cases are classified. Near a threshold where several cases change membership at once, a configuration can drop below the consistency cutoff. Check the truth table of that row in the full report.

A solution appears only at the edge of my range. Look at the number of cases behind it (see the SER >= 9 example above). Solutions built on very few cases are fragile.

The solution changes a lot across thresholds. What do I report? Report the whole sweep, and say where the structure is stable and where it changes. If you also give one headline result, choose its thresholds on substantive grounds before looking at the sweep, and state that the results are threshold-sensitive.

The sweep prints a warning about multiple solutions. Equivalent models exist at one or more thresholds; see "Multiple equivalent solutions". Use extract_mode = "all" or "essential" and generate_report().

Where to find help. Questions and bug reports are welcome at https://github.com/im-research-yt/ThSQCA/issues.

Conclusion

ThSQCA makes the threshold choices of a crisp-set QCA explicit. With the CTS, OTS and DTS sweeps you can see where a sufficiency structure is stable, where it changes, and where it disappears, and you can report this in a reproducible way. Because every cell is an ordinary QCA analysis, each result can be checked against the QCA package.

References

Session info

sessionInfo()


Try the ThSQCA package in your browser

Any scripts or data that you put into this service are public.

ThSQCA documentation built on Sept. 26, 2026, 5:07 p.m.