phylogenie 3.1.5__tar.gz → 3.1.8__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.
Files changed (48) hide show
  1. {phylogenie-3.1.5/src/phylogenie.egg-info → phylogenie-3.1.8}/PKG-INFO +1 -1
  2. {phylogenie-3.1.5 → phylogenie-3.1.8}/pyproject.toml +1 -1
  3. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/__init__.py +17 -4
  4. phylogenie-3.1.8/src/phylogenie/draw.py +694 -0
  5. phylogenie-3.1.8/src/phylogenie/io/__init__.py +3 -0
  6. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/io/fasta.py +8 -0
  7. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/mixins.py +3 -11
  8. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/__init__.py +6 -0
  9. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/features.py +6 -0
  10. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/io/nexus.py +2 -2
  11. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/tree.py +15 -2
  12. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/utils.py +11 -0
  13. {phylogenie-3.1.5 → phylogenie-3.1.8/src/phylogenie.egg-info}/PKG-INFO +1 -1
  14. phylogenie-3.1.5/src/phylogenie/draw.py +0 -152
  15. phylogenie-3.1.5/src/phylogenie/io/__init__.py +0 -3
  16. {phylogenie-3.1.5 → phylogenie-3.1.8}/LICENSE.txt +0 -0
  17. {phylogenie-3.1.5 → phylogenie-3.1.8}/README.md +0 -0
  18. {phylogenie-3.1.5 → phylogenie-3.1.8}/setup.cfg +0 -0
  19. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/generators/__init__.py +0 -0
  20. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/generators/alisim.py +0 -0
  21. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/generators/configs.py +0 -0
  22. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/generators/dataset.py +0 -0
  23. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/generators/factories.py +0 -0
  24. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/generators/trees.py +0 -0
  25. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/generators/typeguards.py +0 -0
  26. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/main.py +0 -0
  27. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/msa.py +0 -0
  28. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/py.typed +0 -0
  29. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/skyline/__init__.py +0 -0
  30. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/skyline/matrix.py +0 -0
  31. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/skyline/parameter.py +0 -0
  32. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/skyline/vector.py +0 -0
  33. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/events/__init__.py +0 -0
  34. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/events/base.py +0 -0
  35. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/events/contact_tracing.py +0 -0
  36. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/events/core.py +0 -0
  37. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/events/mutations.py +0 -0
  38. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/gillespie.py +0 -0
  39. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/io/__init__.py +0 -0
  40. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/io/newick.py +0 -0
  41. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/treesimulator/model.py +0 -0
  42. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/typeguards.py +0 -0
  43. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie/typings.py +0 -0
  44. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie.egg-info/SOURCES.txt +0 -0
  45. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie.egg-info/dependency_links.txt +0 -0
  46. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie.egg-info/entry_points.txt +0 -0
  47. {phylogenie-3.1.5 → phylogenie-3.1.8}/src/phylogenie.egg-info/requires.txt +0 -0
  48. {phylogenie-3.1.5 → phylogenie-3.1.8}/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.5
3
+ Version: 3.1.8
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "phylogenie"
3
- version = "3.1.5"
3
+ version = "3.1.8"
4
4
  description = "Generate phylogenetic datasets with minimal setup effort"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,4 +1,11 @@
