phykit 2.1.67__tar.gz → 2.1.69__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.67 → phykit-2.1.69}/PKG-INFO +1 -1
- {phykit-2.1.67 → phykit-2.1.69}/phykit/cli_registry.py +3 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/phykit.py +102 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/service_factories.py +1 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/__init__.py +1 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/ancestral_reconstruction.py +63 -2
- phykit-2.1.69/phykit/services/tree/phylo_logistic.py +607 -0
- phykit-2.1.69/phykit/version.py +1 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit.egg-info/PKG-INFO +1 -1
- {phykit-2.1.67 → phykit-2.1.69}/phykit.egg-info/SOURCES.txt +1 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit.egg-info/entry_points.txt +3 -0
- {phykit-2.1.67 → phykit-2.1.69}/setup.py +3 -0
- phykit-2.1.67/phykit/version.py +0 -1
- {phykit-2.1.67 → phykit-2.1.69}/LICENSE.md +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/README.md +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/__init__.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/__main__.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/errors.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/__init__.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/boolean_argument_parsing.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/caching.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/circular_layout.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/color_annotations.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/discrete_models.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/files.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/json_output.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/parallel.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/parsimony_utils.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/plot_config.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/quartet_utils.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/stats_summary.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/helpers/streaming.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/__init__.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/__init__.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/alignment_entropy.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/alignment_length.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/alignment_recoding.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/alignment_subsample.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/base.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/column_score.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/composition_per_taxon.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/dfoil.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/dna_threader.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/dstatistic.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/faidx.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/gc_content.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/identity_matrix.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/mask_alignment.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/pairwise_identity.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/plot_alignment_qc.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/rcv.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/rcvt.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/rename_fasta_entries.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/alignment/variable_sites.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/base.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/base.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/bipartition_support_stats.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/branch_length_multiplier.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/character_map.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/collapse_branches.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/concordance_asr.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/consensus_network.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/consensus_tree.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/cont_map.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/cophylo.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/density_map.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/discordance_asymmetry.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/dvmc.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/evo_tempo_map.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/evolutionary_rate.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/fit_continuous.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/fit_discrete.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/hidden_paralogy_check.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/independent_contrasts.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/internal_branch_stats.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/internode_labeler.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/kf_distance.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/lb_score.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/ltt.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/monophyly_check.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/network_signal.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/ou_shift_detection.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/ouwie.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/parsimony_score.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/patristic_distances.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/phenogram.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/phylo_heatmap.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/phylogenetic_glm.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/phylogenetic_ordination.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/phylogenetic_regression.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/phylogenetic_signal.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/phylomorphospace.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/polytomy_test.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/print_tree.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/prune_tree.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/quartet_network.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/quartet_pie.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/rate_heterogeneity.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/relative_rate_test.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/rename_tree_tips.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/rf_distance.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/root_tree.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/saturation.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/spectral_discordance.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/spurious_sequence.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/stochastic_character_map.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/terminal_branch_stats.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/threshold_model.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/tip_labels.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/tip_to_tip_distance.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/total_tree_length.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/trait_correlation.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/trait_rate_map.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/tree_space.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/treeness.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/treeness_over_rcv.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit/services/tree/vcv_utils.py +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit.egg-info/dependency_links.txt +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit.egg-info/requires.txt +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/phykit.egg-info/top_level.txt +0 -0
- {phykit-2.1.67 → phykit-2.1.69}/setup.cfg +0 -0
|
@@ -97,6 +97,9 @@ ALIAS_TO_HANDLER: Dict[str, str] = {
|
|
|
97
97
|
"pgls": "phylogenetic_regression",
|
|
98
98
|
"phylo_glm": "phylogenetic_glm",
|
|
99
99
|
"pglm": "phylogenetic_glm",
|
|
100
|
+
"phylo_logistic": "phylo_logistic",
|
|
101
|
+
"phylo_logreg": "phylo_logistic",
|
|
102
|
+
"plogreg": "phylo_logistic",
|
|
100
103
|
"parsimony": "parsimony_score",
|
|
101
104
|
"pars": "parsimony_score",
|
|
102
105
|
"charmap": "character_map",
|
|
@@ -237,6 +237,8 @@ class Phykit:
|
|
|
237
237
|
- fit phylogenetic generalized least squares (PGLS) regression
|
|
238
238
|
phylogenetic_glm (alias: phylo_glm; pglm)
|
|
239
239
|
- fit phylogenetic GLM for binary (logistic) or count (Poisson) data
|
|
240
|
+
phylo_logistic (alias: phylo_logreg; plogreg)
|
|
241
|
+
- fit phylogenetic logistic regression (Ives & Garland 2010)
|
|
240
242
|
stochastic_character_map (alias: simmap; scm)
|
|
241
243
|
- stochastic character mapping (SIMMAP) of discrete traits
|
|
242
244
|
cont_map (alias: contmap; cmap)
|
|
@@ -2431,6 +2433,16 @@ class Phykit:
|
|
|
2431
2433
|
and branch colors (iTOL-
|
|
2432
2434
|
inspired TSV format)
|
|
2433
2435
|
|
|
2436
|
+
--plot-ci draw confidence interval bars
|
|
2437
|
+
at internal nodes on the
|
|
2438
|
+
contMap plot (requires --ci
|
|
2439
|
+
and --plot)
|
|
2440
|
+
|
|
2441
|
+
--ci-size scale factor for CI bar
|
|
2442
|
+
size (default: 1.0; use
|
|
2443
|
+
2.0 for larger, 0.5 for
|
|
2444
|
+
smaller)
|
|
2445
|
+
|
|
2434
2446
|
--json output results as JSON
|
|
2435
2447
|
"""
|
|
2436
2448
|
),
|
|
@@ -2464,6 +2476,14 @@ class Phykit:
|
|
|
2464
2476
|
"--plot", type=str, required=False, default=None,
|
|
2465
2477
|
help=SUPPRESS, metavar=""
|
|
2466
2478
|
)
|
|
2479
|
+
parser.add_argument(
|
|
2480
|
+
"--plot-ci", action="store_true", required=False, default=False,
|
|
2481
|
+
help=SUPPRESS,
|
|
2482
|
+
)
|
|
2483
|
+
parser.add_argument(
|
|
2484
|
+
"--ci-size", type=float, required=False, default=1.0,
|
|
2485
|
+
help=SUPPRESS, metavar=""
|
|
2486
|
+
)
|
|
2467
2487
|
add_plot_arguments(parser)
|
|
2468
2488
|
_add_json_argument(parser)
|
|
2469
2489
|
_run_service(parser, argv, AncestralReconstruction)
|
|
@@ -4269,6 +4289,84 @@ class Phykit:
|
|
|
4269
4289
|
_add_json_argument(parser)
|
|
4270
4290
|
_run_service(parser, argv, PhylogeneticGLM)
|
|
4271
4291
|
|
|
4292
|
+
@staticmethod
|
|
4293
|
+
def phylo_logistic(argv):
|
|
4294
|
+
parser = _new_parser(
|
|
4295
|
+
description=textwrap.dedent(
|
|
4296
|
+
f"""\
|
|
4297
|
+
{help_header}
|
|
4298
|
+
|
|
4299
|
+
Fit a Phylogenetic Logistic Regression for binary (0/1)
|
|
4300
|
+
response data while accounting for phylogenetic
|
|
4301
|
+
non-independence among species (Ives & Garland 2010).
|
|
4302
|
+
|
|
4303
|
+
Uses Maximum Penalized Likelihood Estimation (logistic_MPLE)
|
|
4304
|
+
with Firth's bias-correction penalty and jointly estimates
|
|
4305
|
+
the phylogenetic signal parameter alpha via the
|
|
4306
|
+
OU-transformed variance-covariance matrix.
|
|
4307
|
+
|
|
4308
|
+
Input is a phylogenetic tree and a tab-delimited multi-trait
|
|
4309
|
+
file with a header row:
|
|
4310
|
+
taxon<tab>trait1<tab>trait2<tab>...
|
|
4311
|
+
|
|
4312
|
+
Output includes coefficient estimates, standard errors,
|
|
4313
|
+
z-values, p-values, alpha, log-likelihood, penalized
|
|
4314
|
+
log-likelihood, and AIC.
|
|
4315
|
+
|
|
4316
|
+
Aliases:
|
|
4317
|
+
phylo_logistic, phylo_logreg, plogreg
|
|
4318
|
+
Command line interfaces:
|
|
4319
|
+
pk_phylo_logistic, pk_phylo_logreg, pk_plogreg
|
|
4320
|
+
|
|
4321
|
+
Usage:
|
|
4322
|
+
phykit phylo_logistic -t <tree> -d <trait_data> --response <column> --predictor <column> [--method logistic_MPLE|logistic_IG10] [--json]
|
|
4323
|
+
|
|
4324
|
+
Options
|
|
4325
|
+
=====================================================
|
|
4326
|
+
-t/--tree a tree file
|
|
4327
|
+
|
|
4328
|
+
-d/--trait-data tab-delimited multi-trait file
|
|
4329
|
+
with header row
|
|
4330
|
+
|
|
4331
|
+
--response binary response column name
|
|
4332
|
+
(must contain only 0 and 1)
|
|
4333
|
+
|
|
4334
|
+
--predictor predictor column name(s),
|
|
4335
|
+
comma-separated for multiple
|
|
4336
|
+
|
|
4337
|
+
--method estimation method: logistic_MPLE
|
|
4338
|
+
or logistic_IG10
|
|
4339
|
+
(default: logistic_MPLE)
|
|
4340
|
+
|
|
4341
|
+
--json optional argument to output
|
|
4342
|
+
results as JSON
|
|
4343
|
+
"""
|
|
4344
|
+
),
|
|
4345
|
+
)
|
|
4346
|
+
parser.add_argument(
|
|
4347
|
+
"-t", "--tree", type=str, required=True, help=SUPPRESS, metavar=""
|
|
4348
|
+
)
|
|
4349
|
+
parser.add_argument(
|
|
4350
|
+
"-d", "--trait-data", type=str, required=True, help=SUPPRESS, metavar=""
|
|
4351
|
+
)
|
|
4352
|
+
parser.add_argument(
|
|
4353
|
+
"--response", type=str, required=True, help=SUPPRESS, metavar=""
|
|
4354
|
+
)
|
|
4355
|
+
parser.add_argument(
|
|
4356
|
+
"--predictor", type=str, required=True, help=SUPPRESS, metavar=""
|
|
4357
|
+
)
|
|
4358
|
+
parser.add_argument(
|
|
4359
|
+
"--method",
|
|
4360
|
+
type=str,
|
|
4361
|
+
required=False,
|
|
4362
|
+
default="logistic_MPLE",
|
|
4363
|
+
choices=["logistic_MPLE", "logistic_IG10"],
|
|
4364
|
+
help=SUPPRESS,
|
|
4365
|
+
metavar="",
|
|
4366
|
+
)
|
|
4367
|
+
_add_json_argument(parser)
|
|
4368
|
+
_run_service(parser, argv, PhyloLogistic)
|
|
4369
|
+
|
|
4272
4370
|
@staticmethod
|
|
4273
4371
|
def stochastic_character_map(argv):
|
|
4274
4372
|
parser = _new_parser(
|
|
@@ -8174,6 +8272,10 @@ def phylogenetic_glm(argv=None):
|
|
|
8174
8272
|
Phykit.phylogenetic_glm(sys.argv[1:])
|
|
8175
8273
|
|
|
8176
8274
|
|
|
8275
|
+
def phylo_logistic(argv=None):
|
|
8276
|
+
Phykit.phylo_logistic(sys.argv[1:])
|
|
8277
|
+
|
|
8278
|
+
|
|
8177
8279
|
def stochastic_character_map(argv=None):
|
|
8178
8280
|
Phykit.stochastic_character_map(sys.argv[1:])
|
|
8179
8281
|
|
|
@@ -71,6 +71,7 @@ PhylogeneticOrdination = _LazyServiceFactory("phykit.services.tree.phylogenetic_
|
|
|
71
71
|
Phylomorphospace = _LazyServiceFactory("phykit.services.tree.phylomorphospace", "Phylomorphospace")
|
|
72
72
|
PhylogeneticRegression = _LazyServiceFactory("phykit.services.tree.phylogenetic_regression", "PhylogeneticRegression")
|
|
73
73
|
PhylogeneticGLM = _LazyServiceFactory("phykit.services.tree.phylogenetic_glm", "PhylogeneticGLM")
|
|
74
|
+
PhyloLogistic = _LazyServiceFactory("phykit.services.tree.phylo_logistic", "PhyloLogistic")
|
|
74
75
|
StochasticCharacterMap = _LazyServiceFactory("phykit.services.tree.stochastic_character_map", "StochasticCharacterMap")
|
|
75
76
|
ContMap = _LazyServiceFactory("phykit.services.tree.cont_map", "ContMap")
|
|
76
77
|
DensityMap = _LazyServiceFactory("phykit.services.tree.density_map", "DensityMap")
|
|
@@ -48,6 +48,7 @@ _EXPORTS = {
|
|
|
48
48
|
"ThresholdModel": "threshold_model",
|
|
49
49
|
"TreenessOverRCV": "treeness_over_rcv",
|
|
50
50
|
"ConcordanceAsr": "concordance_asr",
|
|
51
|
+
"PhyloLogistic": "phylo_logistic",
|
|
51
52
|
"TraitCorrelation": "trait_correlation",
|
|
52
53
|
"TraitRateMap": "trait_rate_map",
|
|
53
54
|
"TreeSpace": "tree_space",
|
|
@@ -48,6 +48,8 @@ class AncestralReconstruction(Tree):
|
|
|
48
48
|
self.json_output = parsed["json_output"]
|
|
49
49
|
self.trait_type = parsed["trait_type"]
|
|
50
50
|
self.model = parsed["model"]
|
|
51
|
+
self.plot_ci = parsed["plot_ci"]
|
|
52
|
+
self.ci_size = parsed["ci_size"]
|
|
51
53
|
self.plot_config = parsed["plot_config"]
|
|
52
54
|
|
|
53
55
|
def run(self) -> None:
|
|
@@ -112,9 +114,11 @@ class AncestralReconstruction(Tree):
|
|
|
112
114
|
)
|
|
113
115
|
|
|
114
116
|
if self.plot_output:
|
|
117
|
+
ci_data = node_cis if (self.plot_ci and self.ci and node_cis) else None
|
|
115
118
|
self._plot_contmap(
|
|
116
119
|
tree_copy, node_estimates, node_labels,
|
|
117
|
-
trait_values, trait_name, self.plot_output
|
|
120
|
+
trait_values, trait_name, self.plot_output,
|
|
121
|
+
node_cis=ci_data,
|
|
118
122
|
)
|
|
119
123
|
result["plot_output"] = self.plot_output
|
|
120
124
|
|
|
@@ -144,6 +148,8 @@ class AncestralReconstruction(Tree):
|
|
|
144
148
|
json_output=getattr(args, "json", False),
|
|
145
149
|
trait_type=getattr(args, "type", "continuous"),
|
|
146
150
|
model=getattr(args, "model", "ER"),
|
|
151
|
+
plot_ci=getattr(args, "plot_ci", False),
|
|
152
|
+
ci_size=getattr(args, "ci_size", 1.0),
|
|
147
153
|
plot_config=PlotConfig.from_args(args),
|
|
148
154
|
)
|
|
149
155
|
|
|
@@ -809,7 +815,7 @@ class AncestralReconstruction(Tree):
|
|
|
809
815
|
|
|
810
816
|
def _plot_contmap(
|
|
811
817
|
self, tree, node_estimates, node_labels, trait_values,
|
|
812
|
-
trait_name, output_path,
|
|
818
|
+
trait_name, output_path, node_cis=None,
|
|
813
819
|
) -> None:
|
|
814
820
|
try:
|
|
815
821
|
import matplotlib
|
|
@@ -827,6 +833,7 @@ class AncestralReconstruction(Tree):
|
|
|
827
833
|
|
|
828
834
|
# Build estimates dict keyed by id(clade) for all nodes
|
|
829
835
|
all_estimates = {}
|
|
836
|
+
node_labels_map = {} # id(clade) → label string (for CI lookup)
|
|
830
837
|
for clade in tree.find_clades(order="preorder"):
|
|
831
838
|
if clade.is_terminal():
|
|
832
839
|
if clade.name in trait_values:
|
|
@@ -834,6 +841,7 @@ class AncestralReconstruction(Tree):
|
|
|
834
841
|
else:
|
|
835
842
|
if id(clade) in node_labels:
|
|
836
843
|
label = node_labels[id(clade)]
|
|
844
|
+
node_labels_map[id(clade)] = label
|
|
837
845
|
if label in node_estimates:
|
|
838
846
|
all_estimates[id(clade)] = node_estimates[label]
|
|
839
847
|
|
|
@@ -1043,6 +1051,59 @@ class AncestralReconstruction(Tree):
|
|
|
1043
1051
|
fontsize=config.title_fontsize,
|
|
1044
1052
|
)
|
|
1045
1053
|
|
|
1054
|
+
# Draw CI bars at internal nodes if requested
|
|
1055
|
+
if node_cis:
|
|
1056
|
+
ci_scale = self.ci_size
|
|
1057
|
+
# Build label→id mapping for looking up CI values
|
|
1058
|
+
label_to_id = {}
|
|
1059
|
+
for clade in tree.find_clades(order="preorder"):
|
|
1060
|
+
if not clade.is_terminal() and id(clade) in node_labels_map:
|
|
1061
|
+
label_to_id[node_labels_map[id(clade)]] = id(clade)
|
|
1062
|
+
|
|
1063
|
+
if config.circular:
|
|
1064
|
+
# Circular: use data coordinates from coords dict
|
|
1065
|
+
for label, (ci_lo, ci_hi) in node_cis.items():
|
|
1066
|
+
cid = label_to_id.get(label)
|
|
1067
|
+
if cid is None or cid not in coords:
|
|
1068
|
+
continue
|
|
1069
|
+
cx = coords[cid]["x"]
|
|
1070
|
+
cy = coords[cid]["y"]
|
|
1071
|
+
angle = coords[cid]["angle"]
|
|
1072
|
+
# CI bar perpendicular to the radius (tangential)
|
|
1073
|
+
bar_len = (ci_hi - ci_lo) * ci_scale * 0.3
|
|
1074
|
+
dx = -np.sin(angle) * bar_len / 2
|
|
1075
|
+
dy = np.cos(angle) * bar_len / 2
|
|
1076
|
+
# Main bar
|
|
1077
|
+
ax.plot([cx - dx, cx + dx], [cy - dy, cy + dy],
|
|
1078
|
+
color="black", lw=1.5 * ci_scale, zorder=8)
|
|
1079
|
+
# Point estimate dot
|
|
1080
|
+
ax.scatter(cx, cy, s=15 * ci_scale, c="black", zorder=9)
|
|
1081
|
+
else:
|
|
1082
|
+
# Rectangular: vertical bars at node positions
|
|
1083
|
+
max_x_val = max(node_x.values()) if node_x else 1.0
|
|
1084
|
+
for label, (ci_lo, ci_hi) in node_cis.items():
|
|
1085
|
+
cid = label_to_id.get(label)
|
|
1086
|
+
if cid is None or cid not in node_x or cid not in node_y:
|
|
1087
|
+
continue
|
|
1088
|
+
cx = node_x[cid]
|
|
1089
|
+
cy = node_y[cid]
|
|
1090
|
+
est = all_estimates.get(cid, (ci_lo + ci_hi) / 2)
|
|
1091
|
+
# Scale CI width relative to y-axis range
|
|
1092
|
+
bar_half = (ci_hi - ci_lo) / (vmax - vmin) * 0.4 * ci_scale if vmax != vmin else 0.2 * ci_scale
|
|
1093
|
+
cap_width = max_x_val * 0.008 * ci_scale
|
|
1094
|
+
# Vertical bar
|
|
1095
|
+
ax.plot([cx, cx], [cy - bar_half, cy + bar_half],
|
|
1096
|
+
color="black", lw=1.2 * ci_scale, zorder=8)
|
|
1097
|
+
# Caps
|
|
1098
|
+
ax.plot([cx - cap_width, cx + cap_width],
|
|
1099
|
+
[cy - bar_half, cy - bar_half],
|
|
1100
|
+
color="black", lw=1.0 * ci_scale, zorder=8)
|
|
1101
|
+
ax.plot([cx - cap_width, cx + cap_width],
|
|
1102
|
+
[cy + bar_half, cy + bar_half],
|
|
1103
|
+
color="black", lw=1.0 * ci_scale, zorder=8)
|
|
1104
|
+
# Point estimate dot
|
|
1105
|
+
ax.scatter(cx, cy, s=12 * ci_scale, c="black", zorder=9)
|
|
1106
|
+
|
|
1046
1107
|
fig.tight_layout()
|
|
1047
1108
|
fig.savefig(output_path, dpi=config.dpi, bbox_inches="tight")
|
|
1048
1109
|
plt.close(fig)
|