nsca_analysis: Necessary and sufficient condition analysis

View source: R/analysis.R

nsca_analysisR Documentation

Necessary and sufficient condition analysis

Description

Estimates the two empty-space frontiers implied by a necessary-and-sufficient statement and assesses them jointly. Sufficiency is delegated to SCAtools::sca_analysis() and necessity to NCA::nca_analysis(), on diagonally opposite corners of the same scatter plot.

Usage

nsca_analysis(
  data,
  x,
  y,
  direction = "HH",
  ceilings = c("ce_fdh", "cr_fdh"),
  reference = NULL,
  scope = NULL,
  threshold.x = "percentage.range",
  threshold.y = "percentage.range",
  convention = c("absolute", "directional"),
  steps = 10,
  step.size = NULL,
  cutoff = 0,
  qr.tau = 0.95,
  test.rep = 0,
  test.p_confidence = 0.95,
  test.p_threshold = 0.05,
  relevance = NULL,
  shared.test.rep = 0,
  shared.seed = NULL,
  geometry.max_overlap = 0.01,
  geometry.max_reconstruction = 0.02,
  purity = FALSE
)

Arguments

data

A data frame or object coercible to a data frame.

x

Columns containing one or more conditions.

y

A single outcome column.

direction

Necessary-and-sufficient direction(s): "HH", "LH", "HL", or "LL". The first letter is the condition level and the second the outcome level.

ceilings

One or more empty-space frontier techniques, applied identically to both sides. "ols" is rejected here because it estimates central tendency rather than an empty space; pass it as reference instead.

reference

Central-tendency lines drawn beside the two frontiers for comparison, or NULL for none. "ols" is the ordinary least-squares regression of the outcome on the condition, fitted to all the data. It is an average-effect summary, enters no joint index, no threshold and no support decision, and is reported by nsca_reference().

scope

Optional theoretical scope c(xmin, xmax, ymin, ymax), shared by both sides.

threshold.x, threshold.y

Reporting scales for nsca_thresholds(), as in SCAtools::sca_analysis().

convention

Reporting convention, as in SCAtools::sca_analysis().

steps

Number of outcome levels, or an explicit vector of levels on the threshold.y scale.

step.size

Optional spacing between outcome levels.

cutoff

How out-of-range threshold values are represented.

qr.tau

Quantile used by the quantile-regression frontier.

test.rep

Number of permutation resamples for the engines' own component tests. Zero skips testing, and without it no joint-support verdict can be reached.

test.p_confidence

Confidence level for permutation p-value accuracy.

test.p_threshold

Significance level used by the intersection-union combination of the two directional component tests.

relevance

Optional pre-specified practical-relevance thresholds for the component effect sizes, as one number applied to both or c(necessity, sufficiency). Support requires an effect at least this large as well as a significant test. NULL means no relevance criterion was pre-specified, and support then rests on significance alone.

shared.test.rep

Number of replications of the shared permutation sequence. Zero, the default, skips it; p_weakest_perm is then NA and the component p-values come from the engines.

shared.seed

Optional seed for the shared sequence. The caller's random stream is restored afterwards.

geometry.max_overlap

Largest excess frontier overlap, beyond what a step frontier produces by construction, that still counts as acceptable geometry.

geometry.max_reconstruction

Largest residual of the area identity that still counts as acceptable geometry.

purity

Compute the engine's extra purity metrics where it offers them. Off by default: the engine computes them only for a corner with neither axis flipped, so exactly one side of an NSCA model can ever have them, and which side depends on the direction. No NSCA statistic uses them, and they are expensive on data with many frontier points.

Value

An object of class nsca_result.

Matched estimation

Both sides are always estimated with the same frontier technique, the same theoretical scope, the same observations and the same outcome levels. Every joint index compares the two empty zones, and a comparison is only meaningful when both sides are measured the same way: envelopment frontiers hug the data and yield the largest empty areas, while regression frontiers cut into them, so mixing techniques across sides would decide which side looks weaker by the choice of technique rather than by the evidence. Supplying several techniques produces one internally matched row per technique, which is the right way to check whether a conclusion survives that choice.

Degenerate scopes

A constant outcome is refused outright when no theoretical scope is given, because it has no scope of its own and every empty area would be measured against a scope of zero height. With a scope imposed the analysis runs but warns, and marks the affected rows degenerate in nsca_table(). An axis that does not move inside the declared scope makes every reported area a property of the scope rather than of the data, and it does so flatteringly: a constant outcome at the centre of the scope leaves the upper and lower halves both empty, so both components reach 0.5, the admissible region closes, and every joint index reports perfect joint support for data carrying no information. A milder warning fires when the observations span less than five per cent of the declared scope on either axis.

The shared permutation sequence

shared.test.rep replaces the engines' two separate permutation runs with one sequence that drives both. Each replication shuffles the outcome once, refits both engines on that same shuffled frame, and records d_nec, d_suf and their minimum together. This is what makes p_weakest_perm meaningful: the null distribution of a statistic of both components depends on how they co-vary under random pairing, and independent permutations would destroy exactly that dependence. When it is used, p_nec and p_suf are taken from the same sequence, the p_source column reports "shared", and all four p-values are mutually consistent. It costs two engine fits per replication, so it is off by default.

See Also

nsca_table(), nsca_results(), nsca_joint(), nsca_thresholds(), nsca_corners()

Examples

set.seed(1)
x <- sort(runif(60))
dat <- data.frame(X = x, Y = pmin(pmax(x + rnorm(60, 0, 0.1), 0), 1))
fit <- nsca_analysis(dat, "X", "Y", direction = "HH", ceilings = "ce_fdh")
fit

NSCA documentation built on Oct. 10, 2026, 5:08 p.m.