biotapy 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {biotapy-0.2.0 → biotapy-0.3.0}/CHANGELOG.md +38 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/PKG-INFO +27 -7
- {biotapy-0.2.0 → biotapy-0.3.0}/README.md +24 -6
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/_data/r_idioms.toml +5 -5
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/api.md +19 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/coming_from_r.md +1 -1
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/contributing.md +15 -2
- biotapy-0.3.0/docs/guide/differential_abundance.md +134 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/diversity.md +1 -1
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/filtering.md +1 -1
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/index.md +1 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/reading_data.md +1 -1
- biotapy-0.3.0/docs/guide/transforms.md +86 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/index.md +1 -0
- biotapy-0.3.0/docs/methods/aldex2.md +84 -0
- biotapy-0.3.0/docs/methods/ancombc2.md +89 -0
- biotapy-0.3.0/docs/methods/consensus.md +65 -0
- biotapy-0.3.0/docs/methods/index.md +17 -0
- biotapy-0.3.0/docs/methods/linda.md +78 -0
- biotapy-0.3.0/docs/methods/maaslin3.md +88 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/performance.md +35 -2
- biotapy-0.3.0/docs/tutorials/differential_abundance.md +148 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/index.md +1 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/phyloseq_analysis.md +6 -4
- {biotapy-0.2.0 → biotapy-0.3.0}/pyproject.toml +14 -4
- biotapy-0.3.0/src/biotapy/__init__.py +7 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_slots.py +5 -5
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_tree.py +6 -5
- biotapy-0.3.0/src/biotapy/da/__init__.py +9 -0
- biotapy-0.3.0/src/biotapy/da/_aldex2.py +158 -0
- biotapy-0.3.0/src/biotapy/da/_ancombc.py +129 -0
- biotapy-0.3.0/src/biotapy/da/_consensus.py +126 -0
- biotapy-0.3.0/src/biotapy/da/_design.py +151 -0
- biotapy-0.3.0/src/biotapy/da/_linda.py +144 -0
- biotapy-0.3.0/src/biotapy/da/_maaslin3.py +159 -0
- biotapy-0.3.0/src/biotapy/da/_r.py +77 -0
- biotapy-0.3.0/src/biotapy/da/_schema.py +78 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_biom.py +1 -1
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_dada2.py +1 -1
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_qiime2.py +1 -1
- biotapy-0.3.0/src/biotapy/pl/__init__.py +11 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pl/_abundance.py +6 -3
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pl/_common.py +10 -5
- biotapy-0.3.0/src/biotapy/pl/_consensus.py +118 -0
- biotapy-0.3.0/src/biotapy/pp/__init__.py +7 -0
- biotapy-0.3.0/src/biotapy/pp/_philr.py +121 -0
- biotapy-0.3.0/src/biotapy/pp/_transform.py +143 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/_alpha.py +1 -1
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/_beta.py +1 -1
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_slots.py +14 -1
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_tree.py +5 -0
- biotapy-0.3.0/tests/da/conftest.py +68 -0
- biotapy-0.3.0/tests/da/test_aldex2.py +262 -0
- biotapy-0.3.0/tests/da/test_aldex2_golden.py +27 -0
- biotapy-0.3.0/tests/da/test_ancombc.py +67 -0
- biotapy-0.3.0/tests/da/test_ancombc_golden.py +45 -0
- biotapy-0.3.0/tests/da/test_consensus.py +214 -0
- biotapy-0.3.0/tests/da/test_design.py +195 -0
- biotapy-0.3.0/tests/da/test_linda.py +94 -0
- biotapy-0.3.0/tests/da/test_linda_golden.py +22 -0
- biotapy-0.3.0/tests/da/test_maaslin3.py +279 -0
- biotapy-0.3.0/tests/da/test_maaslin3_golden.py +31 -0
- biotapy-0.3.0/tests/da/test_schema.py +115 -0
- biotapy-0.3.0/tests/golden/VERSIONS.txt +14 -0
- biotapy-0.3.0/tests/golden/global_patterns/aldex2.csv.gz +0 -0
- biotapy-0.3.0/tests/golden/global_patterns/ancombc2.csv.gz +0 -0
- biotapy-0.3.0/tests/golden/global_patterns/clr.csv.gz +0 -0
- biotapy-0.3.0/tests/golden/global_patterns/linda.csv.gz +0 -0
- biotapy-0.3.0/tests/golden/global_patterns/maaslin3.csv.gz +0 -0
- biotapy-0.3.0/tests/golden/global_patterns/philr.csv.gz +0 -0
- biotapy-0.3.0/tests/golden/global_patterns/philr_sbp.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/test_abundance.py +17 -0
- biotapy-0.3.0/tests/pl/test_consensus.py +116 -0
- biotapy-0.3.0/tests/pp/test_philr.py +186 -0
- biotapy-0.3.0/tests/pp/test_philr_golden.py +38 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_rarefy.py +3 -1
- biotapy-0.3.0/tests/pp/test_transform.py +197 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_transform_golden.py +9 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/test_coming_from_r.py +20 -4
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/test_docstrings.py +1 -1
- biotapy-0.2.0/docs/guide/transforms.md +0 -26
- biotapy-0.2.0/src/biotapy/__init__.py +0 -7
- biotapy-0.2.0/src/biotapy/pl/__init__.py +0 -7
- biotapy-0.2.0/src/biotapy/pp/__init__.py +0 -6
- biotapy-0.2.0/src/biotapy/pp/_transform.py +0 -42
- biotapy-0.2.0/tests/golden/VERSIONS.txt +0 -6
- biotapy-0.2.0/tests/pp/test_transform.py +0 -87
- {biotapy-0.2.0 → biotapy-0.3.0}/.gitignore +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/LICENSE +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/_static/css/custom.css +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/_templates/.gitkeep +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/changelog.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/conf.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/design.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/extensions/coming_from_r.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/aggregation.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/data_model.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/datasets.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/function.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/ordination.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/plotting.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/references.bib +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/references.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/function.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/getting_started.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/quick_tour.md +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/__init__.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_function.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_matrix.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_optional.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_rng.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_taxonomy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_warnings.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/__init__.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/_enzyme.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/_hmp2.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/_remote.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/_toy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/__init__.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_contributions.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_glom.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_hierarchy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_redundancy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_renorm.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/__init__.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_humann.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_join.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_metaphlan.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_phyloseq.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_picrust2.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_rdata.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_table.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pl/_ordination.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pl/_richness.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pp/_filter.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pp/_glom.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pp/_rarefy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/__init__.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/_ordination.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/_permanova.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/conftest.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_function.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_matrix.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_optional.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_rng.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_taxonomy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_warnings.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/dada2/char_vector.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/dada2/seqtab.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/dada2/taxa.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/enzyme/NOTICE.txt +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/enzyme/enzclass.txt +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/enzyme/enzyme.dat +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/NOTICE.txt +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/demo_pathabundance_with_names.tsv +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/gene_families.tsv +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/genefamilies.tsv +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/multi_sample_genefamilies.tsv +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/pathabundance.tsv +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/regroup_map.tsv +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/metaphlan/NOTICE.txt +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/metaphlan/demo_metaphlan_bugs_list.tsv +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/ordered_factor.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/samples_as_rows.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/single_sample.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/toy.RData +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/toy.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/two_objects.RData +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/with_refseq.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/zero_sample.rds +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/datasets/test_enzyme.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/datasets/test_hmp2.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/datasets/test_remote.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/datasets/test_toy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_contributions.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_glom.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_glom_golden.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_hierarchy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_redundancy.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_renorm.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_renorm_golden.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/esophagus/unifrac_unweighted.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/esophagus/unifrac_weighted.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/alpha.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/alpha_faith_pd.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/beta_braycurtis.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/beta_jaccard.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/filter_features.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/nmds_braycurtis_points.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/nmds_braycurtis_stress.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/pcoa_braycurtis_values.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/pcoa_braycurtis_vectors.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/permanova_sampletype.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/rarefy.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/relative.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/tax_glom_genus.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/tax_glom_phylum.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/unifrac_unweighted.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/unifrac_weighted.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/VERSIONS.txt +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/regroup_mean.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/regroup_sum.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/renorm_cpm.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/renorm_cpm_genefamilies.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/renorm_relab.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/renorm_relab_nospecial.csv.gz +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/conftest.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_biom.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_dada2.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_humann.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_metaphlan.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_phyloseq.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_picrust2.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_qiime2.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/conftest.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/test_init.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/test_ordination.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/test_richness.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_filter.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_filter_golden.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_glom.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_glom_golden.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_rarefy_golden.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/test_data_files.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/test_purity_fixture.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_alpha.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_alpha_golden.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_beta.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_beta_golden.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_ordination.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_ordination_golden.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_permanova.py +0 -0
- {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_permanova_golden.py +0 -0
|
@@ -10,6 +10,44 @@ and this project adheres to [Semantic Versioning][].
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.3.0] - 2026-10-07
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `bt.pp.clr`: centred log-ratio transform into `layers["clr"]`, equal to
|
|
18
|
+
`vegan::decostand(x, "clr", pseudocount = 0.5)`.
|
|
19
|
+
- `bt.pp.philr`: PhILR balances along the phylogeny into `obsm["X_philr"]`,
|
|
20
|
+
equal to `philr::philr` with its default weights.
|
|
21
|
+
- `bt.da.linda`, `bt.da.ancombc2`: differential abundance by LinDA (a port
|
|
22
|
+
of `MicrobiomeStat::linda`) and ANCOM-BC2 (through scikit-bio), both in
|
|
23
|
+
Python and compared with R in golden tests.
|
|
24
|
+
- `bt.da.aldex2`, `bt.da.maaslin3`: ALDEx2 and MaAsLin 3's abundance model,
|
|
25
|
+
run in R through rpy2. They need R, the R package, and the new extra
|
|
26
|
+
`biotapy[r]` (`rpy2>=3.6.8`, GPL-2.0-or-later, built against your R).
|
|
27
|
+
- One result table for every method: log2 `effect`, `se`, `pvalue`,
|
|
28
|
+
Benjamini-Hochberg `qvalue` over the features the method tested,
|
|
29
|
+
`direction`, `method` and `contrast`. Methods take `group`, `covariates`
|
|
30
|
+
and `reference` instead of a formula, and never filter features.
|
|
31
|
+
- `bt.da.consensus` and `bt.pl.consensus`: count, per feature, the methods
|
|
32
|
+
that call it and whether they agree on its direction, as a table and as a
|
|
33
|
+
dot matrix.
|
|
34
|
+
- A differential abundance guide, a page per method, and a tutorial on the
|
|
35
|
+
GlobalPatterns genera.
|
|
36
|
+
- asv benchmarks for `pp.philr`, `da.linda` and `da.ancombc2`, with their
|
|
37
|
+
baselines in the docs.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- The readers infer `x_kind` `"counts"` only for non-negative whole numbers,
|
|
42
|
+
and the functions that need counts (`pp.rarefy`, `tl.alpha`'s
|
|
43
|
+
`observed_features` and `chao1`, weighted `tl.unifrac`) refuse a table with
|
|
44
|
+
a negative value.
|
|
45
|
+
- `bt.pl.bar` and `bt.pl.heatmap` raise `ValueError` for a table with a
|
|
46
|
+
negative value, such as `layers["clr"]`, instead of drawing it as
|
|
47
|
+
abundances.
|
|
48
|
+
- The Coming-from-R page marks the phyloseq calls biotapy does not cover yet
|
|
49
|
+
"not in 0.3".
|
|
50
|
+
|
|
13
51
|
## [0.2.0] - 2026-10-05
|
|
14
52
|
|
|
15
53
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: biotapy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: mia-style microbiome toolkit for Python on AnnData/TreeData
|
|
5
5
|
Project-URL: Documentation, https://biotapy.readthedocs.io/
|
|
6
6
|
Project-URL: Homepage, https://github.com/pedrocr83/biotapy
|
|
@@ -33,6 +33,8 @@ Requires-Dist: session-info2
|
|
|
33
33
|
Requires-Dist: threadpoolctl>=3.5
|
|
34
34
|
Requires-Dist: treedata<0.4,>=0.3.1
|
|
35
35
|
Requires-Dist: xarray
|
|
36
|
+
Provides-Extra: r
|
|
37
|
+
Requires-Dist: rpy2>=3.6.8; extra == 'r'
|
|
36
38
|
Description-Content-Type: text/markdown
|
|
37
39
|
|
|
38
40
|
# biotapy
|
|
@@ -51,9 +53,9 @@ against R on real data.
|
|
|
51
53
|
|
|
52
54
|
## Status
|
|
53
55
|
|
|
54
|
-
**biotapy 0.
|
|
56
|
+
**biotapy 0.3 is an early release.** The API can still change between minor versions.
|
|
55
57
|
|
|
56
|
-
What 0.
|
|
58
|
+
What 0.3 does (full signatures in the [API reference][api]):
|
|
57
59
|
|
|
58
60
|
- **Readers**: `bt.io.read_biom` (BIOM 1.0/2.1), `bt.io.read_qiime2`
|
|
59
61
|
(`.qza` artifacts, no QIIME 2 install needed), `bt.io.read_dada2`
|
|
@@ -71,17 +73,24 @@ What 0.2 does (full signatures in the [API reference][api]):
|
|
|
71
73
|
and `bt.datasets.enzyme` (the ENZYME EC hierarchy), downloaded and cached
|
|
72
74
|
on first use.
|
|
73
75
|
- **Preprocessing**: `bt.pp.relative`, `bt.pp.tax_glom`,
|
|
74
|
-
`bt.pp.filter_features`, `bt.pp.filter_samples` and `bt.pp.rarefy
|
|
76
|
+
`bt.pp.filter_features`, `bt.pp.filter_samples` and `bt.pp.rarefy`, and
|
|
77
|
+
the compositional transforms `bt.pp.clr` and `bt.pp.philr` (checked against
|
|
78
|
+
vegan and philr).
|
|
75
79
|
- **Function**: `bt.fn.load_hierarchy`, `bt.fn.func_glom` and `bt.fn.renorm`
|
|
76
80
|
(checked against HUMAnN's own output), `bt.fn.contributions` and
|
|
77
81
|
`bt.fn.functional_redundancy`.
|
|
78
82
|
- **Tools**: `bt.tl.alpha`, `bt.tl.beta`, `bt.tl.unifrac`, `bt.tl.pcoa`,
|
|
79
83
|
`bt.tl.nmds` and `bt.tl.permanova`, each checked against R on real data.
|
|
84
|
+
- **Differential abundance**: `bt.da.linda` and `bt.da.ancombc2` in Python,
|
|
85
|
+
`bt.da.aldex2` and `bt.da.maaslin3` through R (the `r` extra below), all
|
|
86
|
+
returning one result table and checked against their R packages, and
|
|
87
|
+
`bt.da.consensus`, which reports where the methods agree.
|
|
80
88
|
- **Plots**: `bt.pl.bar`, `bt.pl.richness`, `bt.pl.ordination`,
|
|
81
|
-
`bt.pl.scree`, `bt.pl.heatmap
|
|
89
|
+
`bt.pl.scree`, `bt.pl.heatmap`, `bt.pl.contributions` and
|
|
90
|
+
`bt.pl.consensus`.
|
|
82
91
|
|
|
83
|
-
Next, in 0.
|
|
84
|
-
|
|
92
|
+
Next, in 0.4: multi-omics conventions on MuData, leak-free scikit-learn
|
|
93
|
+
transformers, a PyTorch loader and an interface for embedding models. See the
|
|
85
94
|
[roadmap][roadmap]; no dates are promised.
|
|
86
95
|
|
|
87
96
|
## Installation
|
|
@@ -108,6 +117,17 @@ pixi add --pypi biotapy
|
|
|
108
117
|
The development version installs straight from GitHub:
|
|
109
118
|
`pip install git+https://github.com/pedrocr83/biotapy.git`.
|
|
110
119
|
|
|
120
|
+
ALDEx2 and MaAsLin 3 run in R. They need R with the R packages
|
|
121
|
+
(`BiocManager::install(c("ALDEx2", "maaslin3"))`) and the `r` extra, which
|
|
122
|
+
builds rpy2 against that R (Linux and macOS):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pip install 'biotapy[r]'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
rpy2 is GPL-2.0-or-later and the R packages carry their own licences; biotapy
|
|
129
|
+
does not ship any of them.
|
|
130
|
+
|
|
111
131
|
On Python 3.14, the `biom-format` dependency has no wheels yet, so it is built
|
|
112
132
|
from source and needs a C compiler until biom-format publishes 3.14 wheels
|
|
113
133
|
([biocore/biom-format#1004][biom-format-1004]).
|
|
@@ -14,9 +14,9 @@ against R on real data.
|
|
|
14
14
|
|
|
15
15
|
## Status
|
|
16
16
|
|
|
17
|
-
**biotapy 0.
|
|
17
|
+
**biotapy 0.3 is an early release.** The API can still change between minor versions.
|
|
18
18
|
|
|
19
|
-
What 0.
|
|
19
|
+
What 0.3 does (full signatures in the [API reference][api]):
|
|
20
20
|
|
|
21
21
|
- **Readers**: `bt.io.read_biom` (BIOM 1.0/2.1), `bt.io.read_qiime2`
|
|
22
22
|
(`.qza` artifacts, no QIIME 2 install needed), `bt.io.read_dada2`
|
|
@@ -34,17 +34,24 @@ What 0.2 does (full signatures in the [API reference][api]):
|
|
|
34
34
|
and `bt.datasets.enzyme` (the ENZYME EC hierarchy), downloaded and cached
|
|
35
35
|
on first use.
|
|
36
36
|
- **Preprocessing**: `bt.pp.relative`, `bt.pp.tax_glom`,
|
|
37
|
-
`bt.pp.filter_features`, `bt.pp.filter_samples` and `bt.pp.rarefy
|
|
37
|
+
`bt.pp.filter_features`, `bt.pp.filter_samples` and `bt.pp.rarefy`, and
|
|
38
|
+
the compositional transforms `bt.pp.clr` and `bt.pp.philr` (checked against
|
|
39
|
+
vegan and philr).
|
|
38
40
|
- **Function**: `bt.fn.load_hierarchy`, `bt.fn.func_glom` and `bt.fn.renorm`
|
|
39
41
|
(checked against HUMAnN's own output), `bt.fn.contributions` and
|
|
40
42
|
`bt.fn.functional_redundancy`.
|
|
41
43
|
- **Tools**: `bt.tl.alpha`, `bt.tl.beta`, `bt.tl.unifrac`, `bt.tl.pcoa`,
|
|
42
44
|
`bt.tl.nmds` and `bt.tl.permanova`, each checked against R on real data.
|
|
45
|
+
- **Differential abundance**: `bt.da.linda` and `bt.da.ancombc2` in Python,
|
|
46
|
+
`bt.da.aldex2` and `bt.da.maaslin3` through R (the `r` extra below), all
|
|
47
|
+
returning one result table and checked against their R packages, and
|
|
48
|
+
`bt.da.consensus`, which reports where the methods agree.
|
|
43
49
|
- **Plots**: `bt.pl.bar`, `bt.pl.richness`, `bt.pl.ordination`,
|
|
44
|
-
`bt.pl.scree`, `bt.pl.heatmap
|
|
50
|
+
`bt.pl.scree`, `bt.pl.heatmap`, `bt.pl.contributions` and
|
|
51
|
+
`bt.pl.consensus`.
|
|
45
52
|
|
|
46
|
-
Next, in 0.
|
|
47
|
-
|
|
53
|
+
Next, in 0.4: multi-omics conventions on MuData, leak-free scikit-learn
|
|
54
|
+
transformers, a PyTorch loader and an interface for embedding models. See the
|
|
48
55
|
[roadmap][roadmap]; no dates are promised.
|
|
49
56
|
|
|
50
57
|
## Installation
|
|
@@ -71,6 +78,17 @@ pixi add --pypi biotapy
|
|
|
71
78
|
The development version installs straight from GitHub:
|
|
72
79
|
`pip install git+https://github.com/pedrocr83/biotapy.git`.
|
|
73
80
|
|
|
81
|
+
ALDEx2 and MaAsLin 3 run in R. They need R with the R packages
|
|
82
|
+
(`BiocManager::install(c("ALDEx2", "maaslin3"))`) and the `r` extra, which
|
|
83
|
+
builds rpy2 against that R (Linux and macOS):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install 'biotapy[r]'
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
rpy2 is GPL-2.0-or-later and the R packages carry their own licences; biotapy
|
|
90
|
+
does not ship any of them.
|
|
91
|
+
|
|
74
92
|
On Python 3.14, the `biom-format` dependency has no wheels yet, so it is built
|
|
75
93
|
from source and needs a C compiler until biom-format publishes 3.14 wheels
|
|
76
94
|
([biocore/biom-format#1004][biom-format-1004]).
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"phyloseq::prune_samples" = '`tdata[keep].copy()`'
|
|
24
24
|
"phyloseq::subset_taxa" = '`tdata[:, tdata.var["phylum"] == "Chlamydiae"].copy()`'
|
|
25
25
|
"phyloseq::subset_samples" = '`tdata[tdata.obs["SampleType"] == "Feces"].copy()`'
|
|
26
|
-
"phyloseq::tip_glom" = "not in 0.
|
|
27
|
-
"phyloseq::merge_samples" = "not in 0.
|
|
28
|
-
"phyloseq::psmelt" = "not in 0.
|
|
29
|
-
"phyloseq::plot_tree" = "not in 0.
|
|
30
|
-
"phyloseq::plot_net" = "not in 0.
|
|
26
|
+
"phyloseq::tip_glom" = "not in 0.3"
|
|
27
|
+
"phyloseq::merge_samples" = "not in 0.3"
|
|
28
|
+
"phyloseq::psmelt" = "not in 0.3"
|
|
29
|
+
"phyloseq::plot_tree" = "not in 0.3"
|
|
30
|
+
"phyloseq::plot_net" = "not in 0.3"
|
|
@@ -49,8 +49,10 @@ Public functions are listed here as they ship, from Phase 1 onward.
|
|
|
49
49
|
.. autosummary::
|
|
50
50
|
:toctree: generated
|
|
51
51
|
|
|
52
|
+
pp.clr
|
|
52
53
|
pp.filter_features
|
|
53
54
|
pp.filter_samples
|
|
55
|
+
pp.philr
|
|
54
56
|
pp.rarefy
|
|
55
57
|
pp.relative
|
|
56
58
|
pp.tax_glom
|
|
@@ -89,6 +91,22 @@ Public functions are listed here as they ship, from Phase 1 onward.
|
|
|
89
91
|
tl.unifrac
|
|
90
92
|
```
|
|
91
93
|
|
|
94
|
+
## Differential abundance
|
|
95
|
+
|
|
96
|
+
```{eval-rst}
|
|
97
|
+
.. module:: biotapy.da
|
|
98
|
+
.. currentmodule:: biotapy
|
|
99
|
+
|
|
100
|
+
.. autosummary::
|
|
101
|
+
:toctree: generated
|
|
102
|
+
|
|
103
|
+
da.aldex2
|
|
104
|
+
da.ancombc2
|
|
105
|
+
da.consensus
|
|
106
|
+
da.linda
|
|
107
|
+
da.maaslin3
|
|
108
|
+
```
|
|
109
|
+
|
|
92
110
|
## Plots
|
|
93
111
|
|
|
94
112
|
```{eval-rst}
|
|
@@ -99,6 +117,7 @@ Public functions are listed here as they ship, from Phase 1 onward.
|
|
|
99
117
|
:toctree: generated
|
|
100
118
|
|
|
101
119
|
pl.bar
|
|
120
|
+
pl.consensus
|
|
102
121
|
pl.contributions
|
|
103
122
|
pl.heatmap
|
|
104
123
|
pl.ordination
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Every public biotapy function names its R equivalent in its docstring. This table is generated
|
|
4
4
|
from those lines each time the docs are built, plus a short list of phyloseq accessors that are
|
|
5
|
-
plain AnnData/TreeData code (`docs/_data/r_idioms.toml`). Rows marked "not in 0.
|
|
5
|
+
plain AnnData/TreeData code (`docs/_data/r_idioms.toml`). Rows marked "not in 0.3" have no
|
|
6
6
|
biotapy equivalent yet.
|
|
7
7
|
|
|
8
8
|
biotapy keeps samples as rows, so `tdata.X` is phyloseq's `otu_table` with
|
|
@@ -57,9 +57,9 @@ uv run --group test pytest -m "network or golden"
|
|
|
57
57
|
|
|
58
58
|
These tests download `bt.datasets.global_patterns()`, `bt.datasets.enterotype()`
|
|
59
59
|
and `bt.datasets.esophagus()` through [pooch](https://www.fatiando.org/pooch/)
|
|
60
|
-
and compare biotapy with R on that data: `pp.relative`, `pp.tax_glom`, filtering, rarefaction
|
|
60
|
+
and compare biotapy with R on that data: `pp.relative`, `pp.clr`, `pp.tax_glom`, filtering, rarefaction
|
|
61
61
|
(its invariants), alpha and beta diversity, UniFrac, PCoA, NMDS and PERMANOVA against phyloseq,
|
|
62
|
-
vegan, ape and picante. Set
|
|
62
|
+
vegan, ape and picante, `pp.philr` against philr, `da.linda` against MicrobiomeStat and `da.ancombc2` against ANCOMBC. Set
|
|
63
63
|
`BIOTAPY_DATA_DIR` to point the pooch cache somewhere other than the default
|
|
64
64
|
per-user cache directory - CI caches it across runs the same way:
|
|
65
65
|
|
|
@@ -67,6 +67,19 @@ per-user cache directory - CI caches it across runs the same way:
|
|
|
67
67
|
BIOTAPY_DATA_DIR=.pooch uv run --group test pytest -m "network or golden"
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
+
### R bridge tests
|
|
71
|
+
|
|
72
|
+
Tests that call R through rpy2 (`bt.da.aldex2`, `bt.da.maaslin3`) carry the marker `r` and are excluded from the
|
|
73
|
+
runs above. They need R with the packages `tests/r/Dockerfile` installs and the `r` extra; those
|
|
74
|
+
that read GlobalPatterns also need the pooch cache:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
BIOTAPY_DATA_DIR=.pooch uv run --group test --extra r pytest -m r
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
CI runs them in the `r-bridge` job, with R 4.5.3 and the Bioconductor 3.22 packages the golden image
|
|
81
|
+
pins.
|
|
82
|
+
|
|
70
83
|
### Regenerating the R golden files
|
|
71
84
|
|
|
72
85
|
The golden CSVs under `tests/golden/` and the R-written fixtures under
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Differential abundance
|
|
2
|
+
|
|
3
|
+
Differential abundance (DA) asks which features are more abundant in one group of samples than in
|
|
4
|
+
another. Every `bt.da` method takes the same arguments and returns the same table, so their answers
|
|
5
|
+
can be put side by side. The {doc}`tutorial </tutorials/differential_abundance>` runs them on
|
|
6
|
+
GlobalPatterns.
|
|
7
|
+
|
|
8
|
+
## One question, one table
|
|
9
|
+
|
|
10
|
+
A method takes the `obs` column whose effect you want (`group`), the `covariates` to adjust for,
|
|
11
|
+
and the `reference` level that the other level is compared with:
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
import biotapy as bt
|
|
15
|
+
|
|
16
|
+
tdata = bt.datasets.toy()
|
|
17
|
+
table = bt.da.linda(tdata, "group") # "B vs A": A, the first category, is the reference
|
|
18
|
+
table = bt.da.linda(tdata, "group", reference="B") # "A vs B": every effect changes sign
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`group` is a categorical, string or bool column with two levels, or a numeric column, whose effect
|
|
22
|
+
is then a slope. Covariates are numeric, or categorical with one indicator per level against
|
|
23
|
+
their first. There are no formula strings: R and patsy take the alphabetically first level as the
|
|
24
|
+
reference and silently drop samples with a missing value. biotapy takes the reference you give, or
|
|
25
|
+
the first category, says which way round the effect is in the `contrast` column, and raises on a
|
|
26
|
+
missing value, on a constant column, on a group with more than two levels and on collinear covariates.
|
|
27
|
+
|
|
28
|
+
The table has one row per feature, in `var_names` order:
|
|
29
|
+
|
|
30
|
+
| Column | Meaning |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `effect` | log2 fold change of the other level over the reference, or the slope of a numeric group: per standard deviation in LinDA and MaAsLin 3, per unit in ANCOM-BC2, not offered by ALDEx2. ALDEx2's is the median log2 difference between the groups, MaAsLin 3's the coefficient minus the median coefficient |
|
|
33
|
+
| `se` | its standard error; NaN for every feature in ALDEx2, which reports none |
|
|
34
|
+
| `pvalue` | the method's p-value |
|
|
35
|
+
| `qvalue` | Benjamini-Hochberg adjusted p-value, over the features the method tested |
|
|
36
|
+
| `direction` | sign of `effect`: -1, 0 or 1 |
|
|
37
|
+
| `method` | the method's name |
|
|
38
|
+
| `contrast` | `"<level> vs <reference>"`, or the name of a numeric group |
|
|
39
|
+
|
|
40
|
+
A feature a method cannot test keeps its row, with NaN `effect`, `pvalue` and `qvalue` and `direction` 0 (a NaN `se` alone means
|
|
41
|
+
nothing for ALDEx2). Methods never
|
|
42
|
+
filter features: filter once, with `bt.pp.filter_features`, before running any method, so every
|
|
43
|
+
method tests the same features. Methods need raw counts in `X` and a read in every sample
|
|
44
|
+
(`bt.pp.filter_samples(tdata, min_depth=1)` drops empty ones).
|
|
45
|
+
|
|
46
|
+
## Choosing methods
|
|
47
|
+
|
|
48
|
+
biotapy has four methods. They model compositional counts differently, so they call different
|
|
49
|
+
features, and no one of them is right on every dataset:
|
|
50
|
+
|
|
51
|
+
| Method | Model | Runs in | Covariates | Numeric `group` | `seed` |
|
|
52
|
+
|---|---|---|---|---|---|
|
|
53
|
+
| {doc}`bt.da.linda </methods/linda>` | least squares of log2 centred log-ratios, minus the mode of all coefficients | Python | yes | per standard deviation | no |
|
|
54
|
+
| {doc}`bt.da.ancombc2 </methods/ancombc2>` | least squares of log counts corrected for each sample's sampling fraction | Python (scikit-bio) | yes | per unit | no |
|
|
55
|
+
| {doc}`bt.da.aldex2 </methods/aldex2>` | Welch t-tests on Monte Carlo draws of centred log-ratios | R | no | no | yes |
|
|
56
|
+
| {doc}`bt.da.maaslin3 </methods/maaslin3>` | least squares of log2 relative abundance where present, tested against the median | R | yes | per standard deviation | yes |
|
|
57
|
+
|
|
58
|
+
Each method's page gives its model, the R defaults biotapy changes, and how closely biotapy
|
|
59
|
+
matches the R package on real data. Two things to know before you choose:
|
|
60
|
+
|
|
61
|
+
- ANCOM-BC2 and MaAsLin 3 leave zeros out, so a feature with no read in one group cannot be
|
|
62
|
+
fitted: its row is NaN. LinDA adds 0.5 to every count when the table has a zero, and ALDEx2
|
|
63
|
+
draws around every count, so both test it.
|
|
64
|
+
- `reference` changes more than the sign in ANCOM-BC2 (its bias correction is fitted against the
|
|
65
|
+
reference level) and ALDEx2 (its effect's random resampling follows the label order), and
|
|
66
|
+
slightly in MaAsLin 3 (its p-values move by up to about 1.2e-3 with the same `seed`, the calls do not).
|
|
67
|
+
Choose it on the biology, the control or baseline level, before you look at any result.
|
|
68
|
+
|
|
69
|
+
(da-methods-in-r)=
|
|
70
|
+
## Methods that run in R
|
|
71
|
+
|
|
72
|
+
ALDEx2 and MaAsLin 3 exist only in R, so biotapy calls them there through
|
|
73
|
+
[rpy2](https://rpy2.github.io/). They need R, the R package, and biotapy's `r` extra, which builds
|
|
74
|
+
rpy2 against that R (Linux and macOS; it fails to install when no R is found):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
Rscript -e 'install.packages("BiocManager"); BiocManager::install(c("ALDEx2", "maaslin3"))'
|
|
78
|
+
pip install 'biotapy[r]'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
rpy2 is GPL-2.0-or-later and the R packages have their own licences; biotapy itself does not ship
|
|
82
|
+
any of them. Without rpy2 or the R package, the call raises an `ImportError` that names what to
|
|
83
|
+
install. Each call converts `X` to a dense table once, because rpy2 has no sparse converter.
|
|
84
|
+
|
|
85
|
+
Both methods draw random numbers in R. `seed` seeds R for the call, and your R session's own
|
|
86
|
+
random state is restored afterwards, so the same seed gives the same table. Each R warning raised
|
|
87
|
+
during the call, such as ALDEx2's for fewer than 128 `mc_samples`, is re-emitted as a Python
|
|
88
|
+
`UserWarning`; an R error is raised as a `RuntimeError`. Repeated `var_names` or `obs_names`
|
|
89
|
+
raise: call `adata.var_names_make_unique()` first.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
table = bt.da.aldex2(tdata, "group", seed=0)
|
|
93
|
+
table = bt.da.maaslin3(tdata, "group", seed=0)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Where methods agree
|
|
97
|
+
|
|
98
|
+
`bt.da.consensus` puts the tables of several methods side by side and counts, for each feature,
|
|
99
|
+
the methods that call it significant (`qvalue < alpha`, strictly) and whether they agree on its
|
|
100
|
+
direction:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
results = [bt.da.ancombc2(tdata, "group"), bt.da.linda(tdata, "group")]
|
|
104
|
+
table = bt.da.consensus(results) # alpha=0.05, min_methods=2
|
|
105
|
+
table[table["consensus"]]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
A feature is a consensus hit when at least `min_methods` methods call it and all of them give it
|
|
109
|
+
the same sign. Methods that call it in opposite directions mark a `conflict`, which is never a
|
|
110
|
+
consensus. A method that could not test a feature does not count against it: `n_tested` says how
|
|
111
|
+
many methods tested each feature. The tables must come from different methods and compare the same
|
|
112
|
+
`contrast`, so run every method with the same `group` and `reference`. The
|
|
113
|
+
{doc}`consensus page </methods/consensus>` defines every column.
|
|
114
|
+
|
|
115
|
+
Methods disagree a lot on real data, and the literature is split on what to do about it: Nearing
|
|
116
|
+
et al. (2022) recommend a consensus of several methods, Pelto et al. (2025) one simple method. Either
|
|
117
|
+
way, choose the methods before you look at their results; trying methods until one finds what you
|
|
118
|
+
hoped for is selective reporting, and a consensus table does not protect against it. Methods that
|
|
119
|
+
share a model, such as ANCOM-BC and ANCOM-BC2, also agree more often for that reason alone.
|
|
120
|
+
|
|
121
|
+
## Plotting the consensus
|
|
122
|
+
|
|
123
|
+
`bt.pl.consensus` draws the consensus table as a dot matrix: one row per feature that at least one
|
|
124
|
+
method calls, one column per method. A filled dot is a call, red for a positive effect and blue for
|
|
125
|
+
a negative one; a hollow dot is a feature the method tested without calling it; no dot, a feature
|
|
126
|
+
the method could not test. Consensus features have bold labels. Rows are sorted by the number of
|
|
127
|
+
methods calling them, then by their mean absolute effect, and `top` keeps the first 30:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
ax = bt.pl.consensus(table, top=20)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Feature ids are often accession numbers; to label rows with a rank, rename the table's index first,
|
|
134
|
+
for example `table.rename(index=tdata.var["genus"])`.
|
|
@@ -45,7 +45,7 @@ bt.tl.unifrac(tdata, weighted=True, inplace=True) # obsp["weighted_unifrac"]
|
|
|
45
45
|
Without `binary = TRUE` phyloseq computes vegan's quantitative Jaccard, a different number.
|
|
46
46
|
- Weighted UniFrac is normalized to 0-1 by default, as in phyloseq; pass `normalized=False` for
|
|
47
47
|
the raw value.
|
|
48
|
-
- Weighted UniFrac needs raw counts: `x_kind` `"counts"` and whole numbers. scikit-bio's tree code
|
|
48
|
+
- Weighted UniFrac needs raw counts: `x_kind` `"counts"` and non-negative whole numbers. scikit-bio's tree code
|
|
49
49
|
casts abundances to integers, so proportions would all become 0. Unweighted UniFrac uses
|
|
50
50
|
presence only and runs on relative abundances too.
|
|
51
51
|
- A tree whose root has three or more children is used rooted where it is drawn. phyloseq
|
|
@@ -49,7 +49,7 @@ out = bt.pp.rarefy(tdata, depth=60, seed=0)
|
|
|
49
49
|
- Samples with fewer than `depth` reads are dropped, with one warning naming them; a sample with
|
|
50
50
|
exactly `depth` reads is kept.
|
|
51
51
|
- Features left all-zero are dropped, as with phyloseq's `trimOTUs = TRUE`.
|
|
52
|
-
- `X` must hold raw counts: `uns["biotapy"]["x_kind"] == "counts"` and whole numbers.
|
|
52
|
+
- `X` must hold raw counts: `uns["biotapy"]["x_kind"] == "counts"` and non-negative whole numbers.
|
|
53
53
|
- phyloseq samples with replacement by default (`replace = TRUE`). biotapy always samples
|
|
54
54
|
without, like `replace = FALSE`.
|
|
55
55
|
- The same `seed` gives the same result. R and NumPy use different random generators, so the
|
|
@@ -5,7 +5,7 @@ Readers turn a file format into a `TreeData` that follows the one
|
|
|
5
5
|
|
|
6
6
|
BIOM, QIIME 2, DADA2 and phyloseq tables do not record whether they hold
|
|
7
7
|
counts or proportions, so their readers infer `uns["biotapy"]["x_kind"]` from
|
|
8
|
-
the values: whole numbers are `"counts"`; otherwise, if every sample with a
|
|
8
|
+
the values: non-negative whole numbers are `"counts"`; otherwise, if every sample with a
|
|
9
9
|
nonzero total sums to 1 (within `1e-3`), `"relative"`; anything else is
|
|
10
10
|
`"abundance"`. The HUMAnN, MetaPhlAn and PICRUSt2 readers take it from the
|
|
11
11
|
format instead, as their sections say. Functions that need raw counts check
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Transforms
|
|
2
|
+
|
|
3
|
+
Transforms add a layer (or, for PhILR, an `obsm` table) to a copy of your data; they never touch `X` or drop
|
|
4
|
+
anything else, so you can always get back to what you started from.
|
|
5
|
+
|
|
6
|
+
## Relative abundance
|
|
7
|
+
|
|
8
|
+
`bt.pp.relative` scales every sample (row) so its features sum to one, and stores the result in
|
|
9
|
+
`layers["relative"]`:
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import biotapy as bt
|
|
13
|
+
|
|
14
|
+
tdata = bt.datasets.toy()
|
|
15
|
+
out = bt.pp.relative(tdata)
|
|
16
|
+
out.layers["relative"][0].sum() # 1.0
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`X` still holds the original counts; `out.uns["biotapy"]["provenance"]` records that
|
|
20
|
+
`pp.relative` ran.
|
|
21
|
+
|
|
22
|
+
### All-zero samples
|
|
23
|
+
|
|
24
|
+
A sample with no reads has nothing to divide by. Where phyloseq's
|
|
25
|
+
`transform_sample_counts` divides by zero and returns `NaN`, biotapy leaves an
|
|
26
|
+
all-zero sample as all zeros, so downstream steps do not have to special-case `NaN`.
|
|
27
|
+
|
|
28
|
+
## Centred log-ratio (CLR)
|
|
29
|
+
|
|
30
|
+
Microbiome counts are compositional: a sequencing run fixes the total, so only ratios between
|
|
31
|
+
features carry information. `bt.pp.clr` takes the logarithm of each value relative to the
|
|
32
|
+
geometric mean of its sample, and stores it in `layers["clr"]`:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
out = bt.pp.clr(tdata) # pseudocount=0.5
|
|
36
|
+
out.layers["clr"][0].sum() # 0.0, up to rounding
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The logarithm of zero is undefined, so a pseudocount is added to every value first, zeros or
|
|
40
|
+
not, as `mia::transformAssay(method = "clr", pseudocount = 0.5)` and
|
|
41
|
+
`vegan::decostand(x, "clr", pseudocount = 0.5)` do. The default 0.5 suits counts. On relative
|
|
42
|
+
abundances or CPM it would swamp the rarest features, so biotapy warns when the pseudocount is
|
|
43
|
+
larger than the smallest non-zero value; pass one on the data's scale, for example half that value.
|
|
44
|
+
|
|
45
|
+
An all-zero sample gives an all-zero CLR row.
|
|
46
|
+
|
|
47
|
+
`layers["clr"]` is dense: CLR has no zeros, so it takes 8 bytes per sample and feature, and the call peaks at three to five such arrays, the most when `X` is mostly non-zero.
|
|
48
|
+
|
|
49
|
+
## PhILR
|
|
50
|
+
|
|
51
|
+
`bt.pp.philr` turns a sample into balances along its phylogeny: one per internal node with two
|
|
52
|
+
children, the log-ratio of the geometric means of the taxa under the node's two children, scaled so
|
|
53
|
+
that the balances are orthonormal coordinates. They go to `obsm["X_philr"]`, a table with one
|
|
54
|
+
column per balance:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
tdata = bt.datasets.toy()[:, :6].copy()
|
|
58
|
+
out = bt.pp.philr(tdata) # pseudocount=0.5, as in pp.clr
|
|
59
|
+
out.obsm["X_philr"] # columns root, n1, n4, n2, n5
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
A balance is positive when the node's first child is more abundant than its second, as in
|
|
63
|
+
`philr::philr` with its default uniform weights. Distances between samples' balances equal
|
|
64
|
+
their Aitchison distances, the Euclidean distances between their CLR vectors.
|
|
65
|
+
|
|
66
|
+
PhILR needs a rooted binary tree. Filtering features leaves nodes with one child; those define
|
|
67
|
+
no balance and are skipped. A node with three or more children raises: that includes the root
|
|
68
|
+
of an unrooted tree (`bt.datasets.toy()` has one, which is why the example keeps f1-f6). Root
|
|
69
|
+
the tree and resolve its polytomies before reading it, for example with `ape::multi2di` in R, or
|
|
70
|
+
in Python with scikit-bio, which resolves them arbitrarily, as `multi2di` does:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from skbio import TreeNode
|
|
74
|
+
|
|
75
|
+
tree = TreeNode.read("tree.nwk") # root it first (outgroup or midpoint) if it is unrooted: your choice
|
|
76
|
+
tree.bifurcate() # in place; every node ends with two children
|
|
77
|
+
tree.write("binary.nwk")
|
|
78
|
+
# then read your table again with tree="binary.nwk"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Balance names are the node names in `vart["phylo"]`, which differ from the ones `philr` in R
|
|
82
|
+
makes with `makeNodeLabel`; to compare balances across tools, match them by their numerator and
|
|
83
|
+
denominator taxa, not by name.
|
|
84
|
+
|
|
85
|
+
Filtering features with `bt.pp.filter_features` drops `layers["clr"]` and `obsm["X_philr"]`;
|
|
86
|
+
plain `adata[:, ...]` slicing keeps the old values, so transform after subsetting.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# ALDEx2
|
|
2
|
+
|
|
3
|
+
{func}`bt.da.aldex2 <biotapy.da.aldex2>` runs `ALDEx2::aldex` in R through rpy2: it draws
|
|
4
|
+
plausible relative abundances for each sample, takes their log2 centred log-ratios and compares
|
|
5
|
+
the two groups with a Welch t-test on every draw. It needs R, the R package ALDEx2 and biotapy's
|
|
6
|
+
`r` extra ({ref}`Methods that run in R <da-methods-in-r>`).
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
import biotapy as bt
|
|
10
|
+
|
|
11
|
+
table = bt.da.aldex2(bt.datasets.toy(), "group", seed=0)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Model
|
|
15
|
+
|
|
16
|
+
For each sample $i$, ALDEx2 draws `mc_samples` proportion vectors from a Dirichlet distribution
|
|
17
|
+
whose parameters are the sample's counts plus 0.5, so a draw reflects how uncertain proportions
|
|
18
|
+
are at that sequencing depth. Each draw is turned into log2 centred log-ratios over all features
|
|
19
|
+
(`denom = "all"`):
|
|
20
|
+
|
|
21
|
+
$$
|
|
22
|
+
c^{(k)}_{ij} = \log_2 p^{(k)}_{ij} - \frac{1}{m} \sum_{l=1}^{m} \log_2 p^{(k)}_{il},
|
|
23
|
+
\qquad p^{(k)}_{i\cdot} \sim \operatorname{Dirichlet}(y_{i\cdot} + 0.5)
|
|
24
|
+
$$
|
|
25
|
+
|
|
26
|
+
For each draw $k$ and feature $j$, a Welch t-test compares the two groups' values $c^{(k)}_{ij}$;
|
|
27
|
+
`pvalue` is ALDEx2's `we.ep`: for each draw ALDEx2 doubles the one-sided Welch p-value in each
|
|
28
|
+
direction (capped at 1), averages each direction over the draws, and keeps the smaller average.
|
|
29
|
+
`effect` is ALDEx2's `diff.btw`, the median difference between values resampled at random from each
|
|
30
|
+
group's pooled draws, already log2. ALDEx2
|
|
31
|
+
reports no standard error, so `se` is NaN for every feature, and `qvalue` is the
|
|
32
|
+
Benjamini-Hochberg correction of `pvalue`, as for every method. A feature with no read in any
|
|
33
|
+
sample is dropped by ALDEx2 and is NaN in the table (not tested).
|
|
34
|
+
|
|
35
|
+
## Units
|
|
36
|
+
|
|
37
|
+
`effect` is the median log2 difference of the centred log-ratios between the other level and
|
|
38
|
+
`reference`. ALDEx2 compares two groups only: a numeric `group`, covariates and a level in fewer
|
|
39
|
+
than two samples are refused.
|
|
40
|
+
|
|
41
|
+
## Compared with R
|
|
42
|
+
|
|
43
|
+
biotapy calls `ALDEx2::aldex` (ALDEx2 1.42.0) with its defaults:
|
|
44
|
+
|
|
45
|
+
| `aldex` argument or output | R default | biotapy |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `conditions` | required | the labels `"0"` (`reference`) and `"1"`: ALDEx2 fails on a factor and sorts character labels in the R session's locale |
|
|
48
|
+
| `mc.samples` | `128` | `mc_samples=128`; R warns below 128, and biotapy re-emits it as a `UserWarning` |
|
|
49
|
+
| `test`, `effect`, `denom` | `"t"`, `TRUE`, `"all"` | the same |
|
|
50
|
+
| `paired.test`, `iterate`, `gamma` | `FALSE`, `FALSE`, `NULL` | the same: no paired test, no scale model |
|
|
51
|
+
| `we.ep` | output | `pvalue` |
|
|
52
|
+
| `we.eBH` | output | not carried: it averages the draws' corrected values, a different quantity; `qvalue` is BH of `we.ep` |
|
|
53
|
+
| `diff.btw` | output | `effect` |
|
|
54
|
+
| `effect`, `overlap`, `wi.ep`, `wi.eBH`, `rab.*`, `diff.win` | outputs | not carried: ALDEx2's `effect` is a standardised size, not a fold change |
|
|
55
|
+
| random state | R's global seed | `seed` sets R's seed for the call, and the R session's random state is put back afterwards |
|
|
56
|
+
|
|
57
|
+
On the GlobalPatterns genera below, BH of `we.ep` calls 11 genera and ALDEx2's `we.eBH` 19.
|
|
58
|
+
|
|
59
|
+
## Agreement with R
|
|
60
|
+
|
|
61
|
+
ALDEx2 is random, but `seed` becomes one integer for R's `set.seed`, so biotapy and R draw the same
|
|
62
|
+
numbers. The golden test runs `set.seed(...); ALDEx2::aldex(...)` with that integer on the 636
|
|
63
|
+
GlobalPatterns genera in at least 20% of samples, human hosts against the rest: `effect` and
|
|
64
|
+
`diff.btw` agree to a relative 4e-15 and `pvalue` and `we.ep` to 8.4e-13 (checked at 1e-7). The
|
|
65
|
+
golden passes `reference="human"`, the level R sorts first, so both sides resample `diff.btw` in the same
|
|
66
|
+
label order. With other seeds, effects correlate
|
|
67
|
+
at 0.993 (Spearman) and 11 to 13 genera are called, depending on the seed (13 at `seed=0`, the
|
|
68
|
+
tutorial's).
|
|
69
|
+
|
|
70
|
+
## Choosing the reference
|
|
71
|
+
|
|
72
|
+
Swapping `reference` does more than flip the sign. ALDEx2 draws the same Monte Carlo instances, so
|
|
73
|
+
`pvalue` and the calls are unchanged, but `diff.btw` comes from a random resampling of each group's
|
|
74
|
+
values done in label order, so the same `seed` gives different effects, and the two runs are not
|
|
75
|
+
exact mirror images. They differ from exact antisymmetry by 0.1 to 0.3 log2 on `toy()`, depending
|
|
76
|
+
on the seed (its effects are about 4 log2 wide), and by up to 0.65 log2 on the GlobalPatterns
|
|
77
|
+
genera, 15 of 636 of which then do not change direction. The p-values and the calls are the same.
|
|
78
|
+
|
|
79
|
+
## Reference
|
|
80
|
+
|
|
81
|
+
Fernandes AD, Reid JN, Macklaim JM, McMurrough TA, Edgell DR, Gloor GB (2014) Unifying the
|
|
82
|
+
analysis of high-throughput sequencing datasets: characterizing RNA-seq, 16S rRNA gene sequencing
|
|
83
|
+
and selective growth experiments by compositional data analysis. *Microbiome* 2:15.
|
|
84
|
+
[doi:10.1186/2049-2618-2-15](https://doi.org/10.1186/2049-2618-2-15)
|