Skip to contents

Overview

Within a gene, spiDE tests several celltype:condition:niche (ResponseNiche) coefficients — one per surrounding cell type — and must collapse them into a single gene-level p-value and one p-value per index cell type. These coefficients are correlated, because neighbouring cell-type densities are themselves correlated, so the combination step has to remain calibrated under dependence.

spiDE offers two combiners, selected with the combine argument:

  • Brown’s method is a correlation-aware generalisation of Fisher’s method: it estimates the coefficient correlation matrix and adjusts the combined statistic’s reference distribution accordingly. It is exact under multivariate normal dependence but needs a well-conditioned correlation estimate.
  • Cauchy combination (ACAT) forms an average of tangent-transformed p-values. Because a weighted sum of Cauchy variates is again Cauchy regardless of dependence, its tail stays valid under arbitrary correlation, with no correlation matrix to estimate.

This vignette compares them in two settings: a controlled Monte-Carlo experiment that feeds both combiners identical, exactly correlated p-values, and the full spiDE pipeline on realistic simulated data.

A controlled comparison

We draw mm correlated z-scores with exchangeable correlation ρ\rho, convert them to one-sided p-values, and combine. Under the null the combined p-value should be Uniform(0, 1); we sweep the correlation ρ\rho and the number of coefficients mm.

Both combiners stay calibrated under correlation

On a QQ plot a calibrated combiner lies on the diagonal. At ρ=0\rho = 0 both do; as correlation grows, Brown (handed the true correlation matrix) stays on the diagonal while Cauchy bends slightly below it in the body — mildly conservative — but the two converge again in the extreme tail that actually drives FDR control.

Null QQ plots for m = 5 correlated p-values across four correlation levels. Both combiners are calibrated or conservative; neither is anti-conservative.
Null QQ plots for m = 5 correlated p-values across four correlation levels. Both combiners are calibrated or conservative; neither is anti-conservative.

Type-I error is controlled at every correlation

Empirical type-I error at alpha = 0.05 versus correlation, for three values of m. Both combiners control error; Cauchy is marginally conservative as correlation approaches 0.9.
Empirical type-I error at alpha = 0.05 versus correlation, for three values of m. Both combiners control error; Cauchy is marginally conservative as correlation approaches 0.9.

Power is essentially identical

We now shift two of the m=5m = 5 columns by a common effect δ\delta at a challenging correlation ρ=0.6\rho = 0.6 and measure the detection rate.

Power versus effect size. The two combiners are near-identical; Cauchy sacrifices no meaningful power for its distribution-free calibration.
Power versus effect size. The two combiners are near-identical; Cauchy sacrifices no meaningful power for its distribution-free calibration.

In the full pipeline at realistic scale

The controlled experiment isolates the combiners; we also compare them inside the complete spiDE pipeline on realistic simulated data (ten-thousand-plus genes, low counts, a planted niche effect in 5% of genes). The table below, from the benchmark study (research/), reports the realised false discovery proportion and true-positive rate under each combiner at several nominal FDR thresholds.

(The full-pipeline summary table was not found; run the benchmark study in research/ and install_results.R to populate it.)

In the full pipeline the two combiners diverge sharply, and in Cauchy’s favour. Cauchy recovers far more of the planted genes at every threshold — recall around 0.30 at a nominal 20 % FDR against roughly 0.02 for Brown — while Brown makes few discoveries and a large fraction of them are false, its realised FDP sitting near 0.4 regardless of the threshold. Cauchy’s own FDP does exceed the nominal level at the strictest thresholds, where only a handful of genes are called and the proportion is dominated by one or two false positives, but it falls below the line as more true genes accumulate. Both combiners inherit their absolute FDP level from the underlying mixed-effects fit; this comparison holds that fit fixed and isolates the combination step, and on that axis Cauchy is clearly preferable.

Sidedness: why Cauchy needs two-sided input

Everything above combines one-sided p-values, testing each direction separately at fdr/2 so that a gene can be labelled Up, Down or Both. That convention is safe for Brown’s method but not for Cauchy combination, and the reason is the transform each uses.

combiner transform value as p1p \to 1
Brown / Fisher 2logp-2\log p 0\to 0 — contributes nothing
Cauchy / ACAT tan((0.5p)π)\tan((0.5 - p)\pi) \to -\infty — cancels the signal

Consider a gene that is strongly up near one niche cell type and strongly down near another. In the up-direction combination the down coefficient contributes a one-sided p1p \approx 1. Fisher’s transform maps that to 0\approx 0, so it is simply ignored. Cauchy’s maps it to a large negative number that annihilates the large positive contribution from the genuine signal.

#>   effect (t) opposing one-sided p Cauchy term Fisher term
#> 1          3               0.9979   -1.52e+02    4.21e-03
#> 2          8               1.0000   -3.83e+09    1.66e-10
#> 3         15               1.0000   -3.15e+14    2.00e-15
#> 4         25               1.0000   -3.15e+14    2.00e-15

The consequence is not subtle: the stronger the opposing signal, the more completely Cauchy discards the gene.

Detection rate at alpha = 0.05 versus effect size, for m = 5 coefficients. With unidirectional signal all three agree. With opposing signal, one-sided Cauchy collapses toward zero as the effect grows, while Brown and two-sided Cauchy both rise monotonically.
Detection rate at alpha = 0.05 versus effect size, for m = 5 coefficients. With unidirectional signal all three agree. With opposing signal, one-sided Cauchy collapses toward zero as the effect grows, while Brown and two-sided Cauchy both rise monotonically.

