phylogenie 2.1.13__tar.gz → 2.1.15__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.

Files changed (36) hide show
  1. {phylogenie-2.1.13 → phylogenie-2.1.15}/PKG-INFO +1 -1
  2. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/generators/alisim.py +3 -1
  3. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/plot.py +42 -17
  4. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/tree.py +56 -19
  5. phylogenie-2.1.15/phylogenie/treesimulator/features.py +49 -0
  6. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/treesimulator/model.py +1 -3
  7. phylogenie-2.1.15/phylogenie/utils.py +59 -0
  8. {phylogenie-2.1.13 → phylogenie-2.1.15}/pyproject.toml +1 -1
  9. phylogenie-2.1.13/phylogenie/treesimulator/features.py +0 -39
  10. phylogenie-2.1.13/phylogenie/utils.py +0 -28
  11. {phylogenie-2.1.13 → phylogenie-2.1.15}/LICENSE.txt +0 -0
  12. {phylogenie-2.1.13 → phylogenie-2.1.15}/README.md +0 -0
  13. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/__init__.py +0 -0
  14. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/generators/__init__.py +0 -0
  15. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/generators/configs.py +0 -0
  16. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/generators/dataset.py +0 -0
  17. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/generators/factories.py +0 -0
  18. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/generators/trees.py +0 -0
  19. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/generators/typeguards.py +0 -0
  20. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/io.py +0 -0
  21. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/main.py +0 -0
  22. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/models.py +0 -0
  23. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/msa.py +0 -0
  24. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/py.typed +0 -0
  25. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/skyline/__init__.py +0 -0
  26. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/skyline/matrix.py +0 -0
  27. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/skyline/parameter.py +0 -0
  28. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/skyline/vector.py +0 -0
  29. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/treesimulator/__init__.py +0 -0
  30. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/treesimulator/events/__init__.py +0 -0
  31. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/treesimulator/events/contact_tracing.py +0 -0
  32. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/treesimulator/events/core.py +0 -0
  33. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/treesimulator/events/mutations.py +0 -0
  34. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/treesimulator/gillespie.py +0 -0
  35. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/typeguards.py +0 -0
  36. {phylogenie-2.1.13 → phylogenie-2.1.15}/phylogenie/typings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phylogenie
3
- Version: 2.1.13
3
+ Version: 2.1.15
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Author: Gabriele Marino
6
6
  Author-email: gabmarino.8601@gmail.com
@@ -9,6 +9,7 @@ from phylogenie.generators.dataset import DatasetGenerator, DataType
9
9
  from phylogenie.generators.factories import data, string
10
10
  from phylogenie.generators.trees import TreeDatasetGeneratorConfig
11
11
  from phylogenie.io import dump_newick
12
+ from phylogenie.utils import get_node_depths
12
13
 
13
14
  MSAS_DIRNAME = "MSAs"
14
15
  TREES_DIRNAME = "trees"
@@ -71,8 +72,9 @@ class AliSimDatasetGenerator(DatasetGenerator):
71
72
  "Tree simulation timed out, retrying with different parameters..."
72
73
  )
73
74
 
75
+ times = get_node_depths(tree)
74
76
  for leaf in tree.get_leaves():
75
- leaf.name += f"|{leaf.get_time()}"
77
+ leaf.name += f"|{times[leaf]}"
76
78
  dump_newick(tree, f"{tree_filename}.nwk")
77
79
 
78
80
  self._generate_one_from_tree(msa_filename, f"{tree_filename}.nwk", rng, d)
@@ -6,7 +6,7 @@ import matplotlib.pyplot as plt
6
6
  from matplotlib.axes import Axes
7
7
 
8
8
  from phylogenie.tree import Tree
9
- from phylogenie.utils import get_times
9
+ from phylogenie.utils import get_node_depths
10
10
 
11
11
 
12
12
  class Coloring(str, Enum):
@@ -22,44 +22,69 @@ def plot_tree(
22
22
  coloring: str | Coloring | None = None,
23
23
  cmap: str | None = None,
24
24
  show_legend: bool = True,
25
+ x_feature: str | None = None,
25
26
  ) -> Axes:
26
27
  if ax is None:
27
28
  ax = plt.gca()
28
29
 
29
- xs = get_times(tree)
30
+ xs = (
31
+ get_node_depths(tree)
32
+ if x_feature is None
33
+ else {node: node.get(x_feature) for node in tree}
34
+ )
30
35
  ys = {node: i for i, node in enumerate(tree.inorder_traversal())}
31
36
 
32
37
  if color_by is not None:
33
- features = set(node.get(color_by) for node in tree)
38
+ features = set(node.get(color_by) for node in tree if color_by in node.features)
34
39
  if coloring is None and any(isinstance(f, float) for f in features):
35
40
  coloring = Coloring.CONTINUOUS
36
41
  elif coloring is None:
37
42
  coloring = Coloring.DISCRETE
43
+
38
44
  if coloring == Coloring.DISCRETE:
39
45
  if any(isinstance(f, float) for f in features):
40
46
  raise ValueError(
41
47
  "Discrete coloring selected but feature values are not all categorical."
42
48
  )
43
- cmap = "tab20" if cmap is None else cmap
44
- colormap = plt.get_cmap(cmap, len(features))
49
+ colormap = plt.get_cmap("tab20" if cmap is None else cmap)
45
50
  feature_colors = {
46
51
  f: mcolors.to_hex(colormap(i)) for i, f in enumerate(features)
47
52
  }
48
- colors = {node: feature_colors[node.get(color_by)] for node in tree}
49
- legend_handles = [
50
- mpatches.Patch(color=feature_colors[f], label=str(f)) for f in features
51
- ]
53
+ colors = {
54
+ node: (
55
+ feature_colors[node.get(color_by)]
56
+ if color_by in node.features
57
+ else default_color
58
+ )
59
+ for node in tree
60
+ }
61
+
52
62
  if show_legend:
63
+ legend_handles = [
64
+ mpatches.Patch(color=feature_colors[f], label=str(f))
65
+ for f in features
66
+ ]
67
+ if any(color_by not in node.features for node in tree):
68
+ legend_handles.append(
69
+ mpatches.Patch(color=default_color, label="None")
70
+ )
53
71
  ax.legend(handles=legend_handles, title=color_by) # pyright: ignore
54
- elif coloring in {Coloring.CONTINUOUS}:
55
- cmap = "viridis" if cmap is None else cmap
56
- values = list(map(float, features))
57
- norm = mcolors.Normalize(vmin=min(values), vmax=max(values))
58
- colormap = plt.get_cmap(cmap)
59
- colors = {node: colormap(norm(float(node.get(color_by)))) for node in tree}
72
+
73
+ elif coloring == Coloring.CONTINUOUS:
74
+ norm = mcolors.Normalize(vmin=min(features), vmax=max(features))
75
+ colormap = plt.get_cmap("viridis" if cmap is None else cmap)
76
+ colors = {
77
+ node: (
78
+ colormap(norm(float(node.get(color_by))))
79
+ if color_by in node.features
80
+ else default_color
81
+ )
82
+ for node in tree
83
+ }
60
84
 
61
85
  sm = plt.cm.ScalarMappable(cmap=colormap, norm=norm)
62
86
  ax.get_figure().colorbar(sm, ax=ax) # pyright: ignore
87
+
63
88
  else:
