Computational Provenance & Reproducibility Record

RAISINS - R and AI Solutions for INferential Statistics · Online Statistical Analysis Platform for Agricultural Research

Computational Provenance & Reproducibility Record Fuzzy-set Qualitative Comparative Analysis (fsQCA) · 1.0.0 · DOI 10.5281/zenodo.23937009

Computational Provenance & Reproducibility Record

RAISINS · Fuzzy-set Qualitative Comparative Analysis (fsQCA) Module

This Computational Provenance Record documents the statistical computing environment, software dependencies, computational provenance, and bibliographic references associated with the RAISINS Fuzzy-set Qualitative Comparative Analysis (fsQCA) module. It is intended to support computational reproducibility and software transparency. Detailed statistical methodology, mathematical derivations, and user guidance are provided separately in the official module documentation.

Any issues or updates required please comment here

Go to the App from here

1 Module Metadata

Parameter Specification
Module Fuzzy-set Qualitative Comparative Analysis (fsQCA)
Module Version 1.0.0
DOI 10.5281/zenodo.23937009
Document Type Computational workflow
Statistical Engine R
R Version 4.5.2
Reproducibility Execution Environment: Posit Connect · GCR · renv-locked

2 Statistical Dependency Manifest

Package Version Repository Core Statistical Functions
stats 4.5.2 Base R quantile(), complete.cases()
QCA 3.25 CRAN calibrate(), truthTable(), minimize()
admisc 0.41 CRAN compute()
callr 3.8.0 CRAN r() (time-limited background minimization)

3 Statistical Function Registry

Analytical Role Primary Function(s)
Anchor suggestion from the data stats::quantile() (5th / 50th / 95th percentiles by default)
Direct calibration, logistic (default) QCA::calibrate(x, type = "fuzzy", method = "direct", thresholds = c(e =, c =, i =), logistic = TRUE)
Direct calibration, linear QCA::calibrate(..., logistic = FALSE)
Crisp calibration QCA::calibrate(x, type = "crisp", thresholds = c)
Necessity: consistency, coverage, relevance (RoN) Closed-form fuzzy-set formulas shown below; identical to QCA::pof(relation = "necessity")
Truth table (outcome and its absence) QCA::truthTable(incl.cut =, n.cut =, pri.cut =, complete = TRUE, sort.by = "incl, n")
Conservative solution QCA::minimize(TT, include = "1")
Parsimonious solution and simplifying assumptions QCA::minimize(TT, include = "?"), $SA
Intermediate solution and easy counterfactuals QCA::minimize(TT, include = "?", dir.exp =), $i.sol, $EC
Membership in a solution or path admisc::compute(expression, data = memberships)
Large minimizations (8+ conditions or 64+ sufficient rows) callr::r(QCA::minimize, timeout = 60) in a separate R process

4 Default Methods & Parameters

