phykit 2.1.74__tar.gz → 2.1.75__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.74 → phykit-2.1.75}/PKG-INFO +1 -1
- {phykit-2.1.74 → phykit-2.1.75}/phykit/phykit.py +23 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/consensus_network.py +123 -13
- phykit-2.1.75/phykit/version.py +1 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit.egg-info/PKG-INFO +1 -1
- phykit-2.1.74/phykit/version.py +0 -1
- {phykit-2.1.74 → phykit-2.1.75}/LICENSE.md +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/README.md +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/__init__.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/__main__.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/cli_registry.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/errors.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/__init__.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/boolean_argument_parsing.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/caching.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/circular_layout.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/color_annotations.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/discrete_models.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/files.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/json_output.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/parallel.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/parsimony_utils.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/plot_config.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/quartet_utils.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/stats_summary.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/helpers/streaming.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/service_factories.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/__init__.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/__init__.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/alignment_entropy.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/alignment_length.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/alignment_recoding.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/alignment_subsample.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/base.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/column_score.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/composition_per_taxon.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/dfoil.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/dna_threader.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/dstatistic.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/faidx.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/gc_content.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/identity_matrix.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/mask_alignment.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/pairwise_identity.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/phylo_gwas.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/plot_alignment_qc.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/rcv.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/rcvt.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/rename_fasta_entries.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/alignment/variable_sites.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/base.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/__init__.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/ancestral_reconstruction.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/base.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/bipartition_support_stats.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/branch_length_multiplier.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/character_map.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/collapse_branches.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/concordance_asr.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/consensus_tree.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/cont_map.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/cophylo.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/density_map.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/discordance_asymmetry.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/dvmc.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/evo_tempo_map.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/evolutionary_rate.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/fit_continuous.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/fit_discrete.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/hidden_paralogy_check.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/independent_contrasts.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/internal_branch_stats.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/internode_labeler.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/kf_distance.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/lb_score.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/ltt.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/monophyly_check.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/network_signal.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/ou_shift_detection.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/ouwie.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/parsimony_score.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/patristic_distances.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phenogram.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phylo_heatmap.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phylo_impute.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phylo_logistic.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phylogenetic_glm.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phylogenetic_ordination.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phylogenetic_regression.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phylogenetic_signal.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/phylomorphospace.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/polytomy_test.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/print_tree.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/prune_tree.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/quartet_network.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/quartet_pie.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/rate_heterogeneity.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/relative_rate_test.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/rename_tree_tips.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/rf_distance.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/root_tree.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/saturation.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/spectral_discordance.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/spurious_sequence.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/stochastic_character_map.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/terminal_branch_stats.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/threshold_model.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/tip_labels.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/tip_to_tip_distance.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/total_tree_length.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/trait_correlation.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/trait_rate_map.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/tree_space.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/treeness.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/treeness_over_rcv.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit/services/tree/vcv_utils.py +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit.egg-info/SOURCES.txt +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit.egg-info/dependency_links.txt +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit.egg-info/entry_points.txt +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit.egg-info/requires.txt +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/phykit.egg-info/top_level.txt +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/setup.cfg +0 -0
- {phykit-2.1.74 → phykit-2.1.75}/setup.py +0 -0
|
@@ -5759,6 +5759,15 @@ class Phykit:
|
|
|
5759
5759
|
circular splits network
|
|
5760
5760
|
plot (optional)
|
|
5761
5761
|
|
|
5762
|
+
--max-splits maximum number of splits
|
|
5763
|
+
to include in the network
|
|
5764
|
+
graph (default: 30; higher
|
|
5765
|
+
splits are still reported
|
|
5766
|
+
in text/JSON output)
|
|
5767
|
+
|
|
5768
|
+
--histogram output filename for a split
|
|
5769
|
+
frequency histogram (optional)
|
|
5770
|
+
|
|
5762
5771
|
--fig-width figure width in inches
|
|
5763
5772
|
(auto-scaled if omitted)
|
|
5764
5773
|
|
|
@@ -5833,6 +5842,20 @@ class Phykit:
|
|
|
5833
5842
|
required=False,
|
|
5834
5843
|
help=SUPPRESS,
|
|
5835
5844
|
)
|
|
5845
|
+
parser.add_argument(
|
|
5846
|
+
"--max-splits",
|
|
5847
|
+
type=int,
|
|
5848
|
+
default=30,
|
|
5849
|
+
required=False,
|
|
5850
|
+
help=SUPPRESS,
|
|
5851
|
+
)
|
|
5852
|
+
parser.add_argument(
|
|
5853
|
+
"--histogram",
|
|
5854
|
+
type=str,
|
|
5855
|
+
default=None,
|
|
5856
|
+
required=False,
|
|
5857
|
+
help=SUPPRESS,
|
|
5858
|
+
)
|
|
5836
5859
|
add_plot_arguments(parser)
|
|
5837
5860
|
_add_json_argument(parser)
|
|
5838
5861
|
_run_service(parser, argv, ConsensusNetwork)
|
|
@@ -19,6 +19,8 @@ class ConsensusNetwork(Tree):
|
|
|
19
19
|
super().__init__(trees=parsed["trees"])
|
|
20
20
|
self.threshold = parsed["threshold"]
|
|
21
21
|
self.missing_taxa = parsed["missing_taxa"]
|
|
22
|
+
self.max_splits = parsed["max_splits"]
|
|
23
|
+
self.histogram = parsed["histogram"]
|
|
22
24
|
self.plot_output = parsed["plot_output"]
|
|
23
25
|
self.json_output = parsed["json_output"]
|
|
24
26
|
self.plot_config = parsed["plot_config"]
|
|
@@ -28,6 +30,8 @@ class ConsensusNetwork(Tree):
|
|
|
28
30
|
trees=args.trees,
|
|
29
31
|
threshold=args.threshold,
|
|
30
32
|
missing_taxa=args.missing_taxa,
|
|
33
|
+
max_splits=getattr(args, "max_splits", 30),
|
|
34
|
+
histogram=getattr(args, "histogram", None),
|
|
31
35
|
plot_output=getattr(args, "plot_output", None),
|
|
32
36
|
json_output=getattr(args, "json", False),
|
|
33
37
|
plot_config=PlotConfig.from_args(args),
|
|
@@ -414,19 +418,41 @@ class ConsensusNetwork(Tree):
|
|
|
414
418
|
)
|
|
415
419
|
|
|
416
420
|
config = self.plot_config
|
|
417
|
-
|
|
421
|
+
n = len(ordering)
|
|
422
|
+
# Force square figure for network graphs
|
|
423
|
+
if config.fig_width is None and config.fig_height is None:
|
|
424
|
+
size = max(10, min(30, 8 + n * 0.05))
|
|
425
|
+
config.fig_width = size
|
|
426
|
+
config.fig_height = size
|
|
427
|
+
config.resolve(n_rows=n, n_cols=None)
|
|
418
428
|
fig, ax = plt.subplots(1, 1, figsize=(config.fig_width, config.fig_height))
|
|
419
429
|
ax.set_aspect("equal")
|
|
420
430
|
|
|
431
|
+
# Determine label fontsize: auto-suppress for large trees
|
|
432
|
+
if config.ylabel_fontsize is not None:
|
|
433
|
+
label_fontsize = config.ylabel_fontsize
|
|
434
|
+
elif n > 100:
|
|
435
|
+
label_fontsize = 0 # auto-hide for very large trees
|
|
436
|
+
elif n > 50:
|
|
437
|
+
label_fontsize = max(3, 8 - (n - 50) * 0.1)
|
|
438
|
+
else:
|
|
439
|
+
label_fontsize = 10
|
|
440
|
+
|
|
421
441
|
if not splits_list:
|
|
422
442
|
# No splits: place taxa evenly on a circle
|
|
423
443
|
for taxon in ordering:
|
|
424
444
|
angle = angles[taxon]
|
|
425
445
|
x = math.cos(angle)
|
|
426
446
|
y = math.sin(angle)
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
447
|
+
if label_fontsize > 0:
|
|
448
|
+
deg = math.degrees(angle)
|
|
449
|
+
ha = "left" if -90 < deg < 90 or deg > 270 else "right"
|
|
450
|
+
rotation = deg if -90 < deg < 90 or deg > 270 else deg + 180
|
|
451
|
+
ax.text(x, y, taxon, ha=ha, va="center",
|
|
452
|
+
fontsize=label_fontsize, rotation=rotation,
|
|
453
|
+
rotation_mode="anchor")
|
|
454
|
+
else:
|
|
455
|
+
ax.plot(x, y, "o", color="black", markersize=2)
|
|
430
456
|
else:
|
|
431
457
|
# Compute node positions: pos = sum(sign_i * weight_i * dir_i) / 2
|
|
432
458
|
node_positions = {}
|
|
@@ -453,6 +479,16 @@ class ConsensusNetwork(Tree):
|
|
|
453
479
|
x2, y2 = node_positions[s2]
|
|
454
480
|
ax.plot([x1, x2], [y1, y2], "-", color="black", linewidth=1.5, zorder=2)
|
|
455
481
|
|
|
482
|
+
# Find taxa on split boundaries (participating in displayed splits)
|
|
483
|
+
boundary_taxa = set()
|
|
484
|
+
for split, count, freq in circular_splits:
|
|
485
|
+
for i in range(n):
|
|
486
|
+
curr = ordering[i]
|
|
487
|
+
nxt = ordering[(i + 1) % n]
|
|
488
|
+
if (curr in split) != (nxt in split):
|
|
489
|
+
boundary_taxa.add(curr)
|
|
490
|
+
boundary_taxa.add(nxt)
|
|
491
|
+
|
|
456
492
|
# Draw pendant edges and taxon labels
|
|
457
493
|
for taxon in ordering:
|
|
458
494
|
angle = angles[taxon]
|
|
@@ -460,14 +496,30 @@ class ConsensusNetwork(Tree):
|
|
|
460
496
|
nx, ny = node_positions[taxon_signs[taxon]]
|
|
461
497
|
else:
|
|
462
498
|
nx, ny = 0.0, 0.0
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
499
|
+
|
|
500
|
+
# Only draw full pendant edges for boundary taxa;
|
|
501
|
+
# non-boundary taxa get a short stub or are skipped
|
|
502
|
+
if taxon in boundary_taxa or n <= 50:
|
|
503
|
+
plen = pendant_len
|
|
504
|
+
else:
|
|
505
|
+
plen = pendant_len * 0.3 # short stub
|
|
506
|
+
|
|
507
|
+
tx = nx + plen * math.cos(angle)
|
|
508
|
+
ty = ny + plen * math.sin(angle)
|
|
509
|
+
ax.plot([nx, tx], [ny, ty], "-", color="black",
|
|
510
|
+
linewidth=0.8 if taxon not in boundary_taxa else 1.5,
|
|
511
|
+
alpha=0.3 if taxon not in boundary_taxa else 1.0,
|
|
512
|
+
zorder=2)
|
|
513
|
+
|
|
514
|
+
if label_fontsize > 0 and (taxon in boundary_taxa or n <= 50):
|
|
515
|
+
lx = tx + 0.03 * math.cos(angle)
|
|
516
|
+
ly = ty + 0.03 * math.sin(angle)
|
|
517
|
+
deg = math.degrees(angle)
|
|
518
|
+
ha = "left" if -90 < deg < 90 or deg > 270 else "right"
|
|
519
|
+
rotation = deg if -90 < deg < 90 or deg > 270 else deg + 180
|
|
520
|
+
ax.text(lx, ly, taxon, ha=ha, va="center",
|
|
521
|
+
fontsize=label_fontsize, rotation=rotation,
|
|
522
|
+
rotation_mode="anchor", zorder=4)
|
|
471
523
|
|
|
472
524
|
# Frequency labels on internal edges (one per split)
|
|
473
525
|
labeled_splits = set()
|
|
@@ -497,6 +549,35 @@ class ConsensusNetwork(Tree):
|
|
|
497
549
|
# Output
|
|
498
550
|
# ------------------------------------------------------------------
|
|
499
551
|
|
|
552
|
+
def _draw_histogram(self, split_counts, n_trees, output_path):
|
|
553
|
+
"""Draw a histogram of split frequencies."""
|
|
554
|
+
import matplotlib
|
|
555
|
+
matplotlib.use("Agg")
|
|
556
|
+
import matplotlib.pyplot as plt
|
|
557
|
+
|
|
558
|
+
config = self.plot_config
|
|
559
|
+
config.resolve(n_rows=10, n_cols=None)
|
|
560
|
+
|
|
561
|
+
frequencies = [count / n_trees for count in split_counts.values()]
|
|
562
|
+
|
|
563
|
+
fig, ax = plt.subplots(figsize=(config.fig_width or 10, config.fig_height or 6))
|
|
564
|
+
ax.hist(frequencies, bins=50, color="#377eb8", edgecolor="black", linewidth=0.5)
|
|
565
|
+
ax.set_xlabel("Split frequency", fontsize=config.axis_fontsize or 12)
|
|
566
|
+
ax.set_ylabel("Number of splits", fontsize=config.axis_fontsize or 12)
|
|
567
|
+
ax.axvline(x=self.threshold, color="red", linestyle="--", lw=1,
|
|
568
|
+
label=f"Threshold ({self.threshold})")
|
|
569
|
+
ax.legend(fontsize=9)
|
|
570
|
+
|
|
571
|
+
if config.show_title:
|
|
572
|
+
ax.set_title(
|
|
573
|
+
config.title or "Split Frequency Distribution",
|
|
574
|
+
fontsize=config.title_fontsize or 14,
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
fig.tight_layout()
|
|
578
|
+
fig.savefig(output_path, dpi=config.dpi, bbox_inches="tight")
|
|
579
|
+
plt.close(fig)
|
|
580
|
+
|
|
500
581
|
def _format_split(self, split: frozenset) -> str:
|
|
501
582
|
return "{" + ", ".join(sorted(split)) + "}"
|
|
502
583
|
|
|
@@ -551,6 +632,35 @@ class ConsensusNetwork(Tree):
|
|
|
551
632
|
for split, count, freq in filtered:
|
|
552
633
|
print(f"{self._format_split(split)}\t{count}/{n_trees}\t{freq:.4f}")
|
|
553
634
|
|
|
635
|
+
if self.histogram:
|
|
636
|
+
self._draw_histogram(split_counts, n_trees, self.histogram)
|
|
637
|
+
if not self.json_output:
|
|
638
|
+
print(f"Histogram saved: {self.histogram}")
|
|
639
|
+
|
|
554
640
|
if self.plot_output:
|
|
641
|
+
import sys
|
|
642
|
+
n_taxa = len(all_taxa)
|
|
643
|
+
|
|
644
|
+
if n_taxa > 100:
|
|
645
|
+
print(
|
|
646
|
+
f"Warning: {n_taxa} taxa — network graph may not be "
|
|
647
|
+
f"informative at this scale. Consider using "
|
|
648
|
+
f"--histogram for a split frequency distribution instead.",
|
|
649
|
+
file=sys.stderr,
|
|
650
|
+
)
|
|
651
|
+
|
|
555
652
|
ordering = self._compute_circular_ordering(trees, all_taxa)
|
|
556
|
-
|
|
653
|
+
|
|
654
|
+
# Cap splits for graph visualization to avoid exponential blowup
|
|
655
|
+
plot_filtered = filtered
|
|
656
|
+
if len(filtered) > self.max_splits:
|
|
657
|
+
print(
|
|
658
|
+
f"Warning: {len(filtered)} splits above threshold; "
|
|
659
|
+
f"using top {self.max_splits} for network graph "
|
|
660
|
+
f"(use --max-splits to adjust).",
|
|
661
|
+
file=sys.stderr,
|
|
662
|
+
)
|
|
663
|
+
# filtered is already sorted by frequency (descending)
|
|
664
|
+
plot_filtered = filtered[:self.max_splits]
|
|
665
|
+
|
|
666
|
+
self._draw_network(ordering, plot_filtered, all_taxa, self.plot_output)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.75"
|
phykit-2.1.74/phykit/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.1.74"
|
|
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
|
|
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
|