phylogenie 2.1.19__tar.gz → 2.1.21__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.19 → phylogenie-2.1.21}/PKG-INFO +1 -1
  2. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/__init__.py +24 -2
  3. phylogenie-2.1.21/phylogenie/draw.py +142 -0
  4. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/tree.py +20 -4
  5. phylogenie-2.1.21/phylogenie/utils.py +176 -0
  6. {phylogenie-2.1.19 → phylogenie-2.1.21}/pyproject.toml +1 -1
  7. phylogenie-2.1.19/phylogenie/plot.py +0 -136
  8. phylogenie-2.1.19/phylogenie/utils.py +0 -59
  9. {phylogenie-2.1.19 → phylogenie-2.1.21}/LICENSE.txt +0 -0
  10. {phylogenie-2.1.19 → phylogenie-2.1.21}/README.md +0 -0
  11. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/generators/__init__.py +0 -0
  12. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/generators/alisim.py +0 -0
  13. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/generators/configs.py +0 -0
  14. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/generators/dataset.py +0 -0
  15. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/generators/factories.py +0 -0
  16. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/generators/trees.py +0 -0
  17. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/generators/typeguards.py +0 -0
  18. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/io.py +0 -0
  19. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/main.py +0 -0
  20. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/models.py +0 -0
  21. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/msa.py +0 -0
  22. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/py.typed +0 -0
  23. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/skyline/__init__.py +0 -0
  24. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/skyline/matrix.py +0 -0
  25. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/skyline/parameter.py +0 -0
  26. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/skyline/vector.py +0 -0
  27. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/treesimulator/__init__.py +0 -0
  28. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/treesimulator/events/__init__.py +0 -0
  29. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/treesimulator/events/contact_tracing.py +0 -0
  30. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/treesimulator/events/core.py +0 -0
  31. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/treesimulator/events/mutations.py +0 -0
  32. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/treesimulator/features.py +0 -0
  33. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/treesimulator/gillespie.py +0 -0
  34. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/treesimulator/model.py +0 -0
  35. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/typeguards.py +0 -0
  36. {phylogenie-2.1.19 → phylogenie-2.1.21}/phylogenie/typings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phylogenie
3
- Version: 2.1.19
3
+ Version: 2.1.21
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Author: Gabriele Marino
6
6
  Author-email: gabmarino.8601@gmail.com
