The end-to-end wrapper: format_covariates, initialize_esvd
and two rounds of opt_esvd (the case-control coefficient held fixed,
then freed), each of the three followed by
reparameterization_esvd_covariates,
estimate_nuisance, and then either the fused
compute_test_per_gene (bool_diet = TRUE) or
compute_posterior, compute_test_statistic and
compute_pvalue in sequence.
Usage
eSVD(
batch_var_prefix,
case_control_levels,
case_control_var,
categorical_vars,
id_var,
numerical_vars,
seurat_obj,
alpha_max = NULL,
bool_adjust_covariates = FALSE,
bool_covariates_as_library = TRUE,
bool_diet = TRUE,
bool_intercept = TRUE,
bool_library_includes_interept = TRUE,
bool_stabilize_underdispersion = TRUE,
bool_use_log = FALSE,
cap_multiplier = 10,
intermediate_save = NULL,
k = 30,
l2pen = 0.1,
lambda = 0.1,
library_min = 0.1,
max_iter = 100,
min_cells_per_individual = 3,
pseudocount = 0,
tol = 1e-06,
verbose = 0
)Arguments
- batch_var_prefix
NULL, or a regular expression matched against the names ofcategorical_vars; the matching covariates are treated as batch variables and their coefficients are not reparameterized.- case_control_levels
Character vector of length 2 giving the two levels of
case_control_var, control first, then case. Reversing them flips the sign of every test statistic.- case_control_var
Name of the column in
seurat_obj@meta.dataholding the case-control status.- categorical_vars
Character vector of column names in
seurat_obj@meta.datato adjust for as factors (one-hot encoded, first level dropped), orNULL. A variable with a single level is dropped.- id_var
Name of the column in
seurat_obj@meta.datanaming each cell's individual (donor).- numerical_vars
Character vector of column names in
seurat_obj@meta.datato adjust for as numerical covariates, orNULL. They are rescaled byformat_covariates.- seurat_obj
A
Seuratobject whose"RNA"assay has a"counts"layer (genes by cells) and whosemeta.dataholds the variables above.- alpha_max
Maximum value of the prior mean in the posterior;
NULL(the default) uses twice the largest count.- bool_adjust_covariates
See
compute_posterior.- bool_covariates_as_library
See
estimate_nuisanceandcompute_posterior.- bool_diet
If
TRUE(the default), compute the posterior, test statistics and p-values one gene at a time without allocating cell-by-gene posterior matrices, and dropdat,covariatesand the intermediate fitsfit_Initandfit_Firstfrom the returned object. The final fit (fit_Second:x_mat,y_mat,z_mat,nuisance_vec) is kept either way.- bool_intercept
Whether the per-gene GLMs include an intercept.
- bool_library_includes_interept
See
estimate_nuisance.- bool_stabilize_underdispersion
See
compute_posterior.- bool_use_log
See
estimate_nuisance.- cap_multiplier
One positive number, default
10: no gene's nuisance rate may exceed this many times the median of its library size, andInfmeans no cap. Seeestimate_nuisance. The factorization does not depend on it, sorecompute_pvaluecan change it afterwards without fitting again.- intermediate_save
NULL, or a file path at which the object is saved after each major stage.- k
Number of latent dimensions; must not exceed the number of genes.
- l2pen
L2 penalty for
opt_esvd.- lambda
Ridge penalty for
initialize_esvd.- library_min
See
compute_posterior.- max_iter
Maximum iterations for each
opt_esvdcall.- min_cells_per_individual
Minimum number of cells every individual must contribute; an individual with fewer is an error.
0disables the check. Seecompute_test_statistic.- pseudocount
See
compute_posterior.- tol
Convergence tolerance for
opt_esvd.- verbose
Integer;
0is silent.
Value
An eSVD object with elements teststat_vec,
case_mean, control_mean, case_var,
control_var, log2fc_vec and log2fc_se_vec (the log2
fold change and its standard error; see compute_log_fold_change),
pvalue_list (see
compute_pvalue), param, case_control,
individual, latest_Fit and the fit it names (an
eSVD_Fit with x_mat, y_mat, z_mat,
nuisance_vec and the other per-gene vectors of
estimate_nuisance), plus dat, covariates, the earlier
fits and the posterior matrices when bool_diet = FALSE.
param records the settings of every stage (the opt_* entries
are those of the first opt_esvd call, because a later call does not
overwrite an entry that is already there), the number of
genes whose nuisance rate was capped (nuisance_num_capped) and the
arguments of this call that name the variables (as esvd_*), which
is what lets recompute_pvalue and plot_fitted_vs_observed
rebuild the counts and covariates from seurat_obj alone.
report_results turns the object into a data frame, and
plot_nuisance and plot_fitted_vs_observed draw its
diagnostics.
Details
This function refuses inputs the model cannot handle, rather than
filtering them: a gene whose counts are all zero, an individual with fewer
than min_cells_per_individual cells, or an arm with fewer than two
individuals each produce an error naming the offending genes or
individuals. The recommended entry point, eSVD_helper, applies the
cohort filters and removes all-zero genes before calling this function,
and reinserts the removed genes afterwards with a gene_status
record. It does not refuse an underpowered cohort (few cells or few
individuals overall); those minima are eSVD_helper's policy.
The model is a Gamma-Poisson hierarchy: \(A_{ji} \mid \lambda_{ji} \sim
\mathrm{Poisson}(\ell_{ji} \lambda_{ji})\) with \(\lambda_{ji} \sim
\mathrm{Gamma}(\mathrm{mean} = \mu_{ji}, \mathrm{var} = \gamma_j \mu_{ji})\),
where \(\mu_{ji}\) is a low-rank function of the cell and gene latent
vectors plus the case-control covariate, and \(\ell_{ji}\) is the
covariate-adjusted sequencing depth. Note that the package stores the Gamma
rate \(\beta_j = 1/\gamma_j\) as nuisance_vec, so larger
values mean less over-dispersion. Differential expression is a
difference in mean expression between the case and control individuals,
tested with a Welch statistic on the per-individual posterior means. See
Lin, Qiu and Roeder (2024) doi:10.1186/s12859-024-05724-7
.
Examples
# \donttest{
if(requireNamespace("SeuratObject", quietly = TRUE)){
set.seed(10)
sim <- generate_null(cell_per_person = 15, num_genes = 40,
num_individuals = 8)
meta_df <- data.frame(sim$covariates[, c("CC", "Sex", "Age")])
meta_df$Sex <- factor(meta_df$Sex)
meta_df$Individual <- sim$metadata_individual
rownames(meta_df) <- rownames(sim$obs_mat)
# Seurat rewrites "gene_1" as "gene-1" (with a warning); rename first
count_mat <- Matrix::t(sim$obs_mat)
rownames(count_mat) <- gsub("_", "", rownames(count_mat))
seurat_obj <- SeuratObject::CreateSeuratObject(counts = count_mat,
meta.data = meta_df)
esvd_obj <- eSVD(batch_var_prefix = NULL,
case_control_levels = c("0", "1"),
case_control_var = "CC",
categorical_vars = "Sex",
id_var = "Individual",
numerical_vars = "Age",
seurat_obj = seurat_obj,
k = 2,
max_iter = 5)
utils::head(report_results(esvd_obj))
}
#> genes logFC logFC_se log10pvalue pvalue pvalue_adj
#> gene1 gene1 -1.468000 0.2676634 1.4108375 0.03882956 0.5406965
#> gene2 gene2 -1.816972 0.4706980 1.0651751 0.08606467 0.5737645
#> gene3 gene3 1.050755 0.9300542 0.4481459 0.35633137 0.9944779
#> gene4 gene4 1.609953 0.4094298 1.3638726 0.04326408 0.5406965
#> gene5 gene5 -1.686198 0.5471413 0.8323053 0.14712780 0.7356390
#> gene6 gene6 1.764437 0.2837168 1.8455667 0.01427031 0.5406965
#> nuisance_status
#> gene1 estimated
#> gene2 estimated
#> gene3 estimated
#> gene4 estimated
#> gene5 estimated
#> gene6 estimated
# }