phykit 2.1.32__tar.gz → 2.1.34__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 (111) hide show
  1. {phykit-2.1.32 → phykit-2.1.34}/PKG-INFO +1 -1
  2. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/discordance_asymmetry.py +96 -29
  3. phykit-2.1.34/phykit/version.py +1 -0
  4. {phykit-2.1.32 → phykit-2.1.34}/phykit.egg-info/PKG-INFO +1 -1
  5. phykit-2.1.32/phykit/version.py +0 -1
  6. {phykit-2.1.32 → phykit-2.1.34}/LICENSE.md +0 -0
  7. {phykit-2.1.32 → phykit-2.1.34}/README.md +0 -0
  8. {phykit-2.1.32 → phykit-2.1.34}/phykit/__init__.py +0 -0
  9. {phykit-2.1.32 → phykit-2.1.34}/phykit/__main__.py +0 -0
  10. {phykit-2.1.32 → phykit-2.1.34}/phykit/cli_registry.py +0 -0
  11. {phykit-2.1.32 → phykit-2.1.34}/phykit/errors.py +0 -0
  12. {phykit-2.1.32 → phykit-2.1.34}/phykit/helpers/__init__.py +0 -0
  13. {phykit-2.1.32 → phykit-2.1.34}/phykit/helpers/boolean_argument_parsing.py +0 -0
  14. {phykit-2.1.32 → phykit-2.1.34}/phykit/helpers/caching.py +0 -0
  15. {phykit-2.1.32 → phykit-2.1.34}/phykit/helpers/files.py +0 -0
  16. {phykit-2.1.32 → phykit-2.1.34}/phykit/helpers/json_output.py +0 -0
  17. {phykit-2.1.32 → phykit-2.1.34}/phykit/helpers/parallel.py +0 -0
  18. {phykit-2.1.32 → phykit-2.1.34}/phykit/helpers/stats_summary.py +0 -0
  19. {phykit-2.1.32 → phykit-2.1.34}/phykit/helpers/streaming.py +0 -0
  20. {phykit-2.1.32 → phykit-2.1.34}/phykit/phykit.py +0 -0
  21. {phykit-2.1.32 → phykit-2.1.34}/phykit/service_factories.py +0 -0
  22. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/__init__.py +0 -0
  23. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/__init__.py +0 -0
  24. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/alignment_entropy.py +0 -0
  25. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/alignment_length.py +0 -0
  26. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
  27. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
  28. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/alignment_recoding.py +0 -0
  29. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/base.py +0 -0
  30. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/column_score.py +0 -0
  31. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/composition_per_taxon.py +0 -0
  32. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
  33. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
  34. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/dna_threader.py +0 -0
  35. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
  36. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/faidx.py +0 -0
  37. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/gc_content.py +0 -0
  38. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/mask_alignment.py +0 -0
  39. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
  40. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/pairwise_identity.py +0 -0
  41. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
  42. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/plot_alignment_qc.py +0 -0
  43. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/rcv.py +0 -0
  44. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/rcvt.py +0 -0
  45. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/rename_fasta_entries.py +0 -0
  46. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
  47. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/alignment/variable_sites.py +0 -0
  48. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/base.py +0 -0
  49. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/__init__.py +0 -0
  50. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/ancestral_reconstruction.py +0 -0
  51. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/base.py +0 -0
  52. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/bipartition_support_stats.py +0 -0
  53. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/branch_length_multiplier.py +0 -0
  54. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/collapse_branches.py +0 -0
  55. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/concordance_asr.py +0 -0
  56. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/consensus_network.py +0 -0
  57. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/consensus_tree.py +0 -0
  58. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/cont_map.py +0 -0
  59. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/cophylo.py +0 -0
  60. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
  61. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/density_map.py +0 -0
  62. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/dvmc.py +0 -0
  63. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/evo_tempo_map.py +0 -0
  64. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/evolutionary_rate.py +0 -0
  65. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/fit_continuous.py +0 -0
  66. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/hidden_paralogy_check.py +0 -0
  67. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/internal_branch_stats.py +0 -0
  68. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/internode_labeler.py +0 -0
  69. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
  70. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/lb_score.py +0 -0
  71. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/ltt.py +0 -0
  72. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/monophyly_check.py +0 -0
  73. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
  74. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/network_signal.py +0 -0
  75. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/ou_shift_detection.py +0 -0
  76. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/ouwie.py +0 -0
  77. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/patristic_distances.py +0 -0
  78. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/phenogram.py +0 -0
  79. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/phylogenetic_glm.py +0 -0
  80. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/phylogenetic_ordination.py +0 -0
  81. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/phylogenetic_regression.py +0 -0
  82. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/phylogenetic_signal.py +0 -0
  83. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/phylomorphospace.py +0 -0
  84. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/polytomy_test.py +0 -0
  85. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/print_tree.py +0 -0
  86. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/prune_tree.py +0 -0
  87. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/quartet_network.py +0 -0
  88. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/rate_heterogeneity.py +0 -0
  89. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/relative_rate_test.py +0 -0
  90. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/rename_tree_tips.py +0 -0
  91. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/rf_distance.py +0 -0
  92. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/root_tree.py +0 -0
  93. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/saturation.py +0 -0
  94. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/spurious_sequence.py +0 -0
  95. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/stochastic_character_map.py +0 -0
  96. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/terminal_branch_stats.py +0 -0
  97. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/threshold_model.py +0 -0
  98. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/tip_labels.py +0 -0
  99. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/tip_to_tip_distance.py +0 -0
  100. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
  101. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/total_tree_length.py +0 -0
  102. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/treeness.py +0 -0
  103. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/treeness_over_rcv.py +0 -0
  104. {phykit-2.1.32 → phykit-2.1.34}/phykit/services/tree/vcv_utils.py +0 -0
  105. {phykit-2.1.32 → phykit-2.1.34}/phykit.egg-info/SOURCES.txt +0 -0
  106. {phykit-2.1.32 → phykit-2.1.34}/phykit.egg-info/dependency_links.txt +0 -0
  107. {phykit-2.1.32 → phykit-2.1.34}/phykit.egg-info/entry_points.txt +0 -0
  108. {phykit-2.1.32 → phykit-2.1.34}/phykit.egg-info/requires.txt +0 -0
  109. {phykit-2.1.32 → phykit-2.1.34}/phykit.egg-info/top_level.txt +0 -0
  110. {phykit-2.1.32 → phykit-2.1.34}/setup.cfg +0 -0
  111. {phykit-2.1.32 → phykit-2.1.34}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phykit
