phykit 2.1.90__tar.gz → 2.1.92__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 (150) hide show
  1. {phykit-2.1.90 → phykit-2.1.92}/PKG-INFO +2 -18
  2. {phykit-2.1.90 → phykit-2.1.92}/phykit/cli_registry.py +4 -0
  3. {phykit-2.1.90 → phykit-2.1.92}/phykit/phykit.py +89 -6
  4. {phykit-2.1.90 → phykit-2.1.92}/phykit/service_factories.py +1 -0
  5. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/__init__.py +1 -0
  6. phykit-2.1.92/phykit/services/tree/faiths_pd.py +148 -0
  7. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/prune_tree.py +26 -1
  8. phykit-2.1.92/phykit/version.py +1 -0
  9. {phykit-2.1.90 → phykit-2.1.92}/phykit.egg-info/PKG-INFO +2 -18
  10. {phykit-2.1.90 → phykit-2.1.92}/phykit.egg-info/SOURCES.txt +1 -0
  11. {phykit-2.1.90 → phykit-2.1.92}/phykit.egg-info/entry_points.txt +4 -0
  12. phykit-2.1.90/phykit/version.py +0 -1
  13. {phykit-2.1.90 → phykit-2.1.92}/LICENSE.md +0 -0
  14. {phykit-2.1.90 → phykit-2.1.92}/README.md +0 -0
  15. {phykit-2.1.90 → phykit-2.1.92}/phykit/__init__.py +0 -0
  16. {phykit-2.1.90 → phykit-2.1.92}/phykit/__main__.py +0 -0
  17. {phykit-2.1.90 → phykit-2.1.92}/phykit/errors.py +0 -0
  18. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/__init__.py +0 -0
  19. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/boolean_argument_parsing.py +0 -0
  20. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/caching.py +0 -0
  21. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/circular_layout.py +0 -0
  22. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/color_annotations.py +0 -0
  23. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/discrete_models.py +0 -0
  24. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/files.py +0 -0
  25. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/geological_timescale.py +0 -0
  26. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/json_output.py +0 -0
  27. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/parallel.py +0 -0
  28. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/parsimony_utils.py +0 -0
  29. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/pgls_utils.py +0 -0
  30. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/plot_config.py +0 -0
  31. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/quartet_utils.py +0 -0
  32. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/stats_summary.py +0 -0
  33. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/streaming.py +0 -0
  34. {phykit-2.1.90 → phykit-2.1.92}/phykit/helpers/trait_parsing.py +0 -0
  35. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/__init__.py +0 -0
  36. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/__init__.py +0 -0
  37. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/alignment_entropy.py +0 -0
  38. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/alignment_length.py +0 -0
  39. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
  40. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
  41. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/alignment_recoding.py +0 -0
  42. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/alignment_subsample.py +0 -0
  43. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/base.py +0 -0
  44. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/column_score.py +0 -0
  45. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/composition_per_taxon.py +0 -0
  46. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
  47. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
  48. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/dfoil.py +0 -0
  49. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/dna_threader.py +0 -0
  50. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/dstatistic.py +0 -0
  51. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
  52. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/faidx.py +0 -0
  53. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/gc_content.py +0 -0
  54. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/identity_matrix.py +0 -0
  55. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/mask_alignment.py +0 -0
  56. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/occupancy_filter.py +0 -0
  57. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
  58. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/pairwise_identity.py +0 -0
  59. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
  60. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/phylo_gwas.py +0 -0
  61. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/plot_alignment_qc.py +0 -0
  62. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/rcv.py +0 -0
  63. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/rcvt.py +0 -0
  64. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/rename_fasta_entries.py +0 -0
  65. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
  66. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/taxon_groups.py +0 -0
  67. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/alignment/variable_sites.py +0 -0
  68. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/base.py +0 -0
  69. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/ancestral_reconstruction.py +0 -0
  70. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/base.py +0 -0
  71. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/bipartition_support_stats.py +0 -0
  72. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/branch_length_multiplier.py +0 -0
  73. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/character_map.py +0 -0
  74. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/chronogram.py +0 -0
  75. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/collapse_branches.py +0 -0
  76. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/concordance_asr.py +0 -0
  77. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/consensus_network.py +0 -0
  78. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/consensus_tree.py +0 -0
  79. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/cont_map.py +0 -0
  80. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/cophylo.py +0 -0
  81. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
  82. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/density_map.py +0 -0
  83. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/discordance_asymmetry.py +0 -0
  84. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/dtt.py +0 -0
  85. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/dvmc.py +0 -0
  86. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/evo_tempo_map.py +0 -0
  87. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/evolutionary_rate.py +0 -0
  88. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/fit_continuous.py +0 -0
  89. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/fit_discrete.py +0 -0
  90. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/hidden_paralogy_check.py +0 -0
  91. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/hybridization.py +0 -0
  92. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/independent_contrasts.py +0 -0
  93. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/internal_branch_stats.py +0 -0
  94. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/internode_labeler.py +0 -0
  95. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/kf_distance.py +0 -0
  96. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
  97. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/lb_score.py +0 -0
  98. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/ltt.py +0 -0
  99. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/monophyly_check.py +0 -0
  100. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
  101. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/neighbor_net.py +0 -0
  102. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/network_signal.py +0 -0
  103. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/ou_shift_detection.py +0 -0
  104. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/ouwie.py +0 -0
  105. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/parsimony_score.py +0 -0
  106. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/patristic_distances.py +0 -0
  107. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phenogram.py +0 -0
  108. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylo_anova.py +0 -0
  109. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylo_heatmap.py +0 -0
  110. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylo_impute.py +0 -0
  111. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylo_logistic.py +0 -0
  112. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylo_path.py +0 -0
  113. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylogenetic_glm.py +0 -0
  114. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylogenetic_ordination.py +0 -0
  115. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylogenetic_regression.py +0 -0
  116. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylogenetic_signal.py +0 -0
  117. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/phylomorphospace.py +0 -0
  118. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/polytomy_test.py +0 -0
  119. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/print_tree.py +0 -0
  120. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/quartet_network.py +0 -0
  121. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/quartet_pie.py +0 -0
  122. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/rate_heterogeneity.py +0 -0
  123. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/relative_rate_test.py +0 -0
  124. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/rename_tree_tips.py +0 -0
  125. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/rf_distance.py +0 -0
  126. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/root_tree.py +0 -0
  127. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/saturation.py +0 -0
  128. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/simmap_summary.py +0 -0
  129. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/spectral_discordance.py +0 -0
  130. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/spr.py +0 -0
  131. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/spurious_sequence.py +0 -0
  132. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/stochastic_character_map.py +0 -0
  133. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/terminal_branch_stats.py +0 -0
  134. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/threshold_model.py +0 -0
  135. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/tip_labels.py +0 -0
  136. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/tip_to_tip_distance.py +0 -0
  137. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
  138. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/total_tree_length.py +0 -0
  139. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/trait_correlation.py +0 -0
  140. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/trait_rate_map.py +0 -0
  141. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/transfer_annotations.py +0 -0
  142. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/tree_space.py +0 -0
  143. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/treeness.py +0 -0
  144. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/treeness_over_rcv.py +0 -0
  145. {phykit-2.1.90 → phykit-2.1.92}/phykit/services/tree/vcv_utils.py +0 -0
  146. {phykit-2.1.90 → phykit-2.1.92}/phykit.egg-info/dependency_links.txt +0 -0
  147. {phykit-2.1.90 → phykit-2.1.92}/phykit.egg-info/requires.txt +0 -0
  148. {phykit-2.1.90 → phykit-2.1.92}/phykit.egg-info/top_level.txt +0 -0
  149. {phykit-2.1.90 → phykit-2.1.92}/setup.cfg +0 -0
  150. {phykit-2.1.90 → phykit-2.1.92}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: phykit