The left panel is the diagnostic one. One-sided Cauchy is non-monotone in the effect size: detection rises, peaks, and then falls back towards zero as the opposing coefficient’s p-value approaches 1. The clearest possible biology is the most likely to be discarded, and silently. Brown is immune, and two-sided Cauchy is immune while keeping the correlation-agnostic property that made Cauchy the default.

Two-sided input costs nothing where the one-sided convention was already safe: in the right-hand panel the three curves are indistinguishable, because the two-sided p-value’s factor of 2 is exactly offset by dropping the fdr/2 direction split.

Null QQ plot for the three gene-level constructions. All are calibrated; two-sided Cauchy is not anti-conservative.
Null QQ plot for the three gene-level constructions. All are calibrated; two-sided Cauchy is not anti-conservative.
#>             method type-I at 0.05
#> 1  Brown (1-sided)          0.044
#> 2 Cauchy (1-sided)          0.050
#> 3 Cauchy (2-sided)          0.054

What spiDE does

combine = "cauchy" combines two-sided p-values; combine = "brown" keeps one-sided p-values. When the two-sided path is used the fdr/2 direction split is not applied (there is only one test), and Up / Down / Both are resolved from the signs of the significant niche coefficients — which is where direction is well defined in any case. Under the one-sided Cauchy convention the Both label was effectively unreachable, because genes with opposing effects were exactly the ones cancelled at the gene-level gate.

Summary

On both the controlled experiment and the full pipeline, Cauchy combination matches or beats Brown’s method: it controls type-I error at every correlation level, loses no meaningful power, and does so without estimating a correlation matrix or depending on poolr. Its only cost is mild conservativeness in the body of the null distribution under very strong dependence — irrelevant for the extreme tail that FDR control depends on. For these reasons Cauchy combination is the default (combine = "cauchy"); Brown’s method remains available (combine = "brown") for settings where an explicit, correlation-aware combiner is preferred.

That verdict holds provided the Cauchy combiner is given two-sided p-values. Its tan\tan transform diverges to -\infty as p1p \to 1, so with one-sided input a gene that is up near one niche cell type and down near another cancels itself out, and does so more completely the stronger the effect. Brown’s 2logp-2\log p is bounded at zero and has no such failure mode, which is why the directional convention inherited from the Brown era is safe there and unsafe here. spiDE therefore combines two-sided p-values under combine = "cauchy" and one-sided under combine = "brown".

#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 24.04.4 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.26.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] ggplot2_4.0.3    spiDE_0.99.17    BiocStyle_2.40.0
#> 
#> loaded via a namespace (and not attached):
#>  [1] SummarizedExperiment_1.42.0 gtable_0.3.6               
#>  [3] rjson_0.2.23                xfun_0.60                  
#>  [5] bslib_0.12.0                Biobase_2.72.0             
#>  [7] lattice_0.22-9              mathjaxr_2.0-0             
#>  [9] vctrs_0.7.3                 tools_4.6.1                
#> [11] generics_0.1.4              stats4_4.6.1               
#> [13] parallel_4.6.1              tibble_3.3.1               
#> [15] pkgconfig_2.0.3             Matrix_1.7-5               
#> [17] RColorBrewer_1.1-3          S7_0.2.2                   
#> [19] desc_1.4.3                  S4Vectors_0.50.2           
#> [21] lifecycle_1.0.5             compiler_4.6.1             
#> [23] farver_2.1.2                textshaping_1.0.5          
#> [25] statmod_1.5.2               Seqinfo_1.2.0              
#> [27] codetools_0.2-20            htmltools_0.5.9            
#> [29] sass_0.4.10                 yaml_2.3.12                
#> [31] pkgdown_2.2.1               pillar_1.11.1              
#> [33] jquerylib_0.1.4             BiocParallel_1.46.0        
#> [35] SingleCellExperiment_1.34.0 DelayedArray_0.38.2        
#> [37] cachem_1.1.0                limma_3.68.5               
#> [39] magick_2.9.1                abind_1.4-8                
#> [41] tidyselect_1.2.1            digest_0.6.39              
#> [43] dplyr_1.2.1                 bookdown_0.48              
#> [45] labeling_0.4.3              fastmap_1.2.0              
#> [47] grid_4.6.1                  cli_3.6.6                  
#> [49] SparseArray_1.12.2          magrittr_2.0.5             
#> [51] S4Arrays_1.12.0             withr_3.0.3                
#> [53] scales_1.4.0                rmarkdown_2.32             
#> [55] XVector_0.52.0              matrixStats_1.5.0          
#> [57] otel_0.2.0                  ragg_1.5.2                 
#> [59] SpatialExperiment_1.22.0    evaluate_1.0.5             
#> [61] poolr_1.2-0                 knitr_1.52                 
#> [63] GenomicRanges_1.64.0        IRanges_2.46.0             
#> [65] rlang_1.3.0                 Rcpp_1.1.2                 
#> [67] glue_1.8.1                  BiocManager_1.30.27        
#> [69] BiocGenerics_0.58.1         jsonlite_2.0.0             
#> [71] R6_2.6.1                    MatrixGenerics_1.24.0      
#> [73] systemfonts_1.3.2           fs_2.1.0