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.
Files changed (233) hide show
  1. {biotapy-0.2.0 → biotapy-0.3.0}/CHANGELOG.md +38 -0
  2. {biotapy-0.2.0 → biotapy-0.3.0}/PKG-INFO +27 -7
  3. {biotapy-0.2.0 → biotapy-0.3.0}/README.md +24 -6
  4. {biotapy-0.2.0 → biotapy-0.3.0}/docs/_data/r_idioms.toml +5 -5
  5. {biotapy-0.2.0 → biotapy-0.3.0}/docs/api.md +19 -0
  6. {biotapy-0.2.0 → biotapy-0.3.0}/docs/coming_from_r.md +1 -1
  7. {biotapy-0.2.0 → biotapy-0.3.0}/docs/contributing.md +15 -2
  8. biotapy-0.3.0/docs/guide/differential_abundance.md +134 -0
  9. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/diversity.md +1 -1
  10. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/filtering.md +1 -1
  11. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/index.md +1 -0
  12. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/reading_data.md +1 -1
  13. biotapy-0.3.0/docs/guide/transforms.md +86 -0
  14. {biotapy-0.2.0 → biotapy-0.3.0}/docs/index.md +1 -0
  15. biotapy-0.3.0/docs/methods/aldex2.md +84 -0
  16. biotapy-0.3.0/docs/methods/ancombc2.md +89 -0
  17. biotapy-0.3.0/docs/methods/consensus.md +65 -0
  18. biotapy-0.3.0/docs/methods/index.md +17 -0
  19. biotapy-0.3.0/docs/methods/linda.md +78 -0
  20. biotapy-0.3.0/docs/methods/maaslin3.md +88 -0
  21. {biotapy-0.2.0 → biotapy-0.3.0}/docs/performance.md +35 -2
  22. biotapy-0.3.0/docs/tutorials/differential_abundance.md +148 -0
  23. {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/index.md +1 -0
  24. {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/phyloseq_analysis.md +6 -4
  25. {biotapy-0.2.0 → biotapy-0.3.0}/pyproject.toml +14 -4
  26. biotapy-0.3.0/src/biotapy/__init__.py +7 -0
  27. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_slots.py +5 -5
  28. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_tree.py +6 -5
  29. biotapy-0.3.0/src/biotapy/da/__init__.py +9 -0
  30. biotapy-0.3.0/src/biotapy/da/_aldex2.py +158 -0
  31. biotapy-0.3.0/src/biotapy/da/_ancombc.py +129 -0
  32. biotapy-0.3.0/src/biotapy/da/_consensus.py +126 -0
  33. biotapy-0.3.0/src/biotapy/da/_design.py +151 -0
  34. biotapy-0.3.0/src/biotapy/da/_linda.py +144 -0
  35. biotapy-0.3.0/src/biotapy/da/_maaslin3.py +159 -0
  36. biotapy-0.3.0/src/biotapy/da/_r.py +77 -0
  37. biotapy-0.3.0/src/biotapy/da/_schema.py +78 -0
  38. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_biom.py +1 -1
  39. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_dada2.py +1 -1
  40. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_qiime2.py +1 -1
  41. biotapy-0.3.0/src/biotapy/pl/__init__.py +11 -0
  42. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pl/_abundance.py +6 -3
  43. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pl/_common.py +10 -5
  44. biotapy-0.3.0/src/biotapy/pl/_consensus.py +118 -0
  45. biotapy-0.3.0/src/biotapy/pp/__init__.py +7 -0
  46. biotapy-0.3.0/src/biotapy/pp/_philr.py +121 -0
  47. biotapy-0.3.0/src/biotapy/pp/_transform.py +143 -0
  48. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/_alpha.py +1 -1
  49. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/_beta.py +1 -1
  50. {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_slots.py +14 -1
  51. {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_tree.py +5 -0
  52. biotapy-0.3.0/tests/da/conftest.py +68 -0
  53. biotapy-0.3.0/tests/da/test_aldex2.py +262 -0
  54. biotapy-0.3.0/tests/da/test_aldex2_golden.py +27 -0
  55. biotapy-0.3.0/tests/da/test_ancombc.py +67 -0
  56. biotapy-0.3.0/tests/da/test_ancombc_golden.py +45 -0
  57. biotapy-0.3.0/tests/da/test_consensus.py +214 -0
  58. biotapy-0.3.0/tests/da/test_design.py +195 -0
  59. biotapy-0.3.0/tests/da/test_linda.py +94 -0
  60. biotapy-0.3.0/tests/da/test_linda_golden.py +22 -0
  61. biotapy-0.3.0/tests/da/test_maaslin3.py +279 -0
  62. biotapy-0.3.0/tests/da/test_maaslin3_golden.py +31 -0
  63. biotapy-0.3.0/tests/da/test_schema.py +115 -0
  64. biotapy-0.3.0/tests/golden/VERSIONS.txt +14 -0
  65. biotapy-0.3.0/tests/golden/global_patterns/aldex2.csv.gz +0 -0
  66. biotapy-0.3.0/tests/golden/global_patterns/ancombc2.csv.gz +0 -0
  67. biotapy-0.3.0/tests/golden/global_patterns/clr.csv.gz +0 -0
  68. biotapy-0.3.0/tests/golden/global_patterns/linda.csv.gz +0 -0
  69. biotapy-0.3.0/tests/golden/global_patterns/maaslin3.csv.gz +0 -0
  70. biotapy-0.3.0/tests/golden/global_patterns/philr.csv.gz +0 -0
  71. biotapy-0.3.0/tests/golden/global_patterns/philr_sbp.csv.gz +0 -0
  72. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/test_abundance.py +17 -0
  73. biotapy-0.3.0/tests/pl/test_consensus.py +116 -0
  74. biotapy-0.3.0/tests/pp/test_philr.py +186 -0
  75. biotapy-0.3.0/tests/pp/test_philr_golden.py +38 -0
  76. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_rarefy.py +3 -1
  77. biotapy-0.3.0/tests/pp/test_transform.py +197 -0
  78. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_transform_golden.py +9 -0
  79. {biotapy-0.2.0 → biotapy-0.3.0}/tests/test_coming_from_r.py +20 -4
  80. {biotapy-0.2.0 → biotapy-0.3.0}/tests/test_docstrings.py +1 -1
  81. biotapy-0.2.0/docs/guide/transforms.md +0 -26
  82. biotapy-0.2.0/src/biotapy/__init__.py +0 -7
  83. biotapy-0.2.0/src/biotapy/pl/__init__.py +0 -7
  84. biotapy-0.2.0/src/biotapy/pp/__init__.py +0 -6
  85. biotapy-0.2.0/src/biotapy/pp/_transform.py +0 -42
  86. biotapy-0.2.0/tests/golden/VERSIONS.txt +0 -6
  87. biotapy-0.2.0/tests/pp/test_transform.py +0 -87
  88. {biotapy-0.2.0 → biotapy-0.3.0}/.gitignore +0 -0
  89. {biotapy-0.2.0 → biotapy-0.3.0}/LICENSE +0 -0
  90. {biotapy-0.2.0 → biotapy-0.3.0}/docs/_static/css/custom.css +0 -0
  91. {biotapy-0.2.0 → biotapy-0.3.0}/docs/_templates/.gitkeep +0 -0
  92. {biotapy-0.2.0 → biotapy-0.3.0}/docs/changelog.md +0 -0
  93. {biotapy-0.2.0 → biotapy-0.3.0}/docs/conf.py +0 -0
  94. {biotapy-0.2.0 → biotapy-0.3.0}/docs/design.md +0 -0
  95. {biotapy-0.2.0 → biotapy-0.3.0}/docs/extensions/coming_from_r.py +0 -0
  96. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/aggregation.md +0 -0
  97. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/data_model.md +0 -0
  98. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/datasets.md +0 -0
  99. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/function.md +0 -0
  100. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/ordination.md +0 -0
  101. {biotapy-0.2.0 → biotapy-0.3.0}/docs/guide/plotting.md +0 -0
  102. {biotapy-0.2.0 → biotapy-0.3.0}/docs/references.bib +0 -0
  103. {biotapy-0.2.0 → biotapy-0.3.0}/docs/references.md +0 -0
  104. {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/function.md +0 -0
  105. {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/getting_started.md +0 -0
  106. {biotapy-0.2.0 → biotapy-0.3.0}/docs/tutorials/quick_tour.md +0 -0
  107. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/__init__.py +0 -0
  108. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_function.py +0 -0
  109. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_matrix.py +0 -0
  110. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_optional.py +0 -0
  111. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_rng.py +0 -0
  112. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_taxonomy.py +0 -0
  113. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/_core/_warnings.py +0 -0
  114. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/__init__.py +0 -0
  115. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/_enzyme.py +0 -0
  116. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/_hmp2.py +0 -0
  117. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/_remote.py +0 -0
  118. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/datasets/_toy.py +0 -0
  119. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/__init__.py +0 -0
  120. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_contributions.py +0 -0
  121. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_glom.py +0 -0
  122. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_hierarchy.py +0 -0
  123. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_redundancy.py +0 -0
  124. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/fn/_renorm.py +0 -0
  125. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/__init__.py +0 -0
  126. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_humann.py +0 -0
  127. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_join.py +0 -0
  128. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_metaphlan.py +0 -0
  129. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_phyloseq.py +0 -0
  130. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_picrust2.py +0 -0
  131. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_rdata.py +0 -0
  132. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/io/_table.py +0 -0
  133. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pl/_ordination.py +0 -0
  134. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pl/_richness.py +0 -0
  135. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pp/_filter.py +0 -0
  136. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pp/_glom.py +0 -0
  137. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/pp/_rarefy.py +0 -0
  138. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/__init__.py +0 -0
  139. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/_ordination.py +0 -0
  140. {biotapy-0.2.0 → biotapy-0.3.0}/src/biotapy/tl/_permanova.py +0 -0
  141. {biotapy-0.2.0 → biotapy-0.3.0}/tests/conftest.py +0 -0
  142. {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_function.py +0 -0
  143. {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_matrix.py +0 -0
  144. {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_optional.py +0 -0
  145. {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_rng.py +0 -0
  146. {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_taxonomy.py +0 -0
  147. {biotapy-0.2.0 → biotapy-0.3.0}/tests/core/test_warnings.py +0 -0
  148. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/dada2/char_vector.rds +0 -0
  149. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/dada2/seqtab.rds +0 -0
  150. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/dada2/taxa.rds +0 -0
  151. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/enzyme/NOTICE.txt +0 -0
  152. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/enzyme/enzclass.txt +0 -0
  153. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/enzyme/enzyme.dat +0 -0
  154. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/NOTICE.txt +0 -0
  155. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/demo_pathabundance_with_names.tsv +0 -0
  156. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/gene_families.tsv +0 -0
  157. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/genefamilies.tsv +0 -0
  158. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/multi_sample_genefamilies.tsv +0 -0
  159. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/pathabundance.tsv +0 -0
  160. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/humann/regroup_map.tsv +0 -0
  161. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/metaphlan/NOTICE.txt +0 -0
  162. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/metaphlan/demo_metaphlan_bugs_list.tsv +0 -0
  163. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/ordered_factor.rds +0 -0
  164. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/samples_as_rows.rds +0 -0
  165. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/single_sample.rds +0 -0
  166. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/toy.RData +0 -0
  167. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/toy.rds +0 -0
  168. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/two_objects.RData +0 -0
  169. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/with_refseq.rds +0 -0
  170. {biotapy-0.2.0 → biotapy-0.3.0}/tests/data/phyloseq/zero_sample.rds +0 -0
  171. {biotapy-0.2.0 → biotapy-0.3.0}/tests/datasets/test_enzyme.py +0 -0
  172. {biotapy-0.2.0 → biotapy-0.3.0}/tests/datasets/test_hmp2.py +0 -0
  173. {biotapy-0.2.0 → biotapy-0.3.0}/tests/datasets/test_remote.py +0 -0
  174. {biotapy-0.2.0 → biotapy-0.3.0}/tests/datasets/test_toy.py +0 -0
  175. {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_contributions.py +0 -0
  176. {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_glom.py +0 -0
  177. {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_glom_golden.py +0 -0
  178. {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_hierarchy.py +0 -0
  179. {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_redundancy.py +0 -0
  180. {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_renorm.py +0 -0
  181. {biotapy-0.2.0 → biotapy-0.3.0}/tests/fn/test_renorm_golden.py +0 -0
  182. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/esophagus/unifrac_unweighted.csv.gz +0 -0
  183. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/esophagus/unifrac_weighted.csv.gz +0 -0
  184. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/alpha.csv.gz +0 -0
  185. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/alpha_faith_pd.csv.gz +0 -0
  186. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/beta_braycurtis.csv.gz +0 -0
  187. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/beta_jaccard.csv.gz +0 -0
  188. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/filter_features.csv.gz +0 -0
  189. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/nmds_braycurtis_points.csv.gz +0 -0
  190. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/nmds_braycurtis_stress.csv.gz +0 -0
  191. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/pcoa_braycurtis_values.csv.gz +0 -0
  192. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/pcoa_braycurtis_vectors.csv.gz +0 -0
  193. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/permanova_sampletype.csv.gz +0 -0
  194. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/rarefy.csv.gz +0 -0
  195. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/relative.csv.gz +0 -0
  196. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/tax_glom_genus.csv.gz +0 -0
  197. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/tax_glom_phylum.csv.gz +0 -0
  198. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/unifrac_unweighted.csv.gz +0 -0
  199. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/global_patterns/unifrac_weighted.csv.gz +0 -0
  200. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/VERSIONS.txt +0 -0
  201. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/regroup_mean.csv.gz +0 -0
  202. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/regroup_sum.csv.gz +0 -0
  203. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/renorm_cpm.csv.gz +0 -0
  204. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/renorm_cpm_genefamilies.csv.gz +0 -0
  205. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/renorm_relab.csv.gz +0 -0
  206. {biotapy-0.2.0 → biotapy-0.3.0}/tests/golden/humann/renorm_relab_nospecial.csv.gz +0 -0
  207. {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/conftest.py +0 -0
  208. {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_biom.py +0 -0
  209. {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_dada2.py +0 -0
  210. {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_humann.py +0 -0
  211. {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_metaphlan.py +0 -0
  212. {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_phyloseq.py +0 -0
  213. {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_picrust2.py +0 -0
  214. {biotapy-0.2.0 → biotapy-0.3.0}/tests/io/test_qiime2.py +0 -0
  215. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/conftest.py +0 -0
  216. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/test_init.py +0 -0
  217. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/test_ordination.py +0 -0
  218. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pl/test_richness.py +0 -0
  219. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_filter.py +0 -0
  220. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_filter_golden.py +0 -0
  221. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_glom.py +0 -0
  222. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_glom_golden.py +0 -0
  223. {biotapy-0.2.0 → biotapy-0.3.0}/tests/pp/test_rarefy_golden.py +0 -0
  224. {biotapy-0.2.0 → biotapy-0.3.0}/tests/test_data_files.py +0 -0
  225. {biotapy-0.2.0 → biotapy-0.3.0}/tests/test_purity_fixture.py +0 -0
  226. {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_alpha.py +0 -0
  227. {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_alpha_golden.py +0 -0
  228. {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_beta.py +0 -0
  229. {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_beta_golden.py +0 -0
  230. {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_ordination.py +0 -0
  231. {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_ordination_golden.py +0 -0
  232. {biotapy-0.2.0 → biotapy-0.3.0}/tests/tl/test_permanova.py +0 -0
  233. {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.2.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.2 is an early release.** The API can still change between minor versions.
56
+ **biotapy 0.3 is an early release.** The API can still change between minor versions.
55
57
 
56
- What 0.2 does (full signatures in the [API reference][api]):
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` and `bt.pl.contributions`.
89
+ `bt.pl.scree`, `bt.pl.heatmap`, `bt.pl.contributions` and
90
+ `bt.pl.consensus`.
82
91
 
83
- Next, in 0.3: compositional transforms (CLR, PhILR) and differential abundance
84
- (ANCOM-BC, LinDA, ALDEx2, MaAsLin 3) behind one result format. See the
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.2 is an early release.** The API can still change between minor versions.
17
+ **biotapy 0.3 is an early release.** The API can still change between minor versions.
18
18
 
19
- What 0.2 does (full signatures in the [API reference][api]):
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` and `bt.pl.contributions`.
50
+ `bt.pl.scree`, `bt.pl.heatmap`, `bt.pl.contributions` and
51
+ `bt.pl.consensus`.
45
52
 
46
- Next, in 0.3: compositional transforms (CLR, PhILR) and differential abundance
47
- (ANCOM-BC, LinDA, ALDEx2, MaAsLin 3) behind one result format. See the
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.2"
27
- "phyloseq::merge_samples" = "not in 0.2"
28
- "phyloseq::psmelt" = "not in 0.2"
29
- "phyloseq::plot_tree" = "not in 0.2"
30
- "phyloseq::plot_net" = "not in 0.2"
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.2" have no
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
@@ -14,5 +14,6 @@ function
14
14
  filtering
15
15
  diversity
16
16
  ordination
17
+ differential_abundance
17
18
  plotting
18
19
  ```
@@ -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.
@@ -16,6 +16,7 @@ tutorials/index.md
16
16
  :caption: User guide
17
17
 
18
18
  guide/index.md
19
+ methods/index.md
19
20
  coming_from_r.md
20
21
  ```
21
22
 
@@ -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)