phykit 2.1.26__tar.gz → 2.1.27__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.26 → phykit-2.1.27}/PKG-INFO +1 -1
- {phykit-2.1.26 → phykit-2.1.27}/phykit/cli_registry.py +3 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/phykit.py +66 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/service_factories.py +1 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/__init__.py +1 -0
- phykit-2.1.27/phykit/services/tree/ltt.py +217 -0
- phykit-2.1.27/phykit/version.py +1 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/PKG-INFO +1 -1
- {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/SOURCES.txt +1 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/entry_points.txt +3 -0
- {phykit-2.1.26 → phykit-2.1.27}/setup.py +3 -0
- phykit-2.1.26/phykit/version.py +0 -1
- {phykit-2.1.26 → phykit-2.1.27}/LICENSE.md +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/README.md +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/__init__.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/__main__.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/errors.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/__init__.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/boolean_argument_parsing.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/caching.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/files.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/json_output.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/parallel.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/stats_summary.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/streaming.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/__init__.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/__init__.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_entropy.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_length.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_recoding.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/base.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/column_score.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/composition_per_taxon.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/dna_threader.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/faidx.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/gc_content.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/mask_alignment.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/pairwise_identity.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/plot_alignment_qc.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/rcv.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/rcvt.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/rename_fasta_entries.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/variable_sites.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/base.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/ancestral_reconstruction.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/base.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/bipartition_support_stats.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/branch_length_multiplier.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/collapse_branches.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/consensus_network.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/consensus_tree.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/cont_map.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/cophylo.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/density_map.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/dvmc.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/evolutionary_rate.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/fit_continuous.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/hidden_paralogy_check.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/internal_branch_stats.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/internode_labeler.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/lb_score.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/monophyly_check.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/network_signal.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/ou_shift_detection.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/ouwie.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/patristic_distances.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phenogram.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylogenetic_glm.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylogenetic_ordination.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylogenetic_regression.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylogenetic_signal.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylomorphospace.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/polytomy_test.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/print_tree.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/prune_tree.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/quartet_network.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/rate_heterogeneity.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/relative_rate_test.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/rename_tree_tips.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/rf_distance.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/root_tree.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/saturation.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/spurious_sequence.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/stochastic_character_map.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/terminal_branch_stats.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/tip_labels.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/tip_to_tip_distance.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/total_tree_length.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/treeness.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/treeness_over_rcv.py +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/dependency_links.txt +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/requires.txt +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/top_level.txt +0 -0
- {phykit-2.1.26 → phykit-2.1.27}/setup.cfg +0 -0
|
@@ -112,6 +112,9 @@ ALIAS_TO_HANDLER: Dict[str, str] = {
|
|
|
112
112
|
"quartet_net": "quartet_network",
|
|
113
113
|
"qnet": "quartet_network",
|
|
114
114
|
"nanuq": "quartet_network",
|
|
115
|
+
"ltt": "ltt",
|
|
116
|
+
"gamma_stat": "ltt",
|
|
117
|
+
"gamma": "ltt",
|
|
115
118
|
"netsig": "network_signal",
|
|
116
119
|
"net_signal": "network_signal",
|
|
117
120
|
"rrt": "relative_rate_test",
|
|
@@ -235,6 +235,9 @@ class Phykit:
|
|
|
235
235
|
quartet_network (alias: quartet_net; qnet; nanuq)
|
|
236
236
|
- quartet-based network inference (NANUQ-style)
|
|
237
237
|
distinguishing ILS from hybridization
|
|
238
|
+
ltt (alias: gamma_stat; gamma)
|
|
239
|
+
- lineage-through-time plot and Pybus & Harvey
|
|
240
|
+
gamma statistic for diversification rate testing
|
|
238
241
|
network_signal (alias: netsig; net_signal)
|
|
239
242
|
- phylogenetic signal on a network
|
|
240
243
|
polytomy_test (alias: polyt_test; polyt; ptt)
|
|
@@ -3834,6 +3837,65 @@ class Phykit:
|
|
|
3834
3837
|
_add_json_argument(parser)
|
|
3835
3838
|
_run_service(parser, argv, NetworkSignal)
|
|
3836
3839
|
|
|
3840
|
+
@staticmethod
|
|
3841
|
+
def ltt(argv):
|
|
3842
|
+
parser = _new_parser(
|
|
3843
|
+
description=textwrap.dedent(
|
|
3844
|
+
f"""\
|
|
3845
|
+
{help_header}
|
|
3846
|
+
|
|
3847
|
+
Lineage-through-time plot and gamma statistic.
|
|
3848
|
+
|
|
3849
|
+
Computes the Pybus & Harvey (2000) gamma statistic
|
|
3850
|
+
to test for temporal variation in diversification
|
|
3851
|
+
rates. Under a constant-rate pure-birth process,
|
|
3852
|
+
gamma ~ N(0,1). Negative values indicate early
|
|
3853
|
+
diversification (decelerating); positive values
|
|
3854
|
+
indicate late diversification (accelerating).
|
|
3855
|
+
|
|
3856
|
+
Optionally generates a lineage-through-time plot.
|
|
3857
|
+
|
|
3858
|
+
Aliases:
|
|
3859
|
+
ltt, gamma_stat, gamma
|
|
3860
|
+
Command line interfaces:
|
|
3861
|
+
pk_ltt, pk_gamma_stat, pk_gamma
|
|
3862
|
+
|
|
3863
|
+
Usage:
|
|
3864
|
+
phykit ltt -t <tree> [-v/--verbose]
|
|
3865
|
+
[--plot-output <file>] [--json]
|
|
3866
|
+
|
|
3867
|
+
Options
|
|
3868
|
+
=====================================================
|
|
3869
|
+
-t/--tree a rooted phylogeny file
|
|
3870
|
+
with branch lengths
|
|
3871
|
+
(required)
|
|
3872
|
+
|
|
3873
|
+
-v/--verbose print branching times
|
|
3874
|
+
and LTT data points
|
|
3875
|
+
|
|
3876
|
+
--plot-output output filename for the
|
|
3877
|
+
lineage-through-time
|
|
3878
|
+
plot (optional)
|
|
3879
|
+
|
|
3880
|
+
--json optional argument to output
|
|
3881
|
+
results as JSON
|
|
3882
|
+
"""
|
|
3883
|
+
),
|
|
3884
|
+
)
|
|
3885
|
+
parser.add_argument("-t", "--tree", type=str, required=True, help=SUPPRESS)
|
|
3886
|
+
parser.add_argument(
|
|
3887
|
+
"-v", "--verbose", action="store_true", required=False, help=SUPPRESS
|
|
3888
|
+
)
|
|
3889
|
+
parser.add_argument(
|
|
3890
|
+
"--plot-output",
|
|
3891
|
+
type=str,
|
|
3892
|
+
default=None,
|
|
3893
|
+
required=False,
|
|
3894
|
+
help=SUPPRESS,
|
|
3895
|
+
)
|
|
3896
|
+
_add_json_argument(parser)
|
|
3897
|
+
_run_service(parser, argv, LTT)
|
|
3898
|
+
|
|
3837
3899
|
@staticmethod
|
|
3838
3900
|
def prune_tree(argv):
|
|
3839
3901
|
parser = _new_parser(
|
|
@@ -4926,6 +4988,10 @@ def quartet_network(argv=None):
|
|
|
4926
4988
|
Phykit.quartet_network(sys.argv[1:])
|
|
4927
4989
|
|
|
4928
4990
|
|
|
4991
|
+
def ltt(argv=None):
|
|
4992
|
+
Phykit.ltt(sys.argv[1:])
|
|
4993
|
+
|
|
4994
|
+
|
|
4929
4995
|
def network_signal(argv=None):
|
|
4930
4996
|
Phykit.network_signal(sys.argv[1:])
|
|
4931
4997
|
|
|
@@ -77,6 +77,7 @@ OUwie = _LazyServiceFactory("phykit.services.tree.ouwie", "OUwie")
|
|
|
77
77
|
OUShiftDetection = _LazyServiceFactory("phykit.services.tree.ou_shift_detection", "OUShiftDetection")
|
|
78
78
|
QuartetNetwork = _LazyServiceFactory("phykit.services.tree.quartet_network", "QuartetNetwork")
|
|
79
79
|
NetworkSignal = _LazyServiceFactory("phykit.services.tree.network_signal", "NetworkSignal")
|
|
80
|
+
LTT = _LazyServiceFactory("phykit.services.tree.ltt", "LTT")
|
|
80
81
|
RelativeRateTest = _LazyServiceFactory("phykit.services.tree.relative_rate_test", "RelativeRateTest")
|
|
81
82
|
PolytomyTest = _LazyServiceFactory("phykit.services.tree.polytomy_test", "PolytomyTest")
|
|
82
83
|
PrintTree = _LazyServiceFactory("phykit.services.tree.print_tree", "PrintTree")
|
|
@@ -15,6 +15,7 @@ _EXPORTS = {
|
|
|
15
15
|
"InternodeLabeler": "internode_labeler",
|
|
16
16
|
"LastCommonAncestorSubtree": "last_common_ancestor_subtree",
|
|
17
17
|
"LBScore": "lb_score",
|
|
18
|
+
"LTT": "ltt",
|
|
18
19
|
"MonophylyCheck": "monophyly_check",
|
|
19
20
|
"NearestNeighborInterchange": "nearest_neighbor_interchange",
|
|
20
21
|
"PatristicDistances": "patristic_distances",
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Dict, List, Tuple
|
|
3
|
+
|
|
4
|
+
from .base import Tree
|
|
5
|
+
from ...helpers.json_output import print_json
|
|
6
|
+
from ...errors import PhykitUserError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LTT(Tree):
|
|
10
|
+
def __init__(self, args) -> None:
|
|
11
|
+
parsed = self.process_args(args)
|
|
12
|
+
super().__init__(tree_file_path=parsed["tree_file_path"])
|
|
13
|
+
self.verbose = parsed["verbose"]
|
|
14
|
+
self.json_output = parsed["json_output"]
|
|
15
|
+
self.plot_output = parsed["plot_output"]
|
|
16
|
+
|
|
17
|
+
def run(self) -> None:
|
|
18
|
+
tree = self.read_tree_file()
|
|
19
|
+
self._validate_tree(tree)
|
|
20
|
+
|
|
21
|
+
gamma, p_value, bt, g = self._compute_gamma(tree)
|
|
22
|
+
ltt_data = self._compute_ltt(tree)
|
|
23
|
+
|
|
24
|
+
if self.json_output:
|
|
25
|
+
self._output_json(gamma, p_value, ltt_data, bt, g)
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
self._output_text(gamma, p_value, ltt_data, bt, g)
|
|
29
|
+
|
|
30
|
+
if self.plot_output:
|
|
31
|
+
self._plot_ltt(ltt_data, self.plot_output, gamma=gamma, p_value=p_value)
|
|
32
|
+
|
|
33
|
+
def process_args(self, args) -> Dict[str, str]:
|
|
34
|
+
return dict(
|
|
35
|
+
tree_file_path=args.tree,
|
|
36
|
+
verbose=getattr(args, "verbose", False),
|
|
37
|
+
json_output=getattr(args, "json", False),
|
|
38
|
+
plot_output=getattr(args, "plot_output", None),
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def _validate_tree(self, tree) -> None:
|
|
42
|
+
tips = list(tree.get_terminals())
|
|
43
|
+
if len(tips) < 3:
|
|
44
|
+
raise PhykitUserError(
|
|
45
|
+
["Tree must have at least 3 tips for gamma statistic."],
|
|
46
|
+
code=2,
|
|
47
|
+
)
|
|
48
|
+
for clade in tree.find_clades():
|
|
49
|
+
if clade.branch_length is None and clade != tree.root:
|
|
50
|
+
raise PhykitUserError(
|
|
51
|
+
["All branches in the tree must have lengths."],
|
|
52
|
+
code=2,
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
@staticmethod
|
|
56
|
+
def _compute_gamma(tree):
|
|
57
|
+
"""Pybus & Harvey (2000) gamma statistic, matching ape::gammaStat().
|
|
58
|
+
|
|
59
|
+
Replicates the exact algorithm from R's ape source (gammaStat.R):
|
|
60
|
+
N <- length(phy$tip.label)
|
|
61
|
+
bt <- sort(branching.times(phy))
|
|
62
|
+
g <- rev(c(bt[1], diff(bt)))
|
|
63
|
+
ST <- sum((2:N) * g)
|
|
64
|
+
stat <- sum(cumsum((2:(N-1)) * g[-(N-1)])) / (N-2)
|
|
65
|
+
m <- ST / 2
|
|
66
|
+
s <- ST * sqrt(1 / (12 * (N - 2)))
|
|
67
|
+
(stat - m) / s
|
|
68
|
+
"""
|
|
69
|
+
tips = list(tree.get_terminals())
|
|
70
|
+
N = len(tips)
|
|
71
|
+
|
|
72
|
+
if N < 3:
|
|
73
|
+
raise PhykitUserError(
|
|
74
|
+
["Tree must have at least 3 tips for gamma statistic."],
|
|
75
|
+
code=2,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Get branching times (node ages = distance from present/tips)
|
|
79
|
+
root = tree.root
|
|
80
|
+
max_height = max(tree.distance(root, tip) for tip in tips)
|
|
81
|
+
|
|
82
|
+
bt = []
|
|
83
|
+
for clade in tree.find_clades(order="level"):
|
|
84
|
+
if clade.is_terminal():
|
|
85
|
+
continue
|
|
86
|
+
node_dist_from_root = tree.distance(root, clade)
|
|
87
|
+
node_age = max_height - node_dist_from_root
|
|
88
|
+
bt.append(node_age)
|
|
89
|
+
|
|
90
|
+
bt.sort() # ascending: most recent nodes first
|
|
91
|
+
|
|
92
|
+
# Internode intervals reversed (ape: g <- rev(c(bt[1], diff(bt))))
|
|
93
|
+
# This orders intervals from past to present (root interval first)
|
|
94
|
+
g_unreversed = [bt[0]] + [bt[i] - bt[i - 1] for i in range(1, len(bt))]
|
|
95
|
+
g = list(reversed(g_unreversed))
|
|
96
|
+
|
|
97
|
+
# ST = sum((2:N) * g)
|
|
98
|
+
ST = sum((k + 2) * g[k] for k in range(N - 1))
|
|
99
|
+
|
|
100
|
+
# stat = sum(cumsum((2:(N-1)) * g[-(N-1)])) / (N-2)
|
|
101
|
+
# g[-(N-1)] in R removes the last element
|
|
102
|
+
g_partial = g[:-1]
|
|
103
|
+
partial = [(k + 2) * g_partial[k] for k in range(N - 2)]
|
|
104
|
+
cumsum_partial = []
|
|
105
|
+
running = 0.0
|
|
106
|
+
for val in partial:
|
|
107
|
+
running += val
|
|
108
|
+
cumsum_partial.append(running)
|
|
109
|
+
|
|
110
|
+
stat = sum(cumsum_partial) / (N - 2)
|
|
111
|
+
|
|
112
|
+
m = ST / 2
|
|
113
|
+
s = ST * math.sqrt(1.0 / (12 * (N - 2)))
|
|
114
|
+
gamma = (stat - m) / s
|
|
115
|
+
|
|
116
|
+
# Two-tailed p-value from standard normal
|
|
117
|
+
from scipy.stats import norm
|
|
118
|
+
|
|
119
|
+
p_value = 2 * norm.sf(abs(gamma))
|
|
120
|
+
|
|
121
|
+
return gamma, p_value, bt, g
|
|
122
|
+
|
|
123
|
+
@staticmethod
|
|
124
|
+
def _compute_ltt(tree):
|
|
125
|
+
"""Compute lineage-through-time data.
|
|
126
|
+
|
|
127
|
+
Returns list of (time_from_root, n_lineages) tuples.
|
|
128
|
+
Time runs from 0 (root) to tree_height (present).
|
|
129
|
+
"""
|
|
130
|
+
# Get branching times as distances from root
|
|
131
|
+
root = tree.root
|
|
132
|
+
max_height = max(
|
|
133
|
+
tree.distance(root, tip) for tip in tree.get_terminals()
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
branching_times_from_root = []
|
|
137
|
+
for clade in tree.find_clades(order="level"):
|
|
138
|
+
if clade.is_terminal():
|
|
139
|
+
continue
|
|
140
|
+
if clade == root:
|
|
141
|
+
continue
|
|
142
|
+
branching_times_from_root.append(tree.distance(root, clade))
|
|
143
|
+
|
|
144
|
+
branching_times_from_root.sort()
|
|
145
|
+
|
|
146
|
+
# LTT: start with 2 lineages at root (time=0)
|
|
147
|
+
ltt = [(0.0, 2)]
|
|
148
|
+
n_lineages = 2
|
|
149
|
+
for bt in branching_times_from_root:
|
|
150
|
+
n_lineages += 1
|
|
151
|
+
ltt.append((bt, n_lineages))
|
|
152
|
+
# End at present
|
|
153
|
+
ltt.append((max_height, n_lineages))
|
|
154
|
+
|
|
155
|
+
return ltt
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def _plot_ltt(ltt_data, output_path, gamma=None, p_value=None):
|
|
159
|
+
"""Plot lineage-through-time as a step function."""
|
|
160
|
+
import matplotlib
|
|
161
|
+
|
|
162
|
+
matplotlib.use("Agg")
|
|
163
|
+
import matplotlib.pyplot as plt
|
|
164
|
+
|
|
165
|
+
times = [pt[0] for pt in ltt_data]
|
|
166
|
+
lineages = [pt[1] for pt in ltt_data]
|
|
167
|
+
|
|
168
|
+
fig, ax = plt.subplots(figsize=(8, 5))
|
|
169
|
+
ax.step(times, lineages, where="post", linewidth=2, color="black")
|
|
170
|
+
ax.set_xlabel("Time from root", fontsize=12)
|
|
171
|
+
ax.set_ylabel("Number of lineages", fontsize=12)
|
|
172
|
+
ax.set_yscale("log")
|
|
173
|
+
|
|
174
|
+
if gamma is not None:
|
|
175
|
+
label = f"\u03b3 = {gamma:.4f}"
|
|
176
|
+
if p_value is not None:
|
|
177
|
+
label += f" (p = {p_value:.4e})"
|
|
178
|
+
ax.text(
|
|
179
|
+
0.05,
|
|
180
|
+
0.95,
|
|
181
|
+
label,
|
|
182
|
+
transform=ax.transAxes,
|
|
183
|
+
fontsize=11,
|
|
184
|
+
verticalalignment="top",
|
|
185
|
+
bbox=dict(boxstyle="round", facecolor="wheat", alpha=0.8),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
fig.tight_layout()
|
|
189
|
+
fig.savefig(output_path, dpi=300, bbox_inches="tight")
|
|
190
|
+
plt.close(fig)
|
|
191
|
+
|
|
192
|
+
def _output_text(self, gamma, p_value, ltt_data, bt, g):
|
|
193
|
+
try:
|
|
194
|
+
print(f"{round(gamma, 4)}\t{round(p_value, 4)}")
|
|
195
|
+
if self.verbose:
|
|
196
|
+
print("\nBranching times (node ages):")
|
|
197
|
+
for i, t in enumerate(bt):
|
|
198
|
+
print(f" {i + 1}\t{t:.6f}")
|
|
199
|
+
print("\nLineage-through-time:")
|
|
200
|
+
print(" time_from_root\tn_lineages")
|
|
201
|
+
for time_val, n_lin in ltt_data:
|
|
202
|
+
print(f" {time_val:.6f}\t{n_lin}")
|
|
203
|
+
except BrokenPipeError:
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
def _output_json(self, gamma, p_value, ltt_data, bt, g):
|
|
207
|
+
result = dict(
|
|
208
|
+
gamma=float(gamma),
|
|
209
|
+
p_value=float(p_value),
|
|
210
|
+
branching_times=[float(t) for t in bt],
|
|
211
|
+
internode_intervals=[float(v) for v in g],
|
|
212
|
+
ltt=[
|
|
213
|
+
dict(time_from_root=float(t), n_lineages=int(n))
|
|
214
|
+
for t, n in ltt_data
|
|
215
|
+
],
|
|
216
|
+
)
|
|
217
|
+
print_json(result)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.27"
|
|
@@ -69,6 +69,7 @@ phykit/services/tree/internal_branch_stats.py
|
|
|
69
69
|
phykit/services/tree/internode_labeler.py
|
|
70
70
|
phykit/services/tree/last_common_ancestor_subtree.py
|
|
71
71
|
phykit/services/tree/lb_score.py
|
|
72
|
+
phykit/services/tree/ltt.py
|
|
72
73
|
phykit/services/tree/monophyly_check.py
|
|
73
74
|
phykit/services/tree/nearest_neighbor_interchange.py
|
|
74
75
|
phykit/services/tree/network_signal.py
|
|
@@ -64,6 +64,8 @@ pk_fc = phykit.phykit:fit_continuous
|
|
|
64
64
|
pk_fit_continuous = phykit.phykit:fit_continuous
|
|
65
65
|
pk_fit_ouwie = phykit.phykit:ouwie
|
|
66
66
|
pk_fitcontinuous = phykit.phykit:fit_continuous
|
|
67
|
+
pk_gamma = phykit.phykit:ltt
|
|
68
|
+
pk_gamma_stat = phykit.phykit:ltt
|
|
67
69
|
pk_gc = phykit.phykit:gc_content
|
|
68
70
|
pk_gc_content = phykit.phykit:gc_content
|
|
69
71
|
pk_ge = phykit.phykit:faidx
|
|
@@ -81,6 +83,7 @@ pk_lb_score = phykit.phykit:lb_score
|
|
|
81
83
|
pk_lbs = phykit.phykit:lb_score
|
|
82
84
|
pk_lca_subtree = phykit.phykit:last_common_ancestor_subtree
|
|
83
85
|
pk_long_branch_score = phykit.phykit:lb_score
|
|
86
|
+
pk_ltt = phykit.phykit:ltt
|
|
84
87
|
pk_mask = phykit.phykit:mask_alignment
|
|
85
88
|
pk_mask_alignment = phykit.phykit:mask_alignment
|
|
86
89
|
pk_mask_aln = phykit.phykit:mask_alignment
|
|
@@ -196,6 +196,9 @@ setup(
|
|
|
196
196
|
"pk_quartet_net = phykit.phykit:quartet_network",
|
|
197
197
|
"pk_qnet = phykit.phykit:quartet_network",
|
|
198
198
|
"pk_nanuq = phykit.phykit:quartet_network",
|
|
199
|
+
"pk_ltt = phykit.phykit:ltt",
|
|
200
|
+
"pk_gamma_stat = phykit.phykit:ltt",
|
|
201
|
+
"pk_gamma = phykit.phykit:ltt",
|
|
199
202
|
"pk_network_signal = phykit.phykit:network_signal",
|
|
200
203
|
"pk_netsig = phykit.phykit:network_signal",
|
|
201
204
|
"pk_net_signal = phykit.phykit:network_signal",
|
phykit-2.1.26/phykit/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.1.26"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|