Convenience wrapper that (optionally) builds niche covariates, fits the
per-gene negative binomial model over the neighbourhood-interaction design,
and tests for neighbourhood-dependent differential expression. Equivalent to
calling buildNiches() (if the niches are not already present),
fitSpiDE(), and testSpiDE() in sequence.
Usage
spiDE(spe, condition = NULL, ...)
# S4 method for class 'ANY'
spiDE(
spe,
condition = NULL,
index = NULL,
niche = NULL,
covariates = character(),
sigma = c(10, 30, 50, 70),
assay = "counts",
cell_type = "cell_type",
sample_id = "sample_id",
random = c("intercept", "none", "slope"),
winsor = 4,
lambda.a = 0,
backend = c("auto", "cpu", "gpu"),
name = "Niche",
fdr = 0.05,
combine = c("cauchy", "brown"),
df.method = c("satterthwaite", "between"),
re.celltype = TRUE,
converge = TRUE,
converge.maxit = 50L,
converge.tol = 1e-08,
block.size = NULL,
gpu.mem.budget = NULL,
BPPARAM = BiocParallel::SerialParam(),
verbose = TRUE,
...
)Arguments
- spe
a SpatialExperiment with niche reducedDims (see
buildNiches()).- condition
a character, the colData column of the tested condition, or
NULLfor a condition-free (niche-only) analysis. SeefitSpiDE().- ...
further arguments forwarded to
fitNB.- index, niche
character vectors restricting the index / niche cell types considered (NULL = all).
- covariates
a character vector of nuisance colData columns to adjust for (e.g. library size, age, sex).
- sigma
a numeric vector of bandwidths to fit; NULL (default) uses every
Niche<sigma>reducedDim present.- assay
a character, the counts assay to model.
- cell_type
a character, the colData column of cell type labels.
- sample_id
a character, the colData column identifying samples (used when niches must be built and for the random-effects fit).
- random
one of "intercept" (the default), "none" or "slope". Adds patient-level random effects (implemented as ridge-penalised design columns) to correct anti-conservative inference caused by cell-level pseudo-replication.
The default changed from "none" to "intercept". "none" forms Wald standard errors from cell-level information, which treats every cell as an independent replicate of a patient-level contrast. On a null with per-sample intercepts it rejects at \(\approx 0.71\) against a nominal 0.05, worsening as cell counts become imbalanced, where the random intercept holds \(\approx 0.04\) against a calibrated pseudobulk reference of \(\approx 0.04\). "none" is retained for back-compatibility and for reproducing pre-correction results, but it should not be used for inference. "intercept" adds a per-sample random intercept; "slope" additionally adds per-sample random slopes on the niche covariates, which protects the response x niche tests when the niche-slope varies between samples. Note that "slope" estimates an extra variance component (
tau2forSampleSlope) that is collinear with the tested fixed effect and, with few samples, is less stable than the intercept-only variance; prefer "intercept" at smallS, and "slope" when between-sample niche-slope variation is expected. The fit is also stochastic (fitNBsubsamples cells for the dispersion estimate), so set a seed for reproducible variance components. See the mixed-effects and simulation vignettes.In a condition-free analysis (
condition = NULL) the random-slope block sits on exactly theCellType:nichecolumns being tested, so"slope"is the natural correction there when between-sample variation in niche slopes is plausible. Be aware of a limitation specific to that mode: the tested slope is a within-sample contrast on a spatially autocorrelated covariate, and spiDE does not model spatial autocorrelation. Neighbouring cells are therefore not independent replicates of the slope, and neither random-effect structure can recover that – on a null fixture with a planted per-sample intercept, the fixed-effects fit made 37 calls,"intercept"5 and"slope"5 (seelongtests/testthat/test-nicheOnly-mixed.R). Random effects remove most of the inflation but niche mode remains mildly anti-conservative; treat borderline calls with corresponding caution.- winsor, lambda.a
fitting parameters forwarded to
fitNB(coefficient winsorisation and the base ridge penalty on the fixed columns).- backend
a character, the compute backend for both the model fit ("auto", "cpu", or "gpu", forwarded to
fitNB) and the inference stage (where the GPU backend batches the per-gene Wald covariance and negative-binomial working weights across each gene-block on the accelerator, forcing a serialBPPARAMin the process).- name
a character, the niche reducedDim prefix.
- fdr
a numeric, the target false discovery rate.
- combine
one of "cauchy" (default) or "brown", the within-gene combiner for the correlated niche p-values (passed to
testSpiDE()).- df.method
one of "satterthwaite" (default) or "between"; only used when
random != "none". "satterthwaite" derives a separate df per tested column from the shared variance-component fit, distinguishing between-sample contrasts (Response: small df, close to "between") from within-sample contrasts (ResponseNiche: larger df, more power) rather than applyingS - 2to both;@dfis then a named per-column vector. "between" tests every Response/ResponseNiche coefficient against the same scalar between-sample reference df (S - 2), the original back-compatible behaviour, and@dfis a scalar.The default changed to "satterthwaite" after the benchmark study (
research/) measured both arms on identically seeded data: "between" is severely over-conservative when samples are few (null type-I \(\approx 0.001\) at \(S = 4\) against a nominal 0.05, with correspondingly near-zero power), while "satterthwaite" holds type-I in \(0.042\)-\(0.065\) across the whole sampled range and gains \(\approx 0.10\) mean TPR. The trade is a mild liberal drift at larger \(S\) (worst measured \(\approx 0.065\)); use "between" when strict conservatism matters more than power, or for back-compatibility. Ignored whenrandom == "none".In a condition-free analysis (
condition = NULL) the"between"reference df changes, because the testedCellType:nicheslope is a within-sample contrast rather than a between-condition one: it isncells - p_fixedunderrandom = "intercept"(cells are the replicates) andS - 1underrandom = "slope"(the per-sample random slopes sit on the tested columns, moving the contrast into the between-sample stratum). The name"between"is therefore a misnomer in the intercept case; it is retained for back-compatibility."satterthwaite"computes this distinction from the fitted variance components and is preferred.- re.celltype
logical; add a nested (sample x cell type) random intercept so the tested niche slopes are within-group. Default
TRUE. SeefitSpiDE().- converge
logical; converge each gene to its own penalised NB optimum after the shared fit. Default
TRUE. SeefitSpiDE().- converge.maxit, converge.tol
iteration cap and relative log-likelihood tolerance for the per-gene convergence stage.
- block.size
a numeric, genes per inference block (NULL = a single block on the CPU backend, or a memory-bounded auto-selected size on the GPU backend).
- gpu.mem.budget
a numeric, the GPU memory budget in bytes used to size inference blocks (NULL auto-detects; only relevant for the GPU backend).
- BPPARAM
a BiocParallelParam for niche construction and inference.
- verbose
a logical, whether to print fitting progress.
Value
a SpiDEResults object with the tidy results table populated (see
results()).
Examples
data(toySpiDE)
spe <- toySpiDE
res <- spiDE(spe, condition = "condition", sigma = 20, verbose = FALSE)
head(results(res))
#> gene ct_index ct_niche bandwidth.max coef t DirectionGene
#> 1 G1 A B 20 3.173422 4.63951 Up
#> DirectionIndex DirectionNiche fdr.gene fdr.index fdr.niche
#> 1 Up Up 0.0005520939 2.760426e-05 9.201414e-06