phykit 2.1.85__tar.gz → 2.1.88__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.85 → phykit-2.1.88}/PKG-INFO +1 -1
- {phykit-2.1.85 → phykit-2.1.88}/phykit/cli_registry.py +3 -0
- phykit-2.1.88/phykit/helpers/geological_timescale.py +119 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/phykit.py +84 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/service_factories.py +1 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/__init__.py +1 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/ancestral_reconstruction.py +3 -3
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/base.py +20 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/branch_length_multiplier.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/character_map.py +2 -2
- phykit-2.1.88/phykit/services/tree/chronogram.py +661 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/collapse_branches.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/concordance_asr.py +159 -3
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/cont_map.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/independent_contrasts.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/internode_labeler.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/last_common_ancestor_subtree.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/ou_shift_detection.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/ouwie.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/parsimony_score.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phenogram.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylo_logistic.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylogenetic_ordination.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylomorphospace.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/polytomy_test.py +12 -4
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/print_tree.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/prune_tree.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/rate_heterogeneity.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/rename_tree_tips.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/root_tree.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/spectral_discordance.py +3 -3
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/spr.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/trait_rate_map.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/tree_space.py +2 -2
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/vcv_utils.py +2 -2
- phykit-2.1.88/phykit/version.py +1 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit.egg-info/PKG-INFO +1 -1
- {phykit-2.1.85 → phykit-2.1.88}/phykit.egg-info/SOURCES.txt +2 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit.egg-info/entry_points.txt +3 -0
- phykit-2.1.85/phykit/version.py +0 -1
- {phykit-2.1.85 → phykit-2.1.88}/LICENSE.md +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/README.md +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/__init__.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/__main__.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/errors.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/__init__.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/boolean_argument_parsing.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/caching.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/circular_layout.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/color_annotations.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/discrete_models.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/files.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/json_output.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/parallel.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/parsimony_utils.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/pgls_utils.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/plot_config.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/quartet_utils.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/stats_summary.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/streaming.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/helpers/trait_parsing.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/__init__.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/__init__.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/alignment_entropy.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/alignment_length.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/alignment_recoding.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/alignment_subsample.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/base.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/column_score.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/composition_per_taxon.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/dfoil.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/dna_threader.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/dstatistic.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/faidx.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/gc_content.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/identity_matrix.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/mask_alignment.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/occupancy_filter.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/pairwise_identity.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/phylo_gwas.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/plot_alignment_qc.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/rcv.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/rcvt.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/rename_fasta_entries.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/taxon_groups.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/alignment/variable_sites.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/base.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/bipartition_support_stats.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/consensus_network.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/consensus_tree.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/cophylo.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/density_map.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/discordance_asymmetry.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/dvmc.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/evo_tempo_map.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/evolutionary_rate.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/fit_continuous.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/fit_discrete.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/hidden_paralogy_check.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/hybridization.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/internal_branch_stats.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/kf_distance.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/lb_score.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/ltt.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/monophyly_check.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/neighbor_net.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/network_signal.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/patristic_distances.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylo_anova.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylo_heatmap.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylo_impute.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylo_path.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylogenetic_glm.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylogenetic_regression.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/phylogenetic_signal.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/quartet_network.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/quartet_pie.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/relative_rate_test.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/rf_distance.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/saturation.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/simmap_summary.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/spurious_sequence.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/stochastic_character_map.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/terminal_branch_stats.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/threshold_model.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/tip_labels.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/tip_to_tip_distance.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/total_tree_length.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/trait_correlation.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/transfer_annotations.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/treeness.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit/services/tree/treeness_over_rcv.py +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit.egg-info/dependency_links.txt +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit.egg-info/requires.txt +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/phykit.egg-info/top_level.txt +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/setup.cfg +0 -0
- {phykit-2.1.85 → phykit-2.1.88}/setup.py +0 -0
|
@@ -70,6 +70,9 @@ ALIAS_TO_HANDLER: Dict[str, str] = {
|
|
|
70
70
|
"blm": "branch_length_multiplier",
|
|
71
71
|
"collapse": "collapse_branches",
|
|
72
72
|
"cb": "collapse_branches",
|
|
73
|
+
"chronogram": "chronogram",
|
|
74
|
+
"chrono": "chronogram",
|
|
75
|
+
"time_tree": "chronogram",
|
|
73
76
|
"cover": "covarying_evolutionary_rates",
|
|
74
77
|
"consnet": "consensus_network",
|
|
75
78
|
"splitnet": "consensus_network",
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Geological timescale data (ICS 2024).
|
|
3
|
+
|
|
4
|
+
Provides era, period, and epoch boundaries for plotting chronograms
|
|
5
|
+
with geological time bands.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Muted pastel color palette for geological epochs
|
|
9
|
+
# Inspired by ICS colors but softened for publication figures
|
|
10
|
+
EPOCH_COLORS = {
|
|
11
|
+
"Holocene": "#FEF2CC",
|
|
12
|
+
"Pleistocene": "#FFF2AE",
|
|
13
|
+
"Pliocene": "#FFFF99",
|
|
14
|
+
"Miocene": "#FFFF00",
|
|
15
|
+
"Oligocene": "#FDC07A",
|
|
16
|
+
"Eocene": "#FDB46C",
|
|
17
|
+
"Paleocene": "#FDA75F",
|
|
18
|
+
"Late Cretaceous": "#A6D84A",
|
|
19
|
+
"Early Cretaceous": "#8CCD57",
|
|
20
|
+
"Late Jurassic": "#B3E1EB",
|
|
21
|
+
"Middle Jurassic": "#80CFD8",
|
|
22
|
+
"Early Jurassic": "#42B2D0",
|
|
23
|
+
"Late Triassic": "#BD8CC2",
|
|
24
|
+
"Middle Triassic": "#B07FB7",
|
|
25
|
+
"Early Triassic": "#A372AB",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
PERIOD_COLORS = {
|
|
29
|
+
"Quaternary": "#F9F97F",
|
|
30
|
+
"Neogene": "#FFE619",
|
|
31
|
+
"Paleogene": "#FD9A52",
|
|
32
|
+
"Cretaceous": "#7FC64E",
|
|
33
|
+
"Jurassic": "#34B2C9",
|
|
34
|
+
"Triassic": "#812B92",
|
|
35
|
+
"Permian": "#F04028",
|
|
36
|
+
"Carboniferous": "#67A599",
|
|
37
|
+
"Devonian": "#CB8C37",
|
|
38
|
+
"Silurian": "#B3E1B6",
|
|
39
|
+
"Ordovician": "#009270",
|
|
40
|
+
"Cambrian": "#7FA056",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ERA_COLORS = {
|
|
44
|
+
"Cenozoic": "#F2F91D",
|
|
45
|
+
"Mesozoic": "#67C5CA",
|
|
46
|
+
"Paleozoic": "#99C08D",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
EPOCHS = [
|
|
50
|
+
("Holocene", 0.0117, 0),
|
|
51
|
+
("Pleistocene", 2.58, 0.0117),
|
|
52
|
+
("Pliocene", 5.333, 2.58),
|
|
53
|
+
("Miocene", 23.03, 5.333),
|
|
54
|
+
("Oligocene", 33.9, 23.03),
|
|
55
|
+
("Eocene", 56.0, 33.9),
|
|
56
|
+
("Paleocene", 66.0, 56.0),
|
|
57
|
+
("Late Cretaceous", 100.5, 66.0),
|
|
58
|
+
("Early Cretaceous", 145.0, 100.5),
|
|
59
|
+
("Late Jurassic", 163.5, 145.0),
|
|
60
|
+
("Middle Jurassic", 174.7, 163.5),
|
|
61
|
+
("Early Jurassic", 201.4, 174.7),
|
|
62
|
+
("Late Triassic", 237.0, 201.4),
|
|
63
|
+
("Middle Triassic", 247.2, 237.0),
|
|
64
|
+
("Early Triassic", 251.902, 247.2),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
PERIODS = [
|
|
68
|
+
("Quaternary", 2.58, 0),
|
|
69
|
+
("Neogene", 23.03, 2.58),
|
|
70
|
+
("Paleogene", 66.0, 23.03),
|
|
71
|
+
("Cretaceous", 145.0, 66.0),
|
|
72
|
+
("Jurassic", 201.4, 145.0),
|
|
73
|
+
("Triassic", 251.902, 201.4),
|
|
74
|
+
("Permian", 298.9, 251.902),
|
|
75
|
+
("Carboniferous", 358.9, 298.9),
|
|
76
|
+
("Devonian", 419.2, 358.9),
|
|
77
|
+
("Silurian", 443.8, 419.2),
|
|
78
|
+
("Ordovician", 485.4, 443.8),
|
|
79
|
+
("Cambrian", 538.8, 485.4),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
ERAS = [
|
|
83
|
+
("Cenozoic", 66.0, 0),
|
|
84
|
+
("Mesozoic", 251.902, 66.0),
|
|
85
|
+
("Paleozoic", 538.8, 251.902),
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_timescale_for_range(root_age, level="auto"):
|
|
90
|
+
"""Return appropriate timescale intervals for a given root age.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
root_age : float, Ma
|
|
95
|
+
level : "epoch", "period", "era", or "auto"
|
|
96
|
+
|
|
97
|
+
Returns
|
|
98
|
+
-------
|
|
99
|
+
list of (name, start_ma, end_ma), color_dict
|
|
100
|
+
"""
|
|
101
|
+
if level == "auto":
|
|
102
|
+
if root_age <= 66:
|
|
103
|
+
level = "epoch"
|
|
104
|
+
elif root_age <= 252:
|
|
105
|
+
level = "period"
|
|
106
|
+
else:
|
|
107
|
+
level = "era"
|
|
108
|
+
|
|
109
|
+
if level == "epoch":
|
|
110
|
+
intervals = [(n, s, e) for n, s, e in EPOCHS if s > 0 and e < root_age * 1.1]
|
|
111
|
+
# Include any epoch that overlaps the tree's range
|
|
112
|
+
intervals = [(n, s, e) for n, s, e in EPOCHS if e < root_age * 1.05]
|
|
113
|
+
return intervals, EPOCH_COLORS
|
|
114
|
+
elif level == "period":
|
|
115
|
+
intervals = [(n, s, e) for n, s, e in PERIODS if e < root_age * 1.05]
|
|
116
|
+
return intervals, PERIOD_COLORS
|
|
117
|
+
else:
|
|
118
|
+
intervals = [(n, s, e) for n, s, e in ERAS if e < root_age * 1.05]
|
|
119
|
+
return intervals, ERA_COLORS
|
|
@@ -192,6 +192,8 @@ class Phykit:
|
|
|
192
192
|
concordance_asr (alias: conc_asr; casr)
|
|
193
193
|
- concordance-aware ancestral state reconstruction
|
|
194
194
|
incorporating gene tree discordance
|
|
195
|
+
chronogram (alias: chrono; time_tree)
|
|
196
|
+
- plot a time-calibrated tree with geological timescale
|
|
195
197
|
bipartition_support_stats (alias: bss)
|
|
196
198
|
- calculates summary statistics for bipartition support
|
|
197
199
|
branch_length_multiplier (alias: blm)
|
|
@@ -2825,6 +2827,14 @@ class Phykit:
|
|
|
2825
2827
|
--plot output path for concordance
|
|
2826
2828
|
ASR plot
|
|
2827
2829
|
|
|
2830
|
+
--plot-uncertainty output path for uncertainty
|
|
2831
|
+
plot showing the distribution
|
|
2832
|
+
of ancestral estimates across
|
|
2833
|
+
gene trees (distribution
|
|
2834
|
+
method) or concordance
|
|
2835
|
+
sources (weighted method)
|
|
2836
|
+
as violin + boxplots
|
|
2837
|
+
|
|
2828
2838
|
--missing-taxa how to handle taxa mismatches:
|
|
2829
2839
|
shared (default) or error
|
|
2830
2840
|
|
|
@@ -2902,6 +2912,10 @@ class Phykit:
|
|
|
2902
2912
|
"--plot", type=str, required=False, default=None,
|
|
2903
2913
|
help=SUPPRESS, metavar=""
|
|
2904
2914
|
)
|
|
2915
|
+
parser.add_argument(
|
|
2916
|
+
"--plot-uncertainty", type=str, required=False, default=None,
|
|
2917
|
+
help=SUPPRESS, metavar=""
|
|
2918
|
+
)
|
|
2905
2919
|
parser.add_argument(
|
|
2906
2920
|
"--missing-taxa", type=str, required=False, default="shared",
|
|
2907
2921
|
choices=["error", "shared"], help=SUPPRESS, metavar=""
|
|
@@ -2910,6 +2924,72 @@ class Phykit:
|
|
|
2910
2924
|
_add_json_argument(parser)
|
|
2911
2925
|
_run_service(parser, argv, ConcordanceAsr)
|
|
2912
2926
|
|
|
2927
|
+
@staticmethod
|
|
2928
|
+
def chronogram(argv):
|
|
2929
|
+
parser = _new_parser(
|
|
2930
|
+
description=textwrap.dedent(
|
|
2931
|
+
f"""\
|
|
2932
|
+
{help_header}
|
|
2933
|
+
|
|
2934
|
+
Plot a chronogram (time-calibrated phylogeny) with
|
|
2935
|
+
geological timescale bands. Requires an ultrametric
|
|
2936
|
+
tree and the root age in millions of years (Ma).
|
|
2937
|
+
|
|
2938
|
+
Geological epoch/period/era bands are drawn behind
|
|
2939
|
+
the tree as colored stripes, with labels along the
|
|
2940
|
+
top. The time axis runs from past (left) to present
|
|
2941
|
+
(right).
|
|
2942
|
+
|
|
2943
|
+
Aliases:
|
|
2944
|
+
chronogram, chrono, time_tree
|
|
2945
|
+
Command line interfaces:
|
|
2946
|
+
pk_chronogram, pk_chrono, pk_time_tree
|
|
2947
|
+
|
|
2948
|
+
Usage:
|
|
2949
|
+
phykit chronogram -t <tree> --root-age <float>
|
|
2950
|
+
--plot-output <file> [--timescale auto|epoch|period|era]
|
|
2951
|
+
[--node-ages] [--circular] [--ladderize]
|
|
2952
|
+
[--color-file <file>] [--json]
|
|
2953
|
+
|
|
2954
|
+
Options
|
|
2955
|
+
=====================================================
|
|
2956
|
+
-t/--tree ultrametric tree file
|
|
2957
|
+
(required)
|
|
2958
|
+
|
|
2959
|
+
--root-age age of the root in Ma
|
|
2960
|
+
(required)
|
|
2961
|
+
|
|
2962
|
+
--plot-output output figure path
|
|
2963
|
+
(required; .png, .pdf, .svg)
|
|
2964
|
+
|
|
2965
|
+
--timescale timescale level: auto
|
|
2966
|
+
(default), epoch, period,
|
|
2967
|
+
or era. Auto selects based
|
|
2968
|
+
on root age.
|
|
2969
|
+
|
|
2970
|
+
--node-ages label internal nodes with
|
|
2971
|
+
divergence times (Ma)
|
|
2972
|
+
|
|
2973
|
+
--circular draw circular chronogram
|
|
2974
|
+
|
|
2975
|
+
--ladderize ladderize the tree
|
|
2976
|
+
|
|
2977
|
+
--color-file color annotation file
|
|
2978
|
+
(iTOL-inspired TSV)
|
|
2979
|
+
|
|
2980
|
+
--json output node ages as JSON
|
|
2981
|
+
"""
|
|
2982
|
+
),
|
|
2983
|
+
)
|
|
2984
|
+
parser.add_argument("-t", "--tree", type=str, required=True, help=SUPPRESS, metavar="")
|
|
2985
|
+
parser.add_argument("--root-age", type=float, required=True, help=SUPPRESS, metavar="")
|
|
2986
|
+
parser.add_argument("--plot-output", type=str, required=True, help=SUPPRESS, metavar="")
|
|
2987
|
+
parser.add_argument("--timescale", type=str, default="auto", choices=["auto", "epoch", "period", "era"], help=SUPPRESS, metavar="")
|
|
2988
|
+
parser.add_argument("--node-ages", action="store_true", help=SUPPRESS)
|
|
2989
|
+
add_plot_arguments(parser)
|
|
2990
|
+
_add_json_argument(parser)
|
|
2991
|
+
_run_service(parser, argv, Chronogram)
|
|
2992
|
+
|
|
2913
2993
|
@staticmethod
|
|
2914
2994
|
def bipartition_support_stats(argv):
|
|
2915
2995
|
parser = _new_parser(
|
|
@@ -9057,6 +9137,10 @@ def concordance_asr(argv=None):
|
|
|
9057
9137
|
Phykit.concordance_asr(sys.argv[1:])
|
|
9058
9138
|
|
|
9059
9139
|
|
|
9140
|
+
def chronogram(argv=None):
|
|
9141
|
+
Phykit.chronogram(sys.argv[1:])
|
|
9142
|
+
|
|
9143
|
+
|
|
9060
9144
|
def bipartition_support_stats(argv=None):
|
|
9061
9145
|
Phykit.bipartition_support_stats(sys.argv[1:])
|
|
9062
9146
|
|
|
@@ -59,6 +59,7 @@ BipartitionSupportStats = _LazyServiceFactory("phykit.services.tree.bipartition_
|
|
|
59
59
|
BranchLengthMultiplier = _LazyServiceFactory("phykit.services.tree.branch_length_multiplier", "BranchLengthMultiplier")
|
|
60
60
|
CollapseBranches = _LazyServiceFactory("phykit.services.tree.collapse_branches", "CollapseBranches")
|
|
61
61
|
CovaryingEvolutionaryRates = _LazyServiceFactory("phykit.services.tree.covarying_evolutionary_rates", "CovaryingEvolutionaryRates")
|
|
62
|
+
Chronogram = _LazyServiceFactory("phykit.services.tree.chronogram", "Chronogram")
|
|
62
63
|
ConsensusNetwork = _LazyServiceFactory("phykit.services.tree.consensus_network", "ConsensusNetwork")
|
|
63
64
|
NeighborNet = _LazyServiceFactory("phykit.services.tree.neighbor_net", "NeighborNet")
|
|
64
65
|
ConsensusTree = _LazyServiceFactory("phykit.services.tree.consensus_tree", "ConsensusTree")
|
|
@@ -5,6 +5,7 @@ _EXPORTS = {
|
|
|
5
5
|
"BipartitionSupportStats": "bipartition_support_stats",
|
|
6
6
|
"BranchLengthMultiplier": "branch_length_multiplier",
|
|
7
7
|
"CollapseBranches": "collapse_branches",
|
|
8
|
+
"Chronogram": "chronogram",
|
|
8
9
|
"CovaryingEvolutionaryRates": "covarying_evolutionary_rates",
|
|
9
10
|
"ConsensusNetwork": "consensus_network",
|
|
10
11
|
"NeighborNet": "neighbor_net",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import copy
|
|
2
1
|
import math
|
|
2
|
+
import pickle
|
|
3
3
|
import sys
|
|
4
4
|
from typing import Dict, List, Tuple
|
|
5
5
|
|
|
@@ -78,7 +78,7 @@ class AncestralReconstruction(Tree):
|
|
|
78
78
|
x = np.array([trait_values[name] for name in ordered_names])
|
|
79
79
|
|
|
80
80
|
# Prune tree to shared taxa
|
|
81
|
-
tree_copy =
|
|
81
|
+
tree_copy = pickle.loads(pickle.dumps(tree, protocol=pickle.HIGHEST_PROTOCOL))
|
|
82
82
|
tip_names_in_tree = [t.name for t in tree_copy.get_terminals()]
|
|
83
83
|
tips_to_prune = [t for t in tip_names_in_tree if t not in trait_values]
|
|
84
84
|
if tips_to_prune:
|
|
@@ -1368,7 +1368,7 @@ class AncestralReconstruction(Tree):
|
|
|
1368
1368
|
trait_name = "trait"
|
|
1369
1369
|
|
|
1370
1370
|
# Prune tree to shared taxa
|
|
1371
|
-
tree_copy =
|
|
1371
|
+
tree_copy = pickle.loads(pickle.dumps(tree, protocol=pickle.HIGHEST_PROTOCOL))
|
|
1372
1372
|
tip_names_in_tree = [t.name for t in tree_copy.get_terminals()]
|
|
1373
1373
|
tips_to_prune = [t for t in tip_names_in_tree if t not in tip_states]
|
|
1374
1374
|
if tips_to_prune:
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import copy
|
|
2
|
+
import pickle
|
|
2
3
|
from typing import List
|
|
3
4
|
from functools import lru_cache
|
|
4
5
|
import os
|
|
@@ -80,12 +81,29 @@ class Tree(BaseService):
|
|
|
80
81
|
def read_reference_tree_file(self):
|
|
81
82
|
return self._read_tree_with_error(self.reference, "reference")
|
|
82
83
|
|
|
84
|
+
@staticmethod
|
|
85
|
+
def _fast_copy(tree):
|
|
86
|
+
"""Copy a tree using pickle instead of deepcopy.
|
|
87
|
+
|
|
88
|
+
Avoids RecursionError on deeply nested trees (e.g., ladder-like
|
|
89
|
+
topologies with hundreds of cascading bifurcations) where
|
|
90
|
+
copy.deepcopy exceeds Python's default recursion limit.
|
|
91
|
+
Falls back to deepcopy for objects that can't be pickled (e.g.,
|
|
92
|
+
mocks in unit tests).
|
|
93
|
+
"""
|
|
94
|
+
try:
|
|
95
|
+
return pickle.loads(pickle.dumps(tree, protocol=pickle.HIGHEST_PROTOCOL))
|
|
96
|
+
except (pickle.PicklingError, TypeError, AttributeError):
|
|
97
|
+
return copy.deepcopy(tree)
|
|
98
|
+
|
|
83
99
|
def _read_tree_with_error(self, tree_path: str, attr_name: str):
|
|
84
100
|
try:
|
|
85
101
|
file_hash = self._get_file_hash(tree_path)
|
|
86
102
|
tree = self._cached_tree_read(tree_path, self.tree_format, file_hash)
|
|
87
|
-
# Return a
|
|
88
|
-
|
|
103
|
+
# Return a copy to prevent modifications to the cached tree.
|
|
104
|
+
# Uses pickle instead of deepcopy to avoid RecursionError on
|
|
105
|
+
# deeply nested trees.
|
|
106
|
+
return self._fast_copy(tree)
|
|
89
107
|
except FileNotFoundError:
|
|
90
108
|
path = getattr(self, attr_name)
|
|
91
109
|
raise PhykitUserError(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from typing import Dict
|
|
2
|
-
import
|
|
2
|
+
import pickle
|
|
3
3
|
|
|
4
4
|
from Bio.Phylo import Newick
|
|
5
5
|
|
|
@@ -20,7 +20,7 @@ class BranchLengthMultiplier(Tree):
|
|
|
20
20
|
def run(self) -> None:
|
|
21
21
|
tree = self.read_tree_file()
|
|
22
22
|
# Make a deep copy to avoid modifying the cached tree
|
|
23
|
-
tree_copy =
|
|
23
|
+
tree_copy = pickle.loads(pickle.dumps(tree, protocol=pickle.HIGHEST_PROTOCOL))
|
|
24
24
|
scaled_count = self.multiply_branch_lengths_by_factor(tree_copy, self.factor)
|
|
25
25
|
self.write_tree_file(tree_copy, self.output_file_path)
|
|
26
26
|
|
|
@@ -7,7 +7,7 @@ produces a phylogram/cladogram plot with annotated character changes.
|
|
|
7
7
|
|
|
8
8
|
Uses the generalized parsimony utilities in phykit.helpers.parsimony_utils.
|
|
9
9
|
"""
|
|
10
|
-
import
|
|
10
|
+
import pickle
|
|
11
11
|
from collections import Counter
|
|
12
12
|
from typing import Dict, List, Optional, Set, Tuple
|
|
13
13
|
|
|
@@ -79,7 +79,7 @@ class CharacterMap(Tree):
|
|
|
79
79
|
|
|
80
80
|
def run(self) -> None:
|
|
81
81
|
tree = self.read_tree_file()
|
|
82
|
-
tree =
|
|
82
|
+
tree = pickle.loads(pickle.dumps(tree, protocol=pickle.HIGHEST_PROTOCOL))
|
|
83
83
|
|
|
84
84
|
char_names, tip_states = self._parse_character_matrix(self.data_path)
|
|
85
85
|
n_chars = len(char_names)
|