Analysis Step / Parameter Default Method / Value
Input data Layout One row per case: a case ID column, one raw numeric column per condition and one for the outcome
Missing values Only cases complete on every selected column are analysed; dropped case IDs are reported
Calibration
(QCA::calibrate())
Method Direct, logistic (alternatives: direct linear; crisp with one threshold c; already calibrated 0–1). Columns already within [0, 1] default to already calibrated
Anchors Full exclusion e, crossover c, full inclusion i; suggested from the 5th, 50th and 95th percentiles (alternatives: 10/50/90, 25/50/75, minimum/midpoint/maximum) and always editable
Anchor rounding One decimal more than the data carry (at most 4). A crossover equal to an observed value is moved up by one step, so no case is placed at 0.5 by the anchor itself; the order e < c < i is enforced
Membership scores Rounded to 2 decimal places after calibration
Calibration check Cases at exactly 0.5 flagged as Ambiguous; a set with fewer than 20% or more than 80% of cases above 0.5 flagged as Skewed
Necessity Fit measures Consistency = Σmin(X, Y) / ΣY; Coverage = Σmin(X, Y) / ΣX; RoN = Σ(1 − X) / Σ(1 − min(X, Y)), for every condition and its negation, against the outcome and its absence
Thresholds Consistency ≥ 0.90, Coverage ≥ 0.60, RoN ≥ 0.60 (user-adjustable)
Verdict Necessary when all three thresholds are met; Possibly trivial when only consistency is met; otherwise Not necessary
Truth table
(QCA::truthTable())
Consistency cut-off incl.cut = 0.80 (user-adjustable)
Frequency cut-off n.cut = 1 case; rows below it are treated as logical remainders
PRI cut-off pri.cut = 0 (not used). Sufficient rows with PRI < 0.5 are flagged as simultaneous subset relations
Solutions
(QCA::minimize())
Types Conservative (include = "1"), parsimonious (include = "?") and intermediate (include = "?" with dir.exp)
Directional expectations Present for every condition by default (selectable: present, absent, none)
Reported model Intermediate if available, otherwise conservative, otherwise parsimonious; up to 10 equivalent models shown
Safety limits Sufficient rows limited to 160 (64 with 8 conditions, 32 with 9 or more); intermediate solutions not computed beyond 12 conservative × parsimonious model pairs; large problems stopped after 60 seconds
Core and peripheral conditions Classification A condition in an intermediate path is core when it also appears in a parsimonious term contained in that path, otherwise peripheral (Fiss, 2011)
Sufficiency X-Y plot Case types Typical (X > 0.5, Y > 0.5), Deviant for consistency (X > 0.5, Y < 0.5), Uncovered (X < 0.5, Y > 0.5), Irrelevant (X < 0.5, Y < 0.5) (Schneider & Rohlfing, 2013)
Absence of the outcome Analysis Truth table for ~Y with the same cut-offs; conservative and parsimonious solutions; remainders used as simplifying assumptions for both Y and ~Y reported as contradictory
Robustness Consistency cut-off Reported model recomputed at 0.75, 0.80, 0.85, 0.90 (user-editable list) and compared with the main solution
Calibration One set recalibrated with user-supplied alternative anchors; cases that cross 0.5 and the resulting solution are compared with the main analysis
Rounding Display 2 decimal places default (user-adjustable, 0–6); all computations use the unrounded fit values

5 R Code for Key Analytical Steps

The code blocks below illustrate each analytical step on the Lipset data shipped with the QCA package: 18 European countries between the two World Wars, with five conditions (DEV development, URB urbanization, LIT literacy, IND industrialization, STB government stability) and the outcome SURV (survival of democracy). LR holds the raw values and LF the published fuzzy-set version (Dusa, 2019). The comments give the values these calls return with QCA 3.25.

5.1 Calibration and Calibration Checks

library(QCA)
data(LR)                                         # raw Lipset data, 18 countries

# Suggested anchors: 5th, 50th and 95th percentiles (the module default)
anchors <- function(x) {
  a <- unname(quantile(x, c(0.05, 0.50, 0.95)))
  digits <- min(4, max(nchar(sub("^[^.]*\\.?", "", as.character(x)))) + 1)
  a <- round(a, digits)
  if (any(x == a[2])) a[2] <- a[2] + 10^(-digits)   # crossover never equals a case
  c(e = a[1], c = a[2], i = a[3])
}

sets <- c("DEV", "URB", "LIT", "IND", "STB", "SURV")
calibrated <- as.data.frame(lapply(sets, function(s)
  calibrate(LR[[s]], type = "fuzzy", method = "direct",
            thresholds = anchors(LR[[s]]), logistic = TRUE)),
  col.names = sets, row.names = rownames(LR))
calibrated <- round(calibrated, 2)               # membership scores, 2 decimals

anchors(LR$DEV)                                  # e = 329.4, c = 588, i = 1047

# Calibration check: cases above 0.5 and cases at exactly 0.5
data.frame(
  Set       = sets,
  In_pct    = round(100 * colMeans(calibrated > 0.5), 1),
  Ambiguous = sapply(calibrated, function(m) paste(rownames(calibrated)[m == 0.5], collapse = ", "))
)                                                # DEV: FI;  LIT: EE, IE  -> adjust c

With the suggested anchors, three cases sit at exactly 0.5. The module flags them as Ambiguous, and the anchors should then be revised on substantive grounds. The remaining steps use the published calibration LF, in which no case is at 0.5.

5.2 Necessity Analysis

