El agrupamiento (clustering/partitioning) se refiere a la segmentación de un conjunto de elementos en subconjuntos “naturales”.
Una partición \(\mathcal{C}\) de un conjunto finito \(S\) se refiere a una descomposición de \(S\) en \(K\) subconjuntos \(C_1,\ldots,C_K\) de \(S\) tales que: \[ C_k\neq\Phi\,,\qquad C_k\cap C_\ell = \Phi\,,\qquad \cup_{k=1}^K C_k = S\,, \] para \(k,\ell\in\{1,\ldots,K\}\) con \(k\neq \ell\).
La partición de redes (network partitioning) también conocida como detección de comunidades (commuty detection), constituye una metodología no supervisada para encontrar subconjuntos de vértices “homogéneos” respeto a sus patrones relacionales.
Los algoritmos de agrupamiento de grafos establecen una partición \(\mathcal{C}=\{C_1,\ldots,C_K\}\) del conjunto de vértices \(V\) de un grafo \(G=(V,E)\) tal que el conjunto de aristas conectando vértices de \(C_k\) con vértices de \(C_{\ell}\) sea relativamente “pequeño” comparado con el conjunto de aristas conectando vértices dentro de \(C_k\).
Los métodos que se discuten a continuación son algorítmicos y no constituyen modelos estadísticos, y por lo tanto, no permiten cuantificar la incertidumbre asociada con la detección de las comunidades como la formación de enlaces.
Estos métodos adoptan un enfoque computacional intensivo para explorar el espacio de todas las posibles particiones, modificando iterativamente las particiones candidatas.
Los métodos difieren principalmente en la métrica para evaluar la calidad de las agrupaciones y los algoritmos para optimizar tal métrica.
En cada etapa del algoritmo, la partición actual se modifica de manera que se minimice/maximice una función de perdida/utilidad específica mediante la acción menos costosa de fusión/división.
La modulariad de una red respecto a una partición mide qué tan buena es la división o qué tan separados están los diferentes tipos de vértices entre sí: \[ \textsf{mod}(\mathcal{C}) = \frac{1}{2m} \sum_{i,j:i\neq j} \left(y_{i,j} - \tfrac{1}{2m}d_id_j\right)\delta_{c_i,c_j}\,, \] donde:
\(\textsf{mod}(\mathcal{C}) = 0\) si la formación de aristas ocurre sin tener en cuenta las comunidades de la partición de referencia.
Valores más grandes de \(\textsf{mod}(\mathcal{C})\) indican una estructura de comunidades más fuerte.
cluster_fast_greedy
en igraph
.
Clauset, A., Newman, M. E., & Moore, C. (2004). Finding community structure in very large networks. Physical review E, 70(6), 066111.
El método general de agrupación espectral consiste en utilizar un método de agrupación estándar usando los vectores propios más relevantes de la matriz Laplaciana de \(\mathbf{A}\) dada por \(\mathbf{L} = \mathbf{D} - \mathbf{A}\), donde \(\mathbf{A} = [a_{i,j}]\) es una matriz de modularidad y \(\mathbf{D} = \textsf{diag}(d_1,\ldots,d_n)\) es una matriz diagonal con entradas \[ d_i = \sum_{j} a_{i,j}\,. \]
La matriz de modularidad (modularity matrix) de un grafo \(G=(V,E)\) con matriz de adyacencia \(\mathbf{Y}\) corresponde a la matriz \(\mathbf{A} = [a_{i,j}]\), donde \[ a_{i,j} = y_{i,j} - \tfrac{1}{2m}d_id_j\,. \]
Alternativamente, la matriz de modularidad también se puede definir como \[ \mathbf{A} = \mathbf{Y} - \mathbf{P}\,, \] donde \(\mathbf{P}\) contiene las probabilidades de interacción de los vértices bajo el modelo de configuración (configuration model, modelo que genera redes aleatorias de acuerdo con una secuencia grados específica).
cluster_leading_eigen
en igraph
.
Algoritmo | Función en igraph |
Idea |
---|---|---|
Fast-greedy | cluster_fast_greedy |
Optimizar una métrica de modularidad |
Edge-betweenness | cluster_edge_betweenness |
Optimizar una métrica de aristas basada en caminos más cortos |
Leading eigenvector | cluster_leading_eigen |
Calcular el vector propio principal no negativo de la matriz de modularidad |
Louvain | cluster_louvain |
Optimizar una métrica de modularidad múltinivel |
Walktrap | cluster_walktrap |
Caminatas aleatorias cortas tienden a permanecer en la misma comunidad |
Label propagation | cluster_label_prop |
Etiquetar vértices con etiquetas únicas y actualizarlas por votación mayoritaria en la vecindad del vértice |
InfoMAP | cluster_infomap |
Optimizar la longitud esperada de una trayectoria de una caminata aleatoria |
Spinglass | cluster_spinglass |
Modelo spin-glass y simulated annealing |
Optimal | cluster_optimal |
Optimizar una métrica de modularidad |
Cuando se tiene conocimiento de alguna noción de pertenencia a una clase definida externamente, resulta interesante interesante comparar y contrastar las asignaciones resultantes con las que se derivan de la partición.
compare
en igraph
.
Comparación de dos particiones:
rand
: the Rand index (Rand 1971).adjusted.rand
: adjusted Rand index (Hubert
and Arabie 1985).vi
: variation of information (VI) metric
(Meila 2003).nmi
: normalized mutual information measure
(Danon et al. 2005).split.join
: split-join distance (can Dongen
2000).Por ejemplo, el índice Rand tiene un valor entre 0 y 1, donde 0 indica que las dos agrupaciones de datos no coinciden en ningún par de puntos y 1 indica que las agrupaciones de datos son exactamente iguales: \[ \textsf{RI}(X,Y) = \frac{a+b}{a+b+c+d} \] donde:
Considere el modelo de bloques aleatorios dado por \[ y_{i,j}\mid\xi_i,\xi_j,\Theta \sim\textsf{Ber}(\theta_{\phi(\xi_i,\xi_j)})\,,\qquad \text{para} 1\leq i<j\leq n\,, \] donde:
A continuación se genera una red de acuerdo con este modelo con grupos de aproximadamente el mismo tamaño usando \(n = 100\), \(K = 4\) y \[ \Theta = \begin{bmatrix} 0.05 & 0.05 & 0.05 & 0.05 \\ 0.05 & 0.10 & 0.05 & 0.05 \\ 0.05 & 0.05 & 0.20 & 0.05 \\ 0.05 & 0.05 & 0.05 & 0.40 \\ \end{bmatrix}\,. \]
# funciones
#
phi <- function(x, y) c(min(x,y), max(x, y))
#
sim_blocks <- function (n, p, Theta) {
# simulación redes no dirigidas (modelo de bloques estocásticos)
K <- length(p)
xi <- sample(x = 1:K, size = n, replace = T, prob = p)
Y <- matrix(data = 0, nrow = n, ncol = n)
for (i in 1:(n-1)) {
for (j in (i+1):n) {
index <- phi(xi[i], xi[j])
Y[i,j] <- Y[j,i] <- rbinom(n = 1, size = 1, prob = Theta[index[1], index[2]])
}
}
list(Y = Y, xi = xi)
}
# argumentos
n <- 100
p <- rep(0.25, 4)
Theta <- matrix(0.05, 4, 4)
diag(Theta) <- c(0.05, 0.1, 0.2, 0.4)
# simulación
set.seed(1)
tmp <- sim_blocks(n, p, Theta)
Y <- tmp$Y
xi <- tmp$xi
# asignaciones ordenadas
xi2 <- xi[order(xi)]
# matriz de adyacencia ordenada y lineas divisorias de acuerdo con las comunidades
tmp <- get_adjacency_ordered(xi = xi, A = Y)
Y <- tmp$A
d <- tmp$d
g <- graph_from_adjacency_matrix(adjmatrix = Y, mode = "undirected")
# visualización
par(mfrow = c(1,2))
set.seed(123)
plot(g, layout = layout_with_fr, vertex.label = NA, vertex.size = 6, vertex.frame.color = xi2, vertex.color = xi2, main = "")
heat.plot0(mat = Y, labs = F, tick = F)
abline(v = d+.5, h = d+.5)
Ahora, con el fin de establecer la idoneidad de los métodos de
agrupamiento, se agrupa la red simulada utilizando algunos métodos de
agrupamiento y luego se compara la partición real con la partición
recuperado por medio de los métodos:
# agrupamiento
c1 <- cluster_fast_greedy (g)
c2 <- cluster_leading_eigen(g)
c3 <- cluster_walktrap (g)
c4 <- cluster_louvain (g)
# comparación usando el índice de rand
out <- c(compare(comm1 = xi, comm2 = c1$membership, method = "rand"),
compare(comm1 = xi, comm2 = c2$membership, method = "rand"),
compare(comm1 = xi, comm2 = c3$membership, method = "rand"),
compare(comm1 = xi, comm2 = c4$membership, method = "rand"))
names(out) <- c("fast_greedy","leading_eigen","walktrap","louvain")
round(out, 3)
## fast_greedy leading_eigen walktrap louvain
## 0.632 0.579 0.630 0.645
A continuación se remite el experimento del ejemplo anterior \(N = 100\) veces:
# experimento
N <- 100
out <- matrix(NA, nrow = N, ncol = 4)
colnames(out) <- c("fast_greedy","leading_eigen","walktrap","louvain")
set.seed(123)
for (i in 1:N) {
# simular datos
tmp <- sim_blocks(n, p, Theta)
g <- graph_from_adjacency_matrix(adjmatrix = tmp$Y, mode = "undirected")
xi <- tmp$xi
# agrupamiento
c1 <- cluster_fast_greedy (g)
c2 <- cluster_leading_eigen(g)
c3 <- cluster_walktrap (g)
c4 <- cluster_louvain (g)
# comparación
out[i,] <- c(compare(comm1 = xi, comm2 = c1$membership, method = "rand"),
compare(comm1 = xi, comm2 = c2$membership, method = "rand"),
compare(comm1 = xi, comm2 = c3$membership, method = "rand"),
compare(comm1 = xi, comm2 = c4$membership, method = "rand"))
}
# promedio de índice de rand
round(colMeans(out), 3)
## fast_greedy leading_eigen walktrap louvain
## 0.736 0.713 0.759 0.748
# coeficiente de variación
round(apply(X = out, MARGIN = 2, FUN = sd)/colMeans(out), 3)
## fast_greedy leading_eigen walktrap louvain
## 0.048 0.047 0.045 0.040