iplotx 0.1.0__py3-none-any.whl → 0.2.1__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.
@@ -0,0 +1,96 @@
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ )
5
+ from collections.abc import Hashable
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from ....typing import (
10
+ GraphType,
11
+ LayoutType,
12
+ )
13
+ from ...heuristics import (
14
+ normalise_layout,
15
+ detect_directedness,
16
+ )
17
+ from ...typing import (
18
+ NetworkDataProvider,
19
+ NetworkData,
20
+ )
21
+ from ....utils.internal import (
22
+ _make_layout_columns,
23
+ )
24
+
25
+
26
+ class IGraphDataProvider(NetworkDataProvider):
27
+ def __call__(
28
+ self,
29
+ network: GraphType,
30
+ layout: Optional[LayoutType] = None,
31
+ vertex_labels: Optional[Sequence[str] | dict[Hashable, str] | pd.Series] = None,
32
+ edge_labels: Optional[Sequence[str] | dict[str]] = None,
33
+ ) -> NetworkData:
34
+ """Create network data object for iplotx from any provider."""
35
+
36
+ directed = detect_directedness(network)
37
+
38
+ # Recast vertex_labels=False as vertex_labels=None
39
+ if np.isscalar(vertex_labels) and (not vertex_labels):
40
+ vertex_labels = None
41
+
42
+ # Vertices are ordered integers, no gaps
43
+ vertex_df = normalise_layout(layout, network=network)
44
+ ndim = vertex_df.shape[1]
45
+ vertex_df.columns = _make_layout_columns(ndim)
46
+
47
+ # Vertex labels
48
+ if vertex_labels is not None:
49
+ if np.isscalar(vertex_labels):
50
+ vertex_df["label"] = vertex_df.index.astype(str)
51
+ elif len(vertex_labels) != len(vertex_df):
52
+ raise ValueError(
53
+ "Vertex labels must be the same length as the number of vertices."
54
+ )
55
+ else:
56
+ vertex_df["label"] = vertex_labels
57
+
58
+ # Edges are a list of tuples, because of multiedges
59
+ tmp = []
60
+ for edge in network.es:
61
+ row = {"_ipx_source": edge.source, "_ipx_target": edge.target}
62
+ row.update(edge.attributes())
63
+ tmp.append(row)
64
+ if len(tmp):
65
+ edge_df = pd.DataFrame(tmp)
66
+ else:
67
+ edge_df = pd.DataFrame(columns=["_ipx_source", "_ipx_target"])
68
+ del tmp
69
+
70
+ # Edge labels
71
+ if edge_labels is not None:
72
+ if len(edge_labels) != len(edge_df):
73
+ raise ValueError(
74
+ "Edge labels must be the same length as the number of edges."
75
+ )
76
+ edge_df["label"] = edge_labels
77
+
78
+ network_data = {
79
+ "vertex_df": vertex_df,
80
+ "edge_df": edge_df,
81
+ "directed": directed,
82
+ "ndim": ndim,
83
+ }
84
+ return network_data
85
+
86
+ def check_dependencies(self) -> bool:
87
+ try:
88
+ import igraph
89
+ except ImportError:
90
+ return False
91
+ return True
92
+
93
+ def graph_type(self):
94
+ import igraph as ig
95
+
96
+ return ig.Graph
@@ -0,0 +1,133 @@
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ )
5
+ from collections.abc import Hashable
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from ....typing import (
10
+ GraphType,
11
+ LayoutType,
12
+ )
13
+ from ...heuristics import (
14
+ normalise_layout,
15
+ detect_directedness,
16
+ )
17
+ from ...typing import (
18
+ NetworkDataProvider,
19
+ NetworkData,
20
+ )
21
+ from ....utils.internal import (
22
+ _make_layout_columns,
23
+ )
24
+
25
+
26
+ class NetworkXDataProvider(NetworkDataProvider):
27
+ def __call__(
28
+ self,
29
+ network: GraphType,
30
+ layout: Optional[LayoutType] = None,
31
+ vertex_labels: Optional[Sequence[str] | dict[Hashable, str] | pd.Series] = None,
32
+ edge_labels: Optional[Sequence[str] | dict[str]] = None,
33
+ ) -> NetworkData:
34
+ """Create network data object for iplotx from any provider."""
35
+
36
+ import networkx as nx
37
+
38
+ directed = detect_directedness(network)
39
+
40
+ # Recast vertex_labels=False as vertex_labels=None
41
+ if np.isscalar(vertex_labels) and (not vertex_labels):
42
+ vertex_labels = None
43
+
44
+ # Vertices are indexed by node ID
45
+ vertex_df = normalise_layout(
46
+ layout,
47
+ network=network,
48
+ ).loc[pd.Index(network.nodes)]
49
+ ndim = vertex_df.shape[1]
50
+ vertex_df.columns = _make_layout_columns(ndim)
51
+
52
+ # Vertex internal properties
53
+ tmp = pd.DataFrame(dict(network.nodes.data())).T
54
+ # Arrays become a single column, which we have already anyway
55
+ if isinstance(layout, str) and (layout in tmp.columns):
56
+ del tmp[layout]
57
+ for col in tmp.columns:
58
+ vertex_df[col] = tmp[col]
59
+ del tmp
60
+
61
+ # Vertex labels
62
+ if vertex_labels is None:
63
+ if "label" in vertex_df:
64
+ del vertex_df["label"]
65
+ else:
66
+ if (
67
+ np.isscalar(vertex_labels)
68
+ and (not vertex_labels)
69
+ and ("label" in vertex_df)
70
+ ):
71
+ del vertex_df["label"]
72
+ elif vertex_labels is True:
73
+ if "label" not in vertex_df:
74
+ vertex_df["label"] = vertex_df.index
75
+ elif (not np.isscalar(vertex_labels)) and (
76
+ len(vertex_labels) != len(vertex_df)
77
+ ):
78
+ raise ValueError(
79
+ "Vertex labels must be the same length as the number of vertices."
80
+ )
81
+ elif isinstance(vertex_labels, nx.classes.reportviews.NodeDataView):
82
+ vertex_df["label"] = pd.Series(dict(vertex_labels))
83
+ else:
84
+ vertex_df["label"] = vertex_labels
85
+
86
+ # Edges are a list of tuples, because of multiedges
87
+ tmp = []
88
+ for u, v, d in network.edges.data():
89
+ row = {"_ipx_source": u, "_ipx_target": v}
90
+ row.update(d)
91
+ tmp.append(row)
92
+ if len(tmp):
93
+ edge_df = pd.DataFrame(tmp)
94
+ else:
95
+ edge_df = pd.DataFrame(columns=["_ipx_source", "_ipx_target"])
96
+ del tmp
97
+
98
+ # Edge labels
99
+ # Even though they could exist in the dataframe, request the to be explicitely mentioned
100
+ if (edge_labels is None) and ("label" in edge_df):
101
+ del edge_df["label"]
102
+ elif edge_labels is not None:
103
+ if np.isscalar(edge_labels):
104
+ if (not edge_labels) and ("label" in edge_df):
105
+ del edge_df["label"]
106
+ if (edge_labels is True) and ("label" not in edge_df):
107
+ edge_df["label"] = [str(i) for i in edge_df.index]
108
+ else:
109
+ if len(edge_labels) != len(edge_df):
110
+ raise ValueError(
111
+ "Edge labels must be the same length as the number of edges."
112
+ )
113
+ edge_df["label"] = edge_labels
114
+
115
+ network_data = {
116
+ "vertex_df": vertex_df,
117
+ "edge_df": edge_df,
118
+ "directed": directed,
119
+ "ndim": ndim,
120
+ }
121
+ return network_data
122
+
123
+ def check_dependencies(self) -> bool:
124
+ try:
125
+ import networkx
126
+ except ImportError:
127
+ return False
128
+ return True
129
+
130
+ def graph_type(self):
131
+ from networkx import Graph
132
+
133
+ return Graph
@@ -0,0 +1,105 @@
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ )
5
+ from collections.abc import Hashable
6
+ from operator import attrgetter
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from ....typing import (
11
+ TreeType,
12
+ LayoutType,
13
+ )
14
+ from ...typing import (
15
+ TreeDataProvider,
16
+ TreeData,
17
+ )
18
+ from ...heuristics import (
19
+ normalise_tree_layout,
20
+ )
21
+
22
+
23
+ class BiopythonDataProvider(TreeDataProvider):
24
+ def __call__(
25
+ self,
26
+ tree: TreeType,
27
+ layout: str | LayoutType,
28
+ orientation: str = "horizontal",
29
+ directed: bool | str = False,
30
+ vertex_labels: Optional[
31
+ Sequence[str] | dict[Hashable, str] | pd.Series | bool
32
+ ] = None,
33
+ edge_labels: Optional[Sequence[str] | dict] = None,
34
+ ) -> TreeData:
35
+ """Create tree data object for iplotx from BioPython.Phylo.Tree classes."""
36
+
37
+ tree_data = {
38
+ "root": tree.root,
39
+ "leaves": tree.get_terminals(),
40
+ "rooted": tree.rooted,
41
+ "directed": directed,
42
+ "ndim": 2,
43
+ "layout_name": layout,
44
+ }
45
+
46
+ # Add vertex_df including layout
47
+ tree_data["vertex_df"] = normalise_tree_layout(
48
+ layout,
49
+ tree=tree,
50
+ orientation=orientation,
51
+ root_fun=attrgetter("root"),
52
+ preorder_fun=lambda tree: tree.find_clades(order="preorder"),
53
+ postorder_fun=lambda tree: tree.find_clades(order="postorder"),
54
+ children_fun=attrgetter("clades"),
55
+ branch_length_fun=attrgetter("branch_length"),
56
+ )
57
+ if layout in ("radial",):
58
+ tree_data["layout_coordinate_system"] = "polar"
59
+ else:
60
+ tree_data["layout_coordinate_system"] = "cartesian"
61
+
62
+ # Add edge_df
63
+ edge_data = {"_ipx_source": [], "_ipx_target": []}
64
+ for node in tree.find_clades(order="preorder"):
65
+ for child in node.clades:
66
+ if directed == "parent":
67
+ edge_data["_ipx_source"].append(child)
68
+ edge_data["_ipx_target"].append(node)
69
+ else:
70
+ edge_data["_ipx_source"].append(node)
71
+ edge_data["_ipx_target"].append(child)
72
+ edge_df = pd.DataFrame(edge_data)
73
+ tree_data["edge_df"] = edge_df
74
+
75
+ # Add vertex labels
76
+ if vertex_labels is None:
77
+ vertex_labels = False
78
+ if np.isscalar(vertex_labels) and vertex_labels:
79
+ tree_data["vertex_df"]["label"] = [
80
+ x.name for x in tree_data["vertices"].index
81
+ ]
82
+ elif not np.isscalar(vertex_labels):
83
+ # If a dict-like object is passed, it can be incomplete (e.g. only the leaves):
84
+ # we fill the rest with empty strings which are not going to show up in the plot.
85
+ if isinstance(vertex_labels, pd.Series):
86
+ vertex_labels = dict(vertex_labels)
87
+ if isinstance(vertex_labels, dict):
88
+ for vertex in tree_data["vertex_df"].index:
89
+ if vertex not in vertex_labels:
90
+ vertex_labels[vertex] = ""
91
+ tree_data["vertex_df"]["label"] = pd.Series(vertex_labels)
92
+
93
+ return tree_data
94
+
95
+ def check_dependencies(self) -> bool:
96
+ try:
97
+ from Bio import Phylo
98
+ except ImportError:
99
+ return False
100
+ return True
101
+
102
+ def tree_type(self):
103
+ from Bio import Phylo
104
+
105
+ return Phylo.BaseTree.Tree
@@ -0,0 +1,112 @@
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ )
5
+ from collections.abc import Hashable
6
+ from operator import attrgetter
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from ....typing import (
11
+ TreeType,
12
+ LayoutType,
13
+ )
14
+ from ...typing import (
15
+ TreeDataProvider,
16
+ TreeData,
17
+ )
18
+ from ...heuristics import (
19
+ normalise_tree_layout,
20
+ )
21
+
22
+
23
+ class Cogent3DataProvider(TreeDataProvider):
24
+ def __call__(
25
+ self,
26
+ tree: TreeType,
27
+ layout: str | LayoutType,
28
+ orientation: str = "horizontal",
29
+ directed: bool | str = False,
30
+ vertex_labels: Optional[
31
+ Sequence[str] | dict[Hashable, str] | pd.Series | bool
32
+ ] = None,
33
+ edge_labels: Optional[Sequence[str] | dict] = None,
34
+ ) -> TreeData:
35
+ """Create tree data object for iplotx from cogent3.core.tree.PhyloNode classes."""
36
+
37
+ root_fun = lambda tree: tree.root()
38
+ preorder_fun = lambda tree: tree.preorder()
39
+ postorder_fun = lambda tree: tree.postorder()
40
+ children_fun = attrgetter("children")
41
+ branch_length_fun = attrgetter("length")
42
+ leaves_fun = lambda tree: tree.tips()
43
+
44
+ tree_data = {
45
+ "root": root_fun(tree),
46
+ "leaves": leaves_fun(tree),
47
+ "rooted": True,
48
+ "directed": directed,
49
+ "ndim": 2,
50
+ "layout_name": layout,
51
+ }
52
+
53
+ # Add vertex_df including layout
54
+ tree_data["vertex_df"] = normalise_tree_layout(
55
+ layout,
56
+ tree=tree,
57
+ orientation=orientation,
58
+ root_fun=root_fun,
59
+ preorder_fun=preorder_fun,
60
+ postorder_fun=postorder_fun,
61
+ children_fun=children_fun,
62
+ branch_length_fun=branch_length_fun,
63
+ )
64
+ if layout in ("radial",):
65
+ tree_data["layout_coordinate_system"] = "polar"
66
+ else:
67
+ tree_data["layout_coordinate_system"] = "cartesian"
68
+
69
+ # Add edge_df
70
+ edge_data = {"_ipx_source": [], "_ipx_target": []}
71
+ for node in preorder_fun(tree):
72
+ for child in node.children:
73
+ if directed == "parent":
74
+ edge_data["_ipx_source"].append(child)
75
+ edge_data["_ipx_target"].append(node)
76
+ else:
77
+ edge_data["_ipx_source"].append(node)
78
+ edge_data["_ipx_target"].append(child)
79
+ edge_df = pd.DataFrame(edge_data)
80
+ tree_data["edge_df"] = edge_df
81
+
82
+ # Add vertex labels
83
+ if vertex_labels is None:
84
+ vertex_labels = False
85
+ if np.isscalar(vertex_labels) and vertex_labels:
86
+ tree_data["vertex_df"]["label"] = [
87
+ x.name for x in tree_data["vertices"].index
88
+ ]
89
+ elif not np.isscalar(vertex_labels):
90
+ # If a dict-like object is passed, it can be incomplete (e.g. only the leaves):
91
+ # we fill the rest with empty strings which are not going to show up in the plot.
92
+ if isinstance(vertex_labels, pd.Series):
93
+ vertex_labels = dict(vertex_labels)
94
+ if isinstance(vertex_labels, dict):
95
+ for vertex in tree_data["vertex_df"].index:
96
+ if vertex not in vertex_labels:
97
+ vertex_labels[vertex] = ""
98
+ tree_data["vertex_df"]["label"] = pd.Series(vertex_labels)
99
+
100
+ return tree_data
101
+
102
+ def check_dependencies(self) -> bool:
103
+ try:
104
+ import cogent3
105
+ except ImportError:
106
+ return False
107
+ return True
108
+
109
+ def tree_type(self):
110
+ from cogent3.core.tree import PhyloNode
111
+
112
+ return PhyloNode
@@ -0,0 +1,112 @@
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ )
5
+ from collections.abc import Hashable
6
+ from operator import attrgetter
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from ....typing import (
11
+ TreeType,
12
+ LayoutType,
13
+ )
14
+ from ...typing import (
15
+ TreeDataProvider,
16
+ TreeData,
17
+ )
18
+ from ...heuristics import (
19
+ normalise_tree_layout,
20
+ )
21
+
22
+
23
+ class Ete4DataProvider(TreeDataProvider):
24
+ def __call__(
25
+ self,
26
+ tree: TreeType,
27
+ layout: str | LayoutType,
28
+ orientation: str = "horizontal",
29
+ directed: bool | str = False,
30
+ vertex_labels: Optional[
31
+ Sequence[str] | dict[Hashable, str] | pd.Series | bool
32
+ ] = None,
33
+ edge_labels: Optional[Sequence[str] | dict] = None,
34
+ ) -> TreeData:
35
+ """Create tree data object for iplotx from ete4.core.tre.Tree classes."""
36
+
37
+ root_fun = attrgetter("root")
38
+ preorder_fun = lambda tree: tree.traverse("preorder")
39
+ postorder_fun = lambda tree: tree.traverse("postorder")
40
+ children_fun = attrgetter("children")
41
+ branch_length_fun = lambda node: node.dist if node.dist is not None else 1.0
42
+ leaves_fun = lambda tree: tree.leaves()
43
+
44
+ tree_data = {
45
+ "root": tree.root,
46
+ "leaves": leaves_fun(tree),
47
+ "rooted": True,
48
+ "directed": directed,
49
+ "ndim": 2,
50
+ "layout_name": layout,
51
+ }
52
+
53
+ # Add vertex_df including layout
54
+ tree_data["vertex_df"] = normalise_tree_layout(
55
+ layout,
56
+ tree=tree,
57
+ orientation=orientation,
58
+ root_fun=root_fun,
59
+ preorder_fun=preorder_fun,
60
+ postorder_fun=postorder_fun,
61
+ children_fun=children_fun,
62
+ branch_length_fun=branch_length_fun,
63
+ )
64
+ if layout in ("radial",):
65
+ tree_data["layout_coordinate_system"] = "polar"
66
+ else:
67
+ tree_data["layout_coordinate_system"] = "cartesian"
68
+
69
+ # Add edge_df
70
+ edge_data = {"_ipx_source": [], "_ipx_target": []}
71
+ for node in preorder_fun(tree):
72
+ for child in children_fun(node):
73
+ if directed == "parent":
74
+ edge_data["_ipx_source"].append(child)
75
+ edge_data["_ipx_target"].append(node)
76
+ else:
77
+ edge_data["_ipx_source"].append(node)
78
+ edge_data["_ipx_target"].append(child)
79
+ edge_df = pd.DataFrame(edge_data)
80
+ tree_data["edge_df"] = edge_df
81
+
82
+ # Add vertex labels
83
+ if vertex_labels is None:
84
+ vertex_labels = False
85
+ if np.isscalar(vertex_labels) and vertex_labels:
86
+ tree_data["vertex_df"]["label"] = [
87
+ x.name for x in tree_data["vertices"].index
88
+ ]
89
+ elif not np.isscalar(vertex_labels):
90
+ # If a dict-like object is passed, it can be incomplete (e.g. only the leaves):
91
+ # we fill the rest with empty strings which are not going to show up in the plot.
92
+ if isinstance(vertex_labels, pd.Series):
93
+ vertex_labels = dict(vertex_labels)
94
+ if isinstance(vertex_labels, dict):
95
+ for vertex in tree_data["vertex_df"].index:
96
+ if vertex not in vertex_labels:
97
+ vertex_labels[vertex] = ""
98
+ tree_data["vertex_df"]["label"] = pd.Series(vertex_labels)
99
+
100
+ return tree_data
101
+
102
+ def check_dependencies(self) -> bool:
103
+ try:
104
+ from ete4 import Tree
105
+ except ImportError:
106
+ return False
107
+ return True
108
+
109
+ def tree_type(self):
110
+ from ete4 import Tree
111
+
112
+ return Tree
@@ -0,0 +1,112 @@
1
+ from typing import (
2
+ Optional,
3
+ Sequence,
4
+ )
5
+ from collections.abc import Hashable
6
+ from operator import attrgetter
7
+ import numpy as np
8
+ import pandas as pd
9
+
10
+ from ....typing import (
11
+ TreeType,
12
+ LayoutType,
13
+ )
14
+ from ...typing import (
15
+ TreeDataProvider,
16
+ TreeData,
17
+ )
18
+ from ...heuristics import (
19
+ normalise_tree_layout,
20
+ )
21
+
22
+
23
+ class SkbioDataProvider(TreeDataProvider):
24
+ def __call__(
25
+ self,
26
+ tree: TreeType,
27
+ layout: str | LayoutType,
28
+ orientation: str = "horizontal",
29
+ directed: bool | str = False,
30
+ vertex_labels: Optional[
31
+ Sequence[str] | dict[Hashable, str] | pd.Series | bool
32
+ ] = None,
33
+ edge_labels: Optional[Sequence[str] | dict] = None,
34
+ ) -> TreeData:
35
+ """Create tree data object for iplotx from skbio.tree.TreeNode classes."""
36
+
37
+ root_fun = lambda tree: tree.root()
38
+ preorder_fun = lambda tree: tree.preorder()
39
+ postorder_fun = lambda tree: tree.postorder()
40
+ children_fun = attrgetter("children")
41
+ branch_length_fun = attrgetter("length")
42
+ leaves_fun = lambda tree: tree.tips()
43
+
44
+ tree_data = {
45
+ "root": root_fun(tree),
46
+ "leaves": leaves_fun(tree),
47
+ "rooted": True,
48
+ "directed": directed,
49
+ "ndim": 2,
50
+ "layout_name": layout,
51
+ }
52
+
53
+ # Add vertex_df including layout
54
+ tree_data["vertex_df"] = normalise_tree_layout(
55
+ layout,
56
+ tree=tree,
57
+ orientation=orientation,
58
+ root_fun=root_fun,
59
+ preorder_fun=preorder_fun,
60
+ postorder_fun=postorder_fun,
61
+ children_fun=children_fun,
62
+ branch_length_fun=branch_length_fun,
63
+ )
64
+ if layout in ("radial",):
65
+ tree_data["layout_coordinate_system"] = "polar"
66
+ else:
67
+ tree_data["layout_coordinate_system"] = "cartesian"
68
+
69
+ # Add edge_df
70
+ edge_data = {"_ipx_source": [], "_ipx_target": []}
71
+ for node in preorder_fun(tree):
72
+ for child in children_fun(node):
73
+ if directed == "parent":
74
+ edge_data["_ipx_source"].append(child)
75
+ edge_data["_ipx_target"].append(node)
76
+ else:
77
+ edge_data["_ipx_source"].append(node)
78
+ edge_data["_ipx_target"].append(child)
79
+ edge_df = pd.DataFrame(edge_data)
80
+ tree_data["edge_df"] = edge_df
81
+
82
+ # Add vertex labels
83
+ if vertex_labels is None:
84
+ vertex_labels = False
85
+ if np.isscalar(vertex_labels) and vertex_labels:
86
+ tree_data["vertex_df"]["label"] = [
87
+ x.name for x in tree_data["vertices"].index
88
+ ]
89
+ elif not np.isscalar(vertex_labels):
90
+ # If a dict-like object is passed, it can be incomplete (e.g. only the leaves):
91
+ # we fill the rest with empty strings which are not going to show up in the plot.
92
+ if isinstance(vertex_labels, pd.Series):
93
+ vertex_labels = dict(vertex_labels)
94
+ if isinstance(vertex_labels, dict):
95
+ for vertex in tree_data["vertex_df"].index:
96
+ if vertex not in vertex_labels:
97
+ vertex_labels[vertex] = ""
98
+ tree_data["vertex_df"]["label"] = pd.Series(vertex_labels)
99
+
100
+ return tree_data
101
+
102
+ def check_dependencies(self) -> bool:
103
+ try:
104
+ from skbio import TreeNode
105
+ except ImportError:
106
+ return False
107
+ return True
108
+
109
+ def tree_type(self):
110
+ from skbio import TreeNode
111
+
112
+ return TreeNode