Skip to content

Commit 6d2fc69

Browse files
authored
Edge betweenness-based community detection now computes modularity even for weighted graphs (#836)
1 parent e6479b1 commit 6d2fc69

File tree

3 files changed

+43
-39
lines changed

3 files changed

+43
-39
lines changed

src/_igraph/graphobject.c

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13048,68 +13048,63 @@ PyObject *igraphmodule_Graph_community_edge_betweenness(igraphmodule_GraphObject
1304813048
PyObject *res, *qs, *ms;
1304913049
igraph_matrix_int_t merges;
1305013050
igraph_vector_t q;
13051-
igraph_vector_t *weights = 0;
13051+
igraph_vector_t *weights = NULL;
1305213052

13053-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &directed, &weights_o))
13053+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OO", kwlist, &directed, &weights_o)) {
1305413054
return NULL;
13055+
}
1305513056

13056-
if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights,
13057-
ATTRIBUTE_TYPE_EDGE)) return NULL;
13057+
if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights, ATTRIBUTE_TYPE_EDGE)) {
13058+
return NULL;
13059+
}
1305813060

1305913061
if (igraph_matrix_int_init(&merges, 0, 0)) {
13060-
if (weights != 0) {
13062+
if (weights) {
1306113063
igraph_vector_destroy(weights); free(weights);
1306213064
}
1306313065
return igraphmodule_handle_igraph_error();
1306413066
}
1306513067

1306613068
if (igraph_vector_init(&q, 0)) {
1306713069
igraph_matrix_int_destroy(&merges);
13068-
if (weights != 0) {
13070+
if (weights) {
1306913071
igraph_vector_destroy(weights); free(weights);
1307013072
}
1307113073
return igraphmodule_handle_igraph_error();
1307213074
}
1307313075

1307413076
if (igraph_community_edge_betweenness(&self->g,
13075-
/* removed_edges = */ 0,
13076-
/* edge_betweenness = */ 0,
13077+
/* removed_edges = */ NULL,
13078+
/* edge_betweenness = */ NULL,
1307713079
/* merges = */ &merges,
13078-
/* bridges = */ 0,
13079-
/* modularity = */ weights ? 0 : &q,
13080-
/* membership = */ 0,
13080+
/* bridges = */ NULL,
13081+
/* modularity = */ &q,
13082+
/* membership = */ NULL,
1308113083
PyObject_IsTrue(directed),
1308213084
weights,
13083-
/* lengths = */ 0)) {
13084-
igraphmodule_handle_igraph_error();
13085-
if (weights != 0) {
13085+
/* lengths = */ NULL)) {
13086+
13087+
igraph_vector_destroy(&q);
13088+
igraph_matrix_int_destroy(&merges);
13089+
if (weights) {
1308613090
igraph_vector_destroy(weights); free(weights);
1308713091
}
13088-
igraph_matrix_int_destroy(&merges);
13089-
igraph_vector_destroy(&q);
13090-
return NULL;
13092+
13093+
return igraphmodule_handle_igraph_error();;
1309113094
}
1309213095

13093-
if (weights != 0) {
13096+
if (weights) {
1309413097
igraph_vector_destroy(weights); free(weights);
1309513098
}
1309613099

13097-
if (weights == 0) {
13098-
/* Calculate modularity vector only in the unweighted case as we don't
13099-
* calculate modularities for the weighted case */
13100-
qs=igraphmodule_vector_t_to_PyList(&q, IGRAPHMODULE_TYPE_FLOAT);
13101-
igraph_vector_destroy(&q);
13102-
if (!qs) {
13103-
igraph_matrix_int_destroy(&merges);
13104-
return NULL;
13105-
}
13106-
} else {
13107-
qs = Py_None;
13108-
Py_INCREF(qs);
13109-
igraph_vector_destroy(&q);
13100+
qs = igraphmodule_vector_t_to_PyList(&q, IGRAPHMODULE_TYPE_FLOAT);
13101+
igraph_vector_destroy(&q);
13102+
if (!qs) {
13103+
igraph_matrix_int_destroy(&merges);
13104+
return NULL;
1311013105
}
1311113106

13112-
ms=igraphmodule_matrix_int_t_to_PyList(&merges);
13107+
ms = igraphmodule_matrix_int_t_to_PyList(&merges);
1311313108
igraph_matrix_int_destroy(&merges);
1311413109

1311513110
if (ms == NULL) {
@@ -18531,12 +18526,18 @@ struct PyMethodDef igraphmodule_Graph_methods[] = {
1853118526
"is typically high. So we gradually remove the edge with the highest\n"
1853218527
"betweenness from the network and recalculate edge betweenness after every\n"
1853318528
"removal, as long as all edges are removed.\n\n"
18529+
"When edge weights are given, the ratio of betweenness and weight values\n"
18530+
"is used to choose which edges to remove first, as described in\n"
18531+
"M. E. J. Newman: Analysis of Weighted Networks (2004), Section C.\n"
18532+
"Thus, edges with large weights are treated as strong connections,\n"
18533+
"and will be removed later than weak connections having similar betweenness.\n"
18534+
"Weights are also used for calculating modularity.\n\n"
1853418535
"Attention: this function is wrapped in a more convenient syntax in the\n"
1853518536
"derived class L{Graph}. It is advised to use that instead of this version.\n\n"
1853618537
"@param directed: whether to take into account the directedness of the edges\n"
1853718538
" when we calculate the betweenness values.\n"
1853818539
"@param weights: name of an edge attribute or a list containing\n"
18539-
" edge weights.\n\n"
18540+
" edge weights. Higher weights indicate stronger connections.\n\n"
1854018541
"@return: a tuple with the merge matrix that describes the dendrogram\n"
1854118542
" and the modularity scores before each merge. The modularity scores\n"
1854218543
" use the weights if the original graph was weighted.\n"

src/igraph/community.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,13 @@ def _community_edge_betweenness(graph, clusters=None, directed=True, weights=Non
235235
separate components. The result of the clustering will be represented
236236
by a dendrogram.
237237
238+
When edge weights are given, the ratio of betweenness and weight values
239+
is used to choose which edges to remove first, as described in
240+
M. E. J. Newman: Analysis of Weighted Networks (2004), Section C.
241+
Thus, edges with large weights are treated as strong connections,
242+
and will be removed later than weak connections having similar betweenness.
243+
Weights are also used for calculating modularity.
244+
238245
@param clusters: the number of clusters we would like to see. This
239246
practically defines the "level" where we "cut" the dendrogram to
240247
get the membership vector of the vertices. If C{None}, the dendrogram
@@ -245,7 +252,7 @@ def _community_edge_betweenness(graph, clusters=None, directed=True, weights=Non
245252
@param directed: whether the directionality of the edges should be
246253
taken into account or not.
247254
@param weights: name of an edge attribute or a list containing
248-
edge weights.
255+
edge weights. Higher weights indicate stronger connections.
249256
@return: a L{VertexDendrogram} object, initally cut at the maximum
250257
modularity or at the desired number of clusters.
251258
"""

tests/test_decomposition.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,11 +263,7 @@ def testEdgeBetweenness(self):
263263
g.es["weight"] = 1
264264
g[0, 1] = g[1, 2] = g[2, 0] = g[3, 4] = 10
265265

266-
# We need to specify the desired cluster count explicitly; this is
267-
# because edge betweenness-based detection does not play well with
268-
# modularity-based cluster count selection (the edge weights have
269-
# different semantics) so we need to give igraph a hint
270-
cl = g.community_edge_betweenness(weights="weight").as_clustering(n=2)
266+
cl = g.community_edge_betweenness(weights="weight").as_clustering()
271267
self.assertMembershipsEqual(cl, [0, 0, 0, 1, 1])
272268
self.assertAlmostEqual(cl.q, 0.2750, places=3)
273269

0 commit comments

Comments
 (0)