1"""
2Mixing matrices for node attributes and degree.
3"""
4
5import networkx as nx
6from networkx.algorithms.assortativity.pairs import node_attribute_xy, node_degree_xy
7from networkx.utils import dict_to_numpy_array
8
9__all__ = [
10 "attribute_mixing_matrix",
11 "attribute_mixing_dict",
12 "degree_mixing_matrix",
13 "degree_mixing_dict",
14 "mixing_dict",
15]
16
17
18@nx._dispatchable(node_attrs="attribute")
19def attribute_mixing_dict(G, attribute, nodes=None, normalized=False):
20 """Returns dictionary representation of mixing matrix for attribute.
21
22 Parameters
23 ----------
24 G : graph
25 NetworkX graph object.
26
27 attribute : string
28 Node attribute key.
29
30 nodes: list or iterable (optional)
31 Unse nodes in container to build the dict. The default is all nodes.
32
33 normalized : bool (default=False)
34 Return counts if False or probabilities if True.
35
36 Examples
37 --------
38 >>> G = nx.Graph()
39 >>> G.add_nodes_from([0, 1], color="red")
40 >>> G.add_nodes_from([2, 3], color="blue")
41 >>> G.add_edge(1, 3)
42 >>> d = nx.attribute_mixing_dict(G, "color")
43 >>> print(d["red"]["blue"])
44 1
45 >>> print(d["blue"]["red"]) # d symmetric for undirected graphs
46 1
47
48 Returns
49 -------
50 d : dictionary
51 Counts or joint probability of occurrence of attribute pairs.
52 """
53 xy_iter = node_attribute_xy(G, attribute, nodes)
54 return mixing_dict(xy_iter, normalized=normalized)
55
56
57@nx._dispatchable(node_attrs="attribute")
58def attribute_mixing_matrix(G, attribute, nodes=None, mapping=None, normalized=True):
59 """Returns mixing matrix for attribute.
60
61 Parameters
62 ----------
63 G : graph
64 NetworkX graph object.
65
66 attribute : string
67 Node attribute key.
68
69 nodes: list or iterable (optional)
70 Use only nodes in container to build the matrix. The default is
71 all nodes.
72
73 mapping : dictionary, optional
74 Mapping from node attribute to integer index in matrix.
75 If not specified, an arbitrary ordering will be used.
76
77 normalized : bool (default=True)
78 Return counts if False or probabilities if True.
79
80 Returns
81 -------
82 m: numpy array
83 Counts or joint probability of occurrence of attribute pairs.
84
85 Notes
86 -----
87 If each node has a unique attribute value, the unnormalized mixing matrix
88 will be equal to the adjacency matrix. To get a denser mixing matrix,
89 the rounding can be performed to form groups of nodes with equal values.
90 For example, the exact height of persons in cm (180.79155222, 163.9080892,
91 163.30095355, 167.99016217, 168.21590163, ...) can be rounded to (180, 163,
92 163, 168, 168, ...).
93
94 Definitions of attribute mixing matrix vary on whether the matrix
95 should include rows for attribute values that don't arise. Here we
96 do not include such empty-rows. But you can force them to appear
97 by inputting a `mapping` that includes those values.
98
99 Examples
100 --------
101 >>> G = nx.path_graph(3)
102 >>> gender = {0: "male", 1: "female", 2: "female"}
103 >>> nx.set_node_attributes(G, gender, "gender")
104 >>> mapping = {"male": 0, "female": 1}
105 >>> mix_mat = nx.attribute_mixing_matrix(G, "gender", mapping=mapping)
106 >>> mix_mat
107 array([[0. , 0.25],
108 [0.25, 0.5 ]])
109 """
110 d = attribute_mixing_dict(G, attribute, nodes)
111 a = dict_to_numpy_array(d, mapping=mapping)
112 if normalized:
113 a = a / a.sum()
114 return a
115
116
117@nx._dispatchable(edge_attrs="weight")
118def degree_mixing_dict(G, x="out", y="in", weight=None, nodes=None, normalized=False):
119 """Returns dictionary representation of mixing matrix for degree.
120
121 Parameters
122 ----------
123 G : graph
124 NetworkX graph object.
125
126 x: string ('in','out')
127 The degree type for source node (directed graphs only).
128
129 y: string ('in','out')
130 The degree type for target node (directed graphs only).
131
132 weight: string or None, optional (default=None)
133 The edge attribute that holds the numerical value used
134 as a weight. If None, then each edge has weight 1.
135 The degree is the sum of the edge weights adjacent to the node.
136
137 normalized : bool (default=False)
138 Return counts if False or probabilities if True.
139
140 Returns
141 -------
142 d: dictionary
143 Counts or joint probability of occurrence of degree pairs.
144 """
145 xy_iter = node_degree_xy(G, x=x, y=y, nodes=nodes, weight=weight)
146 return mixing_dict(xy_iter, normalized=normalized)
147
148
149@nx._dispatchable(edge_attrs="weight")
150def degree_mixing_matrix(
151 G, x="out", y="in", weight=None, nodes=None, normalized=True, mapping=None
152):
153 """Returns mixing matrix for attribute.
154
155 Parameters
156 ----------
157 G : graph
158 NetworkX graph object.
159
160 x: string ('in','out')
161 The degree type for source node (directed graphs only).
162
163 y: string ('in','out')
164 The degree type for target node (directed graphs only).
165
166 nodes: list or iterable (optional)
167 Build the matrix using only nodes in container.
168 The default is all nodes.
169
170 weight: string or None, optional (default=None)
171 The edge attribute that holds the numerical value used
172 as a weight. If None, then each edge has weight 1.
173 The degree is the sum of the edge weights adjacent to the node.
174
175 normalized : bool (default=True)
176 Return counts if False or probabilities if True.
177
178 mapping : dictionary, optional
179 Mapping from node degree to integer index in matrix.
180 If not specified, an arbitrary ordering will be used.
181
182 Returns
183 -------
184 m: numpy array
185 Counts, or joint probability, of occurrence of node degree.
186
187 Notes
188 -----
189 Definitions of degree mixing matrix vary on whether the matrix
190 should include rows for degree values that don't arise. Here we
191 do not include such empty-rows. But you can force them to appear
192 by inputting a `mapping` that includes those values. See examples.
193
194 Examples
195 --------
196 >>> G = nx.star_graph(3)
197 >>> mix_mat = nx.degree_mixing_matrix(G)
198 >>> mix_mat
199 array([[0. , 0.5],
200 [0.5, 0. ]])
201
202 If you want every possible degree to appear as a row, even if no nodes
203 have that degree, use `mapping` as follows,
204
205 >>> max_degree = max(deg for n, deg in G.degree)
206 >>> mapping = {x: x for x in range(max_degree + 1)} # identity mapping
207 >>> mix_mat = nx.degree_mixing_matrix(G, mapping=mapping)
208 >>> mix_mat
209 array([[0. , 0. , 0. , 0. ],
210 [0. , 0. , 0. , 0.5],
211 [0. , 0. , 0. , 0. ],
212 [0. , 0.5, 0. , 0. ]])
213 """
214 d = degree_mixing_dict(G, x=x, y=y, nodes=nodes, weight=weight)
215 a = dict_to_numpy_array(d, mapping=mapping)
216 if normalized:
217 a = a / a.sum()
218 return a
219
220
221def mixing_dict(xy, normalized=False):
222 """Returns a dictionary representation of mixing matrix.
223
224 Parameters
225 ----------
226 xy : list or container of two-tuples
227 Pairs of (x,y) items.
228
229 attribute : string
230 Node attribute key
231
232 normalized : bool (default=False)
233 Return counts if False or probabilities if True.
234
235 Returns
236 -------
237 d: dictionary
238 Counts or Joint probability of occurrence of values in xy.
239 """
240 d = {}
241 psum = 0.0
242 for x, y in xy:
243 if x not in d:
244 d[x] = {}
245 if y not in d:
246 d[y] = {}
247 v = d[x].get(y, 0)
248 d[x][y] = v + 1
249 psum += 1
250
251 if normalized:
252 for _, jdict in d.items():
253 for j in jdict:
254 jdict[j] /= psum
255 return d