3
- Version: 2.1.32
3
+ Version: 2.1.34
4
4
  Home-page: https://github.com/jlsteenwyk/phykit
5
5
  Author: Jacob L. Steenwyk
6
6
  Author-email: jlsteenwyk@gmail.com
@@ -32,7 +32,7 @@ class DiscordanceAsymmetry(Tree):
32
32
  species_tree = self.read_tree_file()
33
33
  gene_trees = self._parse_gene_trees(self.gene_trees_path)
34
34
 
35
- topology_counts = self._count_topologies(species_tree, gene_trees)
35
+ topology_counts, shared_taxa = self._count_topologies(species_tree, gene_trees)
36
36
 
37
37
  # Test each branch and collect results
38
38
  branch_results = []
@@ -82,7 +82,8 @@ class DiscordanceAsymmetry(Tree):
82
82
  self._output_text(branch_results, summary)
83
83
 
84
84
  if self.plot_output:
85
- self._plot(species_tree, branch_results, self.plot_output)
85
+ self._plot(species_tree, branch_results, self.plot_output,
86
+ shared_taxa=shared_taxa)
86
87
 
87
88
  # ------------------------------------------------------------------
88
89
  # Output methods
@@ -162,7 +163,8 @@ class DiscordanceAsymmetry(Tree):
162
163
  )
163
164
  print_json(result)
164
165
 
165
- def _plot(self, species_tree, branch_results, output_path) -> None:
166
+ def _plot(self, species_tree, branch_results, output_path,
167
+ shared_taxa=None) -> None:
166
168
  """Phylogram colored by asymmetry ratio at each branch."""
167
169
  import matplotlib
168
170
  matplotlib.use("Agg")
@@ -178,7 +180,10 @@ class DiscordanceAsymmetry(Tree):
178
180
 
179
181
  parent_map = self._build_parent_map(species_tree)
180
182
  tips = list(species_tree.get_terminals())
181
- all_taxa_fs = frozenset(t.name for t in tips)
183
+ # Use shared taxa (intersection with gene trees) for split label
184
+ # matching so labels are consistent with _count_topologies.
185
+ all_taxa_fs = (shared_taxa if shared_taxa is not None
186
+ else frozenset(t.name for t in tips))
182
187
 
183
188
  # Compute node positions
