Variance partitioning#
variance_fractions() partitions the variance of
every gene among the variables of a Design and the
residuals, as variancePartition’s fitExtractVarPartModel does: each gene is fitted
with a linear mixed model, by maximum likelihood, whose random intercepts are the
categorical variables (donor, condition, batch) and whose fixed effects are the
continuous ones (age), with precision weights such as voom’s, and the fraction of a
variable is its variance component, or the variance of its fitted effect, over their
sum. variance_partitioning() does it for every
cell type of a cube, after the steps of
dream_voom() up to the precision weights, and
records the samples each fit used. plot_variance_fractions()
and plot_gene_variance_fractions() draw the fractions as
variancePartition’s plotVarPart and plotPercentBars do.
Variance partitioning: the share of each gene’s variance that each variable explains.
Which variables drive the differences between samples, and which must be adjusted
for? For every gene, variance partitioning (Hoffman and Schadt 2016) fits one linear
mixed model with every variable of the design, so that each is assessed together with
all the others, and reports the fraction of the gene’s variance that each variable
explains, and the residual fraction. It is what variancePartition’s
fitExtractVarPartModel computes.
variance_fractions() partitions the variance of every feature of a samples x
features matrix of log-expression values, and variance_partitioning() of every
group (cell type) of a cube, after the steps of
dream_voom() up to voom’s precision weights.
- The model:
Every feature is fitted with the model of
cytepool.stats.mixed_models,y = X beta + Z_1 b_1 + … + Z_K b_K + e, b_k ~ N(0, sigma_k^2 I), e ~ N(0, sigma^2 W^-1),
where categorical variables, such as the donor, the condition or the batch, are random intercepts,
(1 | donor), and continuous variables, such as age, are fixed effects (~ age). Without random effects it is a linear model, fitted by weighted least squares. The variances are estimated by maximum likelihood, asfitExtractVarPartModeldoes by default (REML = FALSE): its documentation explains that REML, which estimates the random effects after removing the fixed effects, biases the fractions of variance, while maximum likelihood estimates the fixed and random effects together.method="reml"fits by REML instead.- The fractions:
Each variable contributes a variance, and its fraction is that variance over their sum:
a random effect k, its variance component sigma_k^2;
the residual, sigma^2 / mean(w): the residual variance of an observation of the feature’s average weight (sigma^2 itself without weights);
a fixed-effect term j, the variance of its fitted contribution X_j beta_j over the samples, the post hoc variance of Hoffman and Schadt (2016). The variances of fitted values are weighted, sum_i w_i (v_i - v_w)^2 / sum_i w_i about the weighted mean v_w, with the denominator of a maximum likelihood variance (n without weights). With random effects, the terms share the variance of all fitted fixed effects, X beta, in proportion to the variances of their own contributions; without random effects, each term has the variance of its own contribution. The two agree when the terms are uncorrelated over the samples.
The fractions of a feature sum to one, and a random effect whose variance is estimated at zero (a singular fit in lme4) has a fraction of exactly zero. They do not change when the weights of a feature are multiplied by a constant, when the levels of a factor are relabelled, or when the values of a feature are multiplied by a constant or shifted.
Conventions found by probing variancePartition 1.36.2, with lme4 1.1-38, as a black box, where its documentation leaves them open:
The variances of the fixed effects are those above: weighted, with the denominator of a maximum likelihood variance, and shared among the terms in a mixed model but not in a linear model (
calcVarPartoflmerandlmfits).fitExtractVarPartModeldivides each gene’s precision weights by their mean, asdreamdoes, so the residual variance is that of an observation of average weight and the fractions do not depend on the scale of the weights. Without random effects the residual variance is the weighted mean of the squared residuals, the maximum likelihood estimate.A design with random effects must give every categorical variable as a random effect: a factor among its fixed effects is refused, as variancePartition refuses it (“Must model either _all_ or _no_ categorical variables as random effects”), and so is a formula without an intercept. Without random effects, factors are fixed effects of the linear model.
variancePartition lists the random effects in alphabetical order, then the fixed effects; here they are in the order of the design.
Deliberate differences from variancePartition, reported by
validation/variance_partitioning.py:
A feature that the fixed and random effects reproduce, such as one that is constant within every donor, has no maximum likelihood estimate (see
MixedModelFit) and its fractions are NaN. lme4 fits it with a residual variance near zero, which gives it a fraction near one for the donor.method="reml"without random effects takes the REML estimate of the residual variance, the residual sum of squares over n - p. variancePartition fits such a design withlmand takes the residual sum of squares over n whateverREMLsays.
- Limits of this version:
Random slopes, which variancePartition uses to partition the variance within subsets of the samples, such as
(tissue + 0 | donor), generalized linear mixed models, and bootstrap confidence intervals of the fractions (varPartConfInf) are not implemented yet.
References
Hoffman GE, Schadt EE (2016). variancePartition: interpreting drivers of variation in complex gene expression studies. BMC Bioinformatics 17, 483. doi:10.1186/s12859-016-1323-z
Bates D, Maechler M, Bolker B, Walker S (2015). Fitting linear mixed-effects models using lme4. Journal of Statistical Software 67(1), 1-48. doi:10.18637/jss.v067.i01
Hoffman GE, Roussos P (2021). Dream: powerful differential expression analysis for repeated measures designs. Bioinformatics 37, 192-201. doi:10.1093/bioinformatics/btaa687
Law CW, Chen Y, Shi W, Smyth GK (2014). voom: precision weights unlock linear model analysis tools for RNA-seq read counts. Genome Biology 15, R29. doi:10.1186/gb-2014-15-2-r29
Patterson HD, Thompson R (1971). Recovery of inter-block information when block sizes are unequal. Biometrika 58, 545-554. doi:10.1093/biomet/58.3.545
- class cytepool.variance_partitioning.VarianceFractions(fractions, variances, variables, random_effects, fixed_effects, method, converged, fit)#
The fraction of every feature’s variance that each variable explains.
Rows of the arrays are features, in the order of the columns of
y, and columns are variables, in the order ofvariables. The arrays are read-only. A feature without a fit has NaN fractions: one that the fixed and random effects reproduce, asMixedModelFitdescribes.- Variables:
fractions –
(n_features, n_variables)fraction of each feature’s variance that each variable explains; each row sums to one.variances –
(n_features, n_variables)the variances the fractions are shares of (calcVarPart(fit, returnFractions = FALSE)): the variance component of each random effect, the variance of the fitted contribution of each fixed-effect term, and the residual variance of an observation of the feature’s average weight, on the scale of the values.variables – The names of the variables: the random effects, as lme4 names their grouping factors (such as
"donor"or"sample:donor"), in the order of the design; the fixed-effect terms, as the formula labels them (such as"age"); and"residuals", last.random_effects – The names of the random effects among
variables.fixed_effects – The names of the fixed-effect terms among
variables.method –
"ml"or"reml", how the variances were estimated.converged –
(n_features,)False where a feature was not fitted, or its mixed model fit did not converge (its fractions are those where the fit stopped).fit – The fits: a
MixedModelFit, or for a design without random effects aLinearModelFit.
Explained in: Mixed models, REML and dream
- class cytepool.variance_partitioning.VariancePartitioningResult(variables, summary, samples, tables, provenance)#
The fractions of variance of every group of a cube, from
variance_partitioning().Tables hold, for every group that was fitted, one row per feature that passed the expression filter, in the cube’s feature order:
feature_idand, when the cube’s feature table has one,symbol; then the fraction of the feature’s variance that each variable explains, one column per variable, named as invariables(NaN for a feature whose mixed model has no maximum, which the fixed and random effects reproduce).The summary has one row per group:
group_id;status,"fitted"or why the group was not fitted ("no_samples","no_residual_df","fixed_effects_not_estimable","random_effects_not_estimable","no_features","too_few_features","empty_library"or"failed");reason, the explanation (empty when fitted);n_samples_usedandn_samples_left_out; andn_features, the number of features fitted (0 where the group was not fitted).The sample table has one row per group and design sample, as for
DifferentialExpressionResult:group_id,sample_id,n_cells(when the cube records cell numbers),used,reason, and thelibrary_sizeandnorm_factorthat entered the fit; withprior_weights, theprior_weightof each sample.Built by
variance_partitioning(). Immutable.- Raises:
TypeError – If
variablesis not a sequence of names,summaryorsamplesnot aTable,tablesnot a mapping of group id toTable, orprovenancenot a mapping that JSON can write.ValueError – If
provenanceholds a NaN or an infinity.
Explained in: Mixed models, REML and dream
- property variables#
The variables, in the order of the columns of the tables, residuals last.
- property groups#
The ids of the groups analysed, in order.
- property summary#
The status, samples and features of every group.
- property samples#
Every design sample of every group, used in its fit or left out, and why.
- property tables#
The table of fractions of every group that was fitted, by group id.
- property settings#
The settings of the analysis, as a new dict (also in the provenance).
- property provenance#
A JSON-serialisable record of what produced every number, as a new dict.
It holds the method; the CytePool, numpy, numba and Python versions; the cube, by its fingerprint, digests and provenance; the layer read; the design (formula, columns, levels, random effects, and the values of the variables of both for every sample); the settings; and, for every group, its status, the samples that entered its fit in the order they were fitted, those left out with the reason, the number of features fitted, the span of its trend (
voom_span), the number of features whose mixed model has no maximum (features_not_fitted) and of those whose fit stopped without converging (features_not_converged), and withprior_weightsthe prior weights of its samples. The cost of the cube’s record is that oflimma_voom()’s (“Provenance”).
- table(group)#
Return the table of fractions of one group.
- Parameters:
group – A group id.
- Raises:
KeyError – If the group was not analysed or not fitted; the message says why.
Explained in: Mixed models, REML and dream
- cytepool.variance_partitioning.variance_fractions(y, design, *, weights=None, method='ml', n_threads=None)#
The fraction of every feature’s variance that each variable of a design explains.
variancePartition’s
fitExtractVarPartModel: every feature (column ofy) is fitted with the design’s fixed effects and random intercepts, by maximum likelihood as lme4’slmer(REML = FALSE)fits it (seefit_mixed_model()), and its variance is partitioned among the random effects, the fixed-effect terms and the residual, as the module documentation describes. Categorical variables are given as random effects and continuous ones as fixed effects, as inDesign(samples, "~ age", random="(1 \| donor) + (1 \| condition)"). A design without random effects is fitted by weighted least squares, and its factors are fixed effects.- Parameters:
y –
(n_samples, n_features)expression values on a log scale, such as the log-CPM values of voom. Every value must be finite.design – A
Designwith an intercept, whose random effects are random intercepts, such asrandom="(1 | donor) + (1 | batch)", and whose fixed effects are then continuous covariates; or a design without random effects. Its fixed effects must have full column rank, and its random effects must passdesign.check().weights – Precision weights,
(n_samples,)for sample weights or(n_samples, n_features)for observation weights, such as those ofvoom_with_dream_weights(): the residual variance of an observation is sigma^2 / weight. They must be finite and positive; only their ratios within a feature matter.method –
"ml"for maximum likelihood, variancePartition’s default, or"reml"for restricted maximum likelihood.n_threads – The most threads for fitting features, as for
fit_mixed_model; the result does not depend on it.
- Returns:
The fractions and variances of every feature, with the fits.
- Raises:
TypeError – If
designis not aDesign, an input is not real-valued,methodis not a string,n_threadsis not an integer, or an input is or holds a numpy masked array.ValueError – If shapes disagree,
yis not finite, a weight is not finite and positive,methodis unknown, the formula has no intercept, a design with random effects has a factor among its fixed effects or a random effect that is not a random intercept, the fixed effects are not of full column rank, a random effect fails the design checks, the fixed and random effects together span every sample, or a variable is named"residuals"or two variables have the same name.
Example
>>> import numpy as np >>> from cytepool.design import Design >>> samples = {"donor": ["D1", "D1", "D2", "D2", "D3", "D3", "D4", "D4"], ... "age": [31.0, 31.0, 45.0, 45.0, 52.0, 52.0, 60.0, 60.0], ... "condition": ["ctrl", "stim"] * 4} >>> design = Design(samples, "~ age", ... random="(1 | donor) + (1 | condition)") >>> y = np.random.default_rng(1).normal(5.0, 1.0, (8, 3)) >>> result = variance_fractions(y, design) >>> result.variables ('donor', 'condition', 'age', 'residuals')
Explained in: Mixed models, REML and dream
- cytepool.variance_partitioning.variance_partitioning(cube, design, *, groups=None, min_cells=10, min_counts=0, expression_filter=True, normalization='tmm', span=None, prior_weights=None, method='ml', n_threads=None)#
Partition the variance of every feature in every group of a cube.
What drives the differences between samples in each cell type: for every group of the cube, the fraction of each feature’s variance that each variable of the design explains, as dreamlet runs variancePartition’s
fitExtractVarPartModelon thevoomWithDreamWeightsoutput of every cell type. For each group, the samples are left out, the features filtered and the counts normalized as fordream_voom(); voom’s precision weights come from the mean-variance trend of the design’s mixed model, fitted by maximum likelihood (voom_with_dream_weights()), or from limma’s voom for a design without random effects; and the fractions of every feature are those ofvariance_fractions()of its log2 counts per million with these weights.Every group’s mixed model uses the random effects of the samples left in it, as in
dream_voom. A group is not fitted, and its status says why, when no sample is left, its samples cannot estimate a random effect (one donor left, or one sample per donor), they cannot separate the fixed effects (a covariate constant over them), fewer than two features with counts pass the filter, or the trend cannot give weights. A run never stops for one group.- Parameters:
cube – A cube with a
countslayer.design – A
Designwith an intercept, matched to the cube bysample_idas forlimma_voom(), whose categorical variables are random intercepts and continuous variables fixed effects, such asDesign(samples, "~ age", random="(1 \| donor) + (1 \| condition)"); or a design without random effects.groups – A group id or a list of them; all groups by default.
min_cells – The fewest cells a sample needs in a group to enter its fit; ignored for cubes without cell numbers.
min_counts – The fewest counts a sample needs in a group, over every feature of the cube, to enter its fit; 0 by default.
expression_filter – True for edgeR’s
filterByExprdefaults with the group’s fixed effects, a mapping of changed settings, or False.normalization –
"tmm"for TMM normalization factors, or None.span – The LOWESS span of voom’s trend, in (0, 1], for every group; None chooses it in each group from the number of features, as
voomdoes. variancePartition’svoomWithDreamWeightsuses 0.5.prior_weights –
"n_cells"to give each sample its number of cells in the group as a prior weight, which the precision weights multiply, as dreamlet weights samples; or None. Needs a cube that records cell numbers.method –
"ml"(maximum likelihood, variancePartition’s default) or"reml", for the fits behind the fractions; the fits behind the precision weights are by maximum likelihood, asvoomWithDreamWeightsfits them.n_threads – The most threads for the fits of the features, at least 1; None keeps numba’s setting. The results do not depend on it.
- Returns:
The tables of fractions of every fitted group, a summary of every group, the samples used and left out, the settings and a provenance record.
- Raises:
TypeError – If
cubeis not a Cube,designnot a Design, an option has the wrong type, orgroups,min_cells,expression_filterorn_threadsis or holds a numpy masked array.ValueError – If the design cannot be partitioned (as for
variance_fractions()), has nosample_idcolumn, names samples the cube does not have or names a sample twice, a variable is named"feature_id"or"symbol", an option is invalid, orprior_weightsis"n_cells"and the cube does not record cell numbers.KeyError – If a group is not in the cube.
Explained in: Mixed models, REML and dream