| Title: | Covariance-Aware Deconvolution of Bulk Transcriptomic Samples |
|---|---|
| Description: | Estimates cell-type proportions in bulk transcriptomic samples with a probabilistic convolution model that integrates the gene-gene covariance structure of purified reference profiles. Cellular ratios are recovered by maximum likelihood under a multivariate Gaussian convolution, using analytic gradients and Hessians, an isometric log-ratio reparametrisation (Helmert basis) that enforces the simplex constraint, and Marquardt-Levenberg or Newton-type optimisers. The methodology is described in Chassagnol, Nuel and Becht (2023) <doi:10.48550/arXiv.2309.09557>. |
| Authors: | Bastien Chassagnol [aut, cre, cph] (ORCID: <https://orcid.org/0000-0002-8955-2391>) |
| Maintainer: | Bastien Chassagnol <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 2.3.1 |
| Built: | 2026-10-01 19:49:48 UTC |
| Source: | https://github.com/bastienchassagnol/DeCovarT |
Inverse additive log-ratio map
of Aitchison
(): a softmax with the last category
pinned as reference (). Solvers and
vcov.decovart_fit() use the isometric log-ratio chart
isometric_logistic() instead. This ALR helper is retained for the
vignette appendix.
Recovers the unconstrained additive log-ratio coordinates
for , with the last part
as reference. This is Aitchison's additive log-ratio
() transform, equivalently the multinomial-logit link
with reference category (see compositions::alr() and
vignette("theory-decovart-generative-model", package = "DeCovarT")).
additive_logistic(rho) additive_log_ratio(p)additive_logistic(rho) additive_log_ratio(p)
rho |
Numeric vector |
p |
Numeric vector |
With ,
Equivalently,
with
.
Jacobians and Hessians of both maps are derived in the package vignette
vignette("theory-decovart-generative-model", package = "DeCovarT").
See also compositions::alrInv().
Numeric vector on the unit
simplex (,
).
Numeric vector .
Evaluated through the log-sum-exp shift
with
, which is
algebraically identical to the ratio above but never forms
directly. The naive quotient overflows to
NaN for , which unconstrained ascent can
reach when the MLE approaches a simplex face; the shifted form
returns an exact zero instead and keeps
positive definite.
The inverse map (additive log-ratio) is documented as
additive_log_ratio() on this help page.
rho <- c(0.2, -0.5) p <- additive_logistic(rho) sum(p) additive_log_ratio(p) # Stable far out in ALR space, where exp(rho) would overflow. additive_logistic(c(800, 1200))rho <- c(0.2, -0.5) p <- additive_logistic(rho) sum(p) additive_log_ratio(p) # Stable far out in ALR space, where exp(rho) would overflow. additive_logistic(c(800, 1200))
Pearson correlations
across Monte Carlo
replicates (and cell types). This is behavioural similarity: two
solvers can correlate near 1 while remaining systematically biased.
Hierarchical order uses Ward D2 on .
algorithm_similarity(benchmark, facet_rows = NULL, facet_cols = NULL)algorithm_similarity(benchmark, facet_rows = NULL, facet_cols = NULL)
benchmark |
List from |
facet_rows, facet_cols
|
Optional scenario columns. When supplied, correlations are computed inside each facet cell. |
A tibble with algorithm_x, algorithm_y, correlation,
and any facet columns.
plot_algorithm_similarity(), pivot_mc_estimates()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls), "rlm" = list(FUN = deconvolute_ratios_rlm) ), n = 4, cores = 1 ) algorithm_similarity(out)set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls), "rlm" = list(FUN = deconvolute_ratios_rlm) ), n = 4, cores = 1 ) algorithm_similarity(out)
The conditional law of the bulk profile is fully specified, so the
asymptotic calibrations of lrt_decovart() can be replaced by
simulation. Given plug-in moments and a composition
, the function draws
, refits DeCovarT, and returns the
empirical distribution of together
with percentile intervals.
bootstrap_decovart( y = NULL, mean_signature_matrix, Sigma, bulk_expression = NULL, p = NULL, null_value = NULL, n_boot = 500L, level = 0.95, n_replicates = NULL, epsilon = 10^-8, itmax = 500L )bootstrap_decovart( y = NULL, mean_signature_matrix, Sigma, bulk_expression = NULL, p = NULL, null_value = NULL, n_boot = 500L, level = 0.95, n_replicates = NULL, epsilon = 10^-8, itmax = 500L )
y |
Numeric vector (or one-column matrix)
|
mean_signature_matrix |
Numeric matrix
|
Sigma |
Array of cell-type covariances in
|
bulk_expression |
Numeric matrix |
p |
Composition to simulate from. Defaults to the MLE computed
from |
null_value |
Named numeric vector of hypothesised ratios, e.g.
|
n_boot |
Number of bootstrap replicates. |
level |
Confidence level for percentile intervals. |
n_replicates |
Number of bulk columns simulated per replicate; defaults to the number of observed columns. |
epsilon, itmax
|
Relative convergence tolerance and iteration
budget passed to |
Supplying null_value switches to a restricted bootstrap: replicates
are generated under the restricted MLE and the likelihood-ratio
statistic is recomputed on each, giving a Monte Carlo p-value that does
not rely on the asymptotic weights. For a composite null
the remaining nuisance proportions are profiled at their restricted
estimate, so the procedure is a plug-in (approximate) rather than an
exact bootstrap.
Reference uncertainty is not propagated here:
and are treated as known, exactly as in the
frequentist likelihood. To resample the purified observations that
produced those plug-in moments, use
reference_bootstrap_decovart().
A list with estimates ( n_boot matrix),
interval (percentile bounds), p_simulated, and, when
null_value is supplied, statistic (observed ),
null_statistics, and p_value.
lrt_decovart(), chi_bar_square_pvalue(),
reference_bootstrap_decovart()
Other decovart_inference:
boundary_diagnostics(),
chi_bar_square_pvalue(),
confint_profile_decovart(),
equivariance_check_decovart(),
lrt_decovart(),
multistart_decovart(),
profile_loglik_decovart(),
reference_bootstrap_decovart(),
restricted_mle_decovart()
mu <- matrix(c(20, 40, 15, 40, 20, 25), nrow = 3) colnames(mu) <- paste0("ct", 1:2) Sigma <- array(c(diag(3), diag(3)), dim = c(3, 3, 2)) y <- drop(mu %*% c(0.6, 0.4)) set.seed(1) bootstrap_decovart(y, mu, Sigma, n_boot = 20)$intervalmu <- matrix(c(20, 40, 15, 40, 20, 25), nrow = 3) colnames(mu) <- paste0("ct", 1:2) Sigma <- array(c(diag(3), diag(3)), dim = c(3, 3, 2)) y <- drop(mu %*% c(0.6, 0.4)) set.seed(1) bootstrap_decovart(y, mu, Sigma, n_boot = 20)$interval
Checks that true_theta stores the DeCovarT mixture parameters:
p: mixing proportions of length , or a matrix
of sample-wise proportions (each column on the simplex);
mu: mean signature
(also accepts );
sigma and/or Theta: array
of covariances and/or precision matrices.
check_true_theta( true_theta, require_p = TRUE, J = NULL, second_moment = c("either", "sigma", "Theta") )check_true_theta( true_theta, require_p = TRUE, J = NULL, second_moment = c("either", "sigma", "Theta") )
true_theta |
Named list with the fields above. |
require_p |
Logical; if |
J |
Optional expected number of cell types. When |
second_moment |
Which second-moment field to require:
|
TRUE invisibly if the structure is valid; otherwise stops
with an informative error.
compute_average_overlap(), compute_average_jeffreys()
theta <- list( p = c(0.5, 0.5), mu = cbind(c(0, 0), c(3, 0)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) check_true_theta(theta)theta <- list( p = c(0.5, 0.5), mu = cbind(c(0, 0), c(3, 0)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) check_true_theta(theta)
Upper-tail probability of the mixture
with constraints active on the boundary of the parameter space
and interior constraints. This is Case 9 of
(Self and Liang 1987), which
generalises the
result of (Chernoff 1954).
For the mixture collapses to the ordinary
of (Wilks 1938).
chi_bar_square_pvalue( statistic, n_boundary = 1L, df_interior = 0L, weights = NULL )chi_bar_square_pvalue( statistic, n_boundary = 1L, df_interior = 0L, weights = NULL )
statistic |
Numeric vector of observed likelihood-ratio
statistics |
n_boundary |
Integer |
df_interior |
Integer |
weights |
Optional numeric vector of length |
The binomial weights hold when the block of the Fisher information
corresponding to the active constraints is diagonal after
orthogonalisation against the nuisance block. Under correlated
constraints the mixing weights depend on the geometry of the
approximating tangent cone and the binomial choice is only an
approximation; Self and Liang also exhibit a nuisance-parameter
configuration whose limit is not a chi-square mixture. Supply
weights explicitly, or calibrate by simulation with
bootstrap_decovart(), whenever and the constrained
cell types share signal.
Numeric vector of upper-tail probabilities.
is a point mass at the origin, so its strict upper
tail is zero for every . The
component therefore only removes weight: with one active constraint and
no interior constraint the p-value is
, capped at when
. Note stats::pchisq() switches to the closed tail at
exactly q = 0 for df = 0; the strict convention is used here so the
p-value stays continuous in statistic.
Self SG, Liang K (1987). “Asymptotic Properties of Maximum Likelihood Estimators and Likelihood Ratio Tests under Nonstandard Conditions.” Journal of the American Statistical Association, 82(398), 605–610. ISSN 0162-1459. doi:10.1080/01621459.1987.10478472.
Chernoff H (1954). “On the Distribution of the Likelihood Ratio.” The Annals of Mathematical Statistics, 25(3), 573–578. ISSN 0003-4851. doi:10.1214/aoms/1177728725.
Wilks SS (1938). “The Large-Sample Distribution of the Likelihood Ratio for Testing Composite Hypotheses.” The Annals of Mathematical Statistics, 9(1), 60–62. ISSN 0003-4851. doi:10.1214/aoms/1177732360.
Other decovart_inference:
bootstrap_decovart(),
boundary_diagnostics(),
confint_profile_decovart(),
equivariance_check_decovart(),
lrt_decovart(),
multistart_decovart(),
profile_loglik_decovart(),
reference_bootstrap_decovart(),
restricted_mle_decovart()
# One active boundary constraint: 50:50 mixture of chi2_0 and chi2_1. chi_bar_square_pvalue(2.71, n_boundary = 1L) # Interior (Wilks) calibration. chi_bar_square_pvalue(3.84, n_boundary = 0L, df_interior = 1L)# One active boundary constraint: 50:50 mixture of chi2_0 and chi2_1. chi_bar_square_pvalue(2.71, n_boundary = 1L) # Interior (Wilks) calibration. chi_bar_square_pvalue(3.84, n_boundary = 0L, df_interior = 1L)
Returns a length- simplex vector of the form
whose normalised Shannon entropy
compute_shannon_entropy() equals h_star. The uniform composition
(h_star = 1) and a Dirac mass (h_star = 0) are returned exactly.
composition_from_entropy(h_star, n_celltypes = 3L, nms = NULL)composition_from_entropy(h_star, n_celltypes = 3L, nms = NULL)
h_star |
Target |
n_celltypes |
Integer |
nms |
Optional names for the returned vector. |
Numeric simplex vector of length .
p <- composition_from_entropy(0.5, n_celltypes = 3L) compute_shannon_entropy(p)p <- composition_from_entropy(0.5, n_celltypes = 3L) compute_shannon_entropy(p)
Returns the -weighted mean of pairwise Jeffreys
(symmetrised KL) divergences between purified Gaussians
with
.
If p is omitted it defaults to the equi-balanced vector , which
recovers the uniform pairwise average
.
compute_average_jeffreys(true_theta, J = NULL)compute_average_jeffreys(true_theta, J = NULL)
true_theta |
List validated by |
J |
Number of cell types. Defaults to the third dimension of |
Scalar average pairwise Jeffreys divergence.
check_true_theta(), compute_average_overlap(),
compute_glmnet_gene_scores()
set.seed(1) theta <- list( mu = cbind(c(0, 0), c(3, 0)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) compute_average_jeffreys(theta)set.seed(1) theta <- list( mu = cbind(c(0, 0), c(3, 0)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) compute_average_jeffreys(theta)
Returns MixSim's BarOmega: the unweighted mean of pairwise overlaps
(up to the MixSim numerical convention), where
already uses the mixture weights inside the
Bayes / MAP rule of MixSim::overlap(). Do not multiply the
directional masses by again.
For MixSim's Davies quadrature becomes expensive.
The helper then switches to overlap_gaussian_mc(): stratified
Sobol draws, inverse-transform sampling through precomputed
Cholesky factors, and log-density MAP comparisons (n_mc
draws per component; default 10,000).
compute_average_overlap( true_theta, J = NULL, n_mc = 10000L, seed = NULL, verbose = TRUE )compute_average_overlap( true_theta, J = NULL, n_mc = 10000L, seed = NULL, verbose = TRUE )
true_theta |
List validated by |
J |
Number of cell types (components). Defaults to the third
dimension of |
n_mc |
Monte Carlo draws per component when |
seed |
Optional seed forwarded to |
verbose |
If |
Scalar average pairwise overlap (MixSim BarOmega).
check_true_theta(), overlap_gaussian_mc()
set.seed(1) theta <- list( p = c(0.5, 0.5), mu = cbind(c(0, 0), c(3, 0)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) compute_average_overlap(theta)set.seed(1) theta <- list( p = c(0.5, 0.5), mu = cbind(c(0, 0), c(3, 0)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) compute_average_overlap(theta)
Mean pairwise AIRM distance of component covariances
compute_average_riemannian(true_theta, J = NULL)compute_average_riemannian(true_theta, J = NULL)
true_theta |
List validated by |
J |
Number of cell types. Defaults to the third dimension of |
Scalar average of over
.
spd_affine_invariant_distance()
a <- diag(2) b <- matrix(c(2, 0.5, 0.5, 1), nrow = 2) theta <- list( mu = cbind(c(0, 0), c(1, 0)), sigma = array(c(a, b), dim = c(2, 2, 2)) ) compute_average_riemannian(theta) # J = 2 has a single pair, so the average equals the pairwise AIRM. all.equal( compute_average_riemannian(theta), spd_affine_invariant_distance(a, b) )a <- diag(2) b <- matrix(c(2, 0.5, 0.5, 1), nrow = 2) theta <- list( mu = cbind(c(0, 0), c(1, 0)), sigma = array(c(a, b), dim = c(2, 2, 2)) ) compute_average_riemannian(theta) # J = 2 has a single pair, so the average equals the pairwise AIRM. all.equal( compute_average_riemannian(theta), spd_affine_invariant_distance(a, b) )
Returns three blocks: composition / regression scores, Monte Carlo
summaries (ADEMP-style), and optimisation / runtime diagnostics. When
is a matrix (or
is), cell-type Pearson correlation and Monte
Carlo summaries are computed across samples, not within a single
composition.
compute_benchmark_metrics( y, mean_signature_matrix, estimated_p, true_ratios = NULL, se = NULL, lower = NULL, upper = NULL, elapsed = NULL, memory_bytes = NULL, kkt_residual = NULL, numerical_converged = NULL, loglik_hat = NULL, loglik_true = NULL, converged = NULL, local_maximum = NULL, min_eigenvalue = NULL, max_eigenvalue = NULL, presence_threshold = 1e-04, level = 0.95, coverage_interval = "wilson", algorithm = NA_character_ )compute_benchmark_metrics( y, mean_signature_matrix, estimated_p, true_ratios = NULL, se = NULL, lower = NULL, upper = NULL, elapsed = NULL, memory_bytes = NULL, kkt_residual = NULL, numerical_converged = NULL, loglik_hat = NULL, loglik_true = NULL, converged = NULL, local_maximum = NULL, min_eigenvalue = NULL, max_eigenvalue = NULL, presence_threshold = 1e-04, level = 0.95, coverage_interval = "wilson", algorithm = NA_character_ )
y |
Numeric vector (or one-column matrix)
|
mean_signature_matrix |
Numeric matrix
|
estimated_p |
Estimated proportions: a length- |
true_ratios |
Optional ground truth with the same layout. When
omitted, reconstitution scores compare
|
se |
Optional standard errors matching |
lower, upper
|
Optional interval bounds matching |
elapsed, memory_bytes, kkt_residual
|
Optional per-sample runtime
diagnostics (recycled to |
numerical_converged |
Optional logical per sample: the solver returned a finite simplex vector. |
loglik_hat, loglik_true
|
Optional per-sample log-likelihoods used
for theoretical convergence (regret
|
converged |
Optional list of length |
local_maximum |
Optional logical per sample: ILR score small and
Hessian negative definite ( |
min_eigenvalue, max_eigenvalue
|
Optional ILR Hessian extrema. |
presence_threshold |
Threshold |
level |
Wald coverage level when |
coverage_interval |
Interval for the Monte Carlo coverage rate
|
algorithm |
Character label stored on each metrics row (typically the solver name). Recycled to length one. |
A named list with:
regression: global (one row per sample) and cell_type (one
row per cell type);
monte_carlo: one row per cell type, including theoretical_se
(filled later from expected Fisher at );
optimisation: one row per sample.
mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(paste0("g", 1:2), paste0("ct", 1:2))) y <- drop(mu %*% c(0.4, 0.6)) compute_benchmark_metrics(y, mu, estimated_p = c(0.45, 0.55), true_ratios = c(0.4, 0.6))mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(paste0("g", 1:2), paste0("ct", 1:2))) y <- drop(mu %*% c(0.4, 0.6)) compute_benchmark_metrics(y, mu, estimated_p = c(0.45, 0.55), true_ratios = c(0.4, 0.6))
Fits a multinomial (or binomial) elastic net (glmnet::glmnet()) that
predicts cell type from expression features. Inputs are purified
expression profiles
(genes
cell types samples) and length- cell-type labels.
Variability across samples replaces synthetic isotropic noise. Gene scores
are the sum over classes of absolute coefficients at a chosen
(intercept excluded). For nested / CV selection of
, see the experimental
compute_glmnet_gene_scores_cv() helper (not shipped in the package
build).
compute_glmnet_gene_scores( expression_profiles, celltype_labels, alpha = 0.5, lambda = NULL, ... )compute_glmnet_gene_scores( expression_profiles, celltype_labels, alpha = 0.5, lambda = NULL, ... )
expression_profiles |
Numeric array
|
celltype_labels |
Character or factor labels of length |
alpha |
Elastic-net mixing parameter in |
lambda |
Optional penalty value at which coefficients are extracted.
When |
... |
Additional arguments forwarded to |
Named numeric vector of length (gene scores; larger means
stronger multinomial signal).
compute_average_jeffreys(), compute_average_overlap()
## Not run: set.seed(1) G <- 4L J <- 3L N <- 12L profiles <- array(0, dim = c(G, J, N)) for (j in seq_len(J)) { profiles[j, j, ] <- 5 + stats::rnorm(N, sd = 0.2) } labels <- paste0("ct", seq_len(J)) scores <- compute_glmnet_gene_scores(profiles, labels) names(scores)[which.max(scores)] ## End(Not run)## Not run: set.seed(1) G <- 4L J <- 3L N <- 12L profiles <- array(0, dim = c(G, J, N)) for (j in seq_len(J)) { profiles[j, j, ] <- 5 + stats::rnorm(N, sd = 0.2) } labels <- paste0("ct", seq_len(J)) scores <- compute_glmnet_gene_scores(profiles, labels) names(scores)[which.max(scores)] ## End(Not run)
For a probability vector over
classes (here: cell types), the Shannon entropy is
with the convention . Dividing by the maximum
entropy (uniform over all classes) yields
normalised Shannon entropy
so for a Dirac mass on one type and
for the uniform distribution over the
cell types. Zero masses are dropped only inside the sum; the
normaliser still uses the original class count .
This is preferable to changing the logarithm base to the number of
positive masses , which would renormalise only on the
support and hide sparsity relative to the full panel of cell types.
compute_shannon_entropy(ratios)compute_shannon_entropy(ratios)
ratios |
Numeric vector of cellular proportions
|
Scalar normalised entropy .
Panel B of the figure contrasts a peaked (type-specific, low entropy)
share vector with a flat (multi-type, high entropy) one for
classes.
compute_shannon_entropy(c(1, 0, 0)) # Dirac -> 0 compute_shannon_entropy(rep(1 / 3, 3)) # uniform -> 1compute_shannon_entropy(c(1, 0, 0)) # Dirac -> 0 compute_shannon_entropy(rep(1 / 3, 3)) # uniform -> 1
Inverts the likelihood-ratio test of lrt_decovart() coordinate by
coordinate: the interval collects every with
. Because likelihood ratios are invariant
under reparametrisation, the interval respects
without the delta-method linearisation that makes
Wald intervals unreliable near a simplex face.
confint_profile_decovart( y = NULL, mean_signature_matrix, Sigma, bulk_expression = NULL, level = 0.95, celltypes = NULL, epsilon = 10^-8, itmax = 500L )confint_profile_decovart( y = NULL, mean_signature_matrix, Sigma, bulk_expression = NULL, level = 0.95, celltypes = NULL, epsilon = 10^-8, itmax = 500L )
y |
Numeric vector (or one-column matrix)
|
mean_signature_matrix |
Numeric matrix
|
Sigma |
Array of cell-type covariances in
|
bulk_expression |
Numeric matrix |
level |
Confidence level |
celltypes |
Cell types to profile; defaults to all. |
epsilon, itmax
|
Relative convergence tolerance and iteration
budget passed to |
A matrix with one row per cell type and columns estimate,
lower, upper.
confint.decovart_fit() for the Wald analogue,
lrt_decovart()
Other decovart_inference:
bootstrap_decovart(),
boundary_diagnostics(),
chi_bar_square_pvalue(),
equivariance_check_decovart(),
lrt_decovart(),
multistart_decovart(),
profile_loglik_decovart(),
reference_bootstrap_decovart(),
restricted_mle_decovart()
mu <- matrix(c(20, 40, 15, 40, 20, 25), nrow = 3) colnames(mu) <- paste0("ct", 1:2) Sigma <- array(c(diag(3), diag(3)), dim = c(3, 3, 2)) y <- drop(mu %*% c(0.6, 0.4)) confint_profile_decovart(y, mu, Sigma)mu <- matrix(c(20, 40, 15, 40, 20, 25), nrow = 3) colnames(mu) <- paste0("ct", 1:2) Sigma <- array(c(diag(3), diag(3)), dim = c(3, 3, 2)) y <- drop(mu %*% c(0.6, 0.4)) confint_profile_decovart(y, mu, Sigma)
Interval for a binomial coverage rate
from independent coverage indicators.
Wilson is the default because the parameter is bounded in
and Wald intervals behave poorly near 0 or 1.
coverage_mc_interval( covered, conf_level = 0.95, method = c("wilson", "wald", "agresti_coull") )coverage_mc_interval( covered, conf_level = 0.95, method = c("wilson", "wald", "agresti_coull") )
covered |
Logical or 0/1 coverage indicators (one per replicate). |
conf_level |
Confidence level for the interval around
|
method |
|
A list with n, successes, coverage, mcse, lower,
upper, and method.
coverage_mc_interval(c(TRUE, TRUE, TRUE, FALSE))coverage_mc_interval(c(TRUE, TRUE, TRUE, FALSE))
For each column of the bulk matrix
, estimates
with every supplied algorithm.
Samples are iterated with furrr when cores > 1; algorithms are
sequential, so workers are never nested. When covariance information
is provided, DeCovarT methods maximise
under
.
deconvolute_ratios( signature_matrix, bulk_expression, true_ratios = NULL, Sigma = NULL, deconvolution_functions = NULL, standardise = FALSE, scaled = FALSE, cores = getOption("mc.cores", 1L), coverage_interval = "wilson", verbose = FALSE, progress_every = 10L )deconvolute_ratios( signature_matrix, bulk_expression, true_ratios = NULL, Sigma = NULL, deconvolution_functions = NULL, standardise = FALSE, scaled = FALSE, cores = getOption("mc.cores", 1L), coverage_interval = "wilson", verbose = FALSE, progress_every = 10L )
signature_matrix |
Mean signature
|
bulk_expression |
Bulk matrix
|
true_ratios |
Optional ground-truth proportions: a length- |
Sigma |
Optional array
|
deconvolution_functions |
Named list; each element has |
standardise |
If |
scaled |
Deprecated. |
cores |
Number of |
coverage_interval |
Passed to |
verbose |
If |
progress_every |
Sample-progress interval when |
A named list from compute_benchmark_metrics() with
regression (global and cell-type subtables), monte_carlo, and
optimisation (per-sample elapsed time, memory, KKT residual, and
). First-generation solvers still call
repair_simplex(); the three native DeCovarT maps (ILR or
) already lie on the simplex. When Sigma is
supplied, Newton-Raphson, Marquardt-Levenberg, L-BFGS-B, BFGS
(gradient), and simulated annealing attach ILR Wald standard
errors from vcov_ilr_delta() so monte_carlo can report
coverage, mean model SE, and the SE/SD ratio. Those SEs are the
expected-Fisher delta-method bounds used by
confint.decovart_fit(), not diagonal entries of the observed
Hessian. Mean-only solvers (NNLS, LSEI) leave Wald columns
missing: those estimators do not use the convolution likelihood.
Erkkilä T, Lehmusvaara S, Ruusuvuori P, Visakorpi T, Shmulevich I, Lähdesmäki H (2010). “Probabilistic Analysis of Gene Expression Measurements from Heterogeneous Tissues.” Bioinformatics, 26. doi:10.1093/bioinformatics/btq406. Ahn J, Yuan Y, Parmigiani G, Suraokar MB, Diao L, Wistuba II, Wang W (2013). “DeMix: Deconvolution for Mixed Cancer Transcriptomes Using Raw Measured Data.” Bioinformatics (Oxford, England), 29. doi:10.1093/bioinformatics/btt301. Wang Z, Cao S, Morris JS, Ahn J, Liu R, Tyekucheva S, Gao F, Li B, Lu W, Tang X, Wistuba II, Bowden M, Mucci L, Loda M, Parmigiani G, Holmes CC, Wang W (2018). “Transcriptome Deconvolution of Heterogeneous Tumor Samples with Immune Infiltration.” iScience, 9. doi:10.1016/j.isci.2018.10.028. Anghel CV, Quon G, Haider S, Nguyen F, Deshwar AG, Morris QD, Boutros PC (2015). “ISOpureR: An R Implementation of a Computational Purification Algorithm of Mixed Tumour Profiles.” BMC Bioinformatics, 16. doi:10.1186/s12859-015-0597-x. Ogundijo OE, Wang X (2017). “A Sequential Monte Carlo Approach to Gene Expression Deconvolution.” PLOS ONE, 12. doi:10.1371/journal.pone.0186167. Hafemeister C, Satija R (2019). “Normalization and variance stabilization of single-cell RNA-seq data using regularized negative binomial regression.” Genome Biology, 20(1), 296. doi:10.1186/s13059-019-1874-1. Chion M, Leroy A (2023). “A Bayesian Framework for Multivariate Differential Analysis Accounting for Missing Data.” arXiv. doi:10.48550/arxiv.2307.08975. Newman A, Liu C, Green M, Gentles A, Feng W, Xu Y, Hoang C, Diehn M, Alizadeh A (2015). “Robust Enumeration of Cell Subsets from Tissue Expression Profiles.” Nature methods, 12. doi:10.1038/nmeth.3337.
deconvolute_ratios_Marquardt_Levenberg()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) Y <- simulate_bulk_mixture(mu, Sigma, p = c(0.5, 0.5), n = 2)$Y deconvolute_ratios( signature_matrix = mu, bulk_expression = Y, true_ratios = c(0.5, 0.5), Sigma = Sigma, deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), cores = 1 )set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) Y <- simulate_bulk_mixture(mu, Sigma, p = c(0.5, 0.5), n = 2)$Y deconvolute_ratios( signature_matrix = mu, bulk_expression = Y, true_ratios = c(0.5, 0.5), Sigma = Sigma, deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), cores = 1 )
Estimates
under the
Gaussian convolution model
subject to the simplex constraint
, .
Optimisation is performed in unconstrained ILR coordinates
via
(Marquardt–Levenberg default; see other methods below and
vignette("theory-decovart-generative-model", package = "DeCovarT")).
deconvolute_ratios_cibersort(y, mean_signature_matrix) deconvolute_ratios_rlm(y, mean_signature_matrix) deconvolute_ratios_nnls(y, mean_signature_matrix) deconvolute_ratios_deconrnaseq(y, mean_signature_matrix) deconvolute_ratios_Marquardt_Levenberg( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 ) deconvolute_ratios_simulated_annealing( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 ) deconvolute_ratios_L_BFGS_B( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 ) deconvolute_ratios_Newton_Raphson( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 ) deconvolute_ratios_gradient_descent( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 )deconvolute_ratios_cibersort(y, mean_signature_matrix) deconvolute_ratios_rlm(y, mean_signature_matrix) deconvolute_ratios_nnls(y, mean_signature_matrix) deconvolute_ratios_deconrnaseq(y, mean_signature_matrix) deconvolute_ratios_Marquardt_Levenberg( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 ) deconvolute_ratios_simulated_annealing( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 ) deconvolute_ratios_L_BFGS_B( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 ) deconvolute_ratios_Newton_Raphson( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 ) deconvolute_ratios_gradient_descent( y, mean_signature_matrix, Sigma, epsilon = 10^-4, itmax = 200, return_model = FALSE, initial_p = NULL, dirichlet_alpha = 1 )
y |
Numeric vector (or one-column matrix)
|
mean_signature_matrix |
Numeric matrix
|
Sigma |
Array of cell-type covariances in
|
epsilon, itmax
|
Absolute convergence tolerance and maximum number of
iterations for the optimiser (same roles as |
return_model |
If |
initial_p |
Optional start: |
dirichlet_alpha |
Dirichlet concentration when
|
Plug-in signature. Argument mean_signature_matrix is the mean
, used as a proxy for the unobserved cell-type
profiles . This is the frequentist plug-in;
recovering sample-specific latents is a Bayesian / MAP problem
(.map_gaussian_convolution()).
Named numeric vector on the simplex
(ILR methods), or that list when return_model = TRUE.
Benchmark metrics are computed by deconvolute_ratios().
deconvolute_ratios_cibersort(): Linear baseline
via nu-SVR
(CIBERSORT-style); no covariance prior is used.
deconvolute_ratios_rlm(): Robust linear model
(MASS::rlm()), as in
Monaco et al. (2019).
deconvolute_ratios_nnls(): Non-negative least squares
for
(nnls::nnls()), then simplex projection.
deconvolute_ratios_deconrnaseq(): Equality- and
inequality-constrained least squares on the simplex
(limSolve::lsei()), in the spirit of deconRNASeq.
deconvolute_ratios_simulated_annealing(): Simulated annealing on
ILR coordinates (stats::optim() with
method = "SANN").
deconvolute_ratios_L_BFGS_B(): Box-constrained L-BFGS-B
directly in (stats::optim() method = "L-BFGS-B").
The box keeps each coordinate in ; the returned vector is
closed by (no repair_simplex() clipping). Wald
standard errors still use the ILR expected-Fisher map
vcov_ilr_delta(), not a -space Hessian.
deconvolute_ratios_Newton_Raphson(): Newton–Raphson /
nlminb on ILR coordinates using analytic
gradient and Hessian (stats::nlminb()).
deconvolute_ratios_gradient_descent(): BFGS quasi-Newton ascent
on ILR coordinates (stats::optim()
method = "BFGS").
Monaco G, Lee B, Xu W, Mustafah S, Hwang YY, Carré C, Burdin N, Visan L, Ceccarelli M, Poidinger M, Zippelius A, Pedro de Magalhães J, Larbi A (2019). “RNA-Seq Signatures Normalized by mRNA Abundance Allow Absolute Deconvolution of Human Immune Cell Types.” Cell Reports, 26. doi:10.1016/j.celrep.2019.01.041.
deconvolute_ratios(), isometric_logistic(),
starting_simplex()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) y <- drop(mu %*% c(0.6, 0.4) + rnorm(2, sd = 0.1)) deconvolute_ratios_Marquardt_Levenberg(y, mu, Sigma, itmax = 50)set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) y <- drop(mu %*% c(0.6, 0.4) + rnorm(2, sd = 0.1)) deconvolute_ratios_Marquardt_Levenberg(y, mu, Sigma, itmax = 50)
Fits
by
MASS::lm.gls() with a known residual
covariance W that does not depend on , then
repair_simplex(). Use fixed_gls_covariance() for the
global-diagonal competitor
.
This is the mean-only GLS gold standard for covariance-structure
benchmarks, not the DeCovarT convolution
.
deconvolute_ratios_gls(y, mean_signature_matrix, W, inverse = TRUE)deconvolute_ratios_gls(y, mean_signature_matrix, W, inverse = TRUE)
y |
Numeric bulk vector of length |
mean_signature_matrix |
Numeric matrix |
W |
Residual covariance (when |
inverse |
Passed to |
Named simplex vector of length .
fixed_gls_covariance(), deconvolute_ratios_deconrnaseq()
mu <- cbind(ct1 = c(20, 40), ct2 = c(40, 20)) y <- drop(mu %*% c(0.4, 0.6)) Sigma <- array(c(diag(2), 2 * diag(2)), dim = c(2, 2, 2)) deconvolute_ratios_gls(y, mu, fixed_gls_covariance(Sigma))mu <- cbind(ct1 = c(20, 40), ct2 = c(40, 20)) y <- drop(mu %*% c(0.4, 0.6)) Sigma <- array(c(diag(2), 2 * diag(2)), dim = c(2, 2, 2)) deconvolute_ratios_gls(y, mu, fixed_gls_covariance(Sigma))
Summarises the generating law
at five layers: composition,
mean geometry, covariance / SPD diagnostics, network sparsity, and
tangent Fisher information (mean versus covariance split). MixSim
BarOmega and average pairwise Hellinger of the component Gaussians
are kept in descriptors (Hellinger is the unweighted pairwise mean;
hellinger_weighted is optional). Jeffreys / symmetrised KL is
returned in supplementary. Mean-geometry columns include the
highest pairwise cosine and the mean Euclidean (CLD) gap.
describe_simulation_scenario( true_theta, adjacency = NULL, active_tol = 1e-08, include_mixsim = TRUE )describe_simulation_scenario( true_theta, adjacency = NULL, active_tol = 1e-08, include_mixsim = TRUE )
true_theta |
List with |
adjacency |
Optional |
active_tol |
Threshold for counting an active simplex component. |
include_mixsim |
Logical. If |
A list with:
theta_true: the convolution parameters p, mu, sigma;
descriptors: one-row tibble of kept scenario statistics in six
families (composition, mean geometry, SPD of
, tangent Fisher, network,
component overlap). MixSim BarOmega uses compute_average_overlap()
(Davies quadrature for , Sobol Monte Carlo otherwise).
Average pairwise Hellinger and affine-invariant Riemannian
distance of the are kept alongside.
hellinger_weighted is optional. SPD columns include both
(kappa_sigma_p) and the reciprocal
(kappa_sigma_reciprocal);
supplementary: one-row tibble of Jeffreys / symmetrised KL,
recorded but not treated as a primary score;
call: the matched call (match.call()).
run_simulation_benchmark(), expected_fisher_unconstrained(),
compute_shannon_entropy(), composition_from_entropy(),
helmert_basis(), compute_average_overlap(),
compute_average_riemannian()
theta <- list( p = c(0.5, 0.5), mu = cbind(c(10, 0), c(0, 10)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) out <- describe_simulation_scenario(theta) out$descriptors$h_star out$calltheta <- list( p = c(0.5, 0.5), mu = cbind(c(10, 0), c(0, 10)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) out <- describe_simulation_scenario(theta) out$descriptors$h_star out$call
. Positive
semidefinite for .
equicorrelation_gram(n_celltypes, target_cosine = 0)equicorrelation_gram(n_celltypes, target_cosine = 0)
n_celltypes |
Integer |
target_cosine |
Pairwise cosine |
Symmetric matrix with unit diagonal.
equicorrelation_gram(3L, 0.4)equicorrelation_gram(3L, 0.4)
For the multivariate-normal mean–covariance map of the DeCovarT
convolution,
with
and precision
,
the expected Fisher information has entries
The first summand is the mean contribution
(an -inner product of signature columns); the
second is the covariance contribution of the quadratic map
. See the
multivariate-normal formula on
https://en.wikipedia.org/wiki/Fisher_information#Multivariate_normal_distribution.
expected_fisher_unconstrained(p, mean_signature_matrix, Sigma)expected_fisher_unconstrained(p, mean_signature_matrix, Sigma)
p |
Numeric proportions on the open simplex. |
mean_signature_matrix |
Mean signature |
Sigma |
Cell-type covariances |
Symmetric expected Fisher information matrix
.
vcov_ilr_delta(), vcov.decovart_fit(),
confint.decovart_fit(), .inner_product()
# Two cell types, bivariate Gaussians: I(p) is 2 x 2. p <- c(0.6, 0.4) mu <- cbind(c(0, 0), c(3, 0)) Sigma <- array(c(diag(2), diag(2)), dim = c(2, 2, 2)) I <- expected_fisher_unconstrained(p, mu, Sigma) I# Two cell types, bivariate Gaussians: I(p) is 2 x 2. p <- c(0.6, 0.4) mu <- cbind(c(0, 0), c(3, 0)) Sigma <- array(c(diag(2), diag(2)), dim = c(2, 2, 2)) I <- expected_fisher_unconstrained(p, mu, Sigma) I
Maximum-likelihood estimator of cellular proportions
under the multivariate Gaussian convolution
This is a variance / likelihood specification, not ordinary least
squares: cell-type covariances enter the residual law and cannot be
written as extra columns of . There is therefore no
formula / lm interface, and no predict() method for forecasting
bulk expression.
The returned object is an S3 class decovart_fit with accessors
coef(), fitted(), residuals(), vcov(), nobs(), confint(),
print(), summary() and plot(). Residuals are
; they are not OLS
residuals. Goodness of fit is the convolution log-likelihood, not
residual sum of squares.
fit_decovart( signature_matrix, bulk_expression, true_ratios = NULL, Sigma = NULL, method = c("Marquardt-Levenberg", "L-BFGS-B", "Newton-Raphson"), epsilon = 10^-4, itmax = 200, standardise = FALSE, scaled = FALSE, n_starts = 0L, boundary_tol = 1e-08 ) ## S3 method for class 'decovart_fit' print(x, ...) ## S3 method for class 'decovart_fit' summary(object, ...) ## S3 method for class 'summary.decovart_fit' print(x, ...) ## S3 method for class 'decovart_fit' coef(object, ...) ## S3 method for class 'decovart_fit' fitted(object, ...) ## S3 method for class 'decovart_fit' residuals(object, ...) ## S3 method for class 'decovart_fit' vcov(object, ...) ## S3 method for class 'decovart_fit' nobs(object, ...) ## S3 method for class 'decovart_fit' confint(object, parm, level = 0.95, ...) ## S3 method for class 'decovart_fit' plot(x, ...)fit_decovart( signature_matrix, bulk_expression, true_ratios = NULL, Sigma = NULL, method = c("Marquardt-Levenberg", "L-BFGS-B", "Newton-Raphson"), epsilon = 10^-4, itmax = 200, standardise = FALSE, scaled = FALSE, n_starts = 0L, boundary_tol = 1e-08 ) ## S3 method for class 'decovart_fit' print(x, ...) ## S3 method for class 'decovart_fit' summary(object, ...) ## S3 method for class 'summary.decovart_fit' print(x, ...) ## S3 method for class 'decovart_fit' coef(object, ...) ## S3 method for class 'decovart_fit' fitted(object, ...) ## S3 method for class 'decovart_fit' residuals(object, ...) ## S3 method for class 'decovart_fit' vcov(object, ...) ## S3 method for class 'decovart_fit' nobs(object, ...) ## S3 method for class 'decovart_fit' confint(object, parm, level = 0.95, ...) ## S3 method for class 'decovart_fit' plot(x, ...)
signature_matrix |
Mean signature
|
bulk_expression |
Bulk matrix
|
true_ratios |
Optional ground-truth proportions ( |
Sigma |
Array
|
method |
Optimiser; one of |
epsilon, itmax
|
Absolute convergence tolerance and iteration
budget, in the same roles as |
standardise |
If |
scaled |
Deprecated. |
n_starts |
Number of additional random Dirichlet starts per
sample. With |
boundary_tol |
Threshold on |
x, object
|
A |
... |
Passed to |
parm |
Kept for compatibility with |
level |
Confidence level |
Standardisation. CIBERSORT requires non-negative expression, no
missing values, and a non-log linear scale
(Newman et al. 2015). A logarithm is
concave, so Jensen's inequality shifts first and second moments and
.
A gene-wise affine map
with the
same on , and
leaves the
MLE of unchanged (equivariance).
Wald covariance. Let
.
The expected Fisher information of the unconstrained mean–covariance
map (multivariate normal; see e.g. the Wikipedia entry Fisher
information, multivariate normal) is
Cramer–Rao gives
in ILR coordinates, with
and
(jacobian_isometric_logistic()). The delta method maps the bound
back to the simplex:
An object of class decovart_fit.
Newman A, Liu C, Green M, Gentles A, Feng W, Xu Y, Hoang C, Diehn M, Alizadeh A (2015). “Robust Enumeration of Cell Subsets from Tissue Expression Profiles.” Nature methods, 12. doi:10.1038/nmeth.3337.
deconvolute_ratios(),
deconvolute_ratios_Marquardt_Levenberg(),
expected_fisher_unconstrained(), vcov_ilr_delta(),
coef.decovart_fit(), vcov.decovart_fit(),
confint.decovart_fit()
toy <- readRDS(system.file( "extdata", "toy_deconvolution.rds", package = "DeCovarT" )) fit <- fit_decovart( signature_matrix = toy$signature_matrix, bulk_expression = toy$bulk_expression, Sigma = toy$Sigma, itmax = 40 ) coef(fit) nobs(fit)toy <- readRDS(system.file( "extdata", "toy_deconvolution.rds", package = "DeCovarT" )) fit <- fit_decovart( signature_matrix = toy$signature_matrix, bulk_expression = toy$bulk_expression, Sigma = toy$Sigma, itmax = 40 ) coef(fit) nobs(fit)
Evaluates the convolution covariance
at a known composition (default the barycentre
) and optionally keeps only the diagonal. That matrix
does not depend on the unknown at fit time.
Do not copy it into every slice of a DeCovarT tensor: that would
yield , which still
depends on .
fixed_gls_covariance(Sigma, p = NULL, diagonal = TRUE)fixed_gls_covariance(Sigma, p = NULL, diagonal = TRUE)
Sigma |
Array |
p |
Optional composition of length |
diagonal |
If |
A covariance matrix.
Sigma <- array(c(diag(2), 2 * diag(2)), dim = c(2, 2, 2)) fixed_gls_covariance(Sigma)Sigma <- array(c(diag(2), 2 * diag(2)), dim = c(2, 2, 2)) fixed_gls_covariance(Sigma)
Builds
whose unit columns
realise a prescribed Gram matrix (pairwise cosines) via the
symmetric square root,
Then whenever
has unit diagonal. The default is the equicorrelation
matrix
(equicorrelation_gram()). Supply target_gram to set unequal
pairwise cosines (for example two close pairs and a distant background),
provided \(R\) is symmetric nonnegative-definite.
generate_mean_signature_matrix( n_genes, n_celltypes, mean_scale = 10, target_cosine = 0, target_gram = NULL, seed = NULL, nonnegative = FALSE, gene_names = NULL, celltype_names = NULL )generate_mean_signature_matrix( n_genes, n_celltypes, mean_scale = 10, target_cosine = 0, target_gram = NULL, seed = NULL, nonnegative = FALSE, gene_names = NULL, celltype_names = NULL )
n_genes |
Integer |
n_celltypes |
Integer |
mean_scale |
Positive scalar |
target_cosine |
Numeric pairwise cosine |
target_gram |
Optional symmetric |
seed |
Optional integer. When supplied, the orthonormal frame
|
nonnegative |
If |
gene_names |
Optional character vector of length |
celltype_names |
Optional character vector of length |
Realisations are unique up to an orthogonal transformation of the gene
space: replacing by with
leaves the Gram unchanged. By
default is a deterministic thin QR frame; pass seed
for a Haar-like Gaussian frame. Cholesky is an
alternative square root used in Monte Carlo simulation; see the
how-to-build-synthetic-scenarios vignette appendix.
Numeric matrix with dimensions
.
generate_mean_signature_matrix( n_genes = 6L, n_celltypes = 2L, target_cosine = 0.5 ) k <- matrix(c(1, 0.98, 0.2, 0.98, 1, 0.2, 0.2, 0.2, 1), 3, 3) generate_mean_signature_matrix( n_genes = 8L, n_celltypes = 3L, target_gram = k )generate_mean_signature_matrix( n_genes = 6L, n_celltypes = 2L, target_cosine = 0.5 ) k <- matrix(c(1, 0.98, 0.2, 0.98, 1, 0.2, 0.2, 0.2, 1), 3, 3) generate_mean_signature_matrix( n_genes = 8L, n_celltypes = 3L, target_gram = k )
Returns the Helmert sub-matrix
with
Column contrasts the first parts against part
. This is the standard ILR basis used with
isometric_log_ratio() / isometric_logistic(); any other valid ILR
basis is for an orthogonal , which rotates
coordinates but leaves simplex geometry, eigenvalues of quadratic
forms, and unchanged.
helmert_basis(n_parts)helmert_basis(n_parts)
n_parts |
Integer |
Numeric matrix .
V <- helmert_basis(3L) crossprod(V) drop(crossprod(V, rep(1, 3)))V <- helmert_basis(3L) crossprod(V) drop(crossprod(V, rep(1, 3)))
Slice is
where ,
and
. Array shape .
hessian_isometric_logistic(z, V = NULL)hessian_isometric_logistic(z, V = NULL)
z |
Numeric vector |
V |
Optional ILR basis; see |
Numeric array used in the constrained Hessian chain rule.
hessian_isometric_logistic(c(0.2, -0.5))hessian_isometric_logistic(c(0.2, -0.5))
Inverse isometric log-ratio map
with a
Helmert basis ,
No cell type is pinned as a reference (unlike
additive_logistic()). Evaluated with a log-sum-exp shift so large
cannot overflow.
which is well-defined because .
Requires a strictly positive composition (open simplex).
isometric_logistic(z, V = NULL) isometric_log_ratio(p, V = NULL)isometric_logistic(z, V = NULL) isometric_log_ratio(p, V = NULL)
z |
Numeric vector |
V |
Optional ILR basis; see |
p |
Numeric vector on the open simplex. |
Numeric vector on the unit simplex.
Numeric vector .
isometric_log_ratio(), helmert_basis(),
additive_logistic()
z <- c(0.2, -0.5) p <- isometric_logistic(z) sum(p) isometric_log_ratio(p) isometric_logistic(c(800, 1200))z <- c(0.2, -0.5) p <- isometric_logistic(z) sum(p) isometric_log_ratio(p) isometric_logistic(c(800, 1200))
with .
jacobian_isometric_logistic(z, V = NULL)jacobian_isometric_logistic(z, V = NULL)
z |
Numeric vector |
V |
Optional ILR basis; see |
Numeric matrix .
jacobian_isometric_logistic(c(0.2, -0.5))jacobian_isometric_logistic(c(0.2, -0.5))
Tests against the unrestricted simplex with
the profile likelihood-ratio statistic
where is the restricted MLE of
restricted_mle_decovart(). Unlike the Wald interval of
confint.decovart_fit(), is invariant under the additive
log-ratio reparametrisation, because a smooth bijection leaves
likelihood ratios unchanged.
lrt_decovart( y = NULL, mean_signature_matrix, Sigma, null_value, bulk_expression = NULL, n_boundary = NULL, boundary_tol = 1e-06, epsilon = 10^-8, itmax = 500L )lrt_decovart( y = NULL, mean_signature_matrix, Sigma, null_value, bulk_expression = NULL, n_boundary = NULL, boundary_tol = 1e-06, epsilon = 10^-8, itmax = 500L )
y |
Numeric vector (or one-column matrix)
|
mean_signature_matrix |
Numeric matrix
|
Sigma |
Array of cell-type covariances in
|
null_value |
Named numeric vector of hypothesised ratios, e.g.
|
bulk_expression |
Numeric matrix |
n_boundary |
Optional integer override for the number of active
boundary constraints; |
boundary_tol |
Distance from 0 or 1 below which a null value counts as being on the boundary. |
epsilon, itmax
|
Relative convergence tolerance and iteration
budget passed to |
Interior null. If every tested ratio lies strictly inside
, is asymptotically
(Wilks 1938).
Boundary null. A null such as lets the parameter move
in one direction only, so the local parameter space is a half-line
rather than a vector space and Wilks' theorem fails. The limit is the
chi-bar-square mixture of chi_bar_square_pvalue(): for a single
active constraint,
Chernoff (1954),
generalised to several active constraints by
Self and Liang (1987).
Boundary calibration is selected automatically when any tested value is
within boundary_tol of 0 or 1; override with n_boundary.
Replication. Both calibrations are population statements: they
need replicate bulk samples that genuinely share one composition
. A single -vector supplies no replication
(genes are dependent through ,
and adding bulk samples with distinct compositions adds parameters as
fast as observations). Use bulk_expression with several replicate
columns, or calibrate by bootstrap_decovart().
A one-row data frame with columns statistic,
n_boundary, df_interior, p_value, loglik_full,
loglik_null, and calibration.
Wilks SS (1938). “The Large-Sample Distribution of the Likelihood Ratio for Testing Composite Hypotheses.” The Annals of Mathematical Statistics, 9(1), 60–62. ISSN 0003-4851. doi:10.1214/aoms/1177732360.
Self SG, Liang K (1987). “Asymptotic Properties of Maximum Likelihood Estimators and Likelihood Ratio Tests under Nonstandard Conditions.” Journal of the American Statistical Association, 82(398), 605–610. ISSN 0162-1459. doi:10.1080/01621459.1987.10478472.
chi_bar_square_pvalue(), confint_profile_decovart(),
bootstrap_decovart()
Other decovart_inference:
bootstrap_decovart(),
boundary_diagnostics(),
chi_bar_square_pvalue(),
confint_profile_decovart(),
equivariance_check_decovart(),
multistart_decovart(),
profile_loglik_decovart(),
reference_bootstrap_decovart(),
restricted_mle_decovart()
mu <- matrix(c(20, 22, 18, 22, 20, 24), nrow = 2) colnames(mu) <- paste0("ct", 1:3) Sigma <- array(c(diag(2), diag(2), diag(2)), dim = c(2, 2, 3)) y <- drop(mu %*% c(0.5, 0.5, 0)) lrt_decovart(y, mu, Sigma, null_value = c(ct3 = 0))mu <- matrix(c(20, 22, 18, 22, 20, 24), nrow = 2) colnames(mu) <- paste0("ct", 1:3) Sigma <- array(c(diag(2), diag(2), diag(2)), dim = c(2, 2, 3)) y <- drop(mu %*% c(0.5, 0.5, 0)) lrt_decovart(y, mu, Sigma, null_value = c(ct3 = 0))
The realised DeCovarT log-likelihood is not globally concave, so a
successful convergence code, a small relative-distance-to-minimum and a
negative-definite local Hessian together establish a local maximum
only. Refitting from independent Dirichlet starts and
comparing the attained log-likelihoods is the cheapest direct probe of
a second, equally good or better mode.
multistart_decovart( y, mean_signature_matrix, Sigma, n_starts = 5L, solver = deconvolute_ratios_Marquardt_Levenberg, loglik_tol = 1e-04, dirichlet_alpha = 1, ... )multistart_decovart( y, mean_signature_matrix, Sigma, n_starts = 5L, solver = deconvolute_ratios_Marquardt_Levenberg, loglik_tol = 1e-04, dirichlet_alpha = 1, ... )
y |
Numeric vector (or one-column matrix)
|
mean_signature_matrix |
Numeric matrix
|
Sigma |
Array of cell-type covariances in
|
n_starts |
Number of Dirichlet restarts (in addition to the equi-balanced start). |
solver |
Solver accepting |
loglik_tol |
Two starts count as reaching the same mode when their log-likelihoods differ by less than this amount. |
dirichlet_alpha |
Concentration for those random starts (default
|
... |
Passed to |
A list with coefficients (best fit), loglik,
loglik_range, starts (matrix of per-start estimates),
logliks, and multimodal.
boundary_diagnostics(), fit_decovart()
Other decovart_inference:
bootstrap_decovart(),
boundary_diagnostics(),
chi_bar_square_pvalue(),
confint_profile_decovart(),
equivariance_check_decovart(),
lrt_decovart(),
profile_loglik_decovart(),
reference_bootstrap_decovart(),
restricted_mle_decovart()
mu <- matrix(c(20, 40, 15, 40, 20, 25), nrow = 3) colnames(mu) <- paste0("ct", 1:2) Sigma <- array(c(diag(3), diag(3)), dim = c(3, 3, 2)) y <- drop(mu %*% c(0.6, 0.4)) set.seed(1) multistart_decovart(y, mu, Sigma, n_starts = 3L)$multimodalmu <- matrix(c(20, 40, 15, 40, 20, 25), nrow = 3) colnames(mu) <- paste0("ct", 1:2) Sigma <- array(c(diag(3), diag(3)), dim = c(3, 3, 2)) y <- drop(mu %*% c(0.6, 0.4)) set.seed(1) multistart_decovart(y, mu, Sigma, n_starts = 3L)$multimodal
Estimates the MixSim Omega map
by drawing n_mc quasi-Monte Carlo points from each component
(inverse-transform sampling of a Sobol sequence through the
Cholesky factor) and comparing log-densities. The average
pairwise overlap BarOmega is the unweighted mean of
over , matching
MixSim::overlap() / FSDA MixSim.
overlap_gaussian_mc(true_theta, n_mc = 10000L, seed = NULL, J = NULL)overlap_gaussian_mc(true_theta, n_mc = 10000L, seed = NULL, J = NULL)
true_theta |
List with |
n_mc |
Draws per component (default |
seed |
Optional RNG seed (passed to Sobol randomisation and to the uniform fallback). |
J |
Optional number of components. |
For two equal-weight components this pairwise overlap equals the
histogram similarity and therefore
(Nielsen and Sun 2018).
A list with BarOmega, MaxOmega, and OmegaMap.
Nielsen F, Sun K (2018). “Guaranteed Deterministic Bounds on the Total Variation Distance between Univariate Mixtures.” arXiv:1806.11311. doi:10.48550/arxiv.1806.11311. https://arxiv.org/abs/1806.11311.
compute_average_overlap(), compute_average_riemannian()
set.seed(1) theta <- list( p = c(0.5, 0.5), mu = cbind(c(0, 0), c(3, 0)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) overlap_gaussian_mc(theta, n_mc = 2000L)$BarOmegaset.seed(1) theta <- list( p = c(0.5, 0.5), mu = cbind(c(0, 0), c(3, 0)), sigma = array(c(diag(2), diag(2)), dim = c(2, 2, 2)) ) overlap_gaussian_mc(theta, n_mc = 2000L)$BarOmega
Turns the optimisation block of run_simulation_benchmark() into
one row per replicate, algorithm, and cell type, with the matching
true proportion from theta_true. The raincloud intervals drawn from
this table are empirical Monte Carlo quantiles of
, not confidence intervals for
(Allen et al. 2019).
pivot_mc_estimates(benchmark)pivot_mc_estimates(benchmark)
benchmark |
List returned by |
A tibble with algorithm, cell_type, estimate, p_true,
error, sample_id, and any scenario metadata columns.
Allen M, Poggiali D, Whitaker K, Marshall TR, van Langen J, Kievit RA (2019). “Raincloud Plots: A Multi-Platform Tool for Robust Data Visualization.” Wellcome Open Research, 4, 63. ISSN 2398-502X. doi:10.12688/wellcomeopenres.15191.2. https://wellcomeopenresearch.org/articles/4-63.
plot_mc_raincloud(), plot_mc_forest()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( ID = "B1", true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 2, cores = 1 ) nrow(pivot_mc_estimates(out))set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( ID = "B1", true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 2, cores = 1 ) nrow(pivot_mc_estimates(out))
ggplot2::geom_tile() display of algorithm_similarity(), with
rows and columns ordered by Ward D2 clustering of
\(1-r\). Optional dendrogram via ggdendro (Suggests), drawn to
the right of the tiles with leaves flush against the heatmap
and Ward merge heights on the axis.
This is the default for a small correlation matrix;
plot_correlation_Heatmap() is reserved for linked multi-omics grids.
plot_algorithm_similarity( benchmark, facet_rows = NULL, facet_cols = NULL, dendrogram = FALSE )plot_algorithm_similarity( benchmark, facet_rows = NULL, facet_cols = NULL, dendrogram = FALSE )
benchmark |
List from |
facet_rows, facet_cols
|
Optional scenario columns. When supplied, correlations are computed inside each facet cell. |
dendrogram |
If |
A ggplot object.
algorithm_similarity(), plot_mc_metric_dots()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls), "rlm" = list(FUN = deconvolute_ratios_rlm) ), n = 4, cores = 1 ) if (requireNamespace("ggplot2", quietly = TRUE)) { plot_algorithm_similarity(out) }set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls), "rlm" = list(FUN = deconvolute_ratios_rlm) ), n = 4, cores = 1 ) if (requireNamespace("ggplot2", quietly = TRUE)) { plot_algorithm_similarity(out) }
For each algorithm, visualises a selected score over the design grid of
simulated (and related scenario factors).
Requires optional Suggests packages ComplexHeatmap, circlize, and
viridis (install ComplexHeatmap via Bioconductor). Use this
helper only for linked, annotated, hierarchical grids. For
algorithm-similarity correlation matrices prefer
plot_algorithm_similarity() (ggplot2::geom_tile()).
plot_correlation_Heatmap( distribution_metrics, score_variable = "model_mse", n_break = 20, uni_scale = TRUE, file = NULL )plot_correlation_Heatmap( distribution_metrics, score_variable = "model_mse", n_break = 20, uni_scale = TRUE, file = NULL )
distribution_metrics |
Tibble of metric scores from a benchmark. |
score_variable |
Column name of the metric to display
( |
n_break |
Number of colour breaks. |
uni_scale |
If |
file |
Optional PDF path. When supplied, heatmaps are drawn with
|
A named list of ComplexHeatmap heatmap objects (one per algorithm).
plot_algorithm_similarity(), plot_mc_metric_dots()
metrics <- tibble::tibble( correlation_celltype1 = c(0, 0, 0.5, 0.5), correlation_celltype2 = c(0, 0.5, 0, 0.5), algorithm = "nnls", model_mse = c(0.01, 0.02, 0.015, 0.03) ) if ( requireNamespace("ComplexHeatmap", quietly = TRUE) && requireNamespace("circlize", quietly = TRUE) && requireNamespace("viridis", quietly = TRUE) ) { ht <- plot_correlation_Heatmap(metrics, score_variable = "model_mse") names(ht) }metrics <- tibble::tibble( correlation_celltype1 = c(0, 0, 0.5, 0.5), correlation_celltype2 = c(0, 0.5, 0, 0.5), algorithm = "nnls", model_mse = c(0.01, 0.02, 0.015, 0.03) ) if ( requireNamespace("ComplexHeatmap", quietly = TRUE) && requireNamespace("circlize", quietly = TRUE) && requireNamespace("viridis", quietly = TRUE) ) { ht <- plot_correlation_Heatmap(metrics, score_variable = "model_mse") names(ht) }
Dot-and-whisker display of bias, RMSE, MAE, coverage, mean interval
width, SE/SD ratio, and optimiser failure rate by algorithm and cell
type
(Allen et al. 2019).
Coverage whiskers are the Wilson interval already stored on
monte_carlo (coverage_mc_interval();
(Wilson 1927)): they are
intervals for the coverage rate, not for . Bias is
referenced at 0; coverage at 0.95; SE/SD (se_sd_ratio, mean model
SE / empirical SD) at 1. Interior Wald SEs come from
vcov_ilr_delta(). Pairwise algorithm contrasts
(MAE differences versus a reference solver on the same Monte Carlo
replicates) can be read from the raincloud of paired errors; they do
not need a second bootstrap.
plot_mc_forest( benchmark, facet_rows = NULL, facet_cols = NULL, metrics = c("bias", "rmse", "mae", "coverage", "mean_interval_width", "se_sd_ratio", "failure_rate") )plot_mc_forest( benchmark, facet_rows = NULL, facet_cols = NULL, metrics = c("bias", "rmse", "mae", "coverage", "mean_interval_width", "se_sd_ratio", "failure_rate") )
benchmark |
List from |
facet_rows, facet_cols
|
Optional column names for
|
metrics |
Character vector of summaries to display. |
A ggplot object.
Allen M, Poggiali D, Whitaker K, Marshall TR, van Langen J, Kievit RA (2019).
“Raincloud Plots: A Multi-Platform Tool for Robust Data Visualization.”
Wellcome Open Research, 4, 63.
ISSN 2398-502X.
doi:10.12688/wellcomeopenres.15191.2.
https://wellcomeopenresearch.org/articles/4-63.
Wilson EB (1927).
“Probable Inference, the Law of Succession, and Statistical Inference.”
Journal of the American Statistical Association, 22(158), 209–212.
ISSN 0162-1459.
doi:10.2307/2276774.
plot_mc_raincloud(), coverage_mc_interval(),
vcov_ilr_delta(), theme_decovart_facets()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( n_genes = 2L, cosine = 0, true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 4, cores = 1 ) if (requireNamespace("ggplot2", quietly = TRUE)) { plot_mc_forest(out, facet_cols = "cosine") }set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( n_genes = 2L, cosine = 0, true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 4, cores = 1 ) if (requireNamespace("ggplot2", quietly = TRUE)) { plot_mc_forest(out, facet_cols = "cosine") }
One panel per metric; colour is a min-max normalised score with
\(1\) = best inside that metric (and facet). Do not map a second
primary score to point size. Coverage is scored as
, so over-wide intervals that inflate
coverage are not rewarded. There is no default weighted composite.
plot_mc_metric_dots( benchmark, facet_rows = NULL, facet_cols = NULL, metrics = c("rmse", "mae", "coverage", "mean_interval_width"), weights = NULL )plot_mc_metric_dots( benchmark, facet_rows = NULL, facet_cols = NULL, metrics = c("rmse", "mae", "coverage", "mean_interval_width"), weights = NULL )
benchmark |
List from |
facet_rows, facet_cols
|
Optional column names for
|
metrics |
Character vector of summaries to display. |
weights |
Optional named numeric weights (sum to 1) used only
to add a |
A ggplot object.
plot_mc_forest(), plot_algorithm_similarity()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( cosine = 0, true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 4, cores = 1 ) if (requireNamespace("ggplot2", quietly = TRUE)) { plot_mc_metric_dots(out, facet_cols = "cosine") }set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( cosine = 0, true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 4, cores = 1 ) if (requireNamespace("ggplot2", quietly = TRUE)) { plot_mc_metric_dots(out, facet_cols = "cosine") }
Half-eye densities, dots, and empirical 50% / 95% intervals
(Allen et al. 2019) for the
Monte Carlo sampling distribution of . Default
aesthetics: numeric axis is the estimation error
(so a vertical line at 0 is bias);
the y-axis is the cell type; colour and dodge group the deconvolution
algorithm. Optional facet_rows / facet_cols split scenarios
(for example number of genes versus pairwise cosine).
plot_mc_raincloud( benchmark, quantity = c("error", "estimate"), facet_rows = NULL, facet_cols = NULL, .width = c(0.5, 0.95), include_dots = TRUE, max_dots = 2000L, max_rows = NULL, dodge_width = 0.95, slab_scale = 1.4, slab_alpha = 1, category_spacing = 1, slab = c("halfeye", "dotsinterval") )plot_mc_raincloud( benchmark, quantity = c("error", "estimate"), facet_rows = NULL, facet_cols = NULL, .width = c(0.5, 0.95), include_dots = TRUE, max_dots = 2000L, max_rows = NULL, dodge_width = 0.95, slab_scale = 1.4, slab_alpha = 1, category_spacing = 1, slab = c("halfeye", "dotsinterval") )
benchmark |
List from |
quantity |
|
facet_rows, facet_cols
|
Optional column names for
|
.width |
Passed to |
include_dots |
If |
max_dots |
Maximum rows passed to |
max_rows |
Optional cap on the plotting table (half-eye and dots). When the Monte Carlo stack is huge, subsample before drawing. |
dodge_width |
Width passed to |
slab_scale |
Slab height for |
slab_alpha |
Transparency of the density slab ( |
category_spacing |
Vertical gap between discrete y categories (cell types). Values greater than 1 insert extra space so slabs from neighbouring types do not overlap; the axis still shows the original labels. |
slab |
|
When quantity = "estimate" and slab = "halfeye", slabs use a
bounded kernel on (ggdist::density_bounded()) with a
coloured outline (slab_colour / slab_linewidth). Vertex pile-up
can leave that KDE empty; slab = "dotsinterval"
(ggdist::stat_dotsinterval()) draws quantile dots instead.
True proportions are labelled at the top of each panel (the most
abundant type to the right of its reference line; the others to the
left).
The inner interval is the central 50% of Monte Carlo replicates; the
outer interval is the central 95%. These are not confidence
intervals for .
A ggplot object.
Allen M, Poggiali D, Whitaker K, Marshall TR, van Langen J, Kievit RA (2019). “Raincloud Plots: A Multi-Platform Tool for Robust Data Visualization.” Wellcome Open Research, 4, 63. ISSN 2398-502X. doi:10.12688/wellcomeopenres.15191.2. https://wellcomeopenresearch.org/articles/4-63.
plot_mc_forest(), pivot_mc_estimates()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( n_genes = 2L, cosine = 0, true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 4, cores = 1 ) if (requireNamespace("ggdist", quietly = TRUE)) { plot_mc_raincloud(out, facet_cols = "cosine") }set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) out <- run_simulation_benchmark( tibble::tibble( n_genes = 2L, cosine = 0, true_theta = list(list(p = c(0.5, 0.5), mu = mu, sigma = Sigma)) ), deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 4, cores = 1 ) if (requireNamespace("ggdist", quietly = TRUE)) { plot_mc_raincloud(out, facet_cols = "cosine") }
The frequentist DeCovarT likelihood treats
and each as known.
In a labelled, reference-based model those moments come from purified
profiles whose cell-type names stay attached. This bootstrap
resamples the experimental units that generated the reference (donors,
by default), or redraws compositions from a Dirichlet law, then
re-estimates the plug-in moments and refits
(Efron 1979).
reference_bootstrap_decovart( y = NULL, reference_profiles, bulk_expression = NULL, cell_type_labels = NULL, donor_ids = NULL, method = c("donors", "cells", "dirichlet"), n_boot = 199L, level = 0.95, dirichlet_alpha = 1, regenerate_bulk = FALSE, p = NULL, n_replicates = NULL, ridge = 1e-06, epsilon = 10^-8, itmax = 500L )reference_bootstrap_decovart( y = NULL, reference_profiles, bulk_expression = NULL, cell_type_labels = NULL, donor_ids = NULL, method = c("donors", "cells", "dirichlet"), n_boot = 199L, level = 0.95, dirichlet_alpha = 1, regenerate_bulk = FALSE, p = NULL, n_replicates = NULL, ridge = 1e-06, epsilon = 10^-8, itmax = 500L )
y |
Numeric vector (or one-column matrix)
|
reference_profiles |
Named list of |
bulk_expression |
Numeric matrix |
cell_type_labels |
Optional character or factor vector of length
|
donor_ids |
Donor (or other clustering) labels aligned with the
purified columns. A named list of vectors matching
|
method |
One of |
n_boot |
Number of bootstrap replicates. |
level |
Confidence level for percentile intervals. |
dirichlet_alpha |
Positive Dirichlet concentration: a scalar
recycled to |
regenerate_bulk |
If |
p |
Composition to simulate from. Defaults to the MLE computed
from |
n_replicates |
Number of bulk columns simulated per replicate; defaults to the number of observed columns. |
ridge |
Non-negative ridge multiple for the sample covariance of
each cell type. The default |
epsilon, itmax
|
Relative convergence tolerance and iteration
budget passed to |
Permuting gene order, or permuting cell-type labels of an already
averaged signature, is not a bootstrap: the first destroys the
gene-wise pairing of bulk and reference, and the second is algebraic
equivariance rather than a source of uncertainty (see
equivariance_check_decovart() and
vignette("theory-DeCovarT-MLE-properties")). The three method options
instead resample units that actually vary.
donorsCluster bootstrap: resample donor identifiers
within each cell type, take every purified column of the
sampled donors (a donor drawn twice is included twice), rebuild
, and refit.
This is the default: it targets biological / reference-population
uncertainty rather than technical cell-level noise alone.
cellsResample purified columns independently within each cell type (with replacement) and rebuild the plug-in moments. Use this when donor labels are unavailable.
dirichletHold the original plug-in moments fixed, draw
,
simulate bulk profiles from the Gaussian convolution, and refit.
This is a composition-sweep diagnostic, not a confidence interval
for one observed bulk.
For donors and cells the observed bulk is kept fixed unless
regenerate_bulk = TRUE, in which case each replicate also draws
from the bootstrapped moments at p
(the observed MLE by default). That is the pipeline
reference units .
When a cell type has fewer purified columns than genes, the sample
covariance is singular. A ridge ridge * mean(diag(S)) is then added
on the diagonal so that remains
factorisable. Set ridge = 0 to disable that guard (and accept
possible Cholesky failures).
A list with method, estimates ( n_boot),
interval (percentile bounds), mean_signature_matrix / Sigma
from the original reference, and p_hat from the original plug-in
fit when a bulk is supplied. For method = "dirichlet",
p_simulated stores the drawn compositions.
Efron B (1979). “Bootstrap Methods: Another Look at the Jackknife.” The Annals of Statistics, 7(1), 1–26. doi:10.1214/aos/1176344552.
bootstrap_decovart(), equivariance_check_decovart()
Other decovart_inference:
bootstrap_decovart(),
boundary_diagnostics(),
chi_bar_square_pvalue(),
confint_profile_decovart(),
equivariance_check_decovart(),
lrt_decovart(),
multistart_decovart(),
profile_loglik_decovart(),
restricted_mle_decovart()
set.seed(1) mu <- matrix(c(20, 40, 15, 40, 20, 25), nrow = 3) colnames(mu) <- paste0("ct", 1:2) Sigma <- array(c(diag(3), diag(3)), dim = c(3, 3, 2)) refs <- lapply(seq_len(2), function(j) { t(MASS::mvrnorm(n = 8, mu = mu[, j], Sigma = Sigma[, , j])) }) names(refs) <- colnames(mu) donor_ids <- lapply(refs, function(x) { rep(paste0("d", 1:2), length.out = ncol(x)) }) y <- drop(mu %*% c(0.6, 0.4)) reference_bootstrap_decovart( y, refs, donor_ids = donor_ids, n_boot = 15 )$intervalset.seed(1) mu <- matrix(c(20, 40, 15, 40, 20, 25), nrow = 3) colnames(mu) <- paste0("ct", 1:2) Sigma <- array(c(diag(3), diag(3)), dim = c(3, 3, 2)) refs <- lapply(seq_len(2), function(j) { t(MASS::mvrnorm(n = 8, mu = mu[, j], Sigma = Sigma[, , j])) }) names(refs) <- colnames(mu) donor_ids <- lapply(refs, function(x) { rep(paste0("d", 1:2), length.out = ncol(x)) }) y <- drop(mu %*% c(0.6, 0.4)) reference_bootstrap_decovart( y, refs, donor_ids = donor_ids, n_boot = 15 )$interval
Clips numerical under/overflow and renormalises so that
and
. This is a repair /
renormalisation step for estimated proportions, not a Euclidean
projection onto the simplex and not a statistical-identifiability
constraint.
When open = TRUE, entries on the closed-simplex boundary are nudged
into the relative interior so that isometric_log_ratio() is defined
(ILR / Marquardt / Newton starts). That open-simplex path is the
former role of starting_simplex().
repair_simplex( p, tolerance = 100 * .Machine$double.eps, open = FALSE, nms = NULL )repair_simplex( p, tolerance = 100 * .Machine$double.eps, open = FALSE, nms = NULL )
p |
Numeric vector |
tolerance |
Non-negative tolerance for treating entries as zero
(default |
open |
Logical; if |
nms |
Optional names attached to the returned vector (e.g. cell-type colnames). |
Numeric vector on the simplex (open when
open = TRUE).
compositions::clo() for compositional closure.
repair_simplex(c(0.2, 0.3, 0.5 + 1e-12)) repair_simplex(c(1, 0, 0), open = TRUE)repair_simplex(c(0.2, 0.3, 0.5 + 1e-12)) repair_simplex(c(1, 0, 0), open = TRUE)
Wrapper around simulate_bulk_mixture(), deconvolute_ratios(), and
compute_benchmark_metrics() (called inside deconvolute_ratios()).
Each row of scenario_config describes one generative model
(, ,
) stored in a list column true_theta. Scenario
rows are always evaluated sequentially to avoid nested
parallelism; sample-level workers live only in
deconvolute_ratios(). Scenario builders (factorial grids, overlap
summaries, etc.) should live in analysis scripts; see
scripts/fig02_bivariate_toy.R and the paper-scenario vignettes.
run_simulation_benchmark( scenario_config, deconvolution_functions, n = 200, standardise = FALSE, scaled = FALSE, cores = 1L, coverage_interval = "wilson", verbose = FALSE, progress_every = 10L )run_simulation_benchmark( scenario_config, deconvolution_functions, n = 200, standardise = FALSE, scaled = FALSE, cores = 1L, coverage_interval = "wilson", verbose = FALSE, progress_every = 10L )
scenario_config |
Tibble or list of scenario rows. Each row must
contain a |
deconvolution_functions |
Named list passed to
|
n |
Default number of bulk replicates |
standardise, scaled
|
Passed to |
cores |
Workers for the per-sample loop inside
|
coverage_interval |
Coverage interval for the Monte Carlo
coverage rate; see |
verbose |
If |
progress_every |
Sample-progress interval passed to
|
A list with:
regression: global (per-sample composition scores) and
cell_type (across-sample Pearson / F1 / spillover);
monte_carlo: ADEMP summaries per cell type;
optimisation: per-sample elapsed time, memory, KKT residual, and
;
config: tibble of scenario metadata (one row per scenario);
theta_true: list of convolution parameters
(, , )
actually used to draw the bulk;
descriptors: kept scenario statistics (composition, mean, SPD,
network, tangent Fisher, MixSim BarOmega, pairwise Hellinger);
supplementary: Jeffreys overlap, recorded separately;
call: the matched call (match.call()).
Scripts should persist those pieces with
write_simulation_artefacts() rather than saving the whole list.
simulate_bulk_mixture(), deconvolute_ratios(),
compute_benchmark_metrics(), describe_simulation_scenario(),
coverage_mc_interval(), plot_mc_raincloud(), plot_mc_forest(),
write_simulation_artefacts()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) scenario_config <- tibble::tibble( ID = "B1_Ho", true_theta = list(list( p = c(0.5, 0.5), mu = mu, sigma = Sigma )) ) out <- run_simulation_benchmark( scenario_config = scenario_config, deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 2, cores = 1 ) nrow(out$optimisation)set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) scenario_config <- tibble::tibble( ID = "B1_Ho", true_theta = list(list( p = c(0.5, 0.5), mu = mu, sigma = Sigma )) ) out <- run_simulation_benchmark( scenario_config = scenario_config, deconvolution_functions = list( "nnls" = list(FUN = deconvolute_ratios_nnls) ), n = 2, cores = 1 ) nrow(out$optimisation)
Replaces each by . Then
, so the support of the precision
(structural zeros from the graph) is unchanged.
scale_covariance_array(sigma, scale)scale_covariance_array(sigma, scale)
sigma |
|
scale |
Positive scalar |
Scaled array.
scale_covariances_to_overlap()
sigma <- array(c(diag(2), diag(2)), dim = c(2, 2, 2)) scaled <- scale_covariance_array(sigma, 4) scaled[, , 1]sigma <- array(c(diag(2), diag(2)), dim = c(2, 2, 2)) scaled <- scale_covariance_array(sigma, 4) scaled[, , 1]
For each bootstrap sample , draws latent purified
profiles
independently for each cell type
, then forms the bulk by the linear convolution
matching the article's conditional model
with
.
The mean signature is shared across samples; only the
latent draws vary with . Stacking
those draws into the three-way array
, the bulk
matrix is the mode-2 tensor–vector contraction
which for each sample recovers
with .
simulate_bulk_mixture( signature_matrix, Sigma, p = rep(1/ncol(signature_matrix), ncol(signature_matrix)), n = 500, truncate_negative = FALSE )simulate_bulk_mixture( signature_matrix, Sigma, p = rep(1/ncol(signature_matrix), ncol(signature_matrix)), n = 500, truncate_negative = FALSE )
signature_matrix |
Mean signature
|
Sigma |
Array of covariances
|
p |
Proportion vector |
n |
Number of bulk / bootstrap samples |
truncate_negative |
If |
A list with:
latent_profiles: array
of
unobserved cell-type-specific draws (one slice per
sample );
Y: matrix whose columns
are bulk vectors , obtained as
.
deconvolute_ratios(),
run_simulation_benchmark()
set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) sim <- simulate_bulk_mixture(mu, Sigma, p = c(0.5, 0.5), n = 5) dim(sim$Y)set.seed(1) genes <- paste0("g", 1:2) cts <- paste0("ct", 1:2) mu <- matrix(c(20, 22, 22, 20), nrow = 2, dimnames = list(genes, cts)) Sigma <- array( c(1, 0, 0, 1, 1, 0, 0, 1), dim = c(2, 2, 2), dimnames = list(genes, genes, cts) ) sim <- simulate_bulk_mixture(mu, Sigma, p = c(0.5, 0.5), n = 5) dim(sim$Y)
Builds a mean matrix
and cell-type-specific second-order moments
under a graph-constrained precision model. Means realise a target
Gram matrix via the symmetric square root of
generate_mean_signature_matrix() (equicorrelation by
default, or a full target_gram). For each cell type, an adjacency is drawn from a
random-graph model (or supplied), i.i.d. signed weights with
inhibitory fraction prop_inhibitory form ,
and the precision is completed by a spectral shift. Distinct cell
types receive independent precision draws by default (biology
rarely shares one network across types); pass a length-
graph_model / graph_params or a pre-built
adjacency list / array for hybrid designs.
simulate_hierarchical_grn_moments( n_genes, n_celltypes = 2L, mean_scale = 10, target_cosine = 0, target_gram = NULL, seed = NULL, nonnegative = FALSE, precision_shift, precision_scale, prop_inhibitory = 0.5, graph_model = c("erdos_renyi", "hub", "star", "scale_free", "stochastic_block_model", "small_world"), graph_params = list(), adjacency = NULL )simulate_hierarchical_grn_moments( n_genes, n_celltypes = 2L, mean_scale = 10, target_cosine = 0, target_gram = NULL, seed = NULL, nonnegative = FALSE, precision_shift, precision_scale, prop_inhibitory = 0.5, graph_model = c("erdos_renyi", "hub", "star", "scale_free", "stochastic_block_model", "small_world"), graph_params = list(), adjacency = NULL )
n_genes |
Integer |
n_celltypes |
Integer |
mean_scale |
Positive scalar |
target_cosine |
Numeric pairwise cosine |
target_gram |
Optional symmetric |
seed |
Optional integer. When supplied, the orthonormal frame
|
nonnegative |
If |
precision_shift |
Diagonal cushion |
precision_scale |
Positive magnitude |
prop_inhibitory |
Numeric in |
graph_model |
One of |
graph_params |
Named list of generator parameters (shared), or a
list of length |
adjacency |
Optional pre-built undirected adjacencies: a list of
|
Named list with:
mean_profiles: matrix ;
covariance_matrices: array
;
precision_matrices: array
;
graph_structure: adjacency_matrices, weighted_adjacencies,
and normalised_precision (all );
objectives: mean_abs_cosine and sum_euclidean_distance.
set.seed(42) moments <- simulate_hierarchical_grn_moments( n_genes = 40L, n_celltypes = 3L, mean_scale = 10, target_cosine = 0.1, precision_shift = 0.1, precision_scale = 0.3, prop_inhibitory = 0.5, graph_model = "scale_free" ) str(moments, max.level = 2)set.seed(42) moments <- simulate_hierarchical_grn_moments( n_genes = 40L, n_celltypes = 3L, mean_scale = 10, target_cosine = 0.1, precision_shift = 0.1, precision_scale = 0.3, prop_inhibitory = 0.5, graph_model = "scale_free" ) str(moments, max.level = 2)
AIRM is the affine-invariant Riemannian metric on the cone of
symmetric positive-definite matrices:
.
Do not use the Frobenius
: that Euclidean chord leaves the cone,
is not inversion-invariant, and suffers the swelling effect.
spd_affine_invariant_distance(a, b)spd_affine_invariant_distance(a, b)
a, b
|
Symmetric positive-definite matrices of equal size. |
Non-negative scalar.
# Two bivariate Gaussians: identity vs a correlated SPD covariance. a <- diag(2) b <- matrix(c(2, 0.5, 0.5, 1), nrow = 2) spd_affine_invariant_distance(a, a) d <- spd_affine_invariant_distance(a, b) d > 0# Two bivariate Gaussians: identity vs a correlated SPD covariance. a <- diag(2) b <- matrix(c(2, 0.5, 0.5, 1), nrow = 2) spd_affine_invariant_distance(a, a) d <- spd_affine_invariant_distance(a, b) d > 0
The convolution log-likelihood is not globally concave, so the start
can change which basin the solver enters. Draw several independent
Dirichlet starts (different RNG streams or
multistart_decovart()) rather than a single draw when probing
multimodality.
starting_simplex( n_celltypes, initial_p = NULL, nms = NULL, y = NULL, mean_signature_matrix = NULL, dirichlet_alpha = 1 )starting_simplex( n_celltypes, initial_p = NULL, nms = NULL, y = NULL, mean_signature_matrix = NULL, dirichlet_alpha = 1 )
n_celltypes |
Integer |
initial_p |
One of:
|
nms |
Optional names (cell-type colnames). |
y, mean_signature_matrix
|
Bulk and signature, required for a QP start. |
dirichlet_alpha |
Positive concentration, recycled to length
|
A length- open-simplex vector.
multistart_decovart(), deconvolute_ratios_deconrnaseq()
starting_simplex(3L) set.seed(1) starting_simplex(3L, "dirichlet")starting_simplex(3L) set.seed(1) starting_simplex(3L, "dirichlet")
theme_minimal() plus a panel border and white-on-black facet
strips, after theme_features() in the
atlas-feature-selection-benchmark
plotting helpers.
theme_decovart_facets(base_size = 11, ...)theme_decovart_facets(base_size = 11, ...)
base_size |
Base font size for |
... |
Passed to |
A ggplot2 theme object.
plot_mc_forest(), plot_mc_raincloud()
ggplot2::ggplot(mtcars, ggplot2::aes(wt, mpg)) + ggplot2::geom_point() + ggplot2::facet_wrap(~cyl) + theme_decovart_facets()ggplot2::ggplot(mtcars, ggplot2::aes(wt, mpg)) + ggplot2::geom_point() + ggplot2::facet_wrap(~cyl) + theme_decovart_facets()
Maps the expected Fisher information of unconstrained proportions through the isometric log-ratio (ILR) chart and back to the simplex via the delta method.
Let
with Jacobian
(jacobian_isometric_logistic()). Fisher information transforms as
The first-order delta method then yields
This simplex covariance is invariant to orthogonal rotations of
. The construction is undefined on the simplex
boundary (the log-ratio chart blows up); the function then returns
NA with a warning.
Wald intervals in vcov.decovart_fit(), confint.decovart_fit()
and deconvolute_ratios() all use this expected-Fisher ILR map.
The observed Hessian hessian_loglik_constrained() is for Newton
steps only; it is not the Wald covariance.
vcov_ilr_delta(p, mean_signature_matrix, Sigma, warn = TRUE)vcov_ilr_delta(p, mean_signature_matrix, Sigma, warn = TRUE)
p |
Numeric proportions on the open simplex. |
mean_signature_matrix |
Mean signature |
Sigma |
Cell-type covariances |
warn |
If |
Symmetric asymptotic covariance of
, or a matrix of NA if the bound is
undefined / singular.
expected_fisher_unconstrained(), vcov.decovart_fit(),
confint.decovart_fit(), jacobian_isometric_logistic()
# Two cell types: a single unconstrained ILR coordinate maps back to # a rank-1 covariance on the simplex (rows sum to zero). p <- c(ct1 = 0.6, ct2 = 0.4) mu <- cbind(ct1 = c(0, 0), ct2 = c(3, 0)) Sigma <- array(c(diag(2), diag(2)), dim = c(2, 2, 2)) v <- vcov_ilr_delta(p, mu, Sigma) round(v, 4) abs(rowSums(v)) < 1e-10# Two cell types: a single unconstrained ILR coordinate maps back to # a rank-1 covariance on the simplex (rows sum to zero). p <- c(ct1 = 0.6, ct2 = 0.4) mu <- cbind(ct1 = c(0, 0), ct2 = c(3, 0)) Sigma <- array(c(diag(2), diag(2)), dim = c(2, 2, 2)) v <- vcov_ilr_delta(p, mu, Sigma) round(v, 4) abs(rowSums(v)) < 1e-10