phylogenie 3.1.7__tar.gz → 3.1.18__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.
Potentially problematic release.
This version of phylogenie might be problematic. Click here for more details.
- {phylogenie-3.1.7/src/phylogenie.egg-info → phylogenie-3.1.18}/PKG-INFO +5 -6
- {phylogenie-3.1.7 → phylogenie-3.1.18}/README.md +4 -5
- {phylogenie-3.1.7 → phylogenie-3.1.18}/pyproject.toml +2 -2
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/__init__.py +8 -14
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/draw.py +154 -53
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/generators/factories.py +17 -13
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/generators/trees.py +17 -3
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/io/fasta.py +9 -3
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/msa.py +15 -7
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/__init__.py +8 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/features.py +6 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/gillespie.py +11 -2
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/tree.py +10 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/utils.py +11 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18/src/phylogenie.egg-info}/PKG-INFO +5 -6
- {phylogenie-3.1.7 → phylogenie-3.1.18}/LICENSE.txt +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/setup.cfg +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/generators/__init__.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/generators/alisim.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/generators/configs.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/generators/dataset.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/generators/typeguards.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/io/__init__.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/main.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/mixins.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/py.typed +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/skyline/__init__.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/skyline/matrix.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/skyline/parameter.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/skyline/vector.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/__init__.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/base.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/contact_tracing.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/core.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/mutations.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/io/__init__.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/io/newick.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/io/nexus.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/model.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/typeguards.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/typings.py +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie.egg-info/SOURCES.txt +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie.egg-info/dependency_links.txt +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie.egg-info/entry_points.txt +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie.egg-info/requires.txt +0 -0
- {phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phylogenie
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.18
|
|
4
4
|
Summary: Generate phylogenetic datasets with minimal setup effort
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -23,7 +23,6 @@ Dynamic: license-file
|
|
|
23
23
|
[](https://pypi.org/project/phylogenie/)
|
|
24
24
|

|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
Phylogenie is a [Python](https://www.python.org/) package designed to easily simulate phylogenetic datasets—such as trees and multiple sequence alignments (MSAs)—with minimal setup effort. Simply specify the distributions from which your parameters should be sampled, and Phylogenie will handle the rest!
|
|
28
27
|
|
|
29
28
|
## ✨ Features
|
|
@@ -73,21 +72,21 @@ Phylogenie relies on [AliSim](https://iqtree.github.io/doc/AliSim) for simulatin
|
|
|
73
72
|
|
|
74
73
|
## 🚀 Quick Start
|
|
75
74
|
|
|
76
|
-
Once you have installed Phylogenie, check out the [
|
|
75
|
+
Once you have installed Phylogenie, check out the [tutorials](https://github.com/gabriele-marino/phylogenie/tree/main/tutorials) folder.
|
|
77
76
|
It includes a collection of thoroughly commented configuration files, organized as a step-by-step tutorial. These examples will help you understand how to use Phylogenie in practice and can be easily adapted to fit your own workflow.
|
|
78
77
|
|
|
79
78
|
For quick start, pick your favorite config file and run Phylogenie with:
|
|
80
79
|
```bash
|
|
81
|
-
phylogenie
|
|
80
|
+
phylogenie tutorials/config_file.yaml
|
|
82
81
|
```
|
|
83
82
|
This command will create the output dataset in the folder specified inside the configuration file, including data directories and metadata files for each dataset split defined in the config.
|
|
84
83
|
|
|
85
84
|
>❗ *Tip*: Can’t choose just one config file?
|
|
86
|
-
You can run them all at once by pointing Phylogenie to the folder! Just use: `phylogenie
|
|
85
|
+
You can run them all at once by pointing Phylogenie to the folder! Just use: `phylogenie tutorials`. In this mode, Phylogenie will automatically find all `.yaml` files in the folder you specified and run for each of them!
|
|
87
86
|
|
|
88
87
|
## 📖 Documentation
|
|
89
88
|
|
|
90
|
-
- The [
|
|
89
|
+
- The [tutorials](https://github.com/gabriele-marino/phylogenie/tree/main/tutorials) folder contains many ready-to-use, extensively commented configuration files that serve as a step-by-step tutorial to guide you through using Phylogenie. You can explore them to learn how it works or adapt them directly to your own workflows.
|
|
91
90
|
- A complete user guide and API reference are under development. In the meantime, feel free to [reach out](mailto:gabmarino.8601@email.com) if you have any questions about integrating Phylogenie into your workflows.
|
|
92
91
|
|
|
93
92
|
## 📄 License
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
[](https://pypi.org/project/phylogenie/)
|
|
9
9
|

|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
Phylogenie is a [Python](https://www.python.org/) package designed to easily simulate phylogenetic datasets—such as trees and multiple sequence alignments (MSAs)—with minimal setup effort. Simply specify the distributions from which your parameters should be sampled, and Phylogenie will handle the rest!
|
|
13
12
|
|
|
14
13
|
## ✨ Features
|
|
@@ -58,21 +57,21 @@ Phylogenie relies on [AliSim](https://iqtree.github.io/doc/AliSim) for simulatin
|
|
|
58
57
|
|
|
59
58
|
## 🚀 Quick Start
|
|
60
59
|
|
|
61
|
-
Once you have installed Phylogenie, check out the [
|
|
60
|
+
Once you have installed Phylogenie, check out the [tutorials](https://github.com/gabriele-marino/phylogenie/tree/main/tutorials) folder.
|
|
62
61
|
It includes a collection of thoroughly commented configuration files, organized as a step-by-step tutorial. These examples will help you understand how to use Phylogenie in practice and can be easily adapted to fit your own workflow.
|
|
63
62
|
|
|
64
63
|
For quick start, pick your favorite config file and run Phylogenie with:
|
|
65
64
|
```bash
|
|
66
|
-
phylogenie
|
|
65
|
+
phylogenie tutorials/config_file.yaml
|
|
67
66
|
```
|
|
68
67
|
This command will create the output dataset in the folder specified inside the configuration file, including data directories and metadata files for each dataset split defined in the config.
|
|
69
68
|
|
|
70
69
|
>❗ *Tip*: Can’t choose just one config file?
|
|
71
|
-
You can run them all at once by pointing Phylogenie to the folder! Just use: `phylogenie
|
|
70
|
+
You can run them all at once by pointing Phylogenie to the folder! Just use: `phylogenie tutorials`. In this mode, Phylogenie will automatically find all `.yaml` files in the folder you specified and run for each of them!
|
|
72
71
|
|
|
73
72
|
## 📖 Documentation
|
|
74
73
|
|
|
75
|
-
- The [
|
|
74
|
+
- The [tutorials](https://github.com/gabriele-marino/phylogenie/tree/main/tutorials) folder contains many ready-to-use, extensively commented configuration files that serve as a step-by-step tutorial to guide you through using Phylogenie. You can explore them to learn how it works or adapt them directly to your own workflows.
|
|
76
75
|
- A complete user guide and API reference are under development. In the meantime, feel free to [reach out](mailto:gabmarino.8601@email.com) if you have any questions about integrating Phylogenie into your workflows.
|
|
77
76
|
|
|
78
77
|
## 📄 License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "phylogenie"
|
|
3
|
-
version = "3.1.
|
|
3
|
+
version = "3.1.18"
|
|
4
4
|
description = "Generate phylogenetic datasets with minimal setup effort"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -29,4 +29,4 @@ phylogenie = "phylogenie.main:main"
|
|
|
29
29
|
|
|
30
30
|
[build-system]
|
|
31
31
|
requires = ["setuptools>=42"]
|
|
32
|
-
build-backend = "setuptools.build_meta"
|
|
32
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
from phylogenie.draw import (
|
|
2
|
-
draw_colored_dated_tree_categorical,
|
|
3
|
-
draw_colored_dated_tree_continuous,
|
|
4
|
-
draw_colored_tree_categorical,
|
|
5
|
-
draw_colored_tree_continuous,
|
|
6
|
-
draw_dated_tree,
|
|
7
|
-
draw_tree,
|
|
8
|
-
)
|
|
9
1
|
from phylogenie.generators import (
|
|
10
2
|
AliSimDatasetGenerator,
|
|
11
3
|
BDEITreeDatasetGenerator,
|
|
@@ -46,6 +38,7 @@ from phylogenie.treesimulator import (
|
|
|
46
38
|
Tree,
|
|
47
39
|
compute_mean_leaf_pairwise_distance,
|
|
48
40
|
compute_sackin_index,
|
|
41
|
+
count_hops,
|
|
49
42
|
dump_newick,
|
|
50
43
|
generate_trees,
|
|
51
44
|
get_BD_events,
|
|
@@ -58,12 +51,15 @@ from phylogenie.treesimulator import (
|
|
|
58
51
|
get_FBD_events,
|
|
59
52
|
get_mrca,
|
|
60
53
|
get_mutation_id,
|
|
54
|
+
get_node_ages,
|
|
61
55
|
get_node_depth_levels,
|
|
62
56
|
get_node_depths,
|
|
63
57
|
get_node_height_levels,
|
|
64
58
|
get_node_heights,
|
|
65
59
|
get_node_leaf_counts,
|
|
66
60
|
get_node_state,
|
|
61
|
+
get_node_times,
|
|
62
|
+
get_path,
|
|
67
63
|
load_newick,
|
|
68
64
|
load_nexus,
|
|
69
65
|
set_features,
|
|
@@ -71,12 +67,6 @@ from phylogenie.treesimulator import (
|
|
|
71
67
|
)
|
|
72
68
|
|
|
73
69
|
__all__ = [
|
|
74
|
-
"draw_colored_dated_tree_categorical",
|
|
75
|
-
"draw_colored_dated_tree_continuous",
|
|
76
|
-
"draw_colored_tree_categorical",
|
|
77
|
-
"draw_colored_tree_continuous",
|
|
78
|
-
"draw_dated_tree",
|
|
79
|
-
"draw_tree",
|
|
80
70
|
"AliSimDatasetGenerator",
|
|
81
71
|
"BDEITreeDatasetGenerator",
|
|
82
72
|
"BDSSTreeDatasetGenerator",
|
|
@@ -114,6 +104,7 @@ __all__ = [
|
|
|
114
104
|
"Tree",
|
|
115
105
|
"compute_mean_leaf_pairwise_distance",
|
|
116
106
|
"compute_sackin_index",
|
|
107
|
+
"count_hops",
|
|
117
108
|
"dump_newick",
|
|
118
109
|
"generate_trees",
|
|
119
110
|
"get_BD_events",
|
|
@@ -126,12 +117,15 @@ __all__ = [
|
|
|
126
117
|
"get_FBD_events",
|
|
127
118
|
"get_mrca",
|
|
128
119
|
"get_mutation_id",
|
|
120
|
+
"get_node_ages",
|
|
129
121
|
"get_node_depth_levels",
|
|
130
122
|
"get_node_depths",
|
|
131
123
|
"get_node_height_levels",
|
|
132
124
|
"get_node_heights",
|
|
133
125
|
"get_node_leaf_counts",
|
|
134
126
|
"get_node_state",
|
|
127
|
+
"get_node_times",
|
|
128
|
+
"get_path",
|
|
135
129
|
"load_newick",
|
|
136
130
|
"load_nexus",
|
|
137
131
|
"set_features",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from collections.abc import Mapping
|
|
1
3
|
from dataclasses import dataclass
|
|
2
|
-
from datetime import datetime
|
|
3
4
|
from typing import Any, Literal, overload
|
|
4
5
|
|
|
5
6
|
import matplotlib.colors as mcolors
|
|
@@ -10,13 +11,18 @@ from matplotlib.axes import Axes
|
|
|
10
11
|
from matplotlib.colors import Colormap
|
|
11
12
|
from mpl_toolkits.axes_grid1.inset_locator import inset_axes # pyright: ignore
|
|
12
13
|
|
|
13
|
-
from phylogenie.treesimulator import
|
|
14
|
+
from phylogenie.treesimulator import (
|
|
15
|
+
Tree,
|
|
16
|
+
get_node_ages,
|
|
17
|
+
get_node_depth_levels,
|
|
18
|
+
get_node_depths,
|
|
19
|
+
)
|
|
14
20
|
|
|
15
21
|
|
|
16
22
|
@dataclass
|
|
17
23
|
class CalibrationNode:
|
|
18
24
|
node: Tree
|
|
19
|
-
date: datetime
|
|
25
|
+
date: datetime.date
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
Color = str | tuple[float, float, float] | tuple[float, float, float, float]
|
|
@@ -25,8 +31,10 @@ Color = str | tuple[float, float, float] | tuple[float, float, float, float]
|
|
|
25
31
|
def draw_tree(
|
|
26
32
|
tree: Tree,
|
|
27
33
|
ax: Axes | None = None,
|
|
28
|
-
colors: Color |
|
|
34
|
+
colors: Color | Mapping[Tree, Color] = "black",
|
|
29
35
|
backward_time: bool = False,
|
|
36
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
37
|
+
sampled_ancestor_kwargs: dict[str, Any] | None = None,
|
|
30
38
|
) -> Axes:
|
|
31
39
|
"""
|
|
32
40
|
Draw a phylogenetic tree with colored branches.
|
|
@@ -37,10 +45,14 @@ def draw_tree(
|
|
|
37
45
|
The phylogenetic tree to draw.
|
|
38
46
|
ax : Axes | None, optional
|
|
39
47
|
The matplotlib Axes to draw on. If None, uses the current Axes.
|
|
40
|
-
colors : Color |
|
|
41
|
-
A single color for all branches or a
|
|
48
|
+
colors : Color | Mapping[Tree, Color], optional
|
|
49
|
+
A single color for all branches or a mapping from each node to a color.
|
|
42
50
|
backward_time : bool, optional
|
|
43
51
|
If True, the x-axis is inverted to represent time going backward.
|
|
52
|
+
branch_kwargs : dict[str, Any] | None, optional
|
|
53
|
+
Additional keyword arguments to pass to the branch drawing functions.
|
|
54
|
+
sampled_ancestor_kwargs : dict[str, Any] | None, optional
|
|
55
|
+
Additional keyword arguments to highlight sampled ancestors.
|
|
44
56
|
|
|
45
57
|
Returns
|
|
46
58
|
-------
|
|
@@ -49,33 +61,51 @@ def draw_tree(
|
|
|
49
61
|
"""
|
|
50
62
|
if ax is None:
|
|
51
63
|
ax = plt.gca()
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
if branch_kwargs is None:
|
|
65
|
+
branch_kwargs = {}
|
|
66
|
+
if sampled_ancestor_kwargs is None:
|
|
67
|
+
sampled_ancestor_kwargs = {}
|
|
68
|
+
if "marker" not in sampled_ancestor_kwargs:
|
|
69
|
+
sampled_ancestor_kwargs["marker"] = "o"
|
|
70
|
+
|
|
71
|
+
if not isinstance(colors, Mapping):
|
|
54
72
|
colors = {node: colors for node in tree}
|
|
55
73
|
|
|
56
74
|
xs = (
|
|
57
|
-
|
|
75
|
+
get_node_ages(tree)
|
|
76
|
+
if backward_time
|
|
77
|
+
else get_node_depth_levels(tree)
|
|
58
78
|
if any(node.branch_length is None for node in tree.iter_descendants())
|
|
59
79
|
else get_node_depths(tree)
|
|
60
80
|
)
|
|
61
|
-
if backward_time:
|
|
62
|
-
max_x = max(xs.values())
|
|
63
|
-
xs = {node: max_x - x for node, x in xs.items()}
|
|
64
81
|
|
|
65
|
-
|
|
82
|
+
leaves = tree.get_leaves()
|
|
83
|
+
ys: dict[Tree, float] = {
|
|
84
|
+
node: i
|
|
85
|
+
for i, node in enumerate(leaves)
|
|
86
|
+
if node.parent is None or node.branch_length != 0
|
|
87
|
+
}
|
|
66
88
|
for node in tree.postorder_traversal():
|
|
67
89
|
if node.is_internal():
|
|
68
|
-
|
|
90
|
+
children = [child for child in node.children if child.branch_length != 0]
|
|
91
|
+
ys[node] = sum(ys[child] for child in children) / len(children)
|
|
92
|
+
for leaf in leaves:
|
|
93
|
+
if leaf.parent is not None and leaf.branch_length == 0:
|
|
94
|
+
ys[leaf] = ys[leaf.parent]
|
|
69
95
|
|
|
70
96
|
if tree.branch_length is not None:
|
|
71
97
|
xmin = xs[tree] + tree.branch_length if backward_time else 0
|
|
72
|
-
ax.hlines(
|
|
98
|
+
ax.hlines( # pyright: ignore
|
|
99
|
+
y=ys[tree], xmin=xmin, xmax=xs[tree], color=colors[tree], **branch_kwargs
|
|
100
|
+
)
|
|
73
101
|
for node in tree:
|
|
74
102
|
x1, y1 = xs[node], ys[node]
|
|
103
|
+
if node.parent is not None and node.branch_length == 0:
|
|
104
|
+
ax.plot(x1, y1, color=colors[node], **sampled_ancestor_kwargs) # pyright: ignore
|
|
75
105
|
for child in node.children:
|
|
76
106
|
x2, y2 = xs[child], ys[child]
|
|
77
|
-
ax.hlines(y=y2, xmin=x1, xmax=x2, color=colors[child]) # pyright: ignore
|
|
78
|
-
ax.vlines(x=x1, ymin=y1, ymax=y2, color=colors[child]) # pyright: ignore
|
|
107
|
+
ax.hlines(y=y2, xmin=x1, xmax=x2, color=colors[child], **branch_kwargs) # pyright: ignore
|
|
108
|
+
ax.vlines(x=x1, ymin=y1, ymax=y2, color=colors[child], **branch_kwargs) # pyright: ignore
|
|
79
109
|
|
|
80
110
|
if backward_time:
|
|
81
111
|
ax.invert_xaxis()
|
|
@@ -86,7 +116,7 @@ def draw_tree(
|
|
|
86
116
|
|
|
87
117
|
def _depth_to_date(
|
|
88
118
|
depth: float, calibration_nodes: tuple[CalibrationNode, CalibrationNode]
|
|
89
|
-
) -> datetime:
|
|
119
|
+
) -> datetime.date:
|
|
90
120
|
"""
|
|
91
121
|
Convert a depth value to a date using linear interpolation between two calibration nodes.
|
|
92
122
|
|
|
@@ -99,7 +129,7 @@ def _depth_to_date(
|
|
|
99
129
|
|
|
100
130
|
Returns
|
|
101
131
|
-------
|
|
102
|
-
datetime
|
|
132
|
+
datetime.date
|
|
103
133
|
The interpolated date corresponding to the given depth.
|
|
104
134
|
"""
|
|
105
135
|
node1, node2 = calibration_nodes
|
|
@@ -112,7 +142,8 @@ def draw_dated_tree(
|
|
|
112
142
|
tree: Tree,
|
|
113
143
|
calibration_nodes: tuple[CalibrationNode, CalibrationNode],
|
|
114
144
|
ax: Axes | None = None,
|
|
115
|
-
colors: Color |
|
|
145
|
+
colors: Color | Mapping[Tree, Color] = "black",
|
|
146
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
116
147
|
) -> Axes:
|
|
117
148
|
"""
|
|
118
149
|
Draw a phylogenetic tree with branches positioned according to calibrated dates.
|
|
@@ -125,8 +156,10 @@ def draw_dated_tree(
|
|
|
125
156
|
Two calibration nodes defining the mapping from depth to date.
|
|
126
157
|
ax : Axes | None, optional
|
|
127
158
|
The matplotlib Axes to draw on. If None, uses the current Axes.
|
|
128
|
-
colors : Color |
|
|
129
|
-
A single color for all branches or a
|
|
159
|
+
colors : Color | Mapping[Tree, Color], optional
|
|
160
|
+
A single color for all branches or a mapping from each node to a color.
|
|
161
|
+
branch_kwargs : dict[str, Any] | None, optional
|
|
162
|
+
Additional keyword arguments to pass to the branch drawing functions.
|
|
130
163
|
|
|
131
164
|
Returns
|
|
132
165
|
-------
|
|
@@ -135,8 +168,10 @@ def draw_dated_tree(
|
|
|
135
168
|
"""
|
|
136
169
|
if ax is None:
|
|
137
170
|
ax = plt.gca()
|
|
171
|
+
if branch_kwargs is None:
|
|
172
|
+
branch_kwargs = {}
|
|
138
173
|
|
|
139
|
-
if not isinstance(colors,
|
|
174
|
+
if not isinstance(colors, Mapping):
|
|
140
175
|
colors = {node: colors for node in tree}
|
|
141
176
|
|
|
142
177
|
xs = {
|
|
@@ -156,6 +191,7 @@ def draw_dated_tree(
|
|
|
156
191
|
xmin=mdates.date2num(origin_date), # pyright: ignore
|
|
157
192
|
xmax=mdates.date2num(xs[tree]), # pyright: ignore
|
|
158
193
|
color=colors[tree],
|
|
194
|
+
**branch_kwargs,
|
|
159
195
|
)
|
|
160
196
|
for node in tree:
|
|
161
197
|
x1, y1 = xs[node], ys[node]
|
|
@@ -166,8 +202,15 @@ def draw_dated_tree(
|
|
|
166
202
|
xmin=mdates.date2num(x1), # pyright: ignore
|
|
167
203
|
xmax=mdates.date2num(x2), # pyright: ignore
|
|
168
204
|
color=colors[child],
|
|
205
|
+
**branch_kwargs,
|
|
206
|
+
)
|
|
207
|
+
ax.vlines( # pyright: ignore
|
|
208
|
+
x=mdates.date2num(x1), # pyright: ignore
|
|
209
|
+
ymin=y1,
|
|
210
|
+
ymax=y2,
|
|
211
|
+
color=colors[child],
|
|
212
|
+
**branch_kwargs,
|
|
169
213
|
)
|
|
170
|
-
ax.vlines(x=mdates.date2num(x1), ymin=y1, ymax=y2, color=colors[child]) # pyright: ignore
|
|
171
214
|
|
|
172
215
|
ax.xaxis.set_major_locator(mdates.AutoDateLocator())
|
|
173
216
|
ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
|
|
@@ -182,9 +225,9 @@ def _init_colored_tree_categorical(
|
|
|
182
225
|
color_by: str,
|
|
183
226
|
ax: Axes | None = None,
|
|
184
227
|
default_color: Color = "black",
|
|
185
|
-
colormap: str | Colormap = "tab20",
|
|
228
|
+
colormap: str | Mapping[str, Color] | Colormap = "tab20",
|
|
186
229
|
show_legend: bool = True,
|
|
187
|
-
labels:
|
|
230
|
+
labels: Mapping[Any, str] | None = None,
|
|
188
231
|
legend_kwargs: dict[str, Any] | None = None,
|
|
189
232
|
) -> tuple[Axes, dict[Tree, Color]]:
|
|
190
233
|
"""
|
|
@@ -200,11 +243,14 @@ def _init_colored_tree_categorical(
|
|
|
200
243
|
The matplotlib Axes to draw on. If None, uses the current Axes.
|
|
201
244
|
default_color : Color, optional
|
|
202
245
|
The color to use for nodes without the specified metadata.
|
|
203
|
-
colormap : str | Colormap, optional
|
|
204
|
-
The colormap to use for coloring categories.
|
|
246
|
+
colormap : str | Mapping[str, Color] | Colormap, optional
|
|
247
|
+
The colormap to use for coloring categories.
|
|
248
|
+
If a string, it is used to get a matplotlib colormap.
|
|
249
|
+
If a mapping, it maps category values to colors directly.
|
|
250
|
+
Defaults to 'tab20'.
|
|
205
251
|
show_legend : bool, optional
|
|
206
252
|
Whether to display a legend for the categories.
|
|
207
|
-
labels :
|
|
253
|
+
labels : Mapping[Any, str] | None, optional
|
|
208
254
|
A mapping from category values to labels for the legend.
|
|
209
255
|
legend_kwargs : dict[str, Any] | None, optional
|
|
210
256
|
Additional keyword arguments to pass to the legend.
|
|
@@ -217,13 +263,16 @@ def _init_colored_tree_categorical(
|
|
|
217
263
|
if ax is None:
|
|
218
264
|
ax = plt.gca()
|
|
219
265
|
|
|
266
|
+
features = {node: node[color_by] for node in tree if color_by in node.metadata}
|
|
220
267
|
if isinstance(colormap, str):
|
|
221
268
|
colormap = plt.get_cmap(colormap)
|
|
269
|
+
if isinstance(colormap, Colormap):
|
|
270
|
+
feature_colors = {
|
|
271
|
+
f: mcolors.to_hex(colormap(i)) for i, f in enumerate(set(features.values()))
|
|
272
|
+
}
|
|
273
|
+
else:
|
|
274
|
+
feature_colors = colormap
|
|
222
275
|
|
|
223
|
-
features = {node: node[color_by] for node in tree if color_by in node.metadata}
|
|
224
|
-
feature_colors = {
|
|
225
|
-
f: mcolors.to_hex(colormap(i)) for i, f in enumerate(set(features.values()))
|
|
226
|
-
}
|
|
227
276
|
colors = {
|
|
228
277
|
node: feature_colors[features[node]] if node in features else default_color
|
|
229
278
|
for node in tree
|
|
@@ -252,10 +301,12 @@ def draw_colored_tree_categorical(
|
|
|
252
301
|
ax: Axes | None = None,
|
|
253
302
|
backward_time: bool = False,
|
|
254
303
|
default_color: Color = "black",
|
|
255
|
-
colormap: str | Colormap = "tab20",
|
|
304
|
+
colormap: str | Mapping[str, Color] | Colormap = "tab20",
|
|
256
305
|
show_legend: bool = True,
|
|
257
|
-
labels:
|
|
306
|
+
labels: Mapping[Any, str] | None = None,
|
|
258
307
|
legend_kwargs: dict[str, Any] | None = None,
|
|
308
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
309
|
+
sampled_ancestor_kwargs: dict[str, Any] | None = None,
|
|
259
310
|
):
|
|
260
311
|
"""
|
|
261
312
|
Draw a phylogenetic tree with branches colored based on categorical metadata.
|
|
@@ -272,14 +323,21 @@ def draw_colored_tree_categorical(
|
|
|
272
323
|
If True, the x-axis is inverted to represent time going backward.
|
|
273
324
|
default_color : Color, optional
|
|
274
325
|
The color to use for nodes without the specified metadata.
|
|
275
|
-
colormap : str | Colormap, optional
|
|
276
|
-
The colormap to use for coloring categories.
|
|
326
|
+
colormap : str | Mapping[str, Color] | Colormap, optional
|
|
327
|
+
The colormap to use for coloring categories.
|
|
328
|
+
If a string, it is used to get a matplotlib colormap.
|
|
329
|
+
If a mapping, it maps category values to colors directly.
|
|
330
|
+
Defaults to 'tab20'.
|
|
277
331
|
show_legend : bool, optional
|
|
278
332
|
Whether to display a legend for the categories.
|
|
279
|
-
labels :
|
|
333
|
+
labels : Mapping[Any, str] | None, optional
|
|
280
334
|
A mapping from category values to labels for the legend.
|
|
281
335
|
legend_kwargs : dict[str, Any] | None, optional
|
|
282
336
|
Additional keyword arguments to pass to the legend.
|
|
337
|
+
branch_kwargs : dict[str, Any] | None, optional
|
|
338
|
+
Additional keyword arguments to pass to the branch drawing functions.
|
|
339
|
+
sampled_ancestor_kwargs : dict[str, Any] | None, optional
|
|
340
|
+
Additional keyword arguments to highlight sampled ancestors.
|
|
283
341
|
|
|
284
342
|
Returns
|
|
285
343
|
-------
|
|
@@ -296,7 +354,14 @@ def draw_colored_tree_categorical(
|
|
|
296
354
|
labels=labels,
|
|
297
355
|
legend_kwargs=legend_kwargs,
|
|
298
356
|
)
|
|
299
|
-
return draw_tree(
|
|
357
|
+
return draw_tree(
|
|
358
|
+
tree=tree,
|
|
359
|
+
ax=ax,
|
|
360
|
+
colors=colors,
|
|
361
|
+
backward_time=backward_time,
|
|
362
|
+
branch_kwargs=branch_kwargs,
|
|
363
|
+
sampled_ancestor_kwargs=sampled_ancestor_kwargs,
|
|
364
|
+
)
|
|
300
365
|
|
|
301
366
|
|
|
302
367
|
def draw_colored_dated_tree_categorical(
|
|
@@ -305,10 +370,11 @@ def draw_colored_dated_tree_categorical(
|
|
|
305
370
|
color_by: str,
|
|
306
371
|
ax: Axes | None = None,
|
|
307
372
|
default_color: Color = "black",
|
|
308
|
-
colormap: str | Colormap = "tab20",
|
|
373
|
+
colormap: str | Mapping[str, Color] | Colormap = "tab20",
|
|
309
374
|
show_legend: bool = True,
|
|
310
|
-
labels:
|
|
375
|
+
labels: Mapping[Any, str] | None = None,
|
|
311
376
|
legend_kwargs: dict[str, Any] | None = None,
|
|
377
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
312
378
|
) -> Axes:
|
|
313
379
|
"""
|
|
314
380
|
Draw a dated phylogenetic tree with branches colored based on categorical metadata.
|
|
@@ -325,14 +391,19 @@ def draw_colored_dated_tree_categorical(
|
|
|
325
391
|
The matplotlib Axes to draw on. If None, uses the current Axes.
|
|
326
392
|
default_color : Color, optional
|
|
327
393
|
The color to use for nodes without the specified metadata.
|
|
328
|
-
colormap : str | Colormap, optional
|
|
329
|
-
The colormap to use for coloring categories.
|
|
394
|
+
colormap : str | Mapping[str, Color] | Colormap, optional
|
|
395
|
+
The colormap to use for coloring categories.
|
|
396
|
+
If a string, it is used to get a matplotlib colormap.
|
|
397
|
+
If a mapping, it maps category values to colors directly.
|
|
398
|
+
Defaults to 'tab20'.
|
|
330
399
|
show_legend : bool, optional
|
|
331
400
|
Whether to display a legend for the categories.
|
|
332
|
-
labels :
|
|
401
|
+
labels : Mapping[Any, str] | None, optional
|
|
333
402
|
A mapping from category values to labels for the legend.
|
|
334
403
|
legend_kwargs : dict[str, Any] | None, optional
|
|
335
404
|
Additional keyword arguments to pass to the legend.
|
|
405
|
+
branch_kwargs : dict[str, Any] | None, optional
|
|
406
|
+
Additional keyword arguments to pass to the branch drawing functions.
|
|
336
407
|
|
|
337
408
|
Returns
|
|
338
409
|
-------
|
|
@@ -350,7 +421,11 @@ def draw_colored_dated_tree_categorical(
|
|
|
350
421
|
legend_kwargs=legend_kwargs,
|
|
351
422
|
)
|
|
352
423
|
return draw_dated_tree(
|
|
353
|
-
tree=tree,
|
|
424
|
+
tree=tree,
|
|
425
|
+
calibration_nodes=calibration_nodes,
|
|
426
|
+
ax=ax,
|
|
427
|
+
colors=colors,
|
|
428
|
+
branch_kwargs=branch_kwargs,
|
|
354
429
|
)
|
|
355
430
|
|
|
356
431
|
|
|
@@ -378,7 +453,7 @@ def _init_colored_tree_continuous(
|
|
|
378
453
|
vmin: float | None = ...,
|
|
379
454
|
vmax: float | None = ...,
|
|
380
455
|
*,
|
|
381
|
-
show_hist: Literal[True],
|
|
456
|
+
show_hist: Literal[True] = True,
|
|
382
457
|
hist_kwargs: dict[str, Any] | None = ...,
|
|
383
458
|
hist_axes_kwargs: dict[str, Any] | None = ...,
|
|
384
459
|
) -> tuple[Axes, dict[Tree, Color], Axes]: ...
|
|
@@ -390,7 +465,6 @@ def _init_colored_tree_continuous(
|
|
|
390
465
|
colormap: str | Colormap = "viridis",
|
|
391
466
|
vmin: float | None = None,
|
|
392
467
|
vmax: float | None = None,
|
|
393
|
-
*,
|
|
394
468
|
show_hist: bool = True,
|
|
395
469
|
hist_kwargs: dict[str, Any] | None = None,
|
|
396
470
|
hist_axes_kwargs: dict[str, Any] | None = None,
|
|
@@ -474,6 +548,8 @@ def draw_colored_tree_continuous(
|
|
|
474
548
|
colormap: str | Colormap = "viridis",
|
|
475
549
|
vmin: float | None = None,
|
|
476
550
|
vmax: float | None = None,
|
|
551
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
552
|
+
sampled_ancestor_kwargs: dict[str, Any] | None = None,
|
|
477
553
|
*,
|
|
478
554
|
show_hist: Literal[False],
|
|
479
555
|
hist_kwargs: dict[str, Any] | None = None,
|
|
@@ -489,8 +565,9 @@ def draw_colored_tree_continuous(
|
|
|
489
565
|
colormap: str | Colormap = "viridis",
|
|
490
566
|
vmin: float | None = None,
|
|
491
567
|
vmax: float | None = None,
|
|
492
|
-
|
|
493
|
-
|
|
568
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
569
|
+
sampled_ancestor_kwargs: dict[str, Any] | None = None,
|
|
570
|
+
show_hist: Literal[True] = True,
|
|
494
571
|
hist_kwargs: dict[str, Any] | None = None,
|
|
495
572
|
hist_axes_kwargs: dict[str, Any] | None = None,
|
|
496
573
|
) -> tuple[Axes, Axes]: ...
|
|
@@ -503,6 +580,8 @@ def draw_colored_tree_continuous(
|
|
|
503
580
|
colormap: str | Colormap = "viridis",
|
|
504
581
|
vmin: float | None = None,
|
|
505
582
|
vmax: float | None = None,
|
|
583
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
584
|
+
sampled_ancestor_kwargs: dict[str, Any] | None = None,
|
|
506
585
|
show_hist: bool = True,
|
|
507
586
|
hist_kwargs: dict[str, Any] | None = None,
|
|
508
587
|
hist_axes_kwargs: dict[str, Any] | None = None,
|
|
@@ -528,6 +607,10 @@ def draw_colored_tree_continuous(
|
|
|
528
607
|
The minimum value for normalization. If None, uses the minimum of the data.
|
|
529
608
|
vmax : float | None, optional
|
|
530
609
|
The maximum value for normalization. If None, uses the maximum of the data.
|
|
610
|
+
branch_kwargs : dict[str, Any] | None, optional
|
|
611
|
+
Additional keyword arguments to pass to the branch drawing functions.
|
|
612
|
+
sampled_ancestor_kwargs : dict[str, Any] | None, optional
|
|
613
|
+
Additional keyword arguments to highlight sampled ancestors.
|
|
531
614
|
show_hist : bool, optional
|
|
532
615
|
Whether to display a histogram of the continuous values.
|
|
533
616
|
hist_kwargs : dict[str, Any] | None, optional
|
|
@@ -555,7 +638,12 @@ def draw_colored_tree_continuous(
|
|
|
555
638
|
hist_axes_kwargs=hist_axes_kwargs,
|
|
556
639
|
)
|
|
557
640
|
return draw_tree(
|
|
558
|
-
tree=tree,
|
|
641
|
+
tree=tree,
|
|
642
|
+
ax=ax,
|
|
643
|
+
colors=colors,
|
|
644
|
+
backward_time=backward_time,
|
|
645
|
+
branch_kwargs=branch_kwargs,
|
|
646
|
+
sampled_ancestor_kwargs=sampled_ancestor_kwargs,
|
|
559
647
|
), hist_ax
|
|
560
648
|
|
|
561
649
|
ax, colors = _init_colored_tree_continuous(
|
|
@@ -570,7 +658,14 @@ def draw_colored_tree_continuous(
|
|
|
570
658
|
hist_kwargs=hist_kwargs,
|
|
571
659
|
hist_axes_kwargs=hist_axes_kwargs,
|
|
572
660
|
)
|
|
573
|
-
return draw_tree(
|
|
661
|
+
return draw_tree(
|
|
662
|
+
tree=tree,
|
|
663
|
+
ax=ax,
|
|
664
|
+
colors=colors,
|
|
665
|
+
backward_time=backward_time,
|
|
666
|
+
branch_kwargs=branch_kwargs,
|
|
667
|
+
sampled_ancestor_kwargs=sampled_ancestor_kwargs,
|
|
668
|
+
)
|
|
574
669
|
|
|
575
670
|
|
|
576
671
|
@overload
|
|
@@ -583,6 +678,7 @@ def draw_colored_dated_tree_continuous(
|
|
|
583
678
|
colormap: str | Colormap = "viridis",
|
|
584
679
|
vmin: float | None = None,
|
|
585
680
|
vmax: float | None = None,
|
|
681
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
586
682
|
*,
|
|
587
683
|
show_hist: Literal[False],
|
|
588
684
|
hist_kwargs: dict[str, Any] | None = None,
|
|
@@ -598,8 +694,8 @@ def draw_colored_dated_tree_continuous(
|
|
|
598
694
|
colormap: str | Colormap = "viridis",
|
|
599
695
|
vmin: float | None = None,
|
|
600
696
|
vmax: float | None = None,
|
|
601
|
-
|
|
602
|
-
show_hist: Literal[True],
|
|
697
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
698
|
+
show_hist: Literal[True] = True,
|
|
603
699
|
hist_kwargs: dict[str, Any] | None = None,
|
|
604
700
|
hist_axes_kwargs: dict[str, Any] | None = None,
|
|
605
701
|
) -> tuple[Axes, Axes]: ...
|
|
@@ -612,6 +708,7 @@ def draw_colored_dated_tree_continuous(
|
|
|
612
708
|
colormap: str | Colormap = "viridis",
|
|
613
709
|
vmin: float | None = None,
|
|
614
710
|
vmax: float | None = None,
|
|
711
|
+
branch_kwargs: dict[str, Any] | None = None,
|
|
615
712
|
show_hist: bool = True,
|
|
616
713
|
hist_kwargs: dict[str, Any] | None = None,
|
|
617
714
|
hist_axes_kwargs: dict[str, Any] | None = None,
|
|
@@ -637,6 +734,8 @@ def draw_colored_dated_tree_continuous(
|
|
|
637
734
|
The minimum value for normalization. If None, uses the minimum of the data.
|
|
638
735
|
vmax : float | None, optional
|
|
639
736
|
The maximum value for normalization. If None, uses the maximum of the data.
|
|
737
|
+
branch_kwargs : dict[str, Any] | None, optional
|
|
738
|
+
Additional keyword arguments to pass to the branch drawing functions.
|
|
640
739
|
show_hist : bool, optional
|
|
641
740
|
Whether to display a histogram of the continuous values.
|
|
642
741
|
hist_kwargs : dict[str, Any] | None, optional
|
|
@@ -668,6 +767,7 @@ def draw_colored_dated_tree_continuous(
|
|
|
668
767
|
calibration_nodes=calibration_nodes,
|
|
669
768
|
ax=ax,
|
|
670
769
|
colors=colors,
|
|
770
|
+
branch_kwargs=branch_kwargs,
|
|
671
771
|
), hist_ax
|
|
672
772
|
|
|
673
773
|
ax, colors = _init_colored_tree_continuous(
|
|
@@ -687,4 +787,5 @@ def draw_colored_dated_tree_continuous(
|
|
|
687
787
|
calibration_nodes=calibration_nodes,
|
|
688
788
|
ax=ax,
|
|
689
789
|
colors=colors,
|
|
790
|
+
branch_kwargs=branch_kwargs,
|
|
690
791
|
)
|
|
@@ -19,18 +19,22 @@ from phylogenie.skyline import (
|
|
|
19
19
|
from phylogenie.treesimulator import EventType, Mutation
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def
|
|
22
|
+
def eval_expression(
|
|
23
|
+
expression: str, data: dict[str, Any], extra_globals: dict[str, Any] | None = None
|
|
24
|
+
) -> Any:
|
|
25
|
+
if extra_globals is None:
|
|
26
|
+
extra_globals = {}
|
|
23
27
|
return np.array(
|
|
24
28
|
eval(
|
|
25
29
|
expression,
|
|
26
|
-
{"np": np, **{k: np.array(v) for k, v in data.items()}},
|
|
30
|
+
{"np": np, **{k: np.array(v) for k, v in data.items()}, **extra_globals},
|
|
27
31
|
)
|
|
28
32
|
).tolist()
|
|
29
33
|
|
|
30
34
|
|
|
31
35
|
def integer(x: cfg.Integer, data: dict[str, Any]) -> int:
|
|
32
36
|
if isinstance(x, str):
|
|
33
|
-
e =
|
|
37
|
+
e = eval_expression(x, data)
|
|
34
38
|
if isinstance(e, int):
|
|
35
39
|
return e
|
|
36
40
|
raise ValueError(
|
|
@@ -41,7 +45,7 @@ def integer(x: cfg.Integer, data: dict[str, Any]) -> int:
|
|
|
41
45
|
|
|
42
46
|
def scalar(x: cfg.Scalar, data: dict[str, Any]) -> pgt.Scalar:
|
|
43
47
|
if isinstance(x, str):
|
|
44
|
-
e =
|
|
48
|
+
e = eval_expression(x, data)
|
|
45
49
|
if isinstance(e, pgt.Scalar):
|
|
46
50
|
return e
|
|
47
51
|
raise ValueError(
|
|
@@ -54,13 +58,13 @@ def string(s: Any, data: dict[str, Any]) -> str:
|
|
|
54
58
|
if not isinstance(s, str):
|
|
55
59
|
return str(s)
|
|
56
60
|
return re.sub(
|
|
57
|
-
r"\{([^{}]+)\}", lambda match: str(
|
|
61
|
+
r"\{([^{}]+)\}", lambda match: str(eval_expression(match.group(1), data)), s
|
|
58
62
|
) # Match content inside curly braces
|
|
59
63
|
|
|
60
64
|
|
|
61
65
|
def many_scalars(x: cfg.ManyScalars, data: dict[str, Any]) -> pgt.ManyScalars:
|
|
62
66
|
if isinstance(x, str):
|
|
63
|
-
e =
|
|
67
|
+
e = eval_expression(x, data)
|
|
64
68
|
if tg.is_many_scalars(e):
|
|
65
69
|
return e
|
|
66
70
|
raise ValueError(
|
|
@@ -73,7 +77,7 @@ def one_or_many_scalars(
|
|
|
73
77
|
x: cfg.OneOrManyScalars, data: dict[str, Any]
|
|
74
78
|
) -> pgt.OneOrManyScalars:
|
|
75
79
|
if isinstance(x, str):
|
|
76
|
-
e =
|
|
80
|
+
e = eval_expression(x, data)
|
|
77
81
|
if tg.is_one_or_many_scalars(e):
|
|
78
82
|
return e
|
|
79
83
|
raise ValueError(
|
|
@@ -99,7 +103,7 @@ def skyline_vector(
|
|
|
99
103
|
x: cfg.SkylineVector, data: dict[str, Any]
|
|
100
104
|
) -> SkylineVectorCoercible:
|
|
101
105
|
if isinstance(x, str):
|
|
102
|
-
e =
|
|
106
|
+
e = eval_expression(x, data)
|
|
103
107
|
if tg.is_one_or_many_scalars(e):
|
|
104
108
|
return e
|
|
105
109
|
raise ValueError(
|
|
@@ -114,7 +118,7 @@ def skyline_vector(
|
|
|
114
118
|
|
|
115
119
|
change_times = many_scalars(x.change_times, data)
|
|
116
120
|
if isinstance(x.value, str):
|
|
117
|
-
e =
|
|
121
|
+
e = eval_expression(x.value, data)
|
|
118
122
|
if tg.is_many_one_or_many_scalars(e):
|
|
119
123
|
value = e
|
|
120
124
|
else:
|
|
@@ -142,7 +146,7 @@ def one_or_many_2D_scalars(
|
|
|
142
146
|
x: cfg.OneOrMany2DScalars, data: dict[str, Any]
|
|
143
147
|
) -> pgt.OneOrMany2DScalars:
|
|
144
148
|
if isinstance(x, str):
|
|
145
|
-
e =
|
|
149
|
+
e = eval_expression(x, data)
|
|
146
150
|
if tg.is_one_or_many_2D_scalars(e):
|
|
147
151
|
return e
|
|
148
152
|
raise ValueError(
|
|
@@ -160,7 +164,7 @@ def skyline_matrix(
|
|
|
160
164
|
return None
|
|
161
165
|
|
|
162
166
|
if isinstance(x, str):
|
|
163
|
-
e =
|
|
167
|
+
e = eval_expression(x, data)
|
|
164
168
|
if tg.is_one_or_many_2D_scalars(e):
|
|
165
169
|
return e
|
|
166
170
|
raise ValueError(
|
|
@@ -175,7 +179,7 @@ def skyline_matrix(
|
|
|
175
179
|
|
|
176
180
|
change_times = many_scalars(x.change_times, data)
|
|
177
181
|
if isinstance(x.value, str):
|
|
178
|
-
e =
|
|
182
|
+
e = eval_expression(x.value, data)
|
|
179
183
|
if tg.is_many_one_or_many_2D_scalars(e):
|
|
180
184
|
value = e
|
|
181
185
|
else:
|
|
@@ -213,7 +217,7 @@ def distribution(x: cfg.Distribution, data: dict[str, Any]) -> cfg.Distribution:
|
|
|
213
217
|
args = x.args
|
|
214
218
|
for arg_name, arg_value in args.items():
|
|
215
219
|
if isinstance(arg_value, str):
|
|
216
|
-
args[arg_name] =
|
|
220
|
+
args[arg_name] = eval_expression(arg_value, data)
|
|
217
221
|
return cfg.Distribution(type=x.type, **args)
|
|
218
222
|
|
|
219
223
|
|
|
@@ -12,6 +12,7 @@ from phylogenie.generators.configs import Distribution
|
|
|
12
12
|
from phylogenie.generators.dataset import DatasetGenerator, DataType
|
|
13
13
|
from phylogenie.generators.factories import (
|
|
14
14
|
data,
|
|
15
|
+
eval_expression,
|
|
15
16
|
integer,
|
|
16
17
|
mutations,
|
|
17
18
|
scalar,
|
|
@@ -57,6 +58,7 @@ class TreeDatasetGenerator(DatasetGenerator):
|
|
|
57
58
|
timeout: float = np.inf
|
|
58
59
|
node_features: list[Feature] | None = None
|
|
59
60
|
acceptance_criterion: str | None = None
|
|
61
|
+
logs: dict[str, str] | None = None
|
|
60
62
|
|
|
61
63
|
@abstractmethod
|
|
62
64
|
def _get_events(self, data: dict[str, Any]) -> list[Event]: ...
|
|
@@ -77,10 +79,21 @@ class TreeDatasetGenerator(DatasetGenerator):
|
|
|
77
79
|
acceptance_criterion: None | Callable[[Tree], bool] = (
|
|
78
80
|
None
|
|
79
81
|
if self.acceptance_criterion is None
|
|
80
|
-
else lambda tree:
|
|
81
|
-
self.acceptance_criterion,
|
|
82
|
+
else lambda tree: eval_expression(
|
|
83
|
+
self.acceptance_criterion, # pyright: ignore
|
|
84
|
+
data,
|
|
85
|
+
{"tree": tree},
|
|
82
86
|
)
|
|
83
87
|
)
|
|
88
|
+
logs: None | dict[str, Callable[[Tree], Any]] = (
|
|
89
|
+
None
|
|
90
|
+
if self.logs is None
|
|
91
|
+
else {
|
|
92
|
+
key: lambda tree, expr=expr: eval_expression(expr, data, {"tree": tree})
|
|
93
|
+
for key, expr in self.logs.items()
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
|
|
84
97
|
return simulate_tree(
|
|
85
98
|
events=events,
|
|
86
99
|
n_tips=None if self.n_tips is None else integer(self.n_tips, data),
|
|
@@ -92,6 +105,7 @@ class TreeDatasetGenerator(DatasetGenerator):
|
|
|
92
105
|
seed=seed,
|
|
93
106
|
timeout=self.timeout,
|
|
94
107
|
acceptance_criterion=acceptance_criterion,
|
|
108
|
+
logs=logs,
|
|
95
109
|
)
|
|
96
110
|
|
|
97
111
|
def generate_one(
|
|
@@ -111,7 +125,7 @@ class TreeDatasetGenerator(DatasetGenerator):
|
|
|
111
125
|
dump_newick(tree, f"{filename}.nwk")
|
|
112
126
|
break
|
|
113
127
|
except TimeoutError:
|
|
114
|
-
print("Simulation timed out
|
|
128
|
+
print("Simulation timed out. Retrying with different parameters...")
|
|
115
129
|
return d | metadata
|
|
116
130
|
|
|
117
131
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from datetime import date
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import Callable
|
|
3
4
|
|
|
@@ -5,7 +6,8 @@ from phylogenie.msa import MSA, Sequence
|
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
def load_fasta(
|
|
8
|
-
fasta_file: str | Path,
|
|
9
|
+
fasta_file: str | Path,
|
|
10
|
+
extract_time_from_id: Callable[[str], float | date] | None = None,
|
|
9
11
|
) -> MSA:
|
|
10
12
|
sequences: list[Sequence] = []
|
|
11
13
|
with open(fasta_file, "r") as f:
|
|
@@ -17,10 +19,14 @@ def load_fasta(
|
|
|
17
19
|
if extract_time_from_id is not None:
|
|
18
20
|
time = extract_time_from_id(id)
|
|
19
21
|
elif "|" in id:
|
|
22
|
+
last_metadata = id.split("|")[-1]
|
|
20
23
|
try:
|
|
21
|
-
time = float(
|
|
24
|
+
time = float(last_metadata)
|
|
22
25
|
except ValueError:
|
|
23
|
-
|
|
26
|
+
try:
|
|
27
|
+
time = date.fromisoformat(last_metadata)
|
|
28
|
+
except ValueError:
|
|
29
|
+
pass
|
|
24
30
|
chars = next(f).strip()
|
|
25
31
|
sequences.append(Sequence(id, chars, time))
|
|
26
32
|
return MSA(sequences)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
from collections.abc import Iterator
|
|
1
|
+
from collections.abc import Iterable, Iterator
|
|
2
2
|
from dataclasses import dataclass
|
|
3
|
+
from datetime import date
|
|
3
4
|
|
|
4
5
|
import numpy as np
|
|
5
6
|
|
|
@@ -8,25 +9,32 @@ import numpy as np
|
|
|
8
9
|
class Sequence:
|
|
9
10
|
id: str
|
|
10
11
|
chars: str
|
|
11
|
-
time: float | None = None
|
|
12
|
+
time: float | date | None = None
|
|
13
|
+
|
|
14
|
+
def __len__(self) -> int:
|
|
15
|
+
return len(self.chars)
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class MSA:
|
|
15
|
-
def __init__(self, sequences:
|
|
16
|
-
self.
|
|
17
|
-
lengths = {len(sequence
|
|
19
|
+
def __init__(self, sequences: Iterable[Sequence]):
|
|
20
|
+
self._sequences = sequences
|
|
21
|
+
lengths = {len(sequence) for sequence in sequences}
|
|
18
22
|
if len(lengths) > 1:
|
|
19
23
|
raise ValueError(
|
|
20
24
|
f"All sequences in the alignment must have the same length (got lengths: {lengths})"
|
|
21
25
|
)
|
|
22
26
|
|
|
27
|
+
@property
|
|
28
|
+
def sequences(self) -> tuple[Sequence, ...]:
|
|
29
|
+
return tuple(self._sequences)
|
|
30
|
+
|
|
23
31
|
@property
|
|
24
32
|
def ids(self) -> list[str]:
|
|
25
33
|
return [sequence.id for sequence in self.sequences]
|
|
26
34
|
|
|
27
35
|
@property
|
|
28
|
-
def times(self) -> list[float]:
|
|
29
|
-
times: list[float] = []
|
|
36
|
+
def times(self) -> list[float | date]:
|
|
37
|
+
times: list[float | date] = []
|
|
30
38
|
for sequence in self:
|
|
31
39
|
if sequence.time is None:
|
|
32
40
|
raise ValueError(f"Time is not set for sequence {sequence.id}.")
|
|
@@ -25,13 +25,17 @@ from phylogenie.treesimulator.tree import Tree
|
|
|
25
25
|
from phylogenie.treesimulator.utils import (
|
|
26
26
|
compute_mean_leaf_pairwise_distance,
|
|
27
27
|
compute_sackin_index,
|
|
28
|
+
count_hops,
|
|
28
29
|
get_distance,
|
|
29
30
|
get_mrca,
|
|
31
|
+
get_node_ages,
|
|
30
32
|
get_node_depth_levels,
|
|
31
33
|
get_node_depths,
|
|
32
34
|
get_node_height_levels,
|
|
33
35
|
get_node_heights,
|
|
34
36
|
get_node_leaf_counts,
|
|
37
|
+
get_node_times,
|
|
38
|
+
get_path,
|
|
35
39
|
)
|
|
36
40
|
|
|
37
41
|
__all__ = [
|
|
@@ -63,11 +67,15 @@ __all__ = [
|
|
|
63
67
|
"Tree",
|
|
64
68
|
"compute_mean_leaf_pairwise_distance",
|
|
65
69
|
"compute_sackin_index",
|
|
70
|
+
"count_hops",
|
|
66
71
|
"get_distance",
|
|
67
72
|
"get_mrca",
|
|
73
|
+
"get_node_ages",
|
|
68
74
|
"get_node_depth_levels",
|
|
69
75
|
"get_node_depths",
|
|
70
76
|
"get_node_height_levels",
|
|
71
77
|
"get_node_heights",
|
|
72
78
|
"get_node_leaf_counts",
|
|
79
|
+
"get_node_times",
|
|
80
|
+
"get_path",
|
|
73
81
|
]
|
|
@@ -5,11 +5,13 @@ from phylogenie.treesimulator.events.mutations import get_mutation_id
|
|
|
5
5
|
from phylogenie.treesimulator.model import get_node_state
|
|
6
6
|
from phylogenie.treesimulator.tree import Tree
|
|
7
7
|
from phylogenie.treesimulator.utils import (
|
|
8
|
+
get_node_ages,
|
|
8
9
|
get_node_depth_levels,
|
|
9
10
|
get_node_depths,
|
|
10
11
|
get_node_height_levels,
|
|
11
12
|
get_node_heights,
|
|
12
13
|
get_node_leaf_counts,
|
|
14
|
+
get_node_times,
|
|
13
15
|
)
|
|
14
16
|
|
|
15
17
|
|
|
@@ -22,6 +24,7 @@ def _get_mutations(tree: Tree) -> dict[Tree, int]:
|
|
|
22
24
|
|
|
23
25
|
|
|
24
26
|
class Feature(str, Enum):
|
|
27
|
+
AGE = "age"
|
|
25
28
|
DEPTH = "depth"
|
|
26
29
|
DEPTH_LEVEL = "depth_level"
|
|
27
30
|
HEIGHT = "height"
|
|
@@ -29,9 +32,11 @@ class Feature(str, Enum):
|
|
|
29
32
|
MUTATION = "mutation"
|
|
30
33
|
N_LEAVES = "n_leaves"
|
|
31
34
|
STATE = "state"
|
|
35
|
+
TIME = "time"
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
FEATURES_EXTRACTORS = {
|
|
39
|
+
Feature.AGE: get_node_ages,
|
|
35
40
|
Feature.DEPTH: get_node_depths,
|
|
36
41
|
Feature.DEPTH_LEVEL: get_node_depth_levels,
|
|
37
42
|
Feature.HEIGHT: get_node_heights,
|
|
@@ -39,6 +44,7 @@ FEATURES_EXTRACTORS = {
|
|
|
39
44
|
Feature.MUTATION: _get_mutations,
|
|
40
45
|
Feature.N_LEAVES: get_node_leaf_counts,
|
|
41
46
|
Feature.STATE: _get_states,
|
|
47
|
+
Feature.TIME: get_node_times,
|
|
42
48
|
}
|
|
43
49
|
|
|
44
50
|
|
|
@@ -25,6 +25,7 @@ def simulate_tree(
|
|
|
25
25
|
seed: int | None = None,
|
|
26
26
|
timeout: float = np.inf,
|
|
27
27
|
acceptance_criterion: Callable[[Tree], bool] | None = None,
|
|
28
|
+
logs: dict[str, Callable[[Tree], Any]] | None = None,
|
|
28
29
|
) -> tuple[Tree, dict[str, Any]]:
|
|
29
30
|
if (max_time != np.inf) == (n_tips is not None):
|
|
30
31
|
raise ValueError("Exactly one of max_time or n_tips must be specified.")
|
|
@@ -85,7 +86,13 @@ def simulate_tree(
|
|
|
85
86
|
model.sample(individual, current_time, True)
|
|
86
87
|
|
|
87
88
|
tree = model.get_sampled_tree()
|
|
88
|
-
|
|
89
|
+
|
|
90
|
+
if acceptance_criterion is not None and not acceptance_criterion(tree):
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if logs is not None:
|
|
94
|
+
for key, func in logs.items():
|
|
95
|
+
metadata[key] = func(tree)
|
|
89
96
|
return (tree, metadata)
|
|
90
97
|
|
|
91
98
|
|
|
@@ -102,6 +109,7 @@ def generate_trees(
|
|
|
102
109
|
n_jobs: int = -1,
|
|
103
110
|
timeout: float = np.inf,
|
|
104
111
|
acceptance_criterion: Callable[[Tree], bool] | None = None,
|
|
112
|
+
logs: dict[str, Callable[[Tree], Any]] | None = None,
|
|
105
113
|
) -> pd.DataFrame:
|
|
106
114
|
if isinstance(output_dir, str):
|
|
107
115
|
output_dir = Path(output_dir)
|
|
@@ -121,6 +129,7 @@ def generate_trees(
|
|
|
121
129
|
seed=seed,
|
|
122
130
|
timeout=timeout,
|
|
123
131
|
acceptance_criterion=acceptance_criterion,
|
|
132
|
+
logs=logs,
|
|
124
133
|
)
|
|
125
134
|
metadata["file_id"] = i
|
|
126
135
|
if node_features is not None:
|
|
@@ -128,7 +137,7 @@ def generate_trees(
|
|
|
128
137
|
dump_newick(tree, output_dir / f"{i}.nwk")
|
|
129
138
|
return metadata
|
|
130
139
|
except TimeoutError:
|
|
131
|
-
print("Simulation timed out
|
|
140
|
+
print("Simulation timed out. Retrying with a different seed...")
|
|
132
141
|
seed += 1
|
|
133
142
|
|
|
134
143
|
rng = default_rng(seed)
|
|
@@ -153,6 +153,16 @@ class Tree(MetadataMixin):
|
|
|
153
153
|
child.branch_length_or_raise() + child.height for child in self.children
|
|
154
154
|
)
|
|
155
155
|
|
|
156
|
+
@property
|
|
157
|
+
def time(self) -> float:
|
|
158
|
+
return self.depth
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def age(self) -> float:
|
|
162
|
+
if self.parent is None:
|
|
163
|
+
return self.height
|
|
164
|
+
return self.parent.age - self.branch_length_or_raise()
|
|
165
|
+
|
|
156
166
|
# -------------
|
|
157
167
|
# Miscellaneous
|
|
158
168
|
# -------------
|
|
@@ -51,6 +51,17 @@ def get_node_heights(tree: Tree) -> dict[Tree, float]:
|
|
|
51
51
|
return heights
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def get_node_times(tree: Tree) -> dict[Tree, float]:
|
|
55
|
+
return get_node_depths(tree)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_node_ages(tree: Tree) -> dict[Tree, float]:
|
|
59
|
+
ages: dict[Tree, float] = {tree: tree.height}
|
|
60
|
+
for node in tree.iter_descendants():
|
|
61
|
+
ages[node] = ages[node.parent] - node.branch_length # pyright: ignore
|
|
62
|
+
return ages
|
|
63
|
+
|
|
64
|
+
|
|
54
65
|
def get_mrca(node1: Tree, node2: Tree) -> Tree:
|
|
55
66
|
node1_ancestors = set(node1.iter_upward())
|
|
56
67
|
for node2_ancestor in node2.iter_upward():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phylogenie
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.18
|
|
4
4
|
Summary: Generate phylogenetic datasets with minimal setup effort
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -23,7 +23,6 @@ Dynamic: license-file
|
|
|
23
23
|
[](https://pypi.org/project/phylogenie/)
|
|
24
24
|

|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
Phylogenie is a [Python](https://www.python.org/) package designed to easily simulate phylogenetic datasets—such as trees and multiple sequence alignments (MSAs)—with minimal setup effort. Simply specify the distributions from which your parameters should be sampled, and Phylogenie will handle the rest!
|
|
28
27
|
|
|
29
28
|
## ✨ Features
|
|
@@ -73,21 +72,21 @@ Phylogenie relies on [AliSim](https://iqtree.github.io/doc/AliSim) for simulatin
|
|
|
73
72
|
|
|
74
73
|
## 🚀 Quick Start
|
|
75
74
|
|
|
76
|
-
Once you have installed Phylogenie, check out the [
|
|
75
|
+
Once you have installed Phylogenie, check out the [tutorials](https://github.com/gabriele-marino/phylogenie/tree/main/tutorials) folder.
|
|
77
76
|
It includes a collection of thoroughly commented configuration files, organized as a step-by-step tutorial. These examples will help you understand how to use Phylogenie in practice and can be easily adapted to fit your own workflow.
|
|
78
77
|
|
|
79
78
|
For quick start, pick your favorite config file and run Phylogenie with:
|
|
80
79
|
```bash
|
|
81
|
-
phylogenie
|
|
80
|
+
phylogenie tutorials/config_file.yaml
|
|
82
81
|
```
|
|
83
82
|
This command will create the output dataset in the folder specified inside the configuration file, including data directories and metadata files for each dataset split defined in the config.
|
|
84
83
|
|
|
85
84
|
>❗ *Tip*: Can’t choose just one config file?
|
|
86
|
-
You can run them all at once by pointing Phylogenie to the folder! Just use: `phylogenie
|
|
85
|
+
You can run them all at once by pointing Phylogenie to the folder! Just use: `phylogenie tutorials`. In this mode, Phylogenie will automatically find all `.yaml` files in the folder you specified and run for each of them!
|
|
87
86
|
|
|
88
87
|
## 📖 Documentation
|
|
89
88
|
|
|
90
|
-
- The [
|
|
89
|
+
- The [tutorials](https://github.com/gabriele-marino/phylogenie/tree/main/tutorials) folder contains many ready-to-use, extensively commented configuration files that serve as a step-by-step tutorial to guide you through using Phylogenie. You can explore them to learn how it works or adapt them directly to your own workflows.
|
|
91
90
|
- A complete user guide and API reference are under development. In the meantime, feel free to [reach out](mailto:gabmarino.8601@email.com) if you have any questions about integrating Phylogenie into your workflows.
|
|
92
91
|
|
|
93
92
|
## 📄 License
|
|
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
|
{phylogenie-3.1.7 → phylogenie-3.1.18}/src/phylogenie/treesimulator/events/contact_tracing.py
RENAMED
|
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
|