data(LF)                                         # published fuzzy-set Lipset data
conditions <- c("DEV", "URB", "LIT", "IND", "STB")
Y <- LF$SURV

necessity_fit <- function(X, Y) {
  m <- pmin(X, Y)
  c(Consistency = sum(m) / sum(Y),
    Coverage    = sum(m) / sum(X),
    RoN         = sum(1 - X) / sum(1 - m))
}

necessity <- rbind(
  t(sapply(conditions, function(cn) necessity_fit(LF[[cn]], Y))),
  t(sapply(conditions, function(cn) necessity_fit(1 - LF[[cn]], Y))))
rownames(necessity) <- c(conditions, paste0("~", conditions))
round(necessity, 3)                              # STB: 0.920, 0.707, 0.680
                                                 # LIT: 0.991, 0.643, 0.509

# Verdict with the default thresholds 0.90 / 0.60 / 0.60
ifelse(necessity[, "Consistency"] >= 0.90 & necessity[, "Coverage"] >= 0.60 &
         necessity[, "RoN"] >= 0.60, "Necessary",
       ifelse(necessity[, "Consistency"] >= 0.90, "Possibly trivial", "Not necessary"))
                                                 # STB: "Necessary"
                                                 # LIT: "Possibly trivial" (RoN < 0.60)

pof(LF[, conditions], Y, relation = "necessity") # same consistency, coverage and RoN

5.3 Truth Table

TT <- truthTable(LF, outcome = "SURV", conditions = conditions,
                 incl.cut = 0.80, n.cut = 1, pri.cut = 0,
                 complete = TRUE, show.cases = TRUE, sort.by = "incl, n")
TT
# 9 of 32 configurations are observed; 23 are logical remainders.
# Row 32 (DEV*URB*LIT*IND*STB; BE, CZ, NL, UK): incl = 0.904, PRI = 0.886 -> OUT = 1
# Row 22 (DEV*~URB*LIT*~IND*STB; FI, IE):       incl = 0.804, PRI = 0.719 -> OUT = 1
# Row 24 (FR, SE):                              incl = 0.709              -> OUT = 0

5.4 Conservative, Parsimonious and Intermediate Solutions

# Conservative: no logical remainders
sc <- minimize(TT, include = "1", details = TRUE, show.cases = TRUE)
sc   # DEV*URB*LIT*IND*STB + DEV*~URB*LIT*~IND*STB -> SURV  (cons 0.870, cov 0.658)

# Parsimonious: every helpful remainder; $SA lists the simplifying assumptions
sp <- minimize(TT, include = "?", details = TRUE, show.cases = TRUE)
sp   # DEV*~IND + URB*STB -> SURV                          (cons 0.850, cov 0.714)
sp$SA$M1                                         # 12 remainder rows

# Intermediate: easy counterfactuals only (all conditions expected "present")
si <- minimize(TT, include = "?", dir.exp = "DEV, URB, LIT, IND, STB",
               details = TRUE, show.cases = TRUE)
si   # DEV*URB*LIT*STB + DEV*LIT*~IND*STB -> SURV          (cons 0.866, cov 0.660)
si$i.sol$C1P1$EC                                 # easy counterfactual: row 30

5.5 Core and Peripheral Conditions

literals <- function(term) strsplit(term, "*", fixed = TRUE)[[1]]
intermediate <- si$i.sol$C1P1$solution[[1]]      # "DEV*URB*LIT*STB" "DEV*LIT*~IND*STB"
parsimonious <- lapply(sp$solution[[1]], literals)

# Core: literals of a parsimonious term contained in the intermediate path
lapply(setNames(intermediate, intermediate), function(path) {
  lit  <- literals(path)
  core <- unique(unlist(Filter(function(p) all(p %in% lit), parsimonious)))
  c(core = paste(core, collapse = ", "),
    peripheral = paste(setdiff(lit, core), collapse = ", "))
})
# DEV*URB*LIT*STB : core URB, STB; peripheral DEV, LIT
# DEV*LIT*~IND*STB: core DEV, ~IND; peripheral LIT, STB

5.6 Sufficiency X-Y Plot and Case Types