64
89
  raise ValueError(
65
90
  f"Unknown coloring method: {coloring}. Choices are {list(Coloring)}."
@@ -68,11 +93,11 @@ def plot_tree(
68
93
  colors = {node: default_color for node in tree}
69
94
 
70
95
  for node in tree:
71
- x1, y1 = xs[node.name], ys[node]
96
+ x1, y1 = xs[node], ys[node]
72
97
  if node.parent is None:
73
98
  ax.hlines(y=y1, xmin=0, xmax=x1, color=colors[node]) # pyright: ignore
74
99
  continue
75
- x0, y0 = xs[node.parent.name], ys[node.parent]
100
+ x0, y0 = xs[node.parent], ys[node.parent]
76
101
  ax.vlines(x=x0, ymin=y0, ymax=y1, color=colors[node]) # pyright: ignore
77
102
  ax.hlines(y=y1, xmin=x0, xmax=x1, color=colors[node]) # pyright: ignore
78
103
 
@@ -1,3 +1,4 @@
1
+ from collections import deque
1
2
  from collections.abc import Iterator
2
3
  from typing import Any
3
4
 
@@ -23,12 +24,49 @@ class Tree:
23
24
  return self._features.copy()
24
25
 
25
26
  @property
26
- def time_to_parent(self) -> float:
27
- if self.parent is None and self.branch_length is None:
27
+ def depth(self) -> float:
28
+ if self.parent is None:
28
29
  return 0.0
29
30
  if self.branch_length is None:
30
31
  raise ValueError(f"Branch length of node {self.name} is not set.")
31
- return self.branch_length
32
+ return self.parent.depth + self.branch_length
33
+
34
+ @property
35
+ def depth_level(self) -> int:
36
+ if self.parent is None:
37
+ return 0
38
+ return self.parent.depth_level + 1
39
+
40
+ @property
41
+ def height(self) -> float:
42
+ if self.is_leaf():
43
+ return 0.0
44
+ if any(child.branch_length is None for child in self.children):
45
+ raise ValueError(
46
+ f"Branch length of one or more children of node {self.name} is not set."
47
+ )
48
+ return max(
49
+ child.branch_length + child.height # pyright: ignore
50
+ for child in self.children
51
+ )
52
+
53
+ @property
54
+ def height_level(self) -> int:
55
+ if self.is_leaf():
56
+ return 0
57
+ return 1 + max(child.height_level for child in self.children)
58
+
59
+ def set(self, key: str, value: Any) -> None:
60
+ self._features[key] = value
61
+
62
+ def update_features(self, features: dict[str, Any]) -> None:
63
+ self._features.update(features)
64
+
65
+ def get(self, key: str) -> Any:
66
+ return self._features[key]
67
+
68
+ def delete(self, key: str) -> None:
69
+ del self._features[key]
32
70
 
33
71
  def add_child(self, child: "Tree") -> "Tree":
34
72
  child._parent = self
@@ -65,6 +103,13 @@ class Tree:
65
103
  yield from child.postorder_traversal()
66
104
  yield self
67
105
 
106
+ def breadth_first_traversal(self) -> Iterator["Tree"]:
107
+ queue: deque["Tree"] = deque([self])
108
+ while queue:
109
+ node = queue.popleft()
110
+ yield node
111
+ queue.extend(node.children)
112
+
68
113
  def get_node(self, name: str) -> "Tree":
69
114
  for node in self:
70
115
  if node.name == name:
@@ -75,23 +120,12 @@ class Tree:
75
120
  return not self.children
76
121
 
77
122
  def get_leaves(self) -> tuple["Tree", ...]:
78
- return tuple(node for node in self if not node.children)
79
-
80
- def get_time(self) -> float:
81
- parent_time = 0 if self.parent is None else self.parent.get_time()
82
- return self.time_to_parent + parent_time
83
-
84
- def set(self, key: str, value: Any) -> None:
85
- self._features[key] = value
86
-
87
- def update_features(self, features: dict[str, Any]) -> None:
88
- self._features.update(features)
89
-
90
- def get(self, key: str) -> Any:
91
- return self._features[key]
123
+ return tuple(node for node in self if node.is_leaf())
92
124
 
93
- def delete(self, key: str) -> None:
94
- del self._features[key]
125
+ def ladderize(self, feature: str) -> None:
126
+ self._children.sort(key=lambda x: x.get(feature))
127
+ for child in self.children:
128
+ child.ladderize(feature)
95
129
 
96
130
  def copy(self):
97
131
  new_tree = Tree(self.name, self.branch_length)
@@ -103,5 +137,8 @@ class Tree:
103
137
  def __iter__(self) -> Iterator["Tree"]:
104
138
  return self.preorder_traversal()
105
139
 
140
+ def __len__(self) -> int:
141
+ return sum(1 for _ in self)
142
+
106
143
  def __repr__(self) -> str:
107
144
  return f"TreeNode(name='{self.name}', branch_length={self.branch_length}, features={self.features})"
@@ -0,0 +1,49 @@
1
+ from collections.abc import Iterable
2
+ from enum import Enum
3
+
4
+ from phylogenie.tree import Tree
5
+ from phylogenie.treesimulator.events import get_mutation_id
6
+ from phylogenie.treesimulator.model import get_node_state
7
+ from phylogenie.utils import (
8
+ get_node_depth_levels,
9
+ get_node_depths,
10
+ get_node_height_levels,
11
+ get_node_heights,
12
+ get_node_leaf_counts,
13
+ )
14
+
15
+
16
+ def _get_states(tree: Tree) -> dict[Tree, str]:
17
+ return {node: get_node_state(node.name) for node in tree}
18
+
19
+
20
+ def _get_mutations(tree: Tree) -> dict[Tree, int]:
21
+ return {node: get_mutation_id(node.name) for node in tree}
22
+
23
+
24
+ class Feature(str, Enum):
25
+ DEPTH = "depth"
26
+ DEPTH_LEVEL = "depth_level"
27
+ HEIGHT = "height"
28
+ HEIGHT_LEVEL = "height_level"
29
+ MUTATION = "mutation"
30
+ N_LEAVES = "n_leaves"
31
+ STATE = "state"
32
+
33
+
34
+ FEATURES_EXTRACTORS = {
35
+ Feature.DEPTH: get_node_depths,
36
+ Feature.DEPTH_LEVEL: get_node_depth_levels,
37
+ Feature.HEIGHT: get_node_heights,
38
+ Feature.HEIGHT_LEVEL: get_node_height_levels,
39
+ Feature.MUTATION: _get_mutations,
40
+ Feature.N_LEAVES: get_node_leaf_counts,
41
+ Feature.STATE: _get_states,
42
+ }
43
+
44
+
45
+ def set_features(tree: Tree, features: Iterable[Feature]) -> None:
46
+ for feature in features:
47
+ feature_maps = FEATURES_EXTRACTORS[feature](tree)
48
+ for node in tree:
49
+ node.set(feature.value, feature_maps[node])
@@ -89,9 +89,7 @@ class Model:
89
89
  def _set_branch_length(self, node: Tree, time: float) -> None:
90
90
  if node.branch_length is not None:
91
91
  raise ValueError(f"Branch length of node {node.name} is already set.")
92
- node.branch_length = (
93
- time if node.parent is None else time - node.parent.get_time()
94
- )
92
+ node.branch_length = time if node.parent is None else time - node.parent.depth
95
93
 
96
94
  def _stem(self, individual: Individual, time: float) -> None:
97
95
  self._set_branch_length(individual.node, time)
@@ -0,0 +1,59 @@
1
+ from phylogenie.tree import Tree
2
+
3
+
4
+ def get_node_leaf_counts(tree: Tree) -> dict[Tree, int]:
5
+ n_leaves: dict[Tree, int] = {}
6
+ for node in tree.postorder_traversal():
7
+ n_leaves[node] = sum(n_leaves[child] for child in node.children) or 1
8
+ return n_leaves
9
+
10
+
11
+ def get_node_depth_levels(tree: Tree) -> dict[Tree, int]:
12
+ depth_levels: dict[Tree, int] = {}
13
+ for node in tree:
14
+ if node.parent is None:
15
+ depth_levels[node] = 0
16
+ else:
17
+ depth_levels[node] = depth_levels[node.parent] + 1
18
+ return depth_levels
19
+
20
+
21
+ def get_node_depths(tree: Tree) -> dict[Tree, float]:
22
+ depths: dict[Tree, float] = {}
23
+ for node in tree:
24
+ if node.parent is None:
25
+ depths[node] = 0
26
+ else:
27
+ if node.branch_length is None:
28
+ raise ValueError(f"Branch length of node {node.name} is not set.")
29
+ depths[node] = depths[node.parent] + node.branch_length
30
+ return depths
31
+
32
+
33
+ def get_node_height_levels(tree: Tree) -> dict[Tree, int]:
34
+ height_levels: dict[Tree, int] = {}
35
+ for node in tree.postorder_traversal():
36
+ if node.is_leaf():
37
+ height_levels[node] = 0
38
+ else:
39
+ height_levels[node] = max(
40
+ 1 + height_levels[child] for child in node.children
41
+ )
42
+ return height_levels
43
+
44
+
45
+ def get_node_heights(tree: Tree) -> dict[Tree, float]:
46
+ heights: dict[Tree, float] = {}
47
+ for node in tree.postorder_traversal():
48
+ if node.is_leaf():
49
+ heights[node] = 0
50
+ else:
51
+ if any(child.branch_length is None for child in node.children):
52
+ raise ValueError(
53
+ f"Branch length of one or more children of node {node.name} is not set."
54
+ )
55
+ heights[node] = max(
56
+ child.branch_length + heights[child] # pyright: ignore
57
+ for child in node.children
58
+ )
59
+ return heights
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "phylogenie"
3
- version = "2.1.13"
3
+ version = "2.1.15"
4
4
  description = "Generate phylogenetic datasets with minimal setup effort"
5
5
  authors = ["Gabriele Marino <gabmarino.8601@gmail.com>"]
6
6
  readme = "README.md"
@@ -1,39 +0,0 @@
1
- from collections.abc import Iterable
2
- from enum import Enum
3
-
4
- from phylogenie.tree import Tree
5
- from phylogenie.treesimulator.events import get_mutation_id
6
- from phylogenie.treesimulator.model import get_node_state
7
- from phylogenie.utils import get_heights, get_n_tips, get_times
8
-
9
-
10
- def _get_states(tree: Tree) -> dict[str, str]:
11
- return {node.name: get_node_state(node.name) for node in tree}
12
-
13
-
14
- def _get_mutations(tree: Tree) -> dict[str, int]:
15
- return {node.name: get_mutation_id(node.name) for node in tree}
16
-
17
-
18
- class Feature(str, Enum):
19
- STATE = "state"
20
- MUTATION = "mutation"
21
- N_TIPS = "n_tips"
22
- TIME = "time"
23
- HEIGHT = "height"
24
-
25
-
26
- FEATURES_EXTRACTORS = {
27
- Feature.STATE: _get_states,
28
- Feature.MUTATION: _get_mutations,
29
- Feature.N_TIPS: get_n_tips,
30
- Feature.TIME: get_times,
31
- Feature.HEIGHT: get_heights,
32
- }
33
-
34
-
35
- def set_features(tree: Tree, features: Iterable[Feature]) -> None:
36
- for feature in features:
37
- feature_maps = FEATURES_EXTRACTORS[feature](tree)
38
- for node in tree:
39
- node.set(feature.value, feature_maps[node.name])
@@ -1,28 +0,0 @@
1
- from phylogenie.tree import Tree
2
-
3
-
4
- def get_n_tips(tree: Tree) -> dict[str, int]:
5
- n_tips: dict[str, int] = {}
6
- for node in tree.postorder_traversal():
7
- n_tips[node.name] = (
8
- 1 if node.is_leaf() else sum(n_tips[child.name] for child in node.children)
9
- )
10
- return n_tips
11
-
12
-
13
- def get_times(tree: Tree) -> dict[str, float]:
14
- times: dict[str, float] = {}
15
- for node in tree:
16
- parent_time = 0 if node.parent is None else times[node.parent.name]
17
- times[node.name] = node.time_to_parent + parent_time
18
- return times
19
-
20
-
21
- def get_heights(tree: Tree) -> dict[str, int]:
22
- heights: dict[str, int] = {}
23
- for node in tree.postorder_traversal():
24
- if node.is_leaf():
25
- heights[node.name] = 0
26
- else:
27
- heights[node.name] = 1 + max(heights[child.name] for child in node.children)
28
- return heights
File without changes
File without changes