From 8d8b4e6a4649ddeffbd043fde4e1a7a16b09ae05 Mon Sep 17 00:00:00 2001 From: parashardhapola Date: Tue, 27 Oct 2026 16:35:37 +0100 Subject: [PATCH] Import CytePool's task functions from the top level, with one name per idea --- README.md | 38 +- cytepool/__init__.py | 157 +- cytepool/_analysis_records.py | 63 + cytepool/_expression_values.py | 95 +- cytepool/{stats => }/_gene_sets.py | 199 +- cytepool/_group_fits.py | 4 +- cytepool/_ranked_genes.py | 6 +- cytepool/_study_statistics.py | 122 +- cytepool/activity.py | 152 +- cytepool/composition.py | 208 ++- cytepool/cube/__init__.py | 18 +- cytepool/cube/_axes.py | 11 +- cytepool/cube/collection.py | 6 +- cytepool/cube/pool.py | 56 +- cytepool/cube/provenance.py | 12 +- cytepool/design/__init__.py | 4 +- cytepool/design/checks.py | 8 +- cytepool/differential_expression.py | 103 +- cytepool/{gsea.py => gene_set_enrichment.py} | 46 +- cytepool/{stats => }/gene_set_tests.py | 227 ++- cytepool/gene_sets.py | 101 +- ..._out_prediction.py => held_out_studies.py} | 241 ++- cytepool/io/__init__.py | 14 +- cytepool/io/anndata.py | 68 +- cytepool/io/cube_files.py | 18 +- cytepool/io/zarr.py | 36 +- cytepool/meta_analysis.py | 1599 ----------------- cytepool/meta_analysis_of_studies.py | 664 +++++++ cytepool/networks.py | 44 +- cytepool/normalization.py | 38 +- ...ion.py => over_representation_analysis.py} | 67 +- cytepool/pathway_scores.py | 209 ++- cytepool/plotting.py | 596 +++--- ...nts.py => principal_component_analysis.py} | 95 +- ...ation.py => replication_across_studies.py} | 216 +-- .../{stats => }/rotation_gene_set_tests.py | 472 ++--- cytepool/score_tests.py | 45 +- cytepool/stats/__init__.py | 5 +- cytepool/stats/_mean_variance_trend.py | 14 +- cytepool/stats/_moderation.py | 4 +- cytepool/stats/combined_p_values.py | 27 +- cytepool/stats/deseq2.py | 8 +- cytepool/stats/dream.py | 54 +- cytepool/stats/empirical_bayes.py | 77 +- cytepool/stats/meta_analysis.py | 791 ++++++++ cytepool/stats/mixed_models.py | 14 +- cytepool/stats/multiple_testing.py | 32 +- cytepool/stats/quasi_likelihood.py | 68 +- cytepool/stats/sample_weights.py | 4 +- cytepool/stats/sumrank.py | 547 ++++++ .../variance_fractions.py} | 543 +----- cytepool/stats/voom.py | 57 +- cytepool/study_results.py | 255 ++- cytepool/sumrank.py | 1059 ----------- cytepool/sumrank_across_studies.py | 610 +++++++ cytepool/variance_components.py | 559 ++++++ docs/coverage_exemptions.toml | 23 +- docs/coverage_report.py | 22 +- docs/datasets.py | 14 +- docs/page_index.py | 17 +- docs/source/guides/anndata_and_mudata.ipynb | 33 +- docs/source/guides/bulk_rna_seq.ipynb | 123 +- docs/source/guides/combine_studies.ipynb | 133 +- docs/source/guides/composition.ipynb | 16 +- docs/source/guides/export_cubes.ipynb | 21 +- docs/source/guides/gene_sets.ipynb | 132 +- docs/source/guides/genes_per_cell_type.ipynb | 24 +- .../guides/normalization_and_quality.ipynb | 81 +- docs/source/guides/pathway_activity.ipynb | 91 +- docs/source/guides/pool_cells.ipynb | 41 +- docs/source/guides/repeated_measures.ipynb | 59 +- .../guides/replication_across_studies.ipynb | 215 +-- docs/source/guides/save_and_trace_cubes.ipynb | 57 +- docs/source/installation.md | 5 +- docs/source/quickstart.ipynb | 38 +- docs/source/reference/api/activity.md | 30 +- docs/source/reference/api/anndata.md | 28 +- docs/source/reference/api/composition.md | 26 +- docs/source/reference/api/cube_files.md | 10 +- docs/source/reference/api/cubes.md | 37 +- docs/source/reference/api/designs.md | 21 +- .../reference/api/differential_expression.md | 43 +- docs/source/reference/api/dream.md | 4 +- docs/source/reference/api/gene_set_tests.md | 15 +- docs/source/reference/api/gene_sets.md | 24 +- docs/source/reference/api/gsea.md | 8 +- .../reference/api/held_out_prediction.md | 23 +- docs/source/reference/api/index.md | 14 + docs/source/reference/api/meta_analysis.md | 36 +- docs/source/reference/api/networks.md | 26 +- .../reference/api/over_representation.md | 22 +- docs/source/reference/api/pathway_scores.md | 26 +- .../reference/api/principal_components.md | 22 +- docs/source/reference/api/quasi_likelihood.md | 7 +- docs/source/reference/api/replication.md | 22 +- .../reference/api/rotation_gene_set_tests.md | 18 +- docs/source/reference/api/score_tests.md | 18 +- docs/source/reference/api/study_results.md | 24 +- docs/source/reference/api/sumrank.md | 34 +- .../reference/api/variance_partitioning.md | 37 +- docs/source/reference/capabilities.md | 14 +- docs/source/reference/citing.md | 2 +- docs/source/reference/datasets.md | 9 +- docs/source/statistics/choosing_a_method.md | 4 +- docs/source/statistics/composition.md | 18 +- docs/source/statistics/count_models.md | 4 +- docs/source/statistics/cubes.md | 5 +- docs/source/statistics/gene_set_tests.md | 51 +- docs/source/statistics/meta_analysis.md | 142 +- docs/source/statistics/mixed_models.md | 2 +- docs/source/validation.md | 20 +- docs/source/why_pseudobulk.ipynb | 24 +- tests/smoke_test_wheel.py | 12 +- tests/test_activity.py | 139 +- tests/test_anndata_conversion.py | 247 ++- .../test_combined_p_values_and_replication.py | 182 +- tests/test_composition.py | 96 +- tests/test_cube.py | 2 +- tests/test_cube_collection.py | 44 +- tests/test_cube_files.py | 14 +- tests/test_cube_fingerprints.py | 10 +- tests/test_deseq2.py | 2 +- tests/test_design.py | 3 +- tests/test_design_checks.py | 11 +- tests/test_design_model_matrix.py | 2 +- tests/test_differential_expression.py | 26 +- tests/test_differential_expression_options.py | 27 +- tests/test_documentation.py | 120 +- tests/test_dream.py | 36 +- tests/test_empirical_bayes.py | 42 +- tests/test_gene_set_tests.py | 310 +++- tests/test_gene_sets.py | 61 +- tests/test_gsea.py | 8 +- tests/test_held_out_prediction.py | 198 +- tests/test_interferon_stimulated_pbmcs.py | 8 +- tests/test_kenward_roger_and_f_tests.py | 10 +- tests/test_linear_models.py | 3 +- tests/test_masked_inputs.py | 475 +++-- tests/test_meta_analysis.py | 223 ++- tests/test_mixed_models.py | 3 +- tests/test_multiple_testing.py | 4 +- tests/test_named_contrasts.py | 7 +- tests/test_negative_binomial_models.py | 2 +- tests/test_networks.py | 11 +- tests/test_normalization.py | 101 +- tests/test_over_representation.py | 7 +- tests/test_pathway_scores.py | 204 ++- tests/test_pca_and_dot_plots.py | 98 +- tests/test_plotting.py | 179 +- tests/test_pool.py | 134 +- tests/test_private_names.py | 19 + tests/test_provenance.py | 29 +- tests/test_quasi_likelihood.py | 37 +- tests/test_result_fits.py | 26 +- tests/test_rotation_gene_set_tests.py | 342 +++- tests/test_sample_quality_weights.py | 14 +- tests/test_score_tests.py | 56 +- tests/test_study_results.py | 24 +- tests/test_sumrank.py | 357 ++-- tests/test_table.py | 2 +- tests/test_top_level.py | 103 ++ tests/test_variance_partitioning.py | 44 +- tests/test_voom.py | 22 +- tests/test_zarr.py | 12 +- validation/activity.py | 42 +- validation/anndata_conversion.py | 33 +- .../combined_p_values_and_replication.py | 41 +- validation/composition_tests.py | 8 +- validation/cube.py | 2 +- validation/cube_collection.py | 28 +- validation/cube_files.py | 3 +- validation/cube_fingerprints.py | 3 +- validation/cube_reads.py | 3 +- validation/deseq2_tests.py | 2 +- validation/design.py | 3 +- validation/differential_expression.py | 14 +- validation/differential_expression_options.py | 14 +- validation/dream.py | 28 +- validation/empirical_bayes.py | 6 +- validation/gene_set_files_and_ora.py | 8 +- validation/gene_set_tests.py | 16 +- validation/gsea.py | 3 +- validation/interferon_stimulated_pbmcs.py | 14 +- validation/kenward_roger_and_f_tests.py | 10 +- validation/meta_analysis.py | 26 +- validation/mixed_models.py | 2 +- validation/negative_binomial_fits.py | 2 +- validation/normalization.py | 39 +- validation/pathway_scores.py | 17 +- validation/pca_and_dot_plots.py | 22 +- validation/plotting.py | 39 +- validation/pool_cells.py | 7 +- validation/quasi_likelihood.py | 20 +- validation/rotation_gene_set_tests.py | 23 +- validation/sample_quality_weights.py | 8 +- validation/sumrank.py | 138 +- validation/variance_partitioning.py | 16 +- validation/voom.py | 6 +- 198 files changed, 9995 insertions(+), 7600 deletions(-) rename cytepool/{stats => }/_gene_sets.py (69%) rename cytepool/{gsea.py => gene_set_enrichment.py} (97%) rename cytepool/{stats => }/gene_set_tests.py (80%) rename cytepool/{held_out_prediction.py => held_out_studies.py} (76%) delete mode 100644 cytepool/meta_analysis.py create mode 100644 cytepool/meta_analysis_of_studies.py rename cytepool/{over_representation.py => over_representation_analysis.py} (88%) rename cytepool/{principal_components.py => principal_component_analysis.py} (80%) rename cytepool/{replication.py => replication_across_studies.py} (79%) rename cytepool/{stats => }/rotation_gene_set_tests.py (79%) create mode 100644 cytepool/stats/meta_analysis.py create mode 100644 cytepool/stats/sumrank.py rename cytepool/{variance_partitioning.py => stats/variance_fractions.py} (50%) delete mode 100644 cytepool/sumrank.py create mode 100644 cytepool/sumrank_across_studies.py create mode 100644 cytepool/variance_components.py create mode 100644 tests/test_top_level.py diff --git a/README.md b/README.md index 1048240..da80405 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ what is planned. | Provenance and fingerprints | A record of each cube's source (the caller's record, kept unchanged), of the settings that built it and of what each operation kept, merged or pooled, with the fingerprint of the cube it was applied to; `Cube.history()` reads it back. A content fingerprint (SHA-256, documented encoding) of the tables, cell numbers, normalization and layers | a reimplementation of the documented encoding, zarr-python | | Cube files | `write_cube`, `read_cube` (a subset of Zarr v3, with orthogonal selections; a read opens only the chunks that hold the selected features; gzip, and zstd on Python 3.14 or newer; stored digests, checked when a file is opened and when a read covers a whole group) | zarr-python | | AnnData and MuData | `to_anndata`, `from_anndata`, `to_mudata`, `from_mudata` (optional extras `anndata` and `mudata`): cubes to and from the long AnnData (with a layer copied into `X` on request) of `scanpy.get.aggregate` and `decoupler.pp.pseudobulk` and the MuData of LIANA's `adata_to_views`, whose layouts are recognised; an import takes sums and counts of cells, refuses means, and gives a cube exported and read back with the same fingerprint | scanpy, decoupler, LIANA, anndata, mudata | -| Several studies | `CubeCollection`: the cubes of several studies aligned, each kept as it is: samples named `study::sample_id`, with donor ids alike; genes aligned by id, with which study measured which, so that a gene a study did not measure is missing there, not zero; cell types aligned by Cell Ontology id, group id or a mapping for each study, merging those that a study splits, and named as given; each study's cube on the collection's axes, for analyses study by study; and one cube of the genes and cell types every study has, for a joint model. `align_study_results` lines up the studies' results gene by gene, with the reason a study has none for a gene; `analyse_each_study` runs one analysis per study, and `StudyResults` keeps the results with the collection, lined up or as a studies x genes matrix | anndata, pandas, limma | +| Several studies | `CubeCollection`: the cubes of several studies aligned, each kept as it is: samples named `study::sample_id`, with donor ids alike; genes aligned by id, with which study measured which, so that a gene a study did not measure is missing there, not zero; cell types aligned by Cell Ontology id, group id or a mapping for each study, merging those that a study splits, and named as given; each study's cube on the collection's axes, for analyses study by study; and one cube of the genes and cell types every study has, for a joint model. `analyse_each_study` runs one analysis per study, and `StudyResults` keeps the results with the collection, lined up gene by gene, with the reason a study has none for a gene, or as a studies x genes matrix | anndata, pandas, limma | | Normalization and filtering | TMM, upper-quartile and RLE factors, DESeq2 size factors, CPM, log-CPM, `filter_by_expression` | edgeR, DESeq2 | | Designs | Model matrices from R formulas, named contrasts, design checks | R, limma, lme4 | | Linear models | Ordinary, weighted and generalised least squares for every gene (in parallel when each gene needs its own fit), contrasts (named contrasts are matched to the coefficients by name) | limma | @@ -25,17 +25,17 @@ what is planned. | dream | `voom_with_dream_weights`: voom's precision weights from the mean-variance trend of every gene's mixed model, fitted by maximum likelihood, as variancePartition's `voomWithDreamWeights`; `moderated_fixed_effect_tests`: Satterthwaite or Kenward-Roger t-tests of a mixed model moderated by empirical Bayes as variancePartition's `eBayes` moderates `dream` fits, with `topTable`'s z-statistic, and the moderated F-test of several contrasts | variancePartition | | Sample quality weights | `sample_quality_weights`: a weight for each sample, or each group of samples, from how closely its values follow the linear model, by REML or the gene-by-gene update, as limma's `arrayWeights` | limma | | Negative binomial models | `fit_negative_binomial_model`: a negative binomial log-linear model for every gene, with known dispersions, as edgeR's `glmFit` (one-way layouts group by group, other designs by Newton's method with Levenberg damping, in parallel), with log fold changes shrunk by prior counts; exact unit deviances and deviances | edgeR, mpmath | -| Quasi-likelihood F-tests | `fit_quasi_likelihood` and `quasi_likelihood_f_test`: edgeR 4's `glmQLFit` and `glmQLFTest` with their defaults: the common negative binomial dispersion of the most highly expressed genes by Cox-Reid adjusted profile likelihood (`common_dispersion`), deviances adjusted for small counts with exact moments of the unit deviance (`negative_binomial_unit_deviance_moments`), the average quasi-dispersion, limma's prior for unequal degrees of freedom with a trend in the average log CPM (`average_log_cpm`) or robust, prior weights and offsets, and F-tests of coefficients and named contrasts with edgeR's columns (logFC, logCPM, F, PValue, FDR) | edgeR, limma, mpmath | +| Quasi-likelihood F-tests | `fit_quasi_likelihood` and `quasi_likelihood_f_test`: edgeR 4's `glmQLFit` and `glmQLFTest` with their defaults: the common negative binomial dispersion of the most highly expressed genes by Cox-Reid adjusted profile likelihood (`common_dispersion`), deviances adjusted for small counts with exact moments of the unit deviance (`negative_binomial_unit_deviance_moments`), the average quasi-dispersion, limma's prior for unequal degrees of freedom with a trend in the average log CPM (`average_log2_cpm`) or robust, prior weights and offsets, and F-tests of coefficients and named contrasts with edgeR's columns (logFC, logCPM, F, PValue, FDR) | edgeR, limma, mpmath | | DESeq2 | `fit_deseq2`, `deseq2_wald_test`, `deseq2_likelihood_ratio_test` and `deseq2_dispersions`: DESeq2's analysis with its defaults, from gene-wise dispersions (Cox-Reid adjusted profile likelihood), the parametric trend and the maximum a posteriori dispersions to its fits of every gene, Wald tests of coefficients and contrasts, and likelihood ratio tests of a reduced design (all the levels of a factor at once, a time course), with the columns of `results()`, its independent filtering and its Cook's distance flags | DESeq2, PyDESeq2 | | Empirical Bayes moderation | Moderated t and F statistics, B-statistics, TREAT, with constant, trend and robust priors, estimated as limma estimates them by default: by the method of moments, or, when the residual degrees of freedom differ between genes, by weighted moments and profile likelihood (limma's `fitFDistUnequalDF1`); either can be chosen | limma | -| Gene-set tests | `camera` (competitive, allowing for the correlation between the genes of a set, given or estimated per set; parametric or rank-based) and `fry` (self-contained, directional and mixed p-values), for one contrast of a linear model, with voom or other precision weights and trend or robust moderation; the rotation tests `roast` and `mroast` (self-contained, with the mean, floor-mean, mean-50 and mean-square set statistics, contribution weights per gene or per set, and the proportions of active genes) and `romer` (competitive, by the ranks of the genes), with rotations from a seeded generator; gene sets named by gene id or position. Gene sets read from and written to GMT files (MSigDB, Reactome, WikiPathways, the Gene Ontology), with the source, version and licence of the collection, matched to an analysis and filtered by size; over-representation analysis of the genes up, down or either way in a differential-expression result, a table or a list of genes (limma's `kegga` and `goana`), with its dot plot; gene set enrichment analysis (`gsea`) of genes ranked by a statistic, as fgsea computes it: enrichment scores, leading edges, normalized scores, and p-values by fgsea's multilevel method or by permutations, from seeds that a numpy generator draws, with its enrichment plot and table; scores of gene sets in every sample, as the GSVA package computes them: `gsva` (with the Gaussian, Poisson or no kernel, chosen as GSVA chooses it, and its three scores), `ssgsea`, `combined_z_scores` and `plage`, from a matrix or a cell type of a cube, with their heatmap, and tested for a contrast of a design by limma (`score_tests.limma`, as for activities) | limma, R, decoupler, fgsea, GSVA | +| Gene-set tests | `camera` (competitive, allowing for the correlation between the genes of a set, given or estimated per set; parametric or rank-based) and `fry` (self-contained, directional and mixed p-values), for one contrast of a cell type of a `limma_voom` or `limma_trend` result, or of a linear model of values with a design, with voom or other precision weights and trend or robust moderation; the rotation tests `roast` and `mroast` (self-contained, with the mean, floor-mean, mean-50 and mean-square set statistics, contribution weights per gene or per set, and the proportions of active genes) and `romer` (competitive, by the ranks of the genes), with rotations from a seeded generator; gene sets named by gene id or position. Gene sets read from and written to GMT files (MSigDB, Reactome, WikiPathways, the Gene Ontology), with the name, version and licence of the collection, matched to an analysis and filtered by size; over-representation analysis of the genes up, down or either way in a differential-expression result, a table or a list of genes (limma's `kegga` and `goana`), with its dot plot; gene set enrichment analysis (`gsea`) of genes ranked by a statistic, as fgsea computes it: enrichment scores, leading edges, normalized scores, and p-values by fgsea's multilevel method or by permutations, from seeds that a numpy generator draws, with its enrichment plot and table; scores of gene sets in every sample, as the GSVA package computes them: `gsva` (with the Gaussian, Poisson or no kernel, chosen as GSVA chooses it, and its three scores), `ssgsea`, `combined_z_scores` and `plage`, from a matrix or a cell type of a cube, with their heatmap, and tested for a contrast of a design by limma (`limma`, as for activities) | limma, R, decoupler, fgsea, GSVA | | Activities of regulators and pathways | `ulm`, `mlm` and `weighted_aggregate`, as decoupler computes them: the activity of every source of a network (transcription factors, pathways) in every sample, cell type of a cube or group and contrast of a differential-expression result, by a linear model of each source's weights, one model of all of them, or the weighted mean or sum of its targets with permutations; the networks CollecTRI (`collectri`, at a licence level for commercial or academic use) and PROGENy (`progeny`, human and mouse), downloaded from their releases on first use and checked against pinned SHA-256 digests, or any network given as a table; their heatmap and bar plot | decoupler, progeny | | Multiple testing | Bonferroni, Holm, Hochberg, Hommel, Benjamini-Hochberg, Benjamini-Yekutieli | R | | Differential expression | `limma_voom` and `limma_trend`: limma-voom and limma-trend on every group (cell type) of a cube, and `dream_voom`: dream, with the design's random intercepts, such as a donor's, in every group's fits, dream's choice of Kenward-Roger's or Satterthwaite's degrees of freedom by the number of samples in each group, and F-tests of several contrasts, with the design matched to the cube by sample id, samples with too few cells left out, rank-deficient groups fitted on their column space with the contrasts they can estimate, and a provenance record for every result, which keeps the cube and design it analysed and computes a cell type's fit again on request. Options for pseudobulk data, off by default: sample quality weights, prior weights from the number of cells, a minimum total count per sample, and p-values adjusted across cell types | limma, edgeR, variancePartition; on simulated data and on published single-cell data | | Variance partitioning | `variance_fractions`: the fraction of every gene's variance that each variable of a design explains, as variancePartition's `fitExtractVarPartModel`: a mixed model of every gene by maximum likelihood (or REML), with the categorical variables as random intercepts, the continuous ones as fixed effects and precision weights such as voom's, or a linear model without random effects; `variance_partitioning`: the same on every cell type of a cube, with dream's precision weights, the samples each fit used and a provenance record | variancePartition, lme4 | -| Meta-analysis | `meta_analysis`: for every gene, the fixed-effect (inverse-variance) or random-effects estimate of a common effect from the effects and standard errors of several studies, with the between-study variance by DerSimonian-Laird, REML or Paule-Mandel, Cochran's Q, I² and H², z-tests or the Knapp-Hartung adjustment, and genes measured in some studies only; `differential_expression_meta_analysis`: the same for the `limma_voom` or `limma_trend` results of several cubes, matched by cell type, contrast and gene id, with p-values adjusted over the genes and a provenance record | metafor | -| Combined p-values and replication | `combine_p_values`: every gene's p-values combined over the studies that tested it, by Fisher's, Stouffer's (weighted), Tippett's, Wilkinson's, Pearson's, Mudholkar and George's or Simes's method, one-sided or two-sided with the directions of the effects; `partial_conjunction`: the p-value that at least u studies have an effect, in one direction (Benjamini and Heller); `proportion_of_true_nulls`: pi0 and pi1 as qvalue's `pi0est`, with its smoother and bootstrap; `differential_expression_replication`: for the studies of a `CubeCollection`, every gene's combined and replication p-values, the studies that find it, its sign agreement and heterogeneity, pi1 (estimated at lambda = 0.5 by default) and sign agreement of every pair of studies, and the genes found in at least k studies, with their figures | scipy, metap, qvalue, metafor, mpmath | -| Replication across studies | `sumrank`: SumRank (Nakatsuka et al. 2025), which ranks every gene within each study by its signed -log10 p-value and sums its best normalized ranks across studies, for genes up and down, with p-values from the Irwin-Hall distribution and from a null pooled over permutations; `differential_expression_sumrank`: the same for one cell type and contrast of the studies of a `CubeCollection`, with permutations that re-run each study's limma analysis from a seeded generator; `held_out_prediction`: each study left out in turn, genes chosen from the others by SumRank and by meta-analysis, its cases scored by UCell's rank score, and the AUC by study and method; with SumRank's Manhattan plot and the held-out AUC of every study | the authors' code, mpmath, scikit-learn | +| Meta-analysis | `meta_analysis`: for every gene, the fixed-effect (inverse-variance) or random-effects estimate of a common effect from the `limma_voom` or `limma_trend` results of several cubes (`StudyResults`), matched by cell type, contrast and gene id, with the between-study variance by DerSimonian-Laird, REML or Paule-Mandel, Cochran's Q, I² and H², z-tests or the Knapp-Hartung adjustment, genes measured in some studies only, p-values adjusted over the genes and a provenance record; `fit_meta_analysis`: the same from arrays of effects and standard errors | metafor | +| Combined p-values and replication | `combine_p_values`: every gene's p-values combined over the studies that tested it, by Fisher's, Stouffer's (weighted), Tippett's, Wilkinson's, Pearson's, Mudholkar and George's or Simes's method, one-sided or two-sided with the directions of the effects; `partial_conjunction`: the p-value that at least u studies have an effect, in one direction (Benjamini and Heller); `proportion_of_true_nulls`: pi0 and pi1 as qvalue's `pi0est`, with its smoother and bootstrap; `replication`: for the studies' results of a `CubeCollection` (`StudyResults`), every gene's combined and replication p-values, the studies that find it, its sign agreement and heterogeneity, pi1 (estimated at lambda = 0.5 by default) and sign agreement of every pair of studies, and the genes found in at least k studies, with their figures | scipy, metap, qvalue, metafor, mpmath | +| Replication across studies | `sumrank`: SumRank (Nakatsuka et al. 2025), which ranks every gene within each study by its signed -log10 p-value and sums its best normalized ranks across studies, for genes up and down, for one cell type and contrast of the studies' results (`StudyResults`), with p-values from the Irwin-Hall distribution and from a null pooled over permutations that re-run each study's limma analysis from a seeded generator; `fit_sumrank`: the same from a matrix of statistics; `held_out_prediction`: each study left out in turn, genes chosen from the others by SumRank and by meta-analysis, its cases scored by UCell's rank score, and the AUC by study and method; with SumRank's Manhattan plot and the held-out AUC of every study | the authors' code, mpmath, scikit-learn | | Composition tests | `propeller`: differences in cell-type proportions between conditions, from the cell numbers of a cube or a table of them, with the logit or arcsine square-root transform, a design matched by sample id, t-tests of its contrasts and the F-test of a factor, robust empirical Bayes moderation, speckle's columns (baseline and mean proportions, proportion ratio, statistic, p-value, FDR) and a provenance record | speckle, limma | | Figures | `cytepool.plotting` (optional extra `plot`, matplotlib): the plots of limma, edgeR, DESeq2, speckle and metafor from CytePool's results, in one call each: MDS plots of pseudobulk samples (`plotMDS`), the cells pooled into each sample and cell type, cell-type composition and `propeller`'s results, voom's mean-variance trend, residual variances and their prior (`plotSA`), sample quality weights, MD and volcano plots, p-value histograms, the genes up and down in each cell type, barcode plots and summaries of gene-set tests, DESeq2's dispersions (`plotDispEsts`), edgeR's quasi-dispersions (`plotQLDisp`), forest plots of meta-analyses, the fractions of variance of every variable over the genes (`plotVarPart`) and of chosen genes (`plotPercentBars`), and heatmaps of pathway scores | limma, edgeR, DESeq2, metafor, speckle, variancePartition, GSVA | | Samples and cells at a glance | `principal_components` and `plot_pca`: samples on the principal components of their most variable genes, from a matrix or a cell type of a cube, as DESeq2's `plotPCA` computes and draws them; `plot_dotplot`: the fraction of cells that express each gene and their mean expression in each cell type, from a cube's sums and so without the cells, as scanpy's `dotplot` draws them | DESeq2, edgeR, scanpy | @@ -293,17 +293,16 @@ These are not implemented yet: compares no levels, where speckle reports regression coefficients; and it refuses data that the design fits exactly, such as copies of samples. - `meta_analysis` fits models without moderators and takes the studies as independent, - with known sampling variances; `differential_expression_meta_analysis` takes limma's - moderated standard errors as such, so a gene that one study tested, kept with - `min_studies=1`, gets a z-test whose p-value is smaller than the study's own - t-test's. Its REML estimate is the largest maximum of the restricted likelihood: - where that likelihood has two maxima, as when an imprecise study lies far from - precise ones that agree, metafor's Fisher scoring from the Hedges estimate can stop - at the lower one or at zero (in 2 of the 9 such features of one validation case), - and on some features it does not converge without `stepadj`. Its REML and - Paule-Mandel estimates converge to rounding, beyond metafor's default stopping - rules, and it reports I² as a proportion, where metafor reports a percentage. It - refuses standard errors that are zero or infinite; NaN marks a study that did not + with known sampling variances, and it takes limma's moderated standard errors as such, + so a gene that one study tested, kept with `min_studies=1`, gets a z-test whose + p-value is smaller than the study's own t-test's. Its REML estimate is the largest + maximum of the restricted likelihood: where that likelihood has two maxima, as when an + imprecise study lies far from precise ones that agree, metafor's Fisher scoring from + the Hedges estimate can stop at the lower one or at zero (in 2 of the 9 such features + of one validation case), and on some features it does not converge without `stepadj`. + Its REML and Paule-Mandel estimates converge to rounding, beyond metafor's default + stopping rules, and it reports I² as a proportion, where metafor reports a percentage. + It refuses standard errors that are zero or infinite; NaN marks a study that did not measure a gene. - `gsva` estimates each gene's distribution from every pair of its samples, O(n²) operations per gene for n samples: 0.8 s for 15,000 genes of 50 samples on two @@ -320,9 +319,8 @@ These are not implemented yet: refuses. `proportion_of_true_nulls` gives an estimate where qvalue stops: where no p-value reaches the largest lambda, and where the estimate is at most 0 (it is 0); with more than 49 values of lambda it keeps a knot at every one, where R's - `smooth.spline` keeps some. `differential_expression_replication` reads limma's - two-sided p-values with the signs of the log2 fold changes, so it needs a contrast, - not an F-test. + `smooth.spline` keeps some. `replication` reads limma's two-sided p-values with the + signs of the log2 fold changes, so it needs a contrast, not an F-test. - `mlm` builds the design of the genes and sources of each set of profiles that observe the same genes, and its QR factor: 16 x genes x sources bytes, 190 MB for 15,000 genes and 800 CollecTRI sources. `collectri` and `progeny` download 4.3 MB and diff --git a/cytepool/__init__.py b/cytepool/__init__.py index 088deb4..72d1c9d 100644 --- a/cytepool/__init__.py +++ b/cytepool/__init__.py @@ -1,4 +1,41 @@ -"""Statistics on pseudobulk and bulk omics data.""" +"""Statistics on pseudobulk and bulk omics data. + +``import cytepool`` gives the functions and classes an analysis starts from, by the +question they answer, the default first: + +- Cubes, tables and files: :class:`Cube`, :class:`CubeCollection`, :class:`Table`, + :func:`from_cells`, :func:`from_bulk`, :func:`read_cube`, :func:`write_cube`, + :func:`from_anndata`, :func:`to_anndata`, :func:`from_mudata`, :func:`to_mudata`. +- Designs: :class:`Design`. +- Which genes change in each cell type: :func:`limma_voom`; for repeated measures, + :func:`dream_voom`; :func:`limma_trend`; :class:`DifferentialExpressionResult`, + :class:`GroupFit`. +- Which cell types expand or contract: :func:`propeller`, :class:`CompositionResult`. +- What drives the variation between samples: :func:`variance_partitioning`, + :class:`VariancePartitioningResult`, :func:`principal_components`, + :class:`PrincipalComponents`. +- Gene sets and networks: :class:`GeneSets`, :func:`read_gmt`, :func:`write_gmt`, + :class:`Network`, :func:`collectri`, :func:`progeny`. +- Pathways for one contrast: :func:`camera`, :func:`fry`, :func:`roast`, + :func:`mroast`, :func:`romer`. +- Pathways along a ranking, or among the changed genes: :func:`gsea`, + :func:`over_representation`. +- Pathway scores per sample: :func:`gsva`, :func:`ssgsea`, :func:`combined_z_scores`, + :func:`plage`, :class:`PathwayScores`. +- Regulator activity: :func:`ulm`, :func:`mlm`, :func:`weighted_aggregate`, + :class:`Activities`. +- Testing scores with a design: :func:`limma`. +- Several studies: :func:`analyse_each_study`, :class:`StudyResults`; combining them, + :func:`meta_analysis`, :class:`MetaAnalysisResult`; replication, :func:`sumrank`, + :class:`SumRankResult`, :func:`replication`, :class:`ReplicationResult`, + :func:`held_out_prediction`, :class:`HeldOutPrediction`. + +The building blocks, which work on arrays, are in ``cytepool.stats`` (linear and +mixed models, empirical Bayes moderation, voom, count models, multiple testing, +combined p-values and the distributions) and :mod:`cytepool.normalization`; the parts +of a design are in :mod:`cytepool.design`, and the figures in +:mod:`cytepool.plotting`, which needs matplotlib. +""" from importlib.metadata import PackageNotFoundError as _PackageNotFoundError from importlib.metadata import version as _distribution_version @@ -18,6 +55,120 @@ def _installed_version() -> str: return version -__version__ = _installed_version() +# Set before the modules below are imported: some of them record it. +__version__: str = _installed_version() + +from .activity import Activities, mlm, ulm, weighted_aggregate # noqa: E402 +from .composition import CompositionResult, propeller # noqa: E402 +from .cube import Cube, CubeCollection, from_bulk, from_cells # noqa: E402 +from .design import Design # noqa: E402 +from .differential_expression import ( # noqa: E402 + DifferentialExpressionResult, + GroupFit, + dream_voom, + limma_trend, + limma_voom, +) +from .gene_set_enrichment import gsea # noqa: E402 +from .gene_set_tests import camera, fry # noqa: E402 +from .gene_sets import GeneSets, read_gmt, write_gmt # noqa: E402 +from .held_out_studies import HeldOutPrediction, held_out_prediction # noqa: E402 +from .io import read_cube, write_cube # noqa: E402 +from .io.anndata import from_anndata, from_mudata, to_anndata, to_mudata # noqa: E402 +from .meta_analysis_of_studies import MetaAnalysisResult, meta_analysis # noqa: E402 +from .networks import Network, collectri, progeny # noqa: E402 +from .over_representation_analysis import over_representation # noqa: E402 +from .pathway_scores import ( # noqa: E402 + PathwayScores, + combined_z_scores, + gsva, + plage, + ssgsea, +) +from .principal_component_analysis import ( # noqa: E402 + PrincipalComponents, + principal_components, +) +from .replication_across_studies import ReplicationResult, replication # noqa: E402 +from .rotation_gene_set_tests import mroast, romer, roast # noqa: E402 +from .score_tests import limma # noqa: E402 +from .study_results import StudyResults, analyse_each_study # noqa: E402 +from .sumrank_across_studies import SumRankResult, sumrank # noqa: E402 +from .table import Table # noqa: E402 +from .variance_components import ( # noqa: E402 + VariancePartitioningResult, + variance_partitioning, +) -__all__ = ["__version__"] +__all__ = [ + # Cubes, tables and files. + "Cube", + "CubeCollection", + "Table", + "from_cells", + "from_bulk", + "read_cube", + "write_cube", + "from_anndata", + "to_anndata", + "from_mudata", + "to_mudata", + # Designs. + "Design", + # Which genes change in each cell type? + "limma_voom", + "dream_voom", + "limma_trend", + "DifferentialExpressionResult", + "GroupFit", + # Which cell types expand or contract? + "propeller", + "CompositionResult", + # What drives the variation between samples? + "variance_partitioning", + "VariancePartitioningResult", + "principal_components", + "PrincipalComponents", + # Gene sets and networks. + "GeneSets", + "read_gmt", + "write_gmt", + "Network", + "collectri", + "progeny", + # Pathways for one contrast. + "camera", + "fry", + "roast", + "mroast", + "romer", + # Pathways along a ranking, or among the changed genes. + "gsea", + "over_representation", + # Pathway scores per sample. + "gsva", + "ssgsea", + "combined_z_scores", + "plage", + "PathwayScores", + # Regulator activity. + "ulm", + "mlm", + "weighted_aggregate", + "Activities", + # Testing scores with a design. + "limma", + # Several studies. + "analyse_each_study", + "StudyResults", + "meta_analysis", + "MetaAnalysisResult", + "sumrank", + "SumRankResult", + "replication", + "ReplicationResult", + "held_out_prediction", + "HeldOutPrediction", + # The version. + "__version__", +] diff --git a/cytepool/_analysis_records.py b/cytepool/_analysis_records.py index f241ad3..2602982 100644 --- a/cytepool/_analysis_records.py +++ b/cytepool/_analysis_records.py @@ -140,6 +140,69 @@ def only_key(value: Any, available: list[str], argument: str, what: str) -> Any: ) +def absence(result: Any, group: str, contrast: str) -> str: + """Why a differential-expression result has no table of a group and contrast, in + the words of its own ``table`` method.""" + try: + result.table(group, contrast) + except KeyError as error: + return str(error.args[0]) + raise AssertionError("a table that is not in result.tables") # pragma: no cover + + +def study_keys( + results: Mapping[str, Any], group: Any, contrast: Any +) -> tuple[Any, Any]: + """The group and contrast of the studies' differential-expression results asked + for, or, for one left out, the only one that the studies analysed. + + Raises: + ValueError: If a key is left out and the studies analysed several, or none. + """ + return ( + only_study_key(results, group, "group", "groups"), + only_study_key(results, contrast, "contrast", "contrasts"), + ) + + +def only_study_key( + results: Mapping[str, Any], value: Any, argument: str, what: str +) -> Any: + """``value`` when given, else the only group (``what="groups"``), or contrast + (``what="contrasts"``), that the studies' results analysed. + + Raises: + ValueError: If ``value`` is None and the studies analysed several, or none. + """ + if value is not None: + return value + available = study_union(results, what) + if len(available) != 1: + raise ValueError( + f"the studies analysed {len(available)} {what}; name one with " + f"{argument}, one of {quoted(available)}" + ) + return available[0] + + +def study_union(results: Mapping[str, Any], what: str) -> list[str]: + """The groups (``what="groups"``), or the contrasts and F-tests, of any study's + result, in the order in which the studies first have them.""" + if what == "groups": + return list( + dict.fromkeys( + group for result in results.values() for group in result.groups + ) + ) + return list( + dict.fromkeys( + name + for result in results.values() + for name in (*result.contrasts.names, *result.f_tests) + ) + ) + + def software_versions() -> dict[str, str]: """The versions of CytePool, numpy, numba and Python that run an analysis.""" return { diff --git a/cytepool/_expression_values.py b/cytepool/_expression_values.py index 28f4e5b..f25c941 100644 --- a/cytepool/_expression_values.py +++ b/cytepool/_expression_values.py @@ -37,12 +37,12 @@ from ._messages import quoted from ._options import checked_choice, checked_flag, integral_number from .cube import Cube -from .normalization import filter_by_expression, log_cpm, tmm_factors +from .normalization import filter_by_expression, log2_cpm, tmm_factors from .table import Table FloatArray = npt.NDArray[np.float64] -VALUES = ("log_cpm", "counts") +VALUES = ("log2_cpm", "counts") # As limma_voom leaves samples out, and edgeR's cpm(log = TRUE) shifts counts. MIN_CELLS = 10 PRIOR_COUNT = 2.0 @@ -61,7 +61,8 @@ class ExpressionValues(NamedTuple): def expression_values( data: Any, - gene_ids: Any, + id_column: Any, + feature_ids: Any, sample_ids: Any, group: Any, values: Any, @@ -76,11 +77,12 @@ def expression_values( Args: data: A 2-D array of values, samples x genes, or a Cube. - gene_ids: For an array, one id per column; for a cube, the name of a column - of its features table (``"feature_id"`` by default) or one id per feature. + id_column: For a cube, the column of its features table with the ids + (``"feature_id"`` by default). + feature_ids: For an array, one id per column. sample_ids: For an array, one id per row; by default "1", "2", ... group: The group of a cube; None for its only one. - values: For a cube, ``"log_cpm"`` or ``"counts"``. + values: For a cube, ``"log2_cpm"`` or ``"counts"``. min_cells: For a cube, the fewest cells a sample needs in the group. expression_filter: For a cube, whether to keep only the genes that filter_by_expression keeps: True or False, or a mapping of its settings, @@ -91,19 +93,27 @@ def expression_values( other_forms: The other forms that ``data`` may take, for messages, such as ", a DifferentialExpressionResult". """ - reject_masked(gene_ids, "gene_ids", missing="None") + reject_masked(feature_ids, "feature_ids", missing="None") reject_masked(sample_ids, "sample_ids") + reject_masked(id_column, "id_column") if isinstance(data, Cube): if sample_ids is not None: raise ValueError( "sample_ids applies to an array; a cube's samples have their ids" ) + if feature_ids is not None: + raise ValueError( + "feature_ids applies to an array; a cube's features are named by a " + "column of its features table, which id_column names (add one with " + "Cube.with_columns(features=...))" + ) return _cube_values( - data, gene_ids, group, values, min_cells, expression_filter, matched_to + data, id_column, group, values, min_cells, expression_filter, matched_to ) defaults = ( ("group", group is None), - ("values", isinstance(values, str) and values == "log_cpm"), + ("values", isinstance(values, str) and values == "log2_cpm"), + ("id_column", isinstance(id_column, str) and id_column == "feature_id"), ( "min_cells", isinstance(min_cells, int) @@ -138,18 +148,18 @@ def expression_values( ) if np.isinf(matrix).any(): raise ValueError("data holds infinite values; mark missing values with NaN") - if gene_ids is None: + if feature_ids is None: raise ValueError( - "gene_ids is needed with an array: one id per column, by which " + "feature_ids is needed with an array: one id per column, by which " f"{matched_to} name the genes" ) - if isinstance(gene_ids, str): + if isinstance(feature_ids, str): raise TypeError( - "gene_ids names a column of a cube's features table; with an array, give " - "one id per column" + "feature_ids gives one id per column of an array; id_column names a " + "column of a cube's features table" ) - ids = _gene_id_list(gene_ids, n_genes, "gene_ids") - _check_unique(ids, "gene_ids", matched_to) + ids = _gene_id_list(feature_ids, n_genes, "feature_ids") + _check_unique(ids, "feature_ids", matched_to) if sample_ids is None: names = [str(i + 1) for i in range(n_samples)] else: @@ -163,6 +173,24 @@ def expression_values( ) +PROFILE_ID = "profile_id" + + +def profile_table(samples: Table, what: str) -> Table: + """The profiles of scores read from a sample table: ``profile_id``, the sample + ids, then the sample table's columns. + + Raises: + ValueError: If the sample table has a column ``profile_id``. + """ + if PROFILE_ID in samples: + raise ValueError( + f"the sample table has a column {PROFILE_ID!r}, the name {what} give the " + "ids of their profiles; rename it" + ) + return Table({PROFILE_ID: samples["sample_id"], **samples.to_dict()}) + + def _gene_id_list(values: Any, n: int, name: str) -> list[str | None]: """One gene id per gene, None where missing. @@ -198,7 +226,7 @@ def _gene_id_list(values: Any, n: int, name: str) -> list[str | None]: def _cube_values( cube: Cube, - gene_ids: Any, + id_column: Any, group: Any, values: Any, min_cells: Any, @@ -250,33 +278,32 @@ def _cube_values( labels = cube.samples[filter_settings[SAMPLE_GROUPS]][rows] else: labels = np.zeros(rows.size) - genes = filter_by_expression(kept, group=labels, **numbers) + genes = filter_by_expression(kept, sample_groups=labels, **numbers) if not genes.any(): raise ValueError( f"no gene of group {group!r} passes filter_by_expression; pass " "expression_filter=False to score every gene" ) kept = kept[:, genes] - if kind == "log_cpm": - matrix = log_cpm(kept, norm_factors=tmm_factors(kept), prior_count=PRIOR_COUNT) + if kind == "log2_cpm": + matrix = log2_cpm(kept, norm_factors=tmm_factors(kept), prior_count=PRIOR_COUNT) integer = False else: matrix = np.asarray(kept, dtype=np.float64) integer = kept.dtype.kind in "iu" - if gene_ids is None: - gene_ids = "feature_id" - if isinstance(gene_ids, str): - features = cube.features - if gene_ids not in features: - raise KeyError( - f"the cube's features table has no column {gene_ids!r}; its columns " - f"are {quoted(list(features.columns))}" - ) - label = f"the features column {gene_ids!r}" - ids = _gene_id_list(features[gene_ids], cube.n_features, label) - else: - label = "gene_ids" - ids = _gene_id_list(gene_ids, cube.n_features, label) + if not isinstance(id_column, str): + raise TypeError( + "id_column must name a column of the cube's features table (str), got " + f"{type(id_column).__name__}" + ) + features = cube.features + if id_column not in features: + raise KeyError( + f"the cube's features table has no column {id_column!r}; its columns " + f"are {quoted(list(features.columns))}" + ) + label = f"the features column {id_column!r}" + ids = _gene_id_list(features[id_column], cube.n_features, label) ids = [ids[j] for j in np.flatnonzero(genes).tolist()] _check_unique(ids, label, matched_to) settings = { diff --git a/cytepool/stats/_gene_sets.py b/cytepool/_gene_sets.py similarity index 69% rename from cytepool/stats/_gene_sets.py rename to cytepool/_gene_sets.py index bf6ad1b..404d151 100644 --- a/cytepool/stats/_gene_sets.py +++ b/cytepool/_gene_sets.py @@ -22,14 +22,16 @@ import math import numbers from collections.abc import Mapping, Set -from typing import Any +from typing import Any, NamedTuple import numpy as np import numpy.typing as npt +from cytepool._analysis_records import only_key from cytepool._jit import jit from cytepool._linear_algebra import RANK_TOLERANCE, householder_qr from cytepool._masked_arrays import real_array, reject_masked, unmasked_array +from cytepool._options import checked_flag from cytepool.design._contrast_weights import is_named_contrasts, weights_on_columns from cytepool.design._design_matrices import ( design_matrix, @@ -37,6 +39,7 @@ require_full_rank, ) from cytepool.design.design import Design +from cytepool.differential_expression import DifferentialExpressionResult from cytepool.stats.voom import VoomResult FloatArray = npt.NDArray[np.float64] @@ -47,16 +50,168 @@ _ZERO_PIVOT = 1e-12 +class GeneSetData(NamedTuple): + """What a gene-set test reads from its data: an array, a VoomResult or a + differential-expression result.""" + + values: FloatArray # samples x genes + precision: FloatArray | None # None, (n_samples,) or (n_samples, n_genes) + design: Any # a design matrix, a Design, or None when not given + contrast: Any # as contrast_design takes it, or None when not given + ids: Any # one id per gene, or None to name genes by position + ids_name: str # how messages name the ids + trend: bool | None # None: not given, and no result to take it from + robust: bool | None + + +def gene_set_data( + data: Any, + *, + group: Any, + contrast: Any, + design: Any, + id_column: Any, + feature_ids: Any, + weights: Any, + trend: Any, + robust: Any, + function: str, +) -> GeneSetData: + """The values, weights, design, contrast and gene ids that a gene-set test of + ``data`` reads, and its trend and robust settings. + + A differential-expression result of limma-voom or limma-trend gives its group's + fit (``result.fit(group)``): the values and weights fitted, the design rows of + the samples fitted on the columns kept, the contrast's weights on them, the ids + of its tested features from the column ``id_column``, and, where ``trend`` and + ``robust`` are None, the settings of its moderation. An array or a VoomResult + gives its values (and a VoomResult its weights), and ``design``, ``contrast`` and + ``feature_ids`` are as given. + """ + with_trend = ( + None if trend is None else checked_flag(trend, "trend", expected="a bool") + ) + with_robust = ( + None if robust is None else checked_flag(robust, "robust", expected="a bool") + ) + if isinstance(data, DifferentialExpressionResult): + for name, value, owner in ( + ("design", design, "the design of its group's fit"), + ("feature_ids", feature_ids, "the ids of the column id_column"), + ("weights", weights, "the weights of its group's fit"), + ): + if value is not None: + reject_masked(value, name) + # romer takes sample weights only, so no VoomResult. + inputs = ( + "an array" if function == "romer" else "an array or a VoomResult" + ) + raise ValueError( + f"{name} applies to {inputs}; a differential-expression result " + f"gives {owner}" + ) + return _result_data( + data, group, contrast, id_column, function, with_trend, with_robust + ) + if group is not None: + reject_masked(group, "group") + raise ValueError( + "group names a group of a differential-expression result, and data is a " + f"{type(data).__name__}" + ) + if not (isinstance(id_column, str) and id_column == "feature_id"): + reject_masked(id_column, "id_column") + raise ValueError( + "id_column names a column of the features of a differential-expression " + f"result, and data is a {type(data).__name__}; name its genes with " + "feature_ids" + ) + values, precision = expression_values(data, weights) + return GeneSetData( + values, precision, design, contrast, feature_ids, "feature_ids", with_trend, + with_robust, + ) # fmt: skip + + +def _result_data( + result: DifferentialExpressionResult, + group: Any, + contrast: Any, + id_column: Any, + function: str, + trend: bool | None, + robust: bool | None, +) -> GeneSetData: + """The fit of a result's group that a gene-set test reads.""" + if result.method == "dream_voom": + raise ValueError( + f"{function} tests the contrast and residual effects of a linear model, " + "and this result is from dream_voom, which fits a mixed model to every " + "gene; test a result of limma_voom or limma_trend, such as limma_voom with " + "the donor as a fixed effect" + ) + if not isinstance(id_column, str): + reject_masked(id_column, "id_column") + raise TypeError( + "id_column must name a column of the result's features (str), got " + f"{type(id_column).__name__}" + ) + fit = result.fit(group) + name = only_key(contrast, list(result.contrasts.names), "contrast", "contrasts") + if not isinstance(name, str): + reject_masked(name, "contrast") + raise TypeError( + f"contrast must be a contrast name of the result (str), got " + f"{type(name).__name__}" + ) + linear = fit.moderated.fit + if name not in linear.coefficient_names: + result.table(fit.group, name) # raises KeyError, saying why + column = linear.coefficient_names.index(name) + assert linear.contrasts is not None + weights_on_kept = np.array(linear.contrasts[:, column], dtype=np.float64) + features = fit.features + if id_column not in features: + raise KeyError( + f"the result's features have no column {id_column!r}; their columns are " + f"{', '.join(map(repr, features.columns))}" + ) + ids = features[id_column].tolist() + return GeneSetData( + values=np.asarray(fit.log2_cpm, dtype=np.float64), + precision=None + if fit.weights is None + else np.ascontiguousarray(fit.weights, dtype=np.float64), + design=np.asarray(linear.design, dtype=np.float64), + contrast=weights_on_kept, + ids=ids, + ids_name=f"the column {id_column!r} of the result's features", + trend=result.method == "limma_trend" if trend is None else trend, + robust=bool(result.settings["robust"]) if robust is None else robust, + ) + + def contrast_design( values: FloatArray, design: Any, contrast: Any, function: str ) -> tuple[FloatArray, int]: """The design, checked, with the contrast as its last coefficient, and d.""" + if design is None: + raise ValueError( + f"{function} of an array or a VoomResult needs the design of its samples: " + "give design, such as the Design the values were fitted with" + ) + if contrast is None: + raise ValueError( + f"{function} of an array or a VoomResult needs the contrast to test: give " + "contrast, a contrast name of the design, a column, or weights on its " + "columns" + ) refuse_random_effects(design, function) x, names = design_matrix(design, None) n_samples, n_coefficients = x.shape if n_samples != values.shape[0]: raise ValueError( - f"y has {values.shape[0]} rows but the design has {n_samples}; rows " + f"data has {values.shape[0]} rows but the design has {n_samples}; rows " "of both are samples" ) require_full_rank(x, names) @@ -306,20 +461,20 @@ def expression_values(y: Any, weights: Any) -> tuple[FloatArray, FloatArray | No "weights cannot be given with a VoomResult, which carries the " "precision weights of its log-CPM values" ) - return np.asarray(y.log_cpm, dtype=np.float64), np.asarray( + return np.asarray(y.log2_cpm, dtype=np.float64), np.asarray( y.weights, dtype=np.float64 ) - values = real_array(unmasked_array(y, "y"), "y", boolean=False, objects=False) + values = real_array(unmasked_array(y, "data"), "data", boolean=False, objects=False) if values.ndim != 2: raise ValueError( - "y must be a 2-D array of shape (n_samples, n_genes), or a VoomResult, got " - f"shape {values.shape}" + "data must be a 2-D array of shape (n_samples, n_genes), a VoomResult or a " + f"DifferentialExpressionResult, got shape {values.shape}" ) if values.shape[1] == 0: - raise ValueError("y must have at least one gene (column)") + raise ValueError("data must have at least one gene (column)") if not np.all(np.isfinite(values)): raise ValueError( - "y must be finite: gene-set tests fit every gene to every sample, so " + "data must be finite: gene-set tests fit every gene to every sample, so " "values cannot be missing (NaN) or infinite" ) if weights is None: @@ -345,9 +500,10 @@ def expression_values(y: Any, weights: Any) -> tuple[FloatArray, FloatArray | No def checked_gene_sets( - gene_sets: Any, gene_ids: Any, n_genes: int + gene_sets: Any, gene_ids: Any, n_genes: int, ids_name: str = "feature_ids" ) -> dict[Any, int] | None: - """Check that ``gene_sets`` is a non-empty mapping; the index of ``gene_ids``.""" + """Check that ``gene_sets`` is a non-empty mapping; the index of ``gene_ids``, + which messages name ``ids_name``.""" reject_masked(gene_sets, "gene_sets") if not isinstance(gene_sets, Mapping): raise TypeError( @@ -356,7 +512,7 @@ def checked_gene_sets( ) if not gene_sets: raise ValueError("gene_sets is empty; give at least one gene set") - return None if gene_ids is None else _gene_index(gene_ids, n_genes) + return None if gene_ids is None else _gene_index(gene_ids, n_genes, ids_name) def set_label(name: Any) -> str: @@ -411,26 +567,29 @@ def id_positions(ids: list[Any], label: str, index: dict[Any, int]) -> IntArray: return np.array(found, dtype=np.intp) -def check_matched(index: dict[Any, int] | None, matched: int) -> None: +def check_matched( + index: dict[Any, int] | None, matched: int, ids_name: str = "feature_ids" +) -> None: if index is not None and matched == 0: raise ValueError( - "no gene of any set is among gene_ids; the sets and gene_ids must name " - "genes the same way (for example both by symbol, or both by Ensembl id)" + f"no gene of any set is among {ids_name}; the sets and {ids_name} must " + "name genes the same way (for example both by symbol, or both by Ensembl " + "id)" ) -def _gene_index(gene_ids: Any, n_genes: int) -> dict[Any, int]: - ids = unmasked_array(gene_ids, "gene_ids") +def _gene_index(gene_ids: Any, n_genes: int, ids_name: str) -> dict[Any, int]: + ids = unmasked_array(gene_ids, ids_name) if ids.shape != (n_genes,): raise ValueError( - f"gene_ids must have one id per gene ({n_genes}), got shape {ids.shape}" + f"{ids_name} must have one id per gene ({n_genes}), got shape {ids.shape}" ) index: dict[Any, int] = {} try: for position, gene in enumerate(ids.tolist()): if gene in index: raise ValueError( - f"gene_ids must be unique, but {gene!r} names genes " + f"{ids_name} must be unique, but {gene!r} names genes " f"{index[gene]} and {position}" ) index[gene] = position @@ -444,8 +603,8 @@ def gene_positions(array: npt.NDArray[Any], label: str, n_genes: int) -> IntArra return np.empty(0, dtype=np.intp) if array.dtype.kind not in "iu": raise TypeError( - f"{label} must hold 0-based gene positions (integers) when gene_ids is not " - f"given; give gene_ids to name genes by id (got dtype {array.dtype})" + f"{label} must hold 0-based gene positions (integers) when feature_ids is " + f"not given; give feature_ids to name genes by id (got dtype {array.dtype})" ) outside = (array < 0) | (array >= n_genes) if np.any(outside): diff --git a/cytepool/_group_fits.py b/cytepool/_group_fits.py index 997f13e..3611819 100644 --- a/cytepool/_group_fits.py +++ b/cytepool/_group_fits.py @@ -223,7 +223,9 @@ def prepare_group( if name != SAMPLE_GROUPS } labels = design.samples[filter_settings[SAMPLE_GROUPS]] - keep = filter_by_expression(y, group=labels[inputs.model_rows[used]], **numbers) + keep = filter_by_expression( + y, sample_groups=labels[inputs.model_rows[used]], **numbers + ) else: keep = filter_by_expression(y, design=x_kept, **filter_settings) fit.tested = np.flatnonzero(keep) diff --git a/cytepool/_ranked_genes.py b/cytepool/_ranked_genes.py index 4d6557d..d4c7dff 100644 --- a/cytepool/_ranked_genes.py +++ b/cytepool/_ranked_genes.py @@ -3,9 +3,9 @@ Gene set enrichment analysis, its figures and over-representation analysis read their genes the same way: from a group and contrast of a -:class:`~cytepool.differential_expression.DifferentialExpressionResult`, from a table -of results, or, for a ranking, from a mapping of gene id to statistic, and their gene -sets from a :class:`~cytepool.gene_sets.GeneSets` or a mapping of set name to genes. +:class:`~cytepool.DifferentialExpressionResult`, from a table of results, or, for a +ranking, from a mapping of gene id to statistic, and their gene sets from a +:class:`~cytepool.GeneSets` or a mapping of set name to genes. The ranking: Genes are ranked by decreasing statistic. Genes with equal statistics keep the diff --git a/cytepool/_study_statistics.py b/cytepool/_study_statistics.py index 641b881..699176d 100644 --- a/cytepool/_study_statistics.py +++ b/cytepool/_study_statistics.py @@ -1,15 +1,16 @@ """The results of the studies of a collection for one group and contrast, as arrays, -and the number of studies whose ranks SumRank sums. +and the factor and levels that a contrast compares in the studies' designs. SumRank and held-out prediction read, from every study's differential expression of one group and contrast, a signed statistic of every gene of the collection, with the log2 fold changes and standard errors of a meta-analysis: one row per study and one column per gene, NaN where the study did not test the gene. They are read from -:func:`~cytepool.meta_analysis.align_study_results`, which checks that each result +:meth:`StudyResults.table `, which checks that each result analysed its study's aligned cube and says why a study has no result for a gene. The signed statistic is -log10 of the p-value, negative where the log2 fold change is -negative, as Nakatsuka et al. (2025) rank DESeq2's results, and a proportion p of k -studies sums floor(p k) ranks, as their code does. +negative, as Nakatsuka et al. (2025) rank DESeq2's results. Permutations of SumRank +permute the factor that the contrast compares, and held-out prediction tells its cases +from its controls by the two levels it compares, when the studies' designs say so. References: Nakatsuka N, Adler D, Jiang L, Hartman A, Cheng E, Klann E, Satija R (2025). @@ -18,8 +19,7 @@ Nature Communications 16, 7436. doi:10.1038/s41467-025-62579-z """ -import math -from collections.abc import Mapping +from collections.abc import Sequence from dataclasses import dataclass from typing import Any @@ -27,10 +27,8 @@ import numpy.typing as npt from ._masked_arrays import reject_masked -from ._options import real_number -from .cube import CubeCollection -from .differential_expression import DifferentialExpressionResult -from .meta_analysis import align_study_results +from .design import Design +from .study_results import StudyResults FloatArray = npt.NDArray[np.float64] BoolArray = npt.NDArray[np.bool_] @@ -79,20 +77,17 @@ def signed_statistics( def study_statistics( - collection: CubeCollection, - results: Mapping[str, DifferentialExpressionResult], - group: str, - contrast: str, + studies: StudyResults, group: str, contrast: str ) -> StudyStatistics: """Every study's signed statistics, log2 fold changes and standard errors of one group and contrast, with which studies analysed them and why the others did not. Raises: - TypeError, ValueError, KeyError: As - :func:`~cytepool.meta_analysis.align_study_results` raises them, and - ValueError if ``contrast`` is an F-test, which has no direction. + TypeError, ValueError, KeyError: As :meth:`StudyResults.table` raises them, + and ValueError if ``contrast`` is an F-test, which has no direction. """ - table = align_study_results(collection, results, group, contrast) + collection = studies.collection + table = studies.table(group, contrast) shape = (collection.n_studies, collection.n_features) if "log2_fold_change" not in table: raise ValueError( @@ -143,19 +138,86 @@ def column(name: str) -> FloatArray: ) -def studies_summed(proportion: Any, n_studies: int) -> int: - """The number of ranks SumRank sums for each gene, m = floor(p k), at least 1; the - product is rounded to 9 decimals first, so that 0.57 of 100 studies is 57. +def study_results(value: Any, function: str) -> StudyResults: + """The studies' results that a function combining studies takes, checked. Raises: - TypeError: If ``proportion`` is not a real number, or is a numpy masked array. - ValueError: If it is not in (0, 1]. + TypeError: If ``value`` is not a ``StudyResults``. """ - reject_masked(proportion, "proportion_of_studies") - p = real_number(proportion, "proportion_of_studies", finite=True) - if not 0.0 < p <= 1.0: - raise ValueError( - f"proportion_of_studies must be in (0, 1], such as 1 to sum every study, " - f"got {p}" + if not isinstance(value, StudyResults): + reject_masked(value, "studies") + raise TypeError( + f"{function} takes the studies' results with their collection, a " + "StudyResults such as analyse_each_study gives, or " + f"StudyResults(collection, results); got {type(value).__name__}" + ) + return value + + +def contrast_levels( + studies: StudyResults, contrast: str, names: Sequence[str] +) -> tuple[str, str, str] | None: + """The factor and the two levels that a contrast compares in the designs of the + studies named, as ``(factor, case, control)``; None unless it compares the same + two levels of one factor in every one of them. + + A contrast compares two levels of a factor when its weights fall on the columns of + that factor alone, which enters the design as a main effect only: one column with + a positive weight, a level against the reference level, which has no column of its + own (``conditioncase`` in ``~ condition``), or two columns with weights of equal + size and opposite sign, one level minus another (``conditionB - conditionA`` in + ``~ 0 + condition``). A negative weight on one column compares the reference + against the level. A study's result without its design (built by hand) gives + None. + """ + found = set() + for name in names: + result = studies[name] + design = result.design + if design is None or contrast not in result.contrasts.names: + return None + compared = _compared_levels(design, result.contrasts[contrast]) + if compared is None: + return None + found.add(compared) + if len(found) != 1: + return None + return found.pop() + + +def _compared_levels( + design: Design, weights: FloatArray +) -> tuple[str, str, str] | None: + """The factor, case level and control level that contrast weights over a design's + columns compare, or None; see :func:`contrast_levels`.""" + coding = design.model_matrix.coding + nonzero = [ + j for j, weight in enumerate(np.asarray(weights).tolist()) if weight != 0 + ] + if not nonzero or len(nonzero) > 2: + return None + factors = set() + for j in nonzero: + parts = coding[j] + if len(parts) != 1 or parts[0][1] is None: + return None # the intercept, a covariate or an interaction + factors.add(parts[0][0]) + if len(factors) != 1: + return None + factor = factors.pop() + if any(len(parts) > 1 and any(v == factor for v, _ in parts) for parts in coding): + return None # the factor interacts with another variable + levels = design.levels[factor] + own = {parts[0][1] for parts in coding if len(parts) == 1 and parts[0][0] == factor} + w = [float(weights[j]) for j in nonzero] + named = [str(coding[j][0][1]) for j in nonzero] + if len(nonzero) == 1: + reference = str(levels[0]) + if reference in own: + return None # a level's mean, in a design that has a column per level + return ( + (factor, named[0], reference) if w[0] > 0 else (factor, reference, named[0]) ) - return max(1, math.floor(round(p * n_studies, 9))) + if w[0] != -w[1]: + return None + return (factor, named[0], named[1]) if w[0] > 0 else (factor, named[1], named[0]) diff --git a/cytepool/activity.py b/cytepool/activity.py index b9b259d..24f4b44 100644 --- a/cytepool/activity.py +++ b/cytepool/activity.py @@ -2,29 +2,27 @@ their target genes, as decoupler computes them: the univariate and multivariate linear models (ULM, MLM) and the weighted aggregate. -A network (:class:`~cytepool.networks.Network`), such as CollecTRI's regulons or -PROGENy's pathway signatures (:mod:`cytepool.networks`), gives each source, a -transcription factor or a pathway, a signed weight on each of its target genes. A -source is active in a profile, the expression of a sample or the statistics of a -contrast, when its targets are high where their weights are positive and low where +A network (:class:`~cytepool.Network`), such as CollecTRI's regulons or PROGENy's +pathway signatures (:func:`~cytepool.collectri`, :func:`~cytepool.progeny`), gives each +source, a transcription factor or a pathway, a signed weight on each of its target +genes. A source is active in a profile, the expression of a sample or the statistics of +a contrast, when its targets are high where their weights are positive and low where they are negative. Each function scores every source in every profile, as decoupler 2.2.0's ``mt.ulm``, ``mt.mlm`` and ``mt.waggr`` do (Badia-i-Mompel et al. 2022), and -returns an :class:`Activities`, profiles x sources, which the linear models take as -they take pathway scores, and which :func:`~cytepool.plotting.plot_activities` draws -as a heatmap and :func:`~cytepool.plotting.plot_top_activities` as bars. +returns an :class:`~cytepool.Activities`, profiles x sources, which the linear models +take as they take pathway scores, and which :func:`~cytepool.plotting.plot_activities` +draws as a heatmap and :func:`~cytepool.plotting.plot_top_activities` as bars. The profiles: - A samples x genes array, one profile per row, with one gene id per column. - - A group (cell type) of a :class:`~cytepool.cube.Cube`: its samples with at + - A group (cell type) of a :class:`~cytepool.Cube`: its samples with at least ``min_cells`` cells and some counts, over the genes that edgeR's ``filterByExpr`` keeps, as log2 counts per million after TMM normalization, - the values that :func:`~cytepool.pathway_scores.gsva` scores. - - The statistics of a - :class:`~cytepool.differential_expression.DifferentialExpressionResult`: one - profile for every group and contrast tested, by default the moderated - t-statistics, as decoupler's guides score the statistics of a contrast; or of a - table of results, a pandas Series or a mapping of gene id to statistic, one - profile. + the values that :func:`~cytepool.gsva` scores. + - The statistics of a :class:`~cytepool.DifferentialExpressionResult`: one profile + for every group and contrast tested, by default the moderated t-statistics, as + decoupler's guides score the statistics of a contrast; or of a table of results, a + pandas Series or a mapping of gene id to statistic, one profile. Each profile's genes are those with an id and a value: NaN marks a missing value, and a gene that a group did not test has none in that group's profiles. Genes @@ -38,7 +36,7 @@ as decoupler's ``tmin`` leaves it out; ``n_targets`` gives each source's targets among each profile's genes. -ULM (:func:`ulm`): +ULM (:func:`~cytepool.ulm`): For each source and profile, a linear model of the profile's n genes on the source's weights, zero for the genes that are not its targets: @@ -49,7 +47,7 @@ x, and its p-value is two-sided, from Student's t distribution on n - 2 degrees of freedom. ``estimates`` are the slopes b_1. -MLM (:func:`mlm`): +MLM (:func:`~cytepool.mlm`): One linear model of each profile's genes on the weights of all its sources at once, with an intercept, y_g = b_0 + sum_s b_s x_gs + e_g, so that each source's score, the t-statistic of b_s on n - m - 1 degrees of freedom for m sources, is @@ -58,7 +56,7 @@ of the weights of other sources, such as two sources with the same targets and weights, cannot be told apart, and are refused: the message names them. -Weighted aggregate (:func:`weighted_aggregate`): +Weighted aggregate (:func:`~cytepool.weighted_aggregate`): The weighted mean of the values of a source's targets, sum_g w_g y_g / sum_g |w_g|, or their weighted sum, sum_g w_g y_g, the score of the original PROGENy method (Schubert et al. 2018). With ``n_permutations`` random @@ -71,12 +69,12 @@ and Smyth 2010). ``estimates`` keep the weighted means or sums. Adjusted p-values: - ``adjusted_p_values`` are Benjamini and Hochberg's (1995) over the sources of each + ``adjusted_p_value`` are Benjamini and Hochberg's (1995) over the sources of each profile, as decoupler adjusts them. Deliberate differences from decoupler 2.2.0: - decoupler reports MLM's p-values without adjusting them, under the name of its - adjusted p-values; here they are ``p_values``, and ``adjusted_p_values`` are + adjusted p-values; here they are ``p_value``, and ``adjusted_p_value`` are adjusted as for the other methods. - decoupler keeps adjusted p-values in single precision, and computes MLM's p-values as one minus the distribution function, which gives 0 below about @@ -134,7 +132,7 @@ restore_provenance, ) from ._columns import is_data_frame, text_column -from ._expression_values import MIN_CELLS, expression_values +from ._expression_values import MIN_CELLS, expression_values, profile_table from ._jit import by_chunks, jit, thread_limit from ._linear_algebra import RANK_TOLERANCE from ._masked_arrays import read_only, reject_masked @@ -181,9 +179,9 @@ class Activities: source has fewer than ``min_targets`` targets among a profile's genes. estimates: The slopes (ULM), coefficients (MLM) or weighted means or sums (weighted aggregate). - p_values: Two-sided p-values: of the t-tests (ULM, MLM), or of the + p_value: Two-sided p-values: of the t-tests (ULM, MLM), or of the permutations (weighted aggregate; NaN without them). - adjusted_p_values: Benjamini and Hochberg's, over the sources of each + adjusted_p_value: Benjamini and Hochberg's, over the sources of each profile. n_targets: ``(n_profiles, n_sources)`` int64: the targets of each source among each profile's genes. @@ -198,8 +196,8 @@ class Activities: scores: FloatArray estimates: FloatArray - p_values: FloatArray - adjusted_p_values: FloatArray + p_value: FloatArray + adjusted_p_value: FloatArray n_targets: IntArray profiles: Table network: Network @@ -214,8 +212,8 @@ def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: ( self.scores, self.estimates, - self.p_values, - self.adjusted_p_values, + self.p_value, + self.adjusted_p_value, self.n_targets, self.profiles, self.network, @@ -238,7 +236,7 @@ def provenance(self) -> dict[str, Any] | None: provenance (``"cube"``), the layer read and the settings; None for activities of values given as an array, or of statistics, whose result keeps its own record. The cube's record costs what it costs for - :func:`~cytepool.differential_expression.limma_voom` ("Provenance").""" + :func:`~cytepool.limma_voom` ("Provenance").""" return provenance_of(self) def table(self) -> Table: @@ -259,8 +257,8 @@ def table(self) -> Table: "n_targets": self.n_targets.T.ravel().copy(), "score": self.scores.T.ravel().copy(), "estimate": self.estimates.T.ravel().copy(), - "p_value": self.p_values.T.ravel().copy(), - "adjusted_p_value": self.adjusted_p_values.T.ravel().copy(), + "p_value": self.p_value.T.ravel().copy(), + "adjusted_p_value": self.adjusted_p_value.T.ravel().copy(), } ) @@ -275,8 +273,8 @@ def __repr__(self) -> str: def _restored_activities( scores: FloatArray, estimates: FloatArray, - p_values: FloatArray, - adjusted_p_values: FloatArray, + p_value: FloatArray, + adjusted_p_value: FloatArray, n_targets: IntArray, profiles: Table, network: Network, @@ -288,8 +286,8 @@ def _restored_activities( restored = Activities( read_only(np.asarray(scores)), read_only(np.asarray(estimates)), - read_only(np.asarray(p_values)), - read_only(np.asarray(adjusted_p_values)), + read_only(np.asarray(p_value)), + read_only(np.asarray(adjusted_p_value)), read_only(np.asarray(n_targets)), profiles, network, @@ -323,7 +321,7 @@ def ulm( data: Any, network: Any, *, - gene_ids: Any = None, + feature_ids: Any = None, sample_ids: Any = None, group: str | None = None, contrast: str | None = None, @@ -346,22 +344,18 @@ def ulm( Args: data: ``(n_profiles, n_genes)`` values, such as log-expression values of samples or statistics of contrasts, NaN where missing; a - :class:`~cytepool.cube.Cube`, whose ``group`` is scored; a - :class:`~cytepool.differential_expression.DifferentialExpressionResult`, - whose statistics of every group and contrast are scored; or a table of - results (a ``Table`` or pandas DataFrame), a pandas Series indexed by - gene id or a mapping of gene id to statistic, one profile. A samples x - genes DataFrame, as decoupler takes it, is given as - ``frame.to_numpy()`` with ``gene_ids`` and ``sample_ids``. - network: A :class:`~cytepool.networks.Network`, such as - :func:`~cytepool.networks.collectri` or - :func:`~cytepool.networks.progeny` returns, or what ``Network`` takes: a - table with the columns ``source``, ``target`` and ``weight``, or a - mapping of source to targets. - gene_ids: For an array, the genes' ids, one per column (None, NaN or an - empty string for a gene without one); for a cube, the name of a column of - its features table, ``"feature_id"`` by default or ``"symbol"``, or one - id per feature. + :class:`~cytepool.Cube`, whose ``group`` is scored; a + :class:`~cytepool.DifferentialExpressionResult`, whose statistics of every + group and contrast are scored; or a table of results (a ``Table`` or pandas + DataFrame), a pandas Series indexed by gene id or a mapping of gene id to + statistic, one profile. A samples x genes DataFrame, as decoupler takes it, + is given as ``frame.to_numpy()`` with ``feature_ids`` and ``sample_ids``. + network: A :class:`~cytepool.Network`, such as + :func:`~cytepool.collectri` or :func:`~cytepool.progeny` returns, or what + ``Network`` takes: a table with the columns ``source``, ``target`` and + ``weight``, or a mapping of source to targets. + feature_ids: For an array, the genes' ids, one per column (None, NaN or an + empty string for a gene without one). sample_ids: For an array, one id per row; by default ``"1"``, ``"2"``, ... group: For a cube, the group (cell type) to score; it may be left out when the cube has one. For a differential-expression result, the group whose @@ -370,15 +364,16 @@ def ulm( statistics are scored; by default every contrast. min_cells: For a cube, the fewest cells a sample needs in the group to be scored, at least 0, as for - :func:`~cytepool.differential_expression.limma_voom`. + :func:`~cytepool.limma_voom`. expression_filter: For a cube, whether to score only the genes that :func:`~cytepool.normalization.filter_by_expression` keeps, with the samples as one group; or a mapping of its settings, which may name a column of the cube's samples whose values group them, such as ``{"sample_groups": "condition"}``, as - :func:`~cytepool.differential_expression.limma_voom` takes it. - id_column: For a differential-expression result or a table, the column of - gene ids, such as ``"feature_id"`` or ``"symbol"``. + :func:`~cytepool.limma_voom` takes it. + id_column: For a cube, a differential-expression result or a table, the + column of gene ids (of the cube's features table), such as + ``"feature_id"`` or ``"symbol"``. statistic_column: For a differential-expression result or a table, the column of statistics, such as ``"t"`` or ``"log2_fold_change"``. min_targets: The fewest targets among a profile's genes for a source to be @@ -408,13 +403,13 @@ def ulm( >>> y = rng.normal(size=(2, 40)) >>> y[1, :6] += 3.0 # the targets of A are up in the second sample >>> network = Network({"A": genes[:6], "B": genes[10:16]}) - >>> ulm(y, network, gene_ids=genes).scores.round(2).tolist() + >>> ulm(y, network, feature_ids=genes).scores.round(2).tolist() [[0.71, -0.1], [7.94, -0.85]] """ profiles, graph, edges, smallest = _prepared( data, network, - gene_ids, + feature_ids, sample_ids, group, contrast, @@ -456,7 +451,7 @@ def mlm( data: Any, network: Any, *, - gene_ids: Any = None, + feature_ids: Any = None, sample_ids: Any = None, group: str | None = None, contrast: str | None = None, @@ -479,7 +474,7 @@ def mlm( Args: data: The profiles, as for :func:`ulm`. network: The network, as for :func:`ulm`. - gene_ids: For an array or a cube, as for :func:`ulm`. + feature_ids: For an array, as for :func:`ulm`. sample_ids: For an array, as for :func:`ulm`. group: For a cube or a differential-expression result, as for :func:`ulm`. contrast: For a differential-expression result, as for :func:`ulm`. @@ -511,7 +506,7 @@ def mlm( profiles, graph, edges, smallest = _prepared( data, network, - gene_ids, + feature_ids, sample_ids, group, contrast, @@ -558,7 +553,7 @@ def weighted_aggregate( statistic: str = "mean", n_permutations: int = _N_PERMUTATIONS, rng: np.random.Generator | int | None = None, - gene_ids: Any = None, + feature_ids: Any = None, sample_ids: Any = None, group: str | None = None, contrast: str | None = None, @@ -589,7 +584,7 @@ def weighted_aggregate( rng: A ``numpy.random.Generator`` to draw the permutations from, an integer seed for one (decoupler's ``seed``, 42 by default there), or None for a fresh, unseeded generator. - gene_ids: For an array or a cube, as for :func:`ulm`. + feature_ids: For an array, as for :func:`ulm`. sample_ids: For an array, as for :func:`ulm`. group: For a cube or a differential-expression result, as for :func:`ulm`. contrast: For a differential-expression result, as for :func:`ulm`. @@ -630,7 +625,7 @@ def weighted_aggregate( profiles, graph, edges, smallest = _prepared( data, network, - gene_ids, + feature_ids, sample_ids, group, contrast, @@ -679,7 +674,7 @@ def weighted_aggregate( def _prepared( data: Any, network: Any, - gene_ids: Any, + feature_ids: Any, sample_ids: Any, group: Any, contrast: Any, @@ -696,7 +691,7 @@ def _prepared( graph = network if isinstance(network, Network) else _network(network) profiles = _profiles( data, - gene_ids, + feature_ids, sample_ids, group, contrast, @@ -770,7 +765,7 @@ def _matched_edges( def _profiles( data: Any, - gene_ids: Any, + feature_ids: Any, sample_ids: Any, group: Any, contrast: Any, @@ -787,7 +782,7 @@ def _profiles( ) if statistics: for name, value, default in ( - ("gene_ids", gene_ids, gene_ids is None), + ("feature_ids", feature_ids, feature_ids is None), ("sample_ids", sample_ids, sample_ids is None), ("min_cells", min_cells, _is_default_cells(min_cells)), ("expression_filter", expression_filter, expression_filter is True), @@ -795,7 +790,7 @@ def _profiles( if not default: reject_masked(value, name) hint = "" - if name == "gene_ids": + if name == "feature_ids": hint = "; name the column of gene ids with id_column" raise ValueError( f"{name} applies to an array or a cube, and data is a " @@ -804,7 +799,6 @@ def _profiles( return _statistics(data, group, contrast, id_column, statistic_column) for name, given, expected in ( ("contrast", contrast, None), - ("id_column", id_column, "feature_id"), ("statistic_column", statistic_column, "t"), ): if expected is None: @@ -819,10 +813,11 @@ def _profiles( ) expression = expression_values( data, - gene_ids, + id_column, + feature_ids, sample_ids, group, - "log_cpm", + "log2_cpm", min_cells, expression_filter, matched_to="the network's targets", @@ -831,14 +826,7 @@ def _profiles( "gene id to statistic" ), ) - samples = expression.samples - if _PROFILE_ID in samples: - raise ValueError( - f"the sample table has a column {_PROFILE_ID!r}, the name the activities " - "give the ids of their profiles; rename it" - ) - ids = samples["sample_id"] - table = Table({_PROFILE_ID: ids, **samples.to_dict()}) + table = profile_table(expression.samples, "the activities") return _Profiles( expression.values, expression.gene_ids, @@ -918,8 +906,8 @@ def _statistics( f"data is read as a table of results, with a column of gene ids " f"({id_column!r}) and one of statistics ({statistic_column!r}), and " "it has neither. To score a samples x genes table, give its values " - "with their ids: data=frame.to_numpy(), gene_ids=list(frame.columns), " - "sample_ids=list(frame.index)" + "with their ids: data=frame.to_numpy(), " + "feature_ids=list(frame.columns), sample_ids=list(frame.index)" ) ranked.append( ranked_genes( diff --git a/cytepool/composition.py b/cytepool/composition.py index 314a87b..6aaffdf 100644 --- a/cytepool/composition.py +++ b/cytepool/composition.py @@ -1,9 +1,9 @@ """Tests of differences in cell-type composition between conditions, by propeller. -:func:`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 +:func:`~cytepool.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: 1. Proportions. Sample j, with x_ij cells of group i and N_j cells in all, has the @@ -20,7 +20,7 @@ Bayes, robustly by default, as speckle calls limma's ``eBayes(robust = TRUE)`` (Smyth 2004; Phipson et al. 2016). 5. Tests. Each contrast is tested by its moderated t-statistic, as - ``propeller.ttest`` does. ``f_test`` names a factor whose levels are tested for + ``propeller.ttest`` does. ``f_tests`` names factors whose levels are tested for equal means by the moderated F-statistic, as ``propeller.anova`` does, and ``propeller`` with more than two groups. The p-values of a test are adjusted by the method of Benjamini and Hochberg over the groups (speckle's ``FDR``). @@ -175,25 +175,24 @@ class CompositionResult: - ``group_id``; - ``baseline_proportion``: the group's share of all the cells of the samples used (speckle's ``BaselineProp``); - - for a contrast that compares the levels of a factor (see - :mod:`cytepool.composition`), ``mean_proportion_`` for each level it - weighs, in level order: the mean proportion of the group over the samples at - that level (``PropMean``), and ``proportion_ratio``: the product of these - means raised to the weights of their levels, such as m_stim / m_ctrl - (``PropRatio``); + - for a contrast that compares the levels of a factor (see "Descriptive columns" in + the description of :func:`propeller`), ``mean_proportion_`` for each level + it weighs, in level order: the mean proportion of the group over the samples at + that level (``PropMean``), and ``proportion_ratio``: the product of these means + raised to the weights of their levels, such as m_stim / m_ctrl (``PropRatio``); - ``t`` and ``p_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``) has ``group_id``, ``baseline_proportion``, - ``mean_proportion_`` for every level of the factor, ``f_statistic``, - ``p_value`` and ``adjusted_p_value`` (speckle's ``Fstatistic``, ``P.Value`` and - ``FDR``). + The table of an F-test (``tables`` and ``table``, by the name of its factor) has + ``group_id``, ``baseline_proportion``, ``mean_proportion_`` for every level + of the factor, ``f_statistic``, ``p_value`` and ``adjusted_p_value`` (speckle's + ``Fstatistic``, ``P.Value`` and ``FDR``); ``f_tests`` names the F-tests. 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``; and ``prior_df``, the prior degrees of freedom of + when tested); ``df_residual``; and ``df_prior``, 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), ``used`` and @@ -205,16 +204,15 @@ class CompositionResult: Raises: TypeError: If ``contrasts`` is not a ``Contrasts``, ``tables`` not a mapping - of contrast name to ``Table``, ``f_test`` not a ``Table`` or None, or - ``summary``, ``samples`` or ``proportions`` not a ``Table``, or - ``provenance`` not a mapping that JSON can write. + of contrast or F-test name to ``Table``, ``summary``, ``samples`` or + ``proportions`` not a ``Table``, or ``provenance`` not a mapping that JSON + can write. ValueError: If ``provenance`` holds a NaN or an infinity. """ __slots__ = ( "_contrasts", "_tables", - "_f_test", "_summary", "_samples", "_proportions", @@ -223,7 +221,6 @@ class CompositionResult: _contrasts: Contrasts _tables: Mapping[str, Table] - _f_test: Table | None _summary: Table _samples: Table _proportions: Table @@ -233,7 +230,6 @@ def __init__( self, contrasts: Contrasts, tables: Mapping[str, Table], - f_test: Table | None, summary: Table, samples: Table, proportions: Table, @@ -245,19 +241,15 @@ def __init__( ) if not isinstance(tables, Mapping): raise TypeError( - "tables must be a mapping of contrast name to Table, got " + "tables must be a mapping of contrast or F-test name to Table, got " f"{type(tables).__name__}" ) for name, table in tables.items(): if not isinstance(name, str) or not isinstance(table, Table): raise TypeError( - "tables must map contrast names (str) to Tables, got " + "tables must map contrast and F-test names (str) to Tables, got " f"{type(name).__name__} {name!r} to {type(table).__name__}" ) - if f_test is not None and not isinstance(f_test, Table): - raise TypeError( - f"f_test must be a Table or None, got {type(f_test).__name__}" - ) for label, table in ( ("summary", summary), ("samples", samples), @@ -272,7 +264,6 @@ def __init__( state = { "_contrasts": contrasts, "_tables": MappingProxyType(dict(tables)), - "_f_test": f_test, "_summary": summary, "_samples": samples, "_proportions": proportions, @@ -294,7 +285,6 @@ def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: ( self._contrasts, dict(self._tables), - self._f_test, self._summary, self._samples, self._proportions, @@ -309,13 +299,15 @@ def contrasts(self) -> Contrasts: @property def tables(self) -> Mapping[str, Table]: - """The table of every contrast, by contrast name.""" + """The table of every contrast and F-test, by its name.""" return self._tables @property - def f_test(self) -> Table | None: - """The table of the F-test across the levels of ``f_test``, or None.""" - return self._f_test + def f_tests(self) -> dict[str, tuple[str, ...]]: + """The F-tests, each named by its factor, with the levels it tests at once, + as a new dict; empty without F-tests.""" + tests = json.loads(self._record)["f_tests"] + return {name: tuple(test["levels"]) for name, test in tests.items()} @property def summary(self) -> Table: @@ -360,25 +352,31 @@ def provenance(self) -> dict[str, Any]: return record def table(self, contrast: str | None = None) -> Table: - """Return the table of one contrast. + """Return the table of one contrast, or F-test. Args: - contrast: A contrast name; it may be left out when the result tests one. + contrast: A contrast name, or the name of an F-test (its factor); it may + be left out when the result tests one contrast and no F-test, or one + F-test alone. Raises: - ValueError: If ``contrast`` is left out and the result tests several, or - none (an F-test alone, which ``f_test`` holds). - KeyError: If the contrast was not tested; the message lists those that - were. + ValueError: If ``contrast`` is left out and the result tests several. + KeyError: If the contrast or F-test was not tested; the message lists + those that were. """ + f_tests = list(self.f_tests) contrast = only_key( - contrast, list(self._contrasts.names), "contrast", "contrasts" + contrast, + [*self._contrasts.names, *f_tests], + "contrast", + "contrasts and F-tests" if f_tests else "contrasts", ) found = self._tables.get(contrast) if found is None: raise KeyError( f"no contrast {contrast!r} was tested; contrasts are: " f"{listing(list(self._contrasts.names)) or '(none)'}" + + (f"; F-tests are: {listing(f_tests)}" if f_tests else "") ) return found @@ -393,9 +391,8 @@ def __repr__(self) -> str: ] if len(self._contrasts): lines.append(" t-tests: " + ", ".join(self._contrasts.names)) - if record["f_test"] is not None: - factor = record["f_test"]["factor"] - levels = record["f_test"]["levels"] + for test in record["f_tests"].values(): + factor, levels = test["factor"], test["levels"] lines.append(f" F-test across the {len(levels):,} levels of {factor}") return fitted_lines("\n".join(lines)) @@ -405,7 +402,7 @@ def propeller( design: Design, *, contrasts: Mapping[str, Any] | Sequence[str] | str | None = None, - f_test: str | None = None, + f_tests: str | Sequence[str] | None = None, transform: Literal["logit", "asin"] = "logit", robust: bool = True, trend: bool = False, @@ -423,30 +420,29 @@ def propeller( 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 is - ``f_test="group"``. ``propeller.ttest`` with a design matrix of group columns + ``f_tests="group"``. ``propeller.ttest`` with a design matrix of group columns followed by covariates is a design such as ``~ 0 + group + age`` with a contrast between groups, and ``propeller.anova`` the F-test of ``group`` in it. The same comparisons in another parametrisation, such as ``~ group + age`` with the contrast ``groupB``, give the same tables. Provenance: - The result records the cube's fingerprint, the digests it is built from and - its own provenance, as :func:`~cytepool.differential_expression.limma_voom` - does, although the test reads only the cell numbers. For a cube opened with - :func:`~cytepool.io.read_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. + The result records the cube's fingerprint, the digests it is built from and its + own provenance, as :func:`~cytepool.limma_voom` does, although the test reads + only the cell numbers. For a cube opened with :func:`~cytepool.read_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. Args: data: The number of cells of every sample in every group: a - :class:`~cytepool.cube.Cube` that records them (``n_cells``), or a - table with one row per sample, a text column ``sample_id`` and one - column of cell numbers (integers) per group, named by its group id: a - ``Table``, a mapping of columns, or a pandas DataFrame. A DataFrame - without a ``sample_id`` column is read with its index as the sample - ids, as ``pandas.crosstab(cells["sample_id"], cells["cell_type"])`` - gives it. - design: A :class:`~cytepool.design.Design` whose sample table has a + :class:`~cytepool.Cube` that records them (``n_cells``), or a table with one + row per sample, a text column ``sample_id`` and one column of cell numbers + (integers) per group, named by its group id: a ``Table``, a mapping of + columns, or a pandas DataFrame. A DataFrame without a ``sample_id`` column + is read with its index as the sample ids, as + ``pandas.crosstab(cells["sample_id"], cells["cell_type"])`` gives it. + design: A :class:`~cytepool.Design` whose sample table has a ``sample_id`` column 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. @@ -454,10 +450,11 @@ def propeller( :func:`~cytepool.design.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 + f_tests: 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. + (speckle's ANOVA), or a list of such factors, an F-test of each, named by + its factor; 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 @@ -478,15 +475,16 @@ def propeller( wrong type, or ``data`` or ``contrasts`` is 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_id`` column, names samples the data do not - have or names a sample twice; a contrast is invalid, or there is nothing - to test; ``transform`` is neither ``"logit"`` nor ``"asin"``; ``f_test`` - does 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. + 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_id`` column, names samples the data do not have or + names a sample twice; a contrast is invalid, or there is nothing to test; + ``transform`` is neither ``"logit"`` nor ``"asin"``; ``f_tests`` names + something other than a factor that enters the formula as a main effect only, + names one twice, or names a contrast; 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. """ cube, given = _as_cube(data) if not isinstance(design, Design): @@ -499,7 +497,7 @@ def propeller( "transform": checked_choice(transform, "transform", ("logit", "asin")), "robust": checked_flag(robust, "robust"), "trend": checked_flag(trend, "trend"), - "f_test": _factor_name(f_test), + "f_tests": _factor_names(f_tests), } named = "the cube" if given == "cube" else "the table of cell numbers" cube_rows, design_rows = matched_samples( @@ -516,14 +514,20 @@ def propeller( tested = ( design.contrasts if contrasts is None else make_contrasts(contrasts, design) ) - factor = None if settings["f_test"] is None else _factor(design, settings["f_test"]) - if not len(tested) and factor is None: + factors = [_factor(design, name) for name in settings["f_tests"] or []] + clashes = [factor.name for factor in factors if factor.name in tested.names] + if clashes: + raise ValueError( + f"f_tests names {quoted(clashes)}, which is also the name of a contrast; " + "an F-test is named by its factor, so rename the contrast" + ) + if not len(tested) and not factors: raise ValueError( "propeller has nothing to test: the design has no named contrasts, none " - "were given, and f_test is None. Name the contrasts in the design, as in " + "were given, and f_tests is None. Name the contrasts in the design, as in " "Design(samples, '~ 0 + group', contrasts={'B_vs_A': 'groupB - " "groupA'}), or pass contrasts= in the same form, or name a factor of " - "the design in f_test= to test all its levels at once; the design's " + "the design in f_tests= to test all its levels at once; the design's " f"columns are: {', '.join(design.columns)}" ) @@ -552,14 +556,14 @@ def propeller( "adjusted_p_value": adjusted[:, k], } ) - f_table, hypothesis = None, None - if factor is not None: + hypotheses: dict[str, Any] = {} + for factor in factors: differences = factor.differences(len(design.columns)) names = [f"{level}_vs_{factor.levels[0]}" for level in factor.levels[1:]] f_fit = _moderate(contrasts_fit(fit.model, differences, names=names), settings) if moderated is None: moderated = f_fit - f_table = Table( + tables[factor.name] = Table( { **_descriptive(fit, group_ids, (factor, None)), "f_statistic": f_fit.f_statistic, @@ -567,7 +571,7 @@ def propeller( "adjusted_p_value": adjust_p_values(f_fit.f_p_value, "bh"), } ) - hypothesis = { + hypotheses[factor.name] = { "factor": factor.name, "levels": list(factor.levels), "contrasts": { @@ -575,14 +579,13 @@ def propeller( }, } assert moderated is not None # there is a contrast or an F-test - prior_df = np.broadcast_to(moderated.prior_df, (fit.cells.shape[1],)) + df_prior = np.broadcast_to(moderated.df_prior, (fit.cells.shape[1],)) used_ids = sample_ids[fit.used] return CompositionResult( tested, tables, - f_table, - _summary_table(group_ids, fit, prior_df), + _summary_table(group_ids, fit, df_prior), _sample_table(sample_ids, fit), _proportion_table(fit, group_ids[fit.tested], used_ids), { @@ -592,7 +595,7 @@ def propeller( "cube": cube_record(cube), "design": design_record(design, tested), "settings": {**settings, **_FIXED_SETTINGS}, - "f_test": hypothesis, + "f_tests": hypotheses, "samples": used_ids.tolist(), "left_out": dict.fromkeys( sample_ids[~fit.used].tolist(), "no cells in any group" @@ -900,17 +903,26 @@ def _main_effect(model: ModelMatrix, name: str) -> _Factor | None: return None -def _factor_name(value: Any) -> str | None: - if value is None or isinstance(value, str): - return value - raise TypeError( - "f_test must be the name of a factor of the design (a str) or None, got " - f"{type(value).__name__}" - ) +def _factor_names(value: Any) -> list[str] | None: + """The factors of the F-tests, as a list, or None for no F-test.""" + if value is None: + return None + reject_masked(value, "f_tests") + names = [value] if isinstance(value, str) else value + if not isinstance(names, Sequence) or not all(isinstance(n, str) for n in names): + raise TypeError( + "f_tests must be the name of a factor of the design (a str), a list of " + f"them, or None, got {type(value).__name__}" + ) + names = list(names) + repeated = [name for name in dict.fromkeys(names) if names.count(name) > 1] + if repeated: + raise ValueError(f"f_tests names {quoted(repeated)} more than once") + return names or None def _factor(design: Design, name: str) -> _Factor: - """The factor that f_test names, checked.""" + """The factor that f_tests names, checked.""" model = design.model_matrix factors = [variable for variable in model.variables if variable in model.levels] if name not in model.levels: @@ -920,7 +932,7 @@ def _factor(design: Design, name: str) -> _Factor: else f"not a variable of the formula {design.formula!r}" ) raise ValueError( - f"f_test names {name!r}, {kind}; the F-test compares the levels of a " + f"f_tests names {name!r}, {kind}; the F-test compares the levels of a " f"factor, and the factors of the design are: {quoted(factors) or '(none)'}" ) factor = _main_effect(model, name) @@ -933,7 +945,7 @@ def _factor(design: Design, name: str) -> _Factor: } ) raise ValueError( - f"f_test names {name!r}, which enters the formula in the interaction " + f"f_tests names {name!r}, which enters the formula in the interaction " f"{listing(terms)}: the differences between its levels then depend on " "the other variables of the interaction, so equal means across its levels " "is not one hypothesis. Test the contrasts of interest with contrasts=, " @@ -1009,12 +1021,12 @@ def _descriptive( return columns -def _summary_table(group_ids: np.ndarray, fit: _Fit, prior_df: np.ndarray) -> Table: +def _summary_table(group_ids: np.ndarray, fit: _Fit, df_prior: np.ndarray) -> Table: n_groups = group_ids.size df = np.full(n_groups, np.nan) df[fit.tested] = fit.model.df_residual[0] prior = np.full(n_groups, np.nan) - prior[fit.tested] = prior_df + prior[fit.tested] = df_prior return Table( { "group_id": group_ids, @@ -1027,7 +1039,7 @@ def _summary_table(group_ids: np.ndarray, fit: _Fit, prior_df: np.ndarray) -> Ta ] ), "df_residual": df, - "prior_df": prior, + "df_prior": prior, } ) diff --git a/cytepool/cube/__init__.py b/cytepool/cube/__init__.py index d0210aa..242f1ec 100644 --- a/cytepool/cube/__init__.py +++ b/cytepool/cube/__init__.py @@ -1,7 +1,15 @@ -"""Pseudobulk cubes: additive statistics per sample, group and feature.""" +"""Pseudobulk cubes: additive statistics per sample, group and feature. -from .collection import CubeCollection -from .cube import Cube -from .pool import from_bulk, from_cells +:class:`cytepool.Cube`, :class:`cytepool.CubeCollection`, :func:`cytepool.from_cells` +and :func:`cytepool.from_bulk` are documented at the top level; the modules +:mod:`cytepool.cube.fingerprints` and :mod:`cytepool.cube.provenance` specify the +fingerprints and provenance records of cube files. +""" -__all__ = ["Cube", "CubeCollection", "from_bulk", "from_cells"] +from .collection import CubeCollection as CubeCollection +from .cube import Cube as Cube +from .pool import from_bulk as from_bulk +from .pool import from_cells as from_cells + +# The names above are the top level's (cytepool.Cube and the others). +__all__: list[str] = [] diff --git a/cytepool/cube/_axes.py b/cytepool/cube/_axes.py index c9945eb..ac41be1 100644 --- a/cytepool/cube/_axes.py +++ b/cytepool/cube/_axes.py @@ -280,12 +280,15 @@ def axis_codes( table_name: str, id_column: str, unit: str = "cell", + axis: str | None = None, ) -> tuple[np.ndarray, Table, bool]: """Return the position of every cell's label on an axis, the axis table, and whether the labels were integers. - ``unit`` names what holds the labels in messages: a cell, or a row of a table - of profiles that already pools cells. + ``name`` names the labels in messages, and ``table_name`` the table; ``unit`` + names what holds the labels: a cell, or a row of a table of profiles that + already pools cells. ``axis`` is the axis whose table is read (``"samples"`` or + ``"groups"``), ``name`` by default. """ codes, distinct, integers = _factorize(labels, name, n_cells, unit) if table is None: @@ -293,7 +296,9 @@ def axis_codes( ids[:] = distinct return codes, Table({id_column: ids}), integers reject_masked_table(table, table_name, id_column) - axis_table, index = checked_axis_table(table, name, id_column) + axis_table, index = checked_axis_table( + table, name if axis is None else axis, id_column + ) positions = np.empty(len(distinct), dtype=np.int64) for code, label in enumerate(distinct): position = index.get(label) diff --git a/cytepool/cube/collection.py b/cytepool/cube/collection.py index 821ca71..f00d7ab 100644 --- a/cytepool/cube/collection.py +++ b/cytepool/cube/collection.py @@ -117,9 +117,9 @@ class CubeCollection: collection's order, and its own samples with their own ids, so that a design built from its sample table applies. Any analysis of one cube, such as ``limma_voom`` with each study's own design, then gives results keyed by - the aligned groups, which ``align_study_results`` lines up gene by gene and - ``differential_expression_meta_analysis`` combines - (``cytepool.meta_analysis``). + the aligned groups; ``analyse_each_study`` runs one on every study, and the + ``StudyResults`` it returns lines them up gene by gene, for + ``meta_analysis`` and the other functions that combine studies. Stacking: ``stack()`` puts the studies into one cube, for a joint model with the study diff --git a/cytepool/cube/pool.py b/cytepool/cube/pool.py index 50a7611..5e0338f 100644 --- a/cytepool/cube/pool.py +++ b/cytepool/cube/pool.py @@ -86,10 +86,10 @@ def from_cells( counts: Any, *, - samples: Any, - groups: Any, - sample_table: Any = None, - group_table: Any = None, + sample_labels: Any, + group_labels: Any, + samples: Any = None, + groups: Any = None, features: Any = None, target_sum: float = 10000.0, library_sizes: Any = None, @@ -114,18 +114,18 @@ def from_cells( ``csr_array``. Counts are non-negative and below 2**53, whole numbers or not. The column indices of a CSR row may be in any order, and repeated entries of one row and column are summed, as scipy does. - samples: The sample label of every cell, as text or integers (integers - become their decimal text), for example a list, a numpy array or a - pandas Series or Categorical. - groups: The group label, usually the cell type, of every cell, in the same - forms. - sample_table: Sample metadata, as a ``Table`` or anything ``Table`` - accepts, with a text column ``sample_id`` that holds every sample label. - Its rows set the order of the samples, and a sample without cells gets - zeros. By default the samples are the sorted distinct labels (integers - in numeric order). - group_table: Group metadata with a text column ``group_id``, used in the - same way. + sample_labels: The sample label of every cell, as text or integers + (integers become their decimal text), for example a list, a numpy array + or a pandas Series or Categorical. + group_labels: The group label, usually the cell type, of every cell, in the + same forms. + samples: The sample table, as a ``Table`` or anything ``Table`` accepts, + with a text column ``sample_id`` that holds every sample label and the + samples' metadata, such as condition and donor. Its rows set the order + of the samples, and a sample without cells gets zeros. By default the + samples are the sorted distinct labels (integers in numeric order). + groups: The group table, with a text column ``group_id``, used in the same + way. features: Feature metadata with a text column ``feature_id`` and one row per column of ``counts``, or a sequence of feature ids. By default the ids are the columns of a pandas DataFrame ``counts`` when they are text, @@ -199,8 +199,8 @@ def from_cells( Example: >>> cube = from_cells( ... np.array([[1, 0], [3, 2], [0, 4]]), - ... samples=["S1", "S1", "S2"], - ... groups=["T", "T", "T"], + ... sample_labels=["S1", "S1", "S2"], + ... group_labels=["T", "T", "T"], ... layers=["counts", "detected"], ... ) >>> cube.layer("counts", "T").tolist() @@ -213,10 +213,22 @@ def from_cells( matrix = _cell_matrix(counts) n_rows, n_features = matrix.shape sample_codes, sample_axis, sample_integers = axis_codes( - samples, "samples", n_rows, sample_table, "sample_table", "sample_id" + sample_labels, + "sample_labels", + n_rows, + samples, + "samples", + "sample_id", + axis="samples", ) group_codes, group_axis, group_integers = axis_codes( - groups, "groups", n_rows, group_table, "group_table", "group_id" + group_labels, + "group_labels", + n_rows, + groups, + "groups", + "group_id", + axis="groups", ) feature_origin = _id_origin(features, counts, "columns") if features is None: @@ -280,11 +292,11 @@ def from_cells( "target_sum": target, "library_sizes": "cell_totals" if sizes is None else "given", "samples": { - "order": "sorted_labels" if sample_table is None else "sample_table", + "order": "sorted_labels" if samples is None else "sample_table", "labels": "integers" if sample_integers else "text", }, "groups": { - "order": "sorted_labels" if group_table is None else "group_table", + "order": "sorted_labels" if groups is None else "group_table", "labels": "integers" if group_integers else "text", }, "features": feature_origin, diff --git a/cytepool/cube/provenance.py b/cytepool/cube/provenance.py index fed6666..90dedcf 100644 --- a/cytepool/cube/provenance.py +++ b/cytepool/cube/provenance.py @@ -28,10 +28,10 @@ - ``library_sizes``: ``"cell_totals"`` (the total count of each cell) or ``"given"`` (the ``library_sizes`` argument), as in the normalization; - ``samples`` and ``groups``: ``{"order": ..., "labels": ...}``, where - ``order`` is ``"sample_table"`` or ``"group_table"`` (the rows of that table) - or ``"sorted_labels"`` (the distinct labels, text sorted by code point and - integers numerically), and ``labels`` is ``"text"`` or ``"integers"`` - (integer labels became their decimal text); + ``order`` is ``"sample_table"`` or ``"group_table"`` (the rows of the sample + or group table given) or ``"sorted_labels"`` (the distinct labels, text + sorted by code point and integers numerically), and ``labels`` is ``"text"`` + or ``"integers"`` (integer labels became their decimal text); - ``features``: where the feature ids came from: ``"table"``, ``"ids"`` (a sequence), ``"data_frame_columns"`` or ``"positions"`` (``feature_0``, ``feature_1``, ...). @@ -127,8 +127,8 @@ _SHOWN_MEMBERS = 20 # merged groups or pooled samples listed in full _SHOWN_CHARACTERS = 200 # of the JSON of one value of a record shown as it is _ORDERS = { - "sample_table": "the rows of sample_table", - "group_table": "the rows of group_table", + "sample_table": "the rows of the sample table given", + "group_table": "the rows of the group table given", "sorted_labels": "the sorted distinct labels", } _ORIGINS = { diff --git a/cytepool/design/__init__.py b/cytepool/design/__init__.py index 6a1dab5..c4d6c33 100644 --- a/cytepool/design/__init__.py +++ b/cytepool/design/__init__.py @@ -2,14 +2,14 @@ from .checks import DesignIssue, DesignReport from .contrasts import Contrasts, make_contrasts -from .design import Design +from .design import Design as Design from .formula import FormulaTerms, RandomEffect, parse_formula, parse_random_effects from .model_matrix import INTERCEPT, ModelMatrix, model_matrix +# Design is the top level's, cytepool.Design. __all__ = [ "INTERCEPT", "Contrasts", - "Design", "DesignIssue", "DesignReport", "FormulaTerms", diff --git a/cytepool/design/checks.py b/cytepool/design/checks.py index 8a784ed..8365cc9 100644 --- a/cytepool/design/checks.py +++ b/cytepool/design/checks.py @@ -182,7 +182,7 @@ class DesignReport: leverage: tuple[float, ...] @property - def residual_df(self) -> int: + def df_residual(self) -> int: """Residual degrees of freedom, the number of samples minus the rank.""" return self.n_samples - self.rank @@ -199,7 +199,7 @@ def __str__(self) -> str: summary = ( f"Design check: {_count(self.n_samples, 'sample')}, " f"{_count(self.n_columns, 'column')}, rank {self.rank}, " - f"{_count(self.residual_df, 'residual degree')} of freedom" + f"{_count(self.df_residual, 'residual degree')} of freedom" ) if not self.errors and not self.warnings: return fitted_lines(summary + "; no problems found.") @@ -705,7 +705,7 @@ def _is_id_word(word: str) -> bool: def _leverage_check( variables: dict[str, _Factor | _Covariate], leverage: np.ndarray, - residual_df: int, + df_residual: int, names: list[str], already_named: set[int], warnings: list[DesignIssue], @@ -787,7 +787,7 @@ def _leverage_check( "high_leverage", "the model nearly fits " + _listing(crowded) - + f" exactly: with {_count(residual_df, 'residual degree')} of " + + f" exactly: with {_count(df_residual, 'residual degree')} of " "freedom, such a sample largely decides some coefficients and barely " "informs the residual variance. Add samples or simplify the formula", ) diff --git a/cytepool/differential_expression.py b/cytepool/differential_expression.py index 4014726..8893d07 100644 --- a/cytepool/differential_expression.py +++ b/cytepool/differential_expression.py @@ -1,14 +1,14 @@ """Differential expression between samples, one group (cell type) of a cube at a time. -:func:`limma_voom` and :func:`limma_trend` run the limma workflows for RNA-seq counts -(Ritchie et al. 2015) on the pseudobulk counts of each group of a cube: limma-voom, -which gives every observation a precision weight from the mean-variance trend of the -log counts per million (Law et al. 2014), and limma-trend, which lets the prior -variance of the empirical Bayes moderation follow that trend instead. -:func:`dream_voom` runs variancePartition's dream (Hoffman and Roussos 2021), -limma-voom with a linear mixed model in place of the linear model, for designs with -random effects such as several samples of each donor. Each tests the named contrasts -of a :class:`~cytepool.design.Design` and returns, for every group and contrast, a +:func:`~cytepool.limma_voom` and :func:`~cytepool.limma_trend` run the limma workflows +for RNA-seq counts (Ritchie et al. 2015) on the pseudobulk counts of each group of a +cube: limma-voom, which gives every observation a precision weight from the +mean-variance trend of the log counts per million (Law et al. 2014), and limma-trend, +which lets the prior variance of the empirical Bayes moderation follow that trend +instead. :func:`~cytepool.dream_voom` runs variancePartition's dream (Hoffman and +Roussos 2021), limma-voom with a linear mixed model in place of the linear model, for +designs with random effects such as several samples of each donor. Each tests the named +contrasts of a :class:`~cytepool.Design` and returns, for every group and contrast, a table of the tested features with ``topTable``'s statistics, together with a summary, the samples behind every fit and a provenance record. @@ -36,7 +36,7 @@ estimable from the group's samples when it is orthogonal to the null space of the group's design matrix (Searle 1971): when its weight on every aliased column is the sum of its weights on the kept columns, weighted by that column's combination. - This is the rule of :meth:`Design.check `. ``c`` + This is the rule of :meth:`Design.check `. ``c`` applied to the coefficients then equals its weights on the kept columns applied to the fit on the kept columns, whatever the coefficients that the samples cannot separate, so its estimate and its exact standard error are those of any full-rank @@ -117,23 +117,23 @@ matter: weights that are all ten times larger give the same results. dream: - :func:`dream_voom` takes the steps of limma-voom up to the normalization, then - those of variancePartition's ``voomWithDreamWeights``, ``dream``, ``eBayes`` and - ``topTable``: precision weights from the mean-variance trend of the design's + :func:`~cytepool.dream_voom` takes the steps of limma-voom up to the normalization, + then those of variancePartition's ``voomWithDreamWeights``, ``dream``, ``eBayes`` + and ``topTable``: precision weights from the mean-variance trend of the design's mixed model, fitted to every feature by maximum likelihood (:func:`~cytepool.stats.dream.voom_with_dream_weights`); the mixed model of every feature fitted by REML with those weights (:func:`~cytepool.stats.mixed_models.fit_mixed_model`); t-tests of the estimable - contrasts (:func:`~cytepool.stats.mixed_models.fixed_effect_tests`) with the - degrees of freedom dream chooses by default (``ddf = "adaptive"``), Kenward and - Roger's (1997) in a group of fewer than 20 samples and Satterthwaite's (1946) - otherwise; F-tests of sets of contrasts; and their empirical Bayes moderation + contrasts (:func:`~cytepool.stats.mixed_models.fixed_effect_tests`) with the degrees + of freedom dream chooses by default (``ddf = "adaptive"``), Kenward and Roger's + (1997) in a group of fewer than 20 samples and Satterthwaite's (1946) otherwise; + F-tests of sets of contrasts; and their empirical Bayes moderation (:func:`~cytepool.stats.dream.moderated_fixed_effect_tests`). The random effects of - a group are those of its samples: a grouping factor's levels without samples in - the group drop out. :mod:`cytepool.stats.dream` documents the conventions found by - probing variancePartition and the deliberate differences from it. Prior weights - from the number of cells are divided by their mean in each group, as - variancePartition divides prior weights, which changes no test. + a group are those of its samples: a grouping factor's levels without samples in the + group drop out. :mod:`cytepool.stats.dream` documents the conventions found by + probing variancePartition and the deliberate differences from it. Prior weights from + the number of cells are divided by their mean in each group, as variancePartition + divides prior weights, which changes no test. P-values adjusted across groups: ``adjusted_p_value`` adjusts the p-values of one group and contrast, as limma's @@ -160,20 +160,19 @@ ``~ donor + condition``, limma reports for the coefficient ``donorD2`` (donor D2 against D1) the difference between donor D2 and the last donor. - With ``contrast_standard_errors="exact"``, the default of - :func:`limma_voom`, the standard errors of contrasts that combine several - coefficients are exact; limma's ``contrasts.fit`` approximates them when + :func:`~cytepool.limma_voom`, the standard errors of contrasts that combine + several coefficients are exact; limma's ``contrasts.fit`` approximates them when weights differ between features, and ``"limma"`` reproduces it. limma's approximation, unlike the exact value, depends on the parametrisation, so only - exact standard errors are the same in every parametrisation of a group's - design. + exact standard errors are the same in every parametrisation of a group's design. Memory: One group is in memory at a time: its counts layer, read from the cube when the group is fitted, and the fit of its features. A cube opened with - :func:`~cytepool.io.read_cube` therefore stays on disk except for the group + :func:`~cytepool.read_cube` therefore stays on disk except for the group being fitted. Results hold one table per group and contrast, and a record that identifies the cube by its fingerprint, which costs no read for a cube file - ("Provenance" in :func:`limma_voom`). + ("Provenance" in :func:`~cytepool.limma_voom`). References: Law CW, Chen Y, Shi W, Smyth GK (2014). voom: precision weights unlock linear model @@ -297,7 +296,7 @@ from .cube import Cube from .design import Contrasts, Design, make_contrasts from .design._design_matrices import is_random_intercept, refuse_random_effects -from .normalization import log_cpm +from .normalization import log2_cpm from .stats._fixed_effect_inference import ( InferenceParts, chosen_parts, @@ -424,7 +423,7 @@ class DifferentialExpressionResult: standard deviation times the feature's posterior standard deviation (limma's ``stdev.unscaled * sqrt(s2.post)``), so that ``t`` is ``log2_fold_change / standard_error``; it is what a meta-analysis of studies - reads (:func:`~cytepool.meta_analysis.differential_expression_meta_analysis`); + reads (:func:`~cytepool.meta_analysis`); - ``average_log2_cpm``: the mean log2 count per million over the group's samples (``AveExpr``); - ``t`` and ``p_value``: the moderated t-statistic and its two-sided p-value; @@ -433,13 +432,13 @@ class DifferentialExpressionResult: - ``adjusted_p_value_across_groups``, with ``adjust_across_groups`` only: Benjamini-Hochberg over the tested features of every group that tests the contrast; - - ``b_statistic``: the log-odds that the feature is differentially expressed - (``B``). + - ``log_odds``: the log-odds that the feature is differentially expressed + (limma's B-statistic, ``B``). Tables from :func:`dream_voom` also give ``df``, the degrees of freedom of each test (Satterthwaite's or Kenward and Roger's plus the prior's, ``df.total``), after ``t``, and ``z_statistic``, the z-statistic with the test's p-value - (``topTable``'s ``z.std``), after ``b_statistic``; a feature whose mixed model has + (``topTable``'s ``z.std``), after ``log_odds``; a feature whose mixed model has no minimum has NaN statistics. Its F-tests have tables of their own, whose columns :func:`dream_voom` lists. @@ -451,7 +450,7 @@ class DifferentialExpressionResult: ``n_samples_used`` and ``n_samples_left_out``; ``n_features_tested``; ``df_residual``, for dream the median over the features of their approximate residual degrees of freedom (in a group not fitted, the residual degrees of - freedom of the fixed effects, as for limma); ``prior_df``, the prior degrees of + freedom of the fixed effects, as for limma); ``df_prior``, the prior degrees of freedom (their median over features with robust moderation, where each feature has its own); for dream, ``df_method``, the degrees of freedom of the group's tests (``"satterthwaite"`` or ``"kenward_roger"``; empty where the group was not @@ -710,7 +709,7 @@ def table(self, group: str | None = None, contrast: str | None = None) -> Table: contrast, [*self._contrasts.names, *self.f_tests], "contrast", - "contrasts and F-tests", + "contrasts and F-tests" if self.f_tests else "contrasts", ) found = self._tables.get((group, contrast)) if found is not None: @@ -876,7 +875,7 @@ def limma_voom( The result records the cube's fingerprint, the digests it is built from and the cube's provenance (``Cube.fingerprint``, ``Cube.digests``, ``Cube.provenance``), computed after the fits. For a cube opened with - :func:`~cytepool.io.read_cube` this reads nothing: the file stores the + :func:`~cytepool.read_cube` this reads nothing: the file stores the digests, and each group the fits read whole is checked against its digest. A cube in memory hashes its layers once, at about 1 s per GB of values where the processor has SHA instructions. A lazy cube derived from a cube @@ -887,8 +886,8 @@ def limma_voom( Args: cube: A cube with a ``counts`` layer, such as one from - :func:`~cytepool.cube.from_cells` or :func:`~cytepool.io.read_cube`. - design: A :class:`~cytepool.design.Design` whose sample table has a + :func:`~cytepool.from_cells` or :func:`~cytepool.read_cube`. + design: A :class:`~cytepool.Design` whose sample table has a ``sample_id`` column with cube sample ids; its rows may be some of the cube's samples, in any order. A design with random effects is refused: these fits have fixed effects only, and :func:`dream_voom` fits random @@ -1036,7 +1035,7 @@ def limma_trend( Args: cube: A cube with a ``counts`` layer. - design: A :class:`~cytepool.design.Design` matched to the cube by + design: A :class:`~cytepool.Design` matched to the cube by ``sample_id``, as for :func:`limma_voom`. groups: A group id or a list of them; all groups by default. contrasts: The contrasts to test; by default the design's named contrasts. @@ -1185,7 +1184,7 @@ def dream_voom( Args: cube: A cube with a ``counts`` layer. - design: A :class:`~cytepool.design.Design` whose random effects are random + design: A :class:`~cytepool.Design` whose random effects are random intercepts, such as ``random="(1 | donor)"``, matched to the cube by ``sample_id`` as for :func:`limma_voom`. groups: A group id or a list of them; all groups by default. @@ -1283,7 +1282,7 @@ def dream_voom( class _GroupFit(GroupRecord): """What happened in one group, and its tables; with ``keep``, its fit.""" - prior_df: float = float("nan") + df_prior: float = float("nan") quality_weights: np.ndarray | None = None tables: dict[str, Table] = field(default_factory=dict) df_method: str = "" # dream: the degrees of freedom of the group's tests @@ -1505,7 +1504,7 @@ def _fit_group( fit.voom_span = weighted.span quality = weighted.sample_weights model = fit_linear_model( - weighted.log_cpm, + weighted.log2_cpm, x_kept, weights=weighted.weights, coefficient_names=columns, @@ -1513,7 +1512,7 @@ def _fit_group( ) method = settings["contrast_standard_errors"] else: - values = log_cpm(y, norm_factors=factors, prior_count=settings["prior_count"]) + values = log2_cpm(y, norm_factors=factors, prior_count=settings["prior_count"]) quality = None if quality_settings is not None: try: @@ -1554,7 +1553,7 @@ def _fit_group( adjusted = adjust_p_values(moderated.p_value, "bh", axis=0) fit.df_residual = float(model.df_residual[0]) - fit.prior_df = float(np.median(moderated.prior_df)) + fit.df_prior = float(np.median(moderated.df_prior)) fit.library_sizes = sizes fit.norm_factors = np.ones(n_used) if factors is None else factors fit.prior_weights = prior @@ -1562,7 +1561,7 @@ def _fit_group( if keep: if inputs.method == "limma_voom": fit.values, fit.weights, fit.voom = ( - weighted.log_cpm, + weighted.log2_cpm, weighted.weights, weighted, ) @@ -1590,7 +1589,7 @@ def _fit_group( "t": moderated.t[:, column], "p_value": moderated.p_value[:, column], "adjusted_p_value": adjusted[:, column], - "b_statistic": moderated.lods[:, column], + "log_odds": moderated.log_odds[:, column], } ) return fit @@ -1623,7 +1622,7 @@ def _fit_dream_group( weighted = _dream_weights(y, sizes, fraction, data.mixed, prior, True) except ValueError as error: # the trend cannot give weights for these data return fit.stop("failed", f"voom with dream weights: {error}") - model = _fit_mixed_model(weighted.log_cpm, weighted.weights, False, data.mixed) + model = _fit_mixed_model(weighted.log2_cpm, weighted.weights, False, data.mixed) fit.voom_span = weighted.span fitted = np.isfinite(model.residual_variance) # Differential expression always has contrasts to test. @@ -1649,13 +1648,13 @@ def _fit_dream_group( fit.features_not_fitted = int(np.count_nonzero(~fitted)) fit.features_not_converged = int(np.count_nonzero(fitted & ~model.converged)) - fit.df_residual = float(np.median(model.residual_df[fitted])) - fit.prior_df = float(np.median(moderated.prior_df)) + fit.df_residual = float(np.median(model.df_residual[fitted])) + fit.df_prior = float(np.median(moderated.df_prior)) fit.library_sizes = data.sizes fit.norm_factors = np.ones(n_used) if data.factors is None else data.factors fit.prior_weights = data.prior if keep: - fit.values, fit.weights = weighted.log_cpm, weighted.weights + fit.values, fit.weights = weighted.log2_cpm, weighted.weights fit.voom, fit.moderated = weighted, moderated features = cube.features identity = {"feature_id": features["feature_id"][fit.tested]} @@ -1671,7 +1670,7 @@ def _fit_dream_group( "df": moderated.df_total[:, column], "p_value": moderated.p_value[:, column], "adjusted_p_value": adjusted[:, column], - "b_statistic": moderated.lods[:, column], + "log_odds": moderated.log_odds[:, column], "z_statistic": moderated.z[:, column], } ) @@ -1994,7 +1993,7 @@ def _summary_table(fits: list[_GroupFit], dream: bool) -> Table: dtype=np.int64, ), "df_residual": np.array([fit.df_residual for fit in fits], dtype=float), - "prior_df": np.array([fit.prior_df for fit in fits], dtype=float), + "df_prior": np.array([fit.df_prior for fit in fits], dtype=float), } if dream: # The degrees of freedom of the group's tests; empty where none were made. diff --git a/cytepool/gsea.py b/cytepool/gene_set_enrichment.py similarity index 97% rename from cytepool/gsea.py rename to cytepool/gene_set_enrichment.py index 680da80..d473041 100644 --- a/cytepool/gsea.py +++ b/cytepool/gene_set_enrichment.py @@ -1,30 +1,30 @@ """Gene set enrichment analysis (GSEA) of genes ranked by a statistic, as fgsea computes it. -:func:`gsea` asks of each gene set whether its genes gather at the top or at the -bottom of a ranking of all genes by a statistic, such as the moderated t-statistic of -a contrast: preranked GSEA (Subramanian et al. 2005) as fgsea 1.32.2 computes it +:func:`~cytepool.gsea` asks of each gene set whether its genes gather at the top or at +the bottom of a ranking of all genes by a statistic, such as the moderated t-statistic +of a contrast: preranked GSEA (Subramanian et al. 2005) as fgsea 1.32.2 computes it (Korotkevich et al. 2021), with p-values by fgsea's default multilevel method (``fgseaMultilevel``) or by permutations of the genes (``fgseaSimple``). Results match fgsea's exactly when CytePool is given the random seeds that fgsea draws, and within Monte Carlo error otherwise, except where noted under "Deliberate differences". The routines port fgsea's (MIT licence), as ``THIRD_PARTY_NOTICES.md`` records. -:func:`~cytepool.plotting.plot_enrichment` and :func:`~cytepool.plotting.plot_gsea_table` -draw its figures. +:func:`~cytepool.plotting.plot_enrichment` and +:func:`~cytepool.plotting.plot_gsea_table` draw its figures. The genes and the sets: The statistics come from a group and contrast of a - :class:`~cytepool.differential_expression.DifferentialExpressionResult`, from a - table of results or from a mapping of gene id to statistic. Genes are ranked by - decreasing statistic, and genes with equal statistics keep the order in which they - are given, as in fgsea: the score of a set whose genes tie with other genes - depends on that order, and a warning says how many statistics tie. Rows without a - gene id or without a statistic (NaN) are left out, where fgsea refuses them; - infinite statistics and ids on two rows are refused. Each set's genes are matched - to the ranked genes, and a set is tested when ``min_size`` to ``max_size`` of its - genes are ranked (fgsea's ``minSize`` and ``maxSize``); a set of every ranked gene - is never tested, as its score is not defined. The table keeps the order of - ``gene_sets``, where fgsea orders the sets by name. + :class:`~cytepool.DifferentialExpressionResult`, from a table of results or from a + mapping of gene id to statistic. Genes are ranked by decreasing statistic, and genes + with equal statistics keep the order in which they are given, as in fgsea: the score + of a set whose genes tie with other genes depends on that order, and a warning says + how many statistics tie. Rows without a gene id or without a statistic (NaN) are + left out, where fgsea refuses them; infinite statistics and ids on two rows are + refused. Each set's genes are matched to the ranked genes, and a set is tested when + ``min_size`` to ``max_size`` of its genes are ranked (fgsea's ``minSize`` and + ``maxSize``); a set of every ranked gene is never tested, as its score is not + defined. The table keeps the order of ``gene_sets``, where fgsea orders the sets by + name. The enrichment score: With weights |r|^p of the statistics r, p the ``exponent`` (fgsea's @@ -254,13 +254,13 @@ def gsea( Args: statistics: The statistics that rank the genes: a - :class:`~cytepool.differential_expression.DifferentialExpressionResult`, - with ``group`` and ``contrast``; a table of results, a ``Table`` or a - pandas DataFrame, with a column of gene ids and a column of statistics; - or a pandas Series of statistics indexed by gene id, or a mapping of gene - id to statistic. NaN marks a gene without a statistic. - gene_sets: A :class:`~cytepool.gene_sets.GeneSets`, such as one read by - :func:`~cytepool.gene_sets.read_gmt`, or a mapping of set name to gene ids. + :class:`~cytepool.DifferentialExpressionResult`, with ``group`` and + ``contrast``; a table of results, a ``Table`` or a pandas DataFrame, with a + column of gene ids and a column of statistics; or a pandas Series of + statistics indexed by gene id, or a mapping of gene id to statistic. NaN + marks a gene without a statistic. + gene_sets: A :class:`~cytepool.GeneSets`, such as one read by + :func:`~cytepool.read_gmt`, or a mapping of set name to gene ids. group: The group (cell type) of a ``DifferentialExpressionResult``; it may be left out when the result has one. contrast: The contrast of a ``DifferentialExpressionResult``; it may be left diff --git a/cytepool/stats/gene_set_tests.py b/cytepool/gene_set_tests.py similarity index 80% rename from cytepool/stats/gene_set_tests.py rename to cytepool/gene_set_tests.py index fc6236d..ea7cf46 100644 --- a/cytepool/stats/gene_set_tests.py +++ b/cytepool/gene_set_tests.py @@ -1,14 +1,15 @@ """Gene-set tests of one contrast of a linear model: limma's camera and fry. A gene set, such as a pathway, is tested as a unit for one contrast of the linear model -fitted to every gene. :func:`camera` is competitive: it asks whether the genes of a set -are more differentially expressed than the other genes, allowing for the correlation -between the genes of the set (Wu and Smyth 2012). :func:`fry` is self-contained: it asks -whether the genes of a set are differentially expressed at all, as the rotation test of -Wu et al. (2010) with the mean statistic does, without drawing rotations (Giner and -Smyth 2016). Results match limma 3.62.1 except where noted under "Deliberate -differences". The rotation tests, which draw rotations (limma's roast, mroast and -romer), are in :mod:`cytepool.stats.rotation_gene_set_tests`. +fitted to every gene. :func:`~cytepool.camera` is competitive: it asks whether the genes +of a set are more differentially expressed than the other genes, allowing for the +correlation between the genes of the set (Wu and Smyth 2012). :func:`~cytepool.fry` is +self-contained: it asks whether the genes of a set are differentially expressed at all, +as the rotation test of Wu et al. (2010) with the mean statistic does, without drawing +rotations (Giner and Smyth 2016). Results match limma 3.62.1 except where noted under +"Deliberate differences". The rotation tests, which draw rotations (limma's roast, +mroast and romer), are :func:`~cytepool.roast`, :func:`~cytepool.mroast` and +:func:`~cytepool.romer`. Effects: Both tests work with the effects of each gene's least squares fit (Wu et al. 2010, @@ -166,12 +167,12 @@ from cytepool._options import checked_flag from cytepool._ranks import average_ranks from cytepool._summation import observed_means -from cytepool.stats._gene_sets import ( +from cytepool._gene_sets import ( check_matched, checked_gene_sets, contrast_design, - expression_values, gene_effects, + gene_set_data, set_label, set_positions, ) @@ -201,18 +202,20 @@ def camera( - y: Any, - design: Any, - contrast: Any, + data: Any, gene_sets: Mapping[str, Any], *, - gene_ids: npt.ArrayLike | None = None, + group: str | None = None, + contrast: Any = None, + design: Any = None, + id_column: str = "feature_id", + feature_ids: npt.ArrayLike | None = None, weights: npt.ArrayLike | None = None, inter_gene_correlation: float | None = 0.01, allow_negative_correlation: bool = False, use_ranks: bool = False, - trend: bool = False, - robust: bool = False, + trend: bool | None = None, + robust: bool | None = None, ) -> Table: """Competitive test of gene sets for one contrast, allowing for their correlation. @@ -224,31 +227,46 @@ def camera( it ranks co-regulated, interpretable sets higher than a correlation estimated for each set, whose error rate control is strict. + A result of :func:`~cytepool.limma_voom` or :func:`~cytepool.limma_trend` is tested + as limma's camera tests the fit of one group: its values and weights, the design + rows of its samples and the contrast's weights on them, as + :meth:`~cytepool.DifferentialExpressionResult.fit` gives them, and the moderation + the result used. ``camera(result, sets, id_column="symbol")`` names the genes by + the symbols of the result's features. + Args: - y: ``(n_samples, n_genes)`` log-expression values, finite, or a - :class:`~cytepool.stats.voom.VoomResult`, whose log-CPM values are tested - with its weights. - design: ``(n_samples, n_coefficients)`` design matrix of full column rank, or - anything else that ``fit_linear_model`` accepts, such as a - :class:`~cytepool.design.Design`. It needs residual degrees of freedom. - contrast: The contrast to test: the name of a named contrast of a ``Design``, - the name or 0-based position of a design column (a coefficient; negative - positions count from the last column), or weights on the design columns: a - vector in their order, a mapping of column name to weight (columns left out - weigh 0), a pandas Series indexed by them, or a ``Contrasts`` holding one + data: A :class:`~cytepool.DifferentialExpressionResult` of limma-voom or + limma-trend; a :class:`~cytepool.stats.voom.VoomResult`, whose log2-CPM + values are tested with its weights; or ``(n_samples, n_genes)`` + log-expression values, finite. + gene_sets: Mapping of set name to its genes: gene ids (matched to the ids of + the data; ids not among them are left out, as limma's ``ids2indices`` + does), 0-based positions of columns of an array when ``feature_ids`` is + not given, or a boolean mask over the genes. A gene listed twice counts + once. A :class:`~cytepool.GeneSets`, such as one read from a GMT file by + :func:`~cytepool.read_gmt`, is such a mapping, of gene ids. + group: For a result, the group (cell type) whose fit is tested; it may be + left out when the result has one. + contrast: The contrast to test. For a result, a contrast name of the result, + which may be left out when it tests one. For an array or a VoomResult: + the name of a named contrast of a ``Design``, the name or 0-based + position of a design column (a coefficient; negative positions count + from the last column), or weights on the design columns: a vector in + their order, a mapping of column name to weight (columns left out weigh + 0), a pandas Series indexed by them, or a ``Contrasts`` holding one contrast. - gene_sets: Mapping of set name to its genes: gene ids (matched to - ``gene_ids``; ids not among them are left out, as limma's ``ids2indices`` - does), 0-based positions of columns of ``y`` when ``gene_ids`` is not - given, or a boolean mask over the genes. A gene listed twice counts once. - A :class:`~cytepool.gene_sets.GeneSets`, such as one read from a GMT file - by :func:`~cytepool.gene_sets.read_gmt`, is such a mapping, of gene ids. - gene_ids: One id per gene (column of ``y``), unique, to name genes in - ``gene_sets`` by id. - weights: Precision weights, positive and finite: ``(n_samples,)`` for sample - weights or ``(n_samples, n_genes)`` for observation weights. Not with a - ``VoomResult``, which carries its own. limma's per-gene weights (a vector - of one weight per gene) have no counterpart. + design: For an array or a VoomResult, ``(n_samples, n_coefficients)`` + design matrix of full column rank, or anything else that + ``fit_linear_model`` accepts, such as a :class:`~cytepool.Design`. It + needs residual degrees of freedom. + id_column: For a result, the column of its features whose ids the sets name, + such as ``"feature_id"`` (the default) or ``"symbol"``. + feature_ids: For an array or a VoomResult, one id per gene (column), + unique, to name genes in ``gene_sets`` by id. + weights: For an array, precision weights, positive and finite: + ``(n_samples,)`` for sample weights or ``(n_samples, n_genes)`` for + observation weights. limma's per-gene weights (a vector of one weight + per gene) have no counterpart. inter_gene_correlation: The correlation between the genes of every set, in (-1, 1), or None to estimate it for each set from its residuals. allow_negative_correlation: Whether a negative correlation, given or @@ -257,9 +275,11 @@ def camera( use_ranks: Whether to test the ranks of the z-scores (a correlation-adjusted Wilcoxon-Mann-Whitney test) rather than their values. trend: Whether the prior variance of the moderation follows the mean - log-expression of the genes (limma's ``trend.var``). + log-expression of the genes (limma's ``trend.var``); None takes the + moderation of a result (limma-trend's trend), and False otherwise. robust: Whether to moderate the variances with the robust estimator, which - limma's camera does not offer. + limma's camera does not offer; None takes the result's setting, and + False otherwise. Returns: A table with one row per gene set, in the order of ``gene_sets``: ``gene_set``; @@ -273,28 +293,43 @@ def camera( Raises: TypeError: If an argument has the wrong type, or is or holds a numpy masked array. - ValueError: If ``y`` is not finite or has fewer than three genes, the design - does not have one row per sample, is not of full column rank or leaves no - residual degrees of freedom, the contrast or a gene set is invalid, a weight - is not positive and finite, or no gene id of any set is among ``gene_ids``. + ValueError: If the values are not finite or have fewer than three genes, the + design is missing for an array, does not have one row per sample, is not + of full column rank or leaves no residual degrees of freedom, the + contrast or a gene set is invalid, a weight is not positive and finite, + no gene id of any set is among the data's ids, an argument applies to + another kind of data, or the result is from ``dream_voom``. + KeyError: If a result has no such group, contrast or column of its features, + or the group cannot estimate the contrast. """ rho = _correlation_option(inter_gene_correlation) allow_negative = checked_flag( allow_negative_correlation, "allow_negative_correlation", expected="a bool" ) ranks = checked_flag(use_ranks, "use_ranks", expected="a bool") - with_trend = checked_flag(trend, "trend", expected="a bool") - with_robust = checked_flag(robust, "robust", expected="a bool") - values, precision = expression_values(y, weights) + read = gene_set_data( + data, + group=group, + contrast=contrast, + design=design, + id_column=id_column, + feature_ids=feature_ids, + weights=weights, + trend=trend, + robust=robust, + function="camera", + ) + values, precision = read.values, read.precision + with_trend, with_robust = bool(read.trend), bool(read.robust) n_genes = values.shape[1] if n_genes < 3: raise ValueError( "camera compares the genes of each set with the other genes and needs at " f"least 3 genes, got {n_genes}" ) - members = _gene_set_members(gene_sets, gene_ids, n_genes) + members = _gene_set_members(gene_sets, read.ids, n_genes, read.ids_name) effects, mean_expression, d = _contrast_effects( - values, design, contrast, precision, "camera" + values, read.design, read.contrast, precision, "camera" ) residual = effects[:, 1:] variances = np.sum(residual**2, axis=1) / d @@ -304,8 +339,8 @@ def camera( covariate=mean_expression if with_trend else None, robust=with_robust, ) - prior_df = np.broadcast_to(squeezed.prior_df, variances.shape) - df_total = np.minimum(d + prior_df, float(d * n_genes)) + df_prior = np.broadcast_to(squeezed.df_prior, variances.shape) + df_total = np.minimum(d + df_prior, float(d * n_genes)) with np.errstate(divide="ignore", invalid="ignore"): t = effects[:, 0] / np.sqrt(squeezed.posterior_variance) z = _hill_z_scores(t, df_total) @@ -346,16 +381,18 @@ def camera( def fry( - y: Any, - design: Any, - contrast: Any, + data: Any, gene_sets: Mapping[str, Any], *, - gene_ids: npt.ArrayLike | None = None, + group: str | None = None, + contrast: Any = None, + design: Any = None, + id_column: str = "feature_id", + feature_ids: npt.ArrayLike | None = None, weights: npt.ArrayLike | None = None, standardize: str = "posterior_sd", - trend: bool = False, - robust: bool = False, + trend: bool | None = None, + robust: bool | None = None, ) -> Table: """Self-contained test of gene sets for one contrast, without rotations. @@ -365,17 +402,21 @@ def fry( et al. (2010) with the mean statistic over infinitely many rotations. The module docstring gives the method and the deliberate differences from limma. limma's gene contribution weights (``gene.weights``, or weights inside a set), which give - genes signs and sizes in the set statistic, are not implemented. + genes signs and sizes in the set statistic, are not implemented. A result of + :func:`~cytepool.limma_voom` or :func:`~cytepool.limma_trend` is tested as for + :func:`camera`. Args: - y: ``(n_samples, n_genes)`` log-expression values, finite, or a - :class:`~cytepool.stats.voom.VoomResult`, whose log-CPM values are tested - with its weights. - design: The design, as for :func:`camera`. - contrast: The contrast to test, as for :func:`camera`. + data: A differential-expression result, a VoomResult or log-expression + values, as for :func:`camera`. gene_sets: Mapping of set name to its genes, as for :func:`camera`. - gene_ids: One id per gene, unique, to name genes in ``gene_sets`` by id. - weights: Precision weights, as for :func:`camera`. + group: For a result, the group whose fit is tested, as for :func:`camera`. + contrast: The contrast to test, as for :func:`camera`. + design: For an array or a VoomResult, the design, as for :func:`camera`. + id_column: For a result, the column of its features whose ids the sets name. + feature_ids: For an array or a VoomResult, one id per gene, unique, to name + genes in ``gene_sets`` by id. + weights: For an array, precision weights, as for :func:`camera`. standardize: How each gene's effects are scaled before they are averaged: ``"posterior_sd"``, by the square root of a posterior variance that combines the prior of the empirical Bayes moderation with the mean square of the @@ -384,16 +425,18 @@ def fry( ``"residual.sd"``); or ``"none"``, unscaled, which suits genes of equal variance. trend: Whether the prior variance follows the mean log-expression of the - genes. Only with ``standardize="posterior_sd"``. + genes. Only with ``standardize="posterior_sd"``; None takes the + moderation of a result, and False otherwise. robust: Whether to estimate the prior robustly. Only with - ``standardize="posterior_sd"``. + ``standardize="posterior_sd"``; None takes the result's setting, and + False otherwise. Returns: A table with one row per gene set, in the order of ``gene_sets``: ``gene_set``; ``n_genes``; ``direction``, ``"up"`` or ``"down"`` (None where the p-value is NaN); ``p_value``, the two-sided directional p-value; ``adjusted_p_value``, Benjamini-Hochberg over the sets; ``mixed_p_value``, the non-directional - p-value; and ``adjusted_mixed_p_value``. A set with no genes has NaN p-values, + p-value; and ``mixed_adjusted_p_value``. A set with no genes has NaN p-values, and so, with ``standardize="residual_sd"``, has a set containing a gene whose d + 1 effects are zero but for at most one, which cannot be scaled. @@ -403,23 +446,44 @@ def fry( ValueError: As :func:`camera` (one gene suffices), if ``standardize`` is unknown, or if ``trend`` or ``robust`` is given with another standardization, which has no prior. + KeyError: As :func:`camera`. """ if not isinstance(standardize, str) or standardize not in _STANDARDIZATIONS: raise ValueError( "standardize must be one of " f"{', '.join(map(repr, _STANDARDIZATIONS))}, got {standardize!r}" ) - with_trend = checked_flag(trend, "trend", expected="a bool") - with_robust = checked_flag(robust, "robust", expected="a bool") - if (with_trend or with_robust) and standardize != "posterior_sd": + given_trend = ( + None if trend is None else checked_flag(trend, "trend", expected="a bool") + ) + given_robust = ( + None if robust is None else checked_flag(robust, "robust", expected="a bool") + ) + if (given_trend or given_robust) and standardize != "posterior_sd": raise ValueError( "trend and robust shape the prior of the posterior standard deviations, so " "they apply only with standardize='posterior_sd'" ) - values, precision = expression_values(y, weights) - members = _gene_set_members(gene_sets, gene_ids, values.shape[1]) + read = gene_set_data( + data, + group=group, + contrast=contrast, + design=design, + id_column=id_column, + feature_ids=feature_ids, + weights=weights, + trend=given_trend, + robust=given_robust, + function="fry", + ) + values, precision = read.values, read.precision + # A result's moderation shapes the prior, which only posterior_sd has. + posterior = standardize == "posterior_sd" + with_trend = posterior and bool(read.trend) + with_robust = posterior and bool(read.robust) + members = _gene_set_members(gene_sets, read.ids, values.shape[1], read.ids_name) effects, mean_expression, d = _contrast_effects( - values, design, contrast, precision, "fry" + values, read.design, read.contrast, precision, "fry" ) squares = effects**2 # The mean square of the d + 1 effects without the largest (found by probing). @@ -441,7 +505,7 @@ def fry( trimmed / _trimmed_mean_square_expectation(d), _TRIMMED_DF_FRACTION * d, np.broadcast_to(squeezed.prior_variance, trimmed.shape), - np.broadcast_to(squeezed.prior_df, trimmed.shape), + np.broadcast_to(squeezed.df_prior, trimmed.shape), ) ) with np.errstate(divide="ignore", invalid="ignore"): @@ -471,7 +535,7 @@ def fry( "p_value": p_value, "adjusted_p_value": adjust_p_values(p_value, "bh"), "mixed_p_value": mixed, - "adjusted_mixed_p_value": adjust_p_values(mixed, "bh"), + "mixed_adjusted_p_value": adjust_p_values(mixed, "bh"), } ) @@ -714,17 +778,18 @@ def _two_sided( def _gene_set_members( - gene_sets: Any, gene_ids: Any, n_genes: int + gene_sets: Any, gene_ids: Any, n_genes: int, ids_name: str ) -> list[tuple[str, IntArray]]: - """Each set's name and the sorted positions of its genes among the columns of y.""" - index = checked_gene_sets(gene_sets, gene_ids, n_genes) + """Each set's name and the sorted positions of its genes among the genes of the + data.""" + index = checked_gene_sets(gene_sets, gene_ids, n_genes, ids_name) members: list[tuple[str, IntArray]] = [] matched = 0 for name, genes in gene_sets.items(): positions, found = set_positions(genes, set_label(name), index, n_genes) matched += found members.append((name, positions)) - check_matched(index, matched) + check_matched(index, matched, ids_name) return members diff --git a/cytepool/gene_sets.py b/cytepool/gene_sets.py index 9024f47..7591471 100644 --- a/cytepool/gene_sets.py +++ b/cytepool/gene_sets.py @@ -1,23 +1,19 @@ """Collections of gene sets, read from and written to GMT files. -A :class:`GeneSets` maps the name of each gene set to its genes, named by gene id, and -keeps the description of each set and where the collection comes from: its source, -version and licence, as the user states them from what the provider of the -collection documents, and, for a collection read from a file, the name of the file -and its SHA-256 digest. CytePool bundles no collection: MSigDB (Liberzon et al. -2011), Reactome, WikiPathways and the Gene Ontology distribute their sets under their -own terms, and :func:`read_gmt` reads the files that users download from them. - -A ``GeneSets`` is a mapping of set name to genes, so every gene-set function of -CytePool takes it as it is: as ``gene_sets`` of -:func:`~cytepool.stats.gene_set_tests.camera`, -:func:`~cytepool.stats.gene_set_tests.fry`, -:func:`~cytepool.stats.rotation_gene_set_tests.mroast`, -:func:`~cytepool.stats.rotation_gene_set_tests.romer` and -:func:`~cytepool.over_representation.over_representation`, and one of its sets, as -``gene_sets[name]``, as ``gene_set`` of -:func:`~cytepool.stats.rotation_gene_set_tests.roast`. The tests of a linear model -match the ids to their ``gene_ids``. +A :class:`~cytepool.GeneSets` maps the name of each gene set to its genes, named by gene +id, and keeps the description of each set and where the collection comes from: its name, +version and licence, as the user states them from what the provider of the collection +documents, and, for a collection read from a file, the name of the file and its SHA-256 +digest. CytePool bundles no collection: MSigDB (Liberzon et al. 2011), Reactome, +WikiPathways and the Gene Ontology distribute their sets under their own terms, and +:func:`~cytepool.read_gmt` reads the files that users download from them. + +A ``GeneSets`` is a mapping of set name to genes, so every gene-set function of CytePool +takes it as it is: as ``gene_sets`` of :func:`~cytepool.camera`, :func:`~cytepool.fry`, +:func:`~cytepool.mroast`, :func:`~cytepool.romer` and +:func:`~cytepool.over_representation`, and one of its sets, as ``gene_sets[name]``, as +``gene_set`` of :func:`~cytepool.roast`. The tests match the genes of each set to the +column ``id_column`` of a result's features, or to the ``feature_ids`` of an array. Genes and sets: A set's genes are a tuple of gene ids (non-empty strings, compared exactly, case @@ -27,20 +23,20 @@ which GSEA writes for a set without one, or an empty description, is None. Matching and filtering: - :meth:`GeneSets.restrict_to` keeps the genes of each set that an analysis measured - and drops the sets left without genes, as limma's ``ids2indices`` does by default, - and refuses ids that match no gene at all, as when the sets name genes by symbol - and the analysis by Ensembl id. :meth:`GeneSets.filter_by_size` keeps the sets of a - range of sizes; restricted first, the sizes are those within the analysis, as - GSEA counts them before it leaves out sets of fewer than 15 or more than 500 - genes, its defaults (GSEA team 2026). :meth:`GeneSets.select` takes sets by name. - Each returns a new ``GeneSets`` with the descriptions and the record of the - source. + :meth:`~cytepool.GeneSets.restrict_to` keeps the genes of each set that an analysis + measured and drops the sets left without genes, as limma's ``ids2indices`` does by + default, and refuses ids that match no gene at all, as when the sets name genes by + symbol and the analysis by Ensembl id. :meth:`~cytepool.GeneSets.filter_by_size` + keeps the sets of a range of sizes; restricted first, the sizes are those within the + analysis, as GSEA counts them before it leaves out sets of fewer than 15 or more + than 500 genes, its defaults (GSEA team 2026). :meth:`~cytepool.GeneSets.select` + takes sets by name. Each returns a new ``GeneSets`` with the descriptions and the + record of the collection. GMT files: The GMT (gene matrix transposed) format of GSEA (Subramanian et al. 2005; GSEA team 2026) holds one gene set per line, in fields separated by tabs: the set's - name, a description, and the set's genes. :func:`read_gmt` reads: + name, a description, and the set's genes. :func:`~cytepool.read_gmt` reads: - fields separated by tabs only, so that names with spaces, such as Reactome's pathway names, are read whole; @@ -52,11 +48,11 @@ - UTF-8 text, with or without a byte-order mark, compressed by gzip or not (recognised by the first bytes of the file, whatever its name). - A line with one field, a set without a name, or a name used for two sets is - refused, with the number of the line. :func:`write_gmt` writes the same format, - with ``"na"`` for a set without a description, so that :func:`read_gmt` reads - back the same sets and descriptions. A GMT file holds no source, version or - licence; they are given again when the file is read. + A line with one field, a set without a name, or a name used for two sets is refused, + with the number of the line. :func:`~cytepool.write_gmt` writes the same format, + with ``"na"`` for a set without a description, so that :func:`~cytepool.read_gmt` + reads back the same sets and descriptions. A GMT file holds no name, version or + licence of the collection; they are given again when the file is read. References: Subramanian A, Tamayo P, Mootha VK, Mukherjee S, Ebert BL, Gillette MA, Paulovich @@ -102,7 +98,7 @@ class GeneSets(Mapping[str, tuple[str, ...]]): - """Named gene sets, with their descriptions and the source of the collection. + """Named gene sets, with their descriptions and where the collection comes from. A read-only mapping of set name to the set's genes, a tuple of gene ids, so that the gene-set functions of CytePool take it as their ``gene_sets``. The module @@ -114,7 +110,8 @@ class GeneSets(Mapping[str, tuple[str, ...]]): tuple, set or 1-D array of them. A gene listed twice counts once. descriptions: Mapping of set name to the set's description (a string), for some or all of the sets; the others have None. - source: Where the sets come from, such as ``"MSigDB hallmark gene sets"``. + name: The name of the collection, which says where the sets come from, + such as ``"MSigDB hallmark gene sets"``. version: The version or release of the collection, such as ``"2024.1.Hs"``. licence: The terms under which the provider distributes the sets, such as @@ -130,7 +127,7 @@ class GeneSets(Mapping[str, tuple[str, ...]]): is not in ``sets``, or ``sha256`` is not a SHA-256 digest. Example: - >>> sets = GeneSets({"interferon": ["ISG15", "IFIT1", "MX1"]}, source="my lab") + >>> sets = GeneSets({"interferon": ["ISG15", "IFIT1", "MX1"]}, name="my lab") >>> sets["interferon"] ('ISG15', 'IFIT1', 'MX1') """ @@ -138,7 +135,7 @@ class GeneSets(Mapping[str, tuple[str, ...]]): __slots__ = ( "_sets", "_descriptions", - "_source", + "_name", "_version", "_licence", "_file", @@ -147,7 +144,7 @@ class GeneSets(Mapping[str, tuple[str, ...]]): _sets: dict[str, tuple[str, ...]] _descriptions: dict[str, str | None] - _source: str | None + _name: str | None _version: str | None _licence: str | None _file: str | None @@ -158,7 +155,7 @@ def __init__( sets: Mapping[str, Any], *, descriptions: Mapping[str, Any] | None = None, - source: str | None = None, + name: str | None = None, version: str | None = None, licence: str | None = None, file: str | None = None, @@ -166,6 +163,7 @@ def __init__( ) -> None: reject_masked(sets, "sets") reject_masked(descriptions, "descriptions") + collection_name = name if not isinstance(sets, Mapping): raise TypeError( "sets must be a mapping of set name to genes, such as " @@ -190,7 +188,7 @@ def __init__( ) texts[name] = _description(text, f"the description of {name!r}") record = { - "source": _text(source, "source"), + "name": _text(collection_name, "name"), "version": _text(version, "version"), "licence": _text(licence, "licence"), "file": _text(file, "file"), @@ -252,9 +250,10 @@ def descriptions(self) -> Mapping[str, str | None]: return MappingProxyType(self._descriptions) @property - def source(self) -> str | None: - """Where the sets come from, as stated; None when not stated.""" - return self._source + def name(self) -> str | None: + """The name of the collection, which says where the sets come from, as + stated; None when not stated.""" + return self._name @property def version(self) -> str | None: @@ -397,7 +396,7 @@ def __repr__(self) -> str: stated = [ f"{label}: {value if value is not None else 'not stated'}" for label, value in ( - ("source", self._source), + ("name", self._name), ("version", self._version), ("licence", self._licence), ) @@ -419,7 +418,7 @@ def __repr__(self) -> str: def _record(self) -> dict[str, str | None]: return { - "source": self._source, + "name": self._name, "version": self._version, "licence": self._licence, "file": self._file, @@ -437,7 +436,7 @@ def _derived(self, sets: dict[str, tuple[str, ...]]) -> "GeneSets": def read_gmt( path: str | os.PathLike[str], *, - source: str | None = None, + name: str | None = None, version: str | None = None, licence: str | None = None, ) -> GeneSets: @@ -446,13 +445,13 @@ def read_gmt( One set per line: its name, a description and its genes, separated by tabs, as MSigDB, Reactome, WikiPathways and the GMT exports of the Gene Ontology write them; the module docstring says what is read and how. The collection records the - name of the file and the SHA-256 digest of its bytes, and the source, version and + name of the file and the SHA-256 digest of its bytes, and the name, version and licence given here, which the file cannot hold: state them as the provider documents them, so that results can say which sets they used. Args: path: The GMT file, compressed by gzip or not. - source: Where the sets come from, such as ``"MSigDB hallmark gene sets"``. + name: The name of the collection, such as ``"MSigDB hallmark gene sets"``. version: The version or release of the collection, such as ``"2024.1.Hs"``. licence: The terms under which the provider distributes the sets, such as ``"CC BY 4.0"``. @@ -470,7 +469,7 @@ def read_gmt( """ location = _file_path(path) record = { - "source": _text(source, "source"), + "name": _text(name, "name"), "version": _text(version, "version"), "licence": _text(licence, "licence"), } @@ -539,7 +538,7 @@ def write_gmt( One line per set, in order: its name, its description (``"na"`` without one) and its genes, separated by tabs, in UTF-8 with line feeds, compressed by gzip when the name of the file ends in ``.gz``. :func:`read_gmt` reads back the same sets - and descriptions. The source, version and licence of a ``GeneSets`` are not + and descriptions. The name, version and licence of a ``GeneSets`` are not written: GMT has no place for them. Args: @@ -585,7 +584,7 @@ def _initialize( ) -> None: object.__setattr__(collection, "_sets", sets) object.__setattr__(collection, "_descriptions", descriptions) - for key in ("source", "version", "licence", "file", "sha256"): + for key in ("name", "version", "licence", "file", "sha256"): object.__setattr__(collection, f"_{key}", record[key]) diff --git a/cytepool/held_out_prediction.py b/cytepool/held_out_studies.py similarity index 76% rename from cytepool/held_out_prediction.py rename to cytepool/held_out_studies.py index 83f02e7..a23a9e7 100644 --- a/cytepool/held_out_prediction.py +++ b/cytepool/held_out_studies.py @@ -1,25 +1,26 @@ """Prediction of case status in held-out studies, from genes chosen in the others. -Genes found by combining studies should separate cases from controls in a study that -had no part in finding them. :func:`held_out_prediction` measures this as the paper -that introduced SumRank does (Nakatsuka et al. 2025): each study of a collection is -left out in turn; genes up and genes down in each cell type are chosen from the other -studies, by SumRank (:func:`~cytepool.sumrank.sumrank`) and, for comparison, by a -random-effects meta-analysis (:func:`~cytepool.meta_analysis.meta_analysis`); every -sample of the held-out study is scored by how high its profiles rank those genes; and -the area under the ROC curve (AUC) of the score says how well it separates the -study's cases from its controls. :func:`~cytepool.plotting.plot_held_out_auc` draws -the AUC of every held-out study by method. +Genes found by combining studies should separate cases from controls in a study that had +no part in finding them. :func:`~cytepool.held_out_prediction` measures this as the +paper that introduced SumRank does (Nakatsuka et al. 2025): each study of a collection +is left out in turn; genes up and genes down in each cell type are chosen from the other +studies, by SumRank (:func:`~cytepool.stats.sumrank.fit_sumrank`) and, for comparison, +by a random-effects meta-analysis +(:func:`~cytepool.stats.meta_analysis.fit_meta_analysis`); every sample of the held-out +study is scored by how high its profiles rank those genes; and the area under the ROC +curve (AUC) of the score says how well it separates the study's cases from its controls. +:func:`~cytepool.plotting.plot_held_out_auc` draws the AUC of every held-out study by +method. Choosing the genes: In every group (cell type), from the studies other than the held-out one whose results test the group and contrast, and among the genes that every one of them - tested, ``n_genes`` genes up and ``n_genes`` down, the same number for each + tested, ``n_features`` genes up and ``n_features`` down, the same number for each method, as the paper compares methods by gene sets of one size: - ``"sumrank"``: the genes with the smallest ``up_statistic``, and those with the - smallest ``down_statistic``, below the cap, with the given proportion of the - studies summed; ties in the collection's order of the genes. Every gene has + smallest ``down_statistic``, below the cap, with the given number or proportion + of the studies summed; ties in the collection's order of the genes. Every gene has the same numbers of studies and ranks, so this is also the order of the permutation p-values, which therefore need no permutations here. - ``"meta_analysis"``: the genes with a positive estimate and the smallest @@ -54,7 +55,10 @@ The probability that a case scores higher than a control, ties counting one half: the Mann-Whitney statistic divided by the number of pairs of a case and a control (Hanley and McNeil 1982). The samples are those of the held-out study's - design whose value of ``variable`` is ``case`` or ``control``. 0.5 says the + design at one of the two levels that the contrast compares, cases at the level it + tests and controls at the other, such as ``case`` and ``control`` of + ``condition`` for the contrast ``conditioncase``; or those whose value of + ``variable`` is ``case`` or ``control``, when these are given. 0.5 says the score does not separate them, and below 0.5 the genes predict the wrong direction; the AUC is NaN, with the reason, for a study without a case or without a control. @@ -74,8 +78,8 @@ normalized expression, which weighs each cell's counts by its depth. Not done: - The proportion of studies is given, not chosen: the paper chose it as the one - whose genes predict best, which a loop over proportions gives. The paper's + The number of studies summed is given, not chosen: the paper chose the proportion + whose genes predict best, which a loop over the numbers gives. The paper's prediction of disease severity in cases (a linear regression on the score) is not done. @@ -106,17 +110,23 @@ import numpy as np import numpy.typing as npt -from ._analysis_records import software_versions +from ._analysis_records import only_study_key, software_versions from ._columns import text_column from ._masked_arrays import reject_masked from ._messages import fitted_lines, listing, quoted from ._options import index_integer from ._ranks import average_ranks -from ._study_statistics import StudyStatistics, study_statistics, studies_summed +from ._study_statistics import ( + StudyStatistics, + contrast_levels, + study_results, + study_statistics, +) from .cube import CubeCollection from .differential_expression import DifferentialExpressionResult -from .meta_analysis import meta_analysis -from .sumrank import sumrank +from .stats.meta_analysis import fit_meta_analysis +from .stats.sumrank import _studies_summed, fit_sumrank +from .study_results import StudyResults from .table import Table __all__ = ["HeldOutPrediction", "held_out_prediction"] @@ -141,13 +151,13 @@ class HeldOutPrediction: - ``auc``: one row per held-out study and method: ``study``, ``method``, ``auc`` (NaN where it cannot be computed), ``n_cases``, ``n_controls``, - ``n_groups`` (the groups whose genes scored the study), ``n_genes_up`` and - ``n_genes_down`` (the genes chosen, summed over the groups, that the study + ``n_groups`` (the groups whose genes scored the study), ``n_features_up`` and + ``n_features_down`` (the genes chosen, summed over the groups, that the study measured) and ``reason`` (why the AUC is NaN; empty otherwise); - ``scores``: one row per held-out study, method and sample of its design that is a case or a control: ``study``, ``method``, ``sample_id``, ``case`` (whether it is a case) and ``score``; - - ``genes``: the genes chosen for each held-out study from the other studies: + - ``features``: the genes chosen for each held-out study from the other studies: ``study`` (held out), ``method``, ``group_id``, ``direction`` (``"up"`` or ``"down"``), ``rank`` (from 1) and ``feature_id``. @@ -159,17 +169,17 @@ class HeldOutPrediction: ValueError: If ``provenance`` holds a NaN or an infinity. """ - __slots__ = ("_auc", "_scores", "_genes", "_record") + __slots__ = ("_auc", "_scores", "_features", "_record") _auc: Table _scores: Table - _genes: Table + _features: Table _record: str def __init__( - self, auc: Table, scores: Table, genes: Table, provenance: Mapping[str, Any] + self, auc: Table, scores: Table, features: Table, provenance: Mapping[str, Any] ) -> None: - for name, given in (("auc", auc), ("scores", scores), ("genes", genes)): + for name, given in (("auc", auc), ("scores", scores), ("features", features)): if not isinstance(given, Table): raise TypeError(f"{name} must be a Table, got {type(given).__name__}") if not isinstance(provenance, Mapping): @@ -179,7 +189,7 @@ def __init__( state = { "_auc": auc, "_scores": scores, - "_genes": genes, + "_features": features, # JSON holds no arrays: json.dumps raises TypeError for one. "_record": json.dumps(provenance, allow_nan=False), } @@ -195,7 +205,7 @@ def __delattr__(self, name: str) -> None: def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: return ( HeldOutPrediction, - (self._auc, self._scores, self._genes, json.loads(self._record)), + (self._auc, self._scores, self._features, json.loads(self._record)), ) @property @@ -209,9 +219,9 @@ def scores(self) -> Table: return self._scores @property - def genes(self) -> Table: + def features(self) -> Table: """The genes chosen from the other studies for every held-out study.""" - return self._genes + return self._features @property def settings(self) -> dict[str, Any]: @@ -238,7 +248,7 @@ def __repr__(self) -> str: f"HeldOutPrediction of {settings['case']!r} against {settings['control']!r}" f" ({settings['variable']}), contrast {settings['contrast']!r}: " f"{len(set(table['study'].tolist())):,} held-out studies, " - f"{settings['n_genes']:,} genes up and down per group" + f"{settings['n_features']:,} genes up and down per group" ] for method in settings["methods"]: values = table["auc"][(table["method"] == method) & ~np.isnan(table["auc"])] @@ -248,16 +258,16 @@ def __repr__(self) -> str: def held_out_prediction( - collection: CubeCollection, - results: Mapping[str, DifferentialExpressionResult], - contrast: str, - variable: str, - case: str, - control: str, + studies: StudyResults, + contrast: str | None = None, *, groups: str | Sequence[str] | None = None, - n_genes: int = 50, - proportion_of_studies: float = 1.0, + n_features: int = 50, + n_studies_summed: int | None = None, + proportion_of_studies: float | None = None, + variable: str | None = None, + case: str | None = None, + control: str | None = None, ) -> HeldOutPrediction: """Leave each study out, choose genes from the others, and predict its cases. @@ -267,49 +277,52 @@ def held_out_prediction( scored by how high its profiles rank those genes, as UCell scores them; and the AUC says how well the scores separate cases from controls. The module description gives each step. The results are those that - :func:`~cytepool.sumrank.differential_expression_sumrank` takes: each study's - analysis of its aligned cube. + :func:`~cytepool.sumrank` takes: each study's analysis of its aligned cube. Args: - collection: The collection whose aligned cubes the studies' results - analysed. - results: The differential-expression result of every study of the - collection, by study name, from - :func:`~cytepool.differential_expression.limma_voom` or - :func:`~cytepool.differential_expression.limma_trend`, whose tables - hold the standard errors of the meta-analysis. - contrast: The contrast that the studies tested, cases against controls. - variable: The variable of the studies' designs that tells cases from - controls, such as ``"condition"``. - case: Its value for a case, such as ``"AD"``. - control: Its value for a control. + studies: The differential-expression result of every study of a + collection, from :func:`~cytepool.limma_voom` or + :func:`~cytepool.limma_trend`, whose tables hold the standard errors of + the meta-analysis, as :func:`~cytepool.analyse_each_study` gives them. + contrast: The contrast that the studies tested, cases against controls; it + may be left out when they tested one. groups: A group id or a list of them, whose scores are summed; by default every group of the collection. A group that no other study tested gives a held-out study no genes. - n_genes: The number of genes chosen up, and down, in each group, by each + n_features: The number of genes chosen up, and down, in each group, by each method; at least 1. - proportion_of_studies: The proportion of the studies summed by SumRank, as - for :func:`~cytepool.sumrank.sumrank`. + n_studies_summed: The number of the other studies summed by SumRank, as for + :func:`~cytepool.sumrank`; by default every one. + proportion_of_studies: Instead of ``n_studies_summed``, the proportion of + the other studies summed by SumRank. + variable: The variable of the studies' designs that tells cases from + controls, such as ``"condition"``. By default, the factor whose two + levels the contrast compares in every study's design (a level against + the reference, or one level minus another), with ``case`` the level it + tests and ``control`` the other; give ``variable``, ``case`` and + ``control`` together otherwise. + case: The variable's value for a case, such as ``"AD"``. + control: Its value for a control. Returns: The AUC of every held-out study and method, the score of every sample, and the genes chosen. Raises: - TypeError: If an argument has the wrong type, or is or holds a numpy masked - array. - ValueError: If ``results`` does not give the result of every study of the - collection or a result did not analyse its study's aligned cube, the - contrast is an F-test, the results have no standard errors (dream's), a - study's design has no variable ``variable``, ``case`` equals + TypeError: If ``studies`` is not a ``StudyResults``, an argument has the + wrong type, or is or holds a numpy masked array. + ValueError: If a result did not analyse its study's aligned cube, the + contrast is left out and the studies tested several, the contrast is an + F-test, the results have no standard errors (dream's), some but not all + of ``variable``, ``case`` and ``control`` are given, none is given and + the contrast does not compare two levels of one factor in every study's + design, a study's design has no variable ``variable``, ``case`` equals ``control``, a group is listed twice, or an option is invalid. KeyError: If a group is not a group of the collection, or no study tested ``contrast``. """ - if not isinstance(collection, CubeCollection): - raise TypeError( - f"collection must be a CubeCollection, got {type(collection).__name__}" - ) + studies = study_results(studies, "held_out_prediction") + collection = studies.collection for name, value in ( ("contrast", contrast), ("variable", variable), @@ -317,21 +330,39 @@ def held_out_prediction( ("control", control), ): reject_masked(value, name) - if not isinstance(value, str): + if value is not None and not isinstance(value, str): raise TypeError(f"{name} must be a string, got {type(value).__name__}") + contrast = only_study_key(studies, contrast, "contrast", "contrasts") + given = [value is not None for value in (variable, case, control)] + if any(given) and not all(given): + raise ValueError( + "variable, case and control are given together, or none of them, to read " + "the two levels that the contrast compares from the studies' designs" + ) + if not any(given): + compared = contrast_levels(studies, contrast, collection.studies) + if compared is None: + raise ValueError( + f"contrast {contrast!r} does not compare two levels of one factor in " + "every study's design; give the variable that tells cases from " + "controls, and its values, as variable, case and control" + ) + variable, case, control = compared + assert variable is not None and case is not None and control is not None if case == control: raise ValueError( f"case and control are both {case!r}; give the two values of {variable!r} " "that the AUC compares" ) chosen = _groups(collection, groups) - count = _n_genes(n_genes) - studies_summed(proportion_of_studies, collection.n_studies) # checks it + count = _n_features(n_features) + # Checked here, against the studies other than one held out; a group that fewer + # studies tested is checked when its genes are chosen. + _studies_summed( + n_studies_summed, proportion_of_studies, max(1, collection.n_studies - 1) + ) # Every group's results, read once: the held-out studies take rows of them. - inputs = { - group: study_statistics(collection, results, group, contrast) - for group in chosen - } + inputs = {group: study_statistics(studies, group, contrast) for group in chosen} for group, found in inputs.items(): missing = found.analysed & np.isnan(found.standard_error).all(axis=1) if missing.any(): @@ -341,14 +372,15 @@ def held_out_prediction( f"{group!r}, which the meta-analysis reads: dream_voom's tables have " "none; give results of limma_voom or limma_trend" ) - studies = list(collection.studies) + summed = (n_studies_summed, proportion_of_studies) + names = list(collection.studies) rows: list[dict[str, Any]] = [] score_rows: list[dict[str, Any]] = [] gene_rows: list[dict[str, Any]] = [] feature_ids = collection.features["feature_id"] - for held, study in enumerate(studies): - samples = _held_out_samples(results[study], study, variable, case, control) - profiles = _profiles(collection, results[study], study, chosen) + for held, study in enumerate(names): + samples = _held_out_samples(studies[study], study, variable, case, control) + profiles = _profiles(collection, studies[study], study, chosen) for method in METHODS: sets = {} for group in chosen: @@ -357,9 +389,15 @@ def held_out_prediction( training[held] = False if not training.any(): continue - chosen_genes = _chosen_genes( - method, found, training, count, proportion_of_studies - ) + try: + chosen_genes = _chosen_genes(method, found, training, count, summed) + except ValueError as error: + error.add_note( + f"(choosing the genes of group {group!r} without study " + f"{study!r}, from the {int(training.sum())} other studies that " + "tested it)" + ) + raise sets[group] = chosen_genes for direction, positions in zip(_DIRECTIONS, chosen_genes, strict=True): for rank, position in enumerate(positions.tolist(), start=1): @@ -395,8 +433,15 @@ def held_out_prediction( "case": case, "control": control, "groups": chosen, - "n_genes": count, - "proportion_of_studies": float(proportion_of_studies), + "n_features": count, + "n_studies_summed": n_studies_summed, + "proportion_of_studies": ( + None + if n_studies_summed is not None + else 1.0 + if proportion_of_studies is None + else float(proportion_of_studies) + ), "methods": list(METHODS), "meta_analysis": dict(_META_ANALYSIS), "score": "UCell, maxRank = the number of genes ranked", @@ -426,7 +471,7 @@ def held_out_prediction( "analysis": "held_out_prediction", "versions": software_versions(), "settings": settings, - "studies": {name: results[name].provenance for name in studies}, + "studies": {name: studies[name].provenance for name in names}, }, ) @@ -466,12 +511,12 @@ def _groups(collection: CubeCollection, value: Any) -> list[str]: return listed -def _n_genes(value: Any) -> int: +def _n_features(value: Any) -> int: if isinstance(value, bool | np.bool_): - raise TypeError("n_genes must be a whole number of genes, not bool") - number = index_integer(value, "n_genes", expected="a whole number of genes") + raise TypeError("n_features must be a whole number of genes, not bool") + number = index_integer(value, "n_features", expected="a whole number of genes") if number < 1: - raise ValueError(f"n_genes must be at least 1, got {number}") + raise ValueError(f"n_features must be at least 1, got {number}") return number @@ -483,7 +528,7 @@ def _chosen_genes( found: StudyStatistics, training: npt.NDArray[np.bool_], count: int, - proportion: float, + summed: tuple[Any, Any], ) -> tuple[IntArray, IntArray]: """The positions, in the collection's features, of the genes chosen up and down from the training studies, best first.""" @@ -494,7 +539,9 @@ def _chosen_genes( return empty, empty chosen: list[IntArray] = [] if method == "sumrank": - ranking = sumrank(statistics, proportion_of_studies=proportion) + ranking = fit_sumrank( + statistics, n_studies_summed=summed[0], proportion_of_studies=summed[1] + ) cap = ranking.n_studies_summed / 2.0 for statistic in (ranking.up_statistic, ranking.down_statistic): candidates = np.flatnonzero(ranking.ranked & (statistic < cap)) @@ -503,7 +550,7 @@ def _chosen_genes( return chosen[0], chosen[1] effects = found.log2_fold_change[training][:, universe] errors = found.standard_error[training][:, universe] - combined = meta_analysis(effects, errors, method="reml", test="knapp_hartung") + combined = fit_meta_analysis(effects, errors, method="reml", test="knapp_hartung") positions = np.flatnonzero(universe) for sign in (1.0, -1.0): candidates = np.flatnonzero(sign * combined.estimate > 0.0) @@ -666,8 +713,8 @@ def _scores( n_groups += contributed return total, { "n_groups": n_groups, - "n_genes_up": measured["up"], - "n_genes_down": measured["down"], + "n_features_up": measured["up"], + "n_features_down": measured["down"], } @@ -696,7 +743,13 @@ def _auc_table(rows: list[dict[str, Any]]) -> Table: "method": text_column([r["method"] for r in rows]), "auc": np.array([r["auc"] for r in rows], dtype=np.float64), } - for name in ("n_cases", "n_controls", "n_groups", "n_genes_up", "n_genes_down"): + for name in ( + "n_cases", + "n_controls", + "n_groups", + "n_features_up", + "n_features_down", + ): columns[name] = np.array([r[name] for r in rows], dtype=np.int64) columns["reason"] = text_column([r["reason"] for r in rows]) return Table(columns) diff --git a/cytepool/io/__init__.py b/cytepool/io/__init__.py index d677053..d8cca11 100644 --- a/cytepool/io/__init__.py +++ b/cytepool/io/__init__.py @@ -1,6 +1,14 @@ """Reading and writing cube files; ``cytepool.io.anndata`` converts cubes to and from -AnnData and MuData.""" +AnnData and MuData, and ``cytepool.io.zarr`` reads and writes the Zarr v3 arrays of cube +files. -from .cube_files import read_cube, write_cube +:func:`cytepool.read_cube`, :func:`cytepool.write_cube`, :func:`cytepool.from_anndata`, +:func:`cytepool.to_anndata`, :func:`cytepool.from_mudata` and +:func:`cytepool.to_mudata` are documented at the top level. +""" -__all__ = ["read_cube", "write_cube"] +from .cube_files import read_cube as read_cube +from .cube_files import write_cube as write_cube + +# The names above are the top level's (cytepool.read_cube and cytepool.write_cube). +__all__: list[str] = [] diff --git a/cytepool/io/anndata.py b/cytepool/io/anndata.py index 07df5ae..867c9a8 100644 --- a/cytepool/io/anndata.py +++ b/cytepool/io/anndata.py @@ -55,8 +55,8 @@ Recognised layouts: The arguments left as None take their values from the layout of the data; a - long AnnData that CytePool did not make still needs ``sample=`` and ``group=``, - the columns of its labels. + long AnnData that CytePool did not make still needs ``sample_column=`` and + ``group_column=``, the columns of its labels. - CytePool's own export, which ``uns["cytepool"]`` marks (see "The record in uns" below): every layer, the cell numbers, the tables, the normalization and @@ -322,12 +322,12 @@ def to_anndata(cube: Cube, *, x_layer: str | None = None) -> Any: def from_anndata( adata: Any, *, - sample: str | None = None, - group: str | None = None, + sample_column: str | None = None, + group_column: str | None = None, layers: Mapping[str, str] | None = None, n_cells: Any = None, - sample_table: Any = None, - group_table: Any = None, + samples: Any = None, + groups: Any = None, normalization: Mapping[str, Any] | None = None, source: Mapping[str, Any] | None = None, ) -> Cube: @@ -343,11 +343,11 @@ def from_anndata( Args: adata: The AnnData. - sample: The ``obs`` column with the sample of each row, as text or integers - (which become their decimal text), as ``from_cells`` takes labels. - Required unless CytePool made the AnnData. - group: The ``obs`` column with the group, usually the cell type, of each - row. Required unless CytePool made the AnnData. + sample_column: The ``obs`` column with the sample of each row, as text or + integers (which become their decimal text), as ``from_cells`` takes + labels. Required unless CytePool made the AnnData. + group_column: The ``obs`` column with the group, usually the cell type, of + each row. Required unless CytePool made the AnnData. layers: Cube layer name to its source, ``"X"`` or the name of a layer, for example ``{"counts": "sum", "detected": "count_nonzero"}``. ``counts`` is required. By default the sources of the recognised layout. @@ -356,12 +356,12 @@ def from_anndata( such as ``pandas.crosstab(cells.obs["donor"], cells.obs["cell_type"])`` for the cells that were pooled. By default the column of the recognised layout, or none. - sample_table: Sample metadata with a text column ``sample_id`` that holds + samples: The sample table, with a text column ``sample_id`` that holds every sample label, as ``from_cells`` takes it: its rows set the order of the samples, and a sample without rows has no cells. By default the samples are the sorted distinct labels, with the ``obs`` columns that are constant within every sample, over the rows with cells. - group_table: Group metadata, used in the same way. By default the groups are + groups: The group table, used in the same way. By default the groups are the sorted distinct labels, with the other ``obs`` columns that are constant within every group. normalization: How the normalized values were computed, required with @@ -389,9 +389,10 @@ def from_anndata( constraint of ``Cube``; or ``uns["cytepool"]`` is not a record this version reads. """ - _check_arguments(layers, n_cells, sample_table, group_table, normalization) + _check_arguments(layers, n_cells, samples, groups, normalization) caller_source = source_record(source) - for argument, value in (("sample", sample), ("group", group)): + sample, group = sample_column, group_column + for argument, value in (("sample_column", sample), ("group_column", group)): if value is not None and not isinstance(value, str): raise TypeError( f"{argument} must name an obs column (str), got {type(value).__name__}" @@ -409,12 +410,14 @@ def from_anndata( if sample is None or group is None: raise ValueError( "name the obs columns that hold the sample and the group of each row, " - "with sample= and group=; obs has: " + "with sample_column= and group_column=; obs has: " f"{', '.join(map(str, obs_columns)) or '(no columns)'}" ) if sample == group: - raise ValueError(f"sample and group both name the obs column {sample!r}") - for argument, column in (("sample", sample), ("group", group)): + raise ValueError( + f"sample_column and group_column both name the obs column {sample!r}" + ) + for argument, column in (("sample_column", sample), ("group_column", group)): _require_column(obs_columns, column, argument) sources = _source_names(adata) if n_cells is None: @@ -433,7 +436,7 @@ def row_name(row: int) -> str: obs[group], len(names), (f"obs[{sample!r}]", f"obs[{group!r}]"), - _tables(record, sample_table, group_table), + _tables(record, samples, groups), row_name, ) if isinstance(n_cells, str): @@ -441,15 +444,15 @@ def row_name(row: int) -> str: else: rows.cells_from(n_cells) inferred: dict[str, list[str]] = {} - if record is None and (sample_table is None or group_table is None): + if record is None and (samples is None or groups is None): exclude = {sample, group, *_TOOL_COLUMNS} if isinstance(n_cells, str): exclude.add(n_cells) inferred = rows.infer_columns( obs, [name for name in obs_columns if name not in exclude], - infer_samples=sample_table is None, - infer_groups=group_table is None, + infer_samples=samples is None, + infer_groups=groups is None, ) features = _feature_table( adata.var, @@ -494,7 +497,7 @@ def read(name: str) -> np.ndarray: "group_column": group, "layers": mapping, "n_cells": _cells_origin(n_cells), - **rows.orders(sample_table, group_table), + **rows.orders(samples, groups), "obs_columns": inferred or None, }, } @@ -571,8 +574,8 @@ def from_mudata( *, layers: Mapping[str, str] | None = None, n_cells: Any = None, - sample_table: Any = None, - group_table: Any = None, + samples: Any = None, + groups: Any = None, normalization: Mapping[str, Any] | None = None, source: Mapping[str, Any] | None = None, ) -> Cube: @@ -598,11 +601,11 @@ def from_mudata( aligned by its labels, such as ``pandas.crosstab`` of the cells' sample and group labels. By default the cell numbers of the recognised layout, or none. - sample_table: Sample metadata with a text column ``sample_id`` that holds + samples: The sample table, with a text column ``sample_id`` that holds every sample, as in ``from_anndata``. By default the samples are the sorted distinct observation names, with the columns of the MuData's ``obs``. - group_table: Group metadata with a text column ``group_id`` that holds every + groups: The group table, with a text column ``group_id`` that holds every modality name. By default the groups are the sorted modality names. normalization: How the normalized values were computed, required with ``log_sum``, ``log_sum_sq`` or ``linear_sum`` (see ``Cube``). @@ -620,8 +623,9 @@ def from_mudata( ValueError: If the modalities hold different features, or for the reasons ``from_anndata`` gives. """ - _check_arguments(layers, n_cells, sample_table, group_table, normalization) + _check_arguments(layers, n_cells, samples, groups, normalization) caller_source = source_record(source) + sample_table, group_table = samples, groups modalities = _modalities(mdata) record = _read_record(mdata.uns, "mudata") names = [name for name, _ in modalities] @@ -823,8 +827,8 @@ def of( """ positions = [] for labels, name, (table, recorded), table_name, id_column in ( - (sample_labels, label_names[0], tables[0], "sample_table", _SAMPLE_ID), - (group_labels, label_names[1], tables[1], "group_table", _GROUP_ID), + (sample_labels, label_names[0], tables[0], "samples", _SAMPLE_ID), + (group_labels, label_names[1], tables[1], "groups", _GROUP_ID), ): try: positions.append( @@ -1441,8 +1445,8 @@ def _check_arguments( """Refuse masked arrays and wrong types in the arguments, before any reading.""" reject_masked(layers, "layers") reject_masked(n_cells, "n_cells") - reject_masked_table(sample_table, "sample_table", _SAMPLE_ID) - reject_masked_table(group_table, "group_table", _GROUP_ID) + reject_masked_table(sample_table, "samples", _SAMPLE_ID) + reject_masked_table(group_table, "groups", _GROUP_ID) reject_masked(normalization, "normalization", missing="None") if layers is not None and not isinstance(layers, Mapping): raise TypeError( diff --git a/cytepool/io/cube_files.py b/cytepool/io/cube_files.py index 3c2caa0..561d56e 100644 --- a/cytepool/io/cube_files.py +++ b/cytepool/io/cube_files.py @@ -176,7 +176,7 @@ def write_cube( path: str | os.PathLike[str], *, compression: str | None = "gzip", - level: int | None = None, + compression_level: int | None = None, overwrite: bool = False, ) -> None: """Save a cube as a cube file, a Zarr v3 group (see the module documentation). @@ -193,8 +193,8 @@ def write_cube( parent must exist. compression: ``"gzip"`` (the default), ``"zstd"`` (Python 3.14 or newer) or None for no compression. - level: The compression level: 0 to 9 for gzip (default 4), -131072 to 22 for - zstd (default 3); None without compression. + compression_level: The compression level: 0 to 9 for gzip (default 4), + -131072 to 22 for zstd (default 3); None without compression. overwrite: Replace what is at ``path``, the whole directory, if it is a cube file (a Zarr group whose ``format`` is ``"cytepool.cube"``) or an empty directory. Nothing else is ever replaced, nor the working directory or @@ -202,8 +202,8 @@ def write_cube( Raises: TypeError: If ``cube`` is not a Cube, or an argument has the wrong type. - ValueError: If ``compression`` or ``level`` is invalid, ``path`` is a URL, - or a lazy layer gives a value that breaks a constraint. + ValueError: If ``compression`` or ``compression_level`` is invalid, ``path`` + is a URL, or a lazy layer gives a value that breaks a constraint. OverflowError: If a sum of a derived lazy layer does not fit (see "Sums" in ``Cube``). FileExistsError: If ``path`` exists and ``overwrite`` is False, or it is not @@ -214,7 +214,7 @@ def write_cube( raise TypeError(f"write_cube() saves a Cube, got {type(cube).__name__}") if not isinstance(overwrite, bool): raise TypeError(f"overwrite must be True or False, got {overwrite!r}") - level = check_compression(compression, level) + level = check_compression(compression, compression_level) target = local_path(path) if not target.parent.is_dir(): raise FileNotFoundError(f"the directory {target.parent} does not exist") @@ -410,7 +410,7 @@ def _write_store( n_cells, chunks=(max(rows, 1), max(cube.n_groups, 1)), compression=compression, - level=level, + compression_level=level, dimension_names=["samples", "groups"], ) write_group(root / "layers") @@ -424,7 +424,7 @@ def _write_store( cube.n_samples, cube.n_features, groups.dtype.itemsize ), compression=compression, - level=level, + compression_level=level, dimension_names=list(AXES), ) digests[name] = groups.digests() @@ -471,7 +471,7 @@ def _write_table( column, chunks=chunks, compression=compression, - level=level, + compression_level=level, attributes=attributes, dimension_names=[axis], ) diff --git a/cytepool/io/zarr.py b/cytepool/io/zarr.py index ce1a453..0318a81 100644 --- a/cytepool/io/zarr.py +++ b/cytepool/io/zarr.py @@ -542,7 +542,7 @@ def write_array( *, chunks: Sequence[int], compression: str | None = "gzip", - level: int | None = None, + compression_level: int | None = None, attributes: Mapping[str, Any] | None = None, dimension_names: Sequence[str | None] | None = None, ) -> None: @@ -559,7 +559,7 @@ def write_array( ``ZarrArray``. It is read one row of chunks at a time. chunks: The chunk shape: a positive length for each dimension. compression: ``"gzip"``, ``"zstd"`` or None (see ``check_compression``). - level: The compression level, or None for the default. + compression_level: The compression level, or None for the default. attributes: JSON-serialisable attributes. dimension_names: A name or None for each dimension. @@ -611,7 +611,7 @@ def write_array( f"({MAX_CHUNK_BYTES:,} bytes), which open_array refuses; choose smaller " "chunks" ) - level = check_compression(compression, level) + level = check_compression(compression, compression_level) document: dict[str, Any] = { "zarr_format": 3, "node_type": "array", @@ -642,28 +642,34 @@ def write_array( _write_new_file(directory / METADATA_FILE, text) -def check_compression(compression: str | None, level: int | None) -> int | None: - """Check a compression and level for ``write_array`` and return the level used. +def check_compression( + compression: str | None, compression_level: int | None +) -> int | None: + """Check a compression and its level for ``write_array`` and return the level + used. Args: compression: ``"gzip"``, ``"zstd"`` (Python 3.14 or newer) or None for no compression. - level: 0 to 9 for gzip, -131072 to 22 for zstd (0 asks zstd for its default), - or None for ``DEFAULT_LEVELS`` (4 for gzip, 3 for zstd). It must be None - without compression. + compression_level: 0 to 9 for gzip, -131072 to 22 for zstd (0 asks zstd for + its default), or None for ``DEFAULT_LEVELS`` (4 for gzip, 3 for zstd). It + must be None without compression. Returns: The compression level, or None without compression. Raises: - TypeError: If ``compression`` is not a string or None, or ``level`` is not an - integer. - ValueError: If ``compression`` is unknown or unavailable, or ``level`` is out - of range or given without compression. + TypeError: If ``compression`` is not a string or None, or + ``compression_level`` is not an integer. + ValueError: If ``compression`` is unknown or unavailable, or + ``compression_level`` is out of range or given without compression. """ + level = compression_level if compression is None: if level is not None: - raise ValueError("level applies only with compression 'gzip' or 'zstd'") + raise ValueError( + "compression_level applies only with compression 'gzip' or 'zstd'" + ) return None if not isinstance(compression, str): raise TypeError( @@ -682,7 +688,9 @@ def check_compression(compression: str | None, level: int | None) -> int | None: if level is None: return DEFAULT_LEVELS[compression] if not isinstance(level, int | np.integer) or isinstance(level, bool): - raise TypeError(f"level must be an integer, got {type(level).__name__}") + raise TypeError( + f"compression_level must be an integer, got {type(level).__name__}" + ) low, high = _LEVEL_RANGES[compression] if not low <= level <= high: raise ValueError( diff --git a/cytepool/meta_analysis.py b/cytepool/meta_analysis.py deleted file mode 100644 index 9b375ac..0000000 --- a/cytepool/meta_analysis.py +++ /dev/null @@ -1,1599 +0,0 @@ -"""Meta-analysis of the effects of many features across studies, as metafor computes it. - -:func:`meta_analysis` combines, for every feature (gene), the estimates of one effect -in several studies, each with its standard error, into an estimate of a common effect, -with the between-study variance and the heterogeneity statistics, as metafor's -``rma.uni`` does for one feature without moderators (Viechtbauer 2010). -:func:`differential_expression_meta_analysis` does it for the differential-expression -results of several cubes, such as the same cell types in several cohorts, tissues or -platforms, matched by group, contrast and feature id. :func:`align_study_results` -lines up the results of the studies of a :class:`~cytepool.cube.CubeCollection` for -one group and contrast, feature by feature, with the reason a study has no result for -a feature: it did not measure it, did not test it, or did not analyse the group or -contrast. - -The model: - Study i estimates the effect of a feature by y_i, with standard error s_i, and its - sampling variance v_i = s_i^2 is taken as known. In the random-effects model the - true effects of the studies vary around a mean mu with the between-study variance - tau^2: y_i = mu + u_i + e_i, with u_i ~ N(0, tau^2) and e_i ~ N(0, v_i) - (DerSimonian and Laird 1986). With the weights w_i = 1 / (v_i + tau^2), mu is - estimated by mu_hat = sum w_i y_i / sum w_i, with standard error - 1 / sqrt(sum w_i). The fixed-effect model, which assumes one true effect (metafor's - equal-effects model, ``method = "EE"``), is the case tau^2 = 0: the - inverse-variance weighted mean. - -The between-study variance (``method``), over the k studies that measured a feature: - - ``"reml"``, metafor's default: the restricted maximum likelihood estimate - (Viechtbauer 2005), the tau^2 >= 0 that maximizes - -1/2 sum log(v_i + tau^2) - 1/2 log(sum w_i) - 1/2 sum w_i (y_i - mu_hat)^2. - This function can have more than one maximum, as when an imprecise study lies - far from precise ones that agree. Its maxima are zero, where the score is not - positive there, and the roots where the score, sum w_i^2 (y_i - mu_hat)^2 - - tr(P), with P = W - w w' / sum w_i for the diagonal matrix W of the weights, - falls from positive to negative. The score is negative beyond - sum (y_i - y_bar)^2 / (k - 1) + 2 max(v_i). The roots are bracketed by the - signs of the score at 8 points per decade of tau^2, from 1e-3 min(v_i) to twice - that bound, so that rounding cannot hide one near the bound; each is found by - Newton's method with the observed information, kept in its bracket by - bisection, and the estimate is the maximum with the largest restricted - likelihood. - - ``"dl"``: the DerSimonian and Laird (1986) estimate, the method of moments - with the weights 1 / v_i: max(0, (Q - (k - 1)) / (sum w_i - sum w_i^2 / - sum w_i)), with Q below. - - ``"pm"``: the Paule and Mandel (1982) estimate, at which the generalized Q - statistic sum w_i (y_i - mu_hat)^2 equals its expectation k - 1, or 0 where Q - is at most k - 1. The statistic falls with tau^2 and is convex, and the root - lies between sum (y_i - y_bar)^2 / (k - 1) minus the largest and minus the - smallest v_i, so Newton's method from the lower end converges to it from below. - - ``"fixed"``: tau^2 = 0. - -Tests and confidence intervals (``test``): - - ``"z"``: z = mu_hat / SE against the standard normal distribution, and the - interval mu_hat +/- z_(1 - alpha / 2) SE, as metafor's default ``test = "z"``. - - ``"knapp_hartung"``: the standard error is multiplied by sqrt(q), with - q = sum w_i (y_i - mu_hat)^2 / (k - 1), and the statistic is referred to a t - distribution with k - 1 degrees of freedom (Hartung 1999; Sidik and Jonkman - 2002; Knapp and Hartung 2003; metafor's ``test = "knha"``), which metafor's - documentation recommends for few studies. Where q < 1, as when the studies - agree more closely than their standard errors imply, the adjusted standard - error is smaller than the unadjusted one; with the fixed-effect model it is - the standard error scaled by sqrt(Q / (k - 1)), which metafor computes with a - warning that the method is not meant for that model. - -Heterogeneity: - Cochran's (1954) Q = sum (y_i - mu_fixed)^2 / v_i, with the fixed-effect estimate - mu_fixed, and its p-value from the chi-squared distribution with k - 1 degrees of - freedom. I^2 and H^2 are those metafor reports: I^2 = tau^2 / (tau^2 + s^2) and - H^2 = (tau^2 + s^2) / s^2, where s^2 = (k - 1) sum w_i / ((sum w_i)^2 - - sum w_i^2), with w_i = 1 / v_i, is the typical within-study variance (Higgins and - Thompson 2002, equation 9), so that they follow the estimate of tau^2. For the - fixed-effect model they are I^2 = max(0, (Q - (k - 1)) / Q) and H^2 = Q / (k - 1), - the conventional definitions, which equal the others for DerSimonian-Laird where - Q > k - 1; where Q <= k - 1, I^2 is 0 for both, and H^2 is Q / (k - 1) for the - fixed effect and 1 for DerSimonian-Laird. I^2 is a proportion here; metafor - reports it in percent. - -Studies that did not measure a feature: - NaN marks them, in the effect or the standard error. Each feature is analysed - over the k studies that measured it, as metafor leaves out studies with missing - values. A feature measured in one study has that study's estimate and standard - error and its z-test, also with ``"knapp_hartung"``, which needs two studies; - tau^2 = 0, Q = 0 with a p-value of 1, I^2 = 0 and H^2 = 1: metafor's results for - one study (found by probing). A feature measured in no study has NaN. - -Accuracy: - Every estimate is computed for the deviations of the effects from their mean and - the standard errors divided, exactly, by a power of two near the largest of them, - so that the results do not depend on the scale of the data, and no square - overflows or underflows however much larger the spread of the effects is than - their standard errors. The sums sum w_i - sum w_i^2 / sum w_i and tr(P^2), which - cancel when one study is far more precise than the others, are formed from the - sum of the weights of the other studies for each study, which does not cancel. - -Deliberate differences from metafor (``validation/meta_analysis.py`` reports them): - - The REML estimate is the largest maximum of the restricted likelihood. metafor - finds a maximum by Fisher scoring from the Hedges estimate, - max(0, sum (y_i - y_bar)^2 / (k - 1) - mean(v_i)), and replaces it by zero, with - a warning that Fisher scoring "may have gotten stuck at a local maximum", where - the restricted likelihood is larger at zero (found by probing). Where the - likelihood has another, larger maximum, metafor's estimate is the smaller one or - zero. Fisher scoring also oscillates without converging where the observed - information exceeds twice the expected one: metafor then stops with an error, - or, at its default threshold, within the oscillation, and its help page's remedy - ``control = list(stepadj = 0.5)`` converges to the estimate here. - - The iterative estimates converge to rounding. metafor stops REML when tau^2 - changes by less than 1e-5 from one step to the next, and Paule-Mandel at the - tolerance of R's ``uniroot``, about 1.2e-4 on tau^2; ``control = list(threshold - = 1e-13)`` and ``control = list(tol = 1e-14)`` make it converge to the values - here. - - Where the restricted likelihood is largest at zero although the Hedges estimate - is positive, the REML estimate is zero; metafor's step halving approaches zero - and stops near its threshold, at about 1e-6 by default (found by probing). - - metafor's Paule-Mandel search stops with an error when the root lies above - max(100, 10 mad(y)^2); here it has no upper limit. - - Standard errors must be positive and finite where they are given. For a zero - standard error metafor stops (fixed effect, DerSimonian-Laird, Paule-Mandel) or - fits without Q, I^2 and H^2 (REML); for an infinite one it stops. - -References: - 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 - - Cochran WG (1954). The combination of estimates from different experiments. - Biometrics 10, 101-129. doi:10.2307/3001666 - - DerSimonian R, Laird N (1986). Meta-analysis in clinical trials. Controlled Clinical - Trials 7, 177-188. doi:10.1016/0197-2456(86)90046-2 - - Hartung J (1999). An alternative method for meta-analysis. Biometrical Journal 41, - 901-916. doi:10.1002/(SICI)1521-4036(199912)41:8<901::AID-BIMJ901>3.0.CO;2-W - - Higgins JPT, Thompson SG (2002). Quantifying heterogeneity in a meta-analysis. - Statistics in Medicine 21, 1539-1558. doi:10.1002/sim.1186 - - Knapp G, Hartung J (2003). Improved tests for a random effects meta-regression with - a single covariate. Statistics in Medicine 22, 2693-2710. doi:10.1002/sim.1482 - - Paule RC, Mandel J (1982). Consensus values and weighting factors. Journal of - Research of the National Bureau of Standards 87, 377-385. doi:10.6028/jres.087.022 - - Sidik K, Jonkman JN (2002). A simple confidence interval for meta-analysis. - Statistics in Medicine 21, 3153-3159. doi:10.1002/sim.1262 - - Viechtbauer W (2005). Bias and efficiency of meta-analytic variance estimators in - the random-effects model. Journal of Educational and Behavioral Statistics 30, - 261-293. doi:10.3102/10769986030003261 - - Viechtbauer W (2010). Conducting meta-analyses in R with the metafor package. - Journal of Statistical Software 36(3), 1-48. doi:10.18637/jss.v036.i03 -""" - -import json -import math -import warnings -from collections.abc import Mapping, Sequence -from dataclasses import dataclass -from types import MappingProxyType -from typing import Any, Literal - -import numpy as np -import numpy.typing as npt - -from ._analysis_records import only_key, software_versions -from ._columns import text_column -from ._jit import jit -from ._masked_arrays import read_only, reject_masked, unmasked_array -from ._messages import PACKAGE_DIRECTORY, fitted_lines, listing, quoted -from ._options import checked_choice, index_integer, real_number -from .cube import CubeCollection -from .differential_expression import DifferentialExpressionResult -from .stats.distributions import ( - chi2_sf_scalar, - norm_isf, - norm_sf_scalar, - t_isf, - t_sf_scalar, -) -from .stats.multiple_testing import adjust_p_values -from .table import Table - -__all__ = [ - "MetaAnalysisFit", - "MetaAnalysisResult", - "align_study_results", - "differential_expression_meta_analysis", - "meta_analysis", -] - -FloatArray = npt.NDArray[np.float64] -IntArray = npt.NDArray[np.int64] -BoolArray = npt.NDArray[np.bool_] - -_METHODS = {"reml": 0, "dl": 1, "pm": 2, "fixed": 3} -_REML, _DERSIMONIAN_LAIRD, _PAULE_MANDEL, _FIXED = 0, 1, 2, 3 -_TESTS = ("z", "knapp_hartung") -# The rows of the compiled routine's output, in order. -_OUTPUTS = ( - "estimate", - "standard_error", - "statistic", - "p_value", - "ci_lower", - "ci_upper", - "tau_squared", - "q_statistic", - "q_p_value", - "i_squared", - "h_squared", -) -# The iterations of REML and Paule-Mandel stop when a step changes tau^2 by at most -# _TOLERANCE times tau^2 + s^2, s^2 the typical within-study variance, or after -# _MAX_ITERATIONS steps. -_TOLERANCE = 1e-13 -_MAX_ITERATIONS = 1000 -# The signs of the restricted score are read on a grid of this many points per decade -# of tau^2, to bracket every maximum of the restricted likelihood. -_GRID_PER_DECADE = 8 -_ANALYSED = "analysed" # the status of a group and contrast that was meta-analysed -# Why a study has no result for a feature, as align_study_results gives it. -_NOT_TESTED = ( - "the study measured the feature and tested the group and contrast, but not the " - "feature, as when its expression filter left the feature out" -) - - -@dataclass(frozen=True, eq=False, repr=False) -class MetaAnalysisFit: - """Meta-analyses of many features, from :func:`meta_analysis`. - - The per-feature arrays have the shape of the effects without their first (study) - axis: one value per feature, or 0-d arrays for the effects of one feature given - as a vector. They are read-only. - - Attributes: - estimate: The estimate of the common effect mu (metafor's ``beta``): the - weighted mean of the studies' effects, with the weights - 1 / (v_i + tau^2). - standard_error: Its standard error (``se``), adjusted by Knapp and Hartung - with ``test="knapp_hartung"``. - statistic: ``estimate / standard_error`` (``zval``): a z-statistic, or with - ``test="knapp_hartung"`` a t-statistic on ``n_studies - 1`` degrees of - freedom. - p_value: Its two-sided p-value (``pval``). - ci_lower: The lower limit of the confidence interval of the estimate at - ``level`` (``ci.lb``). - ci_upper: The upper limit (``ci.ub``). - tau_squared: The estimate of the between-study variance tau^2 (``tau2``); 0 - for the fixed-effect model. - q_statistic: Cochran's Q, the heterogeneity statistic of the fixed-effect - fit (``QE``). - q_p_value: Its p-value, from the chi-squared distribution with - ``n_studies - 1`` degrees of freedom (``QEp``). - i_squared: I^2, as a proportion in [0, 1] (metafor's ``I2`` is in percent). - h_squared: H^2 (``H2``). - n_studies: The number of studies that measured each feature (``k``). - converged: Whether the iterations of REML or Paule-Mandel met their - convergence criterion; True for the other methods. - method: ``"reml"``, ``"dl"``, ``"pm"`` or ``"fixed"``. - test: ``"z"`` or ``"knapp_hartung"``. - level: The confidence level of the intervals. - """ - - estimate: FloatArray - standard_error: FloatArray - statistic: FloatArray - p_value: FloatArray - ci_lower: FloatArray - ci_upper: FloatArray - tau_squared: FloatArray - q_statistic: FloatArray - q_p_value: FloatArray - i_squared: FloatArray - h_squared: FloatArray - n_studies: IntArray - converged: BoolArray - method: str - test: str - level: float - - def __repr__(self) -> str: - studies = self.n_studies[self.n_studies > 0] - span = f"{studies.min()} to {studies.max()}" if studies.size else "none" - return ( - f"MetaAnalysisFit(n_features={self.estimate.size}, method={self.method!r}, " - f"test={self.test!r}, studies per feature: {span})" - ) - - -def meta_analysis( - effects: npt.ArrayLike, - standard_errors: npt.ArrayLike, - *, - method: Literal["reml", "dl", "pm", "fixed"] = "reml", - test: Literal["z", "knapp_hartung"] = "z", - level: float = 0.95, -) -> MetaAnalysisFit: - """Meta-analyse the effects of every feature across studies, as metafor's rma.uni. - - For every feature, the effects of the studies that measured it are combined into - an estimate of a common effect by the fixed-effect or a random-effects model, with - the between-study variance tau^2, Cochran's Q and its p-value, I^2 and H^2, as - ``rma.uni(yi, sei = ..., method = ..., test = ...)`` gives them; the module - description gives the model, the estimators of tau^2 and the tests. - - Args: - effects: ``(n_studies, n_features)`` estimates of the effect of every feature - in every study, such as log fold changes, or ``(n_studies,)`` for one - feature. Finite, or NaN where a study did not measure a feature. - standard_errors: Their standard errors, of the same shape: positive and - finite, or NaN where a study did not measure a feature. A study is left - out of a feature's analysis where its effect or its standard error is NaN. - method: The estimator of the between-study variance: ``"reml"`` (restricted - maximum likelihood, metafor's default), ``"dl"`` (DerSimonian-Laird), - ``"pm"`` (Paule-Mandel), or ``"fixed"`` for the fixed-effect model. - test: ``"z"`` for z-tests and normal confidence intervals, or - ``"knapp_hartung"`` for the Knapp-Hartung adjustment, with t-tests on - ``n_studies - 1`` degrees of freedom. - level: The confidence level of the intervals, in (0, 1). metafor takes it in - percent (``level = 95``). - - Returns: - The estimate, standard error, statistic, p-value and confidence interval of - every feature, with tau^2, Q and its p-value, I^2, H^2 and the number of - studies. - - Raises: - TypeError: If ``effects`` or ``standard_errors`` does not hold real numbers or - is a numpy masked array, or an option has the wrong type. - ValueError: If the shapes differ or are not 1-D or 2-D, there is no study, an - effect or a standard error is infinite, a standard error is not positive, - ``method`` or ``test`` is unknown, or ``level`` is not in (0, 1). - """ - chosen_method = checked_choice(method, "method", tuple(_METHODS)) - chosen_test = checked_choice(test, "test", _TESTS) - confidence = _level(level) - y = _study_values(effects, "effects") - se = _study_values(standard_errors, "standard_errors") - if y.shape != se.shape: - raise ValueError( - f"effects and standard_errors must have the same shape, got {y.shape} and " - f"{se.shape}; both are (n_studies, n_features)" - ) - if y.ndim not in (1, 2): - raise ValueError( - "effects must be (n_studies, n_features), or (n_studies,) for one " - f"feature, got {y.ndim} dimensions" - ) - if y.shape[0] == 0: - raise ValueError("there are no studies: effects has no rows") - _check_values(y, se) - n_studies = y.shape[0] - by_feature = np.ascontiguousarray(y.reshape(n_studies, -1).T) - errors = np.ascontiguousarray(se.reshape(n_studies, -1).T) - n_features = by_feature.shape[0] - - alpha = 1.0 - confidence - z_critical = float(norm_isf(alpha / 2.0)) - t_critical = np.full(n_studies, np.nan) - if n_studies > 1: - t_critical[1:] = t_isf(alpha / 2.0, np.arange(1.0, n_studies)) - results = np.empty((len(_OUTPUTS), n_features)) - counts = np.empty(n_features, dtype=np.int64) - converged = np.empty(n_features, dtype=np.bool_) - _meta_analyse( - by_feature, - errors, - _METHODS[chosen_method], - chosen_test == "knapp_hartung", - z_critical, - t_critical, - results, - counts, - converged, - ) - shape = y.shape[1:] - arrays = { - name: read_only(results[row].reshape(shape)) - for row, name in enumerate(_OUTPUTS) - } - return MetaAnalysisFit( - **arrays, - n_studies=read_only(counts.reshape(shape)), - converged=read_only(converged.reshape(shape)), - method=chosen_method, - test=chosen_test, - level=confidence, - ) - - -# Checks of the arguments. - - -def _study_values(values: Any, name: str) -> FloatArray: - array = unmasked_array(values, name, missing="NaN") - if array.dtype.kind not in "iuf": - raise TypeError(f"{name} must hold real numbers, got dtype {array.dtype}") - converted: FloatArray = np.array(array, dtype=np.float64) - return converted - - -def _check_values(y: FloatArray, se: FloatArray) -> None: - infinite = np.isinf(y) - if infinite.any(): - position = tuple(int(i) for i in np.argwhere(infinite)[0]) - raise ValueError( - f"effects must be finite, or NaN where a study did not measure a feature; " - f"found {np.count_nonzero(infinite)} infinite, such as {y[position]} at " - f"{position}" - ) - invalid = ~np.isnan(se) & ~((se > 0.0) & np.isfinite(se)) - if invalid.any(): - position = tuple(int(i) for i in np.argwhere(invalid)[0]) - raise ValueError( - "standard_errors must be positive and finite, or NaN where a study did " - f"not measure a feature; found {np.count_nonzero(invalid)} that are not, " - f"such as {se[position]} at {position}" - ) - - -def _level(value: Any) -> float: - # real_number would refuse a masked value too, but without saying how to fix it. - reject_masked(value, "level") - number = real_number(value, "level", finite=True) - if not 0.0 < number < 1.0: - raise ValueError( - f"level must be in (0, 1), such as 0.95 for 95% confidence intervals, got " - f"{number}" - ) - return number - - -# The meta-analyses, compiled. Each routine works on the k studies that measured a -# feature, held in the first k entries of its arrays: the deviations of the effects -# from their unweighted mean (d) and the sampling variances (v), at unit scale. - - -@jit(error_model="numpy") -def _sums_without_each(w: FloatArray, k: int, without: FloatArray) -> float: - """Set ``without[i]`` to the sum of ``w`` over the studies other than i, and - return the sum over all. The partial sums before and after i hold no term larger - than their total, so nothing cancels, as it would in ``total - w[i]``.""" - total = 0.0 - for i in range(k): - without[i] = total - total += w[i] - after = 0.0 - for i in range(k - 1, -1, -1): - without[i] += after - after += w[i] - return total - - -@jit(error_model="numpy") -def _fixed_effect( - d: FloatArray, v: FloatArray, k: int, w: FloatArray, without: FloatArray -) -> tuple[float, float]: - """Cochran's Q and c = sum w - sum w^2 / sum w, for the weights w = 1 / v.""" - for i in range(k): - w[i] = 1.0 / v[i] - total = _sums_without_each(w, k, without) - centre = 0.0 - for i in range(k): - centre += w[i] * d[i] - centre /= total - q = 0.0 - c = 0.0 - for i in range(k): - r = d[i] - centre - q += w[i] * r * r - c += w[i] * (without[i] / total) # the ratio is at most 1: no overflow - return q, c - - -@jit(error_model="numpy") -def _restricted_score( - d: FloatArray, - v: FloatArray, - k: int, - tau2: float, - w: FloatArray, - w2: FloatArray, - without: FloatArray, - without2: FloatArray, -) -> tuple[float, float]: - """Twice the score of the restricted log-likelihood at tau2, sum w^2 r^2 - tr(P), - and twice its observed information, 2 sum w^3 r^2 - 2 (sum w^2 r)^2 / sum w - - tr(P^2), the derivative of the score with its sign changed.""" - for i in range(k): - w[i] = 1.0 / (v[i] + tau2) - w2[i] = w[i] * w[i] - total = _sums_without_each(w, k, without) - _sums_without_each(w2, k, without2) - centre = 0.0 - for i in range(k): - centre += w[i] * d[i] - centre /= total - squares = 0.0 - cubes = 0.0 - linear = 0.0 - trace = 0.0 - trace_squared = 0.0 - for i in range(k): - weighted = w[i] * (d[i] - centre) # w r, squared without underflow - squares += weighted * weighted - cubes += w[i] * weighted * weighted - linear += w[i] * weighted - # P_ii = w_i (sum of the other weights) / total, P_ij = -w_i w_j / total. - diagonal = w[i] * (without[i] / total) - trace += diagonal - trace_squared += diagonal * diagonal + (w[i] / total) ** 2 * without2[i] - observed = 2.0 * cubes - 2.0 * linear * linear / total - trace_squared - return squares - trace, observed - - -@jit(error_model="numpy") -def _restricted_log_likelihood( - d: FloatArray, v: FloatArray, k: int, tau2: float -) -> float: - """The restricted log-likelihood at tau2, without its constant.""" - total = 0.0 - centre = 0.0 - logs = 0.0 - for i in range(k): - weight = 1.0 / (v[i] + tau2) - total += weight - centre += weight * d[i] - logs += math.log(v[i] + tau2) - centre /= total - q = 0.0 - for i in range(k): - r = d[i] - centre - q += r * r / (v[i] + tau2) - return -0.5 * (logs + math.log(total) + q) - - -@jit(error_model="numpy") -def _score_root( - d: FloatArray, - v: FloatArray, - k: int, - lower: float, - upper: float, - typical: float, - w: FloatArray, - w2: FloatArray, - without: FloatArray, - without2: FloatArray, -) -> tuple[float, bool]: - """The root of the restricted score in (lower, upper), where it falls from positive - to negative: Newton's method with the observed information, kept in the bracket - and replaced by bisection where its step leaves it or does not halve every two - steps.""" - tau2 = 0.5 * (lower + upper) - last = upper - lower - before_last = last - for _ in range(_MAX_ITERATIONS): - score, observed = _restricted_score(d, v, k, tau2, w, w2, without, without2) - if score > 0.0: - lower = tau2 - elif score < 0.0: - upper = tau2 - else: - return tau2, True - step = score / observed if observed > 0.0 else math.inf - if not (lower < tau2 + step < upper and abs(step) < 0.5 * before_last): - step = 0.5 * (lower + upper) - tau2 - before_last, last = last, abs(step) - tau2 += step - if abs(step) <= _TOLERANCE * (tau2 + typical): - return tau2, True - return tau2, False - - -@jit(error_model="numpy") -def _reml( - d: FloatArray, - v: FloatArray, - k: int, - typical: float, - w: FloatArray, - w2: FloatArray, - without: FloatArray, - without2: FloatArray, -) -> tuple[float, bool]: - """The REML estimate of tau^2, the largest maximum of the restricted likelihood - over tau2 >= 0, and whether its iteration converged; NaN and False where no - maximum is found, which the bounds below exclude in exact arithmetic. - - The maxima are zero, where the score there is not positive, and the roots where - the score falls from positive to negative. The score is negative beyond - sum d^2 / (k - 1) + 2 max(v), where w_max^2 sum d^2 bounds sum w^2 r^2 and - (k - 1) w_min^2 / w_max bounds tr(P) from below. The roots are bracketed by the - signs of the score on a grid with _GRID_PER_DECADE points per decade from - 1e-3 min(v) to twice that bound, so that rounding cannot hide the change of sign - of a root near the bound (as when the standard errors are far smaller than the - spread of the effects), and each is found by ``_score_root``. - """ - squares = 0.0 - smallest = math.inf - largest = 0.0 - for i in range(k): - squares += d[i] * d[i] - smallest = min(smallest, v[i]) - largest = max(largest, v[i]) - bottom = max(1e-3 * smallest, 1e-300) # positive, should a variance underflow - top = 2.0 * (squares / (k - 1) + 2.0 * largest) - if not top < math.inf: - return math.nan, False - n = max(2, int(math.ceil(_GRID_PER_DECADE * math.log10(top / bottom))) + 1) - ratio = (top / bottom) ** (1.0 / (n - 1)) - best = math.nan - best_likelihood = -math.inf - converged = True - left = 0.0 - left_score = _restricted_score(d, v, k, 0.0, w, w2, without, without2)[0] - if left_score <= 0.0: - best = 0.0 - best_likelihood = _restricted_log_likelihood(d, v, k, 0.0) - for j in range(n): - right = top if j == n - 1 else bottom * ratio**j - right_score = _restricted_score(d, v, k, right, w, w2, without, without2)[0] - if left_score > 0.0 and right_score <= 0.0: - if right_score == 0.0: - root, ok = right, True - else: - root, ok = _score_root( - d, v, k, left, right, typical, w, w2, without, without2 - ) - converged = converged and ok - likelihood = _restricted_log_likelihood(d, v, k, root) - if likelihood > best_likelihood or math.isnan(best): - best, best_likelihood = root, likelihood - left, left_score = right, right_score - return best, converged and not math.isnan(best) - - -@jit(error_model="numpy") -def _generalized_q( - d: FloatArray, v: FloatArray, k: int, tau2: float -) -> tuple[float, float]: - """The generalized Q statistic at tau2 and minus its derivative, sum w^2 r^2.""" - total = 0.0 - centre = 0.0 - for i in range(k): - weight = 1.0 / (v[i] + tau2) - total += weight - centre += weight * d[i] - centre /= total - q = 0.0 - slope = 0.0 - for i in range(k): - weight = 1.0 / (v[i] + tau2) - r = d[i] - centre - q += weight * r * r - slope += (weight * r) ** 2 - return q, slope - - -@jit(error_model="numpy") -def _paule_mandel( - d: FloatArray, v: FloatArray, k: int, q_fixed: float, typical: float -) -> tuple[float, bool]: - """The Paule-Mandel estimate of tau^2 by Newton's method, and whether it - converged.""" - target = k - 1.0 - if not q_fixed > target: - return 0.0, True - squares = 0.0 - smallest = math.inf - largest = 0.0 - for i in range(k): - squares += d[i] * d[i] - smallest = min(smallest, v[i]) - largest = max(largest, v[i]) - # 1 / (largest + tau2) <= w_i <= 1 / (smallest + tau2) brackets Q(tau2) between - # sum d^2 / (largest + tau2) and sum d^2 / (smallest + tau2). - tau2 = max(0.0, squares / target - largest) - upper = squares / target - smallest - for _ in range(_MAX_ITERATIONS): - q, slope = _generalized_q(d, v, k, tau2) - excess = q - target - if not excess > 0.0: - return tau2, True - updated = min(tau2 + excess / slope, upper) - if updated - tau2 <= _TOLERANCE * (updated + typical): - return updated, True - tau2 = updated - return tau2, False - - -@jit(error_model="numpy") -def _meta_analyse( - effects: FloatArray, - standard_errors: FloatArray, - method: int, - knapp_hartung: bool, - z_critical: float, - t_critical: FloatArray, - results: FloatArray, - n_studies: IntArray, - converged: BoolArray, -) -> None: - """Meta-analyse every feature: rows of ``effects`` and ``standard_errors`` are - features, columns studies, NaN where a study did not measure a feature. Fills the - rows of ``results`` in the order of ``_OUTPUTS``.""" - n_features, m = effects.shape - d = np.empty(m) - v = np.empty(m) - w = np.empty(m) - w2 = np.empty(m) - without = np.empty(m) - without2 = np.empty(m) - for g in range(n_features): - k = 0 - mean = 0.0 - for i in range(m): - y = effects[g, i] - s = standard_errors[g, i] - if math.isnan(y) or math.isnan(s): - continue - d[k] = y - v[k] = s - mean += y - k += 1 - n_studies[g] = k - converged[g] = True - if k == 0: - results[:, g] = math.nan - continue - mean /= k - largest = 0.0 - for i in range(k): - d[i] -= mean - largest = max(largest, v[i], abs(d[i])) - # Divided, exactly, by a power of two near the largest standard error or - # deviation of the effects from their mean: every number below is then at most - # of order one. - scale = math.ldexp(1.0, math.frexp(largest)[1]) - for i in range(k): - d[i] /= scale - v[i] /= scale - single = v[0] # the standard error of a feature of one study, as given - for i in range(k): - v[i] *= v[i] - - tau2 = 0.0 - if k == 1: - q = 0.0 - q_p_value = 1.0 - i_squared = 0.0 - h_squared = 1.0 - else: - q, c = _fixed_effect(d, v, k, w, without) - typical = (k - 1) / c - q_p_value = chi2_sf_scalar(q, k - 1.0) - ok = True - if method == _REML: - tau2, ok = _reml(d, v, k, typical, w, w2, without, without2) - elif method == _DERSIMONIAN_LAIRD: - tau2 = max(0.0, (q - (k - 1)) / c) - elif method == _PAULE_MANDEL: - tau2, ok = _paule_mandel(d, v, k, q, typical) - converged[g] = ok - if method == _FIXED: - i_squared = (q - (k - 1)) / q if q > k - 1 else 0.0 - h_squared = q / (k - 1) - else: - i_squared = tau2 / (tau2 + typical) - h_squared = (tau2 + typical) / typical - - total = 0.0 - centre = 0.0 - for i in range(k): - weight = 1.0 / (v[i] + tau2) - total += weight - centre += weight * d[i] - centre /= total - error = single if k == 1 else math.sqrt(1.0 / total) - if knapp_hartung and k > 1: - spread = 0.0 - for i in range(k): - r = d[i] - centre - spread += r * r / (v[i] + tau2) - error = math.sqrt(spread / (k - 1) / total) - critical = t_critical[k - 1] - else: - critical = z_critical - estimate = mean + centre * scale - error *= scale - statistic = estimate / error - if knapp_hartung and k > 1: - p_value = 2.0 * t_sf_scalar(abs(statistic), k - 1.0) - else: - p_value = 2.0 * norm_sf_scalar(abs(statistic)) - results[0, g] = estimate - results[1, g] = error - results[2, g] = statistic - results[3, g] = p_value - results[4, g] = estimate - critical * error - results[5, g] = estimate + critical * error - results[6, g] = tau2 * scale * scale - results[7, g] = q - results[8, g] = q_p_value - results[9, g] = i_squared - results[10, g] = h_squared - - -# Meta-analysis of differential-expression results. - - -class MetaAnalysisResult: - """Meta-analyses of the differential expression of several studies. - - Built by :func:`differential_expression_meta_analysis`. Tables hold, for every - group and contrast meta-analysed, one row per feature that at least - ``min_studies`` studies tested, in the order in which the studies, taken in the - order given, first list them: - - - ``feature_id`` and, when a study's tables have one, ``symbol``: the first symbol - that a study gives for the feature, taking the studies in the order given; - - ``n_studies``: the number of studies that tested the feature; - - ``log2_fold_change`` and ``standard_error``: the estimate of the common log2 - fold change and its standard error; - - ``statistic`` and ``p_value``: the z-statistic, or the Knapp-Hartung - t-statistic on ``n_studies - 1`` degrees of freedom, and its two-sided p-value. - A feature that one study tested, kept with ``min_studies=1``, has the z-test of - that study's log2 fold change against its standard error, also with - ``"knapp_hartung"``; - - ``adjusted_p_value``: Benjamini-Hochberg over the features of the table - (Benjamini and Hochberg 1995); - - ``ci_lower`` and ``ci_upper``: the confidence interval of the log2 fold change; - - ``tau_squared``: the between-study variance; - - ``i_squared`` and ``h_squared``: I^2, as a proportion, and H^2; - - ``q_statistic`` and ``q_p_value``: Cochran's Q and its p-value. - - The summary has one row per group and contrast: ``group_id``, ``contrast``, - ``status`` (``"analysed"``; ``"too_few_studies"`` when fewer than ``min_studies`` - studies tested it; ``"no_features"`` when no feature was tested by that many), - ``reason`` (empty when analysed), ``n_studies`` (the studies that tested it), - ``n_features`` (in its table) and ``n_features_left_out`` (tested by fewer than - ``min_studies`` studies). The table of studies has one row per group, contrast and - study: ``group_id``, ``contrast``, ``study``, ``tested`` (whether the study's - results have a table of the group and contrast), ``reason`` (why not; empty when - tested) and ``n_features`` (in the study's table; 0 when not tested). - - Immutable. - - Raises: - TypeError: If ``summary`` or ``studies`` is not a ``Table``, ``tables`` not a - mapping of (group id, contrast name) to ``Table``, or ``provenance`` not a - mapping that JSON can write. - ValueError: If ``provenance`` holds a NaN or an infinity. - """ - - __slots__ = ("_summary", "_studies", "_tables", "_record") - - _summary: Table - _studies: Table - _tables: Mapping[tuple[str, str], Table] - _record: str - - def __init__( - self, - summary: Table, - studies: Table, - tables: Mapping[tuple[str, str], Table], - provenance: Mapping[str, Any], - ) -> None: - for name, table in (("summary", summary), ("studies", studies)): - if not isinstance(table, Table): - raise TypeError(f"{name} must be a Table, got {type(table).__name__}") - if not isinstance(tables, Mapping): - raise TypeError( - f"tables must be a mapping of (group id, contrast name) to Table, got " - f"{type(tables).__name__}" - ) - for key, table in tables.items(): - pair = isinstance(key, tuple) and len(key) == 2 - if not (pair and all(isinstance(part, str) for part in key)): - raise TypeError( - "tables must be keyed by (group id, contrast name), got the key " - f"{key!r}" - ) - if not isinstance(table, Table): - raise TypeError( - f"tables[{key!r}] must be a Table, got {type(table).__name__}" - ) - if not isinstance(provenance, Mapping): - raise TypeError( - f"provenance must be a mapping, got {type(provenance).__name__}" - ) - state = { - "_summary": summary, - "_studies": studies, - "_tables": MappingProxyType(dict(tables)), - # JSON holds no arrays: json.dumps raises TypeError for one. - "_record": json.dumps(provenance, allow_nan=False), - } - for name, value in state.items(): - object.__setattr__(self, name, value) - - def __setattr__(self, name: str, value: object) -> None: - raise AttributeError("MetaAnalysisResult is immutable") - - def __delattr__(self, name: str) -> None: - raise AttributeError("MetaAnalysisResult is immutable") - - def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: - return ( - MetaAnalysisResult, - ( - self._summary, - self._studies, - dict(self._tables), - json.loads(self._record), - ), - ) - - @property - def summary(self) -> Table: - """The status, studies and features of every group and contrast.""" - return self._summary - - @property - def studies(self) -> Table: - """Every study of every group and contrast, whether it tested them, and why not.""" - return self._studies - - @property - def tables(self) -> Mapping[tuple[str, str], Table]: - """The table of every group and contrast meta-analysed, by (group, contrast).""" - return self._tables - - @property - def settings(self) -> dict[str, Any]: - """The settings of the meta-analysis, as a new dict (also in the provenance).""" - settings: dict[str, Any] = json.loads(self._record)["settings"] - return settings - - @property - def provenance(self) -> dict[str, Any]: - """A JSON-serialisable record of what produced every number, as a new dict. - - It holds the CytePool, numpy, numba and Python versions; the settings; the - provenance of every study's differential-expression result, by study name, - which identifies its cube by its fingerprint and gives its design and - settings and the samples of every group's fit; and, for every group and - contrast, its status, the studies that tested it and those that did not with - the reason, the number of features in its table and left out of it, and the - features whose estimate of tau^2 did not converge (``not_converged``). - """ - record: dict[str, Any] = json.loads(self._record) - return record - - def table(self, group: str | None = None, contrast: str | None = None) -> Table: - """Return the table of one group and contrast. - - Args: - group: A group id; it may be left out when the result has one group. - contrast: A contrast name; it may be left out when the result has one - contrast. - - Raises: - ValueError: If a key is left out and the result has several. - KeyError: If the group or contrast was not analysed, fewer than - ``min_studies`` studies tested it, or none of its features; the message - says why. - """ - groups = list(dict.fromkeys(self._summary["group_id"].tolist())) - contrasts = list(dict.fromkeys(self._summary["contrast"].tolist())) - group = only_key(group, groups, "group", "groups") - contrast = only_key(contrast, contrasts, "contrast", "contrasts") - found = self._tables.get((group, contrast)) - if found is not None: - return found - if group not in groups: - raise KeyError( - f"no group {group!r} was analysed; groups are: " - f"{listing(groups) or '(none)'}" - ) - if contrast not in contrasts: - raise KeyError( - f"no contrast {contrast!r} was analysed; contrasts are: " - f"{listing(contrasts) or '(none)'}" - ) - row = np.flatnonzero( - (self._summary["group_id"] == group) - & (self._summary["contrast"] == contrast) - )[0] - raise KeyError( - f"group {group!r} and contrast {contrast!r} were not meta-analysed: " - f"{self._summary['reason'][row]}" - ) - - def __repr__(self) -> str: - record = json.loads(self._record) - settings = record["settings"] - summary = self._summary - n = len(summary) - analysed = summary["status"].tolist().count(_ANALYSED) - lines = [ - f"MetaAnalysisResult ({settings['method']}, test {settings['test']}): " - f"{len(record['studies']):,} studies; {n:,} group and contrast " - f"pair{'' if n == 1 else 's'}, {analysed:,} analysed" - ] - for row in range(n): - label = f" {summary['group_id'][row]}, {summary['contrast'][row]}: " - used = int(summary["n_studies"][row]) - if summary["status"][row] == _ANALYSED: - features = int(summary["n_features"][row]) - lines.append(f"{label}{used:,} studies, {features:,} features") - else: - lines.append(f"{label}not analysed ({summary['status'][row]})") - return fitted_lines("\n".join(lines)) - - -def differential_expression_meta_analysis( - results: Mapping[str, DifferentialExpressionResult], - *, - groups: str | Sequence[str] | None = None, - contrasts: str | Sequence[str] | None = None, - method: Literal["reml", "dl", "pm", "fixed"] = "reml", - test: Literal["z", "knapp_hartung"] = "z", - level: float = 0.95, - min_studies: int = 2, -) -> MetaAnalysisResult: - """Meta-analyse the differential expression of several studies, feature by feature. - - Each study is the result of :func:`~cytepool.differential_expression.limma_voom` - or :func:`~cytepool.differential_expression.limma_trend` on its own cube, such as - one cohort, tissue or platform. For every group (cell type) and contrast, the log2 - fold changes of the studies that tested it, with their standard errors (the - tables' ``log2_fold_change`` and ``standard_error``), are matched by feature id - and meta-analysed by :func:`meta_analysis`. Groups are matched by group id and - contrasts by name, so the studies must name them alike, and a contrast should - mean the same comparison in every study. A feature that some studies did not test, - because their expression filter left it out or their cube does not have it, is - analysed over the studies that tested it. - - The standard errors are the moderated ones of limma's empirical Bayes moderation, - with which the tables' t-statistics are formed; the meta-analysis takes them as - known, as it does any sampling variance, and so does not use their degrees of - freedom. - - Args: - results: The differential-expression result of every study, by study name; - at least two. - groups: A group id or a list of them; by default every group of any study, - in the order in which the studies first list them. - contrasts: A contrast name or a list of them; by default every contrast of - any study, in the order in which the studies first list them. - method: The estimator of the between-study variance, as for - :func:`meta_analysis`: ``"reml"``, ``"dl"``, ``"pm"`` or ``"fixed"``. - test: ``"z"`` or ``"knapp_hartung"``, as for :func:`meta_analysis`. - level: The confidence level of the intervals, in (0, 1). - min_studies: The fewest studies that must test a feature for it to be - meta-analysed, from 1 to the number of studies; 2 by default, since one - study gives no information about the between-study variance. A group and - contrast that fewer studies test is not analysed. With 1, a feature that - one study tested has the z-test of that study's log2 fold change against - its standard error, taken as known, as metafor gives it for one study: its - p-value is smaller than that of the study's own moderated t-test, whose - degrees of freedom are finite. - - Returns: - The table of every group and contrast meta-analysed, a summary of every group - and contrast, the studies that tested them, and a provenance record. - - Warns: - RuntimeWarning: If the estimate of tau^2 of a feature did not converge; the - message names the group, the contrast and the features, which the record - lists too. - - Raises: - TypeError: If ``results`` is not a mapping of study names to - ``DifferentialExpressionResult``, an option has the wrong type, or - ``groups``, ``contrasts`` or ``min_studies`` is or holds a numpy masked - array. - ValueError: If there are fewer than two studies, a group or contrast is - listed twice, an option is invalid, ``min_studies`` exceeds the number of - studies, or a study's table has no ``standard_error`` column, a log2 fold - change that is infinite, or a standard error that is not positive and - finite (NaN marks a feature the study did not test). - KeyError: If a group or contrast is in no study's results. - """ - reject_masked(groups, "groups") - reject_masked(contrasts, "contrasts") - studies = _studies(results) - estimator = checked_choice(method, "method", tuple(_METHODS)) - chosen_test = checked_choice(test, "test", _TESTS) - confidence = _level(level) - fewest = _min_studies(min_studies) - if fewest > len(studies): - raise ValueError( - f"min_studies ({fewest}) is larger than the number of studies " - f"({len(studies)}), so no feature could be meta-analysed" - ) - group_ids = _chosen( - "group", groups, [list(result.groups) for result in studies.values()] - ) - names = _chosen( - "contrast", - contrasts, - [list(result.contrasts.names) for result in studies.values()], - ) - settings = { - "method": estimator, - "test": chosen_test, - "level": confidence, - "min_studies": fewest, - "p_value_adjustment": "BH", - } - rows: list[dict[str, Any]] = [] - study_rows: list[dict[str, Any]] = [] - tables: dict[tuple[str, str], Table] = {} - for group in group_ids: - for contrast in names: - tested: dict[str, Table] = {} - left_out: dict[str, str] = {} - for name, result in studies.items(): - table = result.tables.get((group, contrast)) - if table is None: - left_out[name] = _absence(result, group, contrast) - else: - _check_table(table, name, group, contrast) - tested[name] = table - study_rows.append( - { - "group_id": group, - "contrast": contrast, - "study": name, - "tested": table is not None, - "reason": left_out.get(name, ""), - "n_features": 0 if table is None else len(table), - } - ) - row: dict[str, Any] = { - "group_id": group, - "contrast": contrast, - "status": _ANALYSED, - "reason": "", - "studies": list(tested), - "left_out": left_out, - "n_features": 0, - "n_features_left_out": 0, - "not_converged": [], - } - if len(tested) < fewest: - row["status"] = "too_few_studies" - row["reason"] = ( - f"{len(tested)} of the {len(studies)} studies tested it, fewer " - f"than min_studies ({fewest})" - ) - rows.append(row) - continue - table, left, failed = _meta_analysed_table( - tested, estimator, chosen_test, confidence, fewest - ) - row["n_features"], row["n_features_left_out"] = len(table), left - row["not_converged"] = failed - if len(table): - tables[(group, contrast)] = table - else: - row["status"] = "no_features" - row["reason"] = ( - f"none of the {left} features that the {len(tested)} studies " - f"tested was tested by min_studies ({fewest}) of them" - ) - if failed: - warnings.warn( - f"group {group!r}, contrast {contrast!r}: the estimate of tau^2 " - f"did not converge for {len(failed)} feature" - f"{'' if len(failed) == 1 else 's'}, {quoted(failed)}; the record " - "lists them under not_converged", - RuntimeWarning, - skip_file_prefixes=(PACKAGE_DIRECTORY,), - ) - rows.append(row) - return MetaAnalysisResult( - _summary_table(rows), - Table( - { - column: ( - text_column([row[column] for row in study_rows]) - if column in ("group_id", "contrast", "study", "reason") - else np.array( - [row[column] for row in study_rows], - dtype=bool if column == "tested" else np.int64, - ) - ) - for column in ( - "group_id", - "contrast", - "study", - "tested", - "reason", - "n_features", - ) - } - ), - tables, - { - "analysis": "differential_expression_meta_analysis", - "versions": software_versions(), - "settings": settings, - "studies": {name: result.provenance for name, result in studies.items()}, - "analyses": rows, - }, - ) - - -def align_study_results( - collection: CubeCollection, - results: Mapping[str, DifferentialExpressionResult], - group: str, - contrast: str, -) -> Table: - """Line up the differential expression of every study, feature by feature. - - Each study's result is the analysis of its aligned cube - (:meth:`~cytepool.cube.CubeCollection.aligned_cube`), such as - :func:`~cytepool.differential_expression.limma_voom` with the study's own design, - so that its groups are the collection's aligned groups and its features those - the study measured. For one group and contrast, the table has a row for every - study and every feature of the collection: the studies in order, and the - features in the collection's order within each study, so that - ``table[column].reshape(collection.n_studies, collection.n_features)`` gives a - column with a row per study. ``status`` says whether the study tested the - feature, and why not: - - - ``"tested"``: the study's table of the group and contrast has the feature; - - ``"not_tested"``: the study measured the feature and has a table of the group - and contrast, without the feature, as when its expression filter left it out; - - ``"not_measured"``: the study did not measure the feature - (:attr:`~cytepool.cube.CubeCollection.measured`), so no analysis of it could - test it; - - ``"not_analysed"``: the study measured the feature but has no table of the - group and contrast: it does not have the group, its result leaves the group - out, the group was not fitted, or the contrast cannot be estimated from its - samples; ``reason`` says which, in the words of the study's result. - - The columns are ``study``, ``feature_id``, ``symbol`` (when the collection's - features have it), ``status``, ``reason`` (empty where tested), and then the - numeric columns of the studies' tables, such as ``log2_fold_change``, - ``standard_error``, ``t``, ``p_value`` and ``adjusted_p_value``, in the order in - which the studies first have them, NaN where a study did not test the feature. - A missing feature is thus never a zero, and a feature that a study did not - measure is told apart from one that it measured and left out. - - Args: - collection: The collection whose aligned cubes the studies' results - analysed. - results: The differential-expression result of every study of the - collection, by study name, from - :func:`~cytepool.differential_expression.limma_voom`, - :func:`~cytepool.differential_expression.limma_trend` or - :func:`~cytepool.differential_expression.dream_voom`. - group: An aligned group id of the collection. - contrast: A contrast name, or the name of an F-test of - :func:`~cytepool.differential_expression.dream_voom`, that a study - tested. - - Returns: - A table of ``collection.n_studies * collection.n_features`` rows. - - Raises: - TypeError: If ``collection`` is not a ``CubeCollection``, ``results`` not a - mapping of study names to ``DifferentialExpressionResult``, ``group`` or - ``contrast`` not a string, or ``group`` or ``contrast`` is a numpy masked - array. - ValueError: If ``results`` does not give the result of every study of the - collection and no other, or a study's result analyses a group that the - study does not have in the collection or tests a feature that it did not - measure there: it did not analyse the study's aligned cube. - KeyError: If ``group`` is not a group of the collection, or no study tested - ``contrast``. - """ - if not isinstance(collection, CubeCollection): - raise TypeError( - f"collection must be a CubeCollection, got {type(collection).__name__}" - ) - studies = collection.studies - if not isinstance(results, Mapping): - raise TypeError( - "results must be a mapping of study names to DifferentialExpressionResult, " - f"one for every study of the collection, got {type(results).__name__}" - ) - missing = [study for study in studies if study not in results] - extra = [name for name in results if name not in studies] - if missing or extra: - raise ValueError( - "results must give the result of every study of the collection, and no " - "other: " - + "; ".join( - part - for part in ( - f"missing {quoted(missing)}" if missing else "", - f"not in the collection: {quoted(extra)}" if extra else "", - ) - if part - ) - ) - for study in studies: - if not isinstance(results[study], DifferentialExpressionResult): - raise TypeError( - f"results[{study!r}] must be a DifferentialExpressionResult, from " - f"limma_voom, limma_trend or dream_voom, got " - f"{type(results[study]).__name__}" - ) - for name, value in (("group", group), ("contrast", contrast)): - reject_masked(value, name) - if not isinstance(value, str): - raise TypeError(f"{name} must be a string, got {type(value).__name__}") - group_ids = collection.groups["group_id"].tolist() - if group not in group_ids: - raise KeyError( - f"no group {group!r} in the collection; its groups are: " - f"{listing(group_ids) or '(none)'}" - ) - tested_names = list( - dict.fromkeys( - name - for study in studies - for name in (*results[study].contrasts.names, *results[study].f_tests) - ) - ) - if contrast not in tested_names: - raise KeyError( - f"no study tested the contrast {contrast!r}; the studies tested: " - f"{listing(tested_names) or '(none)'}" - ) - members = collection.group_members - held: dict[str, set[str]] = {study: set() for study in studies} - for study, aligned in zip( - members["study"].tolist(), members["group_id"].tolist(), strict=True - ): - held[study].add(aligned) - feature_ids = collection.features["feature_id"].tolist() - index = {feature: position for position, feature in enumerate(feature_ids)} - n_features = len(feature_ids) - measured = collection.measured - statuses: list[np.ndarray] = [] - reasons: list[list[str]] = [] - values: dict[str, list[tuple[int, np.ndarray, np.ndarray]]] = {} - for row, study in enumerate(studies): - result = results[study] - foreign = [name for name in result.groups if name not in held[study]] - if foreign: - raise ValueError( - f"the result of study {study!r} analyses the group" - f"{'' if len(foreign) == 1 else 's'} {quoted(foreign)}, which the " - "study does not have in this collection; analyse the study's aligned " - f"cube, collection.aligned_cube({study!r})" - ) - table = None - if group not in held[study]: - absence = f"study {study!r} has no group {group!r}" - else: - table = result.tables.get((group, contrast)) - absence = "" if table is not None else _absence(result, group, contrast) - status = np.where(measured[row], "not_analysed", "not_measured").astype(object) - reason = [ - absence if flag else f"study {study!r} did not measure the feature" - for flag in measured[row].tolist() - ] - if table is not None: - positions = np.array( - [index.get(feature, -1) for feature in table["feature_id"].tolist()], - dtype=np.intp, - ) - outside = (positions < 0) | ~measured[row, np.maximum(positions, 0)] - if outside.any(): - feature = table["feature_id"][int(np.flatnonzero(outside)[0])] - raise ValueError( - f"the result of study {study!r} tests the feature {feature!r}, " - "which the study did not measure in this collection; analyse the " - f"study's aligned cube, collection.aligned_cube({study!r})" - ) - status[measured[row]] = "not_tested" - status[positions] = "tested" - for position in np.flatnonzero(measured[row]).tolist(): - reason[position] = _NOT_TESTED - for position in positions.tolist(): - reason[position] = "" - for name in table.columns: - column = table[name] - if name not in ("feature_id", "symbol") and column.dtype.kind in "biuf": - values.setdefault(name, []).append((row, positions, column)) - statuses.append(status) - reasons.append(reason) - columns: dict[str, Any] = { - "study": text_column([study for study in studies for _ in range(n_features)]), - "feature_id": text_column(feature_ids * len(studies)), - } - if "symbol" in collection.features: - columns["symbol"] = text_column( - collection.features["symbol"].tolist() * len(studies) - ) - columns["status"] = text_column( - [status for array in statuses for status in array.tolist()] - ) - columns["reason"] = text_column([text for part in reasons for text in part]) - for name, parts in values.items(): - column = np.full((len(studies), n_features), np.nan) - for row, positions, found in parts: - column[row, positions] = found - columns[name] = column.reshape(-1) - return Table(columns) - - -def _meta_analysed_table( - tested: Mapping[str, Table], method: str, test: str, level: float, fewest: int -) -> tuple[Table, int, list[str]]: - """The meta-analysis of one group and contrast, the number of features that fewer - than ``fewest`` studies tested, and the ids of those in the table whose estimate of - tau^2 did not converge.""" - index: dict[str, int] = {} - positions = [] - for table in tested.values(): - ids = table["feature_id"].tolist() - for feature in ids: - index.setdefault(feature, len(index)) - positions.append(np.array([index[feature] for feature in ids], dtype=np.intp)) - effects = np.full((len(tested), len(index)), np.nan) - errors = np.full((len(tested), len(index)), np.nan) - symbols: list[str | None] | None = None - for row, (table, where) in enumerate(zip(tested.values(), positions, strict=True)): - effects[row, where] = table["log2_fold_change"] - errors[row, where] = table["standard_error"] - if "symbol" in table: - if symbols is None: - symbols = [None] * len(index) - for position, symbol in zip( - where.tolist(), table["symbol"].tolist(), strict=True - ): - if symbols[position] is None: - symbols[position] = symbol - fit = meta_analysis( - effects, - errors, - method=method, # type: ignore[arg-type] - test=test, # type: ignore[arg-type] - level=level, - ) - keep = fit.n_studies >= fewest - ids = list(index) - columns: dict[str, Any] = { - "feature_id": text_column([f for f, k in zip(ids, keep, strict=True) if k]) - } - if symbols is not None: - # A text column whose missing symbols are None. - columns["symbol"] = np.array( - [s for s, k in zip(symbols, keep, strict=True) if k], dtype=object - ) - columns |= { - "n_studies": fit.n_studies[keep], - "log2_fold_change": fit.estimate[keep], - "standard_error": fit.standard_error[keep], - "statistic": fit.statistic[keep], - "p_value": fit.p_value[keep], - "adjusted_p_value": adjust_p_values(fit.p_value[keep], "bh"), - "ci_lower": fit.ci_lower[keep], - "ci_upper": fit.ci_upper[keep], - "tau_squared": fit.tau_squared[keep], - "i_squared": fit.i_squared[keep], - "h_squared": fit.h_squared[keep], - "q_statistic": fit.q_statistic[keep], - "q_p_value": fit.q_p_value[keep], - } - failed = [ids[i] for i in np.flatnonzero(keep & ~fit.converged)] - return Table(columns), int(np.count_nonzero(~keep)), failed - - -def _studies(results: Any) -> dict[str, DifferentialExpressionResult]: - if not isinstance(results, Mapping): - raise TypeError( - "results must be a mapping of study names to DifferentialExpressionResult, " - f"such as {{'cohort_a': limma_voom(cube_a, design_a), ...}}, got " - f"{type(results).__name__}" - ) - for name, result in results.items(): - if not isinstance(name, str): - raise TypeError( - f"results must be keyed by study names (str), got {type(name).__name__} " - f"{name!r}" - ) - if not isinstance(result, DifferentialExpressionResult): - raise TypeError( - f"results[{name!r}] must be a DifferentialExpressionResult, from " - f"limma_voom or limma_trend, got {type(result).__name__}" - ) - if len(results) < 2: - raise ValueError( - f"a meta-analysis combines at least two studies, got {len(results)}" - ) - return dict(results) - - -def _chosen(kind: str, value: Any, listed: list[list[str]]) -> list[str]: - """The groups or contrasts to analyse: those given, or every one of any study.""" - available = list(dict.fromkeys(name for names in listed for name in names)) - if value is None: - return available - if isinstance(value, str): - chosen: list[Any] = [value] - elif isinstance(value, Sequence | np.ndarray): - chosen = list(value) - else: - raise TypeError( - f"{kind}s must be a {kind} name or a list of them, got " - f"{type(value).__name__}" - ) - if not chosen: - raise ValueError( - f"{kind}s is empty; give at least one {kind}, or None for every {kind}" - ) - for name in chosen: - if not isinstance(name, str): - raise TypeError( - f"{kind}s must be {kind} names (str), got {type(name).__name__} " - f"{name!r}" - ) - repeated = [name for name in dict.fromkeys(chosen) if chosen.count(name) > 1] - if repeated: - raise ValueError(f"{kind}s lists {quoted(repeated)} more than once") - missing = [name for name in chosen if name not in available] - if missing: - raise KeyError( - f"no study analysed the {kind} {quoted(missing)}; the studies' {kind}s " - f"are: {listing(available) or '(none)'}" - ) - return chosen - - -def _absence(result: DifferentialExpressionResult, group: str, contrast: str) -> str: - """Why a study's result has no table of a group and contrast: its own message.""" - try: - result.table(group, contrast) - except KeyError as error: - return str(error.args[0]) - raise AssertionError("a table that is not in result.tables") # pragma: no cover - - -def _check_table(table: Table, study: str, group: str, contrast: str) -> None: - """Refuse a study's table without the columns the meta-analysis reads, or with a - log2 fold change or standard error it cannot use; the message names the feature.""" - where = ( - f"the table of study {study!r} for group {group!r} and contrast {contrast!r}" - ) - missing = [ - column - for column in ("feature_id", "log2_fold_change", "standard_error") - if column not in table - ] - if missing: - raise ValueError( - f"{where} has no column {quoted(missing)}; the meta-analysis reads the " - "log2 fold changes and standard errors of limma_voom and limma_trend " - "results" - ) - effects = np.asarray(table["log2_fold_change"], dtype=np.float64) - errors = np.asarray(table["standard_error"], dtype=np.float64) - for values, wrong, expected in ( - (effects, np.isinf(effects), "finite"), - ( - errors, - ~np.isnan(errors) & ~((errors > 0.0) & np.isfinite(errors)), - "positive and finite", - ), - ): - if wrong.any(): - row = int(np.flatnonzero(wrong)[0]) - column = "log2_fold_change" if values is effects else "standard_error" - raise ValueError( - f"{where} has the {column} {values[row]} for feature " - f"{table['feature_id'][row]!r}; it must be {expected}, or NaN for a " - "feature the study did not test" - ) - - -def _min_studies(value: Any) -> int: - if isinstance(value, bool | np.bool_): - raise TypeError("min_studies must be a whole number of studies, not bool") - number = index_integer(value, "min_studies", expected="a whole number of studies") - if number < 1: - raise ValueError(f"min_studies must be at least 1, got {number}") - return number - - -def _summary_table(rows: list[dict[str, Any]]) -> Table: - columns: dict[str, Any] = { - name: text_column([row[name] for row in rows]) - for name in ("group_id", "contrast", "status", "reason") - } - columns["n_studies"] = np.array([len(row["studies"]) for row in rows], np.int64) - for name in ("n_features", "n_features_left_out"): - columns[name] = np.array([row[name] for row in rows], dtype=np.int64) - return Table(columns) diff --git a/cytepool/meta_analysis_of_studies.py b/cytepool/meta_analysis_of_studies.py new file mode 100644 index 0000000..c459c90 --- /dev/null +++ b/cytepool/meta_analysis_of_studies.py @@ -0,0 +1,664 @@ +"""Meta-analysis of the differential expression of several studies, feature by feature. + +The same cell types studied in several cohorts, tissues or platforms give, for every +feature, a log2 fold change and its standard error in each study. +:func:`~cytepool.meta_analysis` combines them into an estimate of a common log2 fold +change for every group (cell type), contrast and feature, with the between-study +variance and the heterogeneity statistics, as metafor's ``rma.uni`` does for one feature +without moderators (Viechtbauer 2010); +:func:`~cytepool.stats.meta_analysis.fit_meta_analysis` computes it, and its +documentation gives the model, the estimators of the between-study variance and the +tests. The studies are matched by group, contrast and feature id, and the result keeps +the studies' results it combined, so that :func:`~cytepool.plotting.plot_forest` draws a +feature's estimate in every study. + +References: + Viechtbauer W (2010). Conducting meta-analyses in R with the metafor package. + Journal of Statistical Software 36(3), 1-48. doi:10.18637/jss.v036.i03 + + 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 +""" + +import json +import warnings +from collections.abc import Mapping, Sequence +from types import MappingProxyType +from typing import Any, Literal + +import numpy as np + +from ._analysis_records import absence, only_key, software_versions +from ._columns import text_column +from ._masked_arrays import reject_masked +from ._messages import PACKAGE_DIRECTORY, fitted_lines, listing, quoted +from ._options import index_integer +from .differential_expression import DifferentialExpressionResult +from .stats.meta_analysis import _checked_options, fit_meta_analysis +from .stats.multiple_testing import adjust_p_values +from .table import Table + +__all__ = ["MetaAnalysisResult", "meta_analysis"] + +_ANALYSED = "analysed" # the status of a group and contrast that was meta-analysed + + +class MetaAnalysisResult: + """Meta-analyses of the differential expression of several studies. + + Built by :func:`meta_analysis`. Tables hold, for every group and contrast + meta-analysed, one row per feature that at least ``min_studies`` studies tested, in + the order in which the studies, taken in the order given, first list them: + + - ``feature_id`` and, when a study's tables have one, ``symbol``: the first symbol + that a study gives for the feature, taking the studies in the order given; + - ``n_studies``: the number of studies that tested the feature; + - ``log2_fold_change`` and ``standard_error``: the estimate of the common log2 + fold change and its standard error; + - ``statistic`` and ``p_value``: the z-statistic, or the Knapp-Hartung + t-statistic on ``n_studies - 1`` degrees of freedom, and its two-sided p-value. + A feature that one study tested, kept with ``min_studies=1``, has the z-test of + that study's log2 fold change against its standard error, also with + ``"knapp_hartung"``; + - ``adjusted_p_value``: Benjamini-Hochberg over the features of the table + (Benjamini and Hochberg 1995); + - ``ci_lower`` and ``ci_upper``: the confidence interval of the log2 fold change; + - ``tau_squared``: the between-study variance; + - ``i_squared`` and ``h_squared``: I^2, as a proportion, and H^2; + - ``q_statistic`` and ``q_p_value``: Cochran's Q and its p-value. + + The summary has one row per group and contrast: ``group_id``, ``contrast``, + ``status`` (``"analysed"``; ``"too_few_studies"`` when fewer than ``min_studies`` + studies tested it; ``"no_features"`` when no feature was tested by that many), + ``reason`` (empty when analysed), ``n_studies`` (the studies that tested it), + ``n_features`` (in its table) and ``n_features_left_out`` (tested by fewer than + ``min_studies`` studies). The table of studies has one row per group, contrast and + study: ``group_id``, ``contrast``, ``study``, ``analysed`` (whether the study's + results have a table of the group and contrast), ``reason`` (why not; empty when + analysed) and ``n_features_tested`` (in the study's table; 0 when not analysed). + + The result keeps the studies' results it combined (``study_results``, the object + given, not a copy), so that :func:`~cytepool.plotting.plot_forest` can draw a + feature's estimate in every study; a pickled result carries them. Immutable. + + Raises: + TypeError: If ``summary`` or ``studies`` is not a ``Table``, ``tables`` not a + mapping of (group id, contrast name) to ``Table``, ``provenance`` not a + mapping that JSON can write, or ``study_results`` not a mapping of study + names to ``DifferentialExpressionResult``. + ValueError: If ``provenance`` holds a NaN or an infinity. + """ + + __slots__ = ("_summary", "_studies", "_tables", "_record", "_study_results") + + _summary: Table + _studies: Table + _tables: Mapping[tuple[str, str], Table] + _record: str + _study_results: Mapping[str, DifferentialExpressionResult] | None + + def __init__( + self, + summary: Table, + studies: Table, + tables: Mapping[tuple[str, str], Table], + provenance: Mapping[str, Any], + *, + study_results: Mapping[str, DifferentialExpressionResult] | None = None, + ) -> None: + for name, table in (("summary", summary), ("studies", studies)): + if not isinstance(table, Table): + raise TypeError(f"{name} must be a Table, got {type(table).__name__}") + if not isinstance(tables, Mapping): + raise TypeError( + f"tables must be a mapping of (group id, contrast name) to Table, got " + f"{type(tables).__name__}" + ) + for key, table in tables.items(): + pair = isinstance(key, tuple) and len(key) == 2 + if not (pair and all(isinstance(part, str) for part in key)): + raise TypeError( + "tables must be keyed by (group id, contrast name), got the key " + f"{key!r}" + ) + if not isinstance(table, Table): + raise TypeError( + f"tables[{key!r}] must be a Table, got {type(table).__name__}" + ) + if not isinstance(provenance, Mapping): + raise TypeError( + f"provenance must be a mapping, got {type(provenance).__name__}" + ) + if study_results is not None: + _studies(study_results, fewest=0, name="study_results") + state = { + "_summary": summary, + "_studies": studies, + "_tables": MappingProxyType(dict(tables)), + # JSON holds no arrays: json.dumps raises TypeError for one. + "_record": json.dumps(provenance, allow_nan=False), + "_study_results": study_results, + } + for name, value in state.items(): + object.__setattr__(self, name, value) + + def __setattr__(self, name: str, value: object) -> None: + raise AttributeError("MetaAnalysisResult is immutable") + + def __delattr__(self, name: str) -> None: + raise AttributeError("MetaAnalysisResult is immutable") + + def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: + return ( + _restore_result, + ( + self._summary, + self._studies, + dict(self._tables), + json.loads(self._record), + self._study_results, + ), + ) + + @property + def summary(self) -> Table: + """The status, studies and features of every group and contrast.""" + return self._summary + + @property + def studies(self) -> Table: + """Every study of every group and contrast, whether it analysed them, and + why not.""" + return self._studies + + @property + def study_results(self) -> Mapping[str, DifferentialExpressionResult] | None: + """The studies' results that were combined, as given (a + :class:`~cytepool.StudyResults` or a mapping of study name to result), or None + for a result built without them.""" + return self._study_results + + @property + def tables(self) -> Mapping[tuple[str, str], Table]: + """The table of every group and contrast meta-analysed, by (group, contrast).""" + return self._tables + + @property + def settings(self) -> dict[str, Any]: + """The settings of the meta-analysis, as a new dict (also in the provenance).""" + settings: dict[str, Any] = json.loads(self._record)["settings"] + return settings + + @property + def provenance(self) -> dict[str, Any]: + """A JSON-serialisable record of what produced every number, as a new dict. + + It holds the CytePool, numpy, numba and Python versions; the settings; the + provenance of every study's differential-expression result, by study name, + which identifies its cube by its fingerprint and gives its design and + settings and the samples of every group's fit; and, for every group and + contrast, its status, the studies that tested it and those that did not with + the reason, the number of features in its table and left out of it, and the + features whose estimate of tau^2 did not converge (``not_converged``). + """ + record: dict[str, Any] = json.loads(self._record) + return record + + def table(self, group: str | None = None, contrast: str | None = None) -> Table: + """Return the table of one group and contrast. + + Args: + group: A group id; it may be left out when the result has one group. + contrast: A contrast name; it may be left out when the result has one + contrast. + + Raises: + ValueError: If a key is left out and the result has several. + KeyError: If the group or contrast was not analysed, fewer than + ``min_studies`` studies tested it, or none of its features; the message + says why. + """ + groups = list(dict.fromkeys(self._summary["group_id"].tolist())) + contrasts = list(dict.fromkeys(self._summary["contrast"].tolist())) + group = only_key(group, groups, "group", "groups") + contrast = only_key(contrast, contrasts, "contrast", "contrasts") + found = self._tables.get((group, contrast)) + if found is not None: + return found + if group not in groups: + raise KeyError( + f"no group {group!r} was analysed; groups are: " + f"{listing(groups) or '(none)'}" + ) + if contrast not in contrasts: + raise KeyError( + f"no contrast {contrast!r} was analysed; contrasts are: " + f"{listing(contrasts) or '(none)'}" + ) + row = np.flatnonzero( + (self._summary["group_id"] == group) + & (self._summary["contrast"] == contrast) + )[0] + raise KeyError( + f"group {group!r} and contrast {contrast!r} were not meta-analysed: " + f"{self._summary['reason'][row]}" + ) + + def __repr__(self) -> str: + record = json.loads(self._record) + settings = record["settings"] + summary = self._summary + n = len(summary) + analysed = summary["status"].tolist().count(_ANALYSED) + lines = [ + f"MetaAnalysisResult ({settings['method']}, test {settings['test']}): " + f"{len(record['studies']):,} studies; {n:,} group and contrast " + f"pair{'' if n == 1 else 's'}, {analysed:,} analysed" + ] + for row in range(n): + label = f" {summary['group_id'][row]}, {summary['contrast'][row]}: " + used = int(summary["n_studies"][row]) + if summary["status"][row] == _ANALYSED: + features = int(summary["n_features"][row]) + lines.append(f"{label}{used:,} studies, {features:,} features") + else: + lines.append(f"{label}not analysed ({summary['status'][row]})") + return fitted_lines("\n".join(lines)) + + +def meta_analysis( + studies: Mapping[str, DifferentialExpressionResult], + *, + groups: str | Sequence[str] | None = None, + contrasts: str | Sequence[str] | None = None, + method: Literal["reml", "dl", "pm", "fixed"] = "reml", + test: Literal["z", "knapp_hartung"] = "z", + confidence_level: float = 0.95, + min_studies: int = 2, +) -> MetaAnalysisResult: + """Meta-analyse the differential expression of several studies, feature by feature. + + Each study is the result of :func:`~cytepool.limma_voom` or + :func:`~cytepool.limma_trend` on its own cube, such as one cohort, tissue or + platform: a :class:`~cytepool.StudyResults` from + :func:`~cytepool.analyse_each_study`, or a mapping of study name to result. For + every group (cell type) and contrast, the log2 fold changes of the studies that + tested it, with their standard errors (the tables' ``log2_fold_change`` and + ``standard_error``), are matched by feature id and meta-analysed by + :func:`~cytepool.stats.meta_analysis.fit_meta_analysis`. Groups are matched by + group id and contrasts by name, so the studies must name them alike, and a + contrast should mean the same comparison in every study. A feature that some + studies did not test, because their expression filter left it out or their cube + does not have it, is analysed over the studies that tested it. + + The standard errors are the moderated ones of limma's empirical Bayes moderation, + with which the tables' t-statistics are formed; the meta-analysis takes them as + known, as it does any sampling variance, and so does not use their degrees of + freedom. + + Args: + studies: The differential-expression result of every study: a + ``StudyResults``, or a mapping of study name to result; at least two. + groups: A group id or a list of them; by default every group of any study, + in the order in which the studies first list them. + contrasts: A contrast name or a list of them; by default every contrast of + any study, in the order in which the studies first list them. + method: The estimator of the between-study variance, as for + :func:`~cytepool.stats.meta_analysis.fit_meta_analysis`: ``"reml"``, + ``"dl"``, ``"pm"`` or ``"fixed"``. + test: ``"z"`` or ``"knapp_hartung"``, as for + :func:`~cytepool.stats.meta_analysis.fit_meta_analysis`. + confidence_level: The confidence level of the intervals, in (0, 1). + min_studies: The fewest studies that must test a feature for it to be + meta-analysed, from 1 to the number of studies; 2 by default, since one + study gives no information about the between-study variance. A group and + contrast that fewer studies test is not analysed. With 1, a feature that + one study tested has the z-test of that study's log2 fold change against + its standard error, taken as known, as metafor gives it for one study: its + p-value is smaller than that of the study's own moderated t-test, whose + degrees of freedom are finite. + + Returns: + The table of every group and contrast meta-analysed, a summary of every group + and contrast, the studies that tested them, the studies' results, and a + provenance record. + + Warns: + RuntimeWarning: If the estimate of tau^2 of a feature did not converge; the + message names the group, the contrast and the features, which the record + lists too. + + Raises: + TypeError: If ``studies`` is not a mapping of study names to + ``DifferentialExpressionResult``, an option has the wrong type, or + ``groups``, ``contrasts`` or ``min_studies`` is or holds a numpy masked + array. + ValueError: If there are fewer than two studies, a group or contrast is + listed twice, an option is invalid, ``min_studies`` exceeds the number of + studies, or a study's table has no ``standard_error`` column, a log2 fold + change that is infinite, or a standard error that is not positive and + finite (NaN marks a feature the study did not test). + KeyError: If a group or contrast is in no study's results. + """ + reject_masked(groups, "groups") + reject_masked(contrasts, "contrasts") + results = _studies(studies) + estimator, chosen_test, confidence = _checked_options( + method, test, confidence_level + ) + fewest = _min_studies(min_studies) + if fewest > len(results): + raise ValueError( + f"min_studies ({fewest}) is larger than the number of studies " + f"({len(results)}), so no feature could be meta-analysed" + ) + group_ids = _chosen( + "group", groups, [list(result.groups) for result in results.values()] + ) + names = _chosen( + "contrast", + contrasts, + [list(result.contrasts.names) for result in results.values()], + ) + settings = { + "method": estimator, + "test": chosen_test, + "confidence_level": confidence, + "min_studies": fewest, + "p_value_adjustment": "BH", + } + rows: list[dict[str, Any]] = [] + study_rows: list[dict[str, Any]] = [] + tables: dict[tuple[str, str], Table] = {} + for group in group_ids: + for contrast in names: + tested: dict[str, Table] = {} + left_out: dict[str, str] = {} + for name, result in results.items(): + table = result.tables.get((group, contrast)) + if table is None: + left_out[name] = absence(result, group, contrast) + else: + _check_table(table, name, group, contrast) + tested[name] = table + study_rows.append( + { + "group_id": group, + "contrast": contrast, + "study": name, + "analysed": table is not None, + "reason": left_out.get(name, ""), + "n_features_tested": 0 if table is None else len(table), + } + ) + row: dict[str, Any] = { + "group_id": group, + "contrast": contrast, + "status": _ANALYSED, + "reason": "", + "studies": list(tested), + "left_out": left_out, + "n_features": 0, + "n_features_left_out": 0, + "not_converged": [], + } + if len(tested) < fewest: + row["status"] = "too_few_studies" + row["reason"] = ( + f"{len(tested)} of the {len(results)} studies tested it, fewer " + f"than min_studies ({fewest})" + ) + rows.append(row) + continue + table, left, failed = _meta_analysed_table( + tested, estimator, chosen_test, confidence, fewest + ) + row["n_features"], row["n_features_left_out"] = len(table), left + row["not_converged"] = failed + if len(table): + tables[(group, contrast)] = table + else: + row["status"] = "no_features" + row["reason"] = ( + f"none of the {left} features that the {len(tested)} studies " + f"tested was tested by min_studies ({fewest}) of them" + ) + if failed: + warnings.warn( + f"group {group!r}, contrast {contrast!r}: the estimate of tau^2 " + f"did not converge for {len(failed)} feature" + f"{'' if len(failed) == 1 else 's'}, {quoted(failed)}; the record " + "lists them under not_converged", + RuntimeWarning, + skip_file_prefixes=(PACKAGE_DIRECTORY,), + ) + rows.append(row) + return MetaAnalysisResult( + _summary_table(rows), + Table( + { + column: ( + text_column([row[column] for row in study_rows]) + if column in ("group_id", "contrast", "study", "reason") + else np.array( + [row[column] for row in study_rows], + dtype=bool if column == "analysed" else np.int64, + ) + ) + for column in ( + "group_id", + "contrast", + "study", + "analysed", + "reason", + "n_features_tested", + ) + } + ), + tables, + { + "analysis": "meta_analysis", + "versions": software_versions(), + "settings": settings, + "studies": {name: result.provenance for name, result in results.items()}, + "analyses": rows, + }, + study_results=studies, + ) + + +def _meta_analysed_table( + tested: Mapping[str, Table], method: str, test: str, level: float, fewest: int +) -> tuple[Table, int, list[str]]: + """The meta-analysis of one group and contrast, the number of features that fewer + than ``fewest`` studies tested, and the ids of those in the table whose estimate of + tau^2 did not converge.""" + index: dict[str, int] = {} + positions = [] + for table in tested.values(): + ids = table["feature_id"].tolist() + for feature in ids: + index.setdefault(feature, len(index)) + positions.append(np.array([index[feature] for feature in ids], dtype=np.intp)) + effects = np.full((len(tested), len(index)), np.nan) + errors = np.full((len(tested), len(index)), np.nan) + symbols: list[str | None] | None = None + for row, (table, where) in enumerate(zip(tested.values(), positions, strict=True)): + effects[row, where] = table["log2_fold_change"] + errors[row, where] = table["standard_error"] + if "symbol" in table: + if symbols is None: + symbols = [None] * len(index) + for position, symbol in zip( + where.tolist(), table["symbol"].tolist(), strict=True + ): + if symbols[position] is None: + symbols[position] = symbol + fit = fit_meta_analysis( + effects, + errors, + method=method, # type: ignore[arg-type] + test=test, # type: ignore[arg-type] + confidence_level=level, + ) + keep = fit.n_studies >= fewest + ids = list(index) + columns: dict[str, Any] = { + "feature_id": text_column([f for f, k in zip(ids, keep, strict=True) if k]) + } + if symbols is not None: + # A text column whose missing symbols are None. + columns["symbol"] = np.array( + [s for s, k in zip(symbols, keep, strict=True) if k], dtype=object + ) + columns |= { + "n_studies": fit.n_studies[keep], + "log2_fold_change": fit.estimate[keep], + "standard_error": fit.standard_error[keep], + "statistic": fit.statistic[keep], + "p_value": fit.p_value[keep], + "adjusted_p_value": adjust_p_values(fit.p_value[keep], "bh"), + "ci_lower": fit.ci_lower[keep], + "ci_upper": fit.ci_upper[keep], + "tau_squared": fit.tau_squared[keep], + "i_squared": fit.i_squared[keep], + "h_squared": fit.h_squared[keep], + "q_statistic": fit.q_statistic[keep], + "q_p_value": fit.q_p_value[keep], + } + failed = [ids[i] for i in np.flatnonzero(keep & ~fit.converged)] + return Table(columns), int(np.count_nonzero(~keep)), failed + + +def _studies( + results: Any, fewest: int = 2, name: str = "studies" +) -> dict[str, DifferentialExpressionResult]: + """The results of the studies, checked, as a dict in their order.""" + if not isinstance(results, Mapping): + raise TypeError( + f"{name} must be a StudyResults, or a mapping of study names to " + "DifferentialExpressionResult, such as {'cohort_a': limma_voom(cube_a, " + f"design_a), ...}}, got {type(results).__name__}" + ) + for study, result in results.items(): + if not isinstance(study, str): + raise TypeError( + f"{name} must be keyed by study names (str), got " + f"{type(study).__name__} {study!r}" + ) + if not isinstance(result, DifferentialExpressionResult): + raise TypeError( + f"{name}[{study!r}] must be a DifferentialExpressionResult, from " + f"limma_voom or limma_trend, got {type(result).__name__}" + ) + if len(results) < fewest: + raise ValueError( + f"a meta-analysis combines at least two studies, got {len(results)}" + ) + return dict(results) + + +def _chosen(kind: str, value: Any, listed: list[list[str]]) -> list[str]: + """The groups or contrasts to analyse: those given, or every one of any study.""" + available = list(dict.fromkeys(name for names in listed for name in names)) + if value is None: + return available + if isinstance(value, str): + chosen: list[Any] = [value] + elif isinstance(value, Sequence | np.ndarray): + chosen = list(value) + else: + raise TypeError( + f"{kind}s must be a {kind} name or a list of them, got " + f"{type(value).__name__}" + ) + if not chosen: + raise ValueError( + f"{kind}s is empty; give at least one {kind}, or None for every {kind}" + ) + for name in chosen: + if not isinstance(name, str): + raise TypeError( + f"{kind}s must be {kind} names (str), got {type(name).__name__} " + f"{name!r}" + ) + repeated = [name for name in dict.fromkeys(chosen) if chosen.count(name) > 1] + if repeated: + raise ValueError(f"{kind}s lists {quoted(repeated)} more than once") + missing = [name for name in chosen if name not in available] + if missing: + raise KeyError( + f"no study analysed the {kind} {quoted(missing)}; the studies' {kind}s " + f"are: {listing(available) or '(none)'}" + ) + return chosen + + +def _check_table(table: Table, study: str, group: str, contrast: str) -> None: + """Refuse a study's table without the columns the meta-analysis reads, or with a + log2 fold change or standard error it cannot use; the message names the feature.""" + where = ( + f"the table of study {study!r} for group {group!r} and contrast {contrast!r}" + ) + missing = [ + column + for column in ("feature_id", "log2_fold_change", "standard_error") + if column not in table + ] + if missing: + raise ValueError( + f"{where} has no column {quoted(missing)}; the meta-analysis reads the " + "log2 fold changes and standard errors of limma_voom and limma_trend " + "results" + ) + effects = np.asarray(table["log2_fold_change"], dtype=np.float64) + errors = np.asarray(table["standard_error"], dtype=np.float64) + for values, wrong, expected in ( + (effects, np.isinf(effects), "finite"), + ( + errors, + ~np.isnan(errors) & ~((errors > 0.0) & np.isfinite(errors)), + "positive and finite", + ), + ): + if wrong.any(): + row = int(np.flatnonzero(wrong)[0]) + column = "log2_fold_change" if values is effects else "standard_error" + raise ValueError( + f"{where} has the {column} {values[row]} for feature " + f"{table['feature_id'][row]!r}; it must be {expected}, or NaN for a " + "feature the study did not test" + ) + + +def _min_studies(value: Any) -> int: + if isinstance(value, bool | np.bool_): + raise TypeError("min_studies must be a whole number of studies, not bool") + number = index_integer(value, "min_studies", expected="a whole number of studies") + if number < 1: + raise ValueError(f"min_studies must be at least 1, got {number}") + return number + + +def _summary_table(rows: list[dict[str, Any]]) -> Table: + columns: dict[str, Any] = { + name: text_column([row[name] for row in rows]) + for name in ("group_id", "contrast", "status", "reason") + } + columns["n_studies"] = np.array([len(row["studies"]) for row in rows], np.int64) + for name in ("n_features", "n_features_left_out"): + columns[name] = np.array([row[name] for row in rows], dtype=np.int64) + return Table(columns) + + +def _restore_result( + summary: Table, + studies: Table, + tables: Mapping[tuple[str, str], Table], + provenance: Mapping[str, Any], + study_results: Mapping[str, DifferentialExpressionResult] | None, +) -> MetaAnalysisResult: + return MetaAnalysisResult( + summary, studies, tables, provenance, study_results=study_results + ) diff --git a/cytepool/networks.py b/cytepool/networks.py index 36bb685..5389f2b 100644 --- a/cytepool/networks.py +++ b/cytepool/networks.py @@ -2,16 +2,17 @@ CollecTRI's transcription factors and PROGENy's pathways, downloaded from their releases, and networks given as tables. -A :class:`Network` maps each source, a transcription factor or a pathway, to its -targets, the genes it regulates or that respond to it, each with a weight whose sign -says whether the source raises the gene (positive) or lowers it (negative). The -activity functions of :mod:`cytepool.activity` take one, as decoupler takes its -networks (Badia-i-Mompel et al. 2022): a table with the columns ``source``, -``target`` and ``weight``. A network records its name, version, where it comes from, -its licence and its citation, and, for a network read from a file, the name of the -file and its SHA-256 digest. - -CollecTRI (:func:`collectri`): +A :class:`~cytepool.Network` maps each source, a transcription factor or a pathway, to +its targets, the genes it regulates or that respond to it, each with a weight whose sign +says whether the source raises the gene (positive) or lowers it (negative). The activity +functions (:func:`~cytepool.ulm`, :func:`~cytepool.mlm` and +:func:`~cytepool.weighted_aggregate`) take one, as decoupler takes its networks +(Badia-i-Mompel et al. 2022): a table with the columns ``source``, ``target`` and +``weight``. A network records its name, version, where it comes from, its licence and +its citation, and, for a network read from a file, the name of the file and its SHA-256 +digest. + +CollecTRI (:func:`~cytepool.collectri`): The signed regulons of 1,186 human transcription factors, 43,175 interactions, that Mueller-Dott et al. (2023) assembled from twelve resources of literature curation and text mining: version 2.0 of the file ``CollecTRI_regulons.csv`` of @@ -32,7 +33,7 @@ CC BY-SA 4.0 (TRRUST), LGPL 3.0 (HTRIdb) and an informal licence (DoRothEA's level A). Use under any level is subject to these terms, such as attribution. -PROGENy (:func:`progeny`): +PROGENy (:func:`~cytepool.progeny`): The weights of the genes that respond to 14 signalling pathways, from the perturbation experiments of Schubert et al. (2018), for human, and their transfer to mouse (Holland, Szalai and Saez-Rodriguez 2020): the data frames @@ -216,7 +217,7 @@ class Network(Mapping[str, Mapping[str, float]]): columns, such as a p-value or the resources of an interaction, are kept with the edges. Or a mapping of source to its targets: a mapping of gene id to weight, or a list of gene ids, each of weight 1, so that a - :class:`~cytepool.gene_sets.GeneSets` is a network of unweighted sets. + :class:`~cytepool.GeneSets` is a network of unweighted sets. name: The network's name, such as ``"CollecTRI"``. version: Its version or release. url: Where it was obtained. @@ -419,21 +420,20 @@ def select(self, sources: str | Any) -> "Network": def to_gene_sets(self) -> GeneSets: """Return the targets of each source as a gene set, without their weights. - The gene-set tests and per-sample scores take gene sets, such as the - regulons of CollecTRI to test with :func:`~cytepool.gsea.gsea` or score with - :func:`~cytepool.pathway_scores.gsva`. Each source becomes a set of its - targets, in the order of the edges, named by the source. The weights, and - so the signs that tell activated targets from repressed ones, are left out: - :func:`~cytepool.activity.ulm` and the other activity methods use them. The - sets keep the network's name, as their ``source``, version, licence, file - and digest. + The gene-set tests and per-sample scores take gene sets, such as the regulons of + CollecTRI to test with :func:`~cytepool.gsea` or score with + :func:`~cytepool.gsva`. Each source becomes a set of its targets, in the order + of the edges, named by the source. The weights, and so the signs that tell + activated targets from repressed ones, are left out: :func:`~cytepool.ulm` and + the other activity methods use them. The sets keep the network's name, version, + licence, file and digest. Returns: - A :class:`~cytepool.gene_sets.GeneSets` with one set per source. + A :class:`~cytepool.GeneSets` with one set per source. """ return GeneSets( {source: list(self._targets[source]) for source in self._sources}, - source=self._name, + name=self._name, version=self._version, licence=self._licence, file=self._file, diff --git a/cytepool/normalization.py b/cytepool/normalization.py index ce2ee9f..91e913b 100644 --- a/cytepool/normalization.py +++ b/cytepool/normalization.py @@ -19,7 +19,7 @@ :func:`median_ratio_size_factors`, the median-of-ratios method of Anders and Huber (2010); - counts per million and log counts per million as edgeR's ``cpm`` computes - them: :func:`cpm` and :func:`log_cpm`; + them: :func:`cpm` and :func:`log2_cpm`; - edgeR's ``filterByExpr`` (Chen, Lun and Smyth 2016): :func:`filter_by_expression`. The routines repeat R's floating-point operations wherever an operation @@ -75,7 +75,7 @@ "cpm", "filter_by_expression", "library_sizes", - "log_cpm", + "log2_cpm", "median_ratio_size_factors", "rle_factors", "tmm_factors", @@ -373,7 +373,7 @@ def cpm( ) from None -def log_cpm( +def log2_cpm( counts: npt.ArrayLike, *, library_sizes: npt.ArrayLike | None = None, @@ -416,8 +416,8 @@ def log_cpm( def filter_by_expression( counts: npt.ArrayLike, *, - group: npt.ArrayLike | None = None, design: object | None = None, + sample_groups: npt.ArrayLike | None = None, library_sizes: npt.ArrayLike | None = None, norm_factors: npt.ArrayLike | None = None, min_count: float = 10, @@ -435,12 +435,11 @@ def filter_by_expression( of the design with an intercept column added, as R's ``hat()`` adds it (found by probing); the two agree for a one-way design. When n exceeds ``large_n`` it is relaxed to ``large_n + (n - large_n) * min_prop``. - Without group and design all samples form one group, as in edgeR, with a + Without groups and design all samples form one group, as in edgeR, with a warning. Args: counts: Counts, samples x features. - group: Group label of each sample; labels must not be missing. design: Design matrix, samples x coefficients, or any object with a ``matrix`` attribute holding one, such as a ``Design``. With the intercept column added, its rank is decided as R's ``qr()`` decides @@ -449,7 +448,9 @@ def filter_by_expression( effects. Leaving them out can only raise n, and so keep fewer features. When a random effect separates groups whose expression may differ, such as the tissues of a variance partition, pass those - groups as ``group`` instead. + groups as ``sample_groups`` instead. + sample_groups: The group of each sample, such as its condition (edgeR's + ``group``), in place of the design; labels must not be missing. library_sizes: Library size of each sample; defaults to the total counts. Must be positive. norm_factors: Normalization factor of each sample, multiplied into the @@ -466,9 +467,9 @@ def filter_by_expression( Raises: TypeError: If an argument has the wrong type, or is a numpy masked array. - ValueError: If an argument is out of range, both group and design are - given (edgeR silently ignores the design then), or a sample has - library size zero. + ValueError: If an argument is out of range, both ``sample_groups`` and + ``design`` are given (edgeR silently ignores the design then), or a + sample has library size zero. OverflowError: If a library size, or the total count of a feature, exceeds the float64 range. """ @@ -481,17 +482,17 @@ def filter_by_expression( min_prop = real_number(min_prop, "min_prop", finite=True) if not 0.0 <= min_prop <= 1.0: raise ValueError(f"min_prop must be in [0, 1], got {min_prop}") - if group is not None and design is not None: - raise ValueError("pass group or design, not both") - if group is not None: - min_samples = float(_smallest_group_size(group, n_samples)) + if sample_groups is not None and design is not None: + raise ValueError("pass sample_groups or design, not both") + if sample_groups is not None: + min_samples = float(_smallest_group_size(sample_groups, n_samples)) elif design is not None: matrix = _design_matrix(design, n_samples) leverages = _leverages(np.column_stack([np.ones(n_samples), matrix])) min_samples = 1.0 / float(np.max(leverages)) else: warnings.warn( - "no group or design given, so all samples are treated as one group", + "no sample_groups or design given, so all samples are treated as one group", UserWarning, stacklevel=2, ) @@ -530,17 +531,18 @@ def _sample_index(value: object, n_samples: int) -> int: def _smallest_group_size(group: npt.ArrayLike, n_samples: int) -> int: - labels = unmasked_array(group, "group") + labels = unmasked_array(group, "sample_groups") if labels.ndim != 1 or labels.shape[0] != n_samples: raise ValueError( - f"group must have one label per sample ({n_samples}), " + f"sample_groups must have one label per sample ({n_samples}), " f"got shape {labels.shape}" ) values = labels.tolist() for i, value in enumerate(values): if value is None or value != value: raise ValueError( - f"group labels must not be missing; sample {i} has {value}" + f"the labels of sample_groups must not be missing; sample {i} has " + f"{value}" ) return min(Counter(values).values()) diff --git a/cytepool/over_representation.py b/cytepool/over_representation_analysis.py similarity index 88% rename from cytepool/over_representation.py rename to cytepool/over_representation_analysis.py index 60e0272..5a4f27c 100644 --- a/cytepool/over_representation.py +++ b/cytepool/over_representation_analysis.py @@ -1,31 +1,31 @@ """Over-representation analysis: gene sets among the genes called significant. -:func:`over_representation` asks of each gene set whether the genes called significant -in an analysis hold more of its genes than chance would give, by the one-sided -hypergeometric test against a universe of genes, as limma's ``kegga`` and ``goana`` -compute it with their defaults (Young et al. 2010). From a differential-expression -result it tests the genes that go up and the genes that go down separately, as -``kegga`` and ``goana`` test a fit of limma, and the genes that change in either -direction together. Results match limma 3.62.1 except where noted under "Deliberate -differences". +:func:`~cytepool.over_representation` asks of each gene set whether the genes called +significant in an analysis hold more of its genes than chance would give, by the +one-sided hypergeometric test against a universe of genes, as limma's ``kegga`` and +``goana`` compute it with their defaults (Young et al. 2010). From a +differential-expression result it tests the genes that go up and the genes that go down +separately, as ``kegga`` and ``goana`` test a fit of limma, and the genes that change in +either direction together. Results match limma 3.62.1 except where noted under +"Deliberate differences". The test: The universe holds the N genes that could have been called significant. Of a gene set, K genes are in the universe; n genes of the universe are selected, and k of them are in the set. Were the n genes drawn at random from the universe, without - regard to the set, k would follow the hypergeometric distribution of n draws - without replacement from N genes of which K are in the set, whose mean K n / N is - the expected overlap. The p-value is P(X >= k), the one-sided p-value of Fisher's - (1922) exact test of the 2 x 2 table of the genes of the universe, in the set or - not and selected or not (Rivals et al. 2007); the hypergeometric test of - functional categories goes back to Tavazoie et al. (1999). It is computed by + regard to the set, k would follow the hypergeometric distribution of n draws without + replacement from N genes of which K are in the set, whose mean K n / N is the + expected overlap. The p-value is P(X >= k), the one-sided p-value of Fisher's (1922) + exact test of the 2 x 2 table of the genes of the universe, in the set or not and + selected or not (Rivals et al. 2007); the hypergeometric test of functional + categories goes back to Tavazoie et al. (1999). It is computed by :func:`~cytepool.stats.distributions.hypergeom_sf`, which keeps its relative - accuracy far into the tail. The adjusted p-values are Benjamini and Hochberg's (1995), over - the sets of each list of selected genes; limma's ``kegga`` and ``goana`` report - p-values only. + accuracy far into the tail. The adjusted p-values are Benjamini and Hochberg's + (1995), over the sets of each list of selected genes; limma's ``kegga`` and + ``goana`` report p-values only. The genes and the universe: - - From a :class:`~cytepool.differential_expression.DifferentialExpressionResult` + - From a :class:`~cytepool.DifferentialExpressionResult` (one group and contrast) or any table of results, the genes whose adjusted p-value is below ``alpha`` are selected, as ``kegga`` selects the genes of a fit whose adjusted p-value is below its ``FDR`` (strictly, found by probing): up @@ -48,15 +48,14 @@ 2022). The default, the genes tested, is the universe that the test assumes. What the test assumes: - The test takes genes as independent draws. The genes of a set are often - correlated, as co-regulated genes are, which makes its p-values too small - (Goeman and Buehlmann 2007); :func:`~cytepool.stats.gene_set_tests.camera` allows - for the correlation. The test also depends on the cutoff ``alpha`` and ignores the - size of each change. In RNA-seq data, long and highly expressed genes are called - significant more often, which biases the test towards sets of such genes (Young et - al. 2010); the adjustment of ``kegga`` and ``goana`` for such a trend - (``trend``, Wallenius' noncentral hypergeometric distribution) is not - implemented. + The test takes genes as independent draws. The genes of a set are often correlated, + as co-regulated genes are, which makes its p-values too small (Goeman and Buehlmann + 2007); :func:`~cytepool.camera` allows for the correlation. The test also depends on + the cutoff ``alpha`` and ignores the size of each change. In RNA-seq data, long and + highly expressed genes are called significant more often, which biases the test + towards sets of such genes (Young et al. 2010); the adjustment of ``kegga`` and + ``goana`` for such a trend (``trend``, Wallenius' noncentral hypergeometric + distribution) is not implemented. Deliberate differences from limma: - A set without genes in the universe has a row with no p-value (NaN), which the @@ -147,13 +146,13 @@ def over_representation( Args: genes: The genes to test: a - :class:`~cytepool.differential_expression.DifferentialExpressionResult`, - with ``group`` and ``contrast``; a table of results, a ``Table`` or a - pandas DataFrame, with a column of gene ids, a column of adjusted p-values - and, for directions, a column of fold changes; or a list of the ids of the - selected genes, with ``universe``. - gene_sets: A :class:`~cytepool.gene_sets.GeneSets`, such as one read by - :func:`~cytepool.gene_sets.read_gmt`, or a mapping of set name to gene ids. + :class:`~cytepool.DifferentialExpressionResult`, with ``group`` and + ``contrast``; a table of results, a ``Table`` or a pandas DataFrame, with a + column of gene ids, a column of adjusted p-values and, for directions, a + column of fold changes; or a list of the ids of the selected genes, with + ``universe``. + gene_sets: A :class:`~cytepool.GeneSets`, such as one read by + :func:`~cytepool.read_gmt`, or a mapping of set name to gene ids. group: The group (cell type) of a ``DifferentialExpressionResult``; it may be left out when the result has one. contrast: The contrast of a ``DifferentialExpressionResult``; it may be left diff --git a/cytepool/pathway_scores.py b/cytepool/pathway_scores.py index 4669742..6408243 100644 --- a/cytepool/pathway_scores.py +++ b/cytepool/pathway_scores.py @@ -6,35 +6,37 @@ (:func:`~cytepool.plotting.plot_pathway_scores`) and tested with the designs and contrasts of the linear models, as GSVA's guide tests its scores with limma. They compute the four methods of GSVA 2.0.0 (Haenzelmann, Castelo and Guinney 2013) with its -options and defaults: :func:`gsva`, the method of that paper; :func:`ssgsea`, single -sample GSEA (Barbie et al. 2009); :func:`combined_z_scores` (Lee et al. 2008); and -:func:`plage` (Tomfohr, Lu and Kepler 2005). ``validation/pathway_scores.py`` compares -them with GSVA run as a black box. GSVA is licensed under the GPL: the methods are -implemented from the papers and GSVA's documentation, and what these leave open was -found by running GSVA on chosen inputs, as marked "found by probing" below. +options and defaults: :func:`~cytepool.gsva`, the method of that paper; +:func:`~cytepool.ssgsea`, single sample GSEA (Barbie et al. 2009); +:func:`~cytepool.combined_z_scores` (Lee et al. 2008); and :func:`~cytepool.plage` +(Tomfohr, Lu and Kepler 2005). ``validation/pathway_scores.py`` compares them with GSVA +run as a black box. GSVA is licensed under the GPL: the methods are implemented from the +papers and GSVA's documentation, and what these leave open was found by running GSVA on +chosen inputs, as marked "found by probing" below. The data: Values are samples x genes: a 2-D array with one gene id per column, or a group - (cell type) of a :class:`~cytepool.cube.Cube`, of which the samples with at least + (cell type) of a :class:`~cytepool.Cube`, of which the samples with at least ``min_cells`` cells and some counts are scored, over the genes that edgeR's ``filterByExpr`` keeps (:func:`~cytepool.normalization.filter_by_expression`), - as log2 counts per million after TMM normalization (``values="log_cpm"``, + as log2 counts per million after TMM normalization (``values="log2_cpm"``, edgeR's ``cpm(log = TRUE)``) or as counts (``values="counts"``), the values GSVA's guide passes. NaN marks a missing value; infinite values are refused. Each method's docstring says how it treats missing values. - :func:`gsva`, :func:`combined_z_scores` and :func:`plage` leave out the genes whose - values are all equal (constant), before the sets are matched, as GSVA does; - :func:`ssgsea` keeps them, as GSVA does (found by probing). + :func:`~cytepool.gsva`, :func:`~cytepool.combined_z_scores` and + :func:`~cytepool.plage` leave out the genes whose values are all equal (constant), + before the sets are matched, as GSVA does; :func:`~cytepool.ssgsea` keeps them, as + GSVA does (found by probing). Gene sets: - The sets are a :class:`~cytepool.gene_sets.GeneSets` or a mapping of set name to - gene ids, matched to the genes' ids exactly. A set is scored when ``min_size`` to - ``max_size`` of its genes are among the genes scored (GSVA's ``minSize`` and - ``maxSize``, counted after matching); the others are left out, as GSVA leaves - them out. The result keeps the order of the sets. + The sets are a :class:`~cytepool.GeneSets` or a mapping of set name to gene ids, + matched to the genes' ids exactly. A set is scored when ``min_size`` to ``max_size`` + of its genes are among the genes scored (GSVA's ``minSize`` and ``maxSize``, counted + after matching); the others are left out, as GSVA leaves them out. The result keeps + the order of the sets. -GSVA (:func:`gsva`): +GSVA (:func:`~cytepool.gsva`): 1. Each gene's values are turned into the probability of a value at most as high under the gene's distribution across samples, estimated by a kernel (Haenzelmann, Castelo and Guinney 2013): for gene i in sample j, @@ -75,7 +77,7 @@ of the sample's observed genes outside the set, with p_j and k_j counting the genes observed in sample j. -ssGSEA (:func:`ssgsea`): +ssGSEA (:func:`~cytepool.ssgsea`): Each sample ranks its genes by their values, and the walk down the ranking rises at each gene of a set by its rank to the power alpha (``exponent``), as a share of the set's total, and falls at each other gene by 1/(p - k) (Barbie et al. @@ -90,13 +92,13 @@ did and GSVA does by default. Missing values are left out of each sample's ranking, as GSVA's ``use = "na.rm"`` does. -Combined z-scores (:func:`combined_z_scores`): +Combined z-scores (:func:`~cytepool.combined_z_scores`): Each gene is standardized across samples, z = (x - mean) / sd, and a set's score in a sample is sum_{g in G} z_g / sqrt(k) (Lee et al. 2008). A missing value leaves its gene out of the mean and standard deviation, and makes the score of every set that holds the gene NaN in that sample, as in GSVA. -PLAGE (:func:`plage`): +PLAGE (:func:`~cytepool.plage`): The standardized values of a set's k genes form a k x n matrix, and the set's scores are the first right singular vector of that matrix: the samples' coordinates on the direction along which the set's genes vary most together @@ -189,7 +191,13 @@ restore_provenance, ) from ._columns import text_column -from ._expression_values import MIN_CELLS, ExpressionValues, expression_values +from ._expression_values import ( + MIN_CELLS, + PROFILE_ID, + ExpressionValues, + expression_values, + profile_table, +) from ._jit import by_chunks, jit, thread_limit from ._masked_arrays import read_only, reject_masked from ._messages import quoted @@ -225,18 +233,20 @@ @dataclass(frozen=True, eq=False, repr=False) class PathwayScores: - """Scores of gene sets in every sample. + """Scores of gene sets in every profile: every sample of an array, or of a + cube's group. - Rows of ``scores`` are samples, in the order of ``samples``, and columns are the - sets scored, in the order of ``gene_sets``, so that - :func:`~cytepool.stats.linear_models.fit_linear_model` takes ``scores`` as its - values, with a design built from ``samples``. The arrays are read-only. + Rows of ``scores`` are profiles, in the order of ``profiles``, and columns are + the sets scored, in the order of ``gene_sets``, so that :func:`~cytepool.limma` + tests them with a design of the samples, matched by ``sample_id``. The arrays are + read-only. Attributes: - scores: ``(n_samples, n_sets)`` float64 scores; NaN where a set has none in - a sample. - samples: One row per sample: ``sample_id`` and, for a cube's group, the - columns of the cube's sample table. + scores: ``(n_profiles, n_sets)`` float64 scores; NaN where a set has none in + a profile. + profiles: One row per profile: ``profile_id``, the sample's id, then + ``sample_id`` and, for a cube's group, the other columns of the cube's + sample table, as :class:`~cytepool.Activities` names its profiles. gene_sets: The sets scored, each with the genes that were used, in the order given, with the description and source of the collection. method: ``"gsva"``, ``"ssgsea"``, ``"combined_z_scores"`` or ``"plage"``. @@ -245,7 +255,7 @@ class PathwayScores: """ scores: FloatArray - samples: Table + profiles: Table gene_sets: GeneSets method: str settings: Mapping[str, Any] @@ -257,7 +267,7 @@ def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: _restored_scores, ( self.scores, - self.samples, + self.profiles, self.gene_sets, self.method, dict(self.settings), @@ -272,7 +282,7 @@ def provenance(self) -> dict[str, Any] | None: the cube's fingerprint, the digests it is built from and its provenance (``"cube"``), the layer read and the settings; None for scores of values given as an array. The cube's record costs what it costs for - :func:`~cytepool.differential_expression.limma_voom` ("Provenance").""" + :func:`~cytepool.limma_voom` ("Provenance").""" return provenance_of(self) @property @@ -283,36 +293,36 @@ def n_genes(self) -> IntArray: return read_only(np.array(sizes, dtype=np.int64)) def table(self) -> Table: - """Return the scores as a table with one row per set and sample. + """Return the scores as a table with one row per set and profile. Returns: - A table with ``gene_set``, ``n_genes``, ``sample_id`` and ``score``, the - rows of each set together, in the order of ``gene_sets``, and its samples - in the order of ``samples``. + A table with ``gene_set``, ``n_genes``, ``profile_id`` and ``score``, the + rows of each set together, in the order of ``gene_sets``, and its + profiles in the order of ``profiles``. """ - n_samples, n_sets = self.scores.shape + n_profiles, n_sets = self.scores.shape names = list(self.gene_sets) - ids = self.samples["sample_id"].tolist() + ids = self.profiles[PROFILE_ID].tolist() return Table( { "gene_set": text_column([name for name in names for _ in ids]), - "n_genes": np.repeat(self.n_genes, n_samples), - "sample_id": text_column(ids * n_sets), + "n_genes": np.repeat(self.n_genes, n_profiles), + "profile_id": text_column(ids * n_sets), "score": self.scores.T.ravel().copy(), } ) def __repr__(self) -> str: - n_samples, n_sets = self.scores.shape + n_profiles, n_sets = self.scores.shape return ( - f"PathwayScores(method={self.method!r}, n_samples={n_samples}, " + f"PathwayScores(method={self.method!r}, n_profiles={n_profiles}, " f"n_sets={n_sets})" ) def _restored_scores( scores: FloatArray, - samples: Table, + profiles: Table, gene_sets: GeneSets, method: str, settings: dict[str, Any], @@ -321,7 +331,7 @@ def _restored_scores( """Pathway scores unpickled: read-only again, with their provenance.""" restored = PathwayScores( read_only(np.asarray(scores)), - samples, + profiles, gene_sets, method, MappingProxyType(settings), @@ -333,10 +343,11 @@ def gsva( data: Any, gene_sets: Mapping[str, Any], *, - gene_ids: Any = None, + id_column: str = "feature_id", + feature_ids: Any = None, sample_ids: Any = None, group: str | None = None, - values: str = "log_cpm", + values: str = "log2_cpm", min_cells: int = MIN_CELLS, expression_filter: bool | Mapping[str, Any] = True, kernel: str = "auto", @@ -360,28 +371,27 @@ def gsva( Args: data: ``(n_samples, n_genes)`` expression values, such as log-CPM or, for the Poisson kernel, counts; NaN marks a missing value. Or a - :class:`~cytepool.cube.Cube`, whose ``group`` is scored. - gene_sets: A :class:`~cytepool.gene_sets.GeneSets`, such as one read by - :func:`~cytepool.gene_sets.read_gmt`, or a mapping of set name to gene - ids. - gene_ids: The genes' ids, which the sets name: one per column of an array - (None, NaN or an empty string for a gene without one); for a cube, the - name of a column of its features table, ``"feature_id"`` by default or - ``"symbol"``, or one id per feature. + :class:`~cytepool.Cube`, whose ``group`` is scored. + gene_sets: A :class:`~cytepool.GeneSets`, such as one read by + :func:`~cytepool.read_gmt`, or a mapping of set name to gene ids. + id_column: For a cube, the column of its features table with the genes' + ids, which the sets name: ``"feature_id"`` by default, or ``"symbol"``. + feature_ids: For an array, the genes' ids, one per column (None, NaN or an + empty string for a gene without one). sample_ids: For an array, one id per row; by default ``"1"``, ``"2"``, ... group: The group (cell type) of a cube to score; it may be left out when the cube has one. - values: For a cube, the values scored: ``"log_cpm"``, log2 counts per million + values: For a cube, the values scored: ``"log2_cpm"``, log2 counts per million after TMM normalization, or ``"counts"``. min_cells: For a cube, the fewest cells a sample needs in the group to be scored, at least 0, as for - :func:`~cytepool.differential_expression.limma_voom`. + :func:`~cytepool.limma_voom`. expression_filter: For a cube, whether to score only the genes that :func:`~cytepool.normalization.filter_by_expression` keeps, with the samples as one group; or a mapping of its settings, which may name a column of the cube's samples whose values group them, such as ``{"sample_groups": "condition"}``, as - :func:`~cytepool.differential_expression.limma_voom` takes it. + :func:`~cytepool.limma_voom` takes it. kernel: How each gene's distribution is estimated (GSVA's ``kcdf``): ``"gaussian"`` for continuous values, ``"poisson"`` for counts (whole numbers of at least 0), ``"none"`` for the empirical distribution @@ -419,7 +429,8 @@ def gsva( >>> y = rng.normal(size=(6, 40)) >>> y[3:, :5] += 2.0 # the first five genes are higher in the last samples >>> genes = [f"g{j}" for j in range(40)] - >>> result = gsva(y, {"up": genes[:5], "other": genes[20:30]}, gene_ids=genes) + >>> sets = {"up": genes[:5], "other": genes[20:30]} + >>> result = gsva(y, sets, feature_ids=genes) >>> result.scores[:, 0].round(2).tolist() [-0.54, -0.82, -0.69, 0.78, 0.34, 0.77] """ @@ -432,7 +443,14 @@ def gsva( score = _SCORE_TYPES.index(checked_choice(score_type, "score_type", _SCORE_TYPES)) collection = _collection(gene_sets) expression = expression_values( - data, gene_ids, sample_ids, group, values, min_cells, expression_filter + data, + id_column, + feature_ids, + sample_ids, + group, + values, + min_cells, + expression_filter, ) n_samples = expression.values.shape[0] if kind == "auto": @@ -472,10 +490,11 @@ def ssgsea( data: Any, gene_sets: Mapping[str, Any], *, - gene_ids: Any = None, + id_column: str = "feature_id", + feature_ids: Any = None, sample_ids: Any = None, group: str | None = None, - values: str = "log_cpm", + values: str = "log2_cpm", min_cells: int = MIN_CELLS, expression_filter: bool | Mapping[str, Any] = True, exponent: float = 0.25, @@ -496,13 +515,14 @@ def ssgsea( Args: data: ``(n_samples, n_genes)`` expression values; NaN marks a missing value, which is left out of its sample's ranking. Or a - :class:`~cytepool.cube.Cube`, whose ``group`` is scored. - gene_sets: A :class:`~cytepool.gene_sets.GeneSets` or a mapping of set name to + :class:`~cytepool.Cube`, whose ``group`` is scored. + gene_sets: A :class:`~cytepool.GeneSets` or a mapping of set name to gene ids. - gene_ids: The genes' ids, as for :func:`gsva`. + id_column: For a cube, the column of its gene ids, as for :func:`gsva`. + feature_ids: For an array, the genes' ids, as for :func:`gsva`. sample_ids: For an array, one id per row, as for :func:`gsva`. group: The group of a cube, as for :func:`gsva`. - values: For a cube, ``"log_cpm"`` or ``"counts"``, as for :func:`gsva`; both + values: For a cube, ``"log2_cpm"`` or ``"counts"``, as for :func:`gsva`; both rank the genes of a sample alike. min_cells: For a cube, as for :func:`gsva`. expression_filter: For a cube, as for :func:`gsva`. @@ -534,7 +554,14 @@ def ssgsea( scaled = checked_flag(normalize, "normalize") collection = _collection(gene_sets) expression = expression_values( - data, gene_ids, sample_ids, group, values, min_cells, expression_filter + data, + id_column, + feature_ids, + sample_ids, + group, + values, + min_cells, + expression_filter, ) kept = np.arange(expression.values.shape[1]) members, names = _matched_sets(collection, expression.gene_ids, kept, checked) @@ -566,10 +593,11 @@ def combined_z_scores( data: Any, gene_sets: Mapping[str, Any], *, - gene_ids: Any = None, + id_column: str = "feature_id", + feature_ids: Any = None, sample_ids: Any = None, group: str | None = None, - values: str = "log_cpm", + values: str = "log2_cpm", min_cells: int = MIN_CELLS, expression_filter: bool | Mapping[str, Any] = True, min_size: int = 1, @@ -585,14 +613,15 @@ def combined_z_scores( Args: data: ``(n_samples, n_genes)`` expression values on a log scale; NaN marks a - missing value. Or a :class:`~cytepool.cube.Cube`, whose ``group`` is + missing value. Or a :class:`~cytepool.Cube`, whose ``group`` is scored. - gene_sets: A :class:`~cytepool.gene_sets.GeneSets` or a mapping of set name to + gene_sets: A :class:`~cytepool.GeneSets` or a mapping of set name to gene ids. - gene_ids: The genes' ids, as for :func:`gsva`. + id_column: For a cube, the column of its gene ids, as for :func:`gsva`. + feature_ids: For an array, the genes' ids, as for :func:`gsva`. sample_ids: For an array, one id per row, as for :func:`gsva`. group: The group of a cube, as for :func:`gsva`. - values: For a cube, ``"log_cpm"`` or ``"counts"``, as for :func:`gsva`. + values: For a cube, ``"log2_cpm"`` or ``"counts"``, as for :func:`gsva`. min_cells: For a cube, as for :func:`gsva`. expression_filter: For a cube, as for :func:`gsva`. min_size: The fewest of a set's genes among the genes for the set to be @@ -615,7 +644,14 @@ def combined_z_scores( checked = _checked_size(min_size, max_size) collection = _collection(gene_sets) expression = expression_values( - data, gene_ids, sample_ids, group, values, min_cells, expression_filter + data, + id_column, + feature_ids, + sample_ids, + group, + values, + min_cells, + expression_filter, ) kept = _varying_genes(expression.values) members, names = _matched_sets(collection, expression.gene_ids, kept, checked) @@ -641,10 +677,11 @@ def plage( data: Any, gene_sets: Mapping[str, Any], *, - gene_ids: Any = None, + id_column: str = "feature_id", + feature_ids: Any = None, sample_ids: Any = None, group: str | None = None, - values: str = "log_cpm", + values: str = "log2_cpm", min_cells: int = MIN_CELLS, expression_filter: bool | Mapping[str, Any] = True, min_size: int = 1, @@ -663,14 +700,15 @@ def plage( Args: data: ``(n_samples, n_genes)`` expression values on a log scale, without - missing values. Or a :class:`~cytepool.cube.Cube`, whose ``group`` is + missing values. Or a :class:`~cytepool.Cube`, whose ``group`` is scored. - gene_sets: A :class:`~cytepool.gene_sets.GeneSets` or a mapping of set name to + gene_sets: A :class:`~cytepool.GeneSets` or a mapping of set name to gene ids. - gene_ids: The genes' ids, as for :func:`gsva`. + id_column: For a cube, the column of its gene ids, as for :func:`gsva`. + feature_ids: For an array, the genes' ids, as for :func:`gsva`. sample_ids: For an array, one id per row, as for :func:`gsva`. group: The group of a cube, as for :func:`gsva`. - values: For a cube, ``"log_cpm"`` or ``"counts"``, as for :func:`gsva`. + values: For a cube, ``"log2_cpm"`` or ``"counts"``, as for :func:`gsva`. min_cells: For a cube, as for :func:`gsva`. expression_filter: For a cube, as for :func:`gsva`. min_size: The fewest of a set's genes among the genes for the set to be @@ -693,7 +731,8 @@ def plage( collection = _collection(gene_sets) expression = expression_values( data, - gene_ids, + id_column, + feature_ids, sample_ids, group, values, @@ -804,8 +843,8 @@ def _matched_sets( examples = [gene for genes in collection.values() for gene in genes[:3]][:3] ids = [gene for gene in gene_ids if gene is not None][:3] raise ValueError( - "no gene of any set is among the genes scored; the sets and gene_ids " - "must name genes the same way (for example both by symbol, or both by " + "no gene of any set is among the genes scored; the sets and the genes' " + "ids must name genes the same way (for example both by symbol, or both by " f"Ensembl id): the sets name genes such as {quoted(examples)}, the data " f"genes such as {quoted(ids)}" ) @@ -835,7 +874,7 @@ def _result( for name, positions in zip(names, members, strict=True) }, descriptions={name: collection.descriptions[name] for name in names}, - source=collection.source, + name=collection.name, version=collection.version, licence=collection.licence, file=collection.file, @@ -843,7 +882,7 @@ def _result( ) result = PathwayScores( read_only(np.ascontiguousarray(scores, dtype=np.float64)), - expression.samples, + profile_table(expression.samples, "the scores"), used, method, MappingProxyType(dict(settings)), diff --git a/cytepool/plotting.py b/cytepool/plotting.py index 1815a52..12a3361 100644 --- a/cytepool/plotting.py +++ b/cytepool/plotting.py @@ -61,8 +61,8 @@ - Variance partitioning: :func:`plot_variance_fractions`, the violins (Hintze and Nelson 1998) of the fractions of variance of every variable over the genes, as variancePartition's ``plotVarPart`` draws them (Hoffman and Schadt 2016); - :func:`plot_gene_variance_fractions`, the fractions of chosen genes as stacked - bars, as its ``plotPercentBars`` draws them. + :func:`plot_feature_variance_fractions`, the fractions of chosen features as + stacked bars, as its ``plotPercentBars`` draws them. ``validation/plotting.py`` compares what each figure shows with what the R function it follows draws for the same data, where one exists, and @@ -227,25 +227,23 @@ from .cube import Cube from .differential_expression import DifferentialExpressionResult from .gene_sets import GeneSets -from .held_out_prediction import HeldOutPrediction -from .meta_analysis import MetaAnalysisResult -from .normalization import filter_by_expression, log_cpm, tmm_factors +from .held_out_studies import HeldOutPrediction +from .meta_analysis_of_studies import MetaAnalysisResult +from .normalization import filter_by_expression, log2_cpm, tmm_factors from .pathway_scores import PathwayScores -from .principal_components import PrincipalComponents, principal_components -from .replication import ReplicationResult +from .principal_component_analysis import PrincipalComponents, principal_components +from .replication_across_studies import ReplicationResult from .stats.deseq2 import DESeq2Dispersions, DESeq2Fit, DESeq2Results from .stats.distributions import f_cdf, f_sf, norm_isf from .stats.empirical_bayes import ModeratedFit, TreatFit from .stats.multiple_testing import adjust_p_values from .stats.quasi_likelihood import QuasiLikelihoodFit, QuasiLikelihoodTest +from .stats.variance_fractions import RESIDUALS, VarianceFractions from .stats.voom import VoomResult -from .sumrank import SumRankResult +from .study_results import StudyResults +from .sumrank_across_studies import SumRankResult from .table import Table -from .variance_partitioning import ( - RESIDUALS, - VarianceFractions, - VariancePartitioningResult, -) +from .variance_components import VariancePartitioningResult if TYPE_CHECKING: from matplotlib.axes import Axes @@ -258,9 +256,9 @@ "plot_dispersions", "plot_dotplot", "plot_enrichment", + "plot_feature_variance_fractions", "plot_forest", "plot_gene_sets", - "plot_gene_variance_fractions", "plot_gsea_table", "plot_held_out_auc", "plot_md", @@ -437,12 +435,13 @@ def _patch(label: str, color: str, **properties: Any) -> Any: ) -def _legend(ax: "Axes", handles: list[Any], *, inside: bool = False) -> None: +def _legend(ax: "Axes", handles: list[Any], *, inside: bool = False) -> bool: """A legend to the right of the plot; with ``inside``, a legend of a few entries inside the plot instead, where it covers no point, line, bar or label at the size the figure is laid out at, so that panels side by side keep their width. Where every place inside would cover something, or the legend has more entries, it goes - beside the plot. Entries without a marker or line are headers, set in bold.""" + beside the plot. Entries without a marker or line are headers, set in bold. + Returns whether the legend is inside the plot.""" if inside and len(handles) <= _FEW_ENTRIES: legend = ax.legend(handles=handles, loc="best", framealpha=0.85) _bold_headers(handles, legend) @@ -450,7 +449,7 @@ def _legend(ax: "Axes", handles: list[Any], *, inside: bool = False) -> None: for place in _INSIDE_PLACES: legend.set_loc(place) if not obstacles.covered_by(legend.get_window_extent()): - return + return True legend.remove() legend = ax.legend( handles=handles, @@ -460,6 +459,7 @@ def _legend(ax: "Axes", handles: list[Any], *, inside: bool = False) -> None: frameon=False, ) _bold_headers(handles, legend) + return False def _bold_headers(handles: list[Any], legend: Any) -> None: @@ -740,21 +740,22 @@ def plot_mds( ``var.explained``. A dimension's sign is arbitrary; here its largest coordinate is positive. - A cube gives the pseudobulk samples of one group, prepared as the edgeR User's - Guide prepares counts for ``plotMDS``: samples with fewer than ``min_cells`` cells - or no counts are left out, as - :func:`~cytepool.differential_expression.limma_voom` leaves them out; genes are - kept by :func:`~cytepool.normalization.filter_by_expression` with the samples as - one group; and the log2 counts per million are those that edgeR's ``plotMDS`` - takes of counts, with TMM normalization factors and an average count of 2 added - (:func:`~cytepool.normalization.log_cpm`). + A cube gives the pseudobulk samples of one group, prepared as the edgeR User's Guide + prepares counts for ``plotMDS``: samples with fewer than ``min_cells`` cells or no + counts are left out, as :func:`~cytepool.limma_voom` leaves them out; genes are kept + by :func:`~cytepool.normalization.filter_by_expression` with the samples as one + group; and the log2 counts per million are those that edgeR's ``plotMDS`` takes of + counts, with TMM normalization factors and an average count of 2 added + (:func:`~cytepool.normalization.log2_cpm`). Draws the samples as points (gid ``"samples"``), coloured by ``color_by`` and marked by ``marker_by``, with a legend of their levels, and labels the samples - (gid ``"sample labels"``) when asked. + (gid ``"sample labels"``) when asked: each label above its point, moved inside the + frame where it would cross it and up where it would overlap another, with a line + to its point when it moved. Args: - data: A :class:`~cytepool.cube.Cube`; a + data: A :class:`~cytepool.Cube`; a :class:`~cytepool.stats.voom.VoomResult`, whose log2 counts per million are used; or ``(n_samples, n_genes)`` log-expression values, finite. group: The group (cell type) of a cube to plot; it may be left out when the @@ -813,6 +814,7 @@ def plot_mds( axis = "Leading log2 fold change, dimension" if pairwise else "Principal component" ax.set_xlabel(f"{axis} {first} ({100 * share[first - 1]:.0f}%)") ax.set_ylabel(f"{axis} {second} ({100 * share[second - 1]:.0f}%)") + _label_and_key_samples(ax, x, y, styles) return ax @@ -837,7 +839,7 @@ def _mds_values(data: Any, group: Any, min_cells: Any) -> tuple[FloatArray, _Sam raise ValueError( f"{given} applies to a cube, and data is a {type(data).__name__}" ) - source = data.log_cpm if isinstance(data, VoomResult) else data + source = data.log2_cpm if isinstance(data, VoomResult) else data values = real_array(unmasked_array(source, "data"), "data", boolean=False) if values.ndim != 2: raise ValueError( @@ -873,14 +875,16 @@ def _mds_values(data: Any, group: Any, min_cells: Any) -> tuple[FloatArray, _Sam f"{fewest} cells; an MDS plot compares two or more" ) kept = counts[rows] - keep = filter_by_expression(kept, group=np.zeros(rows.size)) + keep = filter_by_expression(kept, sample_groups=np.zeros(rows.size)) if not keep.any(): raise ValueError( f"no gene of group {group!r} passes filter_by_expression, so no distance " "can be computed" ) kept = kept[:, keep] - values = log_cpm(kept, norm_factors=tmm_factors(kept), prior_count=_MDS_PRIOR_COUNT) + values = log2_cpm( + kept, norm_factors=tmm_factors(kept), prior_count=_MDS_PRIOR_COUNT + ) return values, _cube_samples(data, rows) @@ -931,10 +935,9 @@ def plot_cell_numbers( """Plot the number of cells pooled into each sample and group of a cube. A heatmap of ``Cube.n_cells``, one column per sample and one row per group (cell - type) in the cube's order, shaded in blue on a logarithmic scale. Entries with - fewer than ``min_cells`` cells, which - :func:`~cytepool.differential_expression.limma_voom` leaves out of a group's fit - with the same ``min_cells``, and entries without cells are pale grey, as the end + type) in the cube's order, shaded in blue on a logarithmic scale. Entries with fewer + than ``min_cells`` cells, which :func:`~cytepool.limma_voom` leaves out of a group's + fit with the same ``min_cells``, and entries without cells are pale grey, as the end of the colour bar shows. Draws the image (gid ``"cell numbers"``) and, when asked, the number of each entry @@ -1031,13 +1034,13 @@ def plot_composition( ) -> "Axes": """Plot the cell-type composition of every sample as stacked bars. - speckle's ``plotCellTypeProps``: one bar per sample, divided into the proportions - of its cells in each group (cell type), the first group on top. A cube gives the + speckle's ``plotCellTypeProps``: one bar per sample, divided into the proportions of + its cells in each group (cell type), the first group on top. A cube gives the proportions ``n_cells`` over each sample's cells in all groups, without the samples - that have no cells; a :class:`~cytepool.composition.CompositionResult` gives those - that :func:`~cytepool.composition.propeller` tested, of its samples used and groups - tested. ``by`` groups the bars by a sample variable, such as the condition, whose - levels are written above their bars. + that have no cells; a :class:`~cytepool.CompositionResult` gives those that + :func:`~cytepool.propeller` tested, of its samples used and groups tested. ``by`` + groups the bars by a sample variable, such as the condition, whose levels are + written above their bars. Draws the segments (gid ``"proportions"``), with a legend of the groups. @@ -1194,7 +1197,7 @@ def plot_proportions( """Plot the proportion of every cell type in every sample, by condition, with the adjusted p-values of propeller. - The figure of a :func:`~cytepool.composition.propeller` result (Phipson et al. + The figure of a :func:`~cytepool.propeller` result (Phipson et al. 2022): for every group (cell type) tested, the proportion of its cells in each sample used, as a point, side by side for the levels of ``by``, with a bar at the mean proportion of each level (speckle's ``PropMean``); above each group, the @@ -1208,8 +1211,9 @@ def plot_proportions( result: The result of ``propeller``. by: A variable of the result's design, such as the condition, or one label per sample of the result's sample table. - contrast: The contrast whose adjusted p-values are shown. By default, the - F-test's when the result has one, else those of its only contrast. + contrast: The contrast, or F-test, whose adjusted p-values are shown. By + default, the F-test's when the result has one, else those of its only + contrast. ax: The Axes to draw into, or None for a new figure. Returns: @@ -1219,7 +1223,8 @@ def plot_proportions( TypeError: If ``result`` is not a CompositionResult, or ``by`` or ``contrast`` has the wrong type or is a numpy masked array. ValueError: If ``by`` has the wrong length, a missing label or more than eight - levels, or no contrast is named and the result has several and no F-test. + levels, or no contrast is named and the result has several and no F-test, + or several F-tests. KeyError: If ``by`` names no variable of the design, or ``contrast`` was not tested. """ @@ -1230,16 +1235,18 @@ def plot_proportions( ) _check_variable(by, "by") name = _optional_text(contrast, "contrast") + f_tests = list(result.f_tests) if name is not None: tests = result.table(name) - elif result.f_test is not None: - tests = result.f_test - elif len(result.tables) == 1: + elif len(f_tests) == 1: + tests = result.tables[f_tests[0]] + elif not f_tests and len(result.tables) == 1: tests = next(iter(result.tables.values())) else: + kind = "F-tests" if f_tests else "contrasts and no F-test" raise ValueError( - "the result tests several contrasts and no F-test; name one with contrast, " - f"one of {quoted(list(result.tables))}" + f"the result tests several {kind}; name one with contrast, one of " + f"{quoted(list(result.tables))}" ) proportions, samples, group_ids = _composition(result) labels, levels = samples.labels(by, "by") @@ -1327,16 +1334,14 @@ def plot_mean_variance( which the precision weights are read (``sqrt_sd_trend``). Genes left out of the trend, such as those without counts, are not drawn. Of a differential-expression result, the trend of one group, which - :meth:`~cytepool.differential_expression.DifferentialExpressionResult.fit` - computes again. + :meth:`~cytepool.DifferentialExpressionResult.fit` computes again. Draws the genes (gid ``"points"``) and the trend (gid ``"trend"``). Args: result: The result of :func:`~cytepool.stats.voom.voom` or :func:`~cytepool.stats.voom.voom_with_quality_weights`, or of - :func:`~cytepool.differential_expression.limma_voom` or - :func:`~cytepool.differential_expression.dream_voom`. + :func:`~cytepool.limma_voom` or :func:`~cytepool.dream_voom`. group: The group of a differential-expression result; it may be left out when the result has one group. ax: The Axes to draw into, or None for a new figure. @@ -1433,10 +1438,9 @@ def plot_residual_variances( whose two-sided p-value as a variance of the prior, from the F distribution on the gene's residual and the prior's degrees of freedom (those of the genes that are not outliers), is below 0.5 once adjusted by Benjamini and Hochberg (1995), as limma - marks them (found by probing). Of a differential-expression result, the - moderation of one group, which - :meth:`~cytepool.differential_expression.DifferentialExpressionResult.fit` - computes again. + marks them (found by probing). Of a differential-expression result, the moderation + of one group, which :meth:`~cytepool.DifferentialExpressionResult.fit` computes + again. Draws the genes (gid ``"points"``), the outliers (gid ``"outliers"``) and the prior (gid ``"prior"``). @@ -1444,8 +1448,7 @@ def plot_residual_variances( Args: fit: The result of :func:`~cytepool.stats.empirical_bayes.moderated_statistics` or :func:`~cytepool.stats.empirical_bayes.treat`, or of - :func:`~cytepool.differential_expression.limma_voom` or - :func:`~cytepool.differential_expression.limma_trend`. + :func:`~cytepool.limma_voom` or :func:`~cytepool.limma_trend`. group: The group of a differential-expression result; it may be left out when the result has one group. ax: The Axes to draw into, or None for a new figure. @@ -1492,10 +1495,10 @@ def plot_residual_variances( np.asarray(fit.prior_variance, dtype=np.float64), sigma.shape ) outlier = np.zeros(sigma.shape, dtype=bool) - robust = np.ndim(fit.prior_df) == 1 + robust = np.ndim(fit.df_prior) == 1 if robust: ratio = np.where(shown, sigma**2 / prior, np.nan) - dfd = float(np.max(np.asarray(fit.prior_df)[shown])) + dfd = float(np.max(np.asarray(fit.df_prior)[shown])) lower, upper = f_cdf(ratio, df, dfd), f_sf(ratio, df, dfd) tails = np.where(shown, np.minimum(1.0, 2.0 * np.minimum(lower, upper)), np.nan) with np.errstate(invalid="ignore"): @@ -1564,8 +1567,8 @@ def plot_sample_weights( weights: The ``sample_weights`` of a :class:`~cytepool.stats.voom.VoomResult` from :func:`~cytepool.stats.voom.voom_with_quality_weights`; the quality weights, or prior weights, of the samples used in one group of a - :class:`~cytepool.differential_expression.DifferentialExpressionResult`; - or the weights, positive and finite, such as those of + :class:`~cytepool.DifferentialExpressionResult`; or the weights, positive + and finite, such as those of :func:`~cytepool.stats.sample_weights.sample_quality_weights`. group: The group of a differential-expression result to plot; it may be left out when the result has one group. @@ -1665,7 +1668,7 @@ def _weights_of_group( class _Tests: """The columns of a test that the figures of differential expression read.""" - log_fold_change: FloatArray | None + log2_fold_change: FloatArray | None abundance: FloatArray | None abundance_label: str log_abundance: bool # whether the abundance is drawn on a logarithmic axis @@ -1724,8 +1727,8 @@ def column(name: str) -> FloatArray | None: if isinstance(result, QuasiLikelihoodTest): j = _coefficient(coefficient, result.names) return _Tests( - np.asarray(result.log_fold_change[:, j], dtype=np.float64), - np.asarray(result.average_log_cpm, dtype=np.float64), + np.asarray(result.log2_fold_change[:, j], dtype=np.float64), + np.asarray(result.average_log2_cpm, dtype=np.float64), "Average log2 CPM", False, np.asarray(result.p_value, dtype=np.float64), @@ -1788,13 +1791,13 @@ def _require(values: FloatArray | None, column: str, function: str) -> FloatArra def _directions( - log_fold_change: FloatArray, adjusted: FloatArray, alpha: float + log2_fold_change: FloatArray, adjusted: FloatArray, alpha: float ) -> tuple[npt.NDArray[np.bool_], npt.NDArray[np.bool_]]: """The genes significant up and down at ``alpha``.""" with np.errstate(invalid="ignore"): significant = adjusted < alpha - return significant & (log_fold_change > 0.0), significant & ( - log_fold_change < 0.0 + return significant & (log2_fold_change > 0.0), significant & ( + log2_fold_change < 0.0 ) @@ -1836,7 +1839,7 @@ def plot_md( whose adjusted p-value is below ``alpha`` coloured by direction, as ``plotMD(fit, status = decideTests(fit))`` colours them. The average expression is the average log2 counts per million of a table of - :class:`~cytepool.differential_expression.DifferentialExpressionResult` or of a + :class:`~cytepool.DifferentialExpressionResult` or of a :class:`~cytepool.stats.quasi_likelihood.QuasiLikelihoodTest`, the average log-expression of a fit, or, for :class:`~cytepool.stats.deseq2.DESeq2Results`, the mean of the normalized counts @@ -1869,7 +1872,7 @@ def plot_md( _check_axes(ax) tests = _tests(result, coefficient) level = _fraction(alpha, "alpha") - y = _require(tests.log_fold_change, "log2_fold_change", "plot_md") + y = _require(tests.log2_fold_change, "log2_fold_change", "plot_md") x = _require(tests.abundance, "average_log2_cpm", "plot_md") adjusted = _require(tests.adjusted_p_value, "adjusted_p_value", "plot_md") shown = np.isfinite(x) & np.isfinite(y) @@ -1942,7 +1945,7 @@ def plot_volcano( raise ValueError(f"top must be at least 0, got {count}") if labels is not None: reject_masked(labels, "labels") - x = _require(tests.log_fold_change, "log2_fold_change", "plot_volcano") + x = _require(tests.log2_fold_change, "log2_fold_change", "plot_volcano") p = _require(tests.p_value, "p_value", "plot_volcano") adjusted = _require(tests.adjusted_p_value, "adjusted_p_value", "plot_volcano") names = tests.names if labels is None else _gene_labels(labels, x.size) @@ -2029,15 +2032,14 @@ def plot_significant_genes( """Plot how many genes go up and down in every group of a differential-expression result. - For every group (cell type) of a - :class:`~cytepool.differential_expression.DifferentialExpressionResult`, in its - order from the top, the genes whose adjusted p-value for ``contrast`` is below + For every group (cell type) of a :class:`~cytepool.DifferentialExpressionResult`, in + its order from the top, the genes whose adjusted p-value for ``contrast`` is below ``alpha``: a bar to the right for those up and to the left for those down, each labelled with its number. A group that was not fitted, or cannot estimate the contrast, has no bars, and its label says so. ``across_groups`` counts by the p-values adjusted over every group (``adjusted_p_value_across_groups``, which - :func:`~cytepool.differential_expression.limma_voom` adds with - ``adjust_across_groups=True``) instead of within each group. + :func:`~cytepool.limma_voom` adds with ``adjust_across_groups=True``) instead of + within each group. Draws the bars of genes up (gid ``"up"``) and down (gid ``"down"``), and their numbers (gid ``"numbers"``). @@ -2258,7 +2260,7 @@ def plot_barcode( statistics: Any, gene_set: Any, *, - gene_ids: Any = None, + feature_ids: Any = None, span: float = _WORM_SPAN, ax: "Axes | None" = None, ) -> "Axes": @@ -2283,11 +2285,12 @@ def plot_barcode( statistics: A table of a differential-expression result, whose ``t`` column (or ``statistic``, for a meta-analysis) ranks its genes, named by its ``feature_id``; or one statistic per gene, finite. - gene_set: The genes of the set: ids (of the table's genes, or of ``gene_ids``), - 0-based positions, or a boolean mask over the genes. Ids that are not - among the genes are left out, as limma's ``ids2indices`` leaves them. - gene_ids: One id per gene, unique, to name the genes of ``gene_set`` by id when - ``statistics`` is not a table. + gene_set: The genes of the set: ids (of the table's genes, or of + ``feature_ids``), 0-based positions, or a boolean mask over the genes. + Ids that are not among the genes are left out, as limma's + ``ids2indices`` leaves them. + feature_ids: One id per gene, unique, to name the genes of ``gene_set`` by + id when ``statistics`` is not a table. span: The share of the genes in each window of the worm, in (0, 1]. ax: The Axes to draw into, or None for a new figure. @@ -2308,9 +2311,9 @@ def plot_barcode( reject_masked(gene_set, "gene_set") label = "Statistic" if isinstance(statistics, Table): - if gene_ids is not None: - reject_masked(gene_ids, "gene_ids") - raise ValueError("gene_ids applies to statistics given as an array") + if feature_ids is not None: + reject_masked(feature_ids, "feature_ids") + raise ValueError("feature_ids applies to statistics given as an array") column = "t" if "t" in statistics else "statistic" if column not in statistics or "feature_id" not in statistics: raise ValueError( @@ -2325,7 +2328,9 @@ def plot_barcode( unmasked_array(statistics, "statistics"), "statistics", boolean=False ) ids = ( - None if gene_ids is None else unmasked_array(gene_ids, "gene_ids").tolist() + None + if feature_ids is None + else unmasked_array(feature_ids, "feature_ids").tolist() ) if values.ndim != 1 or values.size < 2 or not np.isfinite(values).all(): raise ValueError("the statistics must be 1-D, finite and at least two") @@ -2395,10 +2400,10 @@ def _set_members(gene_set: Any, ids: list[Any] | None, n: int) -> npt.NDArray[np found = np.flatnonzero(members) elif members.size and all(isinstance(v, str) for v in members.tolist()): if ids is None: - raise ValueError("gene_set names genes by id; give gene_ids too") + raise ValueError("gene_set names genes by id; give feature_ids too") if len(ids) != n: raise ValueError( - f"gene_ids must have one id per gene ({n}), got {len(ids)}" + f"feature_ids must have one id per gene ({n}), got {len(ids)}" ) position = {str(value): i for i, value in enumerate(ids)} if len(position) != n: @@ -2460,11 +2465,10 @@ def plot_gene_sets( """Plot the most significant gene sets of a gene-set test, by direction. For the ``top`` sets with the smallest p-values of a table of - :func:`~cytepool.stats.gene_set_tests.camera` (Wu and Smyth 2012), - :func:`~cytepool.stats.gene_set_tests.fry` or - :func:`~cytepool.stats.rotation_gene_set_tests.mroast`, a bar of length -log10 of - the adjusted p-value, to the right for sets up and to the left for sets down, - with dashed lines at ``alpha``. Each set is named with its number of genes. + :func:`~cytepool.camera` (Wu and Smyth 2012), :func:`~cytepool.fry` or + :func:`~cytepool.mroast`, a bar of length -log10 of the adjusted p-value, to the + right for sets up and to the left for sets down, with dashed lines at ``alpha``. + Each set is named with its number of genes. Draws the bars (gid ``"gene sets"``) and the lines (gid ``"alpha"``). @@ -2547,15 +2551,14 @@ def plot_over_representation( """Plot the most over-represented gene sets, a dot for each list of genes. For the ``top`` sets with the smallest p-value in any list of a table of - :func:`~cytepool.over_representation.over_representation`, most significant at - the top, a dot in the column of each list of selected genes (up, down and either, - or the genes given), as clusterProfiler draws the lists of genes it compares (Yu - et al. 2012). The area of a dot is proportional to the overlap, the number of the - set's genes among the selected genes, and its colour gives the adjusted p-value - as -log10, darker for smaller; a dot whose adjusted p-value is not below - ``alpha`` is pale grey, and a list that holds none of the set's genes has no dot. - Each set is named with its number of genes in the universe, and each list with - its number of selected genes. + :func:`~cytepool.over_representation`, most significant at the top, a dot in the + column of each list of selected genes (up, down and either, or the genes given), as + clusterProfiler draws the lists of genes it compares (Yu et al. 2012). The area of a + dot is proportional to the overlap, the number of the set's genes among the selected + genes, and its colour gives the adjusted p-value as -log10, darker for smaller; a + dot whose adjusted p-value is not below ``alpha`` is pale grey, and a list that + holds none of the set's genes has no dot. Each set is named with its number of genes + in the universe, and each list with its number of selected genes. Draws the dots (gid ``"gene sets"``). @@ -2702,7 +2705,7 @@ def plot_enrichment( GSEA's enrichment plot (Subramanian et al. 2005) as fgsea's ``plotEnrichment`` draws it (Korotkevich et al. 2021): the genes ranked by decreasing statistic, as - :func:`~cytepool.gsea.gsea` ranks them, along the x-axis, from rank 0 to N + 1; + :func:`~cytepool.gsea` ranks them, along the x-axis, from rank 0 to N + 1; the walk that rises at each gene of the set by its share of the set's weight |r|^p and falls at each other gene, drawn through its values just before and at each gene of the set; a tick at the rank of each gene of the set, spanning a @@ -2714,7 +2717,7 @@ def plot_enrichment( lines (gid ``"extremes"``). Args: - statistics: The statistics, in any form :func:`~cytepool.gsea.gsea` takes. + statistics: The statistics, in any form :func:`~cytepool.gsea` takes. gene_set: The genes of the set, as gene ids, such as ``gene_sets[name]``; ids that are not ranked are left out. group: The group of a ``DifferentialExpressionResult``, as for ``gsea``. @@ -2802,7 +2805,7 @@ def plot_gsea_table( The table of fgsea's ``plotGseaTable`` (Korotkevich et al. 2021): a row for each of the ``top`` sets with a positive enrichment score and the smallest p-values - of a table of :func:`~cytepool.gsea.gsea`, most significant first, followed by + of a table of :func:`~cytepool.gsea`, most significant first, followed by the ``top`` with a negative score, most significant last, as fgsea's guide chooses them; the ties of p-values in the order of the table. Each row shows the set's name, its genes as bars at their ranks among the genes ranked by @@ -2814,10 +2817,9 @@ def plot_gsea_table( Args: statistics: The statistics that ``table`` was computed from, in any form - :func:`~cytepool.gsea.gsea` takes. + :func:`~cytepool.gsea` takes. gene_sets: The gene sets of ``table``: a - :class:`~cytepool.gene_sets.GeneSets` or a mapping of set name to gene - ids. + :class:`~cytepool.GeneSets` or a mapping of set name to gene ids. table: The table of ``gsea``, with the columns ``gene_set``, ``enrichment_score``, ``normalized_enrichment_score``, ``p_value`` and ``adjusted_p_value``. @@ -2980,13 +2982,13 @@ def plot_pathway_scores( ``"groups"``) and the lines between groups (gid ``"group boundaries"``). Args: - scores: The scores of :func:`~cytepool.pathway_scores.gsva`, - :func:`~cytepool.pathway_scores.ssgsea`, - :func:`~cytepool.pathway_scores.combined_z_scores` or - :func:`~cytepool.pathway_scores.plage`. + scores: The scores of :func:`~cytepool.gsva`, + :func:`~cytepool.ssgsea`, :func:`~cytepool.combined_z_scores` or + :func:`~cytepool.plage`. sets: The sets to show, top to bottom; by default every set, in order. - color_by: A column of ``scores.samples``, such as ``"condition"`` for the - scores of a cube's group, or one label per sample, at most eight levels. + color_by: A column of ``scores.profiles``, such as ``"condition"`` for the + scores of a cube's group, or one label per profile, at most eight + levels. standardize: Whether to standardize each set's scores across the samples. ax: The Axes to draw into, or None for a new figure. @@ -3025,14 +3027,14 @@ def plot_pathway_scores( if not chosen: raise ValueError("sets is empty; name at least one set") values = scores.scores[:, [names.index(name) for name in chosen]].T - table = scores.samples + table = scores.profiles n_samples = values.shape[1] samples = _Samples( - ids=[str(value) for value in table["sample_id"].tolist()], + ids=[str(value) for value in table["profile_id"].tolist()], rows=list(range(n_samples)), n_rows=n_samples, variables={name: table[name] for name in table.columns}, - source="the scores' sample table", + source="the scores' profiles", ) order = np.arange(n_samples) groups = None @@ -3185,9 +3187,8 @@ def plot_activities( ``"groups"``) and the lines between groups (gid ``"group boundaries"``). Args: - activities: The activities of :func:`~cytepool.activity.ulm`, - :func:`~cytepool.activity.mlm` or - :func:`~cytepool.activity.weighted_aggregate`. + activities: The activities of :func:`~cytepool.ulm`, + :func:`~cytepool.mlm` or :func:`~cytepool.weighted_aggregate`. sources: The sources to show, top to bottom; by default the ``top`` sources. top: When ``sources`` is None, how many sources to show, at least 1; None @@ -3280,9 +3281,8 @@ def plot_top_activities( Draws the bars (gid ``"activities"``). Args: - activities: The activities of :func:`~cytepool.activity.ulm`, - :func:`~cytepool.activity.mlm` or - :func:`~cytepool.activity.weighted_aggregate`. + activities: The activities of :func:`~cytepool.ulm`, + :func:`~cytepool.mlm` or :func:`~cytepool.weighted_aggregate`. profile: The ``profile_id`` of the profile to show; it may be left out when the activities have one profile. top: How many sources to show, at least 1. @@ -3513,7 +3513,7 @@ def plot_quasi_dispersions( f"{type(fit).__name__}" ) trended = fit.covariate is not None - x = np.asarray(fit.covariate if trended else fit.average_log_cpm, dtype=np.float64) + x = np.asarray(fit.covariate if trended else fit.average_log2_cpm, dtype=np.float64) raw = np.asarray(fit.quasi_dispersion, dtype=np.float64) squeezed = np.asarray(fit.posterior_quasi_dispersion, dtype=np.float64) prior = np.broadcast_to( @@ -3575,7 +3575,6 @@ def plot_quasi_dispersions( def plot_forest( meta: MetaAnalysisResult, - studies: Mapping[str, DifferentialExpressionResult], feature: str, *, group: str | None = None, @@ -3587,23 +3586,24 @@ def plot_forest( The forest plot (Lewis and Clarke 2001) that metafor's ``forest`` draws for a model without moderators (Viechtbauer 2010), of one gene of a - :func:`~cytepool.meta_analysis.differential_expression_meta_analysis`: a row per - study, in the order of ``studies``, with its log2 fold change as a square and the - interval y_i +/- z s_i, z the normal quantile of the meta-analysis's confidence - level and s_i the study's standard error, as metafor draws it whatever the test; - and at the bottom the pooled estimate as a diamond spanning its confidence - interval. A square's area grows with the study's weight in the pooled estimate, - 1 / (s_i^2 + tau^2), scaled as metafor scales it. A study that did not test the - gene keeps its row, marked "not tested". The estimates and intervals are written - to the right of the plot, and the pooled row is named with the estimator of - tau^2 and I^2. + :func:`~cytepool.meta_analysis`: a row per study, in the order of the studies' + results that the meta-analysis keeps (``meta.study_results``), with its log2 fold + change as a square and the interval y_i +/- z s_i, z the normal quantile of the + meta-analysis's confidence level and s_i the study's standard error, as metafor + draws it whatever the test; and at the bottom the pooled estimate as a diamond + spanning its confidence interval. A square's area grows with the study's weight + in the pooled estimate, 1 / (s_i^2 + tau^2), scaled as metafor scales it. A study + without an estimate of the gene keeps its row, marked "not measured" when the + meta-analysis combined a :class:`~cytepool.StudyResults` whose collection says + the study did not measure it, and "not tested" otherwise. The estimates and + intervals are written to the right of the plot, and the pooled row is named with + the estimator of tau^2 and I^2. Draws the intervals (gid ``"intervals"``), the squares (gid ``"estimates"``), the diamond (gid ``"pooled"``) and a dotted line at zero. Args: - meta: The meta-analysis. - studies: The differential-expression results it combined, by study name. + meta: The meta-analysis, which keeps the studies' results it combined. feature: The id of the gene. group: The group (cell type) of the meta-analysis; it may be left out when the meta-analysis has one group, or one with ``contrast``. @@ -3616,21 +3616,19 @@ def plot_forest( Raises: TypeError: If an argument has the wrong type, or is a numpy masked array. ValueError: If the group and contrast are not given and the meta-analysis has - several, or ``studies`` is empty. + several, or the meta-analysis keeps no study results (it was built + without them). KeyError: If the group and contrast, or the gene, were not meta-analysed. """ _check_axes(ax) if not isinstance(meta, MetaAnalysisResult): raise TypeError(f"meta must be a MetaAnalysisResult, got {type(meta).__name__}") - if not isinstance(studies, Mapping) or not all( - isinstance(result, DifferentialExpressionResult) for result in studies.values() - ): - raise TypeError( - "studies must map study names to DifferentialExpressionResult, the " - "results the meta-analysis combined" - ) + studies = meta.study_results if not studies: - raise ValueError("studies holds no study") + raise ValueError( + "this meta-analysis keeps no study results, so its studies' estimates " + "cannot be drawn: it was built without them; meta_analysis keeps them" + ) if not isinstance(feature, str): reject_masked(feature, "feature") raise TypeError( @@ -3648,7 +3646,7 @@ def plot_forest( ) row = int(found[0]) settings = meta.settings - z = float(norm_isf((1.0 - settings["level"]) / 2.0)) + z = float(norm_isf((1.0 - settings["confidence_level"]) / 2.0)) tau2 = float(pooled["tau_squared"][row]) names = [str(name) for name in studies] estimates = np.full(len(names), np.nan) @@ -3661,6 +3659,11 @@ def plot_forest( estimates[i] = float(table["log2_fold_change"][hit[0]]) errors[i] = float(table["standard_error"][hit[0]]) tested = np.isfinite(estimates) & np.isfinite(errors) + measured = np.ones(len(names), dtype=bool) + if isinstance(studies, StudyResults): + ids = studies.collection.features["feature_id"].tolist() + if feature in ids: + measured = np.asarray(studies.collection.measured)[:, ids.index(feature)] lows, highs = estimates - z * errors, estimates + z * errors weight = np.sqrt(1.0 / (errors**2 + tau2)) spread = np.nanmax(weight) - np.nanmin(weight) if tested.any() else 0.0 @@ -3717,6 +3720,8 @@ def plot_forest( f"{estimates[i]:.2f} [{lows[i]:.2f}, {highs[i]:.2f}]" if tested[i] else "not tested" + if measured[i] + else "not measured" for i in range(k) ] for y, text in zip( @@ -3767,14 +3772,13 @@ def plot_study_agreement( ) -> "Axes": """Plot how well each study replicates the genes that each other study finds. - A heatmap of the pairs of studies of a - :func:`~cytepool.replication.differential_expression_replication`: a row per + A heatmap of the pairs of studies of a :func:`~cytepool.replication`: a row per discovery study and a column per replication study, in the collection's order, shaded from 0 to 1 by ``measure`` of the genes the discovery study finds and the replication study tested (``study_pairs``): pi1, Storey's estimate of the share of them with an effect in the replication study (Storey and Tibshirani 2003), or the - share whose log2 fold changes have the same sign. - A pair without such genes, and the diagonal, are pale. + share whose log2 fold changes have the same sign. A pair without such genes, and the + diagonal, are pale. Draws the image (gid ``"agreement"``) and, when asked, each entry's value with the number of genes it covers (gid ``"numbers"``). @@ -3819,7 +3823,7 @@ def plot_study_agreement( pairs["discovery"].tolist(), pairs["replication"].tolist(), pairs[chosen].tolist(), - pairs["n_found"].tolist(), + pairs["n_features_found"].tolist(), strict=True, ): shown[index[discovery], index[replication]] = value @@ -3870,12 +3874,12 @@ def plot_replicated_genes( ) -> "Axes": """Plot how many genes each number of studies finds. - For every k from 1 to the number of studies that analysed the group and contrast - of a :func:`~cytepool.replication.differential_expression_replication`, a bar of - the genes that exactly k studies find (adjusted p-value below its ``alpha`` in - each), split into those that every one of them finds in the same direction and - those found up in some and down in others, with the numbers above. Most genes - that one study finds are usually found by no other (Nakatsuka et al. 2025). + For every k from 1 to the number of studies that analysed the group and contrast of + a :func:`~cytepool.replication`, a bar of the genes that exactly k studies find + (adjusted p-value below its ``alpha`` in each), split into those that every one of + them finds in the same direction and those found up in some and down in others, with + the numbers above. Most genes that one study finds are usually found by no other + (Nakatsuka et al. 2025). Draws the bars of genes found in one direction (gid ``"same direction"``) and in both (gid ``"both directions"``), and their totals (gid ``"numbers"``). @@ -3895,9 +3899,9 @@ def plot_replicated_genes( raise TypeError( f"result must be a ReplicationResult, got {type(result).__name__}" ) - genes = result.genes - found = np.asarray(genes["n_found"], dtype=np.int64) - one_way = np.maximum(genes["n_found_up"], genes["n_found_down"]) == found + features = result.features + found = np.asarray(features["n_found"], dtype=np.int64) + one_way = np.maximum(features["n_found_up"], features["n_found_down"]) == found k = len(result.found_in) studies = np.arange(1, k + 1) same = np.array([np.count_nonzero((found == j) & one_way) for j in studies]) @@ -3987,9 +3991,10 @@ def plot_variance_fractions( per variable). Args: - data: The fractions: a :class:`~cytepool.variance_partitioning.VarianceFractions`, - a :class:`~cytepool.variance_partitioning.VariancePartitioningResult` - (with ``group``), or a table of one. + data: The fractions: a + :class:`~cytepool.stats.variance_fractions.VarianceFractions`, a + :class:`~cytepool.VariancePartitioningResult` (with ``group``), or a table + of one. group: The group (cell type) of a result; it may be left out when one group was fitted. sort: Whether to order the variables by their median, the residuals last; @@ -4042,35 +4047,36 @@ def plot_variance_fractions( return ax -def plot_gene_variance_fractions( +def plot_feature_variance_fractions( data: Any, - genes: Any, + features: Any, *, group: str | None = None, labels: Sequence[Any] | None = None, sort: bool = True, ax: "Axes | None" = None, ) -> "Axes": - """Plot the fractions of variance of chosen genes as stacked bars. + """Plot the fractions of variance of chosen features, such as genes, as stacked + bars. - variancePartition's ``plotPercentBars``: one horizontal bar per gene, the first - at the top, divided into the percentage of the gene's variance that each - variable explains, the residuals last. The variables are in the order of + variancePartition's ``plotPercentBars``: one horizontal bar per feature, the + first at the top, divided into the percentage of the feature's variance that + each variable explains, the residuals last. The variables are in the order of :func:`plot_variance_fractions`: with ``sort``, in decreasing order of their - median over every gene of the data, as ``plotPercentBars(sortCols(x)[genes, ])`` - orders them. + median over every feature of the data, as + ``plotPercentBars(sortCols(x)[genes, ])`` orders them. Draws the segments of each variable (gid ``"fractions"``, one bar container per variable, labelled with it), with a legend of the variables. Args: data: The fractions, as for :func:`plot_variance_fractions`. - genes: The genes: feature ids, or symbols, of a table or result; names given - in ``labels``; or positions among the features. + features: The features: feature ids, or symbols, of a table or result; + names given in ``labels``; or positions among the features. group: The group (cell type) of a result; it may be left out when one group was fitted. - labels: One name per feature of the data, to find and label the genes by; - a table's genes are named by their symbol, or else their id. + labels: One name per feature of the data, to find and label the features + by; a table's features are named by their symbol, or else their id. sort: Whether to order the variables by their median, the residuals last. ax: The Axes to draw into, or None for a new figure. @@ -4078,17 +4084,17 @@ def plot_gene_variance_fractions( The Axes. Raises: - TypeError: If an argument has the wrong type, or ``genes`` or ``labels`` is - or holds a numpy masked array. - ValueError: As :func:`plot_variance_fractions`, or if ``genes`` is empty or - names a gene twice, a symbol names several genes, a gene has no - fractions, or ``labels`` has the wrong length. - KeyError: If a gene is not among the data's, or ``group`` was not analysed - or not fitted. + TypeError: If an argument has the wrong type, or ``features`` or ``labels`` + is or holds a numpy masked array. + ValueError: As :func:`plot_variance_fractions`, or if ``features`` is empty + or names a feature twice, a symbol names several features, a feature + has no fractions, or ``labels`` has the wrong length. + KeyError: If a feature is not among the data's, or ``group`` was not + analysed or not fitted. IndexError: If a position is out of range. """ _check_axes(ax) - reject_masked(genes, "genes") + reject_masked(features, "features") if labels is not None: reject_masked(labels, "labels") name = _optional_text(group, "group") @@ -4096,7 +4102,7 @@ def plot_gene_variance_fractions( partition = _partition(data, name) n = partition.fractions.shape[0] given = None if labels is None else _gene_labels(labels, n) - rows, shown = _chosen_genes(genes, partition, given) + rows, shown = _chosen_features(features, partition, given) chosen = partition.fractions[rows] missing = [shown[i] for i in range(len(rows)) if not np.all(np.isfinite(chosen[i]))] if missing: @@ -4212,20 +4218,20 @@ def _variable_label(variable: str) -> str: return "Residuals" if variable == RESIDUALS else variable -def _chosen_genes( +def _chosen_features( genes: Any, partition: _Partition, labels: list[str] | None ) -> tuple[list[int], list[str]]: - """The rows of the genes asked for, and the names to show for them.""" + """The rows of the features asked for, and the names to show for them.""" n = partition.fractions.shape[0] if isinstance(genes, str | numbers.Integral): genes = [genes] if not isinstance(genes, Sequence | np.ndarray): raise TypeError( - "genes must be a list of gene ids, symbols or positions, got " + "features must be a list of feature ids, symbols or positions, got " f"{type(genes).__name__}" ) if len(genes) == 0: - raise ValueError("genes is empty; give at least one gene") + raise ValueError("features is empty; give at least one feature") names: list[str] | None = labels if names is None and partition.ids is not None: symbols = partition.symbols or [None] * n @@ -4236,7 +4242,9 @@ def _chosen_genes( rows: list[int] = [] for gene in list(genes): if isinstance(gene, bool | np.bool_): - raise TypeError("genes must be gene ids, symbols or positions, not bool") + raise TypeError( + "features must be feature ids, symbols or positions, not bool" + ) if isinstance(gene, numbers.Integral): position = int(gene) if not -n <= position < n: @@ -4248,7 +4256,7 @@ def _chosen_genes( row = _gene_row(gene, partition, labels) else: raise TypeError( - f"genes must be gene ids, symbols or positions, got " + f"features must be feature ids, symbols or positions, got " f"{type(gene).__name__} {gene!r}" ) if row in rows: @@ -4381,7 +4389,7 @@ def plot_sumrank( *, direction: str = "up", alpha: float = 0.05, - n_labels: int = 20, + top: int = 20, ax: "Axes | None" = None, ) -> "Axes": """Plot the SumRank p-value of every gene, the genes in alphabetical order. @@ -4391,7 +4399,7 @@ def plot_sumrank( its p-value (``up_p_value`` or ``down_p_value``, from permutations when they were run), the genes in alphabetical order of their symbol, or of their id without one. The genes whose adjusted p-value is below ``alpha`` are coloured, red for - genes up and blue for genes down, the ``n_labels`` with the smallest p-values are + genes up and blue for genes down, the ``top`` with the smallest p-values are named, and a dotted line lies at the largest -log10 p-value of the other genes, where the authors draw it, when some gene passes. @@ -4399,11 +4407,11 @@ def plot_sumrank( line (gid ``"threshold"``) and the names (gid ``"labels"``). Args: - result: The result of - :func:`~cytepool.sumrank.differential_expression_sumrank`. + result: The result of :func:`~cytepool.sumrank`. direction: ``"up"`` or ``"down"``. alpha: The adjusted p-value below which genes pass, in (0, 1). - n_labels: The most genes named, at least 0. + top: The most genes named, at least 0, as :func:`plot_volcano` names + them. ax: The Axes to draw into, or None for a new figure. Returns: @@ -4419,11 +4427,11 @@ def plot_sumrank( raise TypeError(f"result must be a SumRankResult, got {type(result).__name__}") side = checked_choice(direction, "direction", ("up", "down")) level = _fraction(alpha, "alpha") - reject_masked(n_labels, "n_labels") - named = integral_number(n_labels, "n_labels") + reject_masked(top, "top") + named = integral_number(top, "top") if named < 0: - raise ValueError(f"n_labels must be at least 0, got {named}") - table = result.table + raise ValueError(f"top must be at least 0, got {named}") + table = result.features ids = [str(value) for value in table["feature_id"].tolist()] names = ids if "symbol" in table: @@ -4493,17 +4501,23 @@ def _label_above( y: FloatArray, names: list[str], chosen: Any, - color: str, + color: str | None, + *, + fontsize: float = 7.0, + gid: str = "labels", + in_order: bool = False, ) -> None: """Label points from above, at the size the figure is laid out at. A label that would cross the side of the frame moves in, one that would overlap a label placed before it moves up, and a moved label has a line to its point; the top of the plot - is raised to show the highest label.""" + is raised to show the highest label. The labels are placed from the highest point + down, and added to the plot in that order, or in the order of ``chosen`` with + ``in_order``; ``color`` None is the default colour of text.""" textpath = _module("matplotlib.textpath") root = ax.figure.figure # the Figure, also of Axes in a subfigure if root is not None: root.draw_without_rendering() - fontsize = 7.0 # points + chosen = list(chosen) order = sorted(chosen, key=lambda i: (-y[i], x[i])) widths = [ textpath.TextPath((0.0, 0.0), names[i], size=fontsize).get_extents().width @@ -4520,8 +4534,13 @@ def _label_above( if highest <= top: break ax.set_ylim(bottom, highest) + if _widen_to_keep_aspect(ax) and root is not None: + # The new limits change the ticks, and with them the layout. + root.draw_without_rendering() places = _label_places(ax, x, y, order, widths, fontsize) - for i, (center, level, moved) in zip(order, places, strict=True): + place_of = dict(zip(order, places, strict=True)) + for i in chosen if in_order else order: + center, level, moved = place_of[i] ax.annotate( names[i], (x[i], y[i]), @@ -4534,10 +4553,28 @@ def _label_above( arrowprops=( {"arrowstyle": "-", "color": _GREY, "linewidth": 0.6} if moved else None ), - gid="labels", + gid=gid, ) +def _widen_to_keep_aspect(ax: "Axes") -> bool: + """Widen the x-axis of a plot of fixed aspect, such as a principal component plot, + whose top was raised for labels, so that its limits keep the aspect. Otherwise + matplotlib keeps the aspect by narrowing the y-axis about its middle, which hides + the lowest points. Returns whether the plot has a fixed aspect.""" + aspect = ax.get_aspect() + if isinstance(aspect, str) or ax.get_adjustable() != "datalim": + return False + low, high = ax.get_xlim() + bottom, top = ax.get_ylim() + # A fixed aspect a gives a unit of y a times the length of a unit of x. + width = (top - bottom) * float(aspect) * ax.bbox.width / ax.bbox.height + if width > high - low: + middle = (low + high) / 2.0 + ax.set_xlim(middle - width / 2.0, middle + width / 2.0) + return True + + def _label_places( ax: "Axes", x: FloatArray, @@ -4586,20 +4623,20 @@ def plot_held_out_auc( ) -> "Axes": """Plot the AUC of every held-out study, by the method that chose its genes. - A row per study of a :func:`~cytepool.held_out_prediction.held_out_prediction`, - in the collection's order from the top, with a point for each method at the AUC - of the study's cases against its controls, scored by the genes that the method - chose from the other studies; the legend gives each method's mean AUC, and a - dotted line marks 0.5, the AUC of a score that does not separate them, as the - paper compares its methods (Nakatsuka et al. 2025, Table 1). A study without an - AUC keeps its row, marked "no AUC". + A row per study of a :func:`~cytepool.held_out_prediction`, in the collection's + order from the top, with a point for each method at the AUC of the study's cases + against its controls, scored by the genes that the method chose from the other + studies; the legend gives each method's mean AUC, and a dotted line marks 0.5, the + AUC of a score that does not separate them, as the paper compares its methods + (Nakatsuka et al. 2025, Table 1). A study without an AUC keeps its row, marked "no + AUC". Draws the points of each method (gid its name, ``"sumrank"`` or ``"meta_analysis"``) and the line (gid ``"chance"``). Args: prediction: The result of - :func:`~cytepool.held_out_prediction.held_out_prediction`. + :func:`~cytepool.held_out_prediction`. ax: The Axes to draw into, or None for a new figure. Returns: @@ -4662,7 +4699,7 @@ def plot_held_out_auc( # and the power of the fraction that sets a dot's area. _DOT_AREA = 200.0 _DOT_EXPONENT = 1.5 -_DOT_SCALES = ("gene", "group") +_DOT_SCALES = ("feature", "group") def plot_pca( @@ -4680,23 +4717,24 @@ def plot_pca( """Plot samples on two principal components of their most variable genes. DESeq2's ``plotPCA`` (Love et al. 2014): the samples' coordinates on two components - of :func:`~cytepool.principal_components.principal_components`, which takes the - ``top`` genes of largest variance over the samples, centres each on its mean, and - decomposes them by singular values (Pearson 1901); the sign of each component makes - its largest coordinate positive. The axes are labelled as ``plotPCA`` labels them, - with each component's share of the variance rounded to a whole percent, as in - "PC1: 45% variance", and drawn to the same scale, as ``plotPCA`` draws them, so that - the distances between samples are drawn true. The coordinates are those of - ``principal_components``; pass its result as ``data`` to draw the components it - holds. + of :func:`~cytepool.principal_components`, which takes the ``top`` genes of largest + variance over the samples, centres each on its mean, and decomposes them by singular + values (Pearson 1901); the sign of each component makes its largest coordinate + positive. The axes are labelled as ``plotPCA`` labels them, with each component's + share of the variance rounded to a whole percent, as in "PC1: 45% variance", and + drawn to the same scale, as ``plotPCA`` draws them, so that the distances between + samples are drawn true. The coordinates are those of ``principal_components``; pass + its result as ``data`` to draw the components it holds. Draws the samples as points (gid ``"samples"``), coloured by ``color_by`` and marked by ``marker_by`` as :func:`plot_mds` draws them, with a legend of their - levels, and labels the samples (gid ``"sample labels"``) when asked. + levels, and labels the samples (gid ``"sample labels"``) when asked, placed as + :func:`plot_mds` places them; the plot widens where the labels need room above, + so that its scale stays the same along both axes. Args: - data: A :class:`~cytepool.principal_components.PrincipalComponents`, or what - ``principal_components`` takes: a :class:`~cytepool.cube.Cube`, a + data: A :class:`~cytepool.PrincipalComponents`, or what + ``principal_components`` takes: a :class:`~cytepool.Cube`, a :class:`~cytepool.stats.voom.VoomResult`, or ``(n_samples, n_genes)`` log-expression values, finite. group: The group (cell type) of a cube to plot; it may be left out when the @@ -4767,6 +4805,7 @@ def plot_pca( ax.set_xlabel(f"PC{first}: {100 * share[first - 1]:.0f}% variance") ax.set_ylabel(f"PC{second}: {100 * share[second - 1]:.0f}% variance") ax.set_aspect("equal", adjustable="datalim") + _label_and_key_samples(ax, x, y, styles) return ax @@ -4864,9 +4903,7 @@ def _sample_styles( def _draw_styled_samples( ax: "Axes", x: FloatArray, y: FloatArray, styles: _SampleStyles ) -> None: - """Samples as points of their colours and markers, labelled when asked, with a - legend of their levels: inside the plot where a short one covers nothing, and - beside it otherwise.""" + """Samples as points of their colours and markers.""" n_samples = x.size for marker in dict.fromkeys(styles.markers): rows = [i for i in range(n_samples) if styles.markers[i] == marker] @@ -4881,23 +4918,35 @@ def _draw_styled_samples( zorder=3, gid="samples", ) + + +def _label_and_key_samples( + ax: "Axes", x: FloatArray, y: FloatArray, styles: _SampleStyles +) -> None: + """The samples' labels, when asked, and the legend of their levels, once the + plot has its axis labels. The labels go above their points, inside the frame and + apart, in the samples' order; the legend inside the plot where a short one covers + no point or label, and beside it otherwise, after which the labels are placed again + at the plot's new size.""" + + def label() -> None: + _label_above( + ax, x, y, list(styles.texts or ()), range(x.size), None, + fontsize=8.0, gid="sample labels", in_order=True, + ) # fmt: skip + if styles.texts is not None: - for i, text in enumerate(styles.texts): - ax.annotate( - text, - (x[i], y[i]), - xytext=(4, 4), - textcoords="offset points", - fontsize=8, - gid="sample labels", - ) - if styles.handles: - _legend(ax, styles.handles, inside=True) + label() + if styles.handles and not _legend(ax, styles.handles, inside=True): + if styles.texts is not None: + for text in [t for t in ax.texts if t.get_gid() == "sample labels"]: + text.remove() + label() def plot_dotplot( cube: Cube, - genes: Any, + features: Any, *, groups: Sequence[str] | None = None, samples: Any = None, @@ -4923,32 +4972,32 @@ def plot_dotplot( smallest to the largest value shown, or from ``vmin`` to ``vmax``, scanpy's names, which put several dot plots on one scale. - ``standard_scale`` scales the means of each gene over the groups (``"gene"``, - scanpy's ``"var"``), or of each group over the genes (``"group"``), to run from 0 - to 1, (m - min) / (max - min), 0 where they are all equal, as scanpy does. + ``standard_scale`` scales the means of each feature over the groups + (``"feature"``, scanpy's ``"var"``), or of each group over the features + (``"group"``), to run from 0 to 1, (m - min) / (max - min), 0 where they are all + equal, as scanpy does. ``mean_only_expressed`` takes the mean over the cells that express the gene, ``log_sum`` over ``detected`` (0 where none does), as scanpy's option of that name; it needs a cube whose transform maps a zero count to zero, one whose normalization - records a pseudocount of 1, as :func:`~cytepool.cube.from_cells` records it. Genes - given as a mapping are - drawn in groups, under a bracket labelled with the group's name, as scanpy draws - ``var_names`` given as a dict. + records a pseudocount of 1, as :func:`~cytepool.from_cells` records it. Features + given as a mapping are drawn in groups, under a bracket labelled with the group's + name, as scanpy draws ``var_names`` given as a dict. Draws the dots (gid ``"dots"``: their sizes are the areas, and their array the - values that the colours show) and, for genes in groups, the brackets (gid + values that the colours show) and, for features in groups, the brackets (gid ``"gene groups"``) and their names (gid ``"gene group names"``). Args: cube: A cube with cell numbers and the layers ``detected`` and ``log_sum``, - as :func:`~cytepool.cube.from_cells` builds it. - genes: The genes, the columns from left to right: feature ids or symbols of - the cube's features; or a mapping of names to such lists, for groups of - genes. + as :func:`~cytepool.from_cells` builds it. + features: The features, such as genes, the columns from left to right: + feature ids or symbols of the cube's features; or a mapping of names to + such lists, for groups of features. groups: The groups, the rows from the top; by default every group, in the cube's order. samples: The samples whose cells are pooled: sample ids, or a boolean mask over the cube's samples; by default every sample. - standard_scale: ``"gene"``, ``"group"`` or None. + standard_scale: ``"feature"``, ``"group"`` or None. mean_only_expressed: Whether the mean is taken over the cells that express the gene only. vmin: The value of the lightest colour; the smallest value shown by default. @@ -4965,11 +5014,11 @@ def plot_dotplot( TypeError: If an argument has the wrong type, or is or holds a numpy masked array. ValueError: If the cube lacks cell numbers, ``detected`` or ``log_sum``, a - group has no cells in the samples pooled, ``genes``, ``groups`` or - ``samples`` is empty or names one twice, a symbol names several genes, + group has no cells in the samples pooled, ``features``, ``groups`` or + ``samples`` is empty or names one twice, a symbol names several features, ``mean_only_expressed`` is asked of a cube without a pseudocount of 1, ``vmin`` is not below ``vmax``, or an option is out of range. - KeyError: If a gene, group or sample is not in the cube. + KeyError: If a feature, group or sample is not in the cube. """ _check_axes(ax) if not isinstance(cube, Cube): @@ -4995,7 +5044,7 @@ def plot_dotplot( f"the cube lacks {what}; a dot plot needs cell numbers and the layers " "'detected' and 'log_sum', as from_cells computes them" ) - columns, names, gene_groups = _dot_genes(cube, genes) + columns, names, gene_groups = _dot_features(cube, features) rows = _chosen_ids(groups, cube.groups["group_id"].tolist(), "groups", "group") pooled = _pooled_samples(cube, samples) if expressed_only: @@ -5008,7 +5057,9 @@ def plot_dotplot( ) fractions, means = _dot_values(cube, rows, columns, pooled, expressed_only) shown = ( - means if scale is None else _min_max_scaled(means, 0 if scale == "gene" else 1) + means + if scale is None + else _min_max_scaled(means, 0 if scale == "feature" else 1) ) largest = float(fractions.max()) @@ -5090,12 +5141,12 @@ def plot_dotplot( return ax -def _dot_genes( +def _dot_features( cube: Cube, genes: Any ) -> tuple[list[int], list[str], list[tuple[str, int, int]]]: - """The cube's features of the genes asked for, the names shown, and the groups of - genes as (name, first column, last column).""" - reject_masked(genes, "genes") + """The cube's features asked for, the names shown, and the groups of features + as (name, first column, last column).""" + reject_masked(genes, "features") if isinstance(genes, str): named: dict[str, Any] = {"": [genes]} elif isinstance(genes, Mapping): @@ -5115,20 +5166,21 @@ def _dot_genes( for label, members in named.items(): if not isinstance(label, str): raise TypeError( - f"the names of groups of genes must be text, got {type(label).__name__}" + f"the names of groups of features must be text, got " + f"{type(label).__name__}" ) if isinstance(members, str): members = [members] if not isinstance(members, Sequence | np.ndarray): raise TypeError( - "genes must be feature ids or symbols, or a mapping of names to them, " - f"got {type(members).__name__}" + "features must be feature ids or symbols, or a mapping of names to " + f"them, got {type(members).__name__}" ) start = len(columns) for gene in list(members): if not isinstance(gene, str): raise TypeError( - f"genes must be feature ids or symbols (str), got " + f"features must be feature ids or symbols (str), got " f"{type(gene).__name__} {gene!r}" ) if gene in index: @@ -5137,21 +5189,21 @@ def _dot_genes( found = by_symbol[gene] if len(found) > 1: raise ValueError( - f"the symbol {gene!r} names {len(found)} genes, " + f"the symbol {gene!r} names {len(found)} features, " f"{quoted([ids[j] for j in found])}; give their feature ids" ) columns.append(found[0]) else: raise KeyError( - f"no gene {gene!r} among the cube's {len(ids):,} features, by " + f"no feature {gene!r} among the cube's {len(ids):,} features, by " "feature_id or symbol" ) names.append(gene) if len(columns) == start: raise ValueError( - "genes is empty; give at least one gene" + "features is empty; give at least one feature" if not label - else f"the group of genes {label!r} is empty" + else f"the group of features {label!r} is empty" ) if label: gene_groups.append((label, start, len(columns) - 1)) diff --git a/cytepool/principal_components.py b/cytepool/principal_component_analysis.py similarity index 80% rename from cytepool/principal_components.py rename to cytepool/principal_component_analysis.py index 94164db..58aea4d 100644 --- a/cytepool/principal_components.py +++ b/cytepool/principal_component_analysis.py @@ -1,13 +1,13 @@ """Principal components of samples, as DESeq2's ``plotPCA`` computes them. -:func:`principal_components` places the samples of a matrix of log-expression values, or -of one group (cell type) of a cube, on their principal components (Pearson 1901), as -DESeq2's ``plotPCA`` does for its transformed values (Love et al. 2014): from the -``top`` genes of largest variance over the samples, each centred on its mean, by the -singular value decomposition of that samples x genes matrix, X = U S V'. The -coordinates of the samples are U S, the loadings of the genes are V, and the share of -the variance that each component explains is its squared singular value over their -sum, DESeq2's ``percentVar``. :func:`~cytepool.plotting.plot_pca` draws them. +:func:`~cytepool.principal_components` places the samples of a matrix of log-expression +values, or of one group (cell type) of a cube, on their principal components (Pearson +1901), as DESeq2's ``plotPCA`` does for its transformed values (Love et al. 2014): from +the ``top`` genes of largest variance over the samples, each centred on its mean, by the +singular value decomposition of that samples x genes matrix, X = U S V'. The coordinates +of the samples are U S, the loadings of the genes are V, and the share of the variance +that each component explains is its squared singular value over their sum, DESeq2's +``percentVar``. :func:`~cytepool.plotting.plot_pca` draws them. What DESeq2's help page leaves open was found by running ``plotPCA`` 1.46.0 as a black box: it ranks the genes by their variance over the samples, keeps genes of equal @@ -19,17 +19,17 @@ not depend on the order of the genes or the sign of the values. The values: - A matrix holds one row per sample and one column per gene, finite, such as - log-CPM values or the variance-stabilized values that DESeq2's guide takes. A - :class:`~cytepool.stats.voom.VoomResult` gives its log-CPM values. A cube gives - one group's samples prepared as :func:`~cytepool.plotting.plot_mds` prepares them, - as the edgeR User's Guide prepares counts for ``plotMDS``: the samples with fewer - than ``min_cells`` cells or no counts are left out, as - :func:`~cytepool.differential_expression.limma_voom` leaves them out; the genes are - kept by :func:`~cytepool.normalization.filter_by_expression` (Chen, Lun and Smyth - 2016) with the samples as one group; and the values are their log2 counts per - million with TMM normalization factors and an average count of 2 added - (:func:`~cytepool.normalization.log_cpm`). + A matrix holds one row per sample and one column per gene, finite, such as log-CPM + values or the variance-stabilized values that DESeq2's guide takes. A + :class:`~cytepool.stats.voom.VoomResult` gives its log-CPM values. A cube gives one + group's samples prepared as :func:`~cytepool.plotting.plot_mds` prepares them, as + the edgeR User's Guide prepares counts for ``plotMDS``: the samples with fewer than + ``min_cells`` cells or no counts are left out, as :func:`~cytepool.limma_voom` + leaves them out; the genes are kept by + :func:`~cytepool.normalization.filter_by_expression` (Chen, Lun and Smyth 2016) with + the samples as one group; and the values are their log2 counts per million with TMM + normalization factors and an average count of 2 added + (:func:`~cytepool.normalization.log2_cpm`). References: Pearson K (1901). On lines and planes of closest fit to systems of points in space. @@ -64,7 +64,7 @@ from ._messages import quoted from ._options import integral_number, positive_integer from .cube import Cube -from .normalization import filter_by_expression, log_cpm, tmm_factors +from .normalization import filter_by_expression, log2_cpm, tmm_factors from .stats.voom import VoomResult from .table import Table @@ -92,7 +92,7 @@ class PrincipalComponents: Rows of ``coordinates`` are the samples used, in the order of ``samples``; columns are the components, in decreasing order of the variance they explain. There are - min(n_samples - 1, n_genes) components: the centred values have no more + min(n_samples - 1, n_features) components: the centred values have no more dimensions. The arrays are read-only. Attributes: @@ -101,10 +101,12 @@ class PrincipalComponents: to sign. variance_explained: ``(n_components,)`` float64, each component's share of the total variance of the genes used, summing to 1; DESeq2's ``percentVar``. - loadings: ``(n_genes_used, n_components)`` float64, the unit vectors of the - components over the genes used, in the order of ``genes``. - genes: ``(n_genes_used,)`` positions of the genes used, in decreasing order of - their variance: columns of the matrix, or features of the cube. + loadings: ``(n_features_used, n_components)`` float64, the unit vectors of + the components over the features used, in the order of ``features``. + features: The features used, one row each, in decreasing order of their + variance: ``position``, the column of the matrix or the feature of the + cube, and for a cube the columns of its feature table, such as + ``feature_id`` and ``symbol``. samples: One row per sample used: ``sample_id`` and, for a cube, the columns of its sample table; for a matrix, the ids ``"1"``, ``"2"``, ... of its rows. @@ -114,7 +116,7 @@ class PrincipalComponents: coordinates: FloatArray variance_explained: FloatArray loadings: FloatArray - genes: IntArray + features: Table samples: Table settings: Mapping[str, Any] @@ -127,7 +129,7 @@ def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: self.coordinates, self.variance_explained, self.loadings, - self.genes, + self.features, self.samples, dict(self.settings), provenance_text(self), @@ -147,14 +149,15 @@ def provenance(self) -> dict[str, Any] | None: provenance (``"cube"``), the layer read and the settings; None for components of values given as an array or a VoomResult. The cube's record costs what it costs for - :func:`~cytepool.differential_expression.limma_voom` ("Provenance").""" + :func:`~cytepool.limma_voom` ("Provenance").""" return provenance_of(self) def __repr__(self) -> str: n_samples, n_components = self.coordinates.shape shares = ", ".join(f"{100 * v:.1f}%" for v in self.variance_explained[:3]) return ( - f"PrincipalComponents(n_samples={n_samples}, n_genes={self.genes.size}, " + f"PrincipalComponents(n_samples={n_samples}, " + f"n_features={len(self.features)}, " f"n_components={n_components}, variance_explained=[{shares}" f"{', ...' if n_components > 3 else ''}])" ) @@ -164,7 +167,7 @@ def _restored_components( coordinates: FloatArray, variance_explained: FloatArray, loadings: FloatArray, - genes: IntArray, + features: Table, samples: Table, settings: dict[str, Any], provenance: str | None, @@ -174,7 +177,7 @@ def _restored_components( coordinates=read_only(np.asarray(coordinates)), variance_explained=read_only(np.asarray(variance_explained)), loadings=read_only(np.asarray(loadings)), - genes=read_only(np.asarray(genes)), + features=features, samples=samples, settings=MappingProxyType(settings), ) @@ -198,7 +201,7 @@ def principal_components( Args: data: ``(n_samples, n_genes)`` log-expression values, finite; a :class:`~cytepool.stats.voom.VoomResult`, whose log-CPM values are used; - or a :class:`~cytepool.cube.Cube`, of which one group's samples are used. + or a :class:`~cytepool.Cube`, of which one group's samples are used. group: The group (cell type) of a cube; it may be left out when the cube has one group. Only cubes take it. top: The number of genes of largest variance used, at least 1 (DESeq2's @@ -207,8 +210,8 @@ def principal_components( default as for ``limma_voom``; for a cube that records cell numbers. Returns: - The coordinates, shares of variance and loadings, with the genes and samples - used. + The coordinates, shares of variance and loadings, with the features and + samples used. Raises: TypeError: If an argument has the wrong type, or is or holds a numpy masked @@ -222,6 +225,7 @@ def principal_components( """ count = positive_integer(top, "top") values, genes, samples, settings = _values(data, group, min_cells) + feature_table = data.features if isinstance(data, Cube) else None n_samples, n_genes = values.shape if n_samples < 2: raise ValueError( @@ -255,7 +259,7 @@ def principal_components( coordinates=read_only(np.ascontiguousarray(coordinates)), variance_explained=read_only(singular**2 / total), loadings=read_only(np.ascontiguousarray(loadings)), - genes=read_only(genes[chosen]), + features=_features_used(genes[chosen], feature_table), samples=samples, settings=MappingProxyType(settings), ) @@ -264,6 +268,21 @@ def principal_components( return result +def _features_used(positions: IntArray, features: Table | None) -> Table: + """The features used: their positions, then the columns of the cube's feature + table at those positions.""" + columns: dict[str, Any] = {"position": read_only(np.asarray(positions, np.int64))} + if features is not None: + if "position" in features: + raise ValueError( + "the cube's feature table has a column 'position', the name the " + "principal components give the positions of the features used; " + "rename it" + ) + columns |= features.take(positions).to_dict() + return Table(columns) + + def _signs(coordinates: FloatArray) -> FloatArray: """The sign of each component that makes its largest absolute coordinate positive; of near ties, the first.""" @@ -288,7 +307,7 @@ def _values( raise ValueError( f"{given} applies to a cube, and data is a {type(data).__name__}" ) - source = data.log_cpm if isinstance(data, VoomResult) else data + source = data.log2_cpm if isinstance(data, VoomResult) else data values = real_array(unmasked_array(source, "data"), "data", boolean=False) if values.ndim != 2: raise ValueError( @@ -338,13 +357,13 @@ def _cube_values( f"{fewest} cells; principal components need two or more" ) kept = counts[rows] - keep = filter_by_expression(kept, group=np.zeros(rows.size)) + keep = filter_by_expression(kept, sample_groups=np.zeros(rows.size)) if not keep.any(): raise ValueError( f"no gene of group {group!r} passes filter_by_expression, so there is " "nothing to decompose" ) kept = kept[:, keep] - values = log_cpm(kept, norm_factors=tmm_factors(kept), prior_count=_PRIOR_COUNT) + values = log2_cpm(kept, norm_factors=tmm_factors(kept), prior_count=_PRIOR_COUNT) settings: dict[str, Any] = {"group": group, "min_cells": fewest} return values, np.flatnonzero(keep), cube.samples.take(rows), settings diff --git a/cytepool/replication.py b/cytepool/replication_across_studies.py similarity index 79% rename from cytepool/replication.py rename to cytepool/replication_across_studies.py index 487ee52..04051d2 100644 --- a/cytepool/replication.py +++ b/cytepool/replication_across_studies.py @@ -1,26 +1,25 @@ """Replication of differential expression across studies. -Most genes that one study finds are not found in the next (Nakatsuka et al. 2025), so -a result of several studies says how well they agree next to what they find together. -:func:`differential_expression_replication` reads the differential expression of one -group (cell type) and contrast in every study of a -:class:`~cytepool.cube.CubeCollection`, lined up gene by gene by -:func:`~cytepool.meta_analysis.align_study_results`, and gives for every gene the -p-value combined over the studies that tested it, the partial conjunction p-value -that the effect is found in at least u studies in the same direction (Benjamini and -Heller 2008), how many studies find it and in which direction, and the heterogeneity -of its log2 fold changes; for every pair of studies, pi1 of one study's p-values -among the genes the other finds (Storey and Tibshirani 2003) and their sign -agreement; and the number of genes found in at least k studies. +Most genes that one study finds are not found in the next (Nakatsuka et al. 2025), so a +result of several studies says how well they agree next to what they find together. +:func:`~cytepool.replication` reads the differential expression of one group (cell type) +and contrast in every study of a collection, a :class:`~cytepool.StudyResults`, lined up +gene by gene by :meth:`StudyResults.table `, and gives for +every gene the p-value combined over the studies that tested it, the partial conjunction +p-value that the effect is found in at least u studies in the same direction (Benjamini +and Heller 2008), how many studies find it and in which direction, and the heterogeneity +of its log2 fold changes; for every pair of studies, pi1 of one study's p-values among +the genes the other finds (Storey and Tibshirani 2003) and their sign agreement; and the +number of genes found in at least k studies. :func:`~cytepool.plotting.plot_study_agreement` and :func:`~cytepool.plotting.plot_replicated_genes` draw them. The genes: A study tested a gene when its table of the group and contrast has it; one that did not measure it, measured and left it out, or did not analyse the group is left - out of that gene's numbers, and :func:`~cytepool.meta_analysis.align_study_results` - says which. Each study's two-sided p-values become one-sided ones by the sign of - its log2 fold change (``directions`` of + out of that gene's numbers, and :meth:`StudyResults.table + ` says which. Each study's two-sided p-values + become one-sided ones by the sign of its log2 fold change (``directions`` of :func:`~cytepool.stats.combined_p_values.combine_p_values`), so that the combined and partial conjunction p-values are two-sided and effects in opposite directions count against each other. Stouffer's method, the default, weighs each study by the @@ -32,12 +31,12 @@ Agreement between studies: For an ordered pair of studies, the discovery and the replication, the genes the discovery finds that the replication tested are compared: pi1 = 1 - pi0 of their - p-values in the replication study, Storey's estimate of the proportion of them - with an effect there (:func:`~cytepool.stats.multiple_testing.proportion_of_true_nulls`), - which counts effects that the replication study is too small to find on its - own; the share of them whose log2 fold changes have the same sign; and how many - it finds too. pi1 measures replication in the replication study's p-values, so it - is not symmetric. + p-values in the replication study, Storey's estimate of the proportion of them with + an effect there (:func:`~cytepool.stats.multiple_testing.proportion_of_true_nulls`), + which counts effects that the replication study is too small to find on its own; the + share of them whose log2 fold changes have the same sign; and how many it finds too. + pi1 measures replication in the replication study's p-values, so it is not + symmetric. pi0 is estimated at lambda = 0.5 by default: the share of p-values above 0.5, over 0.5 (Storey and Tibshirani 2003). qvalue's default, a smoother of the @@ -47,14 +46,14 @@ Heterogeneity: Cochran's Q, its p-value, I^2 and tau^2 of the log2 fold changes and their - standard errors, by :func:`~cytepool.meta_analysis.meta_analysis` with + standard errors, by :func:`~cytepool.stats.meta_analysis.fit_meta_analysis` with ``meta_method``, for the genes that two studies or more tested; NaN for the others. Not done: The r-values of Heller, Bogomolov and Benjamini (2014), which claim replication in a follow-up study of the genes a primary study selects. Held-out prediction of case status from the genes the studies find (Nakatsuka et al. 2025) is - :func:`cytepool.held_out_prediction.held_out_prediction`. + :func:`cytepool.held_out_prediction`. References: Nakatsuka N, Adler D, Jiang L, Hartman A, Cheng E, Klann E, Satija R (2025). @@ -90,19 +89,19 @@ import numpy as np import numpy.typing as npt -from ._analysis_records import software_versions +from ._analysis_records import software_versions, study_keys from ._columns import text_column from ._masked_arrays import reject_masked from ._messages import fitted_lines, quoted from ._options import checked_choice, integral_number, real_number -from .cube import CubeCollection -from .differential_expression import DifferentialExpressionResult -from .meta_analysis import align_study_results, meta_analysis +from ._study_statistics import study_results from .stats.combined_p_values import combine_p_values, partial_conjunction +from .stats.meta_analysis import fit_meta_analysis from .stats.multiple_testing import adjust_p_values, proportion_of_true_nulls +from .study_results import StudyResults from .table import Table -__all__ = ["ReplicationResult", "differential_expression_replication"] +__all__ = ["ReplicationResult", "replication"] FloatArray = npt.NDArray[np.float64] @@ -120,14 +119,14 @@ class ReplicationResult: """The replication of one group and contrast across the studies of a collection. - Built by :func:`differential_expression_replication`. Immutable. + Built by :func:`replication`. Immutable. - ``genes`` has a row for every gene of the collection that at least one study + ``features`` has a row for every feature of the collection that at least one study tested, in the collection's order: ``feature_id`` and, when the collection's features have it, ``symbol``; ``n_studies``, the studies that tested it; ``n_up`` and ``n_down``, those whose log2 fold change is above and below zero; ``n_found``, those that find it (adjusted p-value below ``alpha``), and - ``n_found_up`` and ``n_found_down`` by direction; ``direction``, 1 or -1, of the + ``n_found_up`` and ``n_found_down`` by direction; ``sign``, 1 or -1, of the combined effect, and ``sign_agreement``, the share of the studies that tested the gene whose log2 fold change has that sign; ``p_value`` and ``adjusted_p_value``, the combined p-value of every study that tested the gene, two-sided; and @@ -138,23 +137,24 @@ class ReplicationResult: fold changes (NaN where one study tested it). ``study_pairs`` has a row for every ordered pair of the studies that analysed the - group and contrast: ``discovery`` and ``replication``; ``n_found``, the genes the - discovery study finds that the replication study tested; ``n_found_in_both``, - those it finds too; ``sign_agreement``, the share of the ``n_found`` genes whose - log2 fold changes have the same sign in both; and ``pi1``, 1 - pi0 of the - replication study's p-values of those genes (NaN where there is none). + group and contrast: ``discovery`` and ``replication``; ``n_features_found``, the + features the discovery study finds that the replication study tested; + ``n_features_found_in_both``, those it finds too; ``sign_agreement``, the share + of the ``n_features_found`` features whose log2 fold changes have the same sign in + both; and ``pi1``, 1 - pi0 of the replication study's p-values of those features + (NaN where there is none). ``found_in`` has a row for every k from 1 to the number of studies that analysed - the group and contrast: ``n_studies``, k; ``n_genes``, the genes that at least k - studies find; and ``n_genes_same_direction``, those that at least k studies find - in one direction. + the group and contrast: ``n_studies``, k; ``n_features``, the features that at + least k studies find; and ``n_features_same_direction``, those that at least k + studies find in one direction. ``studies`` has a row for every study of the collection: ``study``; ``analysed``, whether its result has a table of the group and contrast; ``reason``, why not (empty when analysed); ``n_samples``, the samples its fit of the group used (0 when not analysed); ``weight``, its weight in Stouffer's method (NaN for another - method, or when not analysed); ``n_tested`` and ``n_found``, the genes it tested - and finds. + method, or when not analysed); ``n_features_tested`` and ``n_features_found``, the + features it tested and finds. Raises: TypeError: If a table is not a ``Table``, or ``provenance`` is not a mapping @@ -162,9 +162,9 @@ class ReplicationResult: ValueError: If ``provenance`` holds a NaN or an infinity. """ - __slots__ = ("_genes", "_study_pairs", "_found_in", "_studies", "_record") + __slots__ = ("_features", "_study_pairs", "_found_in", "_studies", "_record") - _genes: Table + _features: Table _study_pairs: Table _found_in: Table _studies: Table @@ -172,14 +172,14 @@ class ReplicationResult: def __init__( self, - genes: Table, + features: Table, study_pairs: Table, found_in: Table, studies: Table, provenance: Mapping[str, Any], ) -> None: tables = { - "genes": genes, + "features": features, "study_pairs": study_pairs, "found_in": found_in, "studies": studies, @@ -207,7 +207,7 @@ def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: return ( ReplicationResult, ( - self._genes, + self._features, self._study_pairs, self._found_in, self._studies, @@ -216,10 +216,10 @@ def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: ) @property - def genes(self) -> Table: - """Every gene that a study tested, with its combined and replication + def features(self) -> Table: + """Every feature that a study tested, with its combined and replication p-values, the studies that find it and its heterogeneity.""" - return self._genes + return self._features @property def study_pairs(self) -> Table: @@ -259,23 +259,23 @@ def provenance(self) -> dict[str, Any]: def __repr__(self) -> str: settings = self.settings analysed = int(np.count_nonzero(self._studies["analysed"])) - replicated = self._genes["replication_adjusted_p_value"] + replicated = self._features["replication_adjusted_p_value"] with np.errstate(invalid="ignore"): n_replicated = int(np.count_nonzero(replicated < settings["alpha"])) return fitted_lines( f"ReplicationResult of group {settings['group']!r}, contrast " f"{settings['contrast']!r}: {analysed:,} of {len(self._studies):,} " - f"studies, {len(self._genes):,} genes; {n_replicated:,} found in at least " + f"studies, {len(self._features):,} genes; {n_replicated:,} found in at " + "least " f"{settings['at_least']:,} studies in one direction at adjusted p < " f"{settings['alpha']:g} ({settings['conjunction_method']})" ) -def differential_expression_replication( - collection: CubeCollection, - results: Mapping[str, DifferentialExpressionResult], - group: str, - contrast: str, +def replication( + studies: StudyResults, + group: str | None = None, + contrast: str | None = None, *, alpha: float = 0.05, method: str = "stouffer", @@ -286,27 +286,27 @@ def differential_expression_replication( null_proportion: Mapping[str, Any] | None = None, ) -> ReplicationResult: """How well the studies of a collection replicate each other's differential - expression, gene by gene and study by study. + expression, feature by feature and study by study. For one group (cell type) and contrast, the studies' results, each the analysis - of its aligned cube (:meth:`~cytepool.cube.CubeCollection.aligned_cube`), are - lined up by :func:`~cytepool.meta_analysis.align_study_results`, and every gene + of its aligned cube (:meth:`~cytepool.CubeCollection.aligned_cube`), are lined up + by :meth:`StudyResults.table `, and every feature gets its combined p-value, its partial conjunction p-value of an effect in at least ``at_least`` studies in one direction, the studies that find it, and the heterogeneity of its log2 fold changes; every pair of studies its pi1 and sign - agreement; and every k the genes found in at least k studies. The module + agreement; and every k the features found in at least k studies. The module description gives the methods. Args: - collection: The collection whose aligned cubes the studies' results analysed. - results: The differential-expression result of every study of the - collection, by study name, from - :func:`~cytepool.differential_expression.limma_voom`, - :func:`~cytepool.differential_expression.limma_trend` or - :func:`~cytepool.differential_expression.dream_voom`. - group: An aligned group id of the collection. - contrast: A contrast name that at least two studies tested. - alpha: The adjusted p-value below which a study finds a gene, in (0, 1). + studies: The differential-expression result of every study of a + collection, from :func:`~cytepool.limma_voom`, + :func:`~cytepool.limma_trend` or :func:`~cytepool.dream_voom`, as + :func:`~cytepool.analyse_each_study` gives them. + group: An aligned group id of the collection; it may be left out when the + studies analysed one group. + contrast: A contrast name that at least two studies tested; it may be left + out when the studies tested one. + alpha: The adjusted p-value below which a study finds a feature, in (0, 1). method: The combination of every study's p-value: ``"stouffer"`` (weighted, by default by the square roots of the studies' samples), ``"fisher"``, ``"tippett"``, ``"pearson"``, @@ -322,7 +322,7 @@ def differential_expression_replication( used. For ``method="stouffer"`` only. meta_method: The estimator of tau^2 for the heterogeneity statistics: ``"reml"``, ``"dl"``, ``"pm"`` or ``"fixed"`` - (:func:`~cytepool.meta_analysis.meta_analysis`). + (:func:`~cytepool.stats.meta_analysis.fit_meta_analysis`). null_proportion: Options of :func:`~cytepool.stats.multiple_testing.proportion_of_true_nulls` for pi1: ``lambdas``, ``method``, ``smooth_df`` and ``smooth_log``. None, the @@ -330,17 +330,18 @@ def differential_expression_replication( the module description). Returns: - The tables of genes, pairs of studies, counts by number of studies and + The tables of features, pairs of studies, counts by number of studies and studies, with a provenance record. Raises: - TypeError: If an argument has the wrong type, or is a numpy masked array. - ValueError: If ``results`` does not give the result of every study of the - collection, fewer than two studies analysed the group and contrast, a - study's table lacks a column the analysis reads (as an F-test's does), - ``at_least`` exceeds the studies that analysed it, an option is out of - range or unknown, or ``weights`` are given for a method other than - Stouffer's or do not give every study that analysed it. + TypeError: If ``studies`` is not a ``StudyResults``, an argument has the + wrong type, or is a numpy masked array. + ValueError: If a key is left out and the studies analysed several, fewer + than two studies analysed the group and contrast, a study's table lacks + a column the analysis reads (as an F-test's does), ``at_least`` exceeds + the studies that analysed it, an option is out of range or unknown, or + ``weights`` are given for a method other than Stouffer's or do not give + every study that analysed it. KeyError: If ``group`` is not a group of the collection, or no study tested ``contrast``. """ @@ -356,8 +357,11 @@ def differential_expression_replication( estimator = checked_choice(meta_method, "meta_method", _META_METHODS) u = integral_number(at_least, "at_least", expected="a whole number of studies") pi0_options = _null_proportion_options(null_proportion) - aligned = align_study_results(collection, results, group, contrast) - studies = collection.studies + results = study_results(studies, "replication") + collection = results.collection + group, contrast = study_keys(results, group, contrast) + aligned = results.table(group, contrast) + names = collection.studies n_studies, n_features = collection.n_studies, collection.n_features status = np.asarray(aligned["status"].tolist(), dtype=object).reshape( n_studies, n_features @@ -381,7 +385,7 @@ def differential_expression_replication( tested &= ~np.isnan(values["log2_fold_change"]) analysed = (status == "tested").any(axis=1) reasons = aligned["reason"].tolist() - used = [study for study, flag in zip(studies, analysed, strict=True) if flag] + used = [study for study, flag in zip(names, analysed, strict=True) if flag] if len(used) < 2: raise ValueError( f"group {group!r} and contrast {contrast!r} were analysed by " @@ -393,12 +397,12 @@ def differential_expression_replication( f"{group!r} and contrast {contrast!r}, got {u}" ) n_samples = np.zeros(n_studies, dtype=np.int64) - for row, study in enumerate(studies): + for row, study in enumerate(names): if analysed[row]: summary = results[study].summary position = summary["group_id"].tolist().index(group) n_samples[row] = int(summary["n_samples_used"][position]) - study_weights = _study_weights(weights, combination, studies, analysed, n_samples) + study_weights = _study_weights(weights, combination, names, analysed, n_samples) genes = tested.any(axis=0) rows = tested[:, genes] @@ -416,10 +420,14 @@ def differential_expression_replication( directions=directions, ) replicated = partial_conjunction(p, u, method=conjunction, directions=directions) - heterogeneity = meta_analysis(lfc, se, method=estimator) # type: ignore[arg-type] + heterogeneity = fit_meta_analysis( + lfc, + se, + method=estimator, # type: ignore[arg-type] + ) several = combined.n_studies >= 2 - assert combined.direction is not None - direction = np.asarray(combined.direction) + assert combined.sign is not None + direction = np.asarray(combined.sign) up, down = rows & (lfc > 0.0), rows & (lfc < 0.0) agreeing = np.where(direction > 0.0, up.sum(axis=0), down.sum(axis=0)) with np.errstate(invalid="ignore", divide="ignore"): @@ -441,7 +449,7 @@ def differential_expression_replication( "n_found": found.sum(axis=0).astype(np.int64), "n_found_up": found_up.sum(axis=0).astype(np.int64), "n_found_down": found_down.sum(axis=0).astype(np.int64), - "direction": direction, + "sign": direction, "sign_agreement": sign_agreement, "p_value": np.asarray(combined.p_value), "adjusted_p_value": adjust_p_values(combined.p_value, "bh"), @@ -452,24 +460,24 @@ def differential_expression_replication( "i_squared": np.where(several, heterogeneity.i_squared, np.nan), "tau_squared": np.where(several, heterogeneity.tau_squared, np.nan), } - gene_table = Table(columns) - pairs = _study_pairs(studies, analysed, rows, found, lfc, p, pi0_options) + feature_table = Table(columns) + pairs = _study_pairs(names, analysed, rows, found, lfc, p, pi0_options) k = np.arange(1, len(used) + 1) same_direction = np.maximum(found_up.sum(axis=0), found_down.sum(axis=0)) found_in = Table( { "n_studies": k.astype(np.int64), - "n_genes": np.array( + "n_features": np.array( [int(np.count_nonzero(found.sum(axis=0) >= j)) for j in k], np.int64 ), - "n_genes_same_direction": np.array( + "n_features_same_direction": np.array( [int(np.count_nonzero(same_direction >= j)) for j in k], np.int64 ), } ) study_table = Table( { - "study": text_column(list(studies)), + "study": text_column(list(names)), "analysed": analysed.copy(), "reason": text_column( [ @@ -481,8 +489,8 @@ def differential_expression_replication( "weight": np.full(n_studies, np.nan) if study_weights is None else study_weights, - "n_tested": tested.sum(axis=1).astype(np.int64), - "n_found": found.sum(axis=1).astype(np.int64), + "n_features_tested": tested.sum(axis=1).astype(np.int64), + "n_features_found": found.sum(axis=1).astype(np.int64), } ) if study_weights is None: @@ -492,9 +500,7 @@ def differential_expression_replication( else: weight_setting = { study: float(weight) - for study, weight, flag in zip( - studies, study_weights, analysed, strict=True - ) + for study, weight, flag in zip(names, study_weights, analysed, strict=True) if flag } settings = { @@ -510,16 +516,16 @@ def differential_expression_replication( "p_value_adjustment": "BH", } return ReplicationResult( - gene_table, + feature_table, pairs, found_in, study_table, { - "analysis": "differential_expression_replication", + "analysis": "replication", "versions": software_versions(), "settings": settings, "collection": collection.provenance, - "studies": {study: results[study].provenance for study in studies}, + "studies": {study: results[study].provenance for study in names}, }, ) @@ -630,8 +636,12 @@ def _study_pairs( { "discovery": text_column([pair[0] for pair in names]), "replication": text_column([pair[1] for pair in names]), - "n_found": np.array([count[0] for count in counts], dtype=np.int64), - "n_found_in_both": np.array([count[1] for count in counts], dtype=np.int64), + "n_features_found": np.array( + [count[0] for count in counts], dtype=np.int64 + ), + "n_features_found_in_both": np.array( + [count[1] for count in counts], dtype=np.int64 + ), "sign_agreement": np.array(agreement, dtype=np.float64), "pi1": np.array(pi1, dtype=np.float64), } @@ -642,7 +652,7 @@ def _absence( reasons: list[str], status: npt.NDArray[Any], row: int, n_features: int ) -> str: """Why a study has no table of the group and contrast: the reason that - align_study_results gives a gene it measured, or that it measured none.""" + StudyResults.table gives a feature it measured, or that it measured none.""" start = row * n_features for column in range(n_features): if status[row, column] == "not_analysed": diff --git a/cytepool/stats/rotation_gene_set_tests.py b/cytepool/rotation_gene_set_tests.py similarity index 79% rename from cytepool/stats/rotation_gene_set_tests.py rename to cytepool/rotation_gene_set_tests.py index bc15581..73d9567 100644 --- a/cytepool/stats/rotation_gene_set_tests.py +++ b/cytepool/rotation_gene_set_tests.py @@ -3,35 +3,34 @@ A rotation test (Langsrud 2005) draws random rotations of the residual space of the linear model fitted to every gene, which leave the joint distribution of the data -unchanged under the null hypothesis, and compares a set statistic with its values -under the rotations. :func:`roast` and :func:`mroast` are self-contained tests of one -gene set and of many (Wu et al. 2010): whether the genes of a set are differentially -expressed for the contrast at all. :func:`romer` is a competitive test of many sets -(Majewski et al. 2010): whether the genes of a set rank higher than the other genes. -:func:`~cytepool.stats.gene_set_tests.fry` gives the limit of ``mroast`` with the mean -statistic over infinitely many rotations without drawing any. Results match limma -3.62.1 within Monte Carlo error, and exactly when CytePool is given limma's rotations, -except where noted under "Deliberate differences". +unchanged under the null hypothesis, and compares a set statistic with its values under +the rotations. :func:`~cytepool.roast` and :func:`~cytepool.mroast` are self-contained +tests of one gene set and of many (Wu et al. 2010): whether the genes of a set are +differentially expressed for the contrast at all. :func:`~cytepool.romer` is a +competitive test of many sets (Majewski et al. 2010): whether the genes of a set rank +higher than the other genes. :func:`~cytepool.fry` gives the limit of ``mroast`` with +the mean statistic over infinitely many rotations without drawing any. Results match +limma 3.62.1 within Monte Carlo error, and exactly when CytePool is given limma's +rotations, except where noted under "Deliberate differences". Effects and rotations: Each gene's contrast effect u_0 and residual effects u_1, ..., u_d are those of - :mod:`cytepool.stats.gene_set_tests`: coordinates of its data, whitened by its - precision weights, in the basis of the Householder QR decomposition of the design - reparametrised so that the contrast is its last coefficient. Under the null - hypothesis they are independent with variance sigma^2, so one rotation applied to - the (d + 1)-vectors of every gene leaves the joint distribution of the data, and - the correlation between genes, unchanged (Wu et al. 2010, section 4.2). A - rotation is given by its first row r, a point drawn uniformly from the unit - sphere in d + 1 dimensions (a vector of standard normal draws divided by its - length): a gene's rotated contrast effect is u* = r'u and its rotated residual - sum of squares |u|^2 - u*^2. The rotated moderated t-statistic of a gene uses - the prior variance s_0^2 and prior df d_0 estimated once from the observed - residual variances of all genes (Wu et al. 2010, section 2.4): t* = u* / s*, with - s*^2 = (d_0 s_0^2 + |u|^2 - u*^2) / (d_0 + d). The rotations come from - ``rng.standard_normal``, 4,096 at a time, which bounds the memory they take; numpy - gives the same stream however its draws are split, so a seeded generator gives the - same p-values every time. limma draws its own rotations from R's generator, so the - two programs agree within Monte Carlo error. + :func:`~cytepool.camera` and :func:`~cytepool.fry`: coordinates of its data, + whitened by its precision weights, in the basis of the Householder QR decomposition + of the design reparametrised so that the contrast is its last coefficient. Under the + null hypothesis they are independent with variance sigma^2, so one rotation applied + to the (d + 1)-vectors of every gene leaves the joint distribution of the data, and + the correlation between genes, unchanged (Wu et al. 2010, section 4.2). A rotation + is given by its first row r, a point drawn uniformly from the unit sphere in d + 1 + dimensions (a vector of standard normal draws divided by its length): a gene's + rotated contrast effect is u* = r'u and its rotated residual sum of squares |u|^2 - + u*^2. The rotated moderated t-statistic of a gene uses the prior variance s_0^2 and + prior df d_0 estimated once from the observed residual variances of all genes (Wu et + al. 2010, section 2.4): t* = u* / s*, with s*^2 = (d_0 s_0^2 + |u|^2 - u*^2) / (d_0 + + d). The rotations come from ``rng.standard_normal``, 4,096 at a time, which bounds + the memory they take; numpy gives the same stream however its draws are split, so a + seeded generator gives the same p-values every time. limma draws its own rotations + from R's generator, so the two programs agree within Monte Carlo error. roast and mroast: Each gene's moderated t becomes a z-score by Bailey's (1980) normalizing @@ -125,10 +124,10 @@ - A gene of contribution weight zero leaves the set: it counts neither in ``n_genes`` nor among the values that ``"mean50"`` averages. limma keeps it in both (and leaves it out of the active proportions, as CytePool does). - - romer's posterior probability P_g is 1 / (1 + exp(-lods)). limma computes - exp(lods) / (1 + exp(lods)), which is NaN beyond lods = 709.8 (|t| above about - 38 with an infinite prior df); the gene's shrunk contrast effect is then NaN and - the p-values of every set change (found by probing). + - romer's posterior probability P_g is 1 / (1 + exp(-B)), of the log-odds B. + limma computes exp(B) / (1 + exp(B)), which is NaN beyond B = 709.8 (|t| above + about 38 with an infinite prior df); the gene's shrunk contrast effect is then + NaN and the p-values of every set change (found by probing). - A set without genes, or whose genes all have weight zero, has NaN p-values and no direction and is left out of the adjustment of the other sets; limma reports "down" and NA, or the smallest possible p-value when all weights are zero. A @@ -190,11 +189,12 @@ from cytepool._options import checked_flag, positive_integer, random_generator from cytepool._ranks import average_ranks_in_order from cytepool._summation import observed_means -from cytepool.stats._gene_sets import ( +from cytepool._gene_sets import ( + GeneSetData, + gene_set_data, check_matched, checked_gene_sets, contrast_design, - expression_values, gene_effects, gene_positions, id_positions, @@ -234,53 +234,64 @@ def roast( - y: Any, - design: Any, - contrast: Any, + data: Any, gene_set: Any, *, - gene_ids: npt.ArrayLike | None = None, + group: str | None = None, + contrast: Any = None, + design: Any = None, + id_column: str = "feature_id", + feature_ids: npt.ArrayLike | None = None, weights: npt.ArrayLike | None = None, gene_weights: npt.ArrayLike | None = None, set_statistic: str = "mean", n_rotations: int = 1999, - trend: bool = False, - robust: bool = False, + trend: bool | None = None, + robust: bool | None = None, rng: np.random.Generator | int | None = None, n_threads: int | None = None, ) -> Table: """Self-contained rotation test of one gene set for one contrast. limma's ``roast`` (Wu et al. 2010): tests whether the genes of the set are - differentially expressed for ``contrast``, for each alternative: down, up, either + differentially expressed for ``contrast``, down, up, in either direction (two-sided) and mixed (in either direction, gene by gene), by comparing a set statistic of the genes' moderated z-scores with its values under random rotations of the residual space. The module docstring gives the method and the deliberate - differences from limma. With many sets, :func:`mroast` reports one row per set. + differences from limma. With many sets, :func:`mroast` tests them together. A + result of :func:`~cytepool.limma_voom` or :func:`~cytepool.limma_trend` is tested + as :func:`~cytepool.camera` tests it. Args: - y: ``(n_samples, n_genes)`` log-expression values, finite, or a - :class:`~cytepool.stats.voom.VoomResult`, whose log-CPM values are tested - with its weights. All genes are used to estimate the prior of the - residual variances. - design: ``(n_samples, n_coefficients)`` design matrix of full column rank, or - anything else that ``fit_linear_model`` accepts, such as a - :class:`~cytepool.design.Design`. It needs residual degrees of freedom. - contrast: The contrast to test, as for - :func:`~cytepool.stats.gene_set_tests.camera`: the name of a named - contrast of a ``Design``, the name or 0-based position of a design - column, or weights on the design columns. - gene_set: The set's genes: gene ids (matched to ``gene_ids``; ids not among - them are left out), 0-based positions of columns of ``y`` when - ``gene_ids`` is not given, or a boolean mask over the genes; or a mapping - of gene (id or position) to its contribution weight. A gene listed twice - counts once, and a gene of weight zero leaves the set. One set of a - :class:`~cytepool.gene_sets.GeneSets`, ``gene_sets[name]``, is a tuple of + data: A :class:`~cytepool.DifferentialExpressionResult` of limma-voom or + limma-trend, a :class:`~cytepool.stats.voom.VoomResult`, whose log2-CPM + values are tested with its weights, or ``(n_samples, n_genes)`` + log-expression values, finite. All genes are used to estimate the prior + of the residual variances. + gene_set: The set's genes: gene ids (matched to the ids of the data; ids not + among them are left out), 0-based positions of columns of an array when + ``feature_ids`` is not given, or a boolean mask over the genes; or a + mapping of gene (id or position) to its contribution weight. A gene + listed twice counts once, and a gene of weight zero leaves the set. One + set of a :class:`~cytepool.GeneSets`, ``gene_sets[name]``, is a tuple of gene ids. - gene_ids: One id per gene (column of ``y``), unique, to name genes by id. - weights: Precision weights, positive and finite: ``(n_samples,)`` for sample - weights or ``(n_samples, n_genes)`` for observation weights. Not with a - ``VoomResult``, which carries its own. + group: For a result, the group (cell type) whose fit is tested; it may be + left out when the result has one. + contrast: The contrast to test, as for :func:`~cytepool.camera`: for a + result, one of its contrasts; for an array or a VoomResult, the name of a + named contrast of a ``Design``, the name or 0-based position of a design + column, or weights on the design columns. + design: For an array or a VoomResult, ``(n_samples, n_coefficients)`` + design matrix of full column rank, or anything else that + ``fit_linear_model`` accepts, such as a :class:`~cytepool.Design`. It + needs residual degrees of freedom. + id_column: For a result, the column of its features whose ids the set + names, such as ``"feature_id"`` (the default) or ``"symbol"``. + feature_ids: For an array or a VoomResult, one id per gene (column), + unique, to name genes by id. + weights: For an array, precision weights, positive and finite: + ``(n_samples,)`` for sample weights or ``(n_samples, n_genes)`` for + observation weights. gene_weights: ``(n_genes,)`` contribution weights, finite, one per gene: a negative weight marks a gene expected to change in the opposite direction to the set, and larger weights count more in the set @@ -292,36 +303,47 @@ def roast( n_rotations: Number of rotations B, at least 1. The smallest p-value is 1 / (B + 1), and 1 / (2B + 1) for the up and down alternatives. trend: Whether the prior variance follows the mean log-expression of the - genes (limma's ``trend``). - robust: Whether to estimate the prior robustly (limma's ``robust``). + genes (limma's ``trend``); None takes the moderation of a result + (limma-trend's trend), and False otherwise. + robust: Whether to estimate the prior robustly (limma's ``robust``); None + takes the result's setting, and False otherwise. rng: A ``numpy.random.Generator`` to draw the rotations from, or an integer seed for one, or None for a fresh, unseeded generator. n_threads: The most threads to use for the rotations; ``None`` uses numba's current setting. The result does not depend on it. Returns: - A table with one row per alternative, ``"down"``, ``"up"``, ``"up_or_down"`` - (two-sided) and ``"mixed"``: ``alternative``; ``n_genes``, the number of the - set's genes among those tested; ``active_proportion``, the proportion of the - set's genes active for that alternative; and ``p_value``. A set without genes - has NaN proportions and p-values. + A table with one row, as :func:`mroast` gives a row per set: ``n_genes``, + the number of the set's genes among those tested; ``proportion_down``, + ``proportion_up`` and ``proportion_mixed``, the proportions of the set's + genes active down, up and in either direction; ``direction``, ``"up"`` or + ``"down"`` (None where the p-value is NaN); ``p_value``, the two-sided + p-value (limma's ``UpOrDown``); and ``down_p_value``, ``up_p_value`` and + ``mixed_p_value``, those of the other alternatives. A set without genes has + NaN proportions and p-values. Raises: TypeError: If an argument has the wrong type, or is or holds a numpy masked array. - ValueError: If ``y`` is not finite, the design does not have one row per - sample, is not of full column rank or leaves no residual degrees of - freedom, the contrast or the set is invalid, a weight is not positive and - finite, a contribution weight is not finite, an option is invalid, or no - gene id of the set is among ``gene_ids``. + ValueError: If the values are not finite, the design is missing for an + array, does not have one row per sample, is not of full column rank or + leaves no residual degrees of freedom, the contrast or the set is + invalid, a weight is not positive and finite, a contribution weight is + not finite, an option is invalid, no gene id of the set is among the + data's ids, an argument applies to another kind of data, or the result is + from ``dream_voom``. + KeyError: If a result has no such group, contrast or column of its features, + or the group cannot estimate the contrast. """ reject_masked(gene_set, "gene_set") tests = _roast_tests( - y, - design, - contrast, + data, {"gene_set": gene_set}, - gene_ids=gene_ids, + group=group, + contrast=contrast, + design=design, + id_column=id_column, + feature_ids=feature_ids, weights=weights, gene_weights=gene_weights, set_statistic=set_statistic, @@ -333,29 +355,37 @@ def roast( function="roast", ) down, up, mixed = tests.active[0] + p_down, p_up, two_sided, p_mixed = tests.p_values[0].tolist() return Table( { - "alternative": ["down", "up", "up_or_down", "mixed"], - "n_genes": np.full(4, tests.n_genes[0], dtype=np.int64), - "active_proportion": np.array([down, up, max(down, up), mixed]), - "p_value": tests.p_values[0], + "n_genes": tests.n_genes[:1].copy(), + "proportion_down": np.array([down]), + "proportion_up": np.array([up]), + "proportion_mixed": np.array([mixed]), + "direction": [_direction(two_sided, p_up, p_down)], + "p_value": np.array([two_sided]), + "down_p_value": np.array([p_down]), + "up_p_value": np.array([p_up]), + "mixed_p_value": np.array([p_mixed]), } ) def mroast( - y: Any, - design: Any, - contrast: Any, + data: Any, gene_sets: Mapping[str, Any], *, - gene_ids: npt.ArrayLike | None = None, + group: str | None = None, + contrast: Any = None, + design: Any = None, + id_column: str = "feature_id", + feature_ids: npt.ArrayLike | None = None, weights: npt.ArrayLike | None = None, gene_weights: npt.ArrayLike | None = None, set_statistic: str = "mean", n_rotations: int = 1999, - trend: bool = False, - robust: bool = False, + trend: bool | None = None, + robust: bool | None = None, mid_p: bool = True, rng: np.random.Generator | int | None = None, n_threads: int | None = None, @@ -367,22 +397,27 @@ def mroast( the method and the deliberate differences from limma. Args: - y: Log-expression values or a ``VoomResult``, as for :func:`roast`. - design: The design, as for :func:`roast`. - contrast: The contrast to test, as for :func:`roast`. + data: A differential-expression result, a VoomResult or log-expression + values, as for :func:`roast`. gene_sets: Mapping of set name to its genes, each as ``gene_set`` of :func:`roast`: gene ids, positions or a boolean mask, or a mapping of - gene to its contribution weight. A - :class:`~cytepool.gene_sets.GeneSets`, such as one read from a GMT file - by :func:`~cytepool.gene_sets.read_gmt`, is such a mapping, of gene ids. - gene_ids: One id per gene, unique, to name genes by id. - weights: Precision weights, as for :func:`roast`. + gene to its contribution weight. A :class:`~cytepool.GeneSets`, such as + one read from a GMT file by :func:`~cytepool.read_gmt`, is such a + mapping, of gene ids. + group: For a result, the group whose fit is tested, as for :func:`roast`. + contrast: The contrast to test, as for :func:`roast`. + design: For an array or a VoomResult, the design, as for :func:`roast`. + id_column: For a result, the column of its features whose ids the sets name. + feature_ids: For an array or a VoomResult, one id per gene, unique, to name + genes by id. + weights: For an array, precision weights, as for :func:`roast`. gene_weights: ``(n_genes,)`` contribution weights, as for :func:`roast`; not with sets that give their own weights. set_statistic: ``"mean"``, ``"floormean"``, ``"mean50"`` or ``"msq"``. n_rotations: Number of rotations B, at least 1. - trend: Whether the prior variance follows the mean log-expression. - robust: Whether to estimate the prior robustly. + trend: Whether the prior variance follows the mean log-expression, as for + :func:`roast`. + robust: Whether to estimate the prior robustly, as for :func:`roast`. mid_p: Whether the adjustment uses mid-p-values, p - 1 / (2 (B + 1)), as limma does by default (limma's ``midp``), which suit false discovery rates; the adjusted p-values are then at least the p-values. @@ -396,24 +431,26 @@ def mroast( ``"down"`` (None where the p-value is NaN); ``p_value``, the two-sided directional p-value; ``adjusted_p_value``, Benjamini-Hochberg over the sets (Benjamini and Hochberg 1995), limma's default (its other ``adjust.method`` - choices are not offered); - ``mixed_p_value``, the p-value of the mixed alternative; and - ``adjusted_mixed_p_value``. A set without genes has NaN proportions and - p-values. + choices are not offered); ``mixed_p_value``, the p-value of the mixed + alternative; and ``mixed_adjusted_p_value``. A set without genes has NaN + proportions and p-values. Raises: TypeError: If an argument has the wrong type, or is or holds a numpy masked array. ValueError: As :func:`roast`, or if ``gene_sets`` is empty or no gene id of - any set is among ``gene_ids``. + any set is among the data's ids. + KeyError: As :func:`roast`. """ use_mid_p = checked_flag(mid_p, "mid_p", expected="a bool") tests = _roast_tests( - y, - design, - contrast, + data, gene_sets, - gene_ids=gene_ids, + group=group, + contrast=contrast, + design=design, + id_column=id_column, + feature_ids=feature_ids, weights=weights, gene_weights=gene_weights, set_statistic=set_statistic, @@ -425,8 +462,8 @@ def mroast( function="mroast", ) down, up, two_sided, mixed = tests.p_values.T - direction: list[str | None] = [ - None if math.isnan(p) else ("up" if p_up < p_down else "down") + direction = [ + _direction(p, p_up, p_down) for p, p_up, p_down in zip( two_sided.tolist(), up.tolist(), down.tolist(), strict=True ) @@ -442,18 +479,26 @@ def mroast( "p_value": two_sided, "adjusted_p_value": _adjusted(two_sided, shift), "mixed_p_value": mixed, - "adjusted_mixed_p_value": _adjusted(mixed, shift), + "mixed_adjusted_p_value": _adjusted(mixed, shift), } ) +def _direction(p: float, p_up: float, p_down: float) -> str | None: + """The direction of a set with the two-sided p-value p: up when its up p-value is + the smaller, down otherwise; None where p is NaN.""" + return None if math.isnan(p) else ("up" if p_up < p_down else "down") + + def romer( - y: Any, - design: Any, - contrast: Any, + data: Any, gene_sets: Mapping[str, Any], *, - gene_ids: npt.ArrayLike | None = None, + group: str | None = None, + contrast: Any = None, + design: Any = None, + id_column: str = "feature_id", + feature_ids: npt.ArrayLike | None = None, weights: npt.ArrayLike | None = None, set_statistic: str = "mean", n_rotations: int = 9999, @@ -470,20 +515,26 @@ def romer( ranked again. Like gene set enrichment analysis it pits the sets against one another, but it uses rotations instead of permuting samples, so it works for any contrast of a linear model with residual degrees of freedom. The module docstring - gives the method. + gives the method. A result of :func:`~cytepool.limma_trend` is tested as + :func:`~cytepool.camera` tests it; romer has no observation weights, so a result + of :func:`~cytepool.limma_voom`, whose voom weights are, is refused, and limma's + romer moderates the variances without a trend. Args: - y: ``(n_samples, n_genes)`` log-expression values, finite. All genes are + data: A :class:`~cytepool.DifferentialExpressionResult` of limma-trend, or + ``(n_samples, n_genes)`` log-expression values, finite. All genes are ranked. - design: The design, as for :func:`roast`. - contrast: The contrast to test, as for :func:`roast`. - gene_sets: Mapping of set name to its genes: gene ids (matched to - ``gene_ids``; ids not among them are left out), 0-based positions, or a + gene_sets: Mapping of set name to its genes: gene ids (matched to the ids of + the data; ids not among them are left out), 0-based positions, or a boolean mask over the genes. A gene listed twice counts once. A - :class:`~cytepool.gene_sets.GeneSets` is such a mapping, of gene ids. - gene_ids: One id per gene, unique, to name genes by id. - weights: ``(n_samples,)`` sample weights, positive and finite (limma's - ``array.weights``). romer has no observation weights. + :class:`~cytepool.GeneSets` is such a mapping, of gene ids. + group: For a result, the group whose fit is tested, as for :func:`roast`. + contrast: The contrast to test, as for :func:`roast`. + design: For an array, the design, as for :func:`roast`. + id_column: For a result, the column of its features whose ids the sets name. + feature_ids: For an array, one id per gene, unique, to name genes by id. + weights: For an array, ``(n_samples,)`` sample weights, positive and finite + (limma's ``array.weights``). romer has no observation weights. set_statistic: ``"mean"`` (limma's default), ``"floormean"`` or ``"mean50"``; see the module docstring. n_rotations: Number of rotations B, at least 1; limma's default is 9999. The @@ -506,8 +557,10 @@ def romer( Raises: TypeError: If an argument has the wrong type, or is or holds a numpy masked array. - ValueError: As :func:`mroast`, or if ``y`` is a ``VoomResult`` or - ``weights`` are observation weights, which romer does not take. + ValueError: As :func:`mroast`, or if the data have observation weights, + which romer does not take: a ``VoomResult``, a result of limma-voom, or + ``weights`` of one value per observation. + KeyError: As :func:`roast`. """ statistic = _statistic(set_statistic, _ROMER_STATISTICS) n_rotations = _rotation_count(n_rotations) @@ -516,41 +569,38 @@ def romer( ) generator = random_generator(rng) with thread_limit(n_threads): - return _romer_sets( - y, - design, - contrast, - gene_sets, - gene_ids, - weights, - statistic, - n_rotations, - shrink, - generator, + read = gene_set_data( + data, + group=group, + contrast=contrast, + design=design, + id_column=id_column, + feature_ids=feature_ids, + weights=weights, + trend=None, + robust=None, + function="romer", ) + return _romer_sets(read, gene_sets, statistic, n_rotations, shrink, generator) def _romer_sets( - y: Any, - design: Any, - contrast: Any, + read: GeneSetData, gene_sets: Any, - gene_ids: Any, - weights: Any, statistic: int, n_rotations: int, shrink: bool, generator: np.random.Generator, ) -> Table: - """:func:`romer` once its options are checked.""" - values, precision = expression_values(y, weights) + """:func:`romer` once its options are checked and its data read.""" + values, precision = read.values, read.precision if precision is not None and precision.ndim != 1: raise ValueError( "romer takes sample weights only, of shape (n_samples,); limma's romer " - "has no observation weights, so a VoomResult or a matrix of weights " - "cannot be used" + "has no observation weights, so a VoomResult, a result of limma_voom or a " + "matrix of weights cannot be used" ) - index = checked_gene_sets(gene_sets, gene_ids, values.shape[1]) + index = checked_gene_sets(gene_sets, read.ids, values.shape[1], read.ids_name) names, members = [], [] matched = 0 for name, genes in gene_sets.items(): @@ -564,11 +614,11 @@ def _romer_sets( names.append(name) members.append(positions) matched += found - check_matched(index, matched) + check_matched(index, matched, read.ids_name) p_values = _romer_p_values( values, - design, - contrast, + read.design, + read.contrast, precision, members, statistic, @@ -603,12 +653,14 @@ class _RoastTests(NamedTuple): def _roast_tests( - y: Any, - design: Any, - contrast: Any, + data: Any, gene_sets: Any, *, - gene_ids: Any, + group: Any, + contrast: Any, + design: Any, + id_column: Any, + feature_ids: Any, weights: Any, gene_weights: Any, set_statistic: Any, @@ -622,50 +674,60 @@ def _roast_tests( """The active proportions and p-values of each set, for roast and mroast.""" statistic = _statistic(set_statistic, _ROAST_STATISTICS) n_rotations = _rotation_count(n_rotations) - with_trend = checked_flag(trend, "trend", expected="a bool") - with_robust = checked_flag(robust, "robust", expected="a bool") + with_trend = ( + None if trend is None else checked_flag(trend, "trend", expected="a bool") + ) + with_robust = ( + None if robust is None else checked_flag(robust, "robust", expected="a bool") + ) generator = random_generator(rng) with thread_limit(n_threads): + read = gene_set_data( + data, + group=group, + contrast=contrast, + design=design, + id_column=id_column, + feature_ids=feature_ids, + weights=weights, + trend=with_trend, + robust=with_robust, + function=function, + ) return _roast_sets( - y, - design, - contrast, + read, gene_sets, - gene_ids, - weights, gene_weights, statistic, n_rotations, - with_trend, - with_robust, generator, function, ) def _roast_sets( - y: Any, - design: Any, - contrast: Any, + read: GeneSetData, gene_sets: Any, - gene_ids: Any, - weights: Any, gene_weights: Any, statistic: int, n_rotations: int, - with_trend: bool, - with_robust: bool, generator: np.random.Generator, function: str, ) -> _RoastTests: - """:func:`_roast_tests` once its options are checked.""" - values, precision = expression_values(y, weights) + """:func:`_roast_tests` once its options are checked and its data read.""" + values, precision = read.values, read.precision + with_trend, with_robust = bool(read.trend), bool(read.robust) n_genes = values.shape[1] contribution = _gene_weights(gene_weights, n_genes) names, members, set_weights = _weighted_sets( - gene_sets, gene_ids, n_genes, contribution, single=function == "roast" + gene_sets, + read.ids, + n_genes, + contribution, + single=function == "roast", + ids_name=read.ids_name, ) - reparametrised, d = contrast_design(values, design, contrast, function) + reparametrised, d = contrast_design(values, read.design, read.contrast, function) effects = gene_effects(values, reparametrised, precision) variances = np.sum(effects[:, 1:] ** 2, axis=1) / d squeezed = squeeze_variances( @@ -675,11 +737,11 @@ def _roast_sets( robust=with_robust, ) prior_variance = np.broadcast_to(squeezed.prior_variance, (n_genes,)) - prior_df = np.broadcast_to(squeezed.prior_df, (n_genes,)) + df_prior = np.broadcast_to(squeezed.df_prior, (n_genes,)) with np.errstate(divide="ignore", invalid="ignore"): t = effects[:, 0] / np.sqrt(squeezed.posterior_variance) t = np.where(np.isnan(t), 0.0, t) # a contrast effect of 0 over a variance of 0 - z = _bailey_z_scores(t, _capped_df(prior_df, d)) + z = _bailey_z_scores(t, _capped_df(df_prior, d)) # Only the genes of some set are rotated; members index this union. union, inverse = np.unique( @@ -693,7 +755,7 @@ def _roast_sets( np.ascontiguousarray(effects[union]), np.sum(effects[union] ** 2, axis=1), np.ascontiguousarray(prior_variance[union], dtype=np.float64), - np.ascontiguousarray(prior_df[union], dtype=np.float64), + np.ascontiguousarray(df_prior[union], dtype=np.float64), float(d), starts, union_members, @@ -897,7 +959,7 @@ def _roast_counts( effects: FloatArray, norms: FloatArray, prior_variance: FloatArray, - prior_df: FloatArray, + df_prior: FloatArray, d: float, starts: npt.NDArray[np.int64], members: npt.NDArray[np.int64], @@ -916,7 +978,7 @@ def _roast_counts( effects, norms, prior_variance, - prior_df, + df_prior, d, starts, members, @@ -937,7 +999,7 @@ def _roast_counts_in_order( effects: FloatArray, norms: FloatArray, prior_variance: FloatArray, - prior_df: FloatArray, + df_prior: FloatArray, d: float, starts: npt.NDArray[np.int64], members: npt.NDArray[np.int64], @@ -952,7 +1014,7 @@ def _roast_counts_in_order( effects, norms, prior_variance, - prior_df, + df_prior, d, starts, members, @@ -972,7 +1034,7 @@ def _roast_chunk( effects: FloatArray, norms: FloatArray, prior_variance: FloatArray, - prior_df: FloatArray, + df_prior: FloatArray, d: float, starts: npt.NDArray[np.int64], members: npt.NDArray[np.int64], @@ -991,7 +1053,7 @@ def _roast_chunk( effect first. norms: Each gene's squared length of its effects. prior_variance: Each gene's prior variance. - prior_df: Each gene's prior df, possibly infinite. + df_prior: Each gene's prior df, possibly infinite. d: Residual degrees of freedom. starts: ``(n_sets + 1,)`` start of each set in ``members``. members: The genes (rows of ``effects``) of each set, one set after another. @@ -1014,8 +1076,8 @@ def _roast_chunk( u = 0.0 for j in range(k): u += rotations[b, j] * effects[g, j] - t = _moderated_t(u, norms[g], prior_variance[g], prior_df[g], d) - z[g] = _bailey_z(t, min(prior_df[g] + d, _DF_LIMIT)) + t = _moderated_t(u, norms[g], prior_variance[g], df_prior[g], d) + z[g] = _bailey_z(t, min(df_prior[g] + d, _DF_LIMIT)) for s in range(n_sets): if starts[s + 1] == starts[s]: continue @@ -1033,15 +1095,15 @@ def _roast_chunk( @jit def _moderated_t( - u: float, norm: float, prior_variance: float, prior_df: float, d: float + u: float, norm: float, prior_variance: float, df_prior: float, d: float ) -> float: """The moderated t of a rotated contrast effect u of a gene whose effects have squared length ``norm``.""" residual = max(norm - u * u, 0.0) / d - if math.isinf(prior_df): + if math.isinf(df_prior): posterior = prior_variance else: - posterior = (prior_df * prior_variance + d * residual) / (prior_df + d) + posterior = (df_prior * prior_variance + d * residual) / (df_prior + d) if posterior > 0.0: return u / math.sqrt(posterior) if u == 0.0: @@ -1084,10 +1146,10 @@ def _bailey_z_scores(t: FloatArray, df: FloatArray) -> FloatArray: return result -def _capped_df(prior_df: FloatArray, d: int) -> FloatArray: +def _capped_df(df_prior: FloatArray, d: int) -> FloatArray: """min(d_0 + d, 10^4): the degrees of freedom of roast's z-scores.""" capped: FloatArray = np.minimum( - np.asarray(prior_df, dtype=np.float64) + d, _DF_LIMIT + np.asarray(df_prior, dtype=np.float64) + d, _DF_LIMIT ) return capped @@ -1122,7 +1184,7 @@ def _romer_p_values( squeezed = squeeze_variances(variances, d) n_genes = values.shape[1] prior_variance = np.broadcast_to(squeezed.prior_variance, (n_genes,)) - prior_df = np.broadcast_to(squeezed.prior_df, (n_genes,)) + df_prior = np.broadcast_to(squeezed.df_prior, (n_genes,)) with np.errstate(divide="ignore", invalid="ignore"): t = effects[:, 0] / np.sqrt(squeezed.posterior_variance) t = np.where(np.isnan(t), 0.0, t) @@ -1130,7 +1192,7 @@ def _romer_p_values( rotated = effects.copy() if shrink: rotated[:, 0] *= _shrinkage( - reparametrised, weights, t, prior_df, d, prior_variance + reparametrised, weights, t, df_prior, d, prior_variance ) sizes = np.array([rows.size for rows in members], dtype=np.int64) starts = np.concatenate([[0], np.cumsum(sizes)]).astype(np.int64) @@ -1139,7 +1201,7 @@ def _romer_p_values( np.ascontiguousarray(rotated), norms, np.ascontiguousarray(prior_variance, dtype=np.float64), - np.ascontiguousarray(prior_df, dtype=np.float64), + np.ascontiguousarray(df_prior, dtype=np.float64), float(d), starts, flat, @@ -1166,7 +1228,7 @@ def _shrinkage( reparametrised: FloatArray, weights: FloatArray | None, t: FloatArray, - prior_df: FloatArray, + df_prior: FloatArray, d: int, prior_variance: FloatArray, ) -> FloatArray: @@ -1176,7 +1238,7 @@ def _shrinkage( the t-statistics on d_0 + d degrees of freedom and the proportion of differentially expressed genes 1 - pi_0. """ - df = np.asarray(prior_df, dtype=np.float64) + d + df = np.asarray(df_prior, dtype=np.float64) + d p_values = 2.0 * np.asarray(t_sf(np.abs(t), df), dtype=np.float64) null_proportion = _proportion_true_null(p_values) if null_proportion >= 1.0: @@ -1354,7 +1416,7 @@ def _romer_counts( effects: FloatArray, norms: FloatArray, prior_variance: FloatArray, - prior_df: FloatArray, + df_prior: FloatArray, d: float, starts: npt.NDArray[np.int64], members: npt.NDArray[np.int64], @@ -1372,7 +1434,7 @@ def _romer_counts( effects, norms, prior_variance, - prior_df, + df_prior, d, starts, members, @@ -1392,7 +1454,7 @@ def _romer_counts_in_order( effects: FloatArray, norms: FloatArray, prior_variance: FloatArray, - prior_df: FloatArray, + df_prior: FloatArray, d: float, starts: npt.NDArray[np.int64], members: npt.NDArray[np.int64], @@ -1406,7 +1468,7 @@ def _romer_counts_in_order( effects, norms, prior_variance, - prior_df, + df_prior, d, starts, members, @@ -1425,7 +1487,7 @@ def _romer_chunk( effects: FloatArray, norms: FloatArray, prior_variance: FloatArray, - prior_df: FloatArray, + df_prior: FloatArray, d: float, starts: npt.NDArray[np.int64], members: npt.NDArray[np.int64], @@ -1454,7 +1516,7 @@ def _romer_chunk( u = 0.0 for j in range(k): u += rotations[b, j] * effects[g, j] - t[g] = _moderated_t(u, norms[g], prior_variance[g], prior_df[g], d) + t[g] = _moderated_t(u, norms[g], prior_variance[g], df_prior[g], d) _alternative_ranks(t, statistic, up, down, mixed) for s in range(n_sets): start = starts[s] @@ -1490,10 +1552,11 @@ def _weighted_sets( gene_weights: FloatArray | None, *, single: bool, + ids_name: str, ) -> tuple[list[str], list[IntArray], list[FloatArray]]: """Each set's name, the sorted positions of its genes and their contribution weights, with genes of weight zero left out.""" - index = checked_gene_sets(gene_sets, gene_ids, n_genes) + index = checked_gene_sets(gene_sets, gene_ids, n_genes, ids_name) names: list[str] = [] members: list[IntArray] = [] weights: list[FloatArray] = [] @@ -1521,10 +1584,11 @@ def _weighted_sets( matched += found if index is not None and matched == 0 and single: raise ValueError( - "no gene of gene_set is among gene_ids; the set and gene_ids must name " - "genes the same way (for example both by symbol, or both by Ensembl id)" + f"no gene of gene_set is among {ids_name}; the set and {ids_name} must " + "name genes the same way (for example both by symbol, or both by Ensembl " + "id)" ) - check_matched(index, matched) + check_matched(index, matched, ids_name) return names, members, weights diff --git a/cytepool/score_tests.py b/cytepool/score_tests.py index 7b0cd9a..954ea39 100644 --- a/cytepool/score_tests.py +++ b/cytepool/score_tests.py @@ -1,25 +1,25 @@ """Tests of per-sample scores with a design: limma on pathway scores and activities. -Pathway scores (:mod:`cytepool.pathway_scores`) and activities of regulators -(:mod:`cytepool.activity`) give every sample of a cube's group one value per gene set -or source, which differ between conditions as expression values do. GSVA's guide -tests its scores with limma, as decoupler's guides test activities: a linear model of -every set's scores on the design (``lmFit``), the contrast (``contrasts.fit``), +Pathway scores (:func:`~cytepool.gsva` and the others) and activities of regulators +(:func:`~cytepool.ulm` and the others) give every sample of a cube's group one value per +gene set or source, which differ between conditions as expression values do. GSVA's +guide tests its scores with limma, as decoupler's guides test activities: a linear model +of every set's scores on the design (``lmFit``), the contrast (``contrasts.fit``), moderated t-tests (``eBayes``, Smyth 2004) and a table of the results (``topTable``). -:func:`limma` takes these steps in one call, with the :class:`~cytepool.design.Design` -built for the cube: its rows are matched to the scores by ``sample_id``, so that the -samples a score leaves out, such as those with too few cells, leave the fit too. +:func:`~cytepool.limma` takes these steps in one call, with the +:class:`~cytepool.Design` built for the cube: its rows are matched to the scores by +``sample_id``, so that the samples a score leaves out, such as those with too few cells, +leave the fit too. The fit: The samples fitted are those of the scores that the design has, in the order of the scores. The design matrix is the design's rows for them, so every column keeps its meaning; when those rows do not determine every column (the samples left out took a donor's every sample, say), the fit keeps the columns that span them, as - R's ``qr()`` keeps them and :func:`~cytepool.differential_expression.limma_voom` - fits a group, and the contrast is tested when it is estimable from these samples - (see "The design of a group" in :mod:`cytepool.differential_expression`). Missing - scores (NaN) leave a set out of the fit of the samples they miss, as limma's - ``lmFit`` does. + R's ``qr()`` keeps them and :func:`~cytepool.limma_voom` fits a group, and the + contrast is tested when it is estimable from these samples (see "The design of a + group" in the description of :func:`~cytepool.limma_voom`). Missing scores (NaN) + leave a set out of the fit of the samples they miss, as limma's ``lmFit`` does. References: Smyth GK (2004). Linear models and empirical Bayes methods for assessing @@ -81,16 +81,15 @@ def limma( fitted. Sort the table to read it, as ``limma(scores, design).sort("p_value")``. Args: - data: :class:`~cytepool.pathway_scores.PathwayScores`, such as - :func:`~cytepool.pathway_scores.gsva` returns; - :class:`~cytepool.activity.Activities` of samples, such as - :func:`~cytepool.activity.ulm` returns for a cube's group; or an array of - values, samples x features, NaN where missing, such as log-expression - values of another platform. - design: A :class:`~cytepool.design.Design` whose sample table has a + data: :class:`~cytepool.PathwayScores`, such as + :func:`~cytepool.gsva` returns; :class:`~cytepool.Activities` of samples, + such as :func:`~cytepool.ulm` returns for a cube's group; or an array of + values, samples x features, NaN where missing, such as log-expression values + of another platform. + design: A :class:`~cytepool.Design` whose sample table has a ``sample_id`` column, such as the design built for the cube, with named contrasts; random effects are refused, as by - :func:`~cytepool.differential_expression.limma_voom`. + :func:`~cytepool.limma_voom`. contrast: The name of one of the design's contrasts; it may be left out when the design has one. feature_ids: For an array, one id per column, as text; ``feature_0``, @@ -179,7 +178,7 @@ def limma( "t": moderated.t[:, 0], "p_value": p_value, "adjusted_p_value": adjust_p_values(p_value, "bh"), - "log_odds": moderated.lods[:, 0], + "log_odds": moderated.log_odds[:, 0], } ) @@ -198,7 +197,7 @@ def _scores( "their samples and scores" ) if isinstance(data, PathwayScores): - rows = data.samples["sample_id"].tolist() + rows = data.profiles["sample_id"].tolist() ids = text_column(list(data.gene_sets)) return np.asarray(data.scores, dtype=np.float64), rows, "gene_set", ids if isinstance(data, Activities): diff --git a/cytepool/stats/__init__.py b/cytepool/stats/__init__.py index 91f31d5..7a10844 100644 --- a/cytepool/stats/__init__.py +++ b/cytepool/stats/__init__.py @@ -1 +1,4 @@ -"""Statistical building blocks: distributions, multiple testing, linear models and moderation.""" +"""Statistical building blocks on arrays: distributions and special functions, multiple +testing, linear and mixed models, empirical Bayes moderation, voom and dream's weights, +sample weights, count models, combined p-values, meta-analysis, SumRank and fractions of +variance. The functions an analysis starts from are at the top level, ``cytepool``.""" diff --git a/cytepool/stats/_mean_variance_trend.py b/cytepool/stats/_mean_variance_trend.py index b1c3dc3..a3cc68c 100644 --- a/cytepool/stats/_mean_variance_trend.py +++ b/cytepool/stats/_mean_variance_trend.py @@ -56,7 +56,7 @@ class Trend: """voom's quantities before prior weights are multiplied in.""" - log_cpm: FloatArray + log2_cpm: FloatArray weights: FloatArray # the trend's: NaN only where a prior weight is zero mean_log_count: FloatArray sqrt_sd: FloatArray @@ -114,8 +114,8 @@ def voom_log_cpm(y: FloatArray, sizes: FloatArray) -> FloatArray: """voom's log2 counts per million, log2((r + 0.5) / (R + 1) * 1e6).""" # The operations of limma, which give its values to the last bit (found by # probing). - log_cpm: FloatArray = np.log2((y + 0.5) / (sizes + 1.0)[:, np.newaxis] * 1e6) - return log_cpm + log2_cpm: FloatArray = np.log2((y + 0.5) / (sizes + 1.0)[:, np.newaxis] * 1e6) + return log2_cpm def voom_mean_log_count( @@ -136,7 +136,7 @@ def voom_mean_log_count( def trend_weights( - log_cpm: FloatArray, + log2_cpm: FloatArray, mean_log_count: FloatArray, sigma: FloatArray, in_trend: npt.NDArray[np.bool_], @@ -153,7 +153,7 @@ def trend_weights( inverse fourth power of the trend at its fitted log2 count, from ``fitted``. Args: - log_cpm: The log2 counts per million. + log2_cpm: The log2 counts per million. mean_log_count: The x of the trend. sigma: The residual standard deviation of each feature. in_trend: The features whose ``sigma`` enter the trend. @@ -168,7 +168,7 @@ def trend_weights( ValueError: If fewer than two features are in the trend, or the trend is not positive where weights are read from it. """ - n_features = log_cpm.shape[1] + n_features = log2_cpm.shape[1] sqrt_sd = np.full(n_features, np.nan) sqrt_sd_trend = np.full(n_features, np.nan) n_in_trend = int(np.count_nonzero(in_trend)) @@ -207,7 +207,7 @@ def trend_weights( "exactly, can cause this: filter out features with low counts, or use " "more features" ) - return Trend(log_cpm, weights, mean_log_count, sqrt_sd, sqrt_sd_trend, fraction) + return Trend(log2_cpm, weights, mean_log_count, sqrt_sd, sqrt_sd_trend, fraction) def scaled_prior_weights( diff --git a/cytepool/stats/_moderation.py b/cytepool/stats/_moderation.py index fe88fe6..eade09b 100644 --- a/cytepool/stats/_moderation.py +++ b/cytepool/stats/_moderation.py @@ -37,7 +37,7 @@ def posterior_variances( x: FloatArray, d: float | FloatArray, prior_variance: float | FloatArray, - prior_df: float | FloatArray, + df_prior: float | FloatArray, ) -> FloatArray: """Posterior variances (d0 s0^2 + d s^2) / (d0 + d), as in Smyth (2004). @@ -45,7 +45,7 @@ def posterior_variances( df d is zero. """ s0 = np.broadcast_to(prior_variance, x.shape) - d0 = np.broadcast_to(prior_df, x.shape) + d0 = np.broadcast_to(df_prior, x.shape) with np.errstate(invalid="ignore", divide="ignore"): weighted = np.where(d > 0.0, d * x, 0.0) posterior = (weighted + np.where(np.isinf(d0), 0.0, d0 * s0)) / (d + d0) diff --git a/cytepool/stats/combined_p_values.py b/cytepool/stats/combined_p_values.py index b8145b8..8117738 100644 --- a/cytepool/stats/combined_p_values.py +++ b/cytepool/stats/combined_p_values.py @@ -53,7 +53,7 @@ into one-sided ones, as metap's ``two2one``: p / 2 for an effect above zero and 1 - p / 2 for one below, and 1/2 for an effect of exactly zero, for the effects above zero; their complements, for the effects below. Each direction is - combined, giving ``p_value_up`` and ``p_value_down``, and the p-value is + combined, giving ``up_p_value`` and ``down_p_value``, and the p-value is min(1, 2 min(p_up, p_down)), the Bonferroni bound over the two directions (Owen 2009): for Stouffer's method it is the two-sided p-value of Z, 2 (1 - Phi(|Z|)), and effects in opposite directions cancel. The one-sided p-value near 0 is kept @@ -190,11 +190,12 @@ class CombinedPValues: method's t, or the Simes p-value; of the n - u + 1 largest p-values for a partial conjunction; with ``directions``, that of the effects above zero, so that Stouffer's Z is positive for effects mostly up. - p_value_up: With ``directions``, the combined one-sided p-value for effects + up_p_value: With ``directions``, the combined one-sided p-value for effects above zero; None without. - p_value_down: Likewise, for effects below zero. - direction: With ``directions``, 1 where ``p_value_up`` is the smaller, -1 - where ``p_value_down`` is, and 0 where they are equal; None without. + down_p_value: Likewise, for effects below zero. + sign: With ``directions``, the sign of the combined effect: 1 where + ``up_p_value`` is the smaller, -1 where ``down_p_value`` is, and 0 where + they are equal; None without. n_studies: The number of studies that tested each feature (whose p-value is not NaN). method: The method, such as ``"fisher"``. @@ -205,9 +206,9 @@ class CombinedPValues: p_value: FloatArray statistic: FloatArray - p_value_up: FloatArray | None - p_value_down: FloatArray | None - direction: FloatArray | None + up_p_value: FloatArray | None + down_p_value: FloatArray | None + sign: FloatArray | None n_studies: IntArray method: str at_least: int @@ -216,7 +217,7 @@ class CombinedPValues: def __repr__(self) -> str: studies = self.n_studies[self.n_studies > 0] span = f"{studies.min()} to {studies.max()}" if studies.size else "none" - signed = ", with directions" if self.direction is not None else "" + signed = ", with directions" if self.sign is not None else "" return ( f"CombinedPValues(n_features={self.p_value.size}, method={self.method!r}, " f"at_least={self.at_least}{signed}, studies per feature: {span})" @@ -285,7 +286,7 @@ def combine_p_values( >>> signed = combine_p_values( ... two_sided, method="stouffer", directions=[[1.0, 1.0], [1.0, -1.0]] ... ) - >>> signed.direction.tolist(), signed.p_value.round(4).tolist() + >>> signed.sign.tolist(), signed.p_value.round(4).tolist() ([1.0, 1.0], [0.0011, 0.712]) """ chosen = checked_choice(method, "method", _METHODS) @@ -486,9 +487,9 @@ def shaped(values: FloatArray) -> FloatArray: return CombinedPValues( p_value=shaped(p_value), statistic=shaped(statistic), - p_value_up=None if up is None else shaped(up), - p_value_down=None if down is None else shaped(down), - direction=None if direction is None else shaped(direction), + up_p_value=None if up is None else shaped(up), + down_p_value=None if down is None else shaped(down), + sign=None if direction is None else shaped(direction), n_studies=read_only(tested.reshape(shape)), method=method, at_least=at_least, diff --git a/cytepool/stats/deseq2.py b/cytepool/stats/deseq2.py index 31116c5..bec0399 100644 --- a/cytepool/stats/deseq2.py +++ b/cytepool/stats/deseq2.py @@ -541,10 +541,10 @@ def table(self, *, feature_ids: Any = None) -> Table: """Return the test as a table, one row per gene in the order of the counts. DESeq2's ``results()``, with the columns of the tables of - :func:`~cytepool.differential_expression.limma_voom`: ``feature_id``; - ``base_mean`` (``baseMean``); ``log2_fold_change`` (``log2FoldChange``); - ``standard_error`` (``lfcSE``); ``statistic`` (``stat``); ``p_value`` - (``pvalue``); and ``adjusted_p_value`` (``padj``). + :func:`~cytepool.limma_voom`: ``feature_id``; ``base_mean`` (``baseMean``); + ``log2_fold_change`` (``log2FoldChange``); ``standard_error`` (``lfcSE``); + ``statistic`` (``stat``); ``p_value`` (``pvalue``); and ``adjusted_p_value`` + (``padj``). Args: feature_ids: One id per gene, as text, such as the gene ids of the diff --git a/cytepool/stats/dream.py b/cytepool/stats/dream.py index f067949..dbb704f 100644 --- a/cytepool/stats/dream.py +++ b/cytepool/stats/dream.py @@ -24,7 +24,7 @@ feature, that of an observation of its average weight, is shrunk towards a prior by limma's empirical Bayes (Smyth 2004) on the approximate residual degrees of freedom of the mixed model, tr((I - H)' (I - H)) for its hat matrix H - (``MixedModelFit.residual_df``). The moderated t-statistic divides an estimate by + (``MixedModelFit.df_residual``). The moderated t-statistic divides an estimate by its standard error with the posterior variance in place of the residual variance, and has the degrees of freedom of the test plus the prior's. Several contrasts are tested at once by the moderated F-statistic t' R^-1 t / r, limma's, @@ -111,7 +111,7 @@ about 2% of 1e6. Random slopes are not implemented yet; variancePartition's variance partitioning is -:mod:`cytepool.variance_partitioning`. +:func:`cytepool.variance_partitioning`. References: Hoffman GE, Roussos P (2021). Dream: powerful differential expression analysis for @@ -215,11 +215,11 @@ class ModeratedFixedEffectTests: moderated: the residual variance of an observation of each feature's average weight, ``fit.residual_variance / fit.mean_weights`` (dream's ``sigma^2``). - residual_df: ``(n_features,)`` their degrees of freedom, the fit's + df_residual: ``(n_features,)`` their degrees of freedom, the fit's approximate residual degrees of freedom (dream's ``rdf``). prior_variance: The prior variance (``s2.prior``): a float, or one value per feature with a trend. - prior_df: The prior degrees of freedom (``df.prior``): a float, possibly + df_prior: The prior degrees of freedom (``df.prior``): a float, possibly infinite, or one value per feature from a robust fit that finds outliers. posterior_variance: ``(n_features,)`` posterior variances (``s2.post``). @@ -229,13 +229,15 @@ class ModeratedFixedEffectTests: df_total: ``(n_features, n_contrasts)`` their degrees of freedom: Satterthwaite's plus the prior's (``df.total``). p_value: Two-sided p-values of ``t`` on ``df_total`` degrees of freedom. - lods: Log-odds that each contrast is non-zero (the B-statistic), as limma's - ``eBayes`` gives them for these variances on ``residual_df``. + log_odds: Log-odds that each contrast is non-zero (limma's B-statistic, + ``lods``), as limma's ``eBayes`` gives them for these variances on + ``df_residual``. z: Signed z-statistics with the same p-values, sign(t) times the standard normal quantile of half the p-value (``topTable``'s ``z.std``). coefficient_prior_variance: ``(n_contrasts,)`` unscaled prior variance of a non-zero contrast (``var.prior``). - proportion: The assumed proportion of non-zero contrasts behind ``lods``. + proportion: The assumed proportion of non-zero contrasts behind + ``log_odds``. prior_estimator: The estimator of the prior: ``"moments"`` or ``"likelihood"``. f_statistic: ``(n_features,)`` moderated F-statistics testing every @@ -255,14 +257,14 @@ class ModeratedFixedEffectTests: tests: FixedEffectTests residual_variance_at_mean_weight: FloatArray - residual_df: FloatArray + df_residual: FloatArray prior_variance: float | FloatArray - prior_df: float | FloatArray + df_prior: float | FloatArray posterior_variance: FloatArray t: FloatArray df_total: FloatArray p_value: FloatArray - lods: FloatArray + log_odds: FloatArray z: FloatArray coefficient_prior_variance: FloatArray proportion: float @@ -298,7 +300,7 @@ def voom_with_dream_weights( :func:`~cytepool.stats.voom.voom`), with the linear model replaced by the mixed model of ``design``, fitted to every feature by maximum likelihood, and the trend read at the fitted values with the predicted random effects; the module - docstring gives the conventions. Continue with ``fit_mixed_model(result.log_cpm, + docstring gives the conventions. Continue with ``fit_mixed_model(result.log2_cpm, design, weights=result.weights)``, ``fixed_effect_tests`` and :func:`moderated_fixed_effect_tests`. @@ -456,7 +458,7 @@ def _moderated_tests( ) # dream's sigma^2: the residual variance of an observation of average weight. variance = fit.residual_variance / fit.mean_weights - fitted = np.isfinite(variance) & np.isfinite(fit.residual_df) + fitted = np.isfinite(variance) & np.isfinite(fit.df_residual) if not fitted.any(): raise ValueError( "no feature has a mixed model fit, so the prior cannot be estimated; " @@ -475,7 +477,7 @@ def _moderated_tests( stdev_unscaled=stdev_unscaled, sigma=sigma, # A feature without a fit has no information about the prior. - df_residual=np.where(fitted, fit.residual_df, 0.0), + df_residual=np.where(fitted, fit.df_residual, 0.0), cov_coefficients=matrix.T @ np.linalg.solve(cross, matrix), mean_expression=fit.mean_expression, coefficient_names=tests.contrast_names, @@ -492,11 +494,11 @@ def _moderated_tests( winsor_tail_p=winsor_tail_p, prior_estimator=prior_estimator, ) - prior_df = np.broadcast_to(moderated.prior_df, variance.shape) - df_total = tests.df + prior_df[:, np.newaxis] + df_prior = np.broadcast_to(moderated.df_prior, variance.shape) + df_total = tests.df + df_prior[:, np.newaxis] t = moderated.t p_value = 2.0 * np.asarray(t_sf(np.abs(t), df_total), dtype=np.float64) - f_value, f_df1, f_df2 = _moderated_f(t, tests, parts, prior_df) + f_value, f_df1, f_df2 = _moderated_f(t, tests, parts, df_prior) with np.errstate(invalid="ignore"): f_p_value = np.asarray(f_sf(f_value, float(f_df1), f_df2), dtype=np.float64) standardized = np.asarray( @@ -505,14 +507,14 @@ def _moderated_tests( return ModeratedFixedEffectTests( tests=tests, residual_variance_at_mean_weight=_frozen(variance), - residual_df=_frozen(fit.residual_df), + df_residual=_frozen(fit.df_residual), prior_variance=moderated.prior_variance, - prior_df=moderated.prior_df, + df_prior=moderated.df_prior, posterior_variance=moderated.posterior_variance, t=t, df_total=_frozen(df_total), p_value=_frozen(p_value), - lods=moderated.lods, + log_odds=moderated.log_odds, z=_frozen(_signed_z(t, df_total, p_value)), coefficient_prior_variance=moderated.coefficient_prior_variance, proportion=moderated.proportion, @@ -526,7 +528,7 @@ def _moderated_tests( def _moderated_f( - t: FloatArray, tests: FixedEffectTests, parts: InferenceParts, prior_df: FloatArray + t: FloatArray, tests: FixedEffectTests, parts: InferenceParts, df_prior: FloatArray ) -> tuple[FloatArray, int, FloatArray]: """dream's moderated F-statistic of every contrast, and its degrees of freedom. @@ -550,7 +552,7 @@ def _moderated_f( with np.errstate(divide="ignore", invalid="ignore"): scores = np.einsum("gc,gcm->gm", t, vectors) / np.sqrt(values) f_value: FloatArray = np.sum(scores**2, axis=1) / rank - f_df2 = combined_df(df + prior_df[:, np.newaxis]) + f_df2 = combined_df(df + df_prior[:, np.newaxis]) return f_value, rank, f_df2 @@ -574,16 +576,16 @@ def _dream_weights( prior: Prior weights, each feature's scaled to mean one, or None. ml: Whether to fit by maximum likelihood. """ - log_cpm = voom_log_cpm(y, sizes) - fit = _fit(log_cpm, prior, ml, parts) + log2_cpm = voom_log_cpm(y, sizes) + fit = _fit(log2_cpm, prior, ml, parts) mean_log_count = voom_mean_log_count(fit.mean_expression, sizes, y) sigma = np.sqrt(fit.residual_variance) in_trend = (y > 0.0).any(axis=0) & np.isfinite(sigma) # A feature that the effects reproduce has no fit; its fitted values are its # values, the limit of the fits as the residual variance goes to zero. - fitted = np.where(np.isnan(fit.fitted_values), log_cpm, fit.fitted_values) + fitted = np.where(np.isnan(fit.fitted_values), log2_cpm, fit.fitted_values) trend = trend_weights( - log_cpm, + log2_cpm, mean_log_count, sigma, in_trend, @@ -597,7 +599,7 @@ def _dream_weights( if prior is not None: weights = weights * (prior[:, np.newaxis] if prior.ndim == 1 else prior) return VoomResult( - log_cpm=log_cpm, + log2_cpm=log2_cpm, weights=weights, effective_library_sizes=sizes, mean_log_count=mean_log_count, diff --git a/cytepool/stats/empirical_bayes.py b/cytepool/stats/empirical_bayes.py index deb588f..ed92e53 100644 --- a/cytepool/stats/empirical_bayes.py +++ b/cytepool/stats/empirical_bayes.py @@ -276,14 +276,14 @@ class SqueezedVariances(NamedTuple): inverses of the posterior means of their reciprocals. prior_variance: The prior variance: one value per feature when it follows a covariate or with at most two variances, a float otherwise. - prior_df: The prior degrees of freedom: a float, or one value per feature + df_prior: The prior degrees of freedom: a float, or one value per feature from a robust fit (from the likelihood estimator only when it finds outliers, as in limma). """ posterior_variance: FloatArray prior_variance: float | FloatArray - prior_df: float | FloatArray + df_prior: float | FloatArray @dataclass(frozen=True, eq=False, repr=False) @@ -297,27 +297,28 @@ class ModeratedFit: fit: The linear model fit. prior_variance: The prior variance s0^2 (limma's ``s2.prior``): a float, or one value per feature with a trend or with at most two features. - prior_df: The prior degrees of freedom d0 (``df.prior``): a float, possibly + df_prior: The prior degrees of freedom d0 (``df.prior``): a float, possibly infinite, or one value per feature from a robust fit (from the likelihood estimator only when it finds outliers, as in limma). posterior_variance: ``(n_features,)`` posterior variances (``s2.post``). df_total: ``(n_features,)`` degrees of freedom of the moderated t-statistics, - ``df_residual + prior_df`` capped at the residual degrees of freedom + ``df_residual + df_prior`` capped at the residual degrees of freedom summed over all features (``df.total``). t: ``(n_features, n_coefficients)`` moderated t-statistics. p_value: Two-sided p-values of ``t`` on ``df_total`` degrees of freedom. - lods: Log-odds that each coefficient is non-zero (the B-statistic), on the - natural log scale. + log_odds: Log-odds that each coefficient is non-zero (limma's B-statistic, + ``lods``), on the natural log scale. coefficient_prior_variance: ``(n_coefficients,)`` unscaled prior variance v0 of a non-zero coefficient, which is N(0, v0 sigma_g^2) (``var.prior``). f_statistic: ``(n_features,)`` moderated F-statistics testing all coefficients equal to zero. f_p_value: P-values of ``f_statistic`` on ``f_df1`` and - ``df_residual + prior_df`` degrees of freedom; limma does not cap the + ``df_residual + df_prior`` degrees of freedom; limma does not cap the denominator degrees of freedom here (found by probing). f_df1: Numerator degrees of freedom of the F-tests: the rank of the correlation matrix of the coefficients. - proportion: The assumed proportion of non-zero coefficients behind ``lods``. + proportion: The assumed proportion of non-zero coefficients behind + ``log_odds``. prior_estimator: The estimator of the prior: ``"moments"`` or ``"likelihood"`` (see :func:`squeeze_variances`); with at most two features neither is used. @@ -325,12 +326,12 @@ class ModeratedFit: fit: LinearModelFit prior_variance: float | FloatArray - prior_df: float | FloatArray + df_prior: float | FloatArray posterior_variance: FloatArray df_total: FloatArray t: FloatArray p_value: FloatArray - lods: FloatArray + log_odds: FloatArray coefficient_prior_variance: FloatArray f_statistic: FloatArray f_p_value: FloatArray @@ -344,15 +345,15 @@ def table( """Return the tests of one coefficient or contrast as a table. limma's ``topTable(sort.by = "none")``, with the columns of the tables of - :func:`~cytepool.differential_expression.limma_voom`, one row per feature - in the order of the fit: ``feature_id``; ``log2_fold_change``, the - coefficient (``logFC``); ``standard_error``, its moderated standard error, - ``stdev_unscaled`` times the posterior standard deviation; - ``average_log2_cpm``, the mean of the values fitted (``AveExpr``); ``t`` and - ``p_value``; ``adjusted_p_value``, Benjamini-Hochberg over the features - (``adj.P.Val``); and ``log_odds``, the B-statistic (``B``). The names of the - effect and of the mean are those of log2 counts per million, such as voom's, - as limma names them ``logFC`` and ``AveExpr`` whatever the values. + :func:`~cytepool.limma_voom`, one row per feature in the order of the fit: + ``feature_id``; ``log2_fold_change``, the coefficient (``logFC``); + ``standard_error``, its moderated standard error, ``stdev_unscaled`` times the + posterior standard deviation; ``average_log2_cpm``, the mean of the values + fitted (``AveExpr``); ``t`` and ``p_value``; ``adjusted_p_value``, + Benjamini-Hochberg over the features (``adj.P.Val``); and ``log_odds``, the + B-statistic (``B``). The names of the effect and of the mean are those of log2 + counts per million, such as voom's, as limma names them ``logFC`` and + ``AveExpr`` whatever the values. Args: coefficient: The coefficient or contrast, by name or position; it may be @@ -380,7 +381,7 @@ def table( "t": self.t[:, j], "p_value": p_value, "adjusted_p_value": adjust_p_values(p_value, "bh"), - "log_odds": self.lods[:, j], + "log_odds": self.log_odds[:, j], } ) @@ -388,7 +389,7 @@ def __repr__(self) -> str: return ( f"ModeratedFit(n_features={self.t.shape[0]}, " f"coefficient_names={self.fit.coefficient_names!r}, " - f"prior_df={value_range(self.prior_df)}, " + f"df_prior={value_range(self.df_prior)}, " f"prior_variance={value_range(self.prior_variance)}, " f"prior_estimator={self.prior_estimator!r})" ) @@ -403,7 +404,7 @@ class TreatFit: lfc: The threshold tau of the null hypothesis |beta| <= tau, on the scale of the coefficients (log2 fold changes for log2 expression). prior_variance: The prior variance, as in :class:`ModeratedFit`. - prior_df: The prior degrees of freedom, as in :class:`ModeratedFit`. + df_prior: The prior degrees of freedom, as in :class:`ModeratedFit`. posterior_variance: ``(n_features,)`` posterior variances. df_total: ``(n_features,)`` degrees of freedom of the tests. t: ``(n_features, n_coefficients)`` moderated t-statistics shrunk towards @@ -416,7 +417,7 @@ class TreatFit: fit: LinearModelFit lfc: float prior_variance: float | FloatArray - prior_df: float | FloatArray + df_prior: float | FloatArray posterior_variance: FloatArray df_total: FloatArray t: FloatArray @@ -828,8 +829,8 @@ def moderated_statistics( fit.stdev_unscaled * np.sqrt(squeezed.posterior_variance)[:, np.newaxis] ) p_value = 2.0 * _as_array(t_sf(np.abs(t), df_total[:, np.newaxis])) - prior_df = np.broadcast_to(squeezed.prior_df, df_total.shape) - infinite = np.isinf(prior_df) + df_prior = np.broadcast_to(squeezed.df_prior, df_total.shape) + infinite = np.isinf(df_prior) prior_variance = np.broadcast_to(squeezed.prior_variance, df_total.shape) median_prior = float(np.nanmedian(prior_variance)) v0 = np.array( @@ -850,17 +851,17 @@ def moderated_statistics( v0[failed] = np.reciprocal(prior_variance[0]) lods = log_odds(t, fit.stdev_unscaled, df_total, infinite, v0, p) f_value, f_df1 = _f_statistics(t, fit, exact) - f_df2 = prior_df + fit.df_residual + f_df2 = df_prior + fit.df_residual f_p_value = _as_array(f_sf(f_value, float(f_df1), f_df2)) return ModeratedFit( fit=fit, prior_variance=_frozen(squeezed.prior_variance), - prior_df=_frozen(squeezed.prior_df), + df_prior=_frozen(squeezed.df_prior), posterior_variance=_frozen(squeezed.posterior_variance), df_total=_frozen(df_total), t=_frozen(t), p_value=_frozen(p_value), - lods=_frozen(lods), + log_odds=_frozen(lods), coefficient_prior_variance=_frozen(v0), f_statistic=_frozen(f_value), f_p_value=_frozen(f_p_value), @@ -940,7 +941,7 @@ def treat( fit=fit, lfc=threshold, prior_variance=_frozen(squeezed.prior_variance), - prior_df=_frozen(squeezed.prior_df), + df_prior=_frozen(squeezed.df_prior), posterior_variance=_frozen(squeezed.posterior_variance), df_total=_frozen(df_total), t=_frozen(shrunk), @@ -984,7 +985,7 @@ def _fit_moments( df2 = math.inf scale = float(np.mean(values)) return FDistributionFit(scale, df2) - fitted, residual_df = _spline_trend(covariate, usable, e) + fitted, df_residual = _spline_trend(covariate, usable, e) if fitted is None: # A trend of one degree of freedom is a constant: limma then fits no trend, # which matters for an infinite df2, where the scale is the mean variance @@ -992,7 +993,7 @@ def _fit_moments( constant = _fit_moments(x, d, None, usable) return FDistributionFit(np.full(x.size, constant.scale), constant.df2) residuals = e - fitted[usable] - e_variance = float(np.sum(residuals**2)) / residual_df - mean_trigamma + e_variance = float(np.sum(residuals**2)) / df_residual - mean_trigamma if e_variance > 0.0: df2 = 2.0 * float(trigamma_inverse(e_variance)) shift = float(_log_minus_digamma(np.array([df2 / 2.0]))[0]) @@ -1658,7 +1659,7 @@ def _squeeze( if x.size <= 2: return SqueezedVariances(x.copy(), x.copy(), 0.0) prior_variance: float | FloatArray - prior_df: float | FloatArray + df_prior: float | FloatArray if estimator == "likelihood": # limma's likelihood estimator does not stop on mostly zero variances. _check_zero_variances(x, usable, robust=False) @@ -1668,17 +1669,17 @@ def _squeeze( fitted = _fit_likelihood(missing, d, covariate, np.ones(x.size), robust) prior_variance = fitted.scale # A robust fit gives each feature its prior df once it finds outliers. - prior_df = fitted.df2 if math.isnan(fitted.df2_outlier) else fitted.df2_shrunk + df_prior = fitted.df2 if math.isnan(fitted.df2_outlier) else fitted.df2_shrunk elif robust: _check_zero_variances(x, usable, robust) robust_fit = _fit_robust(x, d, covariate, usable, p_lower, p_upper) - prior_variance, prior_df = robust_fit.scale, robust_fit.df2_shrunk + prior_variance, df_prior = robust_fit.scale, robust_fit.df2_shrunk else: _check_zero_variances(x, usable, robust) moments = _fit_moments(x, d, covariate, usable) - prior_variance, prior_df = moments.scale, moments.df2 + prior_variance, df_prior = moments.scale, moments.df2 return SqueezedVariances( - posterior_variances(x, d, prior_variance, prior_df), prior_variance, prior_df + posterior_variances(x, d, prior_variance, df_prior), prior_variance, df_prior ) @@ -1708,8 +1709,8 @@ def _moderate( squeezed = _squeeze( variances, df, covariate, usable, robust, lower, upper, estimator ) - prior_df = np.broadcast_to(squeezed.prior_df, df.shape) - df_total = np.minimum(df + prior_df, float(df.sum())) + df_prior = np.broadcast_to(squeezed.df_prior, df.shape) + df_total = np.minimum(df + df_prior, float(df.sum())) return squeezed, df_total, estimator diff --git a/cytepool/stats/meta_analysis.py b/cytepool/stats/meta_analysis.py new file mode 100644 index 0000000..e38d443 --- /dev/null +++ b/cytepool/stats/meta_analysis.py @@ -0,0 +1,791 @@ +"""Meta-analysis of the effects of many features across studies, as metafor computes it. + +:func:`fit_meta_analysis` combines, for every feature (gene), the estimates of one +effect in several studies, each with its standard error, into an estimate of a common +effect, with the between-study variance and the heterogeneity statistics, as metafor's +``rma.uni`` does for one feature without moderators (Viechtbauer 2010). +:func:`cytepool.meta_analysis` does it for the differential-expression results of +several studies, such as the same cell types in several cohorts, tissues or +platforms, matched by group, contrast and feature id. + +The model: + Study i estimates the effect of a feature by y_i, with standard error s_i, and its + sampling variance v_i = s_i^2 is taken as known. In the random-effects model the + true effects of the studies vary around a mean mu with the between-study variance + tau^2: y_i = mu + u_i + e_i, with u_i ~ N(0, tau^2) and e_i ~ N(0, v_i) + (DerSimonian and Laird 1986). With the weights w_i = 1 / (v_i + tau^2), mu is + estimated by mu_hat = sum w_i y_i / sum w_i, with standard error + 1 / sqrt(sum w_i). The fixed-effect model, which assumes one true effect (metafor's + equal-effects model, ``method = "EE"``), is the case tau^2 = 0: the + inverse-variance weighted mean. + +The between-study variance (``method``), over the k studies that measured a feature: + - ``"reml"``, metafor's default: the restricted maximum likelihood estimate + (Viechtbauer 2005), the tau^2 >= 0 that maximizes + -1/2 sum log(v_i + tau^2) - 1/2 log(sum w_i) - 1/2 sum w_i (y_i - mu_hat)^2. + This function can have more than one maximum, as when an imprecise study lies + far from precise ones that agree. Its maxima are zero, where the score is not + positive there, and the roots where the score, sum w_i^2 (y_i - mu_hat)^2 - + tr(P), with P = W - w w' / sum w_i for the diagonal matrix W of the weights, + falls from positive to negative. The score is negative beyond + sum (y_i - y_bar)^2 / (k - 1) + 2 max(v_i). The roots are bracketed by the + signs of the score at 8 points per decade of tau^2, from 1e-3 min(v_i) to twice + that bound, so that rounding cannot hide one near the bound; each is found by + Newton's method with the observed information, kept in its bracket by + bisection, and the estimate is the maximum with the largest restricted + likelihood. + - ``"dl"``: the DerSimonian and Laird (1986) estimate, the method of moments + with the weights 1 / v_i: max(0, (Q - (k - 1)) / (sum w_i - sum w_i^2 / + sum w_i)), with Q below. + - ``"pm"``: the Paule and Mandel (1982) estimate, at which the generalized Q + statistic sum w_i (y_i - mu_hat)^2 equals its expectation k - 1, or 0 where Q + is at most k - 1. The statistic falls with tau^2 and is convex, and the root + lies between sum (y_i - y_bar)^2 / (k - 1) minus the largest and minus the + smallest v_i, so Newton's method from the lower end converges to it from below. + - ``"fixed"``: tau^2 = 0. + +Tests and confidence intervals (``test``): + - ``"z"``: z = mu_hat / SE against the standard normal distribution, and the + interval mu_hat +/- z_(1 - alpha / 2) SE, as metafor's default ``test = "z"``. + - ``"knapp_hartung"``: the standard error is multiplied by sqrt(q), with + q = sum w_i (y_i - mu_hat)^2 / (k - 1), and the statistic is referred to a t + distribution with k - 1 degrees of freedom (Hartung 1999; Sidik and Jonkman + 2002; Knapp and Hartung 2003; metafor's ``test = "knha"``), which metafor's + documentation recommends for few studies. Where q < 1, as when the studies + agree more closely than their standard errors imply, the adjusted standard + error is smaller than the unadjusted one; with the fixed-effect model it is + the standard error scaled by sqrt(Q / (k - 1)), which metafor computes with a + warning that the method is not meant for that model. + +Heterogeneity: + Cochran's (1954) Q = sum (y_i - mu_fixed)^2 / v_i, with the fixed-effect estimate + mu_fixed, and its p-value from the chi-squared distribution with k - 1 degrees of + freedom. I^2 and H^2 are those metafor reports: I^2 = tau^2 / (tau^2 + s^2) and + H^2 = (tau^2 + s^2) / s^2, where s^2 = (k - 1) sum w_i / ((sum w_i)^2 - + sum w_i^2), with w_i = 1 / v_i, is the typical within-study variance (Higgins and + Thompson 2002, equation 9), so that they follow the estimate of tau^2. For the + fixed-effect model they are I^2 = max(0, (Q - (k - 1)) / Q) and H^2 = Q / (k - 1), + the conventional definitions, which equal the others for DerSimonian-Laird where + Q > k - 1; where Q <= k - 1, I^2 is 0 for both, and H^2 is Q / (k - 1) for the + fixed effect and 1 for DerSimonian-Laird. I^2 is a proportion here; metafor + reports it in percent. + +Studies that did not measure a feature: + NaN marks them, in the effect or the standard error. Each feature is analysed + over the k studies that measured it, as metafor leaves out studies with missing + values. A feature measured in one study has that study's estimate and standard + error and its z-test, also with ``"knapp_hartung"``, which needs two studies; + tau^2 = 0, Q = 0 with a p-value of 1, I^2 = 0 and H^2 = 1: metafor's results for + one study (found by probing). A feature measured in no study has NaN. + +Accuracy: + Every estimate is computed for the deviations of the effects from their mean and + the standard errors divided, exactly, by a power of two near the largest of them, + so that the results do not depend on the scale of the data, and no square + overflows or underflows however much larger the spread of the effects is than + their standard errors. The sums sum w_i - sum w_i^2 / sum w_i and tr(P^2), which + cancel when one study is far more precise than the others, are formed from the + sum of the weights of the other studies for each study, which does not cancel. + +Deliberate differences from metafor (``validation/meta_analysis.py`` reports them): + - The REML estimate is the largest maximum of the restricted likelihood. metafor + finds a maximum by Fisher scoring from the Hedges estimate, + max(0, sum (y_i - y_bar)^2 / (k - 1) - mean(v_i)), and replaces it by zero, with + a warning that Fisher scoring "may have gotten stuck at a local maximum", where + the restricted likelihood is larger at zero (found by probing). Where the + likelihood has another, larger maximum, metafor's estimate is the smaller one or + zero. Fisher scoring also oscillates without converging where the observed + information exceeds twice the expected one: metafor then stops with an error, + or, at its default threshold, within the oscillation, and its help page's remedy + ``control = list(stepadj = 0.5)`` converges to the estimate here. + - The iterative estimates converge to rounding. metafor stops REML when tau^2 + changes by less than 1e-5 from one step to the next, and Paule-Mandel at the + tolerance of R's ``uniroot``, about 1.2e-4 on tau^2; ``control = list(threshold + = 1e-13)`` and ``control = list(tol = 1e-14)`` make it converge to the values + here. + - Where the restricted likelihood is largest at zero although the Hedges estimate + is positive, the REML estimate is zero; metafor's step halving approaches zero + and stops near its threshold, at about 1e-6 by default (found by probing). + - metafor's Paule-Mandel search stops with an error when the root lies above + max(100, 10 mad(y)^2); here it has no upper limit. + - Standard errors must be positive and finite where they are given. For a zero + standard error metafor stops (fixed effect, DerSimonian-Laird, Paule-Mandel) or + fits without Q, I^2 and H^2 (REML); for an infinite one it stops. + +References: + 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 + + Cochran WG (1954). The combination of estimates from different experiments. + Biometrics 10, 101-129. doi:10.2307/3001666 + + DerSimonian R, Laird N (1986). Meta-analysis in clinical trials. Controlled Clinical + Trials 7, 177-188. doi:10.1016/0197-2456(86)90046-2 + + Hartung J (1999). An alternative method for meta-analysis. Biometrical Journal 41, + 901-916. doi:10.1002/(SICI)1521-4036(199912)41:8<901::AID-BIMJ901>3.0.CO;2-W + + Higgins JPT, Thompson SG (2002). Quantifying heterogeneity in a meta-analysis. + Statistics in Medicine 21, 1539-1558. doi:10.1002/sim.1186 + + Knapp G, Hartung J (2003). Improved tests for a random effects meta-regression with + a single covariate. Statistics in Medicine 22, 2693-2710. doi:10.1002/sim.1482 + + Paule RC, Mandel J (1982). Consensus values and weighting factors. Journal of + Research of the National Bureau of Standards 87, 377-385. doi:10.6028/jres.087.022 + + Sidik K, Jonkman JN (2002). A simple confidence interval for meta-analysis. + Statistics in Medicine 21, 3153-3159. doi:10.1002/sim.1262 + + Viechtbauer W (2005). Bias and efficiency of meta-analytic variance estimators in + the random-effects model. Journal of Educational and Behavioral Statistics 30, + 261-293. doi:10.3102/10769986030003261 + + Viechtbauer W (2010). Conducting meta-analyses in R with the metafor package. + Journal of Statistical Software 36(3), 1-48. doi:10.18637/jss.v036.i03 +""" + +import math +from dataclasses import dataclass +from typing import Any, Literal + +import numpy as np +import numpy.typing as npt + +from .._jit import jit +from .._masked_arrays import read_only, reject_masked, unmasked_array +from .._options import checked_choice, real_number +from .distributions import ( + chi2_sf_scalar, + norm_isf, + norm_sf_scalar, + t_isf, + t_sf_scalar, +) + +__all__ = ["MetaAnalysisFit", "fit_meta_analysis"] + +FloatArray = npt.NDArray[np.float64] +IntArray = npt.NDArray[np.int64] +BoolArray = npt.NDArray[np.bool_] + +_METHODS = {"reml": 0, "dl": 1, "pm": 2, "fixed": 3} +_TESTS = ("z", "knapp_hartung") +_REML, _DERSIMONIAN_LAIRD, _PAULE_MANDEL, _FIXED = 0, 1, 2, 3 +# The rows of the compiled routine's output, in order. +_OUTPUTS = ( + "estimate", + "standard_error", + "statistic", + "p_value", + "ci_lower", + "ci_upper", + "tau_squared", + "q_statistic", + "q_p_value", + "i_squared", + "h_squared", +) +# The iterations of REML and Paule-Mandel stop when a step changes tau^2 by at most +# _TOLERANCE times tau^2 + s^2, s^2 the typical within-study variance, or after +# _MAX_ITERATIONS steps. +_TOLERANCE = 1e-13 +_MAX_ITERATIONS = 1000 +# The signs of the restricted score are read on a grid of this many points per decade +# of tau^2, to bracket every maximum of the restricted likelihood. +_GRID_PER_DECADE = 8 + + +@dataclass(frozen=True, eq=False, repr=False) +class MetaAnalysisFit: + """Meta-analyses of many features, from :func:`fit_meta_analysis`. + + The per-feature arrays have the shape of the effects without their first (study) + axis: one value per feature, or 0-d arrays for the effects of one feature given + as a vector. They are read-only. + + Attributes: + estimate: The estimate of the common effect mu (metafor's ``beta``): the + weighted mean of the studies' effects, with the weights + 1 / (v_i + tau^2). + standard_error: Its standard error (``se``), adjusted by Knapp and Hartung + with ``test="knapp_hartung"``. + statistic: ``estimate / standard_error`` (``zval``): a z-statistic, or with + ``test="knapp_hartung"`` a t-statistic on ``n_studies - 1`` degrees of + freedom. + p_value: Its two-sided p-value (``pval``). + ci_lower: The lower limit of the confidence interval of the estimate at + ``confidence_level`` (``ci.lb``). + ci_upper: The upper limit (``ci.ub``). + tau_squared: The estimate of the between-study variance tau^2 (``tau2``); 0 + for the fixed-effect model. + q_statistic: Cochran's Q, the heterogeneity statistic of the fixed-effect + fit (``QE``). + q_p_value: Its p-value, from the chi-squared distribution with + ``n_studies - 1`` degrees of freedom (``QEp``). + i_squared: I^2, as a proportion in [0, 1] (metafor's ``I2`` is in percent). + h_squared: H^2 (``H2``). + n_studies: The number of studies that measured each feature (``k``). + converged: Whether the iterations of REML or Paule-Mandel met their + convergence criterion; True for the other methods. + method: ``"reml"``, ``"dl"``, ``"pm"`` or ``"fixed"``. + test: ``"z"`` or ``"knapp_hartung"``. + confidence_level: The confidence level of the intervals. + """ + + estimate: FloatArray + standard_error: FloatArray + statistic: FloatArray + p_value: FloatArray + ci_lower: FloatArray + ci_upper: FloatArray + tau_squared: FloatArray + q_statistic: FloatArray + q_p_value: FloatArray + i_squared: FloatArray + h_squared: FloatArray + n_studies: IntArray + converged: BoolArray + method: str + test: str + confidence_level: float + + def __repr__(self) -> str: + studies = self.n_studies[self.n_studies > 0] + span = f"{studies.min()} to {studies.max()}" if studies.size else "none" + return ( + f"MetaAnalysisFit(n_features={self.estimate.size}, method={self.method!r}, " + f"test={self.test!r}, studies per feature: {span})" + ) + + +def fit_meta_analysis( + effects: npt.ArrayLike, + standard_errors: npt.ArrayLike, + *, + method: Literal["reml", "dl", "pm", "fixed"] = "reml", + test: Literal["z", "knapp_hartung"] = "z", + confidence_level: float = 0.95, +) -> MetaAnalysisFit: + """Meta-analyse the effects of every feature across studies, as metafor's rma.uni. + + For every feature, the effects of the studies that measured it are combined into + an estimate of a common effect by the fixed-effect or a random-effects model, with + the between-study variance tau^2, Cochran's Q and its p-value, I^2 and H^2, as + ``rma.uni(yi, sei = ..., method = ..., test = ...)`` gives them; the module + description gives the model, the estimators of tau^2 and the tests. + + Args: + effects: ``(n_studies, n_features)`` estimates of the effect of every feature + in every study, such as log fold changes, or ``(n_studies,)`` for one + feature. Finite, or NaN where a study did not measure a feature. + standard_errors: Their standard errors, of the same shape: positive and + finite, or NaN where a study did not measure a feature. A study is left + out of a feature's analysis where its effect or its standard error is NaN. + method: The estimator of the between-study variance: ``"reml"`` (restricted + maximum likelihood, metafor's default), ``"dl"`` (DerSimonian-Laird), + ``"pm"`` (Paule-Mandel), or ``"fixed"`` for the fixed-effect model. + test: ``"z"`` for z-tests and normal confidence intervals, or + ``"knapp_hartung"`` for the Knapp-Hartung adjustment, with t-tests on + ``n_studies - 1`` degrees of freedom. + confidence_level: The confidence level of the intervals, in (0, 1). metafor + takes it in percent as ``level`` (``level = 95``). + + Returns: + The estimate, standard error, statistic, p-value and confidence interval of + every feature, with tau^2, Q and its p-value, I^2, H^2 and the number of + studies. + + Raises: + TypeError: If ``effects`` or ``standard_errors`` does not hold real numbers or + is a numpy masked array, or an option has the wrong type. + ValueError: If the shapes differ or are not 1-D or 2-D, there is no study, an + effect or a standard error is infinite, a standard error is not positive, + ``method`` or ``test`` is unknown, or ``confidence_level`` is not in + (0, 1). + """ + chosen_method, chosen_test, confidence = _checked_options( + method, test, confidence_level + ) + y = _study_values(effects, "effects") + se = _study_values(standard_errors, "standard_errors") + if y.shape != se.shape: + raise ValueError( + f"effects and standard_errors must have the same shape, got {y.shape} and " + f"{se.shape}; both are (n_studies, n_features)" + ) + if y.ndim not in (1, 2): + raise ValueError( + "effects must be (n_studies, n_features), or (n_studies,) for one " + f"feature, got {y.ndim} dimensions" + ) + if y.shape[0] == 0: + raise ValueError("there are no studies: effects has no rows") + _check_values(y, se) + n_studies = y.shape[0] + by_feature = np.ascontiguousarray(y.reshape(n_studies, -1).T) + errors = np.ascontiguousarray(se.reshape(n_studies, -1).T) + n_features = by_feature.shape[0] + + alpha = 1.0 - confidence + z_critical = float(norm_isf(alpha / 2.0)) + t_critical = np.full(n_studies, np.nan) + if n_studies > 1: + t_critical[1:] = t_isf(alpha / 2.0, np.arange(1.0, n_studies)) + results = np.empty((len(_OUTPUTS), n_features)) + counts = np.empty(n_features, dtype=np.int64) + converged = np.empty(n_features, dtype=np.bool_) + _meta_analyse( + by_feature, + errors, + _METHODS[chosen_method], + chosen_test == "knapp_hartung", + z_critical, + t_critical, + results, + counts, + converged, + ) + shape = y.shape[1:] + arrays = { + name: read_only(results[row].reshape(shape)) + for row, name in enumerate(_OUTPUTS) + } + return MetaAnalysisFit( + **arrays, + n_studies=read_only(counts.reshape(shape)), + converged=read_only(converged.reshape(shape)), + method=chosen_method, + test=chosen_test, + confidence_level=confidence, + ) + + +# Checks of the arguments. + + +def _study_values(values: Any, name: str) -> FloatArray: + array = unmasked_array(values, name, missing="NaN") + if array.dtype.kind not in "iuf": + raise TypeError(f"{name} must hold real numbers, got dtype {array.dtype}") + converted: FloatArray = np.array(array, dtype=np.float64) + return converted + + +def _check_values(y: FloatArray, se: FloatArray) -> None: + infinite = np.isinf(y) + if infinite.any(): + position = tuple(int(i) for i in np.argwhere(infinite)[0]) + raise ValueError( + f"effects must be finite, or NaN where a study did not measure a feature; " + f"found {np.count_nonzero(infinite)} infinite, such as {y[position]} at " + f"{position}" + ) + invalid = ~np.isnan(se) & ~((se > 0.0) & np.isfinite(se)) + if invalid.any(): + position = tuple(int(i) for i in np.argwhere(invalid)[0]) + raise ValueError( + "standard_errors must be positive and finite, or NaN where a study did " + f"not measure a feature; found {np.count_nonzero(invalid)} that are not, " + f"such as {se[position]} at {position}" + ) + + +def _checked_options( + method: Any, test: Any, confidence_level: Any +) -> tuple[str, str, float]: + """The estimator, the test and the confidence level, checked in that order.""" + return ( + checked_choice(method, "method", tuple(_METHODS)), + checked_choice(test, "test", _TESTS), + _confidence_level(confidence_level), + ) + + +def _confidence_level(value: Any) -> float: + # real_number would refuse a masked value too, but without saying how to fix it. + reject_masked(value, "confidence_level") + number = real_number(value, "confidence_level", finite=True) + if not 0.0 < number < 1.0: + raise ValueError( + "confidence_level must be in (0, 1), such as 0.95 for 95% confidence " + f"intervals, got {number}" + ) + return number + + +# The meta-analyses, compiled. Each routine works on the k studies that measured a +# feature, held in the first k entries of its arrays: the deviations of the effects +# from their unweighted mean (d) and the sampling variances (v), at unit scale. + + +@jit(error_model="numpy") +def _sums_without_each(w: FloatArray, k: int, without: FloatArray) -> float: + """Set ``without[i]`` to the sum of ``w`` over the studies other than i, and + return the sum over all. The partial sums before and after i hold no term larger + than their total, so nothing cancels, as it would in ``total - w[i]``.""" + total = 0.0 + for i in range(k): + without[i] = total + total += w[i] + after = 0.0 + for i in range(k - 1, -1, -1): + without[i] += after + after += w[i] + return total + + +@jit(error_model="numpy") +def _fixed_effect( + d: FloatArray, v: FloatArray, k: int, w: FloatArray, without: FloatArray +) -> tuple[float, float]: + """Cochran's Q and c = sum w - sum w^2 / sum w, for the weights w = 1 / v.""" + for i in range(k): + w[i] = 1.0 / v[i] + total = _sums_without_each(w, k, without) + centre = 0.0 + for i in range(k): + centre += w[i] * d[i] + centre /= total + q = 0.0 + c = 0.0 + for i in range(k): + r = d[i] - centre + q += w[i] * r * r + c += w[i] * (without[i] / total) # the ratio is at most 1: no overflow + return q, c + + +@jit(error_model="numpy") +def _restricted_score( + d: FloatArray, + v: FloatArray, + k: int, + tau2: float, + w: FloatArray, + w2: FloatArray, + without: FloatArray, + without2: FloatArray, +) -> tuple[float, float]: + """Twice the score of the restricted log-likelihood at tau2, sum w^2 r^2 - tr(P), + and twice its observed information, 2 sum w^3 r^2 - 2 (sum w^2 r)^2 / sum w - + tr(P^2), the derivative of the score with its sign changed.""" + for i in range(k): + w[i] = 1.0 / (v[i] + tau2) + w2[i] = w[i] * w[i] + total = _sums_without_each(w, k, without) + _sums_without_each(w2, k, without2) + centre = 0.0 + for i in range(k): + centre += w[i] * d[i] + centre /= total + squares = 0.0 + cubes = 0.0 + linear = 0.0 + trace = 0.0 + trace_squared = 0.0 + for i in range(k): + weighted = w[i] * (d[i] - centre) # w r, squared without underflow + squares += weighted * weighted + cubes += w[i] * weighted * weighted + linear += w[i] * weighted + # P_ii = w_i (sum of the other weights) / total, P_ij = -w_i w_j / total. + diagonal = w[i] * (without[i] / total) + trace += diagonal + trace_squared += diagonal * diagonal + (w[i] / total) ** 2 * without2[i] + observed = 2.0 * cubes - 2.0 * linear * linear / total - trace_squared + return squares - trace, observed + + +@jit(error_model="numpy") +def _restricted_log_likelihood( + d: FloatArray, v: FloatArray, k: int, tau2: float +) -> float: + """The restricted log-likelihood at tau2, without its constant.""" + total = 0.0 + centre = 0.0 + logs = 0.0 + for i in range(k): + weight = 1.0 / (v[i] + tau2) + total += weight + centre += weight * d[i] + logs += math.log(v[i] + tau2) + centre /= total + q = 0.0 + for i in range(k): + r = d[i] - centre + q += r * r / (v[i] + tau2) + return -0.5 * (logs + math.log(total) + q) + + +@jit(error_model="numpy") +def _score_root( + d: FloatArray, + v: FloatArray, + k: int, + lower: float, + upper: float, + typical: float, + w: FloatArray, + w2: FloatArray, + without: FloatArray, + without2: FloatArray, +) -> tuple[float, bool]: + """The root of the restricted score in (lower, upper), where it falls from positive + to negative: Newton's method with the observed information, kept in the bracket + and replaced by bisection where its step leaves it or does not halve every two + steps.""" + tau2 = 0.5 * (lower + upper) + last = upper - lower + before_last = last + for _ in range(_MAX_ITERATIONS): + score, observed = _restricted_score(d, v, k, tau2, w, w2, without, without2) + if score > 0.0: + lower = tau2 + elif score < 0.0: + upper = tau2 + else: + return tau2, True + step = score / observed if observed > 0.0 else math.inf + if not (lower < tau2 + step < upper and abs(step) < 0.5 * before_last): + step = 0.5 * (lower + upper) - tau2 + before_last, last = last, abs(step) + tau2 += step + if abs(step) <= _TOLERANCE * (tau2 + typical): + return tau2, True + return tau2, False + + +@jit(error_model="numpy") +def _reml( + d: FloatArray, + v: FloatArray, + k: int, + typical: float, + w: FloatArray, + w2: FloatArray, + without: FloatArray, + without2: FloatArray, +) -> tuple[float, bool]: + """The REML estimate of tau^2, the largest maximum of the restricted likelihood + over tau2 >= 0, and whether its iteration converged; NaN and False where no + maximum is found, which the bounds below exclude in exact arithmetic. + + The maxima are zero, where the score there is not positive, and the roots where + the score falls from positive to negative. The score is negative beyond + sum d^2 / (k - 1) + 2 max(v), where w_max^2 sum d^2 bounds sum w^2 r^2 and + (k - 1) w_min^2 / w_max bounds tr(P) from below. The roots are bracketed by the + signs of the score on a grid with _GRID_PER_DECADE points per decade from + 1e-3 min(v) to twice that bound, so that rounding cannot hide the change of sign + of a root near the bound (as when the standard errors are far smaller than the + spread of the effects), and each is found by ``_score_root``. + """ + squares = 0.0 + smallest = math.inf + largest = 0.0 + for i in range(k): + squares += d[i] * d[i] + smallest = min(smallest, v[i]) + largest = max(largest, v[i]) + bottom = max(1e-3 * smallest, 1e-300) # positive, should a variance underflow + top = 2.0 * (squares / (k - 1) + 2.0 * largest) + if not top < math.inf: + return math.nan, False + n = max(2, int(math.ceil(_GRID_PER_DECADE * math.log10(top / bottom))) + 1) + ratio = (top / bottom) ** (1.0 / (n - 1)) + best = math.nan + best_likelihood = -math.inf + converged = True + left = 0.0 + left_score = _restricted_score(d, v, k, 0.0, w, w2, without, without2)[0] + if left_score <= 0.0: + best = 0.0 + best_likelihood = _restricted_log_likelihood(d, v, k, 0.0) + for j in range(n): + right = top if j == n - 1 else bottom * ratio**j + right_score = _restricted_score(d, v, k, right, w, w2, without, without2)[0] + if left_score > 0.0 and right_score <= 0.0: + if right_score == 0.0: + root, ok = right, True + else: + root, ok = _score_root( + d, v, k, left, right, typical, w, w2, without, without2 + ) + converged = converged and ok + likelihood = _restricted_log_likelihood(d, v, k, root) + if likelihood > best_likelihood or math.isnan(best): + best, best_likelihood = root, likelihood + left, left_score = right, right_score + return best, converged and not math.isnan(best) + + +@jit(error_model="numpy") +def _generalized_q( + d: FloatArray, v: FloatArray, k: int, tau2: float +) -> tuple[float, float]: + """The generalized Q statistic at tau2 and minus its derivative, sum w^2 r^2.""" + total = 0.0 + centre = 0.0 + for i in range(k): + weight = 1.0 / (v[i] + tau2) + total += weight + centre += weight * d[i] + centre /= total + q = 0.0 + slope = 0.0 + for i in range(k): + weight = 1.0 / (v[i] + tau2) + r = d[i] - centre + q += weight * r * r + slope += (weight * r) ** 2 + return q, slope + + +@jit(error_model="numpy") +def _paule_mandel( + d: FloatArray, v: FloatArray, k: int, q_fixed: float, typical: float +) -> tuple[float, bool]: + """The Paule-Mandel estimate of tau^2 by Newton's method, and whether it + converged.""" + target = k - 1.0 + if not q_fixed > target: + return 0.0, True + squares = 0.0 + smallest = math.inf + largest = 0.0 + for i in range(k): + squares += d[i] * d[i] + smallest = min(smallest, v[i]) + largest = max(largest, v[i]) + # 1 / (largest + tau2) <= w_i <= 1 / (smallest + tau2) brackets Q(tau2) between + # sum d^2 / (largest + tau2) and sum d^2 / (smallest + tau2). + tau2 = max(0.0, squares / target - largest) + upper = squares / target - smallest + for _ in range(_MAX_ITERATIONS): + q, slope = _generalized_q(d, v, k, tau2) + excess = q - target + if not excess > 0.0: + return tau2, True + updated = min(tau2 + excess / slope, upper) + if updated - tau2 <= _TOLERANCE * (updated + typical): + return updated, True + tau2 = updated + return tau2, False + + +@jit(error_model="numpy") +def _meta_analyse( + effects: FloatArray, + standard_errors: FloatArray, + method: int, + knapp_hartung: bool, + z_critical: float, + t_critical: FloatArray, + results: FloatArray, + n_studies: IntArray, + converged: BoolArray, +) -> None: + """Meta-analyse every feature: rows of ``effects`` and ``standard_errors`` are + features, columns studies, NaN where a study did not measure a feature. Fills the + rows of ``results`` in the order of ``_OUTPUTS``.""" + n_features, m = effects.shape + d = np.empty(m) + v = np.empty(m) + w = np.empty(m) + w2 = np.empty(m) + without = np.empty(m) + without2 = np.empty(m) + for g in range(n_features): + k = 0 + mean = 0.0 + for i in range(m): + y = effects[g, i] + s = standard_errors[g, i] + if math.isnan(y) or math.isnan(s): + continue + d[k] = y + v[k] = s + mean += y + k += 1 + n_studies[g] = k + converged[g] = True + if k == 0: + results[:, g] = math.nan + continue + mean /= k + largest = 0.0 + for i in range(k): + d[i] -= mean + largest = max(largest, v[i], abs(d[i])) + # Divided, exactly, by a power of two near the largest standard error or + # deviation of the effects from their mean: every number below is then at most + # of order one. + scale = math.ldexp(1.0, math.frexp(largest)[1]) + for i in range(k): + d[i] /= scale + v[i] /= scale + single = v[0] # the standard error of a feature of one study, as given + for i in range(k): + v[i] *= v[i] + + tau2 = 0.0 + if k == 1: + q = 0.0 + q_p_value = 1.0 + i_squared = 0.0 + h_squared = 1.0 + else: + q, c = _fixed_effect(d, v, k, w, without) + typical = (k - 1) / c + q_p_value = chi2_sf_scalar(q, k - 1.0) + ok = True + if method == _REML: + tau2, ok = _reml(d, v, k, typical, w, w2, without, without2) + elif method == _DERSIMONIAN_LAIRD: + tau2 = max(0.0, (q - (k - 1)) / c) + elif method == _PAULE_MANDEL: + tau2, ok = _paule_mandel(d, v, k, q, typical) + converged[g] = ok + if method == _FIXED: + i_squared = (q - (k - 1)) / q if q > k - 1 else 0.0 + h_squared = q / (k - 1) + else: + i_squared = tau2 / (tau2 + typical) + h_squared = (tau2 + typical) / typical + + total = 0.0 + centre = 0.0 + for i in range(k): + weight = 1.0 / (v[i] + tau2) + total += weight + centre += weight * d[i] + centre /= total + error = single if k == 1 else math.sqrt(1.0 / total) + if knapp_hartung and k > 1: + spread = 0.0 + for i in range(k): + r = d[i] - centre + spread += r * r / (v[i] + tau2) + error = math.sqrt(spread / (k - 1) / total) + critical = t_critical[k - 1] + else: + critical = z_critical + estimate = mean + centre * scale + error *= scale + statistic = estimate / error + if knapp_hartung and k > 1: + p_value = 2.0 * t_sf_scalar(abs(statistic), k - 1.0) + else: + p_value = 2.0 * norm_sf_scalar(abs(statistic)) + results[0, g] = estimate + results[1, g] = error + results[2, g] = statistic + results[3, g] = p_value + results[4, g] = estimate - critical * error + results[5, g] = estimate + critical * error + results[6, g] = tau2 * scale * scale + results[7, g] = q + results[8, g] = q_p_value + results[9, g] = i_squared + results[10, g] = h_squared diff --git a/cytepool/stats/mixed_models.py b/cytepool/stats/mixed_models.py index 8477213..e9f4920 100644 --- a/cytepool/stats/mixed_models.py +++ b/cytepool/stats/mixed_models.py @@ -162,7 +162,7 @@ Limits of this version: Random slopes are not implemented yet. - :mod:`cytepool.variance_partitioning` partitions the variance of these fits. + :mod:`cytepool.stats.variance_fractions` partitions the variance of these fits. Every feature needs a value for every sample, with a positive weight. Designs whose fixed and random effects together span every sample, and so reproduce every feature exactly, are refused, although their criterion can have a minimum @@ -334,7 +334,7 @@ class MixedModelFit: fitted_values: ``(n_samples, n_features)`` fitted values X beta + Z b, with the random effects b at their best linear unbiased predictions, gamma_k Z_k' P y (lme4's ``fitted``, which includes them). - residual_df: ``(n_features,)`` approximate residual degrees of freedom, + df_residual: ``(n_features,)`` approximate residual degrees of freedom, tr((I - H)' (I - H)) for the matrix H that maps the weighted values to the weighted fitted values (variancePartition's ``rdf.merMod``). It is n - p when every variance component is zero, and falls towards n minus @@ -366,7 +366,7 @@ class MixedModelFit: method: str log_likelihood: FloatArray fitted_values: FloatArray - residual_df: FloatArray + df_residual: FloatArray mean_weights: FloatArray mean_expression: FloatArray weights: FloatArray | None @@ -620,7 +620,7 @@ def _fit( converged, iterations, random_effects, - residual_df, + df_residual, ) = outputs # Back to the coefficients of x: beta = r^-1 (beta_q + projection). @@ -658,7 +658,7 @@ def _fit( method="ml" if ml else "reml", log_likelihood=_frozen(-0.5 * criterion), fitted_values=_frozen(fitted), - residual_df=_frozen(residual_df), + df_residual=_frozen(df_residual), mean_weights=_frozen(mean_weights), mean_expression=_frozen(observed_means(values)), weights=None if given_weights is None else _frozen(given_weights), @@ -1101,7 +1101,7 @@ def _fit_features( converged: BoolArray, iterations: IntArray, random_effects: FloatArray, - residual_df: FloatArray, + df_residual: FloatArray, ) -> None: """Fit features ``start:stop`` into the outputs, with work arrays of their own. @@ -1198,7 +1198,7 @@ def _fit_features( work, random_effects[g], ) - residual_df[g] = _approximate_residual_df(ratio, n_samples, p, work) + df_residual[g] = _approximate_residual_df(ratio, n_samples, p, work) @jit(no_cpython_wrapper=True) diff --git a/cytepool/stats/multiple_testing.py b/cytepool/stats/multiple_testing.py index af0712b..68fd12b 100644 --- a/cytepool/stats/multiple_testing.py +++ b/cytepool/stats/multiple_testing.py @@ -116,7 +116,7 @@ def adjust_p_values( - p: ArrayLike, + p_values: ArrayLike, method: str = "bh", *, n: int | None = None, @@ -143,7 +143,7 @@ def adjust_p_values( Every method takes O(m log m) time for m p-values, Hommel's included. Args: - p: p-values in [0, 1]. NaN marks a missing value; it stays NaN and is + p_values: p-values in [0, 1]. NaN marks a missing value; it stays NaN and is not counted as a test. method: The procedure, case-insensitive: ``"bonferroni"``, ``"holm"``, ``"hochberg"``, ``"hommel"``, ``"bh"`` (also ``"fdr"`` and @@ -159,18 +159,18 @@ def adjust_p_values( of features by contrasts, ``axis=0`` adjusts each column. Returns: - The adjusted p-values as float64, with the shape of ``p``. + The adjusted p-values as float64, with the shape of ``p_values``. Raises: - TypeError: If ``p`` is not real-valued, ``method`` is not a string, ``n`` - or ``axis`` is not an integer, or ``p``, ``n`` or ``axis`` is a numpy + TypeError: If ``p_values`` is not real-valued, ``method`` is not a string, ``n`` + or ``axis`` is not an integer, or ``p_values``, ``n`` or ``axis`` is a numpy masked array. ValueError: If a p-value is outside [0, 1], ``method`` is unknown, ``n`` is smaller than the number of non-missing p-values in a family or larger than 2**53, or ``axis`` is out of range. """ procedure = _procedure(method) - values = _p_value_array(p) + values = _p_value_array(p_values) if n is not None: n = _integer(n, "n") if axis is None: @@ -194,9 +194,9 @@ def _procedure(method: str) -> str: def _p_value_array(p: ArrayLike) -> np.ndarray: - values = unmasked_array(p, "p", missing="NaN") + values = unmasked_array(p, "p_values", missing="NaN") if values.dtype.kind not in "iuf": - raise TypeError(f"p must hold real numbers, got dtype {values.dtype}") + raise TypeError(f"p_values must hold real numbers, got dtype {values.dtype}") values = values.astype(np.float64, copy=False) outside = ~((values >= 0.0) & (values <= 1.0)) & ~np.isnan(values) if outside.any(): @@ -425,7 +425,7 @@ def __repr__(self) -> str: def proportion_of_true_nulls( - p: ArrayLike, + p_values: ArrayLike, *, lambdas: ArrayLike | None = None, method: str = "smoother", @@ -443,8 +443,8 @@ def proportion_of_true_nulls( those genes with an effect in it (Storey and Tibshirani 2003). Args: - p: p-values in [0, 1]; NaN marks a missing one, which is left out, as qvalue - leaves out NA. + p_values: p-values in [0, 1]; NaN marks a missing one, which is left out, as + qvalue leaves out NA. lambdas: One value of lambda in [0, 1), or at least four distinct values, in any order; by default 0.05, 0.10, ..., 0.95 (qvalue's ``lambda``). method: ``"smoother"`` (qvalue's default) or ``"bootstrap"`` @@ -457,8 +457,8 @@ def proportion_of_true_nulls( The estimate, with pi0(lambda) at every lambda and the smoother's values. Raises: - TypeError: If ``p`` or ``lambdas`` does not hold real numbers or is a numpy - masked array, or an option has the wrong type. + TypeError: If ``p_values`` or ``lambdas`` does not hold real numbers or is a + numpy masked array, or an option has the wrong type. ValueError: If a p-value lies outside [0, 1], there is none, ``lambdas`` are outside [0, 1), repeated, or two or three, ``method`` is unknown, ``smooth_df`` is out of range, or ``smooth_log`` meets a lambda that no @@ -474,11 +474,13 @@ def proportion_of_true_nulls( """ chosen = checked_choice(method, "method", _PI0_METHODS) log_scale = checked_flag(smooth_log, "smooth_log") - values = _p_value_array(p) + values = _p_value_array(p_values) values = np.sort(values[~np.isnan(values)].reshape(-1)) m = values.size if m == 0: - raise ValueError("there are no p-values: p is empty or every value is NaN") + raise ValueError( + "there are no p-values: p_values is empty or every value is NaN" + ) grid = _lambdas(lambdas) df = real_number(smooth_df, "smooth_df", finite=True) if grid.size > 1 and chosen == "smoother" and not 1.0 < df <= grid.size: diff --git a/cytepool/stats/quasi_likelihood.py b/cytepool/stats/quasi_likelihood.py index 3449f34..90a62ad 100644 --- a/cytepool/stats/quasi_likelihood.py +++ b/cytepool/stats/quasi_likelihood.py @@ -16,7 +16,7 @@ The steps: -1. Abundance. :func:`average_log_cpm` is edgeR's ``aveLogCPM``: the log2 of the mean +1. Abundance. :func:`average_log2_cpm` is edgeR's ``aveLogCPM``: the log2 of the mean count per million of a gene, fitted as one group of samples by a NB model after a prior count of 2, scaled by library size, is added to every count (edgeR's ``addPriorCount``). ``glmQLFit`` computes it once, at the given NB dispersion, or @@ -230,7 +230,7 @@ "QuasiLikelihoodFit", "QuasiLikelihoodTest", "UnitDevianceMoments", - "average_log_cpm", + "average_log2_cpm", "common_dispersion", "fit_quasi_likelihood", "negative_binomial_unit_deviance_moments", @@ -981,7 +981,7 @@ def _average_quasi_dispersion( # --------------------------------------------------------------------------------------- -def average_log_cpm( +def average_log2_cpm( counts: npt.ArrayLike, *, library_sizes: npt.ArrayLike | None = None, @@ -1164,7 +1164,7 @@ def _common_dispersion( "dispersion can be estimated" ) if keep.size > most: - abundance = average_log_cpm( + abundance = average_log2_cpm( y[:, keep], offsets=_by_samples(log_sizes, keep), weights=_by_samples(w, keep), @@ -1353,7 +1353,7 @@ class QuasiLikelihoodFit: (``df.residual.adj``), fractional; zero for a gene without counts. quasi_dispersion: ``(n_features,)`` quasi-dispersions, the ratio of the two; NaN where the degrees of freedom are zero. - prior_df: The prior degrees of freedom (``df.prior``): a float, or one value + df_prior: The prior degrees of freedom (``df.prior``): a float, or one value per gene from a robust fit that finds outliers. prior_quasi_dispersion: The prior quasi-dispersion (``s2.prior``): one value per gene with a trend, a float otherwise. @@ -1363,7 +1363,7 @@ class QuasiLikelihoodFit: default the average log CPM that also ranks the genes, at the given NB dispersion or at 0.05 when the dispersion is estimated (edgeR's abundance); None without a trend. - average_log_cpm: ``(n_features,)`` average log2 counts per million at the + average_log2_cpm: ``(n_features,)`` average log2 counts per million at the negative binomial dispersion, the ``logCPM`` of the tests. robust: Whether the prior was estimated robustly. counts: ``(n_samples, n_features)`` the counts fitted (``counts``). @@ -1380,11 +1380,11 @@ class QuasiLikelihoodFit: deviance_adjusted: FloatArray df_residual_adjusted: FloatArray quasi_dispersion: FloatArray - prior_df: float | FloatArray + df_prior: float | FloatArray prior_quasi_dispersion: float | FloatArray posterior_quasi_dispersion: FloatArray covariate: FloatArray | None - average_log_cpm: FloatArray + average_log2_cpm: FloatArray robust: bool counts: FloatArray max_iterations: int @@ -1401,7 +1401,7 @@ def __repr__(self) -> str: f"coefficient_names={self.coefficient_names!r}, " f"dispersion={value_range(self.dispersion)}, " f"average_quasi_dispersion={self.average_quasi_dispersion:.6g}, " - f"prior_df={value_range(self.prior_df)}, robust={self.robust})" + f"df_prior={value_range(self.df_prior)}, robust={self.robust})" ) @@ -1527,7 +1527,7 @@ def fit_quasi_likelihood( if dispersion is None: # The genes are ranked, and the trends follow, by the average log CPM at # aveLogCPM's default dispersion (found by probing). - abundance = average_log_cpm( + abundance = average_log2_cpm( y, **given, prior_count=_ABUNDANCE_PRIOR_COUNT, @@ -1552,7 +1552,7 @@ def fit_quasi_likelihood( phi = np.array([[value]]) reported: float | FloatArray = value # The logCPM of the tests is at the dispersion of the fit. - log_cpm = average_log_cpm( + log2_cpm = average_log2_cpm( y, **given, prior_count=_ABUNDANCE_PRIOR_COUNT, @@ -1564,13 +1564,13 @@ def fit_quasi_likelihood( reported = _as_given(_argument(phi)) # With a dispersion given, the trends follow the average log CPM at that # dispersion, which is also the logCPM of the tests (found by probing). - abundance = average_log_cpm( + abundance = average_log2_cpm( y, **given, prior_count=_ABUNDANCE_PRIOR_COUNT, dispersion=_argument(phi), ) - log_cpm = abundance + log2_cpm = abundance weights_by_sample = np.broadcast_to(w.T, y.shape) average = 1.0 for _ in range(_AVERAGE_PASSES): @@ -1620,7 +1620,7 @@ def fit_quasi_likelihood( deviance_adjusted=read_only(adjusted.deviance), df_residual_adjusted=read_only(df), quasi_dispersion=read_only(quasi), - prior_df=_frozen(squeezed.prior_df), + df_prior=_frozen(squeezed.df_prior), prior_quasi_dispersion=_frozen(squeezed.prior_variance), posterior_quasi_dispersion=read_only( np.asarray(squeezed.posterior_variance, dtype=np.float64) @@ -1628,7 +1628,7 @@ def fit_quasi_likelihood( covariate=None if trend_values is False else read_only(np.asarray(trend_values, dtype=np.float64)), - average_log_cpm=read_only(log_cpm), + average_log2_cpm=read_only(log2_cpm), robust=bool(robust), # A copy: the tests refit the null model from these counts. counts=read_only(np.array(y)), @@ -1645,8 +1645,8 @@ def fit_quasi_likelihood( class QuasiLikelihoodTest: """Quasi-likelihood F-tests of coefficients or contrasts, one per gene. - The columns of edgeR's ``topTags`` table are ``log_fold_change`` (``logFC``), - ``average_log_cpm`` (``logCPM``), ``f_statistic`` (``F``), ``p_value`` + The columns of edgeR's ``topTags`` table are ``log2_fold_change`` (``logFC``), + ``average_log2_cpm`` (``logCPM``), ``f_statistic`` (``F``), ``p_value`` (``PValue``) and ``adjusted_p_value`` (``FDR``). The arrays are read-only. Attributes: @@ -1654,9 +1654,9 @@ class QuasiLikelihoodTest: names: Names of the coefficients or contrasts tested together. contrasts: ``(n_coefficients, n_tested)`` weights of each tested contrast on the coefficients; the columns of the identity for coefficients. - log_fold_change: ``(n_features, n_tested)`` log2 fold changes, each contrast + log2_fold_change: ``(n_features, n_tested)`` log2 fold changes, each contrast of the shrunk coefficients divided by log 2. - average_log_cpm: ``(n_features,)`` average log2 counts per million. + average_log2_cpm: ``(n_features,)`` average log2 counts per million. likelihood_ratio: ``(n_features,)`` likelihood ratio statistics: the deviance of the null fit less that of the full fit, both at the working dispersion; at least 0. @@ -1675,8 +1675,8 @@ class QuasiLikelihoodTest: fit: QuasiLikelihoodFit names: tuple[str, ...] contrasts: FloatArray - log_fold_change: FloatArray - average_log_cpm: FloatArray + log2_fold_change: FloatArray + average_log2_cpm: FloatArray likelihood_ratio: FloatArray f_statistic: FloatArray p_value: FloatArray @@ -1688,12 +1688,12 @@ def table(self, *, feature_ids: Any = None) -> Table: """Return the tests as a table, one row per gene in the order of the counts. edgeR's ``topTags(sort.by = "none")``, with the columns of the tables of - :func:`~cytepool.differential_expression.limma_voom`: ``feature_id``; - ``log2_fold_change`` (``logFC``), or, for several coefficients or contrasts - tested together, ``log2_fold_change_`` for each, as the F-tests of - :func:`~cytepool.differential_expression.dream_voom` name them; - ``average_log2_cpm`` (``logCPM``); ``f_statistic`` (``F``); ``p_value`` - (``PValue``); and ``adjusted_p_value`` (``FDR``). + :func:`~cytepool.limma_voom`: ``feature_id``; ``log2_fold_change`` (``logFC``), + or, for several coefficients or contrasts tested together, + ``log2_fold_change_`` for each, as the F-tests of + :func:`~cytepool.dream_voom` name them; ``average_log2_cpm`` (``logCPM``); + ``f_statistic`` (``F``); ``p_value`` (``PValue``); and ``adjusted_p_value`` + (``FDR``). Args: feature_ids: One id per gene, as text, such as the gene ids of the @@ -1708,12 +1708,12 @@ def table(self, *, feature_ids: Any = None) -> Table: "feature_id": feature_id_column(feature_ids, self.f_statistic.size) } if len(self.names) == 1: - columns["log2_fold_change"] = self.log_fold_change[:, 0] + columns["log2_fold_change"] = self.log2_fold_change[:, 0] else: for j, name in enumerate(self.names): - columns[f"log2_fold_change_{name}"] = self.log_fold_change[:, j] + columns[f"log2_fold_change_{name}"] = self.log2_fold_change[:, j] columns |= { - "average_log2_cpm": self.average_log_cpm, + "average_log2_cpm": self.average_log2_cpm, "f_statistic": self.f_statistic, "p_value": self.p_value, "adjusted_p_value": self.adjusted_p_value, @@ -1819,17 +1819,17 @@ def quasi_likelihood_f_test( n_samples, n_features = model.fitted_values.shape cap = float(n_features * (n_samples - n_coefficients)) df_total = np.minimum( - np.asarray(fit.prior_df) + np.asarray(fit.df_residual_adjusted), cap + np.asarray(fit.df_prior) + np.asarray(fit.df_residual_adjusted), cap ) df_total = np.broadcast_to(df_total, (n_features,)).astype(np.float64) p_value = np.asarray(f_sf(f_statistic, float(rank), df_total), dtype=np.float64) - log_fold_change = np.asarray(model.shrunk_coefficients) @ matrix / _LOG_TWO + log2_fold_change = np.asarray(model.shrunk_coefficients) @ matrix / _LOG_TWO return QuasiLikelihoodTest( fit=fit, names=tuple(tested), contrasts=read_only(np.array(matrix, dtype=np.float64)), - log_fold_change=read_only(np.ascontiguousarray(log_fold_change)), - average_log_cpm=fit.average_log_cpm, + log2_fold_change=read_only(np.ascontiguousarray(log2_fold_change)), + average_log2_cpm=fit.average_log2_cpm, likelihood_ratio=read_only(likelihood_ratio), f_statistic=read_only(np.asarray(f_statistic, dtype=np.float64)), p_value=read_only(p_value), diff --git a/cytepool/stats/sample_weights.py b/cytepool/stats/sample_weights.py index 021b45e..ea76527 100644 --- a/cytepool/stats/sample_weights.py +++ b/cytepool/stats/sample_weights.py @@ -194,8 +194,8 @@ def sample_quality_weights( counts per million of :func:`~cytepool.stats.voom.voom`. NaN marks a missing value; infinite values are rejected. design: ``(n_samples, n_coefficients)`` design matrix of full column rank, - or a :class:`~cytepool.design.Design` without random effects. A column - of ones treats the samples as replicates. + or a :class:`~cytepool.Design` without random effects. A column of ones + treats the samples as replicates. weights: Prior weights: ``(n_samples,)`` sample weights, ``(n_samples, n_features)`` observation weights (such as voom's), or ``(1, n_features)`` feature weights. Finite and non-negative where ``y`` diff --git a/cytepool/stats/sumrank.py b/cytepool/stats/sumrank.py new file mode 100644 index 0000000..3a6f6be --- /dev/null +++ b/cytepool/stats/sumrank.py @@ -0,0 +1,547 @@ +"""SumRank: genes whose differential expression ranks high in study after study. + +Most genes that one single-cell study finds differentially expressed are not found by +the next (Nakatsuka et al. 2025). SumRank asks instead whether a gene ranks high in +every study: each study ranks its genes by their signed evidence of change, and a +gene's ranks are summed over the studies, so that a gene ranked high everywhere has a +small sum and a gene found by one study alone does not, however small its p-value +there. :func:`fit_sumrank` computes the statistic and its p-values from a signed +statistic of every gene in every study; :func:`cytepool.sumrank` does it for the +studies' differential-expression results of one group (cell type) and contrast, with +p-values from permutations that re-run each study's analysis on its samples with +their labels permuted. + +The statistic, as the paper defines it and its authors' code (Apache-2.0) computes it: + 1. Each study ranks the genes by their signed statistic, from 1 for the largest + (the gene most up); tied genes share the average of their ranks, as R's + ``rank``, which the authors' code calls, gives them. Results of + differential expression give the signed -log10 p-value: -log10(p_value), + negative where the log2 fold change is negative, as the paper ranks DESeq2's + results. + 2. A gene of rank r among G genes has the normalized rank u = (r - 1) / (G - 1): + 0 for the gene most up and 1 for the gene most down. + 3. For each gene, the m smallest of its normalized ranks in the k studies are + summed: those of the studies that rank it best, which leaves out studies that + rank it low for reasons of their own. m is given (``n_studies_summed``), or + m = floor(p k), at least 1, for a proportion p of the studies + (``proportion_of_studies``), the product rounded to 9 decimals first, so that + 0.57 of 100 studies is 57; by default every study is summed (m = k). The paper + sums every study when there are few (its 3 to 6 studies of Parkinson's, + Huntington's disease and schizophrenia) and chose p by prediction in held-out + studies when there are many (0.65 of 17 studies of Alzheimer's disease, 0.55 + of 16 of COVID-19); :func:`~cytepool.held_out_prediction` gives that + prediction for a chosen m or p. + 4. A sum above m / 2, the mean of a sum of m uniform ranks, is set to m / 2, so + that genes without consistent evidence share one value. + + The result is the statistic of genes up (``up_statistic``): small for a gene that + ranks high in its best m studies. Genes down have the same statistic of the + negated statistics (``down_statistic``), whose normalized ranks are 1 - u, as the + paper tests genes down by testing controls against cases. + +Genes that a study did not measure or did not test: + The genes ranked are those that every study tested (a finite or infinite statistic, + not NaN), as the paper ranks the genes that every dataset measured, the intersection + of their genes. A rank says where a gene stands among the genes ranked with it, so + the studies must rank the same genes; and a gene ranked in fewer studies would have + a statistic, and a distribution under the null, of its own. The other genes have + NaN. A study that did not analyse the group or the contrast at all is left out by + :func:`cytepool.sumrank`, as the authors' code leaves out a dataset without the cell + type: k counts the studies that did. + +Irwin-Hall p-values: + If every study ranked the genes in an order of its own, at random and + independently of the others, each normalized rank would be uniform on [0, 1], + and the sum of m of them would follow the Irwin-Hall distribution of a sum of m + independent uniform variables (Irwin 1927; Hall 1927), whose distribution + function is + + F_m(x) = (1/m!) sum_{j=0}^{floor(x)} (-1)^j C(m, j) (x - j)^m. + + ``up_irwin_hall_p_value`` is F_m(s) for the statistic s of a gene below the cap, + and 1 at the cap: the probability under that null of a statistic at most s. + It treats the ranks as continuous: a gene ranked first in every study (s = 0) + has p-value 0, where its probability is G^-m. + + With every study summed (m = k), the p-value is exact under that null, apart from + the steps of the ranks. A sum of the m best of k ranks (m < k) is smaller than a sum + of m ranks, so its Irwin-Hall p-values are too small: the paper takes its p-values + from permutations instead, as below, and so should an analysis with m < k, as + :func:`cytepool.sumrank` warns. Genes whose ranks are not uniform under the null, + such as genes with a p-value of 1 in many permutations, depart from it too. + + The alternating sum above loses every digit to cancellation near the middle of + the distribution for tens of studies. F_m is computed instead as a sum of + positive terms, F_m(x) = sum_{i=0}^{floor(x)} N_{m+1}(x - floor(x) + i), where + N_{m+1} is the cardinal B-spline of order m + 1, the density of the Irwin-Hall + distribution of m + 1 variables, which de Boor's recurrence evaluates with + positive terms only (de Boor 1978): N_{m+1}' (x) = N_m(x) - N_m(x - 1), so the + sum telescopes to the integral of N_m. It agrees with a high-precision + computation to about 1e-14 relative for up to 200 studies + (``validation/sumrank.py``). + +Permutation p-values: + The paper permutes the case and control labels of the samples of every study, + runs each study's analysis and SumRank again, and pools the statistics of every + gene in every permutation into one null distribution: with B permutations of G + genes, N = B G null statistics. The p-value of a gene of statistic s is + + p = (1 + the number of null statistics at most s) / (1 + N), + + as Phipson and Smyth (2010) form p-values from random permutations, so that it + is never zero, and a gene at the cap has p = 1. Pooling over the genes gives + small p-values from few permutations (100 permutations of 10,000 genes give a + p-value as small as 1e-6), for genes whose null distributions are alike. The + statistics of a permutation are computed as those of the data, over the genes + that every study's analysis of that permutation tested. + + :func:`fit_sumrank` takes the statistics of the permutations as an array; + :func:`cytepool.sumrank` draws them by permuting the labels of each study's + samples and running its analysis again. + +Deliberate differences from the paper and its authors' code: + - The Irwin-Hall p-value is the lower tail of the distribution, as above. The + paper calls its Irwin-Hall p-values two-sided, which would double these, and + the authors' code computes the density of the Irwin-Hall distribution at the + statistic instead (the unifed package's ``dirwin.hall``), which is not a + probability. Its permutation p-values compare -log10 of that density, which + falls as the statistic rises up to the cap for m >= 2: they order genes as the + statistic does, so the permutation p-values here do not depend on this choice. + - The authors' code takes as the permutation p-value the share of null + statistics strictly below the gene's (``1 - ecdf`` of -log10 of the density), + without the one above: a gene beyond every null statistic has the p-value 0, + and a gene at the cap the share of null statistics below the cap rather than + 1. Here ties count as at least as extreme, which keeps the p-value valid. + - The authors' code sums the normalized ranks in floating point, in each gene's + own order, so that genes with equal statistics can compare as unequal in its + calibration; here the sums are exact. It stops when it sums one study (m = 1), + and takes floor(p k) of the product in floating point, which for 0.57 of 100 + studies is 56. + +References: + Nakatsuka N, Adler D, Jiang L, Hartman A, Cheng E, Klann E, Satija R (2025). + Improving reproducibility of differentially expressed genes in single-cell + transcriptomic studies of neurodegenerative diseases through meta-analysis. + Nature Communications 16, 7436. doi:10.1038/s41467-025-62579-z + + Irwin JO (1927). On the frequency distribution of the means of samples from a + population having any law of frequency with finite moments, with special + reference to Pearson's Type II. Biometrika 19, 225-239. + doi:10.1093/biomet/19.3-4.225 + + Hall P (1927). The distribution of means for samples of size N drawn from a + population in which the variate takes values between 0 and 1, all such values + being equally probable. Biometrika 19, 240-244. doi:10.1093/biomet/19.3-4.240 + + de Boor C (1978). A Practical Guide to Splines. Springer, New York. + doi:10.1007/978-1-4612-6333-3 + + Phipson B, Smyth GK (2010). Permutation p-values should never be zero: calculating + exact p-values when permutations are randomly drawn. Statistical Applications in + Genetics and Molecular Biology 9, article 39. doi:10.2202/1544-6115.1585 +""" + +import math +from dataclasses import dataclass +from typing import Any + +import numpy as np +import numpy.typing as npt + +from .._jit import jit +from .._masked_arrays import read_only, reject_masked, unmasked_array +from .._options import index_integer, real_number +from .._ranks import average_ranks + +__all__ = ["SumRankFit", "fit_sumrank"] + +FloatArray = npt.NDArray[np.float64] +BoolArray = npt.NDArray[np.bool_] + + +@dataclass(frozen=True, eq=False, repr=False) +class SumRankFit: + """SumRank of every gene across studies, from :func:`fit_sumrank`. + + The per-gene arrays hold one value per gene (feature), NaN for a gene that some + study did not test; they are read-only. + + Attributes: + up_statistic: The sum of the gene's ``n_studies_summed`` smallest normalized + ranks, set to ``n_studies_summed / 2`` where it is larger: small for a + gene up in the studies that rank it best. + down_statistic: The same for the negated statistics: small for a gene down. + up_irwin_hall_p_value: The Irwin-Hall distribution function of the sum of + ``n_studies_summed`` uniform ranks at ``up_statistic``, or 1 at the cap. + down_irwin_hall_p_value: The same for ``down_statistic``. + up_permutation_p_value: (1 + the number of null statistics at most + ``up_statistic``) / (1 + ``n_null_values``), over the statistics of + every gene in every permutation; None without permutations. + down_permutation_p_value: The same for ``down_statistic``. + normalized_ranks: ``(n_studies, n_features)``: the normalized rank of every + gene in every study, (r - 1) / (G - 1) for the rank r from 1 for the + largest statistic among the G genes ranked; NaN for a gene not ranked. + The ranks of the genes down are 1 minus these. + ranked: Whether each gene was ranked: tested by every study. + n_studies: The number of studies, k. + n_studies_summed: The number of ranks summed for each gene, m. + proportion_of_studies: The proportion of the studies that set m, 1.0 when + every study is summed by default, or None when m was given. + n_permutations: The number of permutations, 0 without them. + n_null_values: The number of null statistics of each direction, the genes + ranked in each permutation, summed over the permutations; 0 without + them. + """ + + up_statistic: FloatArray + down_statistic: FloatArray + up_irwin_hall_p_value: FloatArray + down_irwin_hall_p_value: FloatArray + up_permutation_p_value: FloatArray | None + down_permutation_p_value: FloatArray | None + normalized_ranks: FloatArray + ranked: BoolArray + n_studies: int + n_studies_summed: int + proportion_of_studies: float | None + n_permutations: int + n_null_values: int + + def __repr__(self) -> str: + text = ( + f"SumRankFit(n_features={self.ranked.size}, ranked=" + f"{int(np.count_nonzero(self.ranked))}, n_studies={self.n_studies}, " + f"n_studies_summed={self.n_studies_summed}" + ) + if self.n_permutations: + text += f", n_permutations={self.n_permutations}" + return text + ")" + + +def fit_sumrank( + statistics: npt.ArrayLike, + *, + n_studies_summed: int | None = None, + proportion_of_studies: float | None = None, + null_statistics: npt.ArrayLike | None = None, +) -> SumRankFit: + """Rank every gene in every study and sum its best ranks, as SumRank does. + + Each study ranks the genes that every study tested by their statistic, from the + largest; a gene's normalized ranks in the studies that rank it best are summed and + capped at their mean under the null, for genes up and, with the statistics + negated, for genes down. The module description gives the statistic, its + Irwin-Hall p-values and the permutation p-values. + + Args: + statistics: ``(n_studies, n_features)`` signed statistics, larger for a gene + more up, such as the signed -log10 p-values of each study's test; NaN + where a study did not test a gene, and infinite values are allowed (a + p-value of 0). Only their order within each study matters. + n_studies_summed: The number m of studies whose ranks are summed for each + gene, those that rank it best, from 1 to the number of studies k; by + default every study. + proportion_of_studies: Instead of ``n_studies_summed``, the proportion p of + the k studies whose ranks are summed, in (0, 1]: m = floor(p k), at + least one; 1 sums every study. + null_statistics: ``(n_permutations, n_studies, n_features)``: the statistics + of the same studies with their labels permuted, one study's analysis + re-run on each permutation, NaN where an analysis did not test a gene; + or None for no permutation p-values. + + Returns: + The statistics of every gene, up and down, their Irwin-Hall p-values and, + with ``null_statistics``, their permutation p-values. + + Raises: + TypeError: If an argument is not an array of real numbers, is a numpy masked + array, ``n_studies_summed`` is not a whole number or + ``proportion_of_studies`` not a real number. + ValueError: If the shapes are wrong, a study tested no gene, fewer than two + genes were tested by every study (in the data or in a permutation), + ``n_studies_summed`` is not from 1 to the number of studies, + ``proportion_of_studies`` is not in (0, 1], or both are given. + + Example: + Two studies of four genes: the first gene is the largest in both, so its + ranks sum to 0; the others reach the cap of 1 (two ranks of mean 1/2). + + >>> fit = fit_sumrank([[3.0, 1.0, -2.0, 0.5], [2.0, -1.0, 0.0, 1.5]]) + >>> fit.up_statistic.tolist() + [0.0, 1.0, 1.0, 1.0] + >>> fit.down_statistic.tolist() + [1.0, 0.6666666666666666, 0.3333333333333333, 1.0] + >>> fit.down_irwin_hall_p_value.tolist() # (1/3)^2 / 2 = 1/18 for the third + [1.0, 0.2222222222222222, 0.05555555555555555, 1.0] + """ + values = _statistics(statistics, "statistics", 2) + n_studies = values.shape[0] + summed, proportion = _studies_summed( + n_studies_summed, proportion_of_studies, n_studies + ) + null = None + if null_statistics is not None: + null = _statistics(null_statistics, "null_statistics", 3) + if null.shape[1:] != values.shape: + raise ValueError( + "null_statistics must be (n_permutations, n_studies, n_features) with " + f"the studies and features of statistics, {values.shape}; got " + f"{null.shape}" + ) + if null.shape[0] == 0: + raise ValueError( + "null_statistics holds no permutation; give at least one, or None for " + "no permutation p-values" + ) + observed = _capped_sums(values, summed, "statistics") + up = _full(observed.up, observed.ranked) + down = _full(observed.down, observed.ranked) + permutation: list[FloatArray | None] = [None, None] + n_null = 0 + if null is not None: + counts = _NullCounts(observed) + for b, permuted in enumerate(null): + counts.add(_capped_sums(permuted, summed, f"permutation {b}")) + permutation = [read_only(_full(p, observed.ranked)) for p in counts.p_values()] + n_null = counts.n_values + return SumRankFit( + up_statistic=read_only(up), + down_statistic=read_only(down), + up_irwin_hall_p_value=read_only(_full(observed.up_p, observed.ranked)), + down_irwin_hall_p_value=read_only(_full(observed.down_p, observed.ranked)), + up_permutation_p_value=permutation[0], + down_permutation_p_value=permutation[1], + normalized_ranks=read_only(observed.ranks_of_every_feature()), + ranked=read_only(observed.ranked), + n_studies=n_studies, + n_studies_summed=summed, + proportion_of_studies=proportion, + n_permutations=0 if null is None else int(null.shape[0]), + n_null_values=n_null, + ) + + +# The statistic. + + +def _statistics(values: Any, name: str, ndim: int) -> FloatArray: + array = unmasked_array(values, name, missing="NaN") + if array.dtype.kind not in "iuf": + raise TypeError(f"{name} must hold real numbers, got dtype {array.dtype}") + if array.ndim != ndim: + shape = ( + "(n_studies, n_features)" + if ndim == 2 + else "(n_permutations, n_studies, n_features)" + ) + raise ValueError(f"{name} must be {shape}, got {array.ndim} dimensions") + converted: FloatArray = np.array(array, dtype=np.float64) + if ndim == 2 and converted.shape[0] == 0: + raise ValueError(f"{name} has no study: give one row per study") + return converted + + +@dataclass(frozen=True) +class _Sums: + """The capped sums of the genes every study tested, in units that are exact. + + ``up`` and ``down`` are the statistics of the ``ranked`` genes, in their order, + and ``up_p`` and ``down_p`` their Irwin-Hall p-values; ``ranks`` holds the + normalized ranks of these genes in every study. + """ + + ranked: BoolArray # per feature + up: FloatArray # per ranked gene + down: FloatArray + up_p: FloatArray + down_p: FloatArray + ranks: FloatArray # (n_studies, n_ranked) + + def ranks_of_every_feature(self) -> FloatArray: + full = np.full((self.ranks.shape[0], self.ranked.size), np.nan) + full[:, self.ranked] = self.ranks + return full + + +def _capped_sums(values: FloatArray, summed: int, where: str) -> _Sums: + """The SumRank statistics of the genes every study tested, up and down. + + The rank r of a gene among G (from 1 for the largest value) is an integer or, for + ties, a half-integer, so q = 2 (r - 1) is an integer and a sum of normalized ranks + is (sum of q) / (2 (G - 1)): it is formed exactly in integers, and compared with + the cap m (G - 1) there, so that equal sums are equal in every permutation. + """ + tested = ~np.isnan(values) + empty = np.flatnonzero(~tested.any(axis=1)) + if empty.size: + raise ValueError( + f"{where}: study {int(empty[0])} tested no gene (every value is NaN); " + "leave out a study that did not test the group and contrast" + ) + ranked = tested.all(axis=0) + n_ranked = int(np.count_nonzero(ranked)) + if n_ranked < 2: + genes = "1 gene was" if n_ranked == 1 else f"{n_ranked} genes were" + raise ValueError( + f"{where}: {genes} tested by every study, and SumRank ranks the genes " + "that every study tested against each other; it needs at least two" + ) + kept = values[:, ranked] + n_studies = kept.shape[0] + twice_up = np.empty(kept.shape, dtype=np.int64) # 2 (r - 1), r from the largest + for row in range(n_studies): + ascending = average_ranks(np.ascontiguousarray(kept[row])) # 1 = smallest + # r = G + 1 - ascending rank, so 2 (r - 1) = 2 (G - ascending rank). + twice_up[row] = np.rint(2.0 * (n_ranked - ascending)).astype(np.int64) + twice_down = 2 * (n_ranked - 1) - twice_up + scale = 2.0 * (n_ranked - 1) + statistics = [] + for twice in (twice_up, twice_down): + if summed < n_studies: + best = np.partition(twice, summed - 1, axis=0)[:summed] + else: + best = twice + total = best.sum(axis=0) + cap = summed * (n_ranked - 1) + capped = np.minimum(total, cap) + statistic = capped / scale + p_value = np.ones(statistic.size) + below = capped < cap + p_value[below] = _irwin_hall_cdf(statistic[below], summed) + statistics.append((statistic, p_value)) + return _Sums( + ranked=ranked, + up=statistics[0][0], + down=statistics[1][0], + up_p=statistics[0][1], + down_p=statistics[1][1], + ranks=twice_up / scale, + ) + + +def _full(values: FloatArray, ranked: BoolArray) -> FloatArray: + """Values of the ranked genes in an array of every feature, NaN elsewhere.""" + full = np.full(ranked.size, np.nan) + full[ranked] = values + return full + + +class _NullCounts: + """For every gene, the number of null statistics at most its own, per direction, + accumulated one permutation at a time, so that the null distribution is never + held whole.""" + + def __init__(self, observed: _Sums) -> None: + self._observed = (observed.up, observed.down) + self._counts = [np.zeros(observed.up.size, dtype=np.int64) for _ in range(2)] + self.n_values = 0 + + def add(self, permutation: _Sums) -> None: + for counts, statistic, null in zip( + self._counts, + self._observed, + (permutation.up, permutation.down), + strict=True, + ): + counts += np.searchsorted(np.sort(null), statistic, side="right") + self.n_values += permutation.up.size + + def p_values(self) -> list[FloatArray]: + return [ + np.divide(1.0 + counts, 1.0 + self.n_values, dtype=np.float64) + for counts in self._counts + ] + + +# The Irwin-Hall distribution function. + + +def _irwin_hall_cdf(x: FloatArray, n: int) -> FloatArray: + """F_n at each x, the distribution function of a sum of n uniform variables.""" + out = np.empty(x.size) + _irwin_hall_cdf_into(np.ascontiguousarray(x, dtype=np.float64), n, out) + return out + + +@jit +def _irwin_hall_cdf_into(x: FloatArray, n: int, out: FloatArray) -> None: + """Fill ``out`` with F_n(x), as a sum of values of the cardinal B-spline of order + n + 1 at t + i for the fractional part t of x, which de Boor's recurrence + evaluates with positive terms only; above n / 2 by the symmetry F_n(x) = + 1 - F_n(n - x).""" + b = np.empty(n + 1) + for g in range(x.size): + value = x[g] + if not value > 0.0: + out[g] = 0.0 if value <= 0.0 else math.nan + continue + if not value < n: + out[g] = 1.0 + continue + upper = value > 0.5 * n + if upper: + value = n - value + whole = int(math.floor(value)) + t = value - whole + # b[i] = N_k(t + i) for the cardinal B-spline N_k of order k, from k = 1 + # (the indicator of [0, 1)) to k = n + 1, updating from the top so that + # b[i - 1] still holds order k - 1. + b[0] = 1.0 + for i in range(1, n + 1): + b[i] = 0.0 + for k in range(2, n + 2): + for i in range(k - 1, -1, -1): + term = (t + i) * b[i] + if i > 0: + term += (k - t - i) * b[i - 1] + b[i] = term / (k - 1) + total = 0.0 + for i in range(whole + 1): + total += b[i] + out[g] = 1.0 - total if upper else total + + +def _studies_summed( + n_studies_summed: Any, proportion: Any, n_studies: int +) -> tuple[int, float | None]: + """The number m of ranks SumRank sums for each gene, and the proportion of the + studies that set it: ``n_studies_summed`` when given (no proportion), floor(p k) + of a proportion p, at least 1, the product rounded to 9 decimals first so that + 0.57 of 100 studies is 57, or every study (the proportion 1). + + Raises: + TypeError: If ``n_studies_summed`` is not a whole number or ``proportion`` + not a real number, or either is a numpy masked array. + ValueError: If both are given, ``n_studies_summed`` is not from 1 to + ``n_studies``, or ``proportion`` is not in (0, 1]. + """ + reject_masked(n_studies_summed, "n_studies_summed") + reject_masked(proportion, "proportion_of_studies") + if n_studies_summed is not None and proportion is not None: + raise ValueError( + "give the number of studies summed (n_studies_summed) or their proportion " + "(proportion_of_studies), not both" + ) + if n_studies_summed is not None: + if isinstance(n_studies_summed, bool | np.bool_): + raise TypeError( + "n_studies_summed must be a whole number of studies, not bool" + ) + summed = index_integer( + n_studies_summed, "n_studies_summed", expected="a whole number of studies" + ) + if not 1 <= summed <= n_studies: + raise ValueError( + f"n_studies_summed must be from 1 to the number of studies, " + f"{n_studies:,}, got {summed:,}" + ) + return summed, None + if proportion is None: + return n_studies, 1.0 + p = real_number(proportion, "proportion_of_studies", finite=True) + if not 0.0 < p <= 1.0: + raise ValueError( + f"proportion_of_studies must be in (0, 1], such as 1 to sum every study, " + f"got {p}" + ) + return max(1, math.floor(round(p * n_studies, 9))), p diff --git a/cytepool/variance_partitioning.py b/cytepool/stats/variance_fractions.py similarity index 50% rename from cytepool/variance_partitioning.py rename to cytepool/stats/variance_fractions.py index 6afbec2..2215bec 100644 --- a/cytepool/variance_partitioning.py +++ b/cytepool/stats/variance_fractions.py @@ -8,9 +8,9 @@ ``fitExtractVarPartModel`` computes. :func:`variance_fractions` partitions the variance of every feature of a samples x -features matrix of log-expression values, and :func:`variance_partitioning` of every -group (cell type) of a cube, after the steps of -:func:`~cytepool.differential_expression.dream_voom` up to voom's precision weights. +features matrix of log-expression values, and :func:`cytepool.variance_partitioning` +of every group (cell type) of a cube, after the steps of +:func:`~cytepool.dream_voom` up to voom's precision weights. The model: Every feature is fitted with the model of @@ -110,81 +110,37 @@ sizes are unequal. Biometrika 58, 545-554. doi:10.1093/biomet/58.3.545 """ -import json -from collections.abc import Mapping, Sequence from dataclasses import dataclass -from types import MappingProxyType from typing import Any, Literal import numpy as np import numpy.typing as npt -from ._analysis_records import ( - cube_record, - matched_samples, - mixed_design_record, - only_key, - software_versions, -) -from ._columns import text_column -from ._counts import effective_library_sizes -from ._group_fits import ( - COUNTS_LAYER, - FITTED, - GroupInputs, - GroupRecord, - check_sample_groups, - filter_setting, - group_positions, - min_cells_setting, - min_counts_setting, - normalization_setting, - prepare_group, - prior_weight_setting, - sample_table, - span_setting, - threads_setting, -) -from ._jit import thread_limit -from ._masked_arrays import real_array, reject_masked, unmasked_array -from ._messages import fitted_lines, listing -from .cube import Cube -from .design import Contrasts, Design -from .design.checks import _aliasing, _relation -from .stats._mean_variance_trend import scaled_prior_weights, trend_span -from .stats._mixed_model_designs import ( +from .._jit import thread_limit +from .._masked_arrays import real_array, reject_masked, unmasked_array +from .._messages import listing +from ..design import Design +from ..design.checks import _aliasing, _relation +from ._mixed_model_designs import ( MixedDesign, maximum_likelihood, mixed_design, random_intercepts, ) -from .stats.dream import _dream_weights -from .stats.linear_models import LinearModelFit, fit_linear_model -from .stats.mixed_models import MixedModelFit -from .stats.mixed_models import _fit as _fit_mixed_model -from .stats.voom import voom -from .table import Table +from .linear_models import LinearModelFit, fit_linear_model +from .mixed_models import MixedModelFit +from .mixed_models import _fit as _fit_mixed_model -__all__ = [ - "VarianceFractions", - "VariancePartitioningResult", - "variance_fractions", - "variance_partitioning", -] +__all__ = ["VarianceFractions", "variance_fractions"] FloatArray = npt.NDArray[np.float64] BoolArray = npt.NDArray[np.bool_] RESIDUALS = "residuals" +"""The name of the residual variable, the last of every partition.""" # A feature whose variance is at most this fraction of the size of its values, in # the norm the weights define, is constant to within rounding. _CONSTANT = 1e-12 -# Columns of the tables of a cube's groups other than the fractions. -_IDENTITY = ("feature_id", "symbol") -_METHOD = "variance_partitioning" -# The fits behind the precision weights, recorded in the provenance: maximum -# likelihood, as variancePartition's voomWithDreamWeights fits. -_FIXED_SETTINGS = {"weights_fit": "ml"} @dataclass(frozen=True, eq=False, repr=False) @@ -576,474 +532,3 @@ def _frozen(values: Any, dtype: type = np.float64) -> Any: array: npt.NDArray[Any] = np.array(values, dtype=dtype) array.flags.writeable = False return array - - -# Every group of a cube. - - -class VariancePartitioningResult: - """The fractions of variance of every group of a cube, from - :func:`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_id`` and, 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 in - ``variables`` (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_used`` and ``n_samples_left_out``; and ``n_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 - :class:`~cytepool.differential_expression.DifferentialExpressionResult`: - ``group_id``, ``sample_id``, ``n_cells`` (when the cube records cell numbers), - ``used``, ``reason``, and the ``library_size`` and ``norm_factor`` that entered the - fit; with ``prior_weights``, the ``prior_weight`` of each sample. - - Built by :func:`variance_partitioning`. Immutable. - - Raises: - TypeError: If ``variables`` is not a sequence of names, ``summary`` or - ``samples`` not a ``Table``, ``tables`` not a mapping of group id to - ``Table``, or ``provenance`` not a mapping that JSON can write. - ValueError: If ``provenance`` holds a NaN or an infinity. - """ - - __slots__ = ("_variables", "_summary", "_samples", "_tables", "_record") - - _variables: tuple[str, ...] - _summary: Table - _samples: Table - _tables: Mapping[str, Table] - _record: str - - def __init__( - self, - variables: Sequence[str], - summary: Table, - samples: Table, - tables: Mapping[str, Table], - provenance: Mapping[str, Any], - ) -> None: - if isinstance(variables, str) or not all( - isinstance(name, str) for name in variables - ): - raise TypeError("variables must be a sequence of variable names (str)") - for name, table in (("summary", summary), ("samples", samples)): - if not isinstance(table, Table): - raise TypeError(f"{name} must be a Table, got {type(table).__name__}") - if not isinstance(tables, Mapping): - raise TypeError( - f"tables must be a mapping of group id to Table, got " - f"{type(tables).__name__}" - ) - for key, table in tables.items(): - if not isinstance(key, str) or not isinstance(table, Table): - raise TypeError( - f"tables must map group ids (str) to Tables, got {key!r}: " - f"{type(table).__name__}" - ) - if not isinstance(provenance, Mapping): - raise TypeError( - f"provenance must be a mapping, got {type(provenance).__name__}" - ) - state = { - "_variables": tuple(variables), - "_summary": summary, - "_samples": samples, - "_tables": MappingProxyType(dict(tables)), - # JSON holds no arrays: json.dumps raises TypeError for one. - "_record": json.dumps(provenance, allow_nan=False), - } - for name, value in state.items(): - object.__setattr__(self, name, value) - - def __setattr__(self, name: str, value: object) -> None: - raise AttributeError("VariancePartitioningResult is immutable") - - def __delattr__(self, name: str) -> None: - raise AttributeError("VariancePartitioningResult is immutable") - - def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: - return ( - VariancePartitioningResult, - ( - self._variables, - self._summary, - self._samples, - dict(self._tables), - json.loads(self._record), - ), - ) - - @property - def variables(self) -> tuple[str, ...]: - """The variables, in the order of the columns of the tables, residuals last.""" - return self._variables - - @property - def groups(self) -> tuple[str, ...]: - """The ids of the groups analysed, in order.""" - return tuple(self._summary["group_id"].tolist()) - - @property - def summary(self) -> Table: - """The status, samples and features of every group.""" - return self._summary - - @property - def samples(self) -> Table: - """Every design sample of every group, used in its fit or left out, and why.""" - return self._samples - - @property - def tables(self) -> Mapping[str, Table]: - """The table of fractions of every group that was fitted, by group id.""" - return self._tables - - @property - def settings(self) -> dict[str, Any]: - """The settings of the analysis, as a new dict (also in the provenance).""" - settings: dict[str, Any] = json.loads(self._record)["settings"] - return settings - - @property - def provenance(self) -> dict[str, Any]: - """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 with ``prior_weights`` the prior weights of - its samples. The cost of the cube's record is that of - :func:`~cytepool.differential_expression.limma_voom`'s ("Provenance"). - """ - record: dict[str, Any] = json.loads(self._record) - return record - - def table(self, group: str | None = None) -> Table: - """Return the table of fractions of one group. - - Args: - group: A group id; it may be left out when the result has one group. - - Raises: - ValueError: If ``group`` is left out and the result has several groups. - KeyError: If the group was not analysed or not fitted; the message says - why. - """ - group = only_key(group, list(self.groups), "group", "groups") - found = self._tables.get(group) - if found is not None: - return found - record = {entry["group_id"]: entry for entry in self.provenance["groups"]} - if group not in record: - raise KeyError( - f"no group {group!r} was analysed; groups are: " - f"{listing(list(record)) or '(none)'}" - ) - entry = record[group] - raise KeyError( - f"group {group!r} was not fitted ({entry['status']}): {entry['reason']}" - ) - - def __repr__(self) -> str: - statuses = self._summary["status"].tolist() - fitted = statuses.count(FITTED) - lines = [ - f"VariancePartitioningResult: {len(statuses):,} group" - f"{'' if len(statuses) == 1 else 's'}, {fitted:,} fitted; variables: " - + ", ".join(self._variables), - ] - for row in range(len(statuses)): - group = self._summary["group_id"][row] - if statuses[row] != FITTED: - lines.append(f" {group}: not fitted ({statuses[row]})") - continue - used = int(self._summary["n_samples_used"][row]) - features = int(self._summary["n_features"][row]) - lines.append(f" {group}: {used:,} samples, {features:,} features") - return fitted_lines("\n".join(lines)) - - -@dataclass -class _GroupFit(GroupRecord): - """What happened in one group, and its table of fractions.""" - - table: Table | None = None - - -def variance_partitioning( - cube: Cube, - design: Design, - *, - groups: str | Sequence[str] | None = None, - min_cells: int = 10, - min_counts: float = 0, - expression_filter: bool | Mapping[str, Any] = True, - normalization: Literal["tmm"] | None = "tmm", - span: float | None = None, - prior_weights: Literal["n_cells"] | None = None, - method: Literal["ml", "reml"] = "ml", - n_threads: int | None = None, -) -> VariancePartitioningResult: - """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 ``fitExtractVarPartModel`` - on the ``voomWithDreamWeights`` output of every cell type. For each group, the - samples are left out, the features filtered and the counts normalized as for - :func:`~cytepool.differential_expression.dream_voom`; voom's precision weights - come from the mean-variance trend of the design's mixed model, fitted by maximum - likelihood (:func:`~cytepool.stats.dream.voom_with_dream_weights`), or from - limma's voom for a design without random effects; and the fractions of every - feature are those of :func:`variance_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. - - Args: - cube: A cube with a ``counts`` layer. - design: A :class:`~cytepool.design.Design` with an intercept, matched to the - cube by ``sample_id`` as for - :func:`~cytepool.differential_expression.limma_voom`, whose categorical - variables are random intercepts and continuous variables fixed effects, - such as ``Design(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 ``filterByExpr`` defaults with the - group's fixed effects, a mapping of changed settings or of the column of - ``"sample_groups"``, as for - :func:`~cytepool.differential_expression.limma_voom`, or False. With a - random effect that separates groups whose expression may differ, such - as the tissues of each donor, ``{"sample_groups": "tissue"}`` keeps the - features expressed in any one of them. - 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 ``voom`` - does. variancePartition's ``voomWithDreamWeights`` uses 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, as - ``voomWithDreamWeights`` fits 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 ``cube`` is not a Cube, ``design`` not a Design, an option has - the wrong type, or ``groups``, ``min_cells``, ``expression_filter`` or - ``n_threads`` is or holds a numpy masked array. - ValueError: If the design cannot be partitioned (as for - :func:`variance_fractions`), has no ``sample_id`` column, names samples - the cube does not have or names a sample twice, a variable is named - ``"feature_id"`` or ``"symbol"``, an option is invalid, or - ``prior_weights`` is ``"n_cells"`` and the cube does not record cell - numbers. - KeyError: If a group is not in the cube. - """ - reject_masked(groups, "groups") - if not isinstance(cube, Cube): - raise TypeError(f"cube must be a Cube, got {type(cube).__name__}") - terms = _checked_terms(design) - clashing = [name for name in terms.variables if name in _IDENTITY] - if clashing: - raise ValueError( - f"a variable is named {clashing[0]!r}, a column of the tables of features; " - "rename it in the sample table" - ) - settings = { - "min_cells": min_cells_setting(min_cells), - "min_counts": min_counts_setting(min_counts), - "expression_filter": filter_setting(expression_filter), - "normalization": normalization_setting(normalization), - "span": span_setting(span), - "prior_weights": prior_weight_setting(prior_weights), - "method": "ml" if maximum_likelihood(method) else "reml", - "n_threads": threads_setting(n_threads), - } - if settings["prior_weights"] == "n_cells" and cube.n_cells is None: - raise ValueError( - "prior_weights='n_cells' weights each sample by its number of cells, which " - "this cube does not record (a cube of bulk samples has none); leave " - "prior_weights at None" - ) - check_sample_groups(settings, design.samples) - cube_rows, order = matched_samples( - cube, - design, - no_ids="the design's sample table has no 'sample_id' column, so its rows cannot " - "be matched to the cube's samples; build the Design from cube.samples, or from " - "a table whose 'sample_id' column holds cube sample ids", - text_ids="the cube's does", - data="the cube", - data_ids="the cube's sample ids", - ) - positions = group_positions(cube, groups) - inputs = GroupInputs(_METHOD, cube, order, cube_rows, None, settings) - fits = [_fit_group(inputs, design, terms, g) for g in positions] - sample_ids = cube.samples["sample_id"][cube_rows] - no_contrasts = Contrasts(np.empty((len(design.columns), 0)), (), design.columns) - return VariancePartitioningResult( - terms.variables, - _summary_table(fits), - sample_table(cube, cube_rows, sample_ids, positions, fits, settings), - {fit.group_id: fit.table for fit in fits if fit.table is not None}, - { - "method": _METHOD, - "versions": software_versions(), - "cube": cube_record(cube), - "layer": COUNTS_LAYER, - "design": mixed_design_record(design, no_contrasts), - "settings": {**settings, **_FIXED_SETTINGS}, - "groups": [_group_record(fit, sample_ids, settings) for fit in fits], - }, - ) - - -def _fit_group(inputs: GroupInputs, design: Design, terms: _Terms, g: int) -> _GroupFit: - """Partition the variance of one group, or record why it cannot be done.""" - cube, settings = inputs.cube, inputs.settings - mixed = bool(design.random) - fit, data = prepare_group(_GroupFit, inputs, design, g, mixed=mixed) - if data is None: - return fit - y = data.counts - n_used, n_tested = y.shape - with_counts = int(np.count_nonzero(y.any(axis=0))) - if with_counts < 2: - return fit.stop( - "too_few_features", - "the mean-variance trend of the precision weights needs at least two " - f"features with counts, and {with_counts} of the {n_tested} features " - f"{'has' if with_counts == 1 else 'have'} counts", - ) - ml = settings["method"] == "ml" - with thread_limit(settings["n_threads"]): - try: - if data.mixed is not None: - sizes = np.array(effective_library_sizes(y, None, data.factors)) - fraction = trend_span(settings["span"], n_tested) - prior = ( - None - if data.prior is None - else scaled_prior_weights(data.prior, y.shape) - ) - weighted = _dream_weights(y, sizes, fraction, data.mixed, prior, True) - else: - weighted = voom( - y, - data.x, - norm_factors=data.factors, - span=settings["span"], - weights=data.prior, - ) - except ValueError as error: # the trend cannot give weights for these data - return fit.stop("failed", f"precision weights: {error}") - if data.mixed is not None: - fractions = _mixed_fractions( - weighted.log_cpm, weighted.weights, ml, data.mixed, terms - ) - else: - rows = np.ascontiguousarray(weighted.weights.T) - linear = fit_linear_model( - weighted.log_cpm, - data.x, - weights=weighted.weights, - coefficient_names=data.columns, - n_threads=settings["n_threads"], - ) - fractions = _linear_fractions( - weighted.log_cpm, data.x, rows, linear, ml, terms - ) - fit.voom_span = weighted.span - fitted = np.all(np.isfinite(fractions.fractions), axis=1) - fit.features_not_fitted = int(np.count_nonzero(~fitted)) - fit.features_not_converged = int(np.count_nonzero(fitted & ~fractions.converged)) - fit.library_sizes = data.sizes - fit.norm_factors = np.ones(n_used) if data.factors is None else data.factors - fit.prior_weights = data.prior - features = cube.features - columns: dict[str, Any] = {"feature_id": features["feature_id"][fit.tested]} - if "symbol" in features: - columns["symbol"] = features["symbol"][fit.tested] - for position, name in enumerate(fractions.variables): - columns[name] = fractions.fractions[:, position] - fit.table = Table(columns) - return fit - - -def _summary_table(fits: list[_GroupFit]) -> Table: - return Table( - { - "group_id": text_column([fit.group_id for fit in fits]), - "status": text_column([fit.status for fit in fits]), - "reason": text_column([fit.reason for fit in fits]), - "n_samples_used": np.array( - [np.count_nonzero(fit.used) for fit in fits], dtype=np.int64 - ), - "n_samples_left_out": np.array( - [np.count_nonzero(~fit.used) for fit in fits], dtype=np.int64 - ), - "n_features": np.array( - [fit.tested.size if fit.status == FITTED else 0 for fit in fits], - dtype=np.int64, - ), - } - ) - - -def _group_record( - fit: _GroupFit, sample_ids: np.ndarray, settings: Mapping[str, Any] -) -> dict[str, Any]: - fitted = fit.status == FITTED - record: dict[str, Any] = { - "group_id": fit.group_id, - "status": fit.status, - "reason": fit.reason, - "samples": sample_ids[fit.used].tolist(), - "left_out": { - sample_id: reason - for sample_id, reason in zip(sample_ids.tolist(), fit.reasons, strict=True) - if reason - }, - "aliased_columns": list(fit.aliased), - "n_features": int(fit.tested.size) if fitted else 0, - "voom_span": fit.voom_span, - "features_not_fitted": fit.features_not_fitted if fitted else None, - "features_not_converged": fit.features_not_converged if fitted else None, - } - if settings["prior_weights"] is not None: - weights = fit.prior_weights - record["prior_weights"] = ( - weights.tolist() if fitted and weights is not None else None - ) - return record diff --git a/cytepool/stats/voom.py b/cytepool/stats/voom.py index cac149b..7510c9c 100644 --- a/cytepool/stats/voom.py +++ b/cytepool/stats/voom.py @@ -142,7 +142,7 @@ class VoomResult: """Log2 counts per million and their precision weights, from :func:`voom`. - Fit a linear model with ``fit_linear_model(result.log_cpm, design, + Fit a linear model with ``fit_linear_model(result.log2_cpm, design, weights=result.weights)``, or, for the weights of :func:`~cytepool.stats.dream.voom_with_dream_weights`, a mixed model with ``fit_mixed_model``. Arrays with a sample axis have one row per sample; @@ -150,7 +150,7 @@ class VoomResult: counts. The arrays are read-only views; the constructor does not copy them. Attributes: - log_cpm: ``(n_samples, n_features)`` log2 counts per million, + log2_cpm: ``(n_samples, n_features)`` log2 counts per million, ``log2((r + 0.5) / (R + 1) * 1e6)`` with R the effective library size. weights: ``(n_samples, n_features)`` precision weights: the inverse of each observation's variance predicted by the trend, times the prior weights @@ -184,7 +184,7 @@ class VoomResult: finite. """ - log_cpm: FloatArray + log2_cpm: FloatArray weights: FloatArray effective_library_sizes: FloatArray mean_log_count: FloatArray @@ -194,7 +194,12 @@ class VoomResult: sample_weights: FloatArray | None = None def __post_init__(self) -> None: - for name in ("log_cpm", "weights", "effective_library_sizes", "sample_weights"): + for name in ( + "log2_cpm", + "weights", + "effective_library_sizes", + "sample_weights", + ): reject_masked(getattr(self, name), name) # NaN marks the features that the trend leaves out. for name in ("mean_log_count", "sqrt_sd", "sqrt_sd_trend"): @@ -207,16 +212,16 @@ def __post_init__(self) -> None: f"span must be in (0, 1], or NaN when no trend is fitted, got {span}" ) object.__setattr__(self, "span", float(span)) - log_cpm = np.asarray(self.log_cpm, dtype=np.float64) - if log_cpm.ndim != 2: + log2_cpm = np.asarray(self.log2_cpm, dtype=np.float64) + if log2_cpm.ndim != 2: raise ValueError( - f"log_cpm must be a 2-D array (samples x features), got shape " - f"{log_cpm.shape}" + f"log2_cpm must be a 2-D array (samples x features), got shape " + f"{log2_cpm.shape}" ) - n_samples, n_features = log_cpm.shape + n_samples, n_features = log2_cpm.shape shapes = { - "log_cpm": log_cpm.shape, - "weights": log_cpm.shape, + "log2_cpm": log2_cpm.shape, + "weights": log2_cpm.shape, "effective_library_sizes": (n_samples,), "mean_log_count": (n_features,), "sqrt_sd": (n_features,), @@ -237,7 +242,7 @@ def __post_init__(self) -> None: object.__setattr__(self, name, view) def __repr__(self) -> str: - n_samples, n_features = self.log_cpm.shape + n_samples, n_features = self.log2_cpm.shape in_trend = int(np.count_nonzero(~np.isnan(self.sqrt_sd))) quality = "" if self.sample_weights is None else ", with sample weights" return ( @@ -262,7 +267,7 @@ def voom( which checks it. Filter out features with very low counts first, for example with :func:`~cytepool.normalization.filter_by_expression`, as limma advises. - Continue with ``fit_linear_model(result.log_cpm, design, + Continue with ``fit_linear_model(result.log2_cpm, design, weights=result.weights)`` and :func:`~cytepool.stats.empirical_bayes.moderated_statistics`. With observation weights, limma's ``contrasts.fit`` approximates the standard errors of contrasts @@ -275,7 +280,7 @@ def voom( by library size, such as CPM or TPM: voom needs the library sizes. design: ``(n_samples, n_coefficients)`` design matrix of full column rank, or anything else that ``fit_linear_model`` accepts, such as a - :class:`~cytepool.design.Design`. + :class:`~cytepool.Design`. library_sizes: Library size of each sample; defaults to the total counts. Must be positive. norm_factors: Normalization factor of each sample, such as TMM factors from @@ -329,7 +334,7 @@ def voom( message = _NO_REPLICATION if prior is None else _NO_RESIDUAL_DF warnings.warn(message, RuntimeWarning, stacklevel=2) return VoomResult( - log_cpm=trend.log_cpm, + log2_cpm=trend.log2_cpm, weights=_times_prior(trend.weights, prior), effective_library_sizes=sizes, mean_log_count=trend.mean_log_count, @@ -360,7 +365,7 @@ def voom_with_quality_weights( the sample weights. Samples that follow the model less closely, such as pseudobulk samples pooled from few cells or from an outlying donor, get lower weights instead of being removed. Continue as after :func:`voom`, with - ``fit_linear_model(result.log_cpm, design, weights=result.weights)``. + ``fit_linear_model(result.log2_cpm, design, weights=result.weights)``. Args: counts: ``(n_samples, n_features)`` counts, as for :func:`voom`. @@ -446,15 +451,15 @@ def _quality_weighted_voom( notes.append(_too_few_df_message(y.shape[0] - x.shape[1])) second, quality = first, np.ones(y.shape[0]) else: - log_variances, first_notes = _estimate(first.log_cpm, x, first.weights, model) + log_variances, first_notes = _estimate(first.log2_cpm, x, first.weights, model) second, _ = _voom_trend(y, design, sizes, fraction, _weights_of(log_variances)) log_variances, second_notes = _estimate( - second.log_cpm, x, second.weights, model + second.log2_cpm, x, second.weights, model ) notes += list(dict.fromkeys(first_notes + second_notes)) quality = _weights_of(log_variances) result = VoomResult( - log_cpm=second.log_cpm, + log2_cpm=second.log2_cpm, weights=second.weights * quality[:, np.newaxis], effective_library_sizes=sizes, mean_log_count=second.mean_log_count, @@ -482,14 +487,14 @@ def _voom_trend( fraction: The span. prior: Prior weights from ``_prior_weights``, or None. """ - log_cpm = voom_log_cpm(y, sizes) - fit = fit_linear_model(log_cpm, design, weights=prior) + log2_cpm = voom_log_cpm(y, sizes) + fit = fit_linear_model(log2_cpm, design, weights=prior) mean_log_count = voom_mean_log_count(fit.mean_expression, sizes, y) if not np.any(fit.df_residual > 0.0): # limma returns no span then (found by probing). nan = np.full(y.shape[1], np.nan) unit = Trend( - log_cpm, np.ones_like(log_cpm), mean_log_count, nan, nan.copy(), math.nan + log2_cpm, np.ones_like(log2_cpm), mean_log_count, nan, nan.copy(), math.nan ) return unit, False in_trend = (y > 0.0).any(axis=0) & (fit.df_residual > 0.0) @@ -497,11 +502,11 @@ def _voom_trend( np.ones(y.shape, dtype=bool) if prior is None else _positive(prior, y.shape) ) trend = trend_weights( - log_cpm, + log2_cpm, mean_log_count, fit.sigma, in_trend, - _fitted_values(fit, log_cpm, prior), + _fitted_values(fit, log2_cpm, prior), sizes, fraction, weighted, @@ -511,7 +516,7 @@ def _voom_trend( def _fitted_values( - fit: LinearModelFit, log_cpm: FloatArray, prior: FloatArray | None + fit: LinearModelFit, log2_cpm: FloatArray, prior: FloatArray | None ) -> FloatArray: """Fitted log2 CPM of every observation, NaN where it is not determined. @@ -532,7 +537,7 @@ def _fitted_values( root = np.sqrt(weights[rows]) design = fit.design[rows] solution = np.linalg.lstsq( - design * root[:, np.newaxis], log_cpm[rows, g] * root, rcond=None + design * root[:, np.newaxis], log2_cpm[rows, g] * root, rcond=None )[0] fitted[rows, g] = design @ solution return fitted diff --git a/cytepool/study_results.py b/cytepool/study_results.py index 7d11bad..655839c 100644 --- a/cytepool/study_results.py +++ b/cytepool/study_results.py @@ -2,33 +2,36 @@ Studies of one question, such as cohorts of a disease, are compared or combined only once each is analysed on the collection's axes: its aligned cube -(:meth:`~cytepool.cube.CubeCollection.aligned_cube`), with a design built from its own -samples. :func:`analyse_each_study` runs one analysis per study that way, and -:class:`StudyResults` keeps the results with their collection: it lines them up gene -by gene (:meth:`StudyResults.table`, as -:func:`~cytepool.meta_analysis.align_study_results` does) and gives one column of every -study as a studies x features matrix (:meth:`StudyResults.matrix`), the form that -combined p-values (:func:`~cytepool.stats.combined_p_values.combine_p_values`) and -SumRank (:func:`~cytepool.sumrank.sumrank`) take. +(:meth:`~cytepool.CubeCollection.aligned_cube`), with a design built from its own +samples. :func:`~cytepool.analyse_each_study` runs one analysis per study that way, and +:class:`~cytepool.StudyResults` keeps the results with their collection: it lines them +up feature by feature (:meth:`~cytepool.StudyResults.table`), with the reason a study +has no result for a feature (it did not measure it, did not test it, or did not analyse +the group or contrast), and gives one column of every study as a studies x features +matrix (:meth:`~cytepool.StudyResults.matrix`), the form that combined p-values +(:func:`~cytepool.stats.combined_p_values.combine_p_values`) take. Every function that +combines studies takes it: :func:`~cytepool.meta_analysis`, +:func:`~cytepool.replication`, :func:`~cytepool.sumrank` and +:func:`~cytepool.held_out_prediction`. Memory: Each result keeps the cube it analysed (see - :class:`~cytepool.differential_expression.DifferentialExpressionResult`), here the - study's aligned cube. The aligned cube of a cube read with - :func:`~cytepool.io.read_cube` stays on disk; that of a cube in memory, when its - groups or features are selected or renamed, holds a copy of the study's values, - which the results keep for as long as they exist. + :class:`~cytepool.DifferentialExpressionResult`), here the study's aligned cube. The + aligned cube of a cube read with :func:`~cytepool.read_cube` stays on disk; that of + a cube in memory, when its groups or features are selected or renamed, holds a copy + of the study's values, which the results keep for as long as they exist. """ from collections.abc import Iterator, Mapping from types import MappingProxyType -from typing import Any, Literal +from typing import Any import numpy as np +from ._analysis_records import absence, study_keys, study_union from ._columns import text_column from ._masked_arrays import reject_masked -from ._messages import fitted_lines, quoted +from ._messages import fitted_lines, listing, quoted from .cube import CubeCollection from .design import Design from .differential_expression import ( @@ -37,12 +40,16 @@ limma_trend, limma_voom, ) -from .meta_analysis import align_study_results from .table import Table __all__ = ["StudyResults", "analyse_each_study"] _ANALYSES = (limma_voom, limma_trend, dream_voom) +# Why a study has no result for a feature, as StudyResults.table gives it. +_NOT_TESTED = ( + "the study measured the feature and tested the group and contrast, but not the " + "feature, as when its expression filter left the feature out" +) class StudyResults(Mapping[str, DifferentialExpressionResult]): @@ -57,9 +64,8 @@ class StudyResults(Mapping[str, DifferentialExpressionResult]): Args: collection: The collection whose aligned cubes the results analysed. results: The result of every study of the collection, by study name, from - :func:`~cytepool.differential_expression.limma_voom`, - :func:`~cytepool.differential_expression.limma_trend` or - :func:`~cytepool.differential_expression.dream_voom`. + :func:`~cytepool.limma_voom`, :func:`~cytepool.limma_trend` or + :func:`~cytepool.dream_voom`. Raises: TypeError: If ``collection`` is not a ``CubeCollection``, ``results`` not a @@ -209,31 +215,52 @@ def summary(self) -> Table: def table(self, group: str | None = None, contrast: str | None = None) -> Table: """Line up the studies' results of one group and contrast, feature by feature. - :func:`~cytepool.meta_analysis.align_study_results` of the collection and - the results: a row for every study and every feature of the collection, with - ``status`` and ``reason`` saying whether the study tested the feature, and - why not, and the studies' numeric columns, NaN where a study did not test - the feature. + The table has a row for every study and every feature of the collection: the + studies in order, and the features in the collection's order within each + study, as :meth:`matrix` lays them out. ``status`` says whether the study + tested the feature, and why not: + + - ``"tested"``: the study's table of the group and contrast has the feature; + - ``"not_tested"``: the study measured the feature and has a table of the + group and contrast, without the feature, as when its expression filter + left it out; + - ``"not_measured"``: the study did not measure the feature + (:attr:`CubeCollection.measured `), so no + analysis of it could test it; + - ``"not_analysed"``: the study measured the feature but has no table of the + group and contrast: it does not have the group, its result leaves the group + out, the group was not fitted, or the contrast cannot be estimated from its + samples; ``reason`` says which, in the words of the study's result. + + The columns are ``study``, ``feature_id``, ``symbol`` (when the collection's + features have it), ``status``, ``reason`` (empty where tested), and then the + numeric columns of the studies' tables, such as ``log2_fold_change``, + ``standard_error``, ``t``, ``p_value`` and ``adjusted_p_value``, in the order + in which the studies first have them, NaN where a study did not test the + feature. A missing feature is thus never a zero, and a feature that a study + did not measure is told apart from one that it measured and left out. Args: group: An aligned group id; it may be left out when the studies analysed one group. contrast: A contrast name, or the name of an F-test of - :func:`~cytepool.differential_expression.dream_voom`; it may be - left out when the studies tested one. + :func:`~cytepool.dream_voom`; it may be left out when the studies tested + one. Returns: A table of ``collection.n_studies * collection.n_features`` rows. Raises: ValueError: If a key is left out and the studies analysed several, or a - study's result did not analyse its aligned cube. + study's result did not analyse its aligned cube: it analyses a group + that the study does not have in the collection, or tests a feature + that the study did not measure there. KeyError: If ``group`` is not a group of the collection, or no study tested ``contrast``. TypeError: If a key is not a string. """ - group, contrast = self._keys(group, contrast) - return align_study_results(self._collection, self._results, group, contrast) + group, contrast = study_keys(self._results, group, contrast) + return _aligned_table(self._collection, self._results, group, contrast) def matrix( self, column: str, group: str | None = None, contrast: str | None = None @@ -292,8 +319,8 @@ def matrix( return values def __repr__(self) -> str: - groups = self._union("groups") - contrasts = self._union("contrasts") + groups = study_union(self._results, "groups") + contrasts = study_union(self._results, "contrasts") lines = [ f"StudyResults of {len(self._results):,} " f"stud{'y' if len(self._results) == 1 else 'ies'}: {len(groups):,} " @@ -309,42 +336,6 @@ def __repr__(self) -> str: ) return fitted_lines("\n".join(lines)) - def _union(self, what: Literal["groups", "contrasts"]) -> list[str]: - """The groups, or the contrasts and F-tests, of any study, in order.""" - if what == "groups": - return list( - dict.fromkeys( - group - for result in self._results.values() - for group in result.groups - ) - ) - return list( - dict.fromkeys( - name - for result in self._results.values() - for name in (*result.contrasts.names, *result.f_tests) - ) - ) - - def _keys(self, group: Any, contrast: Any) -> tuple[Any, Any]: - """The group and contrast asked for, or the only ones the studies analysed.""" - found = [] - for name, value, what in ( - ("group", group, "groups"), - ("contrast", contrast, "contrasts"), - ): - if value is None: - available = self._union(what) # type: ignore[arg-type] - if len(available) != 1: - raise ValueError( - f"the studies analysed {len(available)} {what}; name one with " - f"{name}, one of {quoted(available)}" - ) - value = available[0] - found.append(value) - return found[0], found[1] - def analyse_each_study( collection: CubeCollection, @@ -359,22 +350,20 @@ def analyse_each_study( """Analyse every study of a collection alike, each on its aligned cube. For each study, in the collection's order: its aligned cube - (:meth:`~cytepool.cube.CubeCollection.aligned_cube`), a - :class:`~cytepool.design.Design` built from the cube's own samples with the - formula, random effects, levels and contrasts given, and ``analysis`` of the - cube with that design and ``options``. The loop a meta-analysis or a replication - analysis starts from, in one call: ``analyse_each_study(collection, "~ - condition", levels={"condition": ["control", "case"]}, contrasts={"case_vs_control": - "conditioncase"})``. A study whose design or analysis fails stops the run, with - a note that names the study. + (:meth:`~cytepool.CubeCollection.aligned_cube`), a :class:`~cytepool.Design` built + from the cube's own samples with the formula, random effects, levels and contrasts + given, and ``analysis`` of the cube with that design and ``options``. The loop a + meta-analysis or a replication analysis starts from, in one call: + ``analyse_each_study(collection, "~ condition", levels={"condition": ["control", "case"]}, contrasts={"case_vs_control": "conditioncase"})``. + A study whose design or analysis fails stops the run, with a note that names the + study. Args: collection: The studies, aligned. formula: The design's formula, such as ``"~ condition"``, over the columns of each study's sample table. - analysis: :func:`~cytepool.differential_expression.limma_voom` (by default), - :func:`~cytepool.differential_expression.limma_trend` or - :func:`~cytepool.differential_expression.dream_voom`. + analysis: :func:`~cytepool.limma_voom` (by default), + :func:`~cytepool.limma_trend` or :func:`~cytepool.dream_voom`. random: The design's random effects, such as ``"(1 | donor)"``, for ``dream_voom``. levels: The levels of the design's factors, the reference first, such as @@ -428,3 +417,113 @@ def analyse_each_study( error.add_note(f"(in study {study!r} of the collection)") raise return StudyResults(collection, results) + + +def _aligned_table( + collection: CubeCollection, + results: Mapping[str, DifferentialExpressionResult], + group: str, + contrast: str, +) -> Table: + """The table of :meth:`StudyResults.table`, of results checked to be one per + study of the collection.""" + studies = collection.studies + for name, value in (("group", group), ("contrast", contrast)): + reject_masked(value, name) + if not isinstance(value, str): + raise TypeError(f"{name} must be a string, got {type(value).__name__}") + group_ids = collection.groups["group_id"].tolist() + if group not in group_ids: + raise KeyError( + f"no group {group!r} in the collection; its groups are: " + f"{listing(group_ids) or '(none)'}" + ) + tested_names = list( + dict.fromkeys( + name + for study in studies + for name in (*results[study].contrasts.names, *results[study].f_tests) + ) + ) + if contrast not in tested_names: + raise KeyError( + f"no study tested the contrast {contrast!r}; the studies tested: " + f"{listing(tested_names) or '(none)'}" + ) + members = collection.group_members + held: dict[str, set[str]] = {study: set() for study in studies} + for study, aligned in zip( + members["study"].tolist(), members["group_id"].tolist(), strict=True + ): + held[study].add(aligned) + feature_ids = collection.features["feature_id"].tolist() + index = {feature: position for position, feature in enumerate(feature_ids)} + n_features = len(feature_ids) + measured = collection.measured + statuses: list[np.ndarray] = [] + reasons: list[list[str]] = [] + values: dict[str, list[tuple[int, np.ndarray, np.ndarray]]] = {} + for row, study in enumerate(studies): + result = results[study] + foreign = [name for name in result.groups if name not in held[study]] + if foreign: + raise ValueError( + f"the result of study {study!r} analyses the group" + f"{'' if len(foreign) == 1 else 's'} {quoted(foreign)}, which the " + "study does not have in this collection; analyse the study's aligned " + f"cube, collection.aligned_cube({study!r})" + ) + table = None + if group not in held[study]: + why = f"study {study!r} has no group {group!r}" + else: + table = result.tables.get((group, contrast)) + why = "" if table is not None else absence(result, group, contrast) + status = np.where(measured[row], "not_analysed", "not_measured").astype(object) + reason = [ + why if flag else f"study {study!r} did not measure the feature" + for flag in measured[row].tolist() + ] + if table is not None: + positions = np.array( + [index.get(feature, -1) for feature in table["feature_id"].tolist()], + dtype=np.intp, + ) + outside = (positions < 0) | ~measured[row, np.maximum(positions, 0)] + if outside.any(): + feature = table["feature_id"][int(np.flatnonzero(outside)[0])] + raise ValueError( + f"the result of study {study!r} tests the feature {feature!r}, " + "which the study did not measure in this collection; analyse the " + f"study's aligned cube, collection.aligned_cube({study!r})" + ) + status[measured[row]] = "not_tested" + status[positions] = "tested" + for position in np.flatnonzero(measured[row]).tolist(): + reason[position] = _NOT_TESTED + for position in positions.tolist(): + reason[position] = "" + for name in table.columns: + column = table[name] + if name not in ("feature_id", "symbol") and column.dtype.kind in "biuf": + values.setdefault(name, []).append((row, positions, column)) + statuses.append(status) + reasons.append(reason) + columns: dict[str, Any] = { + "study": text_column([study for study in studies for _ in range(n_features)]), + "feature_id": text_column(feature_ids * len(studies)), + } + if "symbol" in collection.features: + columns["symbol"] = text_column( + collection.features["symbol"].tolist() * len(studies) + ) + columns["status"] = text_column( + [status for array in statuses for status in array.tolist()] + ) + columns["reason"] = text_column([text for part in reasons for text in part]) + for name, parts in values.items(): + column = np.full((len(studies), n_features), np.nan) + for row, positions, found in parts: + column[row, positions] = found + columns[name] = column.reshape(-1) + return Table(columns) diff --git a/cytepool/sumrank.py b/cytepool/sumrank.py deleted file mode 100644 index a4c7edc..0000000 --- a/cytepool/sumrank.py +++ /dev/null @@ -1,1059 +0,0 @@ -"""SumRank: genes whose differential expression ranks high in study after study. - -Most genes that one single-cell study finds differentially expressed are not found by -the next (Nakatsuka et al. 2025). SumRank asks instead whether a gene ranks high in -every study: each study ranks its genes by their signed evidence of change, and a -gene's ranks are summed over the studies, so that a gene ranked high everywhere has a -small sum and a gene found by one study alone does not, however small its p-value -there. :func:`sumrank` computes the statistic and its p-values from a signed statistic -of every gene in every study; :func:`differential_expression_sumrank` does it for the -results of the studies of a :class:`~cytepool.cube.CubeCollection`, one group (cell -type) and contrast at a time, with p-values from permutations that re-run each study's -analysis on its samples with their labels permuted. - -The statistic, as the paper defines it and its authors' code (Apache-2.0) computes it: - 1. Each study ranks the genes by their signed statistic, from 1 for the largest - (the gene most up); tied genes share the average of their ranks, as R's - ``rank``, which the authors' code calls, gives them. Results of - differential expression give the signed -log10 p-value: -log10(p_value), - negative where the log2 fold change is negative, as the paper ranks DESeq2's - results. - 2. A gene of rank r among G genes has the normalized rank u = (r - 1) / (G - 1): - 0 for the gene most up and 1 for the gene most down. - 3. For each gene, the m smallest of its normalized ranks in the k studies are - summed: those of the studies that rank it best, which leaves out studies that - rank it low for reasons of their own. m = floor(p k), at least 1, for the - proportion p of the studies (``proportion_of_studies``); the product is rounded - to 9 decimals first, so that 0.57 of 100 studies is 57. The paper sums every - study when there are few (p = 1 for its 3 to 6 studies of Parkinson's, - Huntington's disease and schizophrenia) and chose p by prediction in held-out - studies when there are many (0.65 of 17 studies of Alzheimer's disease, 0.55 - of 16 of COVID-19); :func:`~cytepool.held_out_prediction.held_out_prediction` - gives that prediction for a chosen p. - 4. A sum above m / 2, the mean of a sum of m uniform ranks, is set to m / 2, so - that genes without consistent evidence share one value. - - The result is the statistic of genes up (``up_statistic``): small for a gene that - ranks high in its best m studies. Genes down have the same statistic of the - negated statistics (``down_statistic``), whose normalized ranks are 1 - u, as the - paper tests genes down by testing controls against cases. - -Genes that a study did not measure or did not test: - The genes ranked are those that every study tested (a finite or infinite - statistic, not NaN), as the paper ranks the genes that every dataset measured, - the intersection of their genes. A rank says where a gene stands among the genes - ranked with it, so the studies must rank the same genes; and a gene ranked in - fewer studies would have a statistic, and a distribution under the null, of its - own. The other genes have NaN. A study that did not analyse the group or the - contrast at all is left out by :func:`differential_expression_sumrank`, as the - authors' code leaves out a dataset without the cell type: k counts the studies - that did. - -Irwin-Hall p-values: - If every study ranked the genes in an order of its own, at random and - independently of the others, each normalized rank would be uniform on [0, 1], - and the sum of m of them would follow the Irwin-Hall distribution of a sum of m - independent uniform variables (Irwin 1927; Hall 1927), whose distribution - function is - - F_m(x) = (1/m!) sum_{j=0}^{floor(x)} (-1)^j C(m, j) (x - j)^m. - - ``up_irwin_hall_p_value`` is F_m(s) for the statistic s of a gene below the cap, - and 1 at the cap: the probability under that null of a statistic at most s. - It treats the ranks as continuous: a gene ranked first in every study (s = 0) - has p-value 0, where its probability is G^-m. - - With every study summed (m = k), the p-value is exact under that null, apart - from the steps of the ranks. A sum of the m best of k ranks (m < k) is smaller - than a sum of m ranks, so its Irwin-Hall p-values are too small: the paper takes - its p-values from permutations instead, as below, and so should an analysis - with m < k, as :func:`differential_expression_sumrank` warns. Genes whose ranks - are not uniform under the null, such as genes with a p-value of 1 in many - permutations, depart from it too. - - The alternating sum above loses every digit to cancellation near the middle of - the distribution for tens of studies. F_m is computed instead as a sum of - positive terms, F_m(x) = sum_{i=0}^{floor(x)} N_{m+1}(x - floor(x) + i), where - N_{m+1} is the cardinal B-spline of order m + 1, the density of the Irwin-Hall - distribution of m + 1 variables, which de Boor's recurrence evaluates with - positive terms only (de Boor 1978): N_{m+1}' (x) = N_m(x) - N_m(x - 1), so the - sum telescopes to the integral of N_m. It agrees with a high-precision - computation to about 1e-14 relative for up to 200 studies - (``validation/sumrank.py``). - -Permutation p-values: - The paper permutes the case and control labels of the samples of every study, - runs each study's analysis and SumRank again, and pools the statistics of every - gene in every permutation into one null distribution: with B permutations of G - genes, N = B G null statistics. The p-value of a gene of statistic s is - - p = (1 + the number of null statistics at most s) / (1 + N), - - as Phipson and Smyth (2010) form p-values from random permutations, so that it - is never zero, and a gene at the cap has p = 1. Pooling over the genes gives - small p-values from few permutations (100 permutations of 10,000 genes give a - p-value as small as 1e-6), for genes whose null distributions are alike. The - statistics of a permutation are computed as those of the data, over the genes - that every study's analysis of that permutation tested. - - :func:`sumrank` takes the statistics of the permutations as an array; - :func:`differential_expression_sumrank` permutes the values of one variable of - each study's design (``permute``, such as the condition) among the samples of - the study's fit of the group, with a ``numpy.random.Generator``, and re-runs the - study's analysis, ``limma_voom`` or ``limma_trend`` with the design and settings - its result records, on its aligned cube, for that group and contrast only. The - permutations are drawn in a fixed order, permutation after permutation and - study after study, so a seed gives the same permutations, and the analyses do - not depend on the number of threads. - -Cost: - A permutation runs the analysis of every study once, for one group: with - ``limma_voom``, 0.21 s for a study of 40 samples and 10,000 genes on a laptop on - 2 threads (``limma_trend`` 0.14 s; ``validation/sumrank.py --benchmark``), so 100 - permutations of 20 such studies take about 7 minutes; the SumRank statistics of a - permutation take milliseconds. The memory is that of one study's analysis and a - few arrays of the genes, whatever the number of permutations. - -Deliberate differences from the paper and its authors' code: - - The Irwin-Hall p-value is the lower tail of the distribution, as above. The - paper calls its Irwin-Hall p-values two-sided, which would double these, and - the authors' code computes the density of the Irwin-Hall distribution at the - statistic instead (the unifed package's ``dirwin.hall``), which is not a - probability. Its permutation p-values compare -log10 of that density, which - falls as the statistic rises up to the cap for m >= 2: they order genes as the - statistic does, so the permutation p-values here do not depend on this choice. - - The authors' code takes as the permutation p-value the share of null - statistics strictly below the gene's (``1 - ecdf`` of -log10 of the density), - without the one above: a gene beyond every null statistic has the p-value 0, - and a gene at the cap the share of null statistics below the cap rather than - 1. Here ties count as at least as extreme, which keeps the p-value valid. - - The labels are permuted among the samples of each study's fit of the group, - which keeps the number of cases and controls of the fit. The paper permutes - them among all the individuals of a dataset, for all its cell types at once, - and draws again until every cell type has at least two cases and two controls. - - The authors' code sums the normalized ranks in floating point, in each gene's - own order, so that genes with equal statistics can compare as unequal in its - calibration; here the sums are exact. It stops when it sums one study (m = 1), - and takes floor(p k) of the product in floating point, which for 0.57 of 100 - studies is 56. - -Not done: - Studies are not weighted, a limit the paper notes too. Permutations re-run - designs without random effects only (``limma_voom`` and ``limma_trend``), whose - samples are exchangeable under the null; a design with several samples of a - donor needs the donors' labels permuted instead. - -References: - Nakatsuka N, Adler D, Jiang L, Hartman A, Cheng E, Klann E, Satija R (2025). - Improving reproducibility of differentially expressed genes in single-cell - transcriptomic studies of neurodegenerative diseases through meta-analysis. - Nature Communications 16, 7436. doi:10.1038/s41467-025-62579-z - - Irwin JO (1927). On the frequency distribution of the means of samples from a - population having any law of frequency with finite moments, with special - reference to Pearson's Type II. Biometrika 19, 225-239. - doi:10.1093/biomet/19.3-4.225 - - Hall P (1927). The distribution of means for samples of size N drawn from a - population in which the variate takes values between 0 and 1, all such values - being equally probable. Biometrika 19, 240-244. doi:10.1093/biomet/19.3-4.240 - - de Boor C (1978). A Practical Guide to Splines. Springer, New York. - doi:10.1007/978-1-4612-6333-3 - - Phipson B, Smyth GK (2010). Permutation p-values should never be zero: calculating - exact p-values when permutations are randomly drawn. Statistical Applications in - Genetics and Molecular Biology 9, article 39. doi:10.2202/1544-6115.1585 - - 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 -""" - -import inspect -import json -import math -import numbers -import warnings -from collections.abc import Callable, Iterator, Mapping -from dataclasses import dataclass -from typing import Any - -import numpy as np -import numpy.typing as npt - -from ._analysis_records import software_versions -from ._columns import text_column -from ._jit import jit -from ._masked_arrays import read_only, reject_masked, unmasked_array -from ._messages import ( - PACKAGE_DIRECTORY, - fitted_lines, - fitted_message, - listing, - quoted, -) -from ._options import index_integer, random_generator -from ._ranks import average_ranks -from ._study_statistics import signed_statistics, study_statistics, studies_summed -from .cube import Cube, CubeCollection -from .design import Design -from .differential_expression import ( - DifferentialExpressionResult, - limma_trend, - limma_voom, -) -from .stats.multiple_testing import adjust_p_values -from .table import Table - -__all__ = [ - "SumRankFit", - "SumRankResult", - "differential_expression_sumrank", - "sumrank", -] - -FloatArray = npt.NDArray[np.float64] -IntArray = npt.NDArray[np.int64] -BoolArray = npt.NDArray[np.bool_] - -_DIRECTIONS = ("up", "down") -# The analyses that permutations re-run: a design without random effects, whose -# samples are exchangeable under the null. -_RERUN: dict[str, Callable[..., DifferentialExpressionResult]] = { - "limma_voom": limma_voom, - "limma_trend": limma_trend, -} - - -@dataclass(frozen=True, eq=False, repr=False) -class SumRankFit: - """SumRank of every gene across studies, from :func:`sumrank`. - - The per-gene arrays hold one value per gene (feature), NaN for a gene that some - study did not test; they are read-only. - - Attributes: - up_statistic: The sum of the gene's ``n_studies_summed`` smallest normalized - ranks, set to ``n_studies_summed / 2`` where it is larger: small for a - gene up in the studies that rank it best. - down_statistic: The same for the negated statistics: small for a gene down. - up_irwin_hall_p_value: The Irwin-Hall distribution function of the sum of - ``n_studies_summed`` uniform ranks at ``up_statistic``, or 1 at the cap. - down_irwin_hall_p_value: The same for ``down_statistic``. - up_permutation_p_value: (1 + the number of null statistics at most - ``up_statistic``) / (1 + ``n_null_values``), over the statistics of - every gene in every permutation; None without permutations. - down_permutation_p_value: The same for ``down_statistic``. - normalized_ranks: ``(n_studies, n_features)``: the normalized rank of every - gene in every study, (r - 1) / (G - 1) for the rank r from 1 for the - largest statistic among the G genes ranked; NaN for a gene not ranked. - The ranks of the genes down are 1 minus these. - ranked: Whether each gene was ranked: tested by every study. - n_studies: The number of studies, k. - n_studies_summed: The number of ranks summed for each gene, m. - proportion_of_studies: The proportion of the studies that sets m. - n_permutations: The number of permutations, 0 without them. - n_null_values: The number of null statistics of each direction, the genes - ranked in each permutation, summed over the permutations; 0 without - them. - """ - - up_statistic: FloatArray - down_statistic: FloatArray - up_irwin_hall_p_value: FloatArray - down_irwin_hall_p_value: FloatArray - up_permutation_p_value: FloatArray | None - down_permutation_p_value: FloatArray | None - normalized_ranks: FloatArray - ranked: BoolArray - n_studies: int - n_studies_summed: int - proportion_of_studies: float - n_permutations: int - n_null_values: int - - def __repr__(self) -> str: - text = ( - f"SumRankFit(n_features={self.ranked.size}, ranked=" - f"{int(np.count_nonzero(self.ranked))}, n_studies={self.n_studies}, " - f"n_studies_summed={self.n_studies_summed}" - ) - if self.n_permutations: - text += f", n_permutations={self.n_permutations}" - return text + ")" - - -def sumrank( - statistics: npt.ArrayLike, - *, - proportion_of_studies: float = 1.0, - null_statistics: npt.ArrayLike | None = None, -) -> SumRankFit: - """Rank every gene in every study and sum its best ranks, as SumRank does. - - Each study ranks the genes that every study tested by their statistic, from the - largest; a gene's normalized ranks in the studies that rank it best are summed and - capped at their mean under the null, for genes up and, with the statistics - negated, for genes down. The module description gives the statistic, its - Irwin-Hall p-values and the permutation p-values. - - Args: - statistics: ``(n_studies, n_features)`` signed statistics, larger for a gene - more up, such as the signed -log10 p-values of each study's test; NaN - where a study did not test a gene, and infinite values are allowed (a - p-value of 0). Only their order within each study matters. - proportion_of_studies: The proportion p of the k studies whose ranks are - summed for each gene: the m = floor(p k) studies that rank it best, at - least one, in (0, 1]; 1 sums every study. - null_statistics: ``(n_permutations, n_studies, n_features)``: the statistics - of the same studies with their labels permuted, one study's analysis - re-run on each permutation, NaN where an analysis did not test a gene; - or None for no permutation p-values. - - Returns: - The statistics of every gene, up and down, their Irwin-Hall p-values and, - with ``null_statistics``, their permutation p-values. - - Raises: - TypeError: If an argument is not an array of real numbers, is a numpy masked - array, or ``proportion_of_studies`` is not a real number. - ValueError: If the shapes are wrong, a study tested no gene, fewer than two - genes were tested by every study (in the data or in a permutation), or - ``proportion_of_studies`` is not in (0, 1]. - - Example: - Two studies of four genes: the first gene is the largest in both, so its - ranks sum to 0; the others reach the cap of 1 (two ranks of mean 1/2). - - >>> fit = sumrank([[3.0, 1.0, -2.0, 0.5], [2.0, -1.0, 0.0, 1.5]]) - >>> fit.up_statistic.tolist() - [0.0, 1.0, 1.0, 1.0] - >>> fit.down_statistic.tolist() - [1.0, 0.6666666666666666, 0.3333333333333333, 1.0] - >>> fit.down_irwin_hall_p_value.tolist() # (1/3)^2 / 2 = 1/18 for the third - [1.0, 0.2222222222222222, 0.05555555555555555, 1.0] - """ - values = _statistics(statistics, "statistics", 2) - n_studies = values.shape[0] - summed = studies_summed(proportion_of_studies, n_studies) - null = None - if null_statistics is not None: - null = _statistics(null_statistics, "null_statistics", 3) - if null.shape[1:] != values.shape: - raise ValueError( - "null_statistics must be (n_permutations, n_studies, n_features) with " - f"the studies and features of statistics, {values.shape}; got " - f"{null.shape}" - ) - if null.shape[0] == 0: - raise ValueError( - "null_statistics holds no permutation; give at least one, or None for " - "no permutation p-values" - ) - observed = _capped_sums(values, summed, "statistics") - up = _full(observed.up, observed.ranked) - down = _full(observed.down, observed.ranked) - permutation: list[FloatArray | None] = [None, None] - n_null = 0 - if null is not None: - counts = _NullCounts(observed) - for b, permuted in enumerate(null): - counts.add(_capped_sums(permuted, summed, f"permutation {b}")) - permutation = [read_only(_full(p, observed.ranked)) for p in counts.p_values()] - n_null = counts.n_values - return SumRankFit( - up_statistic=read_only(up), - down_statistic=read_only(down), - up_irwin_hall_p_value=read_only(_full(observed.up_p, observed.ranked)), - down_irwin_hall_p_value=read_only(_full(observed.down_p, observed.ranked)), - up_permutation_p_value=permutation[0], - down_permutation_p_value=permutation[1], - normalized_ranks=read_only(observed.ranks_of_every_feature()), - ranked=read_only(observed.ranked), - n_studies=n_studies, - n_studies_summed=summed, - proportion_of_studies=float(proportion_of_studies), - n_permutations=0 if null is None else int(null.shape[0]), - n_null_values=n_null, - ) - - -# The statistic. - - -def _statistics(values: Any, name: str, ndim: int) -> FloatArray: - array = unmasked_array(values, name, missing="NaN") - if array.dtype.kind not in "iuf": - raise TypeError(f"{name} must hold real numbers, got dtype {array.dtype}") - if array.ndim != ndim: - shape = ( - "(n_studies, n_features)" - if ndim == 2 - else "(n_permutations, n_studies, n_features)" - ) - raise ValueError(f"{name} must be {shape}, got {array.ndim} dimensions") - converted: FloatArray = np.array(array, dtype=np.float64) - if ndim == 2 and converted.shape[0] == 0: - raise ValueError(f"{name} has no study: give one row per study") - return converted - - -@dataclass(frozen=True) -class _Sums: - """The capped sums of the genes every study tested, in units that are exact. - - ``up`` and ``down`` are the statistics of the ``ranked`` genes, in their order, - and ``up_p`` and ``down_p`` their Irwin-Hall p-values; ``ranks`` holds the - normalized ranks of these genes in every study. - """ - - ranked: BoolArray # per feature - up: FloatArray # per ranked gene - down: FloatArray - up_p: FloatArray - down_p: FloatArray - ranks: FloatArray # (n_studies, n_ranked) - - def ranks_of_every_feature(self) -> FloatArray: - full = np.full((self.ranks.shape[0], self.ranked.size), np.nan) - full[:, self.ranked] = self.ranks - return full - - -def _capped_sums(values: FloatArray, summed: int, where: str) -> _Sums: - """The SumRank statistics of the genes every study tested, up and down. - - The rank r of a gene among G (from 1 for the largest value) is an integer or, for - ties, a half-integer, so q = 2 (r - 1) is an integer and a sum of normalized ranks - is (sum of q) / (2 (G - 1)): it is formed exactly in integers, and compared with - the cap m (G - 1) there, so that equal sums are equal in every permutation. - """ - tested = ~np.isnan(values) - empty = np.flatnonzero(~tested.any(axis=1)) - if empty.size: - raise ValueError( - f"{where}: study {int(empty[0])} tested no gene (every value is NaN); " - "leave out a study that did not test the group and contrast" - ) - ranked = tested.all(axis=0) - n_ranked = int(np.count_nonzero(ranked)) - if n_ranked < 2: - genes = "1 gene was" if n_ranked == 1 else f"{n_ranked} genes were" - raise ValueError( - f"{where}: {genes} tested by every study, and SumRank ranks the genes " - "that every study tested against each other; it needs at least two" - ) - kept = values[:, ranked] - n_studies = kept.shape[0] - twice_up = np.empty(kept.shape, dtype=np.int64) # 2 (r - 1), r from the largest - for row in range(n_studies): - ascending = average_ranks(np.ascontiguousarray(kept[row])) # 1 = smallest - # r = G + 1 - ascending rank, so 2 (r - 1) = 2 (G - ascending rank). - twice_up[row] = np.rint(2.0 * (n_ranked - ascending)).astype(np.int64) - twice_down = 2 * (n_ranked - 1) - twice_up - scale = 2.0 * (n_ranked - 1) - statistics = [] - for twice in (twice_up, twice_down): - if summed < n_studies: - best = np.partition(twice, summed - 1, axis=0)[:summed] - else: - best = twice - total = best.sum(axis=0) - cap = summed * (n_ranked - 1) - capped = np.minimum(total, cap) - statistic = capped / scale - p_value = np.ones(statistic.size) - below = capped < cap - p_value[below] = _irwin_hall_cdf(statistic[below], summed) - statistics.append((statistic, p_value)) - return _Sums( - ranked=ranked, - up=statistics[0][0], - down=statistics[1][0], - up_p=statistics[0][1], - down_p=statistics[1][1], - ranks=twice_up / scale, - ) - - -def _full(values: FloatArray, ranked: BoolArray) -> FloatArray: - """Values of the ranked genes in an array of every feature, NaN elsewhere.""" - full = np.full(ranked.size, np.nan) - full[ranked] = values - return full - - -class _NullCounts: - """For every gene, the number of null statistics at most its own, per direction, - accumulated one permutation at a time, so that the null distribution is never - held whole.""" - - def __init__(self, observed: _Sums) -> None: - self._observed = (observed.up, observed.down) - self._counts = [np.zeros(observed.up.size, dtype=np.int64) for _ in range(2)] - self.n_values = 0 - - def add(self, permutation: _Sums) -> None: - for counts, statistic, null in zip( - self._counts, - self._observed, - (permutation.up, permutation.down), - strict=True, - ): - counts += np.searchsorted(np.sort(null), statistic, side="right") - self.n_values += permutation.up.size - - def p_values(self) -> list[FloatArray]: - return [ - np.divide(1.0 + counts, 1.0 + self.n_values, dtype=np.float64) - for counts in self._counts - ] - - -# The Irwin-Hall distribution function. - - -def _irwin_hall_cdf(x: FloatArray, n: int) -> FloatArray: - """F_n at each x, the distribution function of a sum of n uniform variables.""" - out = np.empty(x.size) - _irwin_hall_cdf_into(np.ascontiguousarray(x, dtype=np.float64), n, out) - return out - - -@jit -def _irwin_hall_cdf_into(x: FloatArray, n: int, out: FloatArray) -> None: - """Fill ``out`` with F_n(x), as a sum of values of the cardinal B-spline of order - n + 1 at t + i for the fractional part t of x, which de Boor's recurrence - evaluates with positive terms only; above n / 2 by the symmetry F_n(x) = - 1 - F_n(n - x).""" - b = np.empty(n + 1) - for g in range(x.size): - value = x[g] - if not value > 0.0: - out[g] = 0.0 if value <= 0.0 else math.nan - continue - if not value < n: - out[g] = 1.0 - continue - upper = value > 0.5 * n - if upper: - value = n - value - whole = int(math.floor(value)) - t = value - whole - # b[i] = N_k(t + i) for the cardinal B-spline N_k of order k, from k = 1 - # (the indicator of [0, 1)) to k = n + 1, updating from the top so that - # b[i - 1] still holds order k - 1. - b[0] = 1.0 - for i in range(1, n + 1): - b[i] = 0.0 - for k in range(2, n + 2): - for i in range(k - 1, -1, -1): - term = (t + i) * b[i] - if i > 0: - term += (k - t - i) * b[i - 1] - b[i] = term / (k - 1) - total = 0.0 - for i in range(whole + 1): - total += b[i] - out[g] = 1.0 - total if upper else total - - -# SumRank of the studies of a collection. - - -class SumRankResult: - """SumRank of the differential expression of the studies of a collection. - - Built by :func:`differential_expression_sumrank`, for one group (cell type) and - contrast. The table has one row per gene that every study tested, in the - collection's order of the features: - - - ``feature_id`` and, when the collection's features have it, ``symbol``; - - ``up_statistic``: the SumRank statistic of the gene up, the capped sum of its - best normalized ranks (small for a gene up in the studies that rank it best); - - ``up_p_value``: its p-value, from permutations when they were run - (``settings["p_values"]`` is ``"permutation"``), from the Irwin-Hall - distribution otherwise (``"irwin_hall"``); - - ``up_adjusted_p_value``: Benjamini-Hochberg over the genes of the table - (Benjamini and Hochberg 1995); - - ``up_irwin_hall_p_value``: the Irwin-Hall p-value, also with permutations; - - ``down_statistic``, ``down_p_value``, ``down_adjusted_p_value`` and - ``down_irwin_hall_p_value``: the same for the gene down. - - The table of studies has one row per study of the collection: ``study``, - ``analysed`` (whether its results test the group and contrast), ``reason`` (why - not; empty when analysed) and ``n_features_tested`` (0 when not analysed). - - Immutable. - - Raises: - TypeError: If ``table`` or ``studies`` is not a ``Table``, or ``provenance`` - not a mapping that JSON can write. - ValueError: If ``provenance`` holds a NaN or an infinity. - """ - - __slots__ = ("_table", "_studies", "_record") - - _table: Table - _studies: Table - _record: str - - def __init__( - self, table: Table, studies: Table, provenance: Mapping[str, Any] - ) -> None: - for name, given in (("table", table), ("studies", studies)): - if not isinstance(given, Table): - raise TypeError(f"{name} must be a Table, got {type(given).__name__}") - if not isinstance(provenance, Mapping): - raise TypeError( - f"provenance must be a mapping, got {type(provenance).__name__}" - ) - state = { - "_table": table, - "_studies": studies, - # JSON holds no arrays: json.dumps raises TypeError for one. - "_record": json.dumps(provenance, allow_nan=False), - } - for name, value in state.items(): - object.__setattr__(self, name, value) - - def __setattr__(self, name: str, value: object) -> None: - raise AttributeError("SumRankResult is immutable") - - def __delattr__(self, name: str) -> None: - raise AttributeError("SumRankResult is immutable") - - def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: - return ( - SumRankResult, - (self._table, self._studies, json.loads(self._record)), - ) - - @property - def table(self) -> Table: - """The statistics and p-values of every gene ranked.""" - return self._table - - @property - def studies(self) -> Table: - """Every study of the collection, whether it analysed the group and - contrast, and why not.""" - return self._studies - - @property - def settings(self) -> dict[str, Any]: - """The settings, as a new dict (also in the provenance), with the group, the - contrast, the proportion of studies, the numbers of studies and of ranks - summed, the source of the p-values and the permutations.""" - settings: dict[str, Any] = json.loads(self._record)["settings"] - return settings - - @property - def provenance(self) -> dict[str, Any]: - """A JSON-serialisable record of what produced every number, as a new dict. - - It holds the CytePool, numpy, numba and Python versions; the settings; the - number of genes ranked and of genes left out, measured by a study but not - tested by every study that analysed the group and contrast; and the - provenance of every study's differential-expression result, by study name, - which identifies its cube by its fingerprint and gives its design and - settings and the samples of every group's fit. - """ - record: dict[str, Any] = json.loads(self._record) - return record - - def __repr__(self) -> str: - settings = self.settings - table = self._table - lines = [ - f"SumRankResult for group {settings['group']!r} and contrast " - f"{settings['contrast']!r}: {settings['n_studies']:,} studies, the best " - f"{settings['n_studies_summed']:,} summed; {len(table):,} genes" - ] - if settings["p_values"] == "permutation": - lines.append( - f" p-values from {settings['n_permutations']:,} permutations of " - f"{settings['permute']!r}" - ) - else: - lines.append(" p-values from the Irwin-Hall distribution") - counts = [ - int(np.count_nonzero(table[f"{d}_adjusted_p_value"] < 0.05)) - for d in _DIRECTIONS - ] - lines.append( - f" adjusted p-value below 0.05: {counts[0]:,} up, {counts[1]:,} down" - ) - return fitted_lines("\n".join(lines)) - - -def differential_expression_sumrank( - collection: CubeCollection, - results: Mapping[str, DifferentialExpressionResult], - group: str, - contrast: str, - *, - proportion_of_studies: float = 1.0, - n_permutations: int = 0, - permute: str | None = None, - rng: np.random.Generator | int | None = None, -) -> SumRankResult: - """SumRank of one group and contrast across the studies of a collection. - - Each study's result is the analysis of its aligned cube - (:meth:`~cytepool.cube.CubeCollection.aligned_cube`), such as - :func:`~cytepool.differential_expression.limma_voom` with the study's own design, - as for :func:`~cytepool.meta_analysis.align_study_results`. For the group and - contrast, the studies whose results test them each rank the genes that every one - of them tested by their signed -log10 p-value, and :func:`sumrank` sums each - gene's best ranks; the module description gives the statistic and its p-values. - - With ``n_permutations``, the values of the design variable ``permute``, such as - the condition, are permuted among the samples of each study's fit of the group, - and each study's analysis is run again on its aligned cube with the design and - settings its result records (``limma_voom`` or ``limma_trend``), for this group - and contrast only; the SumRank statistics of every permutation make the null - distribution of the p-values. Each permutation costs one analysis of the group in - every study (see "Cost" in the module description). - - Args: - collection: The collection whose aligned cubes the studies' results - analysed. - results: The differential-expression result of every study of the - collection, by study name, from - :func:`~cytepool.differential_expression.limma_voom`, - :func:`~cytepool.differential_expression.limma_trend` or - :func:`~cytepool.differential_expression.dream_voom` (without - permutations). - group: An aligned group id of the collection. - contrast: A contrast name that the studies tested. - proportion_of_studies: The proportion of the studies that analysed the group - whose ranks are summed for each gene, those that rank it best, in - (0, 1]; 1 sums every study. - n_permutations: The number of permutations, at least 0; 0 gives Irwin-Hall - p-values only. - permute: The variable of the studies' designs whose values are permuted, - such as ``"condition"``; needed with permutations. - rng: A ``numpy.random.Generator`` to draw the permutations from, an integer - seed for one, or None for a fresh, unseeded generator. - - Returns: - The statistics and p-values of every gene that every study analysing the - group and contrast tested, a table of the studies, and a provenance record. - - Warns: - RuntimeWarning: If fewer than all the studies are summed without - permutations: the p-values are then the Irwin-Hall p-values, which are - too small for such sums. - - Raises: - TypeError: If an argument has the wrong type, or is or holds a numpy masked - array. - ValueError: If ``results`` does not give the result of every study of the - collection, a result did not analyse its study's aligned cube, no study - analysed the group and contrast, the contrast is an F-test, fewer than - two genes were tested by every study, an option is invalid, - ``permute`` is given without permutations or missing with them, or a - study cannot be permuted: its analysis has random effects, its design - has no variable ``permute``, or a permutation leaves the contrast - untestable. - KeyError: If ``group`` is not a group of the collection, or no study tested - ``contrast``. - """ - reject_masked(n_permutations, "n_permutations") - permutations = _n_permutations(n_permutations) - if permute is not None and not isinstance(permute, str): - reject_masked(permute, "permute") - raise TypeError( - f"permute must be a design variable name (str), got {type(permute).__name__}" - ) - if permutations and permute is None: - raise ValueError( - "permutations permute the values of one variable of each study's design, " - "such as the condition: give its name as permute" - ) - if not permutations and permute is not None: - raise ValueError( - "permute names the variable that permutations permute, but n_permutations " - "is 0; give the number of permutations" - ) - generator = random_generator(rng) if permutations else None - inputs = study_statistics(collection, results, group, contrast) - used = inputs.analysed - if not used.any(): - raise ValueError( - f"no study tested group {group!r} and contrast {contrast!r}: " - + "; ".join( - f"{study}: {reason}" - for study, reason in zip( - collection.studies, inputs.reasons, strict=True - ) - ) - ) - studies = [ - s for s, flag in zip(collection.studies, used.tolist(), strict=True) if flag - ] - statistics = inputs.statistics[used] - summed = studies_summed(proportion_of_studies, len(studies)) - if summed < len(studies) and not permutations: - warnings.warn( - fitted_message( - f"the Irwin-Hall p-values of a sum of the best {summed} of " - f"{len(studies)} studies' ranks are too small, as the sum of the best " - "ranks is smaller than a sum of uniform ranks; the p_value columns " - "hold them, so take p-values from permutations (n_permutations and " - "permute)" - ), - RuntimeWarning, - skip_file_prefixes=(PACKAGE_DIRECTORY,), - ) - observed = _capped_sums(statistics, summed, "the studies' results") - counts = None - if permutations: - assert generator is not None and permute is not None - counts = _NullCounts(observed) - for b, permuted in enumerate( - _permuted_statistics( - collection, - results, - studies, - group, - contrast, - permute, - permutations, - generator, - ) - ): - counts.add(_capped_sums(permuted, summed, f"permutation {b}")) - ranked = observed.ranked - features = collection.features - columns: dict[str, Any] = {"feature_id": features["feature_id"][ranked]} - if "symbol" in features: - columns["symbol"] = features["symbol"][ranked] - p_values = [observed.up_p, observed.down_p] if counts is None else counts.p_values() - for direction, statistic, p_value, irwin_hall in zip( - _DIRECTIONS, - (observed.up, observed.down), - p_values, - (observed.up_p, observed.down_p), - strict=True, - ): - columns[f"{direction}_statistic"] = statistic - columns[f"{direction}_p_value"] = p_value - columns[f"{direction}_adjusted_p_value"] = adjust_p_values(p_value, "bh") - columns[f"{direction}_irwin_hall_p_value"] = irwin_hall - measured = inputs.measured[used].any(axis=0) - settings = { - "group": group, - "contrast": contrast, - "statistic": "signed -log10 p-value", - "proportion_of_studies": float(proportion_of_studies), - "n_studies": len(studies), - "n_studies_summed": summed, - "p_values": "irwin_hall" if counts is None else "permutation", - "n_permutations": permutations, - "permute": permute, - "seed": ( - int(rng) - if isinstance(rng, numbers.Integral) - and not isinstance(rng, bool | np.bool_) - else None - ), - "n_null_values": 0 if counts is None else counts.n_values, - "p_value_adjustment": "BH", - } - return SumRankResult( - Table(columns), - Table( - { - "study": text_column(list(collection.studies)), - "analysed": used.copy(), - "reason": text_column(inputs.reasons), - "n_features_tested": inputs.n_tested, - } - ), - { - "analysis": "differential_expression_sumrank", - "versions": software_versions(), - "settings": settings, - "n_features": int(np.count_nonzero(ranked)), - "n_features_left_out": int(np.count_nonzero(measured & ~ranked)), - "studies": {name: results[name].provenance for name in collection.studies}, - }, - ) - - -def _n_permutations(value: Any) -> int: - if isinstance(value, bool | np.bool_): - raise TypeError("n_permutations must be a whole number, not bool") - number = index_integer(value, "n_permutations", expected="a whole number") - if number < 0: - raise ValueError(f"n_permutations must be at least 0, got {number}") - return number - - -# Permutations: each study's analysis re-run with the labels of its samples permuted. - - -@dataclass(frozen=True) -class _Rerun: - """What it takes to run a study's analysis again with permuted labels.""" - - study: str - cube: Cube - function: Callable[..., DifferentialExpressionResult] - options: dict[str, Any] - formula: str - levels: dict[str, list[Any]] - samples: dict[str, list[Any]] - weights: list[float] # of the contrast on the design's columns - used: list[int] # the design rows of the samples of the group's fit - fingerprint: str - settings: dict[str, Any] - - -def _permuted_statistics( - collection: CubeCollection, - results: Mapping[str, DifferentialExpressionResult], - studies: list[str], - group: str, - contrast: str, - permute: str, - n_permutations: int, - generator: np.random.Generator, -) -> Iterator[FloatArray]: - """The signed statistics of every study for each permutation, ``(n_studies, - n_features)`` in the collection's order, NaN where an analysis did not test a - gene. The permutations are drawn permutation by permutation, study by study.""" - reruns = [ - _rerun(collection, results[study], study, group, contrast, permute) - for study in studies - ] - index = { - feature: position - for position, feature in enumerate(collection.features["feature_id"].tolist()) - } - checked = [False] * len(reruns) - for _ in range(n_permutations): - statistics = np.full((len(reruns), len(index)), np.nan) - for row, rerun in enumerate(reruns): - values = list(rerun.samples[permute]) - drawn = generator.permutation(len(rerun.used)) - for position, source in zip(rerun.used, drawn.tolist(), strict=True): - values[position] = rerun.samples[permute][rerun.used[source]] - design = Design( - {**rerun.samples, permute: values}, - rerun.formula, - levels=rerun.levels, - contrasts={contrast: rerun.weights}, - ) - result = rerun.function(rerun.cube, design, groups=[group], **rerun.options) - if not checked[row]: - _check_rerun(rerun, result) - checked[row] = True - table = result.tables.get((group, contrast)) - if table is None: - raise ValueError( - f"a permutation of {permute!r} in study {rerun.study!r} leaves " - f"contrast {contrast!r} untestable in group {group!r}: " - f"{_absence(result, group, contrast)}" - ) - positions = np.array( - [index[feature] for feature in table["feature_id"].tolist()], - dtype=np.intp, - ) - statistics[row, positions] = signed_statistics( - table["p_value"], table["log2_fold_change"] - ) - yield statistics - - -def _rerun( - collection: CubeCollection, - result: DifferentialExpressionResult, - study: str, - group: str, - contrast: str, - permute: str, -) -> _Rerun: - """How to run a study's analysis again: its function and options, its design, - and the samples of its fit of the group, from what its result records.""" - function = _RERUN.get(result.method) - if function is None: - raise ValueError( - f"the result of study {study!r} is from {result.method}, whose design has " - "random effects: its samples are not exchangeable under the null, so its " - "labels cannot be permuted sample by sample; permutations re-run " - "limma_voom and limma_trend" - ) - record = result.provenance - design = record["design"] - samples: dict[str, list[Any]] = design["samples"] - variables = [name for name in samples if name != "sample_id"] - if permute not in variables: - raise ValueError( - f"permute names {permute!r}, which is not a variable of the design of " - f"study {study!r}; its variables are: {quoted(variables) or '(none)'}" - ) - settings: dict[str, Any] = record["settings"] - parameters = inspect.signature(function).parameters - options = { - name: settings[name] - for name, parameter in parameters.items() - if parameter.kind is inspect.Parameter.KEYWORD_ONLY - and name not in ("groups", "contrasts") - and name in settings - } - if options.get("expression_filter", True) is None: - options["expression_filter"] = False # recorded as None when it was off - ids = samples["sample_id"] - rows = {sample: position for position, sample in enumerate(ids)} - table = result.samples - in_group = (table["group_id"] == group) & table["used"].astype(bool) - used = [rows[sample] for sample in table["sample_id"][in_group].tolist()] - return _Rerun( - study=study, - cube=collection.aligned_cube(study), - function=function, - options=options, - formula=design["formula"], - levels=design["levels"], - samples=samples, - weights=design["contrasts"][contrast], - used=used, - fingerprint=record["cube"]["fingerprint"], - settings=settings, - ) - - -def _check_rerun(rerun: _Rerun, result: DifferentialExpressionResult) -> None: - """Refuse a re-run on another cube, or with other settings, than the study's - analysis: its permutations would not be those of the study's results.""" - record = result.provenance - if record["cube"]["fingerprint"] != rerun.fingerprint: - raise ValueError( - f"the result of study {rerun.study!r} was computed from a cube whose " - f"fingerprint, {rerun.fingerprint}, is not that of the study's aligned " - f"cube, {record['cube']['fingerprint']}: analyse " - f"collection.aligned_cube({rerun.study!r})" - ) - if record["settings"] != rerun.settings: - changed = [ - name - for name in sorted(set(record["settings"]) | set(rerun.settings)) - if record["settings"].get(name) != rerun.settings.get(name) - ] - raise ValueError( # pragma: no cover - the options are those recorded - f"the analysis of study {rerun.study!r} could not be re-run with its " - f"recorded settings; these differ: {listing(changed)}" - ) - - -def _absence(result: DifferentialExpressionResult, group: str, contrast: str) -> str: - """Why a result has no table of a group and contrast: its own message.""" - try: - result.table(group, contrast) - except KeyError as error: - return str(error.args[0]) - raise AssertionError("a table that is not in result.tables") # pragma: no cover diff --git a/cytepool/sumrank_across_studies.py b/cytepool/sumrank_across_studies.py new file mode 100644 index 0000000..60d2b0a --- /dev/null +++ b/cytepool/sumrank_across_studies.py @@ -0,0 +1,610 @@ +"""SumRank of the differential expression of the studies of a collection. + +:func:`~cytepool.sumrank` ranks the genes of every study, sums each gene's best ranks +and gives their p-values, as SumRank does (Nakatsuka et al. 2025), for one group (cell +type) and contrast of the studies' results: a :class:`~cytepool.StudyResults`, such as +:func:`~cytepool.analyse_each_study` gives, whose studies each analysed their aligned +cube. :mod:`cytepool.stats.sumrank` describes the statistic, its Irwin-Hall p-values and +its permutation p-values, which :func:`~cytepool.stats.sumrank.fit_sumrank` computes. + +The studies' statistics: + Each study ranks the genes by its signed -log10 p-value of the contrast: + -log10(p_value), negative where the log2 fold change is negative, as the paper + ranks DESeq2's results. The genes ranked are those that every study analysing + the group and contrast tested; a study that did not analyse them at all is left + out, as the authors' code leaves out a dataset without the cell type. + +Permutations: + With ``n_permutations``, the values of one variable of each study's design, such + as the condition, are permuted among the samples of the study's fit of the + group, with a ``numpy.random.Generator``, and the study's analysis, + ``limma_voom`` or ``limma_trend`` with the design and settings its result + records, is run again on its aligned cube, for that group and contrast only. + The variable (``permute``) is read from the contrast when it compares two + levels of one factor in every study's design, such as ``conditioncase``, the + level ``case`` of ``condition`` against its reference; otherwise it is given. + The permutations are drawn in a fixed order, permutation after permutation and + study after study, so a seed gives the same permutations, and the analyses do + not depend on the number of threads. + +Cost: + A permutation runs the analysis of every study once, for one group: with + ``limma_voom``, 0.21 s for a study of 40 samples and 10,000 genes on a laptop on + 2 threads (``limma_trend`` 0.14 s; ``validation/sumrank.py --benchmark``), so 100 + permutations of 20 such studies take about 7 minutes; the SumRank statistics of a + permutation take milliseconds. The memory is that of one study's analysis and a + few arrays of the genes, whatever the number of permutations. + +Deliberate differences from the paper: + - The labels are permuted among the samples of each study's fit of the group, + which keeps the number of cases and controls of the fit. The paper permutes + them among all the individuals of a dataset, for all its cell types at once, + and draws again until every cell type has at least two cases and two controls. + +Not done: + Studies are not weighted, a limit the paper notes too. Permutations re-run + designs without random effects only (``limma_voom`` and ``limma_trend``), whose + samples are exchangeable under the null; a design with several samples of a + donor needs the donors' labels permuted instead. + +References: + Nakatsuka N, Adler D, Jiang L, Hartman A, Cheng E, Klann E, Satija R (2025). + Improving reproducibility of differentially expressed genes in single-cell + transcriptomic studies of neurodegenerative diseases through meta-analysis. + Nature Communications 16, 7436. doi:10.1038/s41467-025-62579-z + + Phipson B, Smyth GK (2010). Permutation p-values should never be zero: calculating + exact p-values when permutations are randomly drawn. Statistical Applications in + Genetics and Molecular Biology 9, article 39. doi:10.2202/1544-6115.1585 + + 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 +""" + +import inspect +import json +import numbers +import warnings +from collections.abc import Callable, Iterator, Mapping +from dataclasses import dataclass +from typing import Any + +import numpy as np + +from ._analysis_records import absence, software_versions, study_keys +from ._columns import text_column +from ._masked_arrays import reject_masked +from ._messages import PACKAGE_DIRECTORY, fitted_lines, fitted_message, listing, quoted +from ._options import index_integer, random_generator +from ._study_statistics import ( + contrast_levels, + signed_statistics, + study_results, + study_statistics, +) +from .cube import Cube, CubeCollection +from .design import Design +from .differential_expression import ( + DifferentialExpressionResult, + limma_trend, + limma_voom, +) +from .stats.multiple_testing import adjust_p_values +from .stats.sumrank import _capped_sums, _NullCounts, _studies_summed +from .study_results import StudyResults +from .table import Table + +__all__ = ["SumRankResult", "sumrank"] + +FloatArray = np.ndarray + +_DIRECTIONS = ("up", "down") +# The analyses that permutations re-run: a design without random effects, whose +# samples are exchangeable under the null. +_RERUN: dict[str, Callable[..., DifferentialExpressionResult]] = { + "limma_voom": limma_voom, + "limma_trend": limma_trend, +} + + +# SumRank of the studies of a collection. + + +class SumRankResult: + """SumRank of the differential expression of the studies of a collection. + + Built by :func:`sumrank`, for one group (cell type) and contrast. The table of + features (``features``) has one row per gene that every study tested, in the + collection's order of the features: + + - ``feature_id`` and, when the collection's features have it, ``symbol``; + - ``up_statistic``: the SumRank statistic of the gene up, the capped sum of its + best normalized ranks (small for a gene up in the studies that rank it best); + - ``up_p_value``: its p-value, from permutations when they were run + (``settings["p_values"]`` is ``"permutation"``), from the Irwin-Hall + distribution otherwise (``"irwin_hall"``); + - ``up_adjusted_p_value``: Benjamini-Hochberg over the genes of the table + (Benjamini and Hochberg 1995); + - ``up_irwin_hall_p_value``: the Irwin-Hall p-value, also with permutations; + - ``down_statistic``, ``down_p_value``, ``down_adjusted_p_value`` and + ``down_irwin_hall_p_value``: the same for the gene down. + + The table of studies has one row per study of the collection: ``study``, + ``analysed`` (whether its results test the group and contrast), ``reason`` (why + not; empty when analysed) and ``n_features_tested`` (0 when not analysed). + + Immutable. + + Raises: + TypeError: If ``features`` or ``studies`` is not a ``Table``, or + ``provenance`` not a mapping that JSON can write. + ValueError: If ``provenance`` holds a NaN or an infinity. + """ + + __slots__ = ("_features", "_studies", "_record") + + _features: Table + _studies: Table + _record: str + + def __init__( + self, features: Table, studies: Table, provenance: Mapping[str, Any] + ) -> None: + for name, given in (("features", features), ("studies", studies)): + if not isinstance(given, Table): + raise TypeError(f"{name} must be a Table, got {type(given).__name__}") + if not isinstance(provenance, Mapping): + raise TypeError( + f"provenance must be a mapping, got {type(provenance).__name__}" + ) + state = { + "_features": features, + "_studies": studies, + # JSON holds no arrays: json.dumps raises TypeError for one. + "_record": json.dumps(provenance, allow_nan=False), + } + for name, value in state.items(): + object.__setattr__(self, name, value) + + def __setattr__(self, name: str, value: object) -> None: + raise AttributeError("SumRankResult is immutable") + + def __delattr__(self, name: str) -> None: + raise AttributeError("SumRankResult is immutable") + + def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: + return ( + SumRankResult, + (self._features, self._studies, json.loads(self._record)), + ) + + @property + def features(self) -> Table: + """The statistics and p-values of every feature ranked.""" + return self._features + + @property + def studies(self) -> Table: + """Every study of the collection, whether it analysed the group and + contrast, and why not.""" + return self._studies + + @property + def settings(self) -> dict[str, Any]: + """The settings, as a new dict (also in the provenance), with the group, the + contrast, the proportion of studies, the numbers of studies and of ranks + summed, the source of the p-values and the permutations.""" + settings: dict[str, Any] = json.loads(self._record)["settings"] + return settings + + @property + def provenance(self) -> dict[str, Any]: + """A JSON-serialisable record of what produced every number, as a new dict. + + It holds the CytePool, numpy, numba and Python versions; the settings; the + number of genes ranked and of genes left out, measured by a study but not + tested by every study that analysed the group and contrast; and the + provenance of every study's differential-expression result, by study name, + which identifies its cube by its fingerprint and gives its design and + settings and the samples of every group's fit. + """ + record: dict[str, Any] = json.loads(self._record) + return record + + def __repr__(self) -> str: + settings = self.settings + table = self._features + lines = [ + f"SumRankResult for group {settings['group']!r} and contrast " + f"{settings['contrast']!r}: {settings['n_studies']:,} studies, the best " + f"{settings['n_studies_summed']:,} summed; {len(table):,} genes" + ] + if settings["p_values"] == "permutation": + lines.append( + f" p-values from {settings['n_permutations']:,} permutations of " + f"{settings['permute']!r}" + ) + else: + lines.append(" p-values from the Irwin-Hall distribution") + counts = [ + int(np.count_nonzero(table[f"{d}_adjusted_p_value"] < 0.05)) + for d in _DIRECTIONS + ] + lines.append( + f" adjusted p-value below 0.05: {counts[0]:,} up, {counts[1]:,} down" + ) + return fitted_lines("\n".join(lines)) + + +def sumrank( + studies: StudyResults, + group: str | None = None, + contrast: str | None = None, + *, + n_studies_summed: int | None = None, + proportion_of_studies: float | None = None, + n_permutations: int = 0, + permute: str | None = None, + rng: np.random.Generator | int | None = None, +) -> SumRankResult: + """SumRank of one group and contrast across the studies of a collection. + + Each study's result is the analysis of its aligned cube + (:meth:`~cytepool.CubeCollection.aligned_cube`), such as + :func:`~cytepool.analyse_each_study` gives. For the group and contrast, the + studies whose results test them each rank the genes that every one of them + tested by their signed -log10 p-value, and each gene's best ranks are summed, as + :func:`~cytepool.stats.sumrank.fit_sumrank` sums them; the module description + gives the statistic and its p-values. + + With ``n_permutations``, the values of the design variable ``permute``, such as + the condition, are permuted among the samples of each study's fit of the group, + and each study's analysis is run again on its aligned cube with the design and + settings its result records (``limma_voom`` or ``limma_trend``), for this group + and contrast only; the SumRank statistics of every permutation make the null + distribution of the p-values. Each permutation costs one analysis of the group in + every study (see "Cost" in the module description). + + Args: + studies: The differential-expression result of every study of a + collection, from :func:`~cytepool.limma_voom`, + :func:`~cytepool.limma_trend` or :func:`~cytepool.dream_voom` (without + permutations), as :func:`~cytepool.analyse_each_study` gives them. + group: An aligned group id of the collection; it may be left out when the + studies analysed one group. + contrast: A contrast name that the studies tested; it may be left out when + they tested one. + n_studies_summed: The number of studies, among those that analysed the + group and contrast, whose ranks are summed for each gene: those that + rank it best; by default every study. + proportion_of_studies: Instead of ``n_studies_summed``, the proportion of + those studies whose ranks are summed, in (0, 1]; 1 sums every study. + n_permutations: The number of permutations, at least 0; 0 gives Irwin-Hall + p-values only. + permute: The variable of the studies' designs whose values are permuted, + such as ``"condition"``. By default, the factor whose two levels the + contrast compares in every study's design (a level against the + reference, or one level minus another); it must be given otherwise. + rng: A ``numpy.random.Generator`` to draw the permutations from, an integer + seed for one, or None for a fresh, unseeded generator. + + Returns: + The statistics and p-values of every gene that every study analysing the + group and contrast tested, a table of the studies, and a provenance record. + + Warns: + RuntimeWarning: If fewer than all the studies are summed without + permutations: the p-values are then the Irwin-Hall p-values, which are + too small for such sums. + + Raises: + TypeError: If ``studies`` is not a ``StudyResults``, an argument has the + wrong type, or is or holds a numpy masked array. + ValueError: If a key is left out and the studies analysed several, a result + did not analyse its study's aligned cube, no study analysed the group and + contrast, the contrast is an F-test, fewer than two genes were tested by + every study, an option is invalid, both ``n_studies_summed`` and + ``proportion_of_studies`` are given, ``permute`` is given without + permutations, or is needed with them and the contrast does not compare + two levels of one factor in every study's design, or a study cannot be + permuted: its analysis has random effects, its design has no variable + ``permute``, or a permutation leaves the contrast untestable. + KeyError: If ``group`` is not a group of the collection, or no study tested + ``contrast``. + """ + studies = study_results(studies, "sumrank") + collection = studies.collection + reject_masked(n_permutations, "n_permutations") + permutations = _n_permutations(n_permutations) + if permute is not None and not isinstance(permute, str): + reject_masked(permute, "permute") + raise TypeError( + f"permute must be a design variable name (str), got {type(permute).__name__}" + ) + if not permutations and permute is not None: + raise ValueError( + "permute names the variable that permutations permute, but n_permutations " + "is 0; give the number of permutations" + ) + generator = random_generator(rng) if permutations else None + group, contrast = study_keys(studies, group, contrast) + inputs = study_statistics(studies, group, contrast) + used = inputs.analysed + if not used.any(): + raise ValueError( + f"no study tested group {group!r} and contrast {contrast!r}: " + + "; ".join( + f"{study}: {reason}" + for study, reason in zip( + collection.studies, inputs.reasons, strict=True + ) + ) + ) + analysed = [ + s for s, flag in zip(collection.studies, used.tolist(), strict=True) if flag + ] + statistics = inputs.statistics[used] + summed, proportion = _studies_summed( + n_studies_summed, proportion_of_studies, len(analysed) + ) + if permutations and permute is None: + compared = contrast_levels(studies, contrast, analysed) + if compared is None: + raise ValueError( + f"permutations permute the values of one variable of each study's " + f"design, and contrast {contrast!r} does not compare two levels of one " + "factor in every study's design; give the variable's name as permute" + ) + permute = compared[0] + if summed < len(analysed) and not permutations: + warnings.warn( + fitted_message( + f"the Irwin-Hall p-values of a sum of the best {summed} of " + f"{len(analysed)} studies' ranks are too small, as the sum of the best " + "ranks is smaller than a sum of uniform ranks; the p_value columns " + "hold them, so take p-values from permutations (n_permutations and " + "permute)" + ), + RuntimeWarning, + skip_file_prefixes=(PACKAGE_DIRECTORY,), + ) + observed = _capped_sums(statistics, summed, "the studies' results") + counts = None + if permutations: + assert generator is not None and permute is not None + counts = _NullCounts(observed) + for b, permuted in enumerate( + _permuted_statistics( + studies, + analysed, + group, + contrast, + permute, + permutations, + generator, + ) + ): + counts.add(_capped_sums(permuted, summed, f"permutation {b}")) + ranked = observed.ranked + features = collection.features + columns: dict[str, Any] = {"feature_id": features["feature_id"][ranked]} + if "symbol" in features: + columns["symbol"] = features["symbol"][ranked] + p_values = [observed.up_p, observed.down_p] if counts is None else counts.p_values() + for direction, statistic, p_value, irwin_hall in zip( + _DIRECTIONS, + (observed.up, observed.down), + p_values, + (observed.up_p, observed.down_p), + strict=True, + ): + columns[f"{direction}_statistic"] = statistic + columns[f"{direction}_p_value"] = p_value + columns[f"{direction}_adjusted_p_value"] = adjust_p_values(p_value, "bh") + columns[f"{direction}_irwin_hall_p_value"] = irwin_hall + measured = inputs.measured[used].any(axis=0) + settings = { + "group": group, + "contrast": contrast, + "statistic": "signed -log10 p-value", + "proportion_of_studies": proportion, + "n_studies": len(analysed), + "n_studies_summed": summed, + "p_values": "irwin_hall" if counts is None else "permutation", + "n_permutations": permutations, + "permute": permute, + "seed": ( + int(rng) + if isinstance(rng, numbers.Integral) + and not isinstance(rng, bool | np.bool_) + else None + ), + "n_null_values": 0 if counts is None else counts.n_values, + "p_value_adjustment": "BH", + } + return SumRankResult( + Table(columns), + Table( + { + "study": text_column(list(collection.studies)), + "analysed": used.copy(), + "reason": text_column(inputs.reasons), + "n_features_tested": inputs.n_tested, + } + ), + { + "analysis": "sumrank", + "versions": software_versions(), + "settings": settings, + "n_features": int(np.count_nonzero(ranked)), + "n_features_left_out": int(np.count_nonzero(measured & ~ranked)), + "studies": {name: studies[name].provenance for name in collection.studies}, + }, + ) + + +def _n_permutations(value: Any) -> int: + if isinstance(value, bool | np.bool_): + raise TypeError("n_permutations must be a whole number, not bool") + number = index_integer(value, "n_permutations", expected="a whole number") + if number < 0: + raise ValueError(f"n_permutations must be at least 0, got {number}") + return number + + +# Permutations: each study's analysis re-run with the labels of its samples permuted. + + +@dataclass(frozen=True) +class _Rerun: + """What it takes to run a study's analysis again with permuted labels.""" + + study: str + cube: Cube + function: Callable[..., DifferentialExpressionResult] + options: dict[str, Any] + formula: str + levels: dict[str, list[Any]] + samples: dict[str, list[Any]] + weights: list[float] # of the contrast on the design's columns + used: list[int] # the design rows of the samples of the group's fit + fingerprint: str + settings: dict[str, Any] + + +def _permuted_statistics( + studies: StudyResults, + analysed: list[str], + group: str, + contrast: str, + permute: str, + n_permutations: int, + generator: np.random.Generator, +) -> Iterator[FloatArray]: + """The signed statistics of every study for each permutation, ``(n_studies, + n_features)`` in the collection's order, NaN where an analysis did not test a + gene. The permutations are drawn permutation by permutation, study by study.""" + collection = studies.collection + reruns = [ + _rerun(collection, studies[study], study, group, contrast, permute) + for study in analysed + ] + index = { + feature: position + for position, feature in enumerate(collection.features["feature_id"].tolist()) + } + checked = [False] * len(reruns) + for _ in range(n_permutations): + statistics = np.full((len(reruns), len(index)), np.nan) + for row, rerun in enumerate(reruns): + values = list(rerun.samples[permute]) + drawn = generator.permutation(len(rerun.used)) + for position, source in zip(rerun.used, drawn.tolist(), strict=True): + values[position] = rerun.samples[permute][rerun.used[source]] + design = Design( + {**rerun.samples, permute: values}, + rerun.formula, + levels=rerun.levels, + contrasts={contrast: rerun.weights}, + ) + result = rerun.function(rerun.cube, design, groups=[group], **rerun.options) + if not checked[row]: + _check_rerun(rerun, result) + checked[row] = True + table = result.tables.get((group, contrast)) + if table is None: + raise ValueError( + f"a permutation of {permute!r} in study {rerun.study!r} leaves " + f"contrast {contrast!r} untestable in group {group!r}: " + f"{absence(result, group, contrast)}" + ) + positions = np.array( + [index[feature] for feature in table["feature_id"].tolist()], + dtype=np.intp, + ) + statistics[row, positions] = signed_statistics( + table["p_value"], table["log2_fold_change"] + ) + yield statistics + + +def _rerun( + collection: CubeCollection, + result: DifferentialExpressionResult, + study: str, + group: str, + contrast: str, + permute: str, +) -> _Rerun: + """How to run a study's analysis again: its function and options, its design, + and the samples of its fit of the group, from what its result records.""" + function = _RERUN.get(result.method) + if function is None: + raise ValueError( + f"the result of study {study!r} is from {result.method}, whose design has " + "random effects: its samples are not exchangeable under the null, so its " + "labels cannot be permuted sample by sample; permutations re-run " + "limma_voom and limma_trend" + ) + record = result.provenance + design = record["design"] + samples: dict[str, list[Any]] = design["samples"] + variables = [name for name in samples if name != "sample_id"] + if permute not in variables: + raise ValueError( + f"permute names {permute!r}, which is not a variable of the design of " + f"study {study!r}; its variables are: {quoted(variables) or '(none)'}" + ) + settings: dict[str, Any] = record["settings"] + parameters = inspect.signature(function).parameters + options = { + name: settings[name] + for name, parameter in parameters.items() + if parameter.kind is inspect.Parameter.KEYWORD_ONLY + and name not in ("groups", "contrasts") + and name in settings + } + if options.get("expression_filter", True) is None: + options["expression_filter"] = False # recorded as None when it was off + ids = samples["sample_id"] + rows = {sample: position for position, sample in enumerate(ids)} + table = result.samples + in_group = (table["group_id"] == group) & table["used"].astype(bool) + used = [rows[sample] for sample in table["sample_id"][in_group].tolist()] + return _Rerun( + study=study, + cube=collection.aligned_cube(study), + function=function, + options=options, + formula=design["formula"], + levels=design["levels"], + samples=samples, + weights=design["contrasts"][contrast], + used=used, + fingerprint=record["cube"]["fingerprint"], + settings=settings, + ) + + +def _check_rerun(rerun: _Rerun, result: DifferentialExpressionResult) -> None: + """Refuse a re-run on another cube, or with other settings, than the study's + analysis: its permutations would not be those of the study's results.""" + record = result.provenance + if record["cube"]["fingerprint"] != rerun.fingerprint: + raise ValueError( + f"the result of study {rerun.study!r} was computed from a cube whose " + f"fingerprint, {rerun.fingerprint}, is not that of the study's aligned " + f"cube, {record['cube']['fingerprint']}: analyse " + f"collection.aligned_cube({rerun.study!r})" + ) + if record["settings"] != rerun.settings: + changed = [ + name + for name in sorted(set(record["settings"]) | set(rerun.settings)) + if record["settings"].get(name) != rerun.settings.get(name) + ] + raise ValueError( # pragma: no cover - the options are those recorded + f"the analysis of study {rerun.study!r} could not be re-run with its " + f"recorded settings; these differ: {listing(changed)}" + ) diff --git a/cytepool/variance_components.py b/cytepool/variance_components.py new file mode 100644 index 0000000..c33d3cc --- /dev/null +++ b/cytepool/variance_components.py @@ -0,0 +1,559 @@ +"""Variance partitioning of every group (cell type) of a cube. + +:func:`~cytepool.variance_partitioning` takes, in every group of a cube, the steps of +:func:`~cytepool.dream_voom` up to voom's precision weights (Law et al. 2014), the +mean-variance trend of the design's mixed model (Hoffman and Roussos 2021), and +partitions the variance of every feature among the variables of the design, as +variancePartition's ``fitExtractVarPartModel`` does (Hoffman and Schadt 2016): +:mod:`cytepool.stats.variance_fractions` gives the model, the fractions and the +conventions found by probing variancePartition, and +:func:`~cytepool.stats.variance_fractions.variance_fractions` partitions the variance +of a samples x features matrix. + +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 + + 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 +""" + +import json +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from types import MappingProxyType +from typing import Any, Literal + +import numpy as np +import numpy.typing as npt + +from ._analysis_records import ( + cube_record, + matched_samples, + mixed_design_record, + only_key, + software_versions, +) +from ._columns import text_column +from ._counts import effective_library_sizes +from ._group_fits import ( + COUNTS_LAYER, + FITTED, + GroupInputs, + GroupRecord, + check_sample_groups, + filter_setting, + group_positions, + min_cells_setting, + min_counts_setting, + normalization_setting, + prepare_group, + prior_weight_setting, + sample_table, + span_setting, + threads_setting, +) +from ._jit import thread_limit +from ._masked_arrays import reject_masked +from ._messages import fitted_lines, listing +from .cube import Cube +from .design import Contrasts, Design +from .stats._mean_variance_trend import scaled_prior_weights, trend_span +from .stats._mixed_model_designs import maximum_likelihood +from .stats.dream import _dream_weights +from .stats.linear_models import fit_linear_model +from .stats.variance_fractions import ( + _checked_terms, + _linear_fractions, + _mixed_fractions, + _Terms, +) +from .stats.voom import voom +from .table import Table + +__all__ = ["VariancePartitioningResult", "variance_partitioning"] + +FloatArray = npt.NDArray[np.float64] +BoolArray = npt.NDArray[np.bool_] + +# Columns of the tables of a cube's groups other than the fractions. +_IDENTITY = ("feature_id", "symbol") +_METHOD = "variance_partitioning" +# The fits behind the precision weights, recorded in the provenance: maximum +# likelihood, as variancePartition's voomWithDreamWeights fits. +_FIXED_SETTINGS = {"weights_fit": "ml"} + + +# Every group of a cube. + + +class VariancePartitioningResult: + """The fractions of variance of every group of a cube, from + :func:`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_id`` and, 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 in + ``variables`` (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_used`` and ``n_samples_left_out``; and ``n_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 + :class:`~cytepool.DifferentialExpressionResult`: + ``group_id``, ``sample_id``, ``n_cells`` (when the cube records cell numbers), + ``used``, ``reason``, and the ``library_size`` and ``norm_factor`` that entered the + fit; with ``prior_weights``, the ``prior_weight`` of each sample. + + Built by :func:`variance_partitioning`. Immutable. + + Raises: + TypeError: If ``variables`` is not a sequence of names, ``summary`` or + ``samples`` not a ``Table``, ``tables`` not a mapping of group id to + ``Table``, or ``provenance`` not a mapping that JSON can write. + ValueError: If ``provenance`` holds a NaN or an infinity. + """ + + __slots__ = ("_variables", "_summary", "_samples", "_tables", "_record") + + _variables: tuple[str, ...] + _summary: Table + _samples: Table + _tables: Mapping[str, Table] + _record: str + + def __init__( + self, + variables: Sequence[str], + summary: Table, + samples: Table, + tables: Mapping[str, Table], + provenance: Mapping[str, Any], + ) -> None: + if isinstance(variables, str) or not all( + isinstance(name, str) for name in variables + ): + raise TypeError("variables must be a sequence of variable names (str)") + for name, table in (("summary", summary), ("samples", samples)): + if not isinstance(table, Table): + raise TypeError(f"{name} must be a Table, got {type(table).__name__}") + if not isinstance(tables, Mapping): + raise TypeError( + f"tables must be a mapping of group id to Table, got " + f"{type(tables).__name__}" + ) + for key, table in tables.items(): + if not isinstance(key, str) or not isinstance(table, Table): + raise TypeError( + f"tables must map group ids (str) to Tables, got {key!r}: " + f"{type(table).__name__}" + ) + if not isinstance(provenance, Mapping): + raise TypeError( + f"provenance must be a mapping, got {type(provenance).__name__}" + ) + state = { + "_variables": tuple(variables), + "_summary": summary, + "_samples": samples, + "_tables": MappingProxyType(dict(tables)), + # JSON holds no arrays: json.dumps raises TypeError for one. + "_record": json.dumps(provenance, allow_nan=False), + } + for name, value in state.items(): + object.__setattr__(self, name, value) + + def __setattr__(self, name: str, value: object) -> None: + raise AttributeError("VariancePartitioningResult is immutable") + + def __delattr__(self, name: str) -> None: + raise AttributeError("VariancePartitioningResult is immutable") + + def __reduce__(self) -> tuple[Any, tuple[Any, ...]]: + return ( + VariancePartitioningResult, + ( + self._variables, + self._summary, + self._samples, + dict(self._tables), + json.loads(self._record), + ), + ) + + @property + def variables(self) -> tuple[str, ...]: + """The variables, in the order of the columns of the tables, residuals last.""" + return self._variables + + @property + def groups(self) -> tuple[str, ...]: + """The ids of the groups analysed, in order.""" + return tuple(self._summary["group_id"].tolist()) + + @property + def summary(self) -> Table: + """The status, samples and features of every group.""" + return self._summary + + @property + def samples(self) -> Table: + """Every design sample of every group, used in its fit or left out, and why.""" + return self._samples + + @property + def tables(self) -> Mapping[str, Table]: + """The table of fractions of every group that was fitted, by group id.""" + return self._tables + + @property + def settings(self) -> dict[str, Any]: + """The settings of the analysis, as a new dict (also in the provenance).""" + settings: dict[str, Any] = json.loads(self._record)["settings"] + return settings + + @property + def provenance(self) -> dict[str, Any]: + """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 with ``prior_weights`` the prior weights of + its samples. The cost of the cube's record is that of + :func:`~cytepool.limma_voom`'s ("Provenance"). + """ + record: dict[str, Any] = json.loads(self._record) + return record + + def table(self, group: str | None = None) -> Table: + """Return the table of fractions of one group. + + Args: + group: A group id; it may be left out when the result has one group. + + Raises: + ValueError: If ``group`` is left out and the result has several groups. + KeyError: If the group was not analysed or not fitted; the message says + why. + """ + group = only_key(group, list(self.groups), "group", "groups") + found = self._tables.get(group) + if found is not None: + return found + record = {entry["group_id"]: entry for entry in self.provenance["groups"]} + if group not in record: + raise KeyError( + f"no group {group!r} was analysed; groups are: " + f"{listing(list(record)) or '(none)'}" + ) + entry = record[group] + raise KeyError( + f"group {group!r} was not fitted ({entry['status']}): {entry['reason']}" + ) + + def __repr__(self) -> str: + statuses = self._summary["status"].tolist() + fitted = statuses.count(FITTED) + lines = [ + f"VariancePartitioningResult: {len(statuses):,} group" + f"{'' if len(statuses) == 1 else 's'}, {fitted:,} fitted; variables: " + + ", ".join(self._variables), + ] + for row in range(len(statuses)): + group = self._summary["group_id"][row] + if statuses[row] != FITTED: + lines.append(f" {group}: not fitted ({statuses[row]})") + continue + used = int(self._summary["n_samples_used"][row]) + features = int(self._summary["n_features"][row]) + lines.append(f" {group}: {used:,} samples, {features:,} features") + return fitted_lines("\n".join(lines)) + + +@dataclass +class _GroupFit(GroupRecord): + """What happened in one group, and its table of fractions.""" + + table: Table | None = None + + +def variance_partitioning( + cube: Cube, + design: Design, + *, + groups: str | Sequence[str] | None = None, + min_cells: int = 10, + min_counts: float = 0, + expression_filter: bool | Mapping[str, Any] = True, + normalization: Literal["tmm"] | None = "tmm", + span: float | None = None, + prior_weights: Literal["n_cells"] | None = None, + method: Literal["ml", "reml"] = "ml", + n_threads: int | None = None, +) -> VariancePartitioningResult: + """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 ``fitExtractVarPartModel`` on the + ``voomWithDreamWeights`` output of every cell type. For each group, the samples are + left out, the features filtered and the counts normalized as for + :func:`~cytepool.dream_voom`; voom's precision weights come from the mean-variance + trend of the design's mixed model, fitted by maximum likelihood + (:func:`~cytepool.stats.dream.voom_with_dream_weights`), or from limma's voom for a + design without random effects; and the fractions of every feature are those of + :func:`~cytepool.stats.variance_fractions.variance_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. + + Args: + cube: A cube with a ``counts`` layer. + design: A :class:`~cytepool.Design` with an intercept, matched to the + cube by ``sample_id`` as for :func:`~cytepool.limma_voom`, whose categorical + variables are random intercepts and continuous variables fixed effects, such + as ``Design(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 ``filterByExpr`` defaults with the + group's fixed effects, a mapping of changed settings or of the column of + ``"sample_groups"``, as for :func:`~cytepool.limma_voom`, or False. With a + random effect that separates groups whose expression may differ, such as the + tissues of each donor, ``{"sample_groups": "tissue"}`` keeps the features + expressed in any one of them. + 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 ``voom`` + does. variancePartition's ``voomWithDreamWeights`` uses 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, as + ``voomWithDreamWeights`` fits 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 ``cube`` is not a Cube, ``design`` not a Design, an option has + the wrong type, or ``groups``, ``min_cells``, ``expression_filter`` or + ``n_threads`` is or holds a numpy masked array. + ValueError: If the design cannot be partitioned (as for + :func:`~cytepool.stats.variance_fractions.variance_fractions`), has no + ``sample_id`` column, names samples the cube does not have or names a sample + twice, a variable is named ``"feature_id"`` or ``"symbol"``, an option is + invalid, or ``prior_weights`` is ``"n_cells"`` and the cube does not record + cell numbers. + KeyError: If a group is not in the cube. + """ + reject_masked(groups, "groups") + if not isinstance(cube, Cube): + raise TypeError(f"cube must be a Cube, got {type(cube).__name__}") + terms = _checked_terms(design) + clashing = [name for name in terms.variables if name in _IDENTITY] + if clashing: + raise ValueError( + f"a variable is named {clashing[0]!r}, a column of the tables of features; " + "rename it in the sample table" + ) + settings = { + "min_cells": min_cells_setting(min_cells), + "min_counts": min_counts_setting(min_counts), + "expression_filter": filter_setting(expression_filter), + "normalization": normalization_setting(normalization), + "span": span_setting(span), + "prior_weights": prior_weight_setting(prior_weights), + "method": "ml" if maximum_likelihood(method) else "reml", + "n_threads": threads_setting(n_threads), + } + if settings["prior_weights"] == "n_cells" and cube.n_cells is None: + raise ValueError( + "prior_weights='n_cells' weights each sample by its number of cells, which " + "this cube does not record (a cube of bulk samples has none); leave " + "prior_weights at None" + ) + check_sample_groups(settings, design.samples) + cube_rows, order = matched_samples( + cube, + design, + no_ids="the design's sample table has no 'sample_id' column, so its rows cannot " + "be matched to the cube's samples; build the Design from cube.samples, or from " + "a table whose 'sample_id' column holds cube sample ids", + text_ids="the cube's does", + data="the cube", + data_ids="the cube's sample ids", + ) + positions = group_positions(cube, groups) + inputs = GroupInputs(_METHOD, cube, order, cube_rows, None, settings) + fits = [_fit_group(inputs, design, terms, g) for g in positions] + sample_ids = cube.samples["sample_id"][cube_rows] + no_contrasts = Contrasts(np.empty((len(design.columns), 0)), (), design.columns) + return VariancePartitioningResult( + terms.variables, + _summary_table(fits), + sample_table(cube, cube_rows, sample_ids, positions, fits, settings), + {fit.group_id: fit.table for fit in fits if fit.table is not None}, + { + "method": _METHOD, + "versions": software_versions(), + "cube": cube_record(cube), + "layer": COUNTS_LAYER, + "design": mixed_design_record(design, no_contrasts), + "settings": {**settings, **_FIXED_SETTINGS}, + "groups": [_group_record(fit, sample_ids, settings) for fit in fits], + }, + ) + + +def _fit_group(inputs: GroupInputs, design: Design, terms: _Terms, g: int) -> _GroupFit: + """Partition the variance of one group, or record why it cannot be done.""" + cube, settings = inputs.cube, inputs.settings + mixed = bool(design.random) + fit, data = prepare_group(_GroupFit, inputs, design, g, mixed=mixed) + if data is None: + return fit + y = data.counts + n_used, n_tested = y.shape + with_counts = int(np.count_nonzero(y.any(axis=0))) + if with_counts < 2: + return fit.stop( + "too_few_features", + "the mean-variance trend of the precision weights needs at least two " + f"features with counts, and {with_counts} of the {n_tested} features " + f"{'has' if with_counts == 1 else 'have'} counts", + ) + ml = settings["method"] == "ml" + with thread_limit(settings["n_threads"]): + try: + if data.mixed is not None: + sizes = np.array(effective_library_sizes(y, None, data.factors)) + fraction = trend_span(settings["span"], n_tested) + prior = ( + None + if data.prior is None + else scaled_prior_weights(data.prior, y.shape) + ) + weighted = _dream_weights(y, sizes, fraction, data.mixed, prior, True) + else: + weighted = voom( + y, + data.x, + norm_factors=data.factors, + span=settings["span"], + weights=data.prior, + ) + except ValueError as error: # the trend cannot give weights for these data + return fit.stop("failed", f"precision weights: {error}") + if data.mixed is not None: + fractions = _mixed_fractions( + weighted.log2_cpm, weighted.weights, ml, data.mixed, terms + ) + else: + rows = np.ascontiguousarray(weighted.weights.T) + linear = fit_linear_model( + weighted.log2_cpm, + data.x, + weights=weighted.weights, + coefficient_names=data.columns, + n_threads=settings["n_threads"], + ) + fractions = _linear_fractions( + weighted.log2_cpm, data.x, rows, linear, ml, terms + ) + fit.voom_span = weighted.span + fitted = np.all(np.isfinite(fractions.fractions), axis=1) + fit.features_not_fitted = int(np.count_nonzero(~fitted)) + fit.features_not_converged = int(np.count_nonzero(fitted & ~fractions.converged)) + fit.library_sizes = data.sizes + fit.norm_factors = np.ones(n_used) if data.factors is None else data.factors + fit.prior_weights = data.prior + features = cube.features + columns: dict[str, Any] = {"feature_id": features["feature_id"][fit.tested]} + if "symbol" in features: + columns["symbol"] = features["symbol"][fit.tested] + for position, name in enumerate(fractions.variables): + columns[name] = fractions.fractions[:, position] + fit.table = Table(columns) + return fit + + +def _summary_table(fits: list[_GroupFit]) -> Table: + return Table( + { + "group_id": text_column([fit.group_id for fit in fits]), + "status": text_column([fit.status for fit in fits]), + "reason": text_column([fit.reason for fit in fits]), + "n_samples_used": np.array( + [np.count_nonzero(fit.used) for fit in fits], dtype=np.int64 + ), + "n_samples_left_out": np.array( + [np.count_nonzero(~fit.used) for fit in fits], dtype=np.int64 + ), + "n_features": np.array( + [fit.tested.size if fit.status == FITTED else 0 for fit in fits], + dtype=np.int64, + ), + } + ) + + +def _group_record( + fit: _GroupFit, sample_ids: np.ndarray, settings: Mapping[str, Any] +) -> dict[str, Any]: + fitted = fit.status == FITTED + record: dict[str, Any] = { + "group_id": fit.group_id, + "status": fit.status, + "reason": fit.reason, + "samples": sample_ids[fit.used].tolist(), + "left_out": { + sample_id: reason + for sample_id, reason in zip(sample_ids.tolist(), fit.reasons, strict=True) + if reason + }, + "aliased_columns": list(fit.aliased), + "n_features": int(fit.tested.size) if fitted else 0, + "voom_span": fit.voom_span, + "features_not_fitted": fit.features_not_fitted if fitted else None, + "features_not_converged": fit.features_not_converged if fitted else None, + } + if settings["prior_weights"] is not None: + weights = fit.prior_weights + record["prior_weights"] = ( + weights.tolist() if fitted and weights is not None else None + ) + return record diff --git a/docs/coverage_exemptions.toml b/docs/coverage_exemptions.toml index 23ac7a5..4f04490 100644 --- a/docs/coverage_exemptions.toml +++ b/docs/coverage_exemptions.toml @@ -54,24 +54,9 @@ "cytepool.stats.special.log_gammaincc" = "a special function behind the distributions and the moderation, documented in the API reference and checked against mpmath (validation/distributions.py)" "cytepool.stats.special.trigamma" = "a special function behind the distributions and the moderation, documented in the API reference and checked against mpmath (validation/distributions.py)" "cytepool.stats.special.trigamma_inverse" = "a special function behind the distributions and the moderation, documented in the API reference and checked against mpmath (validation/distributions.py)" -"cytepool.table.Table.equals" = "a method of CytePool's minimal table of columns, documented in the API reference; the guides show the common ones" -"cytepool.table.Table.rename" = "a method of CytePool's minimal table of columns, documented in the API reference; the guides show the common ones" -"cytepool.table.Table.take" = "a method of CytePool's minimal table of columns, documented in the API reference; the guides show the common ones" -"cytepool.table.Table.to_dict" = "a method of CytePool's minimal table of columns, documented in the API reference; the guides show the common ones" +"cytepool.Table.equals" = "a method of CytePool's minimal table of columns, documented in the API reference; the guides show the common ones" +"cytepool.Table.rename" = "a method of CytePool's minimal table of columns, documented in the API reference; the guides show the common ones" +"cytepool.Table.take" = "a method of CytePool's minimal table of columns, documented in the API reference; the guides show the common ones" +"cytepool.Table.to_dict" = "a method of CytePool's minimal table of columns, documented in the API reference; the guides show the common ones" [planned] -"cytepool.cube.Cube.with_columns" = "guides/pool_cells: a column of the samples to pool by, with collapse_samples" -"cytepool.differential_expression.DifferentialExpressionResult.fit" = "guides/normalization_and_quality: the trend figures of a group of a result" -"cytepool.differential_expression.GroupFit" = "guides/normalization_and_quality: the values and weights behind a group's tables" -"cytepool.networks.Network.to_gene_sets" = "guides/gene_sets: CollecTRI's regulons tested as gene sets" -"cytepool.score_tests.limma" = "guides/pathway_activity: pathway scores and activities tested with the design" -"cytepool.stats.deseq2.DESeq2Results.table" = "guides/bulk_rna_seq: DESeq2's calls by gene" -"cytepool.stats.empirical_bayes.ModeratedFit.table" = "guides/bulk_rna_seq: limma's table of a fit on arrays" -"cytepool.stats.quasi_likelihood.QuasiLikelihoodTest.table" = "guides/bulk_rna_seq: edgeR's calls by gene" -"cytepool.study_results.StudyResults" = "guides/combine_studies: the results of every study, kept together" -"cytepool.study_results.StudyResults.matrix" = "guides/combine_studies: the p-values of every study, combined" -"cytepool.study_results.StudyResults.table" = "guides/combine_studies: the studies lined up gene by gene" -"cytepool.study_results.analyse_each_study" = "guides/combine_studies: every study analysed alike" -"cytepool.table.Table.sort" = "guides/genes_per_cell_type: tables read from the smallest p-value" -"cytepool.plotting.plot_dispersions" = "guides/bulk_rna_seq: DESeq2's dispersion estimates" -"cytepool.plotting.plot_quasi_dispersions" = "guides/bulk_rna_seq: edgeR's quasi-dispersions" diff --git a/docs/coverage_report.py b/docs/coverage_report.py index 09fc21f..16f4986 100644 --- a/docs/coverage_report.py +++ b/docs/coverage_report.py @@ -4,10 +4,10 @@ imports when it is installed.) Every public name must earn a place in a page, or be listed with the reason it needs -none. The public names are those the API reference must document (a module's -``__all__``, or else the public functions and classes it defines, as -``tests/test_documentation.py`` defines them) and the public methods of their -classes. A name is covered when a guide or tutorial uses it (its code imports it, or +none. The public names are those the API reference must document (the names of the +top level, ``cytepool.__all__``, and of a module's ``__all__``, or else the public +functions and classes it defines, as ``tests/test_documentation.py`` defines them) and +the public methods of their classes. A name is covered when a guide or tutorial uses it (its code imports it, or its prose links to it) or an explanation links to it (``page_index.py``); index pages, such as the capability matrix and the glossary, do not count. @@ -54,23 +54,23 @@ def public_names() -> set[str]: methods of the public classes.""" modules = _modules() + def exported(package: ModuleType) -> set[str]: + # The names a package re-exports, and those of the top level. + return set(getattr(package, "__all__", ())) | set(cytepool.__all__) + def implements(module: ModuleType, package: ModuleType) -> bool: - exported = set(getattr(package, "__all__", ())) - return package is not cytepool and bool( - set(page_index.public_names(module)) & exported - ) + return bool(set(page_index.public_names(module)) & exported(package)) public_modules = { module.__name__ for module, package in modules if page_index.public_names(module) and not implements(module, package) } - names = set() + names = {f"cytepool.{name}" for name in cytepool.__all__} for module, package in modules: - exported = set(getattr(package, "__all__", ())) for name in page_index.public_names(module): if module.__name__ in public_modules or ( - implements(module, package) and name not in exported + implements(module, package) and name not in exported(package) ): names.add(f"{module.__name__}.{name}") for name in list(names): diff --git a/docs/datasets.py b/docs/datasets.py index 568a6e2..720abbc 100644 --- a/docs/datasets.py +++ b/docs/datasets.py @@ -169,10 +169,10 @@ def simulated_pbmc(downloads: Path, output: Path) -> None: features = {"feature_id": genes, "symbol": genes} cube = from_cells( SimpleNamespace(**cells), - samples=cell_samples, - groups=cell_types, - sample_table=sample_table, - group_table={"group_id": list(CELL_TYPES)}, + sample_labels=cell_samples, + group_labels=cell_types, + samples=sample_table, + groups={"group_id": list(CELL_TYPES)}, features=features, source={ "dataset": "simulated_pbmc", @@ -536,9 +536,9 @@ def kang2018(downloads: Path, output: Path) -> None: donors = sorted({sample.split("_")[0] for sample in labels["sample"]}) cube = from_cells( SimpleNamespace(**_stack(blocks, GEO_GENES)), - samples=labels["sample"], - groups=labels["group"], - sample_table={ + sample_labels=labels["sample"], + group_labels=labels["group"], + samples={ "sample_id": [f"{d}_{c}" for d in donors for c in ("ctrl", "stim")], "donor": [d for d in donors for _ in range(2)], "condition": ["ctrl", "stim"] * len(donors), diff --git a/docs/page_index.py b/docs/page_index.py index 28885bb..3017665 100644 --- a/docs/page_index.py +++ b/docs/page_index.py @@ -23,11 +23,13 @@ Citations are written ``[@key]`` or ``@key`` and resolve in ``references.bib``. The documented names are read from the API sources, as Sphinx documents them: -``automodule`` documents a module's public names (its ``__all__``, or else the public -functions and classes it defines) that have docstrings, and the methods and -properties of its classes that have docstrings; ``autofunction`` and ``autoclass`` -document one object; ``py:data`` documents a constant under the module the page -documents last, or its ``:module:`` option. +``automodule`` documents a module and its public names (its ``__all__``, or else the +public functions and classes it defines) that have docstrings, and the methods and +properties of its classes that have docstrings; with ``:no-members:`` it documents none +of its names, and with ``:no-index:`` the module itself is no entry, as for the +description of a module whose names are documented at the top level; +``autofunction`` and ``autoclass`` document one object; ``py:data`` documents a +constant under the module the page documents last, or its ``:module:`` option. """ import ast @@ -217,7 +219,10 @@ def api_entries(api_source: Path = API_SOURCE) -> dict[str, ApiEntry]: if kind == "automodule": current = name module = importlib.import_module(name) - documented[name] = "module" + if "no-index" not in options: + documented[name] = "module" + if "no-members" in options: + continue for member in public_names(module): if member not in excluded: documented |= _members( diff --git a/docs/source/guides/anndata_and_mudata.ipynb b/docs/source/guides/anndata_and_mudata.ipynb index bf0a736..fa6c7dd 100644 --- a/docs/source/guides/anndata_and_mudata.ipynb +++ b/docs/source/guides/anndata_and_mudata.ipynb @@ -36,7 +36,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953, anndata 0.13.4\n", + "CytePool 0.1.dev63+g8afa32afb, anndata 0.13.4\n", "AnnData object with n_obs × n_vars = 19151 × 2000\n", " obs: 'sample', 'donor', 'condition', 'cell_type'\n", " layers: None (.X) \n", @@ -66,8 +66,7 @@ "import scipy.sparse\n", "\n", "import cytepool\n", - "from cytepool.cube import from_cells\n", - "from cytepool.io.anndata import from_anndata, from_mudata, to_anndata, to_mudata\n", + "from cytepool import from_anndata, from_cells, from_mudata, to_anndata, to_mudata\n", "\n", "arrays = np.load(\"simulated_pbmc_cells.npz\")\n", "sample = arrays[\"cell_sample\"].astype(str)\n", @@ -93,9 +92,9 @@ "samples = cells.obs[[\"sample\", \"donor\", \"condition\"]].drop_duplicates()\n", "cube = from_cells(\n", " cells.X,\n", - " samples=cells.obs[\"sample\"],\n", - " groups=cells.obs[\"cell_type\"],\n", - " sample_table=samples.rename(columns={\"sample\": \"sample_id\"}),\n", + " sample_labels=cells.obs[\"sample\"],\n", + " group_labels=cells.obs[\"cell_type\"],\n", + " samples=samples.rename(columns={\"sample\": \"sample_id\"}),\n", " features=cells.var_names,\n", ")\n", "print(cube)" @@ -112,8 +111,8 @@ "that scanpy and decoupler make: `obs` holds the sample and cell type of each row, the\n", "columns of the sample and group tables and the number of cells, and every layer of the\n", "cube is a layer of the same name. `X` is left empty, so that no layer is stored twice;\n", - "`pseudobulk.X = pseudobulk.layers[\"counts\"].copy()` gives tools that read `X` the\n", - "counts. `uns[\"cytepool\"]` records the cube's fingerprint and provenance, and\n", + "`to_anndata(cube, x_layer=\"counts\")` also copies the counts into `X`, for tools that\n", + "read `X`. `uns[\"cytepool\"]` records the cube's fingerprint and provenance, and\n", "{api}`from_anndata` reads the AnnData back as the same cube, here after a round trip\n", "through an `.h5ad` file." ] @@ -313,10 +312,10 @@ "summed = sc.get.aggregate(\n", " cells, by=[\"sample\", \"cell_type\"], func=[\"sum\", \"count_nonzero\"]\n", ")\n", - "cube = from_anndata(summed, sample=\"sample\", group=\"cell_type\")\n", + "cube = from_anndata(summed, sample_column=\"sample\", group_column=\"cell_type\")\n", "\n", "pooled = dc.pp.pseudobulk(cells, sample_col=\"sample\", groups_col=\"cell_type\")\n", - "cube = from_anndata(pooled, sample=\"sample\", group=\"cell_type\")\n", + "cube = from_anndata(pooled, sample_column=\"sample\", group_column=\"cell_type\")\n", "\n", "views = li.multi.adata_to_views(\n", " cells, groupby=\"cell_type\", sample_key=\"sample\", obs_keys=[\"condition\"]\n", @@ -340,9 +339,9 @@ " apply when given `filter_samples_kwargs`, drops profiles, and a profile that is\n", " missing looks like a sample without cells. Import every profile, and leave out those\n", " with few cells in the analysis (`min_cells` of {api}`limma_voom`).\n", - "- **Other layouts.** An AnnData of profiles that no tool above made needs `sample=`,\n", - " `group=` and `layers=`, such as `layers={\"counts\": \"X\"}`, and `n_cells=`, the name of\n", - " a column of `obs` or a crosstab of the cells.\n", + "- **Other layouts.** An AnnData of profiles that no tool above made needs\n", + " `sample_column=`, `group_column=` and `layers=`, such as `layers={\"counts\": \"X\"}`,\n", + " and `n_cells=`, the name of a column of `obs` or a crosstab of the cells.\n", "- **Files.** `.h5ad`, `.zarr` and `.h5mu` files keep the fingerprint through a round\n", " trip; a cube file ([Save, share and trace cubes](save_and_trace_cubes.ipynb)) also\n", " reads one cell type at a time." @@ -353,16 +352,16 @@ "datasets": [ "simulated_pbmc" ], - "executed_at": "2026-10-27T08:55:59+00:00", - "executed_code_sha256": "09e48cf19350c4949b5f3b57bf234e674a79fd0809f65dea8505084b6c1debd1", + "executed_at": "2026-10-27T15:22:22+00:00", + "executed_code_sha256": "f32dc3e2bab44d675e06640817d7ca96a07d0cd1479cb82544982ded068997cc", "executed_datasets": { "simulated_pbmc": { "simulated_pbmc.cube.zarr": "sha256:554a2b5b4188308ea900cf907fa87f42d12e0458e6d5dc11525e862f3ad8a726", "simulated_pbmc_cells.npz": "sha256:0d4c9154b87dbf8e9796232c2afec4a8340368fd0f5d386df4dd780c4e478129" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 2.1, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 2.5, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/bulk_rna_seq.ipynb b/docs/source/guides/bulk_rna_seq.ipynb index 0a9e243..758241a 100644 --- a/docs/source/guides/bulk_rna_seq.ipynb +++ b/docs/source/guides/bulk_rna_seq.ipynb @@ -32,7 +32,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953, numpy 2.5.4\n", + "CytePool 0.1.dev63+g8afa32afb, numpy 2.5.4\n", "Cube with 12 samples x 1 groups x 10,000 features\n", " layers: counts (uint32, lazy)\n", " cells per entry: not recorded\n", @@ -43,13 +43,13 @@ } ], "source": [ + "import matplotlib.pyplot as plt\n", "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.io import read_cube\n", + "from cytepool import Design, limma_voom, read_cube\n", "from cytepool.normalization import filter_by_expression, tmm_factors\n", + "from cytepool.plotting import plot_dispersions, plot_quasi_dispersions\n", "from cytepool.stats.deseq2 import (\n", " deseq2_likelihood_ratio_test,\n", " deseq2_wald_test,\n", @@ -172,14 +172,20 @@ "({api}`tmm_factors`). {api}`fit_deseq2` takes every gene and filters on the mean\n", "count when it adjusts the p-values; {api}`deseq2_wald_test` tests one contrast, and\n", "{api}`deseq2_likelihood_ratio_test` tests both drugs at once against the design with\n", - "the batch only." + "the batch only. {api}`plot_quasi_dispersions` draws edgeR's quasi-dispersions before\n", + "and after moderation, as `plotQLDisp` does, and {api}`plot_dispersions` DESeq2's\n", + "dispersion estimates, as `plotDispEsts` does." ] }, { "cell_type": "code", "execution_count": 4, "id": "e4072e29", - "metadata": {}, + "metadata": { + "figure_descriptions": [ + "Two panels for the simulated bulk experiment. Left, edgeR's quasi-dispersions of the 9,593 genes that pass the filter, as quarter-roots, against their average log2 CPM, from about -1 to 14.5: the raw values (grey) spread from about 0.4 to 1.7 and the squeezed values (blue) from about 0.8 to 1.45, around a nearly flat prior trend (orange) between 0.93 and 0.98. Right, DESeq2's dispersions against the mean of normalized counts, both on logarithmic axes: the fitted trend (orange) falls from about 8 at the lowest means to about 0.07 from a mean of about 100 on; the final estimates (blue) lie near it, closer than the gene-wise estimates (grey); 64 outliers, open blue circles between 0.3 and 0.7, keep their gene-wise estimates, and 823 genes whose gene-wise estimate is at the floor of 1e-8 lie along the bottom." + ] + }, "outputs": [ { "name": "stdout", @@ -189,6 +195,16 @@ "quasi-likelihood: common dispersion 0.072, prior df 9.9\n", "DESeq2 likelihood ratio test of both drugs: 563 genes at FDR 5%\n" ] + }, + { + "data": { + "image/png": "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nOd+FdcltyLZIhksrSsLqZop6rEIgiAch0RwRRAGWG+zTrpb1UisdiQIQn84LBI2Rep15orDOnCBlVYjR47kvvvu47777kPTNB544AF+9KMfcemll5Kfn4/f7++0cpDL1fcBqN/v5/vf/z5XXnllp8eSkpJ6vQ/A5s2b+da3vsUjjzzCuecenAbS22sMxPMRhjfZGUvsdfdhP/lCml78LXplYWTZ5o9eILRpGbFffQDbpDnkZzlY/N1R3dZjEQRBEI5tIrgiCAOoL1knrasate7buqpRd9sFQRB6IsuR5Zbf3exBN7rfT5HhounxAzolqCsWi4Vbb72VP/3pTxQWFpKfn8/YsWPZuXNnu/127NjR7vvY2Fiam5vbbSsvL2/3/YQJEyguLu6yIG1f9qmtreWKK67guuuu48477+zz8b15PsKxwzpmWsuyzc/jX/KvyLLNdeV4H78D+5yLiLn6HvKzEkVQRRAE4TglCtoKwgDqKuukO3Hx8cydO4/8/CntgjDdbRcEQTiUG+clc6hKaqYJNw5CHYif/vSnrFq1Kvo3MBQK8cQTT+B0OpkxYwYAixYt4h//+Ae7d+8GYP369SxevLjdeU466STWrFlDUVERALt37+aZZ55pt88999zDc889xxtvvBHdtm/fPn7961/3ep9wOMzVV19NTk4OTz75ZKfn05tr9Ob5CMcWyWLFddHNJD74XyzjT4puD639gIZffoXg2g8YynKGXq+H4gNF+LzeIWuDIAjC8UpkrgjCAOpr1klcfHyXwZPutguCIPQkP8vB76/J5v5XKpAk2mWwKHIksPL7a7IH5c76vHnz+OlPf8r69evJysqiqqqKESNG8Oabb5KZmQnAd77zHdatW8fUqVPJysoCYOHChWzYsCF6nquvvprXX3+dSZMmkZWVhc1mY/78+axfvz66z0033YTP5+PWW2/l29/+NoqikJCQwO9///te77N//35WrFhBXl5eNPgDB5di7s01evN8hGOTJXMUCXc/ReiLd2h+43HMgA+zqZGmZ39OaO0HxF5/P0pa7hFtk6jZJgiCMLTEakHiQ0cYYD6vF7e7/rhf6UeseCQInR2pz59dlUFe+NzNB5u9qLqJTZG4aHo8N3axfOxACwaDlJWVkZqaSmJiYpf71NbWoqoqOTk5PPXUU/z5z3/utLqO2+0mFAqRlZWF2+2mvr6e8ePHt9tH13WKi4uJi4trV6C2N/uoqtpueedWkiQxZcqUPl2j7fNpaGigrq6uU1uFwzOc+26Gp46mVx9B3fjxwY1WO64FN+E8/0Yki/WItKP4QBG7dh2clpafP4WRo0YfkWsLgiAcy3r7GSSCK8PsA1oQhqu+BEvE3TNB6NqR/vwxDJOgZuK0SoNeY6W/uguuCEJbR0PfLbR1RWTZ5oaDyzErmaOJ/er9WMfP6LR/QUWQjUUDV/zW5/WyZs0q8dkrCIIwwMRSzIKAyJ4YKH0NlogVjwRheJBlCdcALrcsCEL37NPOwDZhFv73/4/AspfA0NGrivA8+m3sp1xKzMI7kWMTgUhg5bonitD0yJS9b8xL5uLpCYcVZGmt2SayZwVBEIaGKGgrHLNaAwK7du1gzZpVorjbYehLoV44WHsGECseCYLQo2uvvZZ33313qJshCANCcriIuepuEu9/Dsuog9PLQqvfjRS8Xf0epmnywWYPmh55TDfgmRVurn/iALsqg4d1/bj4eEaOGi0CK4IgCENAZK4Ix6z+ZE+ITJeu9adQr7h7JghCbyQlJZGUlDTUzRCEAWXJm0jCD/9FcOUb+N/6O2awOVLw9vlfElrzHvE5i4CYdseomsl7mz1sKBy4qUKCIAjCkSOCK8Ixq68BAVEnpHv9CZaIFY8EQRCE45kkKzjP/Ar26WfT9OqjqBs/AiC8ZyNX7N9Kk+1inrddgirZALAqEi987kbTwWaRWPzdUSLAIgiCcBQR04KEY47X66H4QBEAc+fOIz9/Sq8CJX2d+nK8EanGgiAIgtB3ckIq8Yt+R/wdf0FOyQZA0sPcGHiLf/seZGZ4O4oMp413RacKqZrJhkL/ELZaEARB6KshDa6oqspLL73EWWedRWZmJl988UWvjqusrOS73/0u+fn5zJgxg6eeeorjcNEjoQsd66wAvQ4IiDohgiAIgiAMFtuUU0n62cs4L/gmyAoAuUY1jzY/zP2+p9haUBnd16pIzBrjGqKWCoIgCP0xpMGVBx98kDfffJNbb72V6upqVFU95DHV1dXMmTOHiooKFi9ezOLFi9mxYwerVq06Ai0WhrvDyT5pnfrS20wXQRAEQRCEvpBsDmKuuB33Lf9mm2VCdPv88Bc8772PS0PLkUyDG09LwjThkQ+qeWRJ9WEXuhUEQRAG35DWXPnDH/6ALMuUlZX1+pgHH3wQu93Oq6++itVqBeCvf/2ryFwRgL7XWelI1AkRBEEQBGGwrWvO4PcxP2aBupLbgi8RbzYTZ/q5N/BvFoRXYdp/yHVPuKPThF74vIGXbhc1WARBEIazIc1caZ2C0VumafL666/zta99LRpYaSVJ0kA2TThKDUX2SWuNF7HUsyAIQt8988wzvPPOO0PdjEH1wAMPsHv37qFuhjCMzBrjwmpV+MB+Jjcn/YHgtAuij03R9jL5tdu4qell7GYIgLAuarAIgiAMd0fVakG1tbU0NjaSnp7O1VdfzcaNG8nOzuYb3/gGt9xyS7cBllAoRCgUin7vFYPgY9qRzD45XlcYEktWC4LQ0bp163jllVeAyM2TzMxMzjzzTGbOnNnjcW+88Qbjxo3jsssuG/A2Pf3002RmZnLJJZcM+Ln74rHHHuPMM89k4sSJQ9oOYfjIz3Kw+LujWpZdHk1u1hzUXZfTvPj36DUlyKbO10Lvc054LY85v8GXjumkxCm8+LlbLNMsCIIwTB1VqwVpmgbA/fffz2WXXcYnn3zCHXfcwT333MOf//znbo976KGHSEhIiP7Ly8s7Qi0WDuVoz/o4HlcY6lg0+Gj92QmCMLC2bt3KY489RmZmJmlpaezYsYNTTjmF3/zmNz0ed/PNN3P55ZcPSptee+01Pv3000E5t9B3mzdv5sorr+SCCy7g7bffHurmDLn8LAc3nJYcDZTY8meT+JP/4rr4Vgw5kqGdZdTxcPMj/Nn4Ow8v3sFD71Vz/RMHRA0WQRCEYeioylxJSUlBURSuueYavv71rwMwZswYVq1axXPPPcc999zT5XEPPPAA3//+96Pfe71eEWAZBo6FrI/DrfFyNOoqoHS0/dwEQRgckiRx7733Rr8fNWoUv/jFL7jnnnt4/vnnGT16NImJifzvf/8jMzOTW2+9lYaGBhRFaXeewsJC3njjDRobG5k2bRpXXXVVu32efPLJLs/V1uLFi9m9ezf19fXRNj3wwAO88sor3R67bds23n//fcLhMDNnzuSiiy6Kni8YDPKTn/yEO++8k40bN7J161YyMzO58cYbcbnar+ry5ptv8uWXXzJq1CgWLlw4MC/uUS4QCLBgwQJ++ctfkp2dzaJFi5g4cSL5+flD3bRhRbLacV1yC3+qmMbcbX9nhlYAwNSGNTzLZv7p/Apvm+eyodAvslcEQRCGmaMqc8VutzN9+nRiYmLabY+NjW037aer4+Lj49v9E4besZD1cTyuMCSWrBYEobdmzpyJruuUlpby8ssvc/vtt3PbbbdhGAZJSUlAZFrQsmXLoscsW7aMKVOmsG7dOkzT5Ec/+hHnnXde9PMC6PZcbSUkJGC324mJiSEzM5PMzEwURen22Mcee4yzzz6bqqoqwuEw3//+9/nKV74SPV8wGOSRRx7h/PPPZ/HixQA88cQTnHnmme3advvtt7No0SKCwSBr1qxh9uzZ0czbY4HH4+mxzwWRQEpHy5cv54QTTuDWW2/lkksuYdGiRbz22muD1cyj3slz87kn5n5+6/o2jVIcADEEuTvwAv9o+gVzbUVD3EJBEASho2GfufKDH/yAjRs3RtN67777bu6++24WLVrECSecwM6dO3nuuee46aabhrahQp8dK1kfg1XjZbjWNWkNKLnd9cOubYIgDC8rV67EZrMxcuRIIBJIX7NmDXa7vcv9TdPk9ttv55ZbbuHxxx8H4I477mDcuHE899xzfOtb34rue6hzXXTRRTz++ONMnTq1XTZNV8fu3r2bH/3oR2zZsoVJkyYBcM899zBu3DiWLFnCggULosdefvnlPPzwwwDceuut5OTksGbNGk499VS2b9/Ok08+yeeff84pp5wCwJ/+9Cd++MMf9vm1G06ampp44YUXePLJJ9m+fTu/+93vuP/++zvt9/DDD/Pwww/T2NhIbm4ujz76aDRzp6KiglGjRkX3HTNmDOvXrz9ST+Goc+G0BCoawjz64WmstpzId4Ivc4n6GQAT9APwzHfw7bqcmCtuR47rHFw8lOHaxxAEQTiaDWlw5eWXX+auu+6K3vFZuHAhNpuNe++9N9oR8ng81NXVRY+54YYbqKys5Mwzz4zeCVq0aBG/+MUvjnj7hcMjBundG+5TpsSS1YJwZDU+9HUM75HP7pPjU0h84Ple728YBvfeey+mabJ7927+97//8dhjj+F0OgFYsGBBt8EQgNLSUnbt2sV//vOf6LasrCwuuugiPv7443bBlUOdqycdj3333XeJjY3lueeewzRNIBLocTqdbNiwoV1w5eKLL27XtuTkZEpKSjj11FP55JNPGDNmTDSwAvD1r3/9qA+ufPLJJ2zdupUXXnih3fNv64UXXuAXv/gFb731FmeffTb/+Mc/uPbaa1m3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kZEYOnQoZs2aha1bt8JoNFo9vmLFiirbPn78OCIjIzFnzpyGOVBCCCGEEEJIq0LJFULq6dtvv4VcLsfGjRuhUqmcHU6L9uSTT+Lq1at46qmnsGLFCiQkJKBPnz7Iysqy+xyTyYQFCxZg3rx56NixI/75z39iyZIlkMlkGD9+PDIzMwEAcrkc6enp2LhxIz799FOMGDECr732Gt5880328fj4eGzevBkPHjyw2sdXX32Fe/fuISYmptGOnRBCCCEt07Vr13DgwAHcvHnT2aE0qOjoaJw/f97uz43h9u3bOHPmTKPug5DGwnd2AIS0ZDqdDrt27cK3336LlStXYs+ePXjxxRcBAOfOncOaNWvw22+/QSwWs8/Zvn07fvnlFxw8eBCA+Y/Il19+ieTkZAQEBGDRokUYM2YMu/769etRVlaGsLAw7N27F2KxGIcPH8YPP/yAr7/+GgDg7u6O3r174//9v/8HLy8vqxgPHjyIbdu2gcvlYtSoUZBIJEhISMBXX33FrlNTDI44deoUtmzZgpycHISFheHNN99Et27dahXL3r174erqyq4/evRo+Pn54ccff8SqVats7verr77Cnj17cP36dfTu3Ztd/thjj2HFihVgGIZdJhKJEBkZCQAYOnQoCgoKsGHDBnz++ecAAF9fX/j7+2PXrl14/fXXAQAZGRk4c+YMnnnmGZw7d65W54QQQgghf8vPz8epU6fg4uKCqVOnWj1mNBpx8OBBMAyDxx9/3Oq7U3O2ePFi/PHHHxg0aBAmTZqEvn37OjukBrNjxw4kJydj+PDhNn9uDPv27cPly5cxatSoRttHQ7l58ybKysowbNgwZ4dCmgmqXCGkHqKioiAUCjFt2jQsWbIE33//PftY//79cfPmTfzyyy9Wz9m0aRM6deoEALh48SJGjhyJjh074r333sOQIUMwY8YMHDp0iF3//v372LBhAw4cOIBXX30V//znPwEAY8eOxcaNG7Fx40a8/vrriI+Px4gRI1BeXs4+99ChQ5g3bx6GDx+OV155BdeuXcObb76Ju3fvsus4EkNNfv31V0ycOBFdu3bFu+++C5PJhEGDBiE1NbVWsVRMrACAQCCASCSCXq+3u+/Nmzdj5syZVomVis8XCoV2n+vj4wONRgOtVssuW7RokdXruHXrVkyePBnt2rWr9hwQQgghpHp3797F3Llz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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ @@ -201,11 +217,15 @@ "either_drug = deseq2_likelihood_ratio_test(deseq, Design(cube.samples, \"~ batch\"))\n", "print(f\"{keep.sum():,} of {keep.size:,} genes pass the expression filter\")\n", "print(\n", - " f\"quasi-likelihood: common dispersion {quasi.dispersion:.3f}, prior df {np.median(quasi.prior_df):.1f}\"\n", + " f\"quasi-likelihood: common dispersion {quasi.dispersion:.3f}, prior df {np.median(quasi.df_prior):.1f}\"\n", ")\n", "print(\n", " f\"DESeq2 likelihood ratio test of both drugs: {np.sum(either_drug.adjusted_p_value < 0.05):,} genes at FDR 5%\"\n", - ")" + ")\n", + "figure, (left, right) = plt.subplots(1, 2, figsize=(11, 4.2), layout=\"constrained\")\n", + "plot_quasi_dispersions(quasi, ax=left)\n", + "plot_dispersions(deseq, ax=right)\n", + "plt.show()" ] }, { @@ -213,11 +233,19 @@ "id": "2aa800ed", "metadata": {}, "source": [ + "edgeR squeezes each gene's quasi-dispersion towards a prior trend that changes little\n", + "with the expression level. DESeq2 shrinks each gene's dispersion towards a fitted trend\n", + "that falls with the mean count to about 0.07, except for 64 dispersion outliers, whose\n", + "own estimates lie far above the trend and are kept.\n", + "\n", "## Calls compared with the truth\n", "\n", "At a false discovery rate of 5%, each method's calls are compared with the genes the\n", "simulation changed: how many it called, how many of those truly changed, and the\n", - "share of its calls that are false." + "share of its calls that are false. {api}`QuasiLikelihoodTest.table` and\n", + "{api}`DESeq2Results.table` give edgeR's and DESeq2's tests as tables with the columns\n", + "of the tables of {api}`limma_voom`, each gene named by its id (`feature_ids`), so that\n", + "the three methods' calls are matched gene by gene." ] }, { @@ -234,7 +262,13 @@ "drug_a_vs_control limma-voom 355 337 5.1%\n", "drug_a_vs_control edgeR QL 320 312 2.5%\n", "drug_a_vs_control DESeq2 Wald 360 338 6.1%\n", - " called by all 320\n", + " called by all 320\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "drug_b_vs_control limma-voom 204 192 5.9%\n", "drug_b_vs_control edgeR QL 193 184 4.7%\n", "drug_b_vs_control DESeq2 Wald 238 203 14.7%\n", @@ -248,32 +282,29 @@ " \"drug_a_vs_control\": truth[\"true_log2_fold_change_drug_a\"] != 0,\n", " \"drug_b_vs_control\": truth[\"true_log2_fold_change_drug_b\"] != 0,\n", "}\n", + "\n", + "\n", + "def called(table):\n", + " # Whether each gene is called at a false discovery rate of 5%.\n", + " return np.isin(genes, table[\"feature_id\"][table[\"adjusted_p_value\"] < 0.05])\n", + "\n", + "\n", "print(f\"{'contrast':<19}{'method':<18}{'called':>7}{'true':>7}{'false share':>13}\")\n", "for name in design.contrasts.names:\n", - " voom_table = voom_result.table(\"bulk\", name)\n", - " voom_called = np.isin(\n", - " genes,\n", - " np.asarray(voom_table[\"feature_id\"])[\n", - " np.asarray(voom_table[\"adjusted_p_value\"]) < 0.05\n", - " ],\n", - " )\n", - " quasi_called = np.zeros(genes.size, dtype=bool)\n", - " quasi_test = quasi_likelihood_f_test(quasi, contrasts=design.contrasts.select(name))\n", - " quasi_called[keep] = quasi_test.adjusted_p_value < 0.05\n", - " deseq_called = (\n", - " deseq2_wald_test(deseq, design.contrasts.select(name)).adjusted_p_value < 0.05\n", - " )\n", - " for method, called in [\n", - " (\"limma-voom\", voom_called),\n", - " (\"edgeR QL\", quasi_called),\n", - " (\"DESeq2 Wald\", deseq_called),\n", - " ]:\n", - " true_calls = np.sum(called & truly_changed[name])\n", - " share = 1 - true_calls / max(called.sum(), 1)\n", - " print(f\"{name:<19}{method:<18}{called.sum():>7,}{true_calls:>7,}{share:>13.1%}\")\n", - " print(\n", - " f\"{'':<19}{'called by all':<18}{np.sum(voom_called & quasi_called & deseq_called):>7,}\"\n", - " )" + " contrast = design.contrasts.select(name)\n", + " quasi_test = quasi_likelihood_f_test(quasi, contrasts=contrast)\n", + " deseq_test = deseq2_wald_test(deseq, contrast)\n", + " calls = {\n", + " \"limma-voom\": called(voom_result.table(contrast=name)),\n", + " \"edgeR QL\": called(quasi_test.table(feature_ids=genes[keep])),\n", + " \"DESeq2 Wald\": called(deseq_test.table(feature_ids=genes)),\n", + " }\n", + " for method, call in calls.items():\n", + " true_calls = np.sum(call & truly_changed[name])\n", + " share = 1 - true_calls / max(call.sum(), 1)\n", + " print(f\"{name:<19}{method:<18}{call.sum():>7,}{true_calls:>7,}{share:>13.1%}\")\n", + " by_all = np.logical_and.reduce(list(calls.values()))\n", + " print(f\"{'':<19}{'called by all':<18}{by_all.sum():>7,}\")" ] }, { @@ -312,18 +343,20 @@ "| Design and contrasts | `Design(samples, \"~ batch + condition\", contrasts=...)` | `model.matrix(~ batch + condition)` and `makeContrasts(...)` |\n", "| Precision weights | `voom(counts, design, norm_factors=factors)` | `voom(y, design)` in limma 3.66 or later, `voom(y, design, adaptive.span = TRUE)` in 3.60 to 3.65 |\n", "| The same, with sample quality weights | `voom_with_quality_weights(counts, design, norm_factors=factors)` | `voomWithQualityWeights(y, design)` |\n", - "| Linear models | `fit_linear_model(v.log_cpm, design, weights=v.weights)` | `lmFit(v, design)` |\n", + "| Linear models | `fit_linear_model(v.log2_cpm, design, weights=v.weights)` | `lmFit(v, design)` |\n", "| Contrasts | `contrasts_fit(fit, design.contrasts, method=\"limma\")` | `contrasts.fit(fit, contrasts)` |\n", - "| Moderation | `moderated_statistics(fit)` | `eBayes(fit)` |\n", - "| Adjusted p-values | `adjust_p_values(p, \"bh\", axis=0)` | `topTable(fit, coef = j, sort.by = \"P\")`, which adjusts by BH |\n", - "| Quasi-likelihood F-tests | `fit_quasi_likelihood`, then `quasi_likelihood_f_test` | `glmQLFit`, then `glmQLFTest` |\n", - "| DESeq2's Wald and likelihood ratio tests | `fit_deseq2`, then `deseq2_wald_test` or `deseq2_likelihood_ratio_test` | `DESeq`, then `results`; `DESeq(dds, test = \"LRT\", reduced = ~ batch)` |\n", + "| Moderation | `moderated = moderated_statistics(fit)` | `fit <- eBayes(fit)` |\n", + "| A contrast's table, with p-values adjusted by BH (`adjust_p_values`) | `moderated.table(j).sort(\"p_value\")` | `topTable(fit, coef = j, number = Inf, sort.by = \"P\")` |\n", + "| Quasi-likelihood F-tests | `fit_quasi_likelihood`, then `quasi_likelihood_f_test`, then `.table()` | `glmQLFit`, then `glmQLFTest`, then `topTags` |\n", + "| DESeq2's Wald and likelihood ratio tests | `fit_deseq2`, then `deseq2_wald_test` or `deseq2_likelihood_ratio_test`, then `.table()` | `DESeq`, then `results`; `DESeq(dds, test = \"LRT\", reduced = ~ batch)` |\n", "\n", "`method=\"limma\"` gives the standard errors of contrasts as limma computes them; by\n", "default they are exact, which differs from limma's when the weights differ between\n", "genes and the contrast combines correlated coefficients ([Designs, linear models and\n", - "contrasts](../statistics/linear_models.md#contrasts)). The\n", - "[Validation](../validation.md) page shows how closely each step agrees with R." + "contrasts](../statistics/linear_models.md#contrasts)). {api}`ModeratedFit.table` gives\n", + "the table of one contrast with the columns of the tables of {api}`limma_voom`, as\n", + "`topTable` does. The [Validation](../validation.md) page shows how closely each step\n", + "agrees with R." ] } ], @@ -331,16 +364,16 @@ "datasets": [ "simulated_bulk" ], - "executed_at": "2026-10-27T08:56:02+00:00", - "executed_code_sha256": "67f2aedd5235275db9b92304c750ae470acbb2e4e039e0f53b09918a38cd08b2", + "executed_at": "2026-10-27T15:22:26+00:00", + "executed_code_sha256": "779df2750fcccb5965d8884cb46cab035b3f0f36241ad2a00db4bc70fe7e1d00", "executed_datasets": { "simulated_bulk": { "simulated_bulk.cube.zarr": "sha256:ac41de9421f1171ef9273f8d3104a6ff2e5b7fe34a503d29ad63e11da4770cd2", "simulated_bulk_truth.npz": "sha256:a8a0275532e3cafb02401846176a7ddfa037579aceb804c5f9c007700f022977" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 2.6, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 4.6, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/combine_studies.ipynb b/docs/source/guides/combine_studies.ipynb index 31cbd4b..a12b07c 100644 --- a/docs/source/guides/combine_studies.ipynb +++ b/docs/source/guides/combine_studies.ipynb @@ -38,7 +38,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953, numpy 2.5.4\n", + "CytePool 0.1.dev63+g8afa32afb, numpy 2.5.4\n", "cohort_1: 60 donors (30 cases), 2,000 genes\n", " B cells, CD4 T cells, NK cells, CD14 monocytes\n", "cohort_2: 32 donors (16 cases), 2,000 genes\n", @@ -59,18 +59,18 @@ "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.cube import CubeCollection\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.io import read_cube\n", - "from cytepool.meta_analysis import (\n", - " align_study_results,\n", - " differential_expression_meta_analysis,\n", + "from cytepool import (\n", + " CubeCollection,\n", + " Design,\n", + " analyse_each_study,\n", + " limma_voom,\n", + " meta_analysis,\n", + " read_cube,\n", ")\n", "from cytepool.plotting import plot_forest, plot_significant_genes, plot_volcano\n", "\n", - "studies = [f\"cohort_{i}\" for i in range(1, 7)]\n", - "cubes = {study: read_cube(f\"simulated_{study}.cube.zarr\") for study in studies}\n", + "cohorts = [f\"cohort_{i}\" for i in range(1, 7)]\n", + "cubes = {study: read_cube(f\"simulated_{study}.cube.zarr\") for study in cohorts}\n", "print(f\"CytePool {cytepool.__version__}, numpy {np.__version__}\")\n", "for study, cube in cubes.items():\n", " cases = np.count_nonzero(cube.samples[\"condition\"] == \"case\")\n", @@ -104,7 +104,10 @@ "its CD4 T cells more finely, as naive (CL:0000895) and memory (CL:0000897) cells,\n", "which the ontology places under CL:0000624; `group_mapping` rolls them up to it, and\n", "the collection merges the two, exactly, since a cube's layers are sums\n", - "([The cube and its additive layers](../statistics/cubes.md#several-studies))." + "([The cube and its additive layers](../statistics/cubes.md#several-studies)).\n", + "`group_names` names the aligned cell types for the tables and figures that follow,\n", + "such as CD14 monocytes for CL:0001054, and the collection's group table keeps their\n", + "ids in `cell_ontology_id`." ] }, { @@ -128,7 +131,7 @@ " in every study: 3 groups, 1,650 features\n", " samples: cohort_1::D01, cohort_1::D02, ..., cohort_6::D20\n", "\n", - "CL:0000624 holds\n", + "CD4 T cells (CL:0000624) hold\n", " cohort_1: CD4 T cells\n", " cohort_2: CD4\n", " cohort_3: CD4+ T cell\n", @@ -140,15 +143,22 @@ ], "source": [ "cd4_subsets = {\"CD4 naive\": \"CL:0000624\", \"CD4 memory\": \"CL:0000624\"}\n", - "collection = CubeCollection(cubes, group_mapping={\"cohort_4\": cd4_subsets})\n", + "cell_types = {\n", + " \"CL:0000236\": \"B cells\",\n", + " \"CL:0000624\": \"CD4 T cells\",\n", + " \"CL:0000623\": \"NK cells\",\n", + " \"CL:0001054\": \"CD14 monocytes\",\n", + "}\n", + "collection = CubeCollection(\n", + " cubes, group_mapping={\"cohort_4\": cd4_subsets}, group_names=cell_types\n", + ")\n", "print(collection)\n", "ids = collection.samples[\"sample_id\"]\n", "print(f\" samples: {ids[0]}, {ids[1]}, ..., {ids[-1]}\")\n", - "members = collection.group_members\n", - "cd4 = members[\"group_id\"] == \"CL:0000624\"\n", - "print(\"\\nCL:0000624 holds\")\n", + "members = collection.group_members.filter(group_id=\"CD4 T cells\")\n", + "print(\"\\nCD4 T cells (CL:0000624) hold\")\n", "for study in collection.studies:\n", - " names = members[\"study_group_id\"][cd4 & (members[\"study\"] == study)]\n", + " names = members.filter(study=study)[\"study_group_id\"]\n", " print(f\" {study}: {', '.join(names)}\")" ] }, @@ -159,12 +169,14 @@ "source": [ "## Analyse each cohort on its own\n", "\n", - "{api}`CubeCollection.aligned_cube` gives a cohort's cube on the collection's axes: its\n", - "cell types under their aligned ids, merged where several share one, the genes it\n", - "measured, and its own samples, so that a design built from its own sample table\n", - "applies. Each cohort is tested by {api}`limma_voom`, cases against controls, and its\n", - "results are keyed by the aligned cell types. {api}`plot_significant_genes` shows what\n", - "each cohort finds." + "{api}`analyse_each_study` tests every cohort alike, on its own. For each, it takes the\n", + "cohort's cube on the collection's axes ({api}`CubeCollection.aligned_cube`): its cell\n", + "types under their aligned names, merged where several share one, the genes it\n", + "measured, and its own samples. It builds a design from that cube's sample table, with\n", + "one formula, levels and contrasts for all, and tests cases against controls with\n", + "{api}`limma_voom`. The {api}`StudyResults` it returns keeps every cohort's result,\n", + "keyed by the aligned cell types, with the collection, and the functions that combine\n", + "studies take it. {api}`plot_significant_genes` shows what each cohort finds." ] }, { @@ -173,13 +185,13 @@ "id": "8b7e8560", "metadata": { "figure_descriptions": [ - "Six panels, one per cohort, each with a horizontal bar per aligned cell type for the genes found up (red, to the right) and down (blue, to the left) at an adjusted p-value below 0.05. In CL:0001054, the CD14-positive monocytes, cohort_1 (60 donors) finds 36 genes up and 37 down, cohort_2 (32 donors) 27 and 25, cohort_3 (24 donors) 19 and 11, cohort_4 (16 donors) 14 and 10, cohort_5 (12 donors) 14 and 14, and cohort_6 (20 donors) 15 and 16. In CL:0000624, the CD4 T cells, the cohorts find 5 to 34 genes; in the B cells (CL:0000236) at most 2; in the NK cells (CL:0000623) none, and cohort_6 has no row for them." + "Six panels, one per cohort, each with a horizontal bar per aligned cell type, named B cells, CD4 T cells, NK cells and CD14 monocytes, for the genes found up (red, to the right) and down (blue, to the left) at an adjusted p-value below 0.05. In the CD14 monocytes, cohort_1 (60 donors) finds 36 genes up and 37 down, cohort_2 (32 donors) 27 and 25, cohort_3 (24 donors) 19 and 11, cohort_4 (16 donors) 14 and 10, cohort_5 (12 donors) 14 and 14, and cohort_6 (20 donors) 15 and 16. In the CD4 T cells the cohorts find 5 to 34 genes; in the B cells at most 2; in the NK cells none, and cohort_6 has no row for them." ] }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAysAAANdCAYAAACULOOAAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjIsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvgI3uAAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XdUFNfbB/AvvXdQUFHELqhYUbH3GuwtRo29xpoYjRprbIm9xhp711iwoGKv2AtqREQUVHrve98/fJmfK6C7iuwg3885nOPcefbOnbvO3X2m3NUSQggQERERERHJjLamG0BERERERJQVJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVyvNu376Nrl27aroZedbz58/RqVMnJCQkaLopRJQHTZ48Gbt27dJ0M/Ksv/76C+vWrdN0M4hkS1fTDSD6Uq9fv8bevXu/Wv0PHjzA77//jl27dkFbW7X8XqFQ4NixY9ixYwcKFiyI+fPnf9a2X7x4gfXr1+PZs2eoVKkShg4dCiMjI2m9EAJbt27FyZMnYWBggE6dOqFp06ZqbSMqKgp79+7F2rVrYWxs/FntJKL8y8fHBzo6Ol+t/unTp6NEiRL4/vvvVYpPS0vDnj17cO7cOSgUCri7u+OHH36Arq56X3l8fHxw+PBhREREoEyZMujbty8KFCiQ49u5fPky7O3t1XoNUX7CKytEnxAaGoq9e/dCoVCoFK9QKFCqVCksXboUwcHB8Pb2/qztnj17Fi4uLvD390ezZs0QHR2Nbt26KcUMHToU48aNg5ubGxwcHNCmTRusXr36s7ZHRCRH586dw71791SOr169Og4ePAhXV1dUqFABs2bNQpMmTZCamqpyHePGjcNff/0FJycn1K1bF6dOnYKrqyueP3+eo9shok/jlRXSuNTUVGzZsgXnz5+HiYkJevbsCXd3d2l9TEwMVq1ahbt378LS0hLdu3eHh4dHpnpevXqFtWvXIiAgAOXLl8dPP/0EQ0NDleu5ffs2Zs+ejT///BNLly7FixcvMHz4cPz+++8AgK5du0JLSwtNmjTB4MGDs90fLS0tnDp1Ck5OThg3bhxOnjypdp8kJSWhe/fu6NWrF5YvXy6Vh4eHS/9++PAhVq1ahRMnTkhXUwwMDDB+/Hj07t1bad/fFxoaikWLFuHVq1coX748atWqlSlG1b5asGBBjvf5H3/8AWdnZxw6dAhHjx5Famoq6tati549e6p8ZYuIVOfn54dNmzbh5cuXcHNzy3QF9/z589ixYweio6Ph5uaGQYMGwczMLFM9e/fuxenTpwEAPXr0yDROf6qeyZMno0KFClAoFDh+/DhKliwJExMT3Lt3Dy9fvsTTp08BACtXroSdnV22+3Po0CEUKVJEWm7WrBlKly6NU6dOoUWLFir1yS+//KJ0FaVXr16ws7PDvn37MGbMmC/azuHDh7F3715YWFigbdu2Wcao2lc6Ojo53ue//fYb3r59i7///htPnjyBo6Mj+vTpg1KlSqnUd0Q5jZ/8pFHJyclo3LgxZs6ciQoVKqBs2bIYO3Ysrl69Kq2vVasW9u7di/r168PIyAgNGjTIdH+0QqFAkyZNYGRkhDp16mDdunXo2bOn0nY+Vc/r16+xe/duNGzYENbW1ujUqRMcHR3RqFEjAO+SlW7duqF69eof3SctLS04OTl9Ub94eXkhJCQE48aNUyq3sbGR/n306FFYWlqicePGUlmXLl0QHR2NS5cuZVlvYmIiateujfPnz6NOnToICgpC+/btlWLU6auv0edWVlb4+++/8eOPP8LZ2RkeHh44e/as9AWBiHLO4cOHUblyZbx+/RqNGjVCSEiI0u1W27ZtQ+PGjWFmZoZ69ephx44d8PDwQEpKilI9y5Ytw5YtW1C9enUYGBigfv36uHnzplr1+Pj4YMiQIdiyZQsaNGiA+vXro1atWihYsCDKlSuHbt26oVu3bjAxMfnoPr2fQABA4cKFoaWlhcjISJX75f1EBQBCQkKQkJCA4sWLf9F2NmzYIH22lC1bFj/99JOUbGRQta+GDh36Vfo8JSUFHh4euH79Opo2bQoLCwt07doVgYGBKvcfUY4SRBq0ePFiYW5uLt6+fSuVpaWlicjISCGEEIsWLRK2trYiLi5OWj9p0iRRpEgRkZaWJoQQ4ujRowKAOH36tBRz4sQJoaWlJRITE9Wu5+DBg0pt9PHxEQBEamqq2vs3duxYUalSJbVfN2XKFFGgQAHh5+cnRowYIX788UexZMkSaX+EEGLo0KHC1dVV6XUpKSkCgFizZk2W9S5cuFA4ODiIpKQkqWzgwIECgKz63NPTU/zyyy9KZaGhoR/vNCJSS2pqqnBwcBA///yzUnlYWJgQ4t144uDgIGbOnCmti4qKEpaWlmLZsmVSmYeHh6hdu7ZSHfXq1RPjxo1Tu55KlSoJhUKhVFfjxo3F+PHjP3s/586dKwwNDcWrV6/Uep2/v7/o2LGjaN68uShYsKBYsmTJF20nJSVF2Nvbi7/++ksqe/nypdDX1xfDhg2TYjTd5/fu3RMARExMjFSWmJioNJYT5SZeWSGNOnHiBFq2bKl0SV9HRweWlpYAgAsXLqB58+ZKZ9I6d+6Mly9fKp3l0dLSQp06daTlEiVKQAiB4OBgteoBoHSlQlPi4+ORnJyMDh06oESJEqhZsybWrFmD2rVrIzk5GcC7KxcfPhCvp6cHfX19JCUlZVlvRj8YGBhIZR9eWZFDn1epUgXbtm3Dpk2b8ObNGwCAra1tNr1FRJ/j/v37CAkJQa9evZTKM67gPnv2DCEhIejYsaO0zsLCAk2bNsXFixeVXlOvXj2l5RIlSuDVq1dq19OoUSNoaWl9+c79P29vb0yaNAkLFy5EoUKF1HqtlZUVunXrho4dO8LV1RXLli3DixcvPns7/v7+eP36NTw9PaWywoULK12tl0OfFytWDNbW1hgxYgSuXr2K1NRUGBoafvKKFtHXwmSFNCo6Olrp1qYPhYeHw8rKSqnM2tpaWpdBW1sbenp6SssApIfiVa1HR0dHFjNiWVpaIjo6GsuXL8fIkSMxcOBAHD16FLdv38bBgwcBvPvg+fB2g7i4OKSkpEjJ3ociIiIyrftwWQ59/ttvv2Hy5MnYvHkznJ2dUbVqVZw5cybLfSKizxMdHQ0A2Y7BGcdpVsfx+8cwAKUTIMC78eD9sUDVeszNzdXZhY86e/Ys2rVrh99///2jzxlmx8rKCp06dcKAAQNw/Phx6OvrY9asWZ+9nYiICACZx9z3l+XQ52ZmZrhy5QpMTEzwww8/wNbWFkOHDkViYmK2+0b0NTFZIY0qVqwYnjx5ku16Jycn+Pv7K5VlLKvzXMiX1JOTZ/lUValSJQBA6dKlpbLChQvDxMQEISEhAICKFSsiMDAQMTExUszdu3eldVkpVqwYAgIClMqePXumtCyHPtfR0cHAgQPh7e2NiIgI1K9fHx07doQQQuXtE9HHFStWDACyHYMzjtOsjmN1x4IvqedzxuBz586hdevW+PXXX/Hbb7+p/foP6ejooGTJkpmurKiznYz+/tgYLJc+L1WqFJYvX44nT57gypUr2LdvH1auXKny9olyEpMV0qjevXvj1KlTOHr0qFTm5+cHPz8/AED37t1x4sQJ+Pr6Ang3r/3cuXPRuHHjj84G86EvqSfj9qPQ0FC19u1TBgwYgK1bt2a5rmnTpihUqJDSw+heXl6Ij4+XZu9q27YtDA0NsXTpUgDvfnPlr7/+QsWKFbNNVrp27QovLy9pGtDExEQsXrxYKUYOfb5161bExsYCeHf2sHLlykhOTlZ5+mgi+jQnJyfUq1cPU6dORVxcHIB3V0YPHDgAAChUqBDq16+PefPmIT09HcC73wQ5ffo0unfvrvJ2vrQeW1tbtcbfCxcuoFWrVhg/fjwmT56cZcypU6fQqVOnbG+Z3bRpk9LJkUePHsHHx0fp1itVtvO+woULw8PDA/Pnz5fGsn///Vf6vAPk0eePHj1SupJdunRp2NjY8IeDSWM4dTFpVNOmTTFv3jx06NABlSpVgr6+PuLi4qQPy6ZNm2LUqFHSrDAZc9y/n9youp3Prads2bKoXLkyGjZsCBcXFzRt2vSTtxT8/PPPCAgIwL179/DmzRt06tQJwLsPQGNjY6Snp2PHjh1wc3PL8vWGhobYsWMHOnbsiF27dsHIyAhXr17FnDlzpPubra2tsWnTJvzwww84dOgQYmJiEB0d/dF9atGiBXr16oVatWqhVq1aePLkCSpUqJBjfZVT9URFRaF8+fIoUaIEdHV1cf36dcyfP/+r/vAcUX60ZcsWtGvXDiVLlkSlSpXw33//oX///mjXrh0AYPXq1WjZsiXKli2LokWL4sqVK/j555/RoEEDtbbzJfV07doVPXr0QHBwMExMTD45dXGrVq2gq6uLO3fuSGMvAPTs2VParzt37uDcuXPZjim+vr74/fffUaZMGcTHx+PGjRv4/vvvlWYlVGU7H1q+fDmaNWsGFxcX2NvbIygoKNMYrOk+NzMzw7BhwzBo0CCUKlUKjx8/hqmpKQYNGqTW9olyipbgfRUkA2FhYbh+/TosLS1RrVo1pWchAOD58+e4d+8eLC0tUbNmTaX1b968wcWLF9GhQwepLCEhAV5eXmjZsqXSQ4Hq1pMhNTUVV69exdu3b1GsWDFUrVr1o/tz8uRJREVFZSr39PSEnp4eLl68iF69euHhw4eZ7jt+X3x8PC5fvgzg3a1dH06nCQCRkZG4cuUKDAwMULt27Wx/X+V9d+/elX5nxdLSEt7e3lLbMmi6z2NjY3Hz5k2kpaWhUqVKfMCe6CsRQuDmzZt4/fo1KlWqlGlK3pSUFFy9ehVRUVGoWLGidDtThjNnzqBAgQIoX768VHbjxg2kpaUp/WbW59STITAwEA8ePEBCQgJatWr10WcL9+zZk2V5hQoVUKZMGQBAy5Yt4enp+dETT6Ghobh9+zYMDAxQtmzZTOOvKtvJSlxcHC5cuAALCwtUqlQJd+/ehaGhodLJKzn0+ZMnT/Dff//BwcEBlStX1sgt0UQAkxUijfD390dSUhJcXFw03RQionznyJEjaNGiBa/WEuUBTFaI1DRv3jxcu3Yty3XNmjXDwIEDc7lFRET5Q1BQEEaPHp3t+r///luadZCIvg18ZoVITXXr1oWzs3OW60qUKJHLrSEiyj8sLCzQrVu3bNcbGRnlYmuIKDfwygoREREREckSpy4mIiIiIiJZYrJCRERERESyxGSFiIiIiIhkiQ/Y5wEKhQLBwcEwMzPjPOdE3xghBGJjY1GoUCFoa/P8kdxw/CX6tnEMlj8mK3lAcHAwHB0dNd0MIvqKgoKCMv0YH2kex1+i/IFjsHwxWckDzMzMALw7kMzNzTXcGvraYmJiUKZMGTx+/Jjvdz4QExMDR0dH6TgneeH4m79w/M1/OAbLH5OVPCDj1gNzc3MOnvmElpYW3+98hrcYyRPH3/yH42/+xDFYvnhzHhERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiI8riJEydi9OjRmcpv3ryJmjVr4uXLlxpo1ZfjA/ZERERERJ/wslXLXN1eEa+jasU/efIEcXFxmcpjYmJw9epVJCUl5VTTchWTFSIiIiKifGLGjBlIT09H0aJFceTIEcTGxqJr167o16+fppuWJSYrRERERET5hL+/P3bu3InatWtjzJgxCAwMxMiRI5GYmIjhw4drunmZMFkhkgmXCX4AAEXKu0u4wZ06IkZXV+3LwEREpJ4Px1+ib52BgQH+/fdfmJqaAgBSU1Mxffp0DB48GLq68koP+IA9EREREVE+UrNmTSlRAYBmzZohNDQUL1680GCrssZkhYiIiIgojzM0NMzyIfqMMkNDQ6nMxMREKSZjOSYm5iu28PMwWSEiIiIiyuOcnZ0REBCQqfzZs2cwNDREoUKFpLLHjx8rxTx69AhaWlooXrz4V2+nupisEBERERHlcV27dkVISAhWrVollYWEhGDp0qXo0aMHtLX/97X/wYMH2LZtGwAgISEBM2fORPv27WFhYZHr7f4UJitERERERHmci4sLtm7diunTp6NIkSJwdXVFiRIlULVqVfz1119KsR4eHpgzZw5Kly6NwoULIzIyEosWLdJMwz9BXo/7ExERERHJUF6YnbNz587o1KkTXr58iejoaBQvXjzT8ykA4OTkhC1btuDFixeIjY1F+fLloaWlpYEWfxqTFSIiIiKib4SWlhYcHR3h6Oj4ydiiRYvmQou+DG8DIyIiIiIiWeKVFSIiIiKifGLy5MmaboJamKwQEREREeUTJUqU0HQT1MJkhSiXvHnzBomJiQCAggULwsjISO06AgMDIYQAABQrVky2D8MREclFcnIyQkJCAAAGBgZwcHBQu47o6GgoFApYWVnldPOI6BM+K1nx8vLCwYMH8fLlS9ja2qJy5cro27cvzMzMAADz58/H69evM02T9imPHz/G4sWLERgYiFKlSmHs2LGZHg7KrZjk5GRs2rQJZ8+eRWpqKqpXr44hQ4ZkmlHh0KFD+PfffxEWFobSpUtj4MCBKFmypFKMQqHAli1b4OXlBS0tLfTo0QNt27ZVq28o7/vll19w9uxZhISEYO/evWjTpo3adTRu3Bipqal48eIFEhMTlX6NlvKHt2/fYsOGDfD19UVKSgpKlCiB9u3bo27dugCAsLAwdOvWDfPnz0flypVVrlehUGDdunXw8vKCjo4OPD098cMPP2gs5sGDB9i4cSOePHkCBwcH9OrVC7Vr187UF6tXr8bNmzdhZmaGJk2aoEePHtDVVf5o8/f3x8qVK/H48WO4uLjg559/ho2Njcp9Q3nbvXv30KlTJyQmJqJ48eK4cuWKyq8VQmDAgAHYunUrtLW10aFDB2zcuBE6OjpfscVE9D61HrBPSUmBp6cn+vTpgyJFimDw4MFo3LgxgoKCUKlSJSgUCgDvPmRu3LihVkOePHmCGjVqID4+Hn369EFAQABq1KiB169faySmdu3a8PX1RYsWLdCuXTts27YNtWvXRkJCghTz888/Y9u2bahbty569+6NFy9eoEqVKnj48KEUk56eju+++w5Tp05FkyZN0K1bN2zcuBEHDx5Uq38o7/vnn3/w/Plztb5Afujp06d48OBBDraK8pJTp06hdOnSOH/+PL777jv0798fxYoVw5QpUzB//nwAQFJSEk6dOoXw8HC16v7pp58wadIktG7dGo0aNcKIESMy3decWzH79u1Djx49ULBgQfTt2xd2dnaoX78+Nm7cKMWEhoaiYcOGAIAff/wR7u7u+PXXX9G3b1+lbZ07dw6VKlVCVFQUBg4ciCJFiqBr165q9Q3lbdWqVcPz58+xbt06tV97+PBhnD17Fq9evcKbN29w9+5d7Nu37yu0koiyJdTwyy+/CGNjYxEQEJBpXWRkpFAoFEIIIXr37i3q16+vTtXi+++/FzVq1JCWU1JSRLFixcS4ceM0EhMeHq7UvpCQEKGlpSX27NkjlUVFRSnFKBQK4eDgIGbMmCGVLV++XBgZGWXqs9jY2Ow74wPR0dECgIiOjlb5NSRf7u7u4tChQ5nKy//6UJT/9aEoO+aaMDExEX5Nm4igli0yxcXGxgoAIjExMTeaS1+Zqsd3aGiosLCwEAMGDMhyfUREhBBCiKCgIAFAeHt7q9wGf39/oaWlJf7991+pbP369UJPT0+EhobmekxERIT0eZJh+PDhomzZstJycnJypmNg7dq1QldXV6SmpgohhEhNTRVFixbN1Gccf/OnQ4cOCXd39yzXfTj+Zrzf/fv3F9OmTZPiFi1aJHr06JEr7aXcwWNc/lS+spKWloZVq1Zh8ODBcHJyyrTe0tJS5fvnT506hSZNmiAmJkYqO3HiBDw9PaVlPT09tGnTBidOnNBIjLW1tVKbLSwsoKOjg/j4eKWy9wUEBCAyMhLlypWTyjZu3IjvvvsuU5+Zmppm0TNERFnbtGkT4uPjMWvWrCzXq3Mvfd++fbF48WJp+eTJk9DX10eLFi2ksvbt2yM1NRU+Pj65HmNlZZXp88TGxkZp/NXX1890G+TNmzdRsmRJ6Taw06dP48WLF/jpp5+U4jj+kqpevXqFYsWKScvFihXDy5cvNdgiovxH5WdWnj59ipiYGFStWvWLNxoSEoJTp04hJSUFAJCQkIDQ0FAUKVJEKa5IkSJ4/vx5rsdkZdGiRdDV1UXjxo2Vyp8/f47+/fsjPj4ejx49woIFC9CxY0dp/f3799G5c2csWLAAPj4+KFCgADp16oSWLVtmu63k5GQkJydLy+8ndUSUP928eROOjo6ws7P74rouXboEfX19afn58+coUKCAUpmlpSVMTEykcTE3Yz4UFRWFtWvX4rvvvsu0btasWTh16hSeP38OBwcHHD9+XFp3//59GBsbQwiBH3/8EVFRUahQoQJ++ukn2NraZrktjr/0Pn19faSmpkrLqampMDAw0GCLiPIflZOVjGc1LC0tv3ijTZo0gbe3t3RlIiNp+XB2JGNjY2ldbsZ86MiRI5gyZQpWrVqFwoULK62zs7PDr7/+iqioKOzevRvTp09HgwYNUK5cOQghkJSUhHnz5qF9+/bo378/Hj58iA4dOmDOnDkYOXJkltubPXs2pk2bluU6IsqfEhIScmT8BYANGzYoXYlJSUnJcna6D8fO3Ip5X3JyMjp16gRTU1PMnj0703pPT09Uq1YNjx49wty5czF37lwsX74cwLs+UygU6Nq1K8aMGQMrKyssW7YMGzduxN27d7PsT46/9L7ixYsrPSf48OFDFC9eXIMtIsre6dOnce7cOQCArq4uLCwsUK5cOdStWzdPJ9kqJysZX9I/dvVBVfb29rC3t5eWTU1Noauri4iICKW48PBw6QM1N2Pe5+3tjU6dOmHOnDno169fpvUmJiZo0qQJAKBTp06oVasWZsyYgW3btkFLSwuWlpYoWbIk/v77bwDvPlhjYmIwf/78bJOVCRMmYMyYMdJyTExMptnMKO+JiIhATEwMkpOT8fbtWzx//hxFixaFtrbq81wEBwcjKioKwLtpjM3NzT9rGk7KewoXLowzZ87kSF21atVSWraysso0JgohEBkZKY2LuRmTISUlBZ07d0ZgYCDOnDmT6dZbAHB1dYWrqyuaN28OV1dXNGnSBMOGDUP58uVhbW2NpKQkzJ8/H61btwYANGvWDAULFsSOHTswePDgTPVx/P32KBQKvHjxAm/fvkVycjKeP38OCwsLlW6d/OGHH9C4cWM0adIEhoaGWLFiBfbv358LrSY5cpngl6vbezC73KeD3nP69GmsWLECP/30E1JTU/H48WOsXbsWL1++xIIFC9C7d++v1NKvS+VvSQULFkTVqlWxbdu2HG+Erq4uXF1dcfPmTaXymzdvws3NLddjMpw8eRKenp6YPn06xo4dq9K+FCtWTGlWsSpVqijd7woARYsWRXh4uPR7GR8yMDCAubm50h/lfcuWLUODBg0QGRkpXYGLi4tTq44f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60NbWxseHh7w9/fH3r170aBBg1xpP6+sEBERERF9AyIiIjB16lQIIRAREYELFy4gMDAQa9asQdu2baW4BQsWoG7duqhfvz6qVauGI0eOwN7eHqNHj1aqr0GDBliwYAGGDx8O4N0P+FarVg2XLl1iskJERERERKpp1KiRdFuWnp4eSpUqhbZt26JevXowNDRUii1dujQePXqE/fv34/Xr15g1axY8PT0zTXzVu3dvmJmZoV27dlLZb7/9huvXrys9r9KuXbtMP2FSp06dTLNSfo7Png2Mcg9no8kfOBtY/sTjW974/uQPnA0s/+IxLn98ZoWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWOHUxkUw8mF0OwLuZSQqtBgrt2cuZSYiIcsGH4y8RyQevrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiVdTTeAKDe5TPDTdBM+SZESBwAI7tQRMbrfziFaxOuopptARN+onBrbv6Xxl2MufSt4ZYWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJkq6mG0Ckrrt37yIlJQUAULFiRejr60vr7ty5g9TUVGnZ2dkZ1tbWud5G+rjAwECEhoYCAIoVKwY7Ozul9QkJCQgICICjoyPMzc010UQikpEXL17g7du30rK1tTWcnZ012KK8JTQ0FIGBgQAAOzs7FCtWTGm9EAL+/v4wNTWFvb29JppIlC21r6ykpaVh06ZN6NSpE2rWrIk2bdpg8uTJeP36tRQzZcoU9O/fX+3GXLt2DV26dIG7uzt69uwJPz8/jcYkJydjwYIFaNKkCZo2bYoNGzYorY+KisLs2bPRokULNG7cGL/++qv0BSwrR44cgZub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a7o59JXFxMSgUKFCCA4O5vudD/D4lje+P/kLx9/8h8e4/PHKChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiDQkKCsKFCxcQExPzydjQ0FCcP38eb9++zYWWERF929LS0nD16lXcv3//k7FCCNy6dQs3btyAQqHIhdYR0fvUTlaSkpKwZMkSNGnSBGXLlkWdOnUwYsQI+Pv7SzHjxo1Dt27d1G7M6dOn0axZM5QpUwZt2rSBr6+vRmNiY2MxZcoUVKtWDdWrV8fChQuVBqrg4GCMHz8eNWvWRNWqVTF48GAEBQUp1ZGYmIiFCxeiQYMGcHV1RYcOHXD27Fm1+4a+LYsXL4arqytGjhyJEiVK4Nq1a9nGHjhwACVLlsTo0aNRunRp7N+/PxdbSnJz584dDBgwAJUrV4aLiwu+++47bNiwAWlpaQCA169fw8nJCRcvXlSr3oSEBIwfPx4VKlSAm5sbfv/9d6SkpGgsBgDOnTuHdu3aoVy5cujUqRMePXokrRNC4ODBg2jfvj1cXFzQpEkTbNq0CUIIpTpu376N3r17o0KFCqhduzamTJmi0gkC+naFh4ejUqVK6NOnD5o1a4auXbtmG5uSkoLGjRujXbt26Nq1K+rVq4ekpKRcbC0RQaghKipKVK5cWZQvX15s375d+Pn5iUuXLomVK1eKcuXKibS0NCGEEL179xb169dXp2px5coVoaenJ6ZNmyZu3rwpfvrpJ2FiYiKePHmikZiEhARRuXJl4e7uLnx8fMT9+/fFL7/8Inbu3CnF1KhRQ8ybN09cvnxZXLt2TbRq1Uo4OjqKsLAwKWb06NFi0qRJ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NZNPTEwMLCwsEB0dzVsS8oGYmBgUKlQIwcHBfL/zgc89vqOiopCamqr0rB3wbswKCgrK8jUODg4wMDDAq1evYGhoCBsbm0wx4eHh0NbW/uhzUbkZEx0dDYVCkSkmJiYGERERWb4m4xnBDOnp6QgPD4etrW2mz5xP4fibv3D8zX94jMvfZycrlHt4IOUv/LDMX3h8yxvfn/yF42/+w2Nc/tQ7xURERERERJRLmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlnQ13QDSDJcJfppuAmVDkRIHAAju1BExujxE5aKI11FNN4HyEY7RmsHxV/44Fuc/vLJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZElX0w2gLxMVFYXLly/DwsICNWvWhLb2u/zzzJkzeP36tVJs1apVUapUKU00k+ibolAocP36dURERKBGjRqwsbFRWh8aGorLly8jISEB5cuXR8WKFTXUUtKk6OhoXLlyBRYWFnB3d4eWlhYA4M2bN/Dx8VGK7datmyaaSJTn3b9/H4GBgahSpQocHBwAvBujd+3alSm2bdu2MDExye0m0hdS+8pKdHQ0pkyZAjc3N9ja2qJs2bLo3r07fH19pZjBgwejZcuWajdm7969qFKlCmxsbFCzZk2cOnXqq8SkpKRg+/btqFevHkxNTbFz587Pas+8efNgamqq9FekSJFs92/t2rUwNTXFDz/8oEp3fNKRI0dQvXp1rFixAgMGDECNGjWQkJAAALh48SIOHDgg/fXs2ROBgYE5sl2i/CwyMhINGzbEuHHjsGDBAjg7O+PkyZPS+t27d6N06dJYsmQJDhw4gAcPHuTo9s+cOYP27dvD0dERBQsWRO3atTF//nzEx8cDAIKDg2FqaoozZ86oVW9kZCT69esHBwcHFClSBMOGDUNcXNxXiXn69CnGjRsHBwcHuLi4fHZ7bG1tM43BS5YsybK+hIQEVK5cGaamprh3755affM5/v33X1SuXBmLFi1Cr169ULduXSQmJgIA7t27h1GjRimN0USknuTkZLRt2xY9evTA8uXLUaZMGWzbtg0AkJ6ernR8/f333xgyZAh0dHQ03Gr6HGolK2/fvkX16tXh7e2NP//8E48ePcKhQ4fQsWNHDB8+HOnp6QCApKQkaVBWlY+PD7p164b+/fvj9u3baNWqFVq1aqX0oZJTMStWrMC///6L6dOnIz4+HqmpqZ/VnpSUFJQvXx6vX7+W/h4/fpzl/j18+BDTpk1DmTJl1O6b7JiamuLOnTs4dOgQ7t69i7CwMClp/O2337Bjxw7s2LEDI0eOhKOjIxo3bpwj2yXKzxITE7F48WKcP38e3t7eGDNmDDZt2gTg3cmcwYMHY/fu3Th58iR27NiB7t2759i2Fy9ejObNm6Nq1ao4ffo07t+/j4ULFyIiIgKLFi0C8O6MYnx8PNLS0tSqu0uXLrhz5w68vb1x8OBBnDx5Ej/++ONXiencuTMKFCiArl27SknW57QnLi4OGzduVBqDhwwZkmV9I0aMgJ2dHeLj46XPqq/J0NAQN2/exNGjR/HgwQOEhYXBy8tLWu/q6iqN0Tt27Pjq7SH61qSmpmLw4MG4e/cuvLy8sHr1asyYMQMAoKenp3R81apVCz169IChoaGGW02fQ63bwH766SfEx8fj1q1b0mU0W1tblCpVCp06dfqihsydOxctWrTA0KFDAQBTpkzBgQMHsGDBAmzYsCFHY0aNGpUj7QEAbW1tmJqafrSuxMREdO3aFYsXL8b69evV6JWPq1+/Pv777z/4+vri4cOHKFCgAKpUqZIpbu3atfjxxx+lWxCI6PMVKlQI9vb22LFjB6KionDo0CHpA9Lb2xvFixdHmTJlcPDgQbi4uKBEiRI5st3Hjx9j7NixmDNnDsaNGyeV29nZwd3d/Yvq9vX1xcmTJ3HlyhW4uroCABYuXIjWrVvD398fJUqUyLEYALh16xYAYObMmZ/dngyGhoafHIN37NiBmzdvYvXq1fD29v6CnlJd8+bNpX/r6elBX18fFhYWUllMTAz+/fdfFChQAO7u7tItvESkGlNTU7Ru3VpaNjY2VjrGMqSnp2Pjxo04dOhQbjaPcpDKo2N8fDz27duHoUOHfvH9fjt37oSpqSnCw8MBAEIIXLx4MdOZ/6ZNm+LChQs5GqMKdeq5f/8+HBwcULx4cXTt2hVPnz7NVN/o0aNRo0YNdOjQQeU2qCogIAD79u3Drl27UK1atUxnDeLi4rBnzx706dMnx7dNlF9l3GKwc+dOKBQKODo6AgACAwORkpKCVq1aYe3atahWrRqWLl2aI9vcunUr9PT0MGLEiC+uq2rVqkonbS5cuABTU1PUqFFDKmvcuDG0tLSkMS+nYlShTj2DBg2Cra2t1NcfXjV59uwZRo0aha1bt0JfX1/lNuSkhQsXws7ODo0aNQIA2Nvbw9nZGZs3b0avXr1QrVo1xMTEaKRtRN+C8PBwTJgwAdOmTcu0zsvLK9uTuZQ3qJys+Pv7IzU1FeXKlfvijaampiI+Ph5CCABAbGws4uLiUKBAAaW4AgUKICQkJEdjVKFqPdbW1li4cCGuXbuGgwcPIjk5Ge7u7koxe/bswalTp7B48WKVt5+cnIyYmBilv+w0a9YMu3fvxsOHD3H16tVMz99s374dtWrVQtGiRVXePhF9XMYtBj4+Pvjxxx+lBMLc3ByvXr3CpUuXcPDgQXh5eWHSpEk5ss1Hjx6hePHiMDAw+OK64uPjkZSUJC0HBwfDzs5O6eqrgYEBLCwspPEsp2JUoWo93333Hfbs2YN79+5hzJgxmDJlCiZMmCCtT01NRbdu3TB58mSUL19epW2rM/6qYvny5di7dy8OHDggXT3JuAVsz549ePz4MWxsbLBmzZov2g5RfhUaGormzZvjt99+U7qimWHt2rXo16+fBlpGOUXlZCUjsciJh5O6deuG2NhY2NraKjfmg8vg2tra0nZzOkYVn6pn6NChGDBgABwdHVGhQgXs2LEDenp6WL16NQAgKCgIQ4YMwZYtWz55m8L7Zs+eDQsLC+kv46zthzKuTAHv3pdChQohNjZWKYYHKVHOev+4A4CiRYtKx52rqyssLCxgZmYGAHB0dERiYqLaz49kRQiRYw+H3rx5M9MJlKxuQ/pwzMupGFWoUs+uXbtQq1YtODg4oEePHpg+fToWLFggJWKTJk2Cra0thg0bpvJ2VR1/VfHnn39i+/btOHr0qPR/Iqt9cnZ25pUVos8QEhKCRo0aYcyYMfj+++8zrX/9+jVOnz6d5TrKO1R+ZqV48eLQ0dHBf//99+Ub1dVV+vJuZmYGIyMjhIaGKsWFhoZKVzdyKkYVn1uPoaEhypUrhydPngAAbty4gYiICKXbyTI+RE1NTfHw4cMsr3hMmDABY8aMkZZjYmKy/MAcMmQInJ2dUaZMGVy/fh1XrlzBypUrpfX379/Hs2fP4OnpqeKeE9GnnD17Fhs3bkSbNm0QHR2NJUuWYPz48QCAWrVqoWDBgujbty/q1KmDjRs3okuXLtDV/fJZ4kuWLAkvLy+kp6d/cdJibGystFygQIFM411aWhoiIyOlMS+nYlTxufVUqVIF6enpePbsGcqXL49Tp07h/v370ueNQqEAANSuXRs9evTA2rVrM9Wh6vj7KcuXL8fMmTPx559/4siRI1L7SpcujevXr8Pf3x9paWm4desWtm/fjkuXLqm9DaL8LC4uDvXq1UOlSpWgra0tnTDu2LGjFLNhwwa0bdsWVlZWGmwpfSmVr6yYm5ujRYsWWLlyJVJSUnK0EVpaWnB3d8e5c+eUyn18fFCzZs0cjcmp9mQlLS0NT58+lT5M27Zti+joaKWZapo2bYq2bdvi9evX2X4AGhgYwNzcXOkvK5s3b4aDgwMuXLgAe3t73L59W2nq5MePH2P69Okau0+b6FvUoUMHjBo1Crdv38arV6+wbt06DB8+XFp/5MgRODo64sKFC+jatavShBxfokuXLkhISMjyC/aXqlWrFmJiYnDnzh2p7OzZsxBCSGNeTsXkVHuy8ujRI2hpacHOzg4AcP78eYSFhUnjb8b086dOncLy5cuzrEPV8VcVLVq0wMmTJ6XpU/39/QG8m2DgwIEDOHbsGAwNDeHr6ytNJEBEqklMTETVqlWhq6srHWMfPkQfFxeHkSNHaqiFlFPUOt23ZMkS1KpVCx06dMDcuXPh4uKCuLg43L59G3/++Sf27t2r0hm/nTt3ol+/fggMDJR+TG3UqFHo3Lkz9u3bh9atW2PTpk24fv06FixYIL0up2JUoUo9ffr0wU8//YSKFSsiMjISEyZMQGhoqHTblY6OTqbbv3R0dLIs/xwGBgYfPQjfP7tARDmnUaNG0sPSH7K2ts7yIc8vVblyZYwfPx7jxo2Djo4OunbtClNTUwQFBeHAgQMQQqj8oVy1alXUrVtXmu64du3aqF69OsaMGYMdO3YgNTUVv/76K5o0aSL9DkpOxahClXp27dqF0NBQdO7cGdbW1jh37hwmTZqEzp07S8mKkZGRUr0Zy0ZGRjny7M/HDBs2LNvbzwYOHIiBAwd+1e0Tfevs7Ow+Oe33rFmzcqk19DWpNVeis7Mzbty4AXt7ezRq1Aj6+vooUqQIJk2ahH79+iklKufPn8/0Y13t27cHkPkBewDw9PTEokWLMGzYMBgbG2PWrFnYsmULatWqleMxZ86ckdoEAP3794epqanS2VFV6unRowdGjBgBc3NzlChRAs+fP8f58+dRoUIFdbqViEglc+bMwd9//421a9fCxsYGRkZGqF+/PgIDA9G1a1el2LZt22Yagy9fvgwg8wP2Wlpa2L9/PwwNDVG4cGE4OTnB0dER27dvz/EYAGjZsiVMTU0xY8YMvHjxQmrfixcvVK6nWbNmCAwMhJubG4yNjdG3b18MHDgQGzduzLH+JiIizdMSn/Pk+f9LTU2Fnp5epvLk5OQsf2hRV1cXhoaGSEtLQ1JSUrZXF7KrN6di0tPTs/xhRj09vSzPtn1qW+rcQ57xBUGdHyaKiYmBhYUFoqOjv+iWhPe5TPDLkXoo5ylS4hC0uiF8a9eCaQ4860A5o4jX0a9S7+ce30IIpKenZ3oeRgiR7Q8tGhsbQ1tbGwkJCdDR0clyvEtPT4eWltZHf/fjS2MSExOz/GFGExOTTL8Hpeq2VBmDFQoFEhISpH5QxdcYf1XBMVozOP7KX06PxZo6xkl1X3QkZvcF3sDA4KOX2D98wF7VenMqRt3bsD61LXUeduWvpxJRTtDS0srywX0tLa1Pjm8fPmD/PlXGsy+N+fD2rK+5rfep8iO+REQkL/zJXCIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLWkIIoelG0MfFxMTAwsIC0dHRMDc313Rz6CuLiYlBoUKFEBwczPc7H+DxLW98f/IXjr/5D49x+eOVFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREcmSrqYbQJ+W8budMTExGm4J5YaYmBgIIfh+5xMZ7zN/n1eeOP7mLxx/8x+OwfLHZCUPiI2NBQA4OjpquCWUm/h+5y+xsbGwsLDQdDPoAxx/8ye+3/kPx2D50hJMJWVPoVAgODgYZmZm0NLS+mR8TEwMHB0dERQUBHNz81xoYe7i/uVt3D9lQgjExsaiUKFC0Nbmnblyo+74Kzff+vGW09hf6svrfcYxWP54ZSUP0NbWRpEiRdR+nbm5eZ4cOFTF/cvbuH//w7N58vW546/cfOvHW05jf6kvL/cZx2B5YwpJRERERESyxGSFiIiIiIhkicnKN8jAwAC///47DAwMNN2Ur4L7l7dx/4hyD/8/qof9pT72GX1tfMCeiIiIiIhkiVdWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWeLvrHwDHjx4gNmzZ6Ny5coYO3as0rq3b9/i77//RkBAAEqUKIFBgwbBxsZGQy1VnRAChw8fxtmzZ5Gamorq1auje/fu0NHRUYoLCQnBmjVrEBgYiFKlSmHQoEGwsrLSUKu/3J49e3Dy5EkYGBigU6dOqFu3rqab9FnS0tKwb98+XLp0Cdra2qhTpw7at2+f6Uf1AgICsG7dOrx+/Rqurq4YOHAgjI2NNdTqz+Pt7Y1//vkHrVu3Rvfu3ZXWPX36FOvXr8fbt29RsWJFDBw4EIaGhhpqKX2rEhMTsXPnTvj6+sLExARNmzZFkyZNMsXdvXsXmzdvRlRUFNzd3dGnTx/o6vJrQIZXr15hzZo1CAoKQtmyZTFo0KA8+7shX8P58+dx7NgxREZGokKFCujdu3em8TouLg6rV6+Gn58fChUqhP79+6No0aIaajF9K3hlJY9LTExEt27dcObMGfj4+CitCwkJQZUqVXDu3Dm4ubnB29sbVatWRVhYmIZaq7q6deti7dq1sLe3h5OTE37//Xc0btwYqampUkxQUBCqVKmCK1euwM3NDUeOHEGNGjUQGRmpwZZ/vpEjR2LIkCFwcnKCkZERGjdujA0bNmi6WZ/FxcUF+/btQ/HixVGwYEEMHz4cnTt3xvvzeTx48ABubm7477//ULFiRfzzzz+oW7cukpOTNdhy9QQHB6Nfv344duwYbty4obTu1q1bcHNzQ2BgICpUqIC1a9eiYcOGSv+Hib5UaGgoXFxccO7cOZQrVw6Ghobo1KkTxowZoxR3+vRpVKtWDbGxsShXrhxmz56Ndu3aaabRMhQQEAA3NzfcvHkTbm5u2LdvH9zd3REbG6vppslC3759MWnSJBgbG8PV1RUbNmxApUqVEBERIcUkJCSgdu3a2LlzJypWrIj79+/Dzc0NT5480WDL6ZsgKE8bMGCAGD58uOjYsaNo3bq10roRI0aIMmXKiJSUFCGEEImJiaJo0aLil19+0URT1fLs2bNMywDEv//+K5UNGDBAVKxYUaSlpQkhhIiPjxf29vZiypQpudrWnODn5ye0tLSEl5eXVDZ9+nRhZWUlkpKSNNiyz/Ph+3fp0iUBQFy9elUqa9u2rWjUqJG0HBYWJoyNjcWKFStyrZ1fIj09XTRo0EAsW7ZMuLi4iLFjxyqtb9q0qWjVqpW0HBISIvT19cX69etzu6n0DYuPjxevX79WKtu2bZsAoFReoUIF0bdvX2n5wYMHAoDSmJOf/fDDD6JatWoiPT1dCCFETEyMsLGxEX/88YeGWyYPH47psbGxwtraWsydO1cq++uvv4SlpaWIjo4WQgihUChEzZo1RZcuXXK1rfTt4ZWVPGz37t24ePEi5s+fn+X6w4cPo3379tDT0wMAGBoaol27djh8+HBuNvOzFC9eXGm5SJEi0NPTU7oqdPjwYXTo0EG6NczY2Bjfffddnti/D3l5ecHc3BzNmjWTyrp164bIyEhcunRJgy37PB++f87OzgAgvX9paWk4fvw4unTpIsXY2NigSZMmeeb9mzlzJoyNjTFs2LBM6xITE3H69Gml/bO3t0eDBg3yzP5R3mBsbIyCBQsqlWUcb+Hh4QCAFy9e4N69e0r/H8uXL4+KFSvy/+P/O3LkCDp16gRt7Xdfi8zMzNC6dWv2z//7cEw3NTVFgQIFMn0mt2jRQrp1TktLC126dIGXl5fSVXUidTFZyaOeP3+O4cOHY9u2bVneA69QKPD8+XMUK1ZMqbxYsWJ49uxZbjUzx6xduxZCCDRo0ADAuy+DISEh38z+PXv2DEWKFFF6Jidj3/Li/nxoxYoVMDU1Rc2aNQEAL1++REpKSp59/y5cuIBVq1Zh/fr1Wa4PDAxEenp6nt0/yttWrFgBR0dHlClTBsD/xhD+f8xaVFQUIiIi2D9q8PHxwaNHj9C0aVOp7NmzZ1n2YVxcHN6+fZvbTaRvCJ+sy4PS0tLQvXt3/PLLL6hUqVKWMcnJyRBCwNTUVKnczMwMSUlJEEJkethZri5duoQxY8Zg5syZ0hnDpKQkAMhy/xITE3O9jV8qMTEx077o6+tDX18/T+7P+w4fPoxZs2Zh3bp1sLa2BgBpn/Li+xcREYHvv/8ef//9d6Yz2hny8v5R3rZy5Ups27YNx44dk05+fOz/46tXr3K9jXLD41U9AQEB6N69O/r06aOUrGT1OWZmZiatI/pcTFbyIB8fH/j6+sLZ2Rk9e/YEAFy/fh3p6eno2bMn5s+fDwcHB+jr62d62DwiIgLm5uZ5JlG5fv06WrVqheHDh2P8+PFSuampKbS1tbPcPwsLi9xu5hezsLDItC/x8fFISUnJk/uTwdvbG507d8bcuXPRu3dvqTxjn/Li+7d161bExsZix44d2LFjB4B3V4q8vLzw+vVrbNmyJU/vH+Vd//zzD0aOHImtW7eicePGUvn7/x+LFCkilfP/4zsZty3xeP20Fy9eoFGjRqhduzbWrFmjtC6rz7GMB/DZj/QlmKzkQRkzcbzvyZMnSE1NRYsWLWBiYiLF3b9/Xynu3r17qFChQq619UvcuHEDzZo1Q79+/TI9l6Onp4eyZcvm6f17X4UKFbBixQrExsZKZ6Lu3bsnrcuLTp48CU9PT0yfPj3TlNqFChWCjY0N7t+/j7Zt20rleeH9a9q0aabpsc+ePYvixYujRYsWAN7d+mBmZob79+8rnXm8d+8e3NzccrO5lE9s2rQJAwYMwKZNm5SeTQHePZ+io6OD+/fvS8eXEAL3799XOomQX5mYmMDZ2fmb+Tz5WoKCgtCwYUO4ublh586dmaa9rlChQpZ9WLhw4Tz9kwIkAxp9vJ9yTFazgf3555/CxsZGBAUFCSGE8Pf3F2ZmZmL58uWaaKJabty4IaysrDLNsPS+mTNnioIFC4qQkBAhhBCPHj0SxsbGYt26dbnVzBwTFhYmTE1Nxbx586Syrl27ivLly2uwVZ/v1KlTwsjISGl/PjR8+HBRqlQpaeaYK1euCC0tLXHkyJHcamaOyWo2sP79+4vy5cuL2NhYIYQQ586dEwDEqVOnNNFE+oZt2bJF6Ovri+3bt2cb06ZNG+Hh4SHNDrlr1y6hra0t7t27l1vNlLVJkyaJwoULi7dv3wohhLh3757Q19cXW7du1XDL5CEoKEg4OzuLdu3aSf+HPrRnzx6hp6cnbt++LYR497lWtGhR8fPPP+dmU+kbpCUEp2j4FnTq1AlJSUlKM5ekpKSgU6dOuHz5MmrUqIErV66gYcOG2LFjh+x/CMze3h7x8fHw9PRUKu/QoQM6dOgA4N1zK+3atcOtW7dQrVo1XL58GS1btsTmzZulGV3ykl27dqFv376oVq0aoqOjERwcDC8vL1StWlXTTVNLQkIC7OzsYGxsjObNmyut69u3Lxo1agQAiI6ORrNmzRAcHIwKFSrg/Pnz6NevHxYtWqSBVn8ZV1dXtGjRAn/++adUFh4ejmbNmiE8PBzlypXD+fPnMWzYMMydO1eDLaVvjb+/P8qUKQNHR0d4eHgorfvll19QsWJFAO9u38n4ocjixYvj/PnzmDFjRqarnvlVQkIC2rRpAz8/P1SpUgUXL15E+/btsX79+jxz2/TX1KBBA1y4cAEdO3aUZhgFgFq1ainNiDh48GBs374dderUwe3bt+Hs7IyjR49mepaFSB1MVr4R58+fR3p6ujRb1vuuX78u/YJ9Xvniu337dqSnp2cqr1ixovThC7y7leHatWt48eIFSpUqledvsXn79i0uXrwIAwMD1KtXL08O8Kmpqdi5c2eW69zd3VGqVClpWaFQ4OLFi9Iv2JcrVy63mpmjDh8+jEKFCqFKlSpK5enp6bhw4YL0C/YZszMR5ZTIyEgcOXIky3WNGzeGg4ODtJySkoJz584hKioK1apVg5OTUy61Mm8QQuDKlSt4+fIlypQpo/RZk995eXkp/QBkBicnJ9SpU0ep7P79+9Iv2NeqVStPnjwkeWGyQkREREREssR0l4iIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxXK87y9vWFqaqrpZuRZd+/ehaGhIaKjozXdFCLKgxo3boyZM2dquhl5Vo8ePTB69GhNN4NItpisUJ6Xnp6OpKSkr1b/uXPnYGhoiPT0dJVf8++//6JRo0awtbVFyZIlMWLECERGRqr8+sTERCxZsgQ1atSAjY0NKlWqhKVLl0IIkWV8ZGQkSpQoAUNDQwQGBqq8HQBQKBRITk7Otm4ioo9JTk5GWlraV6u/VatWmDx58me9dvHixTA0NETfvn3Vel1iYiI2btwId3d3GBoaYv/+/VnGBQcHo2/fvihcuDCKFi2KSZMmISUlRa1tpaSkIDU1Va3XEOUnTFaIPkHdL/MPHz7E7t27MW3aNPz333/YsWMHzp07h++//17lbW7YsAH+/v5Yvnw5njx5ghkzZmDixImYM2dOlvF9+/ZF6dKlmXQQ0Tfnc7/M37x5EwsWLEDZsmXVTiCWLl0KHx8fzJ07F8nJyVmerAoPD0ft2rURHh4OHx8f3Lx5E+bm5jhx4oTabSWijxBEGvbo0SPRoUMHYWdnJ5ycnMTvv/8uUlNTpfUXL14UdevWFRYWFqJYsWJi4sSJSuuPHj0qdHR0xK5du0S1atWEjY2NqFu3rrh3757Sdj5Vz4kTJ4SJiYnYvn27KFeunDAxMRG7d+8Wenp6AoAwMDAQBgYGYvDgwWrv4+LFi4Wpqeln9M7/jBw5Uri6umYqX7p0qahXr544deqUACACAgI+Ws+JEydExYoVpX5auXKlACAiIyOlGFX76mv0+Y0bN0RcXJwYOHCgcHR0FPb29qJz584iMDDw8zqOiLKlUCjE4sWLhaurq7C0tBQNGjQQN2/elNanpKSI8ePHi6JFiwoLCwtRv359cfnyZaU6PDw8xIgRI0SfPn1E0aJFRdGiRcXEiROFQqFQq55GjRqJUaNGie+//17Y2tqKtm3bih9++EFoa2sLHR0daQx+/vz5J/crNjZWlC5dWhw9elQ0btxYfP/995/VP4mJiQKA2L17d6Z1o0aNEoULFxaJiYlq1Td48GBha2srSpQoIUaPHi3atGkjhg0bJsWo2lejR4/+Kn0uhBDHjx8X7u7uwsrKSlSsWFGsXLlSqW6i3MRkhTQqKChI2NjYiB9++EE8efJEPH/+XEydOlWcOnVKCCHEy5cvhbGxsRg/frwIDg4WZ86cEYUKFRLjx4+X6jh69KgAIBo0aCAePHggQkJCRKdOnZS+2KtTT61atcS9e/dEYmKiSE9PF8ePHxcARFxcnEhMTFT6sq2K58+fi9q1a3/2h2WG3r17i5o1ayqV3b59WxQsWFAEBgYKHx+fTyYrgYGBwsDAQEybNk2EhISIY8eOCWtra6VkRdN9rlAoxLhx40TVqlWFn5+fCA0NFfv27RO//vrrF/UfEWU2ceJEYWtrK/bs2SPevn0rfHx8xMCBA6X1Y8aMEUWLFhXnz58Xr169EqNHjxampqYiJCREivHw8BA6Ojpi7dq1IjQ0VJw6dUoYGRmJHTt2qF2PlpaWWL58uQgLCxMpKSkiJSVFNGzYUIwbN04kJiZKY8Sn9OzZU4wYMUIIIb5aslKsWDExcuRIteobMWKEKFGihLh69aoICgoSffv2FQCUkhVN93lkZKQwMjISS5cuFREREeLBgwdi2LBh4sGDB2rtK1FOYbJCGvXzzz+LUqVKibS0tGzXu7i4KJVt3LhRGBgYiISEBCHE/77w/vfff1KMr6+vACAiIiLUruf27dtKcRlJgLpJStu2baWrMk2aNBHx8fFqvf59165dE3p6euLvv/+WyuLi4kTZsmXFtm3blNr5sWRl3LhxokqVKkplc+bMUUpW5NDn7dq1U/tLABGpJzo6WhgYGIh169ZluT42Nlbo6+uL7du3S2UKhUKULl1aTJw4USrz8PAQXbt2VXptu3btxNChQ9Wup02bNpna0bhxY6WTHJ+yceNG4eLiIl3x+BrJSmpqqgAgZsyYIdq0aSMsLS1FmTJlxJQpU0RSUlKWdcXExAh9fX2xb98+pfptbW2lZEUOfX7//n0BQISGhqrUR0RfG59ZIY3y9fVFnTp1oKOjk+X6Bw8eoFatWkplHh4eSE5Ohr+/v1Smra2NEiVKSMs2NjYAgIiICLXq0dLSgouLy5ft1P/bu3cvIiIicPnyZYSFhaFDhw6fVY+/vz88PT3RrVs3DBgwQCofNWoUqlWrhu7du6tc14MHD+Du7q5U9uGyHPq8d+/e+Pvvv9GlSxesX78eL1++VHkfiUg19+/fR3JyMho0aJDl+v/++w8pKSlKx7GWlhZq1aqFBw8eKMWWLl1aadnGxkYaC9Spx9XV9Ut2Cf7+/hg7diy2bdsGQ0PDL6rrY8T/Pxs4c+ZM9OjRA/7+/li5ciVWr16NX3/9NcvXZPTD+2OuoaEhKlWqlClGk31etmxZ1KlTB+7u7pg8eTJOnz7NCQBIo5iskEYJIbJNVIB3D7drayv/N81Yfv+BRy0tLWhpaWVZvzr1aGtrQ1dXV829yJqenh5MTU1Rs2ZNLF68GMePH4efn59adTx79gwNGzZEvXr1sGHDBqV1Pj4+2LlzJwwNDWFoaIhmzZoBAMqUKZPtNJgKhSJTP33YL3Lo83bt2sHPzw8eHh74999/UapUKUyfPj3LfSKiz5NxrGY3BisUCgCZxwhtbe1MD5x/GPN+/erUY2BgoGrzs3T9+nVERESgRo0a0tjo4+OD7du3w9DQEG/evPmi+jPo6enBysoKbdq0Qffu3WFtbY2GDRti+PDh2LFjR5avyeiHj43BcuhzHR0dnDlzBsuWLUN8fDwGDx6MsmXLIiAgIMv9IvramKyQRlWqVAmXL1/OdgarcuXKwdfXV6ns2rVr0NPTQ8mSJVXezpfUk/FFOmPw/xwZ+5fdfmYlICAADRs2RK1atbB169ZMXygePnyImJgYREVFISoqCocPHwbw7ndT5s+fn2Wd5cqVw82bN5XKbty4kSlG030OAMWKFcPIkSPx77//Yt26dZgxY8ZXnR6VKL8pX748dHV1cfHixSzXlyxZErq6upmO4+vXr6NcuXIqb+dL69HV1VV5/O3SpQsSEhKkcTEqKgr169dH165dERUVhYIFC6rc7k/x8PDINC7r6OhkO86XKlUKurq6SmNwamoq7t69Ky3Lpc91dHTQsmVLLFiwAI8ePYKRkRHWrVun8vaJchKTFdKoESNGIDAwEKNGjUJYWBiio6OxZMkSnDt3Tlp///59zJs3DwkJCbh37x5+++03DB48GCYmJmpt53PrKVq0KABkGvSzs2bNGqxbtw5v3rxBeno6bt++jbFjx6Jq1apKHxJOTk7Z/nZAYGAgGjZsiJo1a2aZqACAvr6+dObQ0NAQ+vr6AN6dJcvu6tDQoUPh6+uLJUuWICkpCVevXsXcuXOVYuTQ50OHDsXhw4cRGxuLxMREXLt2DYULF86xq15EBFhZWWHw4MH45Zdf4OPjg5SUFNy5cwdjx44FAFhYWGDAgAGYMGECHjx4gISEBMyYMQNPnz7F8OHDVd7Ol9ZTrFgx3L17V6VbkbS1tZXGRUNDQ2hra0vlGTZu3AhDQ0PEx8ervB8fGjt2LA4dOoSjR48iLS0Nt2/fxooVK9C1a9cs4y0sLNCrVy+MHz8eT548QVxcHH7++Welqz1y6PMLFy5g3Lhx+O+//6BQKODn54fQ0FA4OTmpvH2iHKWhZ2WIJNevXxf16tUTurq6ws7OTowaNUp6AFsIIY4dOyYqVKggtLW1hYWFhRgyZIjS+oypi98XEBCQ6QHwz6knw/jx44WFhYXQ19f/5NTFoaGhYtSoUaJQoUJCT09PODo6ipEjRyo9rBgTEyP09PSEl5dXlnVMmjRJABD6+vrSdJ0GBgbCxsYm2+2q8oC9EELs3r1bODo6Cj09PeHq6irmzp2baepiTff5rVu3RNu2bYW5ubkwNjYW9evXF7du3frofhGR+lJTU8XkyZOFvb290NXVFVWqVBFnz56V1sfHx4tBgwYJc3Nzoa2tLSpWrCi8vb2V6vDw8BC///67Ulm/fv2UHgD/3HqEEOLhw4fCzc1NGg9Vmbr4fVk9YD9p0iRRtWrVbGcW8/LyksZdAEJPT08YGBiIUaNGKcXt2LFDlCxZUujo6AgHBwfx888/f3Qq4+joaNGlSxehp6cnbG1tRZ8+fUSzZs2UZgPTdJ8nJSWJv/76S5QqVUro6uoKBwcH8dtvv4n09PRs94voa9ISgr8gR/IghMjyGYgMWT0DkVGempqqdN+tEALJyckwMDDIVKc69XwoJSVFredastun/fv3Y9WqVTh+/HiWr0tLS8vylictLa1s26dQKJCSkpLlPn+sbRl9ldXDqJru80/9nyCinJPdcfqp9VmNi2lpaRBCQE9P74vq+fB16oxxGTKuyLzflmrVqmHhwoWoW7fuR7f1IV1d3Szb96m++9D7Y1tqaiq0tLTUqje3+pxjMMkBkxUiDch4wPFjkwsQEdHXkXFihYjkj8+sEKmpS5cume6JzvgbMWKESnXo6OgwUSEiUtPDhw+zHX8NDQ3x6tUrlephokKUd/DKCpGaUlNTM039mCG7WwSIiOjLZdxump2v+dsqRKQZTFaIiIiIiEiWeBsYERERERHJEpMVIiIiIiKSJd5cnwcoFAoEBwfDzMyMUwgSfWOEEIiNjUWhQoXUmvqUcgfHX6JvG8dg+WOykgcEBwfD0dFR080goq8oKCgIRYoU0XQz6AMcf4nyB47B8sVkJQ8wMzMD8O5AMjc313Br6GuLiYlBmTJl8PjxY77f+UBMTAwcHR2l45zkheNv/sLxN//hGCx/TFbygIxbD8zNzTl45hNaWlp8v/MZ3mIkTxx/8x+Ov/kTx2D54s15REREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiX+KCSRTAV36ogY3Y8fokW8juZSa4iIiIhyH6+sEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlnS/ZIXKxQKaGtnzndSU1MhhIC+vn6O1qupGFUIIaClpZVjcfTtu3jxIvz8/FC1alVUrlw5R+oMDAzEuXPnYGtri2bNmkFHRydH6iV5ym78SkpKgr6+/meNbaqMUXkxRp04+va9ffsWR48ehYmJCdq0aQNDQ8NsY2NjY3H48GEoFAq0adMGFhYWudhSIlL7k+z169cYOnQoihQpAn19fdja2qJp06Y4duyYFDNgwAA0a9ZM7casWbMGRYsWhZ6eHkqWLIk9e/ZoLObSpUvw9PSElZUVTE1N0aBBA1y9elUp5sWLFxg0aBAcHBxgaGiI8uXLY926dZm2FRgYiC5dusDU1BRWVlYYNGgQYmJi1O0e+obMnDkTXbp0wZkzZ9CsWTNs2rTpi+tctWoVmjVrBm9vb0ycOBG1a9dGUlJSDrSW5GTv3r2oW7cujIyMYGxsjNKlS2P8+PEIDQ0FALx8+RJGRkY4ffq0WvWGhITA09MTRkZGMDIyQteuXREREaGRmLi4OMybNw/lypWDgYEBihQpgokTJyI5OVmKEUJg586dcHd3h7GxMaytrdGlSxe8ePEi076tXbsWZcuWhb6+PlxdXXH06FG1+oa+Lc+fP4eLiwv279+Pv/76C/Xr10d6enqWsbGxsahatSrWrFmDzZs3w83NDZGRkbncYqJ8Tqjh+fPnolChQqJ58+bi1q1bIj09XURGRgofHx/RqlUrkZaWJoQQonfv3qJ+/frqVC0OHjwo9PT0xM6dO0V8fLxYvny50NHREVeuXNFITIcOHcTBgwdFZGSkiI6OFkOHDhXm5ubixYsXUsz06dPFli1bRFhYmEhOThabNm0S2tra4vDhw1JMaGioKFy4sOjcubMICQkRKSkpYv369eLgwYMq9010dLQAIKKjo9XqU5Kn2NhYYWZmJh4+fCiEEOL06dPCyclJpKenCyHevd8mJibCr2kTEdSyxUf/3nfmzBmRkpIihBAiJSVFFC9eXBw/fjx3d47Ups7x/fvvvwtjY2OxcuVKERERIdLS0sTTp0/Fn3/+KRYsWCCEECIoKEgAEN7e3iq3QaFQiJo1a4r69euL4OBgERgYKKpUqSKaN2+ukZgdO3aIX375Rfj5+YnU1FRx5coV4eDgIIYPHy7FvHr1SvTt21fcvHlTpKamiufPn4uGDRuKypUrK+3b/Pnzhampqdi/f79IS0sTAQEBYtiwYSr3Dcffb8+oUaPEoEGDhBBCpKWlicqVK4t9+/YJIf43/ma838uWLRONGzeWXtuhQwcxb9683G80fTU8xuVPrWSlbdu2onjx4iI5OfmjcZ9KVtLT00ViYqJSWcOGDUXHjh2Vytzd3UWPHj00EvOh5ORkoaurK9avX59tjBBCODo6iqlTp0rLo0ePFoULFxZJSUkffd3H8ED6tly+fFmUKFFCWlYoFMLU1FS8evVKCPH5ycr7UlNThYODg7hz585X3x/6Mqoe37dv3xZaWlpixYoVH41TJVlJTk4Wqamp0vLFixcFAHHr1i2pzNvbWwCQkurcjMnK9OnTRaFChT667//884/Q0tKSkvbw8HBhaGgoFi5c+NHXfQzH32+Pu7u7OHTokLT822+/iZ9//lkIkTlZ+f7775X+/2zYsEG0b98+V9tLXxePcflT+TawmJgYeHl5YciQIZ/9LEqGbdu2wcjICGFhYRlXd3D16lXUr19fKa5hw4a4fPlyrsdkJTQ0FOnp6bCyslIqF0IgKSkJ4eHhWL16NaKiotCuXTtp/aFDh9CuXTsYGBhAoVB8qmsoHwgNDYWtra20rKWlBWtra7x9+zbHtjFmzBg0a9YMFStWzLE6SbN27twJIyMjDBgw4IvrqlixIoYPHy4tX758Gebm5nBzc5PKGjRoAC0tLWlczM2YrISEhGQaf4F3z0gmJCTg4cOHWLNmDbp06QI9PT0AgLe3N5KSktCtWzcA78Zrog/HYBsbm2zHX3ViiejrUDlZCQgIQHp6OkqVKvXFG9XR0YGBgYH0oGNsbCwSEhJgZ2enFFegQAG8efMm12M+JITAiBEj4OTkhObNmyut8/Pzg6WlJezs7DBq1CgsX74clSpVktY/f/4cZmZmaNSoEQwMDFCwYEEMGzYMsbGx2fZPcnIyYmJilP7o22FlZYXo6GilspiYmCy/iH2OsWPHIiQkBGvXrs2R+kge/vvvPxQvXhy6ul80LwoAwMDAQPpCDwBv3rzJNCbq6urCyspKGhdzM+ZDN27cwLp16zBkyJBM6/r16wdLS0u4uLhAoVBgxYoV0rqAgABYW1tj7969sLOzg5GREapUqaL0jOWHOP5++z4cgz82/qoTS0Rfh8rJSkZikRNXB7p3746kpCTY2NgolX9Yt0KhyDRzS27GZBg3bhxOnz6Nffv2wcjISGld+fLlkZSUhPj4eKxevRr9+/fH3r17pfVCCCxYsACjRo1CYmIiTp48iaNHj2LEiBFZbgsAZs+eDQsLC+nP0dEx21jKe8qWLYvAwECEhIQAAO7evQtdXV0ULlz4i+oVQmDIkCEICgrC9u3bc+RLLcmHlpZWjl2dvXPnDpYuXapUllXdH46LuRmTwd/fH23btkXnzp0xbNiwTOs3bdqElJQUPH36FMbGxmjUqBHS0tIAvDsmIiIicOjQIdy7dw+RkZHw9PSEp6cnHjx4kKkugONvflCxYkWcO3dOWj579qzSScbPjSWir0PlZMXZ2Rm6urp49OhRjjfCzMwMJiYmmS6tvn37Fvb29rke875ff/0Va9euxYkTJ5RuW/iQkZERevXqhdatWyud0XZwcEDLli3x3XffQVdXFxUqVMBPP/2E/fv3Z1vXhAkTEB0dLf0FBQVlG0t5j62tLb7//nu0bt0aM2fOROfOnTFq1KgvTi4mTJiAPXv2oHbt2li1ahWWLVv2VY5X0owyZcogICAAKSkpOV63g4ODNJtYhpSUFERHR0vjYm7GZHj27BkaNmyIevXq4Z9//vnoPpQoUQKLFy/GnTt3pJkbCxUqBODd7Hv29vYwMjLC77//DnNzcxw5ciTLejj+fvtGjBiBJUuW4Ndff0WvXr3g7++Prl27Zhnbt29feHt7Y8iQIRg9ejR2796NQYMG5XKLifI3lZMVU1NTtGvXDitWrEBCQsIXbVShUChNqaqlpQUPDw/4+PgoxZ06dQoeHh65HpNh4sSJWLlyJY4fP44aNWqotG+JiYlKv21Rt25d6SxfhtTU1I9+MTUwMIC5ubnSH31bVq5ciSFDhiAyMhJTp07FxIkTv7hOJycndO3aFU+fPsWjR4/w6NGjTLebUd7Vo0cPpKSkYNmyZV9cV0pKitK4VKdOHcTFxeH69etS2enTpyGEkMbF3IwB3t3C1bBhQ9SqVQtbtmxR6TeDEhMTAUCKrVu3LgAo7atCoUB6errSbXDv4/j77atcuTLOnj0LAChVqhQuX76c6a6JDPb29rh+/Trs7OxgYmKCK1eu8GobUW5T52n84OBg4eTkJDw8PMSZM2dEVFSUCAgIEPv37xceHh5KUxfXrVtXJCYmKv1lzNCyefNmAUCEhoZKdXt7ewsdHR2xevVqERwcLP744w+hp6cnbt++rZGYyZMnC1NTU3HmzBmlfXh/Bp02bdoIb29v8fbtWxEYGCj++OMPoa2tLfbv3y/F3Lx5UxgZGYl//vlHhIeHi3PnzolChQqJkSNHqtzvnKkif8mJ2cAo71Dn+J43b54wMDAQs2fPFgEBASIyMlLcuHFDTJ48Wfzxxx9CiP/NBubl5ZVpDFYoFEIIIcqUKSNN3ZqhYcOGonr16sLPz0/cvXtXlCtXLtOsR7kVExgYKIoVKybatWsnYmNjlfYhw/r168XMmTPFw4cPRUREhLhw4YKoWrWqcHNzkz6LhBCiW7duom7duuLRo0fi9evXYvTo0cLMzEw8e/bsk/0tBMff/ObD2cDo28djXP7USlaEeDcV5Pjx40X58uWFubm5cHZ2Fh07dhSXL1+WYvr37y8MDAwy/bVt21YIIcS2bduEgYGBCAsLU6p7x44dwtXVVZibm4uqVauKY8eOZdp+bsVYWFhkuQ8zZsyQYq5duya+++47UbBgQVGkSBHRvHlzceLEiUzb8vb2FjVq1BAWFhaibNmyYtq0aZ+c/vl9PJDyFyYr+Yu6x/fx48dF69atRcGCBYW1tbWoVq2a+OOPP0RMTIwQQoiXL19mOXYZGBiIixcvCiGEqFixotJvlgjxbmzv1auXsLW1FQUKFBADBgyQ6sztmIULF2a7DxmJSEJCgpgzZ46oUKGCsLKyEuXLlxfjx48X4eHhStuKj48XI0aMEIUKFRJ2dnaiWbNm4tq1ayr1tRAcf/MbJiv5D49x+dMSgnM5yl1MTAwsLCwQHR3NWxLygZiYGBQqVAi+tWvB9BPPsRTx4i9x53U8vuWN70/+kjH+BgcH8/3OJ3iMy5/Kz6wQERERERHlJiYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJZ0Nd0AIspaoT17YW5urulmEBEREWkMr6wQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlXU03gEiOXCb4aWzbipQ4AEBwp46I0f12DtEiXkc13QSib4Imx6dv3bc6/n4ujtskB7yyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGRJ93NfmJ6ejsjISFhaWkJXV7mayMhIpKWlwc7OTu1609LSEBkZCRsbG2hrZ51L5WYMACQkJEAIARMTkyzXJyUlQaFQwNjY+KP7plAoEBwcDGNjY1hbW380lr6+5ORk7N27F/7+/khPT8eoUaNgaWmp6WZ9896+fYsVK1YAAGxtbTF8+HCl9dHR0dixYwfi4uLQvn17ODs7a6KZshcfH4+UlBRYWVkplaenpyMkJAR2dnYwMDBQu97o6Ghoa2vDzMxMFjFCCERERMDGxibbmIzPIi0trWxjACAqKgpxcXEoWLAg9PT0Phr7NUVERGDJkiUAAEtLS4waNUpp/fPnz3H48GHo6+ujQ4cOsLW11UAriZQpFAocOHAA//33HypVqoQWLVpoukmUT6h9ZcXf3x9dunSBhYUFypYtCwsLC1SuXBlbtmyRYkaPHo3OnTur3ZjZs2fD2toaxYsXh52dHdasWaPRmIcPH6JRo0awsbGBk5MTOnbsiNDQUGn94cOH4eHhAVtbW9jZ2cHV1RXe3t7Z7t/vv/8OR0dHDBw4UNUuoa8kPj4e7u7uWLZsGZKSkjTdnHzpxYsXWLZsmVJZSkoK3N3dceDAATx69AhVq1bF06dPNdRCeVq9ejXKlSsHW1tblCxZEjY2NujXrx+eP38OAAgJCYGjoyPOnz+vVr3Pnj2Dh4cHChQoABsbGzRu3BjBwcEaixFCYM6cOShYsCBKly6NokWLYtu2bdL6sLAw/PLLL7Czs4OzszNMTU0xcOBAxMXFZbl/b9++Rbly5eDo6IgH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w8rKCosWLYKxsTG6desGALh37x7atm2LZs2a4dtvv0VERIS0nQXhPPrX36vXtWvXDu3atcvn1hC9W9++ffXdBCqicpWslCpVCpcvX8Yff/yB3377DeHh4bC2tka9evWwc+dOKVEpVaoUwsLCpEPvmdq0aYO///47ywX2ANCkSRMcPHgQ8+bNw59//glnZ2ecOHFC63zI/IypXr06Tp48idmzZ0sXZ86ZM0dretD//vsPSqUSQ4cO1drOiRMnap1G8Dpra+v3uqs0EdGwYcNQp04dLF++HIMHD4ZGo0HlypXRtWtXDB48GEDGeeXly5fH5MmTszx/9+7daNiwYZYL7AFg8+bN+P777zF8+HAYGBigb9++Wb5Q52fMsmXLMG/ePIwaNQoajQb16tXD+fPnYWdnByDjuhtjY2NcvXoVTZo00XpuaGhotrNWmpiYoHz58jAxMXlLLxMRkZwoROaVrCRbarUaSqUSKpWKR2zyicuMQL29dnpKPMJWtcG1pk1gYZSr3xNkzf5Q1vtfEPdvuZPj+6PP8amwK6zj7/sqCuO2HPdx0sbpj4iIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZUgghhL4bQW+nVquhVCqhUqlgaWmp7+bQR6ZWq2FnZ4fIyEi+30UA92954/tTtHD8LXq4j8sfj6wQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLL13spKQkICHDx8iNjY2y7qIiAgEBwe/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Ko4dOyauXr0q/vnnH9GnTx9phsHM2cDWrFmTZQxOSEgQQghRrVo1MWLECK26+/TpIypVqiT2798vdu3aJcqVKydGjRqlt5iJEyeK6tWriwMHDohz586Jbt26CVtbWxEVFSWEEOLmzZvCyspKdOnSJct2Zlq7dq0YPHiw2Ldvn7hy5YpYv369cHR0FJ6enu/s60wcf4sWjr9FD/dx+VMIkbu7E6akpGDjxo3Yv38/wsPDYW1tjXr16mHMmDHSBZszZ87Enj17sjy3efPmWLlyJQ4cOIDp06fj3Llz0nUrQMZ5x7/88gseP34MZ2dnfPvtt9JpWPqIuXPnDubOnYt79+7B1tYWffr0QZ8+faQjK66urtn+mjlq1CiMGjUKQMaNyxYvXozLly/D2NgYLi4uGDNmDKpVq6ZbhyPjAk+lUgmVSsVf+YoAtVoNOzs7REZG8v0uAnK7f9+9excrV67EtWvXoNFoULlyZXTt2hXdunWDgYEBnj17Bnd392yf+/fff6NevXro3LkzGjZsqHXdSnJyMn755RccOnQIBgYG+OyzzzBx4kStX5zzMyY9PR3Lli3Dv//+C41Gg3r16mH69OnSqWLr16/HokWLst3OmzdvStcO/vvvv9i8eTPCw8Nha2uLTp06YeDAgdJ1mO/C8bdo4fhb9HAfl79cJyuU/7gjFS38Z1m0cP+WN74/RQvH36KH+7j88SpdIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUjfTeAKDdcZgTquwkfXXpKPAAgsns3qI24i8qB/aHD+m4CUZ4oCmPoh+D4K38cj4seHlkhIiIiIiJZYrJCRERE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8li0aJHWdMNy6PPAwEAMHz4cgwcPhrOzMwICAuDr64u5c+fq/PpEeYnJCulV8eLFce7cOaxYsQK7d++GlZUVpk6dimbNmknrL126hP/9739Yt24drKyssGvXLnTo0EGqw9raGu7u7lr1mpubw93dXToq8r71AEDJkiWxd+9ebNiwARcvXoSXl9dbk5URI0agSpUq2L59O549e4bKlSvD19dX67zwsLAwxMbGvvW0rXXr1klHXipWrCjdEK1JkyYwMjJCREQEWrZsiSdPnmjdLM3c3DzHZMXS0hIXLlzAggULsHHjRtSoUQP//fcfvv32WykRkEOf79y5E+vWrcPevXuRmpqKzz//PFf/qIlIN3379kWlSpXw119/4a+//kL9+vW1fsAYPnw4bG1tsXXrVpw9exZNmzbFhAkTtL7gN2zYEJUrV9aqt0aNGlrXc7xvPQAwdepUAMCff/6JV69eYfPmzW9NVhwcHHDt2jX8+uuvWL16Nezs7HDs2DG0aNFCirl27Rr69euXbaLyenvWrFmjVTZs2DApWalZsyYuX76M3377DevXr0e1atWwevXqbO83lWncuHEoVaoU/v33XyiVSkyfPh0BAQFa9z/Rd583atQI27Ztw5o1a3Dy5EnY2tri+PHj0v9lovymEEIIfTeCqKg5fvw4bty4If0TJiKi/PPFF19gzpw5H3xBPxF9fExWiHLp1q1biIqKynadvb29NOMVERHlrYSEBFy8eDHH9S1atICpqWk+toiIPjaeBkaUS//++2+Os8p06tSJyQoR0UcSHR2tddrrm+rWrctkhaiQ4ZEVIiIiIiKSJU5dTEREREREssRkhYiIiIiIZInJChERERERyRIvsC8A0tPTERkZiRIlSkChUOi7OUSUh4QQiIuLg52dndZNQ0keOP4SFW4cg+WPyUoBEBkZCQcHB303g4g+orCwMN7zQYY4/hIVDRyD5YvJSgFQokQJABk7kqWlpZ5bQx+bWq1GtWrVcO/ePb7fRYBarYaDg4O0n5O8cPwtWjj+Fj0cg+WPyUoBkHnqgaWlJQfPIkKhUPD9LmJ4ipE8cfwtejj+Fk0cg+WLJ+cREREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGR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OyYiIgLBwcHZbsObM1FGR0cjLCwMtra2OZ7KI4TA3bt3odFo4OzsDHNz82zjssPxt2jh+Fv0cB+XPyYrBQB3pKKF/yyLFu7f8sb3p2jh+Fv0cB+XP05dTEREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLTFaIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZMlI3w0gKuhcZgTmaX3pKfEAgMju3aA24i76JvtDh/XdBCIiIsonPLJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyZLR+zwpMTER58+fR3h4OKytrVG7dm1UqFBBWn/q1CnExcWhc+fOuao3PT0dp0+fRmhoKJydndG8eXO9xgDA8+fPcfr0aSgUCri7u8PKykpr/bNnz3Dx4kVoNBrUr18fTk5OOW5fVFQUdu3aBScnJ3h4eLyjN0gu1q1bh23btsHY2Bh9+/ZF37599d0k+v8OHz6M7777DgDg6emJuXPnaq3fsmUL/vzzT5iammLSpElwd3fXRzOJiIjoPeX6yMqGDRvg4OCAyZMn4/Tp01izZg3atm2L3r17QwghxSxZsiRX9b569QqtW7fG4MGDceTIEXTv3h1dunRBamqqXmIAYO3atahYsSLWrl2LPXv2oFmzZvDz85PWjxs3Do0aNcL69euxbds21KpVC9OnT892+4QQGDhwICZOnIhVq1blqm9If9atW4fr169j6tSpGDhwIMaNG4djx47pu1n0/7m5uWHlypVo27YtHj16pLXu7NmzGDt2LL766it069YN3bp1Q1hYmJ5aSkRERO8jV0dW/v33XwwePBjr16/HoEGDpHIhBHbu3AkhBBQKxXs15JdffsGjR49w8+ZN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eiIgIABHjx7F3r170bZtWwAZ196EhoZiypQpWolK8+bN0bx5cwCAp6cnjI2NUadOHSQmJmLPnj2wsLDAf//9B1tbW536XK1WQ6lUQqVS8SZVRYBarYadnR0iIyP5fhcB3L/lje9P0cLxt+jhPi5/uU5WgIw7ah85ckTrDvaZX86BjKmBr1+/nuV51apVw5AhQ+Dn54dt27Zh1qxZUnIAAPHx8fjnn3/w+PFjODs7o0ePHllOh8rPGI1Gg507d+LevXuwtbVF586dtRKM7777DhqNJst2tmvXDu3atZOWT506hcuXL8PY2BguLi7w9PSUjs7ogjtS0cJ/lkUL92954/tTtHD8LXq4j8vfeyUrlL+4IxUt/GdZtHD/lje+P0ULx9+ih/u4/OXqppBERERERET5hckKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREssRkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEtMVoiIiIiISJaYrBARERERkSwxWSEiIiIiIlliskJERERERLLEZIWIiIiIiGSJyQoREREREckSkxUiIiIiIpIlJitERERERCRLRvpuABERUVHjMiNQ302gbKSnxAMAIrt3g9qIX5H0yf7QYX03gWSCR1aIiIiIiEiWmKwQEREREZEsMVkhIiIiIiJZYrJCRERERESyxGSFiIiIiIhkickKERERERHJEpMVIiIiIiKSJSYrREREREQkS0xWiIiIiIhIlpisEBERERGRLDFZISIiIiIiWWKyQkREREREsmSk7waQPLjMCNR3E+j/S0+JBwBEdu8GtRF3UTmwP3RY300gIiIqknhkhYiIiIiIZInJChERERERyRKTFSIiIiIikiUmK0REREREJEu8epeIqABSqVRISUlBmTJltMrT0tIQFhaGcuXKwczMLNf1vnjxAgYGBihZsuRHj4mOjkZycjLs7e3fq57Q0FAIIbTKSpUqBUtLyxzriouLg52dHUxMTHJ8TV3NmzcPCQkJAIDp06fDwsJCa/3t27dx6NAhqNVq1K5dGz179vzg1yQqaiIjI/HHH38AAGxtbTF69GhpXXh4OFauXKkVP2vWrDzZv0k+eGSFiKiAEEJg+fLlcHZ2hp2dHWrWrAkrKysMGjQI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", + "image/png": "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VFSUzB3HSDrSn7CblvQF79bWh4+TI6z+O6PgqCgr/FxRdvG9Im3f59/g4GD+nSSOnynp4zUrREREREQkSSxWiIiIiIhIklisEBERERGRJLFYISIiIiIiSWKxQkREREREksRbFxMVMo/nWwP4eocTo7WA0YGDCo6IiOj38H3+JaKfxzMrREREREQkSSxWiIiIiIhIklisEBERERGRJLFYISIiIiIiSWKxQkREREREksRihYiIiIiIJInFChERERERSRKLFSIiIiIikiQWK0REREREJEksVoiIiIiISJJYrBARERERkSSxWCEiIiIiIklisUJERERERJLEYoWIiIiIiCSJxQoREREREUkSixUiIiIiIpIkFitERERERCRJLFaIiIiIiEiSWKwQEREREZEksVghIiIiIiJJYrFCRERERESSxGKFiIiIiIgkSVXRARAVJlUmPlV0CKK0pC8AgGCPDohWLTgfdeMTJxUdAhHRT+XzgpR/mXNJ6nhmhYiIiIiIJInFChERERERSRKLFSIiIiIikiQWK0REREREJEksVoiIiIiISJJYrBARERERkSSxWCEiIiIiIklisUJERERERJLEYoWIiIiIiCSJxQoREREREUmSqqIDoLy3fv16FC9eHB07dpQ7TZlLTU3F/fv3AQAqKiqws7MTl6WkpMDX11emfZUqVVCkSJFfGeJv5fXr1/j8+TMAoEKFCihRooTM8tjYWLx58wblypVDsWLFFBEiUQbz5s2Dk5MT6tWrJ3eapIE5PSNfX1+kpKQAAGxtbaGqKvszMTIyEh8/fkSFChWgrq6uiBDpN1Soi5WXL1/i+PHjCAoKQsmSJWFnZ4cmTZpASUkJALBjxw7cvHkTAKCmpgY9PT1UrVoVTZs2hZaWVqb9Pnv2DCtWrICdnR369u2bYXlSUhLGjBmTZWwuLi7o3LnzT2xd5o4fPw5jY2OxOPl+mjIXHx+PQYMGISkpCQEBAfjy5Yu4LDIyEo6Ojqhevbo4b9euXbCwsFBEqL+FzZs348SJE/D398eyZcvQq1cvcdnWrVsxatQoGBoa4sOHD1i3bh08PDwUFyxlcP/+fZw5cwYfP36EkZERHB0d4eTkJC7/+++/8erVKwCAhoYGSpQoATs7OzRq1AhqamqZ9nvlyhXs3bsXTZo0QevWrTMsf//+PebPn59lbO3atUPDhg1z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sVghIiIiIiJJYrFCRERERESSxGKFiIiIiIgkicUKERERERFJEosVIiIiIiKSJBYrREREREQkSSxWiIiIiIhIklisEBERERGRJLFYISIiIiIiSWKxQkREREREksRihYiIiIiIJInFChERERERSRKLFSIiIiIikiQWK0REREREJEksVoiIiIiISJJYrBARERERkSSxWCEiIiIiIklisUJERERERJLEYoWIiIiIiCSJxQoREREREUkSixUiIiIiIpIkFitERERERCRJLFaIiIiIiEiSWKwQEREREZEksVghIiIiIiJJYrFCRERERESSxGKFiIiIiIgkicVKIbRkyRJs3rw502kqXAIDA3H16lXExMRk2S4lJQW3b9/Go0ePflFkRL+nP/74AydPnsx0mgoXf39/XLt2DfHx8Vm2i42NxbVr1xAQEPCLIiMqHFis5MLq1avh6emJM2fOyMx//vw5PD09ERUVJbabNWuWTJuIiAj0798fs2fPhiAI+RLflStXcPfu3UynqfBYtGgRqlWrhhEjRqBSpUrw9fWV2y4iIgJ2dnbo0aMHmjZtCg8Pj18bKFEemjFjBjw9PeHn5ycz/9KlS+jVq5dMu3Xr1sm0efPmDbp3755hfl46efIknj9/nuk0FR7jxo1D7dq1MXToUFhYWOD169dy2z1+/BiVKlXCsGHD4ODgkOG3ARFljsVKLty6dQt79+7F8OHDkZqaKs4PCwvD3r17xaMrt27dwvnz58XlISEhcHV1xaNHjzB06FAoKSn98tip8Pj8+TNmzZqFmzdv4t69exg/fjymTJkit+3KlSthamqKp0+f4sWLF3j48CHOnj37iyMmyhtnz57F3r178ccff8jMf/36NQ4cOCDT7vbt2+L048eP4ezsjNjYWPTs2fOXxUuF08uXL7Fhwwb4+fnB19cXnTt3xty5c+W2nT59Ovr164f79+/D19cXS5YsQXBw8C+OmKhgYrGSS7Vq1cLHjx+xcePGbLX39/eHs7MzypYti7Nnz0JfXz/TtgEBAZg2bRp69+6NpUuXZji17O/vjylTpqBXr16YNWsW3r9/n6PYIyIisHjxYvTt2xdTp07FixcvcvR6koarV6/CxsYGVlZWAIDu3bvjzJkzcs/YnTt3Dt26dYOSkhK0tLTQoUMH/Pfff786ZKI807BhQ5w9e1bmgFBWbt68CRcX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mzm4gDIzM8vWXUGVlJQwYsSILM/sEZF8vGYljyxatOiHT6Vt2LAhjh8/ju3bt2PgwIFy72CzZcsWxMfHw8zMDC1btoSlpSVu3rwpLt+2bRt0dHRgYWGBFi1awNHREW5ubtl+wKSWlhbmzJkDa2trtG3bFlWrVkVcXJx4wT4RUUGjoaGB2bNn/zAH9+rVC9u2bcOECROwYMGCDMtVVFTg7e2N27dvw8LCAu7u7rC0tMS7d+8AAKqqqjh8+DDevn0LMzMzuLu7w97eHh07doSmpma2YtXS0kKfPn1gZ2eHdu3awdraGtbW1nxQKxFRJpSE/HqMeiF269YtJCQkZBhvevz4ccTExKBt27bQ1NTErVu3EBsbiwYNGsi08/Pzw+PHj+Hm5iZzK8N0giDAx8cHwcHBqFatmtwhYX5+fvD394eRkRFsbW1lhglcvXoVRYsWRY0aNeROA8CzZ8/w4sULlC5dGjVr1vzhxf/poqOjoauri6ioqEwvyidpiI6OhpGREYKDg/m3kjh+rnLm3Llz0NPTk8lpaWlpOHjwIJSVldGhQwexnY6ODmrWrCnz+uvXr+Pdu3do0aKF3OtckpOTcfPmTURFRcHe3j5Dnk5LS4OPjw/ev38PU1NTVKtWTeZ2yKdOnULFihVhYWEhd1oQBDx48ABv375F+fLlYWtrm+1t53ulYGD+LTj4mZI+FiuUI/xQFxz8siw4+Lmi7OJ7pWBg/i04+JmSPg4DIyIiIiIiSWKxQkREREREksRihYiIiIiIJInFChERERERSRKLFSIiIiIikiQWK0REREREJEksVoiIiIiISJJYrBARERERkSSxWCEiIiIiIklisUJERERERJLEYoWIiIiIiCSJxQoREREREUkSixUiIiIiIpIkFitERERERCRJLFaIiIiIiEiSWKwQEREREZEksVghIiIiIiJJYrFCRERERESSxGKFiIiIiIgkicUKERERERFJEosVIiIiIiKSJBYrREREREQkSSxWiIiIiIhIklisEBERERGRJLFYISIiIiIiSWKxQkREREREksRihYiIiIiIJInFChERERERSRKLFSIiIiIikiQWK0REREREJEksVoiIiIiISJJYrBARERERkSSxWCEiIiIiIklSVXQA9PuqMvGpokMo1NKSvgAAgj06IFqVH/W8ZHzipKJDIMp3zNG5x/ybv5iD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o0KFCnBxccHZs2fRt29fleKsW7curK2t0b59e2hra8Pb2xt9+/Yt8Io8EVFZsHTp0o8+FdzFxQXBwcG4e/cuunTpovAcrDzbt29Hs2bN0Lx5c1SoUAGTJk1C586d5du3bduGnj17okePHtDW1kaDBg3w7NkzdOrUSaU4W7dujTdv3sDLywtaWlro0aMHFixYgI4dOxbthImI/iPUBD5go8gyMzMhCEK+2/7p6enIzc2Fnp4e1NTUkJmZCZlMBl1dXYVyOTk5yMjIgLa2tvxhZsrk5OQUegU8Oztb6f4ZGRlQV1eHlpaW0uU8ubm5BY6PKYhUKoWhoSFSUlLYHUHkpFIprKysEBcXx/8rkePnqmjS09MhkUjytWlpaWkAAD09PXk5dXX1fA9JzcrKQlZWFnR1dQttAz+1DU5PT4eGhoZ824fLedgGl19sf8sOfqbEj31BPsGHX3x5PkxKCiqnoaEhn76wMB/rqlNQovNhElVQX+qifkkSEYnBh21tnrwk5WPltLS08iU6ynxqG/zhcQuKg20wEdHHsRsYERERERGJEpMVIiIiIiISJSYrREREREQkSkxWiIiIiIhIlJisEBERERGRKDFZISIiIiIiUWKyQkREREREosRkhYiIiIiIRInJChERERERiRKTFSIiIiIiEiUmK0REREREJEpMVoiIiIiISJSYrBARERERkSgxWSEiIiIiIlFiskJERERERKLEZIWIiIiIiESJyQoREREREYkSkxUiIiIiIhIlJitERERERCRKTFaIiIiIiEiUmKwQEREREZEoMVkhIiIiIiJRYrJCRERERESixGSFiIiIiIhEickKERERERGJEpMVIiIiIiISJSYrREREREQkSkxWiIiIiIhIlJisEBERERGRKDFZISIiIiIiUWKyQkREREREosRkhYiIiIiIREmjtAMgEjNXv/ulHcInk2W9AQDE9e4FqQY/6p/D5lhgaYdAVKaV5bb0U7D9/bLYJv+38M4KERERERGJEpMVIiIiIiISJSYrREREREQkSkxWiIiIiIhIlJisEBERERGRKDFZISIiIiIiUWKyQkREREREosRkhYiIiIiIRInJChERERERiRKTFSIiIiIiEiUmK0REREREJEoan7pjTEwMYmJiYGJiAgcHB2ho/F9VDx48wIsXLwAAmpqaMDY2hpOTE7S0tAqs79Wr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X69STKmpqejWrRtcXV3h4OCAy5cvQ09PD927dy+u0yYiKleYrBARUbkzZswY+d0KNTU1ODo6Yty4cWjQoEGh+7Vo0QKRkZE4evQoUlJSMHToUNSvX1++vXHjxoiMjERgYCBiY2PRunVrtGnTBhUrVpSXmTJlCurWrat02dPTEzdv3sTJkyfx9OlTNG/eHB07dizwYcFERP91asL797KJPkIqlcLQ0BApKSkwMDAo7XCoEFKpFFZWVoiLi+P/lcjxc0Wq4nulbGD7W3bwMyV+HLNCRERERESixGSFiIiIiIhEickKERERERGJEpMVIiIiIiISJSYrREREREQkSkxWiIiIiIhIlJisEBERERGRKDFZISIiIiIiUWKyQkREREREosRkhYiIiIiIRInJChERERERiRKTFSIiIiIiEiUmK0REREREJEpMVoiIiIiISJSYrBARERERkSgxWSEiIiIiIlFiskJERERERKLEZIWIiIiIiESJyQoREREREYkSkxUiIiIiIhIlJitERERERCRKTFaIiIiIiEiUmKwQEREREZEoMVkhIiIiIiJRYrJCRERERESixGSFiIiIiIhESaO0AyCi/Fz97n92HbKsNwCAuN69INUo3x91m2OBpR0CERERfQG8s0JERERERKLEZIWIiIiIiESJyQoREREREYkSkxUiIiIiIhIlJitERERERCRKTFaIiIiIiEiUmKwQEREREZEoMVkhIiIiIiJRYrJCRERERESixGSFiIiIiIhEiclKOfTs2TM8f/68wGUiIiIiorJAo7QD+NLS0tLw/PlzmJiYwMjISGFbbGwskpOTAQCampowNjaGmZnZR+vMzs5GeHg4DA0NYWtrm2+7TCbDvXv3Cq2jUqVKsLKyUv1EimDixImwsbHBqlWrlC5T6cvOzsaKFSsQGBgIY2NjjB07Fq1bty7tsMqF0aNH49q1awCA1atXw9PTU75NKpVi3rx5uHbtGmrWrAl/f39Urly5tEIlIiKijyi3ycrt27cxffp0nD9/HtbW1nj9+jUqVqyIadOmYfz48QCAOXPmYPfu3ahSpQpkMhni4+Ohrq6OPn36YMGCBTAxMVFa98yZM7F8+XL06tULe/bsybc9MzMTvr6+8uWkpCS8ePECNWvWlK/r27cv5s2bV8xnTWXFwoULoaamhvnz5yM8PBzdunXDnTt3UKVK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1tu2soCfKfHjyFIiIiIiIhIlJitERERERCRKTFaIiIiIiEiUmKwQEREREZEoMVkhIiIiIiJRYrJCRERERESixGSFiIiIiIhEickKERERERGJEpMVIiIiIiISJSYrREREREQkShqlHQAR5XfX3+Wz65BKpbBaB1jt2QsDA4NiiIqIiIioZPHOChERERERiRKTFSIiIiIiEiUmK0REREREJEpMVoiIiIiISJSYrBARERERkSgxWSEiIipG33zzDQ4cOFDgMolbbm4uQkNDce7cOaSlpZV2OHLZ2dk4f/48goODkZGRUdrhEJUYTl1MRETFLjc3F/v378fJkyfx8uVLWFlZwdvbG/369YOmpiYAYPr06QgLCwMAaGtro3LlynB3d8dXX30FCwuLAuvevXs3Nm7ciL59+2Lo0KH5tkdGRmLs2LGFxjdy5Ej06tXrM86wYBcuXICzs3OByyReT58+xVdffQU1NTVIJBJERkbi1KlTqF27dqnG9fDhQ/Tp0weVKlVCWloaoqOj8c8//8DJyalU4yIqCUxWiIioWCUnJ6NTp054/vw5xo0bh86dO+PFixcIDg7G+vXrceHCBQDA5cuXoa+vj8mTJyMnJwdxcXE4fPgwZs6cid9++01pIhIREYGpU6ciMzMTbm5uSo9vamqKyZMny5fXrVuHGzduYO3atfJ1TB5ImdTUVKxfv17+3powYQKWLVuGzZs3l2pc6enpOHToEOzs7AAAvXr1wr59+zBjxoxSjYuoJDBZISKiYjV69Gg8e/YMd+7cgbGxsXz9qFGjkJCQoFDWxsYGPj4+CmV++eUXjBgxAu7u7goJSVZWFnx9fbF06VJ89913BR6/YsWKCnUeP34cERERCusKExwcjO3btyMhIQHe3t6YMGECtLW15duDgoKwdetWJCUlwdnZGZMmTYK5ublKdQNAeHg41q5di6dPn8LR0RFjx45FlSpVVN6fvpxatWopLBsYGEBdvfR7zNetWxfJycnYs2cPnj9/jvDw8EI/A0TlSel/AomIqNyIi4vDnj17MHv2bIVEJY+JiclH65g8eTIsLCzw+++/K6yfMWMGatasiX79+hVbvB/69ddf0b59e1SuXBm+vr6Ij4/HhAkT5NvXrFmDfv36oWbNmhgwYAASEhJQu3ZtvHz5UqX64+Pj4e3tjaysLAwaNAg2Njbo0aOHqMZG0Du3b9/G1q1bMWXKlNIOBcC7O5Z///03Nm3aBFtb2yIlyERlGe+sEBFRsbl+/ToEQUCDBg0+uQ6JRIL69evLx7MAwJEjR3Dw4EHcunWrOMJUKjExETNmzMDKlSsxatQoAMBXX30FqVQK4N2PxWnTpuH48eNo1qwZAKBnz56IiorCihUr4O/v/9FjXL9+Hbm5ufjtt9/k60aMGKFw5+Z9mZmZyMzMlC/nxUJf1q1bt9CnTx/s3bsXDg4OpR0OAKBq1arYu3cvgHdjrhYtWoSAgIBSjoroy+OdFSIiKjZv374FAKV3VYpCV1dXXldsbCyGDx+Ov/76CwYGBp8dY0EuXryIjIwM+Pr6KqzPO+bFixeRnp6OJUuWoHPnzujUqRM6duyIiIgI3L17V6Vj1K5dGzk5ORg9ejQuXLiArKws6OnpQUND+bVDf39/GBoayv9sbW0/7yTpo65cuYKePXti+/btn5V0F6ekpCSF5SpVquD169elEwxRCeOdFSIiKjZWVlYAgKioKFSrVu2T64mLi5PPCLZjxw5kZWVh0aJF8u0xMTHYtWsX7ty5g6NHj35e0P/fmzdvoKGhAX19faXbU1JSoK6ujrFjx0IikShsU6V7GwBYW1vj6tWrWLNmjXxsz5AhQ7BixYp8dQKAn58fpk6dKl+WSqVMWL6gu3fvok2bNhg3bhyePHmCJ0+ewNzcHE2bNi3VuH777TfExMSgUaNGePbsGZYtW4Zdu3aVakxEJYXJChERFRsPDw+YmJjg77//Rtu2bT+pjtjYWISGhmLp0qUA3s185OrqqlDm7t278PDwwPDhwz875jxVqlRBTk4OIiIiUKNGDaXbZTIZrKys4O7u/snHcXFxwcqVKwEAYWFhaNiwIby9vfH111/nK6utrV1gFzEqfq9fv0bbtm3x8OFDPHz4EABQp06dUk9W5s2bh61bt+LMmTMwNjbGqVOnRHPXh+hLY7JCRETFRltbG0uWLMHIkSPRrFkzDB48GGpqagDejQnZunUrJk2aVOD+jx8/Rv/+/eHg4CBPRBwcHPKNG9DT00OVKlXQrl27Yovdy8sLrq6u+Pbbb7Fz5055V7Rjx46hd+/eaNiwIerUqYOpU6fiwIEDMDQ0BABcvXoVUqkUrVq1+ugxLl68CJlMJv/xa2dnBx0dHeTm5hbbedCna9y4MRo3blzaYSg1YMAADBgwoLTDICpxHLNCRETFatiwYfjzzz8xd+5c2NnZoVWrVnB1dVX6XJQTJ07Ax8cHbdu2haurK+rUqYPq1asjODi4wO5YX4q6ujoOHDiAp0+fws7ODk2aNEH16tXlA9zV1dVx8OBBpKenw8HBAU2aNIGjoyMmTJgAMzMzlY5hbm6OefPmoUqVKmjZsiUcHR3RqFEjfPXVV1/y1IiIyiw1QRCE0g6Cyg6pVApDQ0OkpKR80YGu9PmkUimsrKwQFxfH/yuRK6+fK5lMhnv37iE+Ph5WVlZwdHSUP70eAEJCQpCcnAwA0NLSQuXKleHs7KxSt6cLFy7AzMwM1atX/2jZBw8eIDExUeUr5oIgICwsDCkpKahTp47S/5OHDx8iNjYWDg4O+Z6RcvHiRdjY2MDe3l7pMvBuTM/Tp09hb29fpNmmyut7pSxx9bv/0TKyrDeIXtcSVxt5Q7+AyRNUYXMs8JP3JdXwMyV+7AZGRERfhLq6er6H7L3Py8vrk+tu0qSJymWL+rR6NTU11K5du9Ay1atXLzBR+jApUpYkKevaRkRE+bEbGBERERERiRKTFSIiIiIiEiUmK0REREREJEpMVoiIiIiISJSYrBARERERkSgxWSEiIiIiIlFiskJERERERKLEZIWIiIiIiESJyQoREREREYkSkxUiIiIiIhIlJitERERERCRKTFaIiIiIiEiUmKwQEREREZEoMVkhIiIiIiJRYrJCRERERESixGSlHAoLC0NERESBy0REREREZYFGaQdQFj148AAvXrxA7dq1UblyZfn6lJQU3LhxA40aNYKWlhYePHiAjIwMuLm5Kex/7do1pKeno3HjxlBTUyv2+ObOnQsbGxusWrVK6TKVH8+fP8eUKVNw584d1KtXDytWrFB4T+bJzc3FvHnzcOTIEZiZmcHf3x8eHh6lEDERUdl219/lo2WkUims1gFWe/bCwMCgBKIiKr94Z+UTLF68GC1btsTkyZMV1t+5cwctW7ZEUlKSvNyHZRYtWoTGjRsjPj7+iyQq9N8yZMgQ6Onp4c8//4SamhrGjBmjtNzKlSsRGBiIdevWoVu3bujSpQsyMjJKOFoiIiKiomGy8oksLS2xfft23Lx5U6XygiBg8uTJWLp0KY4dO4aePXsWWj4uLg4hISFISUlRuj06OhqXL19GfHx8UUMHACQmJuLKlSuIjY39pP2p9MXFxeH8+fMICAhAgwYNEBAQgMOHD+P169f5ym7evBkLFy6Et7c3JkyYACcnJxw5cqTkgyYiIiIqAiYrn6hOnTro2rUrZs6c+dGyOTk5GDRoELZv345z586hVatWBZZ9/fo1unXrhmrVqmHMmDGoXr06li9fLt+ekpKCrl27ws3NDRMnTkSNGjUwYMAAZGVlqRz7999/D3t7e4wbNw6NGzdGp06dlP7AJXGLjIyEnZ0d9PX1AQDGxsYwMzPDs2fP8pV98uQJatWqJV92dXVFZGRkicVKRERE9CmYrHwGf39/nDlzBmfOnCmwTHp6Onr06IF//vkH58+fR/369Qutc8SIEYiKisKjR49w/fp1xMTEwNDQUL79m2++gYaGBmJiYhAaGoonT54gLCwMS5cuVSnm+Ph4zJ8/H2fPnkVoaCiioqIwceJEJCYmKi2fmZkJqVSq8EfikJWVBU1NTYV1WlpayMzM/GjZgsoRERERiQmTlc/g7OyMYcOGYcaMGRAEQWmZ0NBQHDlyBAEBAahRo0ah9SUkJGDfvn1YuHAhLC0tAQCampoYNmwYACApKQm7du1Cu3btcO3aNZw/fx63b99Gw4YNERgYqFLMOTk5AIC0tDT5uvbt28PR0VFpeX9/fxgaGsr/bG1tVToOfXnW1taIjY2FTCYDAGRnZ+P58+ewtrbOV9bKykrhjsvTp0+VliMiIiISE84G9pkWLFiAatWqYceOHUp/yDdu3BjW1tYYMmQITpw4AS8vrwLrioyMhCAICt113vfo0SPIZDJs2bIFWlpaCtvs7OxUitfKygr+/v7o0qULatSogZYtW6J///5wd3dXWt7Pzw9Tp06VL0ulUiYsIlG9enWYmJhg69atGDRoEDZv3gxnZ2dYWVnlK9u5c2esWrUKDRs2RHh4OP755x+sW7euFKImIiIiUh2Tlc9kZWWFSZMmYc6cOfj999/zbdfQ0MD27dsxePBgtGvXDsePH4e3t7fSuvT09ACgwEH1FSpUAACsWLECnp6enxzzrFmzMGnSJFy8eBGHDx+Gl5cXDhw4gI4dO+Yrq62tDW1t7U8+Fn05ampqWL16Nfr164epU6dCQ0MD+/fvV1p2zpw56NChAypVqoTMzEz8+OOP8rt3RERERGLFbmDFYObMmZBKpVizZo3S7RKJBFu2bEGPHj3Qvn17XLhwQWk5Z2dnWFhYYO/evQrr87psubi4wMLCAlu2bMm3r6pjSdLS0pCbmwtdXV20adMGAQEBaNKkCU6dOqXS/iQubdq0QVxcHG7cuIGYmJgCE2ELCwvcuHEDYWFhiI+PzzelNhEREZEY8c5KMTA0NMScOXMUukt9SF1dHZs2bYKGhgY6dOiAo0ePolmzZgplJBIJAgICMHDgQKSnp6NFixYIDw9HUFAQjhw5AolEgtWrV8PX1xfZ2dno1KkTkpOTcezYMVSrVg2LFi36aKwREREYNGgQhg8fjho1auDBgwcICQnB9OnTP/t1oNKhqampctc8jlMhIiKisoTJyidwdnaGmZmZwrpx48bh3LlzkEql8vEkzs7OMDIykpdRV1fHhg0bUKlSJSxZsgSOjo75fjz26dMHdnZ22LBhA1avXo26desq3Enp0aMHQkJCsGHDBvz222+wsrJCnz590KtXL3mZWrVqwdTUVOmym5sbdu7ciXXr1uHEiRMwNzfHoUOH0Lp162J7fYiIiIiIioOaUNA0VkRKSKVSGBoaIiUlBQYGBqUdDhVCKpXCysoKcXFx/L8SOX6uSFV8r5QNbH/LDn6mxI9jVoiIiIiISJSYrBARERERkSgxWSEiIiIiIlFiskJERERERKLEZIWIiIiIiESJyQoREREREYkSkxUiIiIiIhIlJitERERERCRKTFaIiIiIiEiUmKwQEREREZEoMVkhIiIiIiJRYrJCRERERESixGSFiIiIiIhEickKERERERGJEpMVIiIiIiISJSYrREREREQkSkxWiIiIiIhIlJisEBERERGRKDFZISIiIiIiUWKyQkREREREoqRR2gEQERFR6XD1u1/aIZRLsqw3AIC43r0g1eBPrc9hcyywtEOgUsY7K0REREREJEpMVoiIiIiISJSYrBARERERkSgxWSEiIiIiIlFiskJERERERKLEZIWIiIiIiESJyQoREREREYkSkxUiIiIiIhIlJitERERERCRKTFaIiIiIiEiUmKwQEREREZEoMVkhIiIiIiJR0ijtAIg+5Op3v7RDKBdkWW8AAHG9e0GqwY/657A5FljaIRAREf0n8c4KERERERGJEpMVIiIiIiISJSYrREREREQkSkxWiIiIiIhIlDjqloioDMrMzERSUhJMTU2h8cEECi9evEBGRgYAQFtbG8bGxtDR0Sm0vrdv3yI+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", "text/plain": [ "
" ] @@ -190,19 +202,15 @@ ], "source": [ "contrast = \"case_vs_control\"\n", - "results = {}\n", - "for study in collection.studies:\n", - " cube = collection.aligned_cube(study)\n", - " design = Design(\n", - " cube.samples,\n", - " \"~ condition\",\n", - " levels={\"condition\": [\"control\", \"case\"]},\n", - " contrasts={contrast: \"conditioncase\"},\n", - " )\n", - " results[study] = limma_voom(cube, design)\n", + "studies = analyse_each_study(\n", + " collection,\n", + " \"~ condition\",\n", + " levels={\"condition\": [\"control\", \"case\"]},\n", + " contrasts={contrast: \"conditioncase\"},\n", + ")\n", "\n", "figure, axes = plt.subplots(3, 2, figsize=(8, 8.5), layout=\"constrained\")\n", - "for ax, (study, result) in zip(axes.flat, results.items()):\n", + "for ax, (study, result) in zip(axes.flat, studies.items()):\n", " plot_significant_genes(result, ax=ax)\n", " ax.set_title(f\"{study}, {cubes[study].n_samples} donors\", fontsize=10)\n", " if ax is not axes.flat[1]:\n", @@ -221,7 +229,7 @@ "the cohorts of 12 to 32 donors 24 to 52. None finds a gene of the NK cells, which the\n", "disease does not change, and cohort_6 has no NK cells to test.\n", "\n", - "{api}`align_study_results` lines up the results of one cell type and contrast gene by\n", + "{api}`StudyResults.table` lines up the results of one cell type and contrast gene by\n", "gene, with a row for every cohort and every gene of the collection, and says why a\n", "cohort has no result for a gene: it did not measure the gene, or measured it and left\n", "it out, as its expression filter does with genes too rarely detected. For the\n", @@ -249,12 +257,12 @@ } ], "source": [ - "monocytes = \"CL:0001054\"\n", - "aligned = align_study_results(collection, results, monocytes, contrast)\n", + "monocytes = \"CD14 monocytes\"\n", + "aligned = studies.table(monocytes, contrast)\n", "statuses = (\"tested\", \"not_tested\", \"not_measured\")\n", "print(f\"{'':<10}{'tested':>8}{'not tested':>12}{'not measured':>14}\")\n", "for study in collection.studies:\n", - " status = aligned[\"status\"][aligned[\"study\"] == study]\n", + " status = aligned.filter(study=study)[\"status\"]\n", " counts = [np.count_nonzero(status == kind) for kind in statuses]\n", " print(f\"{study:<10}{counts[0]:>8,}{counts[1]:>12,}{counts[2]:>14,}\")" ] @@ -266,10 +274,13 @@ "source": [ "cohort_3 did not measure 200 genes and cohort_5 150; their expression filters left out\n", "47 and 296 more, cohort_5's the most, with the fewest donors and counts.\n", + "{api}`StudyResults.matrix` gives one numeric column of these tables as an array of\n", + "cohorts x genes, NaN where a cohort has no result, such as the p-values of every\n", + "cohort, which [Replication across studies](replication_across_studies.ipynb) combines.\n", "\n", "## Meta-analysis\n", "\n", - "{api}`differential_expression_meta_analysis` combines, for every cell type and gene,\n", + "{api}`meta_analysis` combines, for every cell type and gene,\n", "the log2 fold changes of the cohorts that tested it, each with its standard error,\n", "into one estimate. By default it fits the random-effects model, which lets the\n", "cohorts' true effects vary around their mean by the between-study variance τ²\n", @@ -302,7 +313,7 @@ } ], "source": [ - "meta = differential_expression_meta_analysis(results)\n", + "meta = meta_analysis(studies)\n", "table = meta.table(monocytes, contrast)\n", "plot_volcano(table)\n", "plt.show()" @@ -330,7 +341,7 @@ "id": "487660ad", "metadata": { "figure_descriptions": [ - "Two forest plots of monocyte genes, a row per cohort with its log2 fold change as a square and its 95% confidence interval, and the pooled random-effects estimate as an orange diamond at the bottom. Above, gene_0481, changed in every cohort: five cohorts between -1.19 and -0.84, every interval below zero, and cohort_3 marked not tested; the pooled estimate is -0.96 [-1.15, -0.78], with I-squared 0%. Below, gene_1312, changed in cohort_1 only: cohort_1 at 1.47 [1.20, 1.73] and the other five cohorts between -0.43 and 0.18, their intervals covering zero; the pooled estimate is 0.16 [-0.43, 0.74], with I-squared 91%, a wide diamond across zero." + "Two forest plots of monocyte genes, a row per cohort with its log2 fold change as a square and its 95% confidence interval, and the pooled random-effects estimate as an orange diamond at the bottom. Above, gene_0481, changed in every cohort: five cohorts between -1.19 and -0.84, every interval below zero, and cohort_3 marked not measured; the pooled estimate is -0.96 [-1.15, -0.78], with I-squared 0%. Below, gene_1312, changed in cohort_1 only: cohort_1 at 1.47 [1.20, 1.73] and the other five cohorts between -0.43 and 0.18, their intervals covering zero; the pooled estimate is 0.16 [-0.43, 0.74], with I-squared 91%, a wide diamond across zero." ] }, "outputs": [ @@ -345,7 +356,7 @@ }, { "data": { - "image/png": "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DQMGDMD48eNx6tQpbN++HcuWLcOsWbMKjXHp0qVKE44sWrQIDg4OCAoKkhL8ko65NNiSX061atUKS5YsQUpKilK/r/xvpI0bN8aKFSuQlZUlPczh2LFjMDU1Ra1atVTeT1nqye8CpOrglezsbGnO2nynT5+GsbExqlSponLMRG8qc+OivygXt46I3jzOzi74cMan2g5DyZIlS3DkyBHptZ+fH+rVqyc9XMnd3V3poU7Fla9atSpOnjyJFStWYMKECTAxMUHLli010j1I3TgXLlyIOXPm4NGjR2jSpAkuXrwozdXfsWNHpKSkYM2aNQgLC0P16tVx8OBB6Q6EEAL37t3Dl19+qVaMv/32G+bNm4cPP/wQ5ubm+O677zBq1Chp/csxvsja2ho1a9ZU65hLRVC5JJfLhYeHh2jbtq04cuSIOHPmjJg4caLYt2+fEEKI5ORk4erqKvz9/cW5c+fE9u3bhYODg/jyyy+lOs6cOSMAiPT0dGlZTEyMACCuX79epnqEECIpKUmYmJiIFStWiLt374qwsLBij+mXX34RY8eOFQcPHhSXL18WixYtEiYmJmLu3LlFngMAQi6Xq3HmiMq34JhMced5mtJPcEymtsMiojfAm3xdvHfvngAg7t27J6Kjo7UdjuThw4di6NCh2g6jRPfu3ROTJ08W/v7+Km8jE+Klx49SuREbG4tFixbh1KlTsLS0xNChQzFhwgRpfXBwML766itcu3YNlpaWGDx4MCZPniz147927Rreeecd3Lp1S2qlT0xMhLe3N/bu3YvatWuXup58u3btwo8//ojIyEh079692Gk0hRD4/fffsXXrVkRERMDNzQ2jR49Gz549Cy2flJQEa2tryOXyQgfPEBERlVfx8XE4cugAfLv3gp1d4S266nqTr4tPnz5F9+7dAQATJkzA9OnTtRzRmyM3Nxd169YFAPj6+uLbb79VaTsm+VRuvcn/zIiISLclJibg7JmTaNO2A2xsCs64Uhq8LpI62CefNObFuWRfZm1trfTQCCIioreZjY0tevXup+0wSIcxySeNWblyJf76669C1/Xp06dU02QRERG9iRQKBTIzM2FsbCw974bodWJ3HSq3eFuSiIjeVJGREdj02y8YOXo8XFw0cyeb10VSB79aEhEREWmYjY0N/PoNhI2NjbZDIR3F7jpEREREGmZiYgqPWp7a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", + "image/png": "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P/sZLl++jC+//BIAlK0qv/76a4SHh+MnP/kJLCws9O44d+aOviHxPOjll1/GunXrEBISgkWLFiE7O7vDO97h4eHKtp2//OUvYWNjgx07diAyMhKTJk0yKNZZs2bhhRdewB/+8Ae4ubkhKioKJSUlyjagnelzR+MbHh6OhIQEBAUFYdmyZRg6dCiysrKwf/9+nDlzBlZWVp06JxFRb8Q7+UREXTRw4ECEhoYqCbNGo8GxY8ewaNEipKWl4datWzhy5AhefvllpY6VlRVSU1Mxffp0pKWlYejQoYiKigIA2NnZAWhaXz5nzhxYWVkhMTFR76c9c+fOhaurq/LakHge7IOlpSWOHz+O559/HmlpaXB1dcXx48cRGhoKGxubVs9rYWGBffv24S9/+Qu+++475OTkYPXq1UqC7+TkhNDQUGg0GqWOVqtFaGgoBg4cCACYMWMG9u7di9LSUpw+fRqvv/46/v73v+O5555T6hjSjiHjq9FokJiYiM8++wzFxcX45z//CR8fH5w5c0YpQ0TU15mJiJg6CCKih0l5ebnew6N//vOfER0djStXrpgwKvXg+BIRcbkOEVGPW7x4MUaMGAFfX19kZGRg8+bN2Lx5s6nDUg2OLxER7+QTEfW42tpabNq0CVlZWXB2dsacOXPw5JNPmjos1eD4EhExySciIiIiUh0+eEtEREREpDJM8omIiIiIVIZJPhERERGRyjDJJyIiIiJSGSb5REREREQqwySfiIiIiEhlmOQTEREREakMk3wiIiIiIpVhkk9EREREpDL/D293Upe+UtpkAAAAAElFTkSuQmCC", "text/plain": [ "
" ] @@ -357,7 +368,7 @@ "source": [ "truth = np.load(\"simulated_cohorts_truth.npz\")\n", "genes = truth[\"feature_id\"]\n", - "cell_type = list(truth[\"cell_ontology_ids\"]).index(monocytes)\n", + "cell_type = list(truth[\"cell_ontology_ids\"]).index(\"CL:0001054\")\n", "changed = truth[\"true_log2_fold_change\"][:, cell_type] != 0 # cohorts x genes\n", "everywhere = genes[changed.all(axis=0)]\n", "one_cohort = genes[changed.sum(axis=0) == 1]\n", @@ -365,22 +376,21 @@ "truth_sets = (everywhere, one_cohort, unchanged)\n", "\n", "strongest = table[\"feature_id\"][np.argmin(table[\"p_value\"])]\n", - "first = results[\"cohort_1\"].table(monocytes, contrast)\n", + "first = studies[\"cohort_1\"].table(monocytes, contrast)\n", "cohort_1_only = genes[changed[0] & (changed.sum(axis=0) == 1)]\n", "own = np.isin(first[\"feature_id\"], cohort_1_only)\n", "own_gene = first[\"feature_id\"][own][np.argmin(first[\"p_value\"][own])]\n", - "fixed = differential_expression_meta_analysis(results, method=\"fixed\")\n", + "fixed = meta_analysis(studies, method=\"fixed\")\n", "print(f\"{own_gene}, pooled log2 fold change [95% interval]:\")\n", "for name, analysis in ((\"fixed effect\", fixed), (\"random effects\", meta)):\n", - " found = analysis.table(monocytes, contrast)\n", - " i = np.flatnonzero(found[\"feature_id\"] == own_gene)[0]\n", - " change, p = found[\"log2_fold_change\"][i], found[\"adjusted_p_value\"][i]\n", - " low, high = found[\"ci_lower\"][i], found[\"ci_upper\"][i]\n", + " found = analysis.table(monocytes, contrast).filter(feature_id=own_gene)\n", + " change, p = found[\"log2_fold_change\"][0], found[\"adjusted_p_value\"][0]\n", + " low, high = found[\"ci_lower\"][0], found[\"ci_upper\"][0]\n", " print(f\" {name}: {change:.2f} [{low:.2f}, {high:.2f}], adjusted p {p:.2g}\")\n", "\n", "figure, axes = plt.subplots(2, 1, figsize=(7.5, 8), layout=\"constrained\")\n", "for ax, gene in zip(axes, (strongest, own_gene)):\n", - " plot_forest(meta, results, gene, group=monocytes, contrast=contrast, ax=ax)\n", + " plot_forest(meta, gene, group=monocytes, contrast=contrast, ax=ax)\n", "axes[0].set_title(f\"{strongest}: changed in every cohort\", fontsize=10)\n", "axes[1].set_title(f\"{own_gene}: changed in cohort_1 only\", fontsize=10)\n", "plt.show()" @@ -393,7 +403,7 @@ "source": [ "In the upper plot the cohorts agree: every estimate lies below zero, I² is 0%, and\n", "the pooled log2 fold change, -0.96, has a narrow interval; cohort_3 did not measure\n", - "the gene and keeps its row, marked not tested. In the lower one, cohort_1 changes the\n", + "the gene and keeps its row, marked not measured. In the lower one, cohort_1 changes the\n", "gene by 1.47 and the other cohorts' estimates lie around zero. Cochran's Q\n", "[@cochran1954] finds the cohorts far more different than their standard errors allow,\n", "and I² [@higgins2002] puts 91% of the variation down to heterogeneity: the\n", @@ -549,13 +559,14 @@ " `test=\"knapp_hartung\"` gives the t-tests that metafor's documentation recommends for\n", " few studies ([Meta-analysis](../statistics/meta_analysis.md#tests-and-heterogeneity)).\n", "- **Genes that some cohorts did not test.** `min_studies`, two by default, sets how\n", - " many cohorts must test a gene; {api}`align_study_results` says whether a cohort did\n", + " many cohorts must test a gene; {api}`StudyResults.table` says whether a cohort did\n", " not measure a gene or left it out.\n", "- **Cell types and ids.** Cell types are aligned by `cell_ontology_id` when every\n", " cohort has it, by `group_id` otherwise, or by another column (`align_groups_by`);\n", - " `group_mapping` names the aligned id of any group, or None to leave it out. Columns\n", - " of ids other than `sample_id`, such as donors with several samples, are kept apart\n", - " by cohort with `within_study`." + " `group_mapping` names the aligned id of any group, or None to leave it out, and\n", + " `group_names` gives aligned cell types the names that tables and figures show.\n", + " Columns of ids other than `sample_id`, such as donors with several samples, are kept\n", + " apart by cohort with `within_study`." ] } ], @@ -563,8 +574,8 @@ "datasets": [ "simulated_cohorts" ], - "executed_at": "2026-10-27T09:54:56+00:00", - "executed_code_sha256": "deba02883cab4b6b05bc7a5808e0573f453613d3e37d64804183954b8783d956", + "executed_at": "2026-10-27T15:22:31+00:00", + "executed_code_sha256": "7ec0f3c884e25df0241e73a4ad5947bd42ebd122147b223ce5086e262df69038", "executed_datasets": { "simulated_cohorts": { "simulated_cohort_1.cube.zarr": "sha256:e97a0a97353376a5ac3a5c1e34f7e5f6fad6b0ac07c09b738b808fba6ccedb9e", @@ -576,8 +587,8 @@ "simulated_cohorts_truth.npz": "sha256:6599ea11dddb183f630183846f7ece9991003a4cc77875e4e7822317f66346ec" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 12.5, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 4.4, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/composition.ipynb b/docs/source/guides/composition.ipynb index 492dc56..91e9db9 100644 --- a/docs/source/guides/composition.ipynb +++ b/docs/source/guides/composition.ipynb @@ -32,7 +32,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n", + "CytePool 0.1.dev63+g8afa32afb\n", "Cube with 24 samples x 6 groups x 2,000 features\n", " layers: counts (uint32, lazy), detected (uint32, lazy), log_sum (float64,\n", " lazy), log_sum_sq (float64, lazy), linear_sum (float64, lazy)\n", @@ -57,9 +57,7 @@ "import matplotlib.pyplot as plt\n", "\n", "import cytepool\n", - "from cytepool.composition import propeller\n", - "from cytepool.design import Design\n", - "from cytepool.io import read_cube\n", + "from cytepool import Design, propeller, read_cube\n", "from cytepool.plotting import plot_composition, plot_proportions\n", "\n", "cube = read_cube(\"simulated_pbmc.cube.zarr\")\n", @@ -257,7 +255,7 @@ " proportion ratios of the result, not only its p-values.\n", "- **The transform.** The logit, speckle's default, or `transform=\"asin\"`; they agree\n", " on clear changes.\n", - "- **Several conditions.** `f_test=\"condition\"` tests every level of the condition at\n", + "- **Several conditions.** `f_tests=\"condition\"` tests every level of the condition at\n", " once, and {api}`plot_proportions` then shows the F-test's adjusted p-values.\n", "- **Not yet available.** Precision-weighted mixed models and count models of\n", " composition ([Composition tests](../statistics/composition.md#limits))." @@ -268,16 +266,16 @@ "datasets": [ "simulated_pbmc" ], - "executed_at": "2026-10-27T08:56:04+00:00", - "executed_code_sha256": "d4125bfed85af28d3d315f60e96cece11a74191a8dd57278c89324d736db6c36", + "executed_at": "2026-10-27T15:22:34+00:00", + "executed_code_sha256": "c54aa139f6b76b7f267fca319c49247fe16f21cef0bf07a39883db3a51b7c0f3", "executed_datasets": { "simulated_pbmc": { "simulated_pbmc.cube.zarr": "sha256:554a2b5b4188308ea900cf907fa87f42d12e0458e6d5dc11525e862f3ad8a726", "simulated_pbmc_cells.npz": "sha256:0d4c9154b87dbf8e9796232c2afec4a8340368fd0f5d386df4dd780c4e478129" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 1.8, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 2.0, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/export_cubes.ipynb b/docs/source/guides/export_cubes.ipynb index d68fedf..43ede5a 100644 --- a/docs/source/guides/export_cubes.ipynb +++ b/docs/source/guides/export_cubes.ipynb @@ -34,7 +34,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953, numpy 2.5.4\n", + "CytePool 0.1.dev63+g8afa32afb, numpy 2.5.4\n", "19,151 cells x 2,000 genes in 24 samples\n", "cell types: B cells, CD4 T cells, CD8 T cells, NK cells, Monocytes, Dendritic cells\n" ] @@ -47,8 +47,7 @@ "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.cube import Cube, from_cells\n", - "from cytepool.io import read_cube, write_cube\n", + "from cytepool import Cube, from_cells, read_cube, write_cube\n", "\n", "cells = np.load(\"simulated_pbmc_cells.npz\")\n", "n_cells, n_genes = map(int, cells[\"shape\"])\n", @@ -268,10 +267,10 @@ ")\n", "pooled = from_cells(\n", " csr,\n", - " samples=cell_sample,\n", - " groups=cell_type,\n", - " sample_table=sample_table,\n", - " group_table={\"group_id\": groups},\n", + " sample_labels=cell_sample,\n", + " group_labels=cell_type,\n", + " samples=sample_table,\n", + " groups={\"group_id\": groups},\n", " features=cells[\"feature_id\"],\n", " layers=\"counts\",\n", ")\n", @@ -314,16 +313,16 @@ "datasets": [ "simulated_pbmc" ], - "executed_at": "2026-10-27T08:56:07+00:00", - "executed_code_sha256": "c2858033ad96e552e104f8ef84dab502009618f5110bec0d214b3f5cfbdb1c68", + "executed_at": "2026-10-27T15:22:37+00:00", + "executed_code_sha256": "85eb2855e72c9727d4b113fe91ac2ee06939e02febe917bbe62cfd7f7d2906b2", "executed_datasets": { "simulated_pbmc": { "simulated_pbmc.cube.zarr": "sha256:554a2b5b4188308ea900cf907fa87f42d12e0458e6d5dc11525e862f3ad8a726", "simulated_pbmc_cells.npz": "sha256:0d4c9154b87dbf8e9796232c2afec4a8340368fd0f5d386df4dd780c4e478129" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 2.4, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 2.9, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/gene_sets.ipynb b/docs/source/guides/gene_sets.ipynb index 518e4e8..87874df 100644 --- a/docs/source/guides/gene_sets.ipynb +++ b/docs/source/guides/gene_sets.ipynb @@ -18,8 +18,9 @@ "`hallmark_interferon_alpha` datasets of the [Datasets](../reference/datasets.md)\n", "page).\n", "\n", - "camera, fry and roast work on the linear model of one cell type, so they take its\n", - "voom result: the steps that {api}`limma_voom` takes inside, here by hand." + "camera, fry and roast work on the linear model of one cell type, so they take a\n", + "result of {api}`limma_voom` and read that cell type's values, weights, design and\n", + "contrast from it ({api}`DifferentialExpressionResult.fit`)." ] }, { @@ -32,7 +33,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n", + "CytePool 0.1.dev63+g8afa32afb\n", "92 of the set's 97 genes are among the 5,125 genes tested\n" ] } @@ -44,14 +45,17 @@ "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.gene_sets import GeneSets\n", - "from cytepool.gsea import gsea\n", - "from cytepool.io import read_cube\n", - "from cytepool.networks import collectri\n", - "from cytepool.normalization import filter_by_expression, tmm_factors\n", - "from cytepool.over_representation import over_representation\n", + "from cytepool import (\n", + " Design,\n", + " camera,\n", + " collectri,\n", + " fry,\n", + " gsea,\n", + " limma_voom,\n", + " over_representation,\n", + " read_cube,\n", + " roast,\n", + ")\n", "from cytepool.plotting import (\n", " plot_barcode,\n", " plot_enrichment,\n", @@ -59,9 +63,6 @@ " plot_gsea_table,\n", " plot_over_representation,\n", ")\n", - "from cytepool.stats.gene_set_tests import camera, fry\n", - "from cytepool.stats.rotation_gene_set_tests import roast\n", - "from cytepool.stats.voom import voom\n", "\n", "kang = read_cube(\"kang2018.cube.zarr\")\n", "monocytes = \"CD14+ Monocytes\"\n", @@ -72,10 +73,7 @@ " contrasts={\"stim_vs_ctrl\": \"conditionstim\"},\n", ")\n", "result = limma_voom(kang, design, groups=monocytes)\n", - "counts = kang.layer(\"counts\", monocytes)\n", - "keep = filter_by_expression(counts, design=design)\n", - "weighted = voom(counts[:, keep], design, norm_factors=tmm_factors(counts[:, keep]))\n", - "symbols = kang.features[\"symbol\"][keep]\n", + "tested = result.table()[\"symbol\"] # the one cell type and contrast\n", "\n", "with open(\"hallmark_interferon_alpha_response.json\") as file:\n", " hallmark = json.load(file)\n", @@ -83,7 +81,7 @@ "interferon = [hallmark[\"earlier_symbols\"].get(gene, gene) for gene in hallmark[\"genes\"]]\n", "print(f\"CytePool {cytepool.__version__}\")\n", "print(\n", - " f\"{np.isin(interferon, symbols).sum()} of the set's {len(interferon)} genes are among the {keep.sum():,} genes tested\"\n", + " f\"{np.isin(interferon, tested).sum()} of the set's {len(interferon)} genes are among the {len(tested):,} genes tested\"\n", ")" ] }, @@ -134,18 +132,14 @@ "plt.show()\n", "\n", "sets = {\"interferon-alpha response\": interferon}\n", - "rotations = roast(weighted, design, \"stim_vs_ctrl\", interferon, gene_ids=symbols, rng=1)\n", + "rotations = roast(result, interferon, id_column=\"symbol\", rng=1)\n", "tests = {\n", - " \"fry, self-contained\": fry(\n", - " weighted, design, \"stim_vs_ctrl\", sets, gene_ids=symbols\n", - " ),\n", - " \"roast, self-contained\": rotations.filter(rotations[\"alternative\"] == \"up\"),\n", - " \"camera, competitive\": camera(\n", - " weighted, design, \"stim_vs_ctrl\", sets, gene_ids=symbols\n", - " ),\n", + " \"fry, self-contained\": fry(result, sets, id_column=\"symbol\")[\"p_value\"],\n", + " \"roast, self-contained\": rotations[\"up_p_value\"],\n", + " \"camera, competitive\": camera(result, sets, id_column=\"symbol\")[\"p_value\"],\n", "}\n", - "for name, test in tests.items():\n", - " print(f\"{name:<23} p-value {test['p_value'][0]:.2g}\")" + "for name, p_value in tests.items():\n", + " print(f\"{name:<23} p-value {p_value[0]:.2g}\")" ] }, { @@ -167,8 +161,10 @@ "Here each transcription factor's targets in CollecTRI [@muellerdott2023] form a set.\n", "{api}`collectri` downloads it on first use and returns, by default, the interactions\n", "whose sources allow commercial use ([Datasets](../reference/datasets.md#networks)\n", - "gives the terms). {api}`GeneSets.restrict_to` keeps each set's tested genes and\n", - "{api}`GeneSets.filter_by_size` the sets of 15 to 500 of them. camera then ranks them." + "gives the terms). {api}`Network.to_gene_sets` makes each regulator's targets a set,\n", + "without the signs of their regulation; {api}`GeneSets.restrict_to` keeps each set's\n", + "tested genes and {api}`GeneSets.filter_by_size` the sets of 15 to 500 of them. camera\n", + "then ranks them." ] }, { @@ -186,11 +182,13 @@ "output_type": "stream", "text": [ "GeneSets: 199 sets of 15 to 435 genes, 1,893 genes in all\n", - " source: CollecTRI; version: 2.0 (Zenodo record 8192729); licence: CC BY 4.0\n", - " (Zenodo record 8192729); level 'commercial': the interactions of\n", - " resources that OmniPath lists for commercial use, without those that\n", - " GEREDB (listed for academic use) alone supports; each resource keeps\n", - " its own terms\n", + " name: CollecTRI; version: 2.0 (Zenodo record 8192729); licence: CC BY 4.0\n", + " (Zenodo record 8192729); level 'commercial': the interactions of\n", + " resources that OmniPath lists for commercial use, without those that\n", + " GEREDB (listed for academic use) alone supports; each resource keeps its\n", + " own terms\n", + " read from CollecTRI_regulons.csv, SHA-256\n", + " 4473c9189dd53dacc80297709ad1452dda1086a1cc2185f9a56146c261668701\n", " sets: MYC (383), SPI1 (133), SMAD3 (41) and 196 more\n" ] }, @@ -206,16 +204,9 @@ } ], "source": [ - "network = collectri()\n", - "regulons = GeneSets(\n", - " {regulator: list(targets) for regulator, targets in network.items()},\n", - " source=network.name,\n", - " version=network.version,\n", - " licence=network.licence,\n", - ")\n", - "regulons = regulons.restrict_to(symbols).filter_by_size(15, 500)\n", + "regulons = collectri().to_gene_sets().restrict_to(tested).filter_by_size(15, 500)\n", "print(regulons)\n", - "ranked = camera(weighted, design, \"stim_vs_ctrl\", regulons, gene_ids=symbols)\n", + "ranked = camera(result, regulons, id_column=\"symbol\")\n", "plot_gene_sets(ranked)\n", "plt.show()" ] @@ -259,9 +250,7 @@ } ], "source": [ - "enriched = over_representation(\n", - " result, regulons, group=monocytes, contrast=\"stim_vs_ctrl\", id_column=\"symbol\"\n", - ")\n", + "enriched = over_representation(result, regulons, id_column=\"symbol\")\n", "plot_over_representation(enriched)\n", "plt.show()" ] @@ -297,10 +286,10 @@ "name": "stderr", "output_type": "stream", "text": [ - "/tmp/ipykernel_933368/842382742.py:1: RuntimeWarning: 1 gene set(s) have fewer than 10 random scores of the sign of their enrichment\n", + "/tmp/ipykernel_1042034/1970039944.py:1: RuntimeWarning: 1 gene set(s) have fewer than 10 random scores of the sign of their enrichment\n", "score among 1000 permutations, but some; their normalized scores and p-values\n", "are NaN. More permutations (n_permutations=10000) may give them enough\n", - " enrichment = gsea(\n" + " enrichment = gsea(result, regulons, id_column=\"symbol\", rng=1)\n" ] }, { @@ -325,30 +314,10 @@ } ], "source": [ - "enrichment = gsea(\n", - " result,\n", - " regulons,\n", - " group=monocytes,\n", - " contrast=\"stim_vs_ctrl\",\n", - " id_column=\"symbol\",\n", - " rng=1,\n", - ")\n", - "plot_enrichment(\n", - " result,\n", - " regulons[\"STAT1\"],\n", - " group=monocytes,\n", - " contrast=\"stim_vs_ctrl\",\n", - " id_column=\"symbol\",\n", - ")\n", + "enrichment = gsea(result, regulons, id_column=\"symbol\", rng=1)\n", + "plot_enrichment(result, regulons[\"STAT1\"], id_column=\"symbol\")\n", "plt.show()\n", - "plot_gsea_table(\n", - " result,\n", - " regulons,\n", - " enrichment,\n", - " group=monocytes,\n", - " contrast=\"stim_vs_ctrl\",\n", - " id_column=\"symbol\",\n", - ")\n", + "plot_gsea_table(result, regulons, enrichment, id_column=\"symbol\")\n", "plt.show()" ] }, @@ -366,8 +335,9 @@ "\n", "- **One set or many.** For a set chosen in advance, a self-contained test, {api}`fry`\n", " or {api}`roast`, answers whether it responds at all; to rank a collection, use a\n", - " competitive test, {api}`camera` or {api}`romer`. {api}`mroast` runs roast on many\n", - " sets with the same rotations.\n", + " competitive test, {api}`camera` or {api}`romer`, which takes a result of\n", + " {api}`limma_trend`, as it has no observation weights. {api}`mroast` runs roast on\n", + " many sets with the same rotations.\n", "- **camera, over-representation or GSEA.** camera uses every gene's statistic and\n", " allows for the correlation between the genes of a set; over-representation needs\n", " only a list of genes, and depends on the cutoff that made it; GSEA needs only a\n", @@ -378,8 +348,8 @@ " number of rotations (`n_rotations`, 1,999 by default), and GSEA's random sets are\n", " drawn from `rng`: pass a seed to get the same p-values every time.\n", "- **The collection.** Restrict the sets to the genes tested and to a range of sizes\n", - " before testing, and name genes as the data do: symbols here, Ensembl ids in\n", - " `feature_id`.\n", + " before testing, and name genes as the data do, with `id_column`: symbols here, or\n", + " the Ensembl ids of `feature_id`, the default.\n", "- **Scores per sample.** To compare samples rather than test one contrast, score each\n", " set or regulator in every sample: [Pathway and regulator\n", " activity](pathway_activity.ipynb)." @@ -391,8 +361,8 @@ "kang2018", "hallmark_interferon_alpha" ], - "executed_at": "2026-10-27T08:56:13+00:00", - "executed_code_sha256": "65d587e5aec480f66e3aa702a3669b97888f1ef3304ffcd2fb053c0476c85a24", + "executed_at": "2026-10-27T15:23:08+00:00", + "executed_code_sha256": "645668ea574e82f35b283420bd774178657944718fadfc5c23633fb60f2d8054", "executed_datasets": { "hallmark_interferon_alpha": { "hallmark_interferon_alpha_response.json": "sha256:c5ef439a3f9b6e7faef95eb6b9f7c82ed1a48ede2af78629c85be3b873a8f1c8" @@ -401,8 +371,8 @@ "kang2018.cube.zarr": "sha256:a30af3dabe17f21a6c75f41b64a279a1423640b9aa919022cf67897f9cc77a51" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 5.0, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 30.3, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/genes_per_cell_type.ipynb b/docs/source/guides/genes_per_cell_type.ipynb index 189066d..c12100f 100644 --- a/docs/source/guides/genes_per_cell_type.ipynb +++ b/docs/source/guides/genes_per_cell_type.ipynb @@ -32,7 +32,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n", + "CytePool 0.1.dev63+g8afa32afb\n", "Cube with 16 samples x 8 groups x 35,635 features\n", " layers: counts (uint32, lazy), detected (uint32, lazy)\n", " filled: 128 of 128 sample x group entries have cells (100.0%)\n", @@ -56,9 +56,7 @@ "import matplotlib.pyplot as plt\n", "\n", "import cytepool\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_trend, limma_voom\n", - "from cytepool.io import read_cube\n", + "from cytepool import Design, limma_trend, limma_voom, read_cube\n", "from cytepool.plotting import (\n", " plot_cell_numbers,\n", " plot_md,\n", @@ -237,8 +235,9 @@ "id": "b8b75ad5", "metadata": {}, "source": [ - "The table converts to a pandas DataFrame ({api}`Table.to_pandas`) for sorting and\n", - "export; here are the ten genes with the smallest p-values." + "{api}`Table.sort` orders the table by a column, here from the smallest p-value, and\n", + "{api}`Table.to_pandas` converts it to a pandas DataFrame for display and export; here\n", + "are the ten genes with the smallest p-values." ] }, { @@ -377,7 +376,7 @@ } ], "source": [ - "top = table.to_pandas().sort_values(\"p_value\").head(10).set_index(\"symbol\")\n", + "top = table.sort(\"p_value\").to_pandas().head(10).set_index(\"symbol\")\n", "top[[\"log2_fold_change\", \"average_log2_cpm\", \"t\", \"adjusted_p_value\"]]" ] }, @@ -431,9 +430,8 @@ } ], "source": [ - "used = result.samples.filter(result.samples[\"used\"])\n", "for group in result.groups:\n", - " sizes = used.filter(used[\"group_id\"] == group)[\"library_size\"]\n", + " sizes = result.samples.filter(group_id=group, used=True)[\"library_size\"]\n", " print(\n", " f\"{group:<18} largest library {sizes.max() / sizes.min():4.1f} times the smallest\"\n", " )\n", @@ -540,15 +538,15 @@ "datasets": [ "kang2018" ], - "executed_at": "2026-10-27T09:55:08+00:00", - "executed_code_sha256": "bb23383e315edef7514f1417c29a2b2f3b81a0df2423690be65e81a452f6b544", + "executed_at": "2026-10-27T15:23:13+00:00", + "executed_code_sha256": "c06caf3991b426d7951d3ef1577da56188a5c05f25406d8f79f63fea4f567c13", "executed_datasets": { "kang2018": { "kang2018.cube.zarr": "sha256:a30af3dabe17f21a6c75f41b64a279a1423640b9aa919022cf67897f9cc77a51" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 12.1, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 5.0, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/normalization_and_quality.ipynb b/docs/source/guides/normalization_and_quality.ipynb index c73fd72..2e5d88f 100644 --- a/docs/source/guides/normalization_and_quality.ipynb +++ b/docs/source/guides/normalization_and_quality.ipynb @@ -38,7 +38,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n" + "CytePool 0.1.dev63+g8afa32afb\n" ] }, { @@ -57,9 +57,7 @@ "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.io import read_cube\n", + "from cytepool import Design, limma_voom, principal_components, read_cube\n", "from cytepool.normalization import (\n", " filter_by_expression,\n", " median_ratio_size_factors,\n", @@ -74,9 +72,6 @@ " plot_residual_variances,\n", " plot_sample_weights,\n", ")\n", - "from cytepool.principal_components import principal_components\n", - "from cytepool.stats.empirical_bayes import moderated_statistics\n", - "from cytepool.stats.linear_models import contrasts_fit, fit_linear_model\n", "from cytepool.stats.voom import voom\n", "\n", "kang = read_cube(\"kang2018.cube.zarr\")\n", @@ -142,7 +137,7 @@ ], "source": [ "pca = principal_components(kang, group=\"CD14+ Monocytes\")\n", - "symbols = np.asarray(kang.features[\"symbol\"])[pca.genes]\n", + "symbols = pca.features[\"symbol\"]\n", "for component in (0, 1):\n", " largest = np.argsort(-np.abs(pca.loadings[:, component]))[:8]\n", " print(f\"PC{component + 1}, the largest loadings:\", \", \".join(symbols[largest]))\n", @@ -172,7 +167,10 @@ "because they are precise, and they bend the trend down at its low end, where voom\n", "would then weight noisy observations most. {api}`filter_by_expression` keeps the\n", "genes with enough counts in enough samples, edgeR's `filterByExpr` rule [@chen2016],\n", - "and the number of samples it asks for comes from the design." + "and the number of samples it asks for comes from the design. Below, voom of every gene\n", + "with counts in at least two samples, on the left, against the trend that\n", + "{api}`limma_voom` fitted to the genes its filter kept, drawn from its result, on the\n", + "right." ] }, { @@ -215,23 +213,20 @@ "counts = kang.layer(\"counts\", \"CD14+ Monocytes\")\n", "detected = np.count_nonzero(counts, axis=0) >= 2\n", "keep = filter_by_expression(counts, design=design)\n", - "by_condition = filter_by_expression(counts, group=kang.samples[\"condition\"])\n", + "by_condition = filter_by_expression(counts, sample_groups=kang.samples[\"condition\"])\n", "print(f\"{detected.sum():>6,} genes with counts in at least two samples\")\n", "print(f\"{keep.sum():>6,} kept by the filter with the paired design\")\n", "print(f\"{by_condition.sum():>6,} kept by the filter with the condition as the group\")\n", "\n", + "result = limma_voom(kang, design, groups=\"CD14+ Monocytes\")\n", "figure, (left, right) = plt.subplots(\n", " 1, 2, figsize=(11, 4), layout=\"constrained\", sharey=True\n", ")\n", - "for ax, genes, title in (\n", - " (left, detected, \"Genes with counts in two samples\"),\n", - " (right, keep, \"Genes kept by the filter\"),\n", - "):\n", - " plot_mean_variance(\n", - " voom(counts[:, genes], design, norm_factors=tmm_factors(counts[:, genes])),\n", - " ax=ax,\n", - " )\n", - " ax.set_title(title)\n", + "present = counts[:, detected]\n", + "plot_mean_variance(voom(present, design, norm_factors=tmm_factors(present)), ax=left)\n", + "left.set_title(\"Genes with counts in two samples\")\n", + "plot_mean_variance(result, group=\"CD14+ Monocytes\", ax=right)\n", + "right.set_title(\"Genes kept by the filter\")\n", "plt.show()" ] }, @@ -327,7 +322,12 @@ "and empirical Bayes moderation shrinks each towards a common prior\n", "([Empirical Bayes moderation](../statistics/empirical_bayes.md)). limma's `plotSA`\n", "({api}`plot_residual_variances`) shows them, and `robust=True` estimates the prior so\n", - "that genes with outlying variances do not distort it [@phipson2016]." + "that genes with outlying variances do not distort it [@phipson2016].\n", + "{api}`DifferentialExpressionResult.fit` computes the fit of a cell type of a result\n", + "again, from the cube and the design that the result keeps: a {api}`GroupFit`, with the\n", + "samples, genes, values and weights fitted and the moderated tests, from which the\n", + "figures of a result are drawn. Here it gives the prior degrees of freedom and the\n", + "residual variances of the monocytes, moderated without and with `robust=True`." ] }, { @@ -360,20 +360,17 @@ } ], "source": [ - "weights = voom(kept, design, norm_factors=tmm_factors(kept))\n", - "fit = contrasts_fit(\n", - " fit_linear_model(weights.log_cpm, design, weights=weights.weights),\n", - " design.contrasts,\n", - ")\n", - "plain, robust = moderated_statistics(fit), moderated_statistics(fit, robust=True)\n", - "symbols = np.asarray(kang.features[\"symbol\"])[keep]\n", - "print(\n", - " f\"prior degrees of freedom: {plain.prior_df:.1f}, robust {np.max(robust.prior_df):.1f}\"\n", - ")\n", + "robust = limma_voom(kang, design, groups=\"CD14+ Monocytes\", robust=True)\n", + "plain_fit = result.fit(\"CD14+ Monocytes\")\n", + "robust_fit = robust.fit(\"CD14+ Monocytes\")\n", "print(\n", - " \"largest residual variances:\", \", \".join(symbols[np.argsort(fit.sigma)[-12:][::-1]])\n", + " f\"prior degrees of freedom: {plain_fit.moderated.df_prior:.1f}, \"\n", + " f\"robust {np.max(robust_fit.moderated.df_prior):.1f}\"\n", ")\n", - "plot_residual_variances(robust)\n", + "symbols = plain_fit.features[\"symbol\"]\n", + "largest = np.argsort(plain_fit.moderated.fit.sigma)[-12:][::-1]\n", + "print(\"largest residual variances:\", \", \".join(symbols[largest]))\n", + "plot_residual_variances(robust, group=\"CD14+ Monocytes\")\n", "plt.show()" ] }, @@ -411,7 +408,7 @@ "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABFcAAAGvCAYAAACepzouAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjIsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvgI3uAAAAAAlwSFlzAAAPYQAAD2EBqD+naQAA+iVJREFUeJzs3XdYU+fbB/BvFkv2UpChghNQceHeilvrts66t9ZNtVVcUEeX1lG1Vqt1a10Vt3XVBaKiuFCWiAooUwiQvH/wcn7GgIQYRPT7ua5cV86zzn0iknDnOc8jUiqVShARERERERERkVbExR0AEREREREREVFJxuQKEREREREREdF7YHKFiIiIiIiIiOg9MLlCRERERERERPQemFwhIiIiIiIiInoPTK4QEREREREREb0HJleIiIiIiIiIiN4DkytERERERERERO9BWtwBaCMjIwMvXryAjY0N9PX1NeqTmpoKqVSaZ/tXr14hJSVFpUwmk6F06dI6iZeIiIiIiIiIPl0laubK48ePMW3aNDg5OcHR0RH//fdfgX22bt0KT09P2NnZwdzcHF5eXrh69apKm1mzZqFSpUqoX7++8Ojfv39RXQYRERERERERfUJKVHJl3759KF26NI4ePapR++zsbBw5cgR//PEHEhMT8erVK9SoUQPt27dHQkKCStsOHTogOjpaeJw4caIoLoGIiIiIiIiIPjElKrkyZcoUTJ8+HdbW1hq1l0gk2LJlC2rUqAGRSAR9fX3MmzcP8fHxarNXACAhIQFyuVzXYRMRERERERHRJ6xErrnyPh4+fAgAKFOmjEr5vn37cPz4cbx+/Rp16tTBypUrUatWLY3HVSgUiImJgYmJCUQikU5jJiIiAgClUonk5GTY29tDLC5R348Q6Rw/exER0Yeg6eevzyq5kpaWhokTJ6JFixaoUaOGUF6nTh2MGjUKnp6eSExMxLhx49CmTRvcvn1bLQmTKyMjAxkZGcLxkydPUK1atSK/BiIioqioKDg4OOhkrI0bN6J///7Q09PTyXhEH0pMTAwcHR2LOwwiIvpMFPT5S6RUKpUfMB6diI6OhqOjI06fPo3mzZtr1Ecul6Nbt254+PAhzp49m2/SBABev34NGxsb+Pv7Y/z48Xm2mTdvHnx9fdXKo6KiYGpqqlFMREREb3NycsKdO3dgbGysVpeUlARHR0e8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", + "image/png": "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trZyWy3PmzdPiFVPT6/A6UvVq1cHAIhEIvz000+YO3cuQkNDYWxsjEqVKpXofRARERERERFR+VEml4E3NzeHubl5vnXW1tbCe7FYjIYNGyrUp1gs5kgVIiIi+uIkJibi/v37sLCwEL58IiIiouJRakHb58+fY9GiRXLlixYtwvPnzz82JiIiIiL6BNatWwcLCws0a9YMBw4cAACcPXtWoQ0AiIiI6H+USq5MnDgRrq6ucuUuLi6YPHnyRwdFRERERCUrMDAQ8+bNg4+PD7777juhvF27doiKisKdO3dKLzgiIqIyRqnkysWLF9GiRQu58hYtWuDixYsfGxMRERERlbCzZ89i+PDhaNOmDcRi2Zni9evXh5+fXylFRkREVPYolVwxMDDAo0eP5MofPnwot8AsEREREX1+3r17Jzy3iUQimbqEhARoaGiURlhERERlklLJlT59+mD06NEICgoSyu7evYvRo0fD09NTZcERERERUclwc3PD4cOHkZ6eLpNcuXnzJnx8fNC8efNSjI6IiKhsUSq54u3tjYoVK6JevXowMjKCoaEhXFxcUKlSJSxevFjVMRIRERGRinXq1Ak2NjZwcXHBv//+i5MnT6JXr15wd3fHqFGj4OjoWNohEhERlRlKbcWsp6eHc+fOwc/PD//99x9EIhFcXV3RtGlTVcdHRERERCVAJBLhyJEj2LhxI44ePYqYmBhoaWlh8+bNGDZsWGmHR0REVKYolVzJ07RpUyZUiIiIiMoodXV1jB8/HuPHjy/tUIiIiMo0paYFERERERERERFRLiZXiIiIiL4Qs2bNglgsVuj1+++/l3a4REREZcZHTQsiIiIiorJj5MiR8PDwUKitg4NDCUdDRERUfjC5QkRERPSFsLe3h729fWmHQUREVO4onFzZvHmzwp2OHj1aqWCIiIiI6NN6/PgxLl68iFevXqFq1apo164dqlWrVtphERERlSkKJ1cWL14svJdKpQgLCwMAmJqaAgDi4uIAADVq1GByhYiIiKgMmDp1KlavXo1KlSrB3NwcUVFRmDBhAhYsWIDZs2eXdnhERERlhsIL2j59+lR4jRo1Cm3btkVYWBhiY2MRGxuLsLAwtG3blokVIiIiojLg2LFj+Ouvv3D69GlERUXhzp07iI6Oxs6dOzF//nwEBASUdohERERlhlK7BW3ZsgVbt26FjY2N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QgghhBBCiIjExERcuXIFYWFh0NLSgr29vcTnlyhPQkICrly5gvDwcGhpaaFVq1Zo3bp1teXHx8fD3d0d4eHh0NbWhoODA1q1alVt+XFxcXB3d0dERAR0dHTg4OBQrfNrxMbGwt3dHZGRkdDR0UGbNm3QokWLasuPiorCtWvXEBkZCT09PbRt25a7aqM6REZG4tq1a4iKioK+vj7atm0LW1vbassPDw/H9evXER0dDX19fTg5OX07yy9/hgZXCCGEEEIIKUd6ejr27t2LV69eISsrS6Sudu3a2Lx5M6/5aWlp2Lt3L16/fl0iv169evjjjz94zU9NTeXys7OzReoaNGiA33//ndf8lJQU7N27F2/evCmR36hRI6xbt47XfEIIEQdNaEsIIYQQQkgZGGPo0qULYmJi0LFjR2hra4vUGxoa8povFArRqVMnJCYmon379iXyDQwMeM0vKipCx44dkZKSAmdnZ+jo6IjU6+vr857fvn17ZGRkSCU/MTERT548KbPewMAALVu25C0/ISEBT58+LbPe0NCQ1yso4uPj4evrW2a9kZERmjdvzlt+XFwcnj17Vma9sbEx7OzseMuPiYkpd6LamjVr8noFR1RUFDdpbmlMTU15vYIjMjISL1++LLPezMwMjRs35i0/IiICr169KrPe3NwcNjY2vOVXFg2uEEIIIYQQUoaIiAi8f/8e4eHh0NTUrPb80NBQBAUFISwsDBoaGtWeHxQUhIiICISGhkpl2WV/f3/ExsYiJCQEqqqq1Z7/4sWLEisiFRUVobCwEDIyMujRowcuX77MW/7z589L5BcWFqKoqAgyMjLo06dPick+Jenp06cllpf+PH/AgAH4999/ect//PhxiRWhPs8fMmQI/vnnH97yHz58iFGjRpWZP2LECBw7doy3/Hv37mHixIll5o8bNw4HDx7kLf/OnTuYMmVKqfmysrKYOHEi9u7dy1v+rVu3MGPGjDLzp06dip07d/KWX1l0WxAhhBBCCCFliIyMRKdOneDv7y+V/NDQUPTq1Qtv3ryRSn5gYCAGDRpU7qfnfHr//j1GjRpV7tUL1a2oqAg+Pj4YP348rl69Cisrq2rPf/z4MSZMmIDr16/D0tKy2vMfPnyISZMm4ebNmzA3N6/2/Pv372PKlCnw8PCAiYlJteYXFhbC29sbU6dOxb1791CjRo1qz797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", "text/plain": [ "
" ] @@ -446,8 +443,8 @@ "plt.show()\n", "\n", "changed = set(bulk.features[\"feature_id\"][truth[\"true_log2_fold_change_drug_b\"] != 0])\n", - "for name, result in ((\"equal weights\", equal), (\"quality weights\", weighted)):\n", - " table = result.table(\"bulk\", \"drug_b_vs_control\")\n", + "for name, analysis in ((\"equal weights\", equal), (\"quality weights\", weighted)):\n", + " table = analysis.table() # the one group and contrast\n", " called = table[\"feature_id\"][table[\"adjusted_p_value\"] < 0.05]\n", " true = sum(gene in changed for gene in called)\n", " print(f\"{name:<16} {called.size} genes called, {called.size - true} of them false\")" @@ -517,8 +514,10 @@ "\n", "- **The filter.** {api}`limma_voom` filters with the design, as above; a paired design\n", " gives every sample a high leverage, so a gene needs counts in only a few samples.\n", - " `expression_filter` changes the thresholds, and {api}`filter_by_expression` with\n", - " `group=` asks for counts in as many samples as the smallest group has.\n", + " `expression_filter` changes the thresholds, and\n", + " `expression_filter={\"sample_groups\": \"condition\"}` groups the samples by a column\n", + " instead of the design, as {api}`filter_by_expression` does with `sample_groups=`: a\n", + " gene then needs counts in as many samples as the smallest group has.\n", "- **Normalization factors.** TMM is the default of {api}`limma_voom` and of edgeR.\n", " TMM, RLE and DESeq2's median ratios assume that most genes do not change between\n", " samples, and the upper quartile that one quantile of the counts does not; when a\n", @@ -539,8 +538,8 @@ "kang2018", "simulated_bulk" ], - "executed_at": "2026-10-27T09:55:18+00:00", - "executed_code_sha256": "eed2efcee4a18d468aece68892293efe57484a6875e7979e1773b3919dc49e87", + "executed_at": "2026-10-27T15:23:17+00:00", + "executed_code_sha256": "4273edc8fc8913273299dacca602d6a6f46975f14b5a355a815399853e7a4b85", "executed_datasets": { "kang2018": { "kang2018.cube.zarr": "sha256:a30af3dabe17f21a6c75f41b64a279a1423640b9aa919022cf67897f9cc77a51" @@ -550,8 +549,8 @@ "simulated_bulk_truth.npz": "sha256:a8a0275532e3cafb02401846176a7ddfa037579aceb804c5f9c007700f022977" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 9.4, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 4.0, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/pathway_activity.ipynb b/docs/source/guides/pathway_activity.ipynb index 45a90d3..3c4fa79 100644 --- a/docs/source/guides/pathway_activity.ipynb +++ b/docs/source/guides/pathway_activity.ipynb @@ -35,7 +35,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n", + "CytePool 0.1.dev63+g8afa32afb\n", "Network: 1,171 sources, 39,896 edges, 6,505 target genes\n", " name: CollecTRI; version: 2.0 (Zenodo record 8192729); licence: CC BY 4.0\n", " (Zenodo record 8192729); level 'commercial': the interactions of\n", @@ -50,25 +50,27 @@ ], "source": [ "import matplotlib.pyplot as plt\n", - "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.activity import ulm\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.gene_sets import GeneSets\n", - "from cytepool.io import read_cube\n", - "from cytepool.networks import collectri, progeny\n", - "from cytepool.pathway_scores import gsva\n", + "from cytepool import (\n", + " Design,\n", + " collectri,\n", + " gsva,\n", + " limma,\n", + " limma_voom,\n", + " progeny,\n", + " read_cube,\n", + " ulm,\n", + ")\n", "from cytepool.plotting import plot_activities, plot_pathway_scores, plot_top_activities\n", - "from cytepool.stats.empirical_bayes import moderated_statistics\n", - "from cytepool.stats.linear_models import contrasts_fit, fit_linear_model\n", - "from cytepool.stats.multiple_testing import adjust_p_values\n", "\n", "kang = read_cube(\"kang2018.cube.zarr\")\n", - "levels = {\"condition\": [\"ctrl\", \"stim\"]}\n", - "contrasts = {\"stim_vs_ctrl\": \"conditionstim\"}\n", - "design = Design(kang.samples, \"~ donor + condition\", levels=levels, contrasts=contrasts)\n", + "design = Design(\n", + " kang.samples,\n", + " \"~ donor + condition\",\n", + " levels={\"condition\": [\"ctrl\", \"stim\"]},\n", + " contrasts={\"stim_vs_ctrl\": \"conditionstim\"},\n", + ")\n", "result = limma_voom(kang, design)\n", "regulators = collectri()\n", "print(f\"CytePool {cytepool.__version__}\")\n", @@ -145,7 +147,10 @@ "\n", "Scored on the log-CPM values of one cell type, every sample gets an activity for each\n", "pathway, which can be drawn, compared and tested with the design like the expression\n", - "of a gene. PROGENy's footprints are those of 14 pathways in human cells." + "of a gene. PROGENy's footprints are those of 14 pathways in human cells. {api}`limma`\n", + "tests a contrast of the design on the activities of every pathway, as limma tests\n", + "genes: it matches the design's samples to the profiles scored by their ids, so the\n", + "design of the cube serves." ] }, { @@ -191,21 +196,16 @@ } ], "source": [ - "pathways = ulm(kang, progeny(), group=\"CD14+ Monocytes\", gene_ids=\"symbol\")\n", + "pathways = ulm(kang, progeny(), group=\"CD14+ Monocytes\", id_column=\"symbol\")\n", "plot_activities(pathways, color_by=\"condition\")\n", "plt.show()\n", "\n", - "profile_design = Design(\n", - " pathways.profiles, \"~ donor + condition\", levels=levels, contrasts=contrasts\n", - ")\n", - "fit = contrasts_fit(\n", - " fit_linear_model(pathways.scores, profile_design), profile_design.contrasts\n", - ")\n", - "tested = moderated_statistics(fit)\n", - "adjusted = adjust_p_values(tested.p_value[:, 0])\n", + "tested = limma(pathways, design).sort(\"t\", descending=True)\n", "print(f\"{'pathway':<10}{'t':>7}{'adjusted p':>12}\")\n", - "for k in np.argsort(-tested.t[:, 0]):\n", - " print(f\"{pathways.sources[k]:<10}{tested.t[k, 0]:>7.1f}{adjusted[k]:>12.1e}\")" + "for pathway, t, adjusted in zip(\n", + " tested[\"source\"], tested[\"t\"], tested[\"adjusted_p_value\"]\n", + "):\n", + " print(f\"{pathway:<10}{t:>7.1f}{adjusted:>12.1e}\")" ] }, { @@ -222,9 +222,10 @@ "\n", "{api}`gsva` scores each gene set in every sample from how the set's genes rank against\n", "the sample's other genes, each gene measured against its own distribution across the\n", - "samples. Here the sets are the regulators' targets, without their signs; as GSVA's\n", - "guide does, the scores are tested with limma, with a prior that follows the sets'\n", - "sizes, and the heatmap shows the 20 sets that differ most between the conditions." + "samples. Here the sets are the regulators' targets, without their signs\n", + "({api}`Network.to_gene_sets`); as GSVA's guide does, the scores are tested with limma,\n", + "with a prior that follows the sets' sizes (`trend=scores.n_genes`), and the heatmap\n", + "shows the 20 sets that differ most between the conditions." ] }, { @@ -249,30 +250,16 @@ } ], "source": [ - "regulons = GeneSets(\n", - " {regulator: list(targets) for regulator, targets in regulators.items()},\n", - " source=regulators.name,\n", - " licence=regulators.licence,\n", - ")\n", "scores = gsva(\n", " kang,\n", - " regulons,\n", + " regulators.to_gene_sets(),\n", " group=\"CD14+ Monocytes\",\n", - " gene_ids=\"symbol\",\n", + " id_column=\"symbol\",\n", " min_size=15,\n", " max_size=500,\n", ")\n", - "score_design = Design(\n", - " scores.samples, \"~ donor + condition\", levels=levels, contrasts=contrasts\n", - ")\n", - "fit = contrasts_fit(\n", - " fit_linear_model(scores.scores, score_design), score_design.contrasts\n", - ")\n", - "differing = np.argsort(moderated_statistics(fit, trend=scores.n_genes).p_value[:, 0])\n", - "names = list(scores.gene_sets)\n", - "plot_pathway_scores(\n", - " scores, sets=[names[k] for k in differing[:20]], color_by=\"condition\"\n", - ")\n", + "differing = limma(scores, design, trend=scores.n_genes).sort(\"p_value\")\n", + "plot_pathway_scores(scores, sets=list(differing[\"gene_set\"][:20]), color_by=\"condition\")\n", "plt.show()" ] }, @@ -315,15 +302,15 @@ "datasets": [ "kang2018" ], - "executed_at": "2026-10-27T08:56:25+00:00", - "executed_code_sha256": "1928564a0c0ddc49fe7c9dc2dc94a61a075ccffbc0ee7d921400755b9e7a65a6", + "executed_at": "2026-10-27T15:23:27+00:00", + "executed_code_sha256": "02276c359f48a48739d2082eb79908d8fe1d9609bd2840eb972779f0205719c8", "executed_datasets": { "kang2018": { "kang2018.cube.zarr": "sha256:a30af3dabe17f21a6c75f41b64a279a1423640b9aa919022cf67897f9cc77a51" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 3.5, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 9.9, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/pool_cells.ipynb b/docs/source/guides/pool_cells.ipynb index 87b7428..2a20e7d 100644 --- a/docs/source/guides/pool_cells.ipynb +++ b/docs/source/guides/pool_cells.ipynb @@ -38,7 +38,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n", + "CytePool 0.1.dev63+g8afa32afb\n", "Cube with 24 samples x 6 groups x 2,000 features\n", " layers: counts (uint32), detected (uint32), log_sum (float64),\n", " log_sum_sq (float64), linear_sum (float64)\n", @@ -64,7 +64,7 @@ "import scipy.sparse\n", "\n", "import cytepool\n", - "from cytepool.cube import from_cells\n", + "from cytepool import from_cells\n", "from cytepool.plotting import plot_cell_numbers, plot_dotplot, plot_pca\n", "\n", "cells = np.load(\"simulated_pbmc_cells.npz\")\n", @@ -74,14 +74,14 @@ "sample_ids = np.unique(cells[\"cell_sample\"])\n", "cube = from_cells(\n", " counts,\n", - " samples=cells[\"cell_sample\"],\n", - " groups=cells[\"cell_type\"],\n", - " sample_table={\n", + " sample_labels=cells[\"cell_sample\"],\n", + " group_labels=cells[\"cell_type\"],\n", + " samples={\n", " \"sample_id\": sample_ids,\n", " \"donor\": [sample.split(\"_\")[0] for sample in sample_ids],\n", " \"condition\": [sample.split(\"_\")[1] for sample in sample_ids],\n", " },\n", - " group_table={\n", + " groups={\n", " \"group_id\": cells[\"cell_types\"],\n", " \"lineage\": [\"lymphoid\"] * 4 + [\"myeloid\"] * 2,\n", " },\n", @@ -127,7 +127,7 @@ "outputs": [ { "data": { - "image/png": "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", + "image/png": "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", "text/plain": [ "
" ] @@ -141,7 +141,7 @@ " cell_type: list(genes[:3])\n", " for cell_type, genes in zip(cells[\"cell_types\"], cells[\"marker_genes\"])\n", "}\n", - "ax = plot_dotplot(cube, markers, standard_scale=\"gene\")\n", + "ax = plot_dotplot(cube, markers, standard_scale=\"feature\")\n", "plt.show()" ] }, @@ -194,10 +194,10 @@ "by_condition = cube.collapse_samples(\"condition\")\n", "print(by_condition.samples)\n", "\n", - "gene = cube.features[\"feature_id\"].tolist().index(\"gene_0191\")\n", - "detected = by_condition.fraction_detected()[:, :, gene]\n", - "mean = by_condition.mean()[:, :, gene]\n", - "variance = by_condition.variance()[:, :, gene]\n", + "gene = by_condition.select(features=\"gene_0191\")\n", + "detected = gene.fraction_detected()[:, :, 0]\n", + "mean = gene.mean()[:, :, 0]\n", + "variance = gene.variance()[:, :, 0]\n", "print(f\"\\ngene_0191, control -> stimulated{'detected':>12}{'mean':>16}{'variance':>16}\")\n", "for g, cell_type in enumerate(by_condition.groups[\"group_id\"]):\n", " print(\n", @@ -216,7 +216,10 @@ "they are no test, since the samples pooled are the replicates a test needs ([Genes that\n", "change in each cell type](genes_per_cell_type.ipynb) tests the change). Pooling the\n", "samples of each donor, `collapse_samples(\"donor\")`, gives one profile per donor, for\n", - "questions about donors ([Repeated measures and donors](repeated_measures.ipynb)).\n", + "questions about donors ([Repeated measures and donors](repeated_measures.ipynb)). To\n", + "pool other sets of samples, give each sample the label of its set in a new column with\n", + "{api}`Cube.with_columns`, and pool by that column:\n", + "`cube.with_columns(samples={\"pool\": labels}).collapse_samples(\"pool\")`.\n", "\n", "## The samples of each cell type\n", "\n", @@ -239,7 +242,7 @@ "outputs": [ { "data": { - "image/png": "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tK3c0EQrKeDpIoeVVFLDkREREVk+JruIiIiIiGpAPUcZVr3sC9961hXWs5IC307wQgd/e9gp+HhORET0pHg3JSIiIiKqIY9KeDHRRUREVP14RyUiIiIiqkHlJbyY6CIiIqoZvKsSEREREdWwhxNeTHQRERHVHN5ZiYiIiIiMoCTh1bi+NRNdRERENUhm6gCIiIiIiOqKeo4yrH/NH0KAiS4iIqIawmQXEREREZER2VozyUVERFSTeKclIiIiIiIiIiKLwWQXERERERERERFZDCa7iIiIiIiIiIjIYjDZRUREREREREREFoPJLiIiIiIiIiIishhMdhERERERERERkcVgsouIiIiIiIiIiCwGk11ERERERERERGQxmOwiIiIiIiIiIiKLwWQXERERERERERFZDCa7iIiIiIiIiIjIYjDZRUREREREREREFoPJLiIiIqIyJCcnY/LkyZg/f76pQyEiIiKiKmCyi4iIiOghGo0GY8eOxc6dO7Fz505Th0NEREREVcBkFxEREdFDPvzwQzRp0gR9+/Y1dShEREREVEVMdhERERE94LfffsP69evx3XffmToUIiIiInoMMlMHQERERGQu0tLSMGnSJKxduxZKpbJSbVQqFVQqlX47JyenpsIjIiIiokpgsouIiIgs1u+//461a9dWWOeVV15BSEgIhBCYOHEiJk2ahKeeeqrSrzFv3jzMnTv3SUMlIiIiomrCZBcRERFZrEaNGqFLly4V1nFzcwMAxMbG4vfff0fDhg0REREBADhy5Ahyc3MRERGBjz76CL6+vqXav/fee5gxY4Z+OycnB97e3tV4FkRERERUFUx2ERERkcUKCgpCUFBQper6+/tj2bJlBmUJCQmQSCTo0qUL7O3ty2ynUCigUCieOFYiIiIiqh5MdhEREREBcHd31/foKhETEwO5XF6qnIiIiIjMF1djJCIiIiIiIiIii8GeXURERETlePHFF5Gbm2vqMIiIiIioCpjsIiIiIipHjx49TB0CEREREVVRrUt2FRQUIC4uDpmZmfDx8UFwcLCpQyIiIiIiIiIiIjNRq5Jdhw4dwsiRI+Hh4QE/Pz/ExsbC19cXe/bsgYuLi6nDIyIiIiIiIiIiE6tVE9TPmDEDvXv3xpkzZ7Bt2zZcuHABV69exbfffmvq0IiIiIiIiIiIyAzUqmSXSqWCn5+fftvZ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", + "image/png": "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kqKJownc6xprJqO3MDZA35aWkEgAcya6iGqkapvsSkhIgLGxMaysrHQdChEREREREemIiZEUJv8qs+AqjEQ1WrWbs+vo0aNo3LgxPD094eXlhcGDByMtLU3XYRERERERERERURVQrZJdf//9N3r27Il3330Xjx49Qnx8PAICAhAdHa3r0IiIiIiIykStFlDmq6Hi5NlERETlqlolu7766iu0a9cOU6dOhVRaEPqAAQPQokUL3QZGRERERFQGarVAYno+hi6Jwf3UPCa8iIiIylG1SXbl5eXh6NGj6N+/P/Lz8xEbGwulUqnrsIiIiIiIyuRpomvY0hjceKDEkKVMeBEREZUnnU5Q/+DBg+fOt9WgQQMYGhri0aNHUCqViI+Ph6urK6RSKRITEzFgwAAsX74cFhYWRbZXKpVaSbH09PRyPQYiIiIiotJ6NtGVmJ4PAEjJVGHI0hhs/NAV9awMYWAg0XGURERE1ZtOk10///wzdu7cWWKdw4cPw9nZWTNsccOGDTh16hTc3NwQFxcHX19fTJs2Db/++muR7efOnYs5c+aUe+xERERERGVRVKLrKSa8iIiIyo9ECFEt+kurVCqYm5tj7NixWLBggaZ8+vTp2L59O27dulVku6J6djk5OSEtLQ0KhaLC4yYiItI36enpsLS05LW0GDw/VJSSEl3PsjYzYMKLiGo8XkvpZVWbObsMDAzQpUsXJCcna5U/fvwYlpaWxbaTy+VQKBRa/4iIiIiIKktpE13APz28OIcXERHRi9PpMMaymj17Nl5//XW0bt0aPj4+OHv2LH7//XesXLlS16ERERERERVJqRKIeZSLRxklJ7qeSs1S4caDHNiYy2DG3l1ERERlVm16dgFAu3btcOjQIfz111947733cODAAWzduhUjRozQdWhEREREREUyMZSiubMJlrzjBIPn3H1LJMC8wHro2MgMZvJqdatORERUZVSrnl0A4OPjg927d+s6DCIiIiKiUjM1kqKVqymWvOOEj36/C5W6cJ2nia7OHuYwkxtUfpBERER6go+LiIiIiIgqwbMJr3/38GKii4iIqPww2UVEREREVEmKSngx0UVERFS+mOwiIiIiIqpEzya8DA2Y6CIiIipv1W7OLiIiIiKi6u5pwuuvzxvBxEjCRBcREVE5YrKLiIiIiEgHTI2kMDXiQAsiIqLyxqsrERERERERERHpDSa7iIiIiIiIiIhIbzDZRUREREREREREeoPJLiIiIiIiIiIi0htMdhERERERERERkd5gsouIiIiIiIiIiPQGk11ERERERERERKQ3mOwiIiIiIiIiIiK9wWQXERERERERERHpDSa7iIiIiIiIiIhIbzDZRUREREREREREeoPJLiIiIqIiCCEQFRWF+/fv6zoUIiIiIioDJruIiIiIivD999+jUaNGGDFihK5DISIiIqIyYLKLiIiI6F/OnDmDFStWoFevXroOhYiIiIjKiMkuIiIiomekpaVh2LBhWL16NaytrXUdDhERERGVEZNdRERERM8YPXo03nrrLXTt2lXXoRARERHRC5DpOgAiIiKiipKamopHjx6VWKdu3bowMzMDACxfvhxRUVEICgoq9XsolUoolUrN6/T09BcLloiIiIjKBZNdREREpLe2b9+OuXPnllhn2bJl6NatG2JiYvDpp58iKCgIcXFxAICMjAxkZ2cjKioKzs7OMDIyKtR+7ty5mDNnToXET0RERERlJxFCCF0HUZnS09NhaWmJtLQ0KBQKXYdDRERU7ejrtfTs2bMYNmyYVlliYiJUKhXq1auHvXv3onHjxoXaFdWzy8nJSe/ODxERUWXR13sNqjzs2UVEREQEoF27doiKitIqGz58OBISEvDXX38V204ul0Mul1d0eERERERUSpygnoiIiIiIiIiI9AZ7dhEREREVo06dOjAwMNB1GERERERUBkx2ERERERXjxx9/1HUIRERERFRGHMZIRERERERERER6g8kuIiIiIiIiIiLSG0x2ERERERERERGR3mCyi4iIiIiIiIiI9AaTXUREREREREREpDeY7CIiIiIiIiIiIr0h03UAZZWTk4Pz588jJSUFzs7OaNq0qa5DIiIiIiIiIiKiKqJaJbuCg4MREBAAW1tbuLq6IiwsDI0aNcK+fftgZWWl6/CIiIiIiIiIiEjHqtUwxkmTJqFTp06IiIjAvn37cOPGDdy8eRO//PKLrkMjIiIiIiIiIqIqoFolu7Kzs9GgQQPNaxsbG9SqVQs5OTk6jIqIiIiIiIiIiKqKajWMcf78+fjggw9gYWEBFxcXHD58GBYWFpgwYUKxbZRKJZRKpeZ1enp6ZYRKREREREREREQ6oNNkV3h4OGJiYkqs07NnT5iZmQEAGjZsCHd3d6xfvx5u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", "text/plain": [ "
" ] @@ -318,7 +321,7 @@ } ], "source": [ - "myeloid = cube.groups.filter(cube.groups[\"lineage\"] == \"myeloid\")[\"group_id\"]\n", + "myeloid = cube.groups.filter(lineage=\"myeloid\")[\"group_id\"]\n", "merged = cube.regroup({\"Myeloid cells\": list(myeloid)})\n", "print(merged.groups)\n", "plot_cell_numbers(merged)\n", @@ -367,16 +370,16 @@ "datasets": [ "simulated_pbmc" ], - "executed_at": "2026-10-27T09:55:28+00:00", - "executed_code_sha256": "cda3726c97acc5f96fb34da2f1a14df7ca8fd0725e2e817bb1c83427700dfef3", + "executed_at": "2026-10-27T15:23:31+00:00", + "executed_code_sha256": "a3ce492792f0724a29e182158218c75a1d51d25c8b1048f4e92d707eddc57bed", "executed_datasets": { "simulated_pbmc": { "simulated_pbmc.cube.zarr": "sha256:554a2b5b4188308ea900cf907fa87f42d12e0458e6d5dc11525e862f3ad8a726", "simulated_pbmc_cells.npz": "sha256:0d4c9154b87dbf8e9796232c2afec4a8340368fd0f5d386df4dd780c4e478129" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 7.6, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 3.2, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/repeated_measures.ipynb b/docs/source/guides/repeated_measures.ipynb index 0364732..5480953 100644 --- a/docs/source/guides/repeated_measures.ipynb +++ b/docs/source/guides/repeated_measures.ipynb @@ -22,7 +22,13 @@ "\n", "In the Kang data every donor gives one control and one stimulated sample. The paired\n", "design `~ donor + condition` and the mixed model `~ condition` with `(1 | donor)` both\n", - "compare the stimulated samples with the controls of the same donors." + "compare the stimulated samples with the controls of the same donors. By default each\n", + "keeps the genes with enough counts for its own fixed effects, and the paired design\n", + "asks for counts in fewer samples than the condition alone, so that the two would test\n", + "different genes, 5,125 against 3,651 in these monocytes ([Normalization, filtering and\n", + "sample quality](normalization_and_quality.ipynb#which-genes-to-test)).\n", + "`expression_filter={\"sample_groups\": \"condition\"}` filters both by the condition\n", + "instead, so that they test the same genes and their results compare gene for gene." ] }, { @@ -35,16 +41,16 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n" + "CytePool 0.1.dev63+g8afa32afb\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "fixed donor effect 5,125 genes tested 3,168 called\n", + "fixed donor effect 3,651 genes tested 2,536 called\n", "random donor effect 3,651 genes tested 2,542 called\n", - "Of the 3,651 genes both test, the log2 fold changes correlate at 0.999 and 2,273 are called by both.\n" + "Of the 3,651 genes both test, the log2 fold changes correlate at 0.999 and 2,456 are called by both.\n" ] } ], @@ -53,16 +59,19 @@ "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.cube import Cube\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import dream_voom, limma_voom\n", - "from cytepool.io import read_cube\n", + "from cytepool import (\n", + " Cube,\n", + " Design,\n", + " dream_voom,\n", + " limma_voom,\n", + " read_cube,\n", + " variance_partitioning,\n", + ")\n", "from cytepool.plotting import (\n", - " plot_gene_variance_fractions,\n", + " plot_feature_variance_fractions,\n", " plot_p_values,\n", " plot_variance_fractions,\n", ")\n", - "from cytepool.variance_partitioning import variance_partitioning\n", "\n", "kang = read_cube(\"kang2018.cube.zarr\")\n", "print(f\"CytePool {cytepool.__version__}\")\n", @@ -77,8 +86,13 @@ " levels=levels,\n", " contrasts=contrasts,\n", ")\n", - "fixed = limma_voom(kang, paired, groups=monocytes).table(monocytes, \"stim_vs_ctrl\")\n", - "random = dream_voom(kang, mixed, groups=monocytes).table(monocytes, \"stim_vs_ctrl\")\n", + "by_condition = {\"sample_groups\": \"condition\"}\n", + "fixed = limma_voom(\n", + " kang, paired, groups=monocytes, expression_filter=by_condition\n", + ").table(monocytes, \"stim_vs_ctrl\")\n", + "random = dream_voom(\n", + " kang, mixed, groups=monocytes, expression_filter=by_condition\n", + ").table(monocytes, \"stim_vs_ctrl\")\n", "\n", "for name, table in ((\"fixed donor effect\", fixed), (\"random donor effect\", random)):\n", " called = np.count_nonzero(table[\"adjusted_p_value\"] < 0.05)\n", @@ -99,13 +113,10 @@ "id": "f26ba559", "metadata": {}, "source": [ - "Both models find the same response: the fold changes of the genes both test agree\n", - "almost exactly, and most genes that one calls the other calls too. They test different\n", - "numbers of genes because each filters with its own fixed effects, and the paired\n", - "design asks for counts in fewer samples than the condition alone ([Normalization,\n", - "filtering and sample quality](normalization_and_quality.ipynb#which-genes-to-test)).\n", - "When every donor gives a sample in every condition and the question is within donors,\n", - "the paired design is the simpler of the two.\n", + "Both models find the same response: on the same genes, their log2 fold changes agree\n", + "almost exactly, and nearly every gene that one calls the other calls too. When every\n", + "donor gives a sample in every condition and the question is within donors, the paired\n", + "design is the simpler of the two.\n", "\n", "## A variable of the donors\n", "\n", @@ -380,7 +391,7 @@ "\n", "figure, (left, right) = plt.subplots(1, 2, figsize=(11, 4.5), layout=\"constrained\")\n", "plot_variance_fractions(partition, ax=left)\n", - "plot_gene_variance_fractions(partition, chosen, ax=right)\n", + "plot_feature_variance_fractions(partition, chosen, ax=right)\n", "plt.show()" ] }, @@ -423,15 +434,15 @@ "datasets": [ "kang2018" ], - "executed_at": "2026-10-27T08:56:32+00:00", - "executed_code_sha256": "2e5054ab4f70248a8fc0402fc8e5a3b063e1195d610523ac208e67f8a3a1b56b", + "executed_at": "2026-10-27T15:23:34+00:00", + "executed_code_sha256": "10cc8beac825f403ee5850b95a78213b817ff1fbf7d2f770da7ad48090039480", "executed_datasets": { "kang2018": { "kang2018.cube.zarr": "sha256:a30af3dabe17f21a6c75f41b64a279a1423640b9aa919022cf67897f9cc77a51" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 2.8, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 2.7, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/replication_across_studies.ipynb b/docs/source/guides/replication_across_studies.ipynb index a91416e..9aa849c 100644 --- a/docs/source/guides/replication_across_studies.ipynb +++ b/docs/source/guides/replication_across_studies.ipynb @@ -18,9 +18,10 @@ "out. [Meta-analysis](../statistics/meta_analysis.md) explains the statistics.\n", "\n", "The cohorts are the six simulated ones of [Combine studies](combine_studies.ipynb),\n", - "aligned and analysed as there, each by {api}`limma_voom` on its own. The simulation\n", - "knows which monocyte genes the disease changes in every cohort, and which genes one\n", - "cohort alone changes." + "aligned and analysed as there: {api}`analyse_each_study` tests each by\n", + "{api}`limma_voom` on its own, and the functions below take the {api}`StudyResults` it\n", + "returns. The simulation knows which monocyte genes the disease changes in every\n", + "cohort, and which genes one cohort alone changes." ] }, { @@ -33,7 +34,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953, numpy 2.5.4\n", + "CytePool 0.1.dev63+g8afa32afb, numpy 2.5.4\n", "monocyte genes, adjusted p < 0.05 every cohort one cohort unchanged\n", "cohort_1 (60 donors) 62 10 1\n", "cohort_2 (32 donors) 38 10 4\n", @@ -53,14 +54,14 @@ "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.cube import CubeCollection\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.held_out_prediction import held_out_prediction\n", - "from cytepool.io import read_cube\n", - "from cytepool.meta_analysis import (\n", - " align_study_results,\n", - " differential_expression_meta_analysis,\n", + "from cytepool import (\n", + " CubeCollection,\n", + " analyse_each_study,\n", + " held_out_prediction,\n", + " meta_analysis,\n", + " read_cube,\n", + " replication,\n", + " sumrank,\n", ")\n", "from cytepool.plotting import (\n", " plot_held_out_auc,\n", @@ -68,33 +69,35 @@ " plot_study_agreement,\n", " plot_sumrank,\n", ")\n", - "from cytepool.replication import differential_expression_replication\n", "from cytepool.stats.combined_p_values import (\n", " combine_p_values,\n", " partial_conjunction,\n", ")\n", "from cytepool.stats.multiple_testing import adjust_p_values\n", - "from cytepool.sumrank import differential_expression_sumrank\n", "\n", - "studies = [f\"cohort_{i}\" for i in range(1, 7)]\n", - "cubes = {study: read_cube(f\"simulated_{study}.cube.zarr\") for study in studies}\n", + "cohorts = [f\"cohort_{i}\" for i in range(1, 7)]\n", + "cubes = {study: read_cube(f\"simulated_{study}.cube.zarr\") for study in cohorts}\n", "cd4_subsets = {\"CD4 naive\": \"CL:0000624\", \"CD4 memory\": \"CL:0000624\"}\n", - "collection = CubeCollection(cubes, group_mapping={\"cohort_4\": cd4_subsets})\n", - "contrast, monocytes = \"case_vs_control\", \"CL:0001054\"\n", - "results = {}\n", - "for study in collection.studies:\n", - " cube = collection.aligned_cube(study)\n", - " design = Design(\n", - " cube.samples,\n", - " \"~ condition\",\n", - " levels={\"condition\": [\"control\", \"case\"]},\n", - " contrasts={contrast: \"conditioncase\"},\n", - " )\n", - " results[study] = limma_voom(cube, design)\n", + "cell_types = {\n", + " \"CL:0000236\": \"B cells\",\n", + " \"CL:0000624\": \"CD4 T cells\",\n", + " \"CL:0000623\": \"NK cells\",\n", + " \"CL:0001054\": \"CD14 monocytes\",\n", + "}\n", + "collection = CubeCollection(\n", + " cubes, group_mapping={\"cohort_4\": cd4_subsets}, group_names=cell_types\n", + ")\n", + "contrast, monocytes = \"case_vs_control\", \"CD14 monocytes\"\n", + "studies = analyse_each_study(\n", + " collection,\n", + " \"~ condition\",\n", + " levels={\"condition\": [\"control\", \"case\"]},\n", + " contrasts={contrast: \"conditioncase\"},\n", + ")\n", "\n", "truth = np.load(\"simulated_cohorts_truth.npz\")\n", "genes = truth[\"feature_id\"]\n", - "cell_type = list(truth[\"cell_ontology_ids\"]).index(monocytes)\n", + "cell_type = list(truth[\"cell_ontology_ids\"]).index(\"CL:0001054\")\n", "changed = truth[\"true_log2_fold_change\"][:, cell_type] != 0 # cohorts x genes\n", "everywhere = genes[changed.all(axis=0)]\n", "one_cohort = genes[changed.sum(axis=0) == 1]\n", @@ -114,7 +117,7 @@ "\n", "print(f\"CytePool {cytepool.__version__}, numpy {np.__version__}\")\n", "row(\"monocyte genes, adjusted p < 0.05\", columns)\n", - "for study, result in results.items():\n", + "for study, result in studies.items():\n", " table = result.table(monocytes, contrast)\n", " label = f\"{study} ({cubes[study].n_samples} donors)\"\n", " row(label, by_truth(table[\"feature_id\"], table[\"adjusted_p_value\"]))\n", @@ -131,11 +134,10 @@ "\n", "## Genes found in k cohorts\n", "\n", - "{api}`differential_expression_replication` reads one cell type and contrast of every\n", - "cohort, lined up gene by gene, and counts the cohorts that find each gene (adjusted\n", - "p-value below 0.05) and in which direction. {api}`plot_replicated_genes` draws how\n", - "many genes each number of cohorts finds, as the paper that introduced SumRank counts\n", - "them [@nakatsuka2025]." + "{api}`replication` reads one cell type and contrast of every cohort, lined up gene by\n", + "gene, and counts the cohorts that find each gene (adjusted p-value below 0.05) and in\n", + "which direction. {api}`plot_replicated_genes` draws how many genes each number of\n", + "cohorts finds, as the paper that introduced SumRank counts them [@nakatsuka2025]." ] }, { @@ -161,7 +163,7 @@ }, { "data": { - "image/png": "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+ytVqoQDBw4gPj4eKSkpqFevHkxMTEo5JRERERFR8Wm82La1tYWtra3K+hERERERlRUan7NNRERERFResdgmIiIiIlITFttERERERGrCYpuIiIiISE1YbBMRERERqQmLbSIiIiIiNWGxTURERESkJiy2iYiIiIjUhMU2EREREZGasNgmIiIiIlITFttERERERGrCYpuIiIiISE1YbBMRERERqQmLbSIiIiIiNWGxTURERESkJhVKcnBaWhoCAwMRHR0NKysrDB8+HFZWVqrKRkRERESk1YpVbDs4OCA6OhoAcP/+fbRv3x4vX75E7dq1ERsbixUrVuDatWtwcHBQS1giIiIiIm1SrGkkMTEx4vezZs1CixYt8PjxY/zzzz9ISkpC69at8f3336s8JBERERGRNlJ6GsmVK1dw8eJFVKlSBQBgZGSEn3/+GZ07d1ZZOCIiIiIibab0A5KZmZmoWbOmXJuVlRUyMjJKmomIiIiIqFwodrHdo0cP9OjRA7q6uggODpbb988//6B58+aqykZEREREpNWKNY1kypQp4vcODg54+PCh3P6lS5dizpw5qklGRERERKTlilVsr169+r37jx49WpIsRERERETlCl9qQ0RERESkJsVejSQ6Ohrr16/HlStXkJKSAkEQYGlpiTZt2mDChAlcY5uIiIiI6P8Uq9g+f/48unfvDktLS3Ts2BEdO3aERCJBamoqDh8+jF9//RXHjx+Hu7u7muISEREREWmPYhXbM2bMwDfffINFixYVun/evHmYPn06/v77b5WEIyIiIiLSZsWasx0eHo6pU6cWuX/q1KmIiIgoaSYiIiIionKhWHe2a9asiQsXLuCLL74odH9ISEiBF90o4sqVKzh37hwMDAwwYMAAWFtby+1/8uQJdu3ahZSUFDg7O8Pb2xu6urrFPg8RERERUWkq1p3tb775BgMHDsSoUaOwY8cOBAUFISgoCDt27MDo0aMxaNAgzJgxQ+HxZDIZfHx80LNnT6SnpyM9PR3dunXDvXv3xD4xMTFwdnbGoUOHkJ+fD19fX3Tr1g35+fnFiU5EREREVOqKdWd74sSJMDU1xYoVK7B582YIggAAkEgkcHFxwebNmzF48GCFx9uwYQP27NmDsLAwODo6AgBmzpyJV69eiX1mzZoFBwcHBAUFQUdHB2PGjEG9evWwa9euYp2LiIiI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RERERETaSqli297eHgcPHsTz58+xe/dueHp6AgBiYmJgb2+v0oBERERERNpKqWJ73rx5GD58OExNTVGrVi14eHgAADZt2oSRI0eqNCARERERkbZS6gHJAQMGoF27dnj06BGaNm0KXV1dAICXlxc6deqk0oBERERERNpK6XW2ra2txXnbb3Xt2rXEgYiIiIiIygulppEAQFBQEAYNGoSWLVuKbf7+/sjIyFBFLiIiIiIiradUsb1792707dsX5ubmCA0NFdtzcnKwfPlylYUjIiIiItJmShXbS5cuxZ49e+Dv7y/X3rt3b2zbtk0lwYiIiIiItJ1SxfaDBw/QoUMHAIBEIhHba9SogdTUVNUkIyIiIiLSckoV2+bm5oiOjgYgX2yfO3cOdnZ2qklGRERERKTllCq2fXx8MHr0aISFhUEikSAlJQVbt27FqFGjuM42EREREdH/UWrpv7lz5+Lp06dwdXVFfn4+LC0toaOjg3HjxuGbb75RdUYiIiIiIq2kVLFdoUIFrF+/HgsWLEBYWBhkMhkaN24MS0tLVecjIiIiItJaSr/UBngzd9vT01NVWYiIiIiIyhWli+3Dhw/j8uXLePbsWYF9mzZtKlEoIiIiIqLyQKli29fXF6tWrUKHDh1QtWpVVWciIiIiIioXlCq2f//9d5w6dUpca5uIiIiIiApSaum//Px8uLq6qjoLEREREVG5olSx3aFDB5w6dUrVWYiIiIiIyhWlppHY2tpi8ODBGDZsGBwcHOTeIgmAa20TEREREUHJYvv8+fP45JNPcO3aNVy7dq3AfhbbRERERERKFtthYWEqjkFEREREVP4oNWebiIiIiIg+TOliOygoCIMGDULLli3FNn9/f2RkZKgiFxERERGR1lOq2N69ezf69u0Lc3NzhIaGiu05OTlYvny5ysIREREREWkzpYrtpUuXYs+ePfD395dr7927N7Zt26bwONnZ2di6dSvGjBmDCRMmYOfOnZDJZAX63bt3DzNmzMCQIUPw448/4sWLF8rEJiIiIiIqVUoV2w8ePBDfHvnusn81atRAamqqQmPIZDI0bNgQwcHBaN68ORwdHTFz5kz06dMHgiCI/f7++280b94cz549g5ubG/bt2wc3NzdkZ2crE52IiIiIqNQotRqJubk5oqOj4ezsLFdsnzt3DnZ2dgqNIZFIcOHCBVhbW4tt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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAtsAAAGbCAYAAAAVwFxZAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjIsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvgI3uAAAAAAlwSFlzAAAPYQAAD2EBqD+naQAAX01JREFUeJzt3XdYFFfbBvB7KaIgRQ2KIMWCURFsGBVBEcXEEnvBWLEbu8Yoid0YjLEFC2o0xgSx997BFsWGRsACFlApImUVlLbz/eHrfG5oy7LLsnr/rovr3Tkzc+aeWd74OJ45IxEEQQAREREREamcjqYDEBERERF9rFhsExERERGpCYttIiIiIiI1YbFNRERERKQmLLaJiIiIiNSExTYRERERkZqw2CYiIiIiUhMW20REREREasJim4iISp0dO3Zg1qxZmo5BRFRsepoOQFSSpFIpAgMDcf36dWRlZaFmzZro3r076tevDwB48eIFxowZAwCQSCQwMjJCtWrV0KpVK7Rr1w46OvJ/PxUEAadOncKWLVtgbGyMlStXFnj877//Hg8fPsTy5cthbW2tnpPUAg8fPsT333+Pv/76C4aGhpqOQyqg6u80PDwcZ8+eVUEyIiLN4p1t+mRcvHgRtWrVwtatW9GgQQO0bdsWOTk5GDRoEPz9/QEAaWlp2L17NxwdHdG3b1+0bdsWMpkMw4YNQ5MmTRAdHS3XZ/369fHLL78gPj4eR48eLfD4q1atws6dO7F7926kpqaq7Ty1QVJSEnbv3o3MzExNRyEV4XdKRJQ33tmmT0JSUhK6du2KTp06YfPmzXLr5syZg9jYWLm21q1bw93dXVyeMWMGPDw80KtXL4SEhIjthw4dQvXq1TF37lw8ePAg3+PfunULixYtgr+/P7p06VJg1rt372LmzJlYuHAhtm/fjsjISNSpUweTJk1CXFwc1q1bh9jYWLRo0QKjRo2Su9uelpaG9evX4/r16zAxMUHv3r3Rpk2bXH2vWLECmzZtQlRUFGrXro2JEyfCyMhI4X4AIDs7G9u2bUNQUBAMDAzg5eUFNzc3yGQyDB06FEOGDJG7hsnJyRg5ciQmTpyIOXPmAAAGDx4MfX19uLq6YtKkScjOzkZgYCDOnj0LAwMDtG7dGv369ZM77vHjx3Hw4EG8efMGLi4uGDJkCHR1dQu8poVlBoCUlBQMHz4cAKCvr4/q1avD29sb9vb2Ch9fkfx5kclkGDJkCIYNG4bWrVuL7ampqRg+fDjmzp0LBwcHpc+9oPMGiv978+LFC0yfPh2A/Hf61VdfYebMmVi2bBnWrFmDx48fY9asWXBwcMCDBw/w+++/4+nTp7Czs8Po0aNhY2NT6LkQEWkb3tmmT8LmzZuRmpqKxYsX51onkUhgaWlZ4P4mJib46aefcPXqVVy7dk1sr169eqHHTktLg5eXF1atWoWqVasWun1iYiJ2796Njh07omzZsnB1dYW/vz+++uordOjQAebm5mjRogXmzZuHn376SdwvOzsbrVu3xl9//QU3NzdUqFABX375JTZt2pSrbw8PD+jp6aFVq1bYunUrevbsWaR+srOz0alTJ/zwww+oU6cO6tevj1mzZuHs2bPQ0dGBRCLBsmXL5M4rMDAQ58+fh5OTE7788ksAQM+ePeHl5YUWLVogJycHX3/9NdasWYPmzZujYcOGmD9/vlgAA8DWrVvRp08fWFtbo1WrVggJCcHo0aMLvaaFZQaAcuXKwcvLC15eXvj6668hlUrRsGFD/PvvvwodX5H8+dHR0UFGRgZWr14t175r1y6cOXMG9vb2Sp97Yeetit8bIyOjPL/T9/u1bt0a5cuXR8+ePVG5cmXcuXMHjRs3xrNnz9CuXTtERESgYcOGePToUaHnQ0SkdQSiT4CXl5dQs2bNQrd79OiRAEA4e/ZsrnXJyckCAGHDhg251s2ZMyff/ocMGSKMGDFCEARBuHr1qgBA+Pfff/PNcP78eQGAcPDgQbFt3bp1AgDh5MmTYtvSpUuFzz//XFzesGGDYGxsLCQlJYltixYtEszNzYU3b97I9X3gwAFxm0uXLgkAhMTERIX72bhxo1CuXDkhJiZG3EYmkwkvX74UBEEQLl68KOjp6QmxsbHi+saNGwvTpk2Tuw7Jycni+s2bNwu2trbiMQRBEKKjowWJRCJEREQIgiAI/fv3F7799lu56/XixYt8r+WHCsucl6FDhwre3t7ickHHVyR/Qfbv3y+ULVtWSE1NFdvatGkjjBo1qtBjF6Sw81bV701e3+n7/QIDA+Uyde7cWejcubNcHhcXF2HAgAFi25w5c4SWLVsWen5ERKUd72zTJyEtLQ2VKlUqVh/vh1mkpaUpvE9gYCAuXryI5cuXF/l4H/4zv52dXZ5tz58/F5cvXLgADw8PVKhQQWzr3bs3Xrx4gfv378v13apVK/FzzZo1AQDPnj1TuJ8TJ07Aw8MD1apVE7eRSCSoWLEiAMDFxQW1a9fGX3/9BQC4ffs2bty4gaFDh+Z7vseOHROHU/Tt2xd9+vTBlClToK+vL95dbty4MXbv3o0//vhDHPrz2Wef5dvnhwrLDPz/cImBAweiV69euHr1qty1K+j4iuQvSIcOHWBkZIQ9e/YAePd9BAcHY8CAAcU698LOW1W/NwVp27at3PLFixfl/jVFIpGgZ8+euHjxYqF9ERFpG47Zpk+ChYWF3FhrZcTFxQEAqlSpovA+S5YsgZGREQYPHgzg3bhlAJgyZQq6du2KsWPH5ruvgYGB+Pn9uOz/tslkMnH55cuXuf5C8b6gevnyZaF9v+9LkX5SU1NRuXLlfLMDwLBhw/D777/j+++/x8aNG9GiRQvUqVMn3+1TUlJQvXp19OrVS669b9++aNq0KQBg8uTJqFixIrZt24aJEyeiRo0aWLx4sTiEoSCFZb58+TLc3d3h7e2NNm3awMTEBPv27cOdO3fEbQo6viL5C6Kvr4/evXtjy5YtGDJkCLZu3QobGxu0bNmyWOde2Hmr6vemICYmJuLn7OxspKamyhX374/53+MREX0MWGzTJ+HLL7/E77//josXL4rFS1EdOHAAurq6cHV1VXifxYsXIyUlRVx++PAhzpw5gw4dOqBZs2ZK5ciPnZ0dQkND5dqioqLEdarsx9bWFrdu3Sqwn0GDBsHHxwdBQUHYsmULfvnlF3GdRCLJtb21tTUuX76cq1j9kEQiwZAhQzBkyBBkZWVh3rx56NmzJ5KTk6Gvr19gnsIyb9myBR07dhRnpgGAU6dOKXx8RfIXZsCAAWjVqhViY2OxZcsW9O/fX7xWyp57Yeetqt+bvL7TvOjp6cHKyko8xofHLMrxiIi0BYeR0Cehe/fuaNmyJUaNGoUnT57IrQsPD8fx48cL3P/EiRP48ccf8e2338LKykrh47Zr1w69evUSfzw8PAAAnp6ecHZ2LvqJFMDLywsXL17E+fPnAbx7YM/X1xfNmzdX6EHOovQzaNAgXLlyBbt27RL3i4yMxO3bt8Xlzz77DN26dYO3tzfevn2Lvn37yq0D3s1r/p63tzfu3LmDtWvXim2CIGDr1q14/fo1AGDbtm3ivw7o6+ujSZMmyMzMRHZ2NgBg/Pjx2LhxY57nVVjmMmXKIC4uDoIgAADu3buHwMBAuT4KOr4i+Qvj4uICW1tbzJ49G6GhoeIQEkXOPT+Fnbeqfm/y+k7z069fP6xduxZJSUkAgNjYWGzcuFGhmVuIiLQN72zTJ0FHRweHDx/G2LFjUadOHTg7O8PU1BRRUVEoW7ZsrpfRzJkzB+bm5nj79i3u3r2L1NRUTJ06FT4+PnLbzZo1CxEREQgPD0dcXJx4V3P9+vVyY4FLgouLC2bOnImvvvoKzZo1w7Nnz/D27VscPnxY5f24uLhg5cqVGDx4MBYvXgxDQ0NxnuUPjRgxAjt27MCQIUNQvnx5sf398IiOHTvCyckJbm5umDRpEn7//XdMmjQJ/v7+qFq1KsLDw9GqVStxfG9aWhoaNGgAOzs7lC1bFleuXIGvry/KlSsH4N1bBz8cm/zf8yoo87hx47B161bUr18flpaWuHnzJhwdHeXG6Bd0/ObNmxeavzASiQTffPMNFi5ciCZNmsgNuyns3PNT2Hmr6vcmr+80v79Qzpw5E1euXEHdunXRsGFDXLt2Dc2bN8ekSZOKdEwiIm0gEd7fxiH6RCQmJiI0NFR8g6S9vb34T+Dp6ek4cuQIgHeFT7ly5WBtbY26detCTy/3302DgoKQmJiYq71Tp055FkHJyck4ffo02rdvLzeO9UMvX77E2bNn0aNHD3FcbEJCAs6dOyc3ROH58+cICQlBt27d5PaPiYnBrVu3YGxsjObNm8uNs82r78zMTBw4cACenp4wNTVVqJ/3kpKSEBISAmNjYzg7O+faJiYmBjY2Njh37pzcw53AuzuoV65cQVxcHKysrMRhNa9fv0ZISAiysrLg5OSUa7rEtLQ03LhxAxkZGXBychLHI9+6dQsdO3bE/fv35eYML0rm169f48qVK8jKyoKzszOSkpIQHR2Ndu3aFXr8D/soKH9h4uPjcf78edSuXRtOTk4KnbsiFPmuivt789/vtFatWrn2+9CNGzfEebb/e67h4eFISEiQm6udiEgbsdgmIrX58ccfcfDgQbnhJery5MkTpKSkoEGDBmo/FhERkaI4jISIVO706dNYunQpTp8+jf3795fIMW1tbWFra1six1KGn58fzp07l+e6Vq1aYcKECaWqXyIiUg3e2SYilXv8+LE45rlWrVqajlMqXL16NdfDue/Z2toqND1gSfZLRESqwWKbiIiIiEhNOPUfEREREZGasNgmIiIiIlITFttERERERGryScxGIpPJ8Pz5cxgbGyv8SmEiIiLSLEEQ8OrVK1haWuY5VzuRNvgkiu3nz5/D2tpa0zGIiIhICTExMfm+HZaotPskim1jY2MA7/7Pmt9b+4iIiKh0kUqlsLa2Fv8cJ9JGGi22k5OT4ePjgyNHjiA5ORk2NjYYPXo0xo8fL7fdypUrsWLFCsTHx8PR0RHLli1DixYtFD7O+6EjJiYmLLaJiIi0DIeAkjbT6ACokSNH4ty5czh8+DCeP3+OuXPnYurUqQgMDBS32bRpE6ZPn46lS5ciKioKLi4uaN++PWJiYjSYnIiIiIiocBottq9du4Y+ffrA0dERxsbG6N27N+rWrYurV6+K2/z6668YOnQounXrhipVqmDJkiUwMTGBv7+/BpMTERERERVOo8V2nz59sHfvXkRFRSE7OxtHjhzBw4cP0aNHDwDvhplERESgTZs24j4SiQRt2rTBpUuXNBWbiIiIiP7nwYMHaN68OV6/fg0AuH37Npo3b47MzEyN5Fm5ciUmT56skWPnRaNjtn/++WdER0ejVq1aAIAyZcpg/fr1cHNzAwDExsYCACpXriy3n7m5Oa5du5ZvvxkZGcjIyBCXpVKpqqMTERERFdvDhw+xatUqhIeHo2zZsnBxccGoUaNgamqq6WgKS0tLw5UrV5CdnQ3gXd115coVyGQytR97xYoVePr0KZYsWSK2PXnyBBEREWo/tqI0Wmx7e3vjzp07uHHjBmrWrIkTJ05g0KBBMDY2Fu9u50VHRweCIOS73tfXF/PmzVNHZCIiItIiDj4lV3SF+dYt0vb37t3DF198gU6dOmHChAkAgIsXL6JFixYIDw9XR8QS0aBBA/zzzz8wMDBQ+7EeP36MyMhIubYJEyYgPT1d7cdWlMaGkSQmJuLvv//G3Llz0ahRI5iYmKBXr17o0aMHli9fDgCwsLAAALx48UJu34SEBFSpUiXfvn18fJCamir+8GFKIiIiKm02b94Mc3NzBAYGomPHjujYsSMWLlyIkJAQcZunT5+iefPmaN68OVq1agVvb2+5Z9sA4PLly3B1dcWlS5cwbNgweHh4YMKECUhOTsbFixfh5eWFtm3bYt68ecjKypLbNzU1FfPmzUO7du3QpUsX/Pbbb+Id6vxERkZi8ODBaNeuHSZMmCCORHjv0aNHmDRpknis9/lu3bqFb775Bq6urrh79y4AICwsDCNGjIC7uzv69++PU6dO5TpeeHg4Ro8ejTZt2mDYsGF48OABAODPP//E9u3bcfHiRfEaXbx4Efv378e6devk+ggKCoKXl5d4De/cuSO3fvHixfjhhx+wbds29O3bF1999RX8/PxUcndeY8X2+zdB/Xc6Hx0dHejq6gIAKlasiNq1ayM4OFhcLwgCgoKC4OLikm/fBgYG4jR/nO6PiIiISqPs7Gykp6fnugtbvnx58bO5uTlWrFiBFStWYMGCBahRowbc3NwQGhoqbpOSkoKLFy9i5MiR+PLLLzF58mQcPnwYbdq0wdixY9GrVy+MHz8eGzZswIIFC8T90tLS4OrqirCwMEydOhXDhg1DQEAABg4cmG/m1NRUuLq6Ij09Hd999x2srKzQt29fuW3+O4zkfb4+ffqgY8eOWLJkCaytrXH58mW4ubnBxsYGs2fPhpubG/r06YPt27eLfV24cAHOzs6QyWSYPn06XF1dMWDAAACAp6cnWrduDQcHB/EaOTg45BpGcurUKbRv3x61a9fGjz/+CF1dXTRv3lzujvjDhw+xfPlybNu2Dd7e3ujXrx9mz56NNWvWKPJVFkhjw0gqVqyIVq1aYcGCBahXrx5q1KiBEydOYOfOnfD19RW3mzp1KqZMmYKOHTuiefPmWLx4MZKSkjB69GhNRSciIiIqtuHDh2PDhg2oU6cOunbtiubNm8PDwwNVq1YVtzEwMEDz5s3F5datW+Phw4fw9/fPdfd206ZNaNq0KYB3L/IbO3Ys/v33X9SvXx8AcP/+fWzduhXz588HAKxZswZlypTB9u3bxZufTZo0gbW1NRYsWCA+U/ehVatWwdDQENu3b4eOjg6++uorJCYmyo2Zzs/atWvlJr2YOnUqJk2ahFmzZgEAPDw8kJ2djQULFogF/NSpU9G9e3esX79e3K9fv34AACsrK1hYWOD169dy1+i/fvjhB3h7e4vn/eWXX+LevXuYP38+/vrrL3G7KlWqYOfOndDX1wfw7kHP/fv3Y9y4cYWeW0E0OmZ7+/btmD59Otzd3ZGUlAQbGxvMnTsXEydOFLcZOXIkUlJS4O3tjYSEBNSvXx9HjhyBnZ2d5oITERERFVPt2rVx7949/P333zhz5gy2bduGly9fYuTIkVizZo04CuDkyZP4+++/ER0djbdv3+Lp06eoW1d+fLiOjg4aN24sLletWhUGBgZiof2+LS4uTlw+e/Ysnj17Bjc3NwiCIP7o6Ojg7t27eRbb165dg4eHh5gNANq3b69Qsf3hCwnfvn2Ly5cvIyUlBSdOnBCPnZKSgsjISAiCgDdv3uDq1auYNm2aXD9ly5Yt9Fjv5eTkIDQ0FD4+PnLtHTp0QEBAgFxbgwYNxEIbAKpVq4YTJ04ofKz8aLTYtrCwwObNmwvd7vvvv8f3339fAomIiIiISo65uTmmTJmCKVOmQCaT4Y8//sCIESPg5uaG/v37Y9++fejfvz/mzJmDAQMGwMTEBGvXrs31UKBEIhGH4b5f1tPTy7XNhxNMvL8jPGPGjFy5ateunWdeqVQKGxsbuTYjI6NCz1NXV1euSE5PT4dMJsPw4cPzfCu4RCLB27dvIQhCsYYDv3nzBllZWXJDc4B3Q3VevXol1/Zhof0+Q0ETcihKo8U2EREREb2jo6OD4cOHY/r06bh37x4AYPfu3ejXr5/cTcelS5eq5Hi1atVCaGhogUMw/qtGjRpitvfeP+xYFBUqVEClSpUglUrzPf77bW7fvo327dvnuU1hM9SVL18elStXRkREBDw9PcX2iIgI1KxZs8i5laHRl9oQERERfao2b96Mo0ePyhWLe/fuRVJSEpo1awbgXcEZFhYmzuxx6tQp7N27VyXHHzNmDG7fvo1ffvlFzJCeno6ffvop3xfSeHt74+TJkzh79iwA4OXLlwoNIfkviUSCb7/9FsuXL8eVK1fE9ocPH2L16tXiNqNGjcLSpUsRFhYGAJDJZFi5cqW4fZUqVfDs2bMCjzVs2DD89ttv4qwpt2/fRkBAAIYNG1bk3MpgsU1ERESkAfXr18eaNWtgamoKJycn2NraYtiwYfj555/RqVMnAMC0adOQlJQEa2tr1KlTB0OGDEHbtm1VcvymTZti+/bt8PPzg4WFBRwcHGBtbY3MzMxcQyrec3FxwaxZs/Dll1+ibt26sLe3z3MYiCLmzJmD4cOHo02bNrC3t4ednR2++uor2NraitvMnTsX3bt3R5MmTVCnTh1UrlwZT58+Fdf37dsX8fHxsLe3F6f++6+ZM2eiYcOGqFmzJurVq4dmzZph6NCh4qwm6iYRVDEYpZSTSqUwNTVFamoqpwEkIiLSEp/Kn9/p6emIioqCkZERqlWrhjJlysitz87OxoMHDyCTyfD5558jLi4OUqkU9erVA/BuOr67d++Kd8MBIDk5GZGRkeLsJMC7d5xER0fLPUgJvLtbfP/+fQiCAHt7+1xjvfPy4sULxMXFwd7eHjKZDLdv30bTpk2hq6uLV69eISwsDM2aNYNEIskz34fS0tLw4MEDVKpUCdbW1nluk5KSgsePH6NGjRq5fhcyMjIQFRUFqVSKOnXqQCqVIj09HXXq1JHbLjY2FrGxsbCzs0PFihXl1j169AjZ2dmwt7cX2+Li4pCQkAAnJ6dCr0dBWGwTERFRqcQ/v+ljwGEkRERERERqwmKbiIiIiEhNWGwTEREREakJ59kuRQ4cOCD3OlIA+OKLLzB79mwAwNWrV7Fhwwa8efMGX331Fb755htNxCQiIiIiBbHYLkUcHR0xevRocXnWrFmoUKECgHeF9owZM9C/f3/IZDL4+PggIyMD3t7emopLRERERIVgsV2KVK9eHdWrVwcAJCQkICoqCv379wcA1KlTB6dOnYJEIgHwrvh+9OiRxrISERERUeFYbJdSmzdvRufOncV5II2NjZGQkIChQ4fixYsXqFSpEn755RcNpyQiIiKigvAByVJq48aNuV4jamxsjNGjR2PIkCEIDw8XX5VKRERERKUTi+1S6Pz588jIyICHh4dce7ly5dC5c2eMGTMGs2fPxpo1azSUkIiIiD5VcXFxmDt3Lt6+favWfT4WLLZLoQ0bNmDo0KHi+GwAOHv2LJKTk8XlW7duwdLSUhPxiIiISAXCw8PzLUCXLFmCc+fOaSBV4eLi4jBv3rwiF9tF3edjwTHbpYxUKsWePXsQHh4u166vr4/mzZujatWqiI+PR05ODo4fP66hlERERNohcUzTEjvWZ/5Xi7R9eHg45s2bh0mTJqFs2bJy65YsWYJx48ahVatWqoxIGsBiu5TJzMzEwYMHYW1tLdfu6uqKmzdv4vr16zA2NoajoyN0dXU1lJKIiIhKSmRkJAIDAzF16lQcPnwYjx49QqNGjdC+fft890lKSoKfnx8AoEyZMqhZsya+/vprGBoa5ur3+++/F/utV68eOnbsKNdXdnY2du7ciWfPnqFevXqoWrVqoZkV2UcQBBw9ehS3b9+GmZkZunbtKrfdsmXL0K5dOzg5OQEA9u3bhzt37uDHH3+ERCKBTCbDwoULMWjQIGRlZSl0LprAYSSlzGeffQZ3d/c81xkaGsLNzQ0NGzZkoU1ERPSJiIyMxNy5c/HFF19gz549ePLkCXr37o0pU6YotP+rV6/g5+eH+vXrIzExMVe/Li4u2L9/P54+fYoBAwbgu+++E7eRyWRo164dfHx88PTpUyxZsgQDBgwo8HiK7CMIAjp16oSRI0ciNjYW+/fvR+3atXHx4kVxm9OnT+Ovv/4Sl+fMmYNZs2YhLCwMwLshtXPnzoWJiYlC56IpvLNNREREVMoJgoB+/fph5syZAIA+ffqgbdu2GDZsGBwcHHJtX7FiRcydO1eurW3btli+fDkWLlwo1++0adPQr18/AO/+Jf2bb76Br68v9PX1sWXLFty4cQORkZGoXLkyBEFA165dcw13/ZAi+2zZsgXBwcG4d+8eqlWrBgAYNmwYxowZg1u3bkEikcDd3R3bt28H8O5OfUREBNq1a4egoCDUr18fQUFBcHJyEl8AWNi5aArvbBMRERFpgQ/fGu3u7o7q1avjxIkT+W4vlUoRGBiIRYsWYe7cucjKysLt27dzbdetWzfxc8OGDZGVlYXnz58DAI4ePYpOnTqhcuXKAACJRIKhQ4cWmFORfY4ePYoOHTqIhTYAjBo1Cv/++y+ePXsmnuPNmzeRkpKC4OBgNGzYEF26dBGnPg4KCso1GqCgc9EUFttEREREGqCj864My8nJybUuJycn15BRCwsLueWqVavmW0iGhYXB1tYWGzZsQEJCgni8D2c2AwBdXV2UK1dOXNbTezfoISsrCwAQGxub53ELosg+z58/z9X2fpa198V248aNUb58eZw7d04srN3d3REcHIycnBycP39ertgu7Fw0hcNIiIiIiDTg/WQIcXFxqFSpktielZWFly9f5posIS4uDlZWVuJybGxsvtMAr1q1Cq1atcL+/fvFtpEjRxY4/CMvVatWRVxcnFxbbGxssfextLTM1fZ++f056urqwtXVFUFBQQgKCsKiRYtQv359SCQSBAQEIDU1VStma2GxrSQHnwhNR1CrMN+6mo5ARET0UXNwcEDlypURGBgoN45669at0NfXh4uLi9z2mzZtEsdsBwcH4+HDh/D09Myz7/T0dLnpBBMSErBnzx7UqVOnSBm/+uorjB8/HgkJCeL46z/++KPY+3To0AGjRo3C8+fPxb8wrF+/Ho6OjnJ/oXB3d8fatWvx9OlTuLq6QiKRoFWrVliwYIHceO3SjMU2ERERkQYYGhri999/x6BBgxAaGor69evjyZMnOHjwIJYsWYIaNWqI20okEgQGBiIsLAwVKlTAli1bMH78eNSvXz/Pvr29vfHll1/im2++QeXKlbF7926YmpoWOWP//v2xYcMGfPHFF+jevTtu3bqF+Pj4Yu/Tv39/BAYGolmzZujZsycePHiA4OBgHD9+XO6lfu7u7vj+++/RrFkzGBsbi2179uzBpEmTinw+msBim4iIiEhDunTpgvv37+P06dOIiYlBvXr18PPPP8sV2sC78dZXr17FoUOH8PjxY+zYsQNffvllvv26u7sjNDQUx44dg0wmw759+/Dq1Ss8fPhQ3KZWrVqYPXu23H5mZmaYM2cOKlasCODdUI7Tp0+Lc2a3a9cOjRo1wvr163O9iOc9RfaRSCQ4fPgwjh49ilu3buHzzz/Hhg0bco3jbty4MebNm4fGjRuLbV27dsXLly/RvXv3Ip2LpkgEQRA0mqAESKVSmJqaIjU1FSYmJirpk8NIiIiI1Esdf35ro2PHjqFz587Izs7WdBRSAmcjISIiIiJSExbbRERERKVYXkMkSHuw2CYiIiIqxVhsazeNPiDZvn17pKen52rv2rUrpk2bJi5funQJq1evRnx8PBwdHTFjxgxUqVKlJKMSERERERWZRovtefPmyb01KSIiAiNHjsSECRPEtnPnzqFdu3aYMmUK+vTpg1WrVqFly5YIDQ1F+fLlNRGbiIiIiEghGi22W7RoIbe8d+9emJuby73X/scff0S3bt2waNEiAEC7du1QtWpVrF+/HlOmTCnJuERERERERVJqxmxnZmbi77//xuDBg1GmTBkA795+dOnSJXTp0kXczsjICO3atcPJkyc1FZWIiIiISCGlptg+cOAAXrx4geHDh4ttMTExkMlkcq/tBAArKytER0fn21dGRgakUqncDxERERFRSSs1xfbGjRvRqlUrfP7552JbVlYWAOR6Q1G5cuWQmZmZb1++vr4wNTUVf6ytrdUTmoiIiIioAKWi2H769ClOnDiBESNGyLW/f73my5cv5dpfvnxZ4Ks3fXx8kJqaKv7ExMSoPjQRERERUSFKRbG9adMmmJiYoFevXnLtlpaWqFKlCq5fvy7XHhISgkaNGuXbn4GBAUxMTOR+iIiIiIhKmsaLbUEQsGnTJgwcODDXcBEA8Pb2xoYNGxAbGwsA2LdvH+7cuQNvb++SjkpEREREVCQanfoPAM6cOYNHjx7JPRj5oTlz5uDevXuoVasWbG1t8fjxY/z2229o1qxZCSclIiIiIioajRfb9vb2+Oeff+Dk5JTn+rJly2LPnj2Ijo5GfHw8ateuDVNT0xJOSURERERUdBovtm1sbGBjY6Oy7YiIiIiISguNj9kmIiIiIvpYsdgmIiIiIlITFttERERERGrCYpuIiIiISE1YbBMRERERqQmLbSIiIiIiNWGxTURERESkJiy2iYiIiIjUhMU2EREREZGasNgmIiIiIlITFttERERERGrCYpuIiIiISE1YbBMRERERqQmLbSIiIiIiNWGxTURERESkJnrF2fnly5cICAhAZGQkLC0tMXjwYFhaWqoqGxERERGRVitSsV2rVi1ERkYCAO7fv49WrVohLS0NdnZ2ePz4MZYsWYIrV66gVq1aaglLRERERKRNijSMJCoqSvw8ffp0NG3aFLGxsfj333/x/PlztGjRAjNnzlR5SCIiIiIibaT0MJJLly7h/PnzKF++PADA2NgYy5YtQ7t27VQWjoiIiIhImyn9gGRqaiqqVq0q12ZpaYmUlJTiZiIiIiIi+igUudju3LkzOnfuDF1dXQQFBcmt+/fff9GkSRNVZSMiIiIi0mpFGkYyceJE8XOtWrXw8OFDufULFy7EDz/8oJpkRERERERarkjF9ooVKwpcf+jQoeJkISIiIiL6qPClNkREREREalLk2UgiIyOxevVqXLp0CfHx8RAEARYWFnBxccHYsWM5xzYRERER0f8Uqdg+e/YsOnXqBAsLC3h4eMDDwwMSiQQJCQnYv38/1q1bhyNHjsDd3V1NcYmIiIiItEeRiu2pU6fiu+++w/z58/NcP3v2bEyZMgU3btxQSTgiIiIiIm1WpDHbYWFhmDRpUr7rJ02ahPDw8OJmIiIiIiL6KBTpznbVqlVx7tw5dOvWLc/1wcHBuV50o4hLly7hzJkzMDQ0RN++fWFlZSW3/sWLF9i2bRvi4+Ph6OiIXr16QVdXt8jHISIiIiIqSUW6s/3dd9/By8sLI0aMQGBgIE6ePImTJ08iMDAQI0eORL9+/TB16lSF+5PJZPD29kaXLl2QnJyM5ORkdOzYEXfv3hW3iYqKgqOjI/bt24ecnBz4+PigY8eOyMnJKUp0IiIiIqISV6Q72+PGjYOZmRmWLFmCjRs3QhAEAIBEIoGTkxM2btyI/v37K9zfmjVrsGPHDoSGhsLe3h4AMG3aNLx580bcZvr06ahVqxZOnjwJHR0djBo1CrVr18a2bduKdCwiIiIiopImEd5XzEWUlpaG+Ph4AECVKlVgZGRU5D7q1KkDNzc3/P7773muz87OhrGxMZYtW4YxY8aI7Z6enjAzM8POnTsVOo5UKoWpqSlSU1NhYmJS5Jx5cfCJUEk/pVWYb11NRyAiok+cOv78JippRZ5n+z0jIyPUqFFD6QO/evUK9+7dww8//IB9+/bh+vXrsLS0RI8ePVClShUAwJMnT/D27VvUrFlTbt+aNWvin3/+ybfvjIwMZGRkiMtSqVTpnEREREREylLpGyRr1aql8EttUlNTAQBLly6Fv78/9PX1cejQIdjb2yMkJAQAkJ6eDgC5/jZramqKtLS0fPv29fWFqamp+GNtba3M6RARERERFYvSd7bz8tVXXym8rbGxMQCgYsWKOH78uNjeoUMH+Pj44PTp0yhfvjwAICUlRW7flJQUcf+8+Pj4YMqUKeKyVCplwU1EREREJU6lxfaqVasU3tbU1BSWlpZo2rSpXHvTpk3x999/AwBsbGxgZGSEe/fuyRXyd+/eRd26+Y8pNjAwgIGBQRHTExERERGplkqHkRSVl5cXgoODIZPJAACCICAoKAhOTk4AAF1dXfTo0QObN28Wx2DfuXMHFy5cQO/evTWWm4iIiIhIEUWejSQyMhKrV6/GpUuXEB8fD0EQYGFhARcXF4wdO1bhMdvAu3Hbbdu2RU5ODlq0aIGQkBC8ePECZ86cER+KjI2NhZubG8qXL4/GjRvj0KFDaN++PQICAhQ+DmcjKTrORkJERJrG2UjoY1CkYvvs2bPo1KkTLCws4OHhAXNzc0gkEiQkJODMmTOIi4vDkSNH4O7urnCArKwsHDt2DE+ePIGNjQ3at2+PsmXLym2Tnp6OQ4cOiW+QLEr/AIttZbDYJiIiTWOxTR+DIhXbjRs3RufOnTF//vw818+ePRuHDh3CjRs3VBZQFVhsFx2LbSIi0jQW2/QxKNKY7bCwMEyaNCnf9ZMmTUJ4eHhxMxERERERfRSKVGxXrVoV586dy3d9cHAwqlatWuxQREREREQfgyJN/ffdd9/By8sLAwcORJs2bWBubg4AePHiBYKCgvDXX39hyZIlaglKRERERKRtilRsjxs3DmZmZliyZAk2btyI98O9JRIJnJycsHHjRvTv318tQYmIiIiItE2RX2ozYMAADBgwAGlpaYiPjwcAVKlSBUZGRioPR0RERESkzZR+g6SRkRFq1KihyixERERERB8Vjb5BkoiIiIjoY8Zim4iIiIhITVhsExERERGpCYttIiIiIiI1UarYDg8PR2pqaq721NRUvkGSiIiIiOh/lCq2x48fjx9//DFX+w8//IAJEyYUOxQRERER0cdAqWJ72LBhCAwMREZGhtiWkZGBrVu3YtiwYSoLR0RERESkzZQqtnv06AFBELB//36xbd++fQCA7t27qyQYEREREZG2U6rYLlu2LPr3749NmzaJbZs2bUL//v1RtmxZlYUjIiIiItJmSr9BctiwYXB2dsazZ88AACdPnoSvr6/KghERERERaTuli+1GjRqhQYMG+PvvvyEIAho2bIhGjRqpMhsRERERkVZTutgG3t3d9vPzAwBMnDhRJYGIiIiIiD4WxXqpTf/+/REdHY3o6Gh88803qspERERERPRRKNadbTMzM/zyyy+QSCQwMzNTUSQiIiIioo9DsYptAHyJDRERERFRPoo1jISIiIiIiPLHYpuIiIiISE1YbBMRERERqQmLbSIiIiIiNSl2sS0IAmQymSqyEBERERF9VJQutv/88080aNAA5cqVg6GhIRo2bIi//vpLldmIiIiIiLSaUlP/zZ8/H0uWLMG3336L2bNnAwCuXr2KcePGISYmBj/++KNKQxIRERERaSOliu2VK1di27Zt6Nixo9jWs2dPtGrVCt7e3goX2zdv3kRUVJRcm6mpKTw9PeXaBEHAtWvXEB8fj/r168POzk6Z2EREREREJUqpYjsnJweurq652l1dXZGdna1wP+vWrcOhQ4fQvHlzsc3Gxkau2JZKpejYsSOioqJQp04dhISEYPLkyfjpp5+UiU5EREREVGKUKrZbtmyJrVu3YtSoUXLtW7duRcuWLYvUV/PmzbFr165818+cORPx8fGIiIiAmZkZgoOD4e7uDg8PD3h4eCgTn4iIiIioRChVbNeoUQNjxozB/v370bRpU3GYx7FjxzBhwgQsWbJE3Pa7774rsK/U1FQcOXIE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B4+JiIgKUaQx21ZWVrhz506+6//991/xxTREpH2ePHmCBw8eYPLkybn+4jx58mTx+YzHjx/jzZs3GkpJRESkPYp0Z7tDhw6YPXs2Tp06BR0d+To9JycH8+bNk5s1hIi0y9KlS/H69WusWbMmz/X/vbtNREREBStSse3j44NGjRqhUaNGmDhxImrXrg1BEHD//n389ttviIuLw/r169WVlYg0bPPmzdDT04O7uzusrKw0HYeIiKjUK9IwkmrVquHcuXMwNjbGsGHD4ObmhlatWmH48OEwNTXF+fPnYWlpqa6sRKRBEydOxI0bN7Br1y7Uq1cPp0+f1nQkIiKiUq/Ib5CsW7cuLly4gJiYGNy/fx8AULt2bT4oRfSR+3AIyapVq+Dr6ytO0UlERER5U/p17dbW1iywiT5RpqamCs88RERE9ClTutgmoo9PQEAAnj9/DuDd3WsrKyv069cP9+/fx5EjRwC8m7Fk06ZNWLdunSajEhERaQUW20QkevbsGWJjYzFx4kQ8ffpUnHUoLS0Njx8/hkQiQeXKlXHu3Dk4OTlpOC0REVHpx2KbiETTp0/Ps/39LERERERUNEWajYSIiIiIiBTHYpuIiIiISE1YbBMRERERqQmLbSIiIiIiNeEDkkRayMEnQtMR1CrMt66mIxAREakE72wTEREREakJi20iIiIiIjVhsU1EREREpCYstomIiIiI1ITFNhERERGRmrDYJiIiIiJSExbbRERERERqwmKbiIiIiEhNWGwTEREREakJi20iIjV5+fIlTp06hdDQUE1HISIiDeHr2omI1ODYsWP45ptv0LhxYzx8+BDu7u74448/NB2LiIhKGO9sExGpgY+PD1auXIlTp07h7t27uHDhAq5du6bpWEREVMJYbBMRqUFSUhJq1qwJAChTpgysra1x+vRpDaciIqKSxmEkRERq0L17d4wfPx4TJ07E/fv3cevWLTg5OWk6FhERlTCN39nOycnBoUOHsGTJEsTExOS5TXp6Onbu3IlVq1YhODi4hBMSERXdr7/+ikGDBuHo0aMwNDRE586dYW5urulYRERUwjR6Z3vXrl2YNm0arK2tcf78eTg7O8Pa2lpum9jYWLRq1QpGRkZo1KgR5s+fj/bt2yMgIEBDqYmICqevr4/x48dj/PjxePPmDapXr47BgwdrOhYREZUwjRbb5ubmCAoKgq6ubq4i+73p06fD2NgY//zzDwwMDHDnzh00aNAAvXr1Qrdu3Uo2MBGRgh49eoRbt24hNTUVGzZsQJMmTdCmTRtNxyIiohKm0WEkrVu3hq2tbb7rc3JysGfPHgwePBgGBgYAgPr168PV1RU7duwoqZhEREUWERGBP//8E4cPH0aPHj2we/duTUciIiINKNUPSMbExCAtLQ2ff/65XHudOnUQEhKS734ZGRnIyMgQl6VSqdoyEhHlpWPHjujYsaOmYxARkYZp/AHJgrx69QoAYGZmJtduZmYmrsuLr68vTE1NxZ/8hqgQEREREalTqS62DQ0NAeS+M52amgojI6N89/Px8UFqaqr4k98sJ0RERERE6lSqh5HY2trCwMAAUVFRcu1RUVGwt7fPdz8DAwNxjDcRERERkaaU6jvbenp66NSpE7Zs2QKZTAYAePz4MYKDg9G9e3cNpyMiIiIiKphG72yHhYXh6NGjSE1NBQBs374d165dg4uLC1xcXAAAixcvhouLC9q3b48vvvgC27ZtQ5s2beDl5aXJ6ESkxRx8IjQdQa3CfOtqOgIREf2PRu9sv3nzBnFxcXjz5g2mTp0KIyMjxMXF4fXr1+I2NWvWxJ07d9ClSxdIJBL8/PPPOHLkCHR1dTWYnIiIiIiocBq9s+3s7AxnZ+dCtzM3N8eECRNKIBERERERkeqU6jHbRERERETajMU2EREREZGasNgmIiIiIlITFttERFSq3b9/P9f7FoiItAWLbSIiKrXu3LmDpk2bYurUqZqOQkSkFBbbRERUKmVnZ2PcuHEYP368pqMQESmNxTYREZVKP//8M7y8vGBnZ6fpKERESmOxTUREpc6tW7dw+fJljBo1StNRiIiKhcU2ERGVOkOHDsWwYcNw/fp1PHnyBCkpKQgLC9N0LCKiItPoGySJiIj+KycnBxKJBL6+vgCAxMREpKamYsGCBdi2bZuG0xERFQ2LbSIiKlV0dXVx7do1cXnDhg04dOgQC20i0kocRkJERKWaubk5atWqpekYRERK4Z1tIiIq1bp27YquXbtqOgYRkVJ4Z5uIiIiISE1YbBMRERERqQmLbSIiIiIiNWGxTURERESkJnxAkoiIisTBJ0LTEdQqzLeupiMQ0UeEd7aJiIiIiNSExTYREdFH6vXr13j16pWmYxB90lhsExERfWQEQcDo0aNhbm6OKlWqwNvbGzKZTNOxiD5JLLaJiIg+MkeOHMHJkycRExODuLg4XLt2DXv37tV0LKJPEottIiKij8z+/fsxaNAgfPbZZzAxMcHw4cOxZ88eTcci+iSx2CYiIvrIPHv2DLa2tuKyra0tnj59qsFERJ8uFttEREQfGT09PWRnZ4vLWVlZKFOmjAYTEX26WGwTERF9ZKpXr46IiP+fDz0iIgLVq1fXYCKiTxdfakNERPSRGThwIDw9PdG+fXuULVsWa9aswe7duzUdi+iTxGKbiIjoI9OkSRP8+uuvmDZtGmQyGWbPno2WLVtqOhbRJ0lriu2YmBjEx8ejdu3aMDEx0XQcIiKiUm3YsGEYNmyYpmMQffJK/Zjtt2/fomfPnvj8888xcOBAWFhYYOXKlZqORURERERUqFJ/Z3vevHkICQlBVFQUqlatin379qF79+744osv0KxZM03HIyIiIiLKV6m/s71p0yYMHz4cVatWBQB069YN9evXx6ZNmzScjIiIiIioYKX6zvbz588RHx+PJk2ayLV/8cUXuHnzZr77ZWRkICMjQ1xOTU0FAEilUpVly8l4rbK+SiNVXqu88PoVD69f8fD6FQ+vH5WU99+FIAgaTkKkvFJdbCclJQEAKlWqJNdeqVIlcV1efH19MW/evFzt1tbWqg34ETNdrukE2o3Xr3h4/YqH1694eP1Kn1evXsHU1FTTMYiUUqqLbX19fQDvHpL80Js3bwp8E5aPjw+mTJkiLstkMiQlJaFSpUqQSCTqCatGUqkU1tbWiImJ4UwsSuD1Kx5ev+Lh9SseXr/i0fbrJwgCXr16BUtLS01HIVJaqS62ra2toaOjg2fPnsm1P3v2DDY2NvnuZ2BgAAMDA7k2MzMzdUQsUSYmJlr5H8vSgteveHj9iofXr3h4/YpHm68f72iTtivVD0gaGhrCxcUFBw4cENvS0tJw6tQpeHp6ajAZEREREVHhSvWdbQD46aef4OnpCR8fH7Ro0QIrV65E5cqVMXLkSE1HIyIiIiIqUKm+sw0ArVu3xtmzZ/HkyRP89ttvcHBwwIULF1C+fHlNRysxBgYGmDNnTq6hMaQYXr/i4fUrHl6/4uH1Kx5ePyLNkwicT4eIiIiISC1K/Z1tIiIiIiJtxWKbiIiIiEhNWGwTEREREalJqZ+N5FOXnp6OmzdvwtrausC5xSlvsbGxSEhIQI0aNWBsbKzpOFpHKpUiMjISVapUgZWVlabjaK3bt29DKpXCxcUFOjq8x1EYQRBw8eLFXO329vaoUqWKBhJpr5SUFERFRaFWrVqcr5pIQ/hf/VIqPj4ekyZNQq1atdCuXTusX79e05G0yqlTp+Ds7IwmTZpg0KBBsLCwwNSpU8HngRUTGxuL/v37o0aNGhgxYgTq1asHFxcXREdHazqa1jl//jycnZ3h5uaG9PR0TcfRChkZGXBzc8O3336LGTNmiD9Xr17VdDStkZWVhbFjx8LS0hIjR46Eo6Mjfv75Z03HIvoksdgupR49egQ7OzuEhYXB1tZW03G0TlRUFNavX4/nz5/j1q1bOHfuHPz9/fH7779rOppWePr0Kfr27YsXL17g+vXreP78ObKysjBhwgRNR9MqSUlJGDRoEK+bktasWYMLFy6IP507d9Z0JK0xYcIEHDx4EP/++y+uX7+OR48eoVKlSpqORfRJ4tR/WqBOnTro1asXfvrpJ01H0Wpt2rSBtbU1/vrrL01H0Upjx45FSEgI7y4WQbdu3dCoUSM4ODigd+/eePXq1Sf1jgBlvX37FuXKlcO2bdtQq1Yt1KhRAxUqVNB0LK0RExMDOzs7/Pnnnxg4cKCm4xB98nhnmz4J6enpCA8PR61atTQdRatcv34dQUFB+O2337Bjxw7MmjVL05G0xsqVK5GQkICZM2dqOorWGjduHLy9vWFhYYHevXsjKSlJ05G0QlBQEGQyGTp37ozo6GiEhobi9evXmo5F9MlisU2fhAkTJkAQBIwaNUrTUbTKr7/+imnTpmHWrFnw8PBAy5YtNR1JK4SGhmL+/PkICAiArq6upuNoHR0dHWzatAkJCQm4ffs27t69i5s3b2LMmDGajqYVnj9/DhMTE0ybNg0tW7bEwIEDUblyZfj6+mo6GtEnicU2ffRmzZqF7du3Y//+/ZzJoIi2bduGq1ev4unTp3jx4gW6deum6UhaYdCgQejTpw+eP3+OCxcu4O7duwCAf/75hw+ZKqBMmTIYMmQIJBIJAKB69eqYMWMG9uzZg8zMTA2nK/309fUhlUqhr6+PJ0+e4N9//8WuXbvw448/4vTp05qOR/TJYbFNH7W5c+dixYoVOHr0KFq0aKHpOFrLxMQEo0aNwoULF/Dq1StNxyn1rKyscOvWLXEWjS1btgAA5syZg1OnTmk4nXaqUqUKsrOzkZCQoOkopZ6dnR0AYOTIkeJUkx07doS1tTXOnz+vwWREnybOs00frfnz52Pp0qU4cuQIXF1dNR1Hq6SlpcHIyEiuLTIyEkZGRihXrpyGUmmPo0ePyi3v2rULvXv3xokTJ/iApALy+v07ceIEKlasiKpVq2oolfZwd3dHmTJl8OzZMzRq1AjAu+dWkpOTYW5uruF0RJ8eFtulVGZmJkJCQgAAb968QUxMDC5cuABTU1M4OjpqOF3pt2zZMsyZMwc///wzJBIJLly4AAAwMzND/fr1NZyu9Js1axbevn0LDw8PlC9fHpcuXcKvv/6KOXPmQE+P/9kg9dq8eTOCgoLQtWtXVKxYEUePHsXatWvh7+/PMfAKqFixIn788UeMGzcOycnJqFixIlavXg0zMzP069dP0/GIPjmc+q+UevHiBbp3756r3cnJCWvWrNFAIu0yefLkPKeoa9q0KZYvX66BRNpFJpNhy5YtOHLkCJKTk2FnZ4eBAwfyAUklBQUFYebMmTh58iT/ZUBBhw8fxo4dOxAfHy++XOn9XVpSTEBAAHbu3ImsrCw0aNAAU6ZM4Z1tIg1gsU1EREREpCZ8QJKIiIiISE1YbBMRERERqQmLbSIiIiIiNWGxTURERESkJiy2iYiIiIjUhMU2EREREZGasNgmIiIiIlITFttEH5ETJ07g3r174vKRI0fw4MGDEs1w4MABPHr0qMSOl5CQgEOHDmHbtm3Izs7G3r17ER0dXex+/3stFVHS505ERKUfi20iNXv9+jUuXLiAQ4cOISoqKtf6w4cP59mujB9++AEHDx4Ul6dMmYLjx4+rpO+85JX922+/RXBwsFr6/q/bt2+jdu3aWL16Nfbt24ecnByMGDECly5dKvbx/3stFcmnqnMnIqKPB4ttIjXatWsXbG1tMX78ePz+++/o1KkTWrduLXe3eeLEiTh58qRajt+pUyfUrl1bLX0D6s2uSN8BAQFo2bIljh49im3btsHAwAA9evSAra2tWjIVNR8REZGepgMQfaxev36NgQMHYu7cuZg+fbrYfvHiRbx69QoAcOrUKbx+/RrXrl3Dtm3bAABeXl7YuXMnWrZsCUtLS3G/48ePw87ODp9//rnY9uLFC/zzzz+wsLBAgwYNcmVo27YtqlevLteWlZWFy5cvIyUlBXXq1IG9vX2u/UJDQxEdHY2aNWvCwcEhz/PLL/t7CQkJCA0NhaGhIZo3bw49vf//z82ZM2eQkJAAiUQCCwsLNGzYEKampgr3DQDHjh1DSEgIcnJyxG0AwMPDA1ZWVuLygQMH4OjoCCMjo3zzKHItVXXugGLfwX8lJCTg8uXLYr7g4GDY2trK/T4U1q+i10KZfERElDcW20RqEhkZibdv36Jt27Zy7S1bthQ/BwcHIy0tDbdu3cLr168BvCvaBg4ciF27dskV29OnT8eAAQPE4urkyZPo0aMH6tati3LlyiE5ORmpqalyx5oyZQrGjRsnFkthYWHo0qULjI2NYWNjg8uXL6NTp074448/IJFIkJWVha+//hp37tyBs7MzHj9+DBsbG+zZsydXQZZfdgAIDAzEggULUK9ePdy8eRPVqlXDuXPnUKZMGQDAhQsXEB4eDkEQ8PjxY0RFRWHHjh3w8PAotO/3Tp8+jSdPnkAQBOzbt09s37t3LzZv3gwbGxsA74Z21KtXD1FRUfnmUeRaqurcC/sO8nLkyBH07t0bDg4OcvnGjx8v/j4o0q8i10KZfEREVACBiNTi1atXQqVKlYQ2bdoI586dEzIyMvLcrmbNmoK/v79cm4GBgXDw4EG5tgYNGgi//vqrIAiCkJmZKdjZ2QnTp08X169cuVIAIG4jCILw+eefCytXrhQEQRCys7OF2rVrC76+vuL6ly9fCjY2NsKmTZsEQRCEU6dOCeXLlxdSU1PFbQ4fPiy8fftW4exWVlZCw4YNhdevX4vHMDExEQIDA/PsQxAEYenSpUKtWrUK7fu/Bg8eLPTv31+urVKlSsLWrVsVzqPotfwvZc5dke/gvzIyMgRra2u5fP7+/nL5FO1XHfmIiKhgHLNNpCbly5fHsWPHIJPJ0KZNG5QvXx5ubm5Yv349ZDJZsfq+cuUKHj9+jGnTpolto0ePhomJSb77XLx4Effv34elpSV27dqFnTt34tSpU6hZsybOnj0LAChXrhwyMzMREREh7texY0cYGBgUKd/AgQNhZGQEAKhYsSKcnJxyzezx9OlTnDhxAtu3b4euri4iIyORnJxcpOOoIo8y11LZYynyHfzXlStXEBMTI5dvxIgRcsNuitKvqvMREVHBOIyESI2cnZ0RFBSElJQUXL16Ffv27cO3336Lhw8fYtGiRUr3Gx0djfLly6NSpUpim56eHqytrfPd5/Hjx9DT08ORI0fk2itXroy6desCAFxcXDBjxgx06NABlSpVQtu2bTFixAg0adKkSPkqVqwot2xgYIC3b9+Ky9999x38/f3RtGlTmJubIzMzE8C7cckVKlQo0rGKm0eZa6nssRT5Dv4rJiYmVz5dXV25fEXpV9X5iIioYCy2iUqAmZkZPD094enpiczMTAQGBhZYbOvo6OS6+/1hsVqpUiWkpaUhMzNTHGsLoMA7wyYmJsjOzsbq1avlCrf/mjdvHmbNmoUbN25g27ZtaNasGUJCQtC4cWNFTrVQ4eHhWLp0KcLCwlCvXj0A7x7IPHDgAARBUMkxikKZa6ksRb+DD1WsWLHQfMr0q6p8RERUMA4jIVITqVSKpKSkXO1v376FmZmZuFy+fHm5QhoArKysEBkZKS5HR0fj4cOH4nLjxo1RtmxZHDhwQGy7cuUKnj9/nm8eNzc3GBoaYt26dXLtMpkMcXFxAID4+Hjk5ORAT08PX3zxBZYtW4YqVarg2rVrefaZV/bCxMXFQV9fHzVr1hTbdu3apZK+laHMtQSUy6fId5BXPgMDAxw6dEhsu379Op49e1asflWVj4iICsY720Rq8vLlS7i7u6Ndu3Zo2LAhDA0Nce7cOQQGBiIwMFDcztnZGZs3b0bFihVRpkwZeHl5YdCgQVi4cCEkEgl0dXWxYcMGlC1bVtzH3Nwc33//PYYNG4b79++jXLly+O2332BsbJxvnkqVKmHVqlUYNWoUoqKi4OLigmfPnmHPnj2YNWsWevbsiatXr8LHxwe9evWCnZ0dzp8/j/T0dHh6eubZZ17ZC/N+6EivXr3QvXt3XLt2Ddu3b1dJ38pQ5loqm0+R7+C/KleujKlTp2Lo0KG4d+8eypUrBz8/PxgbG4uzgyjTr6ryERFRwXhnm0hNqlevjoiICLRq1QoPHjzAP//8g5o1ayI8PBx9+/YVt1uyZAl69+6N06dPi1PYzZw5E8uWLUN4eDji4+OxdetWfPvtt6hTp46439y5c+Hv748HDx4gISEBe/bswfjx4+W2+e9Lbby9vXH9+nVUqVIF586dg0wmQ2BgoFhEde7cGbt27YJMJkNwcDBsbGwQGhqaa67ugrJ37do11/Zt2rQR5642NjbGP//8AwcHBwQFBcHCwgLnz59H37595R5KzKvv/2rWrBlatGgh1/bfl9oUlkfRa6mKcwcK/w7ysmDBAqxatQr3799HfHw8du3ahUqVKsn9hUCRftWVj4iI8icRNDFIkoiIFJaUlCT3YGN4eDgcHR0REhJS5IdXiYioZHEYCRFRKbd//34cOnQI7du3R3JyMvz8/NCtWzcW2kREWoB3tomItMCBAwdw8uRJSCQSNG/eHP369eMbHYmItACLbSIiIiIiNeEDkkREREREasJim4iIiIhITVhsExERERGpCYttIiIiIiI1YbFNRERERKQmLLaJiIiIiNSExTYRERERkZqw2CYiIiIiUhMW20REREREavJ/xDDa+nleT1MAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -171,18 +173,16 @@ } ], "source": [ - "replication = differential_expression_replication(\n", - " collection, results, group=monocytes, contrast=contrast\n", - ")\n", - "found = replication.genes\n", - "once = found[\"feature_id\"][found[\"n_found\"] == 1]\n", + "replicated = replication(studies, monocytes, contrast)\n", + "found = replicated.features\n", + "once = found.filter(n_found=1)[\"feature_id\"]\n", "every, own, none = (np.isin(once, kind).sum() for kind in truth_sets)\n", "print(f\"monocyte genes found: {np.count_nonzero(found['n_found'])}\")\n", "print(f\" by one cohort only: {once.size}\")\n", "print(f\" changed in every cohort: {every}\")\n", "print(f\" changed in that cohort alone: {own}\")\n", "print(f\" unchanged: {none}\")\n", - "plot_replicated_genes(replication)\n", + "plot_replicated_genes(replicated)\n", "plt.show()" ] }, @@ -231,7 +231,7 @@ }, { "data": { - "image/png": "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", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAvgAAAJPCAYAAAAJ7aAuAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjIsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvgI3uAAAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Xd4FFUXwOHfbnrvPSGVEFoSeu+9dwXEgiD4KSgqYi+IBTsqKgiKBRQFlA4CgtKbQGghQAopkEZ63SS73x8Li0sSEkLCEjgvzz4PO3Pnzp1h2T1z5t47Co1Go0EIIYQQQghxV1AaugFCCCGEEEKI2iMBvhBCCCGEEHcRCfCFEEIIIYS4i0iAL4QQQgghxF1EAnwhhBBCCCHuIhLgCyGEEEIIcReRAF8IIYQQQoi7iAT4QgghhBBC3EUkwBdCCKHn+eefZ926dYZuxj3jtddeY9WqVbr3d/L5v76tQog7k7GhGyDuHVFRUfz222+cO3cOKysrmjRpwoQJE3BwcABgw4YNLFmyBAAjIyNsbW1p1KgRgwYNonHjxuXqy8vL4+eff2bbtm0MHjyYhx56qNJ9p6Sk8NRTT+Hq6soXX3xRNwdYTyxfvpzIyEhmz55t6KaIWlLb/6Zbt27Fzc2NIUOG1Ep9d4Nnn32WXr16MWjQoFqve8eOHZiZmene1/b5r822X9/WO8Xp06dZvHgxqamphIaG8uSTT2JlZXVL2yxYsIBt27bpbePv78+HH35YJ8cgRG2SDL64LebOnUtYWBixsbH07NmTli1bEhMTQ6tWrTh37hwA586dY9OmTYwdO5bRo0fTpk0bTp8+TcuWLXnssccoKyvT1Xf48GGCg4M5ePAgBw4c4Pjx45XuW6PR8OCDD3Lw4EE2bdpU58d6pzt58iT//POPoZshapH8m9a9LVu26L6r6tpHH33E0KFDa62+29l2Qzh06BCtW7cmOzubLl268Ouvv9K1a1dUKtUtbXP48GGSk5MZO3as7tWvX7/bcUhC3DLJ4Is6t2bNGl566SV+//13RowYobfurbfeQqPR6N6bmJgwevRo3fspU6bwxBNP0LVrV/z8/HjllVcAbRblzJkz2NraEh4efsP9v//++5iYmPDAAw+wfPnyG5b98ccfSUhIoF27dmzatIn09HQGDx7MmDFj2Lp1KytXrkSj0TB+/Hi6d++ut+25c+dYtGgRiYmJ+Pn58fjjj9OgQYNydffs2ZPff/+djIwM+vTpw9ixY2+qHoC0tDQWL17M6dOn8fX15YknnsDT05OTJ0/y1ltv8d1332Ftba0rv2XLFn755RdGjRrFqlWrSE9P153nZ555hk6dOpGWlsbChQs5c+YMHh4ejBs3jpYtW+rqKCoqYsmSJRw6dAhbW1tGjBhBt27dbng+q9Nm0GYsFy5cCICNjQ3Nmzdn6tSpetm0qvZfVfsrc/z4cd5++22WLFmit7+tW7eybNkylixZQnFxcY2P/UbHDbf+udmwYUOF/6bR0dEkJCTQrVs3li1bRmFhId9//z0AmzZtYvXq1RQUFNC+fXsmTZqEubl5tY7nRpYtW8aff/6Jq6sro0ePZsOGDYSHhzNq1ChdmcOHD7Ns2TIuX75Mo0aN+N///oejo6Nu/fTp0xk8eDApKSns2bMHc3NzJk6cWO7/eVX1JCQksGjRIuLi4ggICGDy5Ml4e3tX2O533nmHo0ePolAocHNzo0ePHnptfuONN0hISOD7779n9+7dAPzyyy+YmJjcVD1XLV++nI0bN+rO0/X+/PNPunbtSnBwMAAPP/ww//vf/2jfvr2uzMsvv0yrVq109Vd2vDdqe1XnsDptvd706dMZNGgQSUlJ7N+/H3Nzcx5//HGaNm1a5bYVycjIYNmyZYwbNw5nZ+cKy7z44ov07t2bb7/9FoCRI0fi4+PDjz/+yOTJk29pG29v72odtxB3Gsngizr3ySef0Llz53LBPWiDOVtb2xtu37p1ax566CG+/vpr3TInJ6cqtwPYv38/8+fP13X9qcrx48f54IMPePXVV2ncuDG+vr6MGzeOoUOH8uabb9K2bVucnJzo3bs3x44d02138uRJWrZsSVJSEr179yYyMpLw8HBiY2P16v7oo4+YMWMGDRs2pFGjRjz22GMsWLDgpuo5d+4czZs3Z/v27XTp0gVLS0v69euHWq2mUaNG/PPPP/z22296x/Xhhx9iZGRESEgITZs2xdXVVZeR8vX1JSYmhrCwMGJiYujTpw9WVlb07NmT1atX6+p46KGH+Oabb+jQoQPBwcHMmTOHNWvWVOu83qjNAIGBgbr2dOnShXXr1tGpUydKS0urtf/qtL8ywcHBbNmypdyxfP7555SWlqJQKGp87FUdd218bho1alThv+nV7aZPn05YWBgjR44EYN68eYwaNQovLy86duzIF198wYABA/QutK+3atUqHnzwwRse6+zZs/nf//5H48aN8ff3Z8KECXz++edERkbqyvz0008MGDAAe3t7evfuzZkzZ2jWrBlpaWm6Mps2beKBBx5g27ZtdOzYkfz8fDp06EBcXFy168nMzKRNmzbExsbSp08fTE1NGThwILm5uRW2vVu3bowdO5b77rsPf39/nn76aV0yAaBnz57Y2dnRokUL3Tk2MjK66XoA3nvvPaZMmaI7Tw888AAnTpzQK7N161aioqJ079esWUNiYqJeme3bt+vO7Y2Ot7K2V+ffojptvd7Vf7+1a9fSoUMHMjIyaN++PWfOnLnhdv+l0WjYtm0b48aNw9PTkw8//LDSz2dBQQF///03Y8aM0S1zcXGhR48ebNy48Za3iYiI4MEHH+Spp55i5cqV1T4GIQxOI0QdMzc31zz33HNVlvv00081dnZ2Fa5bsmSJBtCkp6eXWxcWFlZh/VlZWRp/f3/N+vXrNRqNRvPKK69oAgMDb9iG5557TuPg4KDJzs7WLRswYIDG1dVVk5eXp1vWpUsXzQsvvKB7P3jwYM3gwYN179VqtaZjx46aCRMmlKs7KytLt2zWrFmazp0731Q9Q4YM0XTr1k2jVqt1yy5fvqx7//zzz2s6deqkW3fhwgWNUqnU7NmzR3ceunXrpnfcI0aM0EydOlVv2fz58zWNGjXSvbeystJs2bJFr0xF/x4VqarN1ysuLta4u7tr1qxZU639V6f9NzJx4kTNwIED9eo1MTHRbNq0qcp930hVx11bn5uK/k2fe+45jZWVlSYtLU23LDMzU2NjY6NZvHixbll8fLzG1NRUs3LlSt2ysLAwzYcffqh7P2fOHI2VlVWlx3n58mWNpaWlZtmyZbplJ0+e1CgUCs2cOXM0Go1Gk5OTo7G1tdWd06t69+6t938pMDBQM3LkSL0ywcHBmo8++qja9WzdurVce7OzszXFxcWVHsN/7dy5U2NmZqZRqVS6ZU2bNtV8+umn1dq+snoyMjI01tbWeucpIiJCA+jOk0ZT/vzb2dlpVqxYoVd3u3btdNtUdbzXt70657C6bb1eYGCgpmXLlnqf+b59+2ruv//+Sre56sKFC5rZs2dr/Pz8NObm5ppx48ZptmzZoikrK6t0m9OnT2sAzd9//623fMqUKZqwsLBb2mbq1Kmahx9+WPPtt99qZs+erXF0dNSMHj26yuMQ4k4gXXREnSorK6OoqAgnJ6dbqudq14n8/Pxq1/XYY48xYMCAmx5YFhYWpnd3wM/PD41Go9d9w8/Pj4sXL+re79mzh08++UT3XqFQMGrUKObPn1+ubjs7O937wMBAVqxYUe16NBoNW7du5YsvvkChUOjK/fe2+uTJk/noo484e/YswcHBLFmyhODgYDp27FjpMf/555+EhoYyduxYNBoNGo2GtLQ0zp07R3FxMWZmZrRs2ZK3334btVpN586dsbKyqta/RXXarNFo2LhxI1u3biU5OZnS0lJKSko4e/asrsyN9l+d9t/IhAkT6NevH2lpabi4uPDbb7/h6OhInz59qtz3rRx3bX1uKhMaGqrXrSEiIoLc3Fy9biM+Pj60a9eOPXv2VNidBGD06NE0a9as0v0cP36cgoIChg0bplvWtGlTAgICdO/37dtHTk4O3377Ld9//73u3yk+Ph5TU1O9+rp27ar3PjAwkKSkpGrX07hxY9RqNTNmzODhhx8u93/6enl5efz4449ERESQkZFBUVERxcXFXLhwgaCgoEq3u9l6IiIiyMvL0+tfHxoaSmBgYLX3UZGbPd7qnMNbaevQoUP1PvMjRozgnXfeqbT89u3b+eCDD9i6dSutW7fmhRdeYNy4cXqf+coUFxcDYGlpqbfc2tqaoqKiW9rmvffe000CATBw4EDatGnDhg0b6mSwtRC1SQJ8UaeMjIxwcXHhwoULt1RPcnIySqWy0j6Y14uPj2fFihUMHDhQ13/y1KlTJCcnM3r0aF544QXatGlT4bbXB4NKpbLCZVe7j5SWlpKdna33QwDaQO7y5ctV1n21u0Z16lGpVFVeMAUHB9O5c2e+++473nvvPb7//nv+97//VVpepVJRUFBAp06d9Pr4Ajz55JMoldqefGvWrOHTTz/llVde4dSpU/Tt25f58+fj4+NTad3VbfMbb7zBN998wxNPPEHr1q0xNzfn+PHj5OXl6cpUtn83N7dqtf9GunfvjpubG7/99htPPvkky5Yt0+uGUZNjr+q4a+tzcyPXB3mXL19GqVSWW17RPv8rJCSEkJCQStdnZGRgYmJSbtYSe3t73d+zsrJQKBSMHj1ar3vLfffdh4uLi952Nzre6tTj5eXF3r17+eKLLxgxYgR5eXlMmjSJd999t1zXmpKSErp164apqSnjx4/HxcWF/Px81q9fr/f5q0p16snIyMDY2FhvfMz156kmbuZ4oXrn8Fbaen0Ze3t7MjIyKi3//fff8+effzJ69Gg++OAD/P39q9zHVVcvAjIzM/WWX758udK2Vneb6/9vtm7dGm9vbw4dOiQBvrjjSYAv6ly/fv3YuHEjRUVFNR7It3btWtq0aVMu41IZZ2fnchnO5cuXk5WVxdixY/Hy8qpROypibGyMl5cX0dHResujo6Px8/Or1XrMzMxwc3PTy2xX5LHHHuOFF16gZ8+eJCYm6k0h+t/MGoCpqSmurq5YWVndcDCZg4MDb731Fm+99RapqamMGjWK5557rlx//+tVp81Llizhgw8+0LVTo9Hw1FNPVXv/1Wn/jSiVSsaNG8eyZcsYNGgQe/fuZd68ebd07FUdd219bqD8v2ll/Pz8UKvVuoGY/93n1T76NdGgQQNKSkpITEzUDWRVq9XEx8fryvj4+KDRaAgODqZFixY13ld16wkPD9cNoDx06BA9evQgLCyM8ePH65U7fvw4R48eJTMzUxf47d+/v1x9VZ3j6tTj6+tLaWlpufNUVQLE3Nxcl3W+6voLshsd7/Vtr845rGlbAb0xJKAdI3P9RAH/9fXXX9OjRw++/fZbAgMD6datGxMnTmTUqFFVTnXp6+uLra0tx48fp2/fvrrlx48fp3Xr1rW2DWi/l3Jzcyu8aBLiTiODbEWde/3118nOzmb69Ol6tz81Gg2rV6/WGzx3PZVKxZtvvsn27duZM2dOtfdpaWnJ6NGj9V4hISG6IPC/s5jUhnHjxrFgwQJdlurSpUt8++23jBs3rtbrefjhh/niiy/0fmhXr16tl9EdPXo0hYWFTJ06lYEDB+Lu7q5b5+zsrDeQDmDSpEl88cUXegMic3Jy+OWXXwDtLe0ffvhBtw9XV1cCAgIoKCgAtDN4jB49utIBeFW12dTUVK/L09dff82lS5d076vaf1Xtr44HHniAffv2MWfOHIKDg3U/9FXt+0aqOu7a+txU9G9akbCwMJo0acJ7772nW7ZmzRoiIyO57777Kt2uqkG24eHhBAUF6c0P/sMPP+i1qV27djRt2pRZs2bpZcbPnDlzU1N8Vqeew4cPc/jwYd26Zs2aYW1tXeG/mampKRqNRvd5U6lUvP322+XKVXWOq1NPeHg4wcHBvP/++7plixYtIj09/YbHHBISwvbt23XvN2/ezPnz53Xvqzre69tenXNY07aCdjalq4OCL1++zKJFi8rNFvZfVlZWTJw4kd27dxMZGUmbNm2YNWsWHh4eTJ48WTf7T0WUSiXjx49n8eLF5OTkAPDXX39x9OhRJkyYoCv35Zdf8vzzz1d7m+Li4nKzrr3//vvk5OTI8yFEvSAZfFHnGjZsyI4dO5g8eTJeXl60bdsWlUpFVFQU7du3p0uXLrqyBQUFuixsVlYWR44cwcvLi/Xr1+v6QwPk5uYyceJEAOLi4li3bh1xcXEEBgbq/SDdLq+++ioHDhygcePGhIeHc/jwYdq3b8+MGTNqvZ7Zs2cTGxtLs2bNaNu2LWlpaQQFBen1f7awsOCBBx7gyy+/1MtEAwwePJjXXnuNLl264ObmxjPPPMObb77JxYsXadWqFW3atEGj0RAXF8fLL78MaLta7dixg1dffZVmzZqRnp5OYmIia9euBbQB/u+//17pRVhVbX7zzTd57LHH2LJlC/n5+aSnp+vdpq9q/1W1vzrCw8Np2rQp3333nd5xVLXvG6nquGvrc1PRv2lFjIyMWLJkCUOHDqVZs2Y4Oztz8OBB3n///RtOYxgZGckff/xR6XpjY2MWLlzI8OHD2blzJzY2NmRmZuLn56fLdiqVSlavXs3o0aMJDAykRYsWJCcno9FodJnn6qhOPTY2Njz88MPk5eXh7+9PREQEISEhFQaZzZs3Z+zYsXTo0IEOHTpw+vTpCvvdjxs3jmeffZbjx49jZmZWbprM6tRjZGTEN998w7Bhw9i1a5fuPP33bkpFZs+ezZAhQzh16hRmZmbk5+fr/f+o6ngrantV57CmbQXthWTbtm1p1qwZERERBAYG8uyzz1a5HWhnhfrggw949913Wb9+Pd9++y3du3fn/Pnzld7Zeu+99xgwYAAhISE0atSIAwcO8Prrr+uN5Th06JDezGdVbWNkZMSmTZt4+eWXadSoEfHx8aSkpPD9999XOTWzEHcChUZzg7nRhKhlUVFRnD9/HisrKxo3boybm5tu3fnz53VfwEZGRtjY2BAcHFzhrV2VSlVhgOXo6EjPnj0r3PepU6dISEigf//+lbbvxIkTZGdn07lzZ92yY8eOUVhYSIcOHXTLDh8+jEajKdeP/8iRI7r5zENDQ6usOy4ujsjISAYMGFDteq46d+4cZ86cITAwkCZNmpRb/8UXX/DOO++QmJiIsbH+tXxGRgb//vsvOTk5tGvXTncLPiEhgePHj2Nvb09YWFi5/reXLl0iIiICGxsbWrduresrfTWQvTrPemVu1Oar+7a1taVdu3bs3bsXV1dXvXKV7f/6Oiprf1WOHj1KdHQ03bt3Lzfeo6p91/S4oXY+N9f/m2ZmZpbb7qrCwkL27dtHYWEhLVu2xMPDQ2/9tm3baNCggW4e9jNnznDmzBmGDx9+w+PMyMhg3759uLq6EhoaSlBQEG+++SaTJk3SldFoNBw7dkx3vE2bNtUbJ7F582aCg4P1Ask9e/ZgbW1NWFhYtesB7f/5Cxcu0KBBgxsOEoZr/wYBAQEEBwezdu1a+vTpozfQ89y5c0RFRVFUVMTIkSMrHN9RnXoyMzPZt28fLi4uhIWFsXfvXtzd3XXjHEJDQ3n00Uf1LvTS0tI4cuQILi4uhIaGsmvXLjw8PPTGRtzoeCtqe3XOYVVtvV5QUBAzZ85k9OjRREREYGZmRseOHas1FqYyFy9exMHBAQsLi0rLaDQaDh48SGpqKs2bNy93MXD48GGysrLo3bt3tbcB7f/748eP4+DgQNOmTavsMiTEnUICfCHuQmVlZbRo0YJhw4bdVNemmtq3bx8NGzas9iBocfc5dOgQAQEBukHFS5cu5ZFHHuHcuXM3NWjyXqdWq3Fzc+PLL7+8YbepO9XVAP/xxx83dFOEuKdJFx0h7jJz585l5cqVZGVl3XRXj5r6792NO9GDDz5IYWFhhetmzpxZbvYdQ9dbH2k0Gtq2bYu/vz85OTmcOnWKr776SoL7m/D333/z3HPPYW1tzcCBAw3dHCFEPSYZfCHuMnv27CErK4vOnTtXax7pe8Hq1av1nor7Xx06dKjxrEp1VW99lZ+fz7///otGo6F58+Z6c/6Lql24cIFz587Rvn37m+5edqeoqIuVEPVRbm4uxsbGN+wadr2ioiLMzMyqPbNZXZIAXwghhBBC3PNKSkpYsWIFX331FXv37uXRRx9l8eLFVW73999/87///Y9z585haWnJ448/zty5c29p7MmtkmkyhRBCCCHEPe/EiROsX7+ed999t9wTtSuTkJDA4MGDGTNmDPn5+Wzbto3FixcbZEa//5IMvhBCCCGEEP/RvXt3goKCqszgX30Se1JSki5j/8orr7BkyRKSkpIM1l1HMvhCCCGEEELUwIEDB+jUqZNed5xu3bpx6dIlvad5324yi44QQgghhDCYoqIiVCpVndWv0WjKZdLNzMxu6nkmlUlNTS33DAVXV1fdOl9f31veR01IgH8HiL9w1tBNuKe0m7nX0E24Z5hYySwqt5NaVWDoJtxTSovzDN2Ee0pZkZzv2yVtw4zbtq+ioiIsbZ3QlNTd95e1tTV5efqfnzfeeIM333yzVupXq9V676/OrmbI2XQkwBdCCCGEEAahUqnQlBRgFf4wCiPTWq9fU6Yi79gPJCQkYGtrq1teG9l7AC8vL1JSUvSWpaamAuDp6Vkr+6gJCfCFEEIIIYRBKYzM6iTAB20W3dbWVi/Ar6nS0lLy8vKwsbHByMiIzp078/7771NSUoKJiQkAW7duxd/f36ABvgyyFUIIIYQQhqeog9dNysrKIisri9LSUlQqFVlZWeTm5urW7969GwcHB06cOAHA5MmTMTY2ZurUqURHR7NixQoWLFjAyy+/XJMzUGskgy+EEEIIIe55ZWVlegNmT548ydq1awkICODIkSMAGBsbY2dnh5GREQBOTk7s2LGDmTNn0qlTJ5ycnPj444+ZPHmyIQ5BRwJ8IYQQQghhYDVMuVer3uoxMjIiKyvrhmU6d+5crkzTpk3ZtGlTDdpWd6SLjhBCCCGEEHcRCfCFEEIIIYS4i0iAL4QQQgghxF1E+uALIYQQQgjDUgB18WAowz1ryqAkgy+EEEIIIcRdRDL4QgghhBDCwAw/i87dRDL4QgghhBBC3EUkgy+EEEIIIQyqrvL39yoJ8IUQQgghhGEpFHU0yPbevGyQLjpCCCGEEELcRSTAF0IIIYQQ4i4iAb4QQgghhBB3EemDL4QQQgghDEymyaxNksEXQgghhBDiLiIZfCGEEEIIYVgyi06tkgy+EEIIIYQQdxEJ8IUQQgghhLiLSIAvhBBCCCHEXUT64AshhBBCCAOTWXRqk2TwhRBCCCGEuItIBl8IIYQQQhiWJPBrlQT4QgghhBDCwCTCr00S4AtxB7C2MMHMRMnlnOJy6yzNjfFxsdZblpxRQHa+ikY+9rplqpIyYpNz67qpdwUXOzPyCkspVJVVWsbESEEDVysAoi/lAWCkVODhaE5adjHFJerb0ta7gYejJalZhZSpNeXW+bhYYWl27aeoTK3h/MUcQDt9tZeTFSmZhZSUyfmuDmMjBa72lly8nF/h+kY+9ij+E/DkFqpISs+vdLm4MSsLEyxMjUnPLqy0jLOdBWamxiSlXft+DvZxRKlUUFBUQnxKzu1oqrjHSIAvhIE9f384kwc2obRUzV9HE3lq/m699U19HflgSgfdez93WyZ9uJ09p5LZ8sEQYq4EQxdSc3nk/e23te31jamxkoXT2xDqZ4+5qZK5KyL5aXtcuXKT+wUwc2QIlzKKuJxTzOj39tC0gS2Ln25LaZkGa3NjZv9yktX7km7/QdQjXs6W/PxidyzMjDEzUTL5090cikrXKzNjZFPCAhwBsDI3IbdARd+X/qRVQyc+e0L7ubexMGbG1wfYEXHpth9DfdK5uSffPNeTIlUZOfnF3Dd7E6lZ+oHnl0/3wNhIG8g721mwbm8sLy3eW+lyUbnpo1vzzP1tKFKVse9kIpPnbkTzn2tYhQIWPj+Abi0aUKQqJSOniNGv/M7lnEK+mtkPW0szUjPzGTxrheEO4o4iGfzaJAG+EAYU4GHL2B4NaffkSgqKSln3zkC6hXryz/GLujKHolLp8dwaAJxszVn79kD+Pn4REyMlKRmFPDj3L7LyiskrLDHUYdQbQ9t7YWKkoPWMP3GzN2fj7G6sO5BEVv61c9ci0IGnhwYz6M2dusw9wJQBQSzcFM3322KxsTBm0+xubDmSTEFx5XcB7nUzRjRl7b54Pl51kl4tPHnroZYMeGWLXpnnFh7U/f2lsaEkZ2gDUi9nK0bO3kZqVhED23jzxJDGEuBX4b3HOvL4JzvYeTyJVya04amR4bz63T69Mr1n/qH7+5q3B7Psr6gbLhcV83Cy5vHhLWj32A9k5BSy+r3RDOwQyIa90boyDb0dadHInWYPLqKkVM3iFwcyoEMgS/88Se+nfyEsyJV3pnQz4FGIu5nMoiOEAbVq6MLO4xfJylOhKlWzft8FWjdyrbT8hN7BrPznPGq1BrVGQ35RCT+/3JuIb+7j1QmtbmPL66fWQQ6sO3gRjQaSM4uIiM2ima+dXpk+4W78sS+JyznFuNmb6ZZfzimmbbAjof729Axzw8nWjGAvm9t9CPVKq4bO/LH3AgB/Hb2Ij6s1ZiYV/+yYGisZ1sGXVbvjAFi7Lx6NBpr62tOhiSsRMRm3q9n1kr21GY7WZuw8rr2rtGrnedrc4Luksa8jJkZKTsZertZyoa9lIzf2n0oiLauAMrWGNbvP0qaxp16ZpPRcilWldG/hS7smnvi42nI8OtVALa4HFHX4ugdJBl8IA7K2NNHLvOcUqAjwsK2wrFKpYGyPIIa/vgmAklL1fzL7Zmx4dzDr9sURES0/zJWxttA/37kFJdhYmuiVcbI1w8XOjA2zu2FhasSZhBwmzjvAJ39E8cKYxrz9YHMOnc0gKikHGwuT63ch/sPmuvOdX1iCjYUJxSXlx5oMad+A3adSyCm4Vn5MVz/u7xaAhZkxT86X7iI3YmNhQq7eZ1uFjZVppeUn9m/MT1vPVHu50GdjaUZugUr3Pidfha2l/vnOLyxh8/4Y5j3dm+KSMk7GpBGdmHm7myruUfU+g79u3TrOnj1r6GYIUSOpmYV4/2cAbQM3G5IzCyos27eVD5HxmaRklh/MdTmnmIjodLydrSvYUlyVmlWEt7Ol7r2PiyUpmUV6ZVIyiygsLqPTzG20evpPlEoFPULdyCsq5bWfTjD0rV18tjaKEG9b4lJkEOKNpGQV4u2iHahsbmqEnZUpGbmqCss+3CeIZX+d1703Uir4at0Zus3cyIS5f/PVtI63pc31VWpWIS52Fpgaa3/WG7jakJJR8XeJjaUJfVo1YPXu6GotF+WlZOTj43otGdPAzZbkDP3vgz5t/GgVou2i03Lid5yNz2DaaLnTWjlJ4demeh/gP//882zZsqXqgjW0ceNGIiMjb3q7w4cPs3TpUlJSUuqgVeJu8c/xi7Rp5MrorgH0aunNmK6BbDoYj0IBwd72emUf6R/CT1uvXczaWJrQyMeeJr4OPNCrIZ2be3DkXNptPoL6ZdPhSzzcy5/OTZx5sKcfrvbmRMRmYWlmhM+VwH/9oYsEeFjTNtiRTo2d8Xa2IDmzEAtTI4K9bGgd5MhnU1qy82QaCekVB1BCa9PBRJ4f05zWwc68PiGc7ccuodZocLI1w8n2WvenZn4OmJkYcTT6Wjecj6e2pX9rL0L9HRjcvgHpOUUV7UJcUVxSxt5Tl3j9oXa0aeTGrLGtWL8vFoAGrtZYml+7YX9/j2A2HYwrN4tUZctFeXtPJNKogRMT+jalR0tfHh7QnPV7tBeoIb5OAOQXleLjakvXsAa0DvEgPNiN/Ct3Wfw97PD3sMfCzIQQXydsrcwq3ZcQNVHvA/y69vLLL7Nhw4Zql9+yZQutWrXiwQcf5MEHH6zRxYG4d+QVlvDI+38xvFMAUwc34fmFe4lLzsXMxIgvn+6qK+dib4GxUsHfEddmbWkd7MqCGd34bFoXerf0YeIH27lUScZOaB06l8Enq6OYPjSYrs1cmPjJAcrUGpr42PH0sGAAziblMm91FDOGNWL60GDmrT7LsZgsGnrZ8OX/WvHquCacTcpl+oJ/DXw0d74FG85w5NxlXh0fjomRkpe+OwTA0PYNGNq+ga5c9zAPvtmkP6jz45UnGdy+Ae9MbI2noyVT5u25rW2vj56e/w82lia8/lBbdp+4yHebTwPw7JiWNL0SdAK0b+zOj1vKd8OpbLkor7ikjPGz19C/fSBPjWnNa4t2cjpOO0PUN7MGYGKsZO+JRL7+4wjPjm3LnMe6cvx8Kt+sOQbAG4924dmxbTExVvLNrAF0bO5lwKO5M0j+vnYpNBpN+YmJb6PS0lL2799PamoqrVq1wtfXV299Xl4eu3btIj8/nzZt2pRbHxISwrRp07jvvvv4999/MTMzo1OnTpiZmd1UPevWraNRo0ZYWVlx8OBB3NzcKCws5NFHH6VTp04MHDgQgLFjx2JsXPnQhXXr1uHu7o6Hhwc+Pj7s2LGD7t273/AcxF+QLka3U7uZ0pf3djGxcjR0E+4papVc4N1OpcV5VRcStaasSM737ZK2YcZt21dOTg52dnbYtn8ahXHt38nQlBaTs/8zsrOzsbWteIzb3cigg2zPnDnDsGHDUKlUhIaG8sILLzBt2jSefvppAPbv38+QIUPw9vbGzc2Nhx56iJdeeonXXntNr55Vq1Yxb948mjdvTkREBFZWVhw4cABLS8tq1/P888/j7u7OhQsXaNmyJb179yY7O5vc3FyioqJQKrU3O8aMGXPDAH/IkCEAJCYm1uq5EkIIIYS4aykU2ldd1HsPMliAr1arGTlyJM2aNeOXX37B1NSUsrIytm7dCkBZWRmTJk1i+PDhLFq0CIA///yTAQMGMGjQIFq2bKmrKzk5maNHj2JjY0N+fj4BAQEsXbqUKVOm3FQ9Fy5c4MiRIzg4OOiWLV++nHHjxjFz5szbcVqEEEIIIYS4JQYL8A8fPkxkZCSrVq3C1FQ7tZSRkRH9+/cH4MSJE5w+fZo1a9botunXrx/h4eGsWLFCLzAfP348Njba+aitrKxo2bKlbmadm63nv8G9EEIIIYS4HeRJtrXJYAF+QkICAEFBQRWuj4+PB8DPz09veUBAABcuXNBbdn1QbmZmRlFR0U3X4+7uXv0DEKIS43s2xMPJSvc+r7CEhetPAdCzhRfB3vYcPZfGgTPywJPaEuBuRZemLjjamLE9IoWI2Czu6+KDl9O1KTEXbY4mr6jUgK2s/7ydrbivm7/esh3HLnI0OgNrC2OGtm+AsZGSTYcSScuWWW9qg1KpoG/rBjTycUCj0fD57xEAuDtaMqCtL7mFJazdE4OqVG3gltZ/9/VsjK/7tQffqUrK+GyFdmB4i2A3OjTz4mx8BtsOxxmohUJUn8Fm0bG3twcgPT29wvXOzs4AZGbqPxQiIyMDFxeXau/nZupR3KP9tETdaebvSMuG2s/gF9O78Ei/ENwcLPjmuR481KeRgVt3d+gd7saa17rQyFt/8NT9XRpgbyUPoqpLE/s2pKRMg6WZMb++0pPm/o6EBTqyZW5/vWkwRc0oFPDLq/15dkwLLMyu5eOcbc3ZNHcYTf2cGNklkO9f6GPAVt6dQnwdad9U+2Tahwc0Z/akLjjZWfLGo1145SF5JoO48xksg9++fXvs7Oz4/vvveemll3TLU1JScHNzo3nz5tjb27NixQqeeOIJQDtwdc+ePbr31XGr9VhbW+vuBghRHT9vP6f7+w8v9NLNXf/VmpNExmsvNE/EZNC3jQ8/btWfGlCpVDCsox+2lqb8dSSRXi29+WGLtkznZh40D3DkQnIuGw9q70yN6OzPsfPptA1xw8RYyYp/oikuKau0vKeTJQPb+WJraUp6dlG5/ddHb4xvxutLT/LHvvID23MKS8jILWbbsZQKs/e2lsYMa+9NflEpJy9k4+NsyV8RKRgbKRjQygMPRwuOnM/k8Hnt/OxT+gey9kAS/Vt5kJxZxOZ/LwFUWj7Uz46OjV2wMDPiWEwmO47X77s2ien5fLLqJADBXrb0DPfgZFwmFmZGTPp4F8lXHsL280vdCfGxZ88p/eeA+LlZ06uFJ9EXcyhVa7icXURkQjb21qYMbOONhZkxfx29SFxKHk62ZnQP8+BMfBYdm7rx79l0jpzXPqW5ovIAvVt6EuJjj6mxkg0HEohKzL6NZ6f2DW7vTwNXa7rNWKWXoR/TvSFbDsfzwjfaqUN3fT6axr6ORF7I0Nu+bYgb4UEu7Dl5kdAAZzbsjyOnQEWgpx3dw71RlZSxZk8MOQUqwgKdsTI3wdzUiCAvezYfjCM+VXteKypvbmrE0I4BeLtYo1QoWLDuhN5Ti+uj37Zfm9Z64awB/LD5BAC7IhL4YZP27xv2nuftx7pVuP3Qzg1xsbdk475ohnZuyMI1RwHo2MyL8GA3klJzWbf3PGq1hiGdgjgdd5nWIe5YmJnw21+nKSgurbS8m6MVQzoFYW9tTnZeMYvWHavDM2EgMsi2VhkswLeysuKrr77ikUceITo6mtatW3PkyBHUajWLFy/GxsaGuXPn8vTTT5OYmIibmxtffvklnTt3ZtSoUdXez63W07ZtW5YtW4a7uzvm5uZVTpMZExPD3r17dXcMtm7dSmJiIqGhoYSGhla73aL+83axJsDDlt0ntUHg1eAeoE9rH9btjSu3zUdTOxLkZcfBMyl89L9OuNlb8MOWKF5+oBXtQlw5EJlClz6edA3z5MVF+xnROYBHBzTm0JlUmvo50rKhC89+vafS8r/PHsBfRxPJyCm+XaehTtlZmuDhaEFkQg6PDwgiOjmPrUeTAfh1VzxeTpaE+tvz3IgQRr+3h7NJubptjY0UrHq5M+cu5pKSVcSU/oEcic5k+/EUfn6+A5dzVcSl5PPOw6Es2HieP/Yl8sSgINo1cuL8xVwe7OmHraUxK3YnVFj+aEwm30xvy9oDSXflg4Me7tuQZX9pn3haWFyGWl3Ms6Oa4e5gQZGqjENR+g9d83GxYs3s3qw/kEBTXwfaNXbh4xUnSMsuYvXsPuw6kUxBcSm/vNyDCe//jbFSyUtjwzhyLp341Hy+f74rI2f/RVZecYXlwwOceGxgI/46epEytUFnf6414UEubNgfx7BOAdhZm7Hl0AXiU/MI8LTjyNlrF4vHzqcR5GmnF+APau/HnEc7sHp3NM/f34pWwa7sPnGR5gFOfPy/Lmw6EIeNpSmTBjah36w1hAU6M3VIcw5EJlNapub3twbT/slfadfYvcLyb01sj7ujFSdi0jHsZNu1z8XekpaN3PnfR5sBiLmYRfcWDejY3Js2jT346vfyz8B4d2o3Wod4sPt4Ip9M70VwAycWrjnKs/e3pV+7AHZFJNCxmTd92wXw5Md/MqRTQ6aPbs2+k0kE+zjSOdSbyXM3Vlp+1Tsj2XcyidRKnnQuxPUMOk3m+PHjdbPoHD16lPbt2/Pwww/r1k+dOpWQkBB+//13Tpw4wfPPP8/DDz+s15Vm6NChNGqk39Whe/fuODo63nI9AHPmzMHb25v9+/dTVFRU5TSZSUlJbN6s/VJ44IEHuHDhAhcuXMDS0lIC/HvMQ30bsewv/WccKJUK5k5uz7Hz6Ww4cKHcun5tfGg5dQXFJWU08rFnwYxuKBQwsV8Iizacpkyt4d+zaTw6IIQXF+0H4NOVEWw/moS1hQmb3ht8w/JZecWkZBRwKCqNw2frdzYZtEF6cUkZz41sxPmLecwYFkzHxk7M/vkUv+1K0JV7amgw93VpwNvLT+mWhfrZk5Gn4omvtD/WE3r40qSBHc197fBxsWTfGW22+N/zGQzv4MUf+xJRKBQ8s+gIOQWl7I+6TP9WHpxJyKmw/P6odHIKSki8XMC/5zI4FZ9zG89M3bIyN6ZvSy/e/vlYuXVqjQYPRwtc7M1J+s+Tfge182HpX9F8uEKbCf38ifYADOvoS2pWoa7P/rmkbAa28WHLv0lk5hbrHnBVUqamRZATtpYmFZY/k5BFbkEJMcm5HIhMIzE9vy5PwW1hZKSgZ7gPlubGKBUKnnl/OANfXFNuyKCigkGEY7o15LmvdrHjWCIKBez/8j4AJvZvwpFzqeQXlZJ/5a5Wy4ba7qq7jifx4iLtc0L+mDMIbxebSstn56swNzHiVFwGe09dqvfZ+/96sF8zfvsrEnUFF4pGSgVN/J1Zv/e83vIR3RrRetIS8gtL8HG1ZfXc0QA8NiycZX+eorikjIjzqUwZFo6xkbZ39Jer/mXN7nMYGyk5uPiRG5bPyi0iJSOfA6cvcuDUxbo9AeKuYNAAH6gys92tWze6dav4dhjABx98UG7ZjBkzaqUe0N5pePbZZyvd7npdunShS5cu1S4v7k6mxkpGdQmgz/NrdcssTLVPp932b6JeN56qKBQK1BoNGq792Hy36YzuruPlK4FOYXEpZiZGNyw/7LVNdGrmTocmbrw1sS0DXlxHaVn9Tb9dzlVRWqZh1ncRZOap+GNfIl8/2Ro4VeW2lVEoFHoZ4LTsYo7HZgHaIDOnQBvkFKrKdOe7ovKXMoq4//29dG7qzIM9/TA1NuLZxUdr3K47yegufvz5bxKFxdo7Ex6OlmTkFum677z1UEv6tfLiuz+r/pwrlQq9QOpYdAYRMdpMdHrOte6R2s+3stLyO08kczYxhw5NXJk7uTWbDiaybHt0rRyvoZxPysLGwpSXF+8DwNrClHaN3Ym+mE14kAu/bNcmEMKCnPlqzfFq1Xn953X9vjjSsgtp6G1Perb++Tav4PN9tfw7Sw/RIsiFVo1c+e2NAcz8ehfHYy7XxmEblFKp4IF+zRgy6zfdsgBPe/4+Gs/fR+OxtjDl+I+T+GDZ/mrXqf7PLY5v1hzDSKn98k7L1nZpKy1T65ZVVn7Ey6voHOpDmxAP3p7Sjf7PLqewWCYNEJUzeIBfn6SkpOjm6a/I+PHjdQ/EEve2YR392R+ZQkbuta4wS17oha2lCR5OVjw3JpyEtDx++/taFkit1vDnoQR+e70vB8+kEhrgRJlag1qt4YctUXRu5sH+0ymoStWkZBZUelu8svKmxkZMG94cAAcbM5xtzTFSKiktq9/dR77ecJ5vprfh8LkMujZz4a+IFMxNjfjfQO0MXV5OFvRt4c7o9/bobXc8LgtHa1O+eqIVyZlFdGzszKFzGZyIyyIxvZBgLxvOX8pFrYZLV/qWV6Sy8v5uVgzv4A2AtYUJdpZ3z4Dfh/o0ZNoX154I7WhjyjczOnHgTBq2lib0a+3FmDnb9bbZcCCB9XP6YG9tioWpMa2Dnfnr6EXW7L3AQ72DcHOwIDWzEA1wKaMAZSX9Zisr37mZG20buaBUgKWZMc525nV4Bm6PP3bFMGVQM96d3AGlQkH3cC8+WH6YguIy/vp4BEZKBZ7OViSl5XH6uv73K/45x8dPdGH17mgCPOywMjehTK3hu02n+OjxzmTkFJFboM26X7xc+d2OysqP6xmMl7M1piZKbC1NsbE0rbsTcRsNaB/A6bg0LqZfe2ru9NGtsTQ3ITE1h06hPmw+EFNuuz/+ieKPd0ex63giTf2cKVNrx0wsWnNM1+WmuKSMjJxC3TipilRUXlVaxsxx2jtedlZmONla6O4C3F1kmszaJAH+TcjIyNB1v6nI/fffLwG+AKCopIzPf9fPqP31byK2Vjf+EZy5cK9ukO3BMyk83DcEgHeX/UuHJm6EBzljaXYtUPxjdwzJV/pkqjUaFm04fcPyABqNhthLOQx+ZcMNf2jqiwWbznMqPpsmDWz5+I8zbI9IxdzUCNAe67GYLD5cdYaULP3B8qVlGka9u5th7b0pKCrlck4xKBSoNfDAh/vo29KdAHdrTI2v/Z/+ZtO1jHBiegGb/710w/KgveDaeTKVjYcu1eFZuH3srUz5eXs0kQnXBrCeupDFk/P30qelF7HJZXyy6qRuwO1VCWn5DH9z25VBtrnYWBiTnFlIalYRg17dwoA23ng4WqK8kslMzyli5a443fYHzqRRWFxaafmrikvULNoYxZ+Hk+ruJNwm+UUljHh9AwPb+WFqYkS/WatJutL1qP+s1fRv68uByGTW7o0tt+2G/XGkZRXSoqEL6/bGsmhmL1KzCrh4OZ+xczbTq6UPDtZmuq6qEdHpWJlf+65YtTOa9OxCziRkVlj+qpx8FdM//4dDUfoDqusrtRo+vC47/8zn2xjYIRB/D3s++nk/f/0bV267lxf+w7DODXG2t2Tn0XieG9cWgE9+Pciu4wm0DvHA2uLa9/+6PedISLnWbe/rP47csDxov8/iU7IZ9Pyv5BaoauuQxV1KodHcbcNj6p/4C2erLiRqTbuZe6suZCDN/R3p27oBVubGDOngx5s/HmLD/gtVb3iHMrFyrLqQAfVt6U7TBnY425oxoLUHw+fsIj6t/g5iU6vu7LY/2q8hDtZmBHnZEuxtR98XN9frAbGlxXlVFzIQS3NjnhgaipGRgo5NPYi9lMOML3caulm3pKzozj3fIb5ODOnUEAszY4Z0bshHP+/n178iq97wDpW2YcZt21dOTg52dnbYdpqJwrj2p9fVlBaTs+cjsrOzsbW1rXqDu4Rk8IW4A6VnF/H4vH/492xa1YXFLTt/MZcRc87X6+C+vlBrNOw8nsysRYfqdXBfXxSryvh24ynW748zdFPuCZm5RTz1yRb2nar/d5BE/SYBvhB3kBOxGZyIzai6oKgVW44ks+VIsqGbcc+ozqBbUTsKikr56Lcjhm7GPePMhcucuVD/BxmLu4cE+EIIIYQQwsBkkG1tkhGhQgghhBBC3EUkgy+EEEIIIQxLoYBKpse95XrvQZLBF0IIIYQQ4i4iAb4QQgghhBB3EQnwhRBCCCGEuItIH3whhBBCCGFQiit/6qLme5Fk8IUQQgghhLiLSAZfCCGEEEIYlkyDX6skgy+EEEIIIcRdRDL4QgghhBDCwCSFX5skgy+EEEIIIcRdRDL4QgghhBDiDnBvZtvrggT4QgghhBDCsKSHTq2SLjpCCCGEEELcRSSDL4QQQgghDExS+LVJMvhCCCGEEELcRSSDL4QQQgghDEwy+LVJMvhCCCGEEELcRSSDL4QQQgghDEqhUKBQ1EG2vS7qrAckgy+EEEIIIcRdRDL4QgghhBDCsBSKusm2SwZfCCGEEEIIUd9JBl8IIYQQQhiWZPBrlWTwhRBCCCGEuItIBl8IIYQQQhiU4sqfuqj5XiQBvhBCCCGEMCzpolOrpIuOEEIIIYQQdxHJ4N8Ben+aaegm3FMOfNTR0E24Z3R7J87QTbinWLsHGroJ95SUiI2GbsI9xc63paGbIOqSQql91UW996B786iFEEIIIYS4S0kGXwghhBBCGJiCuhkQK33whRBCCCGEEPWcZPCFEEIIIYRhKRQo6mDGG43MoiOEEEIIIYSo7ySDL4QQQgghDKyO5sGXPvhCCCGEEEKI+k4y+EIIIYQQwrDkSba1SgJ8IYQQQghhUIorf+qi3nuRBPhCCCGEEEJcERsbS0REBC4uLrRv3x4jI6Mqtzlx4gSxsbHY2dnRqlUrrK2tb0NLKycBvhBCCCGEMKw7pIvOnDlzmDt3Lh06dODs2bM4OzuzZcsWnJ2dKyyfm5vL4MGDiYyMpEOHDiQkJBAXF8fPP/9M//79a+MIakQG2QohhBBCiHvevn37eP3111mzZg3btm0jMjISlUrF888/X+k2ixcv5tixY5w+fZo1a9Zw5MgRhg4dytNPP30bW16eBPhCCCGEEMKwFMq6e1XTsmXLaN68Ob179wbAysqKqVOn8ttvv6FSqSrcpri4GFtbW5ycnHTLAgICKi1/u0gXHSGEEEIIcVfLycnRe29mZoaZmZneshMnTtCsWTO9Zc2bN6egoICYmBhCQkLK1Tt16lQ2btzI/fffz8CBA0lMTGTJkiXMnz+/9g/iJkgGXwghhBBCGJSCazPp1O4fLR8fH+zs7HSv9957r1wbsrOzcXR01Ft2NTOflZVVYbutra3p0KED+/btY9WqVaxcuZIGDRoQEBBQ7WPPzs7m1KlTuvebN2/miSeeYPny5dWu43qSwRdCCCGEEHe1hIQEbG1tde+vz95fXZaXl6e37Op7c3PzCuudM2cOS5cu5fjx47qLgaeffpr+/ftz7tw5TE1Nq2zb5MmTGT9+PE2bNiUqKophw4bRo0cPli1bhomJCaNGjar2cV4lGXwhhBBCCGFYV2fRqYsXYGtrq/eqKMAPDAwkPj5eb9mFCxdQKBT4+/tX2Ox//vmHnj176vXBHzNmDPHx8cTExFR52Dk5OezYsYMRI0YAsHz5ch544AE2b97M559/ztKlS6t9Cv9LAnwhhBBCCHHPGzhwILt37+bSpUu6Zb/++iudOnXCzs4OgNTUVJYvX67rsuPj48OZM2f06omMjEShUODl5VXlPlNSUnBwcNC937FjB4MHDwYgODiYlJSUGh2LdNERQgghhBCGdQfMgz927Fi+/vpr+vbty+OPP86RI0fYtGkTf//9t67M6dOnGTduHEePHiU8PJyZM2fSsWNHRowYweDBg0lISODTTz9lxowZ2NjYVLlPHx8fUlJS2LRpE9bW1hw4cICePXsC2guFxo0b3/QhgwT4QgghhBBCYGxszF9//cXChQs5dOgQTk5OHDlyhCZNmujKuLq6cv/99+uy7uHh4URFRfH999+zZ88e7O3t+eWXXxg4cGC19mlubs67777L4MGDUavVzJ49G3t7ezQaDV9//TULFy6s0bEoNBqNpkZbiloTPOOAoZtwT9n2jEPVhUSt6PZOnKGbcE+xcKz6drCoPSkRGw3dhHuKnW9LQzfhnhGzoNdt21dOTg52dnY4D/wIpYlFrdevLikkfeNMsrOz9QbZ3mlyc3MpKirCxcUFAJVKRVRUFM2bN69RfZLBF0IIIYQQBqa48qqLeu98NjY2el16TE1NaxzcgwT4QgghhBBC3Dbx8fG8/PLL1Srr6+vLO++8c9P7kFl0hBBCCCGEYSmUdfe6wygUCszNzXWvS5cusWzZMk6ePIlarSY3N5ctW7awYsUKTExMarQPyeALIYQQQghxm/j4+LB48WLd+xYtWvDTTz8xYcIE3bLCwkLuu+8+vSk0b8add1kjhBBCCCHuKQqFos5ed7Lk5GQyMjL0gnsACwsLnnnmGTZv3lyjeiXAF0IIIYQQwgAKCgpIT08nOzu73Lro6GgKCgpqVK900REVsrEwwt3OlNjUIkrVFc+kqlSAv6s59pbGXEgvJj23BAA3OxNsLIyJSSmkkk2FMBiFAvzdLMkpKCU9R1VpORsLYwLdrQA4FpuNp6M5no7memVOxudQpFLXaXvrOxMjBX7OpiRlllBQwbnysDPBw/5aH9O49GIy8ssAsDZT0sDZlNTsUtLzSm9bm+szG0tTvF1tOZ+YQUlpxZ9Nd0cr3BytibmYSW7Btf8Drg5WONpacC7hMmXy5V0trnamWJkZEZtaWGkZU2MFge5WWJkZcToxj4LiMloF2unmdlGVqjl+Iff2NPiOdm/OohMQEEDz5s3p06cPr776Ko0bN6agoIDt27fzxhtv8NFHH9WoXgnwRTkDWzgye4w/KdkqjJUKHvwykrScEr0ygW7mfDUpGIVCweXcEhZvv8Q/p7OYc78/nRrZUVyipqRMw4PzT3NZfpjFHcLcVMmPT7fC2dYUOysTvvkzjoV/xpUrN2NIIJP6+HL+Uj6Xc1VMnn+Urk2cGNnREwAjhYImPja0m/WPBPg34OtsyreTfMkrVuNiY8zMXxLZdz5fr8zINvYMCbcj7cr3xDfb09l1No+ujax5/34v4i+X4ONowrc7L/PtP+mGOIx6o3+7QOY/N4Dky3mYm5kw/MVfSUzN0Sszc3wHpgxtSUJKDr4edkx+bx1/H73AR9P6MKB9EHmFKpRKBcNe+JWL6RJ03shzwwIY29mLvKJS4tMKeXR+RLkLo25Nnfh4YhNSs4rJKyrjhZ8iiU0p4KenW3AyXnt+L+eq+N/CE4Y4BHGHWLt2LTNnzmTkyJGUlWkTHG5ubsydO5cpU6bUqE4J8IUepQJeGu7Lg/MjOXOxgOeH+PBodw/eXxuvV+7t+wPYcjyTj9cn6JZZmRlx8HwOL/0SA8BXkxrSJ9SR5XtTb+sxCFGZEe08yMov4b4PD+FobcKfb3Zi1d6LpOdey2K2C3ZgQncf+r+5l6SMIt3y5buTWL47CYD+LV3pE+ZKToFcvN7I4z1d+HlfBt/tvEy7QCteHuLOsHnR5cqtOZrNjtO5xKYVU1yqDZBGt3Xg7TXJbIjIxs/ZlG8n+0mAX4V3pvZk/Jt/cPB0EjPHd2DGfe2YOX+rXpnHhrakx/QfSUrL5YG+zRnftzl7TiRwIjpVV3bBrEGM6BbCl6sOGeIw6gVvJ3PGdPSk1xv7yC0s5YenwhnQ0pX1h1N0ZazMjPjokSa8uuwMm4+m6W2fV1TG679EkVNQwsXM4tvd/DuTQqF91UW9dzhXV1d+/PFHvvnmG+Li4rC2tsbLy+uWxg9IH3yhx93elOISNWcuavt8/Xk8g1BfK70ylqZKwnyt+W7HJZp4W2JnaQRAfnEZfxy69gNsbW6sq0eIO0Gonx1bjmkvODPySoiIzaaxj41eme7NnFl94BIAQR5WFf42TOjmw887E+u8vfVdM28L/jqlzVIeiM7H1dYYcxP9E3opq4SODa2Ye78XW18IJqyB9kmW+87l06upDe0DrRjRyp495/Jue/vrEwcbcyzNTTh4WnsRum73WVo28ihX7p+jF3igb3O6hfvSv10gfx+Jo6RUzQ+bInRlbC3NOBktiZkbadbAhoPnssgpKEWjga3H0gnz039KanNfWzLzSthzJpMmPtaYm1wLuc4n5zNnfCPWvdKOTx9terubL+5Q5ubmhISE4O3tfcuDgyWDL/RYmhnp9ZMtKFZjZWakV8beypicwlI+e6QhVmZKfJzMeWtVHOuPXNaVeW2kL7vOZHEsTn6UxZ3D0syIwuIy3fv84lKszK//fJvg7WzBt9NbYmaiJDu/hPGfHKbgynYB7pY42Zryb3TW7Wx6vWRpqtT/PlFpsDJTUlRy7d/g98NZ/H44C4BhLe14boAbDy2M41RSIfe3d+CZ/m7YWRrxyaaU66sX/2FlYUpB0bWulPlFJVhbmJYr99fhWJ5/oCN92wZgYmxExHn98/r6xK6ciE7hn2MX6rzN9Vn1vkuMUZWqWf5cS8rUGtztzXl0/jFOxucy/pMjAJibKFk5qzWdGzuyOzLjth7DnaauZry502fRAcjKyuKLL74gIiKCvDz9uCkwMJAvv/zypuus9wH+jz/+SFhYGGFhYYZuyl0hNVuFh70pxkoFpWoNvs7mXMrSH4iYllOCrYUxLyyL5lKWitYBNrw60pf1Ry5jYqRg7vgAjsXl8dMu+UEWd5aUrGIauFjq3vu6WnLpP91wAC5mFGFqrOTBT/8F4IenW9K1qRObj2gzmg91b8Byyd5XS2pOCQ2cTEnLLcXSVImNuVI3gLYiRy8UMrWHCwBzRnny6sqLnEgsxMpMybYXGvLPmVxdFx6hLzUzH2d7S8xNjSlSlRLgac/FNP3+9w425sx5rDvhD39DQXEJHZt78/H0vvR/dhlGSgWfPt2P6MQMPltx0EBHUX9ov0ssdO99XSy5lKHf1eZiZjH2Vib0eG0vJWUaJvXyYXxXL15eekZXpqhEzenEXFxsy1+MiXtH//79SU9Pp0+fPvj5+emt8/LyqlGd9T7Af/fdd5k2bVqdBfjLli2jcePGtGzZstrbnDt3jv3792NsbEz79u3x9/evk7bVhZzCMiIu5PHqKF/+PpXF0wO9WbhVe8s3xNOShMvF5BeXsenYZe7r4MrB8zkMbOHEhfQizEwUfDs1hOiUQk4n5tPK35qLmapyFwhCGMr6Q8l882Q4cakF+LtZYmthzPELOdhYGOPpaE5UUh7rDyWz4Ilweoe5YGKkJMjDmoR07QwZFqZGDGjlRu/X9xj4SOqHjRHZPDvAjYXb0xgUbsfWUzloNNqZcwAuZZcQ4mGOpakSa3Mlj3ZzZtdZbfbqcl4pw1rZY2mmpLGnOSVlGlRlEtxXRlVSxvZ/Y/nwyd6s3X2WWQ901HW7aeznTFJaLvlFJSiVCh7o14yo+MuM7NaYtMx8jI2U/DJ7JKmZ+ew/nUS7pl5cTMsl4boBuuKag+ey8HG2YHKfBlzKKGJcFy/GfqxNCrQOtONwdDYnLuSQlV/CyA4eJF0uonszZ3ZHZmBraUywhxUKhYKmDWzo1dyFz9bHGviI7gD3aB/8hIQEIiMjiY+Px87OrtbqlT74Vfjwww/Zvn17tcpqNBpGjBjBkCFD2Lp1K6tWraJJkya8//77ddzK2vXcT+dRqzWM7+TKz7tT2HBUe9vwwS5ueDlqswyv/hqLmYmCyb08KFCV8cZvcdiYG2OkVBDsYcnMIQ2YOaQBHRvV3odViFt1/EIOr/98huHtPfB3s+SheUfQaCDQ3YpxXbwBiE0tYPbyM4xs78nw9h68uTySU1dmu2gdZM/yXYnkFsrg2ur4aU8G207mML6DI+m5pby9Wju2oVOwNZ2CrQF4sJMjzwxwZXwHR3ZF5fHxla44L69IQgFM6e5MiIc5U76LRyPx/Q099elmcgqKmTSkBSt2nGbpn9qZWR4eEEZDb0dUJWWMfX0VrUM8mXFfewqLS3j2iy1YmBljaW6Cn4c9r0/syusTu9K3baCBj+bOVlKm4aHPjtLYy5rBbdx4/ofTxKRox5zNGhGEiZECjQamfBVBuJ8tk3o3YF9UBou3xRPkbsWsEUE8OzSA5g1smDj/GEmXi6rY471AUYevO5epqSnOzs61GtwDKDQaw35l5ufns3XrVlJTU2nbti3h4eF661NTU/nzzz/Jz8+nXbt2tGjRQm99SEgI06ZNo1+/fuzfvx8zMzP69++Pra3tTdVztauPiYkJe/bsoUGDBuTn5zN9+nRat25Njx49AHjyyScxMTGhImq1mrVr1zJs2DBdn6/ly5czfvx4zp49S1BQUIXbBc84UO3zJW7dtmdq9thncfO6vRNn6CbcUywca3YrV9RMSsRGQzfhnmLnW/076eLWxCzoddv2lZOTg52dHa7DvkRpYlH1BjdJXVJI6ponyc7OLhcb3ilatWrF119/Tdu2bWutToN20Tl8+DBDhw7Fzc2Nli1bsnDhQoYNG8brr78OwLZt2xg+fDjt27fH3d2d559/nkmTJjFv3jy9en788UcWLFhAhw4dOHToELNmzSIiIkJ3NVSdet59911sbGzIzc2lW7duWFhYkJubi0qlIjMzk7i4OECbpa+MUqlk+PDhest69uyJRqO5YYAvhBBCCHEvUyiUKBS137GkLuqsTUlJSbi5udGtWzeGDh2Kh4f+7FdeXl48//zzN12vwQL80tJS7rvvPvr27ct3332HUqn9B9i3bx8AKpWKqVOnMnXqVD7++GMA9u7dS+fOnRkzZgydOnXSq+vIkSOYmpqiUqkICAhg6dKlPPnkkzdVT15eHkePHsXC4toV5MKFCxk6dCgzZ86s0XGuXr0aY2PjcncmhBBCCCHEvU2lUqFUKunVqxf5+fmcP39eb71aXbOHKRoswD948CCxsbFs2bJFF9wDdOjQAYCTJ08SExPDU089pVvXsWNH2rRpw9q1a/UC89GjR2Nqqu0bbmpqSlhYGNHR0Tddz5gxY/SC+1t14sQJZs6cyQsvvICnp2et1SuEEEIIcVe5RwfZ+vv7s379+lqv12AB/qVL2sFW108HdFVCgvYJqd7e3nrLfXx8dOuuur5PlYmJCSqV6qbrcXV1vYkjuLGzZ8/St29fRo4cyZw5c2qt3poIdDPngc5uuveHY3LZeGXgbIeGtnRsZEdiRjFrDqVTVFKzK0UhDK1PuAst/O354I9zANhaGjO6oyeudmaciMthw78ybWtteGWou+7v2QVlzN+mfUKnr5Mpg8LtKC3TsO5oNpeySyqrQtwkMxMjXn64C38djmHnsXh83e14YkRr3fojZ5P59a9TBmzh3aFLE0d6hTrr3q85kMzR2BwUChjcyo3G3taciM9l89FUGfAt7ngGC/CdnJwASE5OLhd8A7i7a39E0tLSdH8H7WDZ1q1blytfmdqq52acPXuWHj160KdPH7777juDP2TB08GMZj7WrD2sfcpseq72h3did3e6NrZn39lsBrd0okcTex5ffNaQTRWiRgLcLRnR3pN2wQ66AP/baS24mFHEyQs5TB8ciKu9GUv+ijdwS+u/YS3tmfen9pkAeVce9NPEy5y3R3ny1+lcPB1M+G16AAM/OkdukSQMasPrj3alqZ8LqZn57DwWj7uTNW2berHsyiw5KRnyQMHaEOpri5ONKQfPZQGQW6T9fH/8SBMUCgVRSXk8NzSAhh5WfL5BprWsfXU1482dl8G/dOkSn332GZ6enowZM4bPPvus0rKenp56vVCqy2ABfvv27XFxceHLL7/kvffe0y2Pjo4mMDCQ5s2b4+rqyo8//sisWbMA7fzy+/bt44UXXqj2fm61HltbWwoKCqq9v/Pnz9OjRw969erF999/r9f9yJCMlODpaMr55EL+jdFO+bf1eCZL/k4G4I9D6fzxXLMKt+0X5kgTL0v+icyidYANP+1KoVClppW/NV0a21NQXMbKA2lk5JXSoaEtCgV4OJjRwMmMtf+mE52inf6rovL2lsaMbu+Cu70pSgW8vzae4hJJjYjqMzVW8tywIOb8eob1r3XQLQ/2suaheUcoVJWRkVdCm4b25bZ1sTNlbGdv8otLORKdjZeTORsOp2BhasTojp54OJhzJCaLbRHaLPXM4UH8vu8iQ9t6kJJVxK+7k1BrqLR8lyZOdGrsiIWpEQfOZrLxLrmL4GZnTHZBGZuOawPL1OxSxn4Vi+rKQ6hCG1ji6WBC1CX9B/808TKnd1NbYlOLyVepSc0p4WRiER52JgxuYYeFqZJtp3I4nVSEs40xfZvZcj6liI4NrTkSV8DOKO3+KiqvUMDwlvY0dDfDxEjB74eziLxY/6ceHNyxIReSsylW6T8gzEipxNvVluikTPacSKhw2/7tAmkV4sHfRy7QPNCV5dtOkZVXRKtGHvRpG4CqpIyft5wgOSOfdk29sLE0xdHGgoY+jvzxTxSn47Sf44rK21qZMaFfc3xcbVEqFLzz425y8osrbEd9YmFihIe9GYejszh/KR+AT9bGkHhlGsuzF/MZ16V8l1tjpYL7O3vibGvK2kMpDGvjxrwrc9v3b+FCqK8tFzOL+G3PJVSlakZ38CAyMY/2jRywMFWy7J8kMvNLKi3v52rB8Lbu2FmZkJlXIhcY9VxBQQGHDx8mKCiIlJQUvvzyS9q1a1dh2ZpO0GKw6NPc3JwlS5Ywb948hg8fzttvv83IkSN1M9tYWloyb948Xn31VaZMmcJrr71G9+7dGTJkCIMGDar2fm61ns6dO/Pdd98xd+5c5s2bR0lJ5bedCwsL6dmzJ6WlpYSHh/P5558zb9485s2bx6lThrt9ej65kD8OppOeW8KY9q58+lBDABL/89S9EW2c+f1gWrltH+jsxlP9vcgtKmNCFzem9PbE3ETJoJZOvDHGn/ziMuwsjfnpycaYGClo7G3J7DH+NHAyw9hIweKpISgUVFp+zv3++LuYcyGtiJiUImo4lkTcw2YOD2L+hhjyivQDoLd/i+Lb6S2Y+2AT7uvkxTdb4vTWmxorWTmrLS52ZliaGvHeg03o0MgRYyMFy2e2JtjTmow8FVP6+TG+q/Yu47iu3swc3pCC4jLu7+LN+K4+lZYP8rDi7Qcak5lXQnRyPuk5d8cD3+b9mUpabilBbuasnB6IjbmS9LxSXXDfzNucgmI155L1g70AF1O+meiLWqMhxNOcV4Z64O9shqe9CT9M9cPCVEmhSs0n47wJ8TDH3tKIJ3u7MKSFPXlFat4a5Ukjj8rLj2rtwMjW9lzKKiE2TUVBcf3/MvFysaF3mwC+WXNEb3ncxSx+3BRBamY+o7o3ZtkbI8ttO7Z3U956rAe5BSom9GvOc+M6YGNpSv92gXzxbH8Ki0qwMjdhzftjsTI3obGvMx880ZvGftouKiveHo2piVGl5d97vCfN/F2JuZjJucQMSkvr//neeeoy/5y+TF5RKa+OCebxfr4AuuAeYHg7d1buvVRu23cnhNCvhQuFqjJeGhnEyA7amVBeGhXE2M5eXM4rIdTXls8naxNpXZo48fYDjTA1UhDobsX7DzW+YfnvpoWjVCqISSngQlphnZ4HQ1EoFHX2utO4u7vz0UcfsWDBAgACAwPZtm1bha+rZW6WQafJHDRoEGfOnGHlypVkZGTwyCOPMHToUN36cePG0axZM1avXk1+fj7z589n2LBhenU8/PDD5WaoGTJkCPb29rdcD8Ds2bMJCQnh9OnTFBUV3XCaTLVazciR2i/a+Hj9rgC5ubk3OhV16lKWiqW7tZnDpbtS2DunJVZmSvKv/AA+2sMdHydzXl9RPiPQL8yRl36J4Xi8NpOx803t8wNGt3PhQloRHvbawc3mJkoae1kCsOHoZeZtTASgfUNbXGxMKi0fk1KIv6sFhSo1u6OyKZEnVYqb0LahA92aOWNmosTESImlqRGvjA7m3VVnGdjKndiUfGJTCvB3s6JtkAMxydfuxjX3teVCWgGv/xwJQEp2MWF+djRrYIurnRlqjQYfZwsuZRTRv6UrP+9MRAE8t+QkhaoyziTmMqiNOyfjKy6/5Vgq+UVlmJkoORWfw+HzmQY6S7Xr530Zur/Pf8iHDkHWbDmpfeJpS19Lpvd1ZfpP8aiv+6/co4kty/Zl8PVf2kSCnaURAH2b25KZX4aNuRIbcyXJOaX0aGLD1pM5JGWW8NqqiwBYmCpp6mWBrYVRheWPXSjA2EiBUqHg8IV8Llyu/xdUH0/vS2ZOIe//rxetG3tSpCrldGwaO47EsXjdUQC+WXOE4z8+jquDFamZ+bpth3QO5rkvtrArQvtbdGDRJADG921OzMUsPJ1tdGVbNtIGo5v2n2f2dzsBaNPYCx9X20rLn0/MoFWIJ8UlZez4N46C4vo/5uJEfC4nrjzU7q/j6cyf0pwFf14AtBn69x5szKFzWfx5rHwyrGeoMx1f3IOqVM1qu2RWztJ2AR7T0ZON/6bg42xOgaqMfi1cMTHSBpxfbYpja0Q6SgXsfKfTDcvHpRZgY2FMVFIeO09nlNu/qF/OnTvHI488wrFjx+psHwYN8AF8fX157rnnKl3fvHlzmjdvXun6l156qdyySZMm1Uo9oB2w+9BDD1W63X9ZWVmVm6P/TmNpaoRSoaCkTINCAS8P96WkTM1rv1V8u6+0TIOVmfaH2MRIgZmxQrc8OUvFhTRtZiMmJZmUK4Pqcv7zlE9VqQYTY0Wl5T/dmIifizlhvtYsfCyYF3+OITKp+l2ixL0tLbuYZf9ouyeYmxhRptYQk5KPp6M5jbysmfi5NvN5JDqLV8Y0YvnuJN22pWVqrMyufQVe/XtJmRpVqZro5Pz/7Ed1ZRsNhVe6ShSXqjE1UlRaPj1HxYi5B2gdZE+nECcmdPfh8a8j6uhMGIaNuRGqMm2ioG8zW+5v58BTP8VX2Pe+tEyDw5WgHsDKVKlbnltURmya9hzHpqmIvKjNUOYWXrsroypTY2KkqLT80QuFRF6Mp6WfJVN7unDsQgHf77pcNwd+m/z21ykcbbUzuwV6O1JQVEJmrn721sLMGDMTI1Ql+newSkvVWFtoEypGSgUWVz/fpWVk5hZxLlEbJJ5LzOBCcjaBXg5k512766IqKcPE2KjS8rsi4vHzsKd1iAdLXhnKywt3cPB0EncLW0sTVFfuSliZGfH55GasO5zC6gPJFZZXq7UXoapSNVbm175XSkrVXEgr1E1g8dGaaN26q7+Vas21iV4qK//o/AiaNbAh1NeW1S+2Yei7B8kv1v83r/cU1NEsOrVf5a1ycHAgJSVFNyFMXTB4gF+fJCYmsnLlykrXT58+HSMjo0rXG8rAFo60DrDB1FhJp0Z2LN+bgqpUw7ODvBnU0onNxy7z+ijtrci3Vl3Q2/aXPSl89GAg/0Rm4eVghloDpWoNi7Zf5L1xARyOztV9yWTsqzyDU1n5Kb08cLc3xcxYiaN1xU8IFqIysakFxKZqLwhtLIyZ2t+PX3YloVRAVl4JvzzXmrjUAro0ceLnnYl62x6/oJ0dY9mzrUnJKiLY05p/o7M4FZ/LmaQ8+rd0I/pSPmqNhtTsyvsWV1a+oYcVE7r7ABDgZkV+cWmlddQXwe5mjGnrgFKhoImXOUqFgj1n82kTYMmH47xZdzSLp/pqZyP7ZV8GMWnXfrw2RmSz6qkAPB1MMDNR4utkypaTOaw7ms197RxoF2hFWm4pGo2GI3GVX+RXVr5HYxs6NrRCqVDg62TKsQv1P1Hw+z9ndH/3dLbhck4hx86lMLBDEN3CfTE1MaJLuC9/7DxDVp7+eIPvN0bw9fMDGdghCG9XW4yUSsrUar76/TCLXhyMlbmJrs/88m0nK21DZeUfH94Kfw97TE2McHO0vuHd7frisT4N8HIyx9rcmB7NnHnrtygAvpraHFc7M8L9bQn3tyXpchGLturfpf9lVxKrZrXmcHQ2Hg5mlF65Gz1/UxwPdvPmcHQWqlI1admqG96prqh8qVrDm2ODAbC1MMbcVEn9P9sVuIemyWzQoAH+/v54eXlhZ2dHUlISzZpVPA4yJCTkhrFnZSTAvwlFRUW6J9pW5E79gkvPLSEmpYjiUjW/7k3lRII207jvbA6pVUxlt/VEJgmXiwnxsuRUQjLfPh5CbmEZB8/nMvaz07QNssXe0hgFoNFo6/yvJX9fIiu/lKQMVYXlEzOKKShWU1ii5pu/LnIhvf4P0hKGUaQq4/1V2hl01BoY/t4BujVzwsHKlF93J3EsNluvvEYD4z4+TO9QF/KLy2jua6vL2E396hjtgh0IcLPExFip6z9/dYYegLjUAlbtu1hp+QJVGdHJ+ajVsOv0ZXaeSr8dp6FO5ReriU1TUabW8M+ZXPacy6NMrR1k+/56/cxmoUr/+zAtt5SRn8XQoaEVcWkqnuztwqWsErILyxj1eQydg61xszNGqVCQX1xGTqGaZf/pDrQ7Ko/iEk2l5TPyFcSmqSgt07DxeDb/xtb/AP+/Nu47T5FKe5GYnl3IucQMikvKWLblJIfPXCxXfseROIa98CvhDd05di6Zle+MISUjn4vpefSdsYwu4Q1wtLFAoYAytYZ9JxOxMr+WZPl2/VGSL+eRlVdUYfnE1BxKy9QUFpfy9R+HOZtQ/7uNJGUUUVSiJq+wlC82xOr6uq/cewn7/ySgLueWz7p+ui6G/WczcbIxJSNXxRMD/AD46e9E9kRmEO5vi5W5MTkF2t/cFXsvEpd67W7MJ2tjKi2v0UB0cgFoIKewhHdWnqPgbsve32MUCgU7duxg/fr1HDx4kKVLlzJ58uQKy7q4uNRsH5o7NSq9hwTPOGDoJlTKwcqY6f29MDZS0DbIls3HMnT96+urbc84GLoJ94xu78QZugk3NLydBy0C7HC2NaV1kAND39lPSlb9vci0cPQydBNu6IleLjhaGxPgaoqlqZJxX8XW6/nEUyI2GroJlbK2MOG1R7piZKSkfVMv/jl2gVcW7jB0s26JnW9LQzehUk19bBjTyQMLEyO6NXPizeVRbD5avq9+fRGzoNdt21dOTg52dnZ4jP4WpYllrdevLing0spJZGdnl3tu0p0gPj6ehQsX8s4779RqvZLBFzdUUqYhJqWIUrWGtYcvczjGcIOFhahtqdnFRCfn8290Fm/8fIb0CjJzovYkXFaRmV/GvnN57DqbV6+D+ztdmVqjnd2mTM2aXVG6wbaibuQXlxKdXECxqoxv/4rn7MX8qjcSAm13ndoO7kECfFGFvKIy3Qw8Qtxt9p7JYO+Z+t+1oL5Ydyy76kKiVhQWl+pm2hF1Ly61kLjU+n132+DuoT74t8Od8RQmIYQQQgghRK2QDL4QQgghhDAsyeDXKsngCyGEEEIIYQCRkZFMmDCB7du31+psjBLgCyGEEEIIg1LU4Z87mZOTE1lZWfTt25fAwEDmzJlDQkLCLdcrAb4QQgghhBAG4Orqyvr160lISGDKlCksXboUPz8/+vXrx6+//kpxcc2mbpYAXwghhBBCGJjiWj/82nzd4Rn8qzw8PHjxxReJiopi586duLi4MHbsWDw9PZkxYwaxsbE3VZ8E+EIIIYQQQtwBjh49yq+//sqmTZtwc3NjwoQJHDx4kJCQEFavXl3teiTAF0IIIYQQhlUX2fu6mpmnlmVkZPDFF1/QokULWrduzfnz51m8eDGJiYl89tln7N27l48//phPPvmk2nXKNJlCCCGEEMKgFAolCkXt553ros7aFBUVRVhYGO7u7kycOJG1a9fi4+NTrlyvXr3YsmVLteuVAF8IIYQQQggDsLOzY82aNfTp0welsvKLkcaNG7N27dpq1ysBvhBCCCGEMLC6GhB7Z3fRcXd3x93dvdbrlQBfCCGEEEKI2yQ+Pp6XX365WmV9fX155513bnofd3bHJCGEEEIIcddTKBR19rrTKBQKzM3Nda9Lly6xbNkyTp48iVqtJjc3ly1btrBixQpMTExqtA/J4AshhBBCCHGb+Pj4sHjxYt37Fi1a8NNPPzFhwgTdssLCQu677z4cHBxqtA/J4AshhBBCCMO6R6fJTE5OJiMjQy+4B7CwsOCZZ55h8+bNNapXAnwhhBBCCCEMoKCggPT0dLKzs8uti46OpqCgoEb1ShcdIYQQQghhWHWVbb/DM/gBAQE0b96cPn368Oqrr9K4cWMKCgrYvn07b7zxBh999FGN6pUAXwghhBBCCANZu3YtM2fOZOTIkZSVlQHg5ubG3LlzmTJlSo3qlABfCCGEEEIYlOLKn7qo907n6urKjz/+yDfffENcXBzW1tZ4eXnd0gxAEuALIYQQQghhYObm5oSEhNRKXRLgCyGEEEIIw1Iota+6qPcOVtVDr+RBV0IIIYQQol66lx50dT1jY2O9l0aj4ciRI6xYsQKlsmahumTwhRBCCCGEMIAGDRrw/fffl1uuVqt5+OGH8fb2rlG9ksEXQgghhBCGpaCOHnRl6AOrGaVSyaRJk1i3bl3Ntq/l9gghhBBCCCFuUVJSkjzoSgghhBBC1FP36IOuUlJSWLRoUbnlSUlJLFu2jDlz5tSoXgnwhRBCCCGEMICcnBzWr1+vt0yhUODq6sonn3zCo48+WqN6JcAXQgghhBAGda8+6Mrb25sffviBRo0alVtXWFjIuXPnKlxXFQnw7wAph1cZugn3lNbTfQzdhHvG4S/6GboJ95RRP5kbugn3FEtnf0M34Z7i5N3Q0E0QotZFRUXxyCOPcOzYsZtaVxUJ8IUQQgghhGHdow+6upGcnBxsbGxqtK0E+EIIIYQQQtxGiYmJvP3221y+fJnExEQef/xxvfUlJSXs2rWLQYMG1ah+CfCFEEIIIYRh3WOz6JSWlpKenk52drbu7/9lbm7OxIkTmTZtWo3qlwBfCCGEEEIYlEKhQFEHwXhd1Fkb/Pz8WLlyJYmJifzwww+88sortVp//e2YJIQQQgghRD3m7e3NtGnTOHXqlG7Z5s2beeKJJ1i+fHmN65UAXwghhBBCGJiiDl93tsmTJ3P27FlAO3POsGHDiImJYerUqaxaVbOZFiXAF0IIIYQQwgBycnLYsWMHI0aMAGD58uU88MADbN68mc8//5ylS5fWqF4J8IUQQgghhEFd7YNfF687WUpKCg4ODrr3O3bsYPDgwQAEBweTkpJSo3plkK0QQgghhBAG4OPjQ0pKCps2bcLa2poDBw7Qs2dPACIjI2ncuHGN6pUAXwghhBBCGNY9Nk3mVebm5rz77rsMHjwYtVrN7Nmzsbe3R6PR8PXXX7Nw4cIa1SsBvhBCCCGEEAYybdo0Hn74YYqKinBxcQG0D7r67rvvaN68eY3qlABfCCGEEEIYlkKpfdVFvfWAjY0NNjY2uvempqY1Du5BBtkKIYQQQghxV5EMvhBCCCGEMCjFlT91Ue+9SDL4QgghhBBC3EUkwBdCCCGEEIZ1dRadunjdwSIjI5kwYQLbt29Ho9HUWr0S4AshhBBCCMOqq4dc3eEBvpOTE1lZWfTt25fAwEDmzJlDQkLCLdcrAb4QQgghhBAG4Orqyvr160lISGDKlCksXboUPz8/+vXrx6+//kpxcXGN6pUAXwghhBBCGFhddc+5szP4V3l4ePDiiy8SFRXFzp07cXFxYezYsXh6ejJjxgxiY2Nvqj4J8IUQQgghhLgDHD16lF9//ZVNmzbh5ubGhAkTOHjwICEhIaxevbra9UiAL4QQQgghDOseHWQLkJGRwRdffEGLFi1o3bo158+fZ/HixSQmJvLZZ5+xd+9ePv74Yz755JNq11mjefAPHjxI27Zta7KpEEIIIYQQAoiKiiIsLAx3d3cmTpzI2rVr8fHxKVeuV69ebNmypdr11ijAb9++PU2aNOHRRx/lwQcfxMXFpSbVCCGEEEIIceUxV7XfseROf9CVnZ0da9asoU+fPiiVlR9/48aNWbt2bbXrrdGZPH/+PCNGjGDevHl4eXkxatQoNmzYQFlZWU2qE0IIIYQQ4p6Tm5vLsmXLbhjc10SNagsICGDOnDnExcWxbt06jI2NGTVqFA0aNOCll17i3LlztdpIIYQQQghxF7tH++AbGxuzZ8+eWq/3li4XlEol/fr149tvv+Xtt98mPT2d999/n0aNGjFkyBAuXLhQW+0UQgghhBCiTpWUlPDtt98ybdo0Zs+eTUxMTLW2O3v2LG+//TYzZsxg3bp11d6fn58fVlZW7Nq1q6ZNrtAtBfi7d+/m0UcfxcPDgy+++IKXXnqJuLg4Dh8+jJGRESNGjKitdgohhBBCiLvVHZDBV6vVDBgwgPfffx9vb29Onz5NaGgoR44cueF2S5cuJSwsjPj4eBo2bMhPP/3EW2+9Va19Xrx4EW9vb3r27MmQIUOYPn06M2bM0L0+/PDDarf/v2o0yHbu3LksWbKE2NhYhgwZwm+//Ua/fv10/YcaNGjA8uXLsbKyqlGjhOH5ejjg4+5ARFQSuQUVP0WtRSNvLMxNOHomkcLiEt3yAG9nvF3tOBKZSF5hzZ7Adq9p5u+MmakREefTKC1T661ztrOgTYiH3rKTsWkkpOZibmpEi4Zu5BeVcCImDY3mdrZaiKrZmCsIdjMlIaOU1NzKx2lZmSpo7WfO4QtF5BdrCPMxw97iWg6qVK1hz/mi29Hkes3TyQJfV2tOXcgip6Ck3Po+LTxQKq8FPKlZRRyNzqBlkCMuduYA/PnvxdvW3vouyNUES1MFpy+qKFVXXq6xhylGSjiZpMLWXEmLBmZ666PTSkjMLK3j1oqqrFixgn/++Yfo6GgaNGgAwKBBg5g5cybbt2+vcJvY2FgmT57Mxx9/zJNPPgnAk08+SVJSUrX2qVKpdD1iNBpNuQdaqdU3+GDdQI0C/CVLljBp0iQeeeQRXF1dKyxjbm7ODz/8UKNGCcN6ZGg7Xpncl6gLqfh6ODJo2gLikzN165VKBb9/PAk3J1tSLufg5mTLsBnfkJqRx9tPDqJP+xDSs/IJ9nVh0PSFnL2QasCjufN99WwfXZCeV1jCyFf/QK2+Fqm7O1kxtldj3fsuod6Me2sdJaVqNnwwmsS0XEyMjUjPKuChdzYY4hCEqFBDVxM+H+dK3OUSAlxMmLsxgx1RhRWWfaavA52DLHjql1TOppTQIdCcQBcTAJytjShTIwF+FYZ39OGNB8I4l5SLn5sVY9/bSUxynl6Z0V18MboS4DfytmPjoSSORmfQpZkbzf3s6RXuQdCjf1CmlmxBVV4c4ED7AAuyC8tQa2DKjymUVHANG+hiwhtDHLmYXcazv6bhaKVkcNi1BGgrX3Ne/SP9ng/wFQoFijroL38zda5bt46OHTvqgnuA8ePH89BDD5Gbm4uNjU25bb7//nusrKyYOnWq3nIvL69q7dPf35/169dXu43VVaMAPyoqqlrlJkyYUJPqhQEZGyl59bF+9P3fV8QkpjPrkd5MG9uVWfPW6MqE+Lnh7eZA6/Ha20bTxnbl8dGdeeubzew8Es2rX2qDzLefHMTQbs346MeKr3oFNAtwpmWwO12nLUNVqmblnOEMbBfA+n3RujInY9J5+F3tOW3gZstPrw7mwOmLTBoUyt9HE3juS+35/enVwXQP9+HvYwkGORYhrjexky3f7s5m5b95NPE0Zc5wpwoD/KHhVhyNL6ahq4lu2YK/s3V/f2GAAycSVbelzfXZy/c3Z/z7u4iMz+ax/g15ckgIzy06rFdm6uf7dX//853e/LJD27/4s9WRAJxZNPy2tbc+a+BoTOeGFoz66hLFpRo+vs+Zvk2t2HA8X6+cmbGCKV3t+GZnNoPDrAGIu1zK8yvSAXCyUrLwITf2RcvFKyiuvOqiXsjJydFbamZmhpmZ/p2U8+fP06hRI71l/v7+qNVqYmJiCAsLK1f7kSNHaNu2LREREfz888+YmprStWtXBgwYUMvHcXOq3Qf//Pnz1X6J+svL1Y6cvCJiErVfPlv3nSE02FOvzMW0bBxsLHhqXDfG9GlBv46NdWW27DtDt1ZBjO3fkk7hAWw/JDMq3Ugzfxf2nEhCdeXe7vZ/L9AsoPLnSjwyoBlLt5wC4FxiJt1b+DCud2Me7t+MkAaONL/BtkLcbsHupuyL0QYupy9quyZYmOj/gPs5GRPsZlouMLrKylRB5yAL/oosqPP21meONqYolQoi47UXRtsjkmnqa19p+XaNnEnPKSYuteLzLm6soZsJxxKKKS7V3unYc76IRm4m5cpN62nPNzuzKVRVfEdkREtrNhzPR+6X1D0fHx/s7Ox0r/fee69cmcLCwnJZ+qvvCwsrvvuYm5tLZGQkEydOxMPDA2NjY8aPH8+0adOq3TaVSsU777xDmzZt6NOnDwBFRUU8++yzlJbW7M5OtTP4DRs2rHalGukIXG+Zm5lQpLrWb7NIVYKlmalemazcQkY+9y2PDG1HsJ8r63eeZHCXprr1g7s2JcTPjdIyNbn5kpW4EQtTY4pLrv3nLSopw92s4v+WZiZGjOgSTPenfgFgZ0QCb3y7m/7tAkjNymf7kQuYmhjdlnYLUR1mxgpUpdd+D4pLNZibKCgs0S4zUsIrgx1ZcTiPbsEW2JgraeVrzqXsUnKLtGUGhlqx40yBLpASFTM3NaK45Fpf3eKSMixMK/8+eKh3IMu2V292EFGembFS77OtKtVgbqKfM+0RYoG9pRJvB2MauZviZKUk1NuU41fuRhkpYHCYNZO+T76tbb9TKRRKFIo6eNDVlToTEhKwtbXVLb8+ew9ga2tLZmam3rKMjAzduorY2tqSlJTEvn378PDQjpcLCwtjzJgxvPDCCxU+lfZ6Tz75JAcOHKB///66p9Wam5tTVFTE999/z+TJk6txpPqqfSYTEhJ0r6+//hofHx+WLFnCyZMnOXnyJEuWLMHHx4cFCxbcdCNuxfz58zl48OBt3efd7GJqNl6u9rpAsZGfK/EpmeXKRZxN4pmPfmfaeyto7O9GZGwKJsZGmJka8/ynaxjy9Df8sO4Azz/c63YfQr2SmJZLkJeD7n2wtwMJabkVlh3epSG7TySSnX9t4PL6fdFMm7eVt77fS6fm3kTFZ9R5m4WoruScUnydtBesNuYKrEyVZBZcC0ItTBRkFajp08SSwWFW2FkY0TXYAgfLa4Hp6FbW/HE0r1zdQl9qVhGO1qZYmWvPd5CnLYmXK77r4WJnRqsgJ7YckcG0NZWcU4qv47WMvZ+zCZey9TOtdhZKzE0UDA6zop2/OW62xrQPsNCt79rIgqhLKi7n1WwQpbg5tra2eq+KAvxmzZpx+vRpvWWnTp3C3NycwMDACutt3rw5Li4uuuD+6jKA+Pj4KttVUFDAL7/8wvbt23nggQf01vXv35+VK1dWWUdFqh3ge3t7614LFixg1apVPPLIIzRt2pSmTZvyyCOPsHLlSr7++usaNaSm6jrAX7BgAXv37r2pbWJiYli0aBGffvopW7ZsqVd3NHILitn573kWvHI/4we04vUpA1i64RAAncIDcLDRfjl1CPVjcNemzP7fAIb3CGX+8p1YWZiy6uNJPDykLQ8MbM3EYe05cV5+QG7kn2PxNPR2YNa4djw2JIyhnRuyZtc5lEoF/dr665WdODCUHzef1Fs2oF0Awzo35LsXB1CkKmXjAcnIiTvHhuP5PNvHgUGhVswe6symk9ruIH5Oxvg5GZNXrOH5Fem6V2JmCZ9uzSQ+QxsotfYzI6tQTWz6vT34sDpKyzRsOpzEvKltGN3Zl9fGh/LbP3EAtAl2wsn2WjAzvkcAf+yNp7Ts2m9TiI8d/Vp5YqRU0KelB81u0L1HwLH4YhyslEzrYceYVtYMDbNi85XPd7dgCxTA6qP5us/24l3ZnL6k4pud18aWjGltw+9y8XrNHTBN5rhx4zh27Bi7d+8GtN1kFi1axMiRI3UXBFFRUUyePJmEBO14t/Hjx5Oamsq///6rq2fDhg1YWFjQpEmTKvcZHx+Pl5cXzs7O5QYEV3RHobpqdC/k7NmzBAUFlVvesGFDzp49W6OG3KluNsB/8803GTZsGMeOHSMuLo7JkyfTpUsXCgrqT//RqW8v52x8Gl1aBjJ74SY279EOvhrQqTEujtq+aH3ahzC2X0uMjIzo+dgXxCdnkpVbyMxPVhMW7EXHMH8W/b6XL37ZachDueOpStUMf/l37KzNaOTjyNg315CaVYCJkZL7e4boyrnaW3I6Lp1DZ67dylUoYGyvxgzuGMiJmDSGvfS73uw7Qhja6qP5/HwglzZ+5kQkFvPxFu0PVSN3Uxq5m5Yrf+RCMXnF17KZfk4m/LAnp1w5UbGXlhzhRFwmnZq68sXaSNbs1wYg3UPdcXe4ljl2tjVj2Q79ZECLQEfGdPHlnxPJjO7sS5tGzre17fWNWgP/W5qKibGCYHdTnvstjUvZ2il0BodZobwuurqcX8aJxGt3X63NFKTmlHEgRrqx3km6du3KrFmzGDBgACNGjCAsLIyioiI++ugjXZlLly7x7bffcvnyZQCaNm3Khx9+SM+ePRk9ejR9+/bljTfeYPHixTg4OFS2Kx0PDw8uXrxIVlZWuQB/7dq1BAcH1+hYFJoapJebNWvGkCFDePfdd3WN0Wg0vPzyy2zYsIHjx49Xu66MjAzWrl1Lamoqbdu2pXv37nrrY2NjWb9+Pfn5+bRr144ePXrorQ8JCWHatGl07NiRPXv2YGZmxvDhw8tN31lVPfPnz6dt27ao1Wp27dpFYGAgeXl5zJo1i9DQUDp27AjAyy+/jKlp+R+mqw4fPkyrVq105+XixYv4+Pjw888/c//991e4jV3nWdU6V6J2mDlW3R9O1I7DX/QzdBPuKaN+Mjd0E+4pyVHSPfR2cm/U1tBNuGccerVB1YVqSU5ODnZ2djR8ag9GZta1Xn9ZcR7nPu9EdnZ2pf3or3fy5EmOHTuGk5MTPXv21OvOc+nSJTZs2MDIkSNxdHTULY+NjWXfvn3Y2NjQvn17XFyqP+nFxIkTiY+PZ9y4cXzyyScsWrSI5cuXs3DhQnbu3En79u2rf8BX1GiazPnz5zNkyBBWrFhBq1at0Gg0HDlyhNTU1Juay3PXrl0MHTqU0NBQWrduzezZs9m4cSMffPABoL1yGTNmDEOGDMHd3Z0PP/yQoUOHsmTJEr16FixYwA8//ED37t3Zv38/r7/+OidPnsTZ2bna9cyfPx9jY2NMTEzo27dvpX2tqtK6dWu990ZG2r6kFhYWFRUXQgghhBB3kGbNmtGsWbMK13l4eFQ46NXf3x9/f/8KtqjaV199xbPPPsuTTz6JSqWic+fOeHh4sGzZshoF91DDDD5AWloaixcv1g1GaNKkCY899pguqK6KSqUiICCAYcOG8eWXX+qWHz9+nNDQUIqKimjYsCETJ07UPe732LFjtGrVii1bttCrl3bwZkhICI6OjuzevRulUklZWRmBgYE888wzPP300zdVj5mZGYcPH8bE5NrAmfDwcCZMmMDMmTOrfW7Onz/P0qVLycnJYdu2bQwYMIC5c+dW+rAFyeDfXpLBv30kg397SQb/9pIM/u0lGfzbxxAZ/OCn9tZZBv/s5x1vKoNvKAUFBURHR2NmZkZQUBDK6/t63YQaZfABXFxceOmll2q84wMHDpCUlMTzzz+vtzw0NBSAEydOkJiYyJQpU3TrwsPDadeuHZs2bdIF5gBDhw7VnQQjIyOaNWtGXFzcTdczYsQIveD+VqlUKrKyskhKSqKoqEiy+EIIIYQQQicyMpLZs2czYcIE+vfvr5uB51bVKMCv6mFWFQ3AvV5qaiqgnZ2nIhcvamdf+e+0QwCenp4kJSXpLbO21r/iMzY2pqSk5Kbrqe7dh6oEBQXx5ptvAvDGG2/QuHFjvvjiC2bNMlymvlN4AMN7NCctM5/vVu8jPUs72r9VEx9G9w4nM6eQBSt2kyPz1t8yU2MlD/VvRiMfR/49m8JvO86gVmuwsTTlkQHNaeBqw8HIS6z4u3pPhBbiTuJhZ8SoVjYYK2HjiXzOpmi/ax2tlNzX2gZzEwXrIvKJTiupoiZRHb1beNAj1J3kzEKWbo8hM0+Fr6sVz47Uzs7x578X2XgoqYpaRHX1bWpJywZmxKaX8MeRPFTacbN0C7agXYA5seklrPo3D5nPoA7c5Iw3N1XvHczJyYni4mJGjRqFra0t999/PxMmTKhx15yrapT7b9iw4Q1f1XF1EGxiYmKF6z09tU9GvXTpkt7yS5cu4eXlVe223mo9lXWrqS4XFxeaNWvGiRMnbqmeW9GnfQjPTOhOZEwKPm72/P6xtu9YIz9Xfn1/Ikmp2TRwt+e3DycarI13k29fHIivux2n4tJ5ckRLnh7dSrv8hQGENHDk6PlUBncK4onhLQzcUiFujrWZgvdGOpNVUEZ2oZovH3DF3c4IpQIWPuiGlZl2nvuvJrjiZF37D6y51zzaN4hx3fw5k5hNU197vn+uEwA5BSX8fTwFpUJxw6fVipvzSCdbOgSYcyZZRZeGFszspx1A2aeJJdN72RObXkLXYAtm9LY3bEPvUgqFos5edzJXV1f++OMPkpOTeffddzl58iQdO3akYcOGvPnmm1Um1StTowx+bGys3nu1Ws25c+eYOXMmTz31VLXqaNeuHZ6ennzyySd8/vnnuuVX++A3b94cLy8vFi9erMuGnzhxgv379+v60lfHrdZjZ2dHbm7FDx66XklJCadPnyYsLEy37OLFixw7doz+/ftXu8217fDpeLbOPAOA+SZjote/AcD4Aa34ZtVe5i/XTmV5aNlMQvzdOBOborf9Q4PbEhrsyaY9p+kY5s8nP+0gv1BF3w4h9GnfiPxCFd/+sY+ElCz6tA/B2EhJgLcT/l5OLN1wiGNR2uxSReU9nG15bGRHPF3tUCoUPP3BKgqL63fm77n520nN0k6LmpVXrJvPPizIlS5PLiM1q4Dj51NZ8vJAvlp9VG9bKwsTHh8ajp2VGat2nmVwx0De+XEfxkZKxvduTGigK3HJ2Xy34TgFxaX8b3gLDkVeok8bP0yMlXy9+ihpWYWVlg8PcuW+niHYWZmRmJbLe0v33/bzI+qv4lIN/1uaqnsSbVt/czztjfGyNyanUK2bBtPJSkn/plYsO6D/3dnS14xeIZbEXS4hu1BNcnYpxxNV+DkZMzjMGgsTBdvPFPDvhWJcbYwY0NyK2PQSOgSYcyyhmD9Paf9fVVTeSAn3tbYhyNUEEyMFvx3O5WSS6vaeoFq27kAC323R/rj/vieeg58NAiAzT8Ufe+NxtTfH1rLybqWjOjWgRZAj/xxPoUkDe378K5rMPBVdm7nRq4U7qhI1y7bHEJeaT8cmLthbmeJqb06Qpw1/7E3g33PaaQArKu9ka8bEPkF4OVuiVMDrPx4ju6B+f3f/cSSP7ELtNK0xaSVM72UPwIgW1nyyJZO90UX8ebKA1dM8+XRrFv9N4psZKxjX1gZHKyXrj+czKNSKT7dmoVRop81s4mFKck4ZKw7lkq/SMKaVNWdTtZ9tcxMFyw7kkpZbVmn5YDcThoRZY2uhJCOvjM/+yrrt50fULQcHBx577DEee+wxEhIS+OWXX5g3bx6rV6/m2LFjN11fjVIsfn5+eq+AgAD69evH0qVL+eabb6pVh6mpKcuWLeOHH36gR48ezJw5k549e7Js2TJA+4jer776irlz53Lfffcxffp0evTowUMPPaTXb74qt1pPz549WbRoES+++CJvvvkmKlXlPxgajYbHH3+cwYMHM2vWLKZMmUJoaCht27Zl+vTp1W5zbcvMKaB10wYsfmMcWxc8yax5awBo4O7Imdhr86pHxiTj6+Got+1T47oxcVg7Tp6/xLBuzZk6ujNmpsY8OKgNzz/ci9MxyeTmF7Hq40lYmJkQ4u/Ke08NwUip5FJaNj+/9zBKpaLS8h8/NwIzU2N2HDrHtgNRlJSW3dZzUxeuBvcmxkrG927CD1ceTvXbjjOsf380i1/ozysPdcTD0RqlUj+z8M3M/jT0ceRcUiYzx7albxvtxcFnT/WifVNPjpxLwdvFhvnP9AGgU3MvXn+kExfT83CwMWfu1O43LP/jK4OIS85mx9F4Dkbq39USoiolZdr5v98a5sTXE1y5mFXK0QvFeNobE5N+LbiLTivB014/f9TQ1YR3RziTlFWKjbmSGb3t8bI3xsfBmE/HupBVUEZMWgmz+jvQ1NMUG3MlD3WwpZWvGedSS3iypz2NPUwrLT883JoOgeYcjS9mX3Qh6Xn1/7skLfvavOkP9w7kx23R1d72wV4BTB0YTGR8Nt1C3ZgyMBgrc2OGtPPmhfuacS4pl4xcFT/M7IyNhTEB7ja8Nj4UK3NjEtIK+OapDpibKCst/8YDYdhbm7DrZAp/H0+huLT+P401u1BN36aWvDvCiRf6OzJ/u/ahVB72RrrPd06RmtwiNY5W+uHTG0McaeJpSmx6CY92tqN7I0sAnu/vQIcAC05fUuFgqeS9UdquwOENzHi6lz1puWWYGSt4bbDjDct/MMaFlJxS9kcXcjShmLuSQll3r3rk4sWLrFixgl9//ZVLly7VeFbHGg+yrUhgYCCRkZHVLt+9e3fOnz/P2rVrycjI4M0336Rr16669UOHDiUyMlI3f/2KFSvKzV8/bdo02rbVH1k/duxYvZHSNa0H4NVXXyU8PJzTp09TVHTj/ummpqbs3buXHTt2cPToUQICApg0aRLt2rWr9jmpKynpOWw7EEVyeg6TR3RgxdajqEpKMTW99hEwNTWmpET/iZH9OjZmxoe/E3E2ie/XHqBHW+0DF+7r24Kc/CI6hGoDUCsLM5oGacc5/PrnET7/5R8ABnZpipujTaXlD5+Kp3OLAC6l57Bl3xlKy+r/jwSAlbkJXz/Xlx83n2T/Ke04kNcW72Ll31F4u9gQdymbdXNH6T2YythISctGbjR5cDEaDew8lsCPrw7GSKlgUMdANu6LpnNzbbeyrmHXZgJ6/+f97DmRxK/bjfj78/E3LH8yNp0mfs4cirxExPnU23U6xF2kVK1hX3QhTtZGDAu3JsDFhJIyDSZG1y5WTY0UlJTpd1LuFGTB8kO5/Hwlq+9uq/3u6R5iQaFKQ7Cb9vkiBSoNHYMs2B5ZQHxGCZ9uzQLA2dqIhm4m2FkoKyx/KLaIoeFWeDkY8++FIpKz63+Af9WTQxrh6WjJKz8crbrwFb3CPXjtx2MciEoHoENjbbfYER0bkJ2vok2wEwBGRgrCArTB5caDSXy5Tjs2qHuoO55OlpWWPxqdQd+WniSmF/B3RDJFqrvjfF+4XMK+GAVqYHgLKyISiikp036mr9J+vvW3axtgTr9PkyhTw99RhXz/qDsA/Zpased8Ia19tTNchXqbYaKdPZsle3LYda4QBbDuKc8blo+8qKKhmyn5qmIOxtWfB2eK6snJyWHVqlUsW7aMHTt24Ovry/jx41m2bBkhISFVV1CBWgvwc3Nzee+99/D19b2p7VxcXJg0aVKl6/39/W+Y/Z42bVq5ZWPHjq2VegCUSiVDhw5l6NChlW77XwqFgp49e9KzZ89qlb8d2jX35eDJeJZvPoJCoeDYr7PwdnPgdEwy3VoGsXLrMSzNTWgZ4kPkdd1zCopUuDhqBzFbmptgY6l92EN+kYqzcamcjNZmgbcdiCL+UgYdQv3IK7yWXSgpLcPE2KjS8p+ciuenDYdo1cSHxa+P45mPf+ff0wm347TUGVd7Sxa90J9Pfz3E38f0jyXifCoR51N5/ZGO7D2lPzDu6sWNnZUZWXnFuDpoM0Blag2FxaXsO3WRwmLtBdj2I/G67fIKtZmlklI1xkbKG5Yf/9Y6AjztadHQlU0fjaHzE8sovv6XSohK+DoZk12oZtNJbYDh7WBMK18zjsQXM7WbHcZKKFVDG39zdp4t1Nu2sESNt8O17iRX++gXqTSk55WxL1pbfl80umxpgeraRUKpWoOxUlFp+ajkEqb8mEpTT1OGhlsT5m3Gt7vr91NwlQqY81ALcgpLbiq4BygoLsXZTvt9bWqsxO5KV56C4lLi0/KJiNF2p/r7eArnL+bi52ZNXtG1BE9pmRoTY2Wl5XefSmXNvnjCAxyZ+2grPlh5kr2n02rjsA2mRQMzjsYXE5Vcws6zhax+0pPZZHA+VUVrP3PiM/IIcDahTKMhp0g/GVVaBvYWSi7nq3GyNtItL1SpiUgoJv/KE5r3RRfqBujmq7TL/nspXFn5l35Px8vBmCYepnz7sDsTFl8ir1hG+t4NoqKiCA8Px8rKivvuu4/Zs2fTqVOnW663RgG+sXH5zcrKynB0dOSXX3655UbdqeLi4vj+++8rXf/qq69WeG4MzdvVnnk/jOLshVSCfV05n5BObNJlftpwiL8WTmP1p5PxdnPg9+0RXErX/0FcsHI3i14bx96IGJzsrVGVlFFaqubDH/5iyewHCGrgQt7/2bvv+KbKtoHjv6RtuvfeZbZQ9gbZGwSRJSCiIirTBfII7oFbBEVAlKlMBUGWCMhG9t6jtHTvPdM2ef6IFGNbRmgb2l7f95PPa865z33unCek17nOPbJ1Af2G3aWvYFxa+Xde7IWvuyMWKlM8XOyK9lVmK97tj5mpkie6BvFE1yCuRqQw+9fj9GtXi75tauLtaou3iy3D3v+92LFLt55j56zhnA2Jx87avCjo/2LFEV4a1JwzIfHkFxRyM/bOgUtJ5S1VpsycpHty5WhrSWGhtso8MREVQ6uFuU+6EZ1WgI25Eh9HU5YcTCcuvZBL0Wp+ft6DtBwNtuZKdlzUzzL+eSGb5c97EOBiispEga2FkgINbDmXRf8mNvRvYkNSZiFaLdxMKqCAkoOX0sr3qG9FhzqWKBTQwFvFucjK/1vy6sD69G/tw+6zsXwzriUAUxeewNREwaejm1Hbyw6VqRIfFyveX36GlMzbXUiX7Qjhh1fa8nhbP5xszSnU6P69f7fpCotea4efmzUZ//SZ33kqutQ2lFb+lQH1qOlpg8rMBF9XazJzCkqto7JoW8uCV7o5EJteSANvFWtP6J42/fR3OrOHu9G+tiVBnmbM251W7NgVR9JZNsaDC1FqrMwVFPwTxc/bk8YLHey5FKMmv1BLQkZh0Q1ySUoqv+18Nh8O0D1BsbFQolBQ7AmCqLzs7e355Zdf6N27d5lO1W7QQlfbtm0rts3R0ZHg4OBiU1ZWJeUV4FfEQlc+7g40DfQhKj6Vk5dvz1xkY2lOm8YBpKRnl5o5r+HlRFAND66FJ7Bh9vM0GPyprt02FrRs4I+jrRUKBaz76wy1fHT9Ba/e1HX/6NqyDofPhZGdm19i+a4t6+JoZ0lObj5HL9wkLuneBjQ/iPJe6KpP65pYW97+R5qQms3e0xEEB7hQL8CZhNRsjl+OJSu35AFpTeu4Y2+twlxlyohu9Xj2060A+Lja0qiWK1YWZiSn57Lr5E3aNfDmYlgiqZl5Ref+48iNEsvvOxPB4x3qoNVqSc3M4+9zUeSoy/ePsix0VbEqYqEra5WCpn7m5BVoOf1P9wUABdDI1xwLMwWnw/PIKyj+p8XGXEFTPwvCk/P5X28n5u1O5UK0GqUCGvua42arm5HnZHgemXka6nuqOBam+27XcjVDXaAlIqWgxPL2lkpquZpRoIHQxHyux5f/gM/yXuiqcQ1Hanra6m3beDhC1w2vlf4U09tORJGTpx/1eTtbUd/PnmtR6fzyVifavLoVjRZsLExpUccZB1sVChRsPRaJp5MVFioTLkfogtd29V05F5pCRk5BieVbB7ribGdOrrqQUyHJxKboP7EpDxWx0FUtVzN8nUy5kZBPePLt30dnayXBXubcTM7nZlLJv5uBHmY4WZlQqNXydFs7Jq3UPdFwszUhyFOFtUpBZp6W/ddyaOxrTkRyPslZuiRL50BL9lzJKbV8nwZWaIGMXA2nwvP0nm6VB2MsdBU0+Vi5LXR1+euWlWKhq7JkUID/+OOPs2HDhvveJ0r2MK9k6+pow8eT+mFmakKrBv78+NvfzF6xx9jNeiAP80q27Rp481TP+lhbqGhZz5Mxn23l0IXSs2sPOwnwK9bDvpLtK90ccLE1IcDZjIw8DROWV+5xIA/zSra2lqbMeKYppiZKmtZy4reD4Xy17oKxm/VAHuaVbBt4q4rWgWjia877G5P4O6TyrisjAX7FO3DgAAsWLOD69evY2NjQrFkzXn/9dVxdXQ2qz6D+JL//XrxrAeimy9y0aZNBDREPp+xcNTuPXKGwUMNXP/3FhZDYux8kDBadmMGuk+Hk5BUw/Ye9RCdmGrtJQpSZUxF5WKkUrD+VyZnwyt+F5mGmLtCw52wchRoN8zdf4fzNVGM3qUq7NS4kt0DLrB0pxFShQd6i/H399ddMnTqVnj170r59e7Kzs9mwYQMLFy7k8OHD97zG1L/dV4AfGxtb4n+DLrg/ePBgsRVjReWWlaPml+33N7hLGC4sNp2wu/SvF6Ky+u/AW1F+8vI1rP87/O4FRZmITSvkjzSZ3Ubcv5ycHN599102b95Mnz59irZrNBqeeuopPv30UxYvXnzf9d5XgP/v4L2kQN7U1JSvv/76vhshhBBCCCGqr/JadfZhX8k2LCwMFxcXveAedLM4vvjii7zxxhsG1XtfAf65c+cA3eqwt/77FjMzM7y9vav0IFshhBBCCCHKipOTE3FxcSQnJ+PkpL/Y6IULF3B2djao3vsK8Bs0aABATEwMHh4eevsSExOxsHi4B3gJIYQQQgjxsHB3d6dz585069aNt956i/r165Odnc3OnTv58MMPWbJkiUH1GrR+b0JCAlOmTCl6P27cOFxdXfHw8OD48eMGNUQIIYQQQojqZuXKlTRr1ozhw4cTHBxMy5YtmT17NnPmzGHYsGEG1WlQgD9lyhT69esHwKVLl1ixYgU7d+7khRdeMLivkBBCCCGEENWNo6MjixYtIisri8uXLxMREUFMTAxjxowxuE6Dpsk8fPgwrVu3BmD79u08/vjjdOvWjZYtW+LnV3FzpwohhBBCiMqvug6y/Tdzc3MCAwPLpC6DMvhWVlZERUUBsGXLFrp27QpAdnY2lpaWZdIwIYQQQgghqrq5c+cye/ZsvW1qtZq+ffsSERFhUJ0GZfAHDBjAgAEDaNKkCUeOHGHlypUA7Nixg549exrUECGEEEIIIaqTvLw83nnnnaLE+S0qlYohQ4bw0Ucf8cMPP9x3vQZl8L/55htGjhyJjY0Nf/zxBy4uLgAcP36c9957z5AqhRBCCCGEqFZCQ0NxdHQssQdM7dq1i01Lf68MyuBbWFjw1ltvFdv+zTffGNQIIYQQQghRfVXXPvje3t5ERkYSEhJCrVq19Pbt2LEDHx8fg+o1KMAXQgghhBBCPBhbW1uefPJJevfuzfvvv0+TJk3IzMxk48aNfPXVV2zbts2geiXAF0IIIYQQwkjmzp3Lyy+/zLPPPktBQQEAnp6eLFmyhC5duhhUpwT4QgghhBBCGImVlRULFy5k9uzZ3LhxA0tLS2rVqoVSadBQWcDAQbZHjx41+IRCCCGEEEIInbS0NC5cuICNjQ2NGjUiJCSESZMmsXr1aoPrNCjAb9OmDQ0aNODrr78mISHB4JMLIYQQQghRnT3//PNcvXoVgCtXrjBgwABu3LjB2LFjWbdunUF1GhTgX79+nYEDBzJ79my8vb0ZPHgwW7ZsobCw0KBGCCGEEEKI6kuhuD2TTtm+jP3J7iw9PZ3du3czcOBAAFavXs3IkSPZtm0b3377LcuXLzeoXoMC/Jo1a/LRRx8RFhbGpk2bMDU1ZfDgwfj5+TF9+nSuXbtmUGOEEEIIIYSoLuLi4nB0dCx6v3v3bvr16wdA3bp1iYuLM6hew3vvA0qlkl69erFo0SJmzJhBYmIin3/+OYGBgfTv35+bN28+SPVCCCGEEEJUWb6+vsTFxfHHH3+wf/9+jhw5QteuXQG4dOkS9erVM6jeBwrwDxw4wHPPPYenpydz5sxh+vTphIWFcfz4cUxMTIoeNwghhBBCCFGa8umeUz6LZ5UlCwsLPvnkE/r160fHjh2ZPn06Dg4OaLVa5s+fz8SJEw2q16BpMj/77DOWLFlCaGgo/fv355dffqFXr15F0/n4+fmxevVqrK2tDWqUEEIIIYQQ1cGkSZN45plnyM3NxdXVFYD8/HwWL15Mw4YNDarToAB/yZIljBkzhmeffRY3N7cSy1hYWLBs2TKDGiWEEEIIIUR1YWtri62tbdF7lUplcHAPBgb433zzDb17975ruaeeesqQ6oUQQgghhKiSIiIi+OCDD/Dz82P06NF88MEHpZb18/Pj3Xffve9zGBTg9+vXj/z8/Ie+X5MQQgghhBAPE41GQ2ZmJtnZ2UX/XZrs7GyDzmFQgF+zZk0uXbpE/fr1DTqpEEIIIYQQ1ZG/v7/eKrUPsmJtaQyaRWfatGk8/fTT7Nmzh/j4eFJTU/VeQgghhBBC3KvqOotOeTEogz9mzBgAunTpUuJ+rVZreIuEEEIIIYSoosLDw3nzzTfvqay/vz8ff/zxfZ/DoAD/2LFjhhwmhBBCCCFEtaZQKLCwsCh6Hxoayq5du2jcuDH169cnKyuLQ4cOkZaWxvTp0w06h0EBfosWLQw6mSiZlaeMZahI1u61jN2EaqP9uxeM3YRq5cCHwcZuQrXScNRJYzehWsmKu27sJlQj0yr8jArKpzuNgoevi46vry8LFy4set+0aVN+/vlnvdknc3JyeOKJJ3B0dDToHAavZJuXl8dff/3Fjz/+WLQtKirK0OqEEEIIIYSoVmJjY0lOTi42tbylpSWvvfYa27ZtM6hegwL80NBQGjduzMCBA3nxxReLtr/22musX7/eoIYIIYQQQghRnWRnZ5OYmEhaWlqxfSEhIQZPk2lQgP/aa6/Ro0cPUlJS9LZPmTKFzz//3KCGCCGEEEIIUZ3UrFmThg0b0qNHDzZu3Mi1a9c4c+YMs2bNYsqUKYwcOdKgeg3qg79//36uXbuGiYmJ3vbg4GBOnz5tUEOEEEIIIUT1VF5TWlaGaTI3btzI66+/zqBBgygsLATA3d2dzz77TK+nzP0wKMBXq9VoNBpA/8JFRERgY2NjUEOEEEIIIYSobtzc3Pjpp5/44YcfCAsLw8bGBm9v7we6OTGoi07Xrl2ZM2cOcDvAT09PZ/LkyfTo0cPgxgghhBBCCFEdWVhYEBQUhI+PzwM/eTAogz9z5kw6duzIli1b0Gq19O/fn0OHDmFhYcHBgwcfqEFCCCGEEEIIwxkU4NeuXZvz58+zaNEiatWqhUajYcqUKbz44os4OzuXdRuFEEIIIYQQ98igAP/QoUO0bduWqVOnlnV7hBBCCCGEEA/AoD74jzzyCLVr1+a9997j2rVrZd0mIYQQQghRjdyaRac8XtWRwQtdjRkzhnXr1lG3bl1at27NnDlzSEhIKOv2CSGEEEIIUWWp1Wo+/vhjWrZsWTRZTW5uLpMnT6agoMCgOg0K8P39/Zk+fTrnz5/n9OnTdOrUiS+++AIvLy8effRRgxoihBBCCCFEdTNx4kTWrFlDly5dipLlFhYW5ObmsnTpUoPqNCjA/7fGjRvz+eefs2LFCoKDg9m6deuDVimEEEIIIaqR6tpFJzs7m1WrVrFr165iq9b27t2btWvXGlTvAwX4Fy5c4M0336RGjRp07doVNzc3li1b9iBVCiGEEEIIUS2Eh4fj7e2Ni4tLsZsROzs7UlJSDKrXoFl0vvzyS1asWMGZM2do3rw5r7zyCiNGjMDDw8OgRgghhBBCCFHdeHp6Eh0dTWpqarEAf+PGjdStW9egeg0K8L///nuefPJJ1qxZQ2BgoEEnFkIIIYQQojqzt7dnyJAhDB48mBEjRqBWqzl48CCrV69mwYIF7Nu3z6B6DQrwQ0JCDDqZEEIIIYQQ4rZ58+YxefJkJk6ciFqtpn379nh6erJixQratGljUJ0GBfi3REdHc+XKFbRaLUFBQXh5eT1IdUIIIYQQQlQrlpaWzJ8/n5kzZxISEoK5uTm1a9dGqTR8qKxBAX5mZiYTJ07k559/RqvVArrRz6NGjWLevHlYW1sb3CAhhBBCCFG9lNeMNw/7LDr/ZmVlRcOGDcukLoMC/ClTpnD06FG2bt1K27ZtATh06BCvvfYaU6ZM4fvvvy+TxgkhhBBCCFGVpaamMmfOHM6cOUNmZqbevlq1ajF37tz7rtOgAH/dunXs3r1b7y6jd+/eeHl50bVrVwnwhRBCCCGEuAe9e/cmMTGRHj16EBAQoLfP29vboDoN7qJTUn97Ly+vYnceQgghhBBCiOIiIiK4dOkS4eHh2Nvbl1m9BvXeb9myJTNmzKCwsLBoW2FhIR999BGtWrUqs8YJIYQQQohqoLxWsX3I++CrVCpcXFzKNLgHAzP4X3/9NT179uS3336jefPmAJw4cYKMjAz+/PPPMm2gEEIIIYQQVZG7uzsODg4cPXq0TJPkBgX4LVu25Pr16/z4449cuHABhULB+PHjefHFF3FyciqzxgkhhBBCCFGVREVF8eWXXxa9d3d3p1OnTjz22GN4enrqlfX29mbq1Kn3fQ6D58F3dnZm2rRphh4uhBBCCCFEtaNWq7l+/XrRe6VSSbdu3cjKytLbDqDRaAw6h0EBflZWFjt27ODxxx/X275hwwZ69Ogh8+ALIYQQQghRgho1arB582YAcnJyCA8PJzAwsFi5W/sMYdAg2zfeeIOoqKhi2yMjI5k+fbpBDRFCCCGEENVTeQywLa/Fs8rSlStXGDZs2H3vuxuDMvhr1qzh8uXLxbYPHz6cBg0a8O233xrUGPHwqO/viJ+bLceuxJOUnqu3T6lUMKRjLb1tEfEZHLoYR7dmPjjbWRRt3/h3KLnqQkTplApoWdsec1MlR66nkZdf8uM4X2cLGvrZYKFSsv1MEpm5t69r+yAH1AVajl5Pq6hmV1o2Fqa0DnQhLVvN8WvJxfYH+9lTz09/NoPtJ2NIz86/67FCGFu9Gu7U8nHm2MUI4pIyiu1XKBR0aFoDGytzDp+7SXJa9j0fK/QpFNCmgS/WFmb8fS6C7Nz8YmVUZiZ0bOKPhcqUQ+cjSErLAXR/Rzs09kNlZsr+0zfJVRdUdPNFJZCeno6tra1BxxoU4Ofn55Oeno6zs3OxhuTk5BjUEPHwGNuvPi88Wp9zN5L55PnWDHn/T27EpBftVyqgXbBH0fsWdV1Ztesahy7G8drgxsQkZ5GVq/ux+uPoTQnw72LeC/XxdDQnI6eA1/r788TMM+QXavXKPN3Ji5f6+nHkahpZeYXsv5RSFOC3rGXHtIE1uR6bLQH+Xdhbm7Hx3c7cjM/Cy9mSw5cTefunM3plfF2taRvkAoCFyoSODdzYfDTqno4VwphGP9aK/z3ThZOXo5g5+TEGTVnKhRuxRfsVCgUbZ43BysKMiNhUPhrfh6H/W8qNqOS7HiuK+/6N/tQPcCUhNYv3n+9Mj5d/IifvdqCuMjNh99xnSUrLIS0rl1mv9qb3qz8TGZ/Or58MI09dgJmZCZ9P7EGXiUtIy8wz4qcRFS0yMpIZM2aQlJREZGQk48aN09ufn5/P/v37efTRRw2q36AAv0uXLrz55pssXboUc3NzAHJzc5k2bRpdunQxqCHi4WBmquTlgY3o8b+NRCdlM/6xYMb1D+Z/PxwqKlNQqOXVuQcAXRbiwDcDWb379qCQPaejiE3J4fDF2KJAX5Sssb8tAa6WPPrpCQo18OO4YHo1cWHziYSiMu72KqYOCGDozDNcjsrSO97B2pRRnbz4fnsE3Rs5/7d68R9PdPDn6NUkpi46ibmZkgNf9mT+lqtEJd1OTGw7Ec22E9FF5ZMy8shVFzKqa427HiuEMU0f3Y2+L//I9YhExgxozasjO/LCR78U7ff3cKCGtxMNhn4BwKhHmzNpWAcmf/37XY8V+oL8XWhV35tWz/1AfoGGRW8OYGjXYH764/ZNfy1vJxTAY1NXAjDr1d50aOLPL39d4K3v/+JcSBwAv302nBZBXvx1PNQYH0UYSUFBAYmJiaSlpRX9979ZWFgwevRoJk2aZFD9BgX4X3zxBR06dKBGjRq0atUKrVbLsWPH0Gg07N+/36CGiIeDl7M1yZl5RCfpHtseOBdDn1b+pZbv2dyXMyFJRd14/joZQcsgd/zcbPjixbY8+uYWYpOzSz2+uqvrZcWxkDQK/+mVc+hqKoFeVmw+cbtM4wBbzoVnUqjR0ruJC6fD0olNVQPwxuM1+PS3GzQOsDNC6yufQB87/r6ou3nKy9dwMiSFut52pQbpo7rW4PVFJw06VoiK5OpoQ6FGw/UIXZCw58R1nntcf07tmKQMTJQKPhrfm7DoFAZ0boC5yvSejhX66gW4cPRiFPkFuh/vfafDqFfDVa/MtYgkYpOzeGNUe9Iycwmu4crMlX+TnZvPxdB4RvRoiIezDbZWKo5dijbGx3iolFd/+Ye1D35AQABr164lMjKSZcuW8dZbb5Vp/QYNsq1Tpw7nzp3jpZdewszMDHNzc1566SXOnz9PnTp1yrSBomKpTJUUFNzuA55fqEFlWvrX5NneQfy840rR+1nrzvLq3AMMem8bmw+F8XSP4qPCxW1mJkoK/tUdJ79Qi9l/rrelSomTtRnvDqlFv+aubJrejOY17XiyvSdmJgraBjrQuo49Ps7mtKlbtivhVTVmJkryC29/vwsKNcWu9y1NajqSX6jhSmT6fR8rREVTmZkUBZug+36qTPVzeHnqAvpM0mWca/u58MuO05iaKO/pWKHPzFT/muUXaFCZmuiVMTVRkpyeTYsgL9o28NXrvmNiouSRxn60a+hLWmau/JZUYz4+PmUe3MMDzIPv6uqqN2NOYmIiDg4OZdGm+/Lpp5/SoUMH2rdvX+Hnroqik7LwcrHGUmVCjrqQIF9HwuMzSyxb09MOb2dr/r5Qcj/N2JRs3B2tyrO5lV5kci59m7kUva/racWVaP1uOOGJuRRqtYyacw6AMV296dvMhStRWRRqoHUdB7ydzHG2UVHX05rDV6UffmkiE7Op620H6GYBC/SxIyIhq8Syz3SvyYrdoQYdK0RFi0vKwNHOEnsbC9Iyc6lXw52bMcUHgofFpPDhj9sB+Oq1xzh/PeaejxW3hcelEeR/+7e7XoArN2NT9cr0blMblZkpQ9/SdXWaNKQVYx9vwcxVf5OWmcukr7YA8OmE7gzr3oB5645VWPtF1WdQgH/u3DmWLl3KzJkzARg3bhwLFizA2dmZbdu20aJFizJt5J0sW7YMW1vbcgvwv/zyS1q0aGHQ2IKjR4+ycuVKOnXqxMCBA8uhdWUvK7eA7ccj+GFKZ/adiWFs//pMnn8QgO7NfDh1PbGoO86zvYJYvvNq0bEWKhMea1cDAF9XG8b0rcdTn+ys+A9Rifx9OZX3h9bi7cE1ScsuoGdjZ77eFIaJEvq3cGPD0XhOhWaQq9bwWj9/EtLVPNHOg++2hbPpeAK/HNL14ezdxIXeTV34aa885r2TdQfDWfdWR3LyCqnhYUNGTgGXItJxd7QgyMeOvefiAXC0UdG+vivTlpy667FCPAwKCjX8uuMMP334JH/8fZnxQ9rx5ne6ALJbqzpcvBFHTGI6XVrUxsvVjsZ1vRnctRFdXpx7x2NFyY5ciMTS3JSZL/ciKiGdYd0b0Gn8YgBG9GjImr/OcyU8iXYNfXl1eBuycvIZ1acxs1cfpra3E9Of6cC2w9exsVTRv30gY2ZsMO4HElWOQc+EpkyZQr9+/QC4dOkSK1asYOfOnbzwwgu88cYbZdpAY1uxYgUnTpy4e8H/SElJYcSIESxbtoy9e/eWQ8vKz+T5B9l3JgY/dxumzP+bfWdjAGhU0xk7KzO9sr/uvT241sxUSbtgD9rWd8dcZcITH/zJyWsJiNIVaLQMn32WjNxCLFRKRsw+S0pWASZKBa1r3+5uM3ruefLyNdR0t+LL30PZdFz/ukYm53L0mmTu7+ZGbCZPz/wbR1sV4QlZPDPzbwAcrFUE+zkUlavjZcusDZf1piwt7VghHhZTv9nE5gMXqeHlxP++2cSWA5cAaFTHE0c7SwCCa3nQrnENktKy6Pj8d4TFpNzxWFEyrVY3eDYuORN7GwsG/G8VMUm6p92PNPZDqVBwKSyBke+uxdnOito+Tny6bD+rdpzj+OVoPlm2jxqeDthaqRj1/jqOXCy+tlB1o1SU36s6Umi1Wu3di+mzs7MjNjYWKysrvvnmG44fP87PP/9Meno6fn5+pKam3nNdkZGRrF27lvj4eFq3bs2AAQP09p87d44NGzaQlZVVtF+pvH1fEhQUxKRJk2jQoAH79+/H3NycYcOG4e/vf1/13Orqk5WVxb59+6hXrx4ZGRm8++671KtXj2bNmgG6AcYqlequn2vQoEE0bdqUdevW0blzZ2bPnl1qWc+hS+/hSomyYu1e6+6FRJlQZyTevZAoMwc+DDZ2E6qVhqOWGrsJ1YqJhUwmUFGSt0+rsHOlp6djb29Pmw8uY2ph2Jzvd1KQm8Hh94JIS0vDzu7h/A6lpaURGRlJcLDuN3zbtm1s3LiRjh07Mnz4cIPqNCiDb2VlVbSS7ZYtW+jatSsA2dnZWFpa3nM927ZtIzAwkN27d2NpacnSpUt56aWXivavWrWK5s2bc/PmTUxNTZk4cSJDhw4tVs+sWbP48MMPUSqV7Nu3jyZNmhATE3Nf9Sxbtoynn36aGTNmYGNjg6urK+7u7qhUKhwdHQkICCAgIOCeRmPPnTuX+Ph43nzzzXu+FkIIIYQQovp5/vnnuXpV1+X5ypUrDBgwgBs3bjB27FjWrVtnUJ0G9cEfMGAAAwYMoEmTJhw5coSVK3VzvO7YsYOePXveUx25ubk8/fTTTJo0ic8//7xoe0hICKC7WXj11Vf56KOPirr9jBo1iuDgYLZs2aI38b+fnx+7du0CQKPRULduXVauXMmUKVPuqx4XFxf27t2rl9n/8MMP6dChA6+++uo9fa6zZ8/y4YcfcvjwYUxMTO5+gBBCCCFENadUKFCWw5SW5VFnWUpPT2f37t38+uuvAKxevZqRI0eyePFili1bxvLlyxk8ePB912tQBv+bb75h5MiR2NjY8Mcff+DiohtJfvz4cd577717quPw4cMkJCQUm8C/Vi1d94lz584RHx/P008/XbQvMDCQdu3a8ddff+kd06dPn9sfSKmkXr16RERE3Hc9/fr10wvu71dWVhbDhg3jq6++okaNGgbXI4QQQgghqr64uDgcHR2L3u/evbtonGvdunWJi4szqF6DMvgWFhYlztn5zTff3HMdSUlJAHh6epa4PzZWN/Wim5ub3nY3N7eifbdYWelPxWhiYkJBQcF91+Pk5HTP7S/JihUriImJ4cSJE0UDc6Oioti7dy+vvvoqX3/99QPdQDyI4V1q07GRF5EJmfyw5SKJablF+5ztLPhybFuW77jKrtMy0OdBWZubMLqLN7U9rTgTls7yfTHkF2pL3S4ejK+LFWN61cLBWsX2kzFsPa6bSchCZcKER+vg52bNX6dj2XREvtui8nmiRxN6tgkkJjGNeb/+TUxiOrV9Xfh4Yl8A1v11ll92nDZuI6sIS3NTxg5sQYOabpy5FssPG06Ql19YtL9ZoCdTRz7Cm9/vJDQ61XgNraIUCt2rPOp9mPn6+hIXF8cff/yBjY0NR44cKer6funSJerVq2dQvfccbUZGRhIZGan336W97sWtwD4sLKzE/b6+vkXn+reIiIiifffiQeu5nxXQWrduzfvvv1/UXz8gIACVSoWdnd09998vD68/0YQWdd3481gE3i7WLJyiP+Xnh6NbYaJU4uNqY5T2VTWzRwehVMLOs0k81tKNib397rhdGM5SZcIPL7fmZnwWh68k8uGoxnRqqLuZn/l8M2p42LD3XDyvPV6P3s29jNxaIe7PKyM60KVlbf74+xKOdlb88pnuSXRCSiaLfz9CQkomdf1d71KLuFfz/9cPawsVW/++Rt92dXl3TOeifbZW5rw6vC1errbYW1sYr5GiyrGwsOCTTz6hX79+dOzYkenTp+Pg4IBWq2X+/PlMnDjRoHrvOYN/KxjWarV3DYzvZWKeVq1aERAQwCeffMKiRYuKgt9Dhw7Rtm1bGjRoQI0aNfjuu+/48ssvAThy5AjHjh0rmn//XjxoPQ4ODqSl3dv0g40bN6Zx48Z625YuXUrTpk3vuQ9/efhh80XSs9UAHLoYyx+f9SvaN65/MBsO3KBzE+8SjzVRKnjh0fo0rOHEtmMRtApy4/PVp8jMyWdQ+5p0bepNVm4BC7de5FpUGn1a+WFuZkIdH3sC3G1ZvvMqhy7qHi+VVN7f3ZZx/YPxdLZCqVAwdtZevdX+KqPJyy6TkXM769Ml2OmO2//N0dqU8b18sbU0Zf2RePo2c+H9X0KwMFPydCcvgn1tCE/M4cedUaTnFDCptx8nQ9Pp2dgZUxMFc7dFEJOSV2r5NnXsGdLWHTtLUyKScvlo7Y3yvyDlSF2gYfDH+8jO013XYD8HfF2ssDI3oVNDd5q/vJW8fA1ZuQUM7+jPthP66wQE+tjxfK/aZOXms/98Al7Olvy8KxR3RwvG9KyNl5MlJ64n8dNfoRRqtMx+sTkr94QxvFMAsSk5fLvxCrnqwlLLD+/kT8cGbliqTPnrdAzLd4cZ4SqJymrppmOkZeqetv7592UurNWNI0vLzOXPQ1eo6++KvU3JE1soFApeHNSGFvV92XH4KsG13Jmz+gCJqVk83rkBvdsFoc4v5IffDnE+JJburevi6miNj5sDdf1dWbXtJLuO6aZBLqm8j5s9L4/oiK+HA0qFgnGf/EpKek7FXJhy8vLXf5CelQdAVm4+T/VuVLTvoxe78MHC3cz/X78Sj7WzNue14W1xdbTm5z/OMLJXQ17++g9UZia88FhzmgZ6EBmfzpxfj5CUlsNLQ1tz4UY8PVvXxsrClK9XHSIsJrXU8q3re/P0o01wtLUkLjmT12Zvq5BrUpHKa0rL+63z+vXrTJ8+nVOnTuHq6sr48eP1unnfybVr1+jduzfW1tacPXv2ns85adIknnnmGXJzc3F11d205+fns3jxYho2bHh/H+Af95zBv3TpEpcuXdL779Je98LU1JQ1a9awZcsWmjdvzvPPP0/z5s3ZsWMHACqVih9++IH58+fTs2dPRo0aRY8ePXj55Zfva1GrB62nb9++zJ8/n3HjxvHqq6+iVqvv+dwPi1vBPcBLAxsy7/fzADSu5Yy9tYodJ0p/6vLywIb0bOHLtmMRNK/ryshudVGZKnmudxDDutRm+4kILt5MZvH/umJlYYq/uy1vDG9KdGIWRy/HM//VTpgoFaWWn/FcKxLTclmx8yo/bb+C+l+PQyurW0G8hZmS4Y94sGxv1B23/9s3z9XDUmXC/ospPN3Ji1b/zIX/1TOBeDmZs/VUAlpg5jOBADQKsGF8T1+OhaSTk6fh/Sdq3bH8rNFB/H0llVUHY/njVOWfwrJQoy0K7v1crWhS05FNR6LwcLQkLjW3aB77kJgMvF30u/JZW5iy/PV2hMRkcCY0lcmD6lHP1x5zMyUrpz5CSkYeW45F0aquC+P61gGgS2MPBrf346/TMdT3s+fZ7jVLLR/sZ8/zvWqz9Vg0y3eFcvRqUsVeHFHp3QruAV4b2YlvV+2752PHD2nH0O6N+ePgJerVcOOFgW2xsjBjZJ9mvDi4LTsOX+XUlSiWffgkjraW+Lk78NaYHiSnZXPg1A0WvPUEluall/9oQh9y8tQs23SMxb8fITs3vzwuQYW6FdzrguxmzP9nZdlRvRux52QYIVEppR47b2o/3Jys2Xk0hOf6N6VjkwAAZr3am7p+zvy+7wrZufksfutxABrXcWfqU49w9GIkCanZzH6tzx3LL3xrAIfPRbB0yynW7b5YPhdAkJaWRqdOnVAqlaxdu5YXXniB559/nuXLl9/1WLVazbBhw/D09CQ6+v4XnbS1tS0K7kEXvxoa3MN9ZPCDgoJK/O8H0apVK0JCQvjzzz9JTk7mpZde0suAd+/enRs3bvDnn3+SlZXF5MmTadq0qV4db775ZrGs+ZgxY7C1tX3gegCmTp1K69atuXjxIrm5uffdzWbq1Kn4+Pjc1zHlQaGAGc+1JiElh0V/6G7CvnixLUnpefw0rRuBvg60qedObEo2249HFB3XqbE3by46zMWbKWw6FEafVrpuJY+2CcDURMGQjrqA0tbSjPp+ukEiv+wNYcVf1wAY2qk2bg6WpZbffSqKfm0DMFEq2HEigkJN1eiTbmdpyrfPBfH99gjO3sy863YAU6WCIC9rnp5zDoDTYRksHB+MiVKX7T94JYXBbdwBaFn79ly+3269ybGQdHacSeSPt5rfsfzhq6l0bejMqdB0tlWBAP+WQB87Pn66MRPmHiUtOx8rC1PMzW7nL8zNTMjJ0795bOBvz7mwVL7fqvuuarVaWtV1oVGAI0625rQKdKFVoAtW5ia0D3Zj7uaraLVa3lp2moJCLWlZ+Qxs51tq+eW7QknPzqdzI3dOXEvmyJWqc71FxfpgXC80Gi0zl9/7ooldW9bmrblbOXI+nN92naNXO93f7cc6NcDMxIQnejYBwNLcjKZBuie4v/11lkW/HwFgUNdG+Lg7lFp+17FrjOzTnMJCLdsPXyFPXbmfvN5iY6li4ZuPsWjTKf4+F4G1hRlvP9eJk5djGNa9AbV9nPl4XDcmf7ONK+G3b9rbNvChzhPfotFo2XsqjN1zRwPQ75G6HDoXwZM9dYFasyBPzM10M+zNXPk3O4/d4Lc9lzi3YsIdy+8+EUqvNnU4ejGSTQeuVOQlqTAPQx/8RYsWkZmZyU8//YS5uTlNmjTh5MmTfPjhhzz11FN3PHbq1Kk0bdqUevXq8dlnn91XG9VqNV9++SUbNmzAwcGBHTt2kJuby5tvvskXX3yBqen9D5k1aJBtWloau3btIjQ0FIVCQY0aNejatatBCwjY2NjccfofNzc3Ro0aVer+kh6b9O/fv0zquaVjx4507Nix1P13MnLkSIOOK0sWKhO+e6kDBy/EsmTb5aLtH/58HEuV7ivwdM9AIhMyuRSun6FIy1Lj727LxZsp2FmpcLAx/2d7HpfDU/VWqr0Rk06LQDe9LjaFGg0mJspSyx+/msDqPddpGODEx2Na8/6yYxy9HF8u16GieDuZM3t0EJ/8doNToRl33X5LwT83Ny62ZiRm5OPnouvnWaiBjJwCNh9PICNXF6SuOnB7gHjOP1nqQo0WE6XijuVfW3oFZ1szGvrZsOrVRvT86AS5/1qttTJ6pL4rUwbVY/x3R4lL1WU841JysDAzoY6XLdeiM+jWxIML4al6x6Vn5+PtYoVSARot+LlaA5CWrSYrt4CVe0LR/HNp0v55ClZQqKXgn4HRBRotJiaKUsunZeczaMY+/N2s6dTQjUWvtmHYZwfK/4KIKsPM1IS50wZx8UYcs1fee/YedNn/AC8njpwPx8rCDDdH3Rir1IwcroUnsP/U7e55567HEuDpRHbe7Sx8QaEGUxNlqeV3HbvO2p1naVTHk/fG9mLmz3v46+i1B/vARubpbMOitwbw6U8H2H/6JgB5+YW8NHNrUZka3o5sOnCF2GT9BE2OugAvF1si49MJ8HQo2p6akcvv+66QlJ4NwJItpygo1P1Q/PtvpeIu5V/++g+c7S1pUseD9Z+NoPPEJaRm3H7CI8rG3r176dixI+bm5kXbevfuzdy5c4mNjcXDw6PE4zZt2sTWrVs5deoU33///X2fd+LEiRw5coTevXuzfft2QNc3Pzc3l6VLl/L888/fd533HeCvXLmSCRMmkJaWhlKpRKvVotVqcXBw4Pvvv2fYsGH33YjK4vr163z33Xel7v/qq68Mussqb28/1YLW9dxRmZnQ5Z++9k9/9hcHz98OErs09eZKeCoR8fo/WvM3nmfR1C4816ceFioTsnLzycsv5Ms1p1k8tQtt6ruTmaP7o7D/XAylKa38R6Nb4e9ui7mZCd4uNiSkVu4+nAA/jAvGRKlgfC/dWJWr0dl8tTGs1O3/tmBnBJunN+NqTDYarRZ1ge4PwWfrQ3n9sQBuxOWgLtRwMz6X3eeTS21DSeUPX03lm+d0WTxHazMycguL6q+snGxVLJvSjrOhKXw2WvdUbuORSNb/HcEXay/yy/QO3IjNxMvZkuH/Ca4vRaRzMz6L3Z/1ICFN93TuXFgKV6My2Hk6hndGNCQ0LhONBrafjOH4tZKvd2nl1QUaXntcN/uBn5s1x65KBl/cnzef60bPNoHY21jStlEAAE+/uxKVqQkL3x1GgJcTZqZKGtb2ZOJn60hMzSo6du6vB1n7+TM81bcFVhZmZP/z2/3Fsl38+sUzNK/nS3qWLkA8fO6XUttQWvkPxvUiKMBdN+bK15WYxPTyuxAVZOk7A3Gyt2TCoJZMGNSSG9EpvPX9X2w/ElJU5vUn23H4fCRpmXl6x3696m92ffcsF0LjKSjQkJevC97fXrCLT8Z341pEEnnqQqIS0vXq+6+Syu84GsKqD4cAYG9jTl5+QZXoEvVfCm7f6JR1vaCbb/7fzM3N9QJ50E3I0qxZM71t7u66J+FRUVElBvhRUVG88MIL/P7779jY3P9kJdnZ2axatYqwsDCioqKKAnzQ3VzMmzev/AP8U6dO8cwzzzBhwgQmTJhAnTq6fqlXr15l7ty5jBo1iqCgoBK7ulQFFhYWBAQElLrfWLPk3M3Kv66y5y7TXy778wpZJfxgHL4UR6fXNlDby564lGx+ebcXWbkFXApPocuU3wkOcMLBxhwFkF+oYeuRm3rHf/TzcRLTcohMyCyx/OZDYdhaqchRF3D2RhIZ2ZX/R+ujtSFYqm4vcpaalX/H7f+26K8odp1LxtbSBA8Hc/o31/XH23Asnr0Xkwn0ssbS3IT0bN0fjzlbwwmL190UFWpg2oqrpZZXF2hYdSAWrVZLWnYBZ29mUNl7RGXlFjD22yN620LjdDepv+y/yf4L8fi6WHEhPI2s3OJdCF789giNazqSnVvAkPZ+xKXoApj3V5xj4Z8h1PCwQWWiJDxBFzi9uuB40bGXI9L54Y/rpZZPzshj+a5QNFotMSk5XI6o/AGQqFhrtp/m8Dn939T8gkIKCzUs/l3/e5+ZrR9wnrwUSctRswkMcCUqPo0/5rxIfHImcVotbZ75hkZ1vHCys0KhgJy8fLYfvoK56nZI8PHinUTEppCZoy6x/JYDl/j7TBi5eQWcvRZNSkblT868v2gPtlaqovf/DeIB3lmwq8S++Is3nWLX8VBc7K2wszZn3KAWAGw6cIX9Z25SP8AVGysVWf8kuL5Zc4TI+NsTeEz4ckup5bVaXSZfq4X0zFxOXo0hv5InZ4zhvxPEvPfee7z//vt62zQaTbFErZmZGQCFhcXHCGo0GkaOHMmECRNo3bq1Qe0KDw/H29sbFxeXYn337ezsSEkpfezHnSi09zLlzT9Gjx6NlZUVc+fOLXH/uHHjKCgoYOHChQY1prryHLrU2E0olZuDJV+Na4eZiZKGNZ356pfTLP3z8t0PfIhZu9cydhNK1T7Igac7e2FtbkIdD2te+P4CZ24W785TWagzHu6s9ZietWgf7IaLnTl2VmY89sEe0irxTeaBD4ON3YRqpeGopcZuQqnsbSz44e0nMDVR0qiOF9+t2c83q/Ybu1kPxMTi/rsBV5Q2DXx45Yk2WFqYEVzDlWc/2sDBs+HGbpbBkrdPq7BzpaenY29vT5dPrmJqYXv3A+5TQW4Gu9+sS0REhF5X8pIy+D179sTe3r5oVVmAnTt30qNHD27cuFFsEdPU1FQcHR3x9PQsWucoMzOTjIwMPD09mTVrFkOHDr1j+9LS0vDx8SEiIoKIiAhGjRrF6dOnAZg8eTIJCQn8/PPP9/257yuDf/DgQdauXVvq/nHjxlXpLjrVUUa2mp+2X6FQo+VaVBqRCZl3P0gYLCQ2m1UHYslRF3I5KovUrKoxcO1h9felBMLiskjNUnP+ZmrRrDtCVHY5efks/v0IBYUabkQmERpdepc+8eDColNZsuUUuXkFXAiNJymt8j/RqGjlPcjWzs7urmNFW7VqxdKlS9FqtUW9Mvbv34+bm1uJPTjs7e2JiIjQ27ZgwQLmzp3L4cOH72kBVXt7e4YMGcLgwYMZMWIEarWagwcPsnr1ahYsWMC+ffc3/uaW+wrwIyMjqVu3bqn7AwMDiYqS1SKrkhx1ITtP3tviZeLBxaSqiUmVP8QV5VJEOpek64yogtT5hfx5qGrOtvIwik3OJPaIJMAquzFjxjBz5ky++OILpk6dyuXLl5k/fz4TJkzQW69p6NChbN++nfr16xebKdHe3h6lUnlfMyjOmzePyZMnM3HiRNRqNe3bt8fT05MVK1bQpk0bgz7LPc+DD5CTk4OFRekruFlaWpKVlVXqfiGEEEIIIf7r1kJX5fG6VzVq1GDdunXMnTsXOzs7mjVrxpAhQ3j77beLyuTl5REVFVWm6yJZWloyf/58UlJSOHv2LFeuXCEyMvKu3Xvu5L6nfNm2reqtniaEEEIIIUTfvn25efMmSUlJ2NraFuun37ZtWyIiIopm1/mvcePGGTxFupWV1QMtbvVv9x3g9+nTp0xOLIQQQgghBADl1AffkLk3FQoFLi4uJe4zNze/Y/cbGxub+54uMzU1lTlz5nDmzBkyM/W7etWqVavUyW3u5L4C/JiY0uc5F0IIIYQQQtyf3r17k5iYSI8ePYoN5vX29jaozvsK8EtbwUsIIYQQQghDKbm//vL3U+/DLCIigkuXLhEeHo69vX2Z1fuwf24hhBBCCCGqJJVKhYuLS5kG9yABvhBCCCGEMDKFQlFur4eZu7s7Dg4OHD16tEzrve9BtkIIIYQQQogHFxUVhbu7O506deKxxx7D09NTb7+3tzdTp06973olwBdCCCGEEEZV3ivZPqzUajVKpZJu3bqRlZXF9evX9fZrNIatsC4BvhBCCCGEEEZQo0YNNm/eXOb1SoAvhBBCCCGMSkn5DAytroNNq+vnFkIIIYQQD4lbXXTK4/WwU6vVfPzxx7Rs2ZIePXoAkJuby+TJkykoKDCoTgnwhRBCCCGEMJKJEyeyZs0aunTpQkJCAgAWFhbk5uaydOlSg+qUAF8IIYQQQhiVUlF+r4dZdnY2q1atYteuXYwcOVJvX+/evVm7dq1B9UqAL4QQQgghhBGEh4fj7e2Ni4tLsTn77ezsSElJMaheGWQrhBBCCCGMqrwWpXrYF7ry9PQkOjqa1NTUYm3duHEjdevWNaheCfCFEEIIIYQwAnt7e4YMGcLgwYMZMWIEarWagwcPsnr1ahYsWMC+ffsMqlcCfCGEEEIIYVTl1V/+Ye+DDzBv3jwmT57MxIkTUavVtG/fHk9PT1asWEGbNm0MqlMCfCGEEEIIIYzE0tKS+fPnM3PmTEJCQjA3N6d27doolYYPlZUAXwghhBBCGFV5zVn/kHfB12NlZUXDhg3LpC4J8IUQQgghhDCC8PBw3nzzzRL3KRQKLC0tCQ4OZuTIkbi4uNxzvTJNphBCCCGEMKrqupKtQqEgJiaGFStWcPHiRTQaDZmZmezYsYNffvmF1NRUZs+eTcOGDYmLi7vneiXAF0IIIYQQRlVdF7ry9fUlOTmZNWvWcPLkSVauXMmGDRsIDw+nd+/ePPLII1y9epVWrVrx8ccf33O9EuALIYQQQghhBLGxsSQnJ/PEE0/obTc3N+eVV15h27ZtmJmZMX78eM6ePXvP9UqAL4QQQgghjEpRjq+HWXZ2NomJiaSnpxfbd+PGDbKysgDQarU4ODjcc70S4AshhBBCCGEENWvWpH79+vTs2ZNNmzZx7do1zpw5w+zZs5k8eTLDhw8HYPXq1QwdOvSe65VZdIQQQgghhFEpFAqU5TAiVvGwj7IFNm3axJQpU3j88cfRaDQAuLq68tFHHzFhwgTy8/MZM2YMHTt2vOc6JcB/COQk3TR2E6qVrPDjxm6CEOWi0eh7758pHty5n581dhOqlYYjFxq7CUKUCw8PD1asWMHChQu5efMm1tbWeHt7Fy10ZWZmdl/BPUiAL4QQQgghjEwWutKtaBsUFFQmdUkffCGEEEIIIaoQyeALIYQQQgijKq856x/2efDLi2TwhRBCCCGEqEIkgy+EEEIIIYxK+uCXLcngCyGEEEIIYSRqtZqPP/6Yli1b0qNHDwByc3OZPHkyBQUFBtUpAb4QQgghhDCqWxn88ng97CZOnMiaNWvo0qULCQkJAFhYWJCbm8vSpUsNqlMCfCGEEEIIYVTKfxa6Ko/Xwyw7O5tVq1axa9cuRo4cqbevd+/erF271qB6JcAXQgghhBDCCMLDw/H29sbFxaXYqrt2dnakpKQYVK8MshVCCCGEEEZVXQfZenp6Eh0dTWpqarEAf+PGjdStW9egeiXAF0IIIYQQwgjs7e0ZMmQIgwcPZsSIEajVag4ePMjq1atZsGAB+/btM6heCfCFEEIIIYRRKSmffuOVoS/6vHnzmDx5MhMnTkStVtO+fXs8PT1ZsWIFbdq0MahOCfCFEEIIIYQwEktLS+bPn8/MmTMJCQnB3Nyc2rVro1QafnsiAb4QQgghhDCq6toH/9+srKxo2LBhmdQlAb4QQgghhBBGkpqaypw5czhz5gyZmZl6+2rVqsXcuXPvu04J8IUQQgghhFEpAWU5ZNsrQx/83r17k5iYSI8ePQgICNDb5+3tbVCdEuALIYQQQghhBBEREVy6dInw8HDs7e3LrF4J8IUQQgghhFEpFIpi88CXVb0PM5VKhYuLS5kG91A5nlwIIYQQQghR5bi7u+Pg4MDRo0fLtF7J4AshhBBCCKOqTrPoREVF8eWXXxa9d3d3p1OnTjz22GN4enrqlfX29mbq1Kn3fQ4J8IUQQgghhFEpFeU0yPYhDPDVajXXr18veq9UKunWrRtZWVl62wE0Go1B55AAXwghhBBCiApSo0YNNm/eDEBOTg7h4eEEBgYWK3drnyGkD74QQgghhDAuxe1uOmX54iHM4P/blStXGDZs2H3vuxsJ8IUQQgghhHjIpKenY2tra9Cx0kVHCCGEEEIYVXUaZAsQGRnJjBkzSEpKIjIyknHjxuntz8/PZ//+/Tz66KMG1S8BvhBCCCGEEBWooKCAxMRE0tLSiv773ywsLBg9ejSTJk0yqH4J8IUQQgghhFEpAWU5dJh/WPuiBwQEsHbtWiIjI1m2bBlvvfVWmdb/sH5uIYQQQgghqjQfH58yD+5BMviiFI808sff04EDp8MIj0srtt9EqaBnmzq4Odpw6ko0Z6/HFu3zdrWjfZMAnOws2XsylIuh8RXZ9EpnwvDOANyMTmbLvrMlllGZmdKjbX0szM3Y8fcF0rNy77hdlMzLzYHHuzYB4MjZUE5cvFliOWcHG7q3qUdqRjbb/76IVqu943ZRulo+TrRp4MflsAROXI4qsUyLet40qOVBZHwafx27zq3LamVhRudmNfHzcCAsJoVth65WYMuFuDMTEyU92wRhbaXir8NXSMnIKVbG2cGaHm0Cyc3LZ+fhq2Tm5AGgMjOhzyP1UZmZsv3QJdIy5be7uvXBL2+SwRfF/G9UR2ZP6Uf7xgHsmPs89Wu4FSuz4qPhvDL8EZrU9eLXz0YyqEswAF1a1OTAj+Po0bo2fh4O2FipKrr5lYpCocDP05k2jWsxZlD7Usutmz2eCcO7MKh7M3YumoK5yvSO20XJLFSm+Hk6M7BbM7q2DiqxjIujDfuW/Y/e7RvwxnN9WPDeqDtuF6Vr18iPrbNH066RH4vfGczo/s2LlXnh8ZYseXcIDWt78O6YrsydOgAAN0dr9i54kQlD2hDg6Yirg3VFN1+IO1r+8dO8OrIz/Ts2YNePL2FrZa6339fdgcM/TaZbq0Ae7dCALd+NRWVmgoXKlK3fjWNA54YM6NyAwz9PwVm+36KMSTQg9KjMTBg/uA2tRs8lISWLFx5vyYQhbZj05Ua9cp2a1SDgsc/Jyy/k5OUoOjeryW+7L/DlS3155/vtLN922jgfoJLRarVMm7WO9s3qMPnpHiWWad2oJo521rQf9RkAP306hkHdmnEjKrHE7av+OFph7a9sbkQmMm3WOqaO7lVqmaf6teHPA+eZ/OUvmJooOfPb+9TwdmFA1yYlbg+NSiy1ruru5WHteGven6zddZ6a3k5snvUMSzad0CvTuVlN3pizja1/X8HTxZadc58HYMrIDpy7HstzH60zRtOFuKMGtT0J9Hejxcgv0Wi0zHvzCYb1asbC9YeKynRuUYedR64w/uNfAJj/1hM83qURm/aeZ+yMNYRE6H47fps5hiaB3vx1pHo/oapOK9lmZWVx7do1mjRpUm7nkAy+0OPjZk9CahYJKVkAHDkfQaC/a7Fyc389zJypj/HaiPaM6NWYn7aexMnOEl93ew6dD+eZR5vRrpFfRTe/Sgqq4cGx82FF74+cuUFgDY9St4sHE1TDkyPnQgEoKNRw8lI4gQEepW4XpQv0d+XohQgAbkQlY6pUYm+tn+VcsvkEYwe14pXhj/DFS32Y+6suQGoV7Mv6PRd4sldjHutQDzNT+XMlHh6BAe4cvxiORqPrT3bkXBiBAfpPu09ciqBX23q8/UIv3nmxFy2D/alXw52cvHxuRiczfmh7PhzfFzMzUw6fDTPCp3i4lMciV+XV7edBXb9+nWeffRaAixcv0qtX6UknQ8kvptBjolRQqNEUvS/UaDAp4fbX0c4SexsLfNztMTM1wd7GAnOVKTl5+Xw2sTeN6njy7ZTHmP5M5wpsfdVkolSi+e//JibKUreLB1Ps30ChBqWJotTtonQmSiWFmtvjFAo1WpT/+Y462VlirjLFz8MBWytz7K0tAN3TxHGDWtOyvi/PP96SrbNHV2jbhbiTkv9W6n+3L96I5fHXfqSgUENCSibbDl4s2qfrnumIj7sDZqZK7P753ovqwdHRkbi4ONRqNWq1mri4uDI/R6XvojN9+nS6d+9Ot27djN2UKiEqIR0vFztsLFVk5qhpVNuTG9HJemUCvBzp0ao2jZ78BoC2Df14Z0xX+k1ehomJkuc//o20zFwa1fZg/huP8+myPUb4JFVHaFQiw/q0LHrfKNCXY+dDCYtKKnG7eDChUYk0qO3N2u26riSN6vrw2cKtpW4XpQuNTqZBLXeiEtJxd7JBZWZCSrr+QMQ3nu7EiLdXczU8EYUCbm6cxjdrDnIjKpkNey+ybtd5AK6snYKHsw2xSZnG+ChC6AmNSuKlEZ2K3jeq482NqKRi5c5ei+bstWhMTJQc+XkKM37chqujDSnp2Uz/dhMAn7zUn0HdGjN3zf4Ka//DSKFQoCiHdHt51PmgfH198ff3x9vbG3t7e6KiomjQoEGJZYOCgli7du19n6PSB/jr16/H29u73AL8d999l3bt2tG7d+97Kr9s2TJ2796tt83Hx4cZM2aUR/PKXHZuPut2neeXT59k36lQnnm0Oc99pPtiDe7agINnwohJSMfERMk3U/pzIyqZAR3rceDMTTQaLQt+O8qC6Y9z6FwEvdrUYf/pMON+oErgqf5taBLoi6+HExOGd2bbgfOExyTz/OAOfP/LXvYev8Jnrw3m+3dHkZKeRbfW9Zg+ax0Z2bklbhels7ZU8cyAdrRqWAPQzWC0ZP1BXBxtaFbfn993nWbF5iPsXvI6ALX83IiMS+FyaGyp20XpFv5+jK9eeZQmdU/Rq00dFm44BkDL+j4AHLsYyckr0Xz96qNsP3KNRnU8iIhLJTs3nx/WH+Wzib3xc3eglo8TKek5xCVLcC8eDscuhFNQUMji958kKj6NgV0b0e6ZWQCMH9qeBesOotFoeWFQO6wszOjVrh5xSRn8vuc8jet68dOMUew6ehVbK3OG9mjCsP8tMfInEhVJoVCwZ88eNm/ezLFjx1i+fDnPP/98iWVdXYt3k74XlT7AL28bN27Ezs7ungP8Q4cOceHCBSZOnFi0zdHRsbyaVy5e/3Yrw3s0wt/DkVHvreH4Jd3Udh5ONpibmZKXX0j3iQsZ0q0hjraWzFt7mPV7LgAwY/EuBnYOJrimO4s3Hue3f7aL0nm7OVBQqOGvI5fw83TGytIchUKBr4cTABqNlt5jZ/Hko20wV5nS/fmvSMvUZUFL2y5KZqJU4ufpTEhEAgB+ns6YmCixMFfh7mQHwM2YJHqNnc3jXZuw//hVlm8+fMftonSb9l8mNSOXDk0C+O7XQ/y2W/d74GB7uzvCS19uZEjXBtTydeb4xSimfvsHAPtPh/HyzE30bF2HCzfiePv77cispOJhMuDVH3myb3NsLM3pOW4uCSm6G1A/T8d/ssZa/Dx0f/+XbznGL9tPodVqOX0lite++o3HuzQiJSObvpO+51p4ghE/ycNBSfn0G39YO65aWFgwZMgQWrVqhampKa+++mqZ1q/QGnki5ytXrrBq1Sri4+Np3bo1o0aNQvmvfmwHDx7kt99+Iysri9atW/PUU09hZmZWtD8oKIhJkybh4+PD/v37MTc3Z9SoUdSrV0/vPHer51ZXn6SkJPbu3Uvjxo1JS0vj008/pVatWgQH66aBXLBgAebm+oPE/m3cuHEkJibe1+MUh64f3HNZ8eA0WfJDKqompbVhmR5hmLNLRhi7CdVKw5ELjd2EaiPtwBcVdq709HTs7e157ecwzK3syrz+vOx0Zo0KIC0tDTu7sq//YWXUG5t169bRqFEjQkNDqVevHnv27GHs2LFF+xcuXEjXrl1RKBTUrl2bGTNm8Oijjxar55NPPmHZsmXUrFmTmzdv0qJFC8LDw++rnvXr1/PUU0+xbNkyGjRoQL169WjcuDE2NjbUqlWLzp0707lzZ0xMTO76uS5fvsyECROYPn06W7dKH10hhBBCiDupTrPolOTAgQOMGjWKtm3b0qNHD9544w0SEgxPSBqti052djYvvPACb7/9Nu+8807R9ujoaAAyMzN54403+PLLL3n55ZcBGDp0KHXq1GH9+vUMHDiw6JjGjRuzfv16ACZOnEhQUBBr1qxh6tSp91VP7dq12bJli147nZycaNGiRdF0RnejVCoJDAwkKCiIqKgoRowYQf/+/Vm+fPn9XyQhhBBCCFGlff3110ydOpWePXvSvn17srOz2bBhAwsXLuTw4cPUqVPnvus0WoB/+PBhUlJSGDNmjN52Ly8vAM6dO0dycjLDhw8v2ufv70+7du3Yu3evXmD+3wG2devWJTIy8r7rKYt5SN9//33c3G7PhTto0CDatm3Lk08+Sd++fR+4fiGEEEKIqqa8su0PewY/JyeHd999l82bN9OnT5+i7RqNhqeeeopPP/2UxYsX33e9RgvwU1JSgNJHB996LOHk5KS33cXFhfj4eL1tFhb688cqlUoKCwvvux4HB4f7+AQl+3dwD9C6dWt8fX05fPiw0QJ8E6WCcYNa07l5TcLj0pi96gARcWkATHqiLZ2b1SQjO48f1h/l0Lnwu9Qm7sbJ3prXn+1FUA0Pjl8I4+ufdpCbl4+jnRX/e6439Wp6cTMmia+WbCMiNsXYza306tX05JWnuuNoZ8XmvWf5edPtlSSf6t+Gvh0a4uxgw3crd7FpzxkjtrRq6NWmDqP6NiW/QMOi349x4MxNALq1rMXo/s1RmZnyx99Xiq1YK8TDzt7GgsmjutCgthenr0Qy6+c9ZObkYWtlztRnutGorheR8Wl8/dOuEqfEFMIQYWFhuLi46AX3oItlX3zxRd544w2D6jVaH3wfH900aSEhISXu9/PTrYJ68+ZNve2hoaH4+/vf83ketJ6ymD81OzsbY45lnvZsZ9ydbfn+tyMALHtvKADDezRiaLeGfP/bEXYdD2HVjOFYW5jdqSpxD75/dxSRccnMX7OHDs3r8taLuvEeM14eiKujLbN+2k5mVi4L3nvayC2t/KwtVcx/5yn2HrvCyq1HmPZ8H/p2bAjA1NG9mPJMT9ZsO8Z7c3/n6DlZI+BBdWgSwDOPNmPVn2c4cDqMlTNG4OFsg6eLLQvfGsyGPRdZvPE4E4e0pXur2sZurhD3Zc4bQ0hKzWbeL/tpFuTLhxN0Sbm3X+hFDW9nZi3fQ2xiOks+HGnkllZNSkX5vR5mTk5OxMXFkZycXGzfhQsXcHZ2Nqheo2XwW7ZsSWBgIO+//z7Lly/H1NQUjUbDrl276N69Ow0aNCAoKIiZM2cyb948AHbt2sWpU6dYsGDBPZ/nQetxcnIq8aKXpKCggC1btjBgwICibfPnzycxMdGo3XO+XrGfnLwCAC7ciGPHd7puUbbW5hw+F87Oo9dRmZkw7ZnOqFSmZOXmFx1rbmbC66M60qi2B1sOXqF1sA/T5/5JZo6a5/q3oHur2mTlqJnzy9+cvhrDoC7B2FiZE1zDjQAvRxb9fpztR66hVCpKLF8vwJXJIzvg7WqHQqFgyBvL9c5fGT3z5iJy8nSfwcHWim5tdDM62Vpb8Oufx9l7/CpJaVl0bFG32LFebg689eKj2NtY8vOmQwzq3oyxH/yMvY0lrz7dgyaBvoRGJvDZoj+IT87gg4kDOHoulIHdmmJqasKM7zdzPSK+1PJ9OjTk2QHtsLe15EZkIhM+qtxjQ3Ly8unxwtfkF+ie2HVqXhdXR1sAXn6qO4Nemcux82GlHt+qQQ1efqobmdl57Dh0ES83B+as+Itavq688lR3fNwdOXLuBrN+2ok6v4BVX77I4t8O8Nyg9sTEp/LevI1kZOWWWv6lkd3o1joISwsVG3edZu7q3aW2pTI4fimSJ98JK3r/XP8W2FlbUFCoIT4lk3W7z6PVwpBuDbC1Kj7b2LAejRjYOZhrEYlExadzJTyB3cdv0LFpDZ7t1wwrCxW/773Iqu1n8PdwYNzg1oRGp9C9VW0Onwvn65UHAEosb2Op4u3nulK/phtmpiZ8unQP+07JTZ24d2NnrCn67VaZmvJkn+aA7rd7y4EL7D1xnfCYZAZ1bVzsWFdHG956vieujjYs/v0wT/drxTPvLMfG0pxXRnaiWT1fIuNS+WLpTqLi05j2XA8uhsTwaIdgrCxUfLJoO5dC40ot361VXcYMbIujnRUxCWk89/7KCr02ovy4u7vTuXNnunXrxltvvUX9+vXJzs5m586dfPjhhyxZYtgaCUbL4CuVSn799VeOHDlCvXr1GDJkCPXr1+fs2bMAmJqasmjRItasWUOrVq3o378//fv355133qFFixb3fJ4HrWfw4MF89913DB8+nGeffZa8vLxSyyoUCpYvX05wcDBDhw6ldevWvPHGG8yZM4e2bdvec5vL2q3gHuCt0V35fNleAH7acpI6fi7s/2EsJ356ia+W7yu2yuTrozrSuI4n3/92BE9nW4Z0bYipiZJJQ9vSpXlNflh/lD/+vsKPbw3CztocL1c7Xn6iHX+fC+fXneeYM/WxO5b/aFxPzlyL4aNFu/jgx53kqAuo7G79gbCztuD5IR34dsVfALz33e+8N74/e5ZMZdUXL/La56uLHbtkxmiSUjNZvP4AQ3u2oHGgLwCLPnoWjUbL7J93EBaTxI8fPAPouqeMGdSe1X8c5UpoLJ9PHnzH8t+9+SQ/bTzE+/M2Mr+SB5ugWyPgVnAfVMODxkG+rNtxAndnO5QKBbV8Xdnw7US+nDIUF0cbvWPtbSxZ8cUL7Dx8iQ1/nWLSiC7U9HHF2lLFmq/GcupyON8s34m/lwuvP9sTgHaNa9GjbX1++HUfzo42jB3aqdTyTYP8GNa7Jd+u+Iv35v7Oln1nK/z6lLV//5YM6hLMlfAEroYnciMqmbV/nef08pc5tHg8SoWC3/dd1Du2TQNf3ny2Myu2neLc9VheHfEIrg7WNKrtwecv9WbD3oss/P0Yz/RrRufmNbGyMOPpvs0wNVGy4LcjDO7agEca+ZdaflTfprg6WvPFT3v54MednA+RRcjE/bn1221lYcaEYR2YtWIPADN+/JNXR3Zm948vsfHbsbz0efFpsL9/exh56gIWrT/E450b0TRI10vhu+lDsLRQ8e3KvVwKjWXx+7rsf10/V14Y/Ai/7jzNiUsRfP36oDuW/276UNb8eZIPFvzBt6v2lvelMIpbK9mWx+tht3LlSpo1a8bw4cMJDg6mZcuWzJ49mzlz5jBs2DCD6jTqQlcNGzbkypUr7N+/n+TkZL788ktq1KhRtL9du3aEhoayd+9esrKymD17NrVq1dKr49NPPy025/3LL7+MtbX1A9cDMGHCBNq3b8/FixfJzc294zSZJiYm/Prrr4SEhHD69GkcHR1p0qRJsf7/xmBqomTO6/05eSWa5dtOAzCiVxMA3v1hBy721vzv6Y5sOXCZ+JSsouPaNw5g8qzNXArTZdqG92wE6Prh2lqZM3VURwCc7KyoX0M3/mDV9jP8vlf3x/3Fga1wd7IptfyGPRcY9Wgz/Nwd2HboKhpN1VjJxs3JlsUzRvPud79zMUQ3M9Q74/qx49BFth04T9N6fnz8yiB6vTir6BgzUxNq+7oVbbsSGsu62RMwNVHSqUVdbK0taN9M1+2hUV2fouM+X7yNI2dvsO/ENY6tfuuO5TfuOcPzgztw8uJN1u6oOn2kWzYI4IOJAxj5xkIys/NQKBSYmZpQx8+dWT/tYEDXpnz35kiGT7391K5hXR+OnQ9l6YaDANhYm/NI0zo0CvTF1dGW4X1aAWBhboa/p+4RaaFGw7TZ69BotGg0Gkb0bV1q+W9X/EViSgbPDWzP0fOhrNxypIKvSvkZ1bcpXZrXZOynutnLAv1dGNq9Ie8s2EFOXj5TRnagd5u6bP37StEx7ZsE8OPvx9i0/zIALevrbl67tayNtYWK8YPbAODqYM0jjf35bdd5zlyLYd5a3YJiLer5UMvXGWc7qxLLb9hzgYGdg3mqT1MOnA7j5JXoCrseoupwsrdiyQcj+WrZX5y8FAHAtNHdOXQmjA27zxJcy5OPJ/Wjywtz9Lretqjvy+ApiwA4eTmSfYtfAaBHmyDOXY+mxT/f98aB3liodKHX7OW7+evoVXYevsKLg9vdsfxvf53hucfbcPJSBOt3Vf5kQUmq6yBb0C2IumjRIubNm0dYWBjW1tZ4e3s/0M2J0VeyValUxWbB+Tc7Ozv69+9f6v5/z4JzS9euXcuknlsaNWpEo0aNSt3/X7Vq1Sp2A2FMtlYqFr8zhLW7zrNmx+0fhjYNfPnz0FV2H78BwNhBrQj0d9UL8JPSsgiu6c6lsARcHa1xcdDdOMWnZLH3ZKjeI/CLofG0qO9DXv7tLF+hRouJUlFq+cPnI1i1/QyB/q58/dqj5BcUsv90WHldigoRWMOD+W8/xSufreLctaii7a0b1eSJKd9z4Xo0Z65E8MaYPpiaKCko1ACQX1CIFi1+nk6ExyTToI43AAWFGhJTM/lu5S4SUjKKnU/9z1OPQo0GpVJ5x/Kvfb4aKwsVDev6sOHbSTQd8gHZueryuhQV4tGOjZgwvAtPvbGQ5HTddzcjK5fwmCR+3nSIsOgkcvLymf3GcL3jElMyqOPnjsrMFHV+AcG1ddc7ISmD1IxsZizYXPR0ICMrF9B9n2/dhN76bpdWPjM7j8dfnouTvTW92gWz8vMX6PmvG7rKavoznfFyteP5j38ruhYNa3kQGp3Mxn2XAGhV35dWwb56AX5iajZtGuiCFoUC6gW4cuxiBAkpmVwKi2fWP91vAKIT0rG2VKHOLyzaVqjR6K53KeXD49Lo+dJifN3tGT+4DW/6uvD+jzvL9VqIqqWGlxM/vjeCN77ZyImLEUXbWzcM4KXP13L0/E2OXwxn2nPdsbFUkZF9+4l+RnYetX1duR6RQHAtz6Lt8ckZ/Ljub6IS0oq2qf/5nfj338q7lX/ru81YmpsRXNuTlZ89Q8fnviE5LbtMP78wPnNzcwIDA8ukLqMH+JXJ5cuX+eyzz0rd/+OPP+qtjvuw+ODFHjQP8sbGypxn++n6FPZ5ZQk/bT3J8g+G8WTvJjjZWRIRl8bRCxF6x3698gC/fPIkLw1rhzq/kPSsPNT5hcxYvItVM0bwWMd6ZGTrAsSBU38utQ2llZ/7v8eo6e2MhcoUNycbbsZU/lllVnz2PGZmpnw19QkAzl6JZOrMX5m3eg9/fP8qIeHx1PBxZfFvB4qC+1s++WEr+3+aRlh0IgnJGeT+E7xP/epXvnr9CWISUlEXFHI1LI7DZ26U2oaSyp+7Gslv30wEwMnOmojY5Eof3Ls42rD8s+e5EBLNqq9eBGDlliMs+/1v3vh6HX/+MJmYhFT8vZyZOvNXvWMvh8Zy4uJNzm/4gKTULOKT07l0I4brEfH88udxFn7wLBGxyRRqNKzfeZIL10vOCJdW3trSnI9eehwAP08nfttxslyvRUV49JFAXn+qA8cuRbJl1rMAvD1/OzuPXWfyyA6c/PklcnLzcXaw4vH//B78uvMsYx5rwbFlE9FotKRk5JCrLmD9ngv06xDE99MGEpucgVYLHy/eRVIpwcuanWdLLN+tVW2e6N4IpVJBLW8nXv9GFhkU92fJh0/h7GDNx5P6AXDtZgIvfb6WBWsP8uuXzxESkYC/pxPrdp7RC+5B143nrx8mERadRGxiBnn//Ha/PmsDc94YQlxSBnn5BUTEpnL4bFipbSip/NHzN9k2bzyg61qYkpZNemZu+VwEI1JQPv3GK0ECv1wotMac3qWSiY2NZdu2baXuHzVq1D2tdPtfDl0/eJBm3VVNbyfcHK31th0+rwvkrSzMqO3jTFaumpDIkgcT21qp8PNwICevgPVfPEXjkd8Cuuk3a/k442hriUIBRy5E4OGsG+AYk6jLHNev4cb1yCTU+YUllm9Q0x1rSxU5eflcCkvQy9iVF02W4SvD3YumQX6Ym9++d07PzC3qpuPsYIOfhxMxiWnEJqaVeLy7sx22VhY0qedL3w4Nee6dpQBYWaio7eeGlaWKzOw8zl+LIrCGB5GxyWTlqIvOfepyeInlL4ZE06phDbRaSM3I5kpo5e+jbGZqQvNg/dmwImNTiIzT3SjaWJlT08eV8JgkUjNySqqC2r5uZObkMeWZnly9GcePa/cBupugGj4umJmZEBOfxs2YJJrX9+fERd2MXHbWFrg52XE9Ir7E8mmZ2QTV9ESj0RIdn1rUpvKktC552uGy4mRnSV0/F71tl0LjScvKQ6GAun4umJmYcDUiscR/yyZKBYH+rsQlZ7LmkxG8OnMz52/EAeDjZoeXq27sxJXwRHLz8gnwdORSmO7fq7erHfkFhUVPGP9b3sZS9U8ZDTeik4uNJyoPZ5eMKPdziNsajlxYrvU3quOFlaWq6H1mdh7nr8cA4GhnRYCnE/EpGUTFl/zb7eJgjaOdFbV8XXj2sdYMf2MpABYqU2r7uWJjZU5Obj5nrkZR29eV+OQM0v95OtgsyIeTlyNLLd+mUQBarZa0zFwuh8aV41XQSTvwRbmf45b09HTs7e2ZtvomFlZ2ZV5/bnY6nw33Jy0tDTu7sq//YSUB/kOgvAP8B+HpYsvid4ZgZqqkto8z0+f+yartlXsu8fIO8B9E70ca8NozPbC2NMfd2Y5Br8zV6+YjytaUZ3rS85Fg3JxsycpR03vsLDKzSx9I/7Ar7wD/QS16ezDebnbU8HJi9/EQxn22wdhNeiAS4Fes8g7wH0Sn5rWZPqYHVuYqvNzsGT5tKccvVN51ZYwR4E9fU34B/qfDql+AL110xB0lp2XzwY87KSjUEBJVMVmx6uzEpZu8N/d3cnLVXA2LK5rVQZSPDbtOcfD0dVLSs7l2M67KDPJ+WH275m8sVKbEJOr6zAtRVZy7Hs373/9Bbl4+V2/Gk13Jp3sWlZ8E+OKO8vILi7rziPKXkJxBQnLxgbSifIREJBAS8fA+0alqzlyLMXYThCgXyWnZd+xbL+6uOs+icycXL17k9ddfZ+vW+xtXJAG+EEIIIYQQRhYXF8ePP/6oty02Npbo6Puf9tdoC10JIYQQQggBoFQoyu1VWcTExPDdd99RUFCg9zKEZPCFEEIIIYR4CHh4ePD+++8Xvb9w4QLXr1+/73okwBdCCCGEEEYlffChcePGHD16tOh9QUEB/v7+7Nx5/4v2SRcdIYQQQgghjODixYv07dsXAIVCgUp1ey2GHTt28OijjxpUr2TwhRBCCCGEUSkopwx+2VdZptRqNTt27MDDw6PYvuTkZAYMGGBQvRLgCyGEEEIIo1JSPt1KDKkzPz+fmzdv4uTkhJOT0z0dk5WVRUJCAj4+Ppia3l947enpyYcffqi3TalU4uzsTOfOne+rrlskwBdCCCGEEAL45ZdfGD9+PCqViuTkZAYPHsySJUswNzcvsfyRI0d45513OHz4ME5OTiQlJTFlyhS9gbJ34+TkxLPPPls2H+Af0gdfCCGEEEIYlUKhKLfXvbpy5QojR47kk08+ISYmhuvXr7N3717efffdUo85dOgQb7zxBqmpqYSFhbF161Y+//xzFi9eXBaXxWCSwRdCCCGEENXe4sWL8fb2ZuzYsQD4+voyfvx4Zs2axaeffopSWTwv/uqrr+q979ChA+3atWPXrl0899xz93Teq1ev0qJFixL3BQYGsmLFivv7IEiAL4QQQgghjOxhmCbzxIkTtG7dWm9bu3bteOeddwgNDaVWrVp3rUOtVnPt2jVatWp1z+e1t7fn8ccfL3FfSYNv74UE+EIIIYQQokpLT0/Xe29ubl6sX31SUhJ169bV2+bs7AxAYmLiPQX4b775JmlpaYwfP/6e2uXl5cUnn3zC6NGj76n8vZI++EIIIYQQwqiUivJ7ga67jb29fdHr008/LdYGU1NT1Gq13ra8vDwAzMzM7voZZs+ezXfffccvv/yCn5/fPX1uNze3Mg/uQTL4QgghhBCiiouIiMDOzq7ofUmz4vj6+hITE6O37dZ7X1/fO9b/3XffMW3aNNatW0evXr3KoMUPRjL4QgghhBDCqG71wS+PF4CdnZ3eq6QAv0uXLuzdu5fs7OyibVu2bCE4OBhXV1dAN9/9+fPnyc3NLSozd+5cXn/9ddauXWvwyrNlTQJ8IYQQQghR7Y0ePRo3NzeGDh3K3r17mTVrFosXL9ZbhOrYsWM0bNiQy5cvA7Bo0SJeeuklPvjgAwICAjh//jznz58nNDTUWB8DkC46QgghhBDCyBT/vMqj3ntlY2PD/v37ef/995kyZQrOzs6sW7eOAQMGFJWxtrYmODgYS0tLQBfw169fn59//pmff/65qFzLli1ZsmRJWX2M+yYBvhBCCCGEMCqlQoGyHObJvN86vb29+fHHH0vd37JlS86fP1/0/vvvvze4beVJuugIIYQQQghRhUgGXwghhBBCGNXDsNBVVSIZfCGEEEIIIaoQyeALIYQQQgjjKqcMfrmM3K0EJIMvhBBCCCFEFSIZfCGEEEIIYVRKhe5VHvVWR5LBF0IIIYQQogqRDL4QQgghhDAqhUKBohw64ZdHnZWBZPCFEEIIIYSoQiSD/xBwb9zH2E2oVsytHY3dhGojX51r7CZUK5r8HGM3oVrpV/pil6IcnFvxvLGbIMqRkvLJOlfXTHZ1/dxCCCGEEEJUSZLBF0IIIYQQRiUr2ZYtCfCFEEIIIYRRSYBftqSLjhBCCCGEEFWIZPCFEEIIIYRRyUJXZUsy+EIIIYQQQlQhksEXQgghhBBGJQtdlS3J4AshhBBCCFGFSAZfCCGEEEIYlcyiU7Ykgy+EEEIIIUQVIhl8IYQQQghhVErKJ+tcXTPZ1fVzCyGEEEIIUSVJBl8IIYQQQhiV9MEvW5LBF0IIIYQQogqRDL4QQgghhDAqyeCXLQnwhRBCCCGEUclCV2VLuugIIYQQQghRhUgGXwghhBBCGJUCUJZHF52yr7JSkAy+EEIIIYQQVYhk8IUQQgghhFEpKJ9su2TwhRBCCCGEEJWeZPCFEEIIIYRRyTSZZUsy+EIIIYQQQlQhksEXQgghhBBGpVSUzyw65VFnZSAZfCGEEEIIIaoQyeALIYQQQgijkpVsy5YE+EIIIYQQwqhkkG3Zki46QgghhBBCVCGSwRcl6tbAAR8ncw5eTed6bE6JZWq6WdAu0B4HK1P2XEzlfEQWg1u54OloTqFGy/wd0RXc6srJUqWgR7AN5qZK/rqYSXJWod5+EyW82Nmp6H1KViGrj6QBYGuhpGcDG0yUCnZeKH6sKM7TwYwu9e1Izylk25lUCjT6+32czBjQ3LHo/cWoHHZfzAAgyNOCNnVsyM7TsP1cGqnZcr3vpr6PFS1r2RISm8uBK2kllrG3MqFLsCPeTipC43PZeiqZFjVtaVPHDoD1RxOISlFXZLMrrfZ1rQlwMePojWyuxha/Zv2b2OLjpCp6v+JQCuk5t/8RNPO3pFmAJQv3JldIe4W4RRa6KluSwRfF/K+/LxN7eePtZM7S8YE08LUuVqZbAwdWv1Kf2u4WJdYxvodXeTezylg42odu9W1o6GvB8rG+WJrp/xyZKBWMbOtQ7DhbCyU/jvYmyNOcxr4W/DLRDztL+Sd9J252pqyZVIs6HuYMauHIrKf8ipXxdVLRo4Fdse3NAqxY9EIN3OxMaVXLml9eqo2ZSXX903Fv2gfas+D5ung5mjP9cT9Gd/YoVsbTQcXvrzegZ0NHlCU8S3+suTPeTuYV0dxKb1wXJ17r6YKXgxnznvahmb9lsTL9m9rhZG1S4vFutqY8/Ygjz3d0KnG/EKLykAy+0GNupmBIG1d6fHyGtOxCrsXmMKqDO2+svKFX7s2B/ry/Noytp/SzPOuOJgLwYjfPCmtzZdaihiUo4JUVMQB8OtSdXg1t2XAyXa9cgUZLdp6GlOxCtp/PBECjhYk/R5OUqcsiL33eh5quKk6H51bsh6hEBrVwZOuZND7bFINCAX/+L5AAVxVhCfqZzhy1lrx8DSHxeUXZ+8Z+Vqw/nsJXW2MB2P5GIK62pkSn5lf456gsnu3kzke/3WT72RS8HFX88mp9luyJ1SszrocXh66lM31VqN724zcyOH4jgyb+xRMMojiFAka2dWTQnDASMgo5F5nLU+0cOHmz+BPYjFwNqdmF7LuSWZS9VyhgSh8Xvtgaz28vBVRw64WQaTLLmqT7hB5PB3MS0vNJ+6frwdnwTGq46WfpnWxMcbExIyQ2h+e7etA12MEILa0a/J3NuBh1OyA/H5lHgIuZXplCjZY1R9KwsVDSp5EtK8f5ojJVkJWnIVutYXxXJz4Y6EZyViHnIyW4v5MAV3PORWQDoNXChagcAlz0s8MRyWoOXM3AztKEV3q58+kTPgBsOZ1KfW9L/tfPg8+H+XDgSoYE93cR4GbBufAsAKJT1GgBO0v97HFDP2v2XEzlmY7uDGntgpVK/iwZwsXGhGy1hoQM3W/3uchcAlxUxcptOpVOfqGWQE9z1kzwJ8hT9/1/vqMTvx5LIzFTup0JURVIBl/o+e+NrqKE3mumSgXqQg0v9/EhJC6Hl3p706KWLV9sjKiYRlYh/+2RUFKioVAD83fdflKyaIw3rWpYcuBadtE2jRY8HUxxtDYp+gMviiv+/S4uMjmfuTviAfj+rwR2vxWEtbkSL0cVtpZKNFrIL9QS4GqOhZmC3Hxtube7svrv70dJ19tUqWBke3fO3sykhastT3f0YMBX59HKZb0v93KtATadzij677AENYOa27PueBq9GtpiaqKgTS0rzEwUjO3ixILd0g9fVByZRadsVfpUyfjx49myZYuxm1FlRKfm4WZvhr2VLsvW0M+asAT9rHBChi5rOW3lDb7eEsn0VaG0D7Sv8LZWBWGJ+dT3vv2EJNjHnJtJ95YV9nQwJb9Qy/xdyXywIZ5L0Xl0DJTuDHcSmpBHQ18rQPejX9/bkrDEvFLLa/8VZT7R2ok1h5P5akssb6+NokCjpXkNud53EhqfQ0M/3TXyclShUChIz9G/Ab0Rn8Pqv+P5anMkLy25jpONKa52ZiVVJ+4gMbMAK5USV1vdb3cDH4t7/i3JL9Ty18XM8myeEKKCVfoM/u7duwkODi63+l9++WW6dOnCwIED7/mYgoICfvrpJ3bt2oWVlRVjxoyhdevW5dbGspSXr2Xt4QSWjA/ieEgGfZs6MX7RNQCeaOvK/ktpxKSqWbgrhnlj6nAqLJMOQfbsuZgK6Abf1vO2xkSpYFIvb86GZ7LvUskzZwg4HpqDAvhmpCdpORqa+Fny4e/xmJrAmI66DFptNxU9GtigVEADbwscrUw4FppDXQ8VXw3z5ERYDnZWSjoHWrPsQIqxP9JDbf2JFNa+XBtzMwV+TiquxuYSlqDGz1lFswArNpxIpWOQLQ19LVGZKOhUz5ZdF9LJytNw7EYWL/d0J8DVHAdLE4K9LbkSLV2i7uSnfXF8PKIGzWva0j7QnsW7dWNN2tS2BeDw9QyW7onjkxE1qO9thb+rBfHp+cSn5VPHw5JejZ3wc7FgYCsXAr2s+Hl/nDE/zkNNo4VVh1P5/hkfDodk07uRLf9bo7vejzW149TNHGLS8nmhkzMA7vamdK9vwwuLI7mRoGb+riQAVKYKnmrrKNl7UeFkoauyVekD/PK2b98+/PyKz7RRmtzcXHr27EliYiKvvfYaVlZW/O9//2PWrFk0a9asHFtadj7fGKGbJtPZnNHzr3CthGkyF+yM4UJENoFelszaGlksiJcpMu/dmMWR9Gpgi8pUwew/E8lRazEtYZILdaGWzWcy2Hkhk7wCLeci85j2ayydgqy5maRhzo6kogG3omRxaQU8MSeELvXtOBmazbazpd98ZuRq+PqPOPZd1nVpWH88hfDEPBr7WxGdrObb7XEkZhZUVNMrpX2X0xi/8Botatry2e/h7L9c/HqfvpnJy0uv80igHSFxufxxWj+w3HgiqaKaW+nN25XE+ahc/J3NmLAsiiuxJT+d0mq1XIrK5ftdScSm6X+HCzVaFu6T4F6Iyk6h1Rq3p+Phw4dZvnw58fHxtG7dmkmTJmFufnvQ2++//85vv/1GVlYWrVu3ZsKECVhb334sHhQUxIQJE7CysmL//v2Ym5vz3HPP0aZNG73z3K2e8ePH06dPHyIjI9m7dy+tWrUiJSWFOXPm4O3tTc2aNQFYu3YtFhYlTw0J8O677zJ//nwuXbqEi4sLABqNhqysLGxtbUs8JvC1o/d/4YTBzK0d715IlIl8tWS4K5Imv+Q1K0T5MLd2MHYTqpXNLxi7BdWHn3/dCjtXeno69vb2rDoQh5VN8SmKH1R2Zjoj2ruTlpaGnV3Z1/+wMmof/EWLFtGpUyfMzc0ZOHAgsbGxjB07tmj/zJkzefLJJ6lXrx79+vVj+fLldO/eHY1Gf2Wa9957jyNHjtC3b18sLCzo2LEjV69eva96du/ezdNPP83hw4cZMmQI3bp1o3///jg7O9O2bVvGjRvHuHHjMDO7c9/QJUuWMGrUqKLgHkCpVJYa3AshhBBCCFGWjJbBT09Px9vbm48++ohXX321aHtqaioODg6kpaXh4+PDN998w3PPPQdAXFwcAQEBLFq0iCeffBLQZfDr16/Pb7/9VlRHw4YNGTlyJNOmTbuvevz9/fnzzz/12tmkSROeeuopXn/99bt+puTkZJydnVm2bBnR0dGcOHECLy8vRo0aRYsWLUo9TjL4FUsy+BVHMvgVSzL4FUsy+BVLMvgVxxgZ/NUHyy+DP/wRyeBXmCNHjpCZmcmIESP0tjs4OABw7tw5MjMzGTBgQNE+d3d32rVrx6FDh/SOeeSRR/Te16xZk+jo6Puup3Pnzg/0mbKzddMWTps2jejoaIYOHYqpqSlt2rRh48aND1S3EEIIIURVdWuazPJ4VUdGG2Sbnq5bqdPRseRsakqKbjYQe3v96RcdHBxITtYfAPTvPvugGzF9q/vN/dTzoN1obt2ctGnThm+//RaAJ554gpiYGD799FMee+yxB6rfUAoFPN3Rnb5NnEnOzOe7P6O4EJlNE38bPh1RA4DFe2L59XCCUdpX1diYK5nU3ZnGfhZcic3j2+1JJGcVYmoCL3V3pkUNK67G5vHVH4lk5WnuXqG4o6b+Vozt6oq9lSl/XUhj4Z5Evf2T+7jTsqYNI+aGGKmFVcsjgXa80NUTUxMFvx5O4PfjukGwm/7XAFOlgrPhWcVWvhaGMTNR8HwnJ9rXtSY+vYD5uxK5GqtbdTnAxYzxXZ2p7WZOfqGW4fPDjdxaIcTDxGgZ/Bo1dIHl5cuX77j/+vXretuvX79etO9+zmNoPfczvZKNjQ21a9emVq1aettr1qxJfHz8PddT1vo0ccLR2oyPfrvJvktpzB+je/R2OTqbiYuv8ffVdBysZUKlsvJcR0eux+fx4YZ4tFqY2lc3HuPFTk7UdDXns83xaLQwvZ+rkVta+VmZK3m1tzsrDyXz9R+xDG7pRJ/Gt2/muwXbUagB/xJW9BT3z9HalBHt3Ji3PZrvd8Twen9fAj0tAXh5yXW+3RaFp6Nc67LybHtHCjVaPtkUx8XoXL4Z6Q2AnaWSn17040aCmmm/xvDGLzFGbqkQD04y+GXLaFFd06ZNadKkCdOnT+fXX3/FysqKvLw8Nm/ezODBg6lfvz5NmzZlxowZ/PTTTyiVSn777TcuXLjAypUr7/k8D1qPq6srcXH3Pvfyc889x9KlS3nnnXews7MjMzOT33//nfbt299zHWVt2+lktp7SPa1ISFczsZcXALn5Gm7E55KSVfpUf9bmSv73mB/1vK3YeS6FRn7WTF8VSm6+hhe7e9IxyIFsdSELdsZw+Fo6g1q54GhtSj1va/xczPlpXxybTyZhZqIosXxjf2te7uODh70KpQKGzLpQ6bPa3+1MQvPPyJajN7LpXt8GgG71bXhzbSxXYtWExCew83/FbzD9nc2Y0tsFW0sTfj2WRu8GNry8IgZnGxMmdXemnqc5Ecn5zN6eSFRKAW/3d+VsZC59GtliZqLgqz8SuRyTV2r5x5raMrSlPbYWJoQmqnltZeUODHLUGp79IbRo1dMjIZmYm+p+zT3szegebMenm6IZ1sapxOPb17VhbFc3svIK2X0pAxdbU+buiKe+twXju7nh4WDGqbBsZm+LI1ut4efxNVl9KIkn2zkTl5bPh+ujSc0uLLF8Tr6G13q780hdWyzMFGw6mcr3uyr3U7LU7AImLdElS0yUEJ+Wj5mpLk8UmpCLnVUJ87v+yzOddE8Sb8TnEJWs5kJkFrsvpNKrsSMj2rlhqTJh25lkluyJxd/FnHE9vIhOUdMxyJ4ToZl89rsuS11SeQcrU94e5EddT91KrJ/+Hl7p1+BYvD+Zwn9+DsOT8hnR2gGA/k3suBCVe8e56i3MFLzS04VGPhYcuJaNv7MZX29LJCmrgGfbO9IpyAZ1gZalB1I4cDWLXg1s8HNW4e9iRg0XFWuPp7H+RDomSkosH+Rpzss9XPB0MEWpUDB6YQTJWTJNrxAPC6MF+AqFgnXr1jFo0CBq1KhBvXr1CAkJ4b333gN0M88sWbKExx57jLp16+Lq6srZs2eZOXPmfS1s9aD1jBo1inHjxnHq1CksLCzuOk3m66+/zunTp6lVqxYNGzbkwoULBAUF8fXXX99zm8uaRgsd69nz5uP+ONuaMu0+Hp9P6uWNjYUJ768No4m/DZ2DHTA1UTC2kxfeTio+XBeGg7Up7w32Z/i3F3GwMmVQK1c+XBeGqYmCL0bW4s8zyYztXnL5l/v4sO10Midu6OYaz1ZX7uAedNd7fFcn+jexw9QEnlsUBYCLrSnRqbqbqWy1FnWBFlsLJRm5tz/z50948Oe5TA6HZDO4pT0+TrpZm74Y5sHuS1msOpxKPS8LPh7swbMLI3G3N6OzjSlzdiTRxM+CKb1deGFJVKnlp/R25bWV0SRnFaIuMOoMuWXi31MENPazpJabBZ9sjEGpgNf6uPPJ7zGUNo2Ag5UJnwzz4aP10cSn5/P6o55cj8vFxkLJzCf9+PqPWMKT1Dze3JEJ3d34amsstd3NqeVmwYwN0Yxs58xTjzizdH9iieX/PJdG2zo2vLM2khy1ttgKrpWRVqu76V/7WjDOtmZsOJbI+Yisezq2XV07nmjjxnu/hmGpUjJjWA3CN+fSxN+Gsd29+GxDODlqDa/09eFmQi4RSXn0buzE+2vD+GBdKu8O9qddXTuy8zQllvdzsUBdoGXKzyEUarTEpanL+WqUv1vBvUIB0x51Y/Z2XfczHyczwhLUzB3ljZ2lkj/OZrDycKresc91cMLT3owZm+IJ8jTn+Y5OfLsjkafaOlLXQ/ck0dZCybR+blyJycXO0oQBzeyYsTGeQo2Wz5/wZMf5TAa3sC+x/IudnTh4LYu/r2ej1WpJqwLfb2FcSoUCZTmk28ujzsrAqP0yatasyalTpzh79izJyck0btwYJ6fbmbbGjRtz/fp1jh8/TlZWFk2aNNGbfhLg+++/JyAgQG/b22+/jaWl5QPXA7oAv3Pnzly5coXc3Ny7TpNpZmbGmjVruHbtGjdv3sTPz4+6dStuNHppTtzIYOLiqwR6WfHmQH8OX0u/p0x5kwAb3vkljOuxOVyMzOaZTh4AtK1rh4e9iib+uuy0m72K2h66a/778USOXNcF7OGJubjYmpVafs3f8Yzu7EGbOnbsuZDKjfiqMevKmiNp7LyQSbf6Nrz9mCvjlkaTmVdYFNArFGBuptT738DUBDzsTVnyz2q0P+xO4vtnvTFVQhM/S1xsTBnaUtf9xMvBtOix49IDKVyMzuNqXB5PtXO8Y/m1x9J4qYcLIfFqtpxJL7rhqOweqWvDmE6uTFwWhrpAS48GdnQItGXlREuUCgW2Fiasfbk2Q7693VWvrqcFZ25ms+O8bjzQyr+TaFXLmnpelrjamfJqb3cATE0UxKXlA6DRwJwdcWi1sOlUKgNbOJZafs72OCKT83m9rycXInNYfbhqLNiUrdYwcfE1XO3MmDbAj7Z17Th0Nf2uxzXxt2HtkQSO/3Mzv+Oc7nvepo4tHvYqPhgaAICNpQmN/G2ISMrjfERWUR//vRdT8XE2x8natMTy648m0Km+Pa/39+VYSDqrDhqvW2RZMjNRMGOwO0dCstl4Sneds/I0dAy05tPN8SgVCt4b4E5IfB5HbtyeRamhrwXz/kriUnQel6LzGP5P9r91LStquqn4dKguUeVsY0Kgh24c25/nMjgcopss4mpcHm52pqWWX38inTGdnGjiZ8n+q1mEJeZX1CURQtwDo3e8VigUNG7cuNT9ZmZmtG3bttT9Jc18U9KUlIbUc4uvry++vr6l7i9JnTp1qFOnzn0dU14eberE31fTCYnLJSE9n2mP+eFgZUpW3t0zXLGpalrUtOF6bA4+zua42+tucKKS89h/KZXtZ1OKykanqGnsZ0NB4e2UqVYLSkXp5XPzNWw/m4KrnRmznq5FSlYBB65U7sfqT7Sy57fjaSRnFWJhlsWAZrppuc6E5/JoE1t+3JNCz2AbrsXlFXXlASgohAIN1HZTcT1eTbMA3Q1TgUa3AuuXfyQQmXz7j+itzPSt663V6jJ9dyo/Z2cSCoVugN6SMT70+TqMHHXlzuQPbOFI70b2TFp2s+gJ0N/XMhk5T/ekytpcycLna/D6Sv1BiPFp+dT1tMDaXHej1TxAt/BddIqaxIwC/rc6kux/bsDy8nX/v1CrLbrumn+ud2nl8wq0vLY8HDMTBR2DbJn1lB9PzKncA30b+1uj0cK58CzCEnIJT8rD38WcQ1fvfmxsmprO9R1YqtAFrQ19rTl7M5OoZDXnIrL4YmN40bVNySrAxdaMgn/9A9EU/ZaUXD4lq4DR869gba7kxW5evNzHh083VO6Bp3aWSr4a7snaY2lsP59ZtP3UzRzquptz6mYuCgVEpebj+J9xVHHpBTTzt+RcZC5utqZ4O+p+u6NT8rkUncum0+l6Zfs5mFHwr5yPVqO73qWVz1Fr2X81CydrEz4e7EF2noadF2+3UQhDVM9ce/kweoBfmZw7d47p06eXun/9+vV3zfAbQ1SymrWv6boj2VuZsPxAPFEpatzszVg2PghHa1M0WhjU0oUnZl8kI/f2o9Z526NZODaQsd29iErOIzmzgNx8DV9vieS70bV58hF3svJ05QfOvFBqG0or/93o2tRyt0RlpiS/QMPVmOxyvBIVo1CjZecbNchRazE3VfDJJl2/63m7kpn/tBfDWzugAF5eHl3s2JnbEvjpRR/SczWci8glN18XwXywIY4PB7lTqIH8Qi3X4/KYsjq21DaUVP6d3+JYNd4P0AUO5yLzKn1w72JjyidP+BCRpObXl3WD21cfSubng0mEJuQBYGuhRKPVEpaof0Mblqjmrwvp7H07iLTsQi5G5RCVkk9USj4rDibx45gA0nMKKdRo2XAilR93l9x/vrTyp29m8/4g3XgXJxtTftpf+TP4EUl5zHq6NgGuFliqlJy9mcmmE7rP9cMLdanlYYmzjSl/TGvIF5si2H0htejYzSeTGNjShb8/bEpmbiFRyXnk5mvYeS6FtnXtWPlSfVKy8tFqYcb6m8SnlZwR3nIqqcTydTysGNbWFYVCgYOVCf+rAjP5jOviTEMfS9ztzJjYTffUedi8mxy8lk2PBrbsnlYThQIuReWx65J+cL14XzILnvVhZDsH4tMLSM4qJDdfy7xdScx60ovHm9uT+U/3wGd+LP1GqLTy7w5wp76XBSpTBVqtlksxVePpqxBVhdEWuqqMkpKSis2d/299+/ZFqbz/iYkqaqErTwcVSZn5RX2vTZUK/Fz0pxgNS8jVyyqDLovjYmuGg7Upc0bXodcnZ4v2OVqb4mCl6/5xIz4X+38G2aVlFxadMyE9vygT99/y3o4qzM2U5Kg1xPTBcRoAADHUSURBVKRWTJ/ZiljoSmWqwNHKhISMgmLX09XWhMTMwlL7hluYKTA3VdCjgQ1N/Cx5e51ukLdCAW52pliaKcgr0BKTWoC7nSkp2bf70/s4mRVl7f9bPjatAH9n3Q1oeo6mQgbElfdCVyZK8HPW/w6nZBWQmn37sykU4OukIjyp5O+XvaUJBRotbw3w4lRYFr8e1T1lMjNR4G5vipmJkrRsXYDk53y7HgszXdefhIyCEsvnqDV4OKjQarUkZBRUyODxilroysXWjLx8jV4ywMdJhcr09u9fXJq6xM/saqc79ueJ9Zj883VC4nTfEUuVEvd/BtvHpalRF2hxtjUj9p/fBQcrUwo12qJz/re8ylSJo7Up+YVaYlPV5BeW/5+28l7oysnaBDtL/b8p/+4KY2uhxMxEUeq/ZYUCXGxMsFIpWTDah95fhRbtc7BS4vDP73V4Uj7W5kpMlJCarfvfzN3OlKSsAgoKSy7vZmeKxb9+WyoikpCFriqOMRa6WnckHutyWOgqKzOdwa3dqt1CVxLgPwQe5pVsPR1ULB4XiKmJAgdrU6avCmXnuZS7H/gQe5hXsu0RbMOk7s5YqRQUaGDc0ihuJlXevq0P+0q2r/V2p3sDO5ysTbkam8uLi8LIq8SDjx/2lWwXjg3Ex0mFm72KXw4lFM2KU1k9zCvZOlgpWfaCHyZK3Y3CjI3xbD2bYexmPRAJ8CuOBPiVn3TREXeUkJ7PxMXXKNBoiU5W6/WJFWXv0PVsrsXlkZuvy4qJ8rX8YBIbTqQWZehF+Xp/bRhmSgXx6SVn90XZSc/R8MqKKAo1EJOWX5SJF+JhpVToXuVRb3UkAb64owKNtsrMbFMZZOZpyJTAp8IkZBQUdbER5S8yKc/YTag2NFpkZhshqjEJ8IUQQgghhFGV16qz1XQafAnwhRBCCCGEcSkUChTlEI2XR52Vwf1P+SKEEEIIIYR4aEkGXwghhBBCGJWS8sk6V9dMdnX93EIIIYQQQlRJksEXQgghhBBGJYNsy5Zk8IUQQgghhKhCJIMvhBBCCCGMSjL4ZUsy+EIIIYQQQlQhksEXQgghhBBGpaCcMvhlX2WlIBl8IYQQQgghqhDJ4AshhBBCCKNSKnSv8qi3OpIAXwghhBBCGJXin/8rj3qrI+miI4QQQgghRBUiGXwhhBBCCGFUMk1m2ZIMvhBCCCGEEFWIZPCFEEIIIYRRySDbsiUZfCGEEEIIIaoQyeALIYQQQgijkj74ZUsy+EIIIYQQQlQhksEXQgghhBDGVU4Z/Go6Db5k8IUQQgghhKhKJIMvhBBCCCGM6mFayfbgwYOcOnUKV1dXHn30UWxsbMrlmPIkGXwhhBBCCCGAsWPH8thjj3H8+HG+/PJLgoODuXnzZpkfU94kgy+EEEIIIYzqYZgHf/v27fz4448cO3aM5s2bk5+fT4cOHZgyZQpr164ts2MqgmTwhRBCCCGEUd2aJrM8Xvfq119/pXnz5jRv3hwAMzMzxowZw6ZNm8jNzS2zYyqCZPCFEEIIIYRRZWWml2u96en69Zubm2Nubq637dKlSwQGBuptCwoKQq1WExISQnBwcLH6DTmmIkiA/xC4MquVsZsghBBCCFHhVCoVHh4e9G1do9zOYWNjg6+vr9629957j/fff19vW0ZGBg4ODnrbbr3PyMgosW5DjqkIEuALIYQQQgijsLCwIDQ0FLVaXW7n0Gq1KP7TV+e/2XsAKyurYpn+tLQ0AKytrUus25BjKoIE+EIIIYQQwmgsLCywsLAwdjMIDAzk2rVrettCQkIwMTGhZs2aZXZMRZBBtkIIIYQQotobMGAAhw8fLgrYtVotP/30Ez169CjKxkdERPDVV1+RkJBwz8cYg0Kr1WqNdnYhhBBCCCEeAlqtlkGDBnHixAlGjhzJqVOnOHr0KAcOHKB+/foA7Nmzhy5dunDq1CmaNGlyT8cYgwT4QgghhBBCABqNhvXr13Pq1CmcnZ0ZMWIEHh4eRftDQkKYP38+kydPxsvL656OMQYJ8IUQQgghhKhCpA++EKJaKCgoMHYTqo2jR4+SkpJi7GZUGwsXLuTmzZvGbka1ERMTw9mzZ43dDCHuSAJ8AcDq1avLdYoqcVth4f/bu/O4mvI+DuCf261b0Z4iSog0yL7FRIVQGIOQeCbr2Pd9hmceZoxlhmwPMzTWsVRCIiIiiSwjS2KsSUKlhep2636fP7w6z1xlxlIdHd/3X873d+49n840534793fOKYSfnx9atGiBli1bYteuXWJHkrTMzEz4+vrCwMAAZmZmCAkJETuSpMXFxaFTp07o3LkzN/nlwM/PDz/88IPYMT4JRIRJkybByckJO3fuRE5OjtiRGHsjbvAZtm7dCm9vb/Tp04eb/DJGRPD29sb27dvh4+MDW1tbDBo0CJGRkWJHk6S8vDw4OztDJpPh8OHDcHNzw6hRo8SOJWlPnz6Fra0tkpOTuckvY35+fli5ciUiIyNha2srdhzJW79+PaKionDt2jX8+OOPqFSpktiRGHsjbvAZfvrpJ6xYsQInTpzgJr+MBQYG4ubNm4iOjsbUqVMRFBSEli1bYtWqVWJHk6Rly5ahWbNm2LRpE1xcXLB06VLo6+vj+fPnSEtLEzueJDk4OCAvLw/Hjx/XaPILCgqQmpoqdjzJ8PPzg5+fn9DcP3z4EFOnToWnpydmzpyJp0+fih1Rcvz9/TFlyhQYGBgAAJRKJQICArBu3TqeIsU+Otzgf+LOnj0LIyMjTJ48GYcOHeImv4xt2LABK1euFJ6gJ5PJ0K9fP9y8eVPkZNIUGBiIxYsXC8vBwcHIzs6GhYUFLCwsMG7cOPB9BkqXjY0Nnj17BltbW40mv3///vj+++/FjicZ58+fR3Z2NtLT03Hu3Dk0bdoUd+/ehZWVFfz9/dG8eXPcv39f7JiSkpqaisLCQgBAYmIimjZtiqlTp+Lbb7/FZ599hrCwMJETMvYXxD5p+fn5FBcXJyxHRkZSpUqVyNPTk5RKpYjJpGnp0qWkVqs1atu2baO6deuKlEjaIiIihH8HBweTubk5bd++nTIyMsjf359kMhlt2rRJvIAS1bx5c7pw4QIREV25coXkcjlVqlSJUlJSRE4mHQUFBTRo0CAyMzMja2trCg4OFsaSkpLIxsaGPDw8REwoPT169CBnZ2ciInJ2dqbvvvuOiIhycnLoiy++IAsLC8rPzxczImMCPoP/idPR0UHjxo2F5Y4dOxY7k69UKjFt2jRkZGSIF1QiZsyYAZlMplHT1dXVOIuclJSE1atXl3c0SXJzcxP+XVBQgMOHD8PHxwfGxsYYNmwYevTogVOnTomYUJocHBxw7do1FBQUYMGCBejUqROMjIzg4eHBc/JLiVwux9atW9GtWzd89tln+PLLL4WxGjVq4JtvvkF4eLiICaVn4sSJiIqKwpIlS3Dv3j38+9//BgDo6+tj2bJlePbsGR4+fChySsZe0RY7APv4FDX5Hh4e6NOnD4gIhoaGMDQ0FDuaJMlkMqjVagCvmntXV1eMGzdO5FTS4+XlVaymVCpRr149EdJIm4ODAy5fvozQ0FDk5+fjwIEDuHPnDtzc3PD1118jICBA7IiSUNTknzlzptiYgYEBqlatKkIq6erSpQsmTJiAuXPnwsTEBHl5edDT0wMApKSkwNTUFNbW1iKnZOwVPoPPStSxY0cEBwfj4MGDMDQ0xO+//w65XC52LEmSyWQgIo3mfvLkyWLHkrzQ0FDExcXxXXXKQKNGjbB69Wrk5+cjMDAQCoUCn332GU6cOIHly5eLHU9S5HI5nJ2dNWoqlQqrV6/G+PHjRUolXX5+fhgzZgzS09Ph7e2Nu3fvIjo6GsOGDcPSpUuhUCjEjsgYAH6SLXsDpVKJvn37wsDAgJv7MrZ3716MHDkSpqam3NyXg6dPn8Lf3x9r165FQEAA2rVrJ3YkySEi/Prrrxg6dCg3POWooKAAsbGxmDJlCurWrYutW7fysbuM7Nu3DwsXLsSlS5dQo0YNLFy4EEOHDhU7FmMCbvBZiUJCQrBz505s376dPyDKWFhYGDw8PLBixQpu7stYXl4ehg4divr162Ps2LGwtLQUOxJjpebQoUPYtm0bBgwYgN69e4sd55NQWFjIn5Hso8QNvsSpVCro6OhArVbjyJEj6N69u9iRJKugoADa2q8ua3n+/Dlu3LjxVmeH1Wo1jh07Bnd397KOKClFv9sAcOXKFZiamsLGxkbkVNL11/199OhRdOzYkc/Ol6Gi/U1EOHToEDw9PcWOJFmFhYXQ0tKCTCZDdnY2/vjjD3To0EHsWIx9EJ6DL2Fr166Fu7s7srOzMXToUKxYsUK4hy8rfYMHD8bUqVORnp6OTp064ciRI2/1Oi0tLW7u31FOTg6aNm2KgIAAxMXFoWvXrrhy5YrYsSQrLi4ODg4OiI+Px9atWzFs2DAkJyeLHUuyNm/ejA4dOiAzMxPjxo3D4sWL+dkkZejrr7/G6NGjkZmZia5du2L//v1iR2Lsg/EZfAlLTEyEi4sLsrOz0bRpU4SEhEBfX/+tXx8fH48GDRqUYUJpOX78OHr06IHKlStj+PDhGg9Y+if5+fl4+PAh7OzsyjChtCxbtgxz586FqakpfvnlF43bBP6Tx48fQ19fHyYmJmUXUEJUKhX69++PkydPonLlyoiIiIC9vf1bvz4hIQH169cvdotYVrKUlBS4uLggLS0NDg4OCAsLE56e+jb42P1uzpw5g27dukFXVxcDBw58p9sUFxYW4s6dO+/0/wNj5YHP4EtYzZo10a5dOzx//hwqleqdnti5ePFi9O/fHyqVqgwTSkvbtm1Ru3ZtPH/+/J3OtuXn58PLywvLli0rw3TS06tXL1SuXFn4/X5bycnJcHFxwYEDB8ownbTo6Oigb9++yMrKgkqlQkFBwVu/9vTp0+jYsSPi4+PLMKG0VKtWDS4uLsLvdtFtdN+Gn58fvvzySyiVyjJMKC0tW7ZEvXr1hGP3235WFhYWwsfHBwsWLCjjhIy9O27wJYqIsGzZMgwePBjXr19HYmIiPDw8kJOT84+vXbx4MbZs2YJjx44Jc27Z38vIyMAvv/yCn3/+GeHh4Vi/fj2mTp36j68rau4VCgXWrFlTDkml4cqVK7hw4QL279+PRYsWwcfH563urZ6cnAxXV1eMHDkSQ4YMKYek0hAYGAhzc3PExcXByckJbm5ub9Wwnz59Gn379sXOnTvRsGHDckgqDcuXL8cXX3yBGzduICMjA+7u7sjKyvrH1/n5+WHt2rWIiIiArq5uOSSt+F68eIFVq1bhhx9+wMmTJ7Fz506MHj36H5v8oub+xYsX8Pf3L6e0jL2Dcn92LisXS5YsoQ4dOtDLly+JiOjBgwdUu3Zt6tixo1DLy8ujPXv2aLzuxx9/JAcHB3r8+HG5Z67IunfvTtOnTxeWIyIiSF9fn6ZMmSLUHj58SKdOnRKWlUol9erVi/r160cqlapc81ZkmZmZVLNmTQoODhZqS5cuJW1tbdq9e7dQi4qKosTERGH50aNHZG9vT8uWLSvXvBXd+fPnqXr16nTjxg0iIsrPz6fevXtT1apV6fr168J6QUFBpFQqheWoqCiytLSkiIiIcs9cka1du5Zat25NWVlZRET0+PFjql+/PrVp04YyMzOJiEilUlFAQIDG61asWEF169alhw8flnvmiszLy4vGjBkjLEdHR5OhoSGNGjWK1Go1ERE9efKEjh07JqxTUFBAAwYMIE9PT8rLyyv3zIy9DW7wJUatVtPFixepevXq9OjRI42xoibfycmJTp8+TV26dKGBAwcKBzFu7t9deno6HTp0iD777DMqLCzUGCtq8ocNG0YRERFkZ2dHq1atIiJu7t9XfHw8rVy5koYPH15srKjJX7FiBf3+++9kaWlJUVFRRMTN/fs6f/48+fr60qZNmzTqRU2+hYUFhYSE0DfffEP29vaUkpJCRNzcv68LFy5Q3bp16c8//9SoFzX5zZs3p6ioKOrVqxf16tWLCgoKiIib+/eRlZVFERERVKtWLcrPz9cYK2ryBw0aRJGRkeTg4ECLFi0iIm7uWcXBDb7EBAUFkUKhoMqVK2ucTSuSlJREzs7OpK2tTZMnTxY+IH799Vdu7t/D+PHjSU9Pjzp16lTi+OnTp6l27dpkbGxMv/32m1D39vbm5v4dqdVqatasGenp6dG///3vEtfZsGEDGRkZUZ06dSgmJoaIiHJycqh+/frc3L+jJ0+eUKVKlUhPT4+OHj1abFylUtG4ceNILpeTm5ubcOy4fv06N/fv4fDhwySXy0mhUNCLFy+KjT958oQ6d+5McrmcRo8eLTSlW7Zs4eb+PcyePZv09PSoTZs2JY5fuHCB7O3tycDAgNauXSvUR44cyc09qxC4wZegWbNmEQDhbHFJihr7Io8fP+bm/j28fPmSXF1dSUdHhy5evPjG9V4/u3/16lVu7t/DnTt3yMbGhmxtben58+clrvP67zYR0aVLl8o4mTSFhYWRrq4ueXp6Ct/0ve71/V1QUEBXrlwpj3iSs2DBAgIgnC0uyev7++nTp5SUlFTW0SQnLy+PPDw8SEtLi06fPv3G9V7f39evX+fmnlUIfJtMiZo9ezaWL1+OPXv2oGfPnmLHkbScnBz06NEDt27dwsmTJ/lWl2Xs7t27cHFxQZ06dXDo0CFUqlRJ7EiSdvjwYfTu3RtjxozBihUrxI4jeQsXLsR//vMf7NixA/379xc7jqQplUr06dMHFy5cwIkTJ/jWokxSuMGXgKtXr+LSpUuoV68enJychHtNz549GytWrEBQUBA3+aWEiBAZGYmkpCS0atUKDg4OAP7f5P/555+IjIzkJr+U5ObmIjw8HLm5uXB1dUXVqlUBcJNfVp48eYLjx4+jcuXK6NKli/DcjKImf+zYsVi+fLnIKaXjxo0bOH/+PGrVqgVnZ2fh2M1Nftk4deoUHjx4gGbNmqFRo0YA/t/kX7x4EcePH+cmn0mHqN8fsA+iVqtp/PjxZGRkRLVr1yYA1L59e0pOThbWmTVrFikUCgoJCRExqTRkZGRQx44dydLSkqysrAgADR8+XLjWoWi6jrW1Nd2+fVvktBXftWvXyNbWlmxtbcnY2Jj09PRo5cqVwnjRdJ2/3hmKvb+goCAyMjKievXqkY6ODllZWWncOaRous5f7wzF3t/MmTPJ0NCQ6tSpQwCoVatWdP/+fWF8wYIFJJfLNe4Mxd7PixcvyN3dnapUqUI1atQgAOTj40M5OTlE9P/pOq/fGYqxiowb/Aps5cqV1Lx5c0pLSyOiV/OM7e3tyd7enjIyMoT1Zs2aRT4+PmLFlAwvLy/y9fWl/Px8UqvVFBgYSEZGRtS3b19hnaIm/5dffhExacWXl5dHtWvXpnXr1gnLCxYsIC0tLVq+fLmw3p07d8jBwYHOnz8vVlRJuHHjBhkbG1NsbCwREaWmplK/fv1IoVBozE8OCwujhg0bUnp6ulhRJcHf358aNGhAT548IaJXf8w2atSIatWqRc+ePRPWW7BgAX3xxRcipZSOoUOHUv/+/YW58yEhIWRmZkbdunUTri0pavL/enxhrCLjBr8Ca9KkCW3evFmj9ujRI6pWrRpNmzZNo/76RZ7s3WRmZpKWllaxM/PHjx8nuVxOe/fuFWolXeTJ3k1YWBhVq1atWH3RokWkq6urcVEh7+8P980331C/fv00amq1mjw9PcnBwUHjAlve3x+uffv2tGbNGo3as2fPqGbNmvT1119r1Hl/fxilUkkKhYIuX76sUY+JiSGFQkHbtm0TaryvmZTwk2wrMKVSiYcPH2rUqlevjmnTpmH//v0adS0t/k/9IYoeF//6/nZ1dUWPHj009rdcLi/veJKjVCqRmZmJ7OxsjfqMGTNgYmKCw4cPCzXe3x+upGOJTCbDTz/9hISEBNy6dUuo8/7+cCXt7ypVqmDOnDnFjt28vz9MYWEhCgoKiu3vtm3bwsvLi4/dTLK466vAevfujZUrVxY7cFlbW0OhUIiUSprMzc3x+eefY86cOcjPz9cY4/1d+lxdXaFQKPDtt99q1LW1tVGtWjXe36Wsd+/eOHfuHAIDAzXq1tbWAMD7u5T17t0b69evx+3btzXqfCwpffr6+nB3d8e8efOQm5urMcb7m0ma2F8hsLcXHx9PJ0+eFC7qzMrKooYNG5KdnR1duHCBiIieP39Obdq0oaVLl4oZtcJTq9V05swZunjxojA94erVq2RoaEidO3cWLmROSEigKlWq/O19lNk/e/HiBUVERNDdu3eF2vbt20kmk9GUKVOEi+GCg4OpSpUqPAf8A6WkpFB4eLjGfhwzZgzp6emRv78/FRYWklqtphkzZlCHDh1ETCoNN2/epBMnTghzwHNycqh58+ZkY2ND0dHRRPTqeO7i4kLz588XM6oknDt3jmJjY4Vj961bt8jMzIycnZ0pMTGRiIju3r1LVlZWdOTIETGjMlZmuMH/CKWnp9Po0aMpOztbqE2dOpV0dHRIR0eHHBwcKD4+nohefVB36NCBAFDdunXJ2NiYRo4cyXPu38Hly5dp7ty5wnJ2djZ16tSJ9PX1CQB5enoKFy3HxsZSrVq1SFtbmxwcHMjQ0JAvqH1H27dvp99//11YvnHjBtnZ2VGlSpVIJpPR7NmzhQ/mrVu3kqGhIRkaGpK9vT1ZWloKDRH7Z2q1mmbPnk1Xr14VasHBwWRoaEj6+vpkYGBAAQEBRPTqOp3p06eTlpYWVatWjWrUqEFNmzbVuCsX+3tZWVk0evRojYewzZs3j3R0dEihUFCdOnWEueBpaWnk7u5OAMjOzo5MTU3Jx8eHH4D3Dm7cuEHTp08Xjhe5ubnk6elJenp6JJPJqFOnTsJFy3FxcVSvXj2Sy+XCsXvFihUipmesbHGD/xG6f/8+2drakrOzM2VnZ9OOHTuoVatWlJqaSmlpadSzZ0+ysLDQ+NCOiooif39/fmLnezh69Cjp6enRhAkTiIho7NixNGzYMFKpVBQfH08ODg7UqlUr4UM7Ly+PQkJCaPPmzfTgwQMRk1dMixcvJrlcLjT5jRs3Fv5ICg0NJSMjIxo2bJjwoZ2WlkY7duyg3bt3U2Zmpmi5K6L8/HyN40VSUhJZWlrShQsXSKVS0aJFi0gul9Nvv/0mvOb27du0adMmOnjwIF90+I6Sk5OpXr16wvEiJCSEHB0dKSUlhTIzM8nLy4tMTEyEb1yJXl3suXHjRuEORuztnT59mgwMDITjxcyZM8nb25uUSiXdvn2bmjRpQo6OjkKTn5+fTwcPHqRNmzZpfFvImBRxg/+RunfvntDke3h40IkTJ4QxlUpFffv2Ldbks/cXHh4uNPm2trYa91VPSUmhBg0aaDT57MMUNZbff/89tWnTRmPs7NmzxZp89v6USqXQ5E+dOpVmzZqlMb527dpiTT57f48ePRKa/D59+tCBAweEscLCQhoyZEixJp+9v1OnTglNvr29vXDbaKJXJweaNWum0eQz9qngBv8jVtTkA6Dw8HCNMW7yS19Rk6+trV3sTDE3+aVv0aJFBICaN29ebIyb/NJV1OQDoKlTpxYb5ya/dBU1+QAoODhYY4yb/NJX1OTLZDJKSUnRGOMmn32quMH/yBU1+U2aNNGYk0/0/yb/9fsms/dX1OT7+voWGytq8rdu3SpCMmkqavI3bNhQbOzs2bNUrVo1unbtmgjJpKeoya9UqVKxe4ITvWry69evL1wIyj5MUZNfv379YicFipp8fgBh6Slq8r28vIqdFChq8v/73/+KlI6x8icjIhLvHj7sbdy/fx8uLi6oWbMmDh06BAMDA2GsoKAAwKvbB7LScfToUfTq1QsjR47EqlWrNMZyc3Ohr68vUjJp+vHHHzFv3jxs3boVgwYN0hjj/V268vPz0a9fP5w9exbHjx9Ho0aNNMZ5f5eu5ORkuLi4wMTEBOHh4TAxMRHG1Go1CgoK+DaNpSgqKgoeHh7o378/Nm7cCJlMJozx7zb71HCD/5EJDg7GwYMHYWBggCFDhqBly5YA/r7JZ+9HqVRiw4YNiI2NRa1atTBy5EjY2NgA+Psmn72fJ0+eYM2aNUhMTESLFi0wYsQIVKpUCcDfN/ns/cTFxcHf3x8vX75E165d4eXlBZlM9o9NPns/oaGh2LdvH/T09ODj4wMnJycAf9/ks/ejUqnw22+/ITo6GjY2NhgxYgRq164N4O+bfMY+Jfygq4/I+PHjMWPGDOjq6iImJgatW7fG999/DwCoVasWIiMjkZiYCA8PD7x8+VLktBVbTk4OXF1dsXPnTujr62Pr1q1o0KABDh48CADo0qULQkJCsGHDBkycOFHktBVfQkICmjVrhvj4eBAR5s+fjyZNmuDOnTsAgDlz5mDhwoX417/+hZ07d4qctuILDg6Gq6srXr58idTUVPj4+AjHDYVCgaCgILRt2xZubm64fv262HErvFmzZmHs2LFQKBS4dOkS2rVrhzlz5gB49XTxyMhIZGRkwN3dHZmZmSKnrdiUSiW6desGf39/VK5cGbt370bDhg0RFBQEAHB2dsahQ4cQEBCAESNGiJyWMRGJOkGICQ4ePEg1a9akrKwsobZ69WqSyWQa85Pv3btHEyZM4Hslf6A5c+ZQr169hOW8vDz66quvSE9PT+NWo+Hh4Xyv5FLg5OREP//8s7CclJRELVq0IDs7O41rS5YtW0YRERFiRJSM58+fk4mJCcXExAi1mJgYsrCwoL59+wo1pVJJ48aNo4cPH4oRUzJOnjxJVatW1bh7i7+/P8nlco1jx6NHj2js2LF8jcMH+uGHH6hTp07Cs17y8/NpzJgxpKOjo/GMjFOnTtGiRYvEismY6HiKzkdiwoQJyMjIwLZt2zTqs2bNgr+/P5KTk3muZilq1qwZRo8eja+//lqoFRYWwtXVFQYGBjh06JCI6aQlMzMTJiYmSEhIQP369YX648eP4ejoiJkzZ2LmzJkiJpSWgwcPwtfXF8+ePdOonzhxAm5uboiOjka7du1ESic9c+bMQUJCAvbu3atRX7hwIZYuXYqUlBRUrlxZpHTS4+zsjN69e2PatGlCjYjQvXt35OTk4NSpUyKmY+zjwVN0PhKmpqaIiYmBWq3WqE+ePBlpaWnCVAZWOkxNTREdHa1Rk8vlmDBhAs6dOydSKmnS19eHrq5usf1tZWWFAQMG8P4uZaampkhNTcXNmzc16q6urmjcuDHv71JmamqK2NhYqFQqjfrEiRORk5OD+Ph4kZJJU0nHbplMhkmTJiE2NlakVIx9fLjB/0h89dVXSEpKEuZtFlEqlZDL5ahatapIyaRp1KhR2L59O/bv369RVyqVqF69ukippEmhUMDX1xezZs3CvXv3NMZ4f5c+JycnODo64quvvkJubq7GmEql4v1dynx8fJCZmYnJkydr1FUqFYgIVlZW4gSTqFGjRmHv3r3YsWOHRl2pVPK+ZuyvxJ0h9GlSq9W0dOlScnBwoGnTpgn1TZs2kUwmo8GDB9PVq1fp+vXr1LFjR5o0aZJ4YSUgMzOTRo0aRfXr16fff/9dqI8cOZK0tbXpP//5D927d48iIyPJ1taWAgICRExb8d28eZO6du1Kjo6OdP36dSIiysrKoubNm5OFhQVt27aNkpKSaMuWLWRubk63b98WOXHFFh4eTs2bN6cuXboI1/Bcv36dzM3NqWnTphQREUGJiYk0bdo0cnR0JKVSKXLiim3VqlXk4OBAY8eOFWq7d+8muVxO/fr1oz/++IMSEhKoa9euNHz4cBGTVnwvXrygCRMmkL29vca1aFOmTCEtLS2aO3cu3b59m6Kjo6lu3br8oDbG/oIbfBEsWLCAWrVqRUlJScXGQkJCqE6dOgSAFAoFTZs2jS+o/UBdu3YlX19fysnJ0air1WpasmQJmZiYEAAyMzPjD4gPlJqaStWrVy/xgTKZmZk0bNgw0tHRIQDk4OBA586dEyGldERHR5O5uTlFRUUVG7t16xa5ubkRAAJAXbp0ocePH4uQUjqWL19OjRs3pvv37xcbCw8PJ3t7ewJA2traNG7cOP5j6gP16dOHBgwYUOwhj0Sv/tAyNzcnAGRsbMwPsWLsNXyRbTl7+fIlLCwsEBMTgyZNmpS4DhHh0aNHMDU15YuzPlBUVBT69OmDpKQk6OrqlriOSqVCcnIyatSowQ8M+0ALFy5EbGwsDhw48MZ1Xrx4gYyMDFhbW5djMmnq2rUr3NzcMGvWrDeuk5qaCgCoUqVKecWSJJVKBUtLS4SGhqJ9+/ZvXO/Ro0cwMjKCoaFhOaaTnj/++AMdOnT424uUCwoKkJycjGrVqvFNKBh7Dc/BL2eJiYnIzc1FzZo1i409ffoUWVlZkMlksLa25ua+FCQkJMDS0rLE5v7OnTsgIujo6MDW1pab+1KQkJBQ4u82APz5558AAAMDA27uS8nb7O8qVapwc18KUlJSkJGRUeL+fv78OdLS0gAANWrU4Oa+FCQkJMDMzKzEz8G7d+9CrVZDW1sbNWvW5OaesRJwg1/ObGxsoKenV+LDfE6ePIk1a9aIkEq67O3tcePGDcTFxRUbW7VqFaKiokRIJV329vYIDQ3Fixcvio0NGjQIBQUFIqSSLnt7+xKPJVlZWfyQn1JWtWpVGBkZlbi/z549i59//lmEVNJlb2+PxMREnDlzptiYv78/Dh8+LEIqxioObvDLmYGBASZOnIjp06cjIiJCqOfm5mLx4sVo27atiOmkp0OHDmjXrh369euHu3fvCvV79+4hMDAQjo6OIqaTntGjR+PFixcYNGiQxtOWg4ODoaOjw9+SlLLZs2cjNDQU3333Hf462/K7775DmzZtREwmPQqFAtOnT8e8efMQGhoq1JVKJRYtWsTH7lLWokULuLu7w9vbW+OWr8nJydi8eTOaNm0qXjjGKgCegy+CgoIC+Pj4YM+ePejTpw/s7OwQHByMzz//HP7+/mLHk5wnT57A3d0dDx48gLe3NxQKBbZv346lS5di+PDhYseTnOjoaPTs2ROmpqbo27cvUlJScPDgQRw7dgzNmjUTO57krF+/HuPHj0erVq3g5uaG2NhYPHr0CDExMTA2NhY7nqSo1WoMHz4cW7ZsQe/eveHg4ICQkBA0bNgQu3fvFjue5KSnp6Nbt264ceMGBg4cCENDQ2zfvh3ffvstJk6cKHY8xj5q3OCLKCgoCHv27IFarcYXX3wBb29vyGQysWNJUl5eHn755RecPHkSJiYmGD58+N9eKMc+THJyMlavXo34+HjY2dlh0qRJsLW1FTuWZP3xxx/49ddfkZycjLZt22L8+PE8D7wMhYSEICAgAEqlEj169MCQIUOgpcVfiJeF/Px8bNy4ERERETAwMICvry9cXV3FjsXYR48bfMYYY4wxxiSETzkwxhhjjDEmIdzgM8YYY4wxJiHc4DPGGGOMMSYh3OAzxhhjjDEmIdzgM8YYY4wxJiHc4DPGGGOMMSYh3OAzxhhjjDEmIdzgM8YYY4wxJiHc4DPGWCkJDAxEcnLyG5fLa7sV3d69e5GUlCR2DMYYq7C4wWeMSVZKSgp27dqFXbt2Yffu3Th+/DiePn1aZtsbMmQILl269Mbl0hAQEICUlJS/3a4YSsr1vkaOHImzZ8+WynsxxtiniBt8xphkXb58Gd7e3ti1axf27t2LefPmwdbWFqtWrSqX7ffv3x81atQo1fccNGgQLl++XObbeVcl5WKMMSYObbEDMMZYWVu/fj2qVasGAFiyZAmmTZsGLy8vWFlZCevk5uYiJiYGubm5aNy4MWxsbIQxIsLu3bvh5uaG3NxcXLt2DZaWlmjVqtXfbrdnz56oWrWqRi0vLw9nz55FTk4O2rZtCzMzM2HswIEDePnyJbS0tGBtbY1mzZpBX19fGN+/fz+ICCdPnkRGRgYqV66Mnj17lrid9PR0xMTEAACcnJw0tvPXnyc/Px9Xr16Fubk5Wrdu/bc/j1qtxvnz5/H06VM0aNAAdnZ2b8zVpUsX7Nu3D927d4exsbHwHsHBwWjdujWsra2F2sOHD3Hp0iXUrFkTjRs31tjm0aNHUbVq1WL1I0eOwMrKqlidMcYYN/iMsU9M9+7dMXv2bFy/fl1o8E+ePIkBAwagdu3aMDMzw5kzZzB+/HgsXLgQAFBYWAhvb29069YN8fHxaNCgAWJiYuDu7o7du3dDJpOVuK0hQ4YgKCgI1atXBwBERkZi4MCBMDU1Re3atTF+/HisWbMGHh4eAIDw8HA8e/YMarUaCQkJyMzMRGhoKBwdHQEAYWFhICLExMTg3r17sLCwQM+ePYttJyAgAMOGDUOTJk0AAHFxcdi0aRO8vLw0fh5PT0/cunUL9evXR0xMDDp06IDg4OASf5bMzEy4uroiOzsbDRs2REJCAlxcXLB+/foSc7Vq1Qre3t64evWqRoM/bNgwbNy4Ef369QMAbN26FaNGjUKbNm2Qm5sLPT09KJVKYf2IiAgcP34csbGxQu3Jkyfo0aMHDh069C7/6Rlj7NNBjDEmUWFhYQSAHj9+LNSCg4MJAMXFxRERUUZGBpmZmdHOnTuFde7evUuGhoZ04sQJIiJSqVQEgJo0aULZ2dlERHT79m2qXLky7dixQ3idrq4uHThwoMTltLQ0MjExoRkzZpBarSYioszMTDp+/Pgb80+YMIE6d+6sUZPL5RQWFqZR++t2UlNTycTEhH766SdhfPHixWRiYkKpqakaP0/37t0pPz+fiIhu3rxJWlpadObMmRKzbNy4kezs7IT1i/blm3I9fvyYANDVq1c13sfY2JgCAwOFrIaGhrRu3TphfNq0aQRAWOfPP/8kmUym8T5Lly4lW1tbKiwsLDErY4x96ngOPmNM8vbv349du3Zh2bJlGDt2LAYOHChM7di/fz+USiW0tbURGBiIwMBAnD9/Hra2tjhx4oTG+4wZMwYGBgYAADs7O/Tt2xcBAQFvnUGpVGLBggXCGX8jIyO4urpqrHfnzh2EhYVh165dMDIy0jhz/TYOHz6MgoICTJw4UahNmTIFKpUKR44c0Vh3xIgR0NHRAQDY29ujevXquHnzZonvq6+vj+zsbNy7d0+offnll++U7XVhYWGQy+UYOXKkUJs1a5bGOnXr1kXHjh3x22+/CbVNmzbB19cXWlr8EcYYYyXhKTqMMckLCwuDQqFAQkICiAjz588Xxu7fvw9tbW0EBQVpvKZhw4aoWbOmRq1WrVoay7Vr10ZISMhbZUhMTISNjQ309PRKHFer1Rg8eDBCQ0PRunVrmJmZIT09HVlZWVAqldDV1X2r7Tx48ADW1tZC4w4ACoUC1tbWePDggca6f52XDwC6urrIy8sr8X379++PqKgoNG/eHLVr10bnzp0xZswY2Nvbv1WukiQmJsLa2hpyuVyoWVhYoHLlyhrrjRgxAlOmTMGSJUtw4cIFJCQkwNfX9723yxhjUscNPmNM8v56ke3QoUPh6emJa9euoVKlSjAyMgIRYefOnW+cS1/k+fPnxZarVKnyVhlMTEyQlpb2xvHDhw/jwIEDuHPnDiwtLQEA+/btQ0REBIjorbYBAFWqVEF6enqxenp6+ltnLYm2tjbWrVsHPz8/nDt3Dhs3bkSLFi1w8+ZNYe7/XxWdXVer1UKNiDTm15ubmxfbp/n5+cjJydGo9e3bFxMmTMCBAwcQFhYGNze3Yn9sMcYY+z/+fpMx9knx8/NDVlYWli5dCgBwd3dHdnY2du7cqbGeUqks1pDv27dP+LdKpcKBAwfQvn37t9puly5dkJaWVuzC0GfPngF4dc9+MzMzobkHUOxbBQAwMDB441l2AGjfvj2ePXuG6OhooXby5EmkpqaiXbt2b5W1JEUP0tLV1UWHDh2wYcMGvHz5EteuXSsxV5UqVaCrq4vbt28LtbNnz2qs0759eyQnJ+PChQtCbe/evcX+oNHT08PgwYOxdu1aBAQEYPjw4e/9czDG2KeAz+Azxj4pxsbG+OabbzB//nyMGTMGDRo0wHfffYehQ4ciNjYWjo6OuHfvHvbs2YPNmzfD3NxceG1ERASGDh0KJycn7Nq1CyqVCpMmTXqr7TZo0ABz586Fl5cXJk2ahFq1auHYsWNo1aoVZsyYgc6dO2PixInw9fWFs7MzIiIiEBYWVux9WrZsiTVr1uDFixcwNjZGz549NcYbNmyI0aNH48svv8TMmTMBvLo16JgxY9CwYcP33m/79+/H5s2b0bt3b1hZWSEkJAQ2NjbCrTVLyuXj44OpU6fiyZMnyM7OxqZNmzSmDjVq1Aj/+te/0KtXL8yYMQO5ubn473//C4VCUWz7I0eOROPGjWFiYvLBc/8ZY0zq+Aw+Y0yyrKysMGDAAI17yQPAuHHj0K9fP+Es9/z583H8+HHI5XKcPn0alSpVwpEjR9CmTRuN123btg3NmjVDbGws2rZti3PnzsHExEQYf/2BU68v//DDD9i3bx+ys7Nx6dIlDBgwADNmzAAA1KxZE+fOnYOpqSlOnTqF5s2bIzw8HAMGDNCYo75t2zY4OTnhyJEjCA8PL3E7a9euxapVq5CQkICbN29izZo1WLNmjTCupaWFAQMGaHxbAAA9evRA3bp1S9yXY8aMwdq1a5GVlYWoqCi0bdsWFy5cEH7+knKtX78e06ZNw8WLF5Gbm4vw8HAMGTJE4xkDGzduxLx58xAXF4e8vDxERkbiq6++0lgHABwdHVGnTh0MGjTojdcxMMYYe0VG7zK5kzHGPkEFBQXQ0dHBiRMn4OLiInacT9KtW7fg4OCAS5cuoWnTpmLHYYyxjxpP0WGMMfbRKnrY17p16+Dh4cHNPWOMvQWeosMYY//gTVNaWNnLzs5GaGgoPv/8c2zZskXsOIwxViHwFB3GGGOMMcYkhM/gM8YYY4wxJiHc4DPGGGOMMSYh3OAzxhhjjDEmIdzgM8YYY4wxJiHc4DPGGGOMMSYh3OAzxhhjjDEmIdzgM8YYY4wxJiHc4DPGGGOMMSYh3OAzxhhjjDEmIf8DpwiJQFWQL+wAAAAASUVORK5CYII=", "text/plain": [ "
" ] @@ -241,26 +241,20 @@ } ], "source": [ - "smoother = differential_expression_replication(\n", - " collection,\n", - " results,\n", - " group=monocytes,\n", - " contrast=contrast,\n", - " null_proportion={},\n", - ")\n", - "pairs = replication.study_pairs\n", + "smoother = replication(studies, monocytes, contrast, null_proportion={})\n", + "pairs = replicated.study_pairs\n", "for name, pi1 in (\n", " (\"qvalue's smoother\", smoother.study_pairs[\"pi1\"]),\n", " (\"lambda 0.5\", pairs[\"pi1\"]),\n", "):\n", " low, high, mean = pi1.min(), pi1.max(), pi1.mean()\n", " print(f\"pi1 by {name}: {low:.2f} to {high:.2f}, mean {mean:.2f}\")\n", - "both = pairs[\"n_found_in_both\"] / pairs[\"n_found\"]\n", + "both = pairs[\"n_features_found_in_both\"] / pairs[\"n_features_found\"]\n", "signs = pairs[\"sign_agreement\"]\n", "print(f\"share of the genes found by both cohorts: mean {both.mean():.2f}\")\n", "print(f\"share with the same sign: {signs.min():.2f} to {signs.max():.2f}\")\n", "figure, ax = plt.subplots(figsize=(7.5, 5.8), layout=\"constrained\")\n", - "plot_study_agreement(replication, ax=ax)\n", + "plot_study_agreement(replicated, ax=ax)\n", "plt.show()" ] }, @@ -285,9 +279,11 @@ "replication result holds the latter. A small combined p-value says that some cohort\n", "changes the gene, not that the change replicates. {api}`partial_conjunction` tests\n", "instead whether at least u cohorts change it, in one direction [@benjamini2008]; the\n", - "replication result holds it for u = 2. The monocyte genes each finds at a false\n", - "discovery rate of 5%, with the random-effects meta-analysis (REML) of\n", - "[Combine studies](combine_studies.ipynb) for comparison:" + "replication result holds it for u = 2. Both take the p-values of every cohort as\n", + "{api}`StudyResults.matrix` lines them up, cohorts x genes, with NaN where a cohort did\n", + "not test a gene. The monocyte genes each finds at a false discovery rate of 5%, with\n", + "the random-effects meta-analysis (REML) of [Combine studies](combine_studies.ipynb)\n", + "for comparison:" ] }, { @@ -310,14 +306,12 @@ } ], "source": [ - "aligned = align_study_results(collection, results, monocytes, contrast)\n", - "shape = (collection.n_studies, collection.n_features)\n", - "p_values = aligned[\"p_value\"].reshape(shape)\n", - "log2_fc = aligned[\"log2_fold_change\"].reshape(shape)\n", + "p_values = studies.matrix(\"p_value\", monocytes, contrast)\n", + "log2_fc = studies.matrix(\"log2_fold_change\", monocytes, contrast)\n", "ids, found_ids = collection.features[\"feature_id\"], found[\"feature_id\"]\n", "fisher = combine_p_values(p_values, method=\"fisher\", directions=log2_fc)\n", "in_three = partial_conjunction(p_values, 3, directions=log2_fc)\n", - "meta = differential_expression_meta_analysis(results).table(monocytes, contrast)\n", + "meta = meta_analysis(studies).table(monocytes, contrast)\n", "methods = {\n", " \"Fisher\": (ids, adjust_p_values(fisher.p_value, \"bh\")),\n", " \"Stouffer, weighted\": (found_ids, found[\"adjusted_p_value\"]),\n", @@ -350,10 +344,9 @@ "gene's normalized ranks are summed over the cohorts, for genes up and, from the other\n", "end, for genes down. If the cohorts ranked the genes at random, the sum of a gene's\n", "ranks over every cohort would follow the Irwin-Hall distribution\n", - "[@irwin1927; @hall1927], which gives its p-value.\n", - "{api}`differential_expression_sumrank` computes it for one cell type and contrast, and\n", - "{api}`plot_sumrank` draws every gene's p-value, the genes in alphabetical order, as the\n", - "authors of SumRank draw it." + "[@irwin1927; @hall1927], which gives its p-value. {api}`sumrank` computes it for one\n", + "cell type and contrast, and {api}`plot_sumrank` draws every gene's p-value, the genes\n", + "in alphabetical order, as the authors of SumRank draw it." ] }, { @@ -388,18 +381,16 @@ } ], "source": [ - "sumrank = differential_expression_sumrank(\n", - " collection, results, group=monocytes, contrast=contrast\n", - ")\n", - "table = sumrank.table\n", + "ranks = sumrank(studies, monocytes, contrast)\n", + "table = ranks.features\n", "print(f\"{len(table):,} genes ranked, by all six cohorts\")\n", "row(\"adjusted p < 0.05\", columns)\n", "for direction in (\"up\", \"down\"):\n", " adjusted = table[f\"{direction}_adjusted_p_value\"]\n", " row(f\"genes {direction}\", by_truth(table[\"feature_id\"], adjusted))\n", "figure, axes = plt.subplots(2, 1, figsize=(8, 6.5), layout=\"constrained\")\n", - "plot_sumrank(sumrank, n_labels=4, ax=axes[0])\n", - "plot_sumrank(sumrank, direction=\"down\", n_labels=4, ax=axes[1])\n", + "plot_sumrank(ranks, top=4, ax=axes[0])\n", + "plot_sumrank(ranks, direction=\"down\", top=4, ax=axes[1])\n", "plt.show()" ] }, @@ -421,11 +412,11 @@ "6, and 65% of the 17 studies of Alzheimer's disease and 55% of the 16 of COVID-19,\n", "proportions it chose by cross-validation [@nakatsuka2025]. A sum of the best ranks is\n", "smaller than a sum of uniform ranks, so its Irwin-Hall p-values are too small, as\n", - "{api}`differential_expression_sumrank` warns. Its p-values come instead from\n", - "permutations: the case and control labels are permuted within each cohort, each\n", - "cohort's analysis is run again, and the statistics of every gene in every permutation\n", - "make one null distribution [@phipson2010]. Here the best 4 of the 6 cohorts are\n", - "summed, with 30 permutations." + "{api}`sumrank` warns. Its p-values come instead from permutations: the case and\n", + "control labels are permuted within each cohort, each cohort's analysis is run again,\n", + "and the statistics of every gene in every permutation make one null distribution\n", + "[@phipson2010]. Here the best 4 of the 6 cohorts are summed (`n_studies_summed=4`),\n", + "with 30 permutations." ] }, { @@ -459,27 +450,18 @@ "source": [ "with warnings.catch_warnings(record=True) as caught:\n", " warnings.simplefilter(\"always\")\n", - " irwin_hall = differential_expression_sumrank(\n", - " collection, results, monocytes, contrast, proportion_of_studies=4 / 6\n", - " )\n", + " irwin_hall = sumrank(studies, monocytes, contrast, n_studies_summed=4)\n", "print(textwrap.fill(f\"RuntimeWarning: {caught[0].message}\", 78), end=\"\\n\\n\")\n", - "permuted = differential_expression_sumrank(\n", - " collection,\n", - " results,\n", - " monocytes,\n", - " contrast,\n", - " proportion_of_studies=4 / 6,\n", - " n_permutations=30,\n", - " permute=\"condition\",\n", - " rng=1,\n", + "permuted = sumrank(\n", + " studies, monocytes, contrast, n_studies_summed=4, n_permutations=30, rng=1\n", ")\n", "row(\"up or down, adjusted p < 0.05\", columns)\n", "for name, found_by in (\n", - " (\"all 6 cohorts, Irwin-Hall\", sumrank),\n", + " (\"all 6 cohorts, Irwin-Hall\", ranks),\n", " (\"best 4, Irwin-Hall\", irwin_hall),\n", " (\"best 4, 30 permutations\", permuted),\n", "):\n", - " table = found_by.table\n", + " table = found_by.features\n", " up, down = table[\"up_adjusted_p_value\"], table[\"down_adjusted_p_value\"]\n", " row(name, by_truth(table[\"feature_id\"], np.minimum(up, down)))" ] @@ -502,9 +484,10 @@ "random-effects meta-analysis with the Knapp-Hartung adjustment, as the paper compares\n", "them, scores every donor of the held-out cohort by how high its profiles rank those\n", "genes, as UCell scores them [@andreatta2021], and reports the area under the ROC\n", - "curve (AUC) of its cases against its controls [@hanley1982]. {api}`plot_held_out_auc`\n", - "draws it for every cohort. The paper chose the proportion of studies summed by\n", - "cross-validation; the mean AUC for a few proportions comes first." + "curve (AUC) of its cases against its controls [@hanley1982], the two levels of the\n", + "condition that the contrast compares. {api}`plot_held_out_auc` draws it for every\n", + "cohort. The paper chose the proportion of studies summed by cross-validation; the mean\n", + "AUC for a few proportions comes first." ] }, { @@ -543,23 +526,14 @@ "lines = [\"proportion of the cohorts summed: mean AUC\"]\n", "for proportion in (1.0, 0.8, 0.6, 0.4):\n", " auc = held_out_prediction(\n", - " collection,\n", - " results,\n", - " contrast,\n", - " \"condition\",\n", - " \"case\",\n", - " \"control\",\n", - " n_genes=20,\n", - " proportion_of_studies=proportion,\n", + " studies, contrast, n_features=20, proportion_of_studies=proportion\n", " ).auc\n", - " sumrank_auc = np.nanmean(auc[\"auc\"][auc[\"method\"] == \"sumrank\"])\n", - " meta_auc = np.nanmean(auc[\"auc\"][auc[\"method\"] == \"meta_analysis\"])\n", + " sumrank_auc = np.nanmean(auc.filter(method=\"sumrank\")[\"auc\"])\n", + " meta_auc = np.nanmean(auc.filter(method=\"meta_analysis\")[\"auc\"])\n", " line = f\"SumRank {sumrank_auc:.2f}, meta-analysis {meta_auc:.2f}\"\n", " lines.append(f\" {proportion}: {line}\")\n", "print(\"\\n\".join(lines))\n", - "prediction = held_out_prediction(\n", - " collection, results, contrast, \"condition\", \"case\", \"control\", n_genes=20\n", - ")\n", + "prediction = held_out_prediction(studies, contrast, n_features=20)\n", "plot_held_out_auc(prediction)\n", "plt.show()" ] @@ -586,20 +560,21 @@ " gene rank high in most cohorts, and weighs every cohort alike.\n", "- **Which combination of p-values.** Fisher's method finds genes that any cohort\n", " changes; Stouffer's, weighted by the square roots of the cohorts' sizes, the default\n", - " of {api}`differential_expression_replication`, rewards evidence the cohorts share;\n", - " neither says that a change replicates, which {api}`partial_conjunction` tests\n", - " (`at_least`, and `conjunction_method` for Simes's, Fisher's or Stouffer's\n", - " combination).\n", + " of {api}`replication`, rewards evidence the cohorts share; neither says that a change\n", + " replicates, which {api}`partial_conjunction` tests (`at_least`, and\n", + " `conjunction_method` for Simes's, Fisher's or Stouffer's combination).\n", "- **π₁ on few genes.** π₀ is estimated at λ = 0.5, which few genes allow;\n", " `null_proportion` chooses qvalue's smoother (`{}`) or its bootstrap\n", " (`{\"method\": \"bootstrap\"}`) instead, for cohorts that find many genes.\n", "- **SumRank's proportion and p-values.** Summing every cohort, the Irwin-Hall p-values\n", - " serve; summing fewer needs permutations (`n_permutations` and `permute`), each of\n", - " which runs every cohort's analysis again, and designs with random effects cannot be\n", - " permuted. SumRank ranks only the genes that every cohort tested, so a cohort with a\n", - " short panel of genes or a strict expression filter shortens the list for all.\n", - "- **Held-out prediction.** `n_genes` sets how many genes up and down are chosen in each\n", - " cell type, and `groups` the cell types scored; an AUC below 0.5 says the genes\n", + " serve; summing fewer (`n_studies_summed`, or `proportion_of_studies`) needs\n", + " permutations (`n_permutations`), of the labels of the factor that the contrast\n", + " compares, or of the variable `permute` names. Each permutation runs every cohort's\n", + " analysis again, and designs with random effects cannot be permuted. SumRank ranks\n", + " only the genes that every cohort tested, so a cohort with a short panel of genes or\n", + " a strict expression filter shortens the list for all.\n", + "- **Held-out prediction.** `n_features` sets how many genes up and down are chosen in\n", + " each cell type, and `groups` the cell types scored; an AUC below 0.5 says the genes\n", " predict the wrong direction." ] } @@ -608,8 +583,8 @@ "datasets": [ "simulated_cohorts" ], - "executed_at": "2026-10-27T09:55:55+00:00", - "executed_code_sha256": "9bfed2842a4da7563afba831de632ce6218b6e2ad12310ed995b3f12cbeaaac3", + "executed_at": "2026-10-27T15:23:44+00:00", + "executed_code_sha256": "b01614ea664c39f2c57bbacd88649c2c3117f28848f5fd86b09a25c7da56c2cb", "executed_datasets": { "simulated_cohorts": { "simulated_cohort_1.cube.zarr": "sha256:e97a0a97353376a5ac3a5c1e34f7e5f6fad6b0ac07c09b738b808fba6ccedb9e", @@ -621,8 +596,8 @@ "simulated_cohorts_truth.npz": "sha256:6599ea11dddb183f630183846f7ece9991003a4cc77875e4e7822317f66346ec" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 26.7, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 10.0, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/guides/save_and_trace_cubes.ipynb b/docs/source/guides/save_and_trace_cubes.ipynb index e57942f..9ab3b61 100644 --- a/docs/source/guides/save_and_trace_cubes.ipynb +++ b/docs/source/guides/save_and_trace_cubes.ipynb @@ -39,7 +39,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n", + "CytePool 0.1.dev63+g8afa32afb\n", "Cube with 24 samples x 6 groups x 2,000 features\n", " layers: counts (uint32, lazy), detected (uint32, lazy), log_sum (float64,\n", " lazy), log_sum_sq (float64, lazy), linear_sum (float64, lazy)\n", @@ -70,9 +70,7 @@ "import zarr\n", "\n", "import cytepool\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.io import read_cube, write_cube\n", + "from cytepool import Design, limma_voom, read_cube, write_cube\n", "from cytepool.normalization import library_sizes\n", "\n", "\n", @@ -133,8 +131,8 @@ " samples: \"donor_condition\"\n", " groups: \"cell type\"\n", "Built by from_cells (CytePool 0.1.dev42+g6991e63f8) from 19,151 cells:\n", - " samples: the rows of sample_table\n", - " groups: the rows of group_table\n", + " samples: the rows of the sample table given\n", + " groups: the rows of the group table given\n", " features: the ids of a table\n", " layers: counts, detected, log_sum, log_sum_sq, linear_sum\n", " normalized values: each cell scaled to a total of 10,000, with library sizes\n", @@ -226,9 +224,11 @@ "\n", "{api}`limma_voom` records in its result the fingerprint of the cube it read and, for\n", "every cell type, the samples it fitted, in order, those it left out with the reason,\n", - "and the library size and normalization factor of each sample. From these, the\n", - "profiles of a cell type can be read back by their ids and the values of the fit\n", - "recomputed." + "and the library size and normalization factor of each sample, which the result's table\n", + "of samples lists. From these, the profiles of a cell type can be read back by their\n", + "ids. The result also keeps the cube and the design it analysed, so\n", + "{api}`DifferentialExpressionResult.fit` computes a cell type's fit again: the values,\n", + "weights and moderated tests behind its table ({api}`GroupFit`)." ] }, { @@ -263,27 +263,24 @@ ")\n", "\n", "table = result.table(\"T cells\", \"stim_vs_ctrl\")\n", - "fit = next(group for group in record[\"groups\"] if group[\"group_id\"] == \"T cells\")\n", - "used = result.samples.filter(\n", - " (result.samples[\"group_id\"] == \"T cells\") & result.samples[\"used\"]\n", - ")\n", + "used = result.samples.filter(group_id=\"T cells\", used=True)\n", + "left_out = result.samples.filter(group_id=\"T cells\", used=False)\n", "profiles = opened.select(\n", - " samples=fit[\"samples\"], groups=\"T cells\", features=table[\"feature_id\"]\n", + " samples=used[\"sample_id\"], groups=\"T cells\", features=table[\"feature_id\"]\n", ")\n", "counts = profiles.layer(\"counts\", \"T cells\")\n", "print(\n", - " f\"T cells: {len(fit['samples'])} profiles fitted, left out: {fit['left_out'] or 'none'}\"\n", + " f\"T cells: {len(used)} profiles fitted, left out: {', '.join(left_out['sample_id']) or 'none'}\"\n", ")\n", "print(\n", " \"library sizes as fitted:\",\n", " np.array_equal(library_sizes(counts), used[\"library_size\"]),\n", ")\n", - "# The log-CPM values that voom computes, from the effective library sizes.\n", - "size = used[\"library_size\"] * used[\"norm_factor\"]\n", - "log_cpm = np.log2((counts + 0.5) / (size[:, np.newaxis] + 1) * 1e6)\n", + "# The fit of the T cells, computed again from the cube and design of the result.\n", + "fit = result.fit(\"T cells\")\n", "print(\n", " \"average log2 CPM as in the table:\",\n", - " np.allclose(log_cpm.mean(axis=0), table[\"average_log2_cpm\"], rtol=1e-12),\n", + " np.allclose(fit.log2_cpm.mean(axis=0), table[\"average_log2_cpm\"], rtol=1e-12),\n", ")" ] }, @@ -292,10 +289,10 @@ "id": "e11f1ba3", "metadata": {}, "source": [ - "The profiles read back give the library sizes that entered the fit, and their log-CPM\n", - "values the average expression of every gene in the table. They can be drawn too: here\n", - "the gene with the smallest p-value, in every sample that entered its test, the two\n", - "samples of each donor joined by a line." + "The profiles read back give the library sizes that entered the fit, and the fit\n", + "computed again gives the average expression of every gene in the table. Its values can\n", + "be drawn too: here the log2 CPM of the gene with the smallest p-value, in every sample\n", + "that entered its test, the two samples of each donor joined by a line." ] }, { @@ -321,10 +318,12 @@ ], "source": [ "gene = int(np.argmin(table[\"p_value\"]))\n", - "donor = profiles.samples[\"donor\"]\n", + "donor = fit.samples[\"donor\"]\n", "figure, ax = plt.subplots(figsize=(6, 4))\n", "for name in np.unique(donor):\n", - " ax.plot([\"ctrl\", \"stim\"], log_cpm[donor == name, gene], color=\"0.55\", marker=\"o\")\n", + " ax.plot(\n", + " [\"ctrl\", \"stim\"], fit.log2_cpm[donor == name, gene], color=\"0.55\", marker=\"o\"\n", + " )\n", "ax.set_xlim(-0.4, 1.4)\n", "ax.set_ylabel(\"log2 CPM, as voom computes it\")\n", "ax.set_title(\n", @@ -381,16 +380,16 @@ "datasets": [ "simulated_pbmc" ], - "executed_at": "2026-10-27T08:56:44+00:00", - "executed_code_sha256": "bc541ca76208660d90591cf1253aa41a17b7b9a4cf7fbad83ff44a07b86181d5", + "executed_at": "2026-10-27T15:23:47+00:00", + "executed_code_sha256": "a8691bd0ef96c1c887705507e18a45f1269fe2fa089b1b0bbedd6db74c4a6e7f", "executed_datasets": { "simulated_pbmc": { "simulated_pbmc.cube.zarr": "sha256:554a2b5b4188308ea900cf907fa87f42d12e0458e6d5dc11525e862f3ad8a726", "simulated_pbmc_cells.npz": "sha256:0d4c9154b87dbf8e9796232c2afec4a8340368fd0f5d386df4dd780c4e478129" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 2.0, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 2.2, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/installation.md b/docs/source/installation.md index 7fc1108..bf5a083 100644 --- a/docs/source/installation.md +++ b/docs/source/installation.md @@ -34,8 +34,9 @@ On Windows, activate the environment with `.venv\Scripts\activate` instead. ## Optional integrations -Converting cubes to and from AnnData and MuData (`cytepool.io.anndata`) needs anndata, -and mudata for MuData. Install them with CytePool's `anndata` or `mudata` extra: +Converting cubes to and from AnnData and MuData ({api}`to_anndata`, {api}`from_anndata`, +{api}`to_mudata`, {api}`from_mudata`) needs anndata, and mudata for MuData. Install them +with CytePool's `anndata` or `mudata` extra: ```bash uv pip install "cytepool[mudata] @ git+https://github.com/NygenAnalytics/CytePool.git" diff --git a/docs/source/quickstart.ipynb b/docs/source/quickstart.ipynb index 861eebc..d15622c 100644 --- a/docs/source/quickstart.ipynb +++ b/docs/source/quickstart.ipynb @@ -49,7 +49,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev61+gcd5dbe953\n", + "CytePool 0.1.dev63+g8afa32afb\n", "Cube with 24 samples x 6 groups x 2,000 features\n", " layers: counts (uint32), detected (uint32), log_sum (float64),\n", " log_sum_sq (float64), linear_sum (float64)\n", @@ -76,10 +76,7 @@ "import scipy.sparse\n", "\n", "import cytepool\n", - "from cytepool.cube import from_cells\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.io import read_cube, write_cube\n", + "from cytepool import Design, from_cells, limma_voom, read_cube, write_cube\n", "from cytepool.plotting import plot_cell_numbers, plot_significant_genes, plot_volcano\n", "\n", "cells = np.load(\"simulated_pbmc_cells.npz\")\n", @@ -89,9 +86,9 @@ "sample_ids = np.unique(cells[\"cell_sample\"])\n", "cube = from_cells(\n", " counts,\n", - " samples=cells[\"cell_sample\"],\n", - " groups=cells[\"cell_type\"],\n", - " sample_table={\n", + " sample_labels=cells[\"cell_sample\"],\n", + " group_labels=cells[\"cell_type\"],\n", + " samples={\n", " \"sample_id\": sample_ids,\n", " \"donor\": [sample.split(\"_\")[0] for sample in sample_ids],\n", " \"condition\": [sample.split(\"_\")[1] for sample in sample_ids],\n", @@ -317,10 +314,10 @@ "Every result keeps a record of what produced it: the method and its settings, the\n", "versions of CytePool, numpy, numba and Python, the design, and the cube, named by its\n", "fingerprint, a digest of every value it holds. For every cell type the record lists the\n", - "profiles that entered the fit, and those left out with the reason. {api}`write_cube`\n", - "saves the cube and {api}`read_cube` opens it, on this machine or another, without\n", - "reading its counts; its fingerprint then says whether it is the cube the result was\n", - "computed from." + "profiles that entered the fit, and those left out with the reason, as the result's\n", + "table of samples does. {api}`write_cube` saves the cube and {api}`read_cube` opens it,\n", + "on this machine or another, without reading its counts; its fingerprint then says\n", + "whether it is the cube the result was computed from." ] }, { @@ -354,11 +351,10 @@ " \"the saved file holds that cube:\",\n", " opened.fingerprint() == record[\"cube\"][\"fingerprint\"],\n", ")\n", - "dendritic = next(\n", - " fit for fit in record[\"groups\"] if fit[\"group_id\"] == \"Dendritic cells\"\n", - ")\n", - "print(f\"Dendritic cells: {len(dendritic['samples'])} profiles fitted, left out:\")\n", - "for sample, reason in dendritic[\"left_out\"].items():\n", + "fitted = result.samples.filter(group_id=\"Dendritic cells\", used=True)\n", + "left_out = result.samples.filter(group_id=\"Dendritic cells\", used=False)\n", + "print(f\"Dendritic cells: {len(fitted)} profiles fitted, left out:\")\n", + "for sample, reason in zip(left_out[\"sample_id\"], left_out[\"reason\"]):\n", " print(f\" {sample}: {reason}\")" ] }, @@ -386,16 +382,16 @@ "datasets": [ "simulated_pbmc" ], - "executed_at": "2026-10-27T09:54:43+00:00", - "executed_code_sha256": "c45a47a9acccc6128493bc7f749914d4619686b9fe4c7d9948ad08dba4a141a5", + "executed_at": "2026-10-27T15:23:52+00:00", + "executed_code_sha256": "0ded631f07098d277adda0deb810874bcd1d2153bc220c1ee0cae53624f4577c", "executed_datasets": { "simulated_pbmc": { "simulated_pbmc.cube.zarr": "sha256:554a2b5b4188308ea900cf907fa87f42d12e0458e6d5dc11525e862f3ad8a726", "simulated_pbmc_cells.npz": "sha256:0d4c9154b87dbf8e9796232c2afec4a8340368fd0f5d386df4dd780c4e478129" } }, - "executed_with": "0.1.dev61+gcd5dbe953", - "execution_seconds": 10.7, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 4.2, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/docs/source/reference/api/activity.md b/docs/source/reference/api/activity.md index 515f611..e72154c 100644 --- a/docs/source/reference/api/activity.md +++ b/docs/source/reference/api/activity.md @@ -6,17 +6,27 @@ myst: # Pathway and regulator activity -{func}`~cytepool.activity.ulm`, {func}`~cytepool.activity.mlm` and -{func}`~cytepool.activity.weighted_aggregate` score every source of a network, a -transcription factor or a pathway, in every profile: the samples of an expression -matrix or of a cell type of a cube, or the contrasts of a differential-expression -result, as decoupler's `mt.ulm`, `mt.mlm` and `mt.waggr` compute them. The activities, -an {class}`~cytepool.activity.Activities`, are profiles x sources, so that -{func}`~cytepool.stats.linear_models.fit_linear_model` tests them with a design built -from their profiles; {func}`~cytepool.plotting.plot_activities` draws them as a -heatmap and {func}`~cytepool.plotting.plot_top_activities` the top sources of a -profile as bars. The networks come from {mod}`cytepool.networks`. +{func}`~cytepool.ulm`, {func}`~cytepool.mlm` and {func}`~cytepool.weighted_aggregate` +score every source of a network, a transcription factor or a pathway, in every +profile: the samples of an expression matrix or of a cell type of a cube, or the +contrasts of a differential-expression result, as decoupler's `mt.ulm`, `mt.mlm` and +`mt.waggr` compute them. The activities, an {class}`~cytepool.Activities`, are profiles +x sources, so that {func}`~cytepool.limma` tests the activities of samples with the +design built for the cube; {func}`~cytepool.plotting.plot_activities` draws them as a +heatmap and {func}`~cytepool.plotting.plot_top_activities` the top sources of a profile +as bars. The networks are {class}`~cytepool.Network` objects, such as those of +{func}`~cytepool.collectri` and {func}`~cytepool.progeny`. ```{eval-rst} .. automodule:: cytepool.activity + :no-members: + :no-index: + +.. autofunction:: cytepool.ulm + +.. autofunction:: cytepool.mlm + +.. autofunction:: cytepool.weighted_aggregate + +.. autoclass:: cytepool.Activities ``` diff --git a/docs/source/reference/api/anndata.md b/docs/source/reference/api/anndata.md index d433f7a..52bdf1d 100644 --- a/docs/source/reference/api/anndata.md +++ b/docs/source/reference/api/anndata.md @@ -7,16 +7,26 @@ myst: # AnnData and MuData scverse keeps pseudobulk profiles in two layouts, and CytePool reads and writes both. -{func}`~cytepool.io.anndata.to_anndata` and {func}`~cytepool.io.anndata.from_anndata` -convert a cube to and from a long AnnData, with a row per sample and cell type, as -`scanpy.get.aggregate` and `decoupler.pp.pseudobulk` make it; -{func}`~cytepool.io.anndata.to_mudata` and {func}`~cytepool.io.anndata.from_mudata` -convert it to and from a MuData with a modality per cell type and the samples as its -rows, as LIANA's `adata_to_views` makes it. An import takes only sums and counts of -cells, so a cube made from aggregated profiles holds what one made from the cells -would hold, and a cube exported and read back has the same fingerprint. anndata and -mudata are optional: install CytePool's `anndata` or `mudata` extra. +{func}`~cytepool.to_anndata` and {func}`~cytepool.from_anndata` convert a cube to and +from a long AnnData, with a row per sample and cell type, as `scanpy.get.aggregate` +and `decoupler.pp.pseudobulk` make it; {func}`~cytepool.to_mudata` and +{func}`~cytepool.from_mudata` convert it to and from a MuData with a modality per cell +type and the samples as its rows, as LIANA's `adata_to_views` makes it. An import +takes only sums and counts of cells, so a cube made from aggregated profiles holds +what one made from the cells would hold, and a cube exported and read back has the +same fingerprint. anndata and mudata are optional: install CytePool's `anndata` or +`mudata` extra. ```{eval-rst} .. automodule:: cytepool.io.anndata + :no-members: + :no-index: + +.. autofunction:: cytepool.to_anndata + +.. autofunction:: cytepool.from_anndata + +.. autofunction:: cytepool.to_mudata + +.. autofunction:: cytepool.from_mudata ``` diff --git a/docs/source/reference/api/composition.md b/docs/source/reference/api/composition.md index 5363f16..8049348 100644 --- a/docs/source/reference/api/composition.md +++ b/docs/source/reference/api/composition.md @@ -6,17 +6,23 @@ myst: # Cell-type composition -{func}`~cytepool.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 {class}`~cytepool.design.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. +{func}`~cytepool.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 {class}`~cytepool.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, a {class}`~cytepool.CompositionResult`, 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. ```{eval-rst} .. automodule:: cytepool.composition + :no-members: + :no-index: + +.. autofunction:: cytepool.propeller + +.. autoclass:: cytepool.CompositionResult ``` diff --git a/docs/source/reference/api/cube_files.md b/docs/source/reference/api/cube_files.md index ec9964d..a22cec5 100644 --- a/docs/source/reference/api/cube_files.md +++ b/docs/source/reference/api/cube_files.md @@ -7,13 +7,15 @@ myst: # Cube files A cube file is a Zarr v3 group that any Zarr v3 reader can open. -{func}`~cytepool.io.write_cube` saves a cube and {func}`~cytepool.io.read_cube` opens -one, leaving its layers on disk until they are used; a read then opens only the chunks -that hold the selected groups and features. `cytepool.io.zarr` is the minimal Zarr v3 +{func}`~cytepool.write_cube` saves a cube and {func}`~cytepool.read_cube` opens one, +leaving its layers on disk until they are used; a read then opens only the chunks that +hold the selected groups and features. `cytepool.io.zarr` is the minimal Zarr v3 reader and writer behind them. ```{eval-rst} -.. automodule:: cytepool.io +.. autofunction:: cytepool.write_cube + +.. autofunction:: cytepool.read_cube ``` ```{eval-rst} diff --git a/docs/source/reference/api/cubes.md b/docs/source/reference/api/cubes.md index 0ef3606..b0cde68 100644 --- a/docs/source/reference/api/cubes.md +++ b/docs/source/reference/api/cubes.md @@ -9,20 +9,37 @@ myst: A cube holds additive statistics of the cells of each sample and group (such as a cell type), for every feature: counts, detected cells, and sums of normalized and log-normalized expression. Because the statistics add up, groups can be merged and -samples pooled without going back to the cells. {func}`~cytepool.cube.from_cells` -pools a cell-level count matrix in one pass, and {func}`~cytepool.cube.from_bulk` -wraps a samples x features count matrix as a cube with one group. Sample, group and -feature metadata are {class}`~cytepool.table.Table` objects. -{class}`~cytepool.cube.CubeCollection` aligns the cubes of several studies by sample, -gene and cell type, with what each study measured, for analyses of each study and for -one cube of them all. +samples pooled without going back to the cells. {func}`~cytepool.from_cells` pools a +cell-level count matrix in one pass, and {func}`~cytepool.from_bulk` wraps a samples x +features count matrix as a cube with one group. Sample, group and feature metadata are +{class}`~cytepool.Table` objects. {class}`~cytepool.CubeCollection` aligns the cubes of +several studies by sample, gene and cell type, with what each study measured, for +analyses of each study and for one cube of them all. ```{eval-rst} -.. automodule:: cytepool.cube +.. autoclass:: cytepool.Cube + +.. automodule:: cytepool.cube.pool + :no-members: + :no-index: + +.. autofunction:: cytepool.from_cells + +.. autofunction:: cytepool.from_bulk + +.. automodule:: cytepool.cube.collection + :no-members: + :no-index: + +.. autoclass:: cytepool.CubeCollection ``` ```{eval-rst} .. automodule:: cytepool.table + :no-members: + :no-index: + +.. autoclass:: cytepool.Table ``` ## Fingerprints and provenance @@ -31,8 +48,8 @@ A cube's fingerprint identifies its values, tables and cell numbers included, so result can name the cube it was computed from, and its provenance records how the cube was made: the caller's record of the source, the settings of the function that built it, and every operation that derived it, with what it kept or pooled and the -fingerprint of the cube it was applied to. {meth}`~cytepool.cube.Cube.history` reads -the record back in plain terms. +fingerprint of the cube it was applied to. {meth}`~cytepool.Cube.history` reads the +record back in plain terms. ```{eval-rst} .. automodule:: cytepool.cube.fingerprints diff --git a/docs/source/reference/api/designs.md b/docs/source/reference/api/designs.md index 3cbcde4..ef193b4 100644 --- a/docs/source/reference/api/designs.md +++ b/docs/source/reference/api/designs.md @@ -6,13 +6,20 @@ myst: # Designs and contrasts -A {class}`~cytepool.design.Design` joins a sample table, the fixed effects as an R -formula, optional random effects in lme4 notation and named contrasts. Its model -matrix has the columns and column names that R's `model.matrix` gives for the same -level order. Without explicit levels, factor levels are sorted by code point, as R -sorts them with `LC_COLLATE=C`; R's default follows the session's locale. -{meth}`~cytepool.design.Design.check` reports problems such as aliased columns before -anything is fitted. +A {class}`~cytepool.Design` joins a sample table, the fixed effects as an R formula, +optional random effects in lme4 notation and named contrasts. Its model matrix has the +columns and column names that R's `model.matrix` gives for the same level order. +Without explicit levels, factor levels are sorted by code point, as R sorts them with +`LC_COLLATE=C`; R's default follows the session's locale. +{meth}`~cytepool.Design.check` reports problems such as aliased columns before anything +is fitted. + +```{eval-rst} +.. autoclass:: cytepool.Design +``` + +The parts of a design are in `cytepool.design`: its model matrix, its contrasts, its +formula and random effects as they are read, and the report of its check. ```{eval-rst} .. automodule:: cytepool.design diff --git a/docs/source/reference/api/differential_expression.md b/docs/source/reference/api/differential_expression.md index 45cd9ff..5afacaf 100644 --- a/docs/source/reference/api/differential_expression.md +++ b/docs/source/reference/api/differential_expression.md @@ -6,23 +6,34 @@ myst: # Differential expression -{func}`~cytepool.differential_expression.limma_voom` and -{func}`~cytepool.differential_expression.limma_trend` test the contrasts of a -{class}`~cytepool.design.Design` in every group (cell type) of a cube, one group at a -time, as the limma and edgeR User's Guides do for RNA-seq counts, and -{func}`~cytepool.differential_expression.dream_voom` does so with the random -intercepts of the design, such as a donor's, as variancePartition's dream does. The design is -matched to the cube by `sample_id`; samples with too few cells are left out of a -group's fit, and a group whose design becomes rank deficient is fitted on its column -space, with the contrasts its samples can estimate. Options for pseudobulk data, each -off by default, add sample quality weights, prior weights from the number of cells, a -minimum total count per sample, and p-values adjusted across groups. The result holds -a table per group and contrast, a summary of every group, the samples used and left -out with the weights of each fit, and a record that traces every number to the -profiles of the cube that produced it. It keeps the cube and design it analysed, and -{meth}`~cytepool.differential_expression.DifferentialExpressionResult.fit` computes a -group's fit again, with the values, weights and moderated tests behind its tables. +{func}`~cytepool.limma_voom` and {func}`~cytepool.limma_trend` test the contrasts of a +{class}`~cytepool.Design` in every group (cell type) of a cube, one group at a time, as +the limma and edgeR User's Guides do for RNA-seq counts, and +{func}`~cytepool.dream_voom` does so with the random intercepts of the design, such as +a donor's, as variancePartition's dream does. The design is matched to the cube by +`sample_id`; samples with too few cells are left out of a group's fit, and a group +whose design becomes rank deficient is fitted on its column space, with the contrasts +its samples can estimate. Options for pseudobulk data, each off by default, add sample +quality weights, prior weights from the number of cells, a minimum total count per +sample, and p-values adjusted across groups. The result holds a table per group and +contrast, a summary of every group, the samples used and left out with the weights of +each fit, and a record that traces every number to the profiles of the cube that +produced it. It keeps the cube and design it analysed, and +{meth}`~cytepool.DifferentialExpressionResult.fit` computes a group's fit again, with +the values, weights and moderated tests behind its tables. ```{eval-rst} .. automodule:: cytepool.differential_expression + :no-members: + :no-index: + +.. autofunction:: cytepool.limma_voom + +.. autofunction:: cytepool.limma_trend + +.. autofunction:: cytepool.dream_voom + +.. autoclass:: cytepool.DifferentialExpressionResult + +.. autoclass:: cytepool.GroupFit ``` diff --git a/docs/source/reference/api/dream.md b/docs/source/reference/api/dream.md index 43c03f7..8c11af7 100644 --- a/docs/source/reference/api/dream.md +++ b/docs/source/reference/api/dream.md @@ -14,8 +14,8 @@ Satterthwaite or Kenward-Roger t-tests of {func}`~cytepool.stats.mixed_models.fixed_effect_tests` by empirical Bayes, as variancePartition's `eBayes` does for `dream` fits, adds `topTable`'s z-statistic, and tests the contrasts at once by the moderated F-statistic. -{func}`~cytepool.differential_expression.dream_voom` runs these steps on every cell -type of a cube, with dream's choice of the degrees of freedom. +{func}`~cytepool.dream_voom` runs these steps on every cell type of a cube, with +dream's choice of the degrees of freedom. ```{eval-rst} .. automodule:: cytepool.stats.dream diff --git a/docs/source/reference/api/gene_set_tests.md b/docs/source/reference/api/gene_set_tests.md index 694e630..cc94722 100644 --- a/docs/source/reference/api/gene_set_tests.md +++ b/docs/source/reference/api/gene_set_tests.md @@ -1,11 +1,22 @@ --- myst: html_meta: - description: Gene-set tests of one contrast of a linear model, limma's competitive test camera and its self-contained test fry, with voom weights, in CytePool. + description: Gene-set tests of one contrast in CytePool, limma's competitive test camera and its self-contained test fry, of a cell type of a differential-expression result or of values with a design, with voom weights. --- # Gene-set tests +{func}`~cytepool.camera` and {func}`~cytepool.fry` test gene sets on one contrast of a +linear model: of a group of a result of {func}`~cytepool.limma_voom` or +{func}`~cytepool.limma_trend`, whose fit they compute again, or of values with a design +and a contrast. + ```{eval-rst} -.. automodule:: cytepool.stats.gene_set_tests +.. automodule:: cytepool.gene_set_tests + :no-members: + :no-index: + +.. autofunction:: cytepool.camera + +.. autofunction:: cytepool.fry ``` diff --git a/docs/source/reference/api/gene_sets.md b/docs/source/reference/api/gene_sets.md index 3bd7947..2ab4277 100644 --- a/docs/source/reference/api/gene_sets.md +++ b/docs/source/reference/api/gene_sets.md @@ -1,19 +1,27 @@ --- myst: html_meta: - description: Gene-set collections in CytePool, read from and written to the GMT files of MSigDB, Reactome, WikiPathways and the Gene Ontology, with each collection's source, version and licence, matched to the genes of an analysis and filtered by size. + description: Gene-set collections in CytePool, read from and written to the GMT files of MSigDB, Reactome, WikiPathways and the Gene Ontology, with each collection's name, version and licence, matched to the genes of an analysis and filtered by size. --- # Gene-set files -{func}`~cytepool.gene_sets.read_gmt` reads a GMT file, one gene set per line, as -MSigDB, Reactome, WikiPathways and the GMT exports of the Gene Ontology write them, -into a {class}`~cytepool.gene_sets.GeneSets`: a mapping of set name to gene ids that -every gene-set function of CytePool takes as its `gene_sets`, with each set's -description and the source, version and licence of the collection, which you state -when you read it, and the name and SHA-256 digest of the file. CytePool bundles no -collection. {func}`~cytepool.gene_sets.write_gmt` writes a collection back. +{func}`~cytepool.read_gmt` reads a GMT file, one gene set per line, as MSigDB, +Reactome, WikiPathways and the GMT exports of the Gene Ontology write them, into a +{class}`~cytepool.GeneSets`: a mapping of set name to gene ids that every gene-set +function of CytePool takes as its `gene_sets`, with each set's description and the +name, version and licence of the collection, which you state when you read it, and the +name and SHA-256 digest of the file. CytePool bundles no collection. +{func}`~cytepool.write_gmt` writes a collection back. ```{eval-rst} .. automodule:: cytepool.gene_sets + :no-members: + :no-index: + +.. autoclass:: cytepool.GeneSets + +.. autofunction:: cytepool.read_gmt + +.. autofunction:: cytepool.write_gmt ``` diff --git a/docs/source/reference/api/gsea.md b/docs/source/reference/api/gsea.md index 01267d7..9802724 100644 --- a/docs/source/reference/api/gsea.md +++ b/docs/source/reference/api/gsea.md @@ -6,7 +6,7 @@ myst: # Gene set enrichment analysis -{func}`~cytepool.gsea.gsea` ranks genes by a statistic, of a group and contrast of a +{func}`~cytepool.gsea` ranks genes by a statistic, of a group and contrast of a differential-expression result, of a table of results or of a mapping of gene id to statistic, and asks of each gene set whether its genes gather at the top or at the bottom of the ranking, as fgsea's `fgsea` computes it: the enrichment score and its @@ -17,5 +17,9 @@ set, and {func}`~cytepool.plotting.plot_gsea_table` the top sets with their gene the ranking. ```{eval-rst} -.. automodule:: cytepool.gsea +.. automodule:: cytepool.gene_set_enrichment + :no-members: + :no-index: + +.. autofunction:: cytepool.gsea ``` diff --git a/docs/source/reference/api/held_out_prediction.md b/docs/source/reference/api/held_out_prediction.md index a9b1fc7..718cb97 100644 --- a/docs/source/reference/api/held_out_prediction.md +++ b/docs/source/reference/api/held_out_prediction.md @@ -6,15 +6,22 @@ myst: # Held-out prediction -{func}`~cytepool.held_out_prediction.held_out_prediction` leaves each study of a -{class}`~cytepool.cube.CubeCollection` out in turn, chooses genes up and down in each -cell type from the results of the other studies, by SumRank and by a random-effects +{func}`~cytepool.held_out_prediction` leaves each study of a +{class}`~cytepool.StudyResults` out in turn, chooses genes up and down in each cell +type from the results of the other studies, by SumRank and by a random-effects meta-analysis, scores every case and control of the held-out study by how high its -profiles rank those genes, as UCell scores them, and gives the area under the ROC curve -of cases against controls, by study and method, as Nakatsuka et al. (2025) compare -methods. {func}`~cytepool.plotting.plot_held_out_auc` draws the AUC of every held-out -study by method. +profiles rank those genes, as UCell scores them, and gives the area under the ROC +curve of cases against controls, by study and method, as Nakatsuka et al. (2025) +compare methods. The cases and controls are the two levels that the contrast compares. +{func}`~cytepool.plotting.plot_held_out_auc` draws the AUC of every held-out study by +method. ```{eval-rst} -.. automodule:: cytepool.held_out_prediction +.. automodule:: cytepool.held_out_studies + :no-members: + :no-index: + +.. autofunction:: cytepool.held_out_prediction + +.. autoclass:: cytepool.HeldOutPrediction ``` diff --git a/docs/source/reference/api/index.md b/docs/source/reference/api/index.md index 6b65424..cb10966 100644 --- a/docs/source/reference/api/index.md +++ b/docs/source/reference/api/index.md @@ -8,6 +8,12 @@ myst: Return to the [guides and validation](../). +`import cytepool` gives the functions and classes an analysis starts from, such as +`cytepool.limma_voom` and `cytepool.Cube`, and the pages below document them by topic. +The building blocks that work on arrays are in their modules: `cytepool.stats.voom` +and the other modules of `cytepool.stats`, `cytepool.normalization`, the parts of a +design in `cytepool.design`, and the figures in `cytepool.plotting`. + Arrays of counts and expression values are samples x features: rows are samples, columns are features such as genes. Where an argument accepts missing values, NaN marks them in numbers and None in text. numpy masked arrays, and array-likes that @@ -17,6 +23,14 @@ argument and says how to mark its missing values instead. The [quick start](../quickstart/) shows how the pieces fit together in a differential-expression analysis. +```{eval-rst} +.. py:data:: __version__ + :module: cytepool + + The version of CytePool that is installed, which every result's provenance + records. +``` + ```{toctree} :maxdepth: 1 diff --git a/docs/source/reference/api/meta_analysis.md b/docs/source/reference/api/meta_analysis.md index 07a9785..7d39601 100644 --- a/docs/source/reference/api/meta_analysis.md +++ b/docs/source/reference/api/meta_analysis.md @@ -1,29 +1,39 @@ --- myst: html_meta: - description: Meta-analysis of the effects of many genes across studies in CytePool, by fixed and random effects with the between-study variance by DerSimonian-Laird, REML or Paule-Mandel, Cochran's Q, I-squared, H-squared and the Knapp-Hartung adjustment, as metafor computes them, and of the differential expression of several cubes, with each study's results lined up gene by gene. + description: Meta-analysis of the differential expression of several studies in CytePool, gene by gene, by fixed and random effects with the between-study variance by DerSimonian-Laird, REML or Paule-Mandel, Cochran's Q, I-squared, H-squared and the Knapp-Hartung adjustment, as metafor computes them, from the studies' results or from arrays of effects and standard errors. --- # Meta-analysis -{func}`~cytepool.meta_analysis.meta_analysis` combines, for every gene, the effects of +{func}`~cytepool.meta_analysis` combines, for every gene, the log2 fold changes of several studies and their standard errors into an estimate of a common effect, as metafor's `rma.uni` does without moderators: the fixed-effect (inverse-variance) estimate, or a random-effects estimate with the between-study variance by DerSimonian-Laird, REML or Paule-Mandel, with Cochran's Q, I-squared, H-squared and -z-tests or the Knapp-Hartung adjustment. A gene measured in some studies only is -analysed over those. -{func}`~cytepool.meta_analysis.differential_expression_meta_analysis` does it for the -results of {func}`~cytepool.differential_expression.limma_voom` or -{func}`~cytepool.differential_expression.limma_trend` on several cubes, such as the -same cell types in several cohorts: it matches groups, contrasts and genes by their ids, +z-tests or the Knapp-Hartung adjustment. It takes the results of +{func}`~cytepool.limma_voom` or {func}`~cytepool.limma_trend` on several cubes, such as +the same cell types in several cohorts, kept together as a +{class}`~cytepool.StudyResults`: it matches groups, contrasts and genes by their ids, reads each table's log2 fold changes and moderated standard errors, and returns a table per cell type and contrast with p-values adjusted over its genes, the studies used and -left out with the reason, and the provenance of every study. -{func}`~cytepool.meta_analysis.align_study_results` lines up the results of the studies -of a {class}`~cytepool.cube.CubeCollection` gene by gene, with the reason a study has no -result for a gene. +left out with the reason, and the provenance of every study. A gene measured in some +studies only is analysed over those. {meth}`StudyResults.table +` lines up the studies' results gene by gene, with the +reason a study has no result for a gene, and +{func}`~cytepool.stats.meta_analysis.fit_meta_analysis` meta-analyses arrays of effects +and standard errors. ```{eval-rst} -.. automodule:: cytepool.meta_analysis +.. automodule:: cytepool.meta_analysis_of_studies + :no-members: + :no-index: + +.. autofunction:: cytepool.meta_analysis + +.. autoclass:: cytepool.MetaAnalysisResult +``` + +```{eval-rst} +.. automodule:: cytepool.stats.meta_analysis ``` diff --git a/docs/source/reference/api/networks.md b/docs/source/reference/api/networks.md index 6b5d12e..723e99c 100644 --- a/docs/source/reference/api/networks.md +++ b/docs/source/reference/api/networks.md @@ -6,16 +6,24 @@ myst: # Networks -A {class}`~cytepool.networks.Network` maps each source, a transcription factor or a -pathway, to its target genes with signed weights, and records its name, version, -licence, citation and the file it was read from. {func}`~cytepool.networks.collectri` -returns CollecTRI's regulons of human transcription factors, at the licence level you -choose, and {func}`~cytepool.networks.progeny` the pathway signatures of PROGENy for -human or mouse; both download their files on first use, check them against pinned -SHA-256 digests and keep them in a cache. The activity functions of -{mod}`cytepool.activity` take a network, or a table with the columns `source`, -`target` and `weight`. +A {class}`~cytepool.Network` maps each source, a transcription factor or a pathway, to +its target genes with signed weights, and records its name, version, licence, citation +and the file it was read from. {func}`~cytepool.collectri` returns CollecTRI's regulons +of human transcription factors, at the licence level you choose, and +{func}`~cytepool.progeny` the pathway signatures of PROGENy for human or mouse; both +download their files on first use, check them against pinned SHA-256 digests and keep +them in a cache. The activity functions, such as {func}`~cytepool.ulm`, take a network, +or a table with the columns `source`, `target` and `weight`, and +{meth}`~cytepool.Network.to_gene_sets` gives each source's targets as a gene set. ```{eval-rst} .. automodule:: cytepool.networks + :no-members: + :no-index: + +.. autoclass:: cytepool.Network + +.. autofunction:: cytepool.collectri + +.. autofunction:: cytepool.progeny ``` diff --git a/docs/source/reference/api/over_representation.md b/docs/source/reference/api/over_representation.md index bb4b681..3208ea9 100644 --- a/docs/source/reference/api/over_representation.md +++ b/docs/source/reference/api/over_representation.md @@ -6,15 +6,19 @@ myst: # Over-representation analysis -{func}`~cytepool.over_representation.over_representation` tests each gene set for -over-representation among the genes called significant, by the one-sided -hypergeometric test of limma's `kegga` and `goana`: for the genes that go up, those -that go down and those that change either way in a group and contrast of a -differential-expression result or in a table of results, or for a list of genes -against a universe. Its table gives each set's genes in the universe, the overlap, the -expected overlap, the p-value, the adjusted p-value and the genes of the overlap. -{func}`~cytepool.plotting.plot_over_representation` draws the top sets. +{func}`~cytepool.over_representation` tests each gene set for over-representation +among the genes called significant, by the one-sided hypergeometric test of limma's +`kegga` and `goana`: for the genes that go up, those that go down and those that change +either way in a group and contrast of a differential-expression result or in a table +of results, or for a list of genes against a universe. Its table gives each set's genes +in the universe, the overlap, the expected overlap, the p-value, the adjusted p-value +and the genes of the overlap. {func}`~cytepool.plotting.plot_over_representation` +draws the top sets. ```{eval-rst} -.. automodule:: cytepool.over_representation +.. automodule:: cytepool.over_representation_analysis + :no-members: + :no-index: + +.. autofunction:: cytepool.over_representation ``` diff --git a/docs/source/reference/api/pathway_scores.md b/docs/source/reference/api/pathway_scores.md index 60bf4b3..20d2800 100644 --- a/docs/source/reference/api/pathway_scores.md +++ b/docs/source/reference/api/pathway_scores.md @@ -6,16 +6,26 @@ myst: # Pathway scores -{func}`~cytepool.pathway_scores.gsva`, {func}`~cytepool.pathway_scores.ssgsea`, -{func}`~cytepool.pathway_scores.combined_z_scores` and -{func}`~cytepool.pathway_scores.plage` turn the expression values of every sample into -one score per gene set, as the four methods of the R package GSVA compute them, from a -samples x genes matrix or a cell type of a cube. The scores, a -{class}`~cytepool.pathway_scores.PathwayScores`, are samples x sets, so that -{func}`~cytepool.stats.linear_models.fit_linear_model` tests them with a design built -from their samples, and {func}`~cytepool.plotting.plot_pathway_scores` draws them as a +{func}`~cytepool.gsva`, {func}`~cytepool.ssgsea`, {func}`~cytepool.combined_z_scores` +and {func}`~cytepool.plage` turn the expression values of every sample into one score +per gene set, as the four methods of the R package GSVA compute them, from a samples x +genes matrix or a cell type of a cube. The scores, a {class}`~cytepool.PathwayScores`, +are profiles x sets, so that {func}`~cytepool.limma` tests them with the design built +for the cube, and {func}`~cytepool.plotting.plot_pathway_scores` draws them as a heatmap. ```{eval-rst} .. automodule:: cytepool.pathway_scores + :no-members: + :no-index: + +.. autofunction:: cytepool.gsva + +.. autofunction:: cytepool.ssgsea + +.. autofunction:: cytepool.combined_z_scores + +.. autofunction:: cytepool.plage + +.. autoclass:: cytepool.PathwayScores ``` diff --git a/docs/source/reference/api/principal_components.md b/docs/source/reference/api/principal_components.md index 3a6c56a..c54d604 100644 --- a/docs/source/reference/api/principal_components.md +++ b/docs/source/reference/api/principal_components.md @@ -6,15 +6,21 @@ myst: # Principal components -{func}`~cytepool.principal_components.principal_components` places samples on the -principal components of their most variable genes, as DESeq2's `plotPCA` does: the -`top` genes of largest variance over the samples, each centred, decomposed by singular -values. It takes a samples x genes matrix of log-expression values or a cell type of a -cube, whose samples are prepared as edgeR prepares counts for `plotMDS`. The result, a -{class}`~cytepool.principal_components.PrincipalComponents`, holds the samples' -coordinates, each component's share of the variance and the loadings of the genes, and +{func}`~cytepool.principal_components` places samples on the principal components of +their most variable genes, as DESeq2's `plotPCA` does: the `top` genes of largest +variance over the samples, each centred, decomposed by singular values. It takes a +samples x genes matrix of log-expression values or a cell type of a cube, whose samples +are prepared as edgeR prepares counts for `plotMDS`. The result, a +{class}`~cytepool.PrincipalComponents`, holds the samples' coordinates, each +component's share of the variance, the features used and their loadings, and {func}`~cytepool.plotting.plot_pca` draws it. ```{eval-rst} -.. automodule:: cytepool.principal_components +.. automodule:: cytepool.principal_component_analysis + :no-members: + :no-index: + +.. autofunction:: cytepool.principal_components + +.. autoclass:: cytepool.PrincipalComponents ``` diff --git a/docs/source/reference/api/quasi_likelihood.md b/docs/source/reference/api/quasi_likelihood.md index 082a53c..92bf582 100644 --- a/docs/source/reference/api/quasi_likelihood.md +++ b/docs/source/reference/api/quasi_likelihood.md @@ -13,12 +13,13 @@ dispersion is the Cox-Reid common dispersion of the most highly expressed genes ({func}`~cytepool.stats.quasi_likelihood.common_dispersion`), each gene's quasi-dispersion comes from deviances adjusted for small counts, and limma's prior for unequal degrees of freedom moderates them, towards a trend in the average log CPM -({func}`~cytepool.stats.quasi_likelihood.average_log_cpm`). The tests give edgeR's +({func}`~cytepool.stats.quasi_likelihood.average_log2_cpm`). The tests give edgeR's columns logFC, logCPM, F, PValue and FDR for coefficients or named contrasts of a design. ```python -from cytepool.design import Design, make_contrasts +from cytepool import Design +from cytepool.design import make_contrasts from cytepool.normalization import tmm_factors from cytepool.stats.quasi_likelihood import ( fit_quasi_likelihood, @@ -29,7 +30,7 @@ design = Design(samples, "~ 0 + group") fit = fit_quasi_likelihood(counts, design, norm_factors=tmm_factors(counts)) contrasts = make_contrasts({"b_vs_a": "groupB - groupA"}, design) test = quasi_likelihood_f_test(fit, contrasts=contrasts) -test.log_fold_change, test.p_value, test.adjusted_p_value +test.log2_fold_change, test.p_value, test.adjusted_p_value ``` ```{eval-rst} diff --git a/docs/source/reference/api/replication.md b/docs/source/reference/api/replication.md index a2d13c9..e5aa5e9 100644 --- a/docs/source/reference/api/replication.md +++ b/docs/source/reference/api/replication.md @@ -6,16 +6,22 @@ myst: # Replication across studies -{func}`~cytepool.replication.differential_expression_replication` reads one cell type -and contrast of the studies of a {class}`~cytepool.cube.CubeCollection`, lined up by -{func}`~cytepool.meta_analysis.align_study_results`, and gives for every gene its -combined p-value, its partial conjunction p-value of an effect in at least u studies in -one direction, the studies that find it and the heterogeneity of its log2 fold changes; -for every pair of studies, pi1 of one study's p-values among the genes the other finds, -and their sign agreement; and the number of genes found in at least k studies. +{func}`~cytepool.replication` reads one cell type and contrast of the studies' results, +a {class}`~cytepool.StudyResults`, lined up gene by gene by {meth}`StudyResults.table +`, and gives for every gene its combined p-value, its +partial conjunction p-value of an effect in at least u studies in one direction, the +studies that find it and the heterogeneity of its log2 fold changes; for every pair of +studies, pi1 of one study's p-values among the genes the other finds, and their sign +agreement; and the number of genes found in at least k studies. {func}`~cytepool.plotting.plot_study_agreement` and {func}`~cytepool.plotting.plot_replicated_genes` draw them. ```{eval-rst} -.. automodule:: cytepool.replication +.. automodule:: cytepool.replication_across_studies + :no-members: + :no-index: + +.. autofunction:: cytepool.replication + +.. autoclass:: cytepool.ReplicationResult ``` diff --git a/docs/source/reference/api/rotation_gene_set_tests.md b/docs/source/reference/api/rotation_gene_set_tests.md index dc077f7..bb3a89d 100644 --- a/docs/source/reference/api/rotation_gene_set_tests.md +++ b/docs/source/reference/api/rotation_gene_set_tests.md @@ -1,11 +1,25 @@ --- myst: html_meta: - description: Rotation gene-set tests of one contrast of a linear model in CytePool, limma's self-contained roast and mroast and its competitive, rank-based romer, with seeded rotations. + description: Rotation gene-set tests of one contrast in CytePool, limma's self-contained roast and mroast and its competitive, rank-based romer, of a cell type of a differential-expression result or of values with a design, with seeded rotations. --- # Rotation gene-set tests +{func}`~cytepool.roast`, {func}`~cytepool.mroast` and {func}`~cytepool.romer` test gene +sets by rotations of the residuals on one contrast of a linear model: of a group of a +result of {func}`~cytepool.limma_voom` or {func}`~cytepool.limma_trend`, whose fit they +compute again, or of values with a design and a contrast. romer takes sample weights +only, so of the two results it tests those of limma-trend. + ```{eval-rst} -.. automodule:: cytepool.stats.rotation_gene_set_tests +.. automodule:: cytepool.rotation_gene_set_tests + :no-members: + :no-index: + +.. autofunction:: cytepool.roast + +.. autofunction:: cytepool.mroast + +.. autofunction:: cytepool.romer ``` diff --git a/docs/source/reference/api/score_tests.md b/docs/source/reference/api/score_tests.md index 46ceff4..542a84f 100644 --- a/docs/source/reference/api/score_tests.md +++ b/docs/source/reference/api/score_tests.md @@ -8,15 +8,17 @@ myst: Pathway scores and the activities of regulators give every sample of a cell type one value per gene set or source, and differ between conditions as expression values do. -{func}`~cytepool.score_tests.limma` tests a contrast of a -{class}`~cytepool.design.Design` on them as GSVA's and decoupler's guides do with -limma: a linear model of every set's scores, moderated t-tests of the contrast and -Benjamini-Hochberg adjusted p-values, with the design built for the cube matched to -the scored samples by `sample_id`. It takes -{class}`~cytepool.pathway_scores.PathwayScores`, -{class}`~cytepool.activity.Activities` of samples, or an array of values from another -platform. +{func}`~cytepool.limma` tests a contrast of a {class}`~cytepool.Design` on them as +GSVA's and decoupler's guides do with limma: a linear model of every set's scores, +moderated t-tests of the contrast and Benjamini-Hochberg adjusted p-values, with the +design built for the cube matched to the scored samples by `sample_id`. It takes +{class}`~cytepool.PathwayScores`, {class}`~cytepool.Activities` of samples, or an array +of values from another platform. ```{eval-rst} .. automodule:: cytepool.score_tests + :no-members: + :no-index: + +.. autofunction:: cytepool.limma ``` diff --git a/docs/source/reference/api/study_results.md b/docs/source/reference/api/study_results.md index 7495a14..3a23765 100644 --- a/docs/source/reference/api/study_results.md +++ b/docs/source/reference/api/study_results.md @@ -8,16 +8,22 @@ myst: Studies of one question are compared or combined once each is analysed on the collection's axes: its aligned cube, with a design built from its own samples. -{func}`~cytepool.study_results.analyse_each_study` runs -{func}`~cytepool.differential_expression.limma_voom`, -{func}`~cytepool.differential_expression.limma_trend` or -{func}`~cytepool.differential_expression.dream_voom` on every study of a -{class}`~cytepool.cube.CubeCollection` that way, with one formula, its levels and -contrasts. {class}`~cytepool.study_results.StudyResults` keeps the results with their -collection: it lines them up gene by gene, with the reason a study has no value for a -gene, and gives one column of every study, such as the p-values, as a studies x genes -matrix, the form that combined p-values and SumRank take. +{func}`~cytepool.analyse_each_study` runs {func}`~cytepool.limma_voom`, +{func}`~cytepool.limma_trend` or {func}`~cytepool.dream_voom` on every study of a +{class}`~cytepool.CubeCollection` that way, with one formula, its levels and contrasts. +{class}`~cytepool.StudyResults` keeps the results with their collection: it lines them +up gene by gene, with the reason a study has no value for a gene, and gives one column +of every study, such as the p-values, as a studies x genes matrix, the form that +combined p-values and SumRank take. {func}`~cytepool.meta_analysis`, +{func}`~cytepool.replication`, {func}`~cytepool.sumrank` and +{func}`~cytepool.held_out_prediction` take it. ```{eval-rst} .. automodule:: cytepool.study_results + :no-members: + :no-index: + +.. autofunction:: cytepool.analyse_each_study + +.. autoclass:: cytepool.StudyResults ``` diff --git a/docs/source/reference/api/sumrank.md b/docs/source/reference/api/sumrank.md index ccafe62..cfbf614 100644 --- a/docs/source/reference/api/sumrank.md +++ b/docs/source/reference/api/sumrank.md @@ -6,18 +6,28 @@ myst: # SumRank -{func}`~cytepool.sumrank.sumrank` ranks the genes of every study by a signed statistic, -sums each gene's best normalized ranks across the studies and caps the sum at its mean, -for genes up and genes down, as SumRank does (Nakatsuka et al. 2025), with p-values from -the Irwin-Hall distribution and, given the statistics of permuted studies, from their -pooled null distribution. {func}`~cytepool.sumrank.differential_expression_sumrank` -does it for one cell type and contrast of the studies of a -{class}`~cytepool.cube.CubeCollection`, from each study's differential-expression -result, and runs the permutations itself: it permutes a variable of each study's design -among the samples of its fit, and re-runs the study's analysis on its aligned cube. -{func}`~cytepool.plotting.plot_sumrank` draws the p-value of every gene, the genes in -alphabetical order, as the authors of SumRank draw it. +{func}`~cytepool.sumrank` ranks the genes of every study by a signed statistic, sums +each gene's best normalized ranks across the studies and caps the sum at its mean, for +genes up and genes down, as SumRank does (Nakatsuka et al. 2025), for one cell type +and contrast of the studies' results, a {class}`~cytepool.StudyResults`, with p-values +from the Irwin-Hall distribution or from permutations: it permutes the variable that +the contrast compares among the samples of each study's fit, re-runs the study's +analysis on its aligned cube, and pools the null distribution. +{func}`~cytepool.stats.sumrank.fit_sumrank` does it for a matrix of statistics, with +the statistics of permuted studies. {func}`~cytepool.plotting.plot_sumrank` draws the +p-value of every gene, the genes in alphabetical order, as the authors of SumRank draw +it. ```{eval-rst} -.. automodule:: cytepool.sumrank +.. automodule:: cytepool.sumrank_across_studies + :no-members: + :no-index: + +.. autofunction:: cytepool.sumrank + +.. autoclass:: cytepool.SumRankResult +``` + +```{eval-rst} +.. automodule:: cytepool.stats.sumrank ``` diff --git a/docs/source/reference/api/variance_partitioning.md b/docs/source/reference/api/variance_partitioning.md index 143de9e..157b0e4 100644 --- a/docs/source/reference/api/variance_partitioning.md +++ b/docs/source/reference/api/variance_partitioning.md @@ -1,25 +1,34 @@ --- myst: html_meta: - description: Variance partitioning in CytePool, the fraction of every gene's variance that each variable of a design explains, as variancePartition's fitExtractVarPartModel computes it from linear mixed models fitted by maximum likelihood with precision weights, for a matrix of expression values or every cell type of a pseudobulk cube. + description: Variance partitioning in CytePool, the fraction of every gene's variance that each variable of a design explains, as variancePartition's fitExtractVarPartModel computes it from linear mixed models fitted by maximum likelihood with precision weights, for every cell type of a pseudobulk cube or a matrix of expression values. --- # Variance partitioning -{func}`~cytepool.variance_partitioning.variance_fractions` partitions the variance of -every gene among the variables of a {class}`~cytepool.design.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. {func}`~cytepool.variance_partitioning.variance_partitioning` does it for every -cell type of a cube, after the steps of -{func}`~cytepool.differential_expression.dream_voom` up to the precision weights, and -records the samples each fit used. {func}`~cytepool.plotting.plot_variance_fractions` -and {func}`~cytepool.plotting.plot_gene_variance_fractions` draw the fractions as +{func}`~cytepool.variance_partitioning` partitions the variance of every gene among the +variables of a {class}`~cytepool.Design` and the residuals, in every cell type of a +cube, as variancePartition's `fitExtractVarPartModel` does after the steps of +{func}`~cytepool.dream_voom` up to the precision weights: 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 voom's precision weights, and the fraction of a variable is its variance +component, or the variance of its fitted effect, over their sum. It records the samples +each fit used. {func}`~cytepool.stats.variance_fractions.variance_fractions` does it +for a matrix of expression values. {func}`~cytepool.plotting.plot_variance_fractions` +and {func}`~cytepool.plotting.plot_feature_variance_fractions` draw the fractions as variancePartition's `plotVarPart` and `plotPercentBars` do. ```{eval-rst} -.. automodule:: cytepool.variance_partitioning +.. automodule:: cytepool.variance_components + :no-members: + :no-index: + +.. autofunction:: cytepool.variance_partitioning + +.. autoclass:: cytepool.VariancePartitioningResult +``` + +```{eval-rst} +.. automodule:: cytepool.stats.variance_fractions ``` diff --git a/docs/source/reference/capabilities.md b/docs/source/reference/capabilities.md index 376de05..220d710 100644 --- a/docs/source/reference/capabilities.md +++ b/docs/source/reference/capabilities.md @@ -23,8 +23,8 @@ size; its sections give every check. | Reads of selected features from cube files | {api}`read_cube`, {api}`Cube.select` | zarr-python 3.4.1 | exact | [`cube_reads.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/cube_reads.py) | | Fingerprints of cubes | {api}`Cube.fingerprint`, {api}`Cube.digests` | the documented encoding, with zarr-python 3.4.1 | exact | [`cube_fingerprints.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/cube_fingerprints.py) | | AnnData and MuData layouts of scanpy, decoupler and LIANA | {api}`to_anndata`, {api}`from_anndata`, {api}`to_mudata`, {api}`from_mudata` | scanpy 1.12.4, decoupler 2.2.0, LIANA 1.10.0, anndata 0.13.4, mudata 0.4.1 | exact | [`anndata_conversion.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/anndata_conversion.py) | -| Several studies' cubes aligned by sample, gene and cell type, with what each study measured; stacked for a joint model; each study's results lined up gene by gene | {api}`CubeCollection`, {api}`align_study_results` | anndata 0.13.4, pandas 3.1.0, limma 3.62.1, edgeR 4.4.0 | exact | [`cube_collection.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/cube_collection.py) | -| TMM, upper-quartile and RLE factors; CPM and log-CPM; `filterByExpr` | {api}`tmm_factors`, {api}`upper_quartile_factors`, {api}`rle_factors`, {api}`cpm`, {api}`log_cpm`, {api}`filter_by_expression` | edgeR 4.4.0 | exact | [`normalization.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/normalization.py) | +| Several studies' cubes aligned by sample, gene and cell type, with what each study measured; stacked for a joint model; each study's results lined up gene by gene | {api}`CubeCollection`, {api}`StudyResults.table` | anndata 0.13.4, pandas 3.1.0, limma 3.62.1, edgeR 4.4.0 | exact | [`cube_collection.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/cube_collection.py) | +| TMM, upper-quartile and RLE factors; CPM and log-CPM; `filterByExpr` | {api}`tmm_factors`, {api}`upper_quartile_factors`, {api}`rle_factors`, {api}`cpm`, {api}`log2_cpm`, {api}`filter_by_expression` | edgeR 4.4.0 | exact | [`normalization.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/normalization.py) | | DESeq2's median-of-ratios size factors | {api}`median_ratio_size_factors` | DESeq2 1.46.0 | exact | [`normalization.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/normalization.py) | | Model matrices from R formulas, named contrasts, design checks | {api}`Design`, {api}`model_matrix`, {api}`make_contrasts`, {api}`Design.check` | R 4.4.3, limma 3.62.1, lme4 2.0.6 | exact | [`design.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/design.py) | | Linear models of every gene, contrasts (limma's `lmFit`, `contrasts.fit`) | {api}`fit_linear_model`, {api}`contrasts_fit` | limma 3.62.1 | exact | [`linear_models.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/linear_models.py) | @@ -33,7 +33,7 @@ size; its sections give every check. | voom precision weights, limma's span rule | {api}`voom`, {api}`choose_lowess_span` | limma 3.62.1, edgeR 4.4.0 | exact; close on rarefied counts | [`voom.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/voom.py) | | Sample quality weights (`arrayWeights`, `voomWithQualityWeights`) | {api}`sample_quality_weights`, {api}`voom_with_quality_weights` | limma 3.62.1, edgeR 4.4.0 | close | [`sample_quality_weights.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/sample_quality_weights.py) | | Negative binomial models of every gene (edgeR's `glmFit`), deviances | {api}`fit_negative_binomial_model`, {api}`negative_binomial_deviance`, {api}`negative_binomial_unit_deviance` | edgeR 4.4.0, mpmath 1.3.0 | close | [`negative_binomial_fits.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/negative_binomial_fits.py) | -| Quasi-likelihood F-tests (edgeR 4's `glmQLFit`, `glmQLFTest`) | {api}`fit_quasi_likelihood`, {api}`quasi_likelihood_f_test`, {api}`common_dispersion`, {api}`average_log_cpm` | edgeR 4.4.0, limma 3.62.1, mpmath 1.3.0 | close | [`quasi_likelihood.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/quasi_likelihood.py) | +| Quasi-likelihood F-tests (edgeR 4's `glmQLFit`, `glmQLFTest`) | {api}`fit_quasi_likelihood`, {api}`quasi_likelihood_f_test`, {api}`common_dispersion`, {api}`average_log2_cpm` | edgeR 4.4.0, limma 3.62.1, mpmath 1.3.0 | close | [`quasi_likelihood.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/quasi_likelihood.py) | | DESeq2's dispersions, Wald tests and likelihood ratio tests | {api}`fit_deseq2`, {api}`deseq2_dispersions`, {api}`deseq2_wald_test`, {api}`deseq2_likelihood_ratio_test` | DESeq2 1.46.0, PyDESeq2 0.5.4 | close | [`deseq2_tests.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/deseq2_tests.py) | | Empirical Bayes moderation, moderated t and F, B-statistics, TREAT | {api}`moderated_statistics`, {api}`treat`, {api}`squeeze_variances`, {api}`fit_f_distribution`, {api}`fit_f_distribution_robust`, {api}`fit_f_distribution_likelihood` | limma 3.62.1 | exact; close for the robust and likelihood priors | [`empirical_bayes.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/empirical_bayes.py) | | Gene-set tests camera and fry | {api}`camera`, {api}`fry` | limma 3.62.1 | exact; close with robust moderation; 1e-3 for fry's default standardization, which differs on purpose | [`gene_set_tests.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/gene_set_tests.py) | @@ -47,14 +47,14 @@ size; its sections give every check. | limma-voom and limma-trend on every cell type of a cube | {api}`limma_voom`, {api}`limma_trend` | limma 3.62.1, edgeR 4.4.0 | exact; close with robust moderation | [`differential_expression.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/differential_expression.py) | | Options for pseudobulk data: quality weights, weights from cell numbers, a minimum count, adjustment across cell types | {api}`limma_voom`, {api}`limma_trend` | limma 3.62.1, edgeR 4.4.0 | exact; close with quality weights | [`differential_expression_options.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/differential_expression_options.py) | | dream on every cell type of a cube | {api}`dream_voom`, {api}`voom_with_dream_weights`, {api}`moderated_fixed_effect_tests` | variancePartition 1.36.2, limma 3.62.1, lme4 1.1-38, lmerTest 3.1-3 | close | [`dream.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/dream.py) | -| Variance partitioning (`fitExtractVarPartModel`), on every cell type of a cube, and its figures (`plotVarPart`, `plotPercentBars`) | {api}`variance_fractions`, {api}`variance_partitioning`, {api}`plot_variance_fractions`, {api}`plot_gene_variance_fractions` | variancePartition 1.36.2, lme4 1.1-38, ggplot2 4.0.3 | close; exact at given estimates and for linear models | [`variance_partitioning.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/variance_partitioning.py) | +| Variance partitioning (`fitExtractVarPartModel`), on every cell type of a cube, and its figures (`plotVarPart`, `plotPercentBars`) | {api}`variance_fractions`, {api}`variance_partitioning`, {api}`plot_variance_fractions`, {api}`plot_feature_variance_fractions` | variancePartition 1.36.2, lme4 1.1-38, ggplot2 4.0.3 | close; exact at given estimates and for linear models | [`variance_partitioning.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/variance_partitioning.py) | | limma-voom on published single-cell data (Kang et al. 2018) | {api}`from_cells`, {api}`limma_voom` | limma 3.62.1, edgeR 4.4.0 | exact | [`interferon_stimulated_pbmcs.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/interferon_stimulated_pbmcs.py) | | Cell-type composition (speckle's propeller) | {api}`propeller` | speckle 1.6.0, limma 3.62.1 | exact; close for the robust prior | [`composition_tests.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/composition_tests.py) | -| Meta-analysis by fixed and random effects (metafor's `rma.uni`) | {api}`meta_analysis`, {api}`differential_expression_meta_analysis` | metafor 5.0.1 | exact; close for REML and Paule-Mandel | [`meta_analysis.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/meta_analysis.py) | +| Meta-analysis by fixed and random effects (metafor's `rma.uni`) | {api}`meta_analysis`, {api}`fit_meta_analysis` | metafor 5.0.1 | exact; close for REML and Paule-Mandel | [`meta_analysis.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/meta_analysis.py) | | Combined p-values of several studies (Fisher, Stouffer with weights, Tippett, Wilkinson, Pearson, Mudholkar and George, Simes), one-sided or two-sided with the directions of the effects; partial conjunctions, an effect in at least u studies | {api}`combine_p_values`, {api}`partial_conjunction` | scipy 1.18.1, metap 1.1, mpmath 1.3.0 | exact | [`combined_p_values_and_replication.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/combined_p_values_and_replication.py) | | The proportion of true nulls, pi0, and pi1 (qvalue's `pi0est`, its smoother and bootstrap) | {api}`proportion_of_true_nulls` | qvalue 2.38.0 | exact | [`combined_p_values_and_replication.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/combined_p_values_and_replication.py) | -| Replication across the studies of a collection: combined and partial conjunction p-values, sign agreement, heterogeneity, pi1 between studies, genes found in k studies, and its figures | {api}`differential_expression_replication`, {api}`plot_study_agreement`, {api}`plot_replicated_genes` | metap 1.1, qvalue 2.38.0, metafor 5.0.1 | exact; close for REML | [`combined_p_values_and_replication.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/combined_p_values_and_replication.py) | -| SumRank (Nakatsuka et al. 2025): every gene's best ranks summed across studies, with Irwin-Hall p-values and permutations that re-run each study's analysis; prediction of cases in held-out studies by genes chosen from the others, by SumRank and by meta-analysis; their figures | {api}`sumrank`, {api}`differential_expression_sumrank`, {api}`held_out_prediction`, {api}`plot_sumrank`, {api}`plot_held_out_auc` | the authors' code (commit 46392f66), R 4.4.3, mpmath 1.3.0, scikit-learn 1.9.1 | exact; calibrated for p-values from random permutations | [`sumrank.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/sumrank.py) | +| Replication across the studies of a collection: combined and partial conjunction p-values, sign agreement, heterogeneity, pi1 between studies, genes found in k studies, and its figures | {api}`replication`, {api}`plot_study_agreement`, {api}`plot_replicated_genes` | metap 1.1, qvalue 2.38.0, metafor 5.0.1 | exact; close for REML | [`combined_p_values_and_replication.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/combined_p_values_and_replication.py) | +| SumRank (Nakatsuka et al. 2025): every gene's best ranks summed across studies, with Irwin-Hall p-values and permutations that re-run each study's analysis; prediction of cases in held-out studies by genes chosen from the others, by SumRank and by meta-analysis; their figures | {api}`sumrank`, {api}`fit_sumrank`, {api}`held_out_prediction`, {api}`plot_sumrank`, {api}`plot_held_out_auc` | the authors' code (commit 46392f66), R 4.4.3, mpmath 1.3.0, scikit-learn 1.9.1 | exact; calibrated for p-values from random permutations | [`sumrank.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/sumrank.py) | | Principal components of samples (DESeq2's `plotPCA`), of a matrix or a cell type of a cube, and their figure | {api}`principal_components`, {api}`plot_pca` | DESeq2 1.46.0, edgeR 4.4.0 | exact, up to the sign of each component | [`pca_and_dot_plots.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/pca_and_dot_plots.py) | | Dot plots of the fraction of cells that express each gene and their mean expression, per cell type, from a cube (scanpy's `dotplot`) | {api}`plot_dotplot` | scanpy 1.12.4 | exact | [`pca_and_dot_plots.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/pca_and_dot_plots.py) | | Figures of limma, edgeR, DESeq2, speckle and metafor (MDS, voom's trend, plotSA, MD and volcano plots, barcode plots, dispersions, composition, forest plots) | {api}`cytepool.plotting` | limma 3.62.1, edgeR 4.4.0, DESeq2 1.46.0, speckle 1.6.0, metafor 5.0.1 | exact for what is drawn; close for plotSA's prior | [`plotting.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/plotting.py) | diff --git a/docs/source/reference/citing.md b/docs/source/reference/citing.md index a18f875..337be26 100644 --- a/docs/source/reference/citing.md +++ b/docs/source/reference/citing.md @@ -39,7 +39,7 @@ the credit for the methods belongs to their authors. | {api}`gsva`, {api}`ssgsea`, {api}`combined_z_scores`, {api}`plage` | the GSVA package [@haenzelmann2013], and the paper of the method you chose: GSVA [@haenzelmann2013], ssGSEA [@barbie2009], combined z-scores [@lee2008] or PLAGE [@tomfohr2005] | | {api}`read_gmt` | the collection you read, as its provider asks; for MSigDB, [@subramanian2005; @liberzon2011] | | {api}`propeller` | [@phipson2022] | -| {api}`meta_analysis`, {api}`differential_expression_meta_analysis` | metafor [@viechtbauer2010]; the estimator of the between-study variance you chose: REML [@viechtbauer2005], DerSimonian-Laird [@dersimonian1986] or Paule-Mandel [@paule1982]; Knapp-Hartung [@knapp2003] | +| {api}`meta_analysis`, {api}`fit_meta_analysis` | metafor [@viechtbauer2010]; the estimator of the between-study variance you chose: REML [@viechtbauer2005], DerSimonian-Laird [@dersimonian1986] or Paule-Mandel [@paule1982]; Knapp-Hartung [@knapp2003] | | {api}`adjust_p_values` | the method you chose: [@benjamini1995], [@benjamini2001], [@holm1979], [@hochberg1988] or [@hommel1988] | Each module's documentation lists, under References, every work it implements; the diff --git a/docs/source/reference/datasets.md b/docs/source/reference/datasets.md index 4c7e265..c259d1b 100644 --- a/docs/source/reference/datasets.md +++ b/docs/source/reference/datasets.md @@ -38,10 +38,11 @@ fingerprint of each file it was given, which the build checks against the regist ## Networks -The activity functions of {mod}`cytepool.activity` read networks that {api}`collectri` -and {api}`progeny` download on first use into `cytepool/networks` under -`XDG_CACHE_HOME` or `~/.cache` (or the directory given as `cache_dir`), each checked -against its size and SHA-256 digest; no file of them is in the repository. +The activity functions ({api}`ulm`, {api}`mlm` and {api}`weighted_aggregate`) read +networks that {api}`collectri` and {api}`progeny` download on first use into +`cytepool/networks` under `XDG_CACHE_HOME` or `~/.cache` (or the directory given as +`cache_dir`), each checked against its size and SHA-256 digest; no file of them is in +the repository. | Network | What it holds | Source | Licence | Cite | | --- | --- | --- | --- | --- | diff --git a/docs/source/statistics/choosing_a_method.md b/docs/source/statistics/choosing_a_method.md index 3e2a23f..2b1eb15 100644 --- a/docs/source/statistics/choosing_a_method.md +++ b/docs/source/statistics/choosing_a_method.md @@ -71,9 +71,9 @@ edgeR's and DESeq2's methods run on one matrix at a time, not on every group of | Do the genes of a pathway gather at the top or the bottom of a ranking of all genes? | {api}`gsea`, with {api}`plot_enrichment` and {api}`plot_gsea_table` | [Gene-set tests](gene_set_tests.md) | | Are pathways over-represented among the genes that change? | {api}`over_representation`, with sets from {api}`read_gmt` | [Gene-set tests](gene_set_tests.md) | | Which transcription factors and pathways are active, in each sample or contrast? | {api}`ulm`, {api}`mlm`, {api}`weighted_aggregate`, with networks from {api}`collectri` and {api}`progeny`, and {api}`plot_activities` and {api}`plot_top_activities` | [Gene-set tests](gene_set_tests.md) | -| How active is a pathway in each sample, and does that differ between conditions? | {api}`gsva`, {api}`ssgsea`, {api}`combined_z_scores`, {api}`plage`, with {api}`plot_pathway_scores`; their scores in {api}`fit_linear_model` | [Gene-set tests](gene_set_tests.md) | +| How active is a pathway in each sample, and does that differ between conditions? | {api}`gsva`, {api}`ssgsea`, {api}`combined_z_scores`, {api}`plage`, with {api}`plot_pathway_scores`; their scores tested by {api}`limma` | [Gene-set tests](gene_set_tests.md) | | Does the share of a cell type change? | {api}`propeller` | [Composition tests](composition.md) | -| Do studies agree, and what is their common effect? | {api}`differential_expression_meta_analysis`, {api}`meta_analysis` | [Meta-analysis](meta_analysis.md) | +| Do studies agree, and what is their common effect? | {api}`meta_analysis`, {api}`fit_meta_analysis` | [Meta-analysis](meta_analysis.md) | | How many of the calls are false? | {api}`adjust_p_values` | [Multiple testing](multiple_testing.md) | ## Choices that matter in every method diff --git a/docs/source/statistics/composition.md b/docs/source/statistics/composition.md index 9efb8e4..cc15543 100644 --- a/docs/source/statistics/composition.md +++ b/docs/source/statistics/composition.md @@ -24,15 +24,15 @@ data are variable, so the test compares samples, the biological replicates, not 4. **Moderation.** The residual variances of the cell types are moderated by empirical Bayes, robustly by default, as speckle does ([Empirical Bayes moderation](empirical_bayes.md); @phipson2016). -5. **Tests.** Each contrast is tested by its moderated t-statistic; `f_test` names a - factor whose levels are tested together by a moderated F-statistic. p-values are - adjusted by the method of Benjamini and Hochberg over the cell types. - -The result, a {api}`CompositionResult`, holds a table for every contrast -({api}`CompositionResult.table`) and one for the F-test, with speckle's descriptive -columns: the baseline proportion of every cell type, the mean proportion in each level -of the factor a contrast compares, and their ratio. Its summary says which cell types -and samples were left out, and why. +5. **Tests.** Each contrast is tested by its moderated t-statistic; `f_tests` names a + factor, or several, whose levels are tested together by a moderated F-statistic. + p-values are adjusted by the method of Benjamini and Hochberg over the cell types. + +The result, a {api}`CompositionResult`, holds a table for every contrast and F-test +({api}`CompositionResult.table`), with speckle's descriptive columns: the baseline +proportion of every cell type, the mean proportion in each level of the factor a +contrast compares, and their ratio. Its summary says which cell types and samples were +left out, and why. ## Choices diff --git a/docs/source/statistics/count_models.md b/docs/source/statistics/count_models.md index bc90427..4883cbd 100644 --- a/docs/source/statistics/count_models.md +++ b/docs/source/statistics/count_models.md @@ -18,7 +18,7 @@ mean of M values ({api}`tmm_factors`; @robinson2010a), the upper quartile ({api}`upper_quartile_factors`; @bullard2010) or the relative log expression ({api}`rle_factors`; @anders2010). DESeq2 replaces library sizes by size factors, the median of each sample's ratios to the geometric mean of every gene -({api}`median_ratio_size_factors`; @anders2010). {api}`cpm` and {api}`log_cpm` give +({api}`median_ratio_size_factors`; @anders2010). {api}`cpm` and {api}`log2_cpm` give counts per million, and {api}`filter_by_expression` keeps genes with enough counts in enough samples for the design [@chen2016]. @@ -67,7 +67,7 @@ $$ ({api}`negative_binomial_unit_deviance_moments`, which gives {api}`UnitDevianceMoments`), where edgeR approximates them. 3. The quasi-dispersions are moderated by empirical Bayes towards a trend in the average - log CPM ({api}`average_log_cpm`), with limma's estimator for unequal degrees of + log CPM ({api}`average_log2_cpm`), with limma's estimator for unequal degrees of freedom ([Empirical Bayes moderation](empirical_bayes.md)). The fit is a {api}`QuasiLikelihoodFit`. {api}`quasi_likelihood_f_test` fits the model diff --git a/docs/source/statistics/cubes.md b/docs/source/statistics/cubes.md index 57c786c..f853daa 100644 --- a/docs/source/statistics/cubes.md +++ b/docs/source/statistics/cubes.md @@ -140,8 +140,9 @@ of several studies and keeps each as it was given: Each study is then analysed on its own: {api}`CubeCollection.aligned_cube` gives its cube with the aligned cell types and the genes it measured, and with its own samples and library sizes, and an analysis such as {api}`limma_voom` with the study's own design -gives results keyed by the aligned cell types. {api}`align_study_results` lines them up -gene by gene, and {api}`differential_expression_meta_analysis` combines them +gives results keyed by the aligned cell types. {api}`analyse_each_study` runs one on +every study, and the {api}`StudyResults` it returns lines them up gene by gene +({api}`StudyResults.table`) for {api}`meta_analysis`, which combines them ([Meta-analysis](meta_analysis.md)). For a joint model with the study as a covariate, {api}`CubeCollection.stack` puts the studies into one cube with the genes that every study measured and the cell types that every study has: a cube holds no missing values, diff --git a/docs/source/statistics/gene_set_tests.md b/docs/source/statistics/gene_set_tests.md index c833aa9..0d666c8 100644 --- a/docs/source/statistics/gene_set_tests.md +++ b/docs/source/statistics/gene_set_tests.md @@ -18,10 +18,15 @@ genes respond too: in a response that touches thousands of genes, many sets are. competitive test asks whether a set stands out from the other genes, and so suits ranking many sets, such as a pathway collection. The five tests of limma work on the linear model, so they allow any design and contrast and do not permute samples; all but -romer take voom's precision weights. Gene set enrichment analysis needs only a statistic -of every gene, and over-representation analysis only the genes that an analysis called -significant. [Gene sets and pathways](../guides/gene_sets.ipynb) and [Pathway and -regulator activity](../guides/pathway_activity.ipynb) run these methods on published data. +romer take voom's precision weights. Each tests a cell type of a result of +{api}`limma_voom` or {api}`limma_trend`, whose fit it computes again +({api}`DifferentialExpressionResult.fit`), or values with a design and a contrast; +romer, which takes sample weights only, tests a result of limma-trend. Gene set +enrichment analysis needs only a statistic of every gene, and over-representation +analysis only the genes that an analysis called significant. +[Gene sets and pathways](../guides/gene_sets.ipynb) and +[Pathway and regulator activity](../guides/pathway_activity.ipynb) run these methods on +published data. ## The effects of each gene @@ -194,13 +199,13 @@ The random sets come from seeds that `rng` draws, through the Mersenne Twister The tests above ask one question of a contrast. A pathway score instead turns the expression of a set's genes in each sample into one number, so that pathways can be compared across samples, drawn as a heatmap, correlated with other measurements, and -tested with the same designs and contrasts as genes [@haenzelmann2013]. -{api}`gsva`, {api}`ssgsea`, {api}`combined_z_scores` and {api}`plage` compute the four -methods of the GSVA package, from a samples x genes matrix or a cell type of a cube, -whose log-CPM after TMM normalization (or counts, for GSVA's Poisson kernel) they score -over the genes that `filterByExpr` keeps. They return a {api}`PathwayScores`, samples x -sets, with the samples' table and the genes each set used; {api}`PathwayScores.table` -lists the scores one per row. +tested with the same designs and contrasts as genes [@haenzelmann2013]. {api}`gsva`, +{api}`ssgsea`, {api}`combined_z_scores` and {api}`plage` compute the four methods of the +GSVA package, from a samples x genes matrix or a cell type of a cube, whose log-CPM +after TMM normalization (or counts, for GSVA's Poisson kernel) they score over the genes +that `filterByExpr` keeps. They return a {api}`PathwayScores`, profiles x sets, with the +table of the profiles (the samples scored) and the genes each set used; +{api}`PathwayScores.table` lists the scores one per row. **GSVA** first puts each gene on a common scale: the probability of a value at most as high under the gene's distribution across the $n$ samples, estimated by a kernel, @@ -238,9 +243,9 @@ the other genes of the sample. Combined z-scores and PLAGE also set each gene ag itself across samples, but do not compare it with other genes. GSVA's guide notes that the scores of larger sets vary less; to test them, fit the design to the scores and let the prior variance follow the set sizes, as its guide does with limma-trend: -`moderated_statistics(fit_linear_model(scores.scores, Design(scores.samples, "~ -condition")), trend=scores.n_genes)`. {api}`plot_pathway_scores` draws the scores as a -heatmap, with the samples grouped by a variable. +`limma(scores, design, trend=scores.n_genes)`, with the design built for the cube. +{api}`plot_pathway_scores` draws the scores as a heatmap, with the samples grouped by a +variable. The scores agree with GSVA's to rounding, except where CytePool differs on purpose: PLAGE's sign, GSVA's walk in samples with missing values, and inputs on which GSVA @@ -255,18 +260,18 @@ from GSVA's. A transcription factor raises some of its target genes and lowers others, and a signalling pathway leaves a footprint of genes that respond to it. A network gives each such source signed weights on its targets: 1 for a target that a transcription factor -activates and -1 for one it represses in CollecTRI's regulons [@muellerdott2023], and -in PROGENy the z-scores with which genes responded to the perturbation of each of 14 +activates and -1 for one it represses in CollecTRI's regulons [@muellerdott2023], and in +PROGENy the z-scores with which genes responded to the perturbation of each of 14 pathways in hundreds of experiments [@schubert2018; @holland2020b]. A source is active in a profile, the expression of a sample or the statistics of a contrast, when its targets are high where their weights are positive and low where they are negative. -{api}`ulm`, {api}`mlm` and {api}`weighted_aggregate` score every source in every -profile as decoupler does [@badiaimompel2022], from a samples x genes matrix, a cell -type of a cube (its log-CPM, as for the pathway scores) or a differential-expression -result, one profile per group and contrast, by default of its moderated t-statistics. -They return an {api}`Activities`, profiles x sources, which -{api}`fit_linear_model` takes with a design built from its profiles, as it takes -pathway scores; {api}`Activities.table` lists the scores one per row. +{api}`ulm`, {api}`mlm` and {api}`weighted_aggregate` score every source in every profile +as decoupler does [@badiaimompel2022], from a samples x genes matrix, a cell type of a +cube (its log-CPM, as for the pathway scores) or a differential-expression result, one +profile per group and contrast, by default of its moderated t-statistics. They return an +{api}`Activities`, profiles x sources, which {api}`limma` tests with the design built +for the cube, as it tests pathway scores; {api}`Activities.table` lists the scores one +per row. **ULM**, the univariate linear model, fits for each source a line through the values of the profile's $n$ genes against the source's weights $x_g$, zero for the genes that diff --git a/docs/source/statistics/meta_analysis.md b/docs/source/statistics/meta_analysis.md index cd649e3..9d43838 100644 --- a/docs/source/statistics/meta_analysis.md +++ b/docs/source/statistics/meta_analysis.md @@ -8,20 +8,19 @@ A single study often finds differences that others do not reproduce: of the gene differentially expressed in Alzheimer's disease in one dataset, over 85% were not reproduced in any of 16 others [@nakatsuka2025]. A meta-analysis combines the estimates of several studies into one, weighs each by its precision, and measures how much the -studies disagree. {api}`meta_analysis` does it for every gene at once, as metafor's +studies disagree. {api}`fit_meta_analysis` does it for every gene at once, as metafor's `rma.uni` does for one effect without moderators [@viechtbauer2010], and -{api}`differential_expression_meta_analysis` does it for the results of -{api}`limma_voom` or {api}`limma_trend` on several cubes, matched by cell type, contrast -and gene id. {api}`meta_analysis` returns a {api}`MetaAnalysisFit`, and -{api}`differential_expression_meta_analysis` a {api}`MetaAnalysisResult`, with a table -for every cell type and contrast ({api}`MetaAnalysisResult.table`). Where the studies -give p-values rather than comparable effects, or to ask in how many studies a gene -changes, their p-values are combined, and -{api}`differential_expression_replication` says how well the studies replicate each -other (see [](#combined-p-values) and below). The guides +{api}`meta_analysis` does it for the results of {api}`limma_voom` or {api}`limma_trend` +on several cubes, kept together as a {api}`StudyResults` and matched by cell type, +contrast and gene id. {api}`fit_meta_analysis` returns a {api}`MetaAnalysisFit`, and +{api}`meta_analysis` a {api}`MetaAnalysisResult`, with a table for every cell type and +contrast ({api}`MetaAnalysisResult.table`). Where the studies give p-values rather than +comparable effects, or to ask in how many studies a gene changes, their p-values are +combined, and {api}`replication` says how well the studies replicate each other (see +[](#combined-p-values) and below). The guides [Combine studies](../guides/combine_studies.ipynb) and -[Replication across studies](../guides/replication_across_studies.ipynb) run them on -six simulated cohorts whose true changes are known. +[Replication across studies](../guides/replication_across_studies.ipynb) run them on six +simulated cohorts whose true changes are known. ## The model @@ -66,18 +65,17 @@ percentage. ## Genes that some studies did not measure NaN marks a study that did not measure or test a gene. Each gene is analysed over the -studies that measured it; {api}`differential_expression_meta_analysis` analyses a gene -tested by at least `min_studies` studies (two by default, since one study says nothing -about $\tau^2$). It takes limma's moderated standard errors as the known sampling -errors. +studies that measured it; {api}`meta_analysis` analyses a gene tested by at least +`min_studies` studies (two by default, since one study says nothing about $\tau^2$). It +takes limma's moderated standard errors as the known sampling errors. A gene missing from a study's results was not measured there, or was measured and left out, as by the expression filter, which are different findings. For the studies of a -{api}`CubeCollection`, each analysed on its aligned cube, -{api}`align_study_results` lines up the results of one cell type and contrast with a -row for every study and gene, which says whether the study tested the gene and, where -it did not, why: it did not measure the gene, measured it and left it out, or did not -analyse the cell type or the contrast. +{api}`CubeCollection`, each analysed on its aligned cube by {api}`analyse_each_study`, +{api}`StudyResults.table` lines up the results of one cell type and contrast with a row +for every study and gene, which says whether the study tested the gene and, where it did +not, why: it did not measure the gene, measured it and left it out, or did not analyse +the cell type or the contrast. (combined-p-values)= ## Combined p-values @@ -149,17 +147,17 @@ small for the second study to find on its own [@storey2003]. ## Replication across the studies of a collection -{api}`differential_expression_replication` reads one cell type and contrast of the -studies of a {api}`CubeCollection`, lined up by {api}`align_study_results`, and returns -a {api}`ReplicationResult` with four tables: +{api}`replication` reads one cell type and contrast of the studies' results, a +{api}`StudyResults`, lined up by {api}`StudyResults.table`, and returns a +{api}`ReplicationResult` with four tables: -- `genes`: for every gene, the studies that tested it and find it (adjusted p-value +- `features`: for every gene, the studies that tested it and find it (adjusted p-value below `alpha`), up and down; the combined p-value, by Stouffer's method weighted by the square roots of the studies' samples and signed by their log2 fold changes; the partial conjunction p-value of an effect in at least `at_least` studies in one - direction (Simes's by default), both adjusted over the genes; the share of the - studies whose effect has the combined direction; and Cochran's $Q$, $I^2$ and - $\tau^2$ of its log2 fold changes, from {api}`meta_analysis`; + direction (Simes's by default), both adjusted over the genes; the combined direction + as a sign and the share of the studies whose effect has it; and Cochran's $Q$, $I^2$ + and $\tau^2$ of its log2 fold changes, from {api}`fit_meta_analysis`; - `study_pairs`: for every ordered pair of studies, $\pi_1$ and the sign agreement of the genes the first finds in the second, and how many the second finds too. $\pi_0$ is estimated at $\lambda = 0.5$ by default: on the few dozen p-values of the genes @@ -175,17 +173,16 @@ each number of studies finds. ## SumRank: genes that rank high in most studies -A meta-analysis weighs each study by its precision, so a gene with a very small -p-value in one or two studies can pass without any sign of change in the others; such -genes predicted case status poorly in studies left out [@nakatsuka2025]. SumRank asks -instead that a gene rank high in most studies. {api}`sumrank` computes it from a signed -statistic of every gene in every study and returns a {api}`SumRankFit`; -{api}`differential_expression_sumrank` computes it from the results of one cell type -and contrast of the studies of a {api}`CubeCollection` and returns a -{api}`SumRankResult`, whose table ({api}`SumRankResult.table`) gives each gene's -statistics and p-values, up and down, and whose table of studies -({api}`SumRankResult.studies`) says which studies tested the cell type and contrast, -and why the others did not. +A meta-analysis weighs each study by its precision, so a gene with a very small p-value +in one or two studies can pass without any sign of change in the others; such genes +predicted case status poorly in studies left out [@nakatsuka2025]. SumRank asks instead +that a gene rank high in most studies. {api}`fit_sumrank` computes it from a signed +statistic of every gene in every study and returns a {api}`SumRankFit`; {api}`sumrank` +computes it from the results of one cell type and contrast of the studies of a +collection, a {api}`StudyResults`, and returns a {api}`SumRankResult`, whose table +({api}`SumRankResult.features`) gives each gene's statistics and p-values, up and down, +and whose table of studies ({api}`SumRankResult.studies`) says which studies tested the +cell type and contrast, and why the others did not. Each study ranks the genes that every study tested by its signed $-\log_{10}$ p-value, from $r = 1$ for the gene most up to $r = G$; tied genes share their average @@ -199,24 +196,25 @@ m = \lfloor p\,k \rfloor, $$ with $u_{g(1)} \le \dots \le u_{g(k)}$ and the proportion $p$ of the studies -(`proportion_of_studies`). The paper sums every study when there are few, and chose $p$ -by prediction in held-out studies when there are many (0.65 of 17 studies of Alzheimer's -disease, 0.55 of 16 of COVID-19). Genes down have the statistic of the ranks $1 - u$. -A gene that some study did not test, because it did not measure it or its expression -filter left it out, is not ranked, as the paper ranks the genes every study measured: -a rank says where a gene stands among the genes ranked with it. +(`proportion_of_studies`), or $m$ given itself (`n_studies_summed`). The paper sums +every study when there are few, and chose $p$ by prediction in held-out studies when +there are many (0.65 of 17 studies of Alzheimer's disease, 0.55 of 16 of COVID-19). +Genes down have the statistic of the ranks $1 - u$. A gene that some study did not test, +because it did not measure it or its expression filter left it out, is not ranked, as +the paper ranks the genes every study measured: a rank says where a gene stands among +the genes ranked with it. If each study ranked the genes at random, the normalized ranks would be independent uniform variables, and $S_g$ below the cap would follow the Irwin-Hall distribution of a sum of $m$ of them [@irwin1927; @hall1927], whose distribution function gives the Irwin-Hall p-value. With every study summed it is exact under that null; the sum of the best $m$ of $k$ ranks is smaller than a sum of $m$ ranks, so with $m < k$ these p-values -are much too small: in six simulated studies without changes, summing three, 37% of -them fell below 0.05, and {api}`differential_expression_sumrank` warns when it gives -them. The paper takes its p-values from permutations instead: each study's labels are -permuted and its analysis run again, and the statistics of every gene in every -permutation make one null distribution, $N$ statistics in all, against which a gene of -statistic $s$ has the p-value +are much too small: in six simulated studies without changes, summing three, 37% of them +fell below 0.05, and {api}`sumrank` warns when it gives them. The paper takes its +p-values from permutations instead: each study's labels are permuted and its analysis +run again, and the statistics of every gene in every permutation make one null +distribution, $N$ statistics in all, against which a gene of statistic $s$ has the +p-value $$ p = \frac{1 + \#\{\text{null statistics} \le s\}}{1 + N}, @@ -224,13 +222,14 @@ $$ never zero [@phipson2010]. Pooling over the genes gives small p-values from few permutations, 100 permutations of 10,000 genes reaching $10^{-6}$. With -`n_permutations`, {api}`differential_expression_sumrank` permutes a variable of each -study's design among the samples of its fit of the cell type, from a seeded -`numpy.random.Generator`, and re-runs the study's {api}`limma_voom` or {api}`limma_trend` -with the design and settings its result records: 0.21 s per study and permutation for -40 samples and 10,000 genes with limma-voom, so that 100 permutations of 20 such studies -take about 7 minutes on a laptop. {api}`plot_sumrank` draws every gene's p-value, the -genes in alphabetical order, as the authors of SumRank draw it. +`n_permutations`, {api}`sumrank` permutes the variable whose levels the contrast +compares (or `permute`) in each study's design among the samples of its fit of the cell +type, from a seeded `numpy.random.Generator`, and re-runs the study's {api}`limma_voom` +or {api}`limma_trend` with the design and settings its result records: 0.21 s per study +and permutation for 40 samples and 10,000 genes with limma-voom, so that 100 +permutations of 20 such studies take about 7 minutes on a laptop. {api}`plot_sumrank` +draws every gene's p-value, the genes in alphabetical order, as the authors of SumRank +draw it. Where CytePool departs from the paper and its authors' code, which the validation runs: @@ -249,20 +248,21 @@ Where CytePool departs from the paper and its authors' code, which the validatio ## Prediction in held-out studies How well genes found in some studies replicate can be measured in a study that had no -part in finding them. {api}`held_out_prediction` leaves each study of a collection out -in turn, chooses genes up and genes down in each cell type from the other studies' -results, by SumRank and by the random-effects meta-analysis above with REML and the -Knapp-Hartung adjustment (the paper's comparison), scores every case and control of -the held-out study, and reports the area under the ROC curve of cases against controls -[@hanley1982], by study and method, in a {api}`HeldOutPrediction` -({api}`HeldOutPrediction.auc`). A sample's score is UCell's [@andreatta2021]: in each -cell type, the ranks of the chosen genes among all the genes of the sample's profile, -scaled to [0, 1] over the study's samples, the genes up counted for the sample and the -genes down against it, summed over the cell types, as the authors' code sums them. The genes chosen and the scores are in -{api}`HeldOutPrediction.genes` and {api}`HeldOutPrediction.scores`, and +part in finding them. {api}`held_out_prediction` leaves each study of a +{api}`StudyResults` out in turn, chooses genes up and genes down in each cell type from +the other studies' results, by SumRank and by the random-effects meta-analysis above +with REML and the Knapp-Hartung adjustment (the paper's comparison), scores every case +and control of the held-out study, the two levels that the contrast compares, and +reports the area under the ROC curve of cases against controls [@hanley1982], by study +and method, in a {api}`HeldOutPrediction` ({api}`HeldOutPrediction.auc`). A sample's +score is UCell's [@andreatta2021]: in each cell type, the ranks of the chosen genes +among all the genes of the sample's profile, scaled to [0, 1] over the study's samples, +the genes up counted for the sample and the genes down against it, summed over the cell +types, as the authors' code sums them. The genes chosen and the scores are in +{api}`HeldOutPrediction.features` and {api}`HeldOutPrediction.scores`, and {api}`plot_held_out_auc` draws the AUC of every study by method. The paper fits a -logistic regression on the score and lets pROC choose the direction of each study's -AUC [@robin2011], so that a score that ranks controls above cases also scores above 0.5; +logistic regression on the score and lets pROC choose the direction of each study's AUC +[@robin2011], so that a score that ranks controls above cases also scores above 0.5; here cases must score higher, and an AUC below 0.5 says the genes predict the wrong direction. diff --git a/docs/source/statistics/mixed_models.md b/docs/source/statistics/mixed_models.md index 9939c8b..de016f7 100644 --- a/docs/source/statistics/mixed_models.md +++ b/docs/source/statistics/mixed_models.md @@ -184,7 +184,7 @@ the mean-variance trend of the mixed model, and records the samples each fit use a {api}`VariancePartitioningResult`; {api}`VariancePartitioningResult.table` gives the fractions of one cell type. {api}`plot_variance_fractions` draws variancePartition's violins of the fractions over the genes [@hintze1998], with Silverman's bandwidth -[@silverman1986], and {api}`plot_gene_variance_fractions` its bars for chosen genes. +[@silverman1986], and {api}`plot_feature_variance_fractions` its bars for chosen genes. The residual fraction mixes biological variation, the precision of the measurements and technical variation that the design does not name, so its size alone does not say whether variables are missing from the design. diff --git a/docs/source/validation.md b/docs/source/validation.md index 346a25f..1727d95 100644 --- a/docs/source/validation.md +++ b/docs/source/validation.md @@ -315,7 +315,7 @@ numba 0.68.0. | The genes, as a set and in the order in which the studies first list them, and which study measured which | `anndata.concat`, outer join (NaN where a study lacks a gene), `pandas.unique` | identical; 0 mismatches | 0 | 2 pass | | Every layer of each study's aligned cube: merged cell types, measured genes | the outer join's values | 0 | 1e-10 | 1 pass | | `stack()`: genes in order, cell types and samples; every layer; the cells of each entry, and an entry without cells | `anndata.concat`, inner join, kept to the cell types of every study | 0 | 0 and 1e-10 | 3 pass | -| `limma_voom` on each study's aligned cube, lined up by `align_study_results`: logFC, AveExpr, t, P.Value, adj.P.Val, B and the standard error, in 12 cell types of 3 studies | limma, study by study | 3.8e-13 | 1e-10 | 3 pass | +| `limma_voom` on each study's aligned cube, lined up by `StudyResults.table`: logFC, AveExpr, t, P.Value, adj.P.Val, B and the standard error, in 12 cell types of 3 studies | limma, study by study | 3.8e-13 | 1e-10 | 3 pass | | The status of every gene of every study: tested, measured and left out, or not measured | limma's tables and each study's measured genes | 0 mismatches | 0 | 3 pass | | `limma_voom` on the stack of `lupus` and `covid`, `~ donor + condition` with donors `study::donor` | limma on the stacked counts | 1.4e-13 | 1e-10 | 1 pass | | The same with the donor ids as the studies give them | limma on the stacked counts, donors `study::donor` | 1.9 (log2 fold changes) | must exceed 1e-10 | 1 control | @@ -751,7 +751,7 @@ large counts, and 2e-3 with a dispersion of 2e-5. Deliberate differences: [`validation/quasi_likelihood.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/quasi_likelihood.py) compares `fit_quasi_likelihood` and `quasi_likelihood_f_test` with edgeR's `glmQLFit` and `glmQLFTest` (edgeR 4.4's default, `legacy = FALSE`), `common_dispersion` with -`estimateGLMCommonDisp` and `dispCoxReid`, `average_log_cpm` with `aveLogCPM`, and +`estimateGLMCommonDisp` and `dispCoxReid`, `average_log2_cpm` with `aveLogCPM`, and `negative_binomial_unit_deviance_moments` with sums at 30 digits in mpmath and the gamma limit. The counts are simulated from fixed seeds, with a dispersion for each gene, in 14 cases of 1,000 genes and 8 or 9 samples, with genes without counts and @@ -1739,7 +1739,7 @@ the F-tests here have independent coefficients. [`validation/variance_partitioning.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/variance_partitioning.py) compares `variance_fractions` and `variance_partitioning`, and the figures -`plot_variance_fractions` and `plot_gene_variance_fractions`, with variancePartition's +`plot_variance_fractions` and `plot_feature_variance_fractions`, with variancePartition's `fitExtractVarPartModel`, `calcVarPart`, `plotVarPart` and `plotPercentBars`, on data from fixed seeds (20261101 to 20261108), 60 genes each: one random donor intercept (10 donors x 3 samples, a fifth of the genes without a donor effect); random donor, batch @@ -1784,7 +1784,7 @@ describes, and differs from the kernel density estimate that CytePool draws by u segments with a width are compared by position. The conventions of variancePartition that its documentation leaves open, found by -probing it and listed in the documentation of `cytepool.variance_partitioning`, are +probing it and listed in the documentation of `cytepool.stats.variance_fractions`, are exercised here: the variance of a fixed effect is the weighted variance of its fitted contribution, with the denominator of a maximum likelihood variance; in a mixed model the terms share the variance of all the fitted fixed effects in proportion to their @@ -2015,7 +2015,7 @@ agree to rounding. ## Meta-analysis [`validation/meta_analysis.py`](https://github.com/NygenAnalytics/CytePool/blob/master/validation/meta_analysis.py) -compares `meta_analysis` with metafor's `rma.uni`, run feature by feature in R on the +compares `fit_meta_analysis` with metafor's `rma.uni`, run feature by feature in R on the same simulated effects and standard errors (seed 20261028), for the fixed-effect model (`method = "EE"`) and the DerSimonian-Laird, REML and Paule-Mandel estimators of the between-study variance, each with z-tests and with the Knapp-Hartung adjustment @@ -2036,8 +2036,8 @@ the first has T, B and NK cells and 400 genes, the second too few NK cells to fi cells and 10% fewer genes, and 30% of the changes differ between cohorts. `limma_voom` tests the stimulation in every cohort; R merges the cohorts' tables by gene id, runs `rma.uni` on every gene that enough cohorts tested and adjusts the p-values with -`p.adjust(method = "BH")`, and `differential_expression_meta_analysis` is compared with -its results. Reference: R 4.4.3, metafor 5.0.1, jsonlite 2.0.0. +`p.adjust(method = "BH")`, and `meta_analysis` is compared with its results. Reference: +R 4.4.3, metafor 5.0.1, jsonlite 2.0.0. | Check family | Reference | Largest difference | Tolerance | Outcome | | --- | --- | --- | --- | --- | @@ -2107,8 +2107,8 @@ lambda, with its defaults, `smooth.df = 5`, `smooth.log.pi0`, `pi0.method = "bootstrap"`, one lambda and three other grids. Then three cohorts of 6, 4 and 8 donors, before and after a stimulation that changes 15% of 700 genes in two cell types, alike in every cohort up to 50% more or less, each measuring part of the genes, are aligned in a -`CubeCollection` and tested by `limma_voom`; `differential_expression_replication` runs -on both cell types with four settings, among them qvalue's defaults for pi1 +`CubeCollection` and tested by `limma_voom`; `replication` runs on both cell types +with four settings, among them qvalue's defaults for pi1 (`null_proportion={}`) and pi1 by default (lambda = 0.5), and R computes every column from the cohorts' tables with metap, qvalue, `p.adjust` and metafor's `rma.uni` (REML with `control = list(threshold = 1e-13, maxiter = 10000)`, or DerSimonian-Laird). The @@ -2168,7 +2168,7 @@ labels each are all analysed. Reference: R 4.4.3, the authors' code, mpmath 1.3. | Check family | Reference | Largest difference | Tolerance | Outcome | | --- | --- | --- | --- | --- | -| The Irwin-Hall distribution function on a grid of 1 to 200 studies (118 values), and the Irwin-Hall p-values of `sumrank` for 3, 10 and 30 studies (1,199 genes) | mpmath, 200 digits | 6.2e-15 | 1e-12 | 2 pass | +| The Irwin-Hall distribution function on a grid of 1 to 200 studies (118 values), and the Irwin-Hall p-values of `fit_sumrank` for 3, 10 and 30 studies (1,199 genes) | mpmath, 200 digits | 6.2e-15 | 1e-12 | 2 pass | | The SumRank statistics of 5 studies, T and B, up and down, summing every study, 0.65 and 0.5 of them; of 4 studies with tied p-values | the authors' SumRank | 4.2e-15 | 1e-12 | 2 pass | | Permutation p-values of 40 permutations of the condition, up and down: (1 + the null statistics at most the gene's) / (1 + N) | the authors' null statistics | 0 | 0 | 1 pass | | The authors' calibration, between the null statistics strictly below the gene's and those at most it, divided by N | `CalibratePValueswithPermutations` | 0 genes outside | 0 | 1 pass | diff --git a/docs/source/why_pseudobulk.ipynb b/docs/source/why_pseudobulk.ipynb index 0046619..4b1dd1e 100644 --- a/docs/source/why_pseudobulk.ipynb +++ b/docs/source/why_pseudobulk.ipynb @@ -52,7 +52,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "CytePool 0.1.dev57+gb65bc7e32\n", + "CytePool 0.1.dev63+g8afa32afb\n", "group A: D01, D05, D06, D08, D09, D12\n", "group B: D02, D03, D04, D07, D10, D11\n" ] @@ -73,9 +73,7 @@ "import numpy as np\n", "\n", "import cytepool\n", - "from cytepool.design import Design\n", - "from cytepool.differential_expression import limma_voom\n", - "from cytepool.io import read_cube\n", + "from cytepool import Design, limma_voom, read_cube\n", "from cytepool.plotting import plot_p_values\n", "from cytepool.stats.distributions import t_sf\n", "from cytepool.stats.multiple_testing import adjust_p_values\n", @@ -90,10 +88,9 @@ "\n", "\n", "def pooled_cells(cube, group, samples):\n", - " # The cells of some samples of one cell type: how many, and the mean and\n", - " # variance of every gene's log-normalized values. Every control sample has the\n", - " # condition ctrl, so pooling by condition pools them all.\n", - " pooled = cube.select(samples=samples, groups=group).collapse_samples(\"condition\")\n", + " # The cells of some samples of one cell type, pooled: how many, and the mean and\n", + " # variance of every gene's log-normalized values.\n", + " pooled = cube.select(samples=samples, groups=group).collapse_samples()\n", " return pooled.n_cells[0, 0], pooled.mean(group)[0], pooled.variance(group)[0]\n", "\n", "\n", @@ -219,8 +216,7 @@ "p_cells = test_of_cells(controls, cd4, in_a)\n", "gene = int(np.argmin(p_cells))\n", "name = controls.features[\"feature_id\"][gene]\n", - "table = of_samples.table(cd4, \"B_vs_A\")\n", - "p_samples = table[\"p_value\"][table[\"feature_id\"] == name][0]\n", + "p_samples = of_samples.table(cd4, \"B_vs_A\").filter(feature_id=name)[\"p_value\"][0]\n", "print(\n", " f\"{name}: p-value {p_cells[gene]:.0e} testing cells, {p_samples:.2f} testing samples\"\n", ")\n", @@ -330,16 +326,16 @@ "datasets": [ "simulated_pbmc" ], - "executed_at": "2026-10-26T21:42:08+00:00", - "executed_code_sha256": "0a4cdd530679e2d54393bbd5fe8f70152188a7e8bd5ff86cfbbf7d1f71cffb79", + "executed_at": "2026-10-27T15:23:55+00:00", + "executed_code_sha256": "004b91136d2d3f0defea4a3d05f6753dc47fa05ba253d23be5851f01e588c3b8", "executed_datasets": { "simulated_pbmc": { "simulated_pbmc.cube.zarr": "sha256:554a2b5b4188308ea900cf907fa87f42d12e0458e6d5dc11525e862f3ad8a726", "simulated_pbmc_cells.npz": "sha256:0d4c9154b87dbf8e9796232c2afec4a8340368fd0f5d386df4dd780c4e478129" } }, - "executed_with": "0.1.dev57+gb65bc7e32", - "execution_seconds": 3.4, + "executed_with": "0.1.dev63+g8afa32afb", + "execution_seconds": 2.1, "kernelspec": { "display_name": "Python 3", "language": "python", diff --git a/tests/smoke_test_wheel.py b/tests/smoke_test_wheel.py index 298f0fa..faf7995 100644 --- a/tests/smoke_test_wheel.py +++ b/tests/smoke_test_wheel.py @@ -37,7 +37,7 @@ "t", "p_value", "adjusted_p_value", - "b_statistic", + "log_odds", "standard_error", ) @@ -51,9 +51,7 @@ def analyse() -> None: """ import numpy as np - from cytepool.cube import from_cells - from cytepool.design import Design - from cytepool.differential_expression import limma_voom + from cytepool import Design, from_cells, limma_voom with np.load(DATA / "cells.npz") as data: cells = {key: data[key] for key in data.files} @@ -66,9 +64,9 @@ def analyse() -> None: } cube = from_cells( cells["counts"], - samples=cells["samples"], - groups=cells["groups"], - sample_table=sample_table, + sample_labels=cells["samples"], + group_labels=cells["groups"], + samples=sample_table, layers="counts", ) design = Design( diff --git a/tests/test_activity.py b/tests/test_activity.py index 14b2522..8d1fa27 100644 --- a/tests/test_activity.py +++ b/tests/test_activity.py @@ -21,17 +21,22 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool import activity -from cytepool.activity import Activities, mlm, ulm, weighted_aggregate -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.gene_sets import GeneSets -from cytepool.networks import Network -from cytepool.normalization import filter_by_expression, log_cpm, tmm_factors +from cytepool import ( + Activities, + Cube, + Design, + GeneSets, + Network, + Table, + activity, + limma_voom, + mlm, + ulm, + weighted_aggregate, +) +from cytepool.normalization import filter_by_expression, log2_cpm, tmm_factors from cytepool.plotting import plot_activities, plot_top_activities from cytepool.stats.linear_models import fit_linear_model -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "activity" FIXTURE = json.loads((DATA / "decoupler.json").read_text()) @@ -82,7 +87,7 @@ def _run(case: dict) -> tuple[Activities, list[int], list[int]]: result = function( values, _network(case["network"]), - gene_ids=genes, + feature_ids=genes, sample_ids=profiles, min_targets=case["tmin"], ) @@ -115,16 +120,16 @@ def test_the_linear_models_are_those_of_decoupler(case: dict) -> None: assert _scaled(result.scores[pick], ARRAYS[f"{name}/raw_scores"]) < EXACT assert _scaled(result.estimates[pick], ARRAYS[f"{name}/estimates"]) < EXACT if case["method"] == "ulm": - assert _relative(result.p_values[pick], ARRAYS[f"{name}/raw_p"]) < EXACT + assert _relative(result.p_value[pick], ARRAYS[f"{name}/raw_p"]) < EXACT # Benjamini and Hochberg's over the sources of each profile, by scipy. bh = ARRAYS[f"{name}/bh"] - assert _relative(result.adjusted_p_values[pick], bh) < EXACT + assert _relative(result.adjusted_p_value[pick], bh) < EXACT # decoupler keeps them in single precision. - single = result.adjusted_p_values[pick].astype(np.float32) + single = result.adjusted_p_value[pick].astype(np.float32) assert_array_equal(single, ARRAYS[f"{name}/padj"].astype(np.float32)) else: # decoupler's own are 1 - cdf; scipy's upper tail of its t-statistics. - assert _relative(result.p_values[pick], ARRAYS[f"{name}/t_sf"]) < EXACT + assert _relative(result.p_value[pick], ARRAYS[f"{name}/t_sf"]) < EXACT @pytest.mark.parametrize("name", ["weighted mean", "weighted sum"]) @@ -135,14 +140,14 @@ def test_the_weighted_aggregate_is_that_of_decoupler(name: str) -> None: result = weighted_aggregate( values, _network(case["network"]), - gene_ids=genes, + feature_ids=genes, statistic=statistic, n_permutations=0, ) columns = [result.sources.index(s) for s in FIXTURE["found"][name]["sources"]] assert _scaled(result.scores[:, columns], ARRAYS[f"{name}/raw_scores"]) < EXACT assert_array_equal(result.scores, result.estimates) - assert np.isnan(result.p_values).all() + assert np.isnan(result.p_value).all() class _Replay(np.random.Generator): @@ -167,14 +172,14 @@ def test_normalized_scores_are_decouplers_given_its_permutations() -> None: result = weighted_aggregate( values[:, order], _network(case["network"]), - gene_ids=found["genes"], + feature_ids=found["genes"], n_permutations=permutations.shape[0], rng=_Replay(permutations), ) columns = [result.sources.index(s) for s in found["sources"]] assert _scaled(result.scores[:, columns], ARRAYS[f"{name}/raw_scores"]) < EXACT # The p-values differ from decoupler's on purpose (see the module docstring). - assert np.all((result.p_values > 0.0) & (result.p_values <= 1.0)) + assert np.all((result.p_value > 0.0) & (result.p_value <= 1.0)) def test_the_bar_plot_is_decouplers() -> None: @@ -207,21 +212,21 @@ def test_ulm_of_a_source_with_two_targets_on_three_samples() -> None: # The second sample is 2y + 7, which doubles the slope and keeps t; the third, # -y, negates both. values = np.array([Y, 2.0 * Y + 7.0, -Y]) - result = ulm(values, PAIR, gene_ids=FOUR, min_targets=2) + result = ulm(values, PAIR, feature_ids=FOUR, min_targets=2) t = -13.0 / math.sqrt(108.0) assert_allclose(result.scores[:, 0], [t, t, -t], rtol=1e-14) assert_allclose(result.estimates[:, 0], [-13 / 11, -26 / 11, 13 / 11], rtol=1e-14) p = 1.0 - 13.0 / math.sqrt(385.0) - assert_allclose(result.p_values[:, 0], [p, p, p], rtol=1e-13) + assert_allclose(result.p_value[:, 0], [p, p, p], rtol=1e-13) # One source per profile: nothing to adjust. - assert_allclose(result.adjusted_p_values, result.p_values, rtol=0) + assert_allclose(result.adjusted_p_value, result.p_value, rtol=0) assert_array_equal(result.n_targets, [[2], [2], [2]]) def test_mlm_of_a_network_of_one_source_is_ulm() -> None: values = np.array([Y, 2.0 * Y + 7.0, -Y]) - single = mlm(values, PAIR, gene_ids=FOUR, min_targets=2) + single = mlm(values, PAIR, feature_ids=FOUR, min_targets=2) assert_allclose(single.scores[:, 0], -13.0 / math.sqrt(108.0) * np.array( [1.0, 1.0, -1.0]), rtol=1e-13) # fmt: skip assert_allclose(single.estimates[:, 0], [-13 / 11, -26 / 11, 13 / 11], rtol=1e-13) @@ -229,9 +234,9 @@ def test_mlm_of_a_network_of_one_source_is_ulm() -> None: def test_the_weighted_mean_and_sum_by_hand() -> None: values = np.array([Y]) - mean = weighted_aggregate(values, PAIR, gene_ids=FOUR, min_targets=2, + mean = weighted_aggregate(values, PAIR, feature_ids=FOUR, min_targets=2, n_permutations=0) # fmt: skip - total = weighted_aggregate(values, PAIR, gene_ids=FOUR, min_targets=2, + total = weighted_aggregate(values, PAIR, feature_ids=FOUR, min_targets=2, statistic="sum", n_permutations=0) # fmt: skip # (1 * 1 + 2 * 2) / (|1| + |2|) and 1 * 1 + 2 * 2. assert_allclose(mean.scores, [[5.0 / 3.0]], rtol=1e-15) @@ -245,7 +250,7 @@ def test_ulm_with_weights_of_one_sign_is_the_two_sample_t_test() -> None: y = rng.normal(size=(2, 30)) genes = [f"g{j}" for j in range(30)] targets = genes[:7] - result = ulm(y, {"set": targets}, gene_ids=genes) + result = ulm(y, {"set": targets}, feature_ids=genes) inside, outside = y[:, :7], y[:, 7:] pooled = np.sqrt( (((inside - inside.mean(axis=1, keepdims=True)) ** 2).sum(axis=1) @@ -256,7 +261,7 @@ def test_ulm_with_weights_of_one_sign_is_the_two_sample_t_test() -> None: ) assert_allclose(result.scores[:, 0], t, rtol=1e-12) # A GeneSets is a network of the same sets. - as_sets = ulm(y, GeneSets({"set": targets}), gene_ids=genes) + as_sets = ulm(y, GeneSets({"set": targets}), feature_ids=genes) assert_array_equal(as_sets.scores, result.scores) @@ -274,12 +279,12 @@ def test_permutations_estimate_the_exact_null_distribution() -> None: observed = (0.3 - 1.0) / 1.5 exact = (observed - nulls.mean()) / nulls.std(ddof=0) tails = min(np.mean(nulls >= observed), np.mean(nulls <= observed)) - result = weighted_aggregate(y, network, gene_ids=genes, min_targets=2, + result = weighted_aggregate(y, network, feature_ids=genes, min_targets=2, n_permutations=5000, rng=1) # fmt: skip assert_allclose(result.estimates, [[observed]], rtol=1e-15) # Monte Carlo error: the mean and sd of 5,000 draws, about 2% of the score. assert_allclose(result.scores[0, 0], exact, rtol=0.05) - assert abs(result.p_values[0, 0] - min(1.0, 2 * tails)) < 0.03 + assert abs(result.p_value[0, 0] - min(1.0, 2 * tails)) < 0.03 # Invariants. @@ -306,17 +311,19 @@ def test_permutations_estimate_the_exact_null_distribution() -> None: @pytest.mark.parametrize("function", FUNCTIONS) def test_the_order_of_genes_sources_and_samples_does_not_matter(function) -> None: - base = function(VALUES, NETWORK, gene_ids=GENES) + base = function(VALUES, NETWORK, feature_ids=GENES) genes = RNG.permutation(60) - shuffled = function(VALUES[:, genes], NETWORK, gene_ids=[GENES[j] for j in genes]) + shuffled = function( + VALUES[:, genes], NETWORK, feature_ids=[GENES[j] for j in genes] + ) assert_allclose(shuffled.scores, base.scores, rtol=1e-12, atol=1e-13) # The sources keep the order of the network. reversed_network = Network(NETWORK.edges.take(np.arange(len(NETWORK.edges))[::-1])) - flipped = function(VALUES, reversed_network, gene_ids=GENES) + flipped = function(VALUES, reversed_network, feature_ids=GENES) assert flipped.sources == base.sources[::-1] assert_allclose(flipped.scores[:, ::-1], base.scores, rtol=1e-12, atol=1e-13) rows = [2, 0, 3, 1] - by_rows = function(VALUES[rows], NETWORK, gene_ids=GENES) + by_rows = function(VALUES[rows], NETWORK, feature_ids=GENES) assert_allclose(by_rows.scores, base.scores[rows], rtol=1e-12, atol=1e-13) @@ -325,8 +332,8 @@ def test_negating_a_sources_weights_negates_its_activity(function) -> None: edges = NETWORK.edges weights = np.where(edges["source"] == "S2", -edges["weight"], edges["weight"]) negated = Network(edges.with_columns({"weight": weights})) - base = function(VALUES, NETWORK, gene_ids=GENES) - flipped = function(VALUES, negated, gene_ids=GENES) + base = function(VALUES, NETWORK, feature_ids=GENES) + flipped = function(VALUES, negated, feature_ids=GENES) column = base.sources.index("S2") assert_allclose(flipped.scores[:, column], -base.scores[:, column], rtol=1e-12) assert_allclose(flipped.estimates[:, column], -base.estimates[:, column], @@ -337,11 +344,11 @@ def test_negating_a_sources_weights_negates_its_activity(function) -> None: def test_scaling_a_profile_scales_ulms_estimates_and_keeps_its_t() -> None: - base = ulm(VALUES, NETWORK, gene_ids=GENES) - scaled = ulm(3.0 * VALUES - 5.0, NETWORK, gene_ids=GENES) + base = ulm(VALUES, NETWORK, feature_ids=GENES) + scaled = ulm(3.0 * VALUES - 5.0, NETWORK, feature_ids=GENES) assert_allclose(scaled.estimates, 3.0 * base.estimates, rtol=1e-12) assert_allclose(scaled.scores, base.scores, rtol=1e-11) - assert_allclose(scaled.p_values, base.p_values, rtol=1e-10) + assert_allclose(scaled.p_value, base.p_value, rtol=1e-10) def test_min_targets_and_missing_values() -> None: @@ -349,26 +356,26 @@ def test_min_targets_and_missing_values() -> None: targets = list(NETWORK["S0"]) values = VALUES.copy() values[0, [GENES.index(gene) for gene in targets[:3]]] = np.nan - result = ulm(values, NETWORK, gene_ids=GENES, min_targets=6) + result = ulm(values, NETWORK, feature_ids=GENES, min_targets=6) column = result.sources.index("S0") assert result.n_targets[0, column] == 5 and result.n_targets[1, column] == 8 assert np.isnan(result.scores[0, column]) and np.isfinite(result.scores[1, column]) # The first sample scored alone over its observed genes gives the same score. alone = ulm(values[:1, ~np.isnan(values[0])], NETWORK, - gene_ids=[g for g, v in zip(GENES, values[0], strict=True) + feature_ids=[g for g, v in zip(GENES, values[0], strict=True) if not np.isnan(v)], min_targets=5) # fmt: skip - again = ulm(values, NETWORK, gene_ids=GENES, min_targets=5) + again = ulm(values, NETWORK, feature_ids=GENES, min_targets=5) assert_allclose(again.scores[0], alone.scores[0], rtol=1e-12) with pytest.raises(ValueError, match="no source of the network has 9 targets"): - ulm(values, NETWORK, gene_ids=GENES, min_targets=9) + ulm(values, NETWORK, feature_ids=GENES, min_targets=9) def test_genes_zero_everywhere_and_without_ids_are_left_out() -> None: values = np.hstack([VALUES, np.zeros((4, 3))]) genes = [*GENES, "Z1", "Z2", None] zeros = ulm(values, Network({**NETWORK, "Z": {"Z1": 1.0, "Z2": 1.0}}), - gene_ids=genes) # fmt: skip - base = ulm(VALUES, NETWORK, gene_ids=GENES) + feature_ids=genes) # fmt: skip + base = ulm(VALUES, NETWORK, feature_ids=GENES) assert "Z" not in zeros.sources assert_allclose(zeros.scores, base.scores, rtol=1e-12) @@ -384,8 +391,8 @@ def test_edges_of_weight_zero_are_not_targets() -> None: } ) ) - base = ulm(VALUES, NETWORK, gene_ids=GENES) - padded_result = ulm(VALUES, padded, gene_ids=GENES) + base = ulm(VALUES, NETWORK, feature_ids=GENES) + padded_result = ulm(VALUES, padded, feature_ids=GENES) assert_allclose(padded_result.scores, base.scores, rtol=1e-12) assert_array_equal(padded_result.n_targets, base.n_targets) @@ -408,15 +415,15 @@ def _cube() -> Cube: ) # fmt: skip -def test_a_cubes_group_is_scored_as_its_log_cpm() -> None: +def test_a_cubes_group_is_scored_as_its_log2_cpm() -> None: cube = _cube() result = ulm(cube, NETWORK, group="B") # Its samples with at least 10 cells, over the genes filterByExpr keeps. counts = cube.layer("counts", "B")[:5] - keep = filter_by_expression(counts, group=np.zeros(5)) - values = log_cpm(counts[:, keep], norm_factors=tmm_factors(counts[:, keep]), + keep = filter_by_expression(counts, sample_groups=np.zeros(5)) + values = log2_cpm(counts[:, keep], norm_factors=tmm_factors(counts[:, keep]), prior_count=2.0) # fmt: skip - expected = ulm(values, NETWORK, gene_ids=np.array(GENES)[keep].tolist()) + expected = ulm(values, NETWORK, feature_ids=np.array(GENES)[keep].tolist()) assert_allclose(result.scores, expected.scores, rtol=1e-12) assert result.profiles["profile_id"].tolist() == [f"S{i}" for i in range(5)] assert result.profiles["condition"].tolist() == ["ctrl", "stim"] * 2 + ["ctrl"] @@ -463,7 +470,7 @@ def test_activities_of_a_cube_record_the_cube_they_read() -> None: assert json.loads(json.dumps(record)) == record assert mlm(cube, NETWORK, group="B").provenance["method"] == "mlm" # Activities of values or of statistics name no cube. - of_values = ulm(VALUES, NETWORK, gene_ids=GENES) + of_values = ulm(VALUES, NETWORK, feature_ids=GENES) assert of_values.provenance is None tested = limma_voom(cube, Design(cube.samples, "~ condition"), contrasts={"stim": "conditionstim"}, min_cells=0) # fmt: skip @@ -503,11 +510,11 @@ def test_each_group_and_contrast_of_a_result_is_a_profile() -> None: def test_the_record_and_its_table() -> None: - result = ulm(VALUES, NETWORK, gene_ids=GENES, sample_ids=["a", "b", "c", "d"]) + result = ulm(VALUES, NETWORK, feature_ids=GENES, sample_ids=["a", "b", "c", "d"]) assert repr(result) == "Activities(method='ulm', n_profiles=4, n_sources=6)" assert result.sources == NETWORK.sources - for array in (result.scores, result.estimates, result.p_values, - result.adjusted_p_values, result.n_targets): # fmt: skip + for array in (result.scores, result.estimates, result.p_value, + result.adjusted_p_value, result.n_targets): # fmt: skip assert not array.flags.writeable table = result.table() assert table.columns == ("source", "profile_id", "n_targets", "score", @@ -523,34 +530,34 @@ def test_mlm_refuses_sources_it_cannot_tell_apart() -> None: twins = Network({**{s: dict(NETWORK[s]) for s in NETWORK}, "TWIN": dict(NETWORK["S1"])}) # fmt: skip with pytest.raises(ValueError, match="cannot tell the sources 'TWIN' apart"): - mlm(VALUES, twins, gene_ids=GENES) + mlm(VALUES, twins, feature_ids=GENES) # Three sources of one target each and an intercept use up four genes. with pytest.raises(ValueError, match="more genes than sources"): - mlm(np.array([Y]), {"A": ["g1"], "B": ["g2"], "C": ["g3"]}, gene_ids=FOUR, + mlm(np.array([Y]), {"A": ["g1"], "B": ["g2"], "C": ["g3"]}, feature_ids=FOUR, min_targets=1) # fmt: skip def test_messages() -> None: with pytest.raises(ValueError, match="no target of the network is among the genes"): - ulm(VALUES, {"A": ["X1", "X2"]}, gene_ids=GENES) + ulm(VALUES, {"A": ["X1", "X2"]}, feature_ids=GENES) with pytest.raises(TypeError, match="network must be a Network"): - ulm(VALUES, 3, gene_ids=GENES) - with pytest.raises(ValueError, match="gene_ids is needed with an array"): + ulm(VALUES, 3, feature_ids=GENES) + with pytest.raises(ValueError, match="feature_ids is needed with an array"): ulm(VALUES, NETWORK) with pytest.raises(ValueError, match="contrast applies to the statistics"): - ulm(VALUES, NETWORK, gene_ids=GENES, contrast="stim") - with pytest.raises(ValueError, match="gene_ids applies to an array or a cube"): - ulm({"G1": 1.0, "G2": 2.0}, NETWORK, gene_ids=GENES) + ulm(VALUES, NETWORK, feature_ids=GENES, contrast="stim") + with pytest.raises(ValueError, match="feature_ids applies to an array or a cube"): + ulm({"G1": 1.0, "G2": 2.0}, NETWORK, feature_ids=GENES) with pytest.raises(ValueError, match="min_targets must be at least 1"): - ulm(VALUES, NETWORK, gene_ids=GENES, min_targets=0) + ulm(VALUES, NETWORK, feature_ids=GENES, min_targets=0) with pytest.raises(ValueError, match="n_permutations must be 0, for no"): - weighted_aggregate(VALUES, NETWORK, gene_ids=GENES, n_permutations=1) + weighted_aggregate(VALUES, NETWORK, feature_ids=GENES, n_permutations=1) with pytest.raises(ValueError, match="statistic must be one of 'mean', 'sum'"): - weighted_aggregate(VALUES, NETWORK, gene_ids=GENES, statistic="median") + weighted_aggregate(VALUES, NETWORK, feature_ids=GENES, statistic="median") with pytest.raises(ValueError, match="To score a samples x genes table"): ulm(Table({"G1": [1.0, 2.0], "G2": [3.0, 4.0]}), NETWORK) with pytest.raises(ValueError, match="data holds infinite values"): - ulm(np.where(VALUES > 2, np.inf, VALUES), NETWORK, gene_ids=GENES) + ulm(np.where(VALUES > 2, np.inf, VALUES), NETWORK, feature_ids=GENES) # The heatmap. @@ -560,7 +567,7 @@ def test_the_heatmap_draws_the_scores_of_the_top_sources() -> None: pytest.importorskip("matplotlib") import matplotlib.figure - result = ulm(VALUES, NETWORK, gene_ids=GENES, sample_ids=["a", "b", "c", "d"]) + result = ulm(VALUES, NETWORK, feature_ids=GENES, sample_ids=["a", "b", "c", "d"]) ax = plot_activities(result, top=3, color_by=["x", "y", "x", "y"], ax=matplotlib.figure.Figure().subplots()) # fmt: skip cells = next(image for image in ax.get_images() if image.get_gid() == "scores") diff --git a/tests/test_anndata_conversion.py b/tests/test_anndata_conversion.py index 349bb01..cf2b029 100644 --- a/tests/test_anndata_conversion.py +++ b/tests/test_anndata_conversion.py @@ -18,12 +18,20 @@ from numpy.testing import assert_array_equal import cytepool -from cytepool.cube import Cube, from_bulk, from_cells +from cytepool import ( + Cube, + Design, + from_anndata, + from_bulk, + from_cells, + from_mudata, + limma_voom, + read_cube, + to_anndata, + to_mudata, + write_cube, +) from cytepool.cube.provenance import describe_provenance -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.io import read_cube, write_cube -from cytepool.io.anndata import from_anndata, from_mudata, to_anndata, to_mudata # anndata and mudata warn about their own use of pandas options that pandas 4 removes. pytestmark = [ @@ -67,9 +75,9 @@ def varied_cube() -> Cube: counts, samples, groups = _cells() return from_cells( counts, - samples=samples, - groups=groups, - sample_table={ + sample_labels=samples, + group_labels=groups, + samples={ "sample_id": ["S1", "S2", "S3", "S4"], "collected": np.array( ["2024-01-05", "NaT", "2024-03-01", "2024-04-11"], "datetime64[D]" @@ -82,7 +90,7 @@ def varied_cube() -> Cube: "notes": [None, None, None, None], "..": [1, 2, 3, 4], }, - group_table={ + groups={ "group_id": ["B", "T", "NK", "Plasma"], "parent": ["L", "L", None, "L"], "since": np.full(4, "NaT", dtype="datetime64[s]"), @@ -346,8 +354,8 @@ def cells() -> dict[str, Any]: def _pooled(cells: dict[str, Any], **tables: Any) -> Cube: return from_cells( cells["counts"], - samples=cells["sample"], - groups=cells["group"], + sample_labels=cells["sample"], + group_labels=cells["group"], features={"feature_id": cells["feature_id"], "symbol": cells["symbol"]}, layers=["counts", "detected"], **tables, @@ -367,7 +375,7 @@ def test_scanpy_aggregate_imports_as_the_cube_of_its_cells( ) -> None: aggregated = ad.read_h5ad(DATA / "scanpy_counts.h5ad") - cube = from_anndata(aggregated, sample="sample", group="group") + cube = from_anndata(aggregated, sample_column="sample", group_column="group") # Counts, detected cells and cell numbers, ids and tables: the same cube. assert cube.fingerprint() == _pooled(cells).fingerprint() @@ -386,12 +394,12 @@ def test_decoupler_pseudobulk_imports_with_the_metadata_of_samples_and_groups( # not be its own. assert (pseudobulk.obs["psbulk_cells"] == 0).sum() == 1 - cube = from_anndata(pseudobulk, sample="sample", group="group") + cube = from_anndata(pseudobulk, sample_column="sample", group_column="group") # Detected cells come from decoupler's fractions times its cell numbers. samples = _metadata(cells, "sample", "condition") groups = _metadata(cells, "group", "lineage") - expected = _pooled(cells, sample_table=samples, group_table=groups) + expected = _pooled(cells, samples=samples, groups=groups) assert cube.fingerprint() == expected.fingerprint() assert cube.provenance["construction"]["obs_columns"] == { "samples": ["condition"], @@ -413,7 +421,7 @@ def test_liana_views_import_with_the_cell_numbers_of_the_cells( # Feature ids without LIANA's ":" prefix; groups sorted, as from_cells # sorts them; the condition of each sample from the MuData's obs. - expected = _pooled(cells, sample_table=_metadata(cells, "sample", "condition")) + expected = _pooled(cells, samples=_metadata(cells, "sample", "condition")) assert cube.fingerprint() == expected.fingerprint() assert cube.provenance["construction"]["layout"] == "liana.multi.adata_to_views" # Without fractions, counts alone come without cell numbers... and so cannot @@ -426,12 +434,14 @@ def test_an_imported_cube_feeds_a_design_and_limma_voom( ad: Any, cells: dict[str, Any] ) -> None: imported = from_anndata( - ad.read_h5ad(DATA / "decoupler.h5ad"), sample="sample", group="group" + ad.read_h5ad(DATA / "decoupler.h5ad"), + sample_column="sample", + group_column="group", ) pooled = _pooled( cells, - sample_table=_metadata(cells, "sample", "condition"), - group_table=_metadata(cells, "group", "lineage"), + samples=_metadata(cells, "sample", "condition"), + groups=_metadata(cells, "group", "lineage"), ) def analysis(cube: Cube) -> Any: @@ -458,16 +468,24 @@ def test_means_variances_and_medians_are_refused(ad: Any) -> None: ValueError, match=r"no layer 'sum'; its layers are \['mean', 'var'\], which hold means and variances", ): - from_anndata(means, sample="sample", group="group") + from_anndata(means, sample_column="sample", group_column="group") for source, statistic in (("mean", "means"), ("var", "variances")): with pytest.raises( ValueError, match=f"fill with {statistic}. A cube holds sums" ): from_anndata( - means, sample="sample", group="group", layers={"counts": source} + means, + sample_column="sample", + group_column="group", + layers={"counts": source}, ) with pytest.raises(ValueError, match="'median', which .* fill with medians"): - from_anndata(means, sample="sample", group="group", layers={"counts": "median"}) + from_anndata( + means, + sample_column="sample", + group_column="group", + layers={"counts": "median"}, + ) def test_decoupler_means_are_refused_because_x_adds_up_to_totals_over_cells( @@ -479,10 +497,12 @@ def test_decoupler_means_are_refused_because_x_adds_up_to_totals_over_cells( # mode; X adds up to them divided by the cells of each row. message = r"X holds means: at row 0 \('S\d_\w+'\) it adds up to [\d.]+, the" with pytest.raises(ValueError, match=message): - from_anndata(means, sample="sample", group="group") + from_anndata(means, sample_column="sample", group_column="group") # Whatever X is read as. with pytest.raises(ValueError, match="X holds means"): - from_anndata(means, sample="sample", group="group", layers={"counts": "X"}) + from_anndata( + means, sample_column="sample", group_column="group", layers={"counts": "X"} + ) def _medians(cells: dict[str, Any], samples: Any, groups: Any) -> np.ndarray: @@ -509,7 +529,7 @@ def test_medians_are_refused_where_a_cell_detects_a_feature_and_x_is_zero( message = "X does not hold sums of the counts of its cells: at .* psbulk_props says" with pytest.raises(ValueError, match=message): - from_anndata(pseudobulk, sample="sample", group="group") + from_anndata(pseudobulk, sample_column="sample", group_column="group") crosstab = pd.crosstab(cells["sample"], cells["group"]) with pytest.raises(ValueError, match=message): from_mudata(views, n_cells=crosstab) @@ -523,12 +543,14 @@ def test_decoupler_pseudobulk_of_fewer_features_still_imports( # As decoupler.pp.filter_by_expr leaves it: fewer features, and psbulk_counts # still the totals over all of them. - cube = from_anndata(pseudobulk[:, kept], sample="sample", group="group") + cube = from_anndata( + pseudobulk[:, kept], sample_column="sample", group_column="group" + ) expected = _pooled( cells, - sample_table=_metadata(cells, "sample", "condition"), - group_table=_metadata(cells, "group", "lineage"), + samples=_metadata(cells, "sample", "condition"), + groups=_metadata(cells, "group", "lineage"), ) assert cube.fingerprint() == expected.select(features=kept).fingerprint() @@ -541,7 +563,7 @@ def test_x_that_adds_up_to_more_than_its_counts_is_refused(ad: Any) -> None: r"X adds up to [\d.e+]+ at row 0 \('S1_\w+'\), more than the [\d.]+ counts" ) with pytest.raises(ValueError, match=message): - from_anndata(pseudobulk, sample="sample", group="group") + from_anndata(pseudobulk, sample_column="sample", group_column="group") # Long AnnData made by hand. @@ -574,15 +596,15 @@ def test_given_layers_cell_numbers_and_tables(ad: Any, pd: Any) -> None: cube = from_anndata( adata, - sample="donor", - group="cell_type", + sample_column="donor", + group_column="cell_type", layers={"counts": "sum"}, n_cells="n", - sample_table={"sample_id": ["D2", "D1", "D3"], "age": [30, 40, 50]}, + samples={"sample_id": ["D2", "D1", "D3"], "age": [30, 40, 50]}, source={"study": "hand-made"}, ) - # The rows of sample_table set the order; D3 has no cells. + # The rows of the sample table set the order; D3 has no cells. assert cube.samples["sample_id"].tolist() == ["D2", "D1", "D3"] assert_array_equal(cube.n_cells, [[1, 4], [2, 3], [0, 0]]) assert_array_equal(cube.layer("counts", "B"), [[3, 1], [4, 0], [0, 0]]) @@ -618,7 +640,11 @@ def test_obs_columns_go_to_the_table_they_are_constant_in(ad: Any, pd: Any) -> N ) cube = from_anndata( - adata, sample="donor", group="cell_type", layers={"counts": "sum"}, n_cells="n" + adata, + sample_column="donor", + group_column="cell_type", + layers={"counts": "sum"}, + n_cells="n", ) assert cube.samples.equals( @@ -641,7 +667,11 @@ def test_inference_keeps_the_ids_and_the_types_a_table_holds(ad: Any, pd: Any) - ) cube = from_anndata( - adata, sample="donor", group="cell_type", layers={"counts": "sum"}, n_cells="n" + adata, + sample_column="donor", + group_column="cell_type", + layers={"counts": "sum"}, + n_cells="n", ) assert cube.samples["sample_id"].tolist() == ["D1", "D2"] @@ -659,12 +689,22 @@ def test_feature_columns_that_a_cube_holds_otherwise_are_refused( adata = _long(ad, pd, ROWS, SUMS) adata.var["symbol"] = [1, 2] with pytest.raises(TypeError, match="'symbol' of var has dtype int64, but a cube"): - from_anndata(adata, sample="donor", group="cell_type", layers={"counts": "sum"}) + from_anndata( + adata, + sample_column="donor", + group_column="cell_type", + layers={"counts": "sum"}, + ) adata.var["symbol"] = ["CD3E", "MS4A1"] adata.var["seen"] = pd.to_datetime(["2024-01-01", "2024-01-02"]).tz_localize("UTC") with pytest.raises(TypeError) as raised: - from_anndata(adata, sample="donor", group="cell_type", layers={"counts": "sum"}) + from_anndata( + adata, + sample_column="donor", + group_column="cell_type", + layers={"counts": "sum"}, + ) assert "drop the column to read the data without it" in raised.value.__notes__[0] @@ -675,8 +715,8 @@ def test_integer_labels_become_text_in_numeric_order(ad: Any, pd: Any) -> None: cube = from_anndata( adata, - sample="donor", - group="cell_type", + sample_column="donor", + group_column="cell_type", layers={"counts": "sum"}, n_cells=crosstab, ) @@ -692,10 +732,10 @@ def test_counts_that_a_layout_gives_must_be_whole_numbers(ad: Any, pd: Any) -> N adata = _long(ad, pd, ROWS, sums, n_obs_aggregated=[2, 1, 3, 4]) with pytest.raises(ValueError, match=r"holds 2.5 at row 2 \('r2'\), feature 'G1'"): - from_anndata(adata, sample="donor", group="cell_type") + from_anndata(adata, sample_column="donor", group_column="cell_type") # Counts that are not whole numbers, said so. cube = from_anndata( - adata, sample="donor", group="cell_type", layers={"counts": "sum"} + adata, sample_column="donor", group_column="cell_type", layers={"counts": "sum"} ) assert cube.layer("counts").dtype == np.float64 assert cube.layer("counts", "T")[0, 1] == 2.5 @@ -709,11 +749,15 @@ def test_whole_counts_become_the_narrowest_integers_that_hold_them( small = _long(ad, pd, ROWS, sums, n_obs_aggregated=[2, 1, 3, 4]) assert ( - from_anndata(small, sample="donor", group="cell_type").layer("counts").dtype + from_anndata(small, sample_column="donor", group_column="cell_type") + .layer("counts") + .dtype == np.uint32 ) assert ( - from_anndata(large, sample="donor", group="cell_type").layer("counts").dtype + from_anndata(large, sample_column="donor", group_column="cell_type") + .layer("counts") + .dtype == np.uint64 ) @@ -728,10 +772,10 @@ def test_whole_floats_beyond_2_to_the_53_are_not_counts_of_a_layout( with pytest.raises( ValueError, match=r"feature 'G0', at or above 2\*\*53, beyond which float64" ): - from_anndata(adata, sample="donor", group="cell_type") + from_anndata(adata, sample_column="donor", group_column="cell_type") # Said to be counts, they are float64 counts. cube = from_anndata( - adata, sample="donor", group="cell_type", layers={"counts": "sum"} + adata, sample_column="donor", group_column="cell_type", layers={"counts": "sum"} ) assert cube.layer("counts").dtype == np.float64 @@ -739,7 +783,7 @@ def test_whole_floats_beyond_2_to_the_53_are_not_counts_of_a_layout( def test_counts_must_sum_over_the_cells_that_detected_counts(ad: Any, pd: Any) -> None: adata = _long(ad, pd, ROWS, SUMS, n_obs_aggregated=[2, 1, 3, 4]) adata.layers["count_nonzero"] = np.array([[2, 0], [1, 1], [0, 2], [3, 3]]) - cube = from_anndata(adata, sample="donor", group="cell_type") + cube = from_anndata(adata, sample_column="donor", group_column="cell_type") assert_array_equal(cube.layer("detected", "T"), [[0, 2], [3, 3]]) # A cell detects the feature, but the sum is 0: the sums are not of these cells. @@ -748,16 +792,16 @@ def test_counts_must_sum_over_the_cells_that_detected_counts(ad: Any, pd: Any) - ValueError, match="at group 'B', sample 'D1', feature 'G1' the counts are 0 and 1 cells", ): - from_anndata(adata, sample="donor", group="cell_type") + from_anndata(adata, sample_column="donor", group_column="cell_type") # 3 cells with a positive count sum to 2: not a sum of whole counts. adata.layers["count_nonzero"][0, 1] = 0 adata.layers["count_nonzero"][2, 1] = 3 with pytest.raises(ValueError, match="the counts are 2 and 3 cells detect"): - from_anndata(adata, sample="donor", group="cell_type") + from_anndata(adata, sample_column="donor", group_column="cell_type") # Counts that are not whole numbers can sum to less than their cells. adata.layers["sum"] = SUMS / 4.0 layers = {"counts": "sum", "detected": "count_nonzero"} - from_anndata(adata, sample="donor", group="cell_type", layers=layers) + from_anndata(adata, sample_column="donor", group_column="cell_type", layers=layers) def test_whole_sums_of_counts_that_are_not_whole_are_read_when_said_so( @@ -767,8 +811,8 @@ def test_whole_sums_of_counts_that_are_not_whole_are_read_when_said_so( # the number of cells detecting the feature. cube = from_cells( np.array([[0.5, 1.0], [0.5, 0.0], [1.5, 2.0], [0.5, 1.0]]), - samples=["a", "a", "b", "b"], - groups=["g"] * 4, + sample_labels=["a", "a", "b", "b"], + group_labels=["g"] * 4, layers=["counts", "detected"], ) adata = to_anndata(cube) @@ -777,7 +821,11 @@ def test_whole_sums_of_counts_that_are_not_whole_are_read_when_said_so( layers = {"counts": "counts", "detected": "detected"} back = from_anndata( - adata, sample="sample_id", group="group_id", layers=layers, n_cells="n_cells" + adata, + sample_column="sample_id", + group_column="group_id", + layers=layers, + n_cells="n_cells", ) assert_array_equal(back.layer("counts", "g"), [[1, 1], [2, 3]]) @@ -794,7 +842,11 @@ def test_fractions_are_read_to_the_precision_they_are_stored_in( # 7 / 19 in float32 is 0.36842105, which times 19 is 7.0000001. cube = from_anndata( - adata, sample="donor", group="cell_type", layers=layers, n_cells="n" + adata, + sample_column="donor", + group_column="cell_type", + layers=layers, + n_cells="n", ) assert_array_equal(cube.layer("detected", "B"), detected[:2]) # float16 holds too few digits for the cells of a row of 1,000 cells. @@ -804,7 +856,11 @@ def test_fractions_are_read_to_the_precision_they_are_stored_in( ValueError, match=r"fractions in float16, too few digits .* 1000 cells of row 0" ): from_anndata( - adata, sample="donor", group="cell_type", layers=layers, n_cells="n" + adata, + sample_column="donor", + group_column="cell_type", + layers=layers, + n_cells="n", ) @@ -812,12 +868,17 @@ def test_entries_without_rows_need_cell_numbers(ad: Any, pd: Any) -> None: adata = _long(ad, pd, ROWS[:3], SUMS[:3]) with pytest.raises(ValueError, match="sample 'D2' has no row in group 'T', and"): - from_anndata(adata, sample="donor", group="cell_type", layers={"counts": "sum"}) + from_anndata( + adata, + sample_column="donor", + group_column="cell_type", + layers={"counts": "sum"}, + ) crosstab = pd.DataFrame({"B": [1, 1], "T": [2, 0]}, index=["D1", "D2"]) cube = from_anndata( adata, - sample="donor", - group="cell_type", + sample_column="donor", + group_column="cell_type", layers={"counts": "sum"}, n_cells=crosstab, ) @@ -829,8 +890,8 @@ def test_entries_without_rows_need_cell_numbers(ad: Any, pd: Any) -> None: ): from_anndata( adata, - sample="donor", - group="cell_type", + sample_column="donor", + group_column="cell_type", layers={"counts": "sum"}, n_cells=crosstab, ) @@ -846,8 +907,8 @@ def test_a_table_of_cell_numbers_names_every_sample_and_group(ad: Any, pd: Any) with pytest.raises(ValueError, match=message + r" and has \['D9'\] besides"): from_anndata( adata, - sample="donor", - group="cell_type", + sample_column="donor", + group_column="cell_type", layers={"counts": "sum"}, n_cells=crosstab, ) @@ -856,8 +917,8 @@ def test_a_table_of_cell_numbers_names_every_sample_and_group(ad: Any, pd: Any) def test_without_cell_numbers_every_entry_needs_a_row(ad: Any, pd: Any) -> None: cube = from_anndata( _long(ad, pd, ROWS, SUMS), - sample="donor", - group="cell_type", + sample_column="donor", + group_column="cell_type", layers={"counts": "sum"}, ) @@ -875,7 +936,11 @@ def test_fractions_give_detected_cells_only_when_they_are_whole_numbers( layers = {"counts": "sum", "detected": "psbulk_props"} cube = from_anndata( - adata, sample="donor", group="cell_type", layers=layers, n_cells="n" + adata, + sample_column="donor", + group_column="cell_type", + layers=layers, + n_cells="n", ) assert_array_equal(cube.layer("detected", "T"), [[0, 2], [3, 1]]) @@ -884,29 +949,39 @@ def test_fractions_give_detected_cells_only_when_they_are_whole_numbers( ValueError, match=r"fraction 0.3 of the 4 cells of row 3 \('r3'\)" ): from_anndata( - adata, sample="donor", group="cell_type", layers=layers, n_cells="n" + adata, + sample_column="donor", + group_column="cell_type", + layers=layers, + n_cells="n", ) with pytest.raises( ValueError, match="give 'detected' only with the number of cells" ): - from_anndata(adata, sample="donor", group="cell_type", layers=layers) + from_anndata( + adata, sample_column="donor", group_column="cell_type", layers=layers + ) @pytest.mark.parametrize( ("change", "error", "message"), [ - ({"sample": None}, ValueError, "name the obs columns that hold the sample"), ( - {"group": "donor"}, + {"sample_column": None}, + ValueError, + "name the obs columns that hold the sample", + ), + ( + {"group_column": "donor"}, ValueError, - "sample and group both name the obs column 'donor'", + "sample_column and group_column both name the obs column 'donor'", ), ( - {"group": "lineage"}, + {"group_column": "lineage"}, KeyError, - "group names 'lineage', which is not a column", + "group_column names 'lineage', which is not a column", ), - ({"sample": 3}, TypeError, "sample must name an obs column"), + ({"sample_column": 3}, TypeError, "sample_column must name an obs column"), ({"layers": {"count": "sum"}}, ValueError, "unknown cube layer 'count'"), ( {"layers": {"detected": "sum"}}, @@ -944,8 +1019,8 @@ def test_invalid_arguments_are_refused( ) -> None: adata = _long(ad, pd, ROWS, SUMS) arguments: dict[str, Any] = { - "sample": "donor", - "group": "cell_type", + "sample_column": "donor", + "group_column": "cell_type", "layers": {"counts": "sum"}, } arguments.update(change) @@ -972,8 +1047,8 @@ def test_values_that_cannot_be_counts_are_refused( with pytest.raises(ValueError, match=message): from_anndata( _long(ad, pd, ROWS, values), - sample="donor", - group="cell_type", + sample_column="donor", + group_column="cell_type", layers={"counts": "sum"}, ) @@ -985,7 +1060,10 @@ def test_rows_must_name_each_entry_once_with_its_labels(ad: Any, pd: Any) -> Non match=r"row 2 \('r2'\) and row 4 \('r4'\) are both sample 'D1' in group 'T'", ): from_anndata( - repeated, sample="donor", group="cell_type", layers={"counts": "sum"} + repeated, + sample_column="donor", + group_column="cell_type", + layers={"counts": "sum"}, ) missing = _long(ad, pd, ROWS, SUMS) missing.obs["donor"] = ["D1", None, "D1", "D2"] @@ -993,7 +1071,10 @@ def test_rows_must_name_each_entry_once_with_its_labels(ad: Any, pd: Any) -> Non ValueError, match=r"obs\['donor'\] has a missing label at row 1" ): from_anndata( - missing, sample="donor", group="cell_type", layers={"counts": "sum"} + missing, + sample_column="donor", + group_column="cell_type", + layers={"counts": "sum"}, ) @@ -1003,7 +1084,7 @@ def test_sparse_layers_are_read(ad: Any, pd: Any) -> None: adata.layers["sum"] = sparse.csr_matrix(SUMS) cube = from_anndata( - adata, sample="donor", group="cell_type", layers={"counts": "sum"} + adata, sample_column="donor", group_column="cell_type", layers={"counts": "sum"} ) assert_array_equal(cube.layer("counts", "B"), SUMS[:2]) @@ -1112,7 +1193,7 @@ def test_a_record_table_that_lacks_a_label_says_so(ad: Any) -> None: adata = to_anndata(CUBES["bulk"]) adata.obs["sample_id"] = adata.obs["sample_id"].cat.rename_categories({"z": "w"}) - with pytest.raises(ValueError, match="'w' .* is not in sample_table") as raised: + with pytest.raises(ValueError, match="'w' .* is not in samples") as raised: from_anndata(adata) assert "CytePool recorded in uns['cytepool']" in str(raised.value.__notes__) @@ -1201,7 +1282,7 @@ def test_liana_statistics_give_the_cell_numbers_when_they_hold_them( cube = from_mudata(views) - expected = _pooled(cells, sample_table=_metadata(cells, "sample", "condition")) + expected = _pooled(cells, samples=_metadata(cells, "sample", "condition")) assert cube.fingerprint() == expected.fingerprint() assert cube.provenance["construction"]["n_cells"] == "uns['psbulk_stats']" first = next(iter(views.mod)) @@ -1315,9 +1396,9 @@ def test_the_history_describes_imports_in_plain_terms() -> None: "Built by from_anndata (CytePool 1.0) from 12 profiles of an AnnData, in the", " layout of decoupler.pp.pseudobulk:", " samples: the sorted distinct labels", - " groups: the rows of group_table; the labels were integers, written as " - "decimal", - " text", + " groups: the rows of the group table given; the labels were integers, " + "written", + " as decimal text", " layers: counts from 'X', detected from 'psbulk_props'", ' sample_column: "sample"', ' group_column: "group"', diff --git a/tests/test_combined_p_values_and_replication.py b/tests/test_combined_p_values_and_replication.py index a097a3a..7677820 100644 --- a/tests/test_combined_p_values_and_replication.py +++ b/tests/test_combined_p_values_and_replication.py @@ -24,19 +24,21 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.cube import Cube, CubeCollection -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.meta_analysis import align_study_results, meta_analysis -from cytepool.replication import ( +from cytepool import ( + Cube, + CubeCollection, + Design, ReplicationResult, - differential_expression_replication, + StudyResults, + limma_voom, + replication, ) from cytepool.stats.combined_p_values import ( CombinedPValues, combine_p_values, partial_conjunction, ) +from cytepool.stats.meta_analysis import fit_meta_analysis from cytepool.stats.multiple_testing import ( TrueNullProportion, proportion_of_true_nulls, @@ -216,11 +218,11 @@ def test_p_values_near_1e_300_keep_their_digits() -> None: np.array([1e-300, 1e-300]), method="stouffer", directions=[1.0, -1.0] ) assert float(signed.p_value) == 1.0 - assert float(signed.direction) == 0.0 + assert float(signed.sign) == 0.0 one = combine_p_values(np.array([1e-300]), method="stouffer", directions=[-3.0]) # Through z = 37, p comes back within |log p| = 690 times the rounding of z. assert_allclose(one.p_value, 1e-300, rtol=1e-11) - assert float(one.direction) == -1.0 + assert float(one.sign) == -1.0 # Values derived by hand. @@ -271,14 +273,14 @@ def test_effects_in_opposite_directions_cancel_in_stouffers_method() -> None: assert float(signed.statistic) == 0.0 assert float(signed.p_value) == 1.0 - assert float(signed.direction) == 0.0 - assert float(signed.p_value_up) == float(signed.p_value_down) == 0.5 + assert float(signed.sign) == 0.0 + assert float(signed.up_p_value) == float(signed.down_p_value) == 0.5 # Without directions the two-sided p-values add up, whatever their signs. assert float(combine_p_values(p, method="stouffer").p_value) < 0.01 # In the same direction, the two-sided p-value of Z = 2 z(0.02) / sqrt(2). same = combine_p_values(p, method="stouffer", directions=[2.5, 0.1]) - assert float(same.direction) == 1.0 - assert_allclose(same.p_value, 2.0 * same.p_value_up, rtol=1e-15) + assert float(same.sign) == 1.0 + assert_allclose(same.p_value, 2.0 * same.up_p_value, rtol=1e-15) @pytest.mark.parametrize( @@ -310,7 +312,7 @@ def test_one_study_is_its_own_combination(method: str) -> None: assert_allclose(result.p_value, [0.3, 0.004], rtol=1e-14) # With its direction, one study gives back its two-sided p-value. assert_allclose(signed.p_value, [0.3, 0.004], rtol=1e-14) - assert_array_equal(signed.direction, [-1.0, 1.0]) + assert_array_equal(signed.sign, [-1.0, 1.0]) assert_array_equal(result.n_studies, [1, 1]) @@ -325,7 +327,7 @@ def test_a_feature_that_no_study_tested_has_no_p_value() -> None: p = np.array([[0.1, np.nan], [0.2, np.nan]]) for method in ("fisher", "stouffer", "simes"): result = combine_p_values(p, method=method, directions=np.ones((2, 2))) - assert np.isnan(result.p_value[1]) and np.isnan(result.direction[1]) + assert np.isnan(result.p_value[1]) and np.isnan(result.sign[1]) assert_array_equal(result.n_studies, [2, 0]) assert np.isnan(partial_conjunction(p, 1).p_value[1]) @@ -372,7 +374,7 @@ def test_the_order_of_the_features_does_not_matter() -> None: first = combine_p_values(p, method=method, directions=signs) other = combine_p_values(p[:, order], method=method, directions=signs[:, order]) assert_array_equal(other.p_value, first.p_value[order]) - assert_array_equal(other.direction, first.direction[order]) + assert_array_equal(other.sign, first.sign[order]) def test_turning_every_direction_around_swaps_the_one_sided_p_values() -> None: @@ -382,9 +384,9 @@ def test_turning_every_direction_around_swaps_the_one_sided_p_values() -> None: up = combine_p_values(p, method=method, directions=signs) down = combine_p_values(p, method=method, directions=-signs) assert_array_equal(down.p_value, up.p_value) - assert_array_equal(down.direction, -up.direction) - assert_array_equal(down.p_value_up, up.p_value_down) - assert_array_equal(down.p_value_down, up.p_value_up) + assert_array_equal(down.sign, -up.sign) + assert_array_equal(down.up_p_value, up.down_p_value) + assert_array_equal(down.down_p_value, up.up_p_value) def test_a_direction_is_the_one_sided_p_value_of_its_sign() -> None: @@ -401,9 +403,9 @@ def test_a_direction_is_the_one_sided_p_value_of_its_sign() -> None: p[:, large], method=method, directions=signs[:, large] ) plain = combine_p_values(one_sided[:, large], method=method) - assert_allclose(signed.p_value_up, plain.p_value, rtol=1e-11) + assert_allclose(signed.up_p_value, plain.p_value, rtol=1e-11) reversed_ = combine_p_values(1 - one_sided[:, large], method=method) - assert_allclose(signed.p_value_down, reversed_.p_value, rtol=1e-11) + assert_allclose(signed.down_p_value, reversed_.p_value, rtol=1e-11) def test_weights_scaled_together_change_nothing() -> None: @@ -506,7 +508,7 @@ def test_results_are_read_only_and_say_what_they_hold() -> None: result.p_value[0] = 0.0 assert "with directions" in repr(result) and "method='stouffer'" in repr(result) one = combine_p_values([0.2, 0.3]) - assert one.p_value.shape == () and one.direction is None + assert one.p_value.shape == () and one.sign is None # The proportion of true nulls. @@ -595,7 +597,7 @@ def test_pi0_refuses_p_values_outside_0_and_1_and_none_at_all() -> None: @cache -def cohorts() -> tuple[CubeCollection, dict[str, Any]]: +def cohorts() -> StudyResults: """The three small cohorts of the fixture, aligned and tested by limma_voom.""" data = fixture("replication") cubes = {} @@ -619,53 +621,51 @@ def cohorts() -> tuple[CubeCollection, dict[str, Any]]: contrasts={"stim_vs_ctrl": "conditionstim"}, ) results[study] = limma_voom(cube, design) - return collection, results + return StudyResults(collection, results) @cache -def replication(name: str) -> ReplicationResult: - collection, results = cohorts() - return differential_expression_replication( - collection, results, "T", "stim_vs_ctrl", **SETTINGS[name] - ) +def replication_of(name: str) -> ReplicationResult: + return replication(cohorts(), "T", "stim_vs_ctrl", **SETTINGS[name]) @pytest.mark.parametrize("name", ["T0", "T1"]) def test_replication_matches_r(name: str) -> None: data = fixture("replication") - result = replication(name) - genes, g = result.genes, data[f"{name}/genes"] - counts = ("n_studies", "n_up", "n_down", "n_found", "n_found_up", "n_found_down", "direction") # fmt: skip + result = replication_of(name) + features, g = result.features, data[f"{name}/genes"] + counts = ("n_studies", "n_up", "n_down", "n_found", "n_found_up", "n_found_down", "sign") # fmt: skip for i, column in enumerate(counts): - assert_array_equal(genes[column], g[i], err_msg=column) + assert_array_equal(features[column], g[i], err_msg=column) for i, column in enumerate(("sign_agreement", "p_value", "replication_p_value", "adjusted_p_value", "replication_adjusted_p_value"), 7): # fmt: skip - assert scaled_difference(genes[column], g[i], i > 7) <= EXACT, column + assert scaled_difference(features[column], g[i], i > 7) <= EXACT, column tier = CLOSE if SETTINGS[name]["meta_method"] == "reml" else EXACT for i, column in enumerate(("q_statistic", "q_p_value", "i_squared", "tau_squared")): # fmt: skip - assert scaled_difference(genes[column], data[f"{name}/heterogeneity"][i], i == 1) <= tier, column # fmt: skip + assert scaled_difference(features[column], data[f"{name}/heterogeneity"][i], i == 1) <= tier, column # fmt: skip pairs, r = result.study_pairs, data[f"{name}/pairs"] - assert_array_equal(pairs["n_found"], r[2]) - assert_array_equal(pairs["n_found_in_both"], r[3]) + assert_array_equal(pairs["n_features_found"], r[2]) + assert_array_equal(pairs["n_features_found_in_both"], r[3]) assert scaled_difference(pairs["sign_agreement"], r[4]) <= EXACT known = ~np.isnan(r[5]) # qvalue stops where no p-value reaches its largest lambda assert scaled_difference(pairs["pi1"][known], r[5][known]) <= EXACT assert not np.isnan(pairs["pi1"]).any() f = data[f"{name}/found_in"] - assert_array_equal(result.found_in["n_genes"], f[1]) - assert_array_equal(result.found_in["n_genes_same_direction"], f[2]) + assert_array_equal(result.found_in["n_features"], f[1]) + assert_array_equal(result.found_in["n_features_same_direction"], f[2]) def test_replication_combines_the_aligned_results_as_the_array_functions_do() -> None: - collection, results = cohorts() - result = replication("T0") - aligned = align_study_results(collection, results, "T", "stim_vs_ctrl") + studies = cohorts() + collection = studies.collection + result = replication_of("T0") + aligned = studies.table("T", "stim_vs_ctrl") k, n = collection.n_studies, collection.n_features tested = np.array(aligned["status"].tolist()).reshape(k, n) == "tested" - genes = tested.any(axis=0) + features = tested.any(axis=0) def column(name: str) -> np.ndarray: values = np.asarray(aligned[name]).reshape(k, n) - return np.where(tested, values, np.nan)[:, genes] + return np.where(tested, values, np.nan)[:, features] p, lfc, se = column("p_value"), column("log2_fold_change"), column("standard_error") weights = np.sqrt(result.studies["n_samples"].astype(float)) @@ -675,32 +675,33 @@ def column(name: str) -> np.ndarray: weights=np.where(np.isnan(p), np.nan, weights[:, None]), directions=lfc, ) - assert_array_equal(result.genes["p_value"], combined_p.p_value) - assert_array_equal(result.genes["direction"], combined_p.direction) + assert_array_equal(result.features["p_value"], combined_p.p_value) + assert_array_equal(result.features["sign"], combined_p.sign) replicated = partial_conjunction(p, 2, directions=lfc) - assert_array_equal(result.genes["replication_p_value"], replicated.p_value) - fit = meta_analysis(lfc, se) + assert_array_equal(result.features["replication_p_value"], replicated.p_value) + fit = fit_meta_analysis(lfc, se) several = combined_p.n_studies >= 2 - assert_array_equal(result.genes["tau_squared"][several], fit.tau_squared[several]) - assert np.isnan(result.genes["tau_squared"][~several]).all() + assert_array_equal( + result.features["tau_squared"][several], fit.tau_squared[several] + ) + assert np.isnan(result.features["tau_squared"][~several]).all() # pi1 of the first pair, from the p-values of the genes the first study finds. - found = tested[0, genes] & (column("adjusted_p_value")[0] < 0.05) + found = tested[0, features] & (column("adjusted_p_value")[0] < 0.05) chosen = found & ~np.isnan(p[1]) assert result.study_pairs["pi1"][0] == proportion_of_true_nulls(p[1, chosen]).pi1 - # The genes found in at least k studies, counted from the genes table. + # The genes found in at least k studies, counted from the table of features. for k_studies, count in zip( - result.found_in["n_studies"], result.found_in["n_genes"] + result.found_in["n_studies"], result.found_in["n_features"] ): - assert count == np.count_nonzero(result.genes["n_found"] >= k_studies) + assert count == np.count_nonzero(result.features["n_found"] >= k_studies) def test_pi1_is_at_lambda_one_half_by_default_and_qvalues_with_no_options() -> None: - collection, results = cohorts() + studies = cohorts() + collection = studies.collection def run(**options: Any) -> ReplicationResult: - return differential_expression_replication( - collection, results, "T", "stim_vs_ctrl", **options - ) + return replication(studies, "T", "stim_vs_ctrl", **options) default, half, smoother = ( run(), @@ -712,14 +713,15 @@ def run(**options: Any) -> ReplicationResult: assert default.settings["null_proportion"] == {"lambdas": 0.5} assert smoother.settings["null_proportion"] == {} # Only pi1 changes: every other number is the same. - assert default.genes.equals(smoother.genes) - assert default.study_pairs.select(["discovery", "replication", "n_found"]).equals( - smoother.study_pairs.select(["discovery", "replication", "n_found"]) + assert default.features.equals(smoother.features) + columns = ["discovery", "replication", "n_features_found"] + assert default.study_pairs.select(columns).equals( + smoother.study_pairs.select(columns) ) # pi1 of the first pair from its definition: 1 - pi0, pi0 the share of the # p-values above 0.5 over 0.5, at most 1 (Storey and Tibshirani 2003); and from # qvalue's smoother with {}. - aligned = align_study_results(collection, results, "T", "stim_vs_ctrl") + aligned = studies.table("T", "stim_vs_ctrl") k, n = collection.n_studies, collection.n_features tested = np.array(aligned["status"].tolist()).reshape(k, n) == "tested" p = np.where(tested, np.asarray(aligned["p_value"]).reshape(k, n), np.nan) @@ -732,7 +734,7 @@ def run(**options: Any) -> ReplicationResult: def test_replication_records_its_settings_studies_and_provenance() -> None: - result = replication("T1") + result = replication_of("T1") settings = result.settings assert settings["group"] == "T" and settings["contrast"] == "stim_vs_ctrl" assert settings["method"] == "fisher" and settings["weights"] is None @@ -740,20 +742,47 @@ def test_replication_records_its_settings_studies_and_provenance() -> None: assert "alignment" in result.provenance["collection"] assert_array_equal(result.studies["analysed"], [True, True, True]) assert np.isnan(result.studies["weight"]).all() - assert_array_equal(replication("T0").studies["weight"], np.sqrt(result.studies["n_samples"])) # fmt: skip + assert_array_equal(replication_of("T0").studies["weight"], np.sqrt(result.studies["n_samples"])) # fmt: skip copy = pickle.loads(pickle.dumps(result)) - assert copy.genes.equals(result.genes) and copy.provenance == result.provenance + assert ( + copy.features.equals(result.features) and copy.provenance == result.provenance + ) with pytest.raises(AttributeError, match="immutable"): - result._genes = None # type: ignore[misc] + result._features = None # type: ignore[misc] assert "ReplicationResult of group 'T'" in repr(result) assert max(map(len, repr(result).splitlines())) <= 80 +def test_the_group_and_contrast_of_studies_that_have_one_may_be_left_out() -> None: + # The cohorts tested one contrast, and analysed two groups. + studies = cohorts() + named = replication(studies, "T", "stim_vs_ctrl") + assert replication(studies, "T").features.equals(named.features) + with pytest.raises(ValueError, match="the studies analysed 2 groups; name one"): + replication(studies) + # The same studies with group T alone. + collection = studies.collection + only_t = StudyResults( + collection, + { + study: limma_voom( + collection.aligned_cube(study), result.design, groups=["T"] + ) + for study, result in studies.items() + }, + ) + alone = replication(only_t) + assert alone.settings["group"] == "T" + assert alone.settings["contrast"] == "stim_vs_ctrl" + for name in ("features", "study_pairs", "found_in", "studies"): + assert getattr(alone, name).equals(getattr(named, name)), name + + def test_replication_refuses_invalid_settings() -> None: - collection, results = cohorts() + studies = cohorts() def run(**options: Any) -> None: - differential_expression_replication(collection, results, "T", "stim_vs_ctrl", **options) # fmt: skip + replication(studies, "T", "stim_vs_ctrl", **options) with pytest.raises(ValueError, match="at_least must be from 1 to the 3"): run(at_least=4) @@ -768,12 +797,11 @@ def run(**options: Any) -> None: with pytest.raises(ValueError, match="one value or at least 4"): run(null_proportion={"lambdas": [0.2, 0.5]}) with pytest.raises(KeyError, match="no group 'NK'"): - differential_expression_replication(collection, results, "NK", "stim_vs_ctrl") + replication(studies, "NK", "stim_vs_ctrl") + collection = studies.collection one = CubeCollection({"A": collection.cubes["A"]}, align_groups_by="group_id") with pytest.raises(ValueError, match="replication needs at least two"): - differential_expression_replication( - one, {"A": results["A"]}, "T", "stim_vs_ctrl" - ) + replication(StudyResults(one, {"A": studies["A"]}), "T", "stim_vs_ctrl") # Figures. @@ -783,7 +811,7 @@ def test_the_agreement_heatmap_draws_pi1_of_every_pair() -> None: pyplot = pytest.importorskip("matplotlib.pyplot") from cytepool.plotting import plot_study_agreement - result = replication("T1") + result = replication_of("T1") ax = plot_study_agreement(result) drawn = np.ma.filled(ax.get_images()[0].get_array().astype(float), np.nan) pairs = result.study_pairs @@ -799,7 +827,7 @@ def test_the_agreement_heatmap_draws_pi1_of_every_pair() -> None: with pytest.raises(ValueError, match="measure must be one of"): plot_study_agreement(result, measure="kappa") with pytest.raises(TypeError, match="ReplicationResult"): - plot_study_agreement(result.genes) # type: ignore[arg-type] + plot_study_agreement(result.features) # type: ignore[arg-type] pyplot.close("all") @@ -807,11 +835,11 @@ def test_the_bars_count_the_genes_each_number_of_studies_finds() -> None: pyplot = pytest.importorskip("matplotlib.pyplot") from cytepool.plotting import plot_replicated_genes - result = replication("T0") + result = replication_of("T0") ax = plot_replicated_genes(result) - genes = result.genes - found = np.asarray(genes["n_found"]) - one_way = np.maximum(genes["n_found_up"], genes["n_found_down"]) == found + features = result.features + found = np.asarray(features["n_found"]) + one_way = np.maximum(features["n_found_up"], features["n_found_down"]) == found for gid, mask in (("same direction", one_way), ("both directions", ~one_way)): heights = [bar.get_height() for bar in ax.patches if bar.get_gid() == gid] assert heights == [np.count_nonzero((found == k) & mask) for k in (1, 2, 3)] diff --git a/tests/test_composition.py b/tests/test_composition.py index 28914cb..f1c585c 100644 --- a/tests/test_composition.py +++ b/tests/test_composition.py @@ -21,10 +21,15 @@ import numpy as np import pytest -from cytepool.composition import CompositionResult, propeller -from cytepool.cube import Cube, from_bulk, from_cells -from cytepool.design import Design -from cytepool.table import Table +from cytepool import ( + CompositionResult, + Cube, + Design, + Table, + from_bulk, + from_cells, + propeller, +) DATA = Path(__file__).parent / "data" / "composition_tests" EXACT = 1e-10 @@ -67,10 +72,10 @@ def fixture_cube(case: str) -> Cube: variables = [c for c in columns if "|" not in c and c not in ("n_cells", "groups")] return from_cells( np.zeros((sample_labels.size, 1)), - samples=sample_labels, - groups=group_labels, - sample_table={name: data[f"{case} {name}"] for name in variables}, - group_table={"group_id": groups}, + sample_labels=sample_labels, + group_labels=group_labels, + samples={name: data[f"{case} {name}"] for name in variables}, + groups={"group_id": groups}, layers="counts", ) @@ -171,7 +176,7 @@ def test_two_groups_match_speckle_propeller( assert_matches_speckle(table, f"two_groups | {label}", columns, tolerance) if label == "logit": # Robust moderation gave the cell types prior df of their own. - assert np.ptp(result.summary["prior_df"]) > 0.1 + assert np.ptp(result.summary["df_prior"]) > 0.1 @pytest.mark.parametrize( @@ -229,14 +234,16 @@ def test_only_comparisons_of_levels_have_mean_proportions() -> None: def test_f_test_with_a_covariate_matches_speckle_anova() -> None: cube = fixture_cube("covariate") - result = propeller(cube, covariate_design(), contrasts=[], f_test="group") + result = propeller(cube, covariate_design(), contrasts=[], f_tests="group") - assert result.tables == {} and result.f_test is not None + assert list(result.tables) == ["group"] and result.f_tests == { + "group": ("A", "B", "C") + } columns: dict[str, str | None] = {"baseline_proportion": None} columns |= {f"mean_proportion_{lv}": f"PropMean.group{lv}" for lv in "ABC"} columns |= {"f_statistic": "Fstatistic", **P_VALUES} prefix = "covariate | logit, F-test of group" - assert_matches_speckle(result.f_test, prefix, columns, EXACT) + assert_matches_speckle(result.table("group"), prefix, columns, EXACT) # Values derived by hand. @@ -324,7 +331,9 @@ def test_every_parametrisation_of_a_comparison_gives_the_same_table() -> None: design = covariate_design(formula, contrasts={"B_vs_A": contrast}) assert_tables_equal(propeller(cube, design).table("B_vs_A"), group_means) f_tests = [ - propeller(cube, covariate_design(formula, contrasts={}), f_test="group").f_test + propeller(cube, covariate_design(formula, contrasts={}), f_tests="group").table( + "group" + ) for formula in ("~ 0 + group + age", "~ group + age", "~ age + group") ] assert_tables_equal(f_tests[1], f_tests[0]) @@ -391,19 +400,46 @@ def test_an_f_test_of_two_levels_is_the_two_sided_t_test() -> None: cube = fixture_cube("two_groups") contrasts = {"ctrl_vs_stim": "conditionctrl - conditionstim"} result = propeller( - cube, two_group_design(cube.samples, contrasts=contrasts), f_test="condition" + cube, two_group_design(cube.samples, contrasts=contrasts), f_tests="condition" ) table = result.table("ctrl_vs_stim") - assert result.f_test is not None + assert result.f_tests == {"condition": ("ctrl", "stim")} np.testing.assert_allclose( - result.f_test["f_statistic"], table["t"] ** 2, rtol=1e-12 + result.table("condition")["f_statistic"], table["t"] ** 2, rtol=1e-12 ) # limma caps the df of t-tests at the residual df of all cell types, and not # those of F-tests; here the cap is not reached, so the p-values agree. - df_total = result.summary["df_residual"] + result.summary["prior_df"] + df_total = result.summary["df_residual"] + result.summary["df_prior"] assert np.all(df_total < np.sum(result.summary["df_residual"])) - np.testing.assert_allclose(result.f_test["p_value"], table["p_value"], rtol=1e-12) + np.testing.assert_allclose( + result.table("condition")["p_value"], table["p_value"], rtol=1e-12 + ) + + +def test_f_tests_of_two_factors_are_those_of_each_factor_alone() -> None: + cube = fixture_cube("covariate") + # A batch that crosses the groups A, B, C, A, B, C, A, B, C. + samples = cube.samples.with_columns( + {"batch": ["x", "x", "y", "y", "y", "x", "x", "y", "x"]} + ) + formula = "~ 0 + group + batch + age" + design = Design(samples, formula, contrasts={"B_vs_A": "groupB - groupA"}) + both = propeller(cube, design, f_tests=["group", "batch"]) + + assert both.f_tests == {"group": ("A", "B", "C"), "batch": ("x", "y")} + assert list(both.tables) == ["B_vs_A", "group", "batch"] + for factor in ("group", "batch"): + alone = propeller(cube, design, contrasts=[], f_tests=factor) + assert both.table(factor).equals(alone.table(factor)) + assert both.tables[factor].equals(alone.table()) + assert both.table("B_vs_A").equals(propeller(cube, design).table("B_vs_A")) + # An F-test is named by its factor, so a contrast of that name is refused. + named = Design(samples, formula, contrasts={"batch": "batchy"}) + with pytest.raises( + ValueError, match="f_tests names 'batch', which is also the name of a contrast" + ): + propeller(cube, named, f_tests=["group", "batch"]) def test_two_complementary_cell_types_have_opposite_statistics() -> None: @@ -423,7 +459,7 @@ def test_two_complementary_cell_types_have_opposite_statistics() -> None: table["p_value"][1], table["p_value"][0], rtol=1e-12 ) # limma does not moderate two variances. - assert result.summary["prior_df"].tolist() == [0.0, 0.0] + assert result.summary["df_prior"].tolist() == [0.0, 0.0] # Samples and cell types without cells, and other inputs. @@ -439,7 +475,7 @@ def test_a_cell_type_without_cells_is_left_out_and_changes_nothing() -> None: assert summary["group_id"].tolist() == ["T", "B", "NK", "DC"] assert summary["tested"].tolist() == [True, True, True, False] assert summary["reason"].tolist()[3] == "no cells in the samples used" - assert np.isnan(summary["prior_df"][3]) + assert np.isnan(summary["df_prior"][3]) assert result.provenance["groups_left_out"] == { "DC": "no cells in the samples used" } @@ -529,7 +565,7 @@ def test_a_table_of_cell_numbers_gives_the_result_of_the_cube() -> None: def test_the_result_records_the_cube_and_settings_and_pickles() -> None: cube = fixture_cube("covariate") result = propeller( - cube, covariate_design(), f_test="group", transform="asin", trend=True + cube, covariate_design(), f_tests="group", transform="asin", trend=True ) record = result.provenance @@ -540,19 +576,19 @@ def test_the_result_records_the_cube_and_settings_and_pickles() -> None: "transform": "asin", "robust": True, "trend": True, - "f_test": "group", + "f_tests": ["group"], "logit_offset": 0.5, "winsor_tail_p": None, "p_value_adjustment": "BH", } - assert record["f_test"]["levels"] == ["A", "B", "C"] + assert record["f_tests"]["group"]["levels"] == ["A", "B", "C"] assert record["design"]["formula"] == "~ 0 + group + age" assert record["samples"] == cube.samples["sample_id"].tolist() copy = pickle.loads(pickle.dumps(result)) assert copy.provenance == record assert copy.table("B_vs_A").equals(result.table("B_vs_A")) - assert copy.f_test is not None and result.f_test is not None - assert copy.f_test.equals(result.f_test) + assert copy.f_tests == result.f_tests == {"group": ("A", "B", "C")} + assert copy.table("group").equals(result.table("group")) with pytest.raises(AttributeError, match="immutable"): result.settings = {} # type: ignore[misc] with pytest.raises( @@ -652,14 +688,14 @@ def bulk_cube() -> Cube: ( lambda: CELLS, two_group_design, - {"f_test": 1}, + {"f_tests": 1}, TypeError, - "f_test must be the name of a factor", + "f_tests must be the name of a factor", ), ( lambda: CELLS, two_group_design, - {"f_test": "donor"}, + {"f_tests": "donor"}, ValueError, "not a variable of the formula", ), @@ -703,12 +739,12 @@ def test_invalid_input_is_refused( def test_f_test_needs_a_factor_that_is_a_main_effect_only() -> None: cube = fixture_cube("covariate") with pytest.raises(ValueError, match="'age', a covariate"): - propeller(cube, covariate_design(), f_test="age") + propeller(cube, covariate_design(), f_tests="age") samples = {**fixture_cube("covariate").samples.to_dict()} samples["sex"] = np.array(["F", "M", "F"] * 3, dtype=object) design = Design(samples, "~ group * sex", contrasts={"B": "groupB"}) with pytest.raises(ValueError, match="in the interaction group:sex"): - propeller(cube, design, f_test="group") + propeller(cube, design, f_tests="group") with pytest.raises(ValueError, match="random effects? \\(1 \\| sex\\)"): propeller( cube, diff --git a/tests/test_cube.py b/tests/test_cube.py index b0f999f..db01dd5 100644 --- a/tests/test_cube.py +++ b/tests/test_cube.py @@ -11,7 +11,7 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.cube import Cube +from cytepool import Cube SCANPY_FIXTURE = Path(__file__).parent / "data" / "cube" / "scanpy_aggregate.npz" TARGET_SUM = 1e4 diff --git a/tests/test_cube_collection.py b/tests/test_cube_collection.py index 7a6055d..2af1389 100644 --- a/tests/test_cube_collection.py +++ b/tests/test_cube_collection.py @@ -1,4 +1,5 @@ -"""Tests of CubeCollection and align_study_results. +"""Tests of CubeCollection and of StudyResults.table, which lines up the studies' +results. The reference values come from validation/cube_collection.py --write-fixtures: three small studies (``lupus``, ``covid`` and ``atlas``), anndata's ``concat`` of their @@ -19,13 +20,16 @@ import pytest from numpy.testing import assert_array_equal -from cytepool.cube import Cube, CubeCollection -from cytepool.design import Design -from cytepool.differential_expression import DifferentialExpressionResult, limma_voom -from cytepool.io import read_cube, write_cube -from cytepool.meta_analysis import ( - align_study_results, - differential_expression_meta_analysis, +from cytepool import ( + Cube, + CubeCollection, + Design, + DifferentialExpressionResult, + StudyResults, + limma_voom, + meta_analysis, + read_cube, + write_cube, ) DATA = Path(__file__).parent / "data" / "cube_collection" @@ -42,7 +46,7 @@ "t", "p_value", "adjusted_p_value", - "b_statistic", + "log_odds", "standard_error", ) @@ -182,10 +186,10 @@ def test_stack_is_anndata_inner_join() -> None: def test_studies_analysed_alone_and_lined_up_give_limma() -> None: collection = fixture_collection() - results = fixture_results() + studies = StudyResults(collection, fixture_results()) arrays, _ = fixture() for group in collection.groups["group_id"].tolist(): - table = align_study_results(collection, results, group, "stim") + table = studies.table(group, "stim") assert len(table) == collection.n_studies * collection.n_features for row, study in enumerate(collection.studies): mine = table.filter(table["study"] == study) @@ -225,10 +229,10 @@ def test_the_stack_of_two_studies_fits_their_joint_model_as_limma() -> None: def test_lined_up_results_feed_the_meta_analysis() -> None: results = fixture_results() - meta = differential_expression_meta_analysis(results) + meta = meta_analysis(results) collection = fixture_collection() group = collection.groups["group_id"][0] - table = align_study_results(collection, results, group, "stim") + table = StudyResults(collection, results).table(group, "stim") lined_up = meta.table(group, "stim") tested = table["status"] == "tested" counts = { @@ -965,15 +969,15 @@ def test_lining_up_results_refuses_results_of_other_cubes() -> None: results = fixture_results() group = collection.groups["group_id"][0] with pytest.raises(TypeError, match="collection must be a CubeCollection"): - align_study_results(fixture_cubes(), results, group, "stim") # type: ignore[arg-type] + StudyResults(fixture_cubes(), results).table(group, "stim") # type: ignore[arg-type] with pytest.raises(ValueError, match=r"missing 'atlas'"): - align_study_results( - collection, {s: results[s] for s in ("lupus", "covid")}, group, "stim" + StudyResults(collection, {s: results[s] for s in ("lupus", "covid")}).table( + group, "stim" ) with pytest.raises(KeyError, match=r"no group 'T cell' in the collection"): - align_study_results(collection, results, "T cell", "stim") + StudyResults(collection, results).table("T cell", "stim") with pytest.raises(KeyError, match="no study tested the contrast 'ctrl'"): - align_study_results(collection, results, group, "ctrl") + StudyResults(collection, results).table(group, "ctrl") # The covid study's own cube has its own group ids, which the collection merges. own = fixture_cubes()["covid"] design = Design(own.samples, FORMULAS["covid"], levels=LEVELS) @@ -981,7 +985,7 @@ def test_lining_up_results_refuses_results_of_other_cubes() -> None: "covid": limma_voom(own, design, contrasts={"stim": "conditionstim"}) } with pytest.raises(ValueError, match=r"analyses the groups 'B', 'T4 naive'"): - align_study_results(collection, unaligned, group, "stim") + StudyResults(collection, unaligned).table(group, "stim") # Its unmeasured genes are filled with zeros, which a test without the # expression filter tests. alone = CubeCollection({"covid": own}, align_groups_by="group_id") @@ -989,4 +993,4 @@ def test_lining_up_results_refuses_results_of_other_cubes() -> None: own, design, contrasts={"stim": "conditionstim"}, expression_filter=False ) with pytest.raises(ValueError, match="which the study did not measure"): - align_study_results(alone, {"covid": tested_all}, "B", "stim") + StudyResults(alone, {"covid": tested_all}).table("B", "stim") diff --git a/tests/test_cube_files.py b/tests/test_cube_files.py index 7b73a12..142d756 100644 --- a/tests/test_cube_files.py +++ b/tests/test_cube_files.py @@ -14,8 +14,8 @@ import numpy as np import pytest -from cytepool.cube import Cube -from cytepool.io import cube_files, read_cube, write_cube +from cytepool import Cube, read_cube, write_cube +from cytepool.io import cube_files from cytepool.io import zarr as zarr_module from cytepool.io.zarr import open_array, write_array @@ -434,7 +434,7 @@ def test_array_names_are_portable_and_unique(tmp_path: Path) -> None: def test_compression_choices_reach_every_array( cube: Cube, tmp_path: Path, compression: str | None, level: Any, codec: Any ) -> None: - write_cube(cube, tmp_path / "c", compression=compression, level=level) + write_cube(cube, tmp_path / "c", compression=compression, compression_level=level) arrays = 0 for metadata in (tmp_path / "c").rglob("zarr.json"): document = json.loads(metadata.read_text()) @@ -961,8 +961,12 @@ def test_write_cube_rejects_invalid_arguments( write_cube(*arguments) for options, error, message in [ ({"compression": "lz4"}, ValueError, "'gzip', 'zstd' or None, got 'lz4'"), - ({"level": 10}, ValueError, "gzip level must be from 0 to 9"), - ({"compression": None, "level": 1}, ValueError, "level applies only"), + ({"compression_level": 10}, ValueError, "gzip level must be from 0 to 9"), + ( + {"compression": None, "compression_level": 1}, + ValueError, + "compression_level applies only", + ), ({"overwrite": "yes"}, TypeError, "overwrite must be True or False"), ]: with pytest.raises(error, match=message): diff --git a/tests/test_cube_fingerprints.py b/tests/test_cube_fingerprints.py index ff4e4a3..3629340 100644 --- a/tests/test_cube_fingerprints.py +++ b/tests/test_cube_fingerprints.py @@ -21,8 +21,8 @@ import numpy as np import pytest -from cytepool.cube import Cube, from_cells -from cytepool.io import cube_files, read_cube, write_cube +from cytepool import Cube, from_cells, read_cube, write_cube +from cytepool.io import cube_files FIXTURES = Path(__file__).parent / "data" / "cube_fingerprints" REFERENCE = json.loads((FIXTURES / "reference.json").read_text("utf-8")) @@ -268,10 +268,12 @@ def test_pooling_some_cells_equals_selecting_their_samples() -> None: counts[np.flatnonzero(samples == "S3")[0], 0] = 2.0**33 # S3 needs uint64 counts kept = samples != "S3" - pooled = from_cells(counts, samples=samples, groups=groups).select( + pooled = from_cells(counts, sample_labels=samples, group_labels=groups).select( samples=["S1", "S2"] ) - subset = from_cells(counts[kept], samples=samples[kept], groups=groups[kept]) + subset = from_cells( + counts[kept], sample_labels=samples[kept], group_labels=groups[kept] + ) assert pooled.layer("counts").dtype == np.uint64 assert subset.layer("counts").dtype == np.uint32 diff --git a/tests/test_deseq2.py b/tests/test_deseq2.py index 1b7061d..b7dd4ec 100644 --- a/tests/test_deseq2.py +++ b/tests/test_deseq2.py @@ -26,7 +26,7 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.design import Design +from cytepool import Design from cytepool.stats import deseq2 from cytepool.stats.deseq2 import ( DESeq2Dispersions, diff --git a/tests/test_design.py b/tests/test_design.py index ab4eed6..a56870b 100644 --- a/tests/test_design.py +++ b/tests/test_design.py @@ -8,8 +8,7 @@ import pytest from numpy.testing import assert_array_equal -from cytepool.design import Design -from cytepool.table import Table +from cytepool import Design, Table @pytest.fixture(scope="module") diff --git a/tests/test_design_checks.py b/tests/test_design_checks.py index 432ea3a..bb067c4 100644 --- a/tests/test_design_checks.py +++ b/tests/test_design_checks.py @@ -17,7 +17,8 @@ import numpy as np import pytest -from cytepool.design import Design, DesignIssue, DesignReport +from cytepool import Design +from cytepool.design import DesignIssue, DesignReport from cytepool.stats.linear_models import fit_linear_model from cytepool.stats.mixed_models import fit_mixed_model @@ -94,7 +95,7 @@ def test_batch_nested_in_condition(nested_batches: dict[str, Any]) -> None: "contrast_not_estimable", ) assert (report.n_samples, report.n_columns, report.rank) == (8, 5, 4) - assert report.residual_df == 4 and not report.ok + assert report.df_residual == 4 and not report.ok assert report.aliased == ("conditionstim",) # conditionstim is exactly batchb3 + batchb4. assert ( @@ -156,7 +157,7 @@ def test_no_residual_degrees_of_freedom() -> None: samples = {"a": ["A", "A", "B", "B"], "b": ["X", "Y", "X", "Y"]} report = Design(samples, "~ a * b").check() assert report.kinds() == ("no_residual_df",) - assert report.rank == 4 and report.residual_df == 0 + assert report.rank == 4 and report.df_residual == 0 assert "no residual degrees of freedom: 4 samples and rank 4" in ( _issue(report, "no_residual_df").message ) @@ -300,7 +301,7 @@ def test_duplicate_column_names_are_reported_not_raised() -> None: def test_a_formula_without_columns() -> None: report = Design({"a": ["x", "y"]}, "~ 0").check() assert report.kinds() == ("empty_design",) - assert report.n_columns == 0 and report.residual_df == 2 + assert report.n_columns == 0 and report.df_residual == 2 def test_random_effect_checks() -> None: @@ -581,7 +582,7 @@ def test_high_leverage_from_few_residual_degrees_of_freedom() -> None: "rin": [7.0, 7.3, 6.7, 6.1, 6.5, 6.0], } report = Design(samples, "~ donor + condition + rin").check() - assert report.kinds() == ("high_leverage",) and report.residual_df == 1 + assert report.kinds() == ("high_leverage",) and report.df_residual == 1 message = report.warnings[0].message assert message.startswith("the model nearly fits S1 (leverage 0.99") assert "with 1 residual degree of freedom" in message diff --git a/tests/test_design_model_matrix.py b/tests/test_design_model_matrix.py index adbe519..67bedcf 100644 --- a/tests/test_design_model_matrix.py +++ b/tests/test_design_model_matrix.py @@ -16,8 +16,8 @@ import pytest from numpy.testing import assert_array_equal +from cytepool import Table from cytepool.design import INTERCEPT, ModelMatrix, model_matrix -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "design" diff --git a/tests/test_differential_expression.py b/tests/test_differential_expression.py index e4cb6d7..ad80368 100644 --- a/tests/test_differential_expression.py +++ b/tests/test_differential_expression.py @@ -24,18 +24,22 @@ import numpy as np import pytest -from cytepool.cube import Cube, from_bulk, from_cells -from cytepool.design import Contrasts, Design -from cytepool.differential_expression import ( +from cytepool import ( + Cube, + Design, DifferentialExpressionResult, + Table, dream_voom, + from_bulk, + from_cells, limma_trend, limma_voom, + read_cube, + write_cube, ) -from cytepool.io import read_cube, write_cube +from cytepool.design import Contrasts from cytepool.io.zarr import ZarrArray from cytepool.normalization import filter_by_expression -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "differential_expression" EXACT = 1e-10 @@ -47,7 +51,7 @@ "t": 2, "p_value": 3, "adjusted_p_value": 4, - "b_statistic": 5, + "log_odds": 5, # limma's sqrt(s2.post) * stdev.unscaled, the moderated standard error of logFC. "standard_error": 6, } @@ -91,9 +95,9 @@ def cells_cube() -> Cube: } return from_cells( data["counts"], - samples=data["samples"], - groups=data["groups"], - sample_table=table, + sample_labels=data["samples"], + group_labels=data["groups"], + samples=table, layers="counts", ) @@ -181,7 +185,7 @@ def test_tables_from_cells_match_limma(analysis: str) -> None: assert record.get("voom_span") == span row = result.groups.index(group) prior = float(np.median(limma[f"{key} df_prior"])) - assert result.summary["prior_df"][row] == pytest.approx(prior, rel=tolerance) + assert result.summary["df_prior"][row] == pytest.approx(prior, rel=tolerance) tested = cube.features["feature_id"][limma[f"{key} keep"]] computed = {c for c in CONTRASTS if f"{key} {c}" in limma} ours = {c for g, c in result.tables if g == group} @@ -567,7 +571,7 @@ def test_a_filter_by_sample_groups_is_filter_by_expression_with_their_labels() - rows = [sample_ids.index(sample) for sample in used] keep = filter_by_expression( cube.layer("counts", group)[rows], - group=cube.samples["condition"][rows], + sample_groups=cube.samples["condition"][rows], min_count=5, ) assert genes == features[keep].tolist(), group diff --git a/tests/test_differential_expression_options.py b/tests/test_differential_expression_options.py index a5348c0..697805e 100644 --- a/tests/test_differential_expression_options.py +++ b/tests/test_differential_expression_options.py @@ -23,18 +23,21 @@ import numpy as np import pytest -from cytepool.cube import Cube, from_bulk, from_cells -from cytepool.design import Contrasts, Design -from cytepool.differential_expression import ( +from cytepool import ( + Cube, + Design, DifferentialExpressionResult, + Table, + from_bulk, + from_cells, limma_trend, limma_voom, ) -from cytepool.normalization import log_cpm +from cytepool.design import Contrasts +from cytepool.normalization import log2_cpm from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import contrasts_fit, fit_linear_model from cytepool.stats.voom import voom, voom_with_quality_weights -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "differential_expression_options" EXACT = 1e-10 @@ -47,7 +50,7 @@ "t", "p_value", "adjusted_p_value", - "b_statistic", + "log_odds", ) ACROSS = "adjusted_p_value_across_groups" CONTRASTS = {"stim_vs_ctrl": "conditionstim", "D3_vs_D2": "donorD3 - donorD2"} @@ -101,9 +104,9 @@ def cells_cube() -> Cube: } return from_cells( data["counts"], - samples=data["samples"], - groups=data["groups"], - sample_table=table, + sample_labels=data["samples"], + group_labels=data["groups"], + samples=table, layers="counts", ) @@ -172,7 +175,7 @@ def test_options_match_limma(name: str) -> None: ) assert result.summary["df_residual"][k] == limma[f"{key} df_residual"][0] prior = float(np.median(limma[f"{key} df_prior"])) - assert result.summary["prior_df"][k] == pytest.approx(prior, rel=tolerance) + assert result.summary["df_prior"][k] == pytest.approx(prior, rel=tolerance) # The sample weights: limma's quality weights, or the number of cells. weights = record.get("quality_weights") or record["prior_weights"] np.testing.assert_allclose(weights, limma[f"{key} weights"], rtol=tolerance) @@ -250,9 +253,9 @@ def test_the_record_holds_the_weights_that_reproduce_a_table() -> None: assert weighted.sample_weights is not None np.testing.assert_array_equal(weighted.sample_weights, weights) if name.startswith("voom"): - values, weights, method = weighted.log_cpm, weighted.weights, "limma" + values, weights, method = weighted.log2_cpm, weighted.weights, "limma" else: - values = log_cpm(counts, norm_factors=factors, prior_count=3.0) + values = log2_cpm(counts, norm_factors=factors, prior_count=3.0) method = "exact" model = fit_linear_model( values, x, weights=weights, coefficient_names=design.columns diff --git a/tests/test_documentation.py b/tests/test_documentation.py index 2900443..2d36690 100644 --- a/tests/test_documentation.py +++ b/tests/test_documentation.py @@ -119,15 +119,21 @@ def _modules() -> Iterator[tuple[ModuleType, ModuleType]]: yield module, importlib.import_module(info.name.rpartition(".")[0]) +def _exported(package: ModuleType) -> set[str]: + """The names that a package re-exports, and those of the top level, which the API + reference documents there.""" + return set(getattr(package, "__all__", ())) | set(cytepool.__all__) + + def _is_implementation(module: ModuleType, package: ModuleType) -> bool: - """Whether a package re-exports names of a module, which implements it.""" - exported = set(getattr(package, "__all__", ())) - return package is not cytepool and bool(set(_public_names(module)) & exported) + """Whether a package, or the top level, re-exports names of a module, which + implements them.""" + return bool(set(_public_names(module)) & _exported(package)) def _public_modules() -> set[str]: """The modules users import from: modules with public names that do not just - implement their package. The top level holds only ``__version__``.""" + implement their package or the top level.""" return { module.__name__ for module, package in _modules() @@ -136,12 +142,13 @@ def _public_modules() -> set[str]: def _required_objects() -> set[str]: - """Every public name, as the API reference must name it: the names of the public - modules, and names an implementation module exports but its package does not.""" + """Every public name, as the API reference must name it: the names of the top + level, the names of the public modules, and names an implementation module exports + but neither its package nor the top level does.""" public = _public_modules() - required = set() + required = {f"cytepool.{name}" for name in cytepool.__all__} for module, package in _modules(): - exported = set(getattr(package, "__all__", ())) + exported = _exported(package) implementation = _is_implementation(module, package) for name in _public_names(module): if module.__name__ in public or (implementation and name not in exported): @@ -188,10 +195,12 @@ def _members(name: str, value: Any) -> set[str]: def _documented_objects() -> dict[str, set[str]]: """The full names of the objects each API page documents. - ``automodule`` documents the module's public names that have docstrings and the - methods and properties of its classes. ``autofunction`` and ``autoclass`` take - full names. ``py:data`` names an object in the module that the page documents - last, or in its ``:module:`` option. + ``automodule`` documents the module and its public names that have docstrings, + with the methods and properties of its classes; with ``:no-members:`` it documents + none of its names, and with ``:no-index:`` the module has no entry of its own, as + the pages render the description of a module that implements top-level names. + ``autofunction`` and ``autoclass`` take full names. ``py:data`` names an object in + the module that the page documents last, or in its ``:module:`` option. """ pages = {} for page in API_SOURCE.glob("*.md"): @@ -201,7 +210,10 @@ def _documented_objects() -> dict[str, set[str]]: if kind == "automodule": current = name module = importlib.import_module(name) - documented.add(name) + if "no-index" not in options: + documented.add(name) + if "no-members" in options: + continue for member in _public_names(module): documented |= _members(f"{name}.{member}", getattr(module, member)) elif kind.startswith("auto"): @@ -471,26 +483,41 @@ def test_output_checks_ignore_the_versions_of_cytepool_a_page_prints( def test_every_public_module_is_in_the_api_reference() -> None: - documented = { - name + modules = [ + (name, options) for page in API_SOURCE.glob("*.md") - for kind, name, _ in _directives(page) + for kind, name, options in _directives(page) if kind == "automodule" - } + ] + documented = {name for name, options in modules if "no-index" not in options} public = _public_modules() assert public == documented + # The module of top-level names gives its page the description of the method, + # and documents none of its names, which the page documents at the top level. + narratives = [(name, options) for name, options in modules if "no-index" in options] + assert narratives + assert all("no-members" in options for _, options in narratives) + assert not {name for name, _ in narratives} & public # The definition finds what a user imports from, and not the files that - # implement a package. + # implement a package or the top level. assert { - "cytepool.cube", + "cytepool.cube.provenance", "cytepool.design", "cytepool.io.zarr", + "cytepool.stats.meta_analysis", "cytepool.stats.voom", - "cytepool.table", } <= public assert ( - not {"cytepool.cube.cube", "cytepool.design.formula", "cytepool.stats"} & public + not { + "cytepool.cube", + "cytepool.cube.cube", + "cytepool.design.formula", + "cytepool.differential_expression", + "cytepool.stats", + "cytepool.table", + } + & public ) @@ -499,10 +526,14 @@ def test_every_public_name_is_documented_under_its_own_name() -> None: assert sorted(_required_objects() - documented) == [] # Constants and names that a package does not re-export need directives of - # their own; these are the ones that exist today. - assert {"cytepool.design.INTERCEPT", "cytepool.design.formula.quote_name"} <= ( - _required_objects() - ) + # their own; these are the ones that exist today. The top level's names are + # documented there. + assert { + "cytepool.__version__", + "cytepool.design.INTERCEPT", + "cytepool.design.formula.quote_name", + "cytepool.limma_voom", + } <= _required_objects() def test_every_api_page_is_in_the_api_index() -> None: @@ -613,7 +644,7 @@ def test_link_check_finds_broken_links() -> None: working = [ "installation.md#build-this-documentation", "/api/voom.html#cytepool.stats.voom.voom", - "/api/designs.html#cytepool.design.Design.check", + "/api/designs.html#cytepool.Design.check", "/api/designs.html#cytepool.design.INTERCEPT", REPOSITORY + "blob/master/validation/cube.py#L10", ] @@ -1096,7 +1127,7 @@ def test_the_staged_site_links_roles_to_the_api_reference( assert (site / "index.md").read_text() == ( "# Example\n\nSee [`limma_voom`](" - "/api/differential_expression.html#cytepool.differential_expression.limma_voom).\n" + "/api/differential_expression.html#cytepool.limma_voom).\n" ) (source / "index.md").write_text("# Example\n\nSee {api}`select`.\n") with pytest.raises(ValueError, match="'select' is ambiguous"): @@ -1109,6 +1140,7 @@ def test_the_code_of_a_page_uses_what_it_imports_from_cytepool() -> None: %time import numpy as np from cytepool.cube.pool import from_cells import cytepool.stats.voom as weights + from cytepool import limma_voom from cytepool._jit import jit from cytepool.design import Design as D result = weights.voom(counts, D(samples, "~ group")) @@ -1117,26 +1149,21 @@ def test_the_code_of_a_page_uses_what_it_imports_from_cytepool() -> None: uses = page_index.code_uses(code, page_index.api_entries()) - # A name imported from the module that implements it finds its documented name; - # private names and modules are not uses. + # A name imported from the module that implements it finds its documented name, + # at the top level; private names and modules are not uses. assert uses == { - "cytepool.cube.from_cells", + "cytepool.from_cells", + "cytepool.limma_voom", "cytepool.stats.voom.voom", - "cytepool.design.Design", + "cytepool.Design", } def test_api_entries_link_back_to_the_pages_that_use_and_explain_them() -> None: pytest.importorskip("yaml") links = page_index.links() - used = { - page.file - for page in links.used_in["cytepool.differential_expression.limma_voom"] - } - explained = { - page.file - for page in links.explained_in["cytepool.differential_expression.limma_voom"] - } + used = {page.file for page in links.used_in["cytepool.limma_voom"]} + explained = {page.file for page in links.explained_in["cytepool.limma_voom"]} assert "quickstart.ipynb" in used # its code imports it assert "guides/bulk_rna_seq.ipynb" in used @@ -1145,9 +1172,7 @@ def test_api_entries_link_back_to_the_pages_that_use_and_explain_them() -> None: index = {page.file for page in page_index.pages() if page.kind == page_index.INDEX} for pages in (*links.used_in.values(), *links.explained_in.values()): assert not index & {page.file for page in pages} - lines = page_index.backlink_lines( - "cytepool.differential_expression.limma_voom", links - ) + lines = page_index.backlink_lines("cytepool.limma_voom", links) assert lines[0] == "" assert lines[1].startswith("**Used in:** `Quick start <../quickstart/>`__") assert "`Choosing a method <../statistics/choosing-a-method/>`__" in lines[3] @@ -1500,9 +1525,9 @@ def test_the_public_names_are_those_of_the_api_rule_and_their_methods() -> None: parent.rpartition(".")[2], ) ), name - assert {"cytepool.cube.Cube.select", "cytepool.design.Design.check"} <= names + assert {"cytepool.Cube.select", "cytepool.Design.check"} <= names # Properties are not methods. - assert "cytepool.cube.Cube.n_cells" not in names + assert "cytepool.Cube.n_cells" not in names def test_the_coverage_report_finds_names_listed_wrongly(tmp_path: Path) -> None: @@ -1520,15 +1545,12 @@ def write(entries: dict[str, str], more: str = "") -> None: write( exempt | { - "cytepool.differential_expression.limma_voom": "covered", + "cytepool.limma_voom": "covered", "cytepool.nothing": "x", } ) report = coverage.report(path) - assert report.stale == [ - "cytepool.differential_expression.limma_voom", - "cytepool.nothing", - ] + assert report.stale == ["cytepool.limma_voom", "cytepool.nothing"] first = next(iter(exempt)) write({**exempt, first: " "}) assert coverage.report(path).empty == [first] diff --git a/tests/test_dream.py b/tests/test_dream.py index 1979382..b4d1444 100644 --- a/tests/test_dream.py +++ b/tests/test_dream.py @@ -24,9 +24,7 @@ import numpy as np import pytest -from cytepool.cube import Cube, from_bulk -from cytepool.design import Design -from cytepool.differential_expression import dream_voom, limma_voom +from cytepool import Cube, Design, dream_voom, from_bulk, limma_voom from cytepool.stats.dream import ( ModeratedFixedEffectTests, _signed_z, @@ -79,7 +77,7 @@ def fixture_tests(name: str) -> Any: voom = voom_with_dream_weights( data["counts"], design, span=0.5, weights=data.get("prior_weights") ) - fit = fit_mixed_model(voom.log_cpm, design, weights=voom.weights) + fit = fit_mixed_model(voom.log2_cpm, design, weights=voom.weights) names = data["names"] return voom, fixed_effect_tests(fit, contrast_weights(design, names), names=names) @@ -140,9 +138,9 @@ def test_the_moderation_matches_variance_partitions_ebayes( np.testing.assert_allclose( np.sqrt(moderated.residual_variance_at_mean_weight), data["sigma"], rtol=CLOSE ) - np.testing.assert_allclose(moderated.residual_df, data["rdf"], rtol=CLOSE) + np.testing.assert_allclose(moderated.df_residual, data["rdf"], rtol=CLOSE) for ours, key in ( - (moderated.prior_df, "df_prior"), + (moderated.df_prior, "df_prior"), (moderated.prior_variance, "s2_prior"), ): np.testing.assert_allclose( @@ -152,14 +150,14 @@ def test_the_moderation_matches_variance_partitions_ebayes( moderated.posterior_variance, reference["s2_post"], rtol=CLOSE ) assert scaled(moderated.t, reference["t"]) <= CLOSE - assert scaled(moderated.lods, reference["lods"]) <= CLOSE + assert scaled(moderated.log_odds, reference["lods"]) <= CLOSE np.testing.assert_allclose( moderated.df_total, reference["df_total"], rtol=SATTERTHWAITE ) assert p_value_difference(moderated.p_value, reference["p"]) <= SATTERTHWAITE assert scaled(moderated.z, reference["z"]) <= SATTERTHWAITE if variant == "robust": # the fixture's outliers get smaller prior df - assert np.ptp(moderated.prior_df) > 1.0 + assert np.ptp(moderated.df_prior) > 1.0 @cache @@ -208,7 +206,7 @@ def test_dream_voom_matches_variance_partition_on_every_cell_type(variant: str) assert scaled(table["log2_fold_change"], data[f"{group} logFC"]) <= CLOSE assert scaled(table["average_log2_cpm"], reference["average"]) <= CLOSE assert scaled(table["t"], reference["t"]) <= CLOSE - assert scaled(table["b_statistic"], reference["lods"]) <= CLOSE + assert scaled(table["log_odds"], reference["lods"]) <= CLOSE np.testing.assert_allclose( table["df"], reference["df_total"], rtol=SATTERTHWAITE ) @@ -247,14 +245,14 @@ def test_the_fits_match_lme4_and_lmertest_at_the_same_estimates( evaluated at CytePool's estimates of the first 20 genes.""" data, _ = fixture(name) voom, _ = fixture_tests(name) - n = voom.log_cpm.shape[0] + n = voom.log2_cpm.shape[0] if method == "ml": prior = data.get("prior_weights", np.ones(n)) weights = np.repeat((prior / prior.mean())[:, None], N_GIVEN, axis=1) else: weights = voom.weights[:, :N_GIVEN] fit = fit_mixed_model( - voom.log_cpm[:, :N_GIVEN], + voom.log2_cpm[:, :N_GIVEN], case_design(name), weights=weights, method=method, # type: ignore[arg-type] @@ -266,7 +264,7 @@ def test_the_fits_match_lme4_and_lmertest_at_the_same_estimates( } np.testing.assert_allclose(fit.log_likelihood, reference["loglik"], rtol=EXACT) np.testing.assert_allclose(fit.residual_variance, reference["sigma2"], rtol=EXACT) - np.testing.assert_allclose(fit.residual_df, reference["rdf"], rtol=EXACT) + np.testing.assert_allclose(fit.df_residual, reference["rdf"], rtol=EXACT) largest = np.abs(reference["fitted"]).max() np.testing.assert_allclose( fit.fitted_values, reference["fitted"].T, rtol=0.0, atol=EXACT * largest @@ -334,7 +332,7 @@ def test_fitted_values_and_residual_df_of_a_balanced_design_have_closed_forms( # I - H is the identity on the q (m - 1) contrasts within groups, 1 / (1 + m # gamma) on the q - 1 between them and zero on the grand mean. rdf = q * (m - 1) + (q - 1) / (1.0 + m * gamma) ** 2 - np.testing.assert_allclose(fit.residual_df, rdf, rtol=1e-10) + np.testing.assert_allclose(fit.df_residual, rdf, rtol=1e-10) np.testing.assert_allclose(fit.mean_expression, y.mean(axis=0), rtol=1e-15) np.testing.assert_array_equal(fit.mean_weights, np.ones(3)) @@ -389,10 +387,10 @@ def test_sample_order_donor_labels_and_the_scale_of_prior_weights_change_nothing ) np.testing.assert_allclose(permuted.weights, reference.weights[order], rtol=1e-8) first = fixed_effect_tests( - fit_mixed_model(reference.log_cpm, design, weights=reference.weights) + fit_mixed_model(reference.log2_cpm, design, weights=reference.weights) ) second = fixed_effect_tests( - fit_mixed_model(permuted.log_cpm, other, weights=permuted.weights) + fit_mixed_model(permuted.log2_cpm, other, weights=permuted.weights) ) a, b = moderated_fixed_effect_tests(first), moderated_fixed_effect_tests(second) np.testing.assert_allclose(b.t, a.t, rtol=1e-7) @@ -424,7 +422,7 @@ def test_a_donor_variance_of_zero_gives_limmas_moderated_tests() -> None: fit = fit_mixed_model(y, design, weights=weights) assert np.all(fit.boundary) moderated = moderated_fixed_effect_tests(fixed_effect_tests(fit)) - np.testing.assert_allclose(moderated.residual_df, donor.size - 2, rtol=1e-12) + np.testing.assert_allclose(moderated.df_residual, donor.size - 2, rtol=1e-12) limma = fit_linear_model( y, x, weights=weights / weights.mean(axis=0), coefficient_names=design.columns ) @@ -433,7 +431,7 @@ def test_a_donor_variance_of_zero_gives_limmas_moderated_tests() -> None: moderated.posterior_variance, reference.posterior_variance, rtol=1e-9 ) np.testing.assert_allclose(moderated.t, reference.t, rtol=1e-9) - np.testing.assert_allclose(moderated.lods, reference.lods, rtol=1e-9) + np.testing.assert_allclose(moderated.log_odds, reference.log_odds, rtol=1e-9) # dream adds the prior df to n - p without limma's cap, which these data leave # unreached. total = np.broadcast_to(reference.df_total[:, None], moderated.df_total.shape) @@ -478,7 +476,7 @@ def test_a_feature_the_effects_reproduce_is_left_out_of_the_trend_and_untested() assert np.isnan(voom.sqrt_sd[0]) and np.isfinite(voom.sqrt_sd[1:]).all() assert np.isfinite(voom.weights).all() and (voom.weights > 0.0).all() moderated = moderated_fixed_effect_tests( - fixed_effect_tests(fit_mixed_model(voom.log_cpm, design, weights=voom.weights)) + fixed_effect_tests(fit_mixed_model(voom.log2_cpm, design, weights=voom.weights)) ) assert np.isnan(moderated.t[0]).all() and np.isfinite(moderated.t[1:]).all() assert np.isnan(moderated.p_value[0]).all() and np.isnan(moderated.z[0]).all() @@ -510,7 +508,7 @@ def test_dream_voom_records_its_settings_and_the_features_not_fitted() -> None: "p_value", "adjusted_p_value", "adjusted_p_value_across_groups", - "b_statistic", + "log_odds", "z_statistic", ) # The feature without a fit is untested, and left out of both adjustments. diff --git a/tests/test_empirical_bayes.py b/tests/test_empirical_bayes.py index 449816e..0b237d6 100644 --- a/tests/test_empirical_bayes.py +++ b/tests/test_empirical_bayes.py @@ -248,12 +248,12 @@ def test_moderated_statistics_match_limma(case: str, run: str) -> None: tolerance = tolerance_of(run) for key, ours in ( ("s2.prior", eb.prior_variance), - ("df.prior", eb.prior_df), + ("df.prior", eb.df_prior), ("s2.post", eb.posterior_variance), ("df.total", eb.df_total), ("t", eb.t), ("p.value", eb.p_value), - ("lods", eb.lods), + ("lods", eb.log_odds), ("var.prior", eb.coefficient_prior_variance), ("F", eb.f_statistic), ("F.p.value", eb.f_p_value), @@ -339,7 +339,7 @@ def test_hyperparameters_match_limma(case: str, run: str) -> None: tolerance = CLOSE if robust or estimator == "likelihood" else EXACT check(case, run, "var.post", squeezed.posterior_variance, tolerance) check(case, run, "var.prior", squeezed.prior_variance, tolerance) - check(case, run, "df.prior", squeezed.prior_df, tolerance) + check(case, run, "df.prior", squeezed.df_prior, tolerance) for key, ours in parts.items(): if reference(case)[f"limma {run} {key}"].size: # limma omits some parts check(case, run, key, ours, tolerance) @@ -348,7 +348,7 @@ def test_hyperparameters_match_limma(case: str, run: str) -> None: def test_infinite_prior_df_gives_ordinary_t_on_the_prior_variance() -> None: eb = moderated("infinite_prior", "constant") fit = model_fit("infinite_prior") - assert eb.prior_df == np.inf + assert eb.df_prior == np.inf np.testing.assert_array_equal(eb.posterior_variance, eb.prior_variance) ordinary = fit.coefficients / (fit.stdev_unscaled * np.sqrt(eb.prior_variance)) np.testing.assert_allclose(eb.t, ordinary, rtol=1e-14) @@ -371,7 +371,7 @@ def log_normal_density(x: np.ndarray, variance: np.ndarray) -> np.ndarray: bayes_factor = log_normal_density(fit.coefficients, v + v0) bayes_factor -= log_normal_density(fit.coefficients, v) np.testing.assert_allclose( - eb.lods, np.log(0.01 / 0.99) + bayes_factor, rtol=1e-12, atol=1e-12 + eb.log_odds, np.log(0.01 / 0.99) + bayes_factor, rtol=1e-12, atol=1e-12 ) @@ -447,7 +447,7 @@ def test_zero_coefficient_variance_gives_the_prior_log_odds() -> None: model_fit("two_groups"), proportion=0.05, stdev_coef_lim=(0.0, 0.0) ) np.testing.assert_array_equal(eb.coefficient_prior_variance, 0.0) - np.testing.assert_allclose(eb.lods, np.log(0.05 / 0.95), rtol=1e-14) + np.testing.assert_allclose(eb.log_odds, np.log(0.05 / 0.95), rtol=1e-14) def test_exact_f_statistic_is_invariant_to_coding_and_matches_group_means() -> None: @@ -486,7 +486,7 @@ def test_results_do_not_depend_on_the_order_of_features(run: str) -> None: fit_linear_model(data["y"][order].T, data["X"]), **options(run) ) eb = moderated_statistics(fit_linear_model(data["y"].T, data["X"]), **options(run)) - for name in ("posterior_variance", "df_total", "t", "p_value", "lods"): + for name in ("posterior_variance", "df_total", "t", "p_value", "log_odds"): np.testing.assert_allclose( getattr(permuted, name), getattr(eb, name)[order], rtol=1e-10 ) @@ -510,9 +510,9 @@ def test_rescaling_the_data_rescales_the_variances(run: str) -> None: np.testing.assert_allclose( scaled.posterior_variance, 16.0 * eb.posterior_variance, rtol=1e-10 ) - np.testing.assert_allclose(scaled.prior_df, eb.prior_df, rtol=1e-10) + np.testing.assert_allclose(scaled.df_prior, eb.df_prior, rtol=1e-10) np.testing.assert_allclose(scaled.t, eb.t, rtol=1e-10) - np.testing.assert_allclose(scaled.lods, eb.lods, rtol=1e-10, atol=1e-10) + np.testing.assert_allclose(scaled.log_odds, eb.log_odds, rtol=1e-10, atol=1e-10) def test_features_without_observations_do_not_change_the_others() -> None: @@ -534,12 +534,12 @@ def test_variance_with_zero_df_gets_the_prior() -> None: v = np.array([0.05, 0.9, np.nan, 0.8, 0.3, 2.5, 0.01, 0.25]) d = np.array([4.0, 4.0, 0.0, 0.0, 4.0, 4.0, 4.0, 4.0]) squeezed = squeeze_variances(v, d) - assert np.isfinite(squeezed.prior_df) + assert np.isfinite(squeezed.df_prior) assert squeezed.posterior_variance[2] == squeezed.prior_variance assert squeezed.posterior_variance[3] == squeezed.prior_variance usable = d > 0 alone = squeeze_variances(v[usable], d[usable]) - assert alone.prior_df == squeezed.prior_df + assert alone.df_prior == squeezed.df_prior np.testing.assert_array_equal( alone.posterior_variance, squeezed.posterior_variance[usable] ) @@ -566,7 +566,7 @@ def test_equal_variances_give_an_infinite_prior_df() -> None: def test_at_most_two_variances_are_not_moderated() -> None: for v in (np.array([0.5]), np.array([0.2, 3.0])): squeezed = squeeze_variances(v, 4.0, robust=True) - assert squeezed.prior_df == 0.0 + assert squeezed.df_prior == 0.0 np.testing.assert_array_equal(squeezed.posterior_variance, v) np.testing.assert_array_equal(squeezed.prior_variance, v) @@ -583,9 +583,9 @@ def test_one_or_two_features_with_zero_variance_give_nan_not_an_error() -> None: for trend in (False, True): for robust in (False, True): eb = moderated_statistics(one, trend=trend, robust=robust) - assert np.isnan(eb.t).all() and np.isnan(eb.lods).all() + assert np.isnan(eb.t).all() and np.isnan(eb.log_odds).all() np.testing.assert_array_equal(eb.prior_variance, 0.0) - assert eb.prior_df == 0.0 + assert eb.df_prior == 0.0 np.testing.assert_array_equal(eb.coefficient_prior_variance, np.inf) eb = moderated_statistics(two, trend=trend, robust=robust) # The second feature keeps its own variance, 1 on 2 df: t = sqrt(3). @@ -684,7 +684,7 @@ def test_default_estimator_follows_limmas_rule_for_fits() -> None: default = moderated_statistics(fit, trend=True) moments = moderated_statistics(fit, trend=True, prior_estimator="moments") assert default.prior_estimator == moments.prior_estimator == "moments" - for name in ("prior_variance", "prior_df", "posterior_variance", "t", "lods"): + for name in ("prior_variance", "df_prior", "posterior_variance", "t", "log_odds"): np.testing.assert_array_equal(getattr(default, name), getattr(moments, name)) unequal = model_fit("missing_values_all_df") assert np.unique(unequal.df_residual).size > 1 @@ -705,12 +705,12 @@ def test_default_estimator_compares_the_positive_df_exactly() -> None: v = 0.4 * np.random.default_rng(12).f(4.0, 8.0, 40) def estimator_of(variances: np.ndarray, df: np.ndarray) -> str: - chosen = squeeze_variances(variances, df).prior_df + chosen = squeeze_variances(variances, df).df_prior moments = squeeze_variances(variances, df, prior_estimator="moments") likelihood = squeeze_variances(variances, df, prior_estimator="likelihood") - assert moments.prior_df != likelihood.prior_df - assert chosen in (moments.prior_df, likelihood.prior_df) - return "moments" if chosen == moments.prior_df else "likelihood" + assert moments.df_prior != likelihood.df_prior + assert chosen in (moments.df_prior, likelihood.df_prior) + return "moments" if chosen == moments.df_prior else "likelihood" d = np.full(40, 4.0) assert estimator_of(v, d) == "moments" @@ -801,7 +801,7 @@ def test_robust_fit_without_genes_lacking_residual_df_is_limmas_of_the_others() eb = moderated(case, run) keep = reference(case)["keep"] == 1 assert not keep.all() and eb.prior_estimator == "likelihood" - prior_df = eb.prior_df[keep] if np.ndim(eb.prior_df) else eb.prior_df + prior_df = eb.df_prior[keep] if np.ndim(eb.df_prior) else eb.df_prior for key, ours in ( ("s2.prior", eb.prior_variance), ("df.prior", prior_df), @@ -854,7 +854,7 @@ def test_fit_object_fields_are_read_only() -> None: eb = moderated("two_groups", "trend") with pytest.raises(ValueError, match="read-only"): eb.t[0, 0] = 1.0 - assert "n_features=100" in repr(eb) and "prior_df=" in repr(eb) + assert "n_features=100" in repr(eb) and "df_prior=" in repr(eb) assert "prior_estimator='moments'" in repr(eb) assert "lfc=" in repr(treat(model_fit("two_groups"))) diff --git a/tests/test_gene_set_tests.py b/tests/test_gene_set_tests.py index 1190e81..24c41aa 100644 --- a/tests/test_gene_set_tests.py +++ b/tests/test_gene_set_tests.py @@ -5,7 +5,9 @@ and ``rankSumTestWithCorrelation`` on limma's robust moderated z-scores); ``validation/gene_set_tests.py`` writes them. The other tests check properties that a wrong implementation would break: symmetries, invariances, closed forms, limits of the -standardizations and the documented edge cases. +standardizations and the documented edge cases. A differential-expression result is +tested, bit for bit, as the voom values of its group's fit with the design rows and +contrast weights fitted and the moderation the result used. """ import math @@ -18,14 +20,19 @@ from numpy.testing import assert_allclose, assert_array_equal from scipy import stats -from cytepool.design import Design -from cytepool.stats.gene_set_tests import ( - _trimmed_mean_square_expectation, +from cytepool import ( + Cube, + Design, + DifferentialExpressionResult, + Table, camera, + dream_voom, fry, + limma_trend, + limma_voom, ) +from cytepool.gene_set_tests import _trimmed_mean_square_expectation from cytepool.stats.voom import voom -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "gene_set_tests" EXACT = 1e-10 @@ -59,7 +66,7 @@ "PValue": "p_value", "FDR": "adjusted_p_value", "PValue.Mixed": "mixed_p_value", - "FDR.Mixed": "adjusted_mixed_p_value", + "FDR.Mixed": "mixed_adjusted_p_value", } CASES = ( "groups", @@ -98,7 +105,7 @@ def run(case: str, name: str, **changes: Any) -> Table: test, rho, ranks, negative, trend, standardize, robust = RUNS[name] data = fixture(case) arguments = { - "y": data["y"], + "data": data["y"], "design": data["design"], "contrast": contrast(case), "gene_sets": gene_sets(case), @@ -185,7 +192,7 @@ def test_every_column_matches_limma(case: str, name: str) -> None: assert_p_values_agree(table[COLUMNS[column]], values, tolerance) -def test_a_design_with_named_contrasts_and_gene_ids_gives_limmas_values() -> None: +def test_a_design_with_named_contrasts_and_feature_ids_gives_limmas_values() -> None: # The weighted case rebuilt from its sample table, with genes named by id and the # contrast named in the design; and the unweighted case with a column by name. genes = np.array([f"gene{i}" for i in range(200)]) @@ -208,13 +215,23 @@ def test_a_design_with_named_contrasts_and_gene_ids_gives_limmas_values() -> Non data = fixture(case) assert_array_equal(design.matrix, data["design"]) named = {name: genes[rows] for name, rows in gene_sets(case).items()} - common = {"gene_ids": genes, "weights": data.get("weights")} + common = {"feature_ids": genes, "weights": data.get("weights")} estimated = camera( - data["y"], design, contrast, named, inter_gene_correlation=None, **common + data["y"], + named, + design=design, + contrast=contrast, + inter_gene_correlation=None, + **common, ) residual = fry( - data["y"], design, contrast, named, standardize="residual_sd", **common + data["y"], + named, + design=design, + contrast=contrast, + standardize="residual_sd", + **common, ) assert_p_values_agree( @@ -250,7 +267,9 @@ def test_a_set_and_its_complement_get_the_same_p_value_and_opposite_directions( y, x = three_groups(1) inside = np.arange(0, 25) sets = {"set": inside, "complement": np.arange(25, 60)} - table = camera(y, x, 1, sets, inter_gene_correlation=0.0, use_ranks=use_ranks) + table = camera( + y, sets, design=x, contrast=1, inter_gene_correlation=0.0, use_ranks=use_ranks + ) assert_allclose(table["p_value"][0], table["p_value"][1], rtol=1e-12) assert set(table["direction"]) == {"up", "down"} @@ -263,8 +282,10 @@ def test_the_order_of_the_genes_does_not_matter() -> None: order = np.random.default_rng(3).permutation(60) for test in (camera, fry): - original = test(y, x, 1, sets, gene_ids=genes) - shuffled = test(y[:, order], x, 1, sets, gene_ids=genes[order]) + original = test(y, sets, design=x, contrast=1, feature_ids=genes) + shuffled = test( + y[:, order], sets, design=x, contrast=1, feature_ids=genes[order] + ) for column in original.columns: if original[column].dtype.kind == "f": assert_allclose(shuffled[column], original[column], rtol=1e-12) @@ -286,8 +307,8 @@ def test_negating_the_contrast_flips_directions_and_keeps_p_values() -> None: {"standardize": "residual_sd"}, ): test = fry if "standardize" in options else camera - plus = test(y, x, contrast, sets, weights=weights, **options) - minus = test(y, x, -contrast, sets, weights=weights, **options) + plus = test(y, sets, design=x, contrast=contrast, weights=weights, **options) + minus = test(y, sets, design=x, contrast=-contrast, weights=weights, **options) assert_allclose(minus["p_value"], plus["p_value"], rtol=1e-12) flipped = {"up": "down", "down": "up"} assert [flipped[v] for v in plus["direction"]] == list(minus["direction"]) @@ -302,8 +323,8 @@ def test_rescaling_and_shifting_the_values_changes_nothing() -> None: (camera, {"use_ranks": True, "trend": False}), (fry, {}), ): - original = test(y, x, 2, sets, **options) - changed = test(shifted, x, 2, sets, **options) + original = test(y, sets, design=x, contrast=2, **options) + changed = test(shifted, sets, design=x, contrast=2, **options) for column in ("p_value", "adjusted_p_value"): assert_allclose(changed[column], original[column], rtol=1e-10) @@ -312,26 +333,32 @@ def test_a_column_named_or_counted_from_either_end_is_the_same_contrast() -> Non y, x = three_groups(7) design = Design({"group": np.repeat(["A", "B", "C"], 3)}, "~ group") sets = {"a": np.arange(12), "b": np.arange(12, 30)} - expected = camera(y, x, 2, sets, inter_gene_correlation=None) + expected = camera(y, sets, design=x, contrast=2, inter_gene_correlation=None) for given in ("groupC", -1, 2): - table = camera(y, design, given, sets, inter_gene_correlation=None) + table = camera( + y, sets, design=design, contrast=given, inter_gene_correlation=None + ) assert_allclose(table["p_value"], expected["p_value"], rtol=1e-14) # Without observation weights the residual basis does not matter to camera, so # the column as weights gives the same test. - weights = camera(y, x, [0.0, 0.0, 1.0], sets, inter_gene_correlation=None) + weights = camera( + y, sets, design=x, contrast=[0.0, 0.0, 1.0], inter_gene_correlation=None + ) assert_allclose(weights["p_value"], expected["p_value"], rtol=1e-12) assert_allclose(weights["correlation"], expected["correlation"], atol=1e-13) -def test_a_voom_result_is_its_log_cpm_with_its_weights() -> None: +def test_a_voom_result_is_its_log2_cpm_with_its_weights() -> None: rng = np.random.default_rng(8) counts = rng.poisson(rng.uniform(20, 500, 80), size=(8, 80)) x = np.column_stack([np.ones(8), np.repeat([0.0, 1.0], 4)]) result = voom(counts, x) sets = {"a": np.arange(10), "b": np.arange(5, 40)} for test in (camera, fry): - direct = test(result, x, 1, sets) - explicit = test(result.log_cpm, x, 1, sets, weights=result.weights) + direct = test(result, sets, design=x, contrast=1) + explicit = test( + result.log2_cpm, sets, design=x, contrast=1, weights=result.weights + ) assert_array_equal(direct["p_value"], explicit["p_value"]) @@ -348,7 +375,7 @@ def test_a_set_of_one_gene_in_fry_is_that_genes_t_test() -> None: t = coefficients[1, 0] / (unscaled * math.sqrt(residuals[0] / d)) expected = 2.0 * stats.t.sf(abs(t), d) for standardize in ("posterior_sd", "residual_sd", "none"): - table = fry(y, x, 1, {"one": [17]}, standardize=standardize) + table = fry(y, {"one": [17]}, design=x, contrast=1, standardize=standardize) assert_allclose(table["p_value"], [expected], rtol=1e-12) assert_allclose(table["mixed_p_value"], [expected], rtol=1e-12) assert table["direction"][0] == ("up" if t > 0 else "down") @@ -367,7 +394,7 @@ def test_the_estimated_correlation_is_the_mean_pairwise_residual_correlation() - m = rows.size expected = (pairwise.sum() - np.trace(pairwise)) / (m * (m - 1)) - table = camera(y, x, 1, {"set": rows}, inter_gene_correlation=None) + table = camera(y, {"set": rows}, design=x, contrast=1, inter_gene_correlation=None) assert_allclose(table["correlation"], [expected], rtol=1e-12) @@ -394,8 +421,8 @@ def test_posterior_standardization_has_the_limits_of_its_prior() -> None: # genes, the prior has no weight: "residual_sd". y, x = three_groups(12, n_genes=2) sets = {"both": [0, 1]} - posterior = fry(y, x, 1, sets, standardize="posterior_sd") - residual = fry(y, x, 1, sets, standardize="residual_sd") + posterior = fry(y, sets, design=x, contrast=1, standardize="posterior_sd") + residual = fry(y, sets, design=x, contrast=1, standardize="residual_sd") assert_allclose(posterior["p_value"], residual["p_value"], rtol=1e-12) assert_allclose(posterior["mixed_p_value"], residual["mixed_p_value"], rtol=1e-12) @@ -443,8 +470,8 @@ def test_a_gene_listed_twice_counts_once() -> None: once = {"a": [1, 2, 3, 8], "b": [10, 11]} twice = {"a": [3, 1, 2, 3, 8, 1], "b": [11, 10, 10]} for test in (camera, fry): - expected = test(y, x, 1, once) - table = test(y, x, 1, twice) + expected = test(y, once, design=x, contrast=1) + table = test(y, twice, design=x, contrast=1) assert_array_equal(table["n_genes"], [4, 2]) assert_allclose(table["p_value"], expected["p_value"], rtol=1e-14) @@ -453,15 +480,17 @@ def test_empty_sets_and_sets_of_every_gene_have_no_p_value() -> None: y, x = three_groups(15) genes = np.array([f"g{i}" for i in range(60)]) sets = {"empty": ["not a gene"], "a": genes[:10], "all": genes, "b": genes[20:30]} - table = camera(y, x, 1, sets, gene_ids=genes) + table = camera(y, sets, design=x, contrast=1, feature_ids=genes) assert_array_equal(table["n_genes"], [0, 10, 60, 10]) assert np.isnan(table["p_value"][[0, 2]]).all() assert table["direction"][0] is None and table["direction"][2] is None # The other sets are adjusted among themselves. - alone = camera(y, x, 1, {"a": genes[:10], "b": genes[20:30]}, gene_ids=genes) + alone = camera( + y, {"a": genes[:10], "b": genes[20:30]}, design=x, contrast=1, feature_ids=genes + ) assert_allclose(table["adjusted_p_value"][[1, 3]], alone["adjusted_p_value"]) - tested = fry(y, x, 1, sets, gene_ids=genes) + tested = fry(y, sets, design=x, contrast=1, feature_ids=genes) assert np.isnan(tested["p_value"][0]) and np.isnan(tested["mixed_p_value"][0]) assert np.isfinite(tested["p_value"][2]) # fry's test is self-contained @@ -470,10 +499,23 @@ def test_the_rank_test_p_value_is_at_most_one() -> None: # A set of the lowest and the highest z-score: its rank sum is centred, and with # the continuity correction both tails exceed 1/2. limma reports twice the smaller. y, x = three_groups(16) - t = camera(y, x, 1, {f"g{i}": [i] for i in range(60)}, inter_gene_correlation=0.0) + t = camera( + y, + {f"g{i}": [i] for i in range(60)}, + design=x, + contrast=1, + inter_gene_correlation=0.0, + ) signed = np.where(np.array(t["direction"]) == "up", 1.0, -1.0) * (1 - t["p_value"]) ends = [int(np.argmin(signed)), int(np.argmax(signed))] - table = camera(y, x, 1, {"ends": ends}, inter_gene_correlation=0.0, use_ranks=True) + table = camera( + y, + {"ends": ends}, + design=x, + contrast=1, + inter_gene_correlation=0.0, + use_ranks=True, + ) assert table["p_value"][0] == 1.0 assert table["direction"][0] == "up" @@ -484,7 +526,7 @@ def test_camera_estimates_correlation_with_a_floor_on_tiny_variances() -> None: y, x = three_groups(17) y[:, 5] = x @ np.array([3.0, 1.0, -1.0]) sets = {"with": [5, *range(20, 30)], "without": list(range(20, 30))} - table = camera(y, x, 1, sets, inter_gene_correlation=None) + table = camera(y, sets, design=x, contrast=1, inter_gene_correlation=None) m = 11 vif_without = 1 + 9 * table["correlation"][1] expected = ((10 / m) ** 2 * m / 10 * vif_without - 1) / (m - 1) @@ -504,10 +546,11 @@ def test_a_pivot_that_is_zero_up_to_rounding_counts_as_zero() -> None: (camera, {"inter_gene_correlation": None}), (fry, {"standardize": "residual_sd"}), ): - exact = test(data["y"], data["design"], data["contrast weights"], sets, - weights=data["weights"], **options) # fmt: skip - jittered = test(data["y"], data["design"], data["contrast weights"], sets, - weights=data["weights"] * jitter, **options) # fmt: skip + given = {"design": data["design"], "contrast": data["contrast weights"]} + exact = test(data["y"], sets, **given, weights=data["weights"], **options) + jittered = test( + data["y"], sets, **given, weights=data["weights"] * jitter, **options + ) assert_allclose(jittered["p_value"], exact["p_value"], rtol=1e-9) @@ -519,7 +562,14 @@ def test_genes_with_equal_residuals_have_a_correlation_of_one() -> None: y[:, 10:20] = y[:, [10]] sets = {"identical": np.arange(10, 20), "other": np.arange(30, 45)} for use_ranks in (False, True): - table = camera(y, x, 1, sets, inter_gene_correlation=None, use_ranks=use_ranks) + table = camera( + y, + sets, + design=x, + contrast=1, + inter_gene_correlation=None, + use_ranks=use_ranks, + ) assert table["correlation"][0] == 1.0 assert np.isfinite(table["p_value"]).all() @@ -528,14 +578,21 @@ def test_a_contrast_can_name_its_columns_or_be_a_zero_dimensional_position() -> y, x = three_groups(23) design = Design({"group": np.repeat(["A", "B", "C"], 3)}, "~ group") sets = {"a": np.arange(12), "b": np.arange(12, 30)} - vector = camera(y, design, [0.0, -1.0, 1.0], sets, inter_gene_correlation=None) + vector = camera( + y, sets, design=design, contrast=[0.0, -1.0, 1.0], inter_gene_correlation=None + ) mapping = camera( - y, design, {"groupC": 1.0, "groupB": -1.0}, sets, inter_gene_correlation=None + y, + sets, + design=design, + contrast={"groupC": 1.0, "groupB": -1.0}, + inter_gene_correlation=None, ) assert_array_equal(mapping["p_value"], vector["p_value"]) - column = camera(y, design, "groupC", sets) + column = camera(y, sets, design=design, contrast="groupC") assert_array_equal( - camera(y, design, np.array(2), sets)["p_value"], column["p_value"] + camera(y, sets, design=design, contrast=np.array(2))["p_value"], + column["p_value"], ) @@ -550,14 +607,14 @@ def test_invalid_arguments_are_refused_with_a_reason() -> None: nan = y.copy() nan[0, 0] = np.nan cases = [ - ({"y": nan}, ValueError, "y must be finite"), - ({"y": y[:, :2]}, ValueError, "at least 3 genes"), + ({"data": nan}, ValueError, "data must be finite"), + ({"data": y[:, :2]}, ValueError, "at least 3 genes"), ({"weights": np.zeros(9)}, ValueError, "positive and finite"), ({"weights": np.ones(5)}, ValueError, r"shape \(9,\) for sample weights"), - ({"y": voomed, "weights": np.ones(9)}, ValueError, "carries the precision"), + ({"data": voomed, "weights": np.ones(9)}, ValueError, "carries the precision"), ({"design": x[:, [0, 1, 1]]}, ValueError, "not of full column rank"), ({"design": np.eye(9)}, ValueError, "no residual degrees of freedom"), - ({"design": x[:8]}, ValueError, "y has 9 rows but the design has 8"), + ({"design": x[:8]}, ValueError, "data has 9 rows but the design has 8"), ({"contrast": "groupD"}, ValueError, "neither a named contrast"), ({"contrast": 3}, ValueError, "positions 0 to 2"), ({"contrast": [0.0, 0.0, 0.0]}, ValueError, "zero on every column"), @@ -574,15 +631,19 @@ def test_invalid_arguments_are_refused_with_a_reason() -> None: ({"gene_sets": {"": [0, 1]}}, ValueError, "must not be empty"), ({"contrast": {"x4": 1.0}}, ValueError, "not a design column"), ({"contrast": {"x2": "1"}}, TypeError, "one number per design column"), - ({"gene_ids": genes, "gene_sets": {"a": ["x"]}}, ValueError, "no gene of any"), - ({"gene_ids": ["g1"] * 60}, ValueError, "gene_ids must be unique"), - ({"gene_ids": genes[:3]}, ValueError, "one id per gene"), + ( + {"feature_ids": genes, "gene_sets": {"a": ["x"]}}, + ValueError, + "no gene of any", + ), + ({"feature_ids": ["g1"] * 60}, ValueError, "feature_ids must be unique"), + ({"feature_ids": genes[:3]}, ValueError, "one id per gene"), ({"inter_gene_correlation": 1.0}, ValueError, r"lie in \(-1, 1\)"), ({"inter_gene_correlation": True}, TypeError, "real number or None"), ({"use_ranks": 1}, TypeError, "use_ranks must be a bool"), ] for changes, error, message in cases: - arguments = {"y": y, "design": x, "contrast": 1, "gene_sets": sets} | changes + arguments = {"data": y, "design": x, "contrast": 1, "gene_sets": sets} | changes with pytest.raises(error, match=message): camera(**arguments) @@ -591,11 +652,11 @@ def test_fry_refuses_options_without_effect_and_unknown_standardizations() -> No y, x = three_groups(19) sets = {"a": [0, 1, 2]} with pytest.raises(ValueError, match="only with standardize='posterior_sd'"): - fry(y, x, 1, sets, standardize="none", trend=True) + fry(y, sets, design=x, contrast=1, standardize="none", trend=True) with pytest.raises(ValueError, match="standardize must be one of"): - fry(y, x, 1, sets, standardize="posterior.sd") + fry(y, sets, design=x, contrast=1, standardize="posterior.sd") with pytest.raises(TypeError, match="robust must be a bool"): - fry(y, x, 1, sets, robust="yes") + fry(y, sets, design=x, contrast=1, robust="yes") def test_a_design_with_random_effects_is_refused() -> None: @@ -610,4 +671,139 @@ def test_a_design_with_random_effects_is_refused() -> None: ) for test in (camera, fry): with pytest.raises(ValueError, match="fits fixed effects only"): - test(y, design, "groupB", {"a": [0, 1]}) + test(y, {"a": [0, 1]}, design=design, contrast="groupB") + + +# Results of differential expression. + +CONTRASTS = {"stim_vs_ctrl": "conditionstim", "D2_vs_D1": "donorD2"} +LEVELS = {"condition": ["ctrl", "stim"]} +# Sets of the simulated genes, named by symbol: up, down, both and unchanged. +SYMBOL_SETS = { + "up": [f"G{j}" for j in range(15)], + "down": [f"G{j}" for j in range(15, 25)], + "both": [f"G{j}" for j in range(5, 20)], + "null": [f"G{j}" for j in range(40, 70)], +} + + +@cache +def two_groups_of_six_donors() -> Cube: + """Negative binomial counts of 120 genes, with ids and symbols, in two groups of + 6 donors x 2 conditions; genes G0 to G14 go up and G15 to G24 down.""" + rng = np.random.default_rng(31) + condition = np.tile(["ctrl", "stim"], 6) + change = np.zeros(120) + change[:15], change[15:25] = 1.0, -1.0 + mean = np.exp(rng.normal(4.0, 1.0, size=(2, 1, 120))) + mean = mean * np.where(condition[None, :, None] == "stim", 2.0**change, 1.0) + return Cube( + {"counts": rng.negative_binomial(10, 10 / (10 + mean))}, + samples={ + "sample_id": [f"S{i}" for i in range(12)], + "condition": condition, + "donor": np.repeat([f"D{i}" for i in range(1, 7)], 2), + }, + groups={"group_id": ["B", "T"]}, + features={ + "feature_id": [f"ENSG{j:05d}" for j in range(120)], + "symbol": [f"G{j}" for j in range(120)], + }, + n_cells=rng.integers(20, 60, size=(12, 2)), + ) + + +@cache +def results() -> dict[str, DifferentialExpressionResult]: + """The cube's limma-voom, with robust moderation; its limma-trend, with the cell + numbers as prior weights; and its dream-voom of group B.""" + cube = two_groups_of_six_donors() + paired = Design( + cube.samples, "~ donor + condition", levels=LEVELS, contrasts=CONTRASTS + ) + mixed = Design( + cube.samples, + "~ condition", + random="(1 | donor)", + levels=LEVELS, + contrasts={"stim_vs_ctrl": "conditionstim"}, + ) + return { + "limma_voom": limma_voom(cube, paired, robust=True), + "limma_trend": limma_trend(cube, paired, prior_weights="n_cells"), + "dream_voom": dream_voom(cube, mixed, groups=["B"]), + } + + +def fitted( + result: DifferentialExpressionResult, group: str, contrast: str +) -> tuple[Any, dict[str, Any]]: + """A group's fit, and what a gene-set test of the result reads from it: the + design rows and contrast weights fitted and the symbols of the features tested.""" + fit = result.fit(group) + linear = fit.moderated.fit + column = linear.coefficient_names.index(contrast) + return fit, { + "design": linear.design, + "contrast": linear.contrasts[:, column], + "feature_ids": fit.features["symbol"], + } + + +@pytest.mark.parametrize("contrast", list(CONTRASTS)) +@pytest.mark.parametrize("test", [camera, fry]) +def test_a_limma_voom_result_is_tested_as_the_voom_values_of_its_groups_fit( + test: Any, contrast: str +) -> None: + # Group B's filter leaves a gene out, so its ids are those of its fit. + result = results()["limma_voom"] + fit, read = fitted(result, "B", contrast) + assert len(fit.features) < result.cube.n_features and fit.voom is not None + assert result.settings["robust"] + table = test(result, SYMBOL_SETS, group="B", contrast=contrast, id_column="symbol") + expected = test(fit.voom, SYMBOL_SETS, **read, trend=False, robust=True) + assert table.equals(expected) + # The sets named by feature id, the default column, are the same sets. + symbols = result.cube.features["symbol"].tolist() + ids = result.cube.features["feature_id"] + by_id = { + name: [ids[symbols.index(gene)] for gene in genes] + for name, genes in SYMBOL_SETS.items() + } + assert test(result, by_id, group="B", contrast=contrast).equals(table) + + +def test_a_result_gives_the_design_weights_and_ids_of_its_fit() -> None: + result = results()["limma_voom"] + fit, read = fitted(result, "B", "stim_vs_ctrl") + tested = {"group": "B", "contrast": "stim_vs_ctrl", "id_column": "symbol"} + for test in (camera, fry): + for name, value, owner in ( + ("design", read["design"], "the design of its group's fit"), + ("feature_ids", read["feature_ids"], "the ids of the column id_column"), + ("weights", fit.weights, "the weights of its group's fit"), + ): + message = ( + f"{name} applies to an array or a VoomResult; a " + f"differential-expression result gives {owner}" + ) + with pytest.raises(ValueError, match=message): + test(result, SYMBOL_SETS, **tested, **{name: value}) + # A group names a group of a result, not of an array. + with pytest.raises( + ValueError, + match="group names a group of a differential-expression result, and data " + "is a ndarray", + ): + test(fit.log2_cpm, SYMBOL_SETS, group="B", **read) + + +def test_a_result_of_dream_voom_is_refused() -> None: + result = results()["dream_voom"] + for test in (camera, fry): + with pytest.raises( + ValueError, + match=f"{test.__name__} tests the contrast and residual effects of a " + "linear model, and this result is from dream_voom", + ): + test(result, SYMBOL_SETS, contrast="stim_vs_ctrl", id_column="symbol") diff --git a/tests/test_gene_sets.py b/tests/test_gene_sets.py index afe3610..7190624 100644 --- a/tests/test_gene_sets.py +++ b/tests/test_gene_sets.py @@ -21,9 +21,16 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.gene_sets import GeneSets, read_gmt, write_gmt -from cytepool.stats.gene_set_tests import camera, fry -from cytepool.stats.rotation_gene_set_tests import mroast, roast, romer +from cytepool import ( + GeneSets, + camera, + fry, + mroast, + read_gmt, + roast, + romer, + write_gmt, +) DATA = Path(__file__).parent / "data" / "gene_set_files_and_ora" FILES = ("msigdb.gmt", "reactome.gmt", "wikipathways.gmt", "gene_ontology.gmt.gz") @@ -53,10 +60,10 @@ def assert_same_reading(sets: GeneSets, reading: dict[str, Any]) -> None: @pytest.mark.parametrize("name", FILES) def test_files_are_read_as_r_reads_them_by_hand(name: str) -> None: - sets = read_gmt(DATA / name, source="simulated", version="1", licence="CC0") + sets = read_gmt(DATA / name, name="simulated", version="1", licence="CC0") assert_same_reading(sets, by_hand()[name]) - assert (sets.source, sets.version, sets.licence) == ("simulated", "1", "CC0") + assert (sets.name, sets.version, sets.licence) == ("simulated", "1", "CC0") # The record names the file and the digest of its bytes as stored. assert sets.file == name assert sets.sha256 == hashlib.sha256((DATA / name).read_bytes()).hexdigest() @@ -201,7 +208,7 @@ def test_the_record_and_descriptions_are_kept_by_every_new_collection() -> None: sets = GeneSets( {"A": ["G1", "G2", "G3"], "B": ["G2"], "C": ["G9"]}, descriptions={"A": "first", "B": "na", "C": ""}, - source="a curated list", + name="a curated list", version="2", licence="CC BY 4.0", file="sets.gmt", @@ -217,7 +224,7 @@ def test_the_record_and_descriptions_are_kept_by_every_new_collection() -> None: pickle.loads(pickle.dumps(sets)), ): assert ( - derived.source, + derived.name, derived.version, derived.licence, derived.file, @@ -227,7 +234,7 @@ def test_the_record_and_descriptions_are_kept_by_every_new_collection() -> None: assert list(sets.select(["C", "A"])) == ["C", "A"] assert dict(pickle.loads(pickle.dumps(sets))) == dict(sets) with pytest.raises(AttributeError, match="GeneSets is immutable"): - sets.source = "other" # type: ignore[misc] + sets.name = "other" # type: ignore[misc] with pytest.raises(TypeError): sets.descriptions["A"] = "x" # type: ignore[index] @@ -260,7 +267,9 @@ def test_a_collection_tested_by_camera_gives_limmas_p_values() -> None: kept = read_gmt(DATA / "matching.gmt").restrict_to(identifiers) kept = kept.filter_by_size(15, 200) design = np.column_stack([np.ones(8), np.repeat([0.0, 1.0], 4)]) - tested = camera(fixture["y"].T, design, 1, kept, gene_ids=identifiers) + tested = camera( + fixture["y"].T, kept, design=design, contrast=1, feature_ids=identifiers + ) assert list(kept) == fixture["limma camera names"].tolist() assert_allclose(tested["p_value"], fixture["limma camera"], rtol=EXACT) @@ -276,24 +285,26 @@ def test_every_gene_set_test_takes_a_collection_as_a_mapping_of_ids() -> None: ids = [f"g{i}" for i in range(60)] sets = GeneSets({"up": [*ids[:10], "absent"], "other": ids[20:35]}) plain = {name: list(genes) for name, genes in sets.items()} - options = {"gene_ids": ids, "rng": 7, "n_rotations": 49, "n_threads": 1} + options = {"feature_ids": ids, "rng": 7, "n_rotations": 49, "n_threads": 1} - assert camera(y, design, 1, sets, gene_ids=ids).equals( - camera(y, design, 1, plain, gene_ids=ids) + assert camera(y, sets, design=design, contrast=1, feature_ids=ids).equals( + camera(y, plain, design=design, contrast=1, feature_ids=ids) ) - assert fry(y, design, 1, sets, gene_ids=ids).equals( - fry(y, design, 1, plain, gene_ids=ids) + assert fry(y, sets, design=design, contrast=1, feature_ids=ids).equals( + fry(y, plain, design=design, contrast=1, feature_ids=ids) ) - assert mroast(y, design, 1, sets, **options).equals( - mroast(y, design, 1, plain, **options) + assert mroast(y, sets, design=design, contrast=1, **options).equals( + mroast(y, plain, design=design, contrast=1, **options) ) - assert romer(y, design, 1, sets, **options).equals( - romer(y, design, 1, plain, **options) + assert romer(y, sets, design=design, contrast=1, **options).equals( + romer(y, plain, design=design, contrast=1, **options) ) - assert roast(y, design, 1, sets["up"], **options).equals( - roast(y, design, 1, plain["up"], **options) + assert roast(y, sets["up"], design=design, contrast=1, **options).equals( + roast(y, plain["up"], design=design, contrast=1, **options) ) - assert camera(y, design, 1, sets, gene_ids=ids)["n_genes"].tolist() == [10, 15] + assert camera(y, sets, design=design, contrast=1, feature_ids=ids)[ + "n_genes" + ].tolist() == [10, 15] def test_restrict_to_drops_empty_sets_ignores_missing_ids_and_needs_a_match() -> None: @@ -381,12 +392,12 @@ def test_descriptions_and_the_record_are_checked() -> None: GeneSets({"A": ["G1"]}, descriptions=["x"]) # type: ignore[arg-type] with pytest.raises(ValueError, match="sha256 must be a SHA-256 digest"): GeneSets({"A": ["G1"]}, sha256="ABC") - with pytest.raises(TypeError, match="source must be a string or None"): - GeneSets({"A": ["G1"]}, source=1) # type: ignore[arg-type] + with pytest.raises(TypeError, match="name must be a string or None"): + GeneSets({"A": ["G1"]}, name=1) # type: ignore[arg-type] def test_the_repr_says_what_the_collection_holds_and_where_it_comes_from() -> None: - sets = read_gmt(DATA / "msigdb.gmt", source="simulated MSigDB") + sets = read_gmt(DATA / "msigdb.gmt", name="simulated MSigDB") text = repr(sets) sizes = [len(genes) for genes in sets.values()] @@ -394,7 +405,7 @@ def test_the_repr_says_what_the_collection_holds_and_where_it_comes_from() -> No f"GeneSets: 6 sets of {min(sizes)} to {max(sizes)} genes, " f"{len({g for genes in sets.values() for g in genes}):,} genes in all" ) - assert "source: simulated MSigDB; version: not stated; licence: not stated" in text + assert "name: simulated MSigDB; version: not stated; licence: not stated" in text assert f"read from msigdb.gmt, SHA-256 {sets.sha256}" in " ".join(text.split()) assert max(map(len, text.splitlines())) <= 80 assert "and 3 more" in text diff --git a/tests/test_gsea.py b/tests/test_gsea.py index 7e2698d..7d5f5ff 100644 --- a/tests/test_gsea.py +++ b/tests/test_gsea.py @@ -22,15 +22,11 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool import gsea as module +import cytepool.gene_set_enrichment as module +from cytepool import Cube, Design, Table, gsea, limma_voom from cytepool._ranked_genes import running_sum -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.gsea import gsea from cytepool.plotting import plot_enrichment, plot_gsea_table from cytepool.stats.multiple_testing import adjust_p_values -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "gsea" EXACT = 1e-10 diff --git a/tests/test_held_out_prediction.py b/tests/test_held_out_prediction.py index 49bc6fa..3af2f65 100644 --- a/tests/test_held_out_prediction.py +++ b/tests/test_held_out_prediction.py @@ -2,8 +2,8 @@ The scores of the samples and their AUC are recomputed here from the counts with scipy's rankdata and Mann-Whitney statistic; the genes chosen for a held-out study must -be those that sumrank and meta_analysis rank first over the other studies; and swapping -cases and controls must turn the AUC into one minus it. validation/sumrank.py checks +be those that fit_sumrank and fit_meta_analysis rank first over the other studies; and +swapping cases and controls must turn the AUC into one minus it. validation/sumrank.py checks the AUC against scikit-learn and the genes chosen against the authors' SumRank. """ @@ -15,12 +15,17 @@ from numpy.testing import assert_allclose, assert_array_equal from scipy.stats import mannwhitneyu, rankdata -from cytepool.cube import Cube, CubeCollection -from cytepool.design import Design -from cytepool.differential_expression import DifferentialExpressionResult, limma_voom -from cytepool.held_out_prediction import HeldOutPrediction, held_out_prediction -from cytepool.meta_analysis import align_study_results, meta_analysis -from cytepool.sumrank import sumrank +from cytepool import ( + Cube, + CubeCollection, + Design, + HeldOutPrediction, + StudyResults, + held_out_prediction, + limma_voom, +) +from cytepool.stats.meta_analysis import fit_meta_analysis +from cytepool.stats.sumrank import fit_sumrank CONTRAST = "case_vs_ctrl" LEVELS = {"condition": ["ctrl", "case"]} @@ -52,7 +57,7 @@ def design(samples: object) -> Design: @cache -def studies() -> tuple[CubeCollection, dict[str, DifferentialExpressionResult]]: +def studies() -> StudyResults: """Four studies with groups T and B, the last without B.""" rng = np.random.default_rng(20261031) cubes = { @@ -68,20 +73,21 @@ def studies() -> tuple[CubeCollection, dict[str, DifferentialExpressionResult]]: ) for s in collection.studies } - return collection, results + return StudyResults(collection, results) + + +EXPLICIT = {"variable": "condition", "case": "case", "control": "ctrl"} @cache def prediction() -> HeldOutPrediction: - collection, results = studies() return held_out_prediction( - collection, results, CONTRAST, "condition", "case", "ctrl", n_genes=6, - proportion_of_studies=0.7, - ) # fmt: skip + studies(), CONTRAST, n_features=6, proportion_of_studies=0.7, **EXPLICIT + ) def chosen(found: HeldOutPrediction, study: str, method: str, group: str, direction: str) -> list[str]: # fmt: skip - genes = found.genes + genes = found.features rows = ( (genes["study"] == study) & (genes["method"] == method) @@ -94,7 +100,8 @@ def chosen(found: HeldOutPrediction, study: str, method: str, group: str, direct def recomputed(found: HeldOutPrediction, study: str, method: str) -> dict[str, float]: """Every sample's score, from the counts of the aligned cube.""" - collection, results = studies() + results = studies() + collection = results.collection cube, samples = collection.aligned_cube(study), results[study].samples ids = cube.samples["sample_id"].tolist() columns = {f: i for i, f in enumerate(cube.features["feature_id"].tolist())} @@ -124,7 +131,7 @@ def recomputed(found: HeldOutPrediction, study: str, method: str) -> dict[str, f @pytest.mark.parametrize("method", ["sumrank", "meta_analysis"]) def test_scores_and_auc_match_an_independent_computation(method: str) -> None: found = prediction() - collection, _ = studies() + collection = studies().collection for study in collection.studies: scores = found.scores.filter( (found.scores["study"] == study) & (found.scores["method"] == method) @@ -151,9 +158,10 @@ def test_scores_and_auc_match_an_independent_computation(method: str) -> None: def test_genes_are_chosen_from_the_other_studies() -> None: found = prediction() - collection, results = studies() + results = studies() + collection = results.collection for group in ("T", "B"): - table = align_study_results(collection, results, group, CONTRAST) + table = results.table(group, CONTRAST) shape = (collection.n_studies, collection.n_features) p = table["p_value"].reshape(shape) change = table["log2_fold_change"].reshape(shape) @@ -165,7 +173,7 @@ def test_genes_are_chosen_from_the_other_studies() -> None: if i != held and not np.isnan(p[i]).all() ] # fmt: skip statistics = np.where(change < 0, np.log10(p), -np.log10(p))[others] - fit = sumrank(statistics, proportion_of_studies=0.7) + fit = fit_sumrank(statistics, proportion_of_studies=0.7) cap = fit.n_studies_summed / 2 for direction in ("up", "down"): values = getattr(fit, f"{direction}_statistic") @@ -176,7 +184,7 @@ def test_genes_are_chosen_from_the_other_studies() -> None: == ids[best].tolist() ) universe = fit.ranked - combined = meta_analysis( + combined = fit_meta_analysis( change[others][:, universe], error[others][:, universe], method="reml", test="knapp_hartung", ) # fmt: skip @@ -193,7 +201,8 @@ def test_genes_are_chosen_from_the_other_studies() -> None: def test_the_held_out_studys_own_results_choose_none_of_its_genes() -> None: - collection, results = studies() + results = studies() + collection = results.collection # The first study analysed with its labels permuted: its own genes stay, the # others' change, as the first study is among their training studies. cube = collection.aligned_cube("first") @@ -203,9 +212,12 @@ def test_the_held_out_studys_own_results_choose_none_of_its_genes() -> None: cube, design({"sample_id": cube.samples["sample_id"], "condition": labels}) ) other = held_out_prediction( - collection, shuffled, CONTRAST, "condition", "case", "ctrl", n_genes=6, + StudyResults(collection, shuffled), + CONTRAST, + n_features=6, proportion_of_studies=0.7, - ) # fmt: skip + **EXPLICIT, + ) found = prediction() for method in ("sumrank", "meta_analysis"): assert chosen(other, "first", method, "T", "up") == chosen( @@ -217,11 +229,15 @@ def test_the_held_out_studys_own_results_choose_none_of_its_genes() -> None: def test_swapping_cases_and_controls_gives_one_minus_the_auc() -> None: - collection, results = studies() swapped = held_out_prediction( - collection, results, CONTRAST, "condition", "ctrl", "case", n_genes=6, + studies(), + CONTRAST, + n_features=6, proportion_of_studies=0.7, - ) # fmt: skip + variable="condition", + case="ctrl", + control="case", + ) found = prediction() assert_allclose(swapped.auc["auc"], 1 - found.auc["auc"], rtol=1e-13) assert_array_equal(swapped.scores["case"], ~found.scores["case"]) @@ -229,9 +245,13 @@ def test_swapping_cases_and_controls_gives_one_minus_the_auc() -> None: def test_a_study_without_controls_has_no_auc() -> None: - collection, results = studies() found = held_out_prediction( - collection, results, CONTRAST, "condition", "case", "healthy", n_genes=3 + studies(), + CONTRAST, + n_features=3, + variable="condition", + case="case", + control="healthy", ) assert np.isnan(found.auc["auc"]).all() assert (found.auc["n_controls"] == 0).all() @@ -240,29 +260,118 @@ def test_a_study_without_controls_has_no_auc() -> None: def test_a_group_tested_by_no_other_study_gives_no_genes() -> None: - collection, results = studies() + results = studies() # Group B with the first study alone: held out, it has no other study. - kept = collection.select(studies=["first", "fourth"]) + kept = results.collection.select(studies=["first", "fourth"]) found = held_out_prediction( - kept, - {s: results[s] for s in kept.studies}, + StudyResults(kept, {s: results[s] for s in kept.studies}), CONTRAST, - "condition", - "case", - "ctrl", groups="B", - n_genes=4, + n_features=4, + **EXPLICIT, ) row = (found.auc["study"] == "first") & (found.auc["method"] == "sumrank") assert found.auc["n_groups"][row][0] == 0 - assert found.auc["n_genes_up"][row][0] == 0 + assert found.auc["n_features_up"][row][0] == 0 # Every score is then 0, and the AUC 0.5. assert found.auc["auc"][row][0] == 0.5 +def test_the_contrast_gives_the_variable_and_the_levels_it_compares() -> None: + # CONTRAST, [0, 1] on ~ condition, compares the level case with ctrl, the + # reference: the variable, case and control that prediction() gives. + found = prediction() + read = held_out_prediction( + studies(), CONTRAST, n_features=6, proportion_of_studies=0.7 + ) + for name in ("auc", "scores", "features"): + assert getattr(read, name).equals(getattr(found, name)), name + assert read.settings == found.settings + # The studies tested one contrast, which may be left out. + alone = held_out_prediction(studies(), n_features=6, proportion_of_studies=0.7) + assert alone.auc.equals(found.auc) and alone.scores.equals(found.scores) + + +@cache +def studies_with_age() -> StudyResults: + """The four studies with a covariate in their designs, and contrasts of it and of + the mean of the cases, which compare no two levels of a factor.""" + results = studies() + collection = results.collection + fitted = {} + for study in collection.studies: + cube = collection.aligned_cube(study) + samples = cube.samples.with_columns( + {"age": np.linspace(30.0, 60.0, cube.n_samples)} + ) + # On the columns (Intercept), conditioncase and age. + contrasts = {CONTRAST: [0, 1, 0], "age": [0, 0, 1], "case_mean": [1, 1, 0]} + design = Design( + samples, "~ condition + age", levels=LEVELS, contrasts=contrasts + ) + fitted[study] = limma_voom(cube, design) + return StudyResults(collection, fitted) + + +def test_a_contrast_that_compares_no_two_levels_needs_them_given() -> None: + results = studies_with_age() + for contrast in ("age", "case_mean"): + with pytest.raises( + ValueError, + match=f"contrast '{contrast}' does not compare two levels of one factor " + "in every study's design; give the variable that tells cases from " + "controls", + ): + held_out_prediction(results, contrast, n_features=6) + given = held_out_prediction(results, "age", n_features=6, **EXPLICIT) + assert given.settings["contrast"] == "age" + # A covariate beside the factor leaves the comparison of its levels as it is. + settings = held_out_prediction(results, CONTRAST, n_features=6).settings + assert [settings[name] for name in EXPLICIT] == list(EXPLICIT.values()) + # Some of the variable and its values are not enough. + for partial in ({"variable": "condition"}, {"case": "case", "control": "ctrl"}): + with pytest.raises( + ValueError, + match="variable, case and control are given together, or none of them", + ): + held_out_prediction(studies(), CONTRAST, **partial) + + +def test_a_number_of_studies_summed_is_the_proportion_that_gives_it() -> None: + # In group T each held-out study leaves three others, of which 0.7 sums + # floor(2.1) = 2, as n_studies_summed=2 does; every study, by default, differs. + options = {"groups": "T", "n_features": 6} + by_number = held_out_prediction(studies(), CONTRAST, n_studies_summed=2, **options) + by_proportion = held_out_prediction( + studies(), CONTRAST, proportion_of_studies=0.7, **options + ) + for name in ("auc", "scores", "features"): + assert getattr(by_number, name).equals(getattr(by_proportion, name)), name + every = held_out_prediction(studies(), CONTRAST, **options) + assert not every.features.equals(by_number.features) + settings = by_number.settings, by_proportion.settings + assert [s["n_studies_summed"] for s in settings] == [2, None] + assert [s["proportion_of_studies"] for s in settings] == [None, 0.7] + with pytest.raises( + ValueError, + match=r"\(n_studies_summed\) or their proportion \(proportion_of_studies\), " + "not both", + ): + held_out_prediction( + studies(), CONTRAST, n_studies_summed=2, proportion_of_studies=0.7 + ) + for number in (0, 4): + with pytest.raises( + ValueError, + match="n_studies_summed must be from 1 to the number of studies, 3, got " + f"{number}", + ): + held_out_prediction(studies(), CONTRAST, n_studies_summed=number) + + def test_the_result_is_a_record_that_cannot_change() -> None: found = prediction() - assert found.settings["n_genes"] == 6 + assert found.settings["n_features"] == 6 assert found.settings["meta_analysis"] == { "method": "reml", "test": "knapp_hartung", @@ -271,10 +380,10 @@ def test_the_result_is_a_record_that_cannot_change() -> None: with pytest.raises(AttributeError, match="immutable"): found.auc = found.scores # type: ignore[misc] copy = pickle.loads(pickle.dumps(found)) - assert copy.auc.equals(found.auc) and copy.genes.equals(found.genes) + assert copy.auc.equals(found.auc) and copy.features.equals(found.features) assert "sumrank: mean AUC" in repr(found) assert max(map(len, repr(found).splitlines())) <= 80 - with pytest.raises(TypeError, match="genes must be a Table"): + with pytest.raises(TypeError, match="features must be a Table"): HeldOutPrediction(found.auc, found.scores, {}, {}) # type: ignore[arg-type] @@ -284,8 +393,8 @@ def test_the_result_is_a_record_that_cannot_change() -> None: ({"case": "ctrl"}, ValueError, "both 'ctrl'"), ({"variable": "donor"}, ValueError, "not a variable of the design"), ({"variable": 1}, TypeError, "variable must be a string"), - ({"n_genes": 0}, ValueError, "at least 1"), - ({"n_genes": 2.5}, TypeError, "whole number"), + ({"n_features": 0}, ValueError, "at least 1"), + ({"n_features": 2.5}, TypeError, "whole number"), ({"groups": []}, ValueError, "groups is empty"), ({"groups": ["T", "T"]}, ValueError, "more than once"), ({"groups": "NK"}, KeyError, "no group"), @@ -296,11 +405,10 @@ def test_the_result_is_a_record_that_cannot_change() -> None: def test_invalid_arguments_are_refused( arguments: dict[str, object], error: type[Exception], match: str ) -> None: - collection, results = studies() given = {"contrast": CONTRAST, "variable": "condition", "case": "case"} given |= {"control": "ctrl"} | arguments with pytest.raises(error, match=match): - held_out_prediction(collection, results, **given) # type: ignore[arg-type] + held_out_prediction(studies(), **given) # type: ignore[arg-type] def test_the_figure_shows_every_study_and_method() -> None: diff --git a/tests/test_interferon_stimulated_pbmcs.py b/tests/test_interferon_stimulated_pbmcs.py index 6f0506e..cf6a5b8 100644 --- a/tests/test_interferon_stimulated_pbmcs.py +++ b/tests/test_interferon_stimulated_pbmcs.py @@ -19,9 +19,7 @@ import numpy as np import pytest -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import limma_voom +from cytepool import Cube, Design, limma_voom DATA = Path(__file__).parent / "data" / "interferon_stimulated_pbmcs" / "limma.npz" EXACT = 1e-10 @@ -32,7 +30,7 @@ "t", "p_value", "adjusted_p_value", - "b_statistic", + "log_odds", ) @@ -81,7 +79,7 @@ def test_limma_voom_matches_limma_on_published_counts() -> None: assert result.samples["used"].tolist() == fixture["limma used"].tolist() assert record["aliased_columns"] == fixture["limma aliased"].tolist() assert result.summary["df_residual"][0] == fixture["limma df"][0] - prior_df = result.summary["prior_df"][0] + prior_df = result.summary["df_prior"][0] assert prior_df == pytest.approx(fixture["limma df"][1], rel=EXACT) table = result.table(group, "stim_vs_ctrl") diff --git a/tests/test_kenward_roger_and_f_tests.py b/tests/test_kenward_roger_and_f_tests.py index ae6fe9a..f3f90c9 100644 --- a/tests/test_kenward_roger_and_f_tests.py +++ b/tests/test_kenward_roger_and_f_tests.py @@ -20,10 +20,8 @@ import numpy as np import pytest +from cytepool import Cube, Design, dream_voom, from_bulk from cytepool._jit import thread_limit -from cytepool.cube import Cube, from_bulk -from cytepool.design import Design -from cytepool.differential_expression import dream_voom from cytepool.stats._fixed_effect_inference import combined_df from cytepool.stats.dream import moderated_fixed_effect_tests, voom_with_dream_weights from cytepool.stats.mixed_models import ( @@ -178,7 +176,7 @@ def study(name: str, method: str) -> tuple[Any, Any]: data, samples = fixture(f"study_{name}") design = Design(samples, "~ time + age", random="(1 | donor)") voom = voom_with_dream_weights(data["counts"], design, span=0.5) - fit = fit_mixed_model(voom.log_cpm, design, weights=voom.weights) + fit = fit_mixed_model(voom.log2_cpm, design, weights=voom.weights) tests = fixed_effect_tests(fit, CONTRASTS, df_method=method) # type: ignore[arg-type] time = fixed_effect_tests( fit, @@ -210,7 +208,7 @@ def test_moderated_tests_match_dream(name: str, ddf: str, method: str) -> None: assert_relative(moderated.tests.standard_error, reference["se"], CLOSE) assert_relative(moderated.tests.df, reference["df"], CLOSE) assert_relative(moderated.t, reference["t"], CLOSE, 1.0) - assert_relative(moderated.lods, reference["lods"], CLOSE, 1.0) + assert_relative(moderated.log_odds, reference["lods"], CLOSE, 1.0) assert_relative(moderated.z, reference["z"], CLOSE, 1.0) assert_relative(moderated.df_total, reference["df_total"], CLOSE) assert_relative(moderated.p_value, reference["p"], CLOSE, p_scale(reference["p"])) @@ -268,7 +266,7 @@ def test_dream_voom_by_default_chooses_the_degrees_of_freedom_as_dream_does() -> } assert_relative(table["t"], reference["t"], CLOSE, 1.0) assert_relative(table["df"], reference["df_total"], CLOSE) - assert_relative(table["b_statistic"], reference["lods"], CLOSE, 1.0) + assert_relative(table["log_odds"], reference["lods"], CLOSE, 1.0) assert_relative(table["z_statistic"], reference["z"], CLOSE, 1.0) assert_relative( table["p_value"], reference["p"], CLOSE, p_scale(reference["p"]) diff --git a/tests/test_linear_models.py b/tests/test_linear_models.py index eaae349..cab2202 100644 --- a/tests/test_linear_models.py +++ b/tests/test_linear_models.py @@ -16,9 +16,8 @@ import numpy as np import pytest +from cytepool import Design, from_bulk from cytepool._jit import thread_limit -from cytepool.cube import from_bulk -from cytepool.design import Design from cytepool.stats import linear_models from cytepool.stats.linear_models import LinearModelFit, contrasts_fit, fit_linear_model diff --git a/tests/test_masked_inputs.py b/tests/test_masked_inputs.py index 8d58aca..f380d2d 100644 --- a/tests/test_masked_inputs.py +++ b/tests/test_masked_inputs.py @@ -44,28 +44,41 @@ from numpy.testing import assert_array_equal import cytepool +from cytepool import ( + Cube, + CubeCollection, + Design, + GeneSets, + Network, + StudyResults, + Table, + camera, + dream_voom, + from_bulk, + from_cells, + gsea, + gsva, + held_out_prediction, + limma_trend, + limma_voom, + meta_analysis, + over_representation, + propeller, + replication, + sumrank, + to_anndata, + to_mudata, + ulm, + variance_partitioning, +) from cytepool._masked_arrays import reject_masked -from cytepool.activity import ulm -from cytepool.composition import propeller -from cytepool.cube import Cube, CubeCollection, from_bulk, from_cells -from cytepool.design import Design, make_contrasts -from cytepool.differential_expression import dream_voom, limma_voom -from cytepool.gene_sets import GeneSets -from cytepool.gsea import gsea -from cytepool.held_out_prediction import held_out_prediction -from cytepool.io.anndata import to_anndata, to_mudata +from cytepool.design import make_contrasts from cytepool.io.zarr import open_array, write_array, write_group -from cytepool.meta_analysis import differential_expression_meta_analysis -from cytepool.networks import Network -from cytepool.normalization import cpm, filter_by_expression, log_cpm -from cytepool.over_representation import over_representation -from cytepool.pathway_scores import gsva -from cytepool.replication import differential_expression_replication +from cytepool.normalization import cpm, filter_by_expression, log2_cpm from cytepool.stats import distributions, special from cytepool.stats.deseq2 import deseq2_dispersions, deseq2_wald_test, fit_deseq2 from cytepool.stats.distributions import norm_cdf, t_sf_scalar from cytepool.stats.empirical_bayes import moderated_statistics, squeeze_variances -from cytepool.stats.gene_set_tests import camera from cytepool.stats.linear_models import contrasts_fit, fit_linear_model from cytepool.stats.mixed_models import fit_mixed_model, fixed_effect_tests from cytepool.stats.multiple_testing import adjust_p_values @@ -73,9 +86,6 @@ from cytepool.stats.smoothing import lowess from cytepool.stats.quasi_likelihood import quasi_likelihood_f_test from cytepool.stats.voom import voom, voom_with_quality_weights -from cytepool.sumrank import differential_expression_sumrank -from cytepool.table import Table -from cytepool.variance_partitioning import variance_partitioning # anndata and mudata, which the conversions of cubes call, warn about their own use # of pandas options that pandas 4 removes. @@ -179,7 +189,7 @@ def new_path(self) -> Path: @pytest.fixture(scope="module") def context(tmp_path_factory: pytest.TempPathFactory) -> Context: directory = tmp_path_factory.mktemp("masked_inputs") - expression = log_cpm(COUNTS) + expression = log2_cpm(COUNTS) expression[0, 0] = np.nan fit = fit_linear_model(expression, DESIGN) write_array(directory / "array", np.arange(12.0).reshape(3, 4), chunks=(2, 2)) @@ -188,7 +198,9 @@ def context(tmp_path_factory: pytest.TempPathFactory) -> Context: expression=expression, fit=fit, contrast_fit=contrasts_fit(fit, np.eye(2), names=["first", "second"]), - cube=from_cells(CELL_COUNTS, samples=CELL_SAMPLES, groups=CELL_GROUPS), + cube=from_cells( + CELL_COUNTS, sample_labels=CELL_SAMPLES, group_labels=CELL_GROUPS + ), design=Design(SAMPLE_TABLE, "~ condition"), zarr_array=open_array(directory / "array"), ) @@ -261,10 +273,10 @@ def _collection() -> CubeCollection: def _from_cells_arguments(context: Context) -> dict[str, Any]: return { "counts": CELL_COUNTS, - "samples": CELL_SAMPLES, - "groups": CELL_GROUPS, - "sample_table": {"sample_id": ["S1", "S2"]}, - "group_table": {"group_id": ["B", "T"]}, + "sample_labels": CELL_SAMPLES, + "group_labels": CELL_GROUPS, + "samples": {"sample_id": ["S1", "S2"]}, + "groups": {"group_id": ["B", "T"]}, "features": {"feature_id": ["g1", "g2", "g3"]}, "library_sizes": [3.0, 4.0, 7.0, 5.0], "layers": ["counts", "detected"], @@ -374,7 +386,11 @@ def _scverse_arguments(context: Context, container: str) -> dict[str, Any]: pytest.importorskip("mudata") data = {"mdata": to_mudata(cube)} else: - data = {"adata": to_anndata(cube), "sample": "sample_id", "group": "group_id"} + data = { + "adata": to_anndata(cube), + "sample_column": "sample_id", + "group_column": "group_id", + } return { **data, "layers": {"counts": "counts"}, @@ -383,8 +399,8 @@ def _scverse_arguments(context: Context, container: str) -> dict[str, Any]: index=cube.samples["sample_id"].tolist(), columns=cube.groups["group_id"].tolist(), ), - "sample_table": {"sample_id": ["S1", "S2"]}, - "group_table": {"group_id": ["B", "T"]}, + "samples": {"sample_id": ["S1", "S2"]}, + "groups": {"group_id": ["B", "T"]}, "normalization": {"method": "library_size", "target_sum": 1e4}, "source": {"dataset": "test", "values": [1, 2]}, } @@ -395,15 +411,15 @@ def _scverse_arguments(context: Context, container: str) -> dict[str, Any]: _SCVERSE_ARRAYS: dict[str, str | None] = { "layers": None, "n_cells": None, - "sample_table": None, - "group_table": None, + "samples": None, + "groups": None, "normalization": "None", "source": "None", } _SCVERSE_KEYS = { "layers": "counts", - "sample_table": "sample_id", - "group_table": "group_id", + "samples": "sample_id", + "groups": "group_id", "normalization": "target_sum", "source": "values", } @@ -420,11 +436,11 @@ def _deseq2_fit() -> Any: def _gene_set_arguments() -> dict[str, Any]: """log-CPM of COUNTS (complete), the design, a contrast and one set of two ids.""" return { - "y": log_cpm(COUNTS), + "data": log2_cpm(COUNTS), "design": DESIGN, "contrast": [0.0, 1.0], "gene_sets": {"set": ["g1", "g2"]}, - "gene_ids": [f"g{i}" for i in range(COUNTS.shape[1])], + "feature_ids": [f"g{i}" for i in range(COUNTS.shape[1])], "weights": np.full(COUNTS.shape, 2.0), } @@ -432,7 +448,7 @@ def _gene_set_arguments() -> dict[str, Any]: def _mixed_model(context: Context) -> dict[str, Any]: """Complete log-CPM values, and a design with a random intercept per donor.""" return { - "y": log_cpm(COUNTS), + "y": log2_cpm(COUNTS), "design": Design( SAMPLE_TABLE, "~ condition", random="(1 | donor)", levels=LEVELS ), @@ -452,7 +468,7 @@ def _meta_analysis_arguments(context: Context) -> dict[str, Any]: expression_filter=False, ) return { - "results": {"first": result, "second": result}, + "studies": {"first": result, "second": result}, "groups": ["bulk"], "contrasts": ["stim"], "min_studies": 2, @@ -477,10 +493,8 @@ def _replication_studies() -> tuple[CubeCollection, dict[str, Any]]: def _replication_arguments(context: Context | None) -> dict[str, Any]: """The replication of the two studies, with a value for every argument.""" - collection, results = _replication_studies() return { - "collection": collection, - "results": results, + "studies": StudyResults(*_replication_studies()), "group": "bulk", "contrast": "stim", "alpha": 0.5, @@ -495,7 +509,7 @@ def _replication_arguments(context: Context | None) -> dict[str, Any]: @functools.cache def _replication() -> Any: - return differential_expression_replication(**_replication_arguments(None)) + return replication(**_replication_arguments(None)) def _axes() -> Any: @@ -513,7 +527,9 @@ def _bulk_cube() -> Cube: def _pathway_scores() -> Any: """GSVA scores of two sets in the four samples of COUNTS.""" genes = [f"g{j}" for j in range(COUNTS.shape[1])] - return gsva(log_cpm(COUNTS), {"set": genes[:3], "other": genes[3:]}, gene_ids=genes) + return gsva( + log2_cpm(COUNTS), {"set": genes[:3], "other": genes[3:]}, feature_ids=genes + ) @functools.cache @@ -521,7 +537,7 @@ def _activities() -> Any: """ULM activities of two sources in the four samples of COUNTS.""" genes = [f"g{j}" for j in range(COUNTS.shape[1])] network = {"A": {"g1": 1.0, "g2": -1.0, "g5": 0.5}, "B": ["g0", "g3", "g4"]} - return ulm(log_cpm(COUNTS), network, gene_ids=genes, + return ulm(log2_cpm(COUNTS), network, feature_ids=genes, sample_ids=SAMPLE_TABLE["sample_id"], min_targets=2) # fmt: skip @@ -554,12 +570,24 @@ def _tested(context: Context, **options: Any) -> Any: ) +def _trend_tested(context: Context) -> Any: + """limma_trend of the bulk cube of _differential_expression, every gene tested, + for romer, which takes no observation weights.""" + arguments = _differential_expression(context) + return limma_trend( + arguments["cube"], + arguments["design"], + contrasts=arguments["contrasts"], + expression_filter=False, + ) + + def _forest_arguments(context: Context) -> dict[str, Any]: - """The meta-analysis of _meta_analysis_arguments, its studies and a gene.""" - results = _meta_analysis_arguments(context)["results"] - meta = differential_expression_meta_analysis(results) + """The meta-analysis of _meta_analysis_arguments, which keeps its studies, and a + gene.""" + meta = meta_analysis(_meta_analysis_arguments(context)["studies"]) feature = meta.table("bulk", "stim")["feature_id"][0] - return {"meta": meta, "studies": results, "feature": feature} + return {"meta": meta, "feature": feature} @functools.cache @@ -582,10 +610,8 @@ def _studies() -> tuple[CubeCollection, dict[str, Any]]: def _sumrank_arguments(context: Context) -> dict[str, Any]: """SumRank of the two studies, with permutations and a value for every option.""" - collection, results = _studies() return { - "collection": collection, - "results": results, + "studies": StudyResults(*_studies()), "group": "bulk", "contrast": "stim", "proportion_of_studies": 1.0, @@ -597,16 +623,14 @@ def _sumrank_arguments(context: Context) -> dict[str, Any]: def _held_out_arguments(context: Context) -> dict[str, Any]: """Held-out prediction of the two studies, with a value for every option.""" - collection, results = _studies() return { - "collection": collection, - "results": results, + "studies": StudyResults(*_studies()), "contrast": "stim", "variable": "condition", "case": "stim", "control": "ctrl", "groups": ["bulk"], - "n_genes": 2, + "n_features": 2, "proportion_of_studies": 1.0, } @@ -735,10 +759,10 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: _from_cells_arguments, { "counts": None, + "sample_labels": None, + "group_labels": None, "samples": None, "groups": None, - "sample_table": None, - "group_table": None, "features": None, "library_sizes": None, "layers": None, @@ -748,8 +772,8 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: }, options=("target_sum",), keys={ - "sample_table": "sample_id", - "group_table": "group_id", + "samples": "sample_id", + "groups": "group_id", "features": "feature_id", "source": "values", }, @@ -758,8 +782,8 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "cytepool.cube.pool.from_cells[feature ids]": Entry( lambda c: { "counts": CELL_COUNTS, - "samples": CELL_SAMPLES, - "groups": CELL_GROUPS, + "sample_labels": CELL_SAMPLES, + "group_labels": CELL_GROUPS, "features": ["g1", "g2", "g3"], }, {"features": None}, @@ -806,7 +830,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "cytepool.io.anndata.from_anndata": Entry( lambda c: _scverse_arguments(c, "anndata"), _SCVERSE_ARRAYS, - options=("sample", "group"), + options=("sample_column", "group_column"), objects=("adata",), keys=_SCVERSE_KEYS, ), @@ -821,11 +845,11 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "path": c.new_path(), "data": np.arange(6.0).reshape(2, 3), "chunks": (1, 2), - "level": 4, + "compression_level": 4, "dimension_names": ["row", "column"], }, {"data": None, "chunks": None, "dimension_names": None}, - options=("compression", "level"), + options=("compression", "compression_level"), objects=("path", "attributes"), ), # Designs and contrasts. @@ -915,18 +939,23 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: _normalization(library_sizes=LIBRARY_SIZES, norm_factors=NORM_FACTORS), {"counts": None, "library_sizes": None, "norm_factors": None}, ), - "cytepool.normalization.log_cpm": Entry( + "cytepool.normalization.log2_cpm": Entry( _normalization(library_sizes=LIBRARY_SIZES, norm_factors=NORM_FACTORS), {"counts": None, "library_sizes": None, "norm_factors": None}, options=("prior_count",), ), "cytepool.normalization.filter_by_expression": Entry( _normalization( - group=["a", "b", "a", "b"], + sample_groups=["a", "b", "a", "b"], library_sizes=LIBRARY_SIZES, norm_factors=NORM_FACTORS, ), - {"counts": None, "group": None, "library_sizes": None, "norm_factors": None}, + { + "counts": None, + "sample_groups": None, + "library_sizes": None, + "norm_factors": None, + }, options=("min_count", "min_total_count", "large_n", "min_prop"), ), # A design instead of groups; edgeR refuses both at once. @@ -1095,7 +1124,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "cytepool.stats.voom.VoomResult": Entry( lambda c: _fields(voom_with_quality_weights(COUNTS, DESIGN)), { - "log_cpm": None, + "log2_cpm": None, "weights": None, "effective_library_sizes": None, "mean_log_count": "NaN", @@ -1248,7 +1277,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: objects=("fit",), ), # Quasi-likelihood fits and tests (edgeR's glmQLFit and glmQLFTest). - "cytepool.stats.quasi_likelihood.average_log_cpm": Entry( + "cytepool.stats.quasi_likelihood.average_log2_cpm": Entry( lambda c: { "counts": COUNTS, "library_sizes": LIBRARY_SIZES, @@ -1266,7 +1295,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: }, options=("prior_count",), ), - "cytepool.stats.quasi_likelihood.average_log_cpm[offsets]": Entry( + "cytepool.stats.quasi_likelihood.average_log2_cpm[offsets]": Entry( lambda c: {"counts": COUNTS, "offsets": np.log(LIBRARY_SIZES)}, {"offsets": None}, ), @@ -1452,11 +1481,17 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "cytepool.stats.empirical_bayes.treat[fc]": Entry( lambda c: {"fit": c.fit, "fc": 1.5}, {}, options=("fc",), objects=("fit",) ), - # Gene-set tests take complete values: none may be missing. - "cytepool.stats.gene_set_tests.camera": Entry( - lambda c: {**_gene_set_arguments(), "inter_gene_correlation": 0.05}, + # Gene-set tests take complete values: none may be missing. trend and robust + # are False for an array, and the result's own by default. + "cytepool.gene_set_tests.camera": Entry( + lambda c: { + **_gene_set_arguments(), + "inter_gene_correlation": 0.05, + "trend": False, + "robust": False, + }, dict.fromkeys( - ("y", "design", "contrast", "gene_sets", "gene_ids", "weights"), None + ("data", "design", "contrast", "gene_sets", "feature_ids", "weights"), None ), options=( "inter_gene_correlation", @@ -1467,16 +1502,33 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), keys={"gene_sets": "set"}, ), - "cytepool.stats.gene_set_tests.fry": Entry( - lambda c: _gene_set_arguments(), + "cytepool.gene_set_tests.fry": Entry( + lambda c: {**_gene_set_arguments(), "trend": False, "robust": False}, dict.fromkeys( - ("y", "design", "contrast", "gene_sets", "gene_ids", "weights"), None + ("data", "design", "contrast", "gene_sets", "feature_ids", "weights"), None ), options=("standardize", "trend", "robust"), keys={"gene_sets": "set"}, ), + # A group and a contrast of a differential-expression result, whose features are + # named by a column. + **{ + f"cytepool.gene_set_tests.{test.__name__}[result]": Entry( + lambda c: { + "data": _tested(c), + "gene_sets": {"set": ["feature_1", "feature_2"]}, + "group": "bulk", + "contrast": "stim", + "id_column": "feature_id", + }, + {}, + options=("group", "contrast", "id_column"), + objects=("data", "gene_sets"), + ) + for test in (camera, cytepool.fry) + }, # Rotation tests take complete values too; contribution weights are finite. - "cytepool.stats.rotation_gene_set_tests.roast": Entry( + "cytepool.rotation_gene_set_tests.roast": Entry( lambda c: ( { key: value @@ -1487,17 +1539,19 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "gene_set": ["g1", "g2"], "gene_weights": np.linspace(-1.0, 1.0, COUNTS.shape[1]), "n_rotations": 9, + "trend": False, + "robust": False, "rng": 1, "n_threads": 1, } ), dict.fromkeys( ( - "y", + "data", "design", "contrast", "gene_set", - "gene_ids", + "feature_ids", "weights", "gene_weights", ), @@ -1512,21 +1566,23 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "n_threads", ), ), - "cytepool.stats.rotation_gene_set_tests.mroast": Entry( + "cytepool.rotation_gene_set_tests.mroast": Entry( lambda c: { **_gene_set_arguments(), "gene_weights": np.linspace(-1.0, 1.0, COUNTS.shape[1]), "n_rotations": 9, + "trend": False, + "robust": False, "rng": 1, "n_threads": 1, }, dict.fromkeys( ( - "y", + "data", "design", "contrast", "gene_sets", - "gene_ids", + "feature_ids", "weights", "gene_weights", ), @@ -1544,7 +1600,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: keys={"gene_sets": "set"}, ), # romer takes sample weights only. - "cytepool.stats.rotation_gene_set_tests.romer": Entry( + "cytepool.rotation_gene_set_tests.romer": Entry( lambda c: { **_gene_set_arguments(), "weights": np.full(COUNTS.shape[0], 2.0), @@ -1553,7 +1609,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "n_threads": 1, }, dict.fromkeys( - ("y", "design", "contrast", "gene_sets", "gene_ids", "weights"), None + ("data", "design", "contrast", "gene_sets", "feature_ids", "weights"), None ), options=( "set_statistic", @@ -1564,20 +1620,47 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), keys={"gene_sets": "set"}, ), + # A group and a contrast of a result: of limma_voom for roast and mroast, of + # limma_trend for romer, which takes no observation weights. + **{ + f"cytepool.rotation_gene_set_tests.{name}[result]": Entry( + lambda c, tested=tested, sets=sets: { + "data": tested(c), + sets: ( + ["feature_1", "feature_2"] + if sets == "gene_set" + else {"set": ["feature_1", "feature_2"]} + ), + "group": "bulk", + "contrast": "stim", + "id_column": "feature_id", + "n_rotations": 9, + "rng": 1, + }, + {}, + options=("group", "contrast", "id_column"), + objects=("data", sets), + ) + for name, tested, sets in ( + ("roast", _tested, "gene_set"), + ("mroast", _tested, "gene_sets"), + ("romer", _trend_tested, "gene_sets"), + ) + }, # Gene-set collections: no gene of a set may be missing; the genes of an # analysis, a selection and a universe mark missing ids with None. "cytepool.gene_sets.GeneSets": Entry( lambda c: { "sets": {"set": ["g1", "g2"]}, "descriptions": {"set": "two genes"}, - "source": "a curated list", + "name": "a curated list", "version": "1", "licence": "CC0", "file": "sets.gmt", "sha256": "0" * 64, }, {"sets": None}, - options=("source", "version", "licence", "file", "sha256"), + options=("name", "version", "licence", "file", "sha256"), objects=("descriptions",), keys={"sets": "set"}, ), @@ -1611,7 +1694,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), # A list of selected genes with a universe; the options that read a table are # checked first. - "cytepool.over_representation.over_representation": Entry( + "cytepool.over_representation_analysis.over_representation": Entry( lambda c: { "genes": ["g1", "g2", None], "gene_sets": {"set": ["g1", "g3"]}, @@ -1626,7 +1709,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: keys={"gene_sets": "set"}, ), # A differential-expression result, of which a group and a contrast are tested. - "cytepool.over_representation.over_representation[result]": Entry( + "cytepool.over_representation_analysis.over_representation[result]": Entry( lambda c: { "genes": _tested(c), "gene_sets": {"set": ["feature_1", "feature_2"]}, @@ -1638,7 +1721,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: objects=("genes", "gene_sets"), ), # Statistics by gene id, ranked; the options that read a table are checked too. - "cytepool.gsea.gsea": Entry( + "cytepool.gene_set_enrichment.gsea": Entry( lambda c: { "statistics": RANKED, "gene_sets": {"set": ["g1", "g2", "g5"]}, @@ -1673,7 +1756,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: keys={"statistics": "g0", "gene_sets": "set"}, ), # A differential-expression result, of which a group and a contrast are ranked. - "cytepool.gsea.gsea[result]": Entry( + "cytepool.gene_set_enrichment.gsea[result]": Entry( lambda c: { "statistics": _tested(c), "gene_sets": {"set": ["feature_1", "feature_2"]}, @@ -1691,16 +1774,16 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: **{ f"cytepool.pathway_scores.{name}": Entry( lambda c, options=options: { - "data": c.expression if missing else log_cpm(COUNTS), + "data": c.expression if missing else log2_cpm(COUNTS), "gene_sets": {"set": ["g1", "g2", "g5"], "other": ["g0", "g3"]}, - "gene_ids": [f"g{j}" for j in range(7)] + [None], + "feature_ids": [f"g{j}" for j in range(7)] + [None], "sample_ids": ["S1", "S2", "S3", "S4"], **options, }, { "data": missing, "gene_sets": None, - "gene_ids": "None", + "feature_ids": "None", "sample_ids": None, }, options=tuple(options), @@ -1744,14 +1827,14 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "set": ["feature_1", "feature_2", "feature_5"], "other": ["feature_0", "feature_3"], }, - "gene_ids": "feature_id", + "id_column": "feature_id", "group": "bulk", "values": "counts", "min_cells": 1, "expression_filter": False, }, {}, - options=("gene_ids", "group", "values", "min_cells", "expression_filter"), + options=("id_column", "group", "values", "min_cells", "expression_filter"), objects=("data", "gene_sets"), ) for name in ("gsva", "ssgsea", "combined_z_scores", "plage") @@ -1763,7 +1846,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: lambda c, options=options: { "data": c.expression, "network": NETWORK, - "gene_ids": [f"g{j}" for j in range(7)] + [None], + "feature_ids": [f"g{j}" for j in range(7)] + [None], "sample_ids": ["S1", "S2", "S3", "S4"], "min_targets": 2, **options, @@ -1771,7 +1854,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: { "data": "NaN", "network": "None or NaN", - "gene_ids": "None", + "feature_ids": "None", "sample_ids": None, }, options=("min_targets", *options), @@ -1793,14 +1876,14 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: lambda c: { "data": _bulk_cube(), "network": FEATURE_NETWORK, - "gene_ids": "feature_id", + "id_column": "feature_id", "group": "bulk", "min_cells": 1, "expression_filter": False, "min_targets": 2, }, {}, - options=("gene_ids", "group", "min_cells", "expression_filter"), + options=("id_column", "group", "min_cells", "expression_filter"), objects=("data", "network"), ) for name in ("ulm", "mlm", "weighted_aggregate") @@ -1846,8 +1929,8 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), # n and axis go through operator.index, which reads a 0-d masked integer. "cytepool.stats.multiple_testing.adjust_p_values": Entry( - lambda c: {"p": [0.01, 0.2, 0.5], "n": 5, "axis": 0}, - {"p": "NaN", "n": None, "axis": None}, + lambda c: {"p_values": [0.01, 0.2, 0.5], "n": 5, "axis": 0}, + {"p_values": "NaN", "n": None, "axis": None}, options=("method",), ), # Numbers of points and the settings of the rule, each a number. @@ -1980,10 +2063,10 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: }, "design": Design(SAMPLE_TABLE, "~ condition", levels=LEVELS), "contrasts": {"stim": [0.0, 1.0]}, - "f_test": "condition", + "f_tests": "condition", }, {"data": None, "contrasts": None}, - options=("f_test", "transform", "robust", "trend"), + options=("f_tests", "transform", "robust", "trend"), objects=("design",), keys={"data": "B", "contrasts": "stim"}, ), @@ -2001,34 +2084,34 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: {"contrasts": None}, ), # Principal components: of an array, or of a cube's group (an object). - "cytepool.principal_components.principal_components": Entry( - lambda c: {"data": log_cpm(COUNTS), "top": 4}, + "cytepool.principal_component_analysis.principal_components": Entry( + lambda c: {"data": log2_cpm(COUNTS), "top": 4}, {"data": None}, options=("top",), ), - "cytepool.principal_components.principal_components[cube]": Entry( + "cytepool.principal_component_analysis.principal_components[cube]": Entry( lambda c: {"data": _bulk_cube(), "group": "bulk", "min_cells": 1}, {}, options=("group", "min_cells"), objects=("data",), ), # Meta-analysis. NaN marks a study that did not measure a feature. - "cytepool.meta_analysis.meta_analysis": Entry( + "cytepool.stats.meta_analysis.fit_meta_analysis": Entry( lambda c: { "effects": [[0.1, 0.4], [0.3, np.nan], [0.2, 0.5]], "standard_errors": [[0.1, 0.2], [0.2, 0.3], [0.15, 0.1]], - "level": 0.9, + "confidence_level": 0.9, }, {"effects": "NaN", "standard_errors": "NaN"}, - options=("method", "test", "level"), + options=("method", "test", "confidence_level"), ), # min_studies goes through operator.index, which reads a 0-d masked integer; # the results were checked when they were built. - "cytepool.meta_analysis.differential_expression_meta_analysis": Entry( + "cytepool.meta_analysis_of_studies.meta_analysis": Entry( _meta_analysis_arguments, {"groups": None, "contrasts": None, "min_studies": None}, - options=("method", "test", "level"), - objects=("results",), + options=("method", "test", "confidence_level"), + objects=("studies",), ), # Combined p-values and pi0: NaN marks a study that did not test a feature, or a # missing p-value. @@ -2059,19 +2142,19 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), "cytepool.stats.multiple_testing.proportion_of_true_nulls": Entry( lambda c: { - "p": np.linspace(0.0, 1.0, 21), + "p_values": np.linspace(0.0, 1.0, 21), "lambdas": [0.2, 0.4, 0.6, 0.8], "method": "bootstrap", "smooth_df": 3.0, "smooth_log": False, }, - {"p": "NaN", "lambdas": None}, + {"p_values": "NaN", "lambdas": None}, options=("method", "smooth_df", "smooth_log"), ), # Replication: a mapping of weights by study, and options of - # proportion_of_true_nulls; the collection and results were checked when they - # were built. - "cytepool.replication.differential_expression_replication": Entry( + # proportion_of_true_nulls; the studies' results were checked when they were + # built. + "cytepool.replication_across_studies.replication": Entry( _replication_arguments, {"weights": None, "null_proportion": None}, options=( @@ -2083,12 +2166,13 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "conjunction_method", "meta_method", ), - objects=("collection", "results"), + objects=("studies",), keys={"weights": "A", "null_proportion": "lambdas"}, ), - # SumRank: NaN marks a gene a study did not test. n_permutations and n_genes go - # through operator.index, which reads a 0-d masked integer. - "cytepool.sumrank.sumrank": Entry( + # SumRank: NaN marks a gene a study did not test. n_permutations, n_features + # and n_studies_summed go through operator.index, which reads a 0-d masked + # integer. + "cytepool.stats.sumrank.fit_sumrank": Entry( lambda c: { "statistics": [[1.0, 2.0, np.nan, 0.5], [2.0, 1.0, 3.0, -1.0]], "proportion_of_studies": 0.5, @@ -2097,23 +2181,43 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: {"statistics": "NaN", "null_statistics": "NaN"}, options=("proportion_of_studies",), ), - "cytepool.sumrank.differential_expression_sumrank": Entry( + # A number of studies instead of a proportion; both at once are refused. + "cytepool.stats.sumrank.fit_sumrank[n_studies_summed]": Entry( + lambda c: { + "statistics": [[1.0, 2.0, np.nan, 0.5], [2.0, 1.0, 3.0, -1.0]], + "n_studies_summed": 2, + }, + {"n_studies_summed": None}, + ), + "cytepool.sumrank_across_studies.sumrank": Entry( _sumrank_arguments, {"n_permutations": None}, options=("group", "contrast", "proportion_of_studies", "permute", "rng"), - objects=("collection", "results"), + objects=("studies",), + ), + "cytepool.sumrank_across_studies.sumrank[n_studies_summed]": Entry( + lambda c: {"studies": StudyResults(*_studies()), "n_studies_summed": 2}, + {"n_studies_summed": None}, ), - "cytepool.held_out_prediction.held_out_prediction": Entry( + "cytepool.held_out_studies.held_out_prediction": Entry( _held_out_arguments, - {"groups": None, "n_genes": None}, + {"groups": None, "n_features": None}, options=("contrast", "variable", "case", "control", "proportion_of_studies"), - objects=("collection", "results"), + objects=("studies",), + ), + "cytepool.held_out_studies.held_out_prediction[n_studies_summed]": Entry( + lambda c: { + "studies": StudyResults(*_studies()), + "n_features": 2, + "n_studies_summed": 1, + }, + {"n_studies_summed": None}, ), # Figures, each drawn into an Axes that pyplot does not keep. A variable of the # samples is an array of labels, or a name; the other inputs are objects. "cytepool.plotting.plot_mds": Entry( lambda c: { - "data": log_cpm(COUNTS), + "data": log2_cpm(COUNTS), "color_by": ["a", "b", "a", "b"], "marker_by": ["x", "x", "y", "y"], "labels": ["S1", "S2", "S3", "S4"], @@ -2146,7 +2250,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), "cytepool.plotting.plot_pca": Entry( lambda c: { - "data": log_cpm(COUNTS), + "data": log2_cpm(COUNTS), "color_by": ["a", "b", "a", "b"], "marker_by": ["x", "x", "y", "y"], "labels": ["S1", "S2", "S3", "S4"], @@ -2180,16 +2284,16 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "cytepool.plotting.plot_dotplot": Entry( lambda c: { "cube": c.cube, - "genes": ["feature_0", "feature_2"], + "features": ["feature_0", "feature_2"], "groups": ["T", "B"], "samples": ["S1", "S2"], - "standard_scale": "gene", + "standard_scale": "feature", "mean_only_expressed": True, "vmin": 0.0, "vmax": 2.0, "ax": _axes(), }, - {"genes": None, "groups": None, "samples": None}, + {"features": None, "groups": None, "samples": None}, options=("standard_scale", "mean_only_expressed", "vmin", "vmax"), objects=("cube", "ax"), ), @@ -2330,11 +2434,11 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: lambda c: { "statistics": [0.5, -1.0, 2.0, 0.1, 3.0, -0.2, 1.1, 0.0], "gene_set": ["g1", "g2"], - "gene_ids": [f"g{j}" for j in range(8)], + "feature_ids": [f"g{j}" for j in range(8)], "span": 0.5, "ax": _axes(), }, - {"statistics": None, "gene_set": None, "gene_ids": None}, + {"statistics": None, "gene_set": None, "feature_ids": None}, options=("span",), objects=("ax",), ), @@ -2497,7 +2601,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), {}, options=("feature", "group", "contrast"), - objects=("meta", "studies", "ax"), + objects=("meta", "ax"), ), "cytepool.plotting.plot_study_agreement": Entry( lambda c: { @@ -2517,20 +2621,25 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), "cytepool.plotting.plot_sumrank": Entry( lambda c: { - "result": differential_expression_sumrank(*_studies(), "bulk", "stim"), + "result": sumrank(StudyResults(*_studies()), "bulk", "stim"), "direction": "down", "alpha": 0.1, - "n_labels": 2, + "top": 2, "ax": _axes(), }, {}, - options=("direction", "alpha", "n_labels"), + options=("direction", "alpha", "top"), objects=("result", "ax"), ), "cytepool.plotting.plot_held_out_auc": Entry( lambda c: { "prediction": held_out_prediction( - *_studies(), "stim", "condition", "stim", "ctrl", n_genes=2 + StudyResults(*_studies()), + "stim", + variable="condition", + case="stim", + control="ctrl", + n_features=2, ), "ax": _axes(), }, @@ -2548,17 +2657,17 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: options=("group", "sort"), objects=("data", "ax"), ), - # Genes named by labels, one per gene. - "cytepool.plotting.plot_gene_variance_fractions": Entry( + # Features named by labels, one per feature. + "cytepool.plotting.plot_feature_variance_fractions": Entry( lambda c: { "data": _variance_partition(), - "genes": ["x1", "x2"], + "features": ["x1", "x2"], "group": "bulk", "labels": [f"x{j}" for j in range(COUNTS.shape[1])], "sort": False, "ax": _axes(), }, - {"genes": None, "labels": None}, + {"features": None, "labels": None}, options=("group", "sort"), objects=("data", "ax"), ), @@ -2566,7 +2675,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: # column and per row, weights per sample and a covariate of the trend. "cytepool.score_tests.limma": Entry( lambda c: { - "data": log_cpm(COUNTS), + "data": log2_cpm(COUNTS), "design": Design( SAMPLE_TABLE, "~ condition", @@ -2608,9 +2717,9 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: keys={"levels": "condition", "contrasts": "stim"}, ), # Variance partitioning: a mixed model, a linear model, and every group of a cube. - "cytepool.variance_partitioning.variance_fractions": Entry( + "cytepool.stats.variance_fractions.variance_fractions": Entry( lambda c: { - "y": log_cpm(COUNTS), + "y": log2_cpm(COUNTS), "design": Design(SAMPLE_TABLE, "~ 1", random="(1 | donor)"), "weights": np.full(COUNTS.shape, 2.0), "method": "reml", @@ -2620,15 +2729,15 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: options=("method",), objects=("design",), ), - "cytepool.variance_partitioning.variance_fractions[linear]": Entry( + "cytepool.stats.variance_fractions.variance_fractions[linear]": Entry( lambda c: { - "y": log_cpm(COUNTS), + "y": log2_cpm(COUNTS), "design": Design(SAMPLE_TABLE, "~ condition", levels=LEVELS), "weights": np.full(4, 2.0), }, {"y": None, "weights": None}, ), - "cytepool.variance_partitioning.variance_partitioning": Entry( + "cytepool.variance_components.variance_partitioning": Entry( _variance_partitioning, { "groups": None, @@ -2640,7 +2749,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: objects=("cube", "design"), keys={"expression_filter": "min_count"}, ), - "cytepool.variance_partitioning.variance_partitioning[prior_weights]": Entry( + "cytepool.variance_components.variance_partitioning[prior_weights]": Entry( _variance_partitioning_by_cells, {}, options=("prior_weights",) ), } @@ -2675,7 +2784,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "cytepool.design.formula.quote_name": (("name",), "a variable name, a str"), "cytepool.io.cube_files.read_cube": (("path",), "a path"), "cytepool.io.cube_files.write_cube": ( - ("cube", "path", "compression", "level", "overwrite"), + ("cube", "path", "compression", "compression_level", "overwrite"), "a Cube, whose arrays were checked when it was built, a path and options", ), "cytepool.io.anndata.to_anndata": ( @@ -2692,7 +2801,7 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), "cytepool.io.zarr.ZarrArray.__array__": (("dtype", "copy"), "a dtype and a flag"), "cytepool.io.zarr.check_compression": ( - ("compression", "level"), + ("compression", "compression_level"), "a codec name and an integer level", ), "cytepool.io.zarr.open_array": (("path",), "a path"), @@ -2743,12 +2852,12 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ("group", "contrast"), "a group id and a contrast name, looked up", ), - "cytepool.variance_partitioning.VariancePartitioningResult": ( + "cytepool.variance_components.VariancePartitioningResult": ( ("variables", "summary", "samples", "tables", "provenance"), "variable names, Tables, which were checked when they were built, and a " "record kept as JSON, which cannot hold an array", ), - "cytepool.variance_partitioning.VariancePartitioningResult.table": ( + "cytepool.variance_components.VariancePartitioningResult.table": ( ("group",), "a group id, looked up", ), @@ -2756,7 +2865,6 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ( "contrasts", "tables", - "f_test", "summary", "samples", "proportions", @@ -2767,41 +2875,36 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: ), "cytepool.composition.CompositionResult.table": ( ("contrast",), - "a contrast name, looked up", + "a contrast name or the name of an F-test, looked up", ), - "cytepool.meta_analysis.MetaAnalysisResult": ( - ("summary", "studies", "tables", "provenance"), - "Tables, which were checked when they were built, and a record kept as JSON, " - "which cannot hold an array", + "cytepool.meta_analysis_of_studies.MetaAnalysisResult": ( + ("summary", "studies", "tables", "provenance", "study_results"), + "Tables and the studies' results, which were checked when they were built, " + "and a record kept as JSON, which cannot hold an array", ), - "cytepool.meta_analysis.MetaAnalysisResult.table": ( + "cytepool.meta_analysis_of_studies.MetaAnalysisResult.table": ( ("group", "contrast"), "a group id and a contrast name, looked up", ), - "cytepool.replication.ReplicationResult": ( - ("genes", "study_pairs", "found_in", "studies", "provenance"), + "cytepool.replication_across_studies.ReplicationResult": ( + ("features", "study_pairs", "found_in", "studies", "provenance"), "Tables, which were checked when they were built, and a record kept as JSON, " "which cannot hold an array", ), - "cytepool.sumrank.SumRankResult": ( - ("table", "studies", "provenance"), + "cytepool.sumrank_across_studies.SumRankResult": ( + ("features", "studies", "provenance"), "Tables, which were checked when they were built, and a record kept as JSON, " "which cannot hold an array", ), - "cytepool.held_out_prediction.HeldOutPrediction": ( - ("auc", "scores", "genes", "provenance"), + "cytepool.held_out_studies.HeldOutPrediction": ( + ("auc", "scores", "features", "provenance"), "Tables, which were checked when they were built, and a record kept as JSON, " "which cannot hold an array", ), - "cytepool.meta_analysis.align_study_results": ( - ("collection", "results", "group", "contrast"), - "a CubeCollection and results, which were checked when they were built, and a " - "group id and a contrast name, looked up", - ), "cytepool.gene_sets.GeneSets.__getitem__": (("name",), "a set name, looked up"), "cytepool.gene_sets.GeneSets.__contains__": (("name",), "a set name, looked up"), "cytepool.gene_sets.read_gmt": ( - ("path", "source", "version", "licence"), + ("path", "name", "version", "licence"), "a path, and text that the collection keeps", ), "cytepool.networks.Network.__getitem__": (("source",), "a source, looked up"), @@ -2836,16 +2939,16 @@ def _held_out_arguments(context: Context) -> dict[str, Any]: "cytepool.stats.mixed_models.FixedEffectFTest", "cytepool.stats.mixed_models.FixedEffectTests", "cytepool.stats.mixed_models.MixedModelFit", - "cytepool.meta_analysis.MetaAnalysisFit", + "cytepool.stats.meta_analysis.MetaAnalysisFit", "cytepool.stats.combined_p_values.CombinedPValues", "cytepool.stats.multiple_testing.TrueNullProportion", - "cytepool.sumrank.SumRankFit", - "cytepool.principal_components.PrincipalComponents", + "cytepool.stats.sumrank.SumRankFit", + "cytepool.principal_component_analysis.PrincipalComponents", "cytepool.stats.dream.ModeratedFixedEffectTests", "cytepool.stats.quasi_likelihood.QuasiLikelihoodFit", "cytepool.stats.quasi_likelihood.QuasiLikelihoodTest", "cytepool.stats.quasi_likelihood.UnitDevianceMoments", - "cytepool.variance_partitioning.VarianceFractions", + "cytepool.stats.variance_fractions.VarianceFractions", "cytepool.differential_expression.GroupFit", "cytepool.pathway_scores.PathwayScores", "cytepool.activity.Activities", @@ -3293,14 +3396,14 @@ def test_records_store_masked_fields_as_given(name: str) -> None: def test_a_masked_sample_label_is_refused_and_dropping_its_cells_works() -> None: samples = np.ma.array(["s1", "s1", "hidden_sample", "s2"], mask=[0, 0, 1, 0]) - with pytest.raises(TypeError, match=message("samples")): - from_cells(CELL_COUNTS, samples=samples, groups=CELL_GROUPS) + with pytest.raises(TypeError, match=message("sample_labels")): + from_cells(CELL_COUNTS, sample_labels=samples, group_labels=CELL_GROUPS) keep = ~np.ma.getmaskarray(samples) cube = from_cells( CELL_COUNTS[keep], - samples=samples.compressed(), - groups=np.array(CELL_GROUPS)[keep], + sample_labels=samples.compressed(), + group_labels=np.array(CELL_GROUPS)[keep], ) assert cube.samples["sample_id"].tolist() == ["s1", "s2"] # Cells 0 and 1 are s1 (groups B and T), cell 3 is s2 (group T). @@ -3385,7 +3488,7 @@ def __array__(self, dtype: Any = None, copy: bool | None = None) -> np.ndarray: ARRAY_LIKE_CALLS = [ - pytest.param(adjust_p_values, [0.01, 0.5, 0.02], "p", "NaN", id="p-values"), + pytest.param(adjust_p_values, [0.01, 0.5, 0.02], "p_values", "NaN", id="p-values"), pytest.param(norm_cdf, [0.5, 1.5], "x", "NaN", id="distribution"), pytest.param(cpm, COUNTS, "counts", None, id="normalization counts"), pytest.param( @@ -3407,16 +3510,16 @@ def __array__(self, dtype: Any = None, copy: bool | None = None) -> np.ndarray: ), pytest.param(from_bulk, COUNTS, "counts", None, id="bulk counts"), pytest.param( - lambda v: from_cells(v, samples=CELL_SAMPLES, groups=CELL_GROUPS), + lambda v: from_cells(v, sample_labels=CELL_SAMPLES, group_labels=CELL_GROUPS), CELL_COUNTS, "counts", None, id="cell counts", ), pytest.param( - lambda v: from_cells(CELL_COUNTS, samples=v, groups=CELL_GROUPS), + lambda v: from_cells(CELL_COUNTS, sample_labels=v, group_labels=CELL_GROUPS), CELL_SAMPLES, - "samples", + "sample_labels", None, id="cell labels", ), @@ -3462,7 +3565,9 @@ def test_pandas_inputs_keep_their_missing_values() -> None: frame = pandas.DataFrame({"x": np.ma.array([1.0, 2.0], mask=[0, 1])}) assert_array_equal(Table(frame)["x"], [1.0, np.nan]) labels = pandas.Categorical(CELL_SAMPLES, categories=["S2", "S1"]) - cube = from_cells(CELL_COUNTS, samples=labels, groups=pandas.Series(CELL_GROUPS)) + cube = from_cells( + CELL_COUNTS, sample_labels=labels, group_labels=pandas.Series(CELL_GROUPS) + ) assert cube.samples["sample_id"].tolist() == ["S1", "S2"] design = Design(pandas.DataFrame(SAMPLE_TABLE), "~ condition") assert design.columns == COLUMNS @@ -3521,7 +3626,7 @@ def test_masked_attributes_of_duck_typed_arguments_are_refused( ( f"counts.{part}", lambda counts=counts: from_cells( - counts, samples=CELL_SAMPLES, groups=CELL_GROUPS + counts, sample_labels=CELL_SAMPLES, group_labels=CELL_GROUPS ), ) ) diff --git a/tests/test_meta_analysis.py b/tests/test_meta_analysis.py index e702005..5777739 100644 --- a/tests/test_meta_analysis.py +++ b/tests/test_meta_analysis.py @@ -24,21 +24,21 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import ( +from cytepool import ( + Cube, + CubeCollection, + Design, DifferentialExpressionResult, - limma_voom, -) -from cytepool.meta_analysis import ( - MetaAnalysisFit, MetaAnalysisResult, - differential_expression_meta_analysis, + StudyResults, + Table, + analyse_each_study, + limma_voom, meta_analysis, ) from cytepool.stats.distributions import norm_isf, norm_sf, t_isf, t_sf +from cytepool.stats.meta_analysis import MetaAnalysisFit, fit_meta_analysis from cytepool.stats.multiple_testing import adjust_p_values -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "meta_analysis" EXACT = 1e-10 @@ -116,7 +116,7 @@ def test_meta_analysis_matches_metafor(case: int) -> None: higher = data[f"case{case} reml higher"] for method in METHODS: for test in TESTS: - fit = meta_analysis(y, se, method=method, test=test) + fit = fit_meta_analysis(y, se, method=method, test=test) theirs = data[f"case{case} {method} {test}"] # Where the restricted likelihood has another maximum, REML's is the # higher one; metafor's is the other, compared below. @@ -139,7 +139,7 @@ def test_reml_takes_the_higher_maximum_where_metafor_takes_the_other() -> None: higher = data[f"case{case} reml higher"] y, se = data[f"case{case} y"][:, higher], data[f"case{case} se"][:, higher] theirs = data[f"case{case} reml z"][FIELDS.index("tau_squared"), higher] - ours = meta_analysis(y, se).tau_squared + ours = fit_meta_analysis(y, se).tau_squared assert higher.any() gain = restricted_log_likelihood(y.T, se.T**2, ours) - restricted_log_likelihood( @@ -156,7 +156,7 @@ def test_reml_estimates_maximize_the_restricted_likelihood() -> None: y = rng.normal(0.0, se) + rng.normal(0.0, 0.3, n) y[0] += rng.normal(0.0, 3.0, n) # an imprecise outlying study: two maxima at times se[0] *= 5.0 - fit = meta_analysis(y, se) + fit = fit_meta_analysis(y, se) grid = np.concatenate([[0.0], np.geomspace(1e-9, 1e3, 1500)]) on_grid = restricted_log_likelihood( y.T[:, np.newaxis], se.T[:, np.newaxis] ** 2, grid @@ -178,8 +178,8 @@ def test_paule_mandel_solves_its_estimating_equation() -> None: se = rng.uniform(0.1, 0.5, (k, n)) y = rng.normal(0.0, 1.0, n) + rng.normal(0.0, rng.uniform(0, 0.6, n), (k, n)) y = y + rng.normal(0.0, se) - fit = meta_analysis(y, se, method="pm") - adjusted = meta_analysis(y, se, method="pm", test="knapp_hartung") + fit = fit_meta_analysis(y, se, method="pm") + adjusted = fit_meta_analysis(y, se, method="pm", test="knapp_hartung") w = 1.0 / (se**2 + fit.tau_squared) mu = np.sum(w * y, axis=0) / np.sum(w, axis=0) generalized_q = np.sum(w * (y - mu) ** 2, axis=0) @@ -212,8 +212,8 @@ def test_equal_standard_errors_give_the_closed_forms_of_every_estimator() -> Non tau2 = np.maximum(0.0, squares / (k - 1) - s**2) assert tau2[0] == pytest.approx(14 / 3 - 0.25) and tau2[1] == 0.0 for method in ("dl", "reml", "pm"): - fit = meta_analysis(y, se, method=method) - adjusted = meta_analysis(y, se, method=method, test="knapp_hartung") + fit = fit_meta_analysis(y, se, method=method) + adjusted = fit_meta_analysis(y, se, method=method, test="knapp_hartung") error = np.sqrt((s**2 + tau2) / k) assert_allclose(fit.tau_squared, tau2, rtol=1e-13, atol=1e-15) assert_allclose(fit.estimate, mean, rtol=1e-14) @@ -246,7 +246,7 @@ def test_two_studies_give_the_closed_form_of_every_estimator() -> None: expected = np.maximum(0.0, ((y[0] - y[1]) ** 2 - se[0] ** 2 - se[1] ** 2) / 2) assert expected[0] == pytest.approx(0.295) and expected[1] == 0.0 for method in ("dl", "reml", "pm"): - fit = meta_analysis(y, se, method=method) + fit = fit_meta_analysis(y, se, method=method) w = 1.0 / (se**2 + expected) assert_allclose(fit.tau_squared, expected, rtol=1e-13, atol=1e-16) assert_allclose(fit.estimate, np.sum(w * y, axis=0) / w.sum(axis=0), rtol=1e-13) @@ -256,7 +256,7 @@ def test_two_studies_give_the_closed_form_of_every_estimator() -> None: def test_one_study_is_its_own_estimate_with_metafors_conventions() -> None: for method in METHODS: for test in TESTS: - fit = meta_analysis([0.5], [0.2], method=method, test=test) + fit = fit_meta_analysis([0.5], [0.2], method=method, test=test) assert fit.estimate == 0.5 and fit.standard_error == 0.2 # The z-test, also with Knapp-Hartung, which needs two studies. assert fit.statistic == pytest.approx(2.5, rel=1e-15) @@ -271,7 +271,7 @@ def test_one_study_is_its_own_estimate_with_metafors_conventions() -> None: def test_fixed_effect_is_the_inverse_variance_weighted_mean() -> None: y = np.array([1.0, 2.0, 4.0]) v = np.array([1.0, 0.5, 0.25]) # weights 1, 2 and 4 - fit = meta_analysis(y, np.sqrt(v), method="fixed") + fit = fit_meta_analysis(y, np.sqrt(v), method="fixed") mean = (1 + 4 + 16) / 7 # 3 q = (1 - 3) ** 2 + 2 * (2 - 3) ** 2 + 4 * (4 - 3) ** 2 # 10 @@ -282,15 +282,17 @@ def test_fixed_effect_is_the_inverse_variance_weighted_mean() -> None: assert fit.i_squared == pytest.approx((q - 2) / q, rel=1e-15) assert fit.h_squared == pytest.approx(q / 2, rel=1e-15) assert fit.tau_squared == 0.0 - adjusted = meta_analysis(y, np.sqrt(v), method="fixed", test="knapp_hartung") + adjusted = fit_meta_analysis(y, np.sqrt(v), method="fixed", test="knapp_hartung") assert adjusted.standard_error == pytest.approx(math.sqrt(q / 2 / 7), rel=1e-15) -def test_level_sets_the_quantile_of_the_intervals() -> None: +def test_confidence_level_sets_the_quantile_of_the_intervals() -> None: y, se = np.array([0.2, 0.5, 0.9]), np.array([0.1, 0.2, 0.3]) for level in (0.5, 0.99): - fit = meta_analysis(y, se, method="dl", level=level) - adjusted = meta_analysis(y, se, method="dl", test="knapp_hartung", level=level) + fit = fit_meta_analysis(y, se, method="dl", confidence_level=level) + adjusted = fit_meta_analysis( + y, se, method="dl", test="knapp_hartung", confidence_level=level + ) alpha = (1 - level) / 2 assert fit.ci_upper - fit.estimate == pytest.approx( float(norm_isf(alpha)) * fit.standard_error, rel=1e-14 @@ -298,7 +300,7 @@ def test_level_sets_the_quantile_of_the_intervals() -> None: assert adjusted.estimate - adjusted.ci_lower == pytest.approx( float(t_isf(alpha, 2)) * adjusted.standard_error, rel=1e-14 ) - assert fit.level == level + assert fit.confidence_level == level # Invariants. @@ -326,8 +328,8 @@ def test_the_order_of_the_studies_does_not_matter(method: str) -> None: order = np.random.default_rng(6).permutation(y.shape[0]) for test in TESTS: assert_fits_equal( - meta_analysis(y, se, method=method, test=test), - meta_analysis(y[order], se[order], method=method, test=test), + fit_meta_analysis(y, se, method=method, test=test), + fit_meta_analysis(y[order], se[order], method=method, test=test), rtol=1e-11, ) @@ -335,25 +337,25 @@ def test_the_order_of_the_studies_does_not_matter(method: str) -> None: @pytest.mark.parametrize("method", METHODS) def test_scaling_and_shifting_the_effects(method: str) -> None: y, se = random_studies(7) - fit = meta_analysis(y, se, method=method, test="knapp_hartung") + fit = fit_meta_analysis(y, se, method=method, test="knapp_hartung") unchanged = ("statistic", "p_value", "q_statistic", "q_p_value", "i_squared") unchanged += ("h_squared", "n_studies") # A power of two scales every number exactly. - scaled = meta_analysis(1024 * y, 1024 * se, method=method, test="knapp_hartung") + scaled = fit_meta_analysis(1024 * y, 1024 * se, method=method, test="knapp_hartung") for field in ("estimate", "standard_error", "ci_lower", "ci_upper"): assert_array_equal(getattr(scaled, field), 1024 * getattr(fit, field)) assert_array_equal(scaled.tau_squared, 1024**2 * fit.tau_squared) for field in unchanged: assert_array_equal(getattr(scaled, field), getattr(fit, field)) # Another factor, to rounding. - scaled = meta_analysis(-3.7 * y, 3.7 * se, method=method, test="knapp_hartung") + scaled = fit_meta_analysis(-3.7 * y, 3.7 * se, method=method, test="knapp_hartung") assert_allclose(scaled.estimate, -3.7 * fit.estimate, rtol=1e-11) assert_allclose( scaled.tau_squared, 3.7**2 * fit.tau_squared, rtol=1e-11, atol=1e-15 ) assert_allclose(scaled.statistic, -fit.statistic, rtol=1e-11) # A shift moves the estimate and its interval only. - shifted = meta_analysis(y + 2.5, se, method=method, test="knapp_hartung") + shifted = fit_meta_analysis(y + 2.5, se, method=method, test="knapp_hartung") for field in ("estimate", "ci_lower", "ci_upper"): assert_allclose(getattr(shifted, field), getattr(fit, field) + 2.5, rtol=1e-12) for field in ("standard_error", "q_statistic", "i_squared", "h_squared"): @@ -368,14 +370,16 @@ def test_a_study_that_did_not_measure_a_feature_is_left_out(method: str) -> None se[3, 1] = np.nan # a standard error missing y[[0, 2], 2] = np.nan se[[0, 2], 2] = np.nan - fit = meta_analysis(y, se, method=method) + fit = fit_meta_analysis(y, se, method=method) for feature, studies in ((0, [0, 2, 3, 4]), (1, [0, 1, 2, 4]), (2, [1, 3, 4])): - alone = meta_analysis(y[studies, feature], se[studies, feature], method=method) + alone = fit_meta_analysis( + y[studies, feature], se[studies, feature], method=method + ) for field in FIELDS: assert getattr(fit, field)[feature] == getattr(alone, field), field assert_array_equal(fit.n_studies, [4, 4, 3, 5]) # Each feature is analysed on its own. - single = meta_analysis(y[:, 3:], se[:, 3:], method=method) + single = fit_meta_analysis(y[:, 3:], se[:, 3:], method=method) for field in FIELDS: assert_array_equal(getattr(single, field), getattr(fit, field)[3:]) @@ -384,7 +388,7 @@ def test_features_of_no_study_and_identical_effects() -> None: y = np.array([[np.nan, 0.5], [np.nan, 0.5], [np.nan, 0.5]]) se = np.array([[0.1, 0.1], [0.2, 0.2], [np.nan, 0.25]]) for method in METHODS: - fit = meta_analysis(y, se, method=method) + fit = fit_meta_analysis(y, se, method=method) assert fit.n_studies[0] == 0 assert all(np.isnan(getattr(fit, field)[0]) for field in FIELDS[:-1]) # Identical effects: no heterogeneity at all. @@ -399,7 +403,7 @@ def test_a_far_more_precise_study_dominates_without_cancellation() -> None: # weights of the other studies it does not, so DerSimonian-Laird keeps its digits. y = np.array([0.0, 1.0, 3.0]) v = np.array([1e-10, 1.0, 1.0]) - fit = meta_analysis(y, np.sqrt(v), method="dl") + fit = fit_meta_analysis(y, np.sqrt(v), method="dl") w = 1.0 / v total = w.sum() centre = np.sum(w * y) / total @@ -419,7 +423,7 @@ def test_standard_errors_far_below_the_spread_of_the_effects() -> None: se = scale * rng.uniform(0.5, 1.5, y.shape) variance = np.var(y, axis=0, ddof=1) for method in ("reml", "pm"): - fit = meta_analysis(y, se, method=method) + fit = fit_meta_analysis(y, se, method=method) assert fit.converged.all() assert_allclose(fit.tau_squared, variance, rtol=1e-12) # One such feature, where REML once found no maximum: metafor gives 1.70706. @@ -428,12 +432,14 @@ def test_standard_errors_far_below_the_spread_of_the_effects() -> None: se = np.array( [1.1163182354, 0.8131728389, 1.0605225626, 0.5383073261, 0.6153736126] ) - fit = meta_analysis(y, 1e-8 * se) + fit = fit_meta_analysis(y, 1e-8 * se) assert fit.tau_squared == pytest.approx(np.var(y, ddof=1), rel=1e-12) assert fit.p_value == pytest.approx(0.455020, abs=1e-6) # Effects 1e100 times their standard errors: no square overflows or underflows. for method in ("reml", "pm", "dl"): - extreme = meta_analysis([1e100, -1e100, 0.0], [1.0, 1.0, 1.0], method=method) + extreme = fit_meta_analysis( + [1e100, -1e100, 0.0], [1.0, 1.0, 1.0], method=method + ) assert extreme.tau_squared == pytest.approx(1e200, rel=1e-12) @@ -460,28 +466,40 @@ def test_standard_errors_far_below_the_spread_of_the_effects() -> None: ([0.1, 0.2], ["a", "b"], {}, TypeError, "standard_errors must hold real"), ([0.1], [0.1], {"method": "REML"}, ValueError, "method must be one of"), ([0.1], [0.1], {"test": "knha"}, ValueError, "test must be one of"), - ([0.1], [0.1], {"level": 95}, ValueError, "such as 0.95"), - ([0.1], [0.1], {"level": 0.0}, ValueError, "level must be in \\(0, 1\\)"), - ([0.1], [0.1], {"level": True}, TypeError, "level must be a real number"), + ([0.1], [0.1], {"confidence_level": 95}, ValueError, "such as 0.95"), + ( + [0.1], + [0.1], + {"confidence_level": 0.0}, + ValueError, + "confidence_level must be in \\(0, 1\\)", + ), + ( + [0.1], + [0.1], + {"confidence_level": True}, + TypeError, + "confidence_level must be a real number", + ), ], ) def test_invalid_arguments_are_refused( effects: Any, errors: Any, options: dict[str, Any], error: type, match: str ) -> None: with pytest.raises(error, match=match): - meta_analysis(effects, errors, **options) + fit_meta_analysis(effects, errors, **options) def test_features_and_their_shapes() -> None: - empty = meta_analysis(np.zeros((3, 0)), np.ones((3, 0))) + empty = fit_meta_analysis(np.zeros((3, 0)), np.ones((3, 0))) assert empty.estimate.shape == (0,) and empty.n_studies.dtype == np.int64 y, se = random_studies(9, n=3) - fit = meta_analysis(y, se) + fit = fit_meta_analysis(y, se) assert fit.estimate.shape == (3,) and not fit.estimate.flags.writeable - assert fit.method == "reml" and fit.test == "z" and fit.level == 0.95 + assert fit.method == "reml" and fit.test == "z" and fit.confidence_level == 0.95 assert repr(fit).startswith("MetaAnalysisFit(n_features=3, method='reml'") # Integer effects are read as numbers. - assert meta_analysis([1, 2, 4], [1, 1, 1], method="fixed").estimate == 7 / 3 + assert fit_meta_analysis([1, 2, 4], [1, 1, 1], method="fixed").estimate == 7 / 3 # Differential expression of several cohorts. @@ -520,9 +538,7 @@ def cohort_results() -> dict[str, DifferentialExpressionResult]: ) def test_meta_analysis_of_cohorts_matches_metafor(method: str, test: str) -> None: data = fixture("cohorts") - meta = differential_expression_meta_analysis( - cohort_results(), method=method, test=test - ) + meta = meta_analysis(cohort_results(), method=method, test=test) assert set(meta.tables) == {("T", "stim_vs_ctrl"), ("B", "stim_vs_ctrl")} for (group, contrast), table in meta.tables.items(): prefix = f"{method} {test} 2 {group} {contrast}" @@ -544,7 +560,7 @@ def test_meta_analysis_of_cohorts_matches_metafor(method: str, test: str) -> Non def test_features_are_matched_by_id_across_studies() -> None: results = cohort_results() - meta = differential_expression_meta_analysis(results, method="dl") + meta = meta_analysis(results, method="dl") table = meta.table("T", "stim_vs_ctrl") # Gather each feature's values by id, study by study, and meta-analyse them. by_id = { @@ -565,7 +581,7 @@ def test_features_are_matched_by_id_across_studies() -> None: feature = table["feature_id"][row] values = [found[feature] for found in by_id.values() if feature in found] y, se = np.array(values).T - alone = meta_analysis(y, se, method="dl") + alone = fit_meta_analysis(y, se, method="dl") assert table["n_studies"][row] == len(values) >= 2 assert table["log2_fold_change"][row] == pytest.approx( alone.estimate, rel=1e-14 @@ -579,8 +595,8 @@ def test_features_are_matched_by_id_across_studies() -> None: def test_the_order_of_the_studies_changes_no_number() -> None: results = cohort_results() - forward = differential_expression_meta_analysis(results) - backward = differential_expression_meta_analysis(dict(reversed(results.items()))) + forward = meta_analysis(results) + backward = meta_analysis(dict(reversed(results.items()))) for key, table in forward.tables.items(): other = backward.tables[key] rows = [other["feature_id"].tolist().index(f) for f in table["feature_id"]] @@ -594,9 +610,38 @@ def test_the_order_of_the_studies_changes_no_number() -> None: ) +@cache +def cohort_studies() -> StudyResults: + """The cohorts as the studies of a collection, each analysed on its aligned cube + as cohort_results analyses it.""" + cubes = {name: result.cube for name, result in cohort_results().items()} + return analyse_each_study( + CubeCollection(cubes, align_groups_by="group_id"), + "~ donor + condition", + levels={"condition": ["ctrl", "stim"]}, + contrasts={"stim_vs_ctrl": "conditionstim"}, + ) + + +def test_studies_with_their_collection_are_meta_analysed_as_their_results() -> None: + studies = cohort_studies() + meta = meta_analysis(studies, method="dl") + plain = meta_analysis(dict(studies), method="dl") + assert meta.tables and meta.tables.keys() == plain.tables.keys() + for key, table in plain.tables.items(): + assert meta.tables[key].equals(table), key + assert meta.summary.equals(plain.summary) and meta.studies.equals(plain.studies) + # The result keeps the studies' results it combined, as given, and pickles them. + assert meta.study_results is studies + assert dict(plain.study_results or {}) == dict(studies) + copy = pickle.loads(pickle.dumps(meta)) + assert isinstance(copy.study_results, StudyResults) + assert copy.study_results.studies == studies.studies + + def test_studies_left_out_and_features_tested_by_too_few() -> None: results = cohort_results() - meta = differential_expression_meta_analysis(results) + meta = meta_analysis(results) summary = meta.summary nk = summary.filter(summary["group_id"] == "NK") assert nk["status"].tolist() == ["too_few_studies"] @@ -611,7 +656,7 @@ def test_studies_left_out_and_features_tested_by_too_few() -> None: meta.table() studies = meta.studies.filter(meta.studies["group_id"] == "NK") assert studies["study"].tolist() == ["A", "B", "C"] - assert studies["tested"].tolist() == [True, False, False] + assert studies["analysed"].tolist() == [True, False, False] # B fitted no NK cells, and C has none: the reasons are those of their results. assert "was not fitted (no_samples)" in studies["reason"][1] assert studies["reason"][2].startswith("no group 'NK' was analysed") @@ -628,7 +673,7 @@ def test_studies_left_out_and_features_tested_by_too_few() -> None: def test_one_study_per_feature_gives_its_own_estimates() -> None: results = cohort_results() - meta = differential_expression_meta_analysis(results, min_studies=1) + meta = meta_analysis(results, min_studies=1) table = meta.table("NK", "stim_vs_ctrl") own = results["A"].table("NK", "stim_vs_ctrl") assert table["feature_id"].tolist() == own["feature_id"].tolist() @@ -640,9 +685,9 @@ def test_one_study_per_feature_gives_its_own_estimates() -> None: # with Knapp-Hartung, as metafor gives it for one study; not the study's t-test. z = own["log2_fold_change"] / own["standard_error"] assert_allclose(table["p_value"], 2 * norm_sf(np.abs(z)), rtol=1e-13) - adjusted = differential_expression_meta_analysis( - results, min_studies=1, test="knapp_hartung" - ).table("NK", "stim_vs_ctrl") + adjusted = meta_analysis(results, min_studies=1, test="knapp_hartung").table( + "NK", "stim_vs_ctrl" + ) assert_array_equal(adjusted["p_value"], table["p_value"]) assert np.all(table["p_value"] <= own["p_value"]) # min_studies=1 also keeps the genes of cohort B alone. @@ -651,8 +696,12 @@ def test_one_study_per_feature_gives_its_own_estimates() -> None: def test_groups_and_contrasts_can_be_chosen() -> None: results = cohort_results() - meta = differential_expression_meta_analysis( - results, groups="B", contrasts=["stim_vs_ctrl"], method="fixed", level=0.9 + meta = meta_analysis( + results, + groups="B", + contrasts=["stim_vs_ctrl"], + method="fixed", + confidence_level=0.9, ) assert list(meta.tables) == [("B", "stim_vs_ctrl")] assert meta.summary["group_id"].tolist() == ["B"] @@ -670,15 +719,13 @@ def test_groups_and_contrasts_can_be_chosen() -> None: def test_the_result_records_its_studies_and_settings() -> None: results = cohort_results() - meta = differential_expression_meta_analysis( - results, method="pm", test="knapp_hartung" - ) + meta = meta_analysis(results, method="pm", test="knapp_hartung") record = meta.provenance - assert record["analysis"] == "differential_expression_meta_analysis" + assert record["analysis"] == "meta_analysis" assert meta.settings == { "method": "pm", "test": "knapp_hartung", - "level": 0.95, + "confidence_level": 0.95, "min_studies": 2, "p_value_adjustment": "BH", } @@ -718,7 +765,7 @@ def renamed(result: DifferentialExpressionResult, prefix: str, **changes: Any) - def test_groups_and_contrasts_without_shared_features() -> None: results = cohort_results() - meta = differential_expression_meta_analysis( + meta = meta_analysis( {"A": results["A"], "other": renamed(results["B"], "x")}, groups="T" ) assert meta.tables == {} @@ -745,7 +792,7 @@ def test_a_value_the_meta_analysis_cannot_use_names_its_feature() -> None: f"standard_error 0.0 for feature '{feature}'; it must be positive and finite" ), ): - differential_expression_meta_analysis({"A": results["A"], "B": bad}) + meta_analysis({"A": results["A"], "B": bad}) def test_estimates_that_did_not_converge_are_reported( @@ -753,9 +800,9 @@ def test_estimates_that_did_not_converge_are_reported( ) -> None: # The iterations converge on every input the other tests use; this checks how # the wrapper reports a feature whose estimate does not. - import cytepool.meta_analysis as module + import cytepool.meta_analysis_of_studies as module - original = module.meta_analysis + original = module.fit_meta_analysis def first_fails(*arguments: Any, **options: Any) -> MetaAnalysisFit: fit = original(*arguments, **options) @@ -763,9 +810,9 @@ def first_fails(*arguments: Any, **options: Any) -> MetaAnalysisFit: flags[0] = False return dataclasses.replace(fit, converged=flags) - monkeypatch.setattr(module, "meta_analysis", first_fails) + monkeypatch.setattr(module, "fit_meta_analysis", first_fails) with pytest.warns(RuntimeWarning, match="did not converge for 1 feature, 'g0000'"): - meta = differential_expression_meta_analysis(cohort_results(), groups="T") + meta = meta_analysis(cohort_results(), groups="T") record = meta.provenance["analyses"][0] assert record["not_converged"] == ["g0000"] @@ -773,32 +820,34 @@ def first_fails(*arguments: Any, **options: Any) -> MetaAnalysisFit: def test_invalid_studies_and_options_are_refused() -> None: results = cohort_results() a = results["A"] - with pytest.raises(TypeError, match="results must be a mapping of study names"): - differential_expression_meta_analysis([a, a]) # type: ignore[arg-type] + with pytest.raises( + TypeError, match="studies must be a StudyResults, or a mapping of study names" + ): + meta_analysis([a, a]) # type: ignore[arg-type] with pytest.raises(TypeError, match="keyed by study names"): - differential_expression_meta_analysis({1: a, 2: a}) # type: ignore[dict-item] - with pytest.raises(TypeError, match=r"results\['x'\] must be a Differential"): - differential_expression_meta_analysis({"a": a, "x": a.summary}) # type: ignore[dict-item] + meta_analysis({1: a, 2: a}) # type: ignore[dict-item] + with pytest.raises(TypeError, match=r"studies\['x'\] must be a Differential"): + meta_analysis({"a": a, "x": a.summary}) # type: ignore[dict-item] with pytest.raises(ValueError, match="at least two studies, got 1"): - differential_expression_meta_analysis({"a": a}) + meta_analysis({"a": a}) with pytest.raises(KeyError, match="no study analysed the group 'DC'"): - differential_expression_meta_analysis(results, groups=["T", "DC"]) + meta_analysis(results, groups=["T", "DC"]) with pytest.raises(ValueError, match="groups lists 'T' more than once"): - differential_expression_meta_analysis(results, groups=["T", "T"]) + meta_analysis(results, groups=["T", "T"]) with pytest.raises(ValueError, match="contrasts is empty"): - differential_expression_meta_analysis(results, contrasts=[]) + meta_analysis(results, contrasts=[]) with pytest.raises(TypeError, match="contrasts must be contrast names"): - differential_expression_meta_analysis(results, contrasts=[1]) + meta_analysis(results, contrasts=[1]) with pytest.raises(ValueError, match="min_studies must be at least 1, got 0"): - differential_expression_meta_analysis(results, min_studies=0) + meta_analysis(results, min_studies=0) with pytest.raises( ValueError, match=r"min_studies \(4\) is larger than the number" ): - differential_expression_meta_analysis(results, min_studies=4) + meta_analysis(results, min_studies=4) with pytest.raises(TypeError, match="min_studies must be a whole number"): - differential_expression_meta_analysis(results, min_studies=True) + meta_analysis(results, min_studies=True) with pytest.raises(ValueError, match="method must be one of"): - differential_expression_meta_analysis(results, method="ML") + meta_analysis(results, method="ML") # A result whose tables lack the standard errors. table = a.table("T", "stim_vs_ctrl") bare = DifferentialExpressionResult( @@ -810,6 +859,6 @@ def test_invalid_studies_and_options_are_refused() -> None: a.provenance, ) with pytest.raises(ValueError, match="has no column 'standard_error'"): - differential_expression_meta_analysis({"a": a, "bare": bare}) + meta_analysis({"a": a, "bare": bare}) with pytest.raises(TypeError, match="tables must be keyed by"): MetaAnalysisResult(Table(), Table(), {"T": table}, {}) # type: ignore[dict-item] diff --git a/tests/test_mixed_models.py b/tests/test_mixed_models.py index 6e21231..9b1af49 100644 --- a/tests/test_mixed_models.py +++ b/tests/test_mixed_models.py @@ -16,7 +16,8 @@ import numpy as np import pytest -from cytepool.design import Design, make_contrasts +from cytepool import Design +from cytepool.design import make_contrasts from cytepool.stats.linear_models import fit_linear_model from cytepool.stats.mixed_models import ( FixedEffectTests, diff --git a/tests/test_multiple_testing.py b/tests/test_multiple_testing.py index a92874f..d41ec77 100644 --- a/tests/test_multiple_testing.py +++ b/tests/test_multiple_testing.py @@ -294,7 +294,9 @@ def test_invalid_arguments_are_rejected() -> None: ): adjust_p_values([0.1, bad]) for values in (["0.1"], [0.1 + 0j], [True, False]): - with pytest.raises(TypeError, match="p must hold real numbers, got dtype"): + with pytest.raises( + TypeError, match="p_values must hold real numbers, got dtype" + ): adjust_p_values(values) with pytest.raises(TypeError, match="n must be an integer, got float"): adjust_p_values([0.1], n=2.0) # type: ignore[arg-type] diff --git a/tests/test_named_contrasts.py b/tests/test_named_contrasts.py index 95dc915..b055757 100644 --- a/tests/test_named_contrasts.py +++ b/tests/test_named_contrasts.py @@ -18,7 +18,8 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.design import Contrasts, Design, make_contrasts +from cytepool import Design +from cytepool.design import Contrasts, make_contrasts from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import contrasts_fit, fit_linear_model @@ -481,7 +482,7 @@ def test_moderated_statistics_of_design_contrasts_match_a_reparametrised_design( assert (fit.coefficient_names == design.columns) == (formula == design.formula) contrasted = contrasts_fit(fit, design.contrasts) moderated = moderated_statistics(contrasted) - assert np.isfinite(moderated.prior_df) # the variances are moderated + assert np.isfinite(moderated.df_prior) # the variances are moderated # The independent route: designs whose coefficients are the contrasts, from a # change of reference level and from age centred at 50 with group means. @@ -509,7 +510,7 @@ def test_moderated_statistics_of_design_contrasts_match_a_reparametrised_design( # t-statistics and log-odds near zero carry absolute, not relative, precision. assert_agrees(moderated.t[:, j], reference.t[:, k], floor=1.0) assert_agrees(moderated.p_value[:, j], reference.p_value[:, k]) - assert_agrees(moderated.lods[:, j], reference.lods[:, k], floor=1.0) + assert_agrees(moderated.log_odds[:, j], reference.log_odds[:, k], floor=1.0) # Not a check of the contrasts, which these do not depend on, but of what the # contrasted fit carries over: the residual variances of each feature, and so # the prior and posterior variances of any parametrisation. diff --git a/tests/test_negative_binomial_models.py b/tests/test_negative_binomial_models.py index 4df6278..64605e8 100644 --- a/tests/test_negative_binomial_models.py +++ b/tests/test_negative_binomial_models.py @@ -22,7 +22,7 @@ from numpy.testing import assert_allclose, assert_array_equal from scipy.optimize import brentq -from cytepool.design import Design +from cytepool import Design from cytepool.stats import negative_binomial_models from cytepool.stats.negative_binomial_models import ( NegativeBinomialFit, diff --git a/tests/test_networks.py b/tests/test_networks.py index 9530d99..03684f8 100644 --- a/tests/test_networks.py +++ b/tests/test_networks.py @@ -24,10 +24,7 @@ import pytest from numpy.testing import assert_array_equal -from cytepool import networks -from cytepool.gene_sets import GeneSets -from cytepool.networks import Network, collectri, progeny -from cytepool.table import Table +from cytepool import GeneSets, Network, Table, collectri, networks, progeny DATA = Path(__file__).parent / "data" / "activity" ROWS = (DATA / "collectri_rows.csv").read_bytes() @@ -313,7 +310,7 @@ def test_a_network_selects_records_and_pickles() -> None: def _as_written_in_the_guides(network: Network) -> GeneSets: return GeneSets( {source: list(targets) for source, targets in network.items()}, - source=network.name, + name=network.name, version=network.version, licence=network.licence, file=network.file, @@ -325,7 +322,7 @@ def _same_sets(ours: GeneSets, expected: GeneSets) -> None: assert list(ours) == list(expected) for name in expected: assert ours[name] == expected[name] - for attribute in ("source", "version", "licence", "file", "sha256"): + for attribute in ("name", "version", "licence", "file", "sha256"): assert getattr(ours, attribute) == getattr(expected, attribute), attribute @@ -340,7 +337,7 @@ def test_a_network_as_gene_sets_drops_the_weights_and_keeps_the_record( "IRF1": ("CXCL10", "MYC"), } _same_sets(sets, _as_written_in_the_guides(network)) - assert (sets.source, sets.version, sets.licence) == ("mine", "1", "CC0") + assert (sets.name, sets.version, sets.licence) == ("mine", "1", "CC0") # Back to a network, every target weighs 1. again = Network(sets) assert again.edges.select(["source", "target"]).equals( diff --git a/tests/test_normalization.py b/tests/test_normalization.py index 198ad11..ada2f17 100644 --- a/tests/test_normalization.py +++ b/tests/test_normalization.py @@ -54,6 +54,10 @@ def take(flat: np.ndarray, entry: list) -> np.ndarray: CASES, CALLS = _load_fixtures() +# calls.json names the function log2_cpm and the argument sample_groups of +# filter_by_expression as they were named when validation/normalization.py wrote it. +RENAMED_FUNCTIONS = {"log_cpm": "log2_cpm"} +RENAMED_ARGUMENTS = {"group": "sample_groups"} CHECKS = sorted({call["check"] for call in CALLS}) @@ -65,18 +69,19 @@ def _run(call: dict) -> np.ndarray: value = case[value[1:]] if key in ("a_cutoff", "min_prop"): value = float(value[0]) - kwargs[key] = value - return np.asarray(getattr(nz, call["function"])(case["Y"], **kwargs)) + kwargs[RENAMED_ARGUMENTS.get(key, key)] = value + function = RENAMED_FUNCTIONS.get(call["function"], call["function"]) + return np.asarray(getattr(nz, function)(case["Y"], **kwargs)) def test_fixtures_cover_every_function_and_rule() -> None: - functions = {call["function"] for call in CALLS} + functions = {RENAMED_FUNCTIONS.get(c["function"], c["function"]) for c in CALLS} assert functions == set(nz.__all__) - {"library_sizes"} assert sum(check.startswith("edge case") for check in CHECKS) == 18 assert sum(call["case"].startswith("small") for call in CALLS) == 177 -@pytest.mark.filterwarnings("ignore:no group or design given") +@pytest.mark.filterwarnings("ignore:no sample_groups or design given") @pytest.mark.parametrize("check", CHECKS) def test_matches_edger_and_deseq2(check: str) -> None: failures = [] @@ -95,7 +100,7 @@ def test_matches_edger_and_deseq2(check: str) -> None: assert not failures, failures[:5] -@pytest.mark.filterwarnings("ignore:no group or design given") +@pytest.mark.filterwarnings("ignore:no sample_groups or design given") @pytest.mark.skipif( not SAME_MATH_LIBRARY, reason="R's results came from glibc on x86-64" ) @@ -260,7 +265,7 @@ def test_median_ratio_of_proportional_samples() -> None: assert_allclose(factors(counts), [1.0, 1.0, 1.0], rtol=1e-14) -def test_cpm_and_log_cpm_by_hand() -> None: +def test_cpm_and_log2_cpm_by_hand() -> None: counts = np.array([[1, 3], [2, 2]], dtype=np.float64) assert_array_equal(nz.cpm(counts), [[250000, 750000], [500000, 500000]]) assert_array_equal( @@ -270,15 +275,15 @@ def test_cpm_and_log_cpm_by_hand() -> None: # twice to the library size. swapped = np.array([[0, 4], [4, 0]], dtype=np.float64) expected = np.log2([[2 / 8 * 1e6, 6 / 8 * 1e6], [6 / 8 * 1e6, 2 / 8 * 1e6]]) - assert_allclose(nz.log_cpm(swapped), expected, rtol=1e-14) + assert_allclose(nz.log2_cpm(swapped), expected, rtol=1e-14) # Library sizes 4 and 12 scale the prior to 1 and 3. unequal = np.array([[0, 4], [6, 6]], dtype=np.float64) expected = np.log2([[1 / 6 * 1e6, 5 / 6 * 1e6], [9 / 18 * 1e6, 9 / 18 * 1e6]]) - assert_allclose(nz.log_cpm(unequal, prior_count=2), expected, rtol=1e-14) + assert_allclose(nz.log2_cpm(unequal, prior_count=2), expected, rtol=1e-14) # Without a prior, zero counts give minus infinity. million = math.log2(1e6) assert_allclose( - nz.log_cpm(swapped, prior_count=0), + nz.log2_cpm(swapped, prior_count=0), [[-math.inf, million], [million, -math.inf]], rtol=1e-14, ) @@ -303,13 +308,15 @@ def test_filter_by_expression_follows_the_documented_rule() -> None: ) balanced = ["a", "a", "a", "b", "b", "b"] expected = [True, False, False, True, False, True] - assert_array_equal(nz.filter_by_expression(counts, group=balanced), expected) + assert_array_equal( + nz.filter_by_expression(counts, sample_groups=balanced), expected + ) one_way = np.column_stack([np.ones(6), [0, 0, 0, 1, 1, 1]]) assert_array_equal(nz.filter_by_expression(counts, design=one_way), expected) # A group of two needs only two samples. unbalanced = ["a", "a", "b", "b", "b", "b"] assert_array_equal( - nz.filter_by_expression(counts, group=unbalanced), + nz.filter_by_expression(counts, sample_groups=unbalanced), [True, True, False, True, False, True], ) @@ -319,7 +326,7 @@ def test_filter_by_expression_total_count_is_inclusive() -> None: group = [0, 0, 0, 1, 1, 1] for total, keep in ((3, True), (3.5, False)): got = nz.filter_by_expression( - counts, group=group, min_count=1, min_total_count=total + counts, sample_groups=group, min_count=1, min_total_count=total ) assert_array_equal(got, [keep, True]) @@ -329,17 +336,19 @@ def test_filter_by_expression_relaxes_large_groups() -> None: counts = _equal_depth([[10] * 14 + [0] * 16, [10] * 13 + [0] * 17]) group = [0] * 15 + [1] * 15 assert_array_equal( - nz.filter_by_expression(counts, group=group), [True, False, True] + nz.filter_by_expression(counts, sample_groups=group), [True, False, True] ) design = np.column_stack([np.ones(30), np.repeat([0.0, 1.0], 15)]) assert_array_equal( nz.filter_by_expression(counts, design=design), [True, False, True] ) assert_array_equal( - nz.filter_by_expression(counts, group=group, min_prop=1.0), [False, False, True] + nz.filter_by_expression(counts, sample_groups=group, min_prop=1.0), + [False, False, True], ) assert_array_equal( - nz.filter_by_expression(counts, group=group, large_n=15), [False, False, True] + nz.filter_by_expression(counts, sample_groups=group, large_n=15), + [False, False, True], ) @@ -362,7 +371,7 @@ def test_filter_by_expression_reads_only_the_fixed_effects_of_a_design() -> None # intercept filters as edgeR's filterByExpr does with the model matrix of its # fixed effects. Read as fixed, the donor columns would give every sample # leverage 1, and so n = 1, a laxer filter. - from cytepool.design import Design + from cytepool import Design case = CASES["ties"] (call,) = [ @@ -396,7 +405,7 @@ def test_filter_by_expression_without_group_or_design_uses_one_group() -> None: counts = _equal_depth([[10, 10, 10, 10, 10, 0], [10] * 6]) with pytest.warns(UserWarning, match="one group"): got = nz.filter_by_expression(counts) - assert_array_equal(got, nz.filter_by_expression(counts, group=[0] * 6)) + assert_array_equal(got, nz.filter_by_expression(counts, sample_groups=[0] * 6)) assert_array_equal(got, [False, True, True]) @@ -458,8 +467,8 @@ def test_factors_follow_the_order_of_samples(counts: np.ndarray) -> None: ): assert_allclose(factors(counts[order]), factors(counts)[order], rtol=1e-14) assert_array_equal( - nz.filter_by_expression(counts[order], group=group[order]), - nz.filter_by_expression(counts, group=group), + nz.filter_by_expression(counts[order], sample_groups=group[order]), + nz.filter_by_expression(counts, sample_groups=group), ) @@ -483,10 +492,10 @@ def test_all_zero_features_change_nothing(counts: np.ndarray) -> None: lambda y: nz.median_ratio_size_factors(y, method="poscounts"), ): assert_array_equal(factors(padded), factors(counts)) - keep = nz.filter_by_expression(padded, group=[0, 0, 0, 1, 1, 1]) + keep = nz.filter_by_expression(padded, sample_groups=[0, 0, 0, 1, 1, 1]) assert not keep[:3].any() and not keep[-2:].any() assert_array_equal( - keep[3:-2], nz.filter_by_expression(counts, group=[0, 0, 0, 1, 1, 1]) + keep[3:-2], nz.filter_by_expression(counts, sample_groups=[0, 0, 0, 1, 1, 1]) ) @@ -499,7 +508,7 @@ def test_inputs_are_not_modified(counts: np.ndarray) -> None: def test_memory_layout_changes_nothing_and_compiles_nothing(counts: np.ndarray) -> None: - expected = nz.tmm_factors(counts), nz.log_cpm(counts), nz.rle_factors(counts + 1) + expected = nz.tmm_factors(counts), nz.log2_cpm(counts), nz.rle_factors(counts + 1) kernels = (nz._tmm_log2_factors, nz._log2_cpm, nz._rle_median_ratios, accurate_sums) compiled = [len(kernel.signatures) for kernel in kernels] read_only = counts.copy() @@ -510,7 +519,7 @@ def test_memory_layout_changes_nothing_and_compiles_nothing(counts: np.ndarray) np.repeat(counts, 2, 1)[:, ::2], ): assert_array_equal(nz.tmm_factors(layout), expected[0]) - assert_array_equal(nz.log_cpm(layout), expected[1]) + assert_array_equal(nz.log2_cpm(layout), expected[1]) assert_array_equal(nz.rle_factors(layout + 1), expected[2]) assert [len(kernel.signatures) for kernel in kernels] == compiled @@ -545,15 +554,15 @@ def test_sums_beyond_the_float64_range_raise() -> None: nz.upper_quartile_factors, nz.rle_factors, nz.cpm, - nz.log_cpm, - lambda y: nz.filter_by_expression(y, group=[0, 1]), + nz.log2_cpm, + lambda y: nz.filter_by_expression(y, sample_groups=[0, 1]), ): with pytest.raises(OverflowError, match="counts of sample 0 sum beyond"): compute(huge) # Each sample sums to 1e308 at most, but feature 0 sums to 2e308 over them. tall = np.array([[1e308, 1.0], [1e308, 1.0], [1.0, 1.0]]) with pytest.raises(OverflowError, match="the counts of feature 0 sum beyond"): - nz.filter_by_expression(tall, group=[0, 0, 1]) + nz.filter_by_expression(tall, sample_groups=[0, 0, 1]) # edgeR forms 1e6 * count before dividing by the library size: 1e309 here. with pytest.raises( OverflowError, match="count per million of sample 0, feature 0 exceeds" @@ -561,7 +570,7 @@ def test_sums_beyond_the_float64_range_raise() -> None: nz.cpm(np.array([[1e303, 1.0], [1.0, 1.0]])) # log-CPM scales the prior count by the mean library size, here 2e308 / 2. with pytest.raises(OverflowError, match="the library sizes sum beyond the float64"): - nz.log_cpm(np.ones((2, 2)), library_sizes=[1e308, 1e308]) + nz.log2_cpm(np.ones((2, 2)), library_sizes=[1e308, 1e308]) # Errors @@ -594,8 +603,8 @@ def test_counts_are_checked(counts: np.ndarray, error: type, match: str) -> None nz.rle_factors, nz.median_ratio_size_factors, nz.cpm, - nz.log_cpm, - lambda y: nz.filter_by_expression(y, group=[0] * y.shape[0]), + nz.log2_cpm, + lambda y: nz.filter_by_expression(y, sample_groups=[0] * y.shape[0]), ): with pytest.raises(error, match=match): function(counts) @@ -683,22 +692,26 @@ def test_undefined_factors_raise() -> None: with pytest.raises(ValueError, match='method must be "ratio" or "poscounts"'): nz.median_ratio_size_factors(GOOD, method="iterate") with pytest.raises(ValueError, match="prior_count must be non-negative"): - nz.log_cpm(GOOD, prior_count=-1) + nz.log2_cpm(GOOD, prior_count=-1) with pytest.raises(ValueError, match="prior_count must be finite"): - nz.log_cpm(GOOD, prior_count=np.inf) + nz.log2_cpm(GOOD, prior_count=np.inf) @pytest.mark.parametrize( ("kwargs", "error", "match"), [ ( - dict(group=[0, 1], design=np.ones((2, 1))), + dict(sample_groups=[0, 1], design=np.ones((2, 1))), + ValueError, + "sample_groups or design, not both", + ), + (dict(sample_groups=[0, 1, 2]), ValueError, r"one label per sample \(2\)"), + (dict(sample_groups=[0, None]), ValueError, "sample 1 has None"), + ( + dict(sample_groups=[0.0, np.nan]), ValueError, - "group or design, not both", + "the labels of sample_groups must not be missing", ), - (dict(group=[0, 1, 2]), ValueError, r"one label per sample \(2\)"), - (dict(group=[0, None]), ValueError, "sample 1 has None"), - (dict(group=[0.0, np.nan]), ValueError, "group labels must not be missing"), (dict(design=np.ones((3, 1))), ValueError, r"one row per sample \(2\)"), (dict(design=np.array([[1.0], [np.inf]])), ValueError, "design must be finite"), ( @@ -706,15 +719,23 @@ def test_undefined_factors_raise() -> None: TypeError, "design must be a numeric matrix", ), - (dict(group=[0, 1], min_prop=1.5), ValueError, r"min_prop must be in \[0, 1\]"), ( - dict(group=[0, 1], min_count=-1), + dict(sample_groups=[0, 1], min_prop=1.5), + ValueError, + r"min_prop must be in \[0, 1\]", + ), + ( + dict(sample_groups=[0, 1], min_count=-1), ValueError, "min_count must be non-negative", ), - (dict(group=[0, 1], large_n=-1), ValueError, "large_n must be non-negative"), ( - dict(group=[0, 1], min_total_count=np.nan), + dict(sample_groups=[0, 1], large_n=-1), + ValueError, + "large_n must be non-negative", + ), + ( + dict(sample_groups=[0, 1], min_total_count=np.nan), ValueError, "min_total_count must be finite", ), diff --git a/tests/test_over_representation.py b/tests/test_over_representation.py index dedcfc7..1433817 100644 --- a/tests/test_over_representation.py +++ b/tests/test_over_representation.py @@ -20,13 +20,8 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.gene_sets import GeneSets -from cytepool.over_representation import over_representation +from cytepool import Cube, Design, GeneSets, Table, limma_voom, over_representation from cytepool.stats.multiple_testing import adjust_p_values -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "gene_set_files_and_ora" EXACT = 1e-10 diff --git a/tests/test_pathway_scores.py b/tests/test_pathway_scores.py index 8de94e6..50f9249 100644 --- a/tests/test_pathway_scores.py +++ b/tests/test_pathway_scores.py @@ -20,22 +20,22 @@ from numpy.testing import assert_allclose, assert_array_equal import cytepool -from cytepool import pathway_scores -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.gene_sets import GeneSets -from cytepool.normalization import filter_by_expression, log_cpm, tmm_factors -from cytepool.pathway_scores import ( +from cytepool import ( + Cube, + Design, + GeneSets, PathwayScores, + Table, combined_z_scores, gsva, + pathway_scores, plage, ssgsea, ) +from cytepool.normalization import filter_by_expression, log2_cpm, tmm_factors from cytepool.plotting import plot_pathway_scores from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import fit_linear_model -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "pathway_scores" FIXTURE = json.loads((DATA / "gsva.json").read_text()) @@ -74,7 +74,7 @@ def _run(case: dict) -> tuple[PathwayScores, np.ndarray]: result = function( values, FIXTURE["sets"][case["sets"]], - gene_ids=FIXTURE["genes"], + feature_ids=FIXTURE["genes"], **_options(case), ) return result, ARRAYS[case["name"]] @@ -120,13 +120,13 @@ def test_the_options_that_auto_chose_are_recorded() -> None: result, _ = _run(counts) assert result.settings["kernel"] == "poisson" # integers: GSVA's Poisson kernel as_decimals = gsva( - ARRAYS["counts"], FIXTURE["sets"]["main"], gene_ids=FIXTURE["genes"] + ARRAYS["counts"], FIXTURE["sets"]["main"], feature_ids=FIXTURE["genes"] ) assert as_decimals.settings["kernel"] == "gaussian" many = gsva( ARRAYS["log_expression"], FIXTURE["sets"]["main"], - gene_ids=FIXTURE["genes"], + feature_ids=FIXTURE["genes"], min_samples_without_kernel=10, ) assert many.settings["kernel"] == "none" @@ -172,7 +172,7 @@ def test_gsva_of_a_two_gene_set_on_three_samples( result = gsva( FOUR_GENES, {"pair": ["g1", "g2"], "every gene": GENES}, - gene_ids=GENES, + feature_ids=GENES, kernel="none", score_type=score_type, ) @@ -183,7 +183,7 @@ def test_gsva_of_a_two_gene_set_on_three_samples( def test_a_gene_at_the_middle_rank_weighs_nothing() -> None: # In sample 1, g1 has rank 2 = p/2: a set of g1 alone has no weight there. - result = gsva(FOUR_GENES, {"g1": ["g1"]}, gene_ids=GENES, kernel="none") + result = gsva(FOUR_GENES, {"g1": ["g1"]}, feature_ids=GENES, kernel="none") assert np.isnan(result.scores[0, 0]) assert np.isfinite(result.scores[1:, 0]).all() @@ -196,12 +196,12 @@ def test_ssgsea_by_hand_with_ties() -> None: values = np.array([[1.0, 2.0, 2.0, 3.0, 4.0, 5.0]]) genes = [f"g{i}" for i in range(1, 7)] sets = {"g12": ["g1", "g2"], "g23": ["g2", "g3"], "g34": ["g3", "g4"]} - result = ssgsea(values, sets, gene_ids=genes, exponent=1.0, normalize=False) + result = ssgsea(values, sets, feature_ids=genes, exponent=1.0, normalize=False) # {g2, g3}: (2 * 3 + 2 * 2) / 4 - (21 - 5) / 4; {g3, g4}: (2 * 2 + 4 * 4) / 6 - # (21 - 6) / 4. expected = [7.0 / 3.0 - 17.0 / 4.0, 2.5 - 4.0, 20.0 / 6.0 - 15.0 / 4.0] assert_allclose(result.scores[0], expected, rtol=1e-15) - normalized = ssgsea(values, sets, gene_ids=genes, exponent=1.0) + normalized = ssgsea(values, sets, feature_ids=genes, exponent=1.0) spread = max(expected) - min(expected) assert_allclose(normalized.scores[0], np.array(expected) / spread, rtol=1e-15) @@ -209,7 +209,7 @@ def test_ssgsea_by_hand_with_ties() -> None: def test_combined_z_scores_of_a_two_gene_set() -> None: # z of (1, 2, 3) is (-1, 0, 1); z of (2, 4, 9) is (-3, -1, 4) / sqrt(13). values = np.array([[1.0, 2.0], [2.0, 4.0], [3.0, 9.0]]) - result = combined_z_scores(values, {"pair": ["a", "b"]}, gene_ids=["a", "b"]) + result = combined_z_scores(values, {"pair": ["a", "b"]}, feature_ids=["a", "b"]) expected = np.array([-1.0, 0.0, 1.0]) + np.array([-3.0, -1.0, 4.0]) / 13**0.5 assert_allclose(result.scores[:, 0], expected / 2**0.5, rtol=1e-14, atol=1e-15) @@ -219,7 +219,7 @@ def test_plage_of_correlated_anticorrelated_and_single_genes() -> None: # vector of their standardized values is z / |z| for z = (-1, 0, 1). values = np.array([[1.0, 10.0, 7.0], [2.0, 30.0, 6.0], [3.0, 50.0, 5.0]]) sets = {"together": ["a", "b"], "opposed": ["a", "c"], "alone": ["c"]} - result = plage(values, sets, gene_ids=["a", "b", "c"]) + result = plage(values, sets, feature_ids=["a", "b", "c"]) rising = np.array([-1.0, 0.0, 1.0]) / 2**0.5 # Together: the loadings sum to a positive number for the rising scores. assert_allclose(result.scores[:, 0], rising, atol=1e-15) @@ -235,8 +235,8 @@ def test_plage_scores_rise_with_the_mean_standardized_expression() -> None: values = rng.normal(size=(12, 30)) genes = [f"g{j}" for j in range(30)] sets = {f"s{i}": genes[i : i + 6] for i in range(20)} - scores = plage(values, sets, gene_ids=genes).scores - z = combined_z_scores(values, sets, gene_ids=genes).scores + scores = plage(values, sets, feature_ids=genes).scores + z = combined_z_scores(values, sets, feature_ids=genes).scores assert np.all(np.sum(scores * z, axis=0) > 0.0) assert_allclose(np.sum(scores**2, axis=0), 1.0, rtol=1e-14) assert_allclose(scores.sum(axis=0), 0.0, atol=1e-14) @@ -258,15 +258,17 @@ def _continuous() -> tuple[np.ndarray, list[str], dict[str, list[str]]]: def test_the_order_of_the_sets_and_of_the_genes_does_not_matter(method: str) -> None: values, genes, sets = _continuous() function = FUNCTIONS[method] - result = function(values, sets, gene_ids=genes) + result = function(values, sets, feature_ids=genes) names = list(sets)[::-1] - reordered = function(values, {name: sets[name] for name in names}, gene_ids=genes) + reordered = function( + values, {name: sets[name] for name in names}, feature_ids=genes + ) order = [list(result.gene_sets).index(name) for name in reordered.gene_sets] assert_array_equal(reordered.scores, result.scores[:, order]) # The genes in another order; the data have no tied estimates of two genes. permutation = np.random.default_rng(1).permutation(len(genes)) permuted = function( - values[:, permutation], sets, gene_ids=[genes[j] for j in permutation] + values[:, permutation], sets, feature_ids=[genes[j] for j in permutation] ) assert_allclose(permuted.scores, result.scores, rtol=0, atol=1e-14) @@ -275,13 +277,13 @@ def test_the_order_of_the_sets_and_of_the_genes_does_not_matter(method: str) -> def test_relabelled_samples_permute_the_scores(method: str) -> None: values, genes, sets = _continuous() function = FUNCTIONS[method] - result = function(values, sets, gene_ids=genes) + result = function(values, sets, feature_ids=genes) order = np.array([3, 7, 0, 9, 1, 5, 8, 2, 6, 4]) ids = [f"S{i}" for i in range(values.shape[0])] moved = function( - values[order], sets, gene_ids=genes, sample_ids=[ids[i] for i in order] + values[order], sets, feature_ids=genes, sample_ids=[ids[i] for i in order] ) - assert moved.samples["sample_id"].tolist() == [ids[i] for i in order] + assert moved.profiles["sample_id"].tolist() == [ids[i] for i in order] # Kernel estimates summed in another order can differ in the last bit only. assert_allclose(moved.scores, result.scores[order], rtol=0, atol=1e-14) @@ -296,42 +298,42 @@ def test_the_kernels_follow_shifts_and_scales_of_a_gene() -> None: scaled = values.copy() scaled[:, 1::4] *= 4.0 for kernel in ("gaussian", "none"): - base = gsva(values, sets, gene_ids=genes, kernel=kernel) + base = gsva(values, sets, feature_ids=genes, kernel=kernel) for moved in (shifted, scaled): assert_array_equal( - gsva(moved, sets, gene_ids=genes, kernel=kernel).scores, base.scores + gsva(moved, sets, feature_ids=genes, kernel=kernel).scores, base.scores ) # Only the order of a gene's values matters without a kernel. bent = values.copy() bent[:, 1::2] = np.exp(bent[:, 1::2]) assert_array_equal( - gsva(bent, sets, gene_ids=genes, kernel="none").scores, - gsva(values, sets, gene_ids=genes, kernel="none").scores, + gsva(bent, sets, feature_ids=genes, kernel="none").scores, + gsva(values, sets, feature_ids=genes, kernel="none").scores, ) def test_ssgsea_depends_on_the_order_of_values_within_samples_only() -> None: values, genes, sets = _continuous() - result = ssgsea(values, sets, gene_ids=genes) + result = ssgsea(values, sets, feature_ids=genes) # A monotone transform of every value, and different shifts of the samples. bent = np.exp(values) + np.arange(values.shape[0])[:, np.newaxis] - assert_array_equal(ssgsea(bent, sets, gene_ids=genes).scores, result.scores) + assert_array_equal(ssgsea(bent, sets, feature_ids=genes).scores, result.scores) def test_combined_z_scores_are_affine_invariant_and_odd() -> None: values, genes, sets = _continuous() - result = combined_z_scores(values, sets, gene_ids=genes) - moved = combined_z_scores(3.0 * values - 1.0, sets, gene_ids=genes) + result = combined_z_scores(values, sets, feature_ids=genes) + moved = combined_z_scores(3.0 * values - 1.0, sets, feature_ids=genes) assert_allclose(moved.scores, result.scores, rtol=1e-12, atol=1e-13) - negated = combined_z_scores(-values, sets, gene_ids=genes) + negated = combined_z_scores(-values, sets, feature_ids=genes) assert_allclose(negated.scores, -result.scores, rtol=1e-12, atol=1e-13) @pytest.mark.parametrize("function", [gsva, ssgsea]) def test_the_number_of_threads_does_not_change_the_scores(function) -> None: values, genes, sets = _continuous() - one = function(values, sets, gene_ids=genes, n_threads=1) - two = function(values, sets, gene_ids=genes, n_threads=2) + one = function(values, sets, feature_ids=genes, n_threads=1) + two = function(values, sets, feature_ids=genes, n_threads=2) assert_array_equal(one.scores, two.scores) @@ -344,11 +346,11 @@ def test_sets_are_matched_after_constant_genes_are_left_out() -> None: # ranks them. sets = {"constant": [genes[3], genes[4], genes[10], "OUTSIDE"], "only": [genes[3]]} for function in (gsva, combined_z_scores, plage): - result = function(values, sets, gene_ids=genes) + result = function(values, sets, feature_ids=genes) assert list(result.gene_sets) == ["constant"] assert result.gene_sets["constant"] == (genes[10],) assert result.n_genes.tolist() == [1] - ranked = ssgsea(values, sets, gene_ids=genes) + ranked = ssgsea(values, sets, feature_ids=genes) assert ranked.gene_sets["constant"] == (genes[3], genes[4], genes[10]) assert ranked.n_genes.tolist() == [3, 1] @@ -358,19 +360,19 @@ def test_sizes_are_counted_among_the_genes_scored() -> None: sets = GeneSets( {"small": genes[10:12], "medium": genes[10:15] + ["X", "Y"], "large": genes}, descriptions={"medium": "five genes here"}, - source="tests", + name="tests", version="1", licence="CC0", ) - result = gsva(values, sets, gene_ids=genes, min_size=3, max_size=10) + result = gsva(values, sets, feature_ids=genes, min_size=3, max_size=10) assert list(result.gene_sets) == ["medium"] assert result.n_genes.tolist() == [5] assert result.gene_sets.descriptions["medium"] == "five genes here" - assert (result.gene_sets.source, result.gene_sets.licence) == ("tests", "CC0") + assert (result.gene_sets.name, result.gene_sets.licence) == ("tests", "CC0") with pytest.raises(ValueError, match=r"no gene set has between min_size \(20\)"): - gsva(values, sets, gene_ids=genes, min_size=20, max_size=30) + gsva(values, sets, feature_ids=genes, min_size=20, max_size=30) with pytest.raises(ValueError, match="no gene of any set is among the genes"): - gsva(values, {"other": ["A", "B"]}, gene_ids=genes) + gsva(values, {"other": ["A", "B"]}, feature_ids=genes) def test_genes_without_an_id_are_ranked_but_in_no_set() -> None: @@ -378,7 +380,7 @@ def test_genes_without_an_id_are_ranked_but_in_no_set() -> None: ids = list(genes) ids[100] = None ids[101] = "" - result = ssgsea(values, sets, gene_ids=ids) + result = ssgsea(values, sets, feature_ids=ids) # The universe keeps every gene; the sets lose those two. assert result.scores.shape == (10, len(sets)) assert all(genes[100] not in members for members in result.gene_sets.values()) @@ -386,14 +388,16 @@ def test_genes_without_an_id_are_ranked_but_in_no_set() -> None: def test_the_table_lists_every_set_and_sample() -> None: values, genes, sets = _continuous() - result = gsva(values, sets, gene_ids=genes, sample_ids=[f"S{i}" for i in range(10)]) + result = gsva( + values, sets, feature_ids=genes, sample_ids=[f"S{i}" for i in range(10)] + ) table = result.table() assert len(table) == result.scores.size - assert table["sample_id"].tolist()[:3] == ["S0", "S1", "S2"] + assert table["profile_id"].tolist()[:3] == ["S0", "S1", "S2"] assert table["gene_set"].tolist()[10] == list(result.gene_sets)[1] assert_array_equal(table["score"], result.scores.T.ravel()) assert_array_equal(table["n_genes"], np.repeat(result.n_genes, 10)) - assert repr(result) == "PathwayScores(method='gsva', n_samples=10, n_sets=20)" + assert repr(result) == "PathwayScores(method='gsva', n_profiles=10, n_sets=20)" with pytest.raises(ValueError, match="read-only"): result.scores[0, 0] = 1.0 @@ -414,22 +418,22 @@ def test_missing_values_are_left_out_or_make_the_score_missing() -> None: gappy[2, :] = np.nan # a sample without values gappy[5, 7] = np.nan for function in (gsva, ssgsea): - result = function(gappy, sets, gene_ids=genes) + result = function(gappy, sets, feature_ids=genes) assert np.isnan(result.scores[2]).all() assert np.isfinite(np.delete(result.scores, 2, axis=0)).all() - z = combined_z_scores(gappy, sets, gene_ids=genes) + z = combined_z_scores(gappy, sets, feature_ids=genes) holds = np.array([genes[7] in members for members in z.gene_sets.values()]) assert np.isnan(z.scores[5, holds]).all() and np.isfinite(z.scores[5, ~holds]).all() with pytest.raises(ValueError, match="PLAGE's singular value decomposition"): - plage(gappy, sets, gene_ids=genes) + plage(gappy, sets, feature_ids=genes) def test_ssgsea_scores_a_set_of_every_gene_as_missing() -> None: values, genes, _ = _continuous() sets = {"every": genes, "some": genes[:20], "more": genes[20:60]} - result = ssgsea(values, sets, gene_ids=genes) + result = ssgsea(values, sets, feature_ids=genes) assert np.isnan(result.scores[:, 0]).all() - raw = ssgsea(values, sets, gene_ids=genes, normalize=False).scores[:, 1:] + raw = ssgsea(values, sets, feature_ids=genes, normalize=False).scores[:, 1:] assert_allclose(result.scores[:, 1:], raw / (raw.max() - raw.min()), rtol=1e-15) @@ -461,24 +465,26 @@ def _cube() -> tuple[Cube, np.ndarray]: return cube, counts -def test_a_cube_group_is_scored_as_its_log_cpm() -> None: +def test_a_cube_group_is_scored_as_its_log2_cpm() -> None: cube, counts = _cube() sets = { "treated": [f"G{j}" for j in range(30)], "other": [f"G{j}" for j in range(50, 90)], } - result = gsva(cube, sets, gene_ids="symbol") + result = gsva(cube, sets, id_column="symbol") used = np.flatnonzero(np.arange(12) != 3) kept = counts[used] - keep = filter_by_expression(kept, group=np.zeros(used.size)) - values = log_cpm(kept[:, keep], norm_factors=tmm_factors(kept[:, keep])) + keep = filter_by_expression(kept, sample_groups=np.zeros(used.size)) + values = log2_cpm(kept[:, keep], norm_factors=tmm_factors(kept[:, keep])) symbols = [f"G{j}" for j in np.flatnonzero(keep)] - expected = gsva(values, sets, gene_ids=symbols) + expected = gsva(values, sets, feature_ids=symbols) assert_array_equal(result.scores, expected.scores) - assert result.samples["sample_id"].tolist() == [f"S{i}" for i in used] - assert result.samples["condition"].tolist()[:2] == ["control", "treated"] - assert result.settings["values"] == "log_cpm" and result.settings["min_cells"] == 10 - as_counts = gsva(cube, sets, gene_ids="symbol", values="counts") + assert result.profiles["sample_id"].tolist() == [f"S{i}" for i in used] + assert result.profiles["condition"].tolist()[:2] == ["control", "treated"] + assert ( + result.settings["values"] == "log2_cpm" and result.settings["min_cells"] == 10 + ) + as_counts = gsva(cube, sets, id_column="symbol", values="counts") assert as_counts.settings["kernel"] == "poisson" @@ -489,16 +495,18 @@ def test_a_cube_group_filtered_by_sample_groups_keeps_the_genes_of_each_group() "other": [f"G{j}" for j in range(50, 90)], } by_condition = {"sample_groups": "condition"} - result = gsva(cube, sets, gene_ids="symbol", expression_filter=by_condition) + result = gsva(cube, sets, id_column="symbol", expression_filter=by_condition) used = np.flatnonzero(np.arange(12) != 3) kept = counts[used] - keep = filter_by_expression(kept, group=cube.samples["condition"][used]) - values = log_cpm(kept[:, keep], norm_factors=tmm_factors(kept[:, keep])) + keep = filter_by_expression(kept, sample_groups=cube.samples["condition"][used]) + values = log2_cpm(kept[:, keep], norm_factors=tmm_factors(kept[:, keep])) symbols = [f"G{j}" for j in np.flatnonzero(keep)] - assert_array_equal(result.scores, gsva(values, sets, gene_ids=symbols).scores) + assert_array_equal(result.scores, gsva(values, sets, feature_ids=symbols).scores) assert result.settings["expression_filter"]["sample_groups"] == "condition" with pytest.raises(KeyError, match="names 'site', which is not a column"): - gsva(cube, sets, gene_ids="symbol", expression_filter={"sample_groups": "site"}) + gsva( + cube, sets, id_column="symbol", expression_filter={"sample_groups": "site"} + ) def test_scores_of_a_cube_go_into_a_linear_model() -> None: @@ -507,8 +515,8 @@ def test_scores_of_a_cube_go_into_a_linear_model() -> None: "treated": [f"G{j}" for j in range(30)], "other": [f"G{j}" for j in range(50, 90)], } - result = gsva(cube, sets, gene_ids="symbol") - design = Design(result.samples, "~ condition") + result = gsva(cube, sets, id_column="symbol") + design = Design(result.profiles, "~ condition") moderated = moderated_statistics(fit_linear_model(result.scores, design)) # The set of the genes that the treatment triples is higher in treated samples. assert moderated.t[0, 1] > 5.0 and moderated.p_value[0, 1] < 1e-4 @@ -547,7 +555,7 @@ def test_scores_of_a_cube_record_the_cube_they_read() -> None: "treated": [f"G{j}" for j in range(30)], "other": [f"G{j}" for j in range(50, 90)], } - result = gsva(cube, sets, gene_ids="symbol") + result = gsva(cube, sets, id_column="symbol") record = result.provenance assert record is not None assert list(record) == ["method", "versions", "cube", "layer", "settings"] @@ -559,11 +567,11 @@ def test_scores_of_a_cube_record_the_cube_they_read() -> None: assert json.loads(json.dumps(record)) == record record["method"] = "changed" # a new dict each time assert result.provenance["method"] == "gsva" - assert repr(result) == "PathwayScores(method='gsva', n_samples=11, n_sets=2)" + assert repr(result) == "PathwayScores(method='gsva', n_profiles=11, n_sets=2)" # Scores of values given as an array name no cube. - values = log_cpm(counts[:4], norm_factors=tmm_factors(counts[:4])) + values = log2_cpm(counts[:4], norm_factors=tmm_factors(counts[:4])) symbols = [f"G{j}" for j in range(counts.shape[1])] - of_values = gsva(values, sets, gene_ids=symbols) + of_values = gsva(values, sets, feature_ids=symbols) assert of_values.provenance is None # Both pickle, the provenance with them. assert_pickles(result) @@ -576,44 +584,44 @@ def test_scores_of_a_cube_record_the_cube_they_read() -> None: def test_arguments_are_checked() -> None: values, genes, sets = _continuous() cube, _ = _cube() - with pytest.raises(ValueError, match="gene_ids is needed with an array"): + with pytest.raises(ValueError, match="feature_ids is needed with an array"): gsva(values, sets) with pytest.raises(ValueError, match=r"one id per gene \(298\), got 3"): - gsva(values, sets, gene_ids=["a", "b", "c"]) + gsva(values, sets, feature_ids=["a", "b", "c"]) with pytest.raises(ValueError, match="names two of the genes scored 'G00000'"): - gsva(values, sets, gene_ids=[genes[0]] * 298) + gsva(values, sets, feature_ids=[genes[0]] * 298) with pytest.raises(TypeError, match="gene ids as strings, got int 7"): - gsva(values, sets, gene_ids=[7] + genes[1:]) + gsva(values, sets, feature_ids=[7] + genes[1:]) with pytest.raises(ValueError, match="data holds infinite values"): - gsva(np.where(values > 7.5, np.inf, values), sets, gene_ids=genes) + gsva(np.where(values > 7.5, np.inf, values), sets, feature_ids=genes) with pytest.raises(ValueError, match=r"2-D array of shape \(n_samples, n_genes\)"): - gsva(values[0], sets, gene_ids=genes) + gsva(values[0], sets, feature_ids=genes) with pytest.raises(TypeError, match="data must hold real numbers"): - gsva(values.astype(str), sets, gene_ids=genes) + gsva(values.astype(str), sets, feature_ids=genes) with pytest.raises(ValueError, match="group applies to a cube"): - gsva(values, sets, gene_ids=genes, group="B cells") + gsva(values, sets, feature_ids=genes, group="B cells") with pytest.raises(ValueError, match="sample_ids must be unique"): - gsva(values, sets, gene_ids=genes, sample_ids=["S"] * 10) + gsva(values, sets, feature_ids=genes, sample_ids=["S"] * 10) with pytest.raises(ValueError, match="kernel must be one of"): - gsva(values, sets, gene_ids=genes, kernel="Gaussian") + gsva(values, sets, feature_ids=genes, kernel="Gaussian") with pytest.raises(ValueError, match="the Poisson kernel takes counts"): - gsva(values, sets, gene_ids=genes, kernel="poisson") + gsva(values, sets, feature_ids=genes, kernel="poisson") with pytest.raises(ValueError, match="exponent must be finite and at least 0"): - ssgsea(values, sets, gene_ids=genes, exponent=-1.0) + ssgsea(values, sets, feature_ids=genes, exponent=-1.0) with pytest.raises(ValueError, match=r"max_size \(2\) is smaller than min_size"): - plage(values, sets, gene_ids=genes, min_size=3, max_size=2) + plage(values, sets, feature_ids=genes, min_size=3, max_size=2) with pytest.raises(ValueError, match="every gene has equal values"): - gsva(np.ones((4, 3)), {"s": ["a"]}, gene_ids=["a", "b", "c"]) + gsva(np.ones((4, 3)), {"s": ["a"]}, feature_ids=["a", "b", "c"]) with pytest.raises(ValueError, match="range, which is zero here"): - ssgsea(values[:1], {"s": genes[:5]}, gene_ids=genes) + ssgsea(values[:1], {"s": genes[:5]}, feature_ids=genes) with pytest.raises(KeyError, match="no column 'gene_name'"): - gsva(cube, sets, gene_ids="gene_name") + gsva(cube, sets, id_column="gene_name") with pytest.raises(ValueError, match="sample_ids applies to an array"): gsva(cube, sets, sample_ids=["S1"]) - with pytest.raises(ValueError, match="values must be one of 'log_cpm', 'counts'"): + with pytest.raises(ValueError, match="values must be one of 'log2_cpm', 'counts'"): gsva(cube, sets, values="cpm") with pytest.raises(TypeError, match="gene_sets must be a GeneSets or a mapping"): - gsva(values, ["G00001"], gene_ids=genes) + gsva(values, ["G00001"], feature_ids=genes) def test_the_docstring_example_runs_as_shown() -> None: @@ -621,16 +629,16 @@ def test_the_docstring_example_runs_as_shown() -> None: runner = doctest.DocTestRunner(optionflags=doctest.NORMALIZE_WHITESPACE) for test in finder.find(pathway_scores.gsva, "gsva", globs={"gsva": gsva}): runner.run(test) - assert runner.failures == 0 and runner.tries == 7 + assert runner.failures == 0 and runner.tries == 8 def test_scores_lie_in_their_ranges() -> None: values, genes, sets = _continuous() for score_type, low, high in (("difference", -1, 1), ("kuiper", 0, 2)): - scores = gsva(values, sets, gene_ids=genes, score_type=score_type).scores + scores = gsva(values, sets, feature_ids=genes, score_type=score_type).scores assert low <= np.nanmin(scores) and np.nanmax(scores) <= high assert math.isclose( - np.ptp(ssgsea(values, sets, gene_ids=genes).scores), 1.0, rel_tol=1e-15 + np.ptp(ssgsea(values, sets, feature_ids=genes).scores), 1.0, rel_tol=1e-15 ) @@ -652,7 +660,7 @@ def _cells(ax, gid: str = "scores") -> np.ndarray: def test_the_heatmap_shows_the_scores_grouped_by_samples() -> None: values, genes, sets = _continuous() labels = ["b", "a", "b", "c", "a", "a", "b", "c", "c", "a"] - result = gsva(values, sets, gene_ids=genes) + result = gsva(values, sets, feature_ids=genes) ax = pathway_scores_figure(result, color_by=labels) # Samples in the order their groups first appear, each group in its order. order = [0, 2, 6, 1, 4, 5, 9, 3, 7, 8] @@ -675,7 +683,7 @@ def test_the_heatmap_shows_the_scores_grouped_by_samples() -> None: def test_the_heatmap_standardizes_the_sets_it_shows() -> None: values, genes, sets = _continuous() - result = combined_z_scores(values, sets, gene_ids=genes) + result = combined_z_scores(values, sets, feature_ids=genes) chosen = [list(sets)[3], list(sets)[0]] ax = pathway_scores_figure(result, sets=chosen, standardize=True) columns = [list(result.gene_sets).index(name) for name in chosen] @@ -692,15 +700,15 @@ def test_the_heatmap_standardizes_the_sets_it_shows() -> None: def test_the_heatmap_of_a_cube_groups_by_a_sample_variable() -> None: cube, _ = _cube() sets = {"treated": [f"G{j}" for j in range(30)]} - result = ssgsea(cube, sets, gene_ids="symbol") + result = ssgsea(cube, sets, id_column="symbol") ax = pathway_scores_figure(result, color_by="condition") - condition = result.samples["condition"].tolist() + condition = result.profiles["condition"].tolist() order = np.argsort([value != condition[0] for value in condition], kind="stable") assert_array_equal(_cells(ax), result.scores[order].T) # A missing score is masked, and drawn in the colour for missing values. gappy = PathwayScores( np.where(np.arange(11)[:, None] == 4, np.nan, result.scores), - result.samples, + result.profiles, result.gene_sets, result.method, result.settings, @@ -710,7 +718,7 @@ def test_the_heatmap_of_a_cube_groups_by_a_sample_variable() -> None: def test_the_heatmap_refuses_what_it_cannot_show() -> None: values, genes, sets = _continuous() - result = gsva(values, sets, gene_ids=genes) + result = gsva(values, sets, feature_ids=genes) with pytest.raises(TypeError, match="scores must be a PathwayScores"): pathway_scores_figure(result.scores) with pytest.raises(ValueError, match="no set 'unknown' among the scores"): diff --git a/tests/test_pca_and_dot_plots.py b/tests/test_pca_and_dot_plots.py index c7e8cd2..be22472 100644 --- a/tests/test_pca_and_dot_plots.py +++ b/tests/test_pca_and_dot_plots.py @@ -27,9 +27,14 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool import plotting -from cytepool.cube import Cube, from_cells -from cytepool.principal_components import PrincipalComponents, principal_components +from cytepool import ( + Cube, + PrincipalComponents, + from_cells, + plotting, + principal_components, +) +from cytepool.normalization import filter_by_expression, log2_cpm, tmm_factors FIGURE = pytest.importorskip("matplotlib.figure") @@ -45,7 +50,7 @@ GENES = [gene for genes in MARKERS.values() for gene in genes] DOT_CASES: dict[str, dict[str, Any]] = { "plain": {}, - "scaled by gene": {"standard_scale": "gene"}, + "scaled by gene": {"standard_scale": "feature"}, "scaled by group": {"standard_scale": "group"}, "expressing cells": {"mean_only_expressed": True}, "chosen samples and groups": { @@ -135,7 +140,7 @@ def test_components_are_those_of_plotPCA_up_to_sign( ) assert scaled(result.variance_explained[chosen], data[f"{case} percent"]) <= EXACT # 500 is more than the genes: every gene, in decreasing order of variance. - assert result.genes.size == min(top, data["values"].shape[1]) + assert len(result.features) == min(top, data["values"].shape[1]) def test_genes_of_equal_variance_keep_their_order_as_plotPCA_keeps_them() -> None: @@ -143,7 +148,7 @@ def test_genes_of_equal_variance_keep_their_order_as_plotPCA_keeps_them() -> Non result = principal_components(data["ties"], top=20) # Genes 18 to 21 (from 0) have equal variance; the 20 kept end with 18 and 19. - assert result.genes.tolist()[-2:] == [18, 19] + assert result.features["position"].tolist()[-2:] == [18, 19] assert up_to_sign(result.coordinates[:, :2], data["ties coordinates"]) <= EXACT @@ -172,9 +177,11 @@ def test_components_of_a_cube_record_the_cube_they_read() -> None: # Both pickle, the provenance with them; the copy keeps none it did not have. for found in (result, of_values): copy = pickle.loads(pickle.dumps(found)) - for name in ("coordinates", "variance_explained", "loadings", "genes"): + for name in ("coordinates", "variance_explained", "loadings"): assert_array_equal(getattr(copy, name), getattr(found, name)) assert not getattr(copy, name).flags.writeable + assert copy.features.equals(found.features) + assert not copy.features["position"].flags.writeable assert copy.samples.equals(found.samples) assert dict(copy.settings) == dict(found.settings) assert copy.provenance == found.provenance @@ -213,7 +220,7 @@ def test_dot_plot_areas_and_colours_are_scanpys(case: str) -> None: assert [t.get_text() for t in artists(ax, "gene group names")] == labels -@pytest.mark.parametrize("standard_scale", [None, "gene"]) +@pytest.mark.parametrize("standard_scale", [None, "feature"]) def test_dot_plot_labels_its_colour_bar_clear_of_the_key( standard_scale: str | None, ) -> None: @@ -248,6 +255,41 @@ def test_dot_plot_labels_its_colour_bar_clear_of_the_key( # Values derived by hand, and invariants. +def test_the_features_used_are_their_positions_with_the_cubes_columns() -> None: + cube = pca_cube() + named = cube.with_columns(features={"symbol": [f"G{j}" for j in range(300)]}) + result = principal_components(named, top=100) + features = result.features + positions = features["position"] + assert features.columns == ("position", "feature_id", "symbol") + assert len(features) == 100 and np.unique(positions).size == 100 + assert features.select(["feature_id", "symbol"]).equals( + named.features.take(positions) + ) + # They are the features whose values give the coordinates: those that + # filter_by_expression keeps on the samples with 10 cells or more, as log2-CPM + # with TMM factors and a prior count of 2, in decreasing order of variance. + rows = np.flatnonzero(fixture("pca")["n_cells"] >= 10) + counts = np.asarray(named.layer("counts", "T"), dtype=np.float64)[rows] + kept = np.flatnonzero(filter_by_expression(counts, sample_groups=np.zeros(10))) + values = log2_cpm( + counts[:, kept], norm_factors=tmm_factors(counts[:, kept]), prior_count=2.0 + ) + columns = np.searchsorted(kept, positions) + assert_array_equal(kept[columns], positions) + used = values[:, columns] + assert_allclose( + (used - used.mean(axis=0)) @ result.loadings, result.coordinates, atol=1e-12 + ) + assert np.all(np.diff(np.var(used, axis=0)) <= 0.0) + # The features of an array are its columns, by position alone. + of_values = principal_components(random_values(), top=25) + assert of_values.features.columns == ("position",) + variance = np.var(random_values(), axis=0) + expected = np.argsort(-variance, kind="stable")[:25] + assert_array_equal(of_values.features["position"], expected) + + def test_three_samples_on_two_genes_whose_components_are_the_genes() -> None: # Centred, gene 0 is (-1, 1, 0) and gene 1 (-1/3, -1/3, 2/3): orthogonal, with # sums of squares 2 and 2/3, so the components are the genes with shares 3/4 and @@ -260,7 +302,7 @@ def test_three_samples_on_two_genes_whose_components_are_the_genes() -> None: ) assert_allclose(result.variance_explained, [0.75, 0.25], rtol=1e-15) assert_allclose(np.abs(result.loadings), np.eye(2), atol=1e-15) - assert result.genes.tolist() == [0, 1] + assert result.features["position"].tolist() == [0, 1] assert result.samples["sample_id"].tolist() == ["1", "2", "3"] @@ -270,7 +312,7 @@ def test_two_samples_have_one_component_along_their_difference() -> None: values = np.array([[0.0, 0.0, 5.0], [3.0, 4.0, 5.0]]) result = principal_components(values, top=2) - assert result.genes.tolist() == [1, 0] + assert result.features["position"].tolist() == [1, 0] assert_allclose(result.coordinates, [[2.5], [-2.5]], rtol=1e-15) assert_allclose(result.loadings, [[-0.8], [-0.6]], rtol=1e-15) assert_allclose(result.variance_explained, [1.0]) @@ -282,7 +324,7 @@ def test_one_gene_gives_its_centred_values() -> None: values = np.array([[1.0, 9.0], [4.0, 9.5], [7.0, 10.0], [0.0, 11.0]]) result = principal_components(values, top=1) - assert result.genes.tolist() == [0] # variance 10 against 0.729 + assert result.features["position"].tolist() == [0] # variance 10, not 0.729 assert_allclose(result.coordinates[:, 0], [-2.0, 1.0, 4.0, -3.0]) @@ -307,7 +349,7 @@ def test_the_order_of_samples_and_genes_and_the_sign_of_the_values_do_not_matter assert_allclose(by_sample.coordinates, base.coordinates[samples], atol=1e-12) by_gene = principal_components(values[:, genes], top=25) assert_allclose(by_gene.coordinates, base.coordinates, atol=1e-12) - assert_array_equal(genes[by_gene.genes], base.genes) + assert_array_equal(genes[by_gene.features["position"]], base.features["position"]) negated = principal_components(-values, top=25) assert_allclose(negated.coordinates, base.coordinates, atol=1e-12) assert_allclose(negated.loadings, -base.loadings, atol=1e-12) @@ -318,9 +360,8 @@ def test_the_order_of_samples_and_genes_and_the_sign_of_the_values_do_not_matter def test_components_are_orthonormal_and_their_shares_sum_to_one() -> None: result = principal_components(random_values(), top=25) - centred = random_values()[:, result.genes] - random_values()[:, result.genes].mean( - 0 - ) + used = random_values()[:, result.features["position"]] + centred = used - used.mean(0) assert result.n_components == 8 # one fewer than the samples assert_allclose(result.loadings.T @ result.loadings, np.eye(8), atol=1e-12) @@ -466,8 +507,8 @@ def cells() -> dict[str, Any]: counts[counts.sum(axis=1) == 0, 0] = 1.0 return { "counts": counts, - "samples": rng.choice(["s1", "s2", "s3"], n), - "groups": rng.choice(["B", "T", "NK"], n), + "sample_labels": rng.choice(["s1", "s2", "s3"], n), + "group_labels": rng.choice(["B", "T", "NK"], n), "features": [f"x{j}" for j in range(12)], } @@ -478,9 +519,11 @@ def test_a_merged_group_and_pooled_samples_are_the_same_cells_given_one_label() genes = ["x0", "x3", "x7", "x11"] merged = cube.regroup({"BT": ["B", "T"]}) relabelled = from_cells( - **(data | {"groups": np.where(data["groups"] == "NK", "NK", "BT")}) + **(data | {"group_labels": np.where(data["group_labels"] == "NK", "NK", "BT")}) + ) + one_sample = from_cells( + **(data | {"sample_labels": np.full(data["group_labels"].size, "all")}) ) - one_sample = from_cells(**(data | {"samples": np.full(data["groups"].size, "all")})) for first, second, groups in ( (merged, relabelled, ["BT"]), @@ -499,14 +542,14 @@ def test_a_merged_group_and_pooled_samples_are_the_same_cells_given_one_label() def test_scaled_means_run_from_zero_to_one() -> None: cube = from_cells(**cells()) genes = ["x0", "x3", "x7", "x11"] - for scale, axis in (("gene", 0), ("group", 1)): + for scale, axis in (("feature", 0), ("group", 1)): ax = plotting.plot_dotplot(cube, genes, standard_scale=scale, ax=axes()) values = np.asarray(artists(ax, "dots")[0].get_array()).reshape(3, 4) assert_allclose(values.min(axis=axis), 0.0, atol=0) assert_allclose(values.max(axis=axis), 1.0, rtol=1e-15) # A gene equal in every group is 0 throughout. ax = plotting.plot_dotplot( - hand_cube(), ["a", "b"], standard_scale="gene", ax=axes() + hand_cube(), ["a", "b"], standard_scale="feature", ax=axes() ) assert_array_equal(artists(ax, "dots")[0].get_array(), [0.0, 0.0]) @@ -550,11 +593,11 @@ def test_dot_plots_refuse_what_they_cannot_draw() -> None: ) with pytest.raises(ValueError, match="the cube lacks cell numbers"): plotting.plot_dotplot(bulk, ["a"], ax=axes()) - with pytest.raises(KeyError, match="no gene 'c'"): + with pytest.raises(KeyError, match="no feature 'c'"): plotting.plot_dotplot(hand_cube(), ["c"], ax=axes()) - with pytest.raises(ValueError, match="genes is empty"): + with pytest.raises(ValueError, match="features is empty"): plotting.plot_dotplot(hand_cube(), [], ax=axes()) - with pytest.raises(ValueError, match="the group of genes 'g' is empty"): + with pytest.raises(ValueError, match="the group of features 'g' is empty"): plotting.plot_dotplot(hand_cube(), {"g": []}, ax=axes()) with pytest.raises(KeyError, match="no group 'B'"): plotting.plot_dotplot(hand_cube(), ["a"], groups=["B"], ax=axes()) @@ -568,7 +611,10 @@ def test_dot_plots_refuse_what_they_cannot_draw() -> None: with pytest.raises(ValueError, match="pseudocount of 1"): plotting.plot_dotplot(without, ["a"], mean_only_expressed=True, ax=axes()) empty = from_cells( - **(cells() | {"groups": np.where(cells()["samples"] == "s1", "A", "B")}) + **( + cells() + | {"group_labels": np.where(cells()["sample_labels"] == "s1", "A", "B")} + ) ) with pytest.raises( ValueError, match="group 'A' has no cells in the samples pooled" @@ -586,7 +632,7 @@ def test_dot_plots_refuse_what_they_cannot_draw() -> None: n_cells=np.array([[1]]), normalization={"method": "library_size"}, ) - with pytest.raises(ValueError, match="the symbol 'G' names 2 genes"): + with pytest.raises(ValueError, match="the symbol 'G' names 2 features"): plotting.plot_dotplot(twice, ["G"], ax=axes()) with pytest.raises(ValueError, match=r"vmin \(3\) must be below vmax \(1\)"): plotting.plot_dotplot(hand_cube(), ["a"], vmin=3, vmax=1, ax=axes()) diff --git a/tests/test_plotting.py b/tests/test_plotting.py index c2916af..afd483d 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -30,23 +30,31 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool import plotting -from cytepool.composition import propeller -from cytepool.cube import Cube, from_bulk -from cytepool.design import Design -from cytepool.differential_expression import DifferentialExpressionResult, limma_voom -from cytepool.meta_analysis import differential_expression_meta_analysis -from cytepool.normalization import log_cpm +from cytepool import ( + Cube, + CubeCollection, + Design, + DifferentialExpressionResult, + MetaAnalysisResult, + StudyResults, + Table, + analyse_each_study, + camera, + from_bulk, + limma_voom, + meta_analysis, + plotting, + propeller, +) +from cytepool.normalization import log2_cpm from cytepool.stats.deseq2 import deseq2_wald_test, fit_deseq2 from cytepool.stats.empirical_bayes import moderated_statistics, treat -from cytepool.stats.gene_set_tests import camera from cytepool.stats.linear_models import fit_linear_model from cytepool.stats.quasi_likelihood import ( fit_quasi_likelihood, quasi_likelihood_f_test, ) from cytepool.stats.voom import voom, voom_with_quality_weights -from cytepool.table import Table matplotlib = pytest.importorskip("matplotlib") FIGURE = pytest.importorskip("matplotlib.figure") @@ -152,7 +160,7 @@ def test_mds_coordinates_are_those_of_plotMDS_up_to_sign(case: str) -> None: f"S{i}" for i in range(1, 7) ] else: - ax = plotting.plot_mds(log_cpm(counts()), ax=axes(), **options[case]) + ax = plotting.plot_mds(log2_cpm(counts()), ax=axes(), **options[case]) ours = artists(ax, "samples")[0].get_offsets() theirs = reference[f"{case} points"] signs = np.sign(np.sum(ours * theirs, axis=0)) @@ -186,7 +194,7 @@ def test_sample_weights_are_the_bars_of_voomWithQualityWeights() -> None: @pytest.mark.parametrize("case", ["sa_robust", "sa_plain"]) def test_residual_variances_are_plotSA(case: str) -> None: reference = fixture("limma") - values = log_cpm(counts()) + values = log2_cpm(counts()) options: dict[str, Any] = {} if case == "sa_robust": values[:, 10:16] += reference["noise"] @@ -318,9 +326,9 @@ def test_forest_intervals_and_pooled_estimate_are_metafors( method: str, test: str ) -> None: data = fixture("metafor") - meta = differential_expression_meta_analysis(studies(), method=method, test=test) + meta = meta_analysis(studies(), method=method, test=test) for feature in data["forest features"].tolist(): - ax = plotting.plot_forest(meta, studies(), feature, ax=axes()) + ax = plotting.plot_forest(meta, feature, ax=axes()) segments = sorted( artists(ax, "intervals")[0].get_segments(), key=lambda s: -s[0, 1] ) @@ -337,9 +345,9 @@ def test_forest_intervals_and_pooled_estimate_are_metafors( def test_a_study_that_did_not_test_the_gene_keeps_its_row() -> None: data = fixture("metafor") - meta = differential_expression_meta_analysis(studies()) + meta = meta_analysis(studies()) feature = str(data["forest features"][2]) # tested by two studies - ax = plotting.plot_forest(meta, studies(), feature, ax=axes()) + ax = plotting.plot_forest(meta, feature, ax=axes()) names = [label.get_text() for label in ax.get_yticklabels()] assert names[:3] == ["A", "B", "C"] @@ -353,6 +361,62 @@ def test_a_study_that_did_not_test_the_gene_keeps_its_row() -> None: assert low < min(0.0, np.min(ends)) and high > max(0.0, np.max(ends)) +@cache +def collected_studies() -> StudyResults: + """The studies of the forest plots as the studies of a collection, each analysed + on its aligned cube; study C measured 295 of the 300 genes.""" + cubes = {name: result.cube for name, result in studies().items()} + return analyse_each_study( + CubeCollection(cubes), + "~ donor + condition", + levels=LEVELS, + contrasts={"stim": "conditionstim"}, + ) + + +def forest_rows(meta: Any, feature: str) -> list[str]: + """What the forest plot of a gene writes beside each study, A, B and C.""" + ax = plotting.plot_forest(meta, feature, ax=axes()) + assert [label.get_text() for label in ax.get_yticklabels()][:3] == ["A", "B", "C"] + return [text.get_text() for text in ax.texts][:3] + + +def test_a_study_that_did_not_measure_the_gene_is_told_from_one_that_left_it_out() -> ( + None +): + collected = collected_studies() + collection = collected.collection + ids = collection.features["feature_id"].tolist() + measured = np.asarray(collection.measured) + tested = { + study: set(result.table("bulk", "stim")["feature_id"].tolist()) + for study, result in collected.items() + } + # A gene that study C did not measure, which A and B tested; and a gene that every + # study measured, which B's expression filter left out. + unmeasured = next( + gene + for j, gene in enumerate(ids) + if not measured[2, j] and gene in tested["A"] and gene in tested["B"] + ) + left_out = next( + gene + for j, gene in enumerate(ids) + if measured[:, j].all() + and gene in tested["A"] + and gene in tested["C"] + and gene not in tested["B"] + ) + with_collection = meta_analysis(collected) + plain = meta_analysis(dict(collected)) + first, second, third = forest_rows(with_collection, unmeasured) + assert third == "not measured" and "not" not in first + second + assert forest_rows(plain, unmeasured)[2] == "not tested" + for meta in (with_collection, plain): + rows = forest_rows(meta, left_out) + assert rows[1] == "not tested" and "not" not in rows[0] + rows[2] + + def composition_cube() -> Cube: cells = fixture("speckle")["speckle cells"] return Cube( @@ -510,7 +574,7 @@ def test_a_legend_inside_a_plot_covers_nothing_and_a_long_one_goes_beside() -> N groups={"group_id": ["bulk"]}, features={"feature_id": GENES}, ) - values = log_cpm(counts()) + values = log2_cpm(counts()) scatter = { "md": plotting.plot_md(table(), ax=axes()), "volcano": plotting.plot_volcano(table(), ax=axes()), @@ -606,6 +670,40 @@ def test_mds_and_pca_draw_their_samples_alike() -> None: assert labels[0] == labels[1] == SAMPLES["sample_id"] +def test_sample_labels_stay_inside_the_frame_and_apart_after_the_final_draw() -> None: + from matplotlib.backends.backend_agg import FigureCanvasAgg + from matplotlib.text import Text + + # Each sample has a near copy, so that two labels above one place would overlap, + # and long names at the sides of a small plot, where a label centred on its + # point would cross the frame. + rng = np.random.default_rng(7) + first = rng.normal(5.0, 1.0, size=(4, 200)) + values = np.concatenate([first, first + rng.normal(0.0, 0.01, size=first.shape)]) + names = [f"a_long_sample_name_{i}" for i in range(8)] + for draw in (plotting.plot_mds, plotting.plot_pca): + figure = FIGURE.Figure(figsize=(4.0, 3.0), layout="constrained") + FigureCanvasAgg(figure) + ax = draw(values, labels=names, color_by=["a", "b"] * 4, ax=figure.subplots()) + figure.savefig(io.BytesIO(), format="png") + renderer = figure.canvas.get_renderer() + frame = ax.get_window_extent(renderer) + labels = artists(ax, "sample labels") + assert [text.get_text() for text in labels] == names, draw.__name__ + offsets = np.concatenate([c.get_offsets() for c in artists(ax, "samples")]) + # Every sample is drawn inside the frame, below the room made for the labels. + assert all(frame.contains(*point) for point in ax.transData.transform(offsets)) + boxes = [Text.get_window_extent(text, renderer) for text in labels] # no line + for text, box in zip(labels, boxes, strict=True): + # Each label points at a sample, and lies inside the frame. + assert np.min(np.hypot(*(offsets - text.xy).T)) == 0.0 + assert frame.x0 <= box.x0 and box.x1 <= frame.x1, text.get_text() + assert frame.y0 <= box.y0 and box.y1 <= frame.y1, text.get_text() + for i, box in enumerate(boxes): + for other in boxes[i + 1 :]: + assert not box.overlaps(other), draw.__name__ + + @pytest.mark.parametrize( ("arguments", "error", "message"), [ @@ -797,7 +895,7 @@ def test_sample_weights_of_a_result_are_its_quality_weights() -> None: def test_fits_and_tests_are_drawn_from_their_own_columns() -> None: values = voom(counts(), design()) - fit = fit_linear_model(values.log_cpm, design(), weights=values.weights) + fit = fit_linear_model(values.log2_cpm, design(), weights=values.weights) moderated = moderated_statistics(fit) ax = plotting.plot_md(moderated, coefficient="conditionstim", ax=axes()) drawn = points(ax, "up", "down", "not significant") @@ -835,7 +933,7 @@ def test_fits_and_tests_are_drawn_from_their_own_columns() -> None: drawn = points(ax, "up", "down", "not significant") assert_allclose( drawn, - pairs(test.log_fold_change[:, 0], -np.log10(test.p_value)), + pairs(test.log2_fold_change[:, 0], -np.log10(test.p_value)), rtol=1e-15, ) @@ -1042,10 +1140,12 @@ def test_barcode_takes_ids_positions_or_masks() -> None: mask = np.zeros(t.size, dtype=bool) mask[[3, 40]] = True by_mask = plotting.plot_barcode(t, mask, ax=axes()) - by_gene_ids = plotting.plot_barcode(t, [ids[40], ids[3]], gene_ids=ids, ax=axes()) + by_feature_ids = plotting.plot_barcode( + t, [ids[40], ids[3]], feature_ids=ids, ax=axes() + ) worms = [ artists(ax, "worm")[0].get_ydata() - for ax in (by_id, by_position, by_mask, by_gene_ids) + for ax in (by_id, by_position, by_mask, by_feature_ids) ] for worm in worms[1:]: assert_array_equal(worm, worms[0]) @@ -1058,12 +1158,12 @@ def test_barcode_takes_ids_positions_or_masks() -> None: @pytest.mark.parametrize( ("gene_set", "options", "message"), [ - (["unknown"], {"gene_ids": list("abcde")}, "no gene of the set"), + (["unknown"], {"feature_ids": list("abcde")}, "no gene of the set"), (list(range(5)), {}, "holds every gene"), ([7], {}, "position outside 0 to 4"), - (["a"], {}, "give gene_ids too"), + (["a"], {}, "give feature_ids too"), ([0], {"span": 0.0}, r"span must be in \(0, 1\]"), - ([0], {"gene_ids": ["a", "a", "b", "c", "d"]}, None), + ([0], {"feature_ids": ["a", "a", "b", "c", "d"]}, None), ], ) def test_barcode_refuses_sets_it_cannot_place( @@ -1081,7 +1181,9 @@ def test_barcode_refuses_sets_it_cannot_place( def test_gene_sets_are_drawn_by_p_value_and_direction() -> None: values = voom(counts(), design()) sets = {f"set{i}": GENES[10 * i : 10 * i + 15] for i in range(12)} - tested = camera(values, design(), "conditionstim", sets, gene_ids=GENES) + tested = camera( + values, sets, design=design(), contrast="conditionstim", feature_ids=GENES + ) ax = plotting.plot_gene_sets(tested, top=5, ax=axes()) bars = artists(ax, "gene sets") order = np.argsort(tested["p_value"], kind="stable")[:5] @@ -1132,17 +1234,28 @@ def test_trend_figures_refuse_other_objects() -> None: def test_forest_refuses_what_it_cannot_find() -> None: - meta = differential_expression_meta_analysis(studies()) + meta = meta_analysis(studies()) with pytest.raises(KeyError, match="gene 'nothing' was not meta-analysed"): - plotting.plot_forest(meta, studies(), "nothing") - with pytest.raises(TypeError, match="studies must map study names"): - plotting.plot_forest(meta, {"A": table()}, "g001") + plotting.plot_forest(meta, "nothing") + # The studies' results are the meta-analysis's own, which keeps only results. + record = (meta.summary, meta.studies, meta.tables, meta.provenance) + with pytest.raises( + TypeError, match=r"study_results\['A'\] must be a DifferentialExpressionResult" + ): + MetaAnalysisResult(*record, study_results={"A": table()}) # type: ignore[dict-item] with pytest.raises(TypeError, match="feature must be a gene id"): - plotting.plot_forest(meta, studies(), 3) # type: ignore[arg-type] - with pytest.raises(ValueError, match="studies holds no study"): - plotting.plot_forest(meta, {}, "g001") + plotting.plot_forest(meta, 3) # type: ignore[arg-type] + # A meta-analysis built without its studies' results, or with none, cannot draw + # their estimates. + message = "this meta-analysis keeps no study results, so its studies' estimates" + for without in ( + MetaAnalysisResult(*record), + MetaAnalysisResult(*record, study_results={}), + ): + with pytest.raises(ValueError, match=message): + plotting.plot_forest(without, "g001") with pytest.raises(KeyError, match="no group 'T' was analysed"): - plotting.plot_forest(meta, studies(), "g001", group="T", contrast="stim") + plotting.plot_forest(meta, "g001", group="T", contrast="stim") # Colours, settings and imports. @@ -1229,7 +1342,7 @@ def test_categorical_colours_stay_apart_under_colour_vision_deficiencies() -> No def test_drawing_changes_no_setting_of_matplotlib() -> None: before = dict(matplotlib.rcParams) - plotting.plot_mds(log_cpm(counts()), color_by=SAMPLES["condition"], ax=axes()) + plotting.plot_mds(log2_cpm(counts()), color_by=SAMPLES["condition"], ax=axes()) plotting.plot_md(table(), ax=axes()) plotting.plot_cell_numbers(composition_cube(), ax=axes()) diff --git a/tests/test_pool.py b/tests/test_pool.py index db34b29..4441ded 100644 --- a/tests/test_pool.py +++ b/tests/test_pool.py @@ -8,10 +8,9 @@ from numpy.testing import assert_allclose, assert_array_equal import cytepool -from cytepool.cube import Cube, from_bulk, from_cells +from cytepool import Cube, Table, from_bulk, from_cells from cytepool.cube import pool as pool_module from cytepool.cube.cube import LAYER_NAMES -from cytepool.table import Table FIXTURE = Path(__file__).parent / "data" / "pool" / "scanpy_decoupler.npz" TARGET_SUM = 1e4 @@ -169,7 +168,7 @@ def assert_same_cube(actual: Cube, expected: Cube) -> None: def pool(counts: Any, cells: dict[str, np.ndarray], **options: Any) -> Cube: return from_cells( - counts, samples=cells["samples"], groups=cells["groups"], **options + counts, sample_labels=cells["samples"], group_labels=cells["groups"], **options ) @@ -245,8 +244,8 @@ def test_single_cell_entries_hold_that_cells_values_to_a_few_ulps() -> None: groups = [f"g{i:02d}" for i in range(values.size)] cube = from_cells( values[:, None], - samples=["s"] * values.size, - groups=groups, + sample_labels=["s"] * values.size, + group_labels=groups, target_sum=1.0, library_sizes=np.ones(values.size), ) @@ -355,17 +354,17 @@ def test_sums_above_the_largest_uint32_switch_counts_to_uint64() -> None: expected, n_cells = pooled_with_masks( above, np.array(samples), np.array(groups), ["a", "b"], ["g", "h"] ) - cube = from_cells(above, samples=samples, groups=groups) + cube = from_cells(above, sample_labels=samples, group_labels=groups) assert cube.layer("counts").dtype == np.uint64 assert cube.layer("counts", "g").tolist() == [[2**32, 3], [5, 0]] assert_pooled(cube, expected, n_cells) # 2**31 + (2**31 - 1) is the largest uint32 itself. at_limit = np.array([[2**31, 1], [2**31 - 1, 2], [5, 0], [0, 7]]) - cube = from_cells(at_limit, samples=samples, groups=groups) + cube = from_cells(at_limit, sample_labels=samples, group_labels=groups) assert cube.layer("counts").dtype == np.uint32 assert cube.layer("counts", "g")[0, 0] == 2**32 - 1 # A single count above the largest uint32. - cube = from_cells(np.array([[2**33 + 1]]), samples=["a"], groups=["g"]) + cube = from_cells(np.array([[2**33 + 1]]), sample_labels=["a"], group_labels=["g"]) assert cube.layer("counts").dtype == np.uint64 assert cube.layer("counts")[0, 0, 0] == 2**33 + 1 @@ -424,8 +423,8 @@ def test_tables_set_the_order_and_keep_metadata( ordered = pool( to_csr(cells["counts"]), cells, - sample_table=sample_table, - group_table=group_table, + samples=sample_table, + groups=group_table, features=features, ) assert ordered.samples.equals(Table(sample_table)) @@ -452,25 +451,35 @@ def test_tables_set_the_order_and_keep_metadata( def test_integer_and_categorical_labels() -> None: counts = np.arange(1, 13).reshape(6, 2) # Integer labels sort in numeric order and become text. - cube = from_cells(counts, samples=[10, 2, 2, 10, 1, 2], groups=[0, 0, 1, 1, 0, 0]) + cube = from_cells( + counts, sample_labels=[10, 2, 2, 10, 1, 2], group_labels=[0, 0, 1, 1, 0, 0] + ) assert cube.samples["sample_id"].tolist() == ["1", "2", "10"] assert cube.groups["group_id"].tolist() == ["0", "1"] assert cube.layer("counts", "0").tolist() == [[9, 10], [14, 16], [1, 2]] # numpy reads a list that mixes text with NaN or numbers as text ("nan", "1"). - with pytest.raises(ValueError, match="samples has a missing label at cell 2"): - from_cells(counts, samples=["a", "b", np.nan, "a", "b", "a"], groups=[0] * 6) - with pytest.raises(TypeError, match="groups mixes text and integer labels"): - from_cells(counts, samples=[1] * 6, groups=["a", "b", 1, "a", "b", "a"]) + with pytest.raises(ValueError, match="sample_labels has a missing label at cell 2"): + from_cells( + counts, + sample_labels=["a", "b", np.nan, "a", "b", "a"], + group_labels=[0] * 6, + ) + with pytest.raises(TypeError, match="group_labels mixes text and integer labels"): + from_cells( + counts, sample_labels=[1] * 6, group_labels=["a", "b", 1, "a", "b", "a"] + ) with pytest.raises(ValueError, match=r"features\['feature_id'\] has a missing"): - from_cells(counts, samples=[1] * 6, groups=[0] * 6, features=["x", np.nan]) + from_cells( + counts, sample_labels=[1] * 6, group_labels=[0] * 6, features=["x", np.nan] + ) pd = pytest.importorskip("pandas") missing = pd.Categorical(["a", None, "b", "a", "b", "a"]) - with pytest.raises(ValueError, match="samples has a missing label at cell 1"): - from_cells(counts, samples=missing, groups=[0] * 6) + with pytest.raises(ValueError, match="sample_labels has a missing label at cell 1"): + from_cells(counts, sample_labels=missing, group_labels=[0] * 6) # Categories sort like other labels, and unused categories are dropped. labels = pd.Categorical(["b", "a", "b", "a", "a", "b"], categories=["z", "b", "a"]) for samples in (labels, pd.Series(labels), pd.Series(np.asarray(labels))): - by_category = from_cells(counts, samples=samples, groups=["g"] * 6) + by_category = from_cells(counts, sample_labels=samples, group_labels=["g"] * 6) assert by_category.samples["sample_id"].tolist() == ["a", "b"] assert by_category.layer("counts", "g").tolist() == [[19, 22], [17, 20]] @@ -533,18 +542,18 @@ def test_layer_subsets_equal_the_full_cube( def test_empty_inputs() -> None: no_cells = from_cells( np.zeros((0, 3)), - samples=[], - groups=[], - sample_table={"sample_id": ["S1"]}, - group_table={"group_id": ["g"]}, + sample_labels=[], + group_labels=[], + samples={"sample_id": ["S1"]}, + groups={"group_id": ["g"]}, ) assert no_cells.layer("counts").shape == (1, 1, 3) assert not no_cells.layer("log_sum").any() assert no_cells.n_cells is not None and no_cells.n_cells.tolist() == [[0]] no_features = from_cells( CSR(np.zeros(0), np.zeros(0, np.int32), np.zeros(3, np.int32), (2, 0)), - samples=["a", "b"], - groups=["g", "g"], + sample_labels=["a", "b"], + group_labels=["g", "g"], layers=["counts", "detected"], ) assert no_features.layer("counts").shape == (1, 2, 0) @@ -555,7 +564,7 @@ def test_matches_scanpy_and_decoupler() -> None: # cells that scanpy normalized itself, and decoupler's pseudobulk sums. data = np.load(FIXTURE, allow_pickle=False) counts = CSR(data["data"], data["indices"], data["indptr"], tuple(data["shape"])) - cube = from_cells(counts, samples=data["sample"], groups=data["group"]) + cube = from_cells(counts, sample_labels=data["sample"], group_labels=data["group"]) groups = [cube.groups["group_id"].tolist().index(g) for g in data["keys_group"]] samples = [cube.samples["sample_id"].tolist().index(s) for s in data["keys_sample"]] n_cells = cube.n_cells @@ -652,7 +661,7 @@ def test_text_labels_of_a_data_frame_give_the_ids() -> None: unnamed = from_bulk(frame.reset_index(drop=True), features=["u", "v", "w"]) assert unnamed.samples["sample_id"].tolist() == ["sample_0", "sample_1"] assert unnamed.features["feature_id"].tolist() == ["u", "v", "w"] - pooled = from_cells(frame, samples=["s", "s"], groups=["g", "g"]) + pooled = from_cells(frame, sample_labels=["s", "s"], group_labels=["g", "g"]) assert pooled.features["feature_id"].tolist() == ["x", "y", "z"] assert pooled.layer("counts", "g").tolist() == [[12, 1, 21]] @@ -845,49 +854,51 @@ def with_entry(array: np.ndarray, index: Any, value: Any) -> np.ndarray: ), ( "too few labels", - lambda d: {"samples": np.array(["S1"] * 102)}, + lambda d: {"sample_labels": np.array(["S1"] * 102)}, ValueError, - "counts has 103 rows, samples has 102 labels", + "counts has 103 rows, sample_labels has 102 labels", ), ( "missing label", - lambda d: {"groups": np.array(["B"] * 50 + [None] + ["B"] * 52, dtype=object)}, + lambda d: { + "group_labels": np.array(["B"] * 50 + [None] + ["B"] * 52, dtype=object) + }, ValueError, - "groups has a missing label at cell 50", + "group_labels has a missing label at cell 50", ), ( "empty label", - lambda d: {"samples": np.array(["S1"] * 102 + [""])}, + lambda d: {"sample_labels": np.array(["S1"] * 102 + [""])}, ValueError, - "samples has an empty label at cell 102", + "sample_labels has an empty label at cell 102", ), ( "float labels", - lambda d: {"samples": np.ones(103)}, + lambda d: {"sample_labels": np.ones(103)}, TypeError, - "samples must hold text or integer labels, got dtype float64", + "sample_labels must hold text or integer labels, got dtype float64", ), ( "mixed labels", - lambda d: {"samples": np.array(["S1", 1] * 51 + ["S1"], dtype=object)}, + lambda d: {"sample_labels": np.array(["S1", 1] * 51 + ["S1"], dtype=object)}, TypeError, "mixes text and integer labels", ), ( "2-D labels", - lambda d: {"samples": np.array([["S1"] * 103])}, + lambda d: {"sample_labels": np.array([["S1"] * 103])}, ValueError, - r"samples must be 1-D, one label per cell, got shape \(1, 103\)", + r"sample_labels must be 1-D, one label per cell, got shape \(1, 103\)", ), ( "label without a row", - lambda d: {"sample_table": {"sample_id": ["S1", "S2", "S4"]}}, + lambda d: {"samples": {"sample_id": ["S1", "S2", "S4"]}}, ValueError, - r"the label 'S3' of cell \d+ in samples is not in sample_table\['sample_id'\]", + r"the label 'S3' of cell \d+ in sample_labels is not in samples\['sample_id'\]", ), ( "table without ids", - lambda d: {"group_table": {"name": GROUP_IDS}}, + lambda d: {"groups": {"name": GROUP_IDS}}, ValueError, "the groups table needs a 'group_id' column", ), @@ -964,8 +975,8 @@ def test_invalid_calls_are_rejected( ) -> None: arguments: dict[str, Any] = { "counts": cells["counts"], - "samples": cells["samples"], - "groups": cells["groups"], + "sample_labels": cells["samples"], + "group_labels": cells["groups"], } arguments.update(change(cells["counts"])) with pytest.raises(error, match=message): @@ -976,7 +987,10 @@ def test_sums_up_to_the_largest_int64_are_exact_and_beyond_it_raise() -> None: # 1,024 cells of 2**53 - 1, the largest count, and one of 1,023 sum to 2**63 - 1, # the largest int64; float64 would round that sum to 2**63. counts = np.append(np.full(1024, 2**53 - 1), 1023).reshape(-1, 1) - labels = {"samples": ["a"] * counts.shape[0], "groups": ["g"] * counts.shape[0]} + labels = { + "sample_labels": ["a"] * counts.shape[0], + "group_labels": ["g"] * counts.shape[0], + } cube = from_cells(counts, **labels) assert cube.layer("counts").dtype == np.uint64 assert cube.layer("counts")[0, 0, 0] == 2**63 - 1 @@ -996,7 +1010,7 @@ def test_sums_up_to_the_largest_int64_are_exact_and_beyond_it_raise() -> None: from_cells(counts, **labels) # One count that is not a whole number makes the counts float64, which have no # such limit, wherever that cell comes: here after the sum passes 2**63 - 1. - labels = {"samples": ["a"] * 1026, "groups": ["g"] * 1026} + labels = {"sample_labels": ["a"] * 1026, "group_labels": ["g"] * 1026} expected = sum(counts[:, 0].tolist()) + 0.5 # Python integers, exact for fractional in (np.vstack([counts, [[0.5]]]), np.vstack([[[0.5]], counts])): cube = from_cells(fractional, **labels, layers="counts") @@ -1023,19 +1037,23 @@ def test_an_entry_with_more_cells_than_detected_counts_is_rejected( pool_module._check_detected_fits(np.array([7, 2**32]), ids) # ... and from_cells is run with the limit lowered to two cells. monkeypatch.setattr(pool_module, "_MOST_DETECTED", 2) - labels = {"samples": ["a", "b", "a", "a", "b"], "groups": ["g"] * 5} + labels = {"sample_labels": ["a", "b", "a", "a", "b"], "group_labels": ["g"] * 5} with pytest.raises(OverflowError, match="sample 'a' has 3 cells, more than the 2"): from_cells(np.ones((5, 1)), **labels) counts_only = from_cells(np.ones((5, 1)), **labels, layers="counts") assert counts_only.layer("counts", "g").tolist() == [[3], [2]] - at_limit = from_cells(np.ones((4, 1)), samples=["a", "b"] * 2, groups=["g"] * 4) + at_limit = from_cells( + np.ones((4, 1)), sample_labels=["a", "b"] * 2, group_labels=["g"] * 4 + ) assert at_limit.layer("detected", "g").tolist() == [[2], [2]] def test_normalized_values_that_sum_beyond_float64_are_rejected() -> None: # One cell's normalized values sum to target_sum, 1e308, which float64 holds; # two such cells in an entry do not fit. - one = from_cells(np.array([[1]]), samples=["a"], groups=["g"], target_sum=1e308) + one = from_cells( + np.array([[1]]), sample_labels=["a"], group_labels=["g"], target_sum=1e308 + ) assert one.layer("linear_sum")[0, 0, 0] == 1e308 with pytest.raises( OverflowError, @@ -1044,8 +1062,8 @@ def test_normalized_values_that_sum_beyond_float64_are_rejected() -> None: ): from_cells( np.array([[1], [1]]), - samples=["a", "a"], - groups=["g", "g"], + sample_labels=["a", "a"], + group_labels=["g", "g"], target_sum=1e308, ) @@ -1054,14 +1072,22 @@ def test_normalized_values_beyond_float64_are_rejected() -> None: message = r"normalized values of cell 0 \(row 0 of counts\).*exceed the float64" # target_sum / library size is 5e309, beyond float64 for both counts. with pytest.raises(ValueError, match=message): - from_cells(np.array([[1e-306, 1e-306]]), samples=["a"], groups=["g"]) + from_cells( + np.array([[1e-306, 1e-306]]), sample_labels=["a"], group_labels=["g"] + ) with pytest.raises(ValueError, match=message): from_cells( - np.array([[1, 2]]), samples=["a"], groups=["g"], library_sizes=[1e-306] + np.array([[1, 2]]), + sample_labels=["a"], + group_labels=["g"], + library_sizes=[1e-306], ) # Counts alone need no normalization. cube = from_cells( - np.array([[1e-306, 1e-306]]), samples=["a"], groups=["g"], layers="counts" + np.array([[1e-306, 1e-306]]), + sample_labels=["a"], + group_labels=["g"], + layers="counts", ) assert cube.layer("counts")[0, 0].tolist() == [1e-306, 1e-306] @@ -1071,4 +1097,4 @@ def test_repeated_entries_that_sum_too_high_are_rejected() -> None: np.array([2.0**52, 2.0**52]), np.array([0, 0]), np.array([0, 2]), (1, 1) ) with pytest.raises(ValueError, match="repeated entries that sum to 9.0072e"): - from_cells(matrix, samples=["a"], groups=["g"]) + from_cells(matrix, sample_labels=["a"], group_labels=["g"]) diff --git a/tests/test_private_names.py b/tests/test_private_names.py index 3e2ec78..c0b4546 100644 --- a/tests/test_private_names.py +++ b/tests/test_private_names.py @@ -34,6 +34,14 @@ "error that sample_quality_weights gives its callers" ) _VOOM_RESULT = "builds the public VoomResult of cytepool.stats.voom" +_SUMRANK = ( + "parts of the public fit_sumrank of cytepool.stats.sumrank, which the analyses of " + "several studies run on their own statistics and permutations" +) +_VARIANCE_FRACTIONS = ( + "parts of the public variance_fractions of cytepool.stats.variance_fractions, " + "which variance_partitioning runs on each group of a cube" +) # (module, name): why other modules of the package import it from that module. SHARED_FROM_THEIR_MODULE = { @@ -67,6 +75,10 @@ "builds the public ModeratedFixedEffectTests of cytepool.stats.dream from the " "parts of tests that dream_voom chooses feature by feature" ), + ("cytepool.stats.meta_analysis", "_checked_options"): ( + "the options of the public fit_meta_analysis, which meta_analysis checks " + "alike before it reads the studies" + ), ("cytepool.stats.mixed_models", "_fit"): ( "builds the public MixedModelFit of cytepool.stats.mixed_models" ), @@ -88,6 +100,13 @@ ("cytepool.stats.smoothing", "_weighted_lowess"): ( "tests and validation/empirical_bayes.py import it from smoothing" ), + ("cytepool.stats.sumrank", "_NullCounts"): _SUMRANK, + ("cytepool.stats.sumrank", "_capped_sums"): _SUMRANK, + ("cytepool.stats.sumrank", "_studies_summed"): _SUMRANK, + ("cytepool.stats.variance_fractions", "_Terms"): _VARIANCE_FRACTIONS, + ("cytepool.stats.variance_fractions", "_checked_terms"): _VARIANCE_FRACTIONS, + ("cytepool.stats.variance_fractions", "_linear_fractions"): _VARIANCE_FRACTIONS, + ("cytepool.stats.variance_fractions", "_mixed_fractions"): _VARIANCE_FRACTIONS, ("cytepool.stats.voom", "_quality_weighted_voom"): _VOOM_RESULT, } diff --git a/tests/test_provenance.py b/tests/test_provenance.py index 4b5604f..491dedf 100644 --- a/tests/test_provenance.py +++ b/tests/test_provenance.py @@ -10,9 +10,8 @@ from numpy.testing import assert_allclose, assert_array_equal import cytepool -from cytepool.cube import Cube, from_bulk, from_cells +from cytepool import Cube, from_bulk, from_cells, read_cube, write_cube from cytepool.cube.provenance import describe_provenance -from cytepool.io import read_cube, write_cube SAMPLES = { "sample_id": ["S1", "S2", "S3", "S4", "S5"], @@ -55,10 +54,10 @@ def cube(cells: tuple[np.ndarray, np.ndarray, np.ndarray]) -> Cube: counts, samples, groups = cells return from_cells( counts, - samples=samples, - groups=groups, - sample_table=SAMPLES, - group_table=GROUPS, + sample_labels=samples, + group_labels=groups, + samples=SAMPLES, + groups=GROUPS, features=FEATURES, source=SOURCE, ) @@ -91,8 +90,8 @@ def test_from_cells_records_other_settings( numbers = np.array([int(label[1:]) for label in samples]) # S3 is 3 cube = from_cells( counts, - samples=numbers, - groups=groups, + sample_labels=numbers, + group_labels=groups, features={"feature_id": FEATURES}, target_sum=1e6, library_sizes=counts.sum(axis=1) + 1.0, @@ -107,7 +106,9 @@ def test_from_cells_records_other_settings( assert construction["target_sum"] == 1e6 assert construction["library_sizes"] == "given" assert construction["layers"] == ["counts", "linear_sum"] - positions = from_cells(counts, samples=samples, groups=groups, layers="counts") + positions = from_cells( + counts, sample_labels=samples, group_labels=groups, layers="counts" + ) assert positions.provenance["construction"]["features"] == "positions" @@ -117,7 +118,9 @@ def test_records_name_where_ids_of_data_frames_came_from( pandas = pytest.importorskip("pandas") counts, samples, groups = cells frame = pandas.DataFrame(counts, columns=FEATURES) - pooled = from_cells(frame, samples=samples, groups=groups, layers="counts") + pooled = from_cells( + frame, sample_labels=samples, group_labels=groups, layers="counts" + ) assert pooled.provenance["construction"]["features"] == "data_frame_columns" bulk = pandas.DataFrame(counts[:3], index=["a", "b", "c"], columns=FEATURES) assert from_bulk(bulk).provenance["construction"] == { @@ -172,7 +175,7 @@ def test_invalid_source_records_are_refused( ) -> None: counts, samples, groups = cells with pytest.raises(error, match=message): - from_cells(counts, samples=samples, groups=groups, source=source) + from_cells(counts, sample_labels=samples, group_labels=groups, source=source) with pytest.raises(error, match=message): from_bulk(np.ones((2, 2), dtype=int), source=source) @@ -406,8 +409,8 @@ def test_history_reads_the_chain_in_plain_terms(cube: Cube) -> None: ' artifacts: {"membership": "membership.parquet#sha256:5e1f"}', ' exporter: {"name": "a cell store", "version": "2.1", "min_genes": 200}', f"Built by from_cells (CytePool {cytepool.__version__}) from 400 cells:", - " samples: the rows of sample_table", - " groups: the rows of group_table", + " samples: the rows of the sample table given", + " groups: the rows of the group table given", " features: ids given as a sequence", " layers: counts, detected, log_sum, log_sum_sq, linear_sum", " normalized values: each cell scaled to a total of 10,000, with library " diff --git a/tests/test_quasi_likelihood.py b/tests/test_quasi_likelihood.py index fcaf39a..b186ef6 100644 --- a/tests/test_quasi_likelihood.py +++ b/tests/test_quasi_likelihood.py @@ -21,7 +21,8 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.design import Design, make_contrasts +from cytepool import Design +from cytepool.design import make_contrasts from cytepool.stats.negative_binomial_models import fit_negative_binomial_model from cytepool.stats.quasi_likelihood import ( QuasiLikelihoodFit, @@ -29,7 +30,7 @@ UnitDevianceMoments, _chi_squared_match, _leverages, - average_log_cpm, + average_log2_cpm, common_dispersion, fit_quasi_likelihood, negative_binomial_unit_deviance_moments, @@ -182,10 +183,10 @@ def test_the_quasi_dispersions_are_edgers(fitted, name): rtol=MOMENTS, ) # One value where edgeR reports one, and one per gene where it reports more. - assert np.ndim(fit.prior_df) == (case["edger_df.prior"].size > 1) + assert np.ndim(fit.df_prior) == (case["edger_df.prior"].size > 1) assert np.ndim(fit.prior_quasi_dispersion) == (case["edger_s2.prior"].size > 1) assert_allclose( - np.broadcast_to(fit.prior_df, n_genes), + np.broadcast_to(fit.df_prior, n_genes), np.broadcast_to(case["edger_df.prior"], n_genes), rtol=PRIOR_DF, ) @@ -226,12 +227,12 @@ def test_the_test_columns_are_edgers(fitted, name): # The log CPM is at the NB dispersion, which differs by up to 1e-5 when both # estimate it. assert_allclose( - test.average_log_cpm[genes], + test.average_log2_cpm[genes], e["logCPM"][genes], atol=1e-6 if estimated else 1e-8, ) lfc = e["logFC"].reshape(-1, n_genes).T - assert_allclose(test.log_fold_change, lfc, atol=1e-6 if exact else FITS) + assert_allclose(test.log2_fold_change, lfc, atol=1e-6 if exact else FITS) assert scaled(test.f_statistic[genes], e["F"][genes]) < MOMENTS assert log10_ratio(test.p_value[genes], e["PValue"][genes]) < PROBABILITY assert log10_ratio(test.adjusted_p_value[genes], e["FDR"][genes]) < ( @@ -383,22 +384,22 @@ def test_moments_broadcast_and_scalars_give_floats(): # --------------------------------------------------------------------------------------- -def test_average_log_cpm_without_dispersion_or_prior_pools_the_counts(): +def test_average_log2_cpm_without_dispersion_or_prior_pools_the_counts(): # A Poisson fit of one group: the mean per unit of library size is the ratio of # the totals. counts = np.array([[3.0, 0.0, 50.0], [7.0, 1.0, 20.0], [1.0, 0.0, 90.0]]) sizes = np.array([1e6, 2e6, 3e6]) expected = np.log2(counts.sum(axis=0) / sizes.sum() * 1e6) - result = average_log_cpm( + result = average_log2_cpm( counts, library_sizes=sizes, prior_count=0.0, dispersion=0.0 ) assert_allclose(result[[0, 2]], expected[[0, 2]], rtol=1e-12) assert_allclose(result[1], expected[1], rtol=1e-12) -def test_average_log_cpm_is_the_cpm_of_a_single_sample(): +def test_average_log2_cpm_is_the_cpm_of_a_single_sample(): counts = np.array([[12.0, 0.0, 5.0]]) - result = average_log_cpm(counts, library_sizes=[2e6], prior_count=0.5) + result = average_log2_cpm(counts, library_sizes=[2e6], prior_count=0.5) prior = 0.5 # one sample: its library size is the mean expected = np.log2((counts[0] + prior) / (2e6 + 2 * prior) * 1e6) assert_allclose(result, expected, rtol=1e-12) @@ -460,7 +461,7 @@ def test_sample_order_does_not_matter(two_groups): assert_allclose(result.p_value, test.p_value, rtol=1e-4) -def test_library_sizes_scale_only_the_log_cpm(two_groups): +def test_library_sizes_scale_only_the_log2_cpm(two_groups): case, fit, test = two_groups other = fit_quasi_likelihood( case["counts"], case["design"], library_sizes=case["library_sizes"] * 10.0 @@ -471,10 +472,10 @@ def test_library_sizes_scale_only_the_log_cpm(two_groups): other.posterior_quasi_dispersion, fit.posterior_quasi_dispersion, rtol=1e-6 ) assert_allclose(result.f_statistic, test.f_statistic, rtol=1e-5) - assert_allclose(result.log_fold_change, test.log_fold_change, atol=1e-7) + assert_allclose(result.log2_fold_change, test.log2_fold_change, atol=1e-7) # Up to the prior counts, which do not grow with the libraries: about c / N. - shifted = test.average_log_cpm - math.log2(10.0) - assert_allclose(result.average_log_cpm, shifted, atol=1e-5) + shifted = test.average_log2_cpm - math.log2(10.0) + assert_allclose(result.average_log2_cpm, shifted, atol=1e-5) def test_a_gene_counted_in_one_condition_keeps_the_invariances(fitted): @@ -519,7 +520,7 @@ def test_a_reparametrised_design_gives_the_same_tests(two_groups): other.posterior_quasi_dispersion, fit.posterior_quasi_dispersion, rtol=1e-8 ) assert_allclose(result.f_statistic, test.f_statistic, rtol=1e-8) - assert_allclose(result.log_fold_change, test.log_fold_change, atol=1e-9) + assert_allclose(result.log2_fold_change, test.log2_fold_change, atol=1e-9) def test_equivalent_contrasts_give_the_same_f_test(fitted): @@ -534,7 +535,7 @@ def test_equivalent_contrasts_give_the_same_f_test(fitted): # A coefficient test is the test of its unit contrast. unit = quasi_likelihood_f_test(fit, contrasts=[0.0, 0.0, 0.0, 1.0]) assert_allclose(unit.f_statistic, tests[0].f_statistic, rtol=1e-6) - assert_allclose(unit.log_fold_change, tests[0].log_fold_change) + assert_allclose(unit.log2_fold_change, tests[0].log2_fold_change) # A contrast that repeats one is no more test. repeated = quasi_likelihood_f_test(fit, contrasts=np.column_stack([joint, joint])) assert repeated.df_test == 2 @@ -570,7 +571,7 @@ def test_a_gene_without_counts_is_not_tested(): np.broadcast_to(fit.prior_quasi_dispersion, 30)[3] ) assert test.f_statistic[3] == 0.0 and test.p_value[3] == 1.0 - assert np.isfinite(test.log_fold_change[3, 0]) + assert np.isfinite(test.log2_fold_change[3, 0]) def test_weights_per_sample_are_the_same_weights_per_observation(two_groups): @@ -600,7 +601,7 @@ def test_offsets_are_log_library_sizes(two_groups): result = quasi_likelihood_f_test(other, coefficients=1) assert other.dispersion == pytest.approx(fit.dispersion, rel=1e-10) assert_allclose(result.f_statistic, test.f_statistic, rtol=1e-9) - assert_allclose(result.average_log_cpm, test.average_log_cpm, atol=1e-10) + assert_allclose(result.average_log2_cpm, test.average_log2_cpm, atol=1e-10) def test_a_condition_without_counts_gets_the_leverages_of_the_limit(): diff --git a/tests/test_result_fits.py b/tests/test_result_fits.py index 6b277f4..71885ef 100644 --- a/tests/test_result_fits.py +++ b/tests/test_result_fits.py @@ -21,17 +21,18 @@ from numpy.testing import assert_array_equal from test_differential_expression import cells_cube, paired_design, voom_result -from cytepool.cube import from_bulk -from cytepool.design import Contrasts, Design -from cytepool.design.checks import _aliasing -from cytepool.differential_expression import ( +from cytepool import ( + Design, DifferentialExpressionResult, GroupFit, dream_voom, + from_bulk, limma_trend, limma_voom, ) -from cytepool.normalization import filter_by_expression, log_cpm, tmm_factors +from cytepool.design import Contrasts +from cytepool.design.checks import _aliasing +from cytepool.normalization import filter_by_expression, log2_cpm, tmm_factors from cytepool.stats.empirical_bayes import ModeratedFit, moderated_statistics from cytepool.stats.linear_models import contrasts_fit, fit_linear_model from cytepool.stats.voom import voom @@ -164,7 +165,7 @@ def test_a_group_fitted_again_gives_the_tables_of_the_analysis(name: str) -> Non assert_array_equal(table["log2_fold_change"], estimate[:, j]) assert_array_equal(table["t"], moderated.t[:, j]) assert_array_equal(table["p_value"], moderated.p_value[:, j]) - assert_array_equal(table["b_statistic"], moderated.lods[:, j]) + assert_array_equal(table["log_odds"], moderated.log_odds[:, j]) # The samples fitted are those the result says it used, with their numbers. used = result.samples.filter(group_id=group, used=True) assert fit.samples["sample_id"].tolist() == used["sample_id"].tolist() @@ -193,7 +194,7 @@ def test_a_fit_is_limma_voom_by_hand_in_a_rank_deficient_group() -> None: weighted = voom(y, x, norm_factors=tmm_factors(y)) columns = tuple(design.columns[j] for j in kept) model = fit_linear_model( - weighted.log_cpm, x, weights=weighted.weights, coefficient_names=columns + weighted.log2_cpm, x, weights=weighted.weights, coefficient_names=columns ) estimable = [ p for p, name in enumerate(design.contrasts.names) @@ -210,7 +211,7 @@ def test_a_fit_is_limma_voom_by_hand_in_a_rank_deficient_group() -> None: fit = result.fit(group) assert isinstance(fit, GroupFit) and fit.group == group - assert_array_equal(fit.log2_cpm, weighted.log_cpm) + assert_array_equal(fit.log2_cpm, weighted.log2_cpm) assert_array_equal(fit.weights, weighted.weights) assert fit.voom.span == weighted.span assert_array_equal(fit.voom.sqrt_sd_trend, weighted.sqrt_sd_trend) @@ -230,7 +231,7 @@ def test_a_fit_is_limma_voom_by_hand_in_a_rank_deficient_group() -> None: ) -def test_limma_trend_fits_log_cpm_with_the_sample_weights() -> None: +def test_limma_trend_fits_log2_cpm_with_the_sample_weights() -> None: result = trend_result() fit = result.fit("T") rows = used_rows(result, "T") @@ -239,7 +240,7 @@ def test_limma_trend_fits_log_cpm_with_the_sample_weights() -> None: x = result.design.matrix[rows] y = y[:, filter_by_expression(y, design=x)] assert_array_equal( - fit.log2_cpm, log_cpm(y, norm_factors=tmm_factors(y), prior_count=3.0) + fit.log2_cpm, log2_cpm(y, norm_factors=tmm_factors(y), prior_count=3.0) ) assert fit.voom is None assert_array_equal(fit.weights, np.asarray(cube.n_cells)[rows, 1].astype(float)) @@ -292,7 +293,7 @@ def test_the_table_of_the_only_group_and_contrast_needs_no_keys() -> None: several.table(contrast="stim_vs_ctrl") with pytest.raises( ValueError, - match="the result has 3 contrasts and F-tests; name one with contrast", + match="the result has 3 contrasts; name one with contrast", ): several.table("T") # A dream F-test counts among the keys. @@ -321,8 +322,7 @@ def test_a_moderated_fit_as_a_table_is_the_table_of_limma_voom() -> None: "average_log2_cpm", "t", "p_value", "adjusted_p_value", "log_odds", ) # fmt: skip for column in theirs.columns: - found = "log_odds" if column == "b_statistic" else column - assert_array_equal(mine[found], theirs[column]) + assert_array_equal(mine[column], theirs[column]) moderated = result.fit("T").moderated by_position = moderated.table(1) assert by_position.equals( diff --git a/tests/test_rotation_gene_set_tests.py b/tests/test_rotation_gene_set_tests.py index 714837b..8244333 100644 --- a/tests/test_rotation_gene_set_tests.py +++ b/tests/test_rotation_gene_set_tests.py @@ -5,11 +5,14 @@ the tests replay to CytePool so that the p-values must agree exactly; ``validation/rotation_gene_set_tests.py`` writes them. The other tests check properties that a wrong implementation would break: invariances, symmetries, the -limit that fry computes, closed forms and the documented edge cases. +limit that fry computes, closed forms and the documented edge cases. A result of +limma-voom or limma-trend is tested, bit for bit, as the values of its group's fit, +with the same rotations. """ import json import math +import re from functools import cache from pathlib import Path from typing import Any @@ -18,19 +21,12 @@ import numpy as np import pytest from numpy.testing import assert_allclose, assert_array_equal +from test_gene_set_tests import SYMBOL_SETS, fitted, results -from cytepool.design import Design -from cytepool.stats.gene_set_tests import fry +from cytepool import Design, Table, fry, mroast, roast, romer +from cytepool.rotation_gene_set_tests import _bailey_z_scores, _proportion_true_null from cytepool.stats.multiple_testing import adjust_p_values -from cytepool.stats.rotation_gene_set_tests import ( - _bailey_z_scores, - _proportion_true_null, - mroast, - roast, - romer, -) from cytepool.stats.voom import voom -from cytepool.table import Table DATA = Path(__file__).parent / "data" / "rotation_gene_set_tests" EXACT = 1e-12 @@ -95,7 +91,7 @@ def arguments(case: str) -> dict[str, Any]: else: contrast = data["columns"].tolist().index(str(data["contrast"])) return { - "y": data["y"], + "data": data["y"], "design": data["design"], "contrast": contrast, "weights": data.get("weights"), @@ -106,6 +102,20 @@ def limma(case: str, run: str) -> Any: return json.loads(str(fixture(case)[f"limma {run}"])) +def roast_p_values(table: Table) -> np.ndarray: + """The p-values of roast's row in the order of limma's alternatives: down, up, + up or down (the two-sided p-value) and mixed.""" + columns = ("down_p_value", "up_p_value", "p_value", "mixed_p_value") + return np.array([table[column][0] for column in columns]) + + +def active_proportions(table: Table) -> np.ndarray: + """The proportions of active genes of roast's row in the order of limma's + alternatives: down, up, up or down (the larger of the two) and mixed.""" + down, up = table["proportion_down"][0], table["proportion_up"][0] + return np.array([down, up, max(down, up), table["proportion_mixed"][0]]) + + def replay(case: str, run: str) -> Replay: return Replay(fixture(case)[f"draws {run}"]) @@ -173,9 +183,9 @@ def test_roast_matches_limma_given_its_rotations( ) expected, compared = expected_p_values(run["stat"], z, genes, reference) assert_allclose( - table["p_value"][compared], expected[compared], rtol=0, atol=EXACT + roast_p_values(table)[compared], expected[compared], rtol=0, atol=EXACT ) - assert_allclose(table["active_proportion"], reference["active"], atol=EXACT) + assert_allclose(active_proportions(table), reference["active"], atol=EXACT) zero = isinstance(genes, dict) and 0.0 in genes.values() n_expected = sum(w != 0 for w in genes.values()) if zero else reference["n"][0] assert table["n_genes"][0] == n_expected, name @@ -245,10 +255,12 @@ def assert_tables_equal(first: Table, second: Table) -> None: def test_a_seed_or_a_generator_gives_the_same_tables_every_time() -> None: y, x = three_groups(1) for test, options in ((mroast, {}), (romer, {"n_rotations": 199})): - first = test(y, x, 1, SETS, rng=7, **options) - again = test(y, x, 1, SETS, rng=np.random.default_rng(7), **options) + first = test(y, SETS, design=x, contrast=1, rng=7, **options) + again = test( + y, SETS, design=x, contrast=1, rng=np.random.default_rng(7), **options + ) assert_tables_equal(first, again) - other = test(y, x, 1, SETS, rng=8, **options) + other = test(y, SETS, design=x, contrast=1, rng=8, **options) assert not np.array_equal(first["mixed_p_value"], other["mixed_p_value"]) @@ -259,8 +271,8 @@ def test_the_number_of_threads_does_not_change_the_result() -> None: y, x = three_groups(2) for test, statistic in ((mroast, "mean50"), (romer, "floormean")): options = {"set_statistic": statistic, "n_rotations": 101, "rng": 3} - one = test(y, x, 1, SETS, n_threads=1, **options) - two = test(y, x, 1, SETS, n_threads=2, **options) + one = test(y, SETS, design=x, contrast=1, n_threads=1, **options) + two = test(y, SETS, design=x, contrast=1, n_threads=2, **options) assert_tables_equal(one, two) @@ -273,18 +285,20 @@ def test_rotations_drawn_in_batches_are_those_of_one_draw() -> None: (roast, {"gene_set": [0, 1, 2, 3, 20]}), (romer, {"gene_sets": SETS}), ): - batched = test(y, x, 1, n_rotations=5000, rng=8, **options) - at_once = test(y, x, 1, n_rotations=5000, rng=Replay(one_draw), **options) + batched = test(y, design=x, contrast=1, n_rotations=5000, rng=8, **options) + at_once = test( + y, design=x, contrast=1, n_rotations=5000, rng=Replay(one_draw), **options + ) assert_tables_equal(batched, at_once) def test_mroast_tests_every_set_with_the_rotations_of_roast() -> None: y, x = three_groups(3) - table = mroast(y, x, 1, SETS, set_statistic="floormean", rng=5) + table = mroast(y, SETS, design=x, contrast=1, set_statistic="floormean", rng=5) for i, genes in enumerate(SETS.values()): - alone = roast(y, x, 1, genes, set_statistic="floormean", rng=5) - assert table["p_value"][i] == alone["p_value"][2] - assert table["mixed_p_value"][i] == alone["p_value"][3] + alone = roast(y, genes, design=x, contrast=1, set_statistic="floormean", rng=5) + assert table["p_value"][i] == roast_p_values(alone)[2] + assert table["mixed_p_value"][i] == roast_p_values(alone)[3] # Invariances and symmetries. @@ -305,13 +319,29 @@ def test_negating_the_contrast_and_the_rotations_swaps_up_and_down( flipped[:, 0] *= -1.0 genes = {g: (1.0 if g < 12 else -2.0) for g in range(0, 20)} for gene_set in (list(range(10)), list(range(5, 25)), genes): - plus = roast(y, x, contrast, gene_set, set_statistic=statistic, - n_rotations=499, rng=Replay(draws)) # fmt: skip - minus = roast(y, x, -contrast, gene_set, set_statistic=statistic, - n_rotations=499, rng=Replay(flipped)) # fmt: skip - assert_allclose(minus["p_value"], plus["p_value"][[1, 0, 2, 3]], atol=EXACT) + plus = roast( + y, + gene_set, + design=x, + contrast=contrast, + set_statistic=statistic, + n_rotations=499, + rng=Replay(draws), + ) + minus = roast( + y, + gene_set, + design=x, + contrast=-contrast, + set_statistic=statistic, + n_rotations=499, + rng=Replay(flipped), + ) + assert_allclose( + roast_p_values(minus), roast_p_values(plus)[[1, 0, 2, 3]], atol=EXACT + ) assert_allclose( - minus["active_proportion"], plus["active_proportion"][[1, 0, 2, 3]] + active_proportions(minus), active_proportions(plus)[[1, 0, 2, 3]] ) @@ -327,12 +357,35 @@ def test_the_order_of_the_genes_and_their_scale_change_nothing() -> None: (romer, {}, y + np.arange(60.0)), ): original = test( - y, x, 2, sets, gene_ids=genes, rng=4, n_rotations=301, **options + y, + sets, + design=x, + contrast=2, + feature_ids=genes, + rng=4, + n_rotations=301, + **options, + ) + shuffled = test( + y[:, order], + {k: v[::-1] for k, v in sets.items()}, + design=x, + contrast=2, + feature_ids=genes[order], + rng=4, + n_rotations=301, + **options, + ) + scaled = test( + rescaled, + sets, + design=x, + contrast=2, + feature_ids=genes, + rng=4, + n_rotations=301, + **options, ) - shuffled = test(y[:, order], x, 2, {k: v[::-1] for k, v in sets.items()}, - gene_ids=genes[order], rng=4, n_rotations=301, **options) # fmt: skip - scaled = test(rescaled, x, 2, sets, gene_ids=genes, rng=4, n_rotations=301, - **options) # fmt: skip for other in (shuffled, scaled): for column in original.columns: if original[column].dtype.kind == "f": @@ -353,26 +406,28 @@ def test_only_the_relative_size_and_sign_of_contribution_weights_matter( weights = {g: w for g, w in zip(range(15), [1.0] * 10 + [-1.0] * 5, strict=True)} sizes = {g: w for g, w in zip(range(8), [0.5, 2, 1, 3, 1, 0.2, 1, 4], strict=True)} for gene_set in (weights, sizes): - table = roast(y, x, 1, gene_set, set_statistic=statistic, rng=9) + table = roast(y, gene_set, design=x, contrast=1, set_statistic=statistic, rng=9) scaled = {g: 10.0 * w for g, w in gene_set.items()} - again = roast(y, x, 1, scaled, set_statistic=statistic, rng=9) - assert_allclose(again["p_value"], table["p_value"], atol=EXACT) - signed = roast(y, x, 1, weights, set_statistic=statistic, rng=9) + again = roast(y, scaled, design=x, contrast=1, set_statistic=statistic, rng=9) + assert_allclose(roast_p_values(again), roast_p_values(table), atol=EXACT) + signed = roast(y, weights, design=x, contrast=1, set_statistic=statistic, rng=9) negated = y.copy() negated[:, 10:15] = -negated[:, 10:15] - plain = roast(negated, x, 1, list(range(15)), set_statistic=statistic, rng=9) - assert_allclose(signed["p_value"], plain["p_value"], atol=EXACT) - assert_array_equal(signed["active_proportion"], plain["active_proportion"]) - assert signed["active_proportion"][0] == 0.0 # the down genes now count as up + plain = roast( + negated, list(range(15)), design=x, contrast=1, set_statistic=statistic, rng=9 + ) + assert_allclose(roast_p_values(signed), roast_p_values(plain), atol=EXACT) + assert_array_equal(active_proportions(signed), active_proportions(plain)) + assert active_proportions(signed)[0] == 0.0 # the down genes now count as up def test_genome_wide_weights_equal_the_same_weights_given_by_set() -> None: y, x = three_groups(7) weights = np.random.default_rng(2).choice([-1.0, 0.5, 2.0], 60) sets = {"a": list(range(12)), "b": list(range(20, 40))} - by_gene = mroast(y, x, 1, sets, gene_weights=weights, rng=3) + by_gene = mroast(y, sets, design=x, contrast=1, gene_weights=weights, rng=3) by_set = {name: {g: weights[g] for g in rows} for name, rows in sets.items()} - assert_tables_equal(mroast(y, x, 1, by_set, rng=3), by_gene) + assert_tables_equal(mroast(y, by_set, design=x, contrast=1, rng=3), by_gene) # Limits and closed forms. @@ -391,8 +446,8 @@ def test_mroast_with_many_rotations_approaches_fry_with_equal_variances() -> Non y = q[:, :2] @ (q[:, :2].T @ noise) + q[:, 2:] @ residual y[4:, :6] += 0.6 sets = {"changed": list(range(8)), "null": list(range(10, 30)), "pair": [3, 20]} - limit = fry(y, x, 1, sets, standardize="none") - rotated = mroast(y, x, 1, sets, n_rotations=20_000, rng=11) + limit = fry(y, sets, design=x, contrast=1, standardize="none") + rotated = mroast(y, sets, design=x, contrast=1, n_rotations=20_000, rng=11) p = limit["p_value"] bound = 4 * np.sqrt(p * (1 - p) / 20_000) + 2 / 20_001 # the resolution of p assert np.all(np.abs(rotated["p_value"] - p) <= bound) @@ -404,19 +459,27 @@ def test_the_smallest_p_values_and_one_rotation() -> None: # 1 / (2B + 1) for the up alternative. y, x = three_groups(8) y[3:6, :10] += 20.0 - table = roast(y, x, 1, list(range(10)), n_rotations=99, rng=1) - assert_allclose(table["p_value"][[1, 2, 3]], [1 / 199, 1 / 100, 1 / 100]) - assert table["p_value"][0] == 1.0 # no rotated mean is lower than its negative - one = roast(y, x, 1, list(range(10)), n_rotations=1, rng=1) - assert set(one["p_value"][2:].tolist()) <= {0.5, 1.0} + table = roast(y, list(range(10)), design=x, contrast=1, n_rotations=99, rng=1) + p_values = roast_p_values(table) + assert_allclose(p_values[[1, 2, 3]], [1 / 199, 1 / 100, 1 / 100]) + assert p_values[0] == 1.0 # no rotated mean is lower than its negative + one = roast(y, list(range(10)), design=x, contrast=1, n_rotations=1, rng=1) + assert set(roast_p_values(one)[2:].tolist()) <= {0.5, 1.0} def test_a_set_of_every_gene_has_romer_p_values_of_one() -> None: # The ranks of all genes always sum to G (G + 1) / 2. y, x = three_groups(9) for statistic in ("mean", "floormean"): - table = romer(y, x, 1, {"all": list(range(60))}, set_statistic=statistic, - n_rotations=99, rng=1) # fmt: skip + table = romer( + y, + {"all": list(range(60))}, + design=x, + contrast=1, + set_statistic=statistic, + n_rotations=99, + rng=1, + ) assert_array_equal( [table["up_p_value"], table["down_p_value"], table["mixed_p_value"]], 1.0 ) @@ -426,7 +489,7 @@ def test_romer_up_and_down_p_values_of_mean_ranks_add_to_just_over_one() -> None # The down rank of a gene is G + 1 minus its up rank, so a rotation at least as # extreme up is at most as extreme down: p_up + p_down = 1 + (1 + ties) / (B + 1). y, x = three_groups(10) - table = romer(y, x, 1, SETS, n_rotations=199, rng=2) + table = romer(y, SETS, design=x, contrast=1, n_rotations=199, rng=2) total = table["up_p_value"] + table["down_p_value"] assert np.all(total >= 1.0 + 1.0 / 200 - 1e-12) assert np.all(total <= 1.0 + 10.0 / 200) @@ -471,9 +534,9 @@ def test_the_proportion_of_true_null_hypotheses() -> None: def test_adjusted_p_values_use_mid_p_values_and_never_fall_below_the_p_values() -> None: y, x = three_groups(11) sets = {f"s{i}": list(range(i, i + 6)) for i in range(0, 48, 4)} - plain = mroast(y, x, 1, sets, n_rotations=99, rng=4, mid_p=False) + plain = mroast(y, sets, design=x, contrast=1, n_rotations=99, rng=4, mid_p=False) assert_allclose(plain["adjusted_p_value"], adjust_p_values(plain["p_value"], "bh")) - mid = mroast(y, x, 1, sets, n_rotations=99, rng=4) + mid = mroast(y, sets, design=x, contrast=1, n_rotations=99, rng=4) assert np.all(mid["adjusted_p_value"] >= mid["p_value"]) assert np.all(mid["adjusted_p_value"] <= plain["adjusted_p_value"]) @@ -490,16 +553,25 @@ def test_empty_sets_and_sets_of_weight_zero_have_no_p_value() -> None: "zero": {"g20": 0.0}, "b": genes[30:40], } - table = mroast(y, x, 1, sets, gene_ids=genes, rng=1) + table = mroast(y, sets, design=x, contrast=1, feature_ids=genes, rng=1) assert_array_equal(table["n_genes"], [0, 10, 0, 10]) assert np.isnan(table["p_value"][[0, 2]]).all() assert np.isnan(table["proportion_up"][[0, 2]]).all() assert table["direction"][0] is None and table["direction"][2] is None - alone = mroast(y, x, 1, {"a": genes[:10], "b": genes[30:40]}, gene_ids=genes, rng=1) + alone = mroast( + y, + {"a": genes[:10], "b": genes[30:40]}, + design=x, + contrast=1, + feature_ids=genes, + rng=1, + ) assert_allclose(table["adjusted_p_value"][[1, 3]], alone["adjusted_p_value"]) - assert np.isnan(romer(y, x, 1, {"e": []}, rng=1, n_rotations=9)["up_p_value"][0]) - empty = roast(y, x, 1, [], rng=1) - assert np.isnan(empty["p_value"]).all() and empty["n_genes"][0] == 0 + assert np.isnan( + romer(y, {"e": []}, design=x, contrast=1, rng=1, n_rotations=9)["up_p_value"][0] + ) + empty = roast(y, [], design=x, contrast=1, rng=1) + assert np.isnan(roast_p_values(empty)).all() and empty["n_genes"][0] == 0 @pytest.mark.parametrize("statistic", ["mean", "floormean", "mean50", "msq"]) @@ -509,11 +581,13 @@ def test_genes_listed_twice_count_once_and_weight_zero_leaves_the_set( # A gene of weight zero does not count among mean50's largest values either # (limma keeps it there and in its number of genes). y, x = three_groups(13) - once = roast(y, x, 1, [1, 2, 3, 8], set_statistic=statistic, rng=2) - twice = roast(y, x, 1, [3, 1, 2, 3, 8, 1], set_statistic=statistic, rng=2) + once = roast(y, [1, 2, 3, 8], design=x, contrast=1, set_statistic=statistic, rng=2) + twice = roast( + y, [3, 1, 2, 3, 8, 1], design=x, contrast=1, set_statistic=statistic, rng=2 + ) assert_tables_equal(once, twice) weights = {1: 1.0, 2: 1.0, 3: 1.0, 8: 1.0, 9: 0.0, 40: 0.0} - dropped = roast(y, x, 1, weights, set_statistic=statistic, rng=2) + dropped = roast(y, weights, design=x, contrast=1, set_statistic=statistic, rng=2) assert_tables_equal(once, dropped) @@ -523,10 +597,13 @@ def test_a_gene_without_a_change_has_equal_one_sided_p_values_and_goes_down() -> x = np.column_stack([np.ones(6), np.repeat([0.0, 1.0], 3)]) y = np.random.default_rng(14).normal(size=(6, 30)) y[:, 0] = [1.0, 2.0, 3.0, 2.0, 1.0, 3.0] - table = roast(y, x, 1, [0], rng=3, n_rotations=99) - assert table["p_value"][0] == table["p_value"][1] == 100 / 199 - assert table["p_value"][2] == 1.0 - assert mroast(y, x, 1, {"zero": [0]}, rng=3)["direction"][0] == "down" + table = roast(y, [0], design=x, contrast=1, rng=3, n_rotations=99) + p_values = roast_p_values(table) + assert p_values[0] == p_values[1] == 100 / 199 + assert p_values[2] == 1.0 + assert ( + mroast(y, {"zero": [0]}, design=x, contrast=1, rng=3)["direction"][0] == "down" + ) def test_a_voom_result_and_a_design_with_named_contrasts_work_together() -> None: @@ -538,13 +615,19 @@ def test_a_voom_result_and_a_design_with_named_contrasts_work_together() -> None ) result = voom(counts, design) sets = {"up": list(range(10)), "null": list(range(40, 60))} - direct = mroast(result, design, "b_vs_a", sets, rng=1) - explicit = mroast(result.log_cpm, design, "groupb", sets, weights=result.weights, - rng=1) # fmt: skip + direct = mroast(result, sets, design=design, contrast="b_vs_a", rng=1) + explicit = mroast( + result.log2_cpm, + sets, + design=design, + contrast="groupb", + weights=result.weights, + rng=1, + ) assert_tables_equal(direct, explicit) assert direct["direction"][0] == "up" and direct["p_value"][0] < 0.01 with pytest.raises(ValueError, match="sample weights only"): - romer(result, design, "b_vs_a", sets) + romer(result, sets, design=design, contrast="b_vs_a") # Arguments. @@ -583,8 +666,113 @@ def test_invalid_arguments_are_refused_with_a_reason() -> None: (mroast, {"design": np.eye(9)}, ValueError, "no residual degrees of freedom"), ] for test, changes, error, message in cases: - options = {"y": y, "design": x, "contrast": 1, "gene_sets": sets} | changes + options = {"data": y, "design": x, "contrast": 1, "gene_sets": sets} | changes with pytest.raises(error, match=message): test(**options) - with pytest.raises(ValueError, match="no gene of gene_set is among gene_ids"): - roast(y, x, 1, ["absent"], gene_ids=[f"g{i}" for i in range(60)]) + with pytest.raises(ValueError, match="no gene of gene_set is among feature_ids"): + roast( + y, + ["absent"], + design=x, + contrast=1, + feature_ids=[f"g{i}" for i in range(60)], + ) + + +# Results of differential expression. + +RESULT_TESTS = ((roast, SYMBOL_SETS["up"]), (mroast, SYMBOL_SETS), (romer, SYMBOL_SETS)) + + +@pytest.mark.parametrize("contrast", ["stim_vs_ctrl", "D2_vs_D1"]) +def test_roast_and_mroast_of_a_limma_voom_result_test_the_voom_values_of_its_fit( + contrast: str, +) -> None: + result = results()["limma_voom"] + fit, read = fitted(result, "B", contrast) + moderation = {"trend": False, "robust": result.settings["robust"]} + tested = {"group": "B", "contrast": contrast, "id_column": "symbol"} + rotations = {"n_rotations": 99, "rng": 5} + for name, genes in SYMBOL_SETS.items(): + table = roast(result, genes, **tested, **rotations) + expected = roast(fit.voom, genes, **read, **moderation, **rotations) + assert table.equals(expected), name + table = mroast(result, SYMBOL_SETS, **tested, **rotations) + expected = mroast(fit.voom, SYMBOL_SETS, **read, **moderation, **rotations) + assert table.equals(expected) + + +@pytest.mark.parametrize("contrast", ["stim_vs_ctrl", "D2_vs_D1"]) +def test_romer_of_a_limma_trend_result_tests_its_fit_with_its_sample_weights( + contrast: str, +) -> None: + result = results()["limma_trend"] + fit, read = fitted(result, "B", contrast) + assert fit.weights is not None and fit.weights.shape == (len(fit.samples),) + rotations = {"n_rotations": 99, "rng": 5} + table = romer( + result, + SYMBOL_SETS, + group="B", + contrast=contrast, + id_column="symbol", + **rotations, + ) + expected = romer( + fit.log2_cpm, SYMBOL_SETS, **read, weights=fit.weights, **rotations + ) + assert table.equals(expected) + + +def test_romer_refuses_the_observation_weights_of_voom() -> None: + result = results()["limma_voom"] + fit, read = fitted(result, "B", "stim_vs_ctrl") + message = re.escape( + "romer takes sample weights only, of shape (n_samples,); limma's romer has no " + "observation weights, so a VoomResult, a result of limma_voom or a matrix of " + "weights cannot be used" + ) + with pytest.raises(ValueError, match=message): + romer( + result, SYMBOL_SETS, group="B", contrast="stim_vs_ctrl", id_column="symbol" + ) + with pytest.raises(ValueError, match=message): + romer(fit.voom, SYMBOL_SETS, **read) + + +def test_a_result_gives_the_design_weights_and_ids_of_its_fit() -> None: + result = results()["limma_voom"] + fit, read = fitted(result, "B", "stim_vs_ctrl") + tested = {"group": "B", "contrast": "stim_vs_ctrl", "id_column": "symbol"} + for test, sets in RESULT_TESTS: + for name, value, owner in ( + ("design", read["design"], "the design of its group's fit"), + ("feature_ids", read["feature_ids"], "the ids of the column id_column"), + ("weights", fit.weights, "the weights of its group's fit"), + ): + # romer takes sample weights only, so no VoomResult. + inputs = "an array" if test is romer else "an array or a VoomResult" + message = ( + f"{name} applies to {inputs}; a differential-expression result " + f"gives {owner}" + ) + with pytest.raises(ValueError, match=message): + test(result, sets, **tested, **{name: value}) + # A group names a group of a result, not of an array. + with pytest.raises( + ValueError, + match="group names a group of a differential-expression result, and data " + "is a ndarray", + ): + test(fit.log2_cpm, sets, group="B", **read) + + +def test_a_result_of_dream_voom_is_refused() -> None: + result = results()["dream_voom"] + for test, sets in RESULT_TESTS: + with pytest.raises( + ValueError, + match=f"{test.__name__} tests the contrast and residual effects of a " + "linear model, and this result is from dream_voom", + ): + test(result, sets, contrast="stim_vs_ctrl", id_column="symbol") diff --git a/tests/test_sample_quality_weights.py b/tests/test_sample_quality_weights.py index 9538603..7e0dec8 100644 --- a/tests/test_sample_quality_weights.py +++ b/tests/test_sample_quality_weights.py @@ -20,7 +20,7 @@ import numpy as np import pytest -from cytepool.design import Design +from cytepool import Design from cytepool.normalization import tmm_factors from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import contrasts_fit, fit_linear_model @@ -119,7 +119,7 @@ def assert_moderated_statistics_agree( """result -> fit_linear_model -> contrasts_fit -> moderated_statistics, as the fixture's lmFit -> contrasts.fit -> eBayes.""" design = design_of(case) - fit = fit_linear_model(result.log_cpm, design, weights=result.weights) + fit = fit_linear_model(result.log2_cpm, design, weights=result.weights) if len(design.contrasts): # limma's contrasts.fit approximates the standard errors of contrasts of # correlated coefficients in a weighted fit; method="limma" reproduces it. @@ -152,7 +152,7 @@ def test_voom_with_prior_weights_matches_limma(case: str) -> None: result = run_case(case) n_samples = data["data"].shape[0] np.testing.assert_allclose( - result.log_cpm, limma(case, "E", n_samples), rtol=EXACT, atol=EXACT + result.log2_cpm, limma(case, "E", n_samples), rtol=EXACT, atol=EXACT ) np.testing.assert_allclose( result.weights, limma(case, "weights", n_samples), rtol=EXACT @@ -181,7 +181,7 @@ def test_voom_with_quality_weights_matches_limma(case: str) -> None: result = run_case(case) n_samples = data["data"].shape[0] np.testing.assert_allclose( - result.log_cpm, limma(case, "E", n_samples), rtol=EXACT, atol=EXACT + result.log2_cpm, limma(case, "E", n_samples), rtol=EXACT, atol=EXACT ) np.testing.assert_allclose( result.sample_weights, data["limma sample.weights"], rtol=CLOSE @@ -538,7 +538,7 @@ def test_zero_weights_that_leave_coefficients_inestimable() -> None: result = voom(COUNTS, TWO_GROUPS, span=0.5, weights=weights) np.testing.assert_array_equal(result.weights[:4, 0], 0.0) prior = weights[4:, 0] - fitted = np.sum(prior * result.log_cpm[4:, 0]) / prior.sum() + fitted = np.sum(prior * result.log2_cpm[4:, 0]) / prior.sum() sizes = result.effective_library_sizes[4:] fitted_log_count = np.log2(2.0**fitted * (sizes + 1.0) * 1e-6) in_trend = ~np.isnan(result.sqrt_sd) @@ -604,12 +604,12 @@ def test_quality_weights_combine_voom_and_sample_weights_in_limmas_steps() -> No counts = fixture("quality_gene_by_gene")["data"] first = voom(counts, TWO_GROUPS, span=0.5) quality = sample_quality_weights( - first.log_cpm, TWO_GROUPS, weights=first.weights, method="gene_by_gene" + first.log2_cpm, TWO_GROUPS, weights=first.weights, method="gene_by_gene" ) second = voom(counts, TWO_GROUPS, span=0.5, weights=quality) trend = second.weights / quality[:, np.newaxis] final = sample_quality_weights( - second.log_cpm, TWO_GROUPS, weights=trend, method="gene_by_gene" + second.log2_cpm, TWO_GROUPS, weights=trend, method="gene_by_gene" ) result = voom_with_quality_weights(counts, TWO_GROUPS, span=0.5) np.testing.assert_allclose(result.sample_weights, final, rtol=1e-12) diff --git a/tests/test_score_tests.py b/tests/test_score_tests.py index 6eadc08..6385c3b 100644 --- a/tests/test_score_tests.py +++ b/tests/test_score_tests.py @@ -14,17 +14,21 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.activity import Activities, ulm -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.networks import Network -from cytepool.pathway_scores import PathwayScores, gsva -from cytepool.score_tests import limma +from cytepool import ( + Activities, + Cube, + Design, + Network, + PathwayScores, + Table, + gsva, + limma, + limma_voom, + ulm, +) from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import contrasts_fit, fit_linear_model from cytepool.stats.multiple_testing import adjust_p_values -from cytepool.table import Table LEVELS = {"condition": ["control", "treated"]} CONTRASTS = {"treated": "conditiontreated"} @@ -73,7 +77,7 @@ def scores() -> PathwayScores: "other": [f"G{j}" for j in range(50, 90)], "third": [f"G{j}" for j in range(100, 160)], } - return gsva(cube(), sets, gene_ids="symbol") + return gsva(cube(), sets, id_column="symbol") @cache @@ -84,7 +88,7 @@ def activities() -> Activities: "B": {f"G{j}": (-1.0) ** j for j in range(40, 80)}, } ) - return ulm(cube(), network, group="B cells", gene_ids="symbol") + return ulm(cube(), network, group="B cells", id_column="symbol") def design(samples: Any = None, formula: str = "~ donor + condition") -> Design: @@ -108,7 +112,7 @@ def by_hand( "t": moderated.t[:, 0], "p_value": p_value, "adjusted_p_value": adjust_p_values(p_value, "bh"), - "log_odds": moderated.lods[:, 0], + "log_odds": moderated.log_odds[:, 0], } @@ -122,8 +126,8 @@ def assert_table(table: Table, id_column: str, ids: list[str], expected: dict) - def test_scores_are_fitted_on_the_cubes_design_rows_of_their_samples() -> None: result = scores() # The scores left S3 out; the guide builds the design from the samples kept. - assert "S3" not in result.samples["sample_id"].tolist() - expected = by_hand(result.scores, design(result.samples)) + assert "S3" not in result.profiles["sample_id"].tolist() + expected = by_hand(result.scores, design(result.profiles)) table = limma(result, design()) assert_table(table, "gene_set", list(result.gene_sets), expected) # The set of the tripled genes is higher in treated samples. @@ -142,13 +146,13 @@ def test_activities_of_samples_are_fitted_alike() -> None: def test_an_array_takes_the_design_rows_or_the_samples_named() -> None: result = scores() values = result.scores - rows = result.samples["sample_id"].tolist() - expected = by_hand(values, design(result.samples)) + rows = result.profiles["sample_id"].tolist() + expected = by_hand(values, design(result.profiles)) ids = [f"set{j}" for j in range(values.shape[1])] named = limma(values, design(), sample_ids=rows, feature_ids=ids) assert_table(named, "feature_id", ids, expected) # Without sample_ids, the rows are the design's samples in order. - in_order = limma(values, design(result.samples)) + in_order = limma(values, design(result.profiles)) assert_table( in_order, "feature_id", ["feature_0", "feature_1", "feature_2"], expected ) @@ -165,9 +169,9 @@ def test_a_donor_without_samples_leaves_the_fit_as_its_columns_do() -> None: # the fit leaves out, as a design built from the other samples has no D1. result = scores() keep = [ - i for i, donor in enumerate(result.samples["donor"].tolist()) if donor != "D1" + i for i, donor in enumerate(result.profiles["donor"].tolist()) if donor != "D1" ] - samples = result.samples.take(np.array(keep)) + samples = result.profiles.take(np.array(keep)) values = result.scores[keep] expected = by_hand(values, design(samples)) table = limma(values, design(), sample_ids=samples["sample_id"].tolist()) @@ -176,20 +180,20 @@ def test_a_donor_without_samples_leaves_the_fit_as_its_columns_do() -> None: def test_weights_trend_and_robust_reach_the_fit_and_the_moderation() -> None: result = scores() - weights = np.linspace(0.5, 2.0, len(result.samples)) + weights = np.linspace(0.5, 2.0, len(result.profiles)) expected = by_hand( - result.scores, design(result.samples), weights=weights, trend=True, robust=True + result.scores, design(result.profiles), weights=weights, trend=True, robust=True ) table = limma(result, design(), weights=weights, trend=True, robust=True) assert_table(table, "gene_set", list(result.gene_sets), expected) sizes = np.asarray(result.n_genes, dtype=np.float64) - expected = by_hand(result.scores, design(result.samples), trend=sizes) + expected = by_hand(result.scores, design(result.profiles), trend=sizes) table = limma(result, design(), trend=sizes) assert_table(table, "gene_set", list(result.gene_sets), expected) # Weights per value fit each set alone, so they agree to rounding. per_value = np.tile(weights[:, None], (1, result.scores.shape[1])) alike = limma(result, design(), weights=per_value) - expected = by_hand(result.scores, design(result.samples), weights=per_value) + expected = by_hand(result.scores, design(result.profiles), weights=per_value) assert_table(alike, "gene_set", list(result.gene_sets), expected) once = limma(result, design(), weights=weights) for name in COLUMNS: @@ -228,13 +232,13 @@ def test_what_limma_cannot_test_is_refused() -> None: with pytest.raises(KeyError, match="the design has no contrast 'stim'"): limma(result, design(), contrast="stim") # Without D1's samples, a contrast of D1 is not estimable. - others = [i for i, d in enumerate(result.samples["donor"].tolist()) if d != "D1"] - rows = result.samples["sample_id"][others].tolist() + others = [i for i, d in enumerate(result.profiles["donor"].tolist()) if d != "D1"] + rows = result.profiles["sample_id"][others].tolist() with pytest.raises(ValueError, match="the contrast 'd1' is not estimable"): limma(values[others], two, contrast="d1", sample_ids=rows) # One sample of each donor: the donors' columns span the samples. single = ["S0", "S2", "S5", "S6", "S9", "S10"] - where = [result.samples["sample_id"].tolist().index(i) for i in single] + where = [result.profiles["sample_id"].tolist().index(i) for i in single] with pytest.raises(ValueError, match="no residual degrees of freedom: the design"): limma(values[where], design(), sample_ids=single) with pytest.raises(ValueError, match="none of the samples of the data is in"): @@ -250,6 +254,6 @@ def test_what_limma_cannot_test_is_refused() -> None: with pytest.raises(ValueError, match="weights must hold one weight per sample"): limma(result, design(), weights=np.ones(3)) with pytest.raises(ValueError, match="data holds infinite values"): - limma(np.where(values > 0.5, np.inf, values), design(result.samples)) + limma(np.where(values > 0.5, np.inf, values), design(result.profiles)) with pytest.raises(ValueError, match="sample_ids must be unique"): limma(values, design(), sample_ids=["S0"] * len(values)) diff --git a/tests/test_study_results.py b/tests/test_study_results.py index 16a0927..c9f4efb 100644 --- a/tests/test_study_results.py +++ b/tests/test_study_results.py @@ -1,8 +1,8 @@ """Every study of a collection analysed alike, and the results lined up gene by gene. ``analyse_each_study`` is checked against the loop it replaces, written out with the -validated functions, and ``StudyResults`` against ``align_study_results`` and each -study's own table. +validated functions, and ``StudyResults`` against the same results given as a mapping +and each study's own table. """ import pickle @@ -15,19 +15,17 @@ from numpy.testing import assert_array_equal from test_cube_collection import LEVELS, fixture_collection -from cytepool.cube import CubeCollection -from cytepool.design import Design -from cytepool.differential_expression import ( +from cytepool import ( + CubeCollection, + Design, DifferentialExpressionResult, + StudyResults, + analyse_each_study, dream_voom, limma_trend, limma_voom, + meta_analysis, ) -from cytepool.meta_analysis import ( - align_study_results, - differential_expression_meta_analysis, -) -from cytepool.study_results import StudyResults, analyse_each_study FORMULA = "~ donor + condition" CONTRASTS = {"stim": "conditionstim"} @@ -119,7 +117,7 @@ def test_the_table_lines_up_each_study_and_the_matrix_takes_one_column() -> None for group in collection.groups["group_id"].tolist(): table = results.table(group, "stim") assert table.equals( - align_study_results(collection, dict(results), group, "stim") + StudyResults(collection, dict(results)).table(group, "stim") ) for column in ("p_value", "t", "log2_fold_change"): matrix = results.matrix(column, group, "stim") @@ -202,8 +200,8 @@ def test_the_results_are_a_read_only_mapping_that_pickles() -> None: group = pair().groups["group_id"][0] assert restored.table(group).equals(results.table(group)) # The combining functions take the results as they take a dict of them. - meta = differential_expression_meta_analysis(results) - expected = differential_expression_meta_analysis(dict(results)) + meta = meta_analysis(results) + expected = meta_analysis(dict(results)) assert meta.table(group, "stim").equals(expected.table(group, "stim")) diff --git a/tests/test_sumrank.py b/tests/test_sumrank.py index d46b461..3f5e3fe 100644 --- a/tests/test_sumrank.py +++ b/tests/test_sumrank.py @@ -17,24 +17,20 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool._jit import thread_limit -from cytepool._study_statistics import studies_summed -from cytepool.cube import Cube, CubeCollection -from cytepool.design import Design -from cytepool.differential_expression import ( - DifferentialExpressionResult, +from cytepool import ( + Cube, + CubeCollection, + Design, + StudyResults, + SumRankResult, + Table, dream_voom, limma_trend, limma_voom, -) -from cytepool.meta_analysis import align_study_results -from cytepool.sumrank import ( - SumRankResult, - _irwin_hall_cdf, - differential_expression_sumrank, sumrank, ) -from cytepool.table import Table +from cytepool._jit import thread_limit +from cytepool.stats.sumrank import _irwin_hall_cdf, _studies_summed, fit_sumrank DATA = Path(__file__).parent / "data" / "sumrank" CONTRAST = "case_vs_ctrl" @@ -56,7 +52,7 @@ def signed(p: np.ndarray, change: np.ndarray) -> np.ndarray: @pytest.mark.parametrize("proportion", [1, 0.5]) def test_statistics_match_the_authors_code(proportion: float) -> None: reference = authors() - fit = sumrank( + fit = fit_sumrank( signed(reference["ties_p"], reference["ties_change"]), proportion_of_studies=proportion, ) @@ -72,7 +68,7 @@ def test_statistics_match_the_authors_code(proportion: float) -> None: def test_permutation_p_values_count_the_authors_null_statistics() -> None: reference = authors() statistics = signed(reference["small_p"], reference["small_sign"]) - fit = sumrank( + fit = fit_sumrank( statistics[0], proportion_of_studies=0.75, null_statistics=statistics[1:] ) assert fit.n_studies_summed == 3 @@ -100,7 +96,7 @@ def test_permutation_p_values_count_the_authors_null_statistics() -> None: def test_two_studies_by_hand() -> None: # Study 1 ties genes b and c; study 2 did not test gene d, which is left out. - fit = sumrank([[3.0, 1.0, 1.0, 2.0, -2.0], [2.0, -1.0, 0.5, np.nan, 1.0]]) + fit = fit_sumrank([[3.0, 1.0, 1.0, 2.0, -2.0], [2.0, -1.0, 0.5, np.nan, 1.0]]) # Over a, b, c and e (G = 4), study 1 ranks a 1, b and c 2.5, e 4, so u = (0, # 1/2, 1/2, 1); study 2 ranks a 1, e 2, c 3, b 4, so u = (0, 1, 2/3, 1/3). assert_array_equal(fit.ranked, [True, True, True, False, True]) @@ -122,7 +118,7 @@ def test_two_studies_by_hand() -> None: def test_the_best_of_two_studies_by_hand() -> None: - fit = sumrank( + fit = fit_sumrank( [[3.0, 1.0, 1.0, 2.0, -2.0], [2.0, -1.0, 0.5, np.nan, 1.0]], proportion_of_studies=0.5, ) @@ -140,7 +136,7 @@ def test_permutation_p_values_pool_every_gene_of_every_permutation() -> None: # (1, 1, 0); so of the 12 null statistics two are 0 and ten are 1. forward, backward = [3.0, 2.0, 1.0], [1.0, 2.0, 3.0] null = [[a, b] for a in (forward, backward) for b in (forward, backward)] - fit = sumrank([forward, forward], null_statistics=null) + fit = fit_sumrank([forward, forward], null_statistics=null) assert_allclose(fit.up_statistic, [0, 1, 1]) assert_allclose(fit.up_permutation_p_value, [3 / 13, 1, 1], rtol=1e-15) # Down, the data's statistics are (1, 1, 0): gene c ties the two null zeros. @@ -154,7 +150,7 @@ def test_each_permutation_ranks_the_genes_its_studies_tested() -> None: statistics = [[3.0, 2.0, 1.0, 0.0], [2.0, 3.0, 1.0, 0.0]] null = np.array([statistics, statistics]) null[1, 0, 2] = np.nan - fit = sumrank(statistics, null_statistics=null) + fit = fit_sumrank(statistics, null_statistics=null) assert fit.n_null_values == 4 + 3 # Genes a and b rank 1 and 2 of 4 in one study each: they sum 0 + 1/3. The first # permutation is the data; in the second, over a, b and d, they sum 0 + 1/2. So @@ -199,7 +195,51 @@ def test_irwin_hall_closed_forms() -> None: def test_the_number_of_studies_summed( proportion: float, n_studies: int, summed: int ) -> None: - assert studies_summed(proportion, n_studies) == summed + assert _studies_summed(None, proportion, n_studies) == (summed, proportion) + + +@pytest.mark.parametrize(("summed", "proportion"), [(1, 0.25), (3, 0.6), (5, 1.0)]) +def test_a_number_of_studies_summed_is_the_proportion_that_gives_it( + summed: int, proportion: float +) -> None: + # Of 5 studies, floor(0.25 * 5) = 1, floor(0.6 * 5) = 3 and 1.0 sums all 5. + statistics = random_statistics(14, (5, 30)) + null = random_statistics(15, (3, 5, 30)) + by_number = fit_sumrank(statistics, n_studies_summed=summed, null_statistics=null) + by_proportion = fit_sumrank( + statistics, proportion_of_studies=proportion, null_statistics=null + ) + assert_fits_equal(by_number, by_proportion) + assert_array_equal(by_number.normalized_ranks, by_proportion.normalized_ranks) + assert by_number.n_studies_summed == by_proportion.n_studies_summed == summed + assert by_number.proportion_of_studies is None + assert by_proportion.proportion_of_studies == proportion + + +@pytest.mark.parametrize( + ("options", "error", "match"), + [ + ( + {"n_studies_summed": 1, "proportion_of_studies": 0.5}, + ValueError, + r"\(n_studies_summed\) or their proportion \(proportion_of_studies\), " + "not both", + ), + ( + {"n_studies_summed": 0}, + ValueError, + "n_studies_summed must be from 1 to the number of studies, 2, got 0", + ), + ({"n_studies_summed": 3}, ValueError, "number of studies, 2, got 3"), + ({"n_studies_summed": True}, TypeError, "whole number of studies, not bool"), + ({"n_studies_summed": 1.5}, TypeError, "whole number of studies, got float"), + ], +) +def test_a_number_of_studies_summed_out_of_range_is_refused( + options: dict[str, object], error: type[Exception], match: str +) -> None: + with pytest.raises(error, match=match): + fit_sumrank([[1.0, 2.0], [3.0, 4.0]], **options) # type: ignore[arg-type] # Invariants. @@ -228,10 +268,12 @@ def assert_fits_equal(first: object, second: object) -> None: def test_the_order_of_studies_and_genes_does_not_matter(proportion: float) -> None: statistics = random_statistics(1, (5, 40)) null = random_statistics(2, (6, 5, 40)) - fit = sumrank(statistics, proportion_of_studies=proportion, null_statistics=null) + fit = fit_sumrank( + statistics, proportion_of_studies=proportion, null_statistics=null + ) studies = [3, 0, 4, 1, 2] genes = np.random.default_rng(3).permutation(40) - shuffled = sumrank( + shuffled = fit_sumrank( statistics[studies][:, genes], proportion_of_studies=proportion, null_statistics=null[:, studies][:, :, genes], @@ -260,8 +302,10 @@ def test_negated_statistics_swap_up_and_down() -> None: # Testing controls against cases negates every statistic. statistics = random_statistics(4, (4, 30)) null = random_statistics(5, (3, 4, 30)) - fit = sumrank(statistics, proportion_of_studies=0.75, null_statistics=null) - negated = sumrank(-statistics, proportion_of_studies=0.75, null_statistics=-null) + fit = fit_sumrank(statistics, proportion_of_studies=0.75, null_statistics=null) + negated = fit_sumrank( + -statistics, proportion_of_studies=0.75, null_statistics=-null + ) for name in ("statistic", "irwin_hall_p_value", "permutation_p_value"): assert_array_equal(getattr(negated, f"up_{name}"), getattr(fit, f"down_{name}")) assert_array_equal(getattr(negated, f"down_{name}"), getattr(fit, f"up_{name}")) @@ -272,24 +316,24 @@ def test_a_monotone_transform_of_a_study_changes_nothing() -> None: changed = statistics.copy() changed[1] = np.exp(changed[1]) + changed[1] ** 3 # increasing changed[2] = 10.0 * changed[2] - 4.0 - assert_fits_equal(sumrank(changed), sumrank(statistics)) + assert_fits_equal(fit_sumrank(changed), fit_sumrank(statistics)) def test_infinite_statistics_rank_first_and_tie() -> None: # p-values of 0 give infinite statistics: genes a and b tie at the top. - fit = sumrank([[np.inf, np.inf, 1.0, -np.inf], [5.0, 4.0, 3.0, 2.0]]) + fit = fit_sumrank([[np.inf, np.inf, 1.0, -np.inf], [5.0, 4.0, 3.0, 2.0]]) assert_allclose(fit.normalized_ranks[0], [1 / 6, 1 / 6, 2 / 3, 1], rtol=1e-15) assert_allclose(fit.up_statistic, [1 / 6, 1 / 2, 1, 1], rtol=1e-15) def test_one_study_is_its_own_ranking() -> None: - fit = sumrank([[2.0, 0.5, -1.0, 3.0]]) + fit = fit_sumrank([[2.0, 0.5, -1.0, 3.0]]) assert_allclose(fit.up_statistic, [1 / 3, 0.5, 0.5, 0.0], rtol=1e-15) assert_allclose(fit.up_irwin_hall_p_value, [1 / 3, 1, 1, 0], rtol=1e-15) def test_statistics_are_read_only() -> None: - fit = sumrank( + fit = fit_sumrank( [[1.0, 2.0, 3.0], [3.0, 2.0, 1.0]], null_statistics=np.ones((1, 2, 3)) ) for name in ( @@ -332,7 +376,7 @@ def test_invalid_arguments_are_refused( ) -> None: given = {"statistics": [[1.0, 2.0], [3.0, 4.0]]} | arguments with pytest.raises(error, match=match): - sumrank(**given) # type: ignore[arg-type] + fit_sumrank(**given) # type: ignore[arg-type] # Several studies of a collection. @@ -370,7 +414,7 @@ def design(samples: object) -> Design: @cache -def studies() -> tuple[CubeCollection, dict[str, DifferentialExpressionResult]]: +def studies() -> StudyResults: """Three studies, the third without group B; the second analysed by limma-trend.""" rng = np.random.default_rng(20261030) cubes = { @@ -384,26 +428,27 @@ def studies() -> tuple[CubeCollection, dict[str, DifferentialExpressionResult]]: cube = collection.aligned_cube(study) analysis = limma_trend if study == "second" else limma_voom results[study] = analysis(cube, design(cube.samples)) - return collection, results + return StudyResults(collection, results) -def test_studies_of_a_collection_are_ranked_as_sumrank_ranks_their_results() -> None: - collection, results = studies() +def test_studies_of_a_collection_are_ranked_as_fit_sumrank_ranks_their_results() -> ( + None +): + results = studies() + collection = results.collection # The warning's lines are at most 80 characters. with pytest.warns( RuntimeWarning, match=r"best 2 of 3 studies' ranks are too\ssmall" ): - result = differential_expression_sumrank( - collection, results, "T", CONTRAST, proportion_of_studies=0.7 - ) + result = sumrank(results, "T", CONTRAST, proportion_of_studies=0.7) # The signed -log10 p-values of the aligned results, as plain arrays. - table = align_study_results(collection, results, "T", CONTRAST) + table = results.table("T", CONTRAST) shape = (collection.n_studies, collection.n_features) p = table["p_value"].reshape(shape) change = table["log2_fold_change"].reshape(shape) - fit = sumrank(signed(p, change), proportion_of_studies=0.7) + fit = fit_sumrank(signed(p, change), proportion_of_studies=0.7) ranked = fit.ranked - out = result.table + out = result.features assert_array_equal(out["feature_id"], collection.features["feature_id"][ranked]) for direction in ("up", "down"): assert_array_equal( @@ -423,8 +468,7 @@ def test_studies_of_a_collection_are_ranked_as_sumrank_ranks_their_results() -> def test_a_study_without_the_group_is_left_out() -> None: - collection, results = studies() - result = differential_expression_sumrank(collection, results, "B", CONTRAST) + result = sumrank(studies(), "B", CONTRAST) studies_table = result.studies assert studies_table["analysed"].tolist() == [True, True, False] assert "has no group 'B'" in studies_table["reason"][2] @@ -432,15 +476,57 @@ def test_a_study_without_the_group_is_left_out() -> None: assert result.settings["n_studies"] == 2 +def test_the_studies_summed_may_be_given_as_a_number_or_a_proportion() -> None: + results = studies() + warning = r"best 2 of 3 studies' ranks are too\ssmall" + with pytest.warns(RuntimeWarning, match=warning): + by_number = sumrank(results, "T", CONTRAST, n_studies_summed=2) + with pytest.warns(RuntimeWarning, match=warning): + by_proportion = sumrank(results, "T", CONTRAST, proportion_of_studies=0.7) + assert by_number.features.equals(by_proportion.features) + assert by_number.studies.equals(by_proportion.studies) + settings = by_number.settings, by_proportion.settings + assert [s["n_studies_summed"] for s in settings] == [2, 2] + assert [s["proportion_of_studies"] for s in settings] == [None, 0.7] + with pytest.raises(ValueError, match="not both"): + sumrank(results, "T", CONTRAST, n_studies_summed=2, proportion_of_studies=0.7) + # The number is that of the studies that analysed the group: two have group B. + with pytest.raises(ValueError, match="from 1 to the number of studies, 3, got 4"): + sumrank(results, "T", CONTRAST, n_studies_summed=4) + with pytest.raises(ValueError, match="from 1 to the number of studies, 2, got 3"): + sumrank(results, "B", CONTRAST, n_studies_summed=3) + + +def test_the_group_and_contrast_of_studies_that_have_one_may_be_left_out() -> None: + # The studies tested one contrast, and analysed two groups. + results = studies() + named = sumrank(results, "T", CONTRAST) + assert sumrank(results, "T").features.equals(named.features) + with pytest.raises(ValueError, match="the studies analysed 2 groups; name one"): + sumrank(results) + # The same studies with group T alone. + collection = results.collection + only_t = StudyResults( + collection, + { + study: (limma_trend if study == "second" else limma_voom)( + collection.aligned_cube(study), result.design, groups=["T"] + ) + for study, result in results.items() + }, + ) + alone = sumrank(only_t) + assert alone.features.equals(named.features) and alone.studies.equals(named.studies) + assert (alone.settings["group"], alone.settings["contrast"]) == ("T", CONTRAST) + + def explicit_permutations( - collection: CubeCollection, - results: dict[str, DifferentialExpressionResult], - n_permutations: int, - seed: int, + results: StudyResults, n_permutations: int, seed: int ) -> np.ndarray: """The statistics of group T of the permutations that the seed draws, each study's analysis run by hand on a design with the condition permuted among the samples of its fit of T.""" + collection = results.collection generator = np.random.default_rng(seed) index = {f: i for i, f in enumerate(collection.features["feature_id"].tolist())} null = np.full( @@ -469,27 +555,20 @@ def explicit_permutations( def test_permutations_rerun_each_studys_analysis() -> None: - collection, results = studies() - result = differential_expression_sumrank( - collection, - results, - "T", - CONTRAST, - n_permutations=4, - permute="condition", - rng=7, + results = studies() + collection = results.collection + result = sumrank( + results, "T", CONTRAST, n_permutations=4, permute="condition", rng=7 ) - table = align_study_results(collection, results, "T", CONTRAST) + table = results.table("T", CONTRAST) shape = (collection.n_studies, collection.n_features) observed = signed( table["p_value"].reshape(shape), table["log2_fold_change"].reshape(shape) ) - fit = sumrank( - observed, null_statistics=explicit_permutations(collection, results, 4, 7) - ) + fit = fit_sumrank(observed, null_statistics=explicit_permutations(results, 4, 7)) for direction in ("up", "down"): assert_array_equal( - result.table[f"{direction}_p_value"], + result.features[f"{direction}_p_value"], getattr(fit, f"{direction}_permutation_p_value")[fit.ranked], ) assert result.settings["p_values"] == "permutation" @@ -498,14 +577,13 @@ def test_permutations_rerun_each_studys_analysis() -> None: def test_permutations_follow_the_seed_and_not_the_threads() -> None: - collection, results = studies() + results = studies() def p_values(rng: object, threads: int | None = None) -> np.ndarray: with thread_limit(threads): - out = differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=3, - permute="condition", rng=rng, - ).table # fmt: skip + out = sumrank( + results, "T", CONTRAST, n_permutations=3, permute="condition", rng=rng + ).features return np.concatenate([out["up_p_value"], out["down_p_value"]]) first = p_values(11) @@ -515,51 +593,84 @@ def p_values(rng: object, threads: int | None = None) -> np.ndarray: def test_the_result_records_its_studies_and_is_immutable() -> None: - collection, results = studies() - result = differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=2, permute="condition", rng=1 + results = studies() + collection = results.collection + result = sumrank( + results, "T", CONTRAST, n_permutations=2, permute="condition", rng=1 ) record = result.provenance - assert record["analysis"] == "differential_expression_sumrank" + assert record["analysis"] == "sumrank" assert record["settings"]["permute"] == "condition" assert set(record["studies"]) == set(collection.studies) assert record["studies"]["first"] == results["first"].provenance with pytest.raises(AttributeError, match="immutable"): - result.table = result.studies # type: ignore[misc] + result.features = result.studies # type: ignore[misc] copy = pickle.loads(pickle.dumps(result)) - assert copy.table.equals(result.table) and copy.provenance == record + assert copy.features.equals(result.features) and copy.provenance == record text = repr(result) assert "3 studies, the best 3 summed" in " ".join(text.split()) assert "2 permutations of 'condition'" in text assert max(map(len, text.splitlines())) <= 80 -def test_permutations_need_a_variable_of_each_design() -> None: - collection, results = studies() - with pytest.raises(ValueError, match="give its name as permute"): - differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=2 +@cache +def studies_with_a_covariate() -> StudyResults: + """The three studies with the sequencing depth of each sample in their designs, + and a contrast of it, which compares no two levels of a factor.""" + collection = studies().collection + results = {} + for study in collection.studies: + cube = collection.aligned_cube(study) + samples = cube.samples.with_columns( + {"depth": np.linspace(1.0, 2.0, cube.n_samples)} ) - with pytest.raises(ValueError, match="n_permutations is 0"): - differential_expression_sumrank( - collection, results, "T", CONTRAST, permute="condition" + with_depth = Design( + samples, + "~ condition + depth", + levels={"condition": ["ctrl", "case"]}, + contrasts={CONTRAST: "conditioncase", "depth": "depth"}, ) + analysis = limma_trend if study == "second" else limma_voom + results[study] = analysis(cube, with_depth) + return StudyResults(collection, results) + + +def test_permutations_permute_the_factor_whose_levels_the_contrast_compares() -> None: + # CONTRAST, conditioncase in ~ condition, compares case with ctrl, the reference. + results = studies() + read = sumrank(results, "T", CONTRAST, n_permutations=2, rng=1) + given = sumrank( + results, "T", CONTRAST, n_permutations=2, permute="condition", rng=1 + ) + assert read.features.equals(given.features) and read.studies.equals(given.studies) + assert read.settings == given.settings and read.settings["permute"] == "condition" + # A covariate beside the factor leaves the comparison of its levels as it is. + beside = sumrank(studies_with_a_covariate(), "T", CONTRAST, n_permutations=1, rng=1) + assert beside.settings["permute"] == "condition" + + +def test_permutations_need_a_variable_of_each_design() -> None: + results = studies() + # A contrast of a covariate compares no two levels: the variable must be named. + with pytest.raises( + ValueError, + match="contrast 'depth' does not compare two levels of one factor in every " + "study's design; give the variable's name as permute", + ): + sumrank(studies_with_a_covariate(), "T", "depth", n_permutations=2) + with pytest.raises(ValueError, match="n_permutations is 0"): + sumrank(results, "T", CONTRAST, permute="condition") with pytest.raises(ValueError, match="not a variable of the design of study"): - differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=1, permute="donor" - ) + sumrank(results, "T", CONTRAST, n_permutations=1, permute="donor") with pytest.raises(ValueError, match="at least 0"): - differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=-1 - ) + sumrank(results, "T", CONTRAST, n_permutations=-1) with pytest.raises(TypeError, match="not bool"): - differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=True - ) + sumrank(results, "T", CONTRAST, n_permutations=True) def test_permutations_refuse_a_result_of_another_cube() -> None: - collection, results = studies() + results = studies() + collection = results.collection # The first study's result from counts other than those of its aligned cube, with # the same genes and groups: the line-up accepts it, a re-run would not be its own. cube = collection.aligned_cube("first") @@ -572,11 +683,10 @@ def test_permutations_refuse_a_result_of_another_cube() -> None: ) other = dict(results) other["first"] = limma_voom(changed, design(changed.samples)) - assert len(differential_expression_sumrank(collection, other, "T", CONTRAST).table) + other_studies = StudyResults(collection, other) + assert len(sumrank(other_studies, "T", CONTRAST).features) with pytest.raises(ValueError, match="fingerprint"): - differential_expression_sumrank( - collection, other, "T", CONTRAST, n_permutations=1, permute="condition" - ) + sumrank(other_studies, "T", CONTRAST, n_permutations=1, permute="condition") def test_a_mixed_model_is_not_permuted_sample_by_sample() -> None: @@ -598,33 +708,35 @@ def test_a_mixed_model_is_not_permuted_sample_by_sample() -> None: levels={"condition": ["ctrl", "case"]}, contrasts={CONTRAST: "conditioncase"}, ) - results = { - study: dream_voom(collection.aligned_cube(study), mixed) - for study in collection.studies - } - # Without permutations dream's results are ranked; with them they are refused. - assert len( - differential_expression_sumrank(collection, results, "T", CONTRAST).table + results = StudyResults( + collection, + { + study: dream_voom(collection.aligned_cube(study), mixed) + for study in collection.studies + }, ) + # Without permutations dream's results are ranked; with them they are refused. + assert len(sumrank(results, "T", CONTRAST).features) with pytest.raises(ValueError, match="random effects"): - differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=1, permute="condition" - ) + sumrank(results, "T", CONTRAST, n_permutations=1, permute="condition") def test_invalid_studies_and_contrasts_are_refused() -> None: - collection, results = studies() + results = studies() with pytest.raises(KeyError, match="no group 'NK'"): - differential_expression_sumrank(collection, results, "NK", CONTRAST) + sumrank(results, "NK", CONTRAST) with pytest.raises(KeyError, match="no study tested the contrast"): - differential_expression_sumrank(collection, results, "T", "other") + sumrank(results, "T", "other") with pytest.raises(ValueError, match="every study of the collection"): - differential_expression_sumrank( - collection, {"first": results["first"]}, "T", CONTRAST + sumrank( + StudyResults(results.collection, {"first": results["first"]}), + "T", + CONTRAST, ) + with pytest.raises(TypeError, match="sumrank takes the studies' results"): + sumrank(dict(results), "T", CONTRAST) # type: ignore[arg-type] with pytest.raises(TypeError, match="permute must be"): - differential_expression_sumrank( - collection, + sumrank( results, "T", CONTRAST, @@ -632,10 +744,7 @@ def test_invalid_studies_and_contrasts_are_refused() -> None: permute=1, # type: ignore[arg-type] ) with pytest.raises(ValueError, match="non-negative seed"): - differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=1, - permute="condition", rng=-1, - ) # fmt: skip + sumrank(results, "T", CONTRAST, n_permutations=1, permute="condition", rng=-1) def test_the_figure_shows_the_p_values_in_alphabetical_order() -> None: @@ -644,10 +753,9 @@ def test_the_figure_shows_the_p_values_in_alphabetical_order() -> None: from cytepool.plotting import plot_sumrank - collection, results = studies() - result = differential_expression_sumrank(collection, results, "T", CONTRAST) - ax = plot_sumrank(result, direction="down", n_labels=3, ax=Figure().subplots()) - table = result.table + result = sumrank(studies(), "T", CONTRAST) + ax = plot_sumrank(result, direction="down", top=3, ax=Figure().subplots()) + table = result.features ids = table["feature_id"].tolist() order = np.argsort(ids, kind="stable") # A p-value of 0 is drawn at the smallest positive float. @@ -722,11 +830,11 @@ def test_tied_p_values_are_labelled_by_statistic_and_the_labels_kept_apart() -> result = tied_result() figure = Figure() FigureCanvasAgg(figure) - ax = plot_sumrank(result, n_labels=10, ax=figure.subplots()) + ax = plot_sumrank(result, top=10, ax=figure.subplots()) labels = [text for text in ax.texts if text.get_gid() == "labels"] # Of the 30 genes tied at the smallest p-value, the 10 with the smallest # statistics, the best ranks. - statistic = result.table["up_statistic"] + statistic = result.features["up_statistic"] tied = np.arange(50, 80) best = tied[np.argsort(statistic[tied], kind="stable")[:10]] assert sorted(text.get_text() for text in labels) == sorted( @@ -788,8 +896,8 @@ def test_labels_and_legends_stay_inside_two_panels_after_the_final_draw() -> Non figure = Figure(figsize=(8, 6.5), layout="constrained") FigureCanvasAgg(figure) upper, lower = figure.subplots(2, 1) - plot_sumrank(result, n_labels=4, ax=upper) - plot_sumrank(result, direction="down", n_labels=4, ax=lower) + plot_sumrank(result, top=4, ax=upper) + plot_sumrank(result, direction="down", top=4, ax=lower) figure.savefig(io.BytesIO(), format="png") renderer = figure.canvas.get_renderer() for ax in (upper, lower): @@ -806,9 +914,8 @@ def test_labels_and_legends_stay_inside_two_panels_after_the_final_draw() -> Non def test_a_result_is_built_from_tables_and_a_record() -> None: - collection, results = studies() - result = differential_expression_sumrank(collection, results, "T", CONTRAST) - with pytest.raises(TypeError, match="table must be a Table"): + result = sumrank(studies(), "T", CONTRAST) + with pytest.raises(TypeError, match="features must be a Table"): SumRankResult({}, result.studies, {}) # type: ignore[arg-type] with pytest.raises(ValueError, match="Out of range float values"): - SumRankResult(result.table, result.studies, {"x": float("nan")}) + SumRankResult(result.features, result.studies, {"x": float("nan")}) diff --git a/tests/test_table.py b/tests/test_table.py index b085a40..34833e7 100644 --- a/tests/test_table.py +++ b/tests/test_table.py @@ -7,7 +7,7 @@ from numpy.dtypes import StringDType from numpy.testing import assert_array_equal -from cytepool.table import Table +from cytepool import Table @pytest.fixture(scope="module") diff --git a/tests/test_top_level.py b/tests/test_top_level.py new file mode 100644 index 0000000..419b18b --- /dev/null +++ b/tests/test_top_level.py @@ -0,0 +1,103 @@ +"""The names an analysis starts from, imported from the top level of the package. + +``cytepool.__all__`` must list the names that the package's docstring documents, and +``__version__``; each must be the object that its module implements; and ``import +cytepool`` must load none of the optional libraries, which only the functions that +need them import. +""" + +import importlib +import re +import subprocess +import sys +from pathlib import Path + +import cytepool + +# The module that implements each name of the top level. +MODULES = { + "cytepool.activity": ("Activities", "mlm", "ulm", "weighted_aggregate"), + "cytepool.composition": ("CompositionResult", "propeller"), + "cytepool.cube.collection": ("CubeCollection",), + "cytepool.cube.cube": ("Cube",), + "cytepool.cube.pool": ("from_bulk", "from_cells"), + "cytepool.design.design": ("Design",), + "cytepool.differential_expression": ( + "DifferentialExpressionResult", + "GroupFit", + "dream_voom", + "limma_trend", + "limma_voom", + ), + "cytepool.gene_set_enrichment": ("gsea",), + "cytepool.gene_set_tests": ("camera", "fry"), + "cytepool.gene_sets": ("GeneSets", "read_gmt", "write_gmt"), + "cytepool.held_out_studies": ("HeldOutPrediction", "held_out_prediction"), + "cytepool.io.anndata": ("from_anndata", "from_mudata", "to_anndata", "to_mudata"), + "cytepool.io.cube_files": ("read_cube", "write_cube"), + "cytepool.meta_analysis_of_studies": ("MetaAnalysisResult", "meta_analysis"), + "cytepool.networks": ("Network", "collectri", "progeny"), + "cytepool.over_representation_analysis": ("over_representation",), + "cytepool.pathway_scores": ( + "PathwayScores", + "combined_z_scores", + "gsva", + "plage", + "ssgsea", + ), + "cytepool.principal_component_analysis": ( + "PrincipalComponents", + "principal_components", + ), + "cytepool.replication_across_studies": ("ReplicationResult", "replication"), + "cytepool.rotation_gene_set_tests": ("mroast", "roast", "romer"), + "cytepool.score_tests": ("limma",), + "cytepool.study_results": ("StudyResults", "analyse_each_study"), + "cytepool.sumrank_across_studies": ("SumRankResult", "sumrank"), + "cytepool.table": ("Table",), + "cytepool.variance_components": ( + "VariancePartitioningResult", + "variance_partitioning", + ), +} +# Libraries that some functions use, which importing the package must not load. +OPTIONAL = ("anndata", "matplotlib", "mudata", "pandas") + + +def test_the_top_level_lists_the_names_of_its_docstring() -> None: + documented = re.findall(r":(?:class|func):`(\w+)`", cytepool.__doc__ or "") + assert len(documented) == len(set(documented)) == 56 + assert len(cytepool.__all__) == len(set(cytepool.__all__)) == 57 + assert set(cytepool.__all__) == {*documented, "__version__"} + assert isinstance(cytepool.__version__, str) and cytepool.__version__ + + +def test_each_name_is_the_object_its_module_implements() -> None: + listed = [name for names in MODULES.values() for name in names] + assert sorted(listed) == sorted(set(cytepool.__all__) - {"__version__"}) + for module_name, names in MODULES.items(): + module = importlib.import_module(module_name) + for name in names: + found = getattr(cytepool, name) + assert found is getattr(module, name), name + assert found.__module__ == module_name, name + + +def test_importing_the_package_loads_no_optional_library() -> None: + # A fresh interpreter, started where the package under test is found first. + code = "\n".join( + [ + "import sys", + "import cytepool", + "print(cytepool.__file__)", + f"print([name for name in {OPTIONAL!r} if name in sys.modules])", + ] + ) + root = Path(cytepool.__file__).resolve().parents[1] + done = subprocess.run( + [sys.executable, "-c", code], cwd=root, capture_output=True, text=True + ) + assert done.returncode == 0, done.stderr + location, loaded = done.stdout.splitlines() + assert Path(location).resolve() == Path(cytepool.__file__).resolve() + assert loaded == "[]" diff --git a/tests/test_variance_partitioning.py b/tests/test_variance_partitioning.py index 6916c60..320bd2b 100644 --- a/tests/test_variance_partitioning.py +++ b/tests/test_variance_partitioning.py @@ -25,18 +25,18 @@ import pytest from numpy.testing import assert_allclose, assert_array_equal -from cytepool.cube import Cube, from_bulk -from cytepool.design import Design -from cytepool.normalization import filter_by_expression, tmm_factors -from cytepool.stats.dream import voom_with_dream_weights -from cytepool.stats.voom import voom -from cytepool.table import Table -from cytepool.variance_partitioning import ( - VarianceFractions, +from cytepool import ( + Cube, + Design, + Table, VariancePartitioningResult, - variance_fractions, + from_bulk, variance_partitioning, ) +from cytepool.normalization import filter_by_expression, tmm_factors +from cytepool.stats.dream import voom_with_dream_weights +from cytepool.stats.variance_fractions import VarianceFractions, variance_fractions +from cytepool.stats.voom import voom DATA = Path(__file__).parent / "data" / "variance_partitioning" # lme4's fits stop at about 1e-8 of the optimum (see the validation script); closed @@ -460,7 +460,7 @@ def test_groups_that_cannot_be_partitioned_say_why_and_the_rest_are_fitted() -> norm_factors=tmm_factors(counts), weights=cube.n_cells[:, 0].astype(float), ) - direct = variance_fractions(weighted.log_cpm, design, weights=weighted.weights) + direct = variance_fractions(weighted.log2_cpm, design, weights=weighted.weights) assert_allclose(fractions, direct.fractions, rtol=EXACT, atol=EXACT) assert result.summary["n_features"].tolist() == [30, 0, 0, 0] assert int(result.summary["n_samples_left_out"][1]) == 16 @@ -474,7 +474,7 @@ def test_a_design_without_random_effects_is_fitted_after_voom() -> None: ) counts = np.asarray(cube.layer("counts", 0), dtype=float) weighted = voom(counts, design.matrix) - direct = variance_fractions(weighted.log_cpm, design, weights=weighted.weights) + direct = variance_fractions(weighted.log2_cpm, design, weights=weighted.weights) table = result.table("fine") assert result.table().equals(table) # the only group needs no name @@ -503,7 +503,7 @@ def test_a_filter_by_sample_groups_keeps_the_genes_of_any_one_group() -> None: by_donor = variance_partitioning( cube, design, expression_filter={"sample_groups": "donor"}, normalization=None ) - keep = filter_by_expression(counts, group=cube.samples["donor"]) + keep = filter_by_expression(counts, sample_groups=cube.samples["donor"]) tested = by_donor.table()["feature_id"].tolist() assert tested == cube.features["feature_id"][keep].tolist() assert "g0" in tested and "g1" not in tested @@ -618,7 +618,7 @@ def test_the_figures_draw_what_plotvarpart_and_plotpercentbars_draw() -> None: assert np.max(np.abs(drawn - 0.45 * data["violin_width"][rows])) <= 0.45e-3 genes = data["bar_genes"].tolist() - ax = plotting.plot_gene_variance_fractions( + ax = plotting.plot_feature_variance_fractions( table, genes, ax=figure.Figure().subplots() ) segments = { @@ -657,12 +657,12 @@ def test_figures_take_fractions_results_and_tables_and_say_what_is_wrong() -> No "rin", "Residuals", ] - ax = plotting.plot_gene_variance_fractions( + ax = plotting.plot_feature_variance_fractions( fractions, [2, 0], ax=figure.Figure().subplots() ) widths = np.array([[patch.get_width() for patch in c] for c in ax.containers]).T assert_allclose(widths.sum(axis=1), 100.0, rtol=EXACT) - labelled = plotting.plot_gene_variance_fractions( + labelled = plotting.plot_feature_variance_fractions( fractions, ["x2"], labels=[f"x{i}" for i in range(20)], @@ -684,15 +684,15 @@ def test_figures_take_fractions_results_and_tables_and_say_what_is_wrong() -> No with pytest.raises(TypeError, match="data must be"): plotting.plot_variance_fractions(np.ones((3, 2))) with pytest.raises(KeyError, match="no gene 'g99'"): - plotting.plot_gene_variance_fractions(table, ["g99"]) + plotting.plot_feature_variance_fractions(table, ["g99"]) with pytest.raises(KeyError, match="name no genes"): - plotting.plot_gene_variance_fractions(fractions, ["g1"]) + plotting.plot_feature_variance_fractions(fractions, ["g1"]) with pytest.raises(ValueError, match="more than once"): - plotting.plot_gene_variance_fractions(table, ["g1", "g1"]) - with pytest.raises(ValueError, match="genes is empty"): - plotting.plot_gene_variance_fractions(table, []) + plotting.plot_feature_variance_fractions(table, ["g1", "g1"]) + with pytest.raises(ValueError, match="features is empty"): + plotting.plot_feature_variance_fractions(table, []) with pytest.raises(IndexError, match="out of range"): - plotting.plot_gene_variance_fractions(fractions, [20]) + plotting.plot_feature_variance_fractions(fractions, [20]) many = Table( {f"v{i}": np.full(3, 0.1) for i in range(9)} | {"residuals": np.full(3, 0.1)} ) @@ -702,4 +702,4 @@ def test_figures_take_fractions_results_and_tables_and_say_what_is_wrong() -> No with pytest.raises(ValueError, match="no gene has fractions"): plotting.plot_variance_fractions(unfitted) with pytest.raises(ValueError, match="'a' has no fractions"): - plotting.plot_gene_variance_fractions(unfitted, ["a"]) + plotting.plot_feature_variance_fractions(unfitted, ["a"]) diff --git a/tests/test_voom.py b/tests/test_voom.py index d075cc9..ecd8e6d 100644 --- a/tests/test_voom.py +++ b/tests/test_voom.py @@ -19,7 +19,7 @@ import numpy as np import pytest -from cytepool.design import Design +from cytepool import Design from cytepool.normalization import tmm_factors from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import contrasts_fit, fit_linear_model @@ -132,14 +132,14 @@ def assert_p_values_agree(ours: Any, desired: np.ndarray, tolerance: float) -> N @pytest.mark.parametrize("case", CASES) -def test_log_cpm_weights_and_library_sizes_match_limma(case: str) -> None: +def test_log2_cpm_weights_and_library_sizes_match_limma(case: str) -> None: data = fixture(case) result = run_voom(case) n_samples = data["counts"].shape[0] exact = 1e-10 # log2 CPM is a closed form: limma's to the last bit on the validation inputs. np.testing.assert_allclose( - result.log_cpm, limma_matrix(case, "E", n_samples).T, rtol=exact, atol=exact + result.log2_cpm, limma_matrix(case, "E", n_samples).T, rtol=exact, atol=exact ) np.testing.assert_allclose( result.weights, @@ -187,7 +187,7 @@ def test_moderated_statistics_of_the_weighted_fit_match_limma(case: str) -> None """counts -> voom -> fit_linear_model -> contrasts_fit -> moderated_statistics.""" design = design_of(case) result = run_voom(case) - fit = fit_linear_model(result.log_cpm, design, weights=result.weights) + fit = fit_linear_model(result.log2_cpm, design, weights=result.weights) if len(design.contrasts): # limma's contrasts.fit approximates the standard errors of contrasts of # correlated coefficients in a weighted fit; method="limma" reproduces it. @@ -264,7 +264,7 @@ def test_counts_to_moderated_statistics_with_a_design_and_named_contrasts() -> N contrasts={"BvsA": "groupB - groupA", "CvsB": "groupC - groupB"}, ) result = voom(counts, design, norm_factors=tmm_factors(counts)) - fit = fit_linear_model(result.log_cpm, design, weights=result.weights) + fit = fit_linear_model(result.log2_cpm, design, weights=result.weights) contrasted = contrasts_fit(fit, design.contrasts) moderated = moderated_statistics(contrasted) assert contrasted.coefficient_names == ("BvsA", "CvsB") @@ -286,7 +286,7 @@ def test_no_replication_gives_unit_weights_and_no_trend() -> None: assert float(fixture("no_replication")["span"]) == 0.5 and np.isnan(result.span) -def test_scaling_library_sizes_plus_one_shifts_log_cpm_and_keeps_weights() -> None: +def test_scaling_library_sizes_plus_one_shifts_log2_cpm_and_keeps_weights() -> None: """With R + 1 scaled by c, log2 CPM falls by log2(c) and nothing else changes. log2((r + 0.5) / (c (R + 1)) 1e6) = y - log2(c); with an intercept the fitted @@ -299,7 +299,7 @@ def test_scaling_library_sizes_plus_one_shifts_log_cpm_and_keeps_weights() -> No sizes = counts.sum(axis=1) base = voom(counts, design, library_sizes=sizes) scaled = voom(counts, design, library_sizes=4.0 * (sizes + 1.0) - 1.0) - np.testing.assert_allclose(scaled.log_cpm, base.log_cpm - 2.0, rtol=0, atol=1e-12) + np.testing.assert_allclose(scaled.log2_cpm, base.log2_cpm - 2.0, rtol=0, atol=1e-12) np.testing.assert_allclose(scaled.weights, base.weights, rtol=1e-12) for name in ("mean_log_count", "sqrt_sd", "sqrt_sd_trend"): np.testing.assert_allclose( @@ -320,7 +320,7 @@ def test_weights_depend_on_a_feature_only_through_its_fitted_values() -> None: pair = 2.0**log_cpm * (sizes[:, np.newaxis] + 1.0) / 1e6 - 0.5 counts = np.column_stack([fixture("two_groups")["counts"], pair]) result = voom(counts, TWO_GROUPS, library_sizes=sizes) - np.testing.assert_allclose(result.log_cpm[:, -2:], log_cpm, rtol=1e-13) + np.testing.assert_allclose(result.log2_cpm[:, -2:], log_cpm, rtol=1e-13) np.testing.assert_allclose(result.weights[:, -1], result.weights[:, -2], rtol=1e-12) np.testing.assert_allclose( result.sqrt_sd[-1] / result.sqrt_sd[-2], np.sqrt(3.0), rtol=1e-12 @@ -335,7 +335,7 @@ def test_identical_features_are_weighted_by_their_inverse_variance() -> None: counts = np.column_stack([column, column]) sizes = np.array([1e3, 1.2e3, 0.9e3, 1.5e3, 1.1e3, 1.3e3]) result = voom(counts, TWO_GROUPS, library_sizes=sizes) - _, residual_sum, *_ = np.linalg.lstsq(TWO_GROUPS, result.log_cpm[:, 0]) + _, residual_sum, *_ = np.linalg.lstsq(TWO_GROUPS, result.log2_cpm[:, 0]) np.testing.assert_allclose(result.weights, 4.0 / residual_sum[0], rtol=1e-12) @@ -384,7 +384,7 @@ def test_permuting_samples_and_features_permutes_the_results() -> None: shuffled.weights, base.weights[rows][:, columns], rtol=1e-12 ) np.testing.assert_allclose( - shuffled.log_cpm, base.log_cpm[rows][:, columns], rtol=1e-14 + shuffled.log2_cpm, base.log2_cpm[rows][:, columns], rtol=1e-14 ) np.testing.assert_allclose( shuffled.sqrt_sd_trend, base.sqrt_sd_trend[columns], rtol=1e-12 @@ -408,7 +408,7 @@ def test_result_is_read_only_with_a_summary() -> None: ) with pytest.raises(ValueError, match="effective_library_sizes must have shape"): VoomResult( - result.log_cpm, + result.log2_cpm, result.weights, result.effective_library_sizes[:-1], result.mean_log_count, diff --git a/tests/test_zarr.py b/tests/test_zarr.py index 2867d64..4b44c84 100644 --- a/tests/test_zarr.py +++ b/tests/test_zarr.py @@ -388,7 +388,7 @@ def test_fill_value_and_byte_order_metadata( def test_gzip_chunks_are_reproducible_gzip_members(tmp_path: Path) -> None: values = np.arange(12, dtype=np.float64).reshape(3, 4) for name, level in (("default", None), ("best", 9)): - write_array(tmp_path / name, values, chunks=(3, 4), level=level) + write_array(tmp_path / name, values, chunks=(3, 4), compression_level=level) data = (tmp_path / name / "c" / "0" / "0").read_bytes() # RFC 1952: magic bytes, deflate, and a zero modification time, so that the # same array gives the same bytes on every run. @@ -405,7 +405,9 @@ def test_gzip_chunks_are_reproducible_gzip_members(tmp_path: Path) -> None: @needs_zstd def test_zstd_chunks_are_frames_with_checksums(tmp_path: Path) -> None: values = np.arange(12, dtype=np.int32) - write_array(tmp_path / "a", values, chunks=(12,), compression="zstd", level=-5) + write_array( + tmp_path / "a", values, chunks=(12,), compression="zstd", compression_level=-5 + ) data = (tmp_path / "a" / "c" / "0").read_bytes() assert data[:4] == b"\x28\xb5\x2f\xfd" # RFC 8878 magic number assert data[4] & 0x04 # the frame header's content checksum flag @@ -1246,11 +1248,11 @@ def test_check_compression() -> None: assert check_compression("zstd", None) == 3 assert check_compression("zstd", -131072) == -131072 for compression, level, error, message in [ - (None, 1, ValueError, "level applies only with compression"), + (None, 1, ValueError, "compression_level applies only with compression"), ("gzip", 10, ValueError, "the gzip level must be from 0 to 9, got 10"), ("gzip", -1, ValueError, "from 0 to 9"), - ("gzip", 1.0, TypeError, "level must be an integer, got float"), - ("gzip", True, TypeError, "level must be an integer, got bool"), + ("gzip", 1.0, TypeError, "compression_level must be an integer, got float"), + ("gzip", True, TypeError, "compression_level must be an integer, got bool"), ( "GZIP", None, diff --git a/validation/activity.py b/validation/activity.py index 3811fa8..eed52b8 100644 --- a/validation/activity.py +++ b/validation/activity.py @@ -69,14 +69,20 @@ import matplotlib.figure import numpy as np -from cytepool.activity import mlm, ulm, weighted_aggregate -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.networks import Network, collectri, progeny -from cytepool.normalization import filter_by_expression, log_cpm, tmm_factors +from cytepool import ( + Cube, + Design, + Network, + Table, + collectri, + limma_voom, + mlm, + progeny, + ulm, + weighted_aggregate, +) +from cytepool.normalization import filter_by_expression, log2_cpm, tmm_factors from cytepool.plotting import plot_top_activities -from cytepool.table import Table FIXTURES = Path(__file__).resolve().parents[1] / "tests/data/activity" EXACT = 1e-10 @@ -292,9 +298,9 @@ def cube_and_result(rng, genes, data, cases): n_cells=np.full((8, 2), 40), ) # fmt: skip kept = counts[0] - keep = filter_by_expression(kept, group=np.zeros(8)) - values = log_cpm(kept[:, keep], norm_factors=tmm_factors(kept[:, keep]), - prior_count=2.0) # fmt: skip + keep = filter_by_expression(kept, sample_groups=np.zeros(8)) + values = log2_cpm(kept[:, keep], norm_factors=tmm_factors(kept[:, keep]), + prior_count=2.0) # fmt: skip data["cube"] = (values, [f"S{i}" for i in range(8)], [g for g, k in zip(genes, keep, strict=True) if k]) # fmt: skip cases.append(("ulm, a cube's cell type", "cube", "collectri", "ulm", 5, {})) @@ -394,10 +400,10 @@ def run_case(case, data, nets, special): return found, [0] if case["data"].startswith("missing_"): everything = data["samples"] - found = function(special["missing"], network, gene_ids=everything[2], + found = function(special["missing"], network, feature_ids=everything[2], sample_ids=everything[1], **options) # fmt: skip return found, [int(case["data"].removeprefix("missing_"))] - found = function(values, network, gene_ids=genes, sample_ids=profiles, **options) + found = function(values, network, feature_ids=genes, sample_ids=profiles, **options) return found, list(range(values.shape[0])) @@ -423,7 +429,7 @@ def check_case(case, found, arrays, data, nets, special, table): order = [position[gene] for gene in reference["genes"]] result = weighted_aggregate( values[:, order], Network(Table(nets[case["network"]])), - gene_ids=reference["genes"], sample_ids=profiles, + feature_ids=reference["genes"], sample_ids=profiles, n_permutations=case["options"]["times"], rng=case["options"]["seed"], ) # fmt: skip rows = list(range(values.shape[0])) @@ -457,7 +463,7 @@ def check_case(case, found, arrays, data, nets, special, table): raw = arrays[f"{name}/raw_p"] table.append((f"{name}: p-values", "decoupler `mt.waggr`", "largest difference", - float(np.max(np.abs(result.p_values[pick] - raw))), + float(np.max(np.abs(result.p_value[pick] - raw))), "deliberate")) # fmt: skip return family = f"decoupler `mt.{method}`" @@ -470,16 +476,16 @@ def check_case(case, found, arrays, data, nets, special, table): p_reference = arrays[f"{name}/raw_p"] if method == "ulm" else arrays[f"{name}/t_sf"] table.append((f"{name}: p-values", "decoupler" if method == "ulm" else "scipy `t.sf` of decoupler's t", "max relative difference", - relative_difference(result.p_values[pick], p_reference), EXACT)) # fmt: skip + relative_difference(result.p_value[pick], p_reference), EXACT)) # fmt: skip bh = arrays.get(f"{name}/bh") if method == "ulm" else None if bh is not None and bh.shape[1] > 1: table.append((f"{name}: adjusted p-values", "scipy's Benjamini-Hochberg", "max relative difference", - relative_difference(result.adjusted_p_values[pick], bh), + relative_difference(result.adjusted_p_value[pick], bh), EXACT)) # fmt: skip table.append((f"{name}: adjusted p-values", "decoupler (single precision)", "max relative difference", - single_difference(result.adjusted_p_values[pick], + single_difference(result.adjusted_p_value[pick], arrays[f"{name}/padj"]), SINGLE)) # fmt: skip if "bar_names" in reference: ax = plot_top_activities(result, case["bar"], top=15, @@ -594,7 +600,7 @@ def check_files(table): def check_progeny_scores(r_found, values, genes, network, table): - result = weighted_aggregate(values, network, gene_ids=genes, statistic="sum", + result = weighted_aggregate(values, network, feature_ids=genes, statistic="sum", n_permutations=0, min_targets=1) # fmt: skip theirs = np.array(r_found["scores"]).reshape(values.shape[0], -1) columns = [list(result.sources).index(s) for s in r_found["score_pathways"]] diff --git a/validation/anndata_conversion.py b/validation/anndata_conversion.py index ab8613c..731fe64 100644 --- a/validation/anndata_conversion.py +++ b/validation/anndata_conversion.py @@ -58,8 +58,15 @@ import pandas as pd import scipy.sparse as sp -from cytepool.cube import Cube, from_bulk, from_cells -from cytepool.io.anndata import from_anndata, from_mudata, to_anndata, to_mudata +from cytepool import ( + Cube, + from_anndata, + from_bulk, + from_cells, + from_mudata, + to_anndata, + to_mudata, +) SEED = 20261025 EXACT = 1e-10 @@ -221,14 +228,14 @@ def pooled(cells: Any, layers: Any = None, metadata: str | None = None) -> Cube: ): if metadata == "both" or (metadata == "samples" and kind == "sample"): values = obs.groupby(labels, observed=True)[column].first() - tables[f"{kind}_table"] = { + tables[f"{kind}s"] = { f"{kind}_id": list(values.index), column: list(values), } return from_cells( cells.X, - samples=obs["sample"], - groups=obs["group"], + sample_labels=obs["sample"], + group_labels=obs["group"], features={ "feature_id": list(cells.var_names), "symbol": list(cells.var["symbol"]), @@ -299,9 +306,9 @@ def exotic_cube() -> Cube: samples = rng.choice(["A", "B", "C"], 80) return from_cells( counts, - samples=samples, - groups=rng.choice(["T", "NK"], 80), - sample_table={ + sample_labels=samples, + group_labels=rng.choice(["T", "NK"], 80), + samples={ "sample_id": ["A", "B", "C", "D"], "collected": np.array( ["2024-01-05", "NaT", "2024-03-01", "2024-04-11"], "datetime64[D]" @@ -312,7 +319,7 @@ def exotic_cube() -> Cube: "notes": [None] * 4, "..": [1, 2, 3, 4], }, - group_table={ + groups={ "group_id": ["T", "NK", "empty"], "parent/lineage": ["L", None, "M"], "since": np.full(3, "NaT", dtype="datetime64[s]"), @@ -382,7 +389,9 @@ def read(name: str) -> Any: return anndata.read_h5ad(directory / name) def by_label(name: str, **options: Any) -> Cube: - return from_anndata(read(name), sample="sample", group="group", **options) + return from_anndata( + read(name), sample_column="sample", group_column="group", **options + ) cube = cubes["all five layers"] integer = ("counts", "detected") @@ -403,8 +412,8 @@ def by_label(name: str, **options: Any) -> Cube: sources |= {name: name for name in ("log_sum", "log_sum_sq", "linear_sum")} scanpy_all = from_anndata( normalized, - sample="sample", - group="group", + sample_column="sample", + group_column="group", layers=sources, normalization=cube.normalization, ) diff --git a/validation/combined_p_values_and_replication.py b/validation/combined_p_values_and_replication.py index 3b87680..9ff3e90 100644 --- a/validation/combined_p_values_and_replication.py +++ b/validation/combined_p_values_and_replication.py @@ -28,11 +28,11 @@ roots of sample sizes). Then three cohorts of 6, 4 and 8 donors, before and after a stimulation that changes 15% of 700 genes in two cell types alike in every cohort, up to 50% more or less, each measuring part of the genes, are aligned in a -``CubeCollection``, tested by ``limma_voom`` and passed to -``differential_expression_replication`` with four settings, among them pi1 with -qvalue's defaults (``null_proportion={}``) and pi1 by default (``null_proportion`` left -out), which is ``pi0est(lambda = 0.5)``; R computes every column from the cohorts' -tables, and the figures are compared with what R computes for them. +``CubeCollection``, tested by ``limma_voom`` and passed to ``replication`` with four +settings, among them pi1 with qvalue's defaults (``null_proportion={}``) and pi1 by +default (``null_proportion`` left out), which is ``pi0est(lambda = 0.5)``; R computes +every column from the cohorts' tables, and the figures are compared with what R +computes for them. Tolerances: exact (1e-10) for every closed form, the smoother of pi0 included (R's search for its smoothing parameter is followed step by step); close (1e-6) for REML. @@ -386,9 +386,7 @@ def pi0_sets(rng): def cohorts(rng, n_genes, n_donors=(6, 4, 8)): """Three cohorts' cubes, their collection and limma_voom results.""" - from cytepool.cube import Cube, CubeCollection - from cytepool.design import Design - from cytepool.differential_expression import limma_voom + from cytepool import Cube, CubeCollection, Design, limma_voom genes = [f"g{j:04d}" for j in range(n_genes)] base = {g: rng.normal(0.0, 1.5, n_genes) for g in "TB"} @@ -442,17 +440,18 @@ def cohorts(rng, n_genes, n_donors=(6, 4, 8)): def replications(collection, results, settings=SETTINGS): - """differential_expression_replication of every group with every setting.""" - from cytepool.replication import differential_expression_replication + """replication of every group with every setting.""" + from cytepool import StudyResults, replication + studies = StudyResults(collection, results) out = [] for group in collection.groups["group_id"].tolist(): for s, (method, conjunction, u, alpha, meta, pi0) in enumerate(settings): options = {} if pi0 is None else { "null_proportion": {NAMES[key]: value for key, value in pi0.items()} } # fmt: skip - result = differential_expression_replication( - collection, results, group, "stim_vs_ctrl", alpha=alpha, method=method, + result = replication( + studies, group, "stim_vs_ctrl", alpha=alpha, method=method, at_least=u, conjunction_method=conjunction, meta_method=meta, **options, ) # fmt: skip @@ -466,7 +465,7 @@ def write_binary(path, values): def run_references(directory, cases, extreme, pi0, analyses, collection, results): """Write the inputs, run the reference programs and read their outputs.""" - from cytepool.meta_analysis import align_study_results + from cytepool import StudyResults parts = ("p", "signs", "weights") inputs = { @@ -504,9 +503,9 @@ def run_references(directory, cases, extreme, pi0, analyses, collection, results columns["adjusted_p_value"] = "adj" for name, s, result in analyses: group = result.settings["group"] - table = align_study_results(collection, results, group, "stim_vs_ctrl") + table = StudyResults(collection, results).table(group, "stim_vs_ctrl") k, n = collection.n_studies, collection.n_features - ids = result.genes["feature_id"].tolist() + ids = result.features["feature_id"].tolist() keep = np.isin(table["feature_id"][:n].tolist(), ids) tested = np.array(table["status"].tolist()).reshape(k, n) == "tested" folder = directory / "replication" / name @@ -755,13 +754,13 @@ def check_replication(analyses, reference): worst["default"] = 0.0 compared = stops = by_default = 0 counts = ("n_studies", "n_up", "n_down", "n_found", "n_found_up", "n_found_down") - counts += ("direction",) + counts += ("sign",) values = ("sign_agreement", "p_value", "replication_p_value", "adjusted_p_value") values += ("replication_adjusted_p_value",) heterogeneity = ("q_statistic", "q_p_value", "i_squared", "tau_squared") for name, s, result in analyses: theirs = reference["replication"][name] - genes, g = result.genes, theirs["genes"] + genes, g = result.features, theirs["genes"] if any(not np.array_equal(genes[c], g[i]) for i, c in enumerate(counts)): worst["genes"] = np.inf for i, column in enumerate(values, len(counts)): @@ -771,8 +770,8 @@ def check_replication(analyses, reference): value = difference(genes[column], theirs["heterogeneity"][i], i == 1) worst[meta] = max(worst[meta], value) pairs, r = result.study_pairs, theirs["pairs"] - same = np.array_equal(pairs["n_found"], r[2]) - if not (same and np.array_equal(pairs["n_found_in_both"], r[3])): + same = np.array_equal(pairs["n_features_found"], r[2]) + if not (same and np.array_equal(pairs["n_features_found_in_both"], r[3])): worst["pairs"] = np.inf known = ~np.isnan(r[5]) worst["pairs"] = max(worst["pairs"], difference(pairs["sign_agreement"], r[4])) @@ -787,8 +786,8 @@ def check_replication(analyses, reference): compared += int(known.sum()) stops += int(np.count_nonzero(~known & ~np.isnan(pairs["pi1"]))) f, found_in = theirs["found_in"], result.found_in - same = np.array_equal(found_in["n_genes"], f[1]) - if not (same and np.array_equal(found_in["n_genes_same_direction"], f[2])): + same = np.array_equal(found_in["n_features"], f[1]) + if not (same and np.array_equal(found_in["n_features_same_direction"], f[2])): worst["found_in"] = np.inf n = len(analyses) reml = sum(result.settings["meta_method"] == "reml" for _, _, result in analyses) diff --git a/validation/composition_tests.py b/validation/composition_tests.py index 472a139..07616e7 100644 --- a/validation/composition_tests.py +++ b/validation/composition_tests.py @@ -57,9 +57,7 @@ import numpy as np -from cytepool.composition import propeller -from cytepool.cube import Cube -from cytepool.design import Design +from cytepool import Cube, Design, propeller FIXTURE_DIRECTORY = ( Path(__file__).resolve().parents[1] / "tests" / "data" / "composition_tests" @@ -188,9 +186,9 @@ def run(self, test, design, call): options = {key: call[key] for key in ("transform", "robust", "trend")} if test.startswith("F:"): result = propeller( - self.cube, design, contrasts=[], f_test=test[2:], **options + self.cube, design, contrasts=[], f_tests=test[2:], **options ) - return result.f_test + return result.table(test[2:]) tested = design.contrasts.select(test) return propeller(self.cube, design, contrasts=tested, **options).table(test) diff --git a/validation/cube.py b/validation/cube.py index 260e48c..81c0b4c 100644 --- a/validation/cube.py +++ b/validation/cube.py @@ -46,7 +46,7 @@ import numpy as np -from cytepool.cube import Cube +from cytepool import Cube SEED = 20261022 TARGET_SUM = 1e4 diff --git a/validation/cube_collection.py b/validation/cube_collection.py index e1f2daa..d3c23b4 100644 --- a/validation/cube_collection.py +++ b/validation/cube_collection.py @@ -33,7 +33,7 @@ ``covid`` with ``~ donor + condition``, the steps of ``validation/differential_expression.py`` (``min_cells=10``, ``filterByExpr``, TMM, ``voom`` with ``adaptive.span = TRUE``, ``lmFit``, ``eBayes``, ``topTable``). - ``limma_voom`` on every aligned cube, lined up by ``align_study_results``, must give + ``limma_voom`` on every aligned cube, lined up by ``StudyResults.table``, must give limma's tables, ``"not_measured"`` for every gene the study did not measure and ``"not_tested"`` for the others limma left out; ``limma_voom`` on the stack must give the joint model. A control fits the joint model with the donor ids as the @@ -68,11 +68,14 @@ import numpy as np -from cytepool.cube import CubeCollection, from_cells -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.io.anndata import to_anndata -from cytepool.meta_analysis import align_study_results +from cytepool import ( + CubeCollection, + Design, + StudyResults, + from_cells, + limma_voom, + to_anndata, +) SEED = 20261026 EXACT = 1e-10 @@ -88,7 +91,7 @@ "t", "p_value", "adjusted_p_value", - "b_statistic", + "log_odds", "standard_error", ) ONTOLOGY = {"B": "CL:0000236", "CD4": "CL:0000624", "CD8": "CL:0000625"} @@ -298,10 +301,10 @@ def simulate(rng, name, genes, groups, samples, cells, unmeasured=()): measured[list(unmeasured)] = False return from_cells( np.vstack(blocks), - samples=sample_labels, - groups=group_labels, - sample_table=samples, - group_table=groups, + sample_labels=sample_labels, + group_labels=group_labels, + samples=samples, + groups=groups, features={"feature_id": genes, "measured": measured}, source={"study": name, "simulation": "validation/cube_collection.py"}, ) @@ -548,8 +551,9 @@ def check_limma(collection, reference): cube = collection.aligned_cube(study) design = Design(cube.samples, FORMULAS[study], levels=levels) results[study] = limma_voom(cube, design, contrasts=contrast) + studies = StudyResults(collection, results) tables = { - group: align_study_results(collection, results, group, "stim") + group: studies.table(group, "stim") for group in collection.groups["group_id"].tolist() } rows = [] diff --git a/validation/cube_files.py b/validation/cube_files.py index 105191d..408fb05 100644 --- a/validation/cube_files.py +++ b/validation/cube_files.py @@ -40,8 +40,7 @@ import numpy as np -from cytepool.cube import Cube -from cytepool.io import read_cube, write_cube +from cytepool import Cube, read_cube, write_cube from cytepool.io.zarr import DATA_TYPES, open_array SEED = 20261023 diff --git a/validation/cube_fingerprints.py b/validation/cube_fingerprints.py index bccb352..e1f7af1 100644 --- a/validation/cube_fingerprints.py +++ b/validation/cube_fingerprints.py @@ -45,8 +45,7 @@ import numpy as np -from cytepool.cube import Cube -from cytepool.io import read_cube, write_cube +from cytepool import Cube, read_cube, write_cube SEED = 20261025 FIXTURE_DIR = ( diff --git a/validation/cube_reads.py b/validation/cube_reads.py index 32ffc70..dd6ee49 100644 --- a/validation/cube_reads.py +++ b/validation/cube_reads.py @@ -46,8 +46,7 @@ import numpy as np -from cytepool.cube import Cube -from cytepool.io import read_cube, write_cube +from cytepool import Cube, read_cube, write_cube from cytepool.io.zarr import open_array SEED = 20261024 diff --git a/validation/deseq2_tests.py b/validation/deseq2_tests.py index 88b27a8..83600c5 100644 --- a/validation/deseq2_tests.py +++ b/validation/deseq2_tests.py @@ -64,7 +64,7 @@ from scipy.optimize import minimize_scalar from scipy.special import gammaln -from cytepool.design import Design +from cytepool import Design from cytepool.stats.deseq2 import ( _count_score, _independent_filtering, diff --git a/validation/design.py b/validation/design.py index 9dde21d..d845e38 100644 --- a/validation/design.py +++ b/validation/design.py @@ -54,7 +54,8 @@ import numpy as np -from cytepool.design import Design, make_contrasts, model_matrix, parse_formula +from cytepool import Design +from cytepool.design import make_contrasts, model_matrix, parse_formula SEED = 20261023 N_SAMPLES = 24 diff --git a/validation/differential_expression.py b/validation/differential_expression.py index fe13e9a..9d72019 100644 --- a/validation/differential_expression.py +++ b/validation/differential_expression.py @@ -60,9 +60,7 @@ import numpy as np -from cytepool.cube import from_cells -from cytepool.design import Design -from cytepool.differential_expression import limma_trend, limma_voom +from cytepool import Design, from_cells, limma_trend, limma_voom FIXTURE_DIRECTORY = ( Path(__file__).resolve().parents[1] / "tests" / "data" / "differential_expression" @@ -77,7 +75,7 @@ "t": "t", "P.Value": "p_value", "adj.P.Val": "adjusted_p_value", - "B": "b_statistic", + "B": "log_odds", "SE": "standard_error", } @@ -202,7 +200,11 @@ def pooled(rng, samples, cells, n_genes): """The cube that from_cells pools from simulated cells, and the cells.""" counts, labels, types = simulate_cells(rng, samples, cells, n_genes) cube = from_cells( - counts, samples=labels, groups=types, sample_table=samples, layers="counts" + counts, + sample_labels=labels, + group_labels=types, + samples=samples, + layers="counts", ) return cube, {"counts": counts, "samples": labels, "groups": types} @@ -412,7 +414,7 @@ def compare(name, case, result, limma): ids = next(iter(tables.values()))["feature_id"] tested = np.isin(cube.features["feature_id"], ids) row = list(result.groups).index(group) - df_prior = result.summary["prior_df"][row] + df_prior = result.summary["df_prior"][row] prior = float(np.median(theirs["df_prior"])) # A contrast that one program tests and the other does not, other than the # deliberate differences. diff --git a/validation/differential_expression_options.py b/validation/differential_expression_options.py index b898754..8dc090e 100644 --- a/validation/differential_expression_options.py +++ b/validation/differential_expression_options.py @@ -67,9 +67,7 @@ import numpy as np -from cytepool.cube import from_cells -from cytepool.design import Design -from cytepool.differential_expression import limma_trend, limma_voom +from cytepool import Design, from_cells, limma_trend, limma_voom FIXTURE_DIRECTORY = ( Path(__file__).resolve().parents[1] @@ -86,7 +84,7 @@ "t": "t", "P.Value": "p_value", "adj.P.Val": "adjusted_p_value", - "B": "b_statistic", + "B": "log_odds", } CONTRASTS = {"stim_vs_ctrl": "conditionstim", "D3_vs_D2": "donorD3 - donorD2"} ACROSS = "adjusted_p_value_across_groups" @@ -256,7 +254,11 @@ def study(rng, n_donors, bounds, n_genes, outlier, shallow, few): rng, samples, cells, n_genes, outlier, shallow ) cube = from_cells( - counts, samples=labels, groups=types, sample_table=samples, layers="counts" + counts, + sample_labels=labels, + group_labels=types, + samples=samples, + layers="counts", ) design = Design( cube.samples, @@ -413,7 +415,7 @@ def compare(name, cube, result, limma, options): tested = np.isin( cube.features["feature_id"], tables["stim_vs_ctrl"]["feature_id"] ) - prior_df = result.summary["prior_df"][k] + prior_df = result.summary["df_prior"][k] their_prior = float(np.median(theirs["df_prior"])) mismatches = ( np.count_nonzero(used != theirs["used"].astype(bool)) diff --git a/validation/dream.py b/validation/dream.py index a87065d..e8a291e 100644 --- a/validation/dream.py +++ b/validation/dream.py @@ -61,9 +61,7 @@ import numpy as np -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import dream_voom +from cytepool import Cube, Design, dream_voom from cytepool.normalization import filter_by_expression, library_sizes, tmm_factors from cytepool.stats.dream import moderated_fixed_effect_tests, voom_with_dream_weights from cytepool.stats.mixed_models import fit_mixed_model, fixed_effect_tests @@ -323,7 +321,7 @@ def scaled_prior(weights: np.ndarray, n_samples: int) -> np.ndarray: def add_given_fits(case: dict[str, Any], voom: Any) -> None: """Fits for lme4 to evaluate: the trend's (maximum likelihood) and dream's (REML).""" design, genes = design_of(case), np.arange(N_GIVEN) - n = voom.log_cpm.shape[0] + n = voom.log2_cpm.shape[0] prior = ( np.ones((n, 1)) if case["weights"] is None else scaled_prior(case["weights"], n) ) @@ -334,12 +332,12 @@ def add_given_fits(case: dict[str, Any], voom: Any) -> None: ("reml", voom.weights[:, genes]), ): fit = fit_mixed_model( - voom.log_cpm[:, genes], design, weights=weights, method=kind + voom.log2_cpm[:, genes], design, weights=weights, method=kind ) theta = np.sqrt(fit.variance_components / fit.residual_variance[:, None]) case["fits"][kind] = fit case["given"][kind] = { - "y": voom.log_cpm[:, genes].T, + "y": voom.log2_cpm[:, genes].T, "w": weights.T, "reml": kind == "reml", "theta": theta, @@ -401,8 +399,8 @@ def compare_stats( max(weights), CLOSE, ) - fit = fit_mixed_model(voom.log_cpm, design, weights=voom.weights) - names, n = list(ref["names"]), fit.residual_df.size + fit = fit_mixed_model(voom.log2_cpm, design, weights=voom.weights) + names, n = list(ref["names"]), fit.df_residual.size matrix = np.zeros((len(design.columns), len(names))) for j, column in enumerate(names): for sign, term in zip( @@ -442,14 +440,14 @@ def compare_stats( relative( np.sqrt(mod.residual_variance_at_mean_weight), ref["sigma"] ), - relative(mod.residual_df, ref["rdf"]), + relative(mod.df_residual, ref["rdf"]), ), CLOSE, ) each = [ np.broadcast_to(np.asarray(value, float), (n,)) for value in ( - mod.prior_df, + mod.df_prior, r["df_prior"], mod.prior_variance, r["s2_prior"], @@ -468,7 +466,7 @@ def compare_stats( ) ours = { "t": mod.t, - "lods": mod.lods, + "lods": mod.log_odds, "df_total": mod.df_total, "p": mod.p_value, "adjusted": adjust_p_values(mod.p_value, "bh", axis=0), @@ -479,7 +477,7 @@ def compare_stats( f"{label}: prior df, estimator", "CytePool", "median, smallest, estimator", - f"{np.median(mod.prior_df):.4g}, {np.min(mod.prior_df):.4g}, {mod.prior_estimator}", + f"{np.median(mod.df_prior):.4g}, {np.min(mod.df_prior):.4g}, {mod.prior_estimator}", ) if "extreme_z" in ref: k = names.index(case["extreme_coef"]) @@ -507,7 +505,7 @@ def compare_stats( max( relative(given.log_likelihood, got["loglik"]), relative(given.residual_variance, got["sigma2"]), - relative(given.residual_df, got["rdf"]), + relative(given.df_residual, got["rdf"]), relative( given.fitted_values, np.asarray(got["fitted"]).T, @@ -677,7 +675,7 @@ def compare_cube( ) ours = { "t": table["t"], - "lods": table["b_statistic"], + "lods": table["log_odds"], "df_total": table["df"], "p": table["p_value"], "adjusted": table["adjusted_p_value"], @@ -828,7 +826,7 @@ def main() -> int: case, ref = deviations["reproduced"], refs["reproduced"] voom = voom_with_dream_weights(case["counts"], design_of(case), span=0.5) t = fixed_effect_tests( - fit_mixed_model(voom.log_cpm, design_of(case), weights=voom.weights) + fit_mixed_model(voom.log2_cpm, design_of(case), weights=voom.weights) ).t[0, 1] in_r = ( f"{ref['sqrt_sd'][ref['kept'].index(1)]:.2g}" if 1 in ref["kept"] else "dropped" diff --git a/validation/empirical_bayes.py b/validation/empirical_bayes.py index fb0beb7..0c9de99 100644 --- a/validation/empirical_bayes.py +++ b/validation/empirical_bayes.py @@ -571,8 +571,8 @@ def model_results(case, run): eb = moderated_statistics(fit, f_statistic="limma", **options) tr = treat(fit, lfc=LFC, **options) out = { - "t": eb.t, "p.value": eb.p_value, "lods": eb.lods, "F": eb.f_statistic, - "F.p.value": eb.f_p_value, "s2.prior": eb.prior_variance, "df.prior": eb.prior_df, + "t": eb.t, "p.value": eb.p_value, "lods": eb.log_odds, "F": eb.f_statistic, + "F.p.value": eb.f_p_value, "s2.prior": eb.prior_variance, "df.prior": eb.df_prior, "s2.post": eb.posterior_variance, "df.total": eb.df_total, "var.prior": eb.coefficient_prior_variance, "treat.p": tr.p_value, # limma reports t = 0 for coefficients that are NaN; here they stay NaN @@ -603,7 +603,7 @@ def variance_results(case, run): out = { "var.post": squeezed.posterior_variance, "var.prior": squeezed.prior_variance, - "df.prior": squeezed.prior_df, + "df.prior": squeezed.df_prior, } if legacy == "FALSE": fit = fit_f_distribution_likelihood( diff --git a/validation/gene_set_files_and_ora.py b/validation/gene_set_files_and_ora.py index 20caa29..3e7dc97 100644 --- a/validation/gene_set_files_and_ora.py +++ b/validation/gene_set_files_and_ora.py @@ -62,13 +62,9 @@ import matplotlib.figure import numpy as np -from cytepool import plotting -from cytepool.gene_sets import read_gmt, write_gmt -from cytepool.over_representation import over_representation +from cytepool import Table, camera, over_representation, plotting, read_gmt, write_gmt from cytepool.stats.distributions import hypergeom_sf -from cytepool.stats.gene_set_tests import camera from cytepool.stats.multiple_testing import adjust_p_values -from cytepool.table import Table FIXTURES = Path(__file__).resolve().parents[1] / "tests/data/gene_set_files_and_ora" EXACT = 1e-10 @@ -498,7 +494,7 @@ def check_matching(root, matching, reference, table): table.append((check, "ids2indices", "differences", int(not same), 0)) kept = restricted.filter_by_size(15, 200) design = np.column_stack([np.ones(8), np.repeat([0.0, 1.0], 4)]) - tested = camera(y.T, design, 1, kept, gene_ids=identifiers) + tested = camera(y.T, kept, design=design, contrast=1, feature_ids=identifiers) value = relative(tested["p_value"], reference["camera"]) value = value if list(kept) == reference["camera names"] else np.inf check = f"filter_by_size(15, 200), then camera of the {len(kept)} sets" diff --git a/validation/gene_set_tests.py b/validation/gene_set_tests.py index d16d715..9312a5a 100644 --- a/validation/gene_set_tests.py +++ b/validation/gene_set_tests.py @@ -1,6 +1,6 @@ """Check the gene-set tests camera and fry against limma. -Compares ``cytepool.stats.gene_set_tests`` with limma's ``camera`` and ``fry`` on +Compares ``cytepool.gene_set_tests`` with limma's ``camera`` and ``fry`` on log-expression simulated from fixed seeds, with gene variances from a scaled inverse chi-squared prior around a mean-variance trend. The sets change up, down or in both directions, or not at all, share random sample effects (inter-gene correlation) with @@ -56,8 +56,7 @@ import numpy as np -from cytepool.design import Design -from cytepool.stats.gene_set_tests import camera, fry +from cytepool import Design, camera, fry from cytepool.stats.voom import voom FIXTURE_DIRECTORY = ( @@ -96,7 +95,7 @@ COLUMNS = { "NGenes": "n_genes", "Correlation": "correlation", "Direction": "direction", "PValue": "p_value", "FDR": "adjusted_p_value", "PValue.Mixed": "mixed_p_value", - "FDR.Mixed": "adjusted_mixed_p_value", + "FDR.Mixed": "mixed_adjusted_p_value", } # fmt: skip R_SCRIPT = r""" @@ -265,7 +264,7 @@ def add(name, design, y, sets, contrast, runs, *, weights=None, voom_result=None counts = rng.poisson(np.exp2(expression.T) * rng.uniform(0.5, 2.0, 12)[:, None]) result = voom(counts, design) for name, contrast in (("voom", "conditionstim"), ("voom_weights", "stim")): - add(name, design, result.log_cpm.T, sets, contrast, camera_runs + fry_runs, + add(name, design, result.log2_cpm.T, sets, contrast, camera_runs + fry_runs, voom_result=result) # fmt: skip paired = Design( @@ -375,13 +374,14 @@ def cytepool_results(case, run): genes = [f"g{i:04d}" for i in range(case["y"].shape[0])] sets = {name: [genes[i] for i in rows] for name, rows in case["sets"].items()} data = case["voom"] if case["voom"] is not None else case["y"].T - common = {"gene_ids": genes, "weights": case["weights"], "trend": bool(trend)} + common = {"design": design, "contrast": case["contrast"], "feature_ids": genes} + common |= {"weights": case["weights"], "trend": bool(trend)} if test == "fry": style = {"none": "none", "residual.sd": "residual_sd"} - return fry(data, design, case["contrast"], sets, robust=bool(robust), + return fry(data, sets, robust=bool(robust), standardize=style.get(standardize, "posterior_sd"), **common) # fmt: skip return camera( - data, design, case["contrast"], sets, robust=bool(robust), + data, sets, robust=bool(robust), inter_gene_correlation=None if cor == "NA" else float(cor), allow_negative_correlation=bool(neg), use_ranks=bool(ranks), **common, ) # fmt: skip diff --git a/validation/gsea.py b/validation/gsea.py index 8788de1..1a207fd 100644 --- a/validation/gsea.py +++ b/validation/gsea.py @@ -61,8 +61,7 @@ import matplotlib.figure import numpy as np -from cytepool import plotting -from cytepool.gsea import gsea +from cytepool import gsea, plotting FIXTURES = Path(__file__).resolve().parents[1] / "tests/data/gsea" EXACT = 1e-10 diff --git a/validation/interferon_stimulated_pbmcs.py b/validation/interferon_stimulated_pbmcs.py index 0549382..7e7c911 100644 --- a/validation/interferon_stimulated_pbmcs.py +++ b/validation/interferon_stimulated_pbmcs.py @@ -78,9 +78,7 @@ import scipy.sparse import cytepool -from cytepool.cube import from_cells -from cytepool.design import Design -from cytepool.differential_expression import limma_voom +from cytepool import Design, from_cells, limma_voom SOURCE = "https://ftp.ncbi.nlm.nih.gov/geo/series/GSE96nnn/GSE96583/suppl/" FILES = { # bytes and SHA-256, as downloaded on 2026-10-25 @@ -147,7 +145,7 @@ "t": "t", "P.Value": "p_value", "adj.P.Val": "adjusted_p_value", - "B": "b_statistic", + "B": "log_odds", } R_SCRIPT = r""" @@ -324,9 +322,9 @@ def make_cube(cells, labels, genes): } return from_cells( cells, - samples=labels["sample"], - groups=labels["group"], - sample_table=samples, + sample_labels=labels["sample"], + group_labels=labels["group"], + samples=samples, features={"feature_id": [g[0] for g in genes], "symbol": [g[1] for g in genes]}, layers="counts", ) @@ -450,7 +448,7 @@ def compare(label, cube, result, limma): used = result.samples["used"][result.samples["group_id"] == group] tested = np.isin(cube.features["feature_id"], table["feature_id"]) # Equal, also when both are infinite, or close; a NaN is a mismatch. - prior, limma_prior = result.summary["prior_df"][k], theirs["df"][1] + prior, limma_prior = result.summary["df_prior"][k], theirs["df"][1] same_prior = prior == limma_prior or difference(prior, limma_prior) <= EXACT span = voom_span(result, group) mismatches = ( diff --git a/validation/kenward_roger_and_f_tests.py b/validation/kenward_roger_and_f_tests.py index 97a5f12..9ed0dc1 100644 --- a/validation/kenward_roger_and_f_tests.py +++ b/validation/kenward_roger_and_f_tests.py @@ -69,9 +69,7 @@ import numpy as np -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import dream_voom +from cytepool import Cube, Design, dream_voom from cytepool.normalization import filter_by_expression, library_sizes, tmm_factors from cytepool.stats.distributions import f_sf from cytepool.stats.dream import moderated_fixed_effect_tests, voom_with_dream_weights @@ -625,7 +623,7 @@ def ours_study(case: dict[str, Any], method: str) -> tuple[Any, Any, Any]: coefficient and contrast, and the moderated F-test of time.""" design = Design(case["samples"], FIXED, random=RANDOM) voom = voom_with_dream_weights(case["counts"], design, span=0.5) - fit = fit_mixed_model(voom.log_cpm, design, weights=voom.weights) + fit = fit_mixed_model(voom.log2_cpm, design, weights=voom.weights) tests = fixed_effect_tests(fit, CONTRASTS, df_method=method) time = fixed_effect_tests( fit, {k: CONTRASTS[k] for k in ("timet1", "timet2")}, df_method=method @@ -706,7 +704,7 @@ def compare_study( f"t, B, z: {SCALED}; df.total: {RELATIVE}; p: {P_VALUE}", max( relative(moderated.t, pick["t"], 1.0), - relative(moderated.lods, pick["lods"], 1.0), + relative(moderated.log_odds, pick["lods"], 1.0), relative(moderated.z, pick["z"], 1.0), relative(moderated.df_total, pick["df_total"]), p_value_difference(moderated.p_value, pick["p"]), @@ -857,7 +855,7 @@ def compare_cube( table, k = result.table(group, name), r["names"].index(name) for column, key, floor in ( ("t", "t", 1.0), - ("b_statistic", "lods", 1.0), + ("log_odds", "lods", 1.0), ("z_statistic", "z", 1.0), ("df", "df_total", 0.0), ): diff --git a/validation/meta_analysis.py b/validation/meta_analysis.py index 3e5614c..b515415 100644 --- a/validation/meta_analysis.py +++ b/validation/meta_analysis.py @@ -1,4 +1,4 @@ -"""Check meta_analysis and differential_expression_meta_analysis against metafor. +"""Check fit_meta_analysis and meta_analysis against metafor. Simulates the effects of many features in several studies, with their standard errors, from fixed seeds (in ``main``), and meta-analyses every feature with CytePool and with @@ -41,7 +41,7 @@ cohort. R reads the log2 fold changes and standard errors of each cohort's tables, merges them by feature id, meta-analyses every feature that at least ``min_studies`` cohorts tested and adjusts the p-values by ``p.adjust(method = "BH")``. Each row -compares the features and every column of ``differential_expression_meta_analysis`` +compares the features and every column of ``meta_analysis`` for one setting and cell type, in the tiers above. Behaviour rows: one study with the Knapp-Hartung adjustment (metafor falls back to a z-test, as CytePool does); a Paule-Mandel estimate above metafor's search interval; and zero and infinite standard @@ -68,10 +68,8 @@ import numpy as np -from cytepool.cube import Cube -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.meta_analysis import differential_expression_meta_analysis, meta_analysis +from cytepool import Cube, Design, limma_voom, meta_analysis +from cytepool.stats.meta_analysis import fit_meta_analysis FIXTURE_DIRECTORY = ( Path(__file__).resolve().parents[1] / "tests" / "data" / "meta_analysis" @@ -320,9 +318,7 @@ def cohort_analyses(built): results = {name: limma_voom(cube, design) for name, (cube, design) in built.items()} analyses = [] for method, test, fewest in SETTINGS: - meta = differential_expression_meta_analysis( - results, method=method, test=test, min_studies=fewest - ) + meta = meta_analysis(results, method=method, test=test, min_studies=fewest) for key, table in meta.tables.items(): tables = { name: r.tables[key] for name, r in results.items() if key in r.tables @@ -346,7 +342,7 @@ def run_metafor(cases, analyses): (directory / "shape.txt").write_text(f"{y.shape[0]}\n{y.shape[1]}\n") write_binary(directory / "y.bin", y.T) # R reads columns: features write_binary(directory / "se.bin", se.T) - write_binary(directory / "reml.bin", meta_analysis(y, se).tau_squared) + write_binary(directory / "reml.bin", fit_meta_analysis(y, se).tau_squared) for method in METHODS.values(): runs += [f"case{c}\t{method}\t{test}\ttight" for test in TESTS.values()] runs += [f"case{c}\t{method}\tz\tdefault" for method in ("REML", "PM")] @@ -458,7 +454,7 @@ def compare_cases(cases, outputs, likelihoods): if retried: label += f" (metafor needed stepadj = 0.5 for {retried})" for test, test_code in TESTS.items(): - fit = meta_analysis(y, se, method=method, test=test) + fit = fit_meta_analysis(y, se, method=method, test=test) theirs = outputs[name, code, test_code, "tight"] for field, ours, reference in zip( FIELDS, ours_as_metafor(fit), theirs[:12], strict=True @@ -471,7 +467,7 @@ def compare_cases(cases, outputs, likelihoods): if method == "reml" and higher.any(): rows.append(maxima_row(name, int(np.count_nonzero(higher)), gain)) for method, compared in (("reml", ~higher), ("pm", ~np.zeros_like(higher))): - fit = meta_analysis(y, se, method=method) + fit = fit_meta_analysis(y, se, method=method) theirs = outputs[name, METHODS[method], "z", "default"] pairs = zip(FIELDS, ours_as_metafor(fit), theirs[:12], strict=True) largest = max(difference(o[compared], r[compared], f) for f, o, r in pairs) @@ -516,7 +512,7 @@ def largest(fit, values): pairs = zip(FIELDS, ours_as_metafor(fit), values, strict=True) return max(difference(o, r, f) for f, o, r in pairs) - one = meta_analysis([0.5], [0.2], test="knapp_hartung") + one = fit_meta_analysis([0.5], [0.2], test="knapp_hartung") theirs = probes["one_study_knha"] agree = "values" in theirs and largest(one, theirs["values"]) <= EXACT rows = [ @@ -526,7 +522,7 @@ def largest(fit, values): ) + ("n/a", "pass" if agree else "FAIL") ] - far = meta_analysis([-40.0, 0.0, 0.0, 0.0, 40.0], np.ones(5), method="pm") + far = fit_meta_analysis([-40.0, 0.0, 0.0, 0.0, 40.0], np.ones(5), method="pm") value = largest(far, probes["pm_interval_raised"]["values"]) check = "Paule-Mandel above metafor's interval, against tau2.max = 1e6" rows.append(row(check, "rma.uni", value, CLOSE)) @@ -542,7 +538,7 @@ def largest(fit, values): ("infinite standard error", [0.2, np.inf, 0.22, 0.45], ("REML",)), ): try: - meta_analysis(y, se) + fit_meta_analysis(y, se) refused = False except ValueError: refused = True diff --git a/validation/mixed_models.py b/validation/mixed_models.py index e687c55..5663704 100644 --- a/validation/mixed_models.py +++ b/validation/mixed_models.py @@ -61,7 +61,7 @@ import numpy as np -from cytepool.design import Design +from cytepool import Design from cytepool.stats.mixed_models import fit_mixed_model, fixed_effect_tests FIXTURE_DIRECTORY = ( diff --git a/validation/negative_binomial_fits.py b/validation/negative_binomial_fits.py index 8bf42e0..a69c146 100644 --- a/validation/negative_binomial_fits.py +++ b/validation/negative_binomial_fits.py @@ -69,7 +69,7 @@ import mpmath import numpy as np -from cytepool.design import Design +from cytepool import Design from cytepool.normalization import tmm_factors from cytepool.stats.negative_binomial_models import ( fit_negative_binomial_model, diff --git a/validation/normalization.py b/validation/normalization.py index cdb99c2..f42bba8 100644 --- a/validation/normalization.py +++ b/validation/normalization.py @@ -41,6 +41,9 @@ TMM_KWARGS = "reference log_ratio_trim sum_trim weighted a_cutoff library_sizes".split() METRICS = {"logcpm": "max abs diff of log2", "filter": "features that differ"} METRICS["proportional"] = "max rel spread of the ratio" +# calls.json, which the tests read, names the function log2_cpm and the argument +# sample_groups of filter_by_expression as they were named when it was written. +FIXTURE_NAMES = {"log2_cpm": "log_cpm", "sample_groups": "group"} # kind | check | R call | CytePool function and keyword arguments ($v: case variable v) PER_DATASET = """ @@ -54,18 +57,18 @@ cpm | CPM | cpm(Y) | cpm cpm | CPM, normalization factors | cpm(DGEList(Y, norm.factors=nf)) | cpm norm_factors=$nf cpm | CPM, library sizes given | cpm(Y, lib.size=L) | cpm library_sizes=$L -logcpm | logCPM, prior count 2 | cpm(Y, log=TRUE, prior.count=2) | log_cpm prior_count=2 -logcpm | logCPM, prior count 0.5 | cpm(Y, log=TRUE, prior.count=0.5) | log_cpm prior_count=0.5 -logcpm | logCPM, prior count 5 | cpm(Y, log=TRUE, prior.count=5) | log_cpm prior_count=5 -logcpm | logCPM, prior count 2, normalization factors | cpm(DGEList(Y, norm.factors=nf), log=TRUE) | log_cpm norm_factors=$nf -logcpm | logCPM, prior count 2, library sizes given | cpm(Y, lib.size=L, log=TRUE) | log_cpm library_sizes=$L -filter | filterByExpr, group | filterByExpr(Y, group=factor(g)) | filter_by_expression group=$g -filter | filterByExpr, group, min.count 5, min.total.count 10 | filterByExpr(Y, group=factor(g), min.count=5, min.total.count=10) | filter_by_expression group=$g min_count=5 min_total_count=10 -filter | filterByExpr, group, large.n 2, min.prop 0.5 | filterByExpr(Y, group=factor(g), large.n=2, min.prop=0.5) | filter_by_expression group=$g large_n=2 min_prop=0.5 +logcpm | logCPM, prior count 2 | cpm(Y, log=TRUE, prior.count=2) | log2_cpm prior_count=2 +logcpm | logCPM, prior count 0.5 | cpm(Y, log=TRUE, prior.count=0.5) | log2_cpm prior_count=0.5 +logcpm | logCPM, prior count 5 | cpm(Y, log=TRUE, prior.count=5) | log2_cpm prior_count=5 +logcpm | logCPM, prior count 2, normalization factors | cpm(DGEList(Y, norm.factors=nf), log=TRUE) | log2_cpm norm_factors=$nf +logcpm | logCPM, prior count 2, library sizes given | cpm(Y, lib.size=L, log=TRUE) | log2_cpm library_sizes=$L +filter | filterByExpr, group | filterByExpr(Y, group=factor(g)) | filter_by_expression sample_groups=$g +filter | filterByExpr, group, min.count 5, min.total.count 10 | filterByExpr(Y, group=factor(g), min.count=5, min.total.count=10) | filter_by_expression sample_groups=$g min_count=5 min_total_count=10 +filter | filterByExpr, group, large.n 2, min.prop 0.5 | filterByExpr(Y, group=factor(g), large.n=2, min.prop=0.5) | filter_by_expression sample_groups=$g large_n=2 min_prop=0.5 filter | filterByExpr, design with a covariate | filterByExpr(Y, design=X) | filter_by_expression design=$X filter | filterByExpr, design without an intercept | filterByExpr(Y, design=X0) | filter_by_expression design=$X0 filter | filterByExpr, no group or design (one group) | filterByExpr(Y) | filter_by_expression -filter | filterByExpr, normalization factors | filterByExpr(DGEList(Y, group=factor(g), norm.factors=nf)) | filter_by_expression group=$g norm_factors=$nf +filter | filterByExpr, normalization factors | filterByExpr(DGEList(Y, group=factor(g), norm.factors=nf)) | filter_by_expression sample_groups=$g norm_factors=$nf """ PER_SMALL_MATRIX = """ factor | TMM factors, default reference, 300 small tie-heavy matrices | calcNormFactors(Y, lib.size=L) | tmm_factors library_sizes=$L @@ -85,19 +88,19 @@ factor | median upper quartile 1e-20: documented reference | calcNormFactors(Y, lib.size=L) | tmm_factors library_sizes=$L factor | median upper quartile below 1e-20: square-root reference | calcNormFactors(Y, lib.size=L) | tmm_factors library_sizes=$L factor | identical above the A cutoff only | calcNormFactors(Y, lib.size=L, refColumn=2, Acutoff=ac) | tmm_factors library_sizes=$L reference=1 a_cutoff=$ac -filter | count at the CPM cutoff, L = 100036 | filterByExpr(Y, group=factor(g), lib.size=L) | filter_by_expression group=$g library_sizes=$L -filter | count at the CPM cutoff, L = 100015 | filterByExpr(Y, group=factor(g), lib.size=L) | filter_by_expression group=$g library_sizes=$L +filter | count at the CPM cutoff, L = 100036 | filterByExpr(Y, group=factor(g), lib.size=L) | filter_by_expression sample_groups=$g library_sizes=$L +filter | count at the CPM cutoff, L = 100015 | filterByExpr(Y, group=factor(g), lib.size=L) | filter_by_expression sample_groups=$g library_sizes=$L filter | all-zero design, taken as an intercept | filterByExpr(Y, design=X) | filter_by_expression design=$X filter | near-collinear design, eps = 1.5e-07 | filterByExpr(Y, design=X) | filter_by_expression design=$X filter | near-collinear design, eps = 2.5e-07 | filterByExpr(Y, design=X) | filter_by_expression design=$X -filter | sample threshold 2 + 8.9e-15 | filterByExpr(Y, group=factor(g), large.n=0, min.prop=mp) | filter_by_expression group=$g large_n=0 min_prop=$mp -filter | sample threshold 2 + 1.1e-14 | filterByExpr(Y, group=factor(g), large.n=0, min.prop=mp) | filter_by_expression group=$g large_n=0 min_prop=$mp +filter | sample threshold 2 + 8.9e-15 | filterByExpr(Y, group=factor(g), large.n=0, min.prop=mp) | filter_by_expression sample_groups=$g large_n=0 min_prop=$mp +filter | sample threshold 2 + 1.1e-14 | filterByExpr(Y, group=factor(g), large.n=0, min.prop=mp) | filter_by_expression sample_groups=$g large_n=0 min_prop=$mp """ # Inputs that CytePool rejects, where R returns values (deviation) or fails too (error). DEVIATIONS = """ deviation | all-zero sample: edgeR TMM with refColumn gives it factor 1 | calcNormFactors(Y, refColumn=1) | tmm_factors deviation | upper quartile of zero: edgeR returns NaN factors | calcNormFactors(Y, method='upperquartile') | upper_quartile_factors -deviation | group and design both given: edgeR ignores the design | filterByExpr(Y, group=factor(g), design=X) | filter_by_expression group=$g design=$X +deviation | group and design both given: edgeR ignores the design | filterByExpr(Y, group=factor(g), design=X) | filter_by_expression sample_groups=$g design=$X error | negative count: edgeR normLibSizes rejects it too | normLibSizes(Y) | tmm_factors """ @@ -408,7 +411,7 @@ def main(): parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) parser.add_argument("--write-fixtures", action="store_true") args = parser.parse_args() - warnings.simplefilter("ignore", UserWarning) # filter_by_expression without a group + warnings.simplefilter("ignore", UserWarning) # no sample_groups or design given data, small, edges = datasets(), small_matrices(), edge_cases() cases = {**data, **small, **edges, **deviation_cases()} rows = checks(data, small) @@ -514,8 +517,12 @@ def place(name, values): if kind in ("cpm", "logcpm"): # keep the first 40 features ref = ref.reshape(case["Y"].shape[0], -1)[:, :40] output = place("outputs", ref) + recorded = dict( + function=FIXTURE_NAMES.get(spec["function"], spec["function"]), + kwargs={FIXTURE_NAMES.get(k, k): v for k, v in spec["kwargs"].items()}, + ) records.append( - dict(check=check, case=name, kind=kind, r_call=r, output=output, **spec) + dict(check=check, case=name, kind=kind, r_call=r, output=output, **recorded) ) FIXTURES.mkdir(parents=True, exist_ok=True) arrays = {name: np.concatenate(values) for name, values in stores.items()} diff --git a/validation/pathway_scores.py b/validation/pathway_scores.py index 969d66b..5135eac 100644 --- a/validation/pathway_scores.py +++ b/validation/pathway_scores.py @@ -60,9 +60,8 @@ import matplotlib.figure import numpy as np -from cytepool.cube import Cube -from cytepool.normalization import filter_by_expression, log_cpm, tmm_factors -from cytepool.pathway_scores import combined_z_scores, gsva, plage, ssgsea +from cytepool import Cube, combined_z_scores, gsva, plage, ssgsea +from cytepool.normalization import filter_by_expression, log2_cpm, tmm_factors from cytepool.plotting import plot_pathway_scores FIXTURES = Path(__file__).resolve().parents[1] / "tests/data/pathway_scores" @@ -159,9 +158,9 @@ def cube_of(rng, genes, counts): n_cells=cells[:, np.newaxis], ) kept = pooled[(pooled.sum(axis=1) > 0) & (cells >= 10)] - keep = filter_by_expression(kept, group=np.zeros(kept.shape[0])) + keep = filter_by_expression(kept, sample_groups=np.zeros(kept.shape[0])) kept = kept[:, keep] - values = log_cpm(kept, norm_factors=tmm_factors(kept), prior_count=2.0) + values = log2_cpm(kept, norm_factors=tmm_factors(kept), prior_count=2.0) return cube, values, kept, [g for g, k in zip(genes, keep, strict=True) if k] @@ -301,11 +300,11 @@ def run_cytepool(case, data, sets, cube): function = FUNCTIONS[case["method"]] options = options_of(case) if case["data"].startswith("cube"): - values = "counts" if case["data"] == "cube_counts" else "log_cpm" + values = "counts" if case["data"] == "cube_counts" else "log2_cpm" return function(cube, sets[case["sets"]], values=values, **options) values, genes, integer = data[case["data"]] values = values.astype(np.int64) if integer else values - return function(values, sets[case["sets"]], gene_ids=genes, **options) + return function(values, sets[case["sets"]], feature_ids=genes, **options) # Comparisons. @@ -420,11 +419,11 @@ def benchmark(data, sets, runs, seconds): values, genes, integer = data[run["data"]] values = values.astype(np.int64) if integer else values function, options = FUNCTIONS[run["method"]], options_of(run) - function(values[:4, :50], {"s": genes[:5]}, gene_ids=genes[:50], **options) + function(values[:4, :50], {"s": genes[:5]}, feature_ids=genes[:50], **options) best = np.inf for _ in range(3): start = time.perf_counter() - function(values, sets["bench"], gene_ids=genes, **options) + function(values, sets["bench"], feature_ids=genes, **options) best = min(best, time.perf_counter() - start) label = f"{run['method']} {run['options'].get('kcdf', '')}".strip() print(f"| {label} | {reference:.2f} | {best:.2f} |") diff --git a/validation/pca_and_dot_plots.py b/validation/pca_and_dot_plots.py index e607628..e0d1d40 100644 --- a/validation/pca_and_dot_plots.py +++ b/validation/pca_and_dot_plots.py @@ -52,10 +52,8 @@ import numpy as np from matplotlib.figure import Figure -from cytepool import plotting -from cytepool.cube import Cube, from_cells -from cytepool.normalization import log_cpm, tmm_factors -from cytepool.principal_components import principal_components +from cytepool import Cube, from_cells, plotting, principal_components +from cytepool.normalization import log2_cpm, tmm_factors FIXTURE_DIRECTORY = ( Path(__file__).resolve().parents[1] / "tests" / "data" / "pca_and_dot_plots" @@ -86,6 +84,8 @@ }, "genes in groups": {"genes": MARKERS}, } +# The cases' standard_scale, as the scanpy program reads it, and plot_dotplot's. +STANDARD_SCALE = {"gene": "feature", "group": "group", None: None} R_PROGRAM = r""" suppressPackageStartupMessages({ @@ -231,7 +231,7 @@ def pca_data() -> dict[str, np.ndarray]: effects[1::2, rng.choice(n_genes, 30, replace=False)] += 0.7 # batch y mu = means * np.exp(effects) * rng.uniform(0.6, 1.6, (12, 1)) counts = rng.negative_binomial(8.0, 8.0 / (8.0 + mu)).astype(np.float64) - values = log_cpm(counts, norm_factors=tmm_factors(counts)) + values = log2_cpm(counts, norm_factors=tmm_factors(counts)) # 18 genes of distinct, large variance, then 4 of variance 12 (permutations of # one profile, whose variances are equal to the last bit), then 6 small ones. base = np.array([0.0, 1.0, 3.0, 4.0, 7.0, 9.0] * 2) @@ -281,10 +281,10 @@ def dot_data() -> dict[str, np.ndarray]: def dot_cube(data: dict[str, np.ndarray]) -> Cube: return from_cells( data["counts"], - samples=data["samples"], - groups=data["groups"], - sample_table={"sample_id": CELL_SAMPLES}, - group_table={"group_id": GROUPS}, + sample_labels=data["samples"], + group_labels=data["groups"], + samples={"sample_id": CELL_SAMPLES}, + groups={"group_id": GROUPS}, features=data["genes"].tolist(), layers=["counts", "detected", "log_sum"], ) @@ -436,7 +436,7 @@ def pca_rows(data: dict[str, np.ndarray], r: dict[str, Any]) -> list[tuple]: theirs = np.column_stack([r["ties"]["x"], r["ties"]["y"]]) difference, flips = up_to_sign(result.coordinates[:, :2], theirs) flipped += flips - keeps_first = result.genes.tolist()[-2:] == [18, 19] + keeps_first = result.features["position"].tolist()[-2:] == [18, 19] rows.append( row( "Genes 19 to 22 of equal variance, the 20 of largest variance: the first " @@ -525,7 +525,7 @@ def dot_rows(cube: Cube, scanpy: dict[str, Any]) -> list[tuple]: case["genes"], groups=case["groups"], samples=case["samples"], - standard_scale=case["standard_scale"], + standard_scale=STANDARD_SCALE[case["standard_scale"]], mean_only_expressed=case["mean_only_expressed"], ax=axes(), ) diff --git a/validation/plotting.py b/validation/plotting.py index 029fb99..7fa1a74 100644 --- a/validation/plotting.py +++ b/validation/plotting.py @@ -37,12 +37,12 @@ for information. - ``plot_forest`` against metafor's ``forest``: three studies of 4, 5 and 6 donors, tested by ``limma_voom`` (the third does not measure 5 genes) and meta-analysed by - ``differential_expression_meta_analysis`` by REML with z-tests and by - DerSimonian-Laird with the Knapp-Hartung adjustment. For three genes, one tested by - two studies, R refits ``rma.uni`` on the studies' log2 fold changes and standard - errors (REML to convergence, ``control = list(threshold = 1e-13)``); the studies' - intervals are compared with those forest draws, and the pooled estimate and interval - with those it writes beside its diamond, which it clips at the axis limits. + ``meta_analysis`` by REML with z-tests and by DerSimonian-Laird with the + Knapp-Hartung adjustment. For three genes, one tested by two studies, R refits + ``rma.uni`` on the studies' log2 fold changes and standard errors (REML to + convergence, ``control = list(threshold = 1e-13)``); the studies' intervals are + compared with those forest draws, and the pooled estimate and interval with those it + writes beside its diamond, which it clips at the axis limits. - ``plot_composition`` against speckle's ``plotCellTypeProps``, and ``plot_proportions`` against the proportions, ``PropMean`` and ``FDR`` of ``propeller``, on the cell numbers of 5 cell types in 12 samples of two conditions. @@ -78,13 +78,16 @@ import numpy as np from matplotlib.figure import Figure -from cytepool import plotting -from cytepool.composition import propeller -from cytepool.cube import Cube, from_bulk -from cytepool.design import Design -from cytepool.differential_expression import limma_voom -from cytepool.meta_analysis import differential_expression_meta_analysis -from cytepool.normalization import log_cpm, tmm_factors +from cytepool import ( + Cube, + Design, + from_bulk, + limma_voom, + meta_analysis, + plotting, + propeller, +) +from cytepool.normalization import log2_cpm, tmm_factors from cytepool.stats.deseq2 import fit_deseq2 from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import fit_linear_model @@ -747,11 +750,11 @@ def check_count_models(ref, counts, design, factors): return rows -def check_forests(ref, results, metas, features): +def check_forests(ref, metas, features): rows = [] for (method, test), meta in zip(FOREST_CASES, metas, strict=True): for feature in features: - ax = plotting.plot_forest(meta, results, feature, ax=axes()) + ax = plotting.plot_forest(meta, feature, ax=axes()) segments = sorted( artists(ax, "intervals")[0].get_segments(), key=lambda s: -s[0, 1] ) @@ -869,7 +872,7 @@ def main(): levels={"condition": ["ctrl", "stim"]}, contrasts={"stim": "conditionstim"}, ) - E = log_cpm(counts) + E = log2_cpm(counts) noise = rng.normal(0.0, 1.5, (8, 6)) noisy = E.copy() noisy[:, 10:16] += noise @@ -908,7 +911,7 @@ def main(): by_study = study_counts(rng, counts) results = study_results(by_study) metas = [ - differential_expression_meta_analysis(results, method=method, test=test) + meta_analysis(results, method=method, test=test) for method, test in FOREST_CASES ] features = forest_features(metas[0].table("bulk", "stim")) @@ -937,7 +940,7 @@ def main(): rows += check_tests(ref, table) rows += check_barcodes(ref, t, members, members_span) rows += check_count_models(ref, counts, design, factors) - rows += check_forests(ref, results, metas, features) + rows += check_forests(ref, metas, features) rows += check_composition(ref, composition_data) print("| check | reference | metric | value | tolerance | result |") print("| --- | --- | --- | --- | --- | --- |") diff --git a/validation/pool_cells.py b/validation/pool_cells.py index 9730398..01dbf2b 100644 --- a/validation/pool_cells.py +++ b/validation/pool_cells.py @@ -51,7 +51,7 @@ import numpy as np import scipy.sparse as sp -from cytepool.cube import Cube, from_bulk, from_cells +from cytepool import Cube, from_bulk, from_cells SEED = 20261023 TARGET_SUM = 1e4 @@ -668,7 +668,10 @@ def main() -> int: def pool(counts: Any, **options: Any) -> Cube: return from_cells( - counts, samples=integer["sample"], groups=integer["group"], **options + counts, + sample_labels=integer["sample"], + group_labels=integer["group"], + **options, ) start = time.perf_counter() diff --git a/validation/quasi_likelihood.py b/validation/quasi_likelihood.py index 0c6a021..dc77e5e 100644 --- a/validation/quasi_likelihood.py +++ b/validation/quasi_likelihood.py @@ -64,7 +64,7 @@ from cytepool.normalization import tmm_factors from cytepool.stats.empirical_bayes import squeeze_variances from cytepool.stats.quasi_likelihood import ( - average_log_cpm, + average_log2_cpm, common_dispersion, fit_quasi_likelihood, negative_binomial_unit_deviance_moments, @@ -488,7 +488,7 @@ def check(label, reference, metric, value, tolerance, robust_prior=False): tolerance, True, ) - value = relative(np.broadcast_to(fit.prior_df, n_genes), edger["df.prior"]) + value = relative(np.broadcast_to(fit.df_prior, n_genes), edger["df.prior"]) check("prior df", FIT, MAX_RELATIVE, value, PRIOR_DF, True) # The moderation of edgeR's own adjusted deviances: only the moments differ above. with np.errstate(divide="ignore", invalid="ignore"): @@ -503,7 +503,7 @@ def check(label, reference, metric, value, tolerance, robust_prior=False): ) value = max( relative(np.broadcast_to(squeezed.prior_variance, n_genes), edger["s2.prior"]), - relative(np.broadcast_to(squeezed.prior_df, n_genes), edger["df.prior"]), + relative(np.broadcast_to(squeezed.df_prior, n_genes), edger["df.prior"]), relative(squeezed.posterior_variance, edger["s2.post"]), ) label = "the same from edgeR's adjusted deviances" @@ -537,7 +537,7 @@ def check(label, reference, metric, value, tolerance, robust_prior=False): for k, v in edger.items() if k.startswith(f"test{i}_") } - logcpm = np.asarray(result.average_log_cpm) + logcpm = np.asarray(result.average_log2_cpm) if not genes[0]: value = abs(logcpm[0] - e["logCPM"][0]) check(f"{label}: logCPM of the first gene", TEST, "difference", value, None) @@ -547,7 +547,7 @@ def check(label, reference, metric, value, tolerance, robust_prior=False): tolerance = SEARCH_LOG_CPM if estimated else CLOSE check(f"{label}: logCPM", TEST, "max difference", value, tolerance) lfc = e["logFC"].reshape(-1, n_genes).T - value = float(np.max(np.abs(result.log_fold_change - lfc))) + value = float(np.max(np.abs(result.log2_fold_change - lfc))) check( f"{label}: logFC", TEST, @@ -585,7 +585,7 @@ def check(label, reference, metric, value, tolerance, robust_prior=False): def compare_direct(case, direct, table): - """common_dispersion and average_log_cpm against edgeR's functions.""" + """common_dispersion and average_log2_cpm against edgeR's functions.""" y, x, lib = case["counts"], case["design"], case["library_sizes"] w = DIRECT_WEIGHTS reference = direct["common"] @@ -623,10 +623,10 @@ def compare_direct(case, direct, table): ) mine = np.column_stack( [ - average_log_cpm(y, library_sizes=lib), - average_log_cpm(y, library_sizes=lib, prior_count=0.5, dispersion=0.3), - average_log_cpm(y, library_sizes=lib, weights=w, dispersion=0.0), - average_log_cpm(y + 0.5, offsets=np.log(lib), prior_count=0.0), + average_log2_cpm(y, library_sizes=lib), + average_log2_cpm(y, library_sizes=lib, prior_count=0.5, dispersion=0.3), + average_log2_cpm(y, library_sizes=lib, weights=w, dispersion=0.0), + average_log2_cpm(y + 0.5, offsets=np.log(lib), prior_count=0.0), ] ) value = float(np.max(np.abs(mine - direct["abundance"].reshape(4, -1).T))) diff --git a/validation/rotation_gene_set_tests.py b/validation/rotation_gene_set_tests.py index d749b94..5e31864 100644 --- a/validation/rotation_gene_set_tests.py +++ b/validation/rotation_gene_set_tests.py @@ -1,6 +1,6 @@ """Check the rotation gene-set tests roast, mroast and romer against limma. -Compares ``cytepool.stats.rotation_gene_set_tests`` with limma's ``roast``, ``mroast`` +Compares ``cytepool.rotation_gene_set_tests`` with limma's ``roast``, ``mroast`` and ``romer`` on log-expression of 1,500 genes simulated from fixed seeds, with gene sets that change up, down, in both directions or not at all, share random sample effects, overlap, hold one to five genes or 40% of the genes, or carry contribution @@ -47,10 +47,8 @@ import numpy as np -from cytepool.design import Design -from cytepool.stats.gene_set_tests import fry +from cytepool import Design, fry, mroast, roast, romer from cytepool.stats.multiple_testing import adjust_p_values -from cytepool.stats.rotation_gene_set_tests import mroast, roast, romer from cytepool.stats.voom import voom FIXTURE_DIRECTORY = ( @@ -229,7 +227,7 @@ def add(name, design, y, sets, contrast, case_runs, weights=None): expression, sets = simulate(rng, design.matrix, n_genes, 1) counts = rng.poisson(np.exp2(expression.T) * rng.uniform(0.5, 2.0, 12)[:, None]) voomed = voom(counts, design) - add("voom", design, voomed.log_cpm.T, sets, "conditionstim", + add("voom", design, voomed.log2_cpm.T, sets, "conditionstim", runs("roast", ["mean", "msq"], 50) + calibrated("mroast", "mean50", 52, 1999), weights=voomed.weights) # fmt: skip paired = Design({"donor": np.repeat([f"D{i}" for i in range(6)], 2), @@ -298,10 +296,19 @@ def gene_set(rows, w): def common(case): - return {"y": case["y"].T, "design": case["design"], "contrast": case["contrast"], + return {"data": case["y"].T, "design": case["design"], "contrast": case["contrast"], "weights": case["weights"]} # fmt: skip +def limma_rows(ours): + """roast's row as limma's four rows (down, up, up or down, mixed): the p-values + and the active proportions, that of up or down the larger of down and up.""" + columns = ("down_p_value", "up_p_value", "p_value", "mixed_p_value") + down, up, mixed = (ours[f"proportion_{c}"][0] for c in ("down", "up", "mixed")) + p = np.array([ours[column][0] for column in columns], dtype=float) + return p, np.array([down, up, max(down, up), mixed]) + + def expected_roast(stat, z, rows, w, reference): """limma's p-values, with CytePool's value where a deliberate difference is explained, and which entries are not compared at all.""" @@ -351,10 +358,10 @@ def check_roast(case, run, table): sets.values(), case["sets"].values(), limma["sets"], strict=True ): ours = roast(**common(case), gene_set=genes, rng=Replay(draws), **options) - p, ref = ours["p_value"], np.asarray(reference["p"], float) + (p, active), ref = limma_rows(ours), np.asarray(reference["p"], float) expected, compared, minimum = expected_roast(stat, z, rows, w, reference) worst = max(worst, float(np.max(np.abs(p - expected)[compared], initial=0.0)), - float(np.max(np.abs(ours["active_proportion"] - reference["active"])))) # fmt: skip + float(np.max(np.abs(active - reference["active"])))) # fmt: skip deliberate["minimum"] += np.abs(p - ref)[minimum & compared].tolist() if stat == "msq" and not compared[0]: deliberate["signs"].append(float(np.max(np.abs(p - ref)[:3]))) diff --git a/validation/sample_quality_weights.py b/validation/sample_quality_weights.py index 9bebe92..76e515a 100644 --- a/validation/sample_quality_weights.py +++ b/validation/sample_quality_weights.py @@ -57,7 +57,7 @@ import numpy as np -from cytepool.design import Design +from cytepool import Design from cytepool.normalization import tmm_factors from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import contrasts_fit, fit_linear_model @@ -235,7 +235,7 @@ def make_cases(rng, small): ("reml_criterion_rises", "weights", rises, oscillating, {"method": "reml"}), ("one_gene", "weights", two, values[:, :1], {"method": "gene_by_gene"}), ("no_replication", "weights", saturated, values[:4], {"method": "reml"}), - ("voom_log_cpm", "weights", two, flat.log_cpm, {"method": both, "weights": flat.weights}), + ("voom_log_cpm", "weights", two, flat.log2_cpm, {"method": both, "weights": flat.weights}), (f"{many}_genes", "weights", two, simulate_values(rng, many, [1] * 6 + [3, 1]), {"method": both}), ("voom_sample_weights", "voom", two, counts, {"weights": sample_weights[:8]}), ("voom_observation_weights", "voom", two, counts, {"weights": weights}), @@ -368,7 +368,7 @@ def cytepool_results(case): design, result, caught = run_cytepool(case) if case["kind"] == "weights": return {"sample.weights": result}, caught - out = {"E": result.log_cpm, "weights": result.weights} + out = {"E": result.log2_cpm, "weights": result.weights} if result.sample_weights is not None: out["sample.weights"] = result.sample_weights if case["span"] is None and not np.isnan(result.span): @@ -380,7 +380,7 @@ def cytepool_results(case): out |= {"xy.x": x, "xy.y": result.sqrt_sd[in_trend], "line.x": x[order]} out["line.y"] = result.sqrt_sd_trend[in_trend][order] if in_trend.any(): - fit = fit_linear_model(result.log_cpm, design, weights=result.weights) + fit = fit_linear_model(result.log2_cpm, design, weights=result.weights) if len(design.contrasts): fit = contrasts_fit(fit, design.contrasts, method="limma") moderated = moderated_statistics(fit) diff --git a/validation/sumrank.py b/validation/sumrank.py index d089df6..9418e28 100644 --- a/validation/sumrank.py +++ b/validation/sumrank.py @@ -18,20 +18,20 @@ 1. The Irwin-Hall distribution function against mpmath at 200 digits (its alternating sum) on a grid from 1e-6 to m - 0.5, for 1 to 200 studies, and the Irwin-Hall - p-values of ``sumrank`` for random statistics of 3, 10 and 30 studies. + p-values of ``fit_sumrank`` for random statistics of 3, 10 and 30 studies. 2. SumRank statistics against the authors' SumRank: five studies of cases and controls (seed ``SEED``), cell types T and B, one study without B and one that did not - measure 20 genes, each analysed by ``limma_voom``: ``differential_expression_sumrank`` - against SumRank on their tables, up and down (genes down with the log2 fold changes - negated, as testing controls against cases gives them), summing every study and the - proportions 0.65 and 0.5; and ``sumrank`` against SumRank on 4 studies of 60 genes - whose p-values take 6 values. -3. Permutation p-values: ``differential_expression_sumrank`` with 40 permutations of the - condition in cell type T, against the same permutations drawn here in the documented - order and analysed by ``limma_voom`` with designs built from the cubes' sample - tables, whose tables the authors' SumRank and ``CalibratePValueswithPermutations`` - take. Their sums are floating-point sums, so that equal sums can differ in their last - digits; they are compared as the integers q of q / (2 (G - 1)), G the genes ranked. + measure 20 genes, each analysed by ``limma_voom``: ``sumrank`` against SumRank on + their tables, up and down (genes down with the log2 fold changes negated, as testing + controls against cases gives them), summing every study and the proportions 0.65 and + 0.5; and ``fit_sumrank`` against SumRank on 4 studies of 60 genes whose p-values take + 6 values. +3. Permutation p-values: ``sumrank`` with 40 permutations of the condition in cell + type T, against the same permutations drawn here in the documented order and + analysed by ``limma_voom`` with designs built from the cubes' sample tables, whose + tables the authors' SumRank and ``CalibratePValueswithPermutations`` take. Their + sums are floating-point sums, so that equal sums can differ in their last digits; + they are compared as the integers q of q / (2 (G - 1)), G the genes ranked. CytePool's p-value must be (1 + the null sums at most the gene's) / (1 + N); the authors' counts those strictly below and some of those equal, as rounding decides, without the one (a deliberate difference). @@ -96,12 +96,18 @@ from scipy.stats import rankdata from sklearn.metrics import roc_auc_score -from cytepool.cube import Cube, CubeCollection -from cytepool.design import Design -from cytepool.differential_expression import limma_trend, limma_voom -from cytepool.held_out_prediction import held_out_prediction -from cytepool.meta_analysis import differential_expression_meta_analysis -from cytepool.sumrank import _irwin_hall_cdf, differential_expression_sumrank, sumrank +from cytepool import ( + Cube, + CubeCollection, + Design, + StudyResults, + held_out_prediction, + limma_trend, + limma_voom, + meta_analysis, + sumrank, +) +from cytepool.stats.sumrank import _irwin_hall_cdf, fit_sumrank SEED = 20261030 COMMIT = "46392f66edcbaea2614a17d90bc2409648e63951" @@ -335,7 +341,7 @@ def exact(x, m): f"max relative difference ({count} values)", worst, EXACT)] # fmt: skip worst, count, rng = 0.0, 0, np.random.default_rng(SEED) for k in (3, 10, 30): - fit = sumrank(rng.normal(size=(k, 400))) + fit = fit_sumrank(rng.normal(size=(k, 400))) for direction, _ in DIRECTIONS: statistic = getattr(fit, f"{direction}_statistic") p = getattr(fit, f"{direction}_irwin_hall_p_value") @@ -343,16 +349,16 @@ def exact(x, m): reference = [exact(s, k) for s in statistic[below]] worst = max(worst, relative(p[below], reference)) count += int(below.sum()) - rows.append(("Irwin-Hall p-values of sumrank, 3, 10 and 30 studies", + rows.append(("Irwin-Hall p-values of fit_sumrank, 3, 10 and 30 studies", "mpmath, 200 digits", f"max relative difference ({count} genes)", worst, EXACT)) # fmt: skip return rows def permuted_tables(collection, results, n_permutations): - """Group T's tables of the permutations that differential_expression_sumrank draws - with the seed SEED: permutation by permutation and study by study, the condition - permuted among the samples of the study's fit of T, then limma_voom.""" + """Group T's tables of the permutations that sumrank draws with the seed SEED: + permutation by permutation and study by study, the condition permuted among the + samples of the study's fit of T, then limma_voom.""" tables = {"Real": tables_of(results, collection.studies, "T", 1.0)} generator = np.random.default_rng(SEED) for b in range(n_permutations): @@ -395,10 +401,11 @@ def enumeration(rng): ranks.append((rankdata(-values, axis=1) - 1) / (len(genes) - 1)) rows = [] for proportion, summed in ((1, 3), (0.7, 2)): - ours = differential_expression_sumrank( - collection, results, "T", CONTRAST, proportion_of_studies=proportion, - n_permutations=1000, permute="condition", rng=SEED + summed, - ).table # fmt: skip + ours = sumrank( + StudyResults(collection, results), "T", CONTRAST, + proportion_of_studies=proportion, n_permutations=1000, permute="condition", + rng=SEED + summed, + ).features # fmt: skip worst = 0.0 for direction, sign in DIRECTIONS: u = [r if sign > 0 else 1 - r for r in ranks] @@ -454,7 +461,7 @@ def recomputed_scores(collection, results, genes, held, method): def held_out_rows(collection, results, prediction, runs): - genes, scores, auc = prediction.genes, prediction.scores, prediction.auc + genes, scores, auc = prediction.features, prediction.scores, prediction.auc worst_score = worst_auc = 0.0 mismatched = compared = 0 for held, method in itertools.product( @@ -501,8 +508,8 @@ def manhattan_rows(result, root): from cytepool.plotting import plot_sumrank - ax = plot_sumrank(result, n_labels=len(result.table), ax=Figure().subplots()) - heights = np.empty(len(result.table)) + ax = plot_sumrank(result, top=len(result.features), ax=Figure().subplots()) + heights = np.empty(len(result.features)) for artist in ax.collections: offsets = artist.get_offsets() heights[np.rint(offsets[:, 0]).astype(int)] = offsets[:, 1] @@ -529,16 +536,17 @@ def simulation(rng): rows = [] collection, results, _ = simulate(rng, [16] * 6, 1200, ("T",), np.zeros(1200), 0) for proportion in (1, 0.5): - out = differential_expression_sumrank( - collection, results, "T", CONTRAST, proportion_of_studies=proportion, - n_permutations=40, permute="condition", rng=SEED, + out = sumrank( + StudyResults(collection, results), "T", CONTRAST, + proportion_of_studies=proportion, n_permutations=40, permute="condition", + rng=SEED, ) # fmt: skip summed = out.settings["n_studies_summed"] for source, column in ( ("Irwin-Hall", "irwin_hall_p_value"), ("permutation", "p_value"), ): - p = np.concatenate([out.table[f"{d}_{column}"] for d in ("up", "down")]) + p = np.concatenate([out.features[f"{d}_{column}"] for d in ("up", "down")]) shares = ( f"{np.mean(p <= 0.05):.4f} at 0.05, {np.mean(p <= 0.01):.4f} at 0.01" ) @@ -550,16 +558,16 @@ def simulation(rng): collection, results, own = simulate(rng, [16] * 6, 1200, ("T",), changes, 5) replicated = {f"g{j:04d}" for j in range(60)} single = {f"g{j:04d}" for genes in own.values() for j in genes} - table = differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=40, permute="condition", - rng=SEED, - ).table # fmt: skip + table = sumrank( + StudyResults(collection, results), "T", CONTRAST, n_permutations=40, + permute="condition", rng=SEED, + ).features # fmt: skip either = (table["up_adjusted_p_value"] < 0.05) | ( table["down_adjusted_p_value"] < 0.05 ) found = {"SumRank, 40 permutations": set(table["feature_id"][either].tolist())} for test in ("z", "knapp_hartung"): - meta = differential_expression_meta_analysis(results, test=test) + meta = meta_analysis(results, test=test) meta_table = meta.table("T", CONTRAST) called = meta_table["feature_id"][meta_table["adjusted_p_value"] < 0.05] found[f"meta-analysis, REML, {test} test"] = set(called.tolist()) @@ -569,8 +577,9 @@ def simulation(rng): "the simulated changes", "found of the 60; of the 30", counts, "information")) # fmt: skip prediction = held_out_prediction( - collection, results, CONTRAST, "condition", "case", "ctrl", n_genes=30 - ) + StudyResults(collection, results), CONTRAST, variable="condition", + case="case", control="ctrl", n_features=30, + ) # fmt: skip for method in ("sumrank", "meta_analysis"): auc = prediction.auc["auc"][prediction.auc["method"] == method] text = f"{auc.mean():.3f} ({auc.min():.3f} to {auc.max():.3f})" @@ -592,13 +601,10 @@ def benchmark(): s: limma_trend(c, design_of(c.samples)) for s, c in cubes.items() } options = {"permute": "condition", "rng": SEED} - differential_expression_sumrank( # compiles - collection, results, "T", CONTRAST, n_permutations=1, **options - ) + studies = StudyResults(collection, results) + sumrank(studies, "T", CONTRAST, n_permutations=1, **options) # compiles started = time.perf_counter() - differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=10, **options - ) + sumrank(studies, "T", CONTRAST, n_permutations=10, **options) seconds = (time.perf_counter() - started) / (10 * len(sizes)) print(f"{analysis.__name__}: {seconds:.3f} s per study and permutation") @@ -632,8 +638,8 @@ def main(): small_p[0, :, :10] *= 0.01 small_sign[0, :, :10] = 1.0 prediction = held_out_prediction( - collection, results, CONTRAST, "condition", "case", "ctrl", n_genes=15, - proportion_of_studies=HELD_OUT, + StudyResults(collection, results), CONTRAST, variable="condition", + case="case", control="ctrl", n_features=15, proportion_of_studies=HELD_OUT, ) # fmt: skip proportions = (1, 0.65, 0.5) null = [f"Permutation_{b}" for b in range(40)] @@ -671,20 +677,23 @@ def main(): runs.add(f"{held}_{group}_{direction}", {"Real": tables}, HELD_OUT) # The figure of every study summed, whose p-values from 20 permutations pass # the adjustment for some genes and are never 0 (MakeManhattanPlot jitters 0). - drawn = differential_expression_sumrank( - collection, results, "T", CONTRAST, n_permutations=20, + drawn = sumrank( + StudyResults(collection, results), "T", CONTRAST, n_permutations=20, permute="condition", rng=SEED, ) # fmt: skip - table = drawn.table + table = drawn.features columns = ("feature_id", "up_p_value", "up_adjusted_p_value") references = runs.execute(code, [table[column] for column in columns]) rows += manhattan_rows(drawn, runs.root) worst = 0.0 for group, proportion in itertools.product(("T", "B"), proportions): - ours = differential_expression_sumrank( - collection, results, group, CONTRAST, proportion_of_studies=proportion - ).table + ours = sumrank( + StudyResults(collection, results), + group, + CONTRAST, + proportion_of_studies=proportion, + ).features for direction, _ in DIRECTIONS: name = f"{group}_{proportion}_{direction}" mine = at(ours, runs.runs[name]["genes"], f"{direction}_statistic") @@ -695,20 +704,21 @@ def main(): worst = 0.0 signed = np.where(ties_change < 0, np.log10(ties_p), -np.log10(ties_p)) for proportion in (1, 0.5): - fit = sumrank(signed, proportion_of_studies=proportion) + fit = fit_sumrank(signed, proportion_of_studies=proportion) for direction, _ in DIRECTIONS: mine = getattr(fit, f"{direction}_statistic") worst = max( worst, relative(mine, runs.sums(f"ties_{proportion}_{direction}")) ) - rows.append(("sumrank of 4 studies of 60 genes with tied p-values, proportions " - "1 and 0.5", "the authors' SumRank", "max relative difference", - worst, EXACT)) # fmt: skip - - ours = differential_expression_sumrank( - collection, results, "T", CONTRAST, proportion_of_studies=0.65, - n_permutations=len(null), permute="condition", rng=SEED, - ).table # fmt: skip + rows.append(("fit_sumrank of 4 studies of 60 genes with tied p-values, " + "proportions 1 and 0.5", "the authors' SumRank", + "max relative difference", worst, EXACT)) # fmt: skip + + ours = sumrank( + StudyResults(collection, results), "T", CONTRAST, + proportion_of_studies=0.65, n_permutations=len(null), permute="condition", + rng=SEED, + ).features # fmt: skip differences = deliberate = 0.0 outside = 0 for direction, _ in DIRECTIONS: diff --git a/validation/variance_partitioning.py b/validation/variance_partitioning.py index 515b6c9..20d50a6 100644 --- a/validation/variance_partitioning.py +++ b/validation/variance_partitioning.py @@ -1,8 +1,8 @@ """Check variance partitioning against variancePartition's fitExtractVarPartModel. -Compares ``variance_fractions`` and ``variance_partitioning`` (cytepool. -variance_partitioning) with variancePartition's ``fitExtractVarPartModel`` and -``calcVarPart``, and ``plot_variance_fractions`` and ``plot_gene_variance_fractions`` +Compares ``variance_fractions`` (cytepool.stats.variance_fractions) and +``variance_partitioning`` with variancePartition's ``fitExtractVarPartModel`` and +``calcVarPart``, and ``plot_variance_fractions`` and ``plot_feature_variance_fractions`` (cytepool.plotting) with its ``plotVarPart`` and ``plotPercentBars``, on data simulated from fixed seeds (20261101 to 20261108), 60 genes each: @@ -69,12 +69,10 @@ import numpy as np from matplotlib.figure import Figure -from cytepool import plotting -from cytepool.cube import Cube -from cytepool.design import Design +from cytepool import Cube, Design, plotting, variance_partitioning from cytepool.normalization import filter_by_expression, library_sizes, tmm_factors from cytepool.stats.dream import voom_with_dream_weights -from cytepool.variance_partitioning import variance_fractions, variance_partitioning +from cytepool.stats.variance_fractions import variance_fractions FIXTURES = ( Path(__file__).resolve().parents[1] / "tests" / "data" / "variance_partitioning" @@ -351,7 +349,7 @@ def ours(case: dict[str, Any]) -> Any: if "counts" in case: voom = voom_with_dream_weights(case["counts"], design, span=0.5) return variance_fractions( - voom.log_cpm, design, weights=voom.weights, method=method + voom.log2_cpm, design, weights=voom.weights, method=method ) return variance_fractions( case["values"], design, weights=case["weights"], method=method @@ -603,7 +601,7 @@ def compare_figures(fractions: Any, ref: dict[str, Any], results: Results) -> No ) names = [f"g{i + 1}" for i in range(fractions.fractions.shape[0])] - ax = plotting.plot_gene_variance_fractions( + ax = plotting.plot_feature_variance_fractions( fractions, BAR_GENES, labels=names, ax=Figure().subplots() ) segments = {} diff --git a/validation/voom.py b/validation/voom.py index 9531bbb..7946826 100644 --- a/validation/voom.py +++ b/validation/voom.py @@ -65,7 +65,7 @@ import numpy as np -from cytepool.design import Design +from cytepool import Design from cytepool.normalization import tmm_factors from cytepool.stats.empirical_bayes import moderated_statistics from cytepool.stats.linear_models import contrasts_fit, fit_linear_model @@ -382,7 +382,7 @@ def run_voom(case): def cytepool_results(case): """CytePool's outputs for a case, keyed and shaped as limma writes them.""" design, factors, result, warned = run_voom(case) - out = {"warned": warned, "E": result.log_cpm, "weights": result.weights} + out = {"warned": warned, "E": result.log2_cpm, "weights": result.weights} out["lib.size"] = result.effective_library_sizes if case["span"] is None: out["span"] = result.span # NaN where limma returns no span @@ -394,7 +394,7 @@ def cytepool_results(case): order = np.argsort(x, kind="stable") out |= {"xy.x": x, "xy.y": result.sqrt_sd[in_trend], "line.x": x[order]} out["line.y"] = result.sqrt_sd_trend[in_trend][order] - fit = fit_linear_model(result.log_cpm, design, weights=result.weights) + fit = fit_linear_model(result.log2_cpm, design, weights=result.weights) if len(design.contrasts): fit = contrasts_fit(fit, design.contrasts, method="limma") moderated = moderated_statistics(fit)