X <- admisc::compute(paste(intermediate, collapse = " + "), data = LF)
Y <- LF$SURV
c(Consistency = sum(pmin(X, Y)) / sum(X),        # 0.866
  Coverage    = sum(pmin(X, Y)) / sum(Y))        # 0.660

type <- ifelse(X > 0.5 & Y > 0.5, "Typical",
        ifelse(X > 0.5 & Y < 0.5, "Deviant (consistency)",
        ifelse(X < 0.5 & Y > 0.5, "Uncovered",
        ifelse(X < 0.5 & Y < 0.5, "Irrelevant", "On the 0.5 line"))))
table(type)                                      # Typical 6, Uncovered 2, Irrelevant 10

5.7 Absence of the Outcome and Contradictory Simplifying Assumptions

TTn <- truthTable(LF, outcome = "~SURV", conditions = conditions,
                  incl.cut = 0.80, n.cut = 1, pri.cut = 0,
                  complete = TRUE, show.cases = TRUE, sort.by = "incl, n")
spn <- minimize(TTn, include = "?", details = TRUE, show.cases = TRUE)
spn  # ~DEV + ~STB -> ~SURV                                 (cons 0.849, cov 0.888)

# Remainders assumed to produce both SURV and ~SURV
sa_rows <- function(s) as.integer(unlist(lapply(s$SA, rownames)))
intersect(sa_rows(sp), sa_rows(spn))             # 10 12 14 16 17 21 25 29

5.8 Robustness to the Consistency Cut-off

for (cut in c(0.75, 0.80, 0.85, 0.90)) {
  tt <- truthTable(LF, outcome = "SURV", conditions = conditions,
                   incl.cut = cut, n.cut = 1, pri.cut = 0)
  s  <- minimize(tt, include = "?", dir.exp = "DEV, URB, LIT, IND, STB")
  cat(cut, sum(tt$tt$OUT == "1"), paste(s$i.sol[[1]]$solution[[1]], collapse = " + "), "\n")
}
# 0.75 and 0.80: DEV*URB*LIT*STB + DEV*LIT*~IND*STB
# 0.85 and 0.90: DEV*URB*LIT*IND*STB  (row 22 no longer passes)

Explore the entire Fuzzy-set Qualitative Comparative Analysis (fsQCA) module in preview mode using our demo datasets. To submit suggestions or report a workflow issue, please use the discussion section below, or visit the official RAISINS website.

6 RAISINS Native Statistical Framework

RAISINS uses R for all its statistical computations. Principal statistical functions are listed with illustrative examples. Exact reproduction also requires the release source, input data, analysis settings and runtime environment. These results are then organized and formatted on the RAISINS website along with visualisation to make them easier to use and interpret. RAISINS also has its own custom-built statistical tools for managing workflows, validating results, and generating reports. Details of these are not fully covered here, they’re shared with outside researchers only on request, and are subject to licensing terms.

7 Package References

R Core Team. (2025). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. https://www.R-project.org/

Dusa, A. (2019). QCA with R: A Comprehensive Resource. Springer International Publishing. https://doi.org/10.1007/978-3-319-75668-4

Dusa, A. (2025). QCA: Qualitative Comparative Analysis (R package version 3.25). https://doi.org/10.32614/CRAN.package.QCA

Dusa, A. (2025). admisc: Adrian Dusa’s Miscellaneous (R package version 0.41). https://doi.org/10.32614/CRAN.package.admisc

Csárdi, G., & Chang, W. (2024). callr: Call R from R (R package version 3.8.0). https://doi.org/10.32614/CRAN.package.callr

Fiss, P. C. (2011). Building better causal theories: A fuzzy set approach to typologies in organization research. Academy of Management Journal, 54(2), 393–420. https://doi.org/10.5465/amj.2011.60263120

Ragin, C. C. (2008). Redesigning Social Inquiry: Fuzzy Sets and Beyond. University of Chicago Press.

Schneider, C. Q., & Rohlfing, I. (2013). Combining QCA and process tracing in set-theoretic multi-method research. Sociological Methods & Research, 42(4), 559–597. https://doi.org/10.1177/0049124113481341

Schneider, C. Q., & Wagemann, C. (2012). Set-Theoretic Methods for the Social Sciences: A Guide to Qualitative Comparative Analysis. Cambridge University Press.

Feedback & Discussion