184
189
  node_x = {}
@@ -212,7 +217,7 @@ class DiscordanceAsymmetry(Tree):
212
217
  for clade in species_tree.find_clades(order="preorder"):
213
218
  if clade.is_terminal():
214
219
  continue
215
- node_tips = frozenset(t.name for t in clade.get_terminals())
220
+ node_tips = frozenset(t.name for t in clade.get_terminals()) & all_taxa_fs
216
221
  split_label = (
217
222
  sorted(node_tips)
218
223
  if len(node_tips) <= len(all_taxa_fs) - len(node_tips)
@@ -361,6 +366,51 @@ class DiscordanceAsymmetry(Tree):
361
366
  parent_map[id(child)] = clade
362
367
  return parent_map
363
368
 
369
+ @staticmethod
370
+ def _collect_taxa(trees) -> set:
371
+ """Collect the union of all tip names across a list of trees.
372
+
373
+ Uses a direct stack walk over .clades to avoid the overhead of
374
+ Bio.Phylo's find_clades / match_attrs dispatch layer.
375
+ """
376
+ taxa = set()
377
+ for tree in trees:
378
+ stack = [tree.root]
379
+ while stack:
380
+ node = stack.pop()
381
+ if not node.clades:
382
+ taxa.add(node.name)
383
+ else:
384
+ stack.extend(node.clades)
385
+ return taxa
386
+
387
+ def _extract_splits(self, tree, all_taxa_fs) -> set:
388
+ """Extract canonical bipartitions from a single tree.
389
+
390
+ Performs a single postorder traversal, building tip sets
391
+ bottom-up via .clades, and canonicalises each non-trivial split.
392
+ """
393
+ splits = set()
394
+ tip_sets = {}
395
+ stack = [(tree.root, False)]
396
+ while stack:
397
+ node, children_done = stack[-1]
398
+ if not node.clades:
399
+ stack.pop()
400
+ name = node.name
401
+ tip_sets[id(node)] = frozenset((name,)) if name in all_taxa_fs else frozenset()
402
+ elif children_done:
403
+ stack.pop()
404
+ merged = frozenset().union(*(tip_sets[id(c)] for c in node.clades))
405
+ tip_sets[id(node)] = merged
406
+ if len(merged) > 1 and merged != all_taxa_fs:
407
+ splits.add(self._canonical_split(merged, all_taxa_fs))
408
+ else:
409
+ stack[-1] = (node, True)
410
+ for child in reversed(node.clades):
411
+ stack.append((child, False))
412
+ return splits
413
+
364
414
  def _get_four_groups(self, tree, node, parent_map, all_taxa_fs):
365
415
  """Identify the four subtree groups around an internal branch.
366
416
 
@@ -376,26 +426,54 @@ class DiscordanceAsymmetry(Tree):
376
426
  if node.is_terminal() or len(node.clades) < 2:
377
427
  return None
378
428
 
379
- C1 = frozenset(t.name for t in node.clades[0].get_terminals())
380
- C2 = frozenset(t.name for t in node.clades[1].get_terminals())
429
+ C1 = frozenset(t.name for t in node.clades[0].get_terminals()) & all_taxa_fs
430
+ C2 = frozenset(t.name for t in node.clades[1].get_terminals()) & all_taxa_fs
381
431
  # If node has >2 children (polytomy), merge extras into C2
382
432
  for extra_child in node.clades[2:]:
383
- C2 = C2 | frozenset(t.name for t in extra_child.get_terminals())
433
+ C2 = C2 | (frozenset(t.name for t in extra_child.get_terminals()) & all_taxa_fs)
384
434
 
385
435
  parent = parent_map.get(id(node))
386
436
  if parent is None:
387
437
  # node is root — no branch above it
388
438
  return None
389
439
 
390
- # Get siblings of node under parent
440
+ # Get siblings of node under parent; pick the first sibling
441
+ # whose shared taxa are non-empty (avoids skipping valid branches
442
+ # when the first sibling's taxa are all absent from gene trees).
391
443
  siblings = [c for c in parent.clades if id(c) != id(node)]
392
444
  if not siblings:
393
445
  return None
394
446
 
395
- S = frozenset(t.name for t in siblings[0].get_terminals())
447
+ chosen_sib = None
448
+ S = frozenset()
449
+ for sib in siblings:
450
+ candidate = frozenset(t.name for t in sib.get_terminals()) & all_taxa_fs
451
+ if candidate:
452
+ S = candidate
453
+ chosen_sib = sib
454
+ break
455
+
456
+ if not S:
457
+ return None
458
+
396
459
  # D = everything else (other siblings + above parent)
397
460
  D = all_taxa_fs - C1 - C2 - S
398
461
 
462
+ # For the root branch (D empty), the sibling encompasses the
463
+ # entire other side of the tree. Decompose the sibling into its
464
+ # children to obtain proper 4-subtree NNI groups.
465
+ if not D:
466
+ if chosen_sib.is_terminal() or len(chosen_sib.clades) < 2:
467
+ return None
468
+ S = frozenset(t.name for t in chosen_sib.clades[0].get_terminals()) & all_taxa_fs
469
+ D = frozenset(t.name for t in chosen_sib.clades[1].get_terminals()) & all_taxa_fs
470
+ for extra in chosen_sib.clades[2:]:
471
+ D = D | (frozenset(t.name for t in extra.get_terminals()) & all_taxa_fs)
472
+
473
+ # Skip if any group is empty (branch is degenerate after taxon filtering)
474
+ if not C1 or not C2 or not S:
475
+ return None
476
+
399
477
  return C1, C2, S, D
400
478
 
401
479
  def _count_topologies(self, species_tree, gene_trees) -> Dict:
@@ -409,28 +487,17 @@ class DiscordanceAsymmetry(Tree):
409
487
  n_alt1: int
410
488
  n_alt2: int
411
489
  """
412
- all_taxa = sorted(
413
- set(t.name for t in species_tree.get_terminals())
414
- & set().union(*(
415
- set(t.name for t in gt.get_terminals()) for gt in gene_trees
416
- ))
417
- )
418
- all_taxa_fs = frozenset(all_taxa)
490
+ species_taxa = set(t.name for t in species_tree.get_terminals())
491
+ gene_taxa = self._collect_taxa(gene_trees)
492
+ all_taxa_fs = frozenset(sorted(species_taxa & gene_taxa))
419
493
  parent_map = self._build_parent_map(species_tree)
420
494
 
421
495
  # Extract bipartitions from all gene trees (topology only, no lengths).
422
- # Restrict bipartitions to shared taxa without mutating gene tree objects.
496
+ # Builds tip sets bottom-up in a single postorder pass per gene tree,
497
+ # avoiding repeated get_terminals() calls (O(n) vs O(n²) per tree).
423
498
  gene_tree_splits = []
424
499
  for gt in gene_trees:
425
- splits = set()
426
- for clade in gt.get_nonterminals():
427
- tips = frozenset(
428
- t.name for t in clade.get_terminals()
429
- if t.name in all_taxa_fs
430
- )
431
- if len(tips) <= 1 or tips == all_taxa_fs:
432
- continue
433
- splits.add(self._canonical_split(tips, all_taxa_fs))
500
+ splits = self._extract_splits(gt, all_taxa_fs)
434
501
  gene_tree_splits.append(splits)
435
502
 
436
503
  result = {}
@@ -452,7 +519,7 @@ class DiscordanceAsymmetry(Tree):
452
519
  n_alt1 = sum(1 for splits in gene_tree_splits if nni_alt1_bp in splits)
453
520
  n_alt2 = sum(1 for splits in gene_tree_splits if nni_alt2_bp in splits)
454
521
 
455
- node_tips = frozenset(t.name for t in clade.get_terminals())
522
+ node_tips = frozenset(t.name for t in clade.get_terminals()) & all_taxa_fs
456
523
  split_label = (
457
524
  sorted(node_tips)
458
525
  if len(node_tips) <= len(all_taxa_fs) - len(node_tips)
@@ -465,7 +532,7 @@ class DiscordanceAsymmetry(Tree):
465
532
  n_alt1=n_alt1,
466
533
  n_alt2=n_alt2,
467
534
  )
468
- return result
535
+ return result, all_taxa_fs
469
536
 
470
537
  # ------------------------------------------------------------------
471
538
  # Statistical testing
@@ -0,0 +1 @@
1
+ __version__ = "2.1.34"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phykit
3
- Version: 2.1.32
3
+ Version: 2.1.34
4
4
  Home-page: https://github.com/jlsteenwyk/phykit
5
5
  Author: Jacob L. Steenwyk
6
6
  Author-email: jlsteenwyk@gmail.com
@@ -1 +0,0 @@
1
- __version__ = "2.1.32"
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