comparison env/lib/python3.9/site-packages/networkx/relabel.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 import networkx as nx
2
3 __all__ = ["convert_node_labels_to_integers", "relabel_nodes"]
4
5
6 def relabel_nodes(G, mapping, copy=True):
7 """Relabel the nodes of the graph G.
8
9 Parameters
10 ----------
11 G : graph
12 A NetworkX graph
13
14 mapping : dictionary
15 A dictionary with the old labels as keys and new labels as values.
16 A partial mapping is allowed. Mapping 2 nodes to a single node is allowed.
17
18 copy : bool (optional, default=True)
19 If True return a copy, or if False relabel the nodes in place.
20
21 Examples
22 --------
23 To create a new graph with nodes relabeled according to a given
24 dictionary:
25
26 >>> G = nx.path_graph(3)
27 >>> sorted(G)
28 [0, 1, 2]
29 >>> mapping = {0: "a", 1: "b", 2: "c"}
30 >>> H = nx.relabel_nodes(G, mapping)
31 >>> sorted(H)
32 ['a', 'b', 'c']
33
34 Nodes can be relabeled with any hashable object, including numbers
35 and strings:
36
37 >>> import string
38 >>> G = nx.path_graph(26) # nodes are integers 0 through 25
39 >>> sorted(G)[:3]
40 [0, 1, 2]
41 >>> mapping = dict(zip(G, string.ascii_lowercase))
42 >>> G = nx.relabel_nodes(G, mapping) # nodes are characters a through z
43 >>> sorted(G)[:3]
44 ['a', 'b', 'c']
45 >>> mapping = dict(zip(G, range(1, 27)))
46 >>> G = nx.relabel_nodes(G, mapping) # nodes are integers 1 through 26
47 >>> sorted(G)[:3]
48 [1, 2, 3]
49
50 To perform a partial in-place relabeling, provide a dictionary
51 mapping only a subset of the nodes, and set the `copy` keyword
52 argument to False:
53
54 >>> G = nx.path_graph(3) # nodes 0-1-2
55 >>> mapping = {0: "a", 1: "b"} # 0->'a' and 1->'b'
56 >>> G = nx.relabel_nodes(G, mapping, copy=False)
57 >>> sorted(G, key=str)
58 [2, 'a', 'b']
59
60 A mapping can also be given as a function:
61
62 >>> G = nx.path_graph(3)
63 >>> H = nx.relabel_nodes(G, lambda x: x ** 2)
64 >>> list(H)
65 [0, 1, 4]
66
67 In a multigraph, relabeling two or more nodes to the same new node
68 will retain all edges, but may change the edge keys in the process:
69
70 >>> G = nx.MultiGraph()
71 >>> G.add_edge(0, 1, value="a") # returns the key for this edge
72 0
73 >>> G.add_edge(0, 2, value="b")
74 0
75 >>> G.add_edge(0, 3, value="c")
76 0
77 >>> mapping = {1: 4, 2: 4, 3: 4}
78 >>> H = nx.relabel_nodes(G, mapping, copy=True)
79 >>> print(H[0])
80 {4: {0: {'value': 'a'}, 1: {'value': 'b'}, 2: {'value': 'c'}}}
81
82 This works for in-place relabeling too:
83
84 >>> G = nx.relabel_nodes(G, mapping, copy=False)
85 >>> print(G[0])
86 {4: {0: {'value': 'a'}, 1: {'value': 'b'}, 2: {'value': 'c'}}}
87
88 Notes
89 -----
90 Only the nodes specified in the mapping will be relabeled.
91
92 The keyword setting copy=False modifies the graph in place.
93 Relabel_nodes avoids naming collisions by building a
94 directed graph from ``mapping`` which specifies the order of
95 relabelings. Naming collisions, such as a->b, b->c, are ordered
96 such that "b" gets renamed to "c" before "a" gets renamed "b".
97 In cases of circular mappings (e.g. a->b, b->a), modifying the
98 graph is not possible in-place and an exception is raised.
99 In that case, use copy=True.
100
101 If a relabel operation on a multigraph would cause two or more
102 edges to have the same source, target and key, the second edge must
103 be assigned a new key to retain all edges. The new key is set
104 to the lowest non-negative integer not already used as a key
105 for edges between these two nodes. Note that this means non-numeric
106 keys may be replaced by numeric keys.
107
108 See Also
109 --------
110 convert_node_labels_to_integers
111 """
112 # you can pass a function f(old_label)->new_label
113 # but we'll just make a dictionary here regardless
114 if not hasattr(mapping, "__getitem__"):
115 m = {n: mapping(n) for n in G}
116 else:
117 m = mapping
118 if copy:
119 return _relabel_copy(G, m)
120 else:
121 return _relabel_inplace(G, m)
122
123
124 def _relabel_inplace(G, mapping):
125 old_labels = set(mapping.keys())
126 new_labels = set(mapping.values())
127 if len(old_labels & new_labels) > 0:
128 # labels sets overlap
129 # can we topological sort and still do the relabeling?
130 D = nx.DiGraph(list(mapping.items()))
131 D.remove_edges_from(nx.selfloop_edges(D))
132 try:
133 nodes = reversed(list(nx.topological_sort(D)))
134 except nx.NetworkXUnfeasible as e:
135 raise nx.NetworkXUnfeasible(
136 "The node label sets are overlapping and no ordering can "
137 "resolve the mapping. Use copy=True."
138 ) from e
139 else:
140 # non-overlapping label sets
141 nodes = old_labels
142
143 multigraph = G.is_multigraph()
144 directed = G.is_directed()
145
146 for old in nodes:
147 try:
148 new = mapping[old]
149 except KeyError:
150 continue
151 if new == old:
152 continue
153 try:
154 G.add_node(new, **G.nodes[old])
155 except KeyError as e:
156 raise KeyError(f"Node {old} is not in the graph") from e
157 if multigraph:
158 new_edges = [
159 (new, new if old == target else target, key, data)
160 for (_, target, key, data) in G.edges(old, data=True, keys=True)
161 ]
162 if directed:
163 new_edges += [
164 (new if old == source else source, new, key, data)
165 for (source, _, key, data) in G.in_edges(old, data=True, keys=True)
166 ]
167 # Ensure new edges won't overwrite existing ones
168 seen = set()
169 for i, (source, target, key, data) in enumerate(new_edges):
170 if target in G[source] and key in G[source][target]:
171 new_key = 0 if not isinstance(key, (int, float)) else key
172 while new_key in G[source][target] or (target, new_key) in seen:
173 new_key += 1
174 new_edges[i] = (source, target, new_key, data)
175 seen.add((target, new_key))
176 else:
177 new_edges = [
178 (new, new if old == target else target, data)
179 for (_, target, data) in G.edges(old, data=True)
180 ]
181 if directed:
182 new_edges += [
183 (new if old == source else source, new, data)
184 for (source, _, data) in G.in_edges(old, data=True)
185 ]
186 G.remove_node(old)
187 G.add_edges_from(new_edges)
188 return G
189
190
191 def _relabel_copy(G, mapping):
192 H = G.__class__()
193 H.add_nodes_from(mapping.get(n, n) for n in G)
194 H._node.update((mapping.get(n, n), d.copy()) for n, d in G.nodes.items())
195 if G.is_multigraph():
196 new_edges = [
197 (mapping.get(n1, n1), mapping.get(n2, n2), k, d.copy())
198 for (n1, n2, k, d) in G.edges(keys=True, data=True)
199 ]
200
201 # check for conflicting edge-keys
202 undirected = not G.is_directed()
203 seen_edges = set()
204 for i, (source, target, key, data) in enumerate(new_edges):
205 while (source, target, key) in seen_edges:
206 if not isinstance(key, (int, float)):
207 key = 0
208 key += 1
209 seen_edges.add((source, target, key))
210 if undirected:
211 seen_edges.add((target, source, key))
212 new_edges[i] = (source, target, key, data)
213
214 H.add_edges_from(new_edges)
215 else:
216 H.add_edges_from(
217 (mapping.get(n1, n1), mapping.get(n2, n2), d.copy())
218 for (n1, n2, d) in G.edges(data=True)
219 )
220 H.graph.update(G.graph)
221 return H
222
223
224 def convert_node_labels_to_integers(
225 G, first_label=0, ordering="default", label_attribute=None
226 ):
227 """Returns a copy of the graph G with the nodes relabeled using
228 consecutive integers.
229
230 Parameters
231 ----------
232 G : graph
233 A NetworkX graph
234
235 first_label : int, optional (default=0)
236 An integer specifying the starting offset in numbering nodes.
237 The new integer labels are numbered first_label, ..., n-1+first_label.
238
239 ordering : string
240 "default" : inherit node ordering from G.nodes()
241 "sorted" : inherit node ordering from sorted(G.nodes())
242 "increasing degree" : nodes are sorted by increasing degree
243 "decreasing degree" : nodes are sorted by decreasing degree
244
245 label_attribute : string, optional (default=None)
246 Name of node attribute to store old label. If None no attribute
247 is created.
248
249 Notes
250 -----
251 Node and edge attribute data are copied to the new (relabeled) graph.
252
253 There is no guarantee that the relabeling of nodes to integers will
254 give the same two integers for two (even identical graphs).
255 Use the `ordering` argument to try to preserve the order.
256
257 See Also
258 --------
259 relabel_nodes
260 """
261 N = G.number_of_nodes() + first_label
262 if ordering == "default":
263 mapping = dict(zip(G.nodes(), range(first_label, N)))
264 elif ordering == "sorted":
265 nlist = sorted(G.nodes())
266 mapping = dict(zip(nlist, range(first_label, N)))
267 elif ordering == "increasing degree":
268 dv_pairs = [(d, n) for (n, d) in G.degree()]
269 dv_pairs.sort() # in-place sort from lowest to highest degree
270 mapping = dict(zip([n for d, n in dv_pairs], range(first_label, N)))
271 elif ordering == "decreasing degree":
272 dv_pairs = [(d, n) for (n, d) in G.degree()]
273 dv_pairs.sort() # in-place sort from lowest to highest degree
274 dv_pairs.reverse()
275 mapping = dict(zip([n for d, n in dv_pairs], range(first_label, N)))
276 else:
277 raise nx.NetworkXError(f"Unknown node ordering: {ordering}")
278 H = relabel_nodes(G, mapping)
279 # create node attribute with the old label
280 if label_attribute is not None:
281 nx.set_node_attributes(H, {v: k for k, v in mapping.items()}, label_attribute)
282 return H