phykit 2.1.90__tar.gz → 2.1.93__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.
- {phykit-2.1.90 → phykit-2.1.93}/PKG-INFO +2 -18
- {phykit-2.1.90 → phykit-2.1.93}/phykit/cli_registry.py +4 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/phykit.py +142 -15
- {phykit-2.1.90 → phykit-2.1.93}/phykit/service_factories.py +1 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/__init__.py +1 -0
- phykit-2.1.93/phykit/services/tree/faiths_pd.py +148 -0
- phykit-2.1.93/phykit/services/tree/nearest_neighbor_interchange.py +313 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/prune_tree.py +26 -1
- phykit-2.1.93/phykit/version.py +1 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit.egg-info/PKG-INFO +2 -18
- {phykit-2.1.90 → phykit-2.1.93}/phykit.egg-info/SOURCES.txt +1 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit.egg-info/entry_points.txt +4 -0
- phykit-2.1.90/phykit/services/tree/nearest_neighbor_interchange.py +0 -155
- phykit-2.1.90/phykit/version.py +0 -1
- {phykit-2.1.90 → phykit-2.1.93}/LICENSE.md +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/README.md +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/__init__.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/__main__.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/errors.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/__init__.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/boolean_argument_parsing.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/caching.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/circular_layout.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/color_annotations.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/discrete_models.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/files.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/geological_timescale.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/json_output.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/parallel.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/parsimony_utils.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/pgls_utils.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/plot_config.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/quartet_utils.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/stats_summary.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/streaming.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/helpers/trait_parsing.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/__init__.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/__init__.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/alignment_entropy.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/alignment_length.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/alignment_recoding.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/alignment_subsample.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/base.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/column_score.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/composition_per_taxon.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/dfoil.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/dna_threader.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/dstatistic.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/faidx.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/gc_content.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/identity_matrix.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/mask_alignment.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/occupancy_filter.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/pairwise_identity.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/phylo_gwas.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/plot_alignment_qc.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/rcv.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/rcvt.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/rename_fasta_entries.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/taxon_groups.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/alignment/variable_sites.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/base.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/ancestral_reconstruction.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/base.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/bipartition_support_stats.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/branch_length_multiplier.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/character_map.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/chronogram.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/collapse_branches.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/concordance_asr.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/consensus_network.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/consensus_tree.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/cont_map.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/cophylo.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/density_map.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/discordance_asymmetry.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/dtt.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/dvmc.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/evo_tempo_map.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/evolutionary_rate.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/fit_continuous.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/fit_discrete.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/hidden_paralogy_check.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/hybridization.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/independent_contrasts.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/internal_branch_stats.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/internode_labeler.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/kf_distance.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/lb_score.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/ltt.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/monophyly_check.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/neighbor_net.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/network_signal.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/ou_shift_detection.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/ouwie.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/parsimony_score.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/patristic_distances.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phenogram.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylo_anova.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylo_heatmap.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylo_impute.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylo_logistic.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylo_path.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylogenetic_glm.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylogenetic_ordination.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylogenetic_regression.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylogenetic_signal.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/phylomorphospace.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/polytomy_test.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/print_tree.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/quartet_network.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/quartet_pie.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/rate_heterogeneity.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/relative_rate_test.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/rename_tree_tips.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/rf_distance.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/root_tree.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/saturation.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/simmap_summary.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/spectral_discordance.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/spr.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/spurious_sequence.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/stochastic_character_map.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/terminal_branch_stats.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/threshold_model.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/tip_labels.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/tip_to_tip_distance.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/total_tree_length.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/trait_correlation.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/trait_rate_map.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/transfer_annotations.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/tree_space.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/treeness.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/treeness_over_rcv.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit/services/tree/vcv_utils.py +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit.egg-info/dependency_links.txt +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit.egg-info/requires.txt +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/phykit.egg-info/top_level.txt +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/setup.cfg +0 -0
- {phykit-2.1.90 → phykit-2.1.93}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: phykit
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.93
|
|
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)
|
|
@@ -3739,10 +3742,19 @@ class Phykit:
|
|
|
3739
3742
|
f"""\
|
|
3740
3743
|
{help_header}
|
|
3741
3744
|
|
|
3742
|
-
Generate
|
|
3743
|
-
rooted tree.
|
|
3745
|
+
Generate nearest neighbor interchange (NNI) moves for a
|
|
3746
|
+
binary rooted tree.
|
|
3744
3747
|
|
|
3745
|
-
|
|
3748
|
+
By default, all NNI rearrangements are emitted. Pass
|
|
3749
|
+
--branch (a comma-separated pair of taxa) or --branches
|
|
3750
|
+
(a file with one pair per line) to restrict output to the
|
|
3751
|
+
two NNI rearrangements around a specific internal branch.
|
|
3752
|
+
The branch is identified as the edge leading to the MRCA
|
|
3753
|
+
of the supplied taxa. Useful for branch-by-branch
|
|
3754
|
+
likelihood comparison in IQ-TREE / RAxML.
|
|
3755
|
+
|
|
3756
|
+
The output file includes the input phylogeny at the top
|
|
3757
|
+
unless --no-input-tree is supplied.
|
|
3746
3758
|
|
|
3747
3759
|
Aliases:
|
|
3748
3760
|
nearest_neighbor_interchange, nni
|
|
@@ -3750,22 +3762,49 @@ class Phykit:
|
|
|
3750
3762
|
pk_nearest_neighbor_interchange, pk_nni
|
|
3751
3763
|
|
|
3752
3764
|
Usage:
|
|
3753
|
-
phykit nearest_neighbor_interchange <tree>
|
|
3765
|
+
phykit nearest_neighbor_interchange <tree>
|
|
3766
|
+
[-o/--output <output_file>] [--branch A,B]
|
|
3767
|
+
[--branches <file>] [--no-input-tree] [--json]
|
|
3754
3768
|
|
|
3755
3769
|
Options
|
|
3756
3770
|
=====================================================
|
|
3757
|
-
<tree> first argument after
|
|
3771
|
+
<tree> first argument after
|
|
3758
3772
|
function name should be
|
|
3759
3773
|
a tree file
|
|
3760
3774
|
|
|
3761
3775
|
-o/--output name of output file that will
|
|
3762
3776
|
contain all trees with the
|
|
3763
3777
|
nearest neighbor interchange
|
|
3764
|
-
moves.
|
|
3765
|
-
Default output will have
|
|
3778
|
+
moves.
|
|
3779
|
+
Default output will have
|
|
3766
3780
|
the same name as the input
|
|
3767
|
-
file but with the suffix
|
|
3768
|
-
".
|
|
3781
|
+
file but with the suffix
|
|
3782
|
+
".nnis"
|
|
3783
|
+
|
|
3784
|
+
--branch optional argument. Two taxa
|
|
3785
|
+
separated by a comma
|
|
3786
|
+
(e.g., --branch A,B) whose
|
|
3787
|
+
MRCA defines the internal
|
|
3788
|
+
branch to rearrange. Emits
|
|
3789
|
+
the two alternative NNI
|
|
3790
|
+
topologies for that branch.
|
|
3791
|
+
|
|
3792
|
+
--branches optional argument. Path to a
|
|
3793
|
+
file with one taxon pair per
|
|
3794
|
+
line (comma- or tab-
|
|
3795
|
+
separated). Lines may
|
|
3796
|
+
optionally start with a
|
|
3797
|
+
label followed by the two
|
|
3798
|
+
taxa, in which case the
|
|
3799
|
+
label is echoed in the
|
|
3800
|
+
--json report. Lines that
|
|
3801
|
+
are blank or start with #
|
|
3802
|
+
are ignored. Each pair
|
|
3803
|
+
yields two NNI trees.
|
|
3804
|
+
|
|
3805
|
+
--no-input-tree optional argument. Omit the
|
|
3806
|
+
input phylogeny from the
|
|
3807
|
+
top of the output file.
|
|
3769
3808
|
|
|
3770
3809
|
--json optional argument to output
|
|
3771
3810
|
results as JSON
|
|
@@ -3774,9 +3813,78 @@ class Phykit:
|
|
|
3774
3813
|
)
|
|
3775
3814
|
parser.add_argument("tree", type=str, help=SUPPRESS)
|
|
3776
3815
|
parser.add_argument("-o", "--output", type=str, required=False, help=SUPPRESS)
|
|
3816
|
+
parser.add_argument("--branch", type=str, default=None, help=SUPPRESS)
|
|
3817
|
+
parser.add_argument("--branches", type=str, default=None, help=SUPPRESS)
|
|
3818
|
+
parser.add_argument(
|
|
3819
|
+
"--no-input-tree",
|
|
3820
|
+
action="store_true",
|
|
3821
|
+
default=False,
|
|
3822
|
+
help=SUPPRESS,
|
|
3823
|
+
)
|
|
3777
3824
|
_add_json_argument(parser)
|
|
3778
3825
|
_run_service(parser, argv, NearestNeighborInterchange)
|
|
3779
3826
|
|
|
3827
|
+
@staticmethod
|
|
3828
|
+
def faiths_pd(argv):
|
|
3829
|
+
parser = _new_parser(
|
|
3830
|
+
description=textwrap.dedent(
|
|
3831
|
+
f"""\
|
|
3832
|
+
{help_header}
|
|
3833
|
+
|
|
3834
|
+
Calculate Faith's phylogenetic diversity (PD) for a
|
|
3835
|
+
community of tips on a phylogeny.
|
|
3836
|
+
|
|
3837
|
+
Faith's PD is the sum of branch lengths in the minimum
|
|
3838
|
+
subtree that connects a set of taxa. By default, the
|
|
3839
|
+
path from the community's most recent common ancestor
|
|
3840
|
+
up to the tree root is included, matching Faith (1992)
|
|
3841
|
+
and picante::pd(..., include.root = TRUE). Use
|
|
3842
|
+
--exclude-root to sum only the branches of the induced
|
|
3843
|
+
subtree rooted at the MRCA, matching
|
|
3844
|
+
picante::pd(..., include.root = FALSE).
|
|
3845
|
+
|
|
3846
|
+
Aliases:
|
|
3847
|
+
faiths_pd, faith_pd, fpd, phylo_diversity
|
|
3848
|
+
Command line interfaces:
|
|
3849
|
+
pk_faiths_pd, pk_faith_pd, pk_fpd, pk_phylo_diversity
|
|
3850
|
+
|
|
3851
|
+
Usage:
|
|
3852
|
+
phykit faiths_pd <tree> -t/--taxa <taxa_file>
|
|
3853
|
+
[--exclude-root] [--json]
|
|
3854
|
+
|
|
3855
|
+
Options
|
|
3856
|
+
=====================================================
|
|
3857
|
+
<tree> first argument after
|
|
3858
|
+
function name should be
|
|
3859
|
+
a tree file
|
|
3860
|
+
|
|
3861
|
+
-t/--taxa file with one tip label per
|
|
3862
|
+
line defining the community
|
|
3863
|
+
|
|
3864
|
+
--exclude-root sum only branches of the
|
|
3865
|
+
induced subtree rooted at
|
|
3866
|
+
the community MRCA; by
|
|
3867
|
+
default the path up to the
|
|
3868
|
+
tree root is included
|
|
3869
|
+
|
|
3870
|
+
--json optional argument to output
|
|
3871
|
+
results as JSON
|
|
3872
|
+
"""
|
|
3873
|
+
),
|
|
3874
|
+
)
|
|
3875
|
+
parser.add_argument("tree", type=str, help=SUPPRESS)
|
|
3876
|
+
parser.add_argument(
|
|
3877
|
+
"-t", "--taxa", type=str, required=True, help=SUPPRESS, metavar=""
|
|
3878
|
+
)
|
|
3879
|
+
parser.add_argument(
|
|
3880
|
+
"--exclude-root",
|
|
3881
|
+
dest="exclude_root",
|
|
3882
|
+
action="store_true",
|
|
3883
|
+
help=SUPPRESS,
|
|
3884
|
+
)
|
|
3885
|
+
_add_json_argument(parser)
|
|
3886
|
+
_run_service(parser, argv, FaithsPD)
|
|
3887
|
+
|
|
3780
3888
|
@staticmethod
|
|
3781
3889
|
def patristic_distances(argv):
|
|
3782
3890
|
parser = _new_parser(
|
|
@@ -6887,29 +6995,38 @@ class Phykit:
|
|
|
6887
6995
|
|
|
6888
6996
|
Usage:
|
|
6889
6997
|
phykit prune_tree <tree> <list_of_taxa> [-o/--output <output_file>
|
|
6890
|
-
-k/--keep] [--json]
|
|
6998
|
+
-k/--keep] [--ignore-branch-labels] [--json]
|
|
6891
6999
|
|
|
6892
7000
|
Options
|
|
6893
7001
|
=====================================================
|
|
6894
|
-
<tree> first argument after
|
|
7002
|
+
<tree> first argument after
|
|
6895
7003
|
function name should be
|
|
6896
7004
|
a tree file
|
|
6897
7005
|
|
|
6898
7006
|
<list_of_taxa> single column file with the
|
|
6899
7007
|
names of the tips to remove
|
|
6900
|
-
from the phylogeny
|
|
7008
|
+
from the phylogeny
|
|
6901
7009
|
|
|
6902
7010
|
-o/--output name of output file for the
|
|
6903
|
-
pruned phylogeny.
|
|
6904
|
-
Default output will have
|
|
7011
|
+
pruned phylogeny.
|
|
7012
|
+
Default output will have
|
|
6905
7013
|
the same name as the input
|
|
6906
|
-
file but with the suffix
|
|
7014
|
+
file but with the suffix
|
|
6907
7015
|
".pruned"
|
|
6908
7016
|
|
|
6909
7017
|
-k/--keep optional argument. If used
|
|
6910
7018
|
instead of pruning taxa in
|
|
6911
7019
|
<list_of_taxa>, keep them
|
|
6912
7020
|
|
|
7021
|
+
--ignore-branch-labels optional argument. Strip
|
|
7022
|
+
HyPhy/aBSREL-style {{...}}
|
|
7023
|
+
branch labels (e.g.,
|
|
7024
|
+
"Hydlep{{FG}}") from tip
|
|
7025
|
+
names when matching
|
|
7026
|
+
against <list_of_taxa>.
|
|
7027
|
+
The labels are preserved
|
|
7028
|
+
in the output tree.
|
|
7029
|
+
|
|
6913
7030
|
--json optional argument to output
|
|
6914
7031
|
results as JSON
|
|
6915
7032
|
"""
|
|
@@ -6921,6 +7038,12 @@ class Phykit:
|
|
|
6921
7038
|
parser.add_argument(
|
|
6922
7039
|
"-k", "--keep", type=str2bool, nargs="?", default=False, help=SUPPRESS
|
|
6923
7040
|
)
|
|
7041
|
+
parser.add_argument(
|
|
7042
|
+
"--ignore-branch-labels",
|
|
7043
|
+
action="store_true",
|
|
7044
|
+
default=False,
|
|
7045
|
+
help=SUPPRESS,
|
|
7046
|
+
)
|
|
6924
7047
|
_add_json_argument(parser)
|
|
6925
7048
|
_run_service(parser, argv, PruneTree)
|
|
6926
7049
|
|
|
@@ -9270,6 +9393,10 @@ def nearest_neighbor_interchange(argv=None):
|
|
|
9270
9393
|
Phykit.nearest_neighbor_interchange(sys.argv[1:])
|
|
9271
9394
|
|
|
9272
9395
|
|
|
9396
|
+
def faiths_pd(argv=None):
|
|
9397
|
+
Phykit.faiths_pd(sys.argv[1:])
|
|
9398
|
+
|
|
9399
|
+
|
|
9273
9400
|
def patristic_distances(argv=None):
|
|
9274
9401
|
Phykit.patristic_distances(sys.argv[1:])
|
|
9275
9402
|
|
|
@@ -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)
|