@@ -1,3 +1,4 @@
1
+ from phylogenie.draw import Coloring, draw_tree
1
2
  from phylogenie.generators import (
2
3
  AliSimDatasetGenerator,
3
4
  BDEITreeDatasetGenerator,
@@ -12,7 +13,6 @@ from phylogenie.generators import (
12
13
  )
13
14
  from phylogenie.io import dump_newick, load_fasta, load_newick
14
15
  from phylogenie.msa import MSA
15
- from phylogenie.plot import Coloring, plot_tree
16
16
  from phylogenie.skyline import (
17
17
  SkylineMatrix,
18
18
  SkylineMatrixCoercible,
@@ -46,6 +46,18 @@ from phylogenie.treesimulator import (
46
46
  get_FBD_events,
47
47
  simulate_tree,
48
48
  )
49
+ from phylogenie.utils import (
50
+ compute_colless_index,
51
+ compute_mean_leaf_pairwise_distance,
52
+ compute_sackin_index,
53
+ get_distance,
54
+ get_mrca,
55
+ get_node_depth_levels,
56
+ get_node_depths,
57
+ get_node_height_levels,
58
+ get_node_heights,
59
+ get_node_leaf_counts,
60
+ )
49
61
 
50
62
  __all__ = [
51
63
  "AliSimDatasetGenerator",
@@ -92,5 +104,15 @@ __all__ = [
92
104
  "load_newick",
93
105
  "MSA",
94
106
  "Coloring",
95
- "plot_tree",
107
+ "draw_tree",
108
+ "compute_colless_index",
109
+ "compute_mean_leaf_pairwise_distance",
110
+ "compute_sackin_index",
111
+ "get_distance",
112
+ "get_mrca",
113
+ "get_node_depths",
114
+ "get_node_depth_levels",
115
+ "get_node_heights",
116
+ "get_node_height_levels",
117
+ "get_node_leaf_counts",
96
118
  ]
@@ -0,0 +1,142 @@
1
+ from enum import Enum
2
+ from typing import Any
3
+
4
+ import matplotlib.colors as mcolors
5
+ import matplotlib.patches as mpatches
6
+ import matplotlib.pyplot as plt
7
+ from matplotlib.axes import Axes
8
+ from mpl_toolkits.axes_grid1.inset_locator import inset_axes # pyright: ignore
9
+
10
+ from phylogenie.tree import Tree
11
+ from phylogenie.utils import get_node_depth_levels, get_node_depths
12
+
13
+
14
+ class Coloring(str, Enum):
15
+ DISCRETE = "discrete"
16
+ CONTINUOUS = "continuous"
17
+
18
+
19
+ Color = str | tuple[float, float, float] | tuple[float, float, float, float]
20
+
21
+
22
+ def _draw_colored_tree(tree: Tree, ax: Axes, colors: Color | dict[Tree, Color]) -> Axes:
23
+ if not isinstance(colors, dict):
24
+ colors = {node: colors for node in tree}
25
+
26
+ xs = (
27
+ get_node_depth_levels(tree)
28
+ if any(node.branch_length is None for node in tree)
29
+ else get_node_depths(tree)
30
+ )
31
+ ys = {node: i for i, node in enumerate(tree.inorder_traversal())}
32
+
33
+ for node in tree:
34
+ x1, y1 = xs[node], ys[node]
35
+ if node.parent is None:
36
+ ax.hlines(y=y1, xmin=0, xmax=x1, color=colors[node]) # pyright: ignore
37
+ continue
38
+ x0, y0 = xs[node.parent], ys[node.parent]
39
+ ax.vlines(x=x0, ymin=y0, ymax=y1, color=colors[node]) # pyright: ignore
40
+ ax.hlines(y=y1, xmin=x0, xmax=x1, color=colors[node]) # pyright: ignore
41
+
42
+ ax.set_yticks([]) # pyright: ignore
43
+ return ax
44
+
45
+
46
+ def draw_tree(
47
+ tree: Tree,
48
+ ax: Axes | None = None,
49
+ color_by: str | None = None,
50
+ coloring: str | Coloring | None = None,
51
+ default_color: Color = "black",
52
+ cmap: str | None = None,
53
+ show_legend: bool = True,
54
+ vmin: float | None = None,
55
+ vmax: float | None = None,
56
+ show_hist: bool = True,
57
+ hist_kwargs: dict[str, Any] | None = None,
58
+ hist_axes_kwargs: dict[str, Any] | None = None,
59
+ ) -> Axes | tuple[Axes, Axes]:
60
+ if ax is None:
61
+ ax = plt.gca()
62
+
63
+ if color_by is None:
64
+ return _draw_colored_tree(tree, ax, colors=default_color)
65
+
66
+ features = [node.get(color_by) for node in tree if color_by in node.features]
67
+
68
+ if coloring is None:
69
+ coloring = (
70
+ Coloring.CONTINUOUS
71
+ if any(isinstance(f, float) for f in features)
72
+ else Coloring.DISCRETE
73
+ )
74
+
75
+ if coloring == Coloring.DISCRETE:
76
+ if any(isinstance(f, float) for f in features):
77
+ raise ValueError(
78
+ "Discrete coloring selected but feature values are not all categorical."
79
+ )
80
+
81
+ colormap = plt.get_cmap("tab20" if cmap is None else cmap)
82
+ feature_colors = {
83
+ f: mcolors.to_hex(colormap(i)) for i, f in enumerate(set(features))
84
+ }
85
+ colors = {
86
+ node: (
87
+ feature_colors[node.get(color_by)]
88
+ if color_by in node.features
89
+ else default_color
90
+ )
91
+ for node in tree
92
+ }
93
+
94
+ if show_legend:
95
+ legend_handles = [
96
+ mpatches.Patch(color=feature_colors[f], label=str(f))
97
+ for f in feature_colors
98
+ ]
99
+ if any(color_by not in node.features for node in tree):
100
+ legend_handles.append(mpatches.Patch(color=default_color, label="NA"))
101
+ ax.legend(handles=legend_handles) # pyright: ignore
102
+
103
+ return _draw_colored_tree(tree, ax, colors)
104
+
105
+ if coloring == Coloring.CONTINUOUS:
106
+ vmin = min(features) if vmin is None else vmin
107
+ vmax = max(features) if vmax is None else vmax
108
+ norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
109
+ colormap = plt.get_cmap("viridis" if cmap is None else cmap)
110
+ colors = {
111
+ node: (
112
+ colormap(norm(float(node.get(color_by))))
113
+ if color_by in node.features
114
+ else default_color
115
+ )
116
+ for node in tree
117
+ }
118
+
119
+ if show_hist:
120
+ default_hist_axes_kwargs = {"width": "25%", "height": "25%"}
121
+ if hist_axes_kwargs is not None:
122
+ default_hist_axes_kwargs.update(hist_axes_kwargs)
123
+ hist_ax = inset_axes(ax, **default_hist_axes_kwargs) # pyright: ignore
124
+
125
+ hist_kwargs = {} if hist_kwargs is None else hist_kwargs
126
+ _, bins, patches = hist_ax.hist(features, **hist_kwargs) # pyright: ignore
127
+
128
+ for patch, b0, b1 in zip( # pyright: ignore
129
+ patches, bins[:-1], bins[1:] # pyright: ignore
130
+ ):
131
+ midpoint = (b0 + b1) / 2 # pyright: ignore
132
+ patch.set_facecolor(colormap(norm(midpoint))) # pyright: ignore
133
+ return _draw_colored_tree(tree, ax, colors), hist_ax # pyright: ignore
134
+
135
+ else:
136
+ sm = plt.cm.ScalarMappable(cmap=colormap, norm=norm)
137
+ ax.get_figure().colorbar(sm, ax=ax) # pyright: ignore
138
+ return _draw_colored_tree(tree, ax, colors)
139
+
140
+ raise ValueError(
141
+ f"Unknown coloring method: {coloring}. Choices are {list(Coloring)}."
142
+ )
@@ -73,6 +73,8 @@ class Tree:
73
73
  del self._features[key]
74
74
 
75
75
  def add_child(self, child: "Tree") -> "Tree":
76
+ if child.parent is not None:
77
+ raise ValueError(f"Node {child.name} already has a parent.")
76
78
  child._parent = self
77
79
  self._children.append(child)
78
80
  return self
@@ -81,10 +83,12 @@ class Tree:
81
83
  self._children.remove(child)
82
84
  child._parent = None
83
85
 
84
- def set_parent(self, node: "Tree | None"):
85
- self._parent = node
86
- if node is not None:
87
- node._children.append(self)
86
+ def set_parent(self, parent: "Tree | None"):
87
+ if self.parent is not None:
88
+ self.parent.remove_child(self)
89
+ self._parent = parent
90
+ if parent is not None:
91
+ parent._children.append(self)
88
92
 
89
93
  def inorder_traversal(self) -> Iterator["Tree"]:
90
94
  if self.is_leaf():
@@ -107,6 +111,12 @@ class Tree:
107
111
  yield from child.postorder_traversal()
108
112
  yield self
109
113
 
114
+ def iter_ancestors(self, stop: "Tree | None" = None) -> Iterator["Tree"]:
115
+ node = self
116
+ while node is not None and node is not stop:
117
+ yield node
118
+ node = node.parent
119
+
110
120
  def breadth_first_traversal(self) -> Iterator["Tree"]:
111
121
  queue: deque["Tree"] = deque([self])
112
122
  while queue:
@@ -126,6 +136,12 @@ class Tree:
126
136
  def get_leaves(self) -> tuple["Tree", ...]:
127
137
  return tuple(node for node in self if node.is_leaf())
128
138
 
139
+ def get_internal_nodes(self) -> tuple["Tree", ...]:
140
+ return tuple(node for node in self if not node.is_leaf())
141
+
142
+ def is_binary(self) -> bool:
143
+ return all(len(node.children) in (0, 2) for node in self)
144
+
129
145
  def ladderize(self, criterion: Callable[["Tree"], Any]) -> None:
130
146
  self._children.sort(key=criterion)
131
147
  for child in self.children:
@@ -0,0 +1,176 @@
1
+ from itertools import chain
2
+ from math import comb
3
+
4
+ from phylogenie.tree import Tree
5
+
6
+
7
+ def get_node_leaf_counts(tree: Tree) -> dict[Tree, int]:
8
+ n_leaves: dict[Tree, int] = {}
9
+ for node in tree.postorder_traversal():
10
+ n_leaves[node] = sum(n_leaves[child] for child in node.children) or 1
11
+ return n_leaves
12
+
13
+
14
+ def get_node_depth_levels(tree: Tree) -> dict[Tree, int]:
15
+ depth_levels: dict[Tree, int] = {}
16
+ for node in tree:
17
+ if node.parent is None:
18
+ depth_levels[node] = 0
19
+ else:
20
+ depth_levels[node] = depth_levels[node.parent] + 1
21
+ return depth_levels
22
+
23
+
24
+ def get_node_depths(tree: Tree) -> dict[Tree, float]:
25
+ depths: dict[Tree, float] = {}
26
+ for node in tree:
27
+ if node.parent is None:
28
+ depths[node] = 0 if node.branch_length is None else node.branch_length
29
+ else:
30
+ if node.branch_length is None:
31
+ raise ValueError(f"Branch length of node {node.name} is not set.")
32
+ depths[node] = depths[node.parent] + node.branch_length
33
+ return depths
34
+
35
+
36
+ def get_node_height_levels(tree: Tree) -> dict[Tree, int]:
37
+ height_levels: dict[Tree, int] = {}
38
+ for node in tree.postorder_traversal():
39
+ if node.is_leaf():
40
+ height_levels[node] = 0
41
+ else:
42
+ height_levels[node] = max(
43
+ 1 + height_levels[child] for child in node.children
44
+ )
45
+ return height_levels
46
+
47
+
48
+ def get_node_heights(tree: Tree) -> dict[Tree, float]:
49
+ heights: dict[Tree, float] = {}
50
+ for node in tree.postorder_traversal():
51
+ if node.is_leaf():
52
+ heights[node] = 0
53
+ else:
54
+ if any(child.branch_length is None for child in node.children):
55
+ raise ValueError(
56
+ f"Branch length of one or more children of node {node.name} is not set."
57
+ )
58
+ heights[node] = max(
59
+ child.branch_length + heights[child] # pyright: ignore
60
+ for child in node.children
61
+ )
62
+ return heights
63
+
64
+
65
+ def get_mrca(node1: Tree, node2: Tree) -> Tree:
66
+ node1_ancestors = set(node1.iter_ancestors())
67
+ for node2_ancestor in node2.iter_ancestors():
68
+ if node2_ancestor in node1_ancestors:
69
+ return node2_ancestor
70
+ raise ValueError(f"No common ancestor found between node {node1} and node {node2}.")
71
+
72
+
73
+ def get_distance(node1: Tree, node2: Tree) -> float:
74
+ mrca = get_mrca(node1, node2)
75
+ path = list(chain(node1.iter_ancestors(stop=mrca), node2.iter_ancestors(stop=mrca)))
76
+ if any(node.branch_length is None for node in path):
77
+ return len(path)
78
+ return sum(node.branch_length for node in path) # pyright: ignore
79
+
80
+
81
+ def compute_sackin_index(tree: Tree, normalize: bool = False) -> float:
82
+ """
83
+ Compute the Sackin index of a tree.
84
+
85
+ Parameters
86
+ ----------
87
+ tree : Tree
88
+ The input tree.
89
+ normalize : bool, optional
90
+ Whether to normalize the index between 0 and 1, by default False.
91
+
92
+ Returns
93
+ -------
94
+ float
95
+ The Sackin index of the tree.
96
+
97
+ References
98
+ ----------
99
+ - Kwang-Tsao Shao and Robert R Sokal. Tree Balance. Systematic Zoology, 39(3):266, 1990.
100
+ """
101
+ leaves = tree.get_leaves()
102
+ depth_levels = get_node_depth_levels(tree)
103
+ sackin_index = sum(depth_levels[leaf] for leaf in leaves)
104
+ if normalize:
105
+ n = len(leaves)
106
+ max_sackin_index = (n + 2) * (n - 1) / 2
107
+ return (sackin_index - n) / (max_sackin_index - n)
108
+ return sackin_index
109
+
110
+
111
+ def compute_colless_index(tree: Tree, normalize: bool = False) -> float:
112
+ """
113
+ Compute the Colless index of a binary tree.
114
+
115
+ Parameters
116
+ ----------
117
+ tree : Tree
118
+ The input binary tree.
119
+ normalize : bool, optional
120
+ Whether to normalize the index between 0 and 1, by default False.
121
+
122
+ Returns
123
+ -------
124
+ float
125
+ The Colless index of the tree.
126
+
127
+ References
128
+ ----------
129
+ - Donald H. Colless. Review of phylogenetics: the theory and practice of phylogenetic systematics. Systematic Zoology, 31(1):100–104, 1982.
130
+ """
131
+ if not tree.is_binary():
132
+ raise ValueError("Colless index is only defined for binary trees.")
133
+
134
+ internal_nodes = tree.get_internal_nodes()
135
+ if not internal_nodes:
136
+ raise ValueError(
137
+ "Tree must have at least one internal node to compute the Colless index."
138
+ )
139
+
140
+ colless_index = 0
141
+ leaf_counts = get_node_leaf_counts(tree)
142
+ for node in internal_nodes:
143
+ left, right = node.children
144
+ colless_index += abs(leaf_counts[left] - leaf_counts[right])
145
+ if normalize:
146
+ n_leaves = len(leaf_counts) - len(internal_nodes)
147
+ max_colless_index = comb(n_leaves, 2)
148
+ return colless_index / max_colless_index
149
+ return colless_index
150
+
151
+
152
+ def compute_mean_leaf_pairwise_distance(tree: Tree) -> float:
153
+ """
154
+ Compute the mean pairwise distance between all pairs of leaves in the tree.
155
+
156
+ Parameters
157
+ ----------
158
+ tree : Tree
159
+ The input tree.
160
+
161
+ Returns
162
+ -------
163
+ float
164
+ The mean pairwise distance between all pairs of leaves in the tree.
165
+ """
166
+ leaves = tree.get_leaves()
167
+ n_leaves = len(leaves)
168
+ if n_leaves < 2:
169
+ return 0.0
170
+
171
+ total_distance = sum(
172
+ get_distance(leaves[i], leaves[j])
173
+ for i in range(n_leaves)
174
+ for j in range(i + 1, n_leaves)
175
+ )
176
+ return total_distance / comb(n_leaves, 2)
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "phylogenie"
3
- version = "2.1.19"
3
+ version = "2.1.21"
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,136 +0,0 @@
1
- from enum import Enum
2
- from typing import Any
3
-
4
- import matplotlib.colors as mcolors
5
- import matplotlib.patches as mpatches
6
- import matplotlib.pyplot as plt
7
- from matplotlib.axes import Axes
8
- from mpl_toolkits.axes_grid1.inset_locator import inset_axes # pyright: ignore
9
-
10
- from phylogenie.tree import Tree
11
- from phylogenie.utils import get_node_depth_levels, get_node_depths
12
-
13
-
14
- class Coloring(str, Enum):
15
- DISCRETE = "discrete"
16
- CONTINUOUS = "continuous"
17
-
18
-
19
- def plot_tree(
20
- tree: Tree,
21
- ax: Axes | None = None,
22
- color_by: str | None = None,
23
- default_color: str = "black",
24
- coloring: str | Coloring | None = None,
25
- cmap: str | None = None,
26
- show_legend: bool = True,
27
- show_hist: bool = True,
28
- hist_kwargs: dict[str, Any] | None = None,
29
- hist_axes_kwargs: dict[str, Any] | None = None,
30
- ) -> Axes | tuple[Axes, Axes]:
31
- if ax is None:
32
- ax = plt.gca()
33
-
34
- xs = (
35
- get_node_depth_levels(tree)
36
- if any(node.branch_length is None for node in tree)
37
- else get_node_depths(tree)
38
- )
39
- ys = {node: i for i, node in enumerate(tree.inorder_traversal())}
40
-
41
- if color_by is not None:
42
- features = [node.get(color_by) for node in tree if color_by in node.features]
43
-
44
- if coloring is None and any(isinstance(f, float) for f in features):
45
- coloring = Coloring.CONTINUOUS
46
- elif coloring is None:
47
- coloring = Coloring.DISCRETE
48
-
49
- if coloring == Coloring.DISCRETE:
50
- if any(isinstance(f, float) for f in features):
51
- raise ValueError(
52
- "Discrete coloring selected but feature values are not all categorical."
53
- )
54
- colormap = plt.get_cmap("tab20" if cmap is None else cmap)
55
- feature_colors = {
56
- f: mcolors.to_hex(colormap(i)) for i, f in enumerate(set(features))
57
- }
58
- colors = {
59
- node: (
60
- feature_colors[node.get(color_by)]
61
- if color_by in node.features
62
- else default_color
63
- )
64
- for node in tree
65
- }
66
-
67
- if show_legend:
68
- legend_handles = [
69
- mpatches.Patch(color=feature_colors[f], label=str(f))
70
- for f in feature_colors
71
- ]
72
- if any(color_by not in node.features for node in tree):
73
- legend_handles.append(
74
- mpatches.Patch(color=default_color, label="NA")
75
- )
76
- ax.legend(handles=legend_handles, title=color_by) # pyright: ignore
77
-
78
- elif coloring == Coloring.CONTINUOUS:
79
- norm = mcolors.Normalize(vmin=min(features), vmax=max(features))
80
- colormap = plt.get_cmap("viridis" if cmap is None else cmap)
81
- colors = {
82
- node: (
83
- colormap(norm(float(node.get(color_by))))
84
- if color_by in node.features
85
- else default_color
86
- )
87
- for node in tree
88
- }
89
-
90
- if show_hist:
91
- default_hist_axes_kwargs = {"width": "25%", "height": "25%"}
92
- if hist_axes_kwargs is not None:
93
- default_hist_axes_kwargs.update(hist_axes_kwargs)
94
- hist_ax = inset_axes(ax, **default_hist_axes_kwargs) # pyright: ignore
95
-
96
- hist_kwargs = {} if hist_kwargs is None else hist_kwargs
97
- _, bins, patches = hist_ax.hist( # pyright: ignore
98
- features, **hist_kwargs
99
- )
100
-
101
- for patch, b0, b1 in zip( # pyright: ignore
102
- patches, bins[:-1], bins[1:] # pyright: ignore
103
- ):
104
- midpoint = (b0 + b1) / 2 # pyright: ignore
105
- patch.set_facecolor(colormap(norm(midpoint))) # pyright: ignore
106
- else:
107
- sm = plt.cm.ScalarMappable(cmap=colormap, norm=norm)
108
- ax.get_figure().colorbar(sm, ax=ax) # pyright: ignore
109
-
110
- else:
111
- raise ValueError(
112
- f"Unknown coloring method: {coloring}. Choices are {list(Coloring)}."
113
- )
114
- else:
115
- colors = {node: default_color for node in tree}
116
-
117
- for node in tree:
118
- x1, y1 = xs[node], ys[node]
119
- if node.parent is None:
120
- ax.hlines(y=y1, xmin=0, xmax=x1, color=colors[node]) # pyright: ignore
121
- continue
122
- x0, y0 = xs[node.parent], ys[node.parent]
123
- ax.vlines(x=x0, ymin=y0, ymax=y1, color=colors[node]) # pyright: ignore
124
- ax.hlines(y=y1, xmin=x0, xmax=x1, color=colors[node]) # pyright: ignore
125
-
126
- for node in tree:
127
- x1, y1 = xs[node], ys[node]
128
- if node.parent is None:
129
- ax.hlines(y=y1, xmin=0, xmax=x1, color=colors[node]) # pyright: ignore
130
- continue
131
- x0, y0 = xs[node.parent], ys[node.parent]
132
- ax.vlines(x=x0, ymin=y0, ymax=y1, color=colors[node]) # pyright: ignore
133
- ax.hlines(y=y1, xmin=x0, xmax=x1, color=colors[node]) # pyright: ignore
134
-
135
- ax.set_yticks([]) # pyright: ignore
136
- return ax
@@ -1,59 +0,0 @@
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 if node.branch_length is None else node.branch_length
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
File without changes
File without changes