phykit 2.1.64__tar.gz → 2.1.66__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.64 → phykit-2.1.66}/PKG-INFO +3 -2
- {phykit-2.1.64 → phykit-2.1.66}/phykit/phykit.py +49 -16
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/dstatistic.py +200 -4
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/quartet_pie.py +4 -2
- phykit-2.1.66/phykit/version.py +1 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit.egg-info/PKG-INFO +3 -2
- {phykit-2.1.64 → phykit-2.1.66}/setup.py +2 -1
- phykit-2.1.64/phykit/version.py +0 -1
- {phykit-2.1.64 → phykit-2.1.66}/LICENSE.md +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/README.md +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/__init__.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/__main__.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/cli_registry.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/errors.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/__init__.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/boolean_argument_parsing.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/caching.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/circular_layout.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/color_annotations.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/discrete_models.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/files.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/json_output.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/parallel.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/parsimony_utils.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/plot_config.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/quartet_utils.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/stats_summary.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/helpers/streaming.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/service_factories.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/__init__.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/__init__.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/alignment_entropy.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/alignment_length.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/alignment_recoding.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/alignment_subsample.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/base.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/column_score.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/composition_per_taxon.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/compositional_bias_per_site.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/create_concatenation_matrix.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/dna_threader.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/evolutionary_rate_per_site.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/faidx.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/gc_content.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/identity_matrix.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/mask_alignment.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/pairwise_identity.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/plot_alignment_qc.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/rcv.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/rcvt.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/rename_fasta_entries.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/alignment/variable_sites.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/base.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/__init__.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/ancestral_reconstruction.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/base.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/bipartition_support_stats.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/branch_length_multiplier.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/character_map.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/collapse_branches.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/concordance_asr.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/consensus_network.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/consensus_tree.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/cont_map.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/cophylo.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/covarying_evolutionary_rates.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/density_map.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/discordance_asymmetry.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/dvmc.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/evo_tempo_map.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/evolutionary_rate.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/fit_continuous.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/fit_discrete.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/hidden_paralogy_check.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/independent_contrasts.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/internal_branch_stats.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/internode_labeler.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/kf_distance.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/lb_score.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/ltt.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/monophyly_check.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/network_signal.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/ou_shift_detection.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/ouwie.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/parsimony_score.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/patristic_distances.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/phenogram.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/phylo_heatmap.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/phylogenetic_glm.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/phylogenetic_ordination.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/phylogenetic_regression.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/phylogenetic_signal.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/phylomorphospace.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/polytomy_test.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/print_tree.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/prune_tree.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/quartet_network.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/rate_heterogeneity.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/relative_rate_test.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/rename_tree_tips.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/rf_distance.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/root_tree.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/saturation.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/spectral_discordance.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/spurious_sequence.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/stochastic_character_map.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/terminal_branch_stats.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/threshold_model.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/tip_labels.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/tip_to_tip_distance.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/total_tree_length.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/trait_correlation.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/trait_rate_map.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/tree_space.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/treeness.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/treeness_over_rcv.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit/services/tree/vcv_utils.py +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit.egg-info/SOURCES.txt +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit.egg-info/dependency_links.txt +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit.egg-info/entry_points.txt +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit.egg-info/requires.txt +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/phykit.egg-info/top_level.txt +0 -0
- {phykit-2.1.64 → phykit-2.1.66}/setup.cfg +0 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phykit
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.66
|
|
4
4
|
Home-page: https://github.com/jlsteenwyk/phykit
|
|
5
5
|
Author: Jacob L. Steenwyk
|
|
6
6
|
Author-email: jlsteenwyk@gmail.com
|
|
7
7
|
Classifier: Operating System :: OS Independent
|
|
8
8
|
Classifier: Intended Audience :: Science/Research
|
|
9
9
|
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
14
|
Classifier: Topic :: Scientific/Engineering
|
|
14
|
-
Requires-Python: >=3.
|
|
15
|
+
Requires-Python: >=3.10
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
License-File: LICENSE.md
|
|
17
18
|
Requires-Dist: biopython>=1.82
|
|
@@ -1956,21 +1956,26 @@ class Phykit:
|
|
|
1956
1956
|
{help_header}
|
|
1957
1957
|
|
|
1958
1958
|
Compute Patterson's D-statistic (ABBA-BABA test) for
|
|
1959
|
-
detecting introgression or gene flow
|
|
1960
|
-
alignment.
|
|
1959
|
+
detecting introgression or gene flow.
|
|
1961
1960
|
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
significant excess of either pattern indicates gene
|
|
1966
|
-
flow.
|
|
1961
|
+
Two input modes:
|
|
1962
|
+
1) Site patterns from an alignment (-a)
|
|
1963
|
+
2) Quartet topologies from gene trees (-g)
|
|
1967
1964
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1965
|
+
Species topology: (((P1, P2), P3), Outgroup).
|
|
1966
|
+
Under ILS alone, ABBA and BABA patterns (or
|
|
1967
|
+
discordant topologies) are equally frequent. A
|
|
1968
|
+
significant excess indicates introgression.
|
|
1971
1969
|
|
|
1972
|
-
|
|
1973
|
-
|
|
1970
|
+
D > 0: introgression between P2 and P3.
|
|
1971
|
+
D < 0: introgression between P1 and P3.
|
|
1972
|
+
D = 0: consistent with ILS alone.
|
|
1973
|
+
Note: D identifies which lineages exchanged genes
|
|
1974
|
+
but cannot determine direction of flow.
|
|
1975
|
+
|
|
1976
|
+
Gene trees can have any number of taxa; only the
|
|
1977
|
+
quartet induced by the four specified taxa is
|
|
1978
|
+
evaluated from each tree.
|
|
1974
1979
|
|
|
1975
1980
|
Aliases:
|
|
1976
1981
|
dstatistic, dstat, abba_baba
|
|
@@ -1978,13 +1983,22 @@ class Phykit:
|
|
|
1978
1983
|
pk_dstatistic, pk_dstat, pk_abba_baba
|
|
1979
1984
|
|
|
1980
1985
|
Usage:
|
|
1981
|
-
phykit dstatistic -a <alignment> --p1 <taxon>
|
|
1982
|
-
--p3 <taxon> --outgroup <taxon>
|
|
1986
|
+
phykit dstatistic -a <alignment> --p1 <taxon>
|
|
1987
|
+
--p2 <taxon> --p3 <taxon> --outgroup <taxon>
|
|
1983
1988
|
[--block-size 100] [--json]
|
|
1989
|
+
phykit dstatistic -g <gene_trees> --p1 <taxon>
|
|
1990
|
+
--p2 <taxon> --p3 <taxon> --outgroup <taxon>
|
|
1991
|
+
[--json]
|
|
1984
1992
|
|
|
1985
1993
|
Options
|
|
1986
1994
|
=====================================================
|
|
1987
1995
|
-a/--alignment FASTA alignment file
|
|
1996
|
+
(site-pattern mode)
|
|
1997
|
+
|
|
1998
|
+
-g/--gene-trees gene trees file, one
|
|
1999
|
+
Newick per line (gene-
|
|
2000
|
+
tree mode; trees can
|
|
2001
|
+
have any number of taxa)
|
|
1988
2002
|
|
|
1989
2003
|
--p1 taxon name for P1
|
|
1990
2004
|
(sister to P2)
|
|
@@ -2000,18 +2014,28 @@ class Phykit:
|
|
|
2000
2014
|
|
|
2001
2015
|
--block-size block size for jackknife
|
|
2002
2016
|
estimation of standard
|
|
2003
|
-
error (default: 100
|
|
2017
|
+
error (default: 100;
|
|
2018
|
+
alignment mode only)
|
|
2019
|
+
|
|
2020
|
+
--support minimum branch support
|
|
2021
|
+
threshold for gene trees;
|
|
2022
|
+
branches below this value
|
|
2023
|
+
are collapsed (treated as
|
|
2024
|
+
unresolved). Gene-tree
|
|
2025
|
+
mode only.
|
|
2004
2026
|
|
|
2005
2027
|
--json output results as JSON
|
|
2006
2028
|
"""
|
|
2007
2029
|
),
|
|
2008
2030
|
)
|
|
2009
|
-
parser.add_argument("-a", "--alignment", type=str, required=
|
|
2031
|
+
parser.add_argument("-a", "--alignment", type=str, required=False, default=None, help=SUPPRESS, metavar="")
|
|
2032
|
+
parser.add_argument("-g", "--gene-trees", type=str, required=False, default=None, help=SUPPRESS, metavar="")
|
|
2010
2033
|
parser.add_argument("--p1", type=str, required=True, help=SUPPRESS, metavar="")
|
|
2011
2034
|
parser.add_argument("--p2", type=str, required=True, help=SUPPRESS, metavar="")
|
|
2012
2035
|
parser.add_argument("--p3", type=str, required=True, help=SUPPRESS, metavar="")
|
|
2013
2036
|
parser.add_argument("--outgroup", type=str, required=True, help=SUPPRESS, metavar="")
|
|
2014
2037
|
parser.add_argument("--block-size", type=int, default=100, help=SUPPRESS, metavar="")
|
|
2038
|
+
parser.add_argument("--support", type=float, default=None, help=SUPPRESS, metavar="")
|
|
2015
2039
|
_add_json_argument(parser)
|
|
2016
2040
|
_run_service(parser, argv, Dstatistic)
|
|
2017
2041
|
|
|
@@ -5556,6 +5580,11 @@ class Phykit:
|
|
|
5556
5580
|
--csv output per-branch concordance
|
|
5557
5581
|
values as a CSV file
|
|
5558
5582
|
|
|
5583
|
+
--pie-size scale factor for pie chart
|
|
5584
|
+
size (default: 1.0; use
|
|
5585
|
+
2.0 for double, 0.5 for
|
|
5586
|
+
half, etc.)
|
|
5587
|
+
|
|
5559
5588
|
--json optional argument to output
|
|
5560
5589
|
per-node concordance as JSON
|
|
5561
5590
|
"""
|
|
@@ -5578,6 +5607,10 @@ class Phykit:
|
|
|
5578
5607
|
"--csv", type=str, required=False, default=None,
|
|
5579
5608
|
help=SUPPRESS, metavar=""
|
|
5580
5609
|
)
|
|
5610
|
+
parser.add_argument(
|
|
5611
|
+
"--pie-size", type=float, required=False, default=1.0,
|
|
5612
|
+
help=SUPPRESS, metavar=""
|
|
5613
|
+
)
|
|
5581
5614
|
add_plot_arguments(parser)
|
|
5582
5615
|
_add_json_argument(parser)
|
|
5583
5616
|
_run_service(parser, argv, QuartetPie)
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
"""Patterson's D-statistic (ABBA-BABA test) for detecting introgression.
|
|
1
|
+
"""Patterson's D-statistic (ABBA-BABA test) for detecting introgression.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Supports two modes:
|
|
4
|
+
1) Site patterns from a FASTA alignment (-a)
|
|
5
|
+
2) Quartet topologies from gene trees (-g)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from io import StringIO
|
|
9
|
+
from typing import Dict, List, Optional, Tuple
|
|
4
10
|
|
|
5
11
|
import numpy as np
|
|
6
|
-
from Bio import SeqIO
|
|
12
|
+
from Bio import Phylo, SeqIO
|
|
7
13
|
|
|
8
14
|
from .base import Alignment
|
|
9
15
|
from ...helpers.json_output import print_json
|
|
@@ -14,25 +20,215 @@ class Dstatistic(Alignment):
|
|
|
14
20
|
def __init__(self, args) -> None:
|
|
15
21
|
parsed = self.process_args(args)
|
|
16
22
|
super().__init__(alignment_file_path=parsed["alignment_path"])
|
|
23
|
+
self.gene_trees_path = parsed["gene_trees_path"]
|
|
17
24
|
self.p1 = parsed["p1"]
|
|
18
25
|
self.p2 = parsed["p2"]
|
|
19
26
|
self.p3 = parsed["p3"]
|
|
20
27
|
self.outgroup = parsed["outgroup"]
|
|
21
28
|
self.block_size = parsed["block_size"]
|
|
29
|
+
self.support_threshold = parsed["support_threshold"]
|
|
22
30
|
self.json_output = parsed["json_output"]
|
|
23
31
|
|
|
24
32
|
def process_args(self, args) -> Dict[str, object]:
|
|
33
|
+
aln = getattr(args, "alignment", None)
|
|
34
|
+
gt = getattr(args, "gene_trees", None)
|
|
35
|
+
if aln is None and gt is None:
|
|
36
|
+
raise PhykitUserError(
|
|
37
|
+
["Either -a/--alignment or -g/--gene-trees is required."],
|
|
38
|
+
code=2,
|
|
39
|
+
)
|
|
40
|
+
if aln is not None and gt is not None:
|
|
41
|
+
raise PhykitUserError(
|
|
42
|
+
["-a/--alignment and -g/--gene-trees are mutually exclusive."],
|
|
43
|
+
code=2,
|
|
44
|
+
)
|
|
25
45
|
return dict(
|
|
26
|
-
alignment_path=
|
|
46
|
+
alignment_path=aln,
|
|
47
|
+
gene_trees_path=gt,
|
|
27
48
|
p1=args.p1,
|
|
28
49
|
p2=args.p2,
|
|
29
50
|
p3=args.p3,
|
|
30
51
|
outgroup=args.outgroup,
|
|
31
52
|
block_size=getattr(args, "block_size", 100),
|
|
53
|
+
support_threshold=getattr(args, "support", None),
|
|
32
54
|
json_output=getattr(args, "json", False),
|
|
33
55
|
)
|
|
34
56
|
|
|
35
57
|
def run(self):
|
|
58
|
+
if self.gene_trees_path:
|
|
59
|
+
self._run_gene_tree_mode()
|
|
60
|
+
else:
|
|
61
|
+
self._run_alignment_mode()
|
|
62
|
+
|
|
63
|
+
# ------------------------------------------------------------------
|
|
64
|
+
# Gene tree mode
|
|
65
|
+
# ------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
def _run_gene_tree_mode(self):
|
|
68
|
+
"""Count quartet topologies from gene trees."""
|
|
69
|
+
gene_trees = self._parse_gene_trees(self.gene_trees_path)
|
|
70
|
+
quartet = (self.p1, self.p2, self.p3, self.outgroup)
|
|
71
|
+
|
|
72
|
+
# For each gene tree, determine the quartet topology
|
|
73
|
+
# Species tree: (((P1,P2),P3),O)
|
|
74
|
+
# Concordant: P1+P2 together → ((P1,P2),(P3,O))
|
|
75
|
+
# ABBA: P2+P3 together → ((P2,P3),(P1,O))
|
|
76
|
+
# BABA: P1+P3 together → ((P1,P3),(P2,O))
|
|
77
|
+
concordant = 0
|
|
78
|
+
abba_count = 0
|
|
79
|
+
baba_count = 0
|
|
80
|
+
unresolved = 0
|
|
81
|
+
|
|
82
|
+
for gt in gene_trees:
|
|
83
|
+
topo = self._get_quartet_topology(gt, quartet)
|
|
84
|
+
if topo == "concordant":
|
|
85
|
+
concordant += 1
|
|
86
|
+
elif topo == "abba":
|
|
87
|
+
abba_count += 1
|
|
88
|
+
elif topo == "baba":
|
|
89
|
+
baba_count += 1
|
|
90
|
+
else:
|
|
91
|
+
unresolved += 1
|
|
92
|
+
|
|
93
|
+
n_informative = abba_count + baba_count
|
|
94
|
+
n_total = len(gene_trees)
|
|
95
|
+
|
|
96
|
+
# D-statistic
|
|
97
|
+
if n_informative == 0:
|
|
98
|
+
d_stat = 0.0
|
|
99
|
+
else:
|
|
100
|
+
d_stat = (abba_count - baba_count) / n_informative
|
|
101
|
+
|
|
102
|
+
# Chi-squared test: are ABBA and BABA equally frequent?
|
|
103
|
+
from scipy.stats import chi2
|
|
104
|
+
|
|
105
|
+
p_value = None
|
|
106
|
+
chi2_stat = None
|
|
107
|
+
if n_informative > 0:
|
|
108
|
+
expected = n_informative / 2.0
|
|
109
|
+
chi2_stat = ((abba_count - expected) ** 2 + (baba_count - expected) ** 2) / expected
|
|
110
|
+
p_value = float(chi2.sf(chi2_stat, df=1))
|
|
111
|
+
|
|
112
|
+
# Output
|
|
113
|
+
if self.json_output:
|
|
114
|
+
payload = {
|
|
115
|
+
"mode": "gene_trees",
|
|
116
|
+
"p1": self.p1,
|
|
117
|
+
"p2": self.p2,
|
|
118
|
+
"p3": self.p3,
|
|
119
|
+
"outgroup": self.outgroup,
|
|
120
|
+
"n_gene_trees": n_total,
|
|
121
|
+
"concordant": concordant,
|
|
122
|
+
"abba_count": abba_count,
|
|
123
|
+
"baba_count": baba_count,
|
|
124
|
+
"unresolved": unresolved,
|
|
125
|
+
"d_statistic": round(d_stat, 4),
|
|
126
|
+
"support_threshold": self.support_threshold,
|
|
127
|
+
"chi2_statistic": round(chi2_stat, 4) if chi2_stat is not None else None,
|
|
128
|
+
"p_value": round(p_value, 6) if p_value is not None else None,
|
|
129
|
+
}
|
|
130
|
+
print_json(payload, sort_keys=False)
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
print("Patterson's D-statistic (Gene Tree Mode)")
|
|
135
|
+
print("=========================================")
|
|
136
|
+
print(f"Topology: ((({self.p1}, {self.p2}), {self.p3}), {self.outgroup})")
|
|
137
|
+
print(f"P1: {self.p1}")
|
|
138
|
+
print(f"P2: {self.p2}")
|
|
139
|
+
print(f"P3: {self.p3}")
|
|
140
|
+
print(f"Outgroup: {self.outgroup}")
|
|
141
|
+
print()
|
|
142
|
+
print(f"Gene trees: {n_total}")
|
|
143
|
+
if self.support_threshold is not None:
|
|
144
|
+
print(f"Support threshold: {self.support_threshold}")
|
|
145
|
+
print(f"Concordant ((P1,P2),P3): {concordant}")
|
|
146
|
+
print(f"ABBA ((P2,P3),P1): {abba_count}")
|
|
147
|
+
print(f"BABA ((P1,P3),P2): {baba_count}")
|
|
148
|
+
print(f"Unresolved: {unresolved}")
|
|
149
|
+
print(f"D-statistic: {d_stat:.4f}")
|
|
150
|
+
if chi2_stat is not None:
|
|
151
|
+
print(f"Chi-squared: {chi2_stat:.4f}")
|
|
152
|
+
print(f"p-value: {p_value:.6f}")
|
|
153
|
+
print()
|
|
154
|
+
print(f"Interpretation: {self._interpret(d_stat, p_value)}")
|
|
155
|
+
else:
|
|
156
|
+
print()
|
|
157
|
+
print("No informative (discordant) gene trees found.")
|
|
158
|
+
except BrokenPipeError:
|
|
159
|
+
pass
|
|
160
|
+
|
|
161
|
+
def _parse_gene_trees(self, path: str) -> list:
|
|
162
|
+
"""Parse gene trees from a file (one Newick per line)."""
|
|
163
|
+
try:
|
|
164
|
+
return list(Phylo.parse(path, "newick"))
|
|
165
|
+
except Exception:
|
|
166
|
+
raise PhykitUserError(
|
|
167
|
+
[f"Could not parse gene trees from {path}."],
|
|
168
|
+
code=2,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def _get_quartet_topology(self, tree, quartet) -> str:
|
|
172
|
+
"""Determine quartet topology from a (possibly multi-taxon) gene tree.
|
|
173
|
+
|
|
174
|
+
If support_threshold is set, branches with support below the
|
|
175
|
+
threshold are excluded (treated as collapsed/unresolved).
|
|
176
|
+
|
|
177
|
+
Returns 'concordant', 'abba', 'baba', or 'unresolved'.
|
|
178
|
+
"""
|
|
179
|
+
p1, p2, p3, outgroup = quartet
|
|
180
|
+
|
|
181
|
+
# Get all taxa in the tree
|
|
182
|
+
tree_taxa = {t.name for t in tree.get_terminals()}
|
|
183
|
+
|
|
184
|
+
# Check all four taxa are present
|
|
185
|
+
if not all(t in tree_taxa for t in quartet):
|
|
186
|
+
return "unresolved"
|
|
187
|
+
|
|
188
|
+
# Extract bipartitions from the gene tree
|
|
189
|
+
# Skip branches with support below threshold
|
|
190
|
+
all_taxa = frozenset(tree_taxa)
|
|
191
|
+
bipartitions = []
|
|
192
|
+
for clade in tree.get_nonterminals():
|
|
193
|
+
# Check support threshold
|
|
194
|
+
if self.support_threshold is not None:
|
|
195
|
+
support = clade.confidence
|
|
196
|
+
if support is not None and support < self.support_threshold:
|
|
197
|
+
continue # collapse this branch (skip its bipartition)
|
|
198
|
+
|
|
199
|
+
tips = frozenset(t.name for t in clade.get_terminals())
|
|
200
|
+
if len(tips) <= 1 or tips == all_taxa:
|
|
201
|
+
continue
|
|
202
|
+
complement = all_taxa - tips
|
|
203
|
+
if len(complement) <= 0:
|
|
204
|
+
continue
|
|
205
|
+
bipartitions.append((tips, complement))
|
|
206
|
+
|
|
207
|
+
# Check which quartet topology the bipartitions support
|
|
208
|
+
quartet_set = {p1, p2, p3, outgroup}
|
|
209
|
+
for side_a, side_b in bipartitions:
|
|
210
|
+
in_a = quartet_set & side_a
|
|
211
|
+
in_b = quartet_set & side_b
|
|
212
|
+
if len(in_a) == 2 and len(in_b) == 2:
|
|
213
|
+
pair = frozenset(in_a)
|
|
214
|
+
# Concordant: P1+P2 on one side
|
|
215
|
+
if pair == frozenset({p1, p2}) or pair == frozenset({p3, outgroup}):
|
|
216
|
+
return "concordant"
|
|
217
|
+
# ABBA: P2+P3 on one side
|
|
218
|
+
if pair == frozenset({p2, p3}) or pair == frozenset({p1, outgroup}):
|
|
219
|
+
return "abba"
|
|
220
|
+
# BABA: P1+P3 on one side
|
|
221
|
+
if pair == frozenset({p1, p3}) or pair == frozenset({p2, outgroup}):
|
|
222
|
+
return "baba"
|
|
223
|
+
|
|
224
|
+
return "unresolved"
|
|
225
|
+
|
|
226
|
+
# ------------------------------------------------------------------
|
|
227
|
+
# Alignment mode
|
|
228
|
+
# ------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
def _run_alignment_mode(self):
|
|
231
|
+
"""Count ABBA/BABA site patterns from an alignment."""
|
|
36
232
|
# Read alignment sequences
|
|
37
233
|
sequences = {}
|
|
38
234
|
for record in SeqIO.parse(self.alignment_file_path, "fasta"):
|
|
@@ -48,6 +48,7 @@ class QuartetPie(Tree):
|
|
|
48
48
|
self.annotate = parsed["annotate"]
|
|
49
49
|
self.json_output = parsed["json_output"]
|
|
50
50
|
self.csv_output = parsed["csv_output"]
|
|
51
|
+
self.pie_size = parsed["pie_size"]
|
|
51
52
|
self.plot_config = parsed["plot_config"]
|
|
52
53
|
|
|
53
54
|
def run(self) -> None:
|
|
@@ -104,6 +105,7 @@ class QuartetPie(Tree):
|
|
|
104
105
|
annotate=getattr(args, "annotate", False),
|
|
105
106
|
json_output=getattr(args, "json", False),
|
|
106
107
|
csv_output=getattr(args, "csv", None),
|
|
108
|
+
pie_size=getattr(args, "pie_size", 1.0),
|
|
107
109
|
plot_config=PlotConfig.from_args(args),
|
|
108
110
|
)
|
|
109
111
|
|
|
@@ -253,7 +255,7 @@ class QuartetPie(Tree):
|
|
|
253
255
|
# Circular mode: scale with n_tips but stay larger than rectangular
|
|
254
256
|
# since radial spacing gives more room between nodes
|
|
255
257
|
n_tips = len(tips)
|
|
256
|
-
pie_size = min(0.05, 0.6 / max(n_tips, 1))
|
|
258
|
+
pie_size = min(0.05, 0.6 / max(n_tips, 1)) * self.pie_size
|
|
257
259
|
|
|
258
260
|
for clade in tree.find_clades(order="preorder"):
|
|
259
261
|
if clade.is_terminal() or clade == root:
|
|
@@ -400,7 +402,7 @@ class QuartetPie(Tree):
|
|
|
400
402
|
# appear as perfect circles regardless of axis scaling, and are
|
|
401
403
|
# drawn above the phylogeny branches.
|
|
402
404
|
n_tips = len(tips)
|
|
403
|
-
pie_size = min(0.06, 0.8 / max(n_tips, 1))
|
|
405
|
+
pie_size = min(0.06, 0.8 / max(n_tips, 1)) * self.pie_size
|
|
404
406
|
|
|
405
407
|
for clade in tree.find_clades(order="preorder"):
|
|
406
408
|
if clade.is_terminal() or clade == root:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.1.66"
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phykit
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.66
|
|
4
4
|
Home-page: https://github.com/jlsteenwyk/phykit
|
|
5
5
|
Author: Jacob L. Steenwyk
|
|
6
6
|
Author-email: jlsteenwyk@gmail.com
|
|
7
7
|
Classifier: Operating System :: OS Independent
|
|
8
8
|
Classifier: Intended Audience :: Science/Research
|
|
9
9
|
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
13
14
|
Classifier: Topic :: Scientific/Engineering
|
|
14
|
-
Requires-Python: >=3.
|
|
15
|
+
Requires-Python: >=3.10
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
License-File: LICENSE.md
|
|
17
18
|
Requires-Dist: biopython>=1.82
|
|
@@ -12,6 +12,7 @@ CLASSIFIERS = [
|
|
|
12
12
|
'Operating System :: OS Independent',
|
|
13
13
|
'Intended Audience :: Science/Research',
|
|
14
14
|
'Programming Language :: Python',
|
|
15
|
+
'Programming Language :: Python :: 3.10',
|
|
15
16
|
'Programming Language :: Python :: 3.11',
|
|
16
17
|
'Programming Language :: Python :: 3.12',
|
|
17
18
|
'Programming Language :: Python :: 3.13',
|
|
@@ -37,7 +38,7 @@ setup(
|
|
|
37
38
|
author_email="jlsteenwyk@gmail.com",
|
|
38
39
|
url="https://github.com/jlsteenwyk/phykit",
|
|
39
40
|
packages=find_packages(),
|
|
40
|
-
python_requires=">=3.
|
|
41
|
+
python_requires=">=3.10",
|
|
41
42
|
classifiers=CLASSIFIERS,
|
|
42
43
|
entry_points={
|
|
43
44
|
"console_scripts": [
|
phykit-2.1.64/phykit/version.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.1.64"
|
|
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
|