/src/igraph/src/misc/mixing.c
Line | Count | Source |
1 | | /* |
2 | | igraph library. |
3 | | Copyright (C) 2009-2025 The igraph development team <igraph@igraph.org> |
4 | | |
5 | | This program is free software; you can redistribute it and/or modify |
6 | | it under the terms of the GNU General Public License as published by |
7 | | the Free Software Foundation; either version 2 of the License, or |
8 | | (at your option) any later version. |
9 | | |
10 | | This program is distributed in the hope that it will be useful, |
11 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | | GNU General Public License for more details. |
14 | | |
15 | | You should have received a copy of the GNU General Public License |
16 | | along with this program. If not, see <https://www.gnu.org/licenses/>. |
17 | | */ |
18 | | |
19 | | #include "igraph_mixing.h" |
20 | | |
21 | | #include "igraph_interface.h" |
22 | | #include "igraph_structural.h" |
23 | | |
24 | | /** |
25 | | * \function igraph_assortativity_nominal |
26 | | * \brief Assortativity of a graph based on vertex categories. |
27 | | * |
28 | | * Assuming the vertices of the input graph belong to different |
29 | | * categories, this function calculates the assortativity coefficient of |
30 | | * the graph. The assortativity coefficient is between minus one and one |
31 | | * and it is one if all connections stay within categories, it is |
32 | | * minus one, if the network is perfectly disassortative. For a |
33 | | * randomly connected network it is (asymptotically) zero. |
34 | | * |
35 | | * </para><para> |
36 | | * The unnormalized version, computed when \p normalized is set to false, |
37 | | * is identical to the modularity, and is defined as follows for |
38 | | * directed networks: |
39 | | * </para><para> |
40 | | * <code>1/m sum_ij (A_ij - k^out_i k^in_j / m) d(i,j)</code>, |
41 | | * </para><para> |
42 | | * where \c m denotes the number of edges, \c A_ij is the adjacency matrix, |
43 | | * <code>k^out</code> and <code>k^in</code> are the out- and in-degrees, |
44 | | * and <code>d(i,j)</code> is one if vertices \c i and \c j are in the same |
45 | | * category and zero otherwise. |
46 | | * |
47 | | * </para><para> |
48 | | * The normalized assortativity coefficient is obtained by dividing the |
49 | | * previous expression by |
50 | | * </para><para> |
51 | | * <code>1/m sum_ij (m - k^out_i k^in_j d(i,j) / m)</code>. |
52 | | * </para><para> |
53 | | * It can take any value within the interval [-1, 1]. |
54 | | * |
55 | | * </para><para> |
56 | | * Undirected graphs are effectively treated as directed ones with all-reciprocal |
57 | | * edges. Thus, self-loops are taken into account twice in undirected graphs. |
58 | | * |
59 | | * </para><para> |
60 | | * References: |
61 | | * |
62 | | * </para><para> |
63 | | * M. E. J. Newman: Mixing patterns in networks, |
64 | | * Phys. Rev. E 67, 026126 (2003) |
65 | | * https://doi.org/10.1103/PhysRevE.67.026126. |
66 | | * See section II and equation (2) for the definition of the concept. |
67 | | * |
68 | | * </para><para> |
69 | | * For an educational overview of assortativity, see |
70 | | * M. E. J. Newman, |
71 | | * Networks: An Introduction, Oxford University Press (2010). |
72 | | * https://doi.org/10.1093/acprof%3Aoso/9780199206650.001.0001. |
73 | | * |
74 | | * \param graph The input graph, it can be directed or undirected. |
75 | | * \param weights Weighted nominal assortativity is not currently implemented. |
76 | | * Pass \c NULL to ignore. |
77 | | * \param types Integer vector giving the vertex categories. The types |
78 | | * are represented by integers starting at zero. |
79 | | * \param res Pointer to a real variable, the result is stored here. |
80 | | * \param directed Boolean, it gives whether to consider edge |
81 | | * directions in a directed graph. It is ignored for undirected |
82 | | * graphs. |
83 | | * \param normalized Boolean, whether to compute the usual normalized |
84 | | * assortativity. The unnormalized version is identical to |
85 | | * modularity. Supply true here to compute the standard assortativity. |
86 | | * \return Error code. |
87 | | * |
88 | | * Time complexity: O(|E|+t), |E| is the number of edges, t is the |
89 | | * number of vertex types. |
90 | | * |
91 | | * \sa \ref igraph_assortativity() for computing the assortativity |
92 | | * based on continuous vertex values instead of discrete categories. |
93 | | * \ref igraph_modularity() to compute generalized modularity. |
94 | | * \ref igraph_joint_type_distribution() to obtain the mixing matrix. |
95 | | * |
96 | | * \example examples/simple/igraph_assortativity_nominal.c |
97 | | */ |
98 | | |
99 | | igraph_error_t igraph_assortativity_nominal( |
100 | | const igraph_t *graph, const igraph_vector_t *weights, |
101 | | const igraph_vector_int_t *types, |
102 | | igraph_real_t *res, |
103 | 3.43k | igraph_bool_t directed, igraph_bool_t normalized) { |
104 | | |
105 | 3.43k | igraph_int_t no_of_nodes = igraph_vcount(graph); |
106 | 3.43k | igraph_int_t no_of_edges = igraph_ecount(graph); |
107 | 3.43k | igraph_real_t no_of_edges_real = (igraph_real_t) no_of_edges; /* for divisions */ |
108 | 3.43k | igraph_int_t no_of_types; |
109 | 3.43k | igraph_vector_int_t ai, bi, eii; |
110 | 3.43k | igraph_real_t sumaibi = 0.0, sumeii = 0.0; |
111 | | |
112 | 3.43k | if (weights) { |
113 | 0 | IGRAPH_ERROR("Weighted nominal assortativity is not yet implemented.", |
114 | 0 | IGRAPH_UNIMPLEMENTED); |
115 | 0 | } |
116 | | |
117 | 3.43k | if (igraph_vector_int_size(types) != no_of_nodes) { |
118 | 0 | IGRAPH_ERROR("Invalid types vector length.", IGRAPH_EINVAL); |
119 | 0 | } |
120 | | |
121 | 3.43k | if (no_of_nodes == 0) { |
122 | 1 | *res = IGRAPH_NAN; |
123 | 1 | return IGRAPH_SUCCESS; |
124 | 1 | } |
125 | | |
126 | | /* 'types' length > 0 here, safe to call vector_min() */ |
127 | 3.42k | if (igraph_vector_int_min(types) < 0) { |
128 | 0 | IGRAPH_ERROR("Vertex types must not be negative.", IGRAPH_EINVAL); |
129 | 0 | } |
130 | | |
131 | 3.42k | directed = directed && igraph_is_directed(graph); |
132 | | |
133 | 3.42k | no_of_types = igraph_vector_int_max(types) + 1; |
134 | 3.42k | IGRAPH_VECTOR_INT_INIT_FINALLY(&ai, no_of_types); |
135 | 3.42k | IGRAPH_VECTOR_INT_INIT_FINALLY(&bi, no_of_types); |
136 | 3.42k | IGRAPH_VECTOR_INT_INIT_FINALLY(&eii, no_of_types); |
137 | | |
138 | 109k | for (igraph_int_t e = 0; e < no_of_edges; e++) { |
139 | 105k | igraph_int_t from = IGRAPH_FROM(graph, e); |
140 | 105k | igraph_int_t to = IGRAPH_TO(graph, e); |
141 | 105k | igraph_int_t from_type = VECTOR(*types)[from]; |
142 | 105k | igraph_int_t to_type = VECTOR(*types)[to]; |
143 | | |
144 | 105k | VECTOR(ai)[from_type] += 1; |
145 | 105k | VECTOR(bi)[to_type] += 1; |
146 | 105k | if (from_type == to_type) { |
147 | 38.2k | VECTOR(eii)[from_type] += 1; |
148 | 38.2k | } |
149 | 105k | if (!directed) { |
150 | 0 | if (from_type == to_type) { |
151 | 0 | VECTOR(eii)[from_type] += 1; |
152 | 0 | } |
153 | 0 | VECTOR(ai)[to_type] += 1; |
154 | 0 | VECTOR(bi)[from_type] += 1; |
155 | 0 | } |
156 | 105k | } |
157 | | |
158 | 164k | for (igraph_int_t i = 0; i < no_of_types; i++) { |
159 | 161k | sumaibi += (VECTOR(ai)[i] / no_of_edges_real) * (VECTOR(bi)[i] / no_of_edges_real); |
160 | 161k | sumeii += (VECTOR(eii)[i] / no_of_edges_real); |
161 | 161k | } |
162 | | |
163 | 3.42k | if (!directed) { |
164 | 0 | sumaibi /= 4.0; |
165 | 0 | sumeii /= 2.0; |
166 | 0 | } |
167 | | |
168 | 3.42k | if (normalized) { |
169 | 3.42k | *res = (sumeii - sumaibi) / (1.0 - sumaibi); |
170 | 3.42k | } else { |
171 | 0 | *res = (sumeii - sumaibi); |
172 | 0 | } |
173 | | |
174 | 3.42k | igraph_vector_int_destroy(&eii); |
175 | 3.42k | igraph_vector_int_destroy(&bi); |
176 | 3.42k | igraph_vector_int_destroy(&ai); |
177 | 3.42k | IGRAPH_FINALLY_CLEAN(3); |
178 | | |
179 | 3.42k | return IGRAPH_SUCCESS; |
180 | 3.42k | } |
181 | | |
182 | | /** |
183 | | * \function igraph_assortativity |
184 | | * \brief Assortativity based on numeric properties of vertices. |
185 | | * |
186 | | * This function calculates the assortativity coefficient of a |
187 | | * graph based on given values \c x_i for each vertex \c i. This type of |
188 | | * assortativity coefficient equals the Pearson correlation of the values |
189 | | * at the two ends of the edges. |
190 | | * |
191 | | * </para><para> |
192 | | * The unnormalized covariance of values, computed when \p normalized is |
193 | | * set to false, is defined as follows in a directed graph: |
194 | | * </para><para> |
195 | | * <code>cov(x_out, x_in) = 1/m sum_ij (A_ij - k^out_i k^in_j / m) x_i x_j</code>, |
196 | | * </para><para> |
197 | | * where \c m denotes the number of edges, \c A_ij is the adjacency matrix, and |
198 | | * <code>k^out</code> and <code>k^in</code> are the out- and in-degrees. |
199 | | * \c x_out and \c x_in refer to the sets of vertex values at the start and end of |
200 | | * the directed edges. |
201 | | * |
202 | | * </para><para> |
203 | | * The normalized covariance, i.e. Pearson correlation, is obtained by dividing |
204 | | * the previous expression by |
205 | | * <code>sqrt(var(x_out)) sqrt(var(x_in))</code>, where |
206 | | * </para><para> |
207 | | * <code>var(x_out) = 1/m sum_i k^out_i x_i^2 - (1/m sum_i k^out_i x_i^2)^2</code> |
208 | | * </para><para> |
209 | | * <code>var(x_in) = 1/m sum_j k^in_j x_j^2 - (1/m sum_j k^in_j x_j^2)^2</code> |
210 | | * |
211 | | * </para><para> |
212 | | * Undirected graphs are effectively treated as directed graphs where all edges |
213 | | * are reciprocal. Therefore, self-loops are effectively considered twice in |
214 | | * undirected graphs. |
215 | | * |
216 | | * </para><para> |
217 | | * When edge weights are given, they are effectively treated as edge multiplicities. |
218 | | * The above formulas are valid for weighted graph as well when \c m is interpreted |
219 | | * as the total edge weight (instead of the edge count) and \c k as vertex strengths |
220 | | * (instead of degrees). |
221 | | * |
222 | | * </para><para> |
223 | | * References: |
224 | | * |
225 | | * </para><para> |
226 | | * M. E. J. Newman: Mixing patterns |
227 | | * in networks, Phys. Rev. E 67, 026126 (2003) |
228 | | * https://doi.org/10.1103/PhysRevE.67.026126. |
229 | | * See section III and equation (21) for the definition, and equation (26) for |
230 | | * performing the calculation in directed graphs with the degrees as values. |
231 | | * |
232 | | * </para><para> |
233 | | * M. E. J. Newman: Assortative mixing in networks, |
234 | | * Phys. Rev. Lett. 89, 208701 (2002) |
235 | | * https://doi.org/10.1103/PhysRevLett.89.208701. |
236 | | * See equation (4) for performing the calculation in undirected |
237 | | * graphs with the degrees as values. |
238 | | * |
239 | | * </para><para> |
240 | | * For an educational overview of the concept of assortativity, see |
241 | | * M. E. J. Newman, |
242 | | * Networks: An Introduction, Oxford University Press (2010). |
243 | | * https://doi.org/10.1093/acprof%3Aoso/9780199206650.001.0001. |
244 | | * |
245 | | * \param graph The input graph, it can be directed or undirected. |
246 | | * \param weights The edge weights. Pass \c NULL to compute unweighed |
247 | | * assortativity, which in effect assumes all weights to be 1. |
248 | | * \param values The vertex values, these can be arbitrary numeric |
249 | | * values. |
250 | | * \param values_in A second value vector to be used for the incoming |
251 | | * edges when calculating assortativity for a directed graph. |
252 | | * Supply \c NULL here if you want to use the same values |
253 | | * for outgoing and incoming edges. This argument is ignored |
254 | | * (with a warning) if it is not a null pointer and the undirected |
255 | | * assortativity coefficient is being calculated. |
256 | | * \param res Pointer to a real variable, the result is stored here. |
257 | | * \param directed Boolean, whether to consider edge directions for |
258 | | * directed graphs. It is ignored for undirected graphs. |
259 | | * \param normalized Boolean, whether to compute the normalized |
260 | | * covariance, i.e. Pearson correlation. Supply true here to |
261 | | * compute the standard assortativity. |
262 | | * \return Error code. |
263 | | * |
264 | | * Time complexity: O(|E|), linear in the number of edges of the |
265 | | * graph. |
266 | | * |
267 | | * \sa \ref igraph_assortativity_nominal() if you have discrete vertex |
268 | | * categories instead of numeric labels, and \ref |
269 | | * igraph_assortativity_degree() for the special case of assortativity |
270 | | * based on vertex degrees. |
271 | | */ |
272 | | |
273 | | igraph_error_t igraph_assortativity( |
274 | | const igraph_t *graph, const igraph_vector_t *weights, |
275 | | const igraph_vector_t *values, const igraph_vector_t *values_in, |
276 | | igraph_real_t *res, |
277 | 0 | igraph_bool_t directed, igraph_bool_t normalized) { |
278 | |
|
279 | 0 | const igraph_int_t no_of_nodes = igraph_vcount(graph); |
280 | 0 | const igraph_int_t no_of_edges = igraph_ecount(graph); |
281 | 0 | igraph_real_t total_weight; |
282 | |
|
283 | 0 | directed = directed && igraph_is_directed(graph); |
284 | |
|
285 | 0 | if (weights && igraph_vector_size(weights) != no_of_edges) { |
286 | 0 | IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", |
287 | 0 | IGRAPH_EINVAL, |
288 | 0 | igraph_vector_size(weights), no_of_edges); |
289 | 0 | } |
290 | | |
291 | 0 | if (!directed && values_in) { |
292 | 0 | IGRAPH_WARNING( |
293 | 0 | "Incoming vertex values are ignored when calculating undirected assortativity."); |
294 | 0 | } |
295 | |
|
296 | 0 | if (igraph_vector_size(values) != no_of_nodes) { |
297 | 0 | IGRAPH_ERROR("Invalid vertex values vector length.", IGRAPH_EINVAL); |
298 | 0 | } |
299 | | |
300 | 0 | if (values_in && igraph_vector_size(values_in) != no_of_nodes) { |
301 | 0 | IGRAPH_ERROR("Invalid incoming vertex values vector length.", IGRAPH_EINVAL); |
302 | 0 | } |
303 | | |
304 | 0 | total_weight = weights ? igraph_vector_sum(weights) : no_of_edges; |
305 | |
|
306 | 0 | if (!directed) { |
307 | 0 | igraph_real_t num1 = 0.0, num2 = 0.0, den1 = 0.0; |
308 | |
|
309 | 0 | if (weights) { |
310 | 0 | for (igraph_int_t e = 0; e < no_of_edges; e++) { |
311 | 0 | igraph_int_t from = IGRAPH_FROM(graph, e); |
312 | 0 | igraph_int_t to = IGRAPH_TO(graph, e); |
313 | 0 | igraph_real_t from_value = VECTOR(*values)[from]; |
314 | 0 | igraph_real_t to_value = VECTOR(*values)[to]; |
315 | 0 | igraph_real_t w = VECTOR(*weights)[e]; |
316 | |
|
317 | 0 | num1 += w * from_value * to_value; |
318 | 0 | num2 += w * (from_value + to_value); |
319 | 0 | if (normalized) { |
320 | 0 | den1 += w * (from_value * from_value + to_value * to_value); |
321 | 0 | } |
322 | 0 | } |
323 | 0 | } else { |
324 | 0 | for (igraph_int_t e = 0; e < no_of_edges; e++) { |
325 | 0 | igraph_int_t from = IGRAPH_FROM(graph, e); |
326 | 0 | igraph_int_t to = IGRAPH_TO(graph, e); |
327 | 0 | igraph_real_t from_value = VECTOR(*values)[from]; |
328 | 0 | igraph_real_t to_value = VECTOR(*values)[to]; |
329 | |
|
330 | 0 | num1 += from_value * to_value; |
331 | 0 | num2 += from_value + to_value; |
332 | 0 | if (normalized) { |
333 | 0 | den1 += from_value * from_value + to_value * to_value; |
334 | 0 | } |
335 | 0 | } |
336 | 0 | } |
337 | |
|
338 | 0 | num1 /= total_weight; |
339 | 0 | if (normalized) { |
340 | 0 | den1 /= total_weight * 2.0; |
341 | 0 | } |
342 | 0 | num2 /= total_weight * 2.0; |
343 | 0 | num2 = num2 * num2; |
344 | |
|
345 | 0 | if (normalized) { |
346 | 0 | *res = (num1 - num2) / (den1 - num2); |
347 | 0 | } else { |
348 | 0 | *res = (num1 - num2); |
349 | 0 | } |
350 | |
|
351 | 0 | } else { |
352 | 0 | igraph_real_t num1 = 0.0, num2 = 0.0, num3 = 0.0, |
353 | 0 | den1 = 0.0, den2 = 0.0; |
354 | 0 | igraph_real_t num, den; |
355 | |
|
356 | 0 | if (!values_in) { |
357 | 0 | values_in = values; |
358 | 0 | } |
359 | |
|
360 | 0 | if (weights) { |
361 | 0 | for (igraph_int_t e = 0; e < no_of_edges; e++) { |
362 | 0 | igraph_int_t from = IGRAPH_FROM(graph, e); |
363 | 0 | igraph_int_t to = IGRAPH_TO(graph, e); |
364 | 0 | igraph_real_t from_value = VECTOR(*values)[from]; |
365 | 0 | igraph_real_t to_value = VECTOR(*values_in)[to]; |
366 | 0 | igraph_real_t w = VECTOR(*weights)[e]; |
367 | |
|
368 | 0 | num1 += w * from_value * to_value; |
369 | 0 | num2 += w * from_value; |
370 | 0 | num3 += w * to_value; |
371 | 0 | if (normalized) { |
372 | 0 | den1 += w * (from_value * from_value); |
373 | 0 | den2 += w * (to_value * to_value); |
374 | 0 | } |
375 | 0 | } |
376 | 0 | } else { |
377 | 0 | for (igraph_int_t e = 0; e < no_of_edges; e++) { |
378 | 0 | igraph_int_t from = IGRAPH_FROM(graph, e); |
379 | 0 | igraph_int_t to = IGRAPH_TO(graph, e); |
380 | 0 | igraph_real_t from_value = VECTOR(*values)[from]; |
381 | 0 | igraph_real_t to_value = VECTOR(*values_in)[to]; |
382 | |
|
383 | 0 | num1 += from_value * to_value; |
384 | 0 | num2 += from_value; |
385 | 0 | num3 += to_value; |
386 | 0 | if (normalized) { |
387 | 0 | den1 += from_value * from_value; |
388 | 0 | den2 += to_value * to_value; |
389 | 0 | } |
390 | 0 | } |
391 | 0 | } |
392 | |
|
393 | 0 | num = num1 - num2 * num3 / total_weight; |
394 | 0 | if (normalized) { |
395 | 0 | den = sqrt(den1 - num2 * num2 / total_weight) * |
396 | 0 | sqrt(den2 - num3 * num3 / total_weight); |
397 | |
|
398 | 0 | *res = num / den; |
399 | 0 | } else { |
400 | 0 | *res = num / total_weight; |
401 | 0 | } |
402 | 0 | } |
403 | |
|
404 | 0 | return IGRAPH_SUCCESS; |
405 | 0 | } |
406 | | |
407 | | /** |
408 | | * \function igraph_assortativity_degree |
409 | | * \brief Assortativity of a graph based on vertex degree. |
410 | | * |
411 | | * Assortativity based on vertex degree, please see the discussion at |
412 | | * the documentation of \ref igraph_assortativity() for details. |
413 | | * This function simply calls \ref igraph_assortativity() with |
414 | | * the degrees as the vertex values and normalization enabled. |
415 | | * In the directed case, it uses out-degrees as out-values and |
416 | | * in-degrees as in-values. |
417 | | * |
418 | | * </para><para> |
419 | | * For regular graphs, i.e. graphs in which all vertices have the |
420 | | * same degree, computing degree correlations is not meaningful, |
421 | | * and this function returns NaN. |
422 | | * |
423 | | * \param graph The input graph, it can be directed or undirected. |
424 | | * \param res Pointer to a real variable, the result is stored here. |
425 | | * \param directed Boolean, whether to consider edge directions for |
426 | | * directed graphs. This argument is ignored for undirected |
427 | | * graphs. Supply true here to do the natural thing, i.e. use |
428 | | * directed version of the measure for directed graphs and the |
429 | | * undirected version for undirected graphs. |
430 | | * \return Error code. |
431 | | * |
432 | | * Time complexity: O(|E|+|V|), |E| is the number of edges, |V| is |
433 | | * the number of vertices. |
434 | | * |
435 | | * \sa \ref igraph_assortativity() for the general function |
436 | | * calculating assortativity for any kind of numeric vertex values, |
437 | | * and \ref igraph_joint_degree_distribution() to get the complete |
438 | | * joint degree distribution. |
439 | | * |
440 | | * \example examples/simple/igraph_assortativity_degree.c |
441 | | */ |
442 | | |
443 | | igraph_error_t igraph_assortativity_degree(const igraph_t *graph, |
444 | | igraph_real_t *res, |
445 | 0 | igraph_bool_t directed) { |
446 | |
|
447 | 0 | directed = directed && igraph_is_directed(graph); |
448 | 0 | igraph_int_t no_of_nodes = igraph_vcount(graph); |
449 | | |
450 | | /* This function uses igraph_strength() instead of igraph_degree() in order to obtain |
451 | | * a vector of reals instead of a vector of integers. */ |
452 | 0 | if (directed) { |
453 | 0 | igraph_vector_t indegree, outdegree; |
454 | 0 | IGRAPH_VECTOR_INIT_FINALLY(&indegree, no_of_nodes); |
455 | 0 | IGRAPH_VECTOR_INIT_FINALLY(&outdegree, no_of_nodes); |
456 | 0 | IGRAPH_CHECK(igraph_strength(graph, &indegree, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS, NULL)); |
457 | 0 | IGRAPH_CHECK(igraph_strength(graph, &outdegree, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS, NULL)); |
458 | 0 | IGRAPH_CHECK(igraph_assortativity(graph, NULL, &outdegree, &indegree, res, /* directed */ true, /* normalized */ true)); |
459 | 0 | igraph_vector_destroy(&indegree); |
460 | 0 | igraph_vector_destroy(&outdegree); |
461 | 0 | IGRAPH_FINALLY_CLEAN(2); |
462 | 0 | } else { |
463 | 0 | igraph_vector_t degree; |
464 | 0 | IGRAPH_VECTOR_INIT_FINALLY(°ree, no_of_nodes); |
465 | 0 | IGRAPH_CHECK(igraph_strength(graph, °ree, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS, NULL)); |
466 | 0 | IGRAPH_CHECK(igraph_assortativity(graph, NULL, °ree, 0, res, /* directed */ false, /* normalized */ true)); |
467 | 0 | igraph_vector_destroy(°ree); |
468 | 0 | IGRAPH_FINALLY_CLEAN(1); |
469 | 0 | } |
470 | | |
471 | 0 | return IGRAPH_SUCCESS; |
472 | 0 | } |
473 | | |
474 | | /** |
475 | | * \function igraph_joint_degree_matrix |
476 | | * \brief The joint degree matrix of a graph. |
477 | | * |
478 | | * In graph theory, the joint degree matrix \c J_ij of a graph gives the number |
479 | | * of edges, or sum of edge weights, between vertices of degree \c i and degree |
480 | | * \c j. This function stores \c J_ij into <code>jdm[i-1, j-1]</code>. |
481 | | * Each edge, including self-loops, is counted precisely once, both in undirected |
482 | | * and directed graphs. |
483 | | * |
484 | | * </para><para> |
485 | | * <code>sum_(i,j) J_ij</code> is the total number of edges (or total edge weight) |
486 | | * \c m in the graph, where <code>(i,j)</code> refers to ordered or unordered |
487 | | * pairs in directed and undirected graphs, respectively. Thus <code>J_ij / m</code> |
488 | | * is the probability that an edge chosen at random (with probability proportional |
489 | | * to its weight) connects vertices with degrees \c i and \c j. |
490 | | * |
491 | | * </para><para> |
492 | | * Note that \c J_ij is similar, but not identical to the joint degree |
493 | | * \em distribution, computed by \ref igraph_joint_degree_distribution(), |
494 | | * which is defined for \em ordered <code>(i, j)</code> degree |
495 | | * pairs even in the undirected case. When considering undirected graphs, the |
496 | | * diagonal of the joint degree distribution is twice that of the joint |
497 | | * degree matrix. |
498 | | * |
499 | | * </para><para> |
500 | | * References: |
501 | | * |
502 | | * </para><para> |
503 | | * Isabelle Stanton and Ali Pinar: |
504 | | * Constructing and sampling graphs with a prescribed joint degree distribution. |
505 | | * ACM J. Exp. Algorithmics 17, Article 3.5 (2012). |
506 | | * https://doi.org/10.1145/2133803.2330086 |
507 | | * |
508 | | * \param graph A pointer to an initialized graph object. |
509 | | * \param weights A vector containing the weights of the edges. If passing a |
510 | | * \c NULL pointer, edges will be assumed to have unit weights, i.e. |
511 | | * the matrix entries will be connection counts. |
512 | | * \param jdm A pointer to an initialized matrix that will be resized. The values |
513 | | * will be written here. |
514 | | * \param max_out_degree Number of rows in the result, i.e. the largest (out-)degree |
515 | | * to consider. If negative or \ref IGRAPH_UNLIMITED, the largest (out-)degree |
516 | | * of the graph will be used. |
517 | | * \param max_in_degree Number of columns in the result, i.e. the largest (in-)degree |
518 | | * to consider. If negative or \ref IGRAPH_UNLIMITED, the largest (in-)degree |
519 | | * of the graph will be used. |
520 | | * \return Error code. |
521 | | * |
522 | | * \sa \ref igraph_joint_degree_distribution() to count ordered vertex pairs instead of |
523 | | * edges, or to obtain a normalized matrix. |
524 | | * |
525 | | * Time complexity: O(E), where E is the number of edges in input graph. |
526 | | */ |
527 | | |
528 | | igraph_error_t igraph_joint_degree_matrix( |
529 | | const igraph_t *graph, const igraph_vector_t *weights, |
530 | | igraph_matrix_t *jdm, |
531 | 0 | igraph_int_t max_out_degree, igraph_int_t max_in_degree) { |
532 | |
|
533 | 0 | igraph_int_t no_of_nodes = igraph_vcount(graph); |
534 | 0 | igraph_int_t no_of_edges = igraph_ecount(graph); |
535 | 0 | igraph_eit_t eit; |
536 | 0 | igraph_int_t eid; |
537 | 0 | igraph_int_t v1id; |
538 | 0 | igraph_int_t v2id; |
539 | 0 | igraph_int_t v1deg; |
540 | 0 | igraph_int_t v2deg; |
541 | |
|
542 | 0 | if (weights && igraph_vector_size(weights) != no_of_edges) { |
543 | 0 | IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", |
544 | 0 | IGRAPH_EINVAL, |
545 | 0 | igraph_vector_size(weights), no_of_edges); |
546 | 0 | } |
547 | | |
548 | 0 | if (igraph_is_directed(graph)) { |
549 | 0 | igraph_vector_int_t out_degrees; |
550 | 0 | igraph_vector_int_t in_degrees; |
551 | | |
552 | | // Compute max degrees |
553 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&out_degrees, no_of_nodes); |
554 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(&in_degrees, no_of_nodes); |
555 | 0 | IGRAPH_CHECK(igraph_degree(graph, &out_degrees, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)); |
556 | 0 | IGRAPH_CHECK(igraph_degree(graph, &in_degrees, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)); |
557 | | |
558 | 0 | if (max_out_degree < 0) { |
559 | 0 | max_out_degree = no_of_nodes > 0 ? igraph_vector_int_max(&out_degrees) : 0; |
560 | 0 | } |
561 | |
|
562 | 0 | if (max_in_degree < 0) { |
563 | 0 | max_in_degree = no_of_nodes > 0 ? igraph_vector_int_max(&in_degrees) : 0; |
564 | 0 | } |
565 | |
|
566 | 0 | IGRAPH_CHECK(igraph_matrix_resize(jdm, max_out_degree, max_in_degree)); |
567 | 0 | igraph_matrix_null(jdm); |
568 | |
|
569 | 0 | IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &eit)); |
570 | 0 | IGRAPH_FINALLY(igraph_eit_destroy, &eit); |
571 | 0 | for (; !IGRAPH_EIT_END(eit); IGRAPH_EIT_NEXT(eit)) { |
572 | 0 | eid = IGRAPH_EIT_GET(eit); |
573 | 0 | v1id = IGRAPH_FROM(graph, eid); |
574 | 0 | v2id = IGRAPH_TO(graph, eid); |
575 | 0 | v1deg = VECTOR(out_degrees)[v1id]; |
576 | 0 | v2deg = VECTOR(in_degrees)[v2id]; |
577 | 0 | if (v1deg <= max_out_degree && v2deg <= max_in_degree) { |
578 | 0 | MATRIX(*jdm, v1deg-1, v2deg-1) += weights ? VECTOR(*weights)[eid] : 1; |
579 | 0 | } |
580 | 0 | } |
581 | |
|
582 | 0 | igraph_eit_destroy(&eit); |
583 | 0 | igraph_vector_int_destroy(&in_degrees); |
584 | 0 | igraph_vector_int_destroy(&out_degrees); |
585 | 0 | IGRAPH_FINALLY_CLEAN(3); |
586 | |
|
587 | 0 | } else { |
588 | 0 | igraph_vector_int_t degrees; |
589 | 0 | igraph_int_t maxdeg; |
590 | |
|
591 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(°rees, no_of_nodes); |
592 | 0 | IGRAPH_CHECK(igraph_degree(graph, °rees, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); |
593 | | |
594 | | // Compute max degree of the graph only if needed |
595 | 0 | if (max_out_degree < 0 || max_in_degree < 0) { |
596 | 0 | maxdeg = no_of_nodes > 0 ? igraph_vector_int_max(°rees) : 0; |
597 | 0 | } |
598 | |
|
599 | 0 | if (max_out_degree < 0) { |
600 | 0 | max_out_degree = maxdeg; |
601 | 0 | } |
602 | 0 | if (max_in_degree < 0) { |
603 | 0 | max_in_degree = maxdeg; |
604 | 0 | } |
605 | |
|
606 | 0 | IGRAPH_CHECK(igraph_matrix_resize(jdm, max_out_degree, max_in_degree)); |
607 | 0 | igraph_matrix_null(jdm); |
608 | |
|
609 | 0 | IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &eit)); |
610 | 0 | IGRAPH_FINALLY(igraph_eit_destroy, &eit); |
611 | 0 | while (!IGRAPH_EIT_END(eit)) { |
612 | 0 | eid = IGRAPH_EIT_GET(eit); |
613 | 0 | v1id = IGRAPH_FROM(graph, eid); |
614 | 0 | v2id = IGRAPH_TO(graph, eid); |
615 | 0 | v1deg = VECTOR(degrees)[v1id]; |
616 | 0 | v2deg = VECTOR(degrees)[v2id]; |
617 | | |
618 | | // Undirected JDMs are symmetrical, needs to be accounted for this when indexing. |
619 | 0 | if (v1deg <= max_out_degree && v2deg <= max_in_degree) { |
620 | 0 | MATRIX(*jdm, v1deg-1, v2deg-1) += weights ? VECTOR(*weights)[eid] : 1; |
621 | 0 | } |
622 | | // Do not double-count connections between same-degree vertices. |
623 | 0 | if (v1deg != v2deg && v2deg <= max_out_degree && v1deg <= max_in_degree) { |
624 | 0 | MATRIX(*jdm, v2deg-1, v1deg-1) += weights ? VECTOR(*weights)[eid] : 1; |
625 | 0 | } |
626 | |
|
627 | 0 | IGRAPH_EIT_NEXT(eit); |
628 | 0 | } |
629 | |
|
630 | 0 | igraph_eit_destroy(&eit); |
631 | 0 | igraph_vector_int_destroy(°rees); |
632 | 0 | IGRAPH_FINALLY_CLEAN(2); |
633 | 0 | } |
634 | | |
635 | 0 | return IGRAPH_SUCCESS; |
636 | 0 | } |
637 | | |
638 | | /** |
639 | | * Common implementation for igraph_joint_type_distribution() and igraph_joint_degree_distribution() |
640 | | * |
641 | | * For the max_from/to_type parameters, negative values mean "automatic". These are used |
642 | | * only with igraph_joint_degree_distribution(). |
643 | | * |
644 | | * check_types controls whether types should be validated to be non-negative. Validation |
645 | | * is only necessary with igraph_joint_type_distribution() but not with igraph_joint_degree_distribution(). |
646 | | * |
647 | | * directed_neighbors must NOT be true when the graph is undirected. |
648 | | */ |
649 | | static igraph_error_t mixing_matrix( |
650 | | const igraph_t *graph, const igraph_vector_t *weights, |
651 | | igraph_matrix_t *p, |
652 | | const igraph_vector_int_t *from_types, const igraph_vector_int_t *to_types, |
653 | | igraph_bool_t directed_neighbors, igraph_bool_t normalized, |
654 | | igraph_int_t max_from_type, igraph_int_t max_to_type, |
655 | 0 | igraph_bool_t check_types) { |
656 | |
|
657 | 0 | igraph_int_t no_of_nodes = igraph_vcount(graph); |
658 | 0 | igraph_int_t no_of_edges = igraph_ecount(graph); |
659 | 0 | igraph_int_t nrow, ncol; |
660 | 0 | igraph_real_t sum; |
661 | 0 | igraph_bool_t negative_weight; |
662 | |
|
663 | 0 | if (igraph_vector_int_size(from_types) != no_of_nodes) { |
664 | 0 | IGRAPH_ERROR("Length of 'from' type vector must agree with vertex count.", IGRAPH_EINVAL); |
665 | 0 | } |
666 | | |
667 | 0 | if (igraph_vector_int_size(to_types) != no_of_nodes) { |
668 | 0 | IGRAPH_ERROR("Length of 'to' type vector must agree with vertex count.", IGRAPH_EINVAL); |
669 | 0 | } |
670 | | |
671 | 0 | if (weights && igraph_vector_size(weights) != no_of_edges) { |
672 | 0 | IGRAPH_ERRORF("Weight vector length (%" IGRAPH_PRId ") does not match number of edges (%" IGRAPH_PRId ").", |
673 | 0 | IGRAPH_EINVAL, |
674 | 0 | igraph_vector_size(weights), no_of_edges); |
675 | 0 | } |
676 | | |
677 | 0 | if (max_from_type < 0) { |
678 | 0 | if (no_of_nodes == 0) { |
679 | 0 | nrow = 0; |
680 | 0 | } else { |
681 | 0 | nrow = igraph_vector_int_max(from_types) + 1; |
682 | 0 | } |
683 | 0 | } else { |
684 | 0 | nrow = max_from_type + 1; |
685 | 0 | } |
686 | |
|
687 | 0 | if (max_to_type < 0) { |
688 | 0 | if (no_of_nodes == 0) { |
689 | 0 | ncol = 0; |
690 | 0 | } else if (to_types == from_types) { |
691 | | /* Avoid computing the maximum again if target vertex types |
692 | | * are the same as source vertex types. */ |
693 | 0 | ncol = nrow; |
694 | 0 | } else { |
695 | 0 | ncol = igraph_vector_int_max(to_types) + 1; |
696 | 0 | } |
697 | 0 | } else { |
698 | 0 | ncol = max_to_type + 1; |
699 | 0 | } |
700 | |
|
701 | 0 | if (check_types && no_of_nodes > 0) { |
702 | 0 | igraph_int_t min; |
703 | |
|
704 | 0 | min = igraph_vector_int_min(from_types); |
705 | 0 | if (min < 0) { |
706 | 0 | IGRAPH_ERROR("Invalid source vertex type.", IGRAPH_EINVAL); |
707 | 0 | } |
708 | | |
709 | 0 | if (to_types != from_types) { |
710 | 0 | min = igraph_vector_int_min(from_types); |
711 | 0 | if (min < 0) { |
712 | 0 | IGRAPH_ERROR("Invalid target vertex type.", IGRAPH_EINVAL); |
713 | 0 | } |
714 | 0 | } |
715 | 0 | } |
716 | | |
717 | 0 | IGRAPH_CHECK(igraph_matrix_resize(p, nrow, ncol)); |
718 | 0 | igraph_matrix_null(p); |
719 | |
|
720 | 0 | sum = 0; |
721 | 0 | negative_weight = false; |
722 | 0 | for (igraph_int_t eid=0; eid < no_of_edges; eid++) { |
723 | 0 | igraph_int_t from = IGRAPH_FROM(graph, eid); |
724 | 0 | igraph_int_t to = IGRAPH_TO(graph, eid); |
725 | 0 | igraph_int_t from_type = VECTOR(*from_types)[from]; |
726 | 0 | igraph_int_t to_type = VECTOR(*to_types)[to]; |
727 | 0 | igraph_real_t w = weights ? VECTOR(*weights)[eid] : 1; |
728 | |
|
729 | 0 | if (from_type >= nrow || to_type >= ncol) { |
730 | 0 | continue; |
731 | 0 | } |
732 | | |
733 | 0 | MATRIX(*p, from_type, to_type) += w; |
734 | 0 | sum += w; |
735 | |
|
736 | 0 | if (! directed_neighbors) { |
737 | 0 | MATRIX(*p, to_type, from_type) += w; |
738 | 0 | sum += w; |
739 | 0 | } |
740 | |
|
741 | 0 | if (w < 0) { |
742 | 0 | negative_weight = true; |
743 | 0 | } |
744 | 0 | } |
745 | |
|
746 | 0 | if (normalized) { |
747 | 0 | if (negative_weight) { |
748 | | /* When some edge weights are negative, they cannot be interpreted as sampling weights, |
749 | | * and the sum of weights may be zero, potentially leading to Inf/NaN results. */ |
750 | 0 | IGRAPH_WARNING("Negative edge weights are present. Normalization may not be meaningful."); |
751 | 0 | } |
752 | 0 | if (no_of_edges > 0) { |
753 | | /* Scale only when there are some edges, thus 'sum' can be non-zero. */ |
754 | 0 | igraph_matrix_scale(p, 1.0 / sum); |
755 | 0 | } |
756 | 0 | } |
757 | |
|
758 | 0 | return IGRAPH_SUCCESS; |
759 | 0 | } |
760 | | |
761 | | |
762 | | /** |
763 | | * \function igraph_joint_degree_distribution |
764 | | * \brief The joint degree distribution of a graph. |
765 | | * |
766 | | * Computes the joint degree distribution \c P_ij of a graph, used in the |
767 | | * study of degree correlations. \c P_ij is the probability that a randomly |
768 | | * chosen ordered pair of \em connected vertices have degrees \c i and \c j. |
769 | | * |
770 | | * </para><para> |
771 | | * In directed graphs, directionally connected <code>u -> v</code> pairs |
772 | | * are considered. The joint degree distribution of an undirected graph is the |
773 | | * same as that of the corresponding directed graph in which all connection are |
774 | | * bidirectional, assuming that \p from_mode is \c IGRAPH_OUT, \p to_mode is |
775 | | * \c IGRAPH_IN and \p directed_neighbors is true. |
776 | | * |
777 | | * </para><para> |
778 | | * When \p normalized is false, <code>sum_ij P_ij</code> gives the total |
779 | | * number of connections in a directed graph, or twice that value in an |
780 | | * undirected graph. The sum is taken over ordered <code>(i,j)</code> degree |
781 | | * pairs. |
782 | | * |
783 | | * </para><para> |
784 | | * The joint degree distribution relates to other concepts used in the study of |
785 | | * degree correlations. If \c P_ij is normalized then the degree correlation |
786 | | * function <code>k_nn(k)</code> is obtained as |
787 | | * |
788 | | * </para><para> |
789 | | * <code>k_nn(k) = (sum_j j P_kj) / (sum_j P_kj)</code>. |
790 | | * |
791 | | * </para><para> |
792 | | * The non-normalized degree assortativity is obtained as |
793 | | * |
794 | | * </para><para> |
795 | | * <code>a = sum_ij i j (P_ij - q_i r_j)</code>, |
796 | | * |
797 | | * </para><para> |
798 | | * where <code>q_i = sum_k P_ik</code> and <code>r_j = sum_k P_kj</code>. |
799 | | * |
800 | | * </para><para> |
801 | | * Note that the joint degree distribution \c P_ij is similar, but not identical |
802 | | * to the joint degree matrix \c J_ij computed by \ref igraph_joint_degree_matrix(). |
803 | | * If the graph is undirected, then the diagonal entries of an unnormalized \c P_ij |
804 | | * are double that of \c J_ij, as any undirected connection between same-degree vertices |
805 | | * is counted in both directions. In contrast to \ref igraph_joint_degree_matrix(), |
806 | | * this function returns matrices which include the row and column corresponding |
807 | | * to zero degrees. In directed graphs, this row and column is not necessarily |
808 | | * zero when \p from_mode is different from \c IGRAPH_OUT or \p to_mode is different |
809 | | * from \c IGRAPH_IN. |
810 | | * |
811 | | * </para><para> |
812 | | * References: |
813 | | * |
814 | | * </para><para> |
815 | | * M. E. J. Newman: Mixing patterns in networks, |
816 | | * Phys. Rev. E 67, 026126 (2003) |
817 | | * https://doi.org/10.1103/PhysRevE.67.026126. |
818 | | * |
819 | | * \param graph A pointer to an initialized graph object. |
820 | | * \param weights A vector containing the weights of the edges. If passing a |
821 | | * \c NULL pointer, edges will be assumed to have unit weights. |
822 | | * \param p A pointer to an initialized matrix that will be resized. The \c P_ij |
823 | | * value will be written into <code>p[i,j]</code>. |
824 | | * \param from_mode How to compute the degree of sources? Can be \c IGRAPH_OUT |
825 | | * for out-degree, \c IGRAPH_IN for in-degree, or \c IGRAPH_ALL for total degree. |
826 | | * Ignored in undirected graphs. |
827 | | * \param to_mode How to compute the degree of targets? Can be \c IGRAPH_OUT |
828 | | * for out-degree, \c IGRAPH_IN for in-degree, or \c IGRAPH_ALL for total degree. |
829 | | * Ignored in undirected graphs. |
830 | | * \param directed_neighbors Whether to consider <code>u -> v</code> connections |
831 | | * to be directed. Undirected connections are treated as reciprocal directed ones, |
832 | | * i.e. both <code>u -> v</code> and <code>v -> u</code> will be considered. |
833 | | * Ignored in undirected graphs. |
834 | | * \param normalized Whether to normalize the matrix so that entries sum to 1.0. |
835 | | * If false, matrix entries will be connection counts. Normalization is not |
836 | | * meaningful if some edge weights are negative. |
837 | | * \param max_from_degree The largest source vertex degree to consider. If |
838 | | * negative or \ref IGRAPH_UNLIMITED, the largest source degree will be used. |
839 | | * The row count of the result matrix is one larger than this value. |
840 | | * \param max_to_degree The largest target vertex degree to consider. If |
841 | | * negative or \ref IGRAPH_UNLIMITED, the largest target degree will be used. |
842 | | * The column count of the result matrix is one larger than this value. |
843 | | * \return Error code. |
844 | | * |
845 | | * \sa \ref igraph_joint_degree_matrix() for computing the joint degree matrix; |
846 | | * \ref igraph_assortativity_degree() and \ref igraph_assortativity() for |
847 | | * degree correlations coefficients, and \ref igraph_degree_correlation_vector() |
848 | | * for the degree correlation function. |
849 | | * |
850 | | * Time complexity: O(E), where E is the number of edges in the input graph. |
851 | | */ |
852 | | igraph_error_t igraph_joint_degree_distribution( |
853 | | const igraph_t *graph, const igraph_vector_t *weights, |
854 | | igraph_matrix_t *p, |
855 | | igraph_neimode_t from_mode, igraph_neimode_t to_mode, |
856 | | igraph_bool_t directed_neighbors, |
857 | | igraph_bool_t normalized, |
858 | 0 | igraph_int_t max_from_degree, igraph_int_t max_to_degree) { |
859 | |
|
860 | 0 | igraph_int_t no_of_nodes = igraph_vcount(graph); |
861 | 0 | igraph_vector_int_t *deg_from, *deg_to, deg_out, deg_in, deg_all; |
862 | | |
863 | | /* Make sure directionality parameters are consistent for undirected graphs. */ |
864 | 0 | if (! igraph_is_directed(graph)) { |
865 | 0 | from_mode = to_mode = IGRAPH_ALL; |
866 | 0 | directed_neighbors = false; |
867 | 0 | } |
868 | |
|
869 | 0 | igraph_bool_t have_out = from_mode == IGRAPH_OUT || to_mode == IGRAPH_OUT; |
870 | 0 | igraph_bool_t have_in = from_mode == IGRAPH_IN || to_mode == IGRAPH_IN; |
871 | 0 | igraph_bool_t have_all = from_mode == IGRAPH_ALL || to_mode == IGRAPH_ALL; |
872 | |
|
873 | 0 | if (have_out) { |
874 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(°_out, no_of_nodes); |
875 | 0 | IGRAPH_CHECK(igraph_degree(graph, °_out, igraph_vss_all(), IGRAPH_OUT, IGRAPH_LOOPS)); |
876 | 0 | } |
877 | | |
878 | 0 | if (have_in) { |
879 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(°_in, no_of_nodes); |
880 | 0 | IGRAPH_CHECK(igraph_degree(graph, °_in, igraph_vss_all(), IGRAPH_IN, IGRAPH_LOOPS)); |
881 | 0 | } |
882 | | |
883 | 0 | if (have_all) { |
884 | 0 | IGRAPH_VECTOR_INT_INIT_FINALLY(°_all, no_of_nodes); |
885 | 0 | IGRAPH_CHECK(igraph_degree(graph, °_all, igraph_vss_all(), IGRAPH_ALL, IGRAPH_LOOPS)); |
886 | 0 | } |
887 | | |
888 | 0 | switch (from_mode) { |
889 | 0 | case IGRAPH_OUT: deg_from = °_out; break; |
890 | 0 | case IGRAPH_IN: deg_from = °_in; break; |
891 | 0 | case IGRAPH_ALL: deg_from = °_all; break; |
892 | 0 | default: |
893 | 0 | IGRAPH_ERROR("Invalid 'from' degree mode.", IGRAPH_EINVMODE); |
894 | 0 | } |
895 | | |
896 | 0 | switch (to_mode) { |
897 | 0 | case IGRAPH_OUT: deg_to = °_out; break; |
898 | 0 | case IGRAPH_IN: deg_to = °_in; break; |
899 | 0 | case IGRAPH_ALL: deg_to = °_all; break; |
900 | 0 | default: |
901 | 0 | IGRAPH_ERROR("Invalid 'to' degree mode.", IGRAPH_EINVMODE); |
902 | 0 | } |
903 | | |
904 | 0 | IGRAPH_CHECK(mixing_matrix(graph, |
905 | 0 | weights, p, |
906 | 0 | deg_from, deg_to, |
907 | 0 | directed_neighbors, normalized, |
908 | 0 | max_from_degree, max_to_degree, |
909 | 0 | /*check_types=*/ false)); |
910 | | |
911 | 0 | if (have_all) { |
912 | 0 | igraph_vector_int_destroy(°_all); |
913 | 0 | IGRAPH_FINALLY_CLEAN(1); |
914 | 0 | } |
915 | |
|
916 | 0 | if (have_in) { |
917 | 0 | igraph_vector_int_destroy(°_in); |
918 | 0 | IGRAPH_FINALLY_CLEAN(1); |
919 | 0 | } |
920 | |
|
921 | 0 | if (have_out) { |
922 | 0 | igraph_vector_int_destroy(°_out); |
923 | 0 | IGRAPH_FINALLY_CLEAN(1); |
924 | 0 | } |
925 | |
|
926 | 0 | return IGRAPH_SUCCESS; |
927 | 0 | } |
928 | | |
929 | | |
930 | | /** |
931 | | * \function igraph_joint_type_distribution |
932 | | * \brief Mixing matrix for vertex categories. |
933 | | * |
934 | | * Computes the mixing matrix M_ij, i.e. the joint distribution of vertex types |
935 | | * at the endpoints directed of edges. Categories are represented by non-negative integer |
936 | | * indices, passed in \p from_types and \p to_types. The row and column counts of \p m |
937 | | * will be one larger than the largest source and target type, respectively. Re-index type |
938 | | * vectors using \ref igraph_reindex_membership() if they are not contiguous integers, |
939 | | * to avoid producing a very large matrix. |
940 | | * |
941 | | * </para><para> |
942 | | * M_ij is proportional to the probability that a randomly chosen ordered pair of vertices |
943 | | * have types \c i and \c j. |
944 | | * |
945 | | * </para><para> |
946 | | * When there is a single categorization of vertices, i.e. \p from_types and \p to_types |
947 | | * are the same, M_ij is related to the modularity (\ref igraph_modularity()) and nominal |
948 | | * assortativity (\ref igraph_assortativity_nominal()). Let <code>a_i = sum_j M_ij</code> and |
949 | | * <code>b_j = sum_i M_ij</code>. If M_ij is normalized, i.e. <code>sum_ij M_ij = 1</code>, |
950 | | * and the types represent membership in vertex partitions, then the modularity of the |
951 | | * partitioning can be computed as |
952 | | * |
953 | | * </para><para> |
954 | | * <code>Q = sum_ii M_ii - sum_i a_i b_i</code> |
955 | | * |
956 | | * </para><para> |
957 | | * The normalized nominal assortativity is |
958 | | * |
959 | | * </para><para> |
960 | | * <code>Q / (1 - sum_i a_i b_i)</code> |
961 | | * |
962 | | * </para><para> |
963 | | * \ref igraph_joint_degree_distribution() is a special case of this function, with |
964 | | * categories consisting vertices of the same degree. |
965 | | * |
966 | | * </para><para> |
967 | | * References: |
968 | | * |
969 | | * </para><para> |
970 | | * M. E. J. Newman: Mixing patterns in networks, |
971 | | * Phys. Rev. E 67, 026126 (2003) |
972 | | * https://doi.org/10.1103/PhysRevE.67.026126. |
973 | | * |
974 | | * \param graph The input graph. |
975 | | * \param weights A vector containing the weights of the edges. If passing a |
976 | | * \c NULL pointer, edges will be assumed to have unit weights. |
977 | | * \param p The mixing matrix \c M_ij will be stored here. |
978 | | * \param from_types Vertex types for source vertices. These must be non-negative integers. |
979 | | * \param to_types Vertex types for target vertices. These must be non-negative integers. |
980 | | * If \c NULL, it is assumed to be the same as \p from_types. |
981 | | * \param directed Whether to treat edges are directed. Ignored for undirected graphs. |
982 | | * \param normalized Whether to normalize the matrix so that entries sum to 1.0. |
983 | | * If false, matrix entries will be connection counts. Normalization is not |
984 | | * meaningful if some edge weights are negative. |
985 | | * \return Error code. |
986 | | * |
987 | | * \sa \ref igraph_joint_degree_distribution() to compute the joint distribution |
988 | | * of vertex degrees; \ref igraph_modularity() to compute the modularity of |
989 | | * a vertex partitioning; \ref igraph_assortativity_nominal() to compute |
990 | | * assortativity based on vertex categories. |
991 | | * |
992 | | * Time complexity: O(E), where E is the number of edges in the input graph. |
993 | | */ |
994 | | igraph_error_t igraph_joint_type_distribution( |
995 | | const igraph_t *graph, const igraph_vector_t *weights, |
996 | | igraph_matrix_t *p, |
997 | | const igraph_vector_int_t *from_types, const igraph_vector_int_t *to_types, |
998 | 0 | igraph_bool_t directed, igraph_bool_t normalized) { |
999 | |
|
1000 | 0 | IGRAPH_ASSERT(from_types != NULL); |
1001 | 0 | if (to_types == NULL) { |
1002 | 0 | to_types = from_types; |
1003 | 0 | } |
1004 | 0 | if (! igraph_is_directed(graph)) { |
1005 | 0 | directed = false; |
1006 | 0 | } |
1007 | | return mixing_matrix(graph, weights, p, from_types, to_types, directed, normalized, -1, -1, true); |
1008 | 0 | } |