1
- from phylogenie.draw import Coloring, draw_tree
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
+ )
2
9
  from phylogenie.generators import (
3
10
  AliSimDatasetGenerator,
4
11
  BDEITreeDatasetGenerator,
@@ -11,8 +18,8 @@ from phylogenie.generators import (
11
18
  FBDTreeDatasetGenerator,
12
19
  TreeDatasetGeneratorConfig,
13
20
  )
14
- from phylogenie.io import load_fasta
15
- from phylogenie.msa import MSA
21
+ from phylogenie.io import dump_fasta, load_fasta
22
+ from phylogenie.msa import MSA, Sequence
16
23
  from phylogenie.skyline import (
17
24
  SkylineMatrix,
18
25
  SkylineMatrixCoercible,
@@ -64,7 +71,11 @@ from phylogenie.treesimulator import (
64
71
  )
65
72
 
66
73
  __all__ = [
67
- "Coloring",
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",
68
79
  "draw_tree",
69
80
  "AliSimDatasetGenerator",
70
81
  "BDEITreeDatasetGenerator",
@@ -76,8 +87,10 @@ __all__ = [
76
87
  "EpidemiologicalTreeDatasetGenerator",
77
88
  "FBDTreeDatasetGenerator",
78
89
  "TreeDatasetGeneratorConfig",
90
+ "dump_fasta",
79
91
  "load_fasta",
80
92
  "MSA",
93
+ "Sequence",
81
94
  "SkylineMatrix",
82
95
  "SkylineMatrixCoercible",
83
96
  "SkylineParameter",
@@ -0,0 +1,694 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime
3
+ from typing import Any, Literal, overload
4
+
5
+ import matplotlib.colors as mcolors
6
+ import matplotlib.dates as mdates
7
+ import matplotlib.patches as mpatches
8
+ import matplotlib.pyplot as plt
9
+ from matplotlib.axes import Axes
10
+ from matplotlib.colors import Colormap
11
+ from mpl_toolkits.axes_grid1.inset_locator import inset_axes # pyright: ignore
12
+
13
+ from phylogenie.treesimulator import (
14
+ Tree,
15
+ get_node_ages,
16
+ get_node_depth_levels,
17
+ get_node_depths,
18
+ )
19
+
20
+
21
+ @dataclass
22
+ class CalibrationNode:
23
+ node: Tree
24
+ date: datetime
25
+
26
+
27
+ Color = str | tuple[float, float, float] | tuple[float, float, float, float]
28
+
29
+
30
+ def draw_tree(
31
+ tree: Tree,
32
+ ax: Axes | None = None,
33
+ colors: Color | dict[Tree, Color] = "black",
34
+ backward_time: bool = False,
35
+ ) -> Axes:
36
+ """
37
+ Draw a phylogenetic tree with colored branches.
38
+
39
+ Parameters
40
+ ----------
41
+ tree : Tree
42
+ The phylogenetic tree to draw.
43
+ ax : Axes | None, optional
44
+ The matplotlib Axes to draw on. If None, uses the current Axes.
45
+ colors : Color | dict[Tree, Color], optional
46
+ A single color for all branches or a dictionary mapping each node to a color.
47
+ backward_time : bool, optional
48
+ If True, the x-axis is inverted to represent time going backward.
49
+
50
+ Returns
51
+ -------
52
+ Axes
53
+ The Axes with the drawn tree.
54
+ """
55
+ if ax is None:
56
+ ax = plt.gca()
57
+
58
+ if not isinstance(colors, dict):
59
+ colors = {node: colors for node in tree}
60
+
61
+ xs = (
62
+ get_node_ages(tree)
63
+ if backward_time
64
+ else get_node_depth_levels(tree)
65
+ if any(node.branch_length is None for node in tree.iter_descendants())
66
+ else get_node_depths(tree)
67
+ )
68
+
69
+ ys: dict[Tree, float] = {node: i for i, node in enumerate(tree.get_leaves())}
70
+ for node in tree.postorder_traversal():
71
+ if node.is_internal():
72
+ ys[node] = sum(ys[child] for child in node.children) / len(node.children)
73
+
74
+ if tree.branch_length is not None:
75
+ xmin = xs[tree] + tree.branch_length if backward_time else 0
76
+ ax.hlines(y=ys[tree], xmin=xmin, xmax=xs[tree], color=colors[tree]) # pyright: ignore
77
+ for node in tree:
78
+ x1, y1 = xs[node], ys[node]
79
+ for child in node.children:
80
+ x2, y2 = xs[child], ys[child]
81
+ ax.hlines(y=y2, xmin=x1, xmax=x2, color=colors[child]) # pyright: ignore
82
+ ax.vlines(x=x1, ymin=y1, ymax=y2, color=colors[child]) # pyright: ignore
83
+
84
+ if backward_time:
85
+ ax.invert_xaxis()
86
+
87
+ ax.set_yticks([]) # pyright: ignore
88
+ return ax
89
+
90
+
91
+ def _depth_to_date(
92
+ depth: float, calibration_nodes: tuple[CalibrationNode, CalibrationNode]
93
+ ) -> datetime:
94
+ """
95
+ Convert a depth value to a date using linear interpolation between two calibration nodes.
96
+
97
+ Parameters
98
+ ----------
99
+ depth : float
100
+ The depth value to convert.
101
+ calibration_nodes : tuple[CalibrationNode, CalibrationNode]
102
+ Two calibration nodes defining the mapping from depth to date.
103
+
104
+ Returns
105
+ -------
106
+ datetime
107
+ The interpolated date corresponding to the given depth.
108
+ """
109
+ node1, node2 = calibration_nodes
110
+ depth1, depth2 = node1.node.depth, node2.node.depth
111
+ date1, date2 = node1.date, node2.date
112
+ return date1 + (depth - depth1) * (date2 - date1) / (depth2 - depth1)
113
+
114
+
115
+ def draw_dated_tree(
116
+ tree: Tree,
117
+ calibration_nodes: tuple[CalibrationNode, CalibrationNode],
118
+ ax: Axes | None = None,
119
+ colors: Color | dict[Tree, Color] = "black",
120
+ ) -> Axes:
121
+ """
122
+ Draw a phylogenetic tree with branches positioned according to calibrated dates.
123
+
124
+ Parameters
125
+ ----------
126
+ tree : Tree
127
+ The phylogenetic tree to draw.
128
+ calibration_nodes : tuple[CalibrationNode, CalibrationNode]
129
+ Two calibration nodes defining the mapping from depth to date.
130
+ ax : Axes | None, optional
131
+ The matplotlib Axes to draw on. If None, uses the current Axes.
132
+ colors : Color | dict[Tree, Color], optional
133
+ A single color for all branches or a dictionary mapping each node to a color.
134
+
135
+ Returns
136
+ -------
137
+ Axes
138
+ The Axes with the drawn dated tree.
139
+ """
140
+ if ax is None:
141
+ ax = plt.gca()
142
+
143
+ if not isinstance(colors, dict):
144
+ colors = {node: colors for node in tree}
145
+
146
+ xs = {
147
+ node: _depth_to_date(depth=depth, calibration_nodes=calibration_nodes)
148
+ for node, depth in get_node_depths(tree).items()
149
+ }
150
+
151
+ ys: dict[Tree, float] = {node: i for i, node in enumerate(tree.get_leaves())}
152
+ for node in tree.postorder_traversal():
153
+ if node.is_internal():
154
+ ys[node] = sum(ys[child] for child in node.children) / len(node.children)
155
+
156
+ if tree.branch_length is not None:
157
+ origin_date = _depth_to_date(depth=0, calibration_nodes=calibration_nodes)
158
+ ax.hlines( # pyright: ignore
159
+ y=ys[tree],
160
+ xmin=mdates.date2num(origin_date), # pyright: ignore
161
+ xmax=mdates.date2num(xs[tree]), # pyright: ignore
162
+ color=colors[tree],
163
+ )
164
+ for node in tree:
165
+ x1, y1 = xs[node], ys[node]
166
+ for child in node.children:
167
+ x2, y2 = xs[child], ys[child]
168
+ ax.hlines( # pyright: ignore
169
+ y=y2,
170
+ xmin=mdates.date2num(x1), # pyright: ignore
171
+ xmax=mdates.date2num(x2), # pyright: ignore
172
+ color=colors[child],
173
+ )
174
+ ax.vlines(x=mdates.date2num(x1), ymin=y1, ymax=y2, color=colors[child]) # pyright: ignore
175
+
176
+ ax.xaxis.set_major_locator(mdates.AutoDateLocator())
177
+ ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m-%d"))
178
+ ax.tick_params(axis="x", labelrotation=45) # pyright: ignore
179
+
180
+ ax.set_yticks([]) # pyright: ignore
181
+ return ax
182
+
183
+
184
+ def _init_colored_tree_categorical(
185
+ tree: Tree,
186
+ color_by: str,
187
+ ax: Axes | None = None,
188
+ default_color: Color = "black",
189
+ colormap: str | Colormap = "tab20",
190
+ show_legend: bool = True,
191
+ labels: dict[Any, str] | None = None,
192
+ legend_kwargs: dict[str, Any] | None = None,
193
+ ) -> tuple[Axes, dict[Tree, Color]]:
194
+ """
195
+ Initialize colors for drawing a tree based on categorical metadata.
196
+
197
+ Parameters
198
+ ----------
199
+ tree : Tree
200
+ The phylogenetic tree.
201
+ color_by : str
202
+ The metadata key to color branches by.
203
+ ax : Axes | None, optional
204
+ The matplotlib Axes to draw on. If None, uses the current Axes.
205
+ default_color : Color, optional
206
+ The color to use for nodes without the specified metadata.
207
+ colormap : str | Colormap, optional
208
+ The colormap to use for coloring categories. Defaults to 'tab20'.
209
+ show_legend : bool, optional
210
+ Whether to display a legend for the categories.
211
+ labels : dict[Any, str] | None, optional
212
+ A mapping from category values to labels for the legend.
213
+ legend_kwargs : dict[str, Any] | None, optional
214
+ Additional keyword arguments to pass to the legend.
215
+
216
+ Returns
217
+ -------
218
+ tuple[Axes, dict[Tree, Color]]
219
+ The Axes and a dictionary mapping each node to its assigned color.
220
+ """
221
+ if ax is None:
222
+ ax = plt.gca()
223
+
224
+ if isinstance(colormap, str):
225
+ colormap = plt.get_cmap(colormap)
226
+
227
+ features = {node: node[color_by] for node in tree if color_by in node.metadata}
228
+ feature_colors = {
229
+ f: mcolors.to_hex(colormap(i)) for i, f in enumerate(set(features.values()))
230
+ }
231
+ colors = {
232
+ node: feature_colors[features[node]] if node in features else default_color
233
+ for node in tree
234
+ }
235
+
236
+ if show_legend:
237
+ legend_handles = [
238
+ mpatches.Patch(
239
+ color=feature_colors[f],
240
+ label=str(f) if labels is None else labels[f],
241
+ )
242
+ for f in feature_colors
243
+ ]
244
+ if any(color_by not in node.metadata for node in tree):
245
+ legend_handles.append(mpatches.Patch(color=default_color, label="NA"))
246
+ if legend_kwargs is None:
247
+ legend_kwargs = {}
248
+ ax.legend(handles=legend_handles, **legend_kwargs) # pyright: ignore
249
+
250
+ return ax, colors
251
+
252
+
253
+ def draw_colored_tree_categorical(
254
+ tree: Tree,
255
+ color_by: str,
256
+ ax: Axes | None = None,
257
+ backward_time: bool = False,
258
+ default_color: Color = "black",
259
+ colormap: str | Colormap = "tab20",
260
+ show_legend: bool = True,
261
+ labels: dict[Any, str] | None = None,
262
+ legend_kwargs: dict[str, Any] | None = None,
263
+ ):
264
+ """
265
+ Draw a phylogenetic tree with branches colored based on categorical metadata.
266
+
267
+ Parameters
268
+ ----------
269
+ tree : Tree
270
+ The phylogenetic tree to draw.
271
+ color_by : str
272
+ The metadata key to color branches by.
273
+ ax : Axes | None, optional
274
+ The matplotlib Axes to draw on. If None, uses the current Axes.
275
+ backward_time : bool, optional
276
+ If True, the x-axis is inverted to represent time going backward.
277
+ default_color : Color, optional
278
+ The color to use for nodes without the specified metadata.
279
+ colormap : str | Colormap, optional
280
+ The colormap to use for coloring categories. Defaults to 'tab20'.
281
+ show_legend : bool, optional
282
+ Whether to display a legend for the categories.
283
+ labels : dict[Any, str] | None, optional
284
+ A mapping from category values to labels for the legend.
285
+ legend_kwargs : dict[str, Any] | None, optional
286
+ Additional keyword arguments to pass to the legend.
287
+
288
+ Returns
289
+ -------
290
+ Axes
291
+ The Axes with the drawn colored tree.
292
+ """
293
+ ax, colors = _init_colored_tree_categorical(
294
+ tree=tree,
295
+ color_by=color_by,
296
+ ax=ax,
297
+ default_color=default_color,
298
+ colormap=colormap,
299
+ show_legend=show_legend,
300
+ labels=labels,
301
+ legend_kwargs=legend_kwargs,
302
+ )
303
+ return draw_tree(tree=tree, ax=ax, colors=colors, backward_time=backward_time)
304
+
305
+
306
+ def draw_colored_dated_tree_categorical(
307
+ tree: Tree,
308
+ calibration_nodes: tuple[CalibrationNode, CalibrationNode],
309
+ color_by: str,
310
+ ax: Axes | None = None,
311
+ default_color: Color = "black",
312
+ colormap: str | Colormap = "tab20",
313
+ show_legend: bool = True,
314
+ labels: dict[Any, str] | None = None,
315
+ legend_kwargs: dict[str, Any] | None = None,
316
+ ) -> Axes:
317
+ """
318
+ Draw a dated phylogenetic tree with branches colored based on categorical metadata.
319
+
320
+ Parameters
321
+ ----------
322
+ tree : Tree
323
+ The phylogenetic tree to draw.
324
+ calibration_nodes : tuple[CalibrationNode, CalibrationNode]
325
+ Two calibration nodes defining the mapping from depth to date.
326
+ color_by : str
327
+ The metadata key to color branches by.
328
+ ax : Axes | None, optional
329
+ The matplotlib Axes to draw on. If None, uses the current Axes.
330
+ default_color : Color, optional
331
+ The color to use for nodes without the specified metadata.
332
+ colormap : str | Colormap, optional
333
+ The colormap to use for coloring categories. Defaults to 'tab20'.
334
+ show_legend : bool, optional
335
+ Whether to display a legend for the categories.
336
+ labels : dict[Any, str] | None, optional
337
+ A mapping from category values to labels for the legend.
338
+ legend_kwargs : dict[str, Any] | None, optional
339
+ Additional keyword arguments to pass to the legend.
340
+
341
+ Returns
342
+ -------
343
+ Axes
344
+ The Axes with the drawn colored dated tree.
345
+ """
346
+ ax, colors = _init_colored_tree_categorical(
347
+ tree=tree,
348
+ color_by=color_by,
349
+ ax=ax,
350
+ default_color=default_color,
351
+ colormap=colormap,
352
+ show_legend=show_legend,
353
+ labels=labels,
354
+ legend_kwargs=legend_kwargs,
355
+ )
356
+ return draw_dated_tree(
357
+ tree=tree, calibration_nodes=calibration_nodes, ax=ax, colors=colors
358
+ )
359
+
360
+
361
+ @overload
362
+ def _init_colored_tree_continuous(
363
+ tree: Tree,
364
+ color_by: str,
365
+ ax: Axes | None = ...,
366
+ default_color: Color = ...,
367
+ colormap: str | Colormap = ...,
368
+ vmin: float | None = ...,
369
+ vmax: float | None = ...,
370
+ *,
371
+ show_hist: Literal[False],
372
+ hist_kwargs: dict[str, Any] | None = ...,
373
+ hist_axes_kwargs: dict[str, Any] | None = ...,
374
+ ) -> tuple[Axes, dict[Tree, Color]]: ...
375
+ @overload
376
+ def _init_colored_tree_continuous(
377
+ tree: Tree,
378
+ color_by: str,
379
+ ax: Axes | None = ...,
380
+ default_color: Color = ...,
381
+ colormap: str | Colormap = ...,
382
+ vmin: float | None = ...,
383
+ vmax: float | None = ...,
384
+ *,
385
+ show_hist: Literal[True],
386
+ hist_kwargs: dict[str, Any] | None = ...,
387
+ hist_axes_kwargs: dict[str, Any] | None = ...,
388
+ ) -> tuple[Axes, dict[Tree, Color], Axes]: ...
389
+ def _init_colored_tree_continuous(
390
+ tree: Tree,
391
+ color_by: str,
392
+ ax: Axes | None = None,
393
+ default_color: Color = "black",
394
+ colormap: str | Colormap = "viridis",
395
+ vmin: float | None = None,
396
+ vmax: float | None = None,
397
+ *,
398
+ show_hist: bool = True,
399
+ hist_kwargs: dict[str, Any] | None = None,
400
+ hist_axes_kwargs: dict[str, Any] | None = None,
401
+ ) -> tuple[Axes, dict[Tree, Color]] | tuple[Axes, dict[Tree, Color], Axes]:
402
+ """
403
+ Initialize colors for drawing a tree based on continuous metadata.
404
+
405
+ Parameters
406
+ ----------
407
+ tree : Tree
408
+ The phylogenetic tree.
409
+ color_by : str
410
+ The metadata key to color branches by.
411
+ ax : Axes | None, optional
412
+ The matplotlib Axes to draw on. If None, uses the current Axes.
413
+ default_color : Color, optional
414
+ The color to use for nodes without the specified metadata.
415
+ colormap : str | Colormap, optional
416
+ The colormap to use for coloring continuous values. Defaults to 'viridis'.
417
+ vmin : float | None, optional
418
+ The minimum value for normalization. If None, uses the minimum of the data.
419
+ vmax : float | None, optional
420
+ The maximum value for normalization. If None, uses the maximum of the data.
421
+ show_hist : bool, optional
422
+ Whether to display a histogram of the continuous values.
423
+ hist_kwargs : dict[str, Any] | None, optional
424
+ Additional keyword arguments to pass to the histogram.
425
+ hist_axes_kwargs : dict[str, Any] | None, optional
426
+ Additional keyword arguments to define the histogram Axes.
427
+
428
+ Returns
429
+ -------
430
+ tuple[Axes, dict[Tree, Color]] | tuple[Axes, dict[Tree, Color], Axes]
431
+ The Axes, a dictionary mapping each node to its assigned color,
432
+ and optionally the histogram Axes if `show_hist` is True.
433
+ """
434
+ if ax is None:
435
+ ax = plt.gca()
436
+
437
+ if isinstance(colormap, str):
438
+ colormap = plt.get_cmap(colormap)
439
+
440
+ features = {node: node[color_by] for node in tree if color_by in node.metadata}
441
+ values = list(features.values())
442
+ vmin = min(values) if vmin is None else vmin
443
+ vmax = max(values) if vmax is None else vmax
444
+ norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
445
+ colors = {
446
+ node: colormap(norm(float(features[node])))
447
+ if node in features
448
+ else default_color
449
+ for node in tree
450
+ }
451
+
452
+ if show_hist:
453
+ default_hist_axes_kwargs = {"width": "25%", "height": "25%"}
454
+ if hist_axes_kwargs is not None:
455
+ default_hist_axes_kwargs.update(hist_axes_kwargs)
456
+ hist_ax = inset_axes(ax, **default_hist_axes_kwargs) # pyright: ignore
457
+
458
+ hist_kwargs = {} if hist_kwargs is None else hist_kwargs
459
+ _, bins, patches = hist_ax.hist(values, **hist_kwargs) # pyright: ignore
460
+
461
+ for patch, b0, b1 in zip(patches, bins[:-1], bins[1:]): # pyright: ignore
462
+ midpoint = (b0 + b1) / 2 # pyright: ignore
463
+ patch.set_facecolor(colormap(norm(midpoint))) # pyright: ignore
464
+ return ax, colors, hist_ax # pyright: ignore
465
+
466
+ sm = plt.cm.ScalarMappable(cmap=colormap, norm=norm)
467
+ ax.get_figure().colorbar(sm, ax=ax) # pyright: ignore
468
+ return ax, colors
469
+
470
+
471
+ @overload
472
+ def draw_colored_tree_continuous(
473
+ tree: Tree,
474
+ color_by: str,
475
+ ax: Axes | None = None,
476
+ backward_time: bool = False,
477
+ default_color: Color = "black",
478
+ colormap: str | Colormap = "viridis",
479
+ vmin: float | None = None,
480
+ vmax: float | None = None,
481
+ *,
482
+ show_hist: Literal[False],
483
+ hist_kwargs: dict[str, Any] | None = None,
484
+ hist_axes_kwargs: dict[str, Any] | None = None,
485
+ ) -> Axes: ...
486
+ @overload
487
+ def draw_colored_tree_continuous(
488
+ tree: Tree,
489
+ color_by: str,
490
+ ax: Axes | None = None,
491
+ backward_time: bool = False,
492
+ default_color: Color = "black",
493
+ colormap: str | Colormap = "viridis",
494
+ vmin: float | None = None,
495
+ vmax: float | None = None,
496
+ *,
497
+ show_hist: Literal[True],
498
+ hist_kwargs: dict[str, Any] | None = None,
499
+ hist_axes_kwargs: dict[str, Any] | None = None,
500
+ ) -> tuple[Axes, Axes]: ...
501
+ def draw_colored_tree_continuous(
502
+ tree: Tree,
503
+ color_by: str,
504
+ ax: Axes | None = None,
505
+ backward_time: bool = False,
506
+ default_color: Color = "black",
507
+ colormap: str | Colormap = "viridis",
508
+ vmin: float | None = None,
509
+ vmax: float | None = None,
510
+ show_hist: bool = True,
511
+ hist_kwargs: dict[str, Any] | None = None,
512
+ hist_axes_kwargs: dict[str, Any] | None = None,
513
+ ) -> Axes | tuple[Axes, Axes]:
514
+ """
515
+ Draw a phylogenetic tree with branches colored based on continuous metadata.
516
+
517
+ Parameters
518
+ ----------
519
+ tree : Tree
520
+ The phylogenetic tree to draw.
521
+ color_by : str
522
+ The metadata key to color branches by.
523
+ ax : Axes | None, optional
524
+ The matplotlib Axes to draw on. If None, uses the current Axes.
525
+ backward_time : bool, optional
526
+ If True, the x-axis is inverted to represent time going backward.
527
+ default_color : Color, optional
528
+ The color to use for nodes without the specified metadata.
529
+ colormap : str | Colormap, optional
530
+ The colormap to use for coloring continuous values. Defaults to 'viridis'.
531
+ vmin : float | None, optional
532
+ The minimum value for normalization. If None, uses the minimum of the data.
533
+ vmax : float | None, optional
534
+ The maximum value for normalization. If None, uses the maximum of the data.
535
+ show_hist : bool, optional
536
+ Whether to display a histogram of the continuous values.
537
+ hist_kwargs : dict[str, Any] | None, optional
538
+ Additional keyword arguments to pass to the histogram.
539
+ hist_axes_kwargs : dict[str, Any] | None, optional
540
+ Additional keyword arguments to define the histogram Axes.
541
+
542
+ Returns
543
+ -------
544
+ Axes | tuple[Axes, Axes]
545
+ The Axes with the drawn colored tree,
546
+ and optionally the histogram Axes if `show_hist` is True.
547
+ """
548
+ if show_hist:
549
+ ax, colors, hist_ax = _init_colored_tree_continuous(
550
+ tree=tree,
551
+ color_by=color_by,
552
+ ax=ax,
553
+ default_color=default_color,
554
+ colormap=colormap,
555
+ vmin=vmin,
556
+ vmax=vmax,
557
+ show_hist=show_hist,
558
+ hist_kwargs=hist_kwargs,
559
+ hist_axes_kwargs=hist_axes_kwargs,
560
+ )
561
+ return draw_tree(
562
+ tree=tree, ax=ax, colors=colors, backward_time=backward_time
563
+ ), hist_ax
564
+
565
+ ax, colors = _init_colored_tree_continuous(
566
+ tree=tree,
567
+ color_by=color_by,
568
+ ax=ax,
569
+ default_color=default_color,
570
+ colormap=colormap,
571
+ vmin=vmin,
572
+ vmax=vmax,
573
+ show_hist=show_hist,
574
+ hist_kwargs=hist_kwargs,
575
+ hist_axes_kwargs=hist_axes_kwargs,
576
+ )
577
+ return draw_tree(tree=tree, ax=ax, colors=colors, backward_time=backward_time)
578
+
579
+
580
+ @overload
581
+ def draw_colored_dated_tree_continuous(
582
+ tree: Tree,
583
+ calibration_nodes: tuple[CalibrationNode, CalibrationNode],
584
+ color_by: str,
585
+ ax: Axes | None = None,
586
+ default_color: Color = "black",
587
+ colormap: str | Colormap = "viridis",
588
+ vmin: float | None = None,
589
+ vmax: float | None = None,
590
+ *,
591
+ show_hist: Literal[False],
592
+ hist_kwargs: dict[str, Any] | None = None,
593
+ hist_axes_kwargs: dict[str, Any] | None = None,
594
+ ) -> Axes: ...
595
+ @overload
596
+ def draw_colored_dated_tree_continuous(
597
+ tree: Tree,
598
+ calibration_nodes: tuple[CalibrationNode, CalibrationNode],
599
+ color_by: str,
600
+ ax: Axes | None = None,
601
+ default_color: Color = "black",
602
+ colormap: str | Colormap = "viridis",
603
+ vmin: float | None = None,
604
+ vmax: float | None = None,
605
+ *,
606
+ show_hist: Literal[True],
607
+ hist_kwargs: dict[str, Any] | None = None,
608
+ hist_axes_kwargs: dict[str, Any] | None = None,
609
+ ) -> tuple[Axes, Axes]: ...
610
+ def draw_colored_dated_tree_continuous(
611
+ tree: Tree,
612
+ calibration_nodes: tuple[CalibrationNode, CalibrationNode],
613
+ color_by: str,
614
+ ax: Axes | None = None,
615
+ default_color: Color = "black",
616
+ colormap: str | Colormap = "viridis",
617
+ vmin: float | None = None,
618
+ vmax: float | None = None,
619
+ show_hist: bool = True,
620
+ hist_kwargs: dict[str, Any] | None = None,
621
+ hist_axes_kwargs: dict[str, Any] | None = None,
622
+ ) -> Axes | tuple[Axes, Axes]:
623
+ """
624
+ Draw a dated phylogenetic tree with branches colored based on continuous metadata.
625
+
626
+ Parameters
627
+ ----------
628
+ tree : Tree
629
+ The phylogenetic tree to draw.
630
+ calibration_nodes : tuple[CalibrationNode, CalibrationNode]
631
+ Two calibration nodes defining the mapping from depth to date.
632
+ color_by : str
633
+ The metadata key to color branches by.
634
+ ax : Axes | None, optional
635
+ The matplotlib Axes to draw on. If None, uses the current Axes.
636
+ default_color : Color, optional
637
+ The color to use for nodes without the specified metadata.
638
+ colormap : str | Colormap, optional
639
+ The colormap to use for coloring continuous values. Defaults to 'viridis'.
640
+ vmin : float | None, optional
641
+ The minimum value for normalization. If None, uses the minimum of the data.
642
+ vmax : float | None, optional
643
+ The maximum value for normalization. If None, uses the maximum of the data.
644
+ show_hist : bool, optional
645
+ Whether to display a histogram of the continuous values.
646
+ hist_kwargs : dict[str, Any] | None, optional
647
+ Additional keyword arguments to pass to the histogram.
648
+ hist_axes_kwargs : dict[str, Any] | None, optional
649
+ Additional keyword arguments to define the histogram Axes.
650
+
651
+ Returns
652
+ -------
653
+ Axes | tuple[Axes, Axes]
654
+ The Axes with the drawn colored dated tree,
655
+ and optionally the histogram Axes if `show_hist` is True.
656
+ """
657
+ if show_hist:
658
+ ax, colors, hist_ax = _init_colored_tree_continuous(
659
+ tree=tree,
660
+ color_by=color_by,
661
+ ax=ax,
662
+ default_color=default_color,
663
+ colormap=colormap,
664
+ vmin=vmin,
665
+ vmax=vmax,
666
+ show_hist=show_hist,
667
+ hist_kwargs=hist_kwargs,
668
+ hist_axes_kwargs=hist_axes_kwargs,
669
+ )
670
+ return draw_dated_tree(
671
+ tree=tree,
672
+ calibration_nodes=calibration_nodes,
673
+ ax=ax,
674
+ colors=colors,
675
+ ), hist_ax
676
+
677
+ ax, colors = _init_colored_tree_continuous(
678
+ tree=tree,
679
+ color_by=color_by,
680
+ ax=ax,
681
+ default_color=default_color,
682
+ colormap=colormap,
683
+ vmin=vmin,
684
+ vmax=vmax,
685
+ show_hist=show_hist,
686
+ hist_kwargs=hist_kwargs,
687
+ hist_axes_kwargs=hist_axes_kwargs,
688
+ )
689
+ return draw_dated_tree(
690
+ tree=tree,
691
+ calibration_nodes=calibration_nodes,
692
+ ax=ax,
693
+ colors=colors,
694
+ )
@@ -0,0 +1,3 @@
1
+ from phylogenie.io.fasta import dump_fasta, load_fasta
2
+
3
+ __all__ = ["load_fasta", "dump_fasta"]
@@ -24,3 +24,11 @@ def load_fasta(
24
24
  chars = next(f).strip()
25
25
  sequences.append(Sequence(id, chars, time))
26
26
  return MSA(sequences)
27
+
28
+
29
+ def dump_fasta(msa: MSA | list[Sequence], fasta_file: str | Path) -> None:
30
+ with open(fasta_file, "w") as f:
31
+ sequences = msa.sequences if isinstance(msa, MSA) else msa
32
+ for seq in sequences:
33
+ f.write(f">{seq.id}\n")
34
+ f.write(f"{seq.chars}\n")
@@ -1,39 +1,31 @@
1
+ from collections.abc import Mapping
1
2
  from types import MappingProxyType
2
- from typing import Any, Mapping, Optional
3
+ from typing import Any
3
4
 
4
5
 
5
6
  class MetadataMixin:
6
- """A mixin that provides metadata management with dictionary-like access."""
7
-
8
7
  def __init__(self) -> None:
9
8
  self._metadata: dict[str, Any] = {}
10
9
 
11
10
  @property
12
11
  def metadata(self) -> Mapping[str, Any]:
13
- """Return a read-only view of all metadata."""
14
12
  return MappingProxyType(self._metadata)
15
13
 
16
14
  def set(self, key: str, value: Any) -> None:
17
- """Set or update a metadata value."""
18
15
  self._metadata[key] = value
19
16
 
20
17
  def update(self, metadata: Mapping[str, Any]) -> None:
21
- """Bulk update metadata values."""
22
18
  self._metadata.update(metadata)
23
19
 
24
- def get(self, key: str, default: Optional[Any] = None) -> Any:
25
- """Get a metadata value, returning `default` if not found."""
20
+ def get(self, key: str, default: Any = None) -> Any:
26
21
  return self._metadata.get(key, default)
27
22
 
28
23
  def delete(self, key: str) -> None:
29
- """Delete a metadata if it exists, else do nothing."""
30
24
  self._metadata.pop(key, None)
31
25
 
32
26
  def clear(self) -> None:
33
- """Remove all metadata."""
34
27
  self._metadata.clear()
35
28
 
36
- # Dict-like behavior
37
29
  def __getitem__(self, key: str) -> Any:
38
30
  return self._metadata[key]
39
31
 
@@ -27,11 +27,14 @@ from phylogenie.treesimulator.utils import (
27
27
  compute_sackin_index,
28
28
  get_distance,
29
29
  get_mrca,
30
+ get_node_ages,
30
31
  get_node_depth_levels,
31
32
  get_node_depths,
32
33
  get_node_height_levels,
33
34
  get_node_heights,
34
35
  get_node_leaf_counts,
36
+ get_node_times,
37
+ get_path,
35
38
  )
36
39
 
37
40
  __all__ = [
@@ -65,9 +68,12 @@ __all__ = [
65
68
  "compute_sackin_index",
66
69
  "get_distance",
67
70
  "get_mrca",
71
+ "get_node_ages",
68
72
  "get_node_depth_levels",
69
73
  "get_node_depths",
70
74
  "get_node_height_levels",
71
75
  "get_node_heights",
72
76
  "get_node_leaf_counts",
77
+ "get_node_times",
78
+ "get_path",
73
79
  ]
@@ -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
 
@@ -15,7 +15,7 @@ def _parse_translate_block(lines: Iterator[str]) -> dict[str, str]:
15
15
  if ";" in line:
16
16
  return translations
17
17
  else:
18
- raise ValueError(f"Invalid translate line. Expected '<num> <name>'.")
18
+ raise ValueError("Invalid translate line. Expected '<num> <name>'.")
19
19
  translations[match.group(1)] = match.group(2)
20
20
  raise ValueError("Translate block not terminated with ';'.")
21
21
 
@@ -33,7 +33,7 @@ def _parse_trees_block(lines: Iterator[str]) -> dict[str, Tree]:
33
33
  match = re.match(r"^TREE\s*\*?\s+(\S+)\s*=\s*(.+)$", line, re.IGNORECASE)
34
34
  if match is None:
35
35
  raise ValueError(
36
- f"Invalid tree line. Expected 'TREE <name> = <newick>'."
36
+ "Invalid tree line. Expected 'TREE <name> = <newick>'."
37
37
  )
38
38
  name = match.group(1)
39
39
  if name in trees:
@@ -153,14 +153,27 @@ 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
  # -------------
159
169
  # Other useful miscellaneous methods.
160
170
 
161
171
  def ladderize(self, key: Callable[["Tree"], Any] | None = None) -> None:
172
+ def _default_key(node: Tree) -> int:
173
+ return node.n_leaves
174
+
162
175
  if key is None:
163
- key = lambda node: node.n_leaves
176
+ key = _default_key
164
177
  self._children.sort(key=key)
165
178
  for child in self.children:
166
179
  child.ladderize(key)
@@ -169,7 +182,7 @@ class Tree(MetadataMixin):
169
182
  for node in self:
170
183
  if node.name == name:
171
184
  return node
172
- raise ValueError(f"Node with name {name} not found.")
185
+ raise ValueError(f"Node {name} not found.")
173
186
 
174
187
  def copy(self):
175
188
  new_tree = Tree(self.name, self.branch_length)
@@ -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.5
3
+ Version: 3.1.8
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,152 +0,0 @@
1
- from enum import Enum
2
- from typing import Any, Callable
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 matplotlib.colors import Colormap
9
- from mpl_toolkits.axes_grid1.inset_locator import inset_axes # pyright: ignore
10
-
11
- from phylogenie.treesimulator import Tree, 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(
23
- tree: Tree, ax: Axes | None = None, colors: Color | dict[Tree, Color] = "black"
24
- ) -> Axes:
25
- if ax is None:
26
- ax = plt.gca()
27
-
28
- if not isinstance(colors, dict):
29
- colors = {node: colors for node in tree}
30
-
31
- xs = (
32
- get_node_depth_levels(tree)
33
- if any(node.branch_length is None for node in tree.iter_descendants())
34
- else get_node_depths(tree)
35
- )
36
- ys: dict[Tree, float] = {node: i for i, node in enumerate(tree.get_leaves())}
37
- for node in tree.postorder_traversal():
38
- if node.is_internal():
39
- ys[node] = sum(ys[child] for child in node.children) / len(node.children)
40
-
41
- if tree.branch_length is not None:
42
- ax.hlines(y=ys[tree], xmin=0, xmax=xs[tree], color=colors[tree]) # pyright: ignore
43
- for node in tree:
44
- x1, y1 = xs[node], ys[node]
45
- for child in node.children:
46
- x2, y2 = xs[child], ys[child]
47
- ax.hlines(y=y2, xmin=x1, xmax=x2, color=colors[child]) # pyright: ignore
48
- ax.vlines(x=x1, ymin=y1, ymax=y2, color=colors[child]) # pyright: ignore
49
-
50
- ax.set_yticks([]) # pyright: ignore
51
- return ax
52
-
53
-
54
- def draw_tree(
55
- tree: Tree,
56
- ax: Axes | None = None,
57
- color_by: str | dict[str, Any] | None = None,
58
- coloring: str | Coloring | None = None,
59
- default_color: Color = "black",
60
- colormap: str | Colormap | None = None,
61
- vmin: float | None = None,
62
- vmax: float | None = None,
63
- show_legend: bool = True,
64
- labels: dict[Any, Any] | None = None,
65
- legend_kwargs: dict[str, Any] | None = None,
66
- show_hist: bool = True,
67
- hist_kwargs: dict[str, Any] | None = None,
68
- hist_axes_kwargs: dict[str, Any] | None = None,
69
- ) -> Axes | tuple[Axes, Axes]:
70
- if ax is None:
71
- ax = plt.gca()
72
-
73
- if color_by is None:
74
- return draw_colored_tree(tree, ax, colors=default_color)
75
-
76
- if isinstance(color_by, str):
77
- features = {node: node[color_by] for node in tree if color_by in node.metadata}
78
- else:
79
- features = {node: color_by[node.name] for node in tree if node.name in color_by}
80
- values = list(features.values())
81
-
82
- if coloring is None:
83
- coloring = (
84
- Coloring.CONTINUOUS
85
- if any(isinstance(f, float) for f in values)
86
- else Coloring.DISCRETE
87
- )
88
- if colormap is None:
89
- colormap = "tab20" if coloring == Coloring.DISCRETE else "viridis"
90
- if isinstance(colormap, str):
91
- colormap = plt.get_cmap(colormap)
92
-
93
- def _get_colors(feature_map: Callable[[Any], Color]) -> dict[Tree, Color]:
94
- return {
95
- node: feature_map(features[node]) if node in features else default_color
96
- for node in tree
97
- }
98
-
99
- if coloring == Coloring.DISCRETE:
100
- if any(isinstance(f, float) for f in values):
101
- raise ValueError(
102
- "Discrete coloring selected but feature values are not all categorical."
103
- )
104
- feature_colors = {
105
- f: mcolors.to_hex(colormap(i)) for i, f in enumerate(set(values))
106
- }
107
- colors = _get_colors(lambda f: feature_colors[f])
108
-
109
- if show_legend:
110
- legend_handles = [
111
- mpatches.Patch(
112
- color=feature_colors[f],
113
- label=str(f) if labels is None else labels[f],
114
- )
115
- for f in feature_colors
116
- ]
117
- if any(color_by not in node.metadata for node in tree):
118
- legend_handles.append(mpatches.Patch(color=default_color, label="NA"))
119
- if legend_kwargs is None:
120
- legend_kwargs = {}
121
- ax.legend(handles=legend_handles, **legend_kwargs) # pyright: ignore
122
-
123
- return draw_colored_tree(tree, ax, colors)
124
-
125
- if coloring == Coloring.CONTINUOUS:
126
- vmin = min(values) if vmin is None else vmin
127
- vmax = max(values) if vmax is None else vmax
128
- norm = mcolors.Normalize(vmin=vmin, vmax=vmax)
129
- colors = _get_colors(lambda f: colormap(norm(float(f))))
130
-
131
- if show_hist:
132
- default_hist_axes_kwargs = {"width": "25%", "height": "25%"}
133
- if hist_axes_kwargs is not None:
134
- default_hist_axes_kwargs.update(hist_axes_kwargs)
135
- hist_ax = inset_axes(ax, **default_hist_axes_kwargs) # pyright: ignore
136
-
137
- hist_kwargs = {} if hist_kwargs is None else hist_kwargs
138
- _, bins, patches = hist_ax.hist(values, **hist_kwargs) # pyright: ignore
139
-
140
- for patch, b0, b1 in zip(patches, bins[:-1], bins[1:]): # pyright: ignore
141
- midpoint = (b0 + b1) / 2 # pyright: ignore
142
- patch.set_facecolor(colormap(norm(midpoint))) # pyright: ignore
143
- return draw_colored_tree(tree, ax, colors), hist_ax # pyright: ignore
144
-
145
- else:
146
- sm = plt.cm.ScalarMappable(cmap=colormap, norm=norm)
147
- ax.get_figure().colorbar(sm, ax=ax) # pyright: ignore
148
- return draw_colored_tree(tree, ax, colors)
149
-
150
- raise ValueError(
151
- f"Unknown coloring method: {coloring}. Choices are {list(Coloring)}."
152
- )
@@ -1,3 +0,0 @@
1
- from phylogenie.io.fasta import load_fasta
2
-
3
- __all__ = ["load_fasta"]
File without changes
File without changes
File without changes