Changelog
Source:NEWS.md
eSVD2 1.2.0
The first version intended for CRAN. Everything below is relative to 1.0.1.07, the version on the master branch from April 2026 and the last one installed from GitHub. The development versions 1.0.2 and 1.1.0 in between were never released; their changes are folded in here.
Results differ from 1.0.1.07
Every analysis gives different statistics, p-values and fold changes, for all genes. The reasons, from largest to smallest effect:
-
The nuisance rate is capped.
estimate_nuisance()bounds each gene’s Gamma rate (the reciprocal of the over-dispersion) atcap_multipliertimes the median of the gene’s library size, withcap_multiplier = 10by default, and floors it atmin_valtimes the same median. A gene whose counts are no more variable than a Poisson has no finite maximum-likelihood rate; 1.0.1.07 gave such a gene the largest library size (see the next item), its posterior followed the fit, and its statistic was inflated. The cap is a calibration device, and the documentation says so.cap_multiplier = Infremoves it.eSVD()andeSVD_helper()take the new argumentcap_multiplier. -
The rate is now the maximum-likelihood estimate.
gamma_rate()truncated its search at the largest library size of the gene and returned that bound as if it were the estimate whenever the true maximizer lay above it. The bracket now grows until the maximizer is inside it. -
A degraded empirical null is no longer silent. Welch statistics are mapped to Gaussian statistics on the log scale, so one strongly up-regulated gene no longer produces an infinite statistic that pushed
locfdronto its crudest fallback for the whole data set.multtest()reports which estimator ran (pvalue_list$method) and warns whenever it is notlocfdr. The truncated-Gaussian fallback is Efron’s estimator with the constraintp0 <= 1restored (without it, 200 draws fromN(0, 1)gave a null standard deviation of 2.6), and the moment estimator is corrected for truncation. -
Two defaults changed.
compute_test_per_gene()floors the library size atlibrary_min = 0.1, aseSVD()andcompute_posterior()always did, instead of0.01.estimate_nuisance()on aneSVDobject defaults tobool_covariates_as_library = TRUE, ascompute_posterior()does and aseSVD()has always passed it; a stage-by-stage analysis run with defaults used to estimate the rate on one library size and compute the posterior on another. - The fit is deterministic. The iterative SVD solvers start from a fixed vector, so identical inputs give identical fits and the user’s random seed is not consumed. Two runs of 1.0.1.07 on the same data differed by up to 0.006 in the Welch statistic.
New functions
-
eSVD_helper()wrapseSVD()with the cohort filters of the paper, andfilter_cohort()applies them alone: minimum cells per individual, per arm and overall, and minimum individuals per arm. All-zero genes are removed before the fit and reinserted afterwards withNAresults; the outcome of every filter is recorded ingene_statusandparam. -
compute_log_fold_change()gives each gene a log2 fold change between the case and control individuals and a delta-method standard error for it, on the unit of replication of the individual (as ‘DESeq2’, ‘dreamlet’ and ‘NEBULA’ report it). Both are computed bycompute_test_statistic()andcompute_test_per_gene()and stored aslog2fc_vecandlog2fc_se_vec.?compute_log_fold_changesays what the standard error does and does not account for. -
recompute_pvalue()redoes the posterior, the statistic and the p-values at anothercap_multiplieron a fitted object, without fitting again. An object built withbool_diet = TRUEhas no counts; pass the Seurat object the analysis was run on, and the counts and covariates are rebuilt and checked against summaries recorded at the fit. -
plot_nuisance()draws each gene’s rate, in units of its library size, against its mean count, its -log10 p-value or its sparsity, with the cap drawn.plot_fitted_vs_observed()draws the fitted against the observed count per cell and gene, marking the points more thannum_sdmodel standard deviations from the fit. Both need ‘ggplot2’ (and ‘ggrepel’), which are suggested, not required. -
multtest()andreparameterization_esvd_covariates()are exported by name, with documentation and examples.
Changes that can affect existing code
-
report_results()returns seven columns where it returned four:genes,logFC,logFC_se,log10pvalue,pvalue,pvalue_adjandnuisance_status. Code that reads columns by position breaks. - Every gene has a
nuisance_status(estimated,capped,boundaryorfailed), stored as a factor besidenuisance_vecin the fit, with the uncapped estimate innuisance_mle_vec. The fit andparamcarry more entries (gene means and sparsity, the library medians, the counts of capped and boundary genes, the variable nameseSVD()was called with, the posterior settings, and the individuals of each arm). -
compute_test_statistic(),compute_pvalue()andcompute_test_per_gene()takemin_cells_per_individual(default 3), whicheSVD()passes through. -
opt_esvd()on matrices takesnuisance_vec = NULL(meaning 1 for every gene) instead of a vector ofNA; four of the seven families consumed theNAinside the objective and failed in the line search. -
estimate_nuisance(),compute_posterior(),compute_test_statistic()andcompute_test_per_gene()overwrite their entries ofparamwhen rerun, so a rerun on a changed cohort or setting is recorded. - An object saved by 1.0.1.07 still works with
report_results()(logFC_seandnuisance_statusareNA, with a warning) and is refused bycompute_log_fold_change(),recompute_pvalue()and the two plots, which need what 1.2.0 stores. -
compute_pvalue()on an object built witheSVD(bool_diet = TRUE)stops with a message saying the p-values were already computed bycompute_test_per_gene(); it used to fail inside.compute_df().
Correctness fixes
Each of these was a silent wrong answer in 1.0.1.07.
-
initialize_esvd()zeroesNAcounts in sparse matrices too (a singleNAin adgCMatrixerrored inside ‘glmnet’), keeps the library-size offset in the single-covariatestats::glmfallback (a design with only the case-control indicator was initialized without depth adjustment), and refuses all-zero genes instead of mis-fitting them. -
reparameterization_esvd_covariates()regresses on the design matrix directly instead of throughstats::lm(x ~ ., data = ...), which rewrote non-syntactic covariate names such as"Diagnosis_ASD (severe)"(a subscript error) and returnedNAcoefficients for aliased columns that propagated intoz_matand every posterior. The identification step floors an eigenvalue at or belowtolinstead of inverting it (an exact zero gaveInf, a rounding-error negative gaveNaN). -
opt_esvd()counts failed Newton line searches in C++ and raises one R warning at the end, instead ofRcpp::warning()from inside a C++ frame (which can skip destructors underoptions(warn = 2)), and stops with a message when the objective becomes non-finite. Its verbose output printed nothing useful. -
gamma_rate()andlog_gamma_rate()check thatx,muandshave the same length (a shorter input was an out-of-bounds read). - The C++ data loader rejects an unsupported matrix type on entry instead of returning a null pointer that failed later as “external pointer is not valid”.
-
bool_stabilize_underdispersionkeepsnuisance_veca named numeric vector on both branches. - Column standard deviations of a sparse matrix are computed from the stored non-zeros without cancellation; the two-pass formula returned
NaNfor a column with a large mean and a small variance. -
compute_pvalue()stops when an arm has one individual (the Welch degrees of freedom were zero and every p-valueNaN), andmulttest()stops when no estimator can fit a null and warns when the null has no spread. -
fisher_test(verbose = TRUE)prints its summary; the string was built and discarded.
Input validation
New errors that name the cause, in place of failures far downstream:
- A rank-deficient design is refused by
initialize_esvd()and by the reparameterization, naming the aliased columns. Including an indicator for every individual, or for every level of a factor, causes this. The 2024 ASD design of the articles is rank deficient (Seqbatchandregionare determined byCapbatch); 1.0.1.07 fit it anyway. -
eSVD()refuses abatch_var_prefixmatching no variable, variables missing from the Seurat object, acase_control_varwhose values are not the twocase_control_levels,kabove the number of genes, all-zero genes, individuals in both arms, an arm with fewer than two individuals, and individuals with fewer thanmin_cells_per_individualcells. - The posterior’s settings are validated once, for
compute_posterior(),compute_test_per_gene()andeSVD()alike: a boolean that is not oneTRUEorFALSE,bool_adjust_covariates = TRUEtogether withbool_covariates_as_library = TRUE, and a non-positivealpha_max,library_minorpseudocount.nuisance_lower_quantile = NULLmeans no floor on both paths; it used to empty the vector of rates. -
initialize_esvd()refusesNAor non-finite covariates and akthat is not a positive integer;opt_esvd()refusesNAin any input;format_covariates()names a factor with one level; the family constructor names an unknown family;.svd_safe()refuses amean_vecorsd_vecof the wrong type or length.
Packaging
-
DESCRIPTION: the title and description are rewritten for CRAN and cite the paper; ‘Rmpfr’ and ‘sparseMatrixStats’ are no longer needed (the log-scale p-values and the sparse standard deviations replace them); ‘Rcpp’ moved fromDependstoImports; ‘stats’ and ‘utils’ are imported; ‘devtools’, ‘EnhancedVolcano’ and ‘npregfast’ leaveSuggestsand ‘ggplot2’, ‘ggrepel’, ‘rmarkdown’ and ‘withr’ join it;biocViewsis dropped andBugReportsadded. -
LICENSEis the two-line CRAN form for MIT, andNEWS.mdandcran-comments.mdexist. -
NAMESPACEexports only the functions marked@export; the pattern export that exposed every non-dot object, including the raw Rcpp bindings that take external pointers, is gone. - Every exported function has documentation and a runnable example, and about 35 misleading comments and roxygen descriptions in
R/andsrc/are corrected. - The test suite grows from 12 files and 31 blocks to 29 files, 298 blocks and about 2,100 expectations, with fixtures regenerated by
tests/assets/data_generation.R(the shipped fixture shrinks from 10 MB to 0.4 MB). - The vignette
eSVD2.Rmdruns without ‘Seurat’ or ‘ggplot2’ (those chunks are skipped) and gains sections onreport_results(), the over-dispersion diagnostics andrecompute_pvalue(). The two ASD tutorials, which need multi-gigabyte downloads, are pkgdown articles undervignettes/articles/and were rerun under 1.2.0 througheSVD_helper(), with the diagnostic plots and a comparison of caps. -
.Rbuildignorekeeps the collaboration material (additional_context/, theCLAUDE*.mdandHISTORY_*.mdnotes,.githooks/,oldcode/) out of the tarball; it stays on GitHub.
eSVD2 1.0.1.07
The master branch on GitHub from April 2026 until 1.2.0: small fixes on top of 1.0.0.
eSVD2 1.0.0
GitHub release accompanying Lin, Qiu and Roeder (2024), doi:10.1186/s12859-024-05724-7.