phylogenie 2.1.21__py3-none-any.whl → 2.1.23__py3-none-any.whl

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.
phylogenie/draw.py CHANGED
@@ -28,7 +28,10 @@ def _draw_colored_tree(tree: Tree, ax: Axes, colors: Color | dict[Tree, Color])
28
28
  if any(node.branch_length is None for node in tree)
29
29
  else get_node_depths(tree)
30
30
  )
31
- ys = {node: i for i, node in enumerate(tree.inorder_traversal())}
31
+ ys: dict[Tree, float] = {node: i for i, node in enumerate(tree.get_leaves())}
32
+ for node in tree.postorder_traversal():
33
+ if node.is_internal():
34
+ ys[node] = sum(ys[child] for child in node.children) / len(node.children)
32
35
 
33
36
  for node in tree:
34
37
  x1, y1 = xs[node], ys[node]
@@ -50,9 +53,10 @@ def draw_tree(
50
53
  coloring: str | Coloring | None = None,
51
54
  default_color: Color = "black",
52
55
  cmap: str | None = None,
53
- show_legend: bool = True,
54
56
  vmin: float | None = None,
55
57
  vmax: float | None = None,
58
+ show_legend: bool = True,
59
+ legend_kwargs: dict[str, Any] | None = None,
56
60
  show_hist: bool = True,
57
61
  hist_kwargs: dict[str, Any] | None = None,
58
62
  hist_axes_kwargs: dict[str, Any] | None = None,
@@ -98,7 +102,9 @@ def draw_tree(
98
102
  ]
99
103
  if any(color_by not in node.features for node in tree):
100
104
  legend_handles.append(mpatches.Patch(color=default_color, label="NA"))
101
- ax.legend(handles=legend_handles) # pyright: ignore
105
+ if legend_kwargs is None:
106
+ legend_kwargs = {}
107
+ ax.legend(handles=legend_handles, **legend_kwargs) # pyright: ignore
102
108
 
103
109
  return _draw_colored_tree(tree, ax, colors)
104
110
 
phylogenie/io.py CHANGED
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from typing import Callable
2
3
 
3
4
  from phylogenie.msa import MSA, Sequence
@@ -12,54 +13,56 @@ def _parse_newick(newick: str) -> Tree:
12
13
  i = 0
13
14
  while i < len(newick):
14
15
 
15
- def _parse_chars(stoppers: list[str]) -> str:
16
+ def _read_chars(stoppers: list[str]) -> str:
16
17
  nonlocal i
17
18
  chars = ""
18
- while newick[i] not in stoppers:
19
+ while i < len(newick) and newick[i] not in stoppers:
19
20
  chars += newick[i]
20
21
  i += 1
22
+ if i == len(newick):
23
+ raise ValueError(f"Expected one of {stoppers}, got end of string")
21
24
  return chars
22
25
 
23
26
  if newick[i] == "(":
24
27
  stack.append(current_nodes)
25
28
  current_nodes = []
26
- else:
27
- id = _parse_chars([":", ",", ")", ";", "["])
28
- branch_length = None
29
- if newick[i] == ":":
30
- i += 1
31
- branch_length = float(_parse_chars([",", ")", ";", "["]))
29
+ i += 1
30
+ continue
31
+
32
+ current_node = Tree(_read_chars(["[", ":", ",", ")", ";"]))
33
+
34
+ if newick[i] == "[":
35
+ i += 1
36
+ if newick[i] != "&":
37
+ raise ValueError("Expected '[&' at the start of node features")
38
+ i += 1
39
+ features = re.split(r",(?=[^,]+=)", _read_chars(["]"]))
40
+ i += 1
41
+ for feature in features:
42
+ key, value = feature.split("=")
43
+ try:
44
+ current_node.set(key, eval(value))
45
+ except Exception:
46
+ current_node.set(key, value)
32
47
 
33
- current_node = Tree(id, branch_length)
34
- for node in current_children:
35
- current_node.add_child(node)
36
- current_children = []
37
- current_nodes.append(current_node)
48
+ if newick[i] == ":":
49
+ i += 1
50
+ current_node.branch_length = float(_read_chars([",", ")", ";"]))
38
51
 
39
- if newick[i] == "[":
40
- i += 1
41
- features = _parse_chars(["]"]).split(":")
42
- i += 1
43
- if features[0] != "&&NHX":
44
- raise ValueError(f"Expected '&&NHX' for node features.")
45
- for feature in features[1:]:
46
- key, value = feature.split("=", 1)
47
- try:
48
- current_node.set(key, eval(value))
49
- except Exception as e:
50
- raise ValueError(
51
- f"Error setting node feature `{key}` to `{value}`: {e}"
52
- )
53
-
54
- if newick[i] == ")":
55
- current_children = current_nodes
56
- current_nodes = stack.pop()
57
- elif newick[i] == ";":
58
- return current_node
52
+ for node in current_children:
53
+ current_node.add_child(node)
54
+ current_children = []
55
+ current_nodes.append(current_node)
56
+
57
+ if newick[i] == ")":
58
+ current_children = current_nodes
59
+ current_nodes = stack.pop()
60
+ elif newick[i] == ";":
61
+ return current_node
59
62
 
60
63
  i += 1
61
64
 
62
- raise ValueError("Newick string is invalid.")
65
+ raise ValueError("Newick string should end with ';'")
63
66
 
64
67
 
65
68
  def load_newick(filepath: str) -> Tree | list[Tree]:
@@ -71,19 +74,23 @@ def load_newick(filepath: str) -> Tree | list[Tree]:
71
74
  def _to_newick(tree: Tree) -> str:
72
75
  children_newick = ",".join([_to_newick(child) for child in tree.children])
73
76
  newick = tree.name
74
- if children_newick:
75
- newick = f"({children_newick}){newick}"
76
- if tree.branch_length is not None:
77
- newick += f":{tree.branch_length}"
78
77
  if tree.features:
79
78
  reprs = {k: repr(v).replace("'", '"') for k, v in tree.features.items()}
80
79
  for k, r in reprs.items():
81
- if ":" in r or "=" in r or "]" in r:
80
+ if "," in k or "=" in k or "]" in k:
81
+ raise ValueError(
82
+ f"Invalid feature key `{k}`: keys must not contain ',', '=', or ']'"
83
+ )
84
+ if "=" in r or "]" in r:
82
85
  raise ValueError(
83
- f"Cannot serialize feature `{k}` with value `{r}`: contains reserved characters."
86
+ f"Invalid value `{r}` for feature `{k}`: values must not contain '=' or ']'"
84
87
  )
85
88
  features = [f"{k}={repr}" for k, repr in reprs.items()]
86
- newick += f"[&&NHX:{':'.join(features)}]"
89
+ newick += f"[&{','.join(features)}]"
90
+ if children_newick:
91
+ newick = f"({children_newick}){newick}"
92
+ if tree.branch_length is not None:
93
+ newick += f":{tree.branch_length}"
87
94
  return newick
88
95
 
89
96
 
phylogenie/tree.py CHANGED
@@ -136,16 +136,19 @@ class Tree:
136
136
  def get_leaves(self) -> tuple["Tree", ...]:
137
137
  return tuple(node for node in self if node.is_leaf())
138
138
 
139
+ def is_internal(self) -> bool:
140
+ return not self.is_leaf()
141
+
139
142
  def get_internal_nodes(self) -> tuple["Tree", ...]:
140
- return tuple(node for node in self if not node.is_leaf())
143
+ return tuple(node for node in self if node.is_internal())
141
144
 
142
145
  def is_binary(self) -> bool:
143
146
  return all(len(node.children) in (0, 2) for node in self)
144
147
 
145
- def ladderize(self, criterion: Callable[["Tree"], Any]) -> None:
146
- self._children.sort(key=criterion)
148
+ def ladderize(self, key: Callable[["Tree"], Any]) -> None:
149
+ self._children.sort(key=key)
147
150
  for child in self.children:
148
- child.ladderize(criterion)
151
+ child.ladderize(key)
149
152
 
150
153
  def copy(self):
151
154
  new_tree = Tree(self.name, self.branch_length)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: phylogenie
3
- Version: 2.1.21
3
+ Version: 2.1.23
4
4
  Summary: Generate phylogenetic datasets with minimal setup effort
5
5
  Author: Gabriele Marino
6
6
  Author-email: gabmarino.8601@gmail.com
@@ -1,5 +1,5 @@
1
1
  phylogenie/__init__.py,sha256=8ulA-U7-WnBLNsqRYqPOPrJG8X4ZzyEU02oaDbR_Hxs,2849
2
- phylogenie/draw.py,sha256=mXCBS5xtJh0zJza8K2QkmRg_YbeCINst0vh6YMv6HQg,4959
2
+ phylogenie/draw.py,sha256=WCjr_HCn-wCuxwkDhHA9Ijl1Sm6av6FBLkyR0MK2bVM,5271
3
3
  phylogenie/generators/__init__.py,sha256=zsOxy28-9j9alOQLIgrOAFfmM58NNHO_NEtW-KXQXAY,888
4
4
  phylogenie/generators/alisim.py,sha256=0aCLuGInifWgAvfh7zARWSKF4EMw3TjlPXMLSECui0k,2783
5
5
  phylogenie/generators/configs.py,sha256=WFoeKpgj9ZQIom7BKqwpgXbriiQGg3jFBMLoD8KButk,1073
@@ -7,7 +7,7 @@ phylogenie/generators/dataset.py,sha256=pPwW9yxm9fkU0PPllFq8EsPlqau8tth-4OatbA_h
7
7
  phylogenie/generators/factories.py,sha256=TuVFQWRjq33Hewjw_Lp8tQ0l_IPtqYDyQCNJhtiHpw8,7882
8
8
  phylogenie/generators/trees.py,sha256=6tHS013RNRyIsObeYq2Kx9it7Yc0TgiMkeAHbskHzAM,10748
9
9
  phylogenie/generators/typeguards.py,sha256=yj4VkhOaUXJ2OrY-6zhOeY9C4yKIQxjZtk2d-vIxttQ,828
10
- phylogenie/io.py,sha256=nwy8DOknt0HqF9qMeFZHrCmSXpM5AGrU5oajwTtD6vY,3973
10
+ phylogenie/io.py,sha256=vUG2yVtoV98tNHut46uSuB3VPj6s64VDhUp1EaSk0o0,4084
11
11
  phylogenie/main.py,sha256=vtvSpQxBNlYABoFQ25czl-l3fIr4QRo3svWVd-jcArw,1170
12
12
  phylogenie/models.py,sha256=pCg9ob0RpLUHwM49x4knKxL4FNPr3-EU_6zMXsvxtAg,370
13
13
  phylogenie/msa.py,sha256=JDGyZUsAq6-m-SQjoCDjAkAZIxfgyl_PDIhdYn5HOow,2064
@@ -16,7 +16,7 @@ phylogenie/skyline/__init__.py,sha256=7pF4CUb4ZCLzNYJNhOjpuTOLTRhlK7L6ugfccNqjIG
16
16
  phylogenie/skyline/matrix.py,sha256=Gl8OgKjtieG0NwPYiPimKI36gefV8fm_OeorjdXxPTs,9146
17
17
  phylogenie/skyline/parameter.py,sha256=EM9qlPt0JhMBy3TbztM0dj24BaGNEy8KWKdTObDKhbI,4644
18
18
  phylogenie/skyline/vector.py,sha256=bJP7_FNX_Klt6wXqsyfj0KX3VNj6-dIhzCKSJuQcOV0,7115
19
- phylogenie/tree.py,sha256=mv8Jrb2jzK5q-vx5E1-mAQLagkP4aHgaL6tNpAOPfIE,5194
19
+ phylogenie/tree.py,sha256=P1uM6s32TsODpvNJQIPMix9oj39vGSw_wsHYp2wmy5U,5246
20
20
  phylogenie/treesimulator/__init__.py,sha256=yqS2vtYMhdWSXc9RAnX1dd4zAqSQweMLyVKTnJLfGTU,1106
21
21
  phylogenie/treesimulator/events/__init__.py,sha256=6zSgZ0MEUMvTK4yPlSolJnRWzCARLS-jYreTzh45mQo,1033
22
22
  phylogenie/treesimulator/events/contact_tracing.py,sha256=_nJ85yhgGkeruQgMHvGpDYoyhheBf8M4LgZWiWdi5dY,4801
@@ -28,8 +28,8 @@ phylogenie/treesimulator/model.py,sha256=Nyg6R8XmMwZMSw1-dII81sU9uU7tDe-NMs8v1qK
28
28
  phylogenie/typeguards.py,sha256=JtqmbEWJZBRHbWgCvcl6nrWm3VcBfzRbklbTBYHItn0,1325
29
29
  phylogenie/typings.py,sha256=GknvAFXyiaWeeYJ8Lk5d6E2VHT-xW6ONEojYbtJYiB8,476
30
30
  phylogenie/utils.py,sha256=ehVk_2kvjW8Q_EyM2kxBPHYiK-KlPmZQx7JeVN6Fh-E,5419
31
- phylogenie-2.1.21.dist-info/LICENSE.txt,sha256=NUrDqElK-eD3I0WqC004CJsy6cs0JgsAoebDv_42-pw,1071
32
- phylogenie-2.1.21.dist-info/METADATA,sha256=De8Vqs1ngZOKgBdivLw1hWrtMDdtFWLRDW3M4-IAAyg,5477
33
- phylogenie-2.1.21.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
34
- phylogenie-2.1.21.dist-info/entry_points.txt,sha256=Rt6_usN0FkBX1ZfiqCirjMN9FKOgFLG8rydcQ8kugeE,51
35
- phylogenie-2.1.21.dist-info/RECORD,,
31
+ phylogenie-2.1.23.dist-info/LICENSE.txt,sha256=NUrDqElK-eD3I0WqC004CJsy6cs0JgsAoebDv_42-pw,1071
32
+ phylogenie-2.1.23.dist-info/METADATA,sha256=ym-NHK9hkvBR4kH_KGCnLnckV_iEzvFk0fdMzrkVudQ,5477
33
+ phylogenie-2.1.23.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
34
+ phylogenie-2.1.23.dist-info/entry_points.txt,sha256=Rt6_usN0FkBX1ZfiqCirjMN9FKOgFLG8rydcQ8kugeE,51
35
+ phylogenie-2.1.23.dist-info/RECORD,,