Cell-type composition#
propeller() tests whether the proportions of cell types
differ between conditions, as speckle’s propeller, propeller.ttest and
propeller.anova do. It reads the number of cells of every sample in every group of a
cube (or a table of them), transforms the proportions (logit or arcsine square root),
fits a linear model with a Design matched to the samples by
sample_id, moderates the variances of the cell types by empirical Bayes, and tests
the design’s named contrasts by moderated t-statistics and the levels of a factor by
a moderated F-statistic. The result holds speckle’s columns for every cell type, the
samples and cell types left out, the proportions, and a record that identifies the
cell numbers by the cube’s fingerprint.
Tests of differences in cell-type composition between conditions, by propeller.
propeller() tests, for every group (cell type) of a cube, whether the share of a
sample’s cells that belong to the group differs between conditions, as speckle’s
propeller does (Phipson et al. 2022). It reads only the number of cells of every
sample in every group (Cube.n_cells), or a table of them, and fits one linear
model to all groups at once:
Proportions. Sample j, with x_ij cells of group i and N_j cells in all, has the proportion p_ij = x_ij / N_j of group i.
Transform. The logit transform, log(q_ij / (1 - q_ij)), takes the proportions after half a cell is added to every count, q_ij = (x_ij + 0.5) / sum_i (x_ij + 0.5), so that a group absent from a sample has a finite value; the arcsine square-root transform, arcsin(sqrt(p_ij)), takes the proportions as they are (Phipson et al. 2022, supplementary section 1).
Linear model. The transformed proportions of every group are fitted with the design by least squares (limma’s
lmFit), and the contrasts are formed (contrasts.fit).Moderation. The residual variances of the groups are moderated by empirical Bayes, robustly by default, as speckle calls limma’s
eBayes(robust = TRUE)(Smyth 2004; Phipson et al. 2016).Tests. Each contrast is tested by its moderated t-statistic, as
propeller.ttestdoes.f_testnames a factor whose levels are tested for equal means by the moderated F-statistic, aspropeller.anovadoes, andpropellerwith more than two groups. The p-values of a test are adjusted by the method of Benjamini and Hochberg over the groups (speckle’sFDR).
- Samples and groups:
The design’s sample table must have a
sample_idcolumn; its samples are matched to those of the data bysample_id, never by position, and may be some of them, in any order. A sample of the design without cells in any group has no proportions and is left out, and the result records why. Proportions are taken over the groups of the data, so to test the composition of one lineage, select its groups first (Cube.select(groups=...)). A group without cells in any sample used is left out too: it is not observed in the experiment, and speckle, which reads the label of every cell, never sees it. At least two groups must remain.- Descriptive columns:
Each table also gives speckle’s descriptive columns, whose definitions were found by probing speckle 1.6.0. The baseline proportion of a group is its share of all the cells of the samples used (speckle’s
BaselineProp). The mean proportion of a level of a factor is the mean of p_ij over the samples at that level (PropMean), taken over the samples as they are, whatever the other terms of the design. A contrast compares the levels of a factor when it is a combination, with weights d_l that sum to zero, of the differences between the levels of a factor that enters the formula as a main effect only; for exampleconditionstimin~ donor + condition,conditionstim - conditionctrlin~ 0 + condition, or(groupA + groupB)/2 - groupC, but notgroupA + groupB. The proportion ratio of such a contrast is the product of the mean proportions m_l of its levels raised to their weights, prod_l m_l^d_l (PropRatio): m_stim / m_ctrl for stim against ctrl, and sqrt(m_A m_B) / m_C for(groupA + groupB)/2 - groupC. The table of such a contrast gives the mean proportion of each level with a weight other than zero, and the ratio; the table of another contrast, such as a covariate’s slope, gives neither.- Deliberate differences from speckle:
validation/composition_tests.pyreports each of them.speckle’s
PropMeanare the coefficients of the proportions regressed on the columns of the design matrix that the contrast weighs, and itsPropRatiois their product raised to the contrast’s weights (found by probing). Where those columns indicate groups of samples, as in a design without an intercept whose first columns are the groups, the coefficients are the groups’ mean proportions and the ratio is the one above. Otherwise they are not: forgroupA + groupB, which compares nothing, speckle reports the product m_A m_B as a ratio; for(Intercept) + groupBin~ group + age, the mean of the samples outside B and a difference of means; and for a contrast that weighs a single column, such asconditionstimin~ conditionor a covariate, it stops with an error. Here the levels a contrast compares are found from the design’s coding, so every parametrisation of the same comparison gives the same table, and a contrast that compares no levels is tested without descriptive columns.A group without cells in any sample is left out. speckle keeps an unused level of a factor of cell labels, tests it, and with the logit transform its half cells change the transformed proportions of every other group (found by probing).
A sample without cells is left out. speckle keeps an unused level of a factor of sample labels: with the arcsine transform its proportions are NaN and limma leaves it out too, but with the logit transform every group of that sample has the proportion 1 / (number of groups), which enters the fit as data (found by probing).
Data that the design fits exactly in every group, up to rounding, as when the samples of each condition are copies of one another, raise an error: no variance can be estimated. speckle returns statistics of the rounding errors (t-statistics near 1e15, for example) or NaN.
- Accuracy:
The transforms are computed as log((x_ij + 0.5) / (N_j - x_ij + 0.5 (I - 1))) for I groups and as arctan2(sqrt(x_ij), sqrt(N_j - x_ij)): the formulas above, without the cancellation in 1 - q_ij and the loss of digits of arcsin near 1. They agree with speckle’s values to rounding, and stay within a few units in the last place where those lose digits, for a group that holds nearly all the cells of a sample. Cell numbers are summed exactly, in float64 up to 2**53.
References
Phipson B, Sim CB, Porrello ER, Hewitt AW, Powell J, Oshlack A (2022). propeller: testing for differences in cell type proportions in single cell data. Bioinformatics 38, 4720-4726. doi:10.1093/bioinformatics/btac582
Smyth GK (2004). Linear models and empirical Bayes methods for assessing differential expression in microarray experiments. Statistical Applications in Genetics and Molecular Biology 3, article 3. doi:10.2202/1544-6115.1027
Phipson B, Lee S, Majewski IJ, Alexander WS, Smyth GK (2016). Robust hyperparameter estimation protects against hypervariable genes and improves power to detect differential expression. Annals of Applied Statistics 10, 946-963. doi:10.1214/16-AOAS920
Benjamini Y, Hochberg Y (1995). Controlling the false discovery rate: a practical and powerful approach to multiple testing. Journal of the Royal Statistical Society B 57, 289-300. doi:10.1111/j.2517-6161.1995.tb02031.x
- class cytepool.composition.CompositionResult(contrasts, tables, f_test, summary, samples, proportions, provenance)#
Tests of differences in cell-type composition, from
propeller().Tables hold one row per group (cell type) tested, in the order of the data’s groups; speckle sorts them by p-value, as
table.take(numpy.argsort( table["p_value"], kind="stable"))does. The table of a contrast (tables,table) has the columnsgroup_id;baseline_proportion: the group’s share of all the cells of the samples used (speckle’sBaselineProp);for a contrast that compares the levels of a factor (see
cytepool.composition),mean_proportion_<level>for each level it weighs, in level order: the mean proportion of the group over the samples at that level (PropMean), andproportion_ratio: the product of these means raised to the weights of their levels, such as m_stim / m_ctrl (PropRatio);tandp_value: the moderated t-statistic of the contrast on the transformed proportions and its two-sided p-value (Tstatistic,P.Value);adjusted_p_value: Benjamini-Hochberg over the groups (FDR).
The F-test table (
f_test) hasgroup_id,baseline_proportion,mean_proportion_<level>for every level of the factor,f_statistic,p_valueandadjusted_p_value(speckle’sFstatistic,P.ValueandFDR).The summary has one row per group of the data:
group_id;n_cells, its cells in the samples used;tested;reason, why it was not tested (empty when tested);df_residual; andprior_df, the prior degrees of freedom of the moderation (each group has its own with robust moderation; NaN for a group not tested). The sample table has one row per design sample, in the data’s sample order:sample_id,n_cells(in all groups),usedandreason. The proportions table has one row per group tested and sample used:group_id,sample_id,n_cells,proportionandtransformed_proportion, the value the linear model was fitted to.Built by
propeller()from the tables and record it computed. Immutable.- Raises:
TypeError – If
contrastsis not aContrasts,tablesnot a mapping of contrast name toTable,f_testnot aTableor None, orsummary,samplesorproportionsnot aTable, orprovenancenot a mapping that JSON can write.ValueError – If
provenanceholds a NaN or an infinity.
Explained in: Composition tests
- property contrasts#
The contrasts tested by t-tests, over the columns of the design.
- property tables#
The table of every contrast, by contrast name.
- property f_test#
The table of the F-test across the levels of
f_test, or None.
- property summary#
Every group of the data, with its cells, whether it was tested, and its df.
- property samples#
Every design sample, used or left out, and why.
- property proportions#
The proportion and transformed proportion of every group in every sample.
- 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; what the data were (
"input":"cube"or"table"); the cube ("cube"): its fingerprint, the digests it is built from and its own provenance, where a table of cell numbers is recorded as the cube without features that holds them, whosesamples,groupsandn_cellsdigests identify the table; the design (formula, columns, levels, the values of its variables for every sample, and the weights of every contrast); the settings; the hypothesis of the F-test (the factor, its levels and the contrasts between them), or null; the samples used, in the order fitted, and those left out with the reason; and the groups tested and those left out with the reason. The fingerprint, and the digest ofn_cells, say whether a cube at hand holds the cell numbers behind the tables (Cube.verify_fingerprint).
- table(contrast)#
Return the table of one contrast.
- Parameters:
contrast – A contrast name.
- Raises:
KeyError – If the contrast was not tested; the message lists those that were.
Explained in: Composition tests
- cytepool.composition.propeller(data, design, *, contrasts=None, f_test=None, transform='logit', robust=True, trend=False)#
Test whether the cell-type composition of samples differs between conditions.
For every group (cell type), the proportion of each sample’s cells in the group is transformed, a linear model is fitted to the transformed proportions with the design, and its contrasts are tested by moderated t-statistics, and the levels of a factor by a moderated F-statistic, after empirical Bayes moderation of the residual variances across groups: speckle’s
propeller,propeller.ttestandpropeller.anova. The module documentation describes the steps, the descriptive columns and the deliberate differences from speckle.As in speckle,
propeller(clusters, sample, group)with two groups tests the first level against the second:Design(samples, "~ 0 + group", contrasts={"A_vs_B": "groupA - groupB"}); with more groups it isf_test="group".propeller.ttestwith a design matrix of group columns followed by covariates is a design such as~ 0 + group + agewith a contrast between groups, andpropeller.anovathe F-test ofgroupin it. The same comparisons in another parametrisation, such as~ group + agewith the contrastgroupB, give the same tables.- Provenance:
The result records the cube’s fingerprint, the digests it is built from and its own provenance, as
limma_voom()does, although the test reads only the cell numbers. For a cube opened withread_cube()this reads nothing, as the file stores the digests; a cube in memory hashes its layers once, at about 1 s per GB of values where the processor has SHA instructions, and keeps the digests.
- Parameters:
data – The number of cells of every sample in every group: a
Cubethat records them (n_cells), or a table with one row per sample, a text columnsample_idand one column of cell numbers (integers) per group, named by its group id: aTable, a mapping of columns, or a pandas DataFrame. A DataFrame without asample_idcolumn is read with its index as the sample ids, aspandas.crosstab(cells["sample_id"], cells["cell_type"])gives it.design – A
Designwhose sample table has asample_idcolumn with sample ids of the data; its rows may be some of the samples, in any order. A design with random effects is refused: the fit has fixed effects only.contrasts – The contrasts to test by t-tests, as accepted by
make_contrasts()and matched to the design’s columns by name, such as{"stim_vs_ctrl": "conditionstim"}. By default the design’s named contrasts; an empty list tests none.f_test – The name of a factor of the design, entering its formula as a main effect only, whose levels are tested for equal means by an F-test (speckle’s ANOVA), or None for no F-test. To compare some of its levels only, test their differences as contrasts.
transform –
"logit"(speckle’s default), with half a cell added to every count, or"asin"for the arcsine square root.robust – Whether to estimate the prior of the moderation robustly, as speckle does by default (
eBayes(robust = TRUE)), which protects against groups whose variance is unusually large.trend – Whether the prior variance follows the mean transformed proportion of the groups (
eBayes(trend = TRUE)).
- Returns:
The table of every contrast and of the F-test, a summary of every group, the samples used and left out, the proportions, the settings and a provenance record that identifies the data by their fingerprint.
- Raises:
TypeError – If
datais not a Cube or a table of cell numbers, a column of cell numbers does not hold integers, an id of the data or the design’ssample_idis not text,designis not a Design, an option has the wrong type, ordataorcontrastsis or holds a numpy masked array.ValueError – If the cube records no cell numbers; the table has no rows or no group columns, or an id that is missing, empty or repeated; a cell number is negative or a sample has 2**53 cells or more; the design has random effects or no
sample_idcolumn, names samples the data do not have or names a sample twice; a contrast is invalid, or there is nothing to test;transformis neither"logit"nor"asin";f_testdoes not name a factor that enters the formula as a main effect only; fewer than two groups have cells; the design is not of full column rank or leaves no residual degrees of freedom on the samples used; or it fits the transformed proportions of every group exactly.
Explained in: Choosing a method, Composition tests