3
- Version: 2.1.90
3
+ Version: 2.1.92
4
4
  Home-page: https://github.com/jlsteenwyk/phykit
5
5
  Author: Jacob L. Steenwyk
6
6
  Author-email: jlsteenwyk@gmail.com
@@ -15,22 +15,6 @@ Classifier: Topic :: Scientific/Engineering
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE.md
18
- Requires-Dist: biopython>=1.82
19
- Requires-Dist: matplotlib>=3.7.0
20
- Requires-Dist: numpy>=1.24.0
21
- Requires-Dist: scipy>=1.11.3
22
- Requires-Dist: scikit-learn>=1.4.2
23
- Requires-Dist: umap-learn>=0.5.0
24
- Requires-Dist: tqdm>=4.65.0
25
- Dynamic: author
26
- Dynamic: author-email
27
- Dynamic: classifier
28
- Dynamic: description
29
- Dynamic: description-content-type
30
- Dynamic: home-page
31
- Dynamic: license-file
32
- Dynamic: requires-dist
33
- Dynamic: requires-python
34
18
 
35
19
  <p align="center">
36
20
  <a href="https://github.com/jlsteenwyk/phykit">
@@ -83,6 +83,10 @@ ALIAS_TO_HANDLER: Dict[str, str] = {
83
83
  "ctree": "consensus_tree",
84
84
  "degree_of_violation_of_a_molecular_clock": "dvmc",
85
85
  "evo_rate": "evolutionary_rate",
86
+ "faiths_pd": "faiths_pd",
87
+ "faith_pd": "faiths_pd",
88
+ "fpd": "faiths_pd",
89
+ "phylo_diversity": "faiths_pd",
86
90
  "clan_check": "hidden_paralogy_check",
87
91
  "ibs": "internal_branch_stats",
88
92
  "il": "internode_labeler",
@@ -230,6 +230,9 @@ class Phykit:
230
230
  - determines if a set of tip names are monophyletic
231
231
  nearest_neighbor_interchange (alias: nni)
232
232
  - make nearest neighbor interchange moves on a tree
233
+ faiths_pd (alias: faith_pd; fpd; phylo_diversity)
234
+ - calculate Faith's phylogenetic diversity for a
235
+ community of tips
233
236
  patristic_distances (alias: pd)
234
237
  - calculate all pairwise distances between tips in a tree
235
238
  phylogenetic_signal (alias: phylo_signal; ps)
@@ -3777,6 +3780,67 @@ class Phykit:
3777
3780
  _add_json_argument(parser)
3778
3781
  _run_service(parser, argv, NearestNeighborInterchange)
3779
3782
 
3783
+ @staticmethod
3784
+ def faiths_pd(argv):
3785
+ parser = _new_parser(
3786
+ description=textwrap.dedent(
3787
+ f"""\
3788
+ {help_header}
3789
+
3790
+ Calculate Faith's phylogenetic diversity (PD) for a
3791
+ community of tips on a phylogeny.
3792
+
3793
+ Faith's PD is the sum of branch lengths in the minimum
3794
+ subtree that connects a set of taxa. By default, the
3795
+ path from the community's most recent common ancestor
3796
+ up to the tree root is included, matching Faith (1992)
3797
+ and picante::pd(..., include.root = TRUE). Use
3798
+ --exclude-root to sum only the branches of the induced
3799
+ subtree rooted at the MRCA, matching
3800
+ picante::pd(..., include.root = FALSE).
3801
+
3802
+ Aliases:
3803
+ faiths_pd, faith_pd, fpd, phylo_diversity
3804
+ Command line interfaces:
3805
+ pk_faiths_pd, pk_faith_pd, pk_fpd, pk_phylo_diversity
3806
+
3807
+ Usage:
3808
+ phykit faiths_pd <tree> -t/--taxa <taxa_file>
3809
+ [--exclude-root] [--json]
3810
+
3811
+ Options
3812
+ =====================================================
3813
+ <tree> first argument after
3814
+ function name should be
3815
+ a tree file
3816
+
3817
+ -t/--taxa file with one tip label per
3818
+ line defining the community
3819
+
3820
+ --exclude-root sum only branches of the
3821
+ induced subtree rooted at
3822
+ the community MRCA; by
3823
+ default the path up to the
3824
+ tree root is included
3825
+
3826
+ --json optional argument to output
3827
+ results as JSON
3828
+ """
3829
+ ),
3830
+ )
3831
+ parser.add_argument("tree", type=str, help=SUPPRESS)
3832
+ parser.add_argument(
3833
+ "-t", "--taxa", type=str, required=True, help=SUPPRESS, metavar=""
3834
+ )
3835
+ parser.add_argument(
3836
+ "--exclude-root",
3837
+ dest="exclude_root",
3838
+ action="store_true",
3839
+ help=SUPPRESS,
3840
+ )
3841
+ _add_json_argument(parser)
3842
+ _run_service(parser, argv, FaithsPD)
3843
+
3780
3844
  @staticmethod
3781
3845
  def patristic_distances(argv):
3782
3846
  parser = _new_parser(
@@ -6887,29 +6951,38 @@ class Phykit:
6887
6951
 
6888
6952
  Usage:
6889
6953
  phykit prune_tree <tree> <list_of_taxa> [-o/--output <output_file>
6890
- -k/--keep] [--json]
6954
+ -k/--keep] [--ignore-branch-labels] [--json]
6891
6955
 
6892
6956
  Options
6893
6957
  =====================================================
6894
- <tree> first argument after
6958
+ <tree> first argument after
6895
6959
  function name should be
6896
6960
  a tree file
6897
6961
 
6898
6962
  <list_of_taxa> single column file with the
6899
6963
  names of the tips to remove
6900
- from the phylogeny
6964
+ from the phylogeny
6901
6965
 
6902
6966
  -o/--output name of output file for the
6903
- pruned phylogeny.
6904
- Default output will have
6967
+ pruned phylogeny.
6968
+ Default output will have
6905
6969
  the same name as the input
6906
- file but with the suffix
6970
+ file but with the suffix
6907
6971
  ".pruned"
6908
6972
 
6909
6973
  -k/--keep optional argument. If used
6910
6974
  instead of pruning taxa in
6911
6975
  <list_of_taxa>, keep them
6912
6976
 
6977
+ --ignore-branch-labels optional argument. Strip
6978
+ HyPhy/aBSREL-style {{...}}
6979
+ branch labels (e.g.,
6980
+ "Hydlep{{FG}}") from tip
6981
+ names when matching
6982
+ against <list_of_taxa>.
6983
+ The labels are preserved
6984
+ in the output tree.
6985
+
6913
6986
  --json optional argument to output
6914
6987
  results as JSON
6915
6988
  """
@@ -6921,6 +6994,12 @@ class Phykit:
6921
6994
  parser.add_argument(
6922
6995
  "-k", "--keep", type=str2bool, nargs="?", default=False, help=SUPPRESS
6923
6996
  )
6997
+ parser.add_argument(
6998
+ "--ignore-branch-labels",
6999
+ action="store_true",
7000
+ default=False,
7001
+ help=SUPPRESS,
7002
+ )
6924
7003
  _add_json_argument(parser)
6925
7004
  _run_service(parser, argv, PruneTree)
6926
7005
 
@@ -9270,6 +9349,10 @@ def nearest_neighbor_interchange(argv=None):
9270
9349
  Phykit.nearest_neighbor_interchange(sys.argv[1:])
9271
9350
 
9272
9351
 
9352
+ def faiths_pd(argv=None):
9353
+ Phykit.faiths_pd(sys.argv[1:])
9354
+
9355
+
9273
9356
  def patristic_distances(argv=None):
9274
9357
  Phykit.patristic_distances(sys.argv[1:])
9275
9358
 
@@ -66,6 +66,7 @@ NeighborNet = _LazyServiceFactory("phykit.services.tree.neighbor_net", "Neighbor
66
66
  ConsensusTree = _LazyServiceFactory("phykit.services.tree.consensus_tree", "ConsensusTree")
67
67
  DVMC = _LazyServiceFactory("phykit.services.tree.dvmc", "DVMC")
68
68
  EvolutionaryRate = _LazyServiceFactory("phykit.services.tree.evolutionary_rate", "EvolutionaryRate")
69
+ FaithsPD = _LazyServiceFactory("phykit.services.tree.faiths_pd", "FaithsPD")
69
70
  HiddenParalogyCheck = _LazyServiceFactory("phykit.services.tree.hidden_paralogy_check", "HiddenParalogyCheck")
70
71
  InternalBranchStats = _LazyServiceFactory("phykit.services.tree.internal_branch_stats", "InternalBranchStats")
71
72
  InternodeLabeler = _LazyServiceFactory("phykit.services.tree.internode_labeler", "InternodeLabeler")
@@ -15,6 +15,7 @@ _EXPORTS = {
15
15
  "DiscordanceAsymmetry": "discordance_asymmetry",
16
16
  "EvolutionaryRate": "evolutionary_rate",
17
17
  "EvoTempoMap": "evo_tempo_map",
18
+ "FaithsPD": "faiths_pd",
18
19
  "FitDiscrete": "fit_discrete",
19
20
  "HiddenParalogyCheck": "hidden_paralogy_check",
20
21
  "Hybridization": "hybridization",
@@ -0,0 +1,148 @@
1
+ """
2
+ Faith's phylogenetic diversity (PD).
3
+
4
+ Given a tree and a community (list of tip labels), sum the branch
5
+ lengths of the minimum subtree connecting the community. When
6
+ ``include_root`` is True (default), the sum includes the path from
7
+ the community's MRCA up to the tree root, matching Faith (1992) and
8
+ ``picante::pd(..., include.root = TRUE)``.
9
+ """
10
+ from typing import Dict, List, Tuple
11
+
12
+ from .base import Tree
13
+ from ...errors import PhykitUserError
14
+ from ...helpers.files import read_single_column_file_to_list
15
+ from ...helpers.json_output import print_json
16
+
17
+
18
+ class FaithsPD(Tree):
19
+ def __init__(self, args) -> None:
20
+ parsed = self.process_args(args)
21
+ super().__init__(tree_file_path=parsed["tree_file_path"])
22
+ self.taxa_file = parsed["taxa_file"]
23
+ self.include_root = parsed["include_root"]
24
+ self.json_output = parsed["json_output"]
25
+
26
+ def process_args(self, args) -> Dict:
27
+ return dict(
28
+ tree_file_path=args.tree,
29
+ taxa_file=args.taxa,
30
+ include_root=not getattr(args, "exclude_root", False),
31
+ json_output=getattr(args, "json", False),
32
+ )
33
+
34
+ def run(self) -> None:
35
+ tree = self.read_tree_file()
36
+ taxa = self._load_taxa(self.taxa_file)
37
+ pd_value, n_tips = self.calculate_faiths_pd(
38
+ tree, taxa, include_root=self.include_root
39
+ )
40
+ pd_rounded = round(pd_value, 4)
41
+
42
+ if self.json_output:
43
+ print_json(
44
+ dict(
45
+ faiths_pd=pd_rounded,
46
+ n_taxa=n_tips,
47
+ include_root=self.include_root,
48
+ )
49
+ )
50
+ return
51
+ print(pd_rounded)
52
+
53
+ @staticmethod
54
+ def _load_taxa(taxa_file: str) -> List[str]:
55
+ raw = read_single_column_file_to_list(taxa_file)
56
+ seen = set()
57
+ taxa: List[str] = []
58
+ for name in raw:
59
+ if not name or name in seen:
60
+ continue
61
+ seen.add(name)
62
+ taxa.append(name)
63
+ if not taxa:
64
+ raise PhykitUserError(
65
+ [f"No taxa found in {taxa_file}."], code=2,
66
+ )
67
+ return taxa
68
+
69
+ def calculate_faiths_pd(
70
+ self, tree, taxa: List[str], include_root: bool = True,
71
+ ) -> Tuple[float, int]:
72
+ """Compute Faith's PD for ``taxa`` on ``tree``.
73
+
74
+ Returns (pd, n_taxa). n_taxa is the deduplicated community size.
75
+ """
76
+ self.validate_tree(tree, min_tips=2, require_branch_lengths=True,
77
+ context="Faith's PD")
78
+
79
+ seen: set = set()
80
+ deduped: List[str] = []
81
+ for name in taxa:
82
+ if name and name not in seen:
83
+ seen.add(name)
84
+ deduped.append(name)
85
+ taxa = deduped
86
+ if not taxa:
87
+ raise PhykitUserError(
88
+ ["Community must contain at least one taxon."], code=2,
89
+ )
90
+
91
+ tip_map = {t.name: t for t in tree.get_terminals()}
92
+ missing = [name for name in taxa if name not in tip_map]
93
+ if missing:
94
+ sample = ", ".join(sorted(missing)[:5])
95
+ suffix = f" ... ({len(missing)} total)" if len(missing) > 5 else ""
96
+ raise PhykitUserError(
97
+ [
98
+ "Taxa not found in tree:",
99
+ sample + suffix,
100
+ ],
101
+ code=2,
102
+ )
103
+
104
+ community_tips = [tip_map[name] for name in taxa]
105
+
106
+ if len(community_tips) == 1:
107
+ # Single-tip community: PD with include_root=True is the path
108
+ # length from the root to that tip; with include_root=False it
109
+ # is 0 (no induced subtree). picante returns NA for the latter;
110
+ # we return 0 for programmatic convenience.
111
+ if not include_root:
112
+ return 0.0, 1
113
+ path = tree.get_path(community_tips[0])
114
+ return (
115
+ sum((c.branch_length or 0.0) for c in path),
116
+ 1,
117
+ )
118
+
119
+ if include_root:
120
+ start_clade = tree.root
121
+ else:
122
+ mrca = tree.common_ancestor(community_tips)
123
+ # If the MRCA is the root, include.root=False is equivalent
124
+ # to include.root=True because there is no branch leading to
125
+ # the MRCA to subtract (matches picante).
126
+ start_clade = mrca
127
+
128
+ total = 0.0
129
+ seen_ids = set()
130
+ for tip in community_tips:
131
+ # Skip clades at or above start_clade. For include_root=True,
132
+ # start_clade is the root, which is never in get_path.
133
+ path = tree.get_path(tip)
134
+ if start_clade is not tree.root:
135
+ try:
136
+ idx = path.index(start_clade)
137
+ path = path[idx + 1:]
138
+ except ValueError:
139
+ # start_clade (MRCA) not on path - defensive; shouldn't happen
140
+ pass
141
+ for clade in path:
142
+ cid = id(clade)
143
+ if cid in seen_ids:
144
+ continue
145
+ seen_ids.add(cid)
146
+ total += clade.branch_length or 0.0
147
+
148
+ return total, len(community_tips)
@@ -1,5 +1,6 @@
1
1
  from typing import Dict
2
2
  import pickle
3
+ import re
3
4
 
4
5
  from .base import Tree
5
6
 
@@ -7,6 +8,14 @@ from ...helpers.files import read_single_column_file_to_list
7
8
  from ...helpers.json_output import print_json
8
9
 
9
10
 
11
+ _BRANCH_LABEL_RE = re.compile(r"\{[^{}]*\}")
12
+
13
+
14
+ def _strip_branch_label(name: str) -> str:
15
+ """Remove HyPhy/aBSREL-style {…} branch labels from a tip name."""
16
+ return _BRANCH_LABEL_RE.sub("", name) if name else name
17
+
18
+
10
19
  class PruneTree(Tree):
11
20
  def __init__(self, args) -> None:
12
21
  parsed = self.process_args(args)
@@ -17,6 +26,7 @@ class PruneTree(Tree):
17
26
  keep=parsed["keep"],
18
27
  )
19
28
  self.json_output = parsed["json_output"]
29
+ self.ignore_branch_labels = parsed["ignore_branch_labels"]
20
30
 
21
31
  def run(self) -> None:
22
32
  tree = self.read_tree_file()
@@ -25,7 +35,20 @@ class PruneTree(Tree):
25
35
 
26
36
  taxa = read_single_column_file_to_list(self.list_of_taxa)
27
37
 
28
- if self.keep:
38
+ if self.ignore_branch_labels:
39
+ taxa_set = set(taxa)
40
+ tips_in_tree = [term.name for term in tree_copy.get_terminals()]
41
+ if self.keep:
42
+ taxa = [
43
+ tip for tip in tips_in_tree
44
+ if _strip_branch_label(tip) not in taxa_set
45
+ ]
46
+ else:
47
+ taxa = [
48
+ tip for tip in tips_in_tree
49
+ if _strip_branch_label(tip) in taxa_set
50
+ ]
51
+ elif self.keep:
29
52
  tips_in_tree = [term.name for term in tree_copy.get_terminals()]
30
53
  taxa = [x for x in tips_in_tree if x not in taxa]
31
54
 
@@ -39,6 +62,7 @@ class PruneTree(Tree):
39
62
  input_tree=self.tree_file_path,
40
63
  input_taxa_file=self.list_of_taxa,
41
64
  keep_input_taxa=self.keep,
65
+ ignore_branch_labels=self.ignore_branch_labels,
42
66
  taxa_pruned=sorted(taxa),
43
67
  pruned_count=len(taxa),
44
68
  remaining_tips=tree_copy.count_terminals(),
@@ -58,5 +82,6 @@ class PruneTree(Tree):
58
82
  list_of_taxa=args.list_of_taxa,
59
83
  output_file_path=output_file_path,
60
84
  keep=keep,
85
+ ignore_branch_labels=getattr(args, "ignore_branch_labels", False),
61
86
  json_output=getattr(args, "json", False),
62
87
  )
@@ -0,0 +1 @@
1
+ __version__ = "2.1.92"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: phykit
3
- Version: 2.1.90
3
+ Version: 2.1.92
4
4
  Home-page: https://github.com/jlsteenwyk/phykit
5
5
  Author: Jacob L. Steenwyk
6
6
  Author-email: jlsteenwyk@gmail.com
@@ -15,22 +15,6 @@ Classifier: Topic :: Scientific/Engineering
15
15
  Requires-Python: >=3.10
16
16
  Description-Content-Type: text/markdown
17
17
  License-File: LICENSE.md
18
- Requires-Dist: biopython>=1.82
19
- Requires-Dist: matplotlib>=3.7.0
20
- Requires-Dist: numpy>=1.24.0
21
- Requires-Dist: scipy>=1.11.3
22
- Requires-Dist: scikit-learn>=1.4.2
23
- Requires-Dist: umap-learn>=0.5.0
24
- Requires-Dist: tqdm>=4.65.0
25
- Dynamic: author
26
- Dynamic: author-email
27
- Dynamic: classifier
28
- Dynamic: description
29
- Dynamic: description-content-type
30
- Dynamic: home-page
31
- Dynamic: license-file
32
- Dynamic: requires-dist
33
- Dynamic: requires-python
34
18
 
35
19
  <p align="center">
36
20
  <a href="https://github.com/jlsteenwyk/phykit">
@@ -85,6 +85,7 @@ phykit/services/tree/dtt.py
85
85
  phykit/services/tree/dvmc.py
86
86
  phykit/services/tree/evo_tempo_map.py
87
87
  phykit/services/tree/evolutionary_rate.py
88
+ phykit/services/tree/faiths_pd.py
88
89
  phykit/services/tree/fit_continuous.py
89
90
  phykit/services/tree/fit_discrete.py
90
91
  phykit/services/tree/hidden_paralogy_check.py
@@ -55,12 +55,15 @@ pk_erps = phykit.phykit:evolutionary_rate_per_site
55
55
  pk_etm = phykit.phykit:evo_tempo_map
56
56
  pk_evo_rate = phykit.phykit:evolutionary_rate
57
57
  pk_evo_rate_per_site = phykit.phykit:evolutionary_rate_per_site
58
+ pk_faith_pd = phykit.phykit:faiths_pd
59
+ pk_faiths_pd = phykit.phykit:faiths_pd
58
60
  pk_fc = phykit.phykit:fit_continuous
59
61
  pk_fd = phykit.phykit:fit_discrete
60
62
  pk_filter_occupancy = phykit.phykit:occupancy_filter
61
63
  pk_fit_ouwie = phykit.phykit:ouwie
62
64
  pk_fitcontinuous = phykit.phykit:fit_continuous
63
65
  pk_fitdiscrete = phykit.phykit:fit_discrete
66
+ pk_fpd = phykit.phykit:faiths_pd
64
67
  pk_gamma = phykit.phykit:ltt
65
68
  pk_gamma_stat = phykit.phykit:ltt
66
69
  pk_gc = phykit.phykit:gc_content
@@ -121,6 +124,7 @@ pk_phylo_anova = phykit.phykit:phylo_anova
121
124
  pk_phylo_contrasts = phykit.phykit:independent_contrasts
122
125
  pk_phylo_corr = phykit.phykit:trait_correlation
123
126
  pk_phylo_dimreduce = phykit.phykit:phylogenetic_ordination
127
+ pk_phylo_diversity = phykit.phykit:faiths_pd
124
128
  pk_phylo_glm = phykit.phykit:phylogenetic_glm
125
129
  pk_phylo_gwas = phykit.phykit:phylo_gwas
126
130
  pk_phylo_imp = phykit.phykit:phylo_impute
@@ -1 +0,0 @@
1
- __version__ = "2.1.90"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes