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.
Files changed (106) hide show
  1. {phykit-2.1.26 → phykit-2.1.27}/PKG-INFO +1 -1
  2. {phykit-2.1.26 → phykit-2.1.27}/phykit/cli_registry.py +3 -0
  3. {phykit-2.1.26 → phykit-2.1.27}/phykit/phykit.py +66 -0
  4. {phykit-2.1.26 → phykit-2.1.27}/phykit/service_factories.py +1 -0
  5. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/__init__.py +1 -0
  6. phykit-2.1.27/phykit/services/tree/ltt.py +217 -0
  7. phykit-2.1.27/phykit/version.py +1 -0
  8. {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/PKG-INFO +1 -1
  9. {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/SOURCES.txt +1 -0
  10. {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/entry_points.txt +3 -0
  11. {phykit-2.1.26 → phykit-2.1.27}/setup.py +3 -0
  12. phykit-2.1.26/phykit/version.py +0 -1
  13. {phykit-2.1.26 → phykit-2.1.27}/LICENSE.md +0 -0
  14. {phykit-2.1.26 → phykit-2.1.27}/README.md +0 -0
  15. {phykit-2.1.26 → phykit-2.1.27}/phykit/__init__.py +0 -0
  16. {phykit-2.1.26 → phykit-2.1.27}/phykit/__main__.py +0 -0
  17. {phykit-2.1.26 → phykit-2.1.27}/phykit/errors.py +0 -0
  18. {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/__init__.py +0 -0
  19. {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/boolean_argument_parsing.py +0 -0
  20. {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/caching.py +0 -0
  21. {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/files.py +0 -0
  22. {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/json_output.py +0 -0
  23. {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/parallel.py +0 -0
  24. {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/stats_summary.py +0 -0
  25. {phykit-2.1.26 → phykit-2.1.27}/phykit/helpers/streaming.py +0 -0
  26. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/__init__.py +0 -0
  27. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/__init__.py +0 -0
  28. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_entropy.py +0 -0
  29. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_length.py +0 -0
  30. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
  31. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
  32. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/alignment_recoding.py +0 -0
  33. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/base.py +0 -0
  34. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/column_score.py +0 -0
  35. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/composition_per_taxon.py +0 -0
  36. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
  37. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
  38. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/dna_threader.py +0 -0
  39. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
  40. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/faidx.py +0 -0
  41. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/gc_content.py +0 -0
  42. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/mask_alignment.py +0 -0
  43. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
  44. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/pairwise_identity.py +0 -0
  45. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
  46. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/plot_alignment_qc.py +0 -0
  47. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/rcv.py +0 -0
  48. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/rcvt.py +0 -0
  49. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/rename_fasta_entries.py +0 -0
  50. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
  51. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/alignment/variable_sites.py +0 -0
  52. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/base.py +0 -0
  53. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/ancestral_reconstruction.py +0 -0
  54. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/base.py +0 -0
  55. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/bipartition_support_stats.py +0 -0
  56. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/branch_length_multiplier.py +0 -0
  57. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/collapse_branches.py +0 -0
  58. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/consensus_network.py +0 -0
  59. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/consensus_tree.py +0 -0
  60. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/cont_map.py +0 -0
  61. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/cophylo.py +0 -0
  62. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
  63. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/density_map.py +0 -0
  64. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/dvmc.py +0 -0
  65. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/evolutionary_rate.py +0 -0
  66. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/fit_continuous.py +0 -0
  67. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/hidden_paralogy_check.py +0 -0
  68. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/internal_branch_stats.py +0 -0
  69. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/internode_labeler.py +0 -0
  70. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
  71. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/lb_score.py +0 -0
  72. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/monophyly_check.py +0 -0
  73. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
  74. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/network_signal.py +0 -0
  75. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/ou_shift_detection.py +0 -0
  76. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/ouwie.py +0 -0
  77. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/patristic_distances.py +0 -0
  78. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phenogram.py +0 -0
  79. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylogenetic_glm.py +0 -0
  80. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylogenetic_ordination.py +0 -0
  81. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylogenetic_regression.py +0 -0
  82. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylogenetic_signal.py +0 -0
  83. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/phylomorphospace.py +0 -0
  84. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/polytomy_test.py +0 -0
  85. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/print_tree.py +0 -0
  86. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/prune_tree.py +0 -0
  87. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/quartet_network.py +0 -0
  88. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/rate_heterogeneity.py +0 -0
  89. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/relative_rate_test.py +0 -0
  90. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/rename_tree_tips.py +0 -0
  91. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/rf_distance.py +0 -0
  92. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/root_tree.py +0 -0
  93. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/saturation.py +0 -0
  94. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/spurious_sequence.py +0 -0
  95. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/stochastic_character_map.py +0 -0
  96. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/terminal_branch_stats.py +0 -0
  97. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/tip_labels.py +0 -0
  98. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/tip_to_tip_distance.py +0 -0
  99. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
  100. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/total_tree_length.py +0 -0
  101. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/treeness.py +0 -0
  102. {phykit-2.1.26 → phykit-2.1.27}/phykit/services/tree/treeness_over_rcv.py +0 -0
  103. {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/dependency_links.txt +0 -0
  104. {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/requires.txt +0 -0
  105. {phykit-2.1.26 → phykit-2.1.27}/phykit.egg-info/top_level.txt +0 -0
  106. {phykit-2.1.26 → phykit-2.1.27}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phykit
3
- Version: 2.1.26
3
+ Version: 2.1.27
4
4
  Home-page: https://github.com/jlsteenwyk/phykit
5
5
  Author: Jacob L. Steenwyk
6
6
  Author-email: jlsteenwyk@gmail.com
@@ -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"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phykit
3
- Version: 2.1.26
3
+ Version: 2.1.27
4
4
  Home-page: https://github.com/jlsteenwyk/phykit
5
5
  Author: Jacob L. Steenwyk
6
6
  Author-email: jlsteenwyk@gmail.com
@@ -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",
@@ -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