En este capítulo, exploraremos cómo crear, manipular y visualizar redes utilizando la librería igraph[@csardi2006], el estándar industrial en R, y su integración con el ecosistema moderno de tidygraph[@pedersen2024tidygraph] y ggraph[@pedersen2024ggraph].
7.1 Fundamentos de Representación
Las redes se representan fundamentalmente de dos maneras para su procesamiento computacional:
7.1.1 1. Grafos como Matrices de Adyacencia
La matriz de adyacencia es una representación \(N \times N\) que indica la presencia (1) o ausencia (0) de una conexión. Es clave para el álgebra lineal y el cálculo de caminos.
# Definición manual con rbind (por filas)A <-rbind(c(0,1,0), c(1,0,1), c(1,0,0))# Definición equivalente usando matrix()A_mat <-matrix(c(0,1,0, 1,0,1, 1,0,0), nrow =3, byrow =TRUE)nodeNames <-c("A","B","C")dimnames(A) <-list(nodeNames, nodeNames)dimnames(A_mat) <-list(nodeNames, nodeNames)# Cálculo de Caminos: A^k indica caminos de longitud kA2 <- A %*% A # Caminos de longitud 2A2
A B C
A 1 0 1
B 1 1 0
C 0 1 0
Al elevar la matriz de adyacencia al cuadrado (\(A^2 = A \times A\)), el número resultante en la posición \((i, j)\) indica exactamente cuántos caminos distintos de longitud 2 existen para ir del nodo \(i\) al nodo \(j\).
Por ejemplo, si analizamos las conexiones de la matriz A: - El nodo B se conecta con A y con C. - El nodo C se conecta solo con A. - El nodo A se conecta solo con B.
Si miramos el resultado en la matriz A2 para la fila B, vemos que hay un 1 en la columna A y un 1 en la columna B. Esto significa que partiendo desde B hay un único camino de dos “saltos” para llegar a A (que es el camino \(B \rightarrow C \rightarrow A\)) y un único camino de dos pasos para volver a B (que es ir y volver, \(B \rightarrow A \rightarrow B\)). De la misma forma, desde A hay un camino de longitud 2 hacia C (\(A \rightarrow B \rightarrow C\)).
7.1.2 2. Grafos como Listas de Arcos (Edge Lists)
Una estructura tabular donde cada fila define una conexión entre un nodo de origen (from) y uno de destino (to).
# Lista de Aristas (Tidy)edge_list <-tibble(from =c("Luis", "Ana", "Fran"),to =c("Juan", "Jose", "Amalia"))edge_list
# A tibble: 3 × 2
from to
<chr> <chr>
1 Luis Juan
2 Ana Jose
3 Fran Amalia
7.2 Métodos de igraph para la Construcción
7.3 La función make_graph()
Es una herramienta polivalente para crear grafos manualmente. Dependiendo de sus argumentos, puede: 1. Por lista de aristas:make_graph(edges = c(1,2, 2,3)). 2. Por fórmula:make_graph(~ A-B-C-A). 3. Grafos famosos:make_graph("Zachary"). Argumentos clave: edges (conexiones), n (nº de nodos), directed (si hay flechas). :::
7.3.1 A. Construcción Secuencial
Podemos construir un grafo vacío y añadir elementos con el operador + o eliminar con el operador -.
g <-make_empty_graph(n =0, directed =TRUE) +vertices(c("A","B","C")) +edges(c("A","C", "B","C"))# Eliminar elementosg <- g -V(g)["A"]g
IGRAPH 9e2fe41 DN-- 2 1 --
+ attr: name (v/c)
+ edge from 9e2fe41 (vertex names):
[1] B->C
7.3.2 B. Desde Data Frames e Índices
Es el método más común para importar datos reales.
El Modelo de Erdős-Rényi Llamado así en honor a los matemáticos húngaros Paul Erdős y Alfréd Rényi, quienes lo introdujeron en 1959. Es la forma más pura de generar una red aleatoria (modelo \(G(n, p)\)): se toma un número fijo de nodos (\(n\)) y, para cada par posible, se “lanza una moneda” con probabilidad \(p\) para decidir si se conectan o no. Este modelo sentó las bases de la Teoría de Redes moderna.
# Grafo de Erdos-Renyi (10 nodos, p=0.2)# Genera una red de 10 nodos donde cada par tiene un 20% (p=0.2) de probabilidad de estar conectado, sin dirección y sin conexiones de un nodo hacia sí mismo.g_random <-sample_gnp(10, 0.2, directed =FALSE, loops =FALSE)plot(g_random)
7.4 Estructuras Canónicas y Generación
Para pruebas y validación de algoritmos, igraph proporciona generadores de estructuras clásicas:
plot(make_tree(20, children =3), main ="Árbol")plot(make_full_graph(6), main ="Clique (Grafo Completo)")plot(make_lattice(dimvector =c(5,5)), vertex.label=NA, main ="Malla (Lattice)")plot(make_star(10), main ="Estrella")
7.4.1 Ejercicio Práctico: Construcción de Redes (Aeropuertos Españoles)
Para consolidar los métodos de creación, vamos a modelar las conexiones entre 6 aeropuertos españoles: Madrid (MAD), Barcelona (BCN), Palma de Mallorca (PMI), Málaga (AGP), Alicante (ALC) y Granada (GRX). El aeropuerto de Granada tiene una particularidad: solo tiene conexión directa con Madrid.
Reto: Construye el mismo grafo utilizando dos formatos de entrada distintos y comprueba que el resultado visual es coherente.
7.4.1.1 1. Desde una Lista de Aristas (Dataframe)
# Definición de conexiones (vuelos)vuelos_df <-data.frame(origen =c("MAD", "MAD", "MAD", "MAD", "MAD", "BCN", "BCN", "BCN", "PMI"),destino =c("BCN", "AGP", "PMI", "ALC", "GRX", "AGP", "PMI", "ALC", "ALC"))# Construir el grafog_vuelos1 <-graph_from_data_frame(vuelos_df, directed =FALSE)# Visualizarplot(g_vuelos1, main ="Red de Aeropuertos (Edge List)")
Podemos asociar información a cualquier parte del grafo usando el operador $ (metadatos).
La gestión de metadatos (atributos) a nivel de grafo, nodos (\(V\)) y aristas (\(E\)) es una característica muy importante.
Estos atributos permiten asociar información relevante (como nombres, pesos, colores, distancias o, cualquier otra propiedad a cada elemento de la red.
# V(karate) accede a los vértices, y $name crea la variable# Cargamos el Club de Karate de Zachary# Usamos el generador interno de igraph para cargar el 'Club de Karate de Zachary', # una red clásica que documenta las amistades entre 34 miembros de un club que se dividió en dos facciones.karate <-make_graph("Zachary")# Crear y asignar el nuevo atributo 'name'V(karate)$name <-paste0("N", 1:vcount(karate))V(karate)
# Modificación condicional: Rectángulo para el líder (nodo 1) # y el instructor (nodo 34)V(karate)$shape <-"circle"V(karate)$color <-"lightblue"# En el dataset Zachary, el nodo 1 y 34 son los líderesV(karate)[c(1, 34)]$shape <-"rectangle"V(karate)[c(1, 34)]$color <-"orange"plot(karate, vertex.size =15, main ="Atributos Condicionales")
Podemos modificar atributos de aristas para resaltar relaciones específicas o de forma general:
if(is.null(E(karate)$weight)) {E(karate)$weight <-sample(1:5, ecount(karate), replace =TRUE)}E(karate)$width <-E(karate)$weight *0.5# 1. Configuración de atributos de Vértices (Nodos)V(karate)$size <-15V(karate)$label.color <-"black"V(karate)$label.font <-2# NegritaV(karate)$label.cex <-0.8# Tamaño del texto# 2. Diferenciar a los líderes visualmente# El nodo 1 (Mr. Hi) y el 34 (John A.)V(karate)[c(1, 34)]$shape <-"rectangle"V(karate)[c(1, 34)]$color <-"orange"V(karate)[c(1, 34)]$size <-30# 3. Configuración de Aristas (Edges)# Usamos el peso (weight) para el grosor de las líneasE(karate)$width <-E(karate)$weight *0.5E(karate)$color <-"gray80"E(karate)$curved <-0.2# Un toque de curvatura hace que se vea más orgánico# 4. El Plot final consolidadoplot(karate,main ="Red Social de Zachary: Facciones de Liderazgo",vertex.frame.color ="white", # Borde de los nodosvertex.label.dist =1.2, # Desplaza la etiqueta para que no pise el nodoedge.arrow.size =0, # El karate club es no dirigidolayout =layout_with_kk(karate)) # Algoritmo Kamada-Kawai para mejor distribución
ImportanteEsqueleto vs. Datos Enriquecidos
Notarás que make_graph("Zachary") carga únicamente la topología (quién se conecta con quién) pero no trae metadatos. Por eso, para el siguiente ejemplo usaremos data(karate) del paquete igraphdata, que ya incluye los atributos de Facciones (el bando real de cada miembro).
Ejemplo de colorear aristas si son de la misma facción:
## Un paréntesis: ¿Qué hace `sapply()`?#Si no has usado la familia `apply` en R, piensa en `sapply()` como un **bucle super-eficiente**. Toma una lista o vector y aplica una función a cada uno de sus elementos, devolviéndote los resultados en un vector limpio.# **Ejemplo rápido:**nombres <-c("Ana", "Juan", "Beatriz")# Aplicamos la función 'nchar' (contar letras) a cada nombresapply(nombres, nchar)
Ana Juan Beatriz
3 4 7
En nuestro caso, lo usaremos para recorrer todas las aristas de la red y decidir su color una a una. :::
# 2. Lógica para las aristas (Edges)# Obtenemos los nodos de inicio (from) y fin (to) de cada aristaedge_nodes <-as_edgelist(karate, names =FALSE)# Creamos un vector de colores para las aristasedge_colors <-sapply(1:ecount(karate), function(i) { faccion_nodo1 <-V(karate)$Faction[edge_nodes[i, 1]] faccion_nodo2 <-V(karate)$Faction[edge_nodes[i, 2]]if (faccion_nodo1 == faccion_nodo2) {return(color_facciones[faccion_nodo1]) # Color de la facción si son iguales } else {return("gray80") # Gris si conectan facciones distintas }})# 3. Visualización con estilización avanzadaplot(karate,layout =layout_with_fr(karate), # Layout de fuerzasvertex.size =12,vertex.label =paste0("N", 1:vcount(karate)),vertex.label.cex =0.7,vertex.label.color ="black",vertex.label.dist =1.2,edge.color = edge_colors, # Aplicamos los colores calculadosedge.width =2, # Aristas un poco más gruesasedge.curved =0.2,main ="Coloreado por Facciones (Interno vs Inter-grupal)")# Añadir una leyenda para entender el gráficolegend("topleft", legend =c("Facción Mr. Hi", "Facción John A.", "Inter-facciones"), fill =c("skyblue", "tomato", "gray80"), bty ="n", cex =0.8)
7.5.2 Lógica de Coloreado Condicional de Aristas
Para resaltar la estructura del conflicto en el Club de Karate, no basta con colorear los nodos. El código anterior utiliza una lógica de inspección arista por arista:
Extracción de extremos con as_edgelist: Esta función desmonta el grafo y nos devuelve una matriz de dos columnas donde cada fila es un enlace (columna 1 = origen, columna 2 = destino). Al usar names = FALSE, obtenemos los IDs numéricos de los nodos, lo que facilita la indexación posterior.
Comparación de Atributos: Para cada arista, comprobamos si el nodo de origen y el de destino pertenecen a la misma Faction.
Asignación de Color:
Si pertenecen al mismo bando: La arista toma el color de la facción (representa cohesión interna).
Si pertenecen a bandos distintos: La arista se pinta de gris (representa un “puente” o relación inter-grupal).
¿Por qué la leyenda tiene 3 elementos? Aunque solo hay dos facciones de personas, en el gráfico existen tres tipos de vínculos. La etiqueta “Inter-facciones” es clave para identificar visualmente a aquellos individuos que mantienen lazos con el bando contrario a pesar de la división del club.
7.5.2.1 Atributos de los Vértices (Vertex):
vertex.size = 12: Define el tamaño de los nodos. Un valor fijo asegura uniformidad, aunque podría escalarse según métricas de centralidad.
vertex.label: Determina el texto que aparecerá sobre cada nodo.
vertex.label.cex = 0.7: Controla el tamaño de la fuente de las etiquetas (donde 1 es el tamaño por defecto). Ayuda a que el texto no sature la imagen.
vertex.label.color = “black”: Define el color de la fuente de las etiquetas para asegurar un buen contraste con los colores del nodo.
vertex.label.dist = 1.2: Desplaza la etiqueta del centro del nodo. Un valor mayor a 0 evita que el texto se superponga al círculo del vértice.
Atributos de las Aristas (Edge):
edge.color = edge_colors: Asigna colores a los enlaces. En nuestro caso, permite diferenciar visualmente las conexiones internas de cada facción frente a las conexiones entre grupos.
edge.width = 2: Aumenta el grosor de las líneas de los enlaces para que la estructura de la red sea más visible y clara.
edge.curved = 0.2: Aplica una ligera curvatura a las líneas. Esto evita que las aristas se solapen en grafos densos y le da una estética más orgánica y profesional.
7.5.3 Layouts (Disposición Espacial): El Arte de Visualizar lo Invisible
El algoritmo de “layout” es el encargado de asignar coordenadas \((x, y)\) a cada nodo. Elegir el layout correcto es la diferencia entre ver una “bola de pelo” ininteligible o descubrir la estructura oculta de la red.
Los algoritmos más populares se basan en fuerza dirigida (force-directed). Imaginan la red como un sistema físico: * Los nodos son partículas cargadas que se repelen entre sí. * Las aristas son muelles que atraen a los nodos conectados. * El sistema evoluciona hasta encontrar un estado de equilibrio de mínima energía.
Ejemplo Comparativo: Red de Escala Libre (Barabási-Albert)
El modelo Barabási-Albert (sample_pa) simula el crecimiento de redes reales (como Internet o redes de citación). Sigue el principio de “acoplamiento preferencial”: los nodos nuevos prefieren conectarse a aquellos que ya son populares. Esto resulta en una red con unos pocos “hubs” masivamente conectados y una gran mayoría de nodos con muy pocos enlaces.
Existen decenas de algoritmos de disposición, pero los más utilizados se dividen en dos familias: geométricos y de fuerza dirigida.
Fruchterman-Reingold (layout_with_fr): Algoritmo de fuerza dirigida. Trata los nodos como partículas que se repelen y las aristas como muelles que los atraen. Es el estándar para visualizar clusters y comunidades de forma orgánica.
Kamada-Kawai (layout_with_kk): Basado en la minimización de la energía de resortes. A diferencia de FR, tiene en cuenta la distancia de los caminos más cortos entre todos los pares de nodos, intentando que la distancia geométrica en el dibujo sea proporcional a su distancia lógica en la red. Esto suele generar distribuciones más equilibradas y simétricas.
Circular (layout_in_circle): Coloca los nodos en un círculo. Es útil para redes pequeñas donde el orden es importante o cuando queremos ver la densidad de conexiones sin que los nodos se solapen en el centro.
Grid (layout_on_grid): Distribuye los nodos en una cuadrícula regular. Útil para inspecciones rápidas donde la estructura topológica no es prioritaria, asegurando que ningún nodo pise a otro.
# 1. Generamos red sintética (Barabasi-Albert)# n=100 nodos, cada nuevo nodo trae m=2 enlaces.set.seed(42)g_ba <-sample_pa(n =100, power =1.2, m =2, directed =FALSE)# Layout 1: Fruchterman-Reingold (Fuerza dirigida)# El estándar de oro [@fruchterman1991]. Equilibra bien atracción y repulsión.plot(g_ba, layout =layout_with_fr(g_ba), vertex.size =5, vertex.label =NA, main ="Fruchterman-Reingold (Force-Directed)")# Layout 2: Kamada-Kawai (Energía de resortes) [@kamada1989]# Intenta que la distancia geométrica sea proporcional a la distancia de caminos.plot(g_ba, layout =layout_with_kk(g_ba), vertex.size =5, vertex.label =NA, main ="Kamada-Kawai (Spring-Based)")# Layout 3: Círculo# Útil para ver densidad de aristas, pero oculta la estructura interna.plot(g_ba, layout =layout_in_circle(g_ba), vertex.size =5, vertex.label =NA, main ="Circular Layout")# Layout 4: Grid (Cuadrícula)# Muestra los nodos ordenados pero ignora la topología.plot(g_ba, layout =layout_on_grid(g_ba), vertex.size =5, vertex.label =NA, main ="Grid Layout")
Figura 7.1: Comparativa de algoritmos de disposición espacial
Figura 7.2: Comparativa de algoritmos de disposición espacial
Figura 7.3: Comparativa de algoritmos de disposición espacial
Figura 7.4: Comparativa de algoritmos de disposición espacial
7.5.3.1 Resumen: ¿Qué layout elegir?
Algoritmo
Mejor uso (Use Case)
Fortalezas
Fruchterman-Reingold (FR)
Redes sociales, comunidades y clústeres.
Revela la estructura orgánica y grupos.
Kamada-Kawai (KK)
Redes pequeñas o con distancias críticas.
Refleja muy bien la proximidad lógica (caminos cortos).
Círculo (Circle)
Redes muy densas o procesos cíclicos.
Evita el solapamiento de nodos en el centro de la imagen.
Cuadrícula (Grid)
Inspección individual de nodos.
Útil para ver a todos los actores sin importar sus lazos.
Como vemos, los algoritmos de fuerza dirigida (panel superior) revelan que hay unos pocos “hubs” centrales a los que se conectan la mayoría de nodos periféricos, una característica típica de las redes libres de escala que el layout circular o grid no logran mostrar.
7.5.4 C. Ejemplos Avanzados: Mapeo por Índices y Agrupaciones
El verdadero potencial de la estilización viene al aplicar estilos diferentes a subconjuntos de nodos o aristas basados en sus atributos (e.g., ser vocal, pertenecer a una comunidad).
Mapeo de Atributos por Condición Lógica
Utiliza un vector lógico para indexar los nodos que cumplen una condición.
# Grafo baseg <-make_ring(10, directed=TRUE, mutual=TRUE)V(g)$name <- LETTERS[1:10]g <- g +edges(9,5, 7,1, 1,5)lo <-layout_in_circle(g)# Identificar vocalesisVowel <-V(g)$name %in%c("A","E","I","O","U")# Asignación de atributos generales y luego sobreescritura condicionalV(g)$shape <-"circle"# Forma generalV(g)$size <-15V(g)$color <-"orange"# Sobreescribir los nodos vocales (isVowel = TRUE)V(g)[isVowel]$shape <-"square"V(g)[isVowel]$color <-"royalblue"V(g)[isVowel]$size <-25
ImportanteSecuencias de Vértices (V(g))
Cuando hacemos v1 <- V(g)[isVowel], no estamos creando una simple lista de números, sino una Vertex Sequence. igraph es inteligente: si luego borras un nodo, esta secuencia se mantiene vinculada a los objetos originales, no solo a su posición.
plot(g, layout=lo, main="Mapeo de Atributos (Vocales vs. Consonantes)")
Propiedades Condicionales de Arcos
# Restaurar el índice de vocalesisVowel <-V(g)$name %in%c("A","E","I","O","U")v1 <-V(g)[isVowel]E(g)$width <-1# Grosor general# Aumentar el grosor de arcos que conectan solo a nodos vocales.# El operador %--% es bidireccional: busca (Vocal -> Vocal) en ambos sentidos.E(g)[v1 %--% v1]$width <-4plot(g, layout=lo, main="Grosor de Arcos entre Vocales")
# Arcos curvados condicionalmente (ej: mutuos vs. no mutuos)# which_mutual(g) da TRUE/FALSE si el arco es mutuoplot(g, layout=lo, edge.curved=0.3*which_mutual(g), main="Curvatura Condicional de Arcos")
Importante:
A. Operadores de Selección de Aristas (E(g))
Estos operadores permiten seleccionar aristas que conectan conjuntos específicos de nodos (llamémoslos conjunto A y conjunto B).
El Operador Bidireccional: %--%
* **Función:** Selecciona todas las aristas que conectan el conjunto A con el B, sin importar la dirección (A -> B y B -> A).
* **Uso:** `E(g)[A %--% B]`. Es ideal para grafos no dirigidos o para analizar "relaciones totales" entre dos grupos.
El Operador Unidireccional: %->%
* **Función:** Sensible a la dirección. Selecciona solo las aristas que nacen en A y mueren en B.
* **Uso:** `E(g)[A %->% B]`. Perfecto para redes de jerarquía o flujos de información.
El Operador Inverso: %<-%
* **Función:** Selecciona solo las aristas que nacen en B y mueren en A.
TipTruco de Selección
Puedes usar estos operadores combinados con lógica de nodos. Por ejemplo, E(g)[V(g)[color=="blue"] %--% V(g)[color=="red"]] seleccionaría todos los puentes entre nodos azules y rojos.
Ayuda:
Identificación de Nodos mediante Lógica de Conjuntos
Crea un vector lógico (TRUE/FALSE) comparando el nombre de cada nodo con el conjunto de vocales.
``v1 <- V(g)[isVowel]``
Crea un subconjunto de vértices que contiene únicamente los nodos identificados como vocales.
Modificación Selectiva de Atributos
``E(g)$width <- 1``
Establece un valor por defecto (grosor fino) para todas las aristas del grafo.
``E(g)[v1 %--% v1]$width <- 4``
Sobrescribe el grosor únicamente para las aristas que cumplen la condición de “conectar vocal con vocal”, haciéndolas 4 veces más gruesas.
B. Agrupaciones Visuales (mark.groups)
# 3. Agrupaciones Visuales (mark.groups)# Definición de los grupos de nodos (por índices)# which(isVowel) convierte el vector TRUE/FALSE en los números de índice de los nodos.groupList <-list(vowelGroup =which(isVowel), constGroup1 =c(2,3,4),constGroup2 =c(6,7,8))# Definimos colores usando rgb(). El 4º parámetro (0.5) es el 'alpha' o transparencia.# Es vital en redes para poder ver las aristas que pasan por debajo del área sombreada.groupColours <-c(rgb(0,0.3,1,0.5), # Azul transparentergb(0.8,0.4,0.1,0.5), rgb(0.8,0.4,0.1,0.5))# mark.groups dibuja una "Envoltura Convexa" (Convex Hull) rodeando a los nodos.plot(g, layout=layout_with_fr, mark.groups=groupList, mark.col= groupColours, mark.border =NA, edge.curved=0.1*which_mutual(g),main="Visualización de Grupos (mark.groups)")
Ayuda:
Marcado de Comunidades (Envolturas Convexas) - mark.groups = groupList: Es el parámetro clave para visualizar clusters. igraph dibuja un polígono sombreado (llamado Convex Hull o envoltura convexa) rodeando a los nodos.
Nota¿Qué es una Envoltura Convexa (Hull)?
Imagina que los nodos son chinchetas en una pared. Si pones una goma elástica alrededor de un grupo de chinchetas, la forma que adopta la goma al tensarse es la “envoltura convexa”. Es la forma más eficiente de agrupar visualmente un conjunto de puntos.
>> mark.col = groupColours: Define el color de relleno de las áreas sombreadas de los grupos. Se recomienda usar colores con transparencia (canal alfa) para que las aristas del fondo sigan siendo visibles.
>> mark.border = NA: Elimina la línea de contorno de los polígonos de grupo. Esto ayuda a que la visualización sea más limpia y menos saturada si hay muchos grupos superpuestos.
Dinámica de las Aristas
edge.curved = 0.1 * which_mutual(g): Este es un truco técnico avanzado.
which_mutual(g) devuelve un vector lógico que indica si una conexión es recíproca (bidireccional). Al multiplicarlo por 0.1, solo las aristas que van y vuelven entre dos nodos se curvan, evitando que las dos líneas se solapen y permitiendo ver ambas direcciones claramente.
TipResumen de la Sesión 2
En este bloque hemos aprendido a: 1. Construir redes desde cero (matrices y dataframes). 2. Generar redes sintéticas (Erdős-Rényi y Barabási-Albert). 3. Manipular atributos de nodos y aristas de forma condicional. 4. Aplicar layouts (disposiciones espaciales) según el objetivo analítico. 5. Estilizar grupos mediante envolturas convexas (mark.groups).
7.6 El Paradigma Tidygraph: Redes como Dataframes
Hasta ahora hemos manipulado los grafos usando la sintaxis tradicional de igraph (matrices, el operador $, selectores complejos como %--%). Aunque potente, puede resultar anti-intuitiva para quienes están acostumbrados al mundo tidyverse.
El paquete tidygraph revoluciona el análisis al tratar la red no como un objeto monolítico, sino como dos tablas vinculadas (tibbles): una para los Nodos (nodes) y otra para las Aristas (edges). Esto permite usar todos los verbos de dplyr (mutate, filter, arrange) mediante saltos (pipes%>%) para iterar el grafo.
7.6.1 1. El Objeto tbl_graph y activate()
Para manipular un grafo al estilo tidy, primero debemos convertirlo (o crearlo) en un objeto tbl_graph. Como tiene dos tablas internas, el verbo clave es activate(), que le dice a R sobre qué tabla vamos a operar.
library(tidygraph)# Convertimos nuestro grafo de Zachary a tidygraphkarate_tidy <-as_tbl_graph(karate)# Al imprimirlo, vemos explícitamente las dos tablas:karate_tidy
Al trabajar con redes tidy, es vital distinguir entre estos dos verbos de dplyr:
select(columna): Mantiene la estructura de tabla. Devuelve un tibble con esa columna.
pull(columna): Extrae los valores. Devuelve un vector (una lista de datos sueltos).
Si necesitas obtener el nombre de un nodo específico para una función externa, usa pull(name).
7.6.2 2. Flujos de Trabajo Tidy (Integración de Algoritmos)
La gran ventaja de tidygraph es que envuelve casi todos los algoritmos complejos de igraph para que funcionen dentro del flujo de datos de tidyverse-dplyr, sin necesidad de especificar el nombre del grafo constantemente ni usar el símbolo $.
7.6.2.1 Cuadro de Mando: ¿Qué métrica elegir?
Métrica
Objetivo Analítico
¿Qué estamos midiendo realmente?
centrality_degree()
Popularidad / Actividad
Nº de conexiones directas. El “hub” de la red.
centrality_betweenness()
Control / Intermediación
Capacidad de ser un puente entre grupos. Control del flujo.
centrality_closeness()
Eficiencia / Rapidez
Cercanía media a todos los demás. Rapidez de difusión.
centrality_pagerank()
Influencia / Prestigio
Calidad de las conexiones (no solo cuántas, sino con quién).
7.6.2.2 Criterios de Selección de Métricas:
Para identificar actores con alta visibilidad o liderazgo de opinión (popularidad) \(\rightarrow\) utilizar Degree.
Para identificar puntos de articulación o actores que controlan de forma crítica el flujo de información \(\rightarrow\) utilizar Betweenness.
Para identificar la ubicación óptima de recursos o la máxima velocidad de difusión \(\rightarrow\) utilizar Closeness.
Para identificar el prestigio o autoridad dentro de una estructura jerárquica \(\rightarrow\) utilizar PageRank.
ImportanteDiferencia Técnica: degree() vs centrality_degree()
Aunque calculan matemáticamente lo mismo, su uso cambia radicalmente tu eficiencia al programar:
igraph::degree(g): Es una función “huérfana”. Debes pasarle el objeto g manualmente y te devuelve un vector independiente. Si luego filtras el grafo, el vector ya no coincide.
centrality_degree(): Es una función “contextual”. Solo funciona dentro de mutate() y sabe automáticamente sobre qué grafo está operando. Esto garantiza que la métrica siempre esté sincronizada con la tabla de nodos, incluso si realizas filtrados complejos en el mismo pipe.
Los métodos renombrados nativamente en tidygraph empiezan por centrality_* o group_*.
NotaVerbos dplyr conscientes de la red
En tidygraph, los verbos de dplyr no son meros alias. Son funciones conscientes de la topología que mantienen la coherencia del grafo automáticamente:
Verbo
Acción en la Red
Ejemplo Práctico
filter()
Crea subgrafos inteligentes
filter(!node_is_isolated())
mutate()
Calcula métricas algorítmicas
mutate(comu = group_infomap())
arrange()
Ordena la inspección visual
arrange(desc(centrality_pagerank()))
Nota¿Qué hace group_infomap()?
Es un algoritmo de detección de comunidades. Utiliza la teoría de la información para encontrar grupos de nodos donde un “caminante aleatorio” pasaría la mayor parte de su tiempo. Es decir, identifica “barrios” densamente conectados dentro de la red.
En el ecosistema tradicional, eliminar un nodo obligaba a reindexar manualmente las aristas. Con filter(), la red se reestructura en tiempo real sin romper los punteros internos.
grafo_procesado <- karate_tidy %>%# 1. Operamos sobre los Nodosactivate(nodes) %>%mutate(grado =centrality_degree(), # Calcula centralidadcomunidad =as.factor(group_infomap()), # Algoritmo de faccioneses_lider = grado >15# Booleano lógico ) %>%# node_is_isolated() detecta nodos sin ninguna conexión - útil para limpiar la redfilter(!node_is_isolated()) %>%# 2. Saltamos a operar sobre las Aristasactivate(edges) %>%mutate(# 'Betweenness' mide cuántos caminos cortos pasan por cada arista.# Valores altos indican "puentes" críticos o cuellos de botella.puente =centrality_edge_betweenness() ) %>%filter(puente >2) # Filtramos la red: eliminamos aristas poco importantes# Visualización del resultado tras el procesamiento Tidyplot(grafo_procesado, vertex.size =V(grafo_procesado)$grado, vertex.color =V(grafo_procesado)$comunidad,vertex.label.cex =0.8,main ="Red tras Mutate y Filter (Tidygraph)")
TipEl poder de filter() en Tidygraph
A diferencia de igraph clásico, donde borrar aristas suele requerir pasos extra para no perder la coherencia, en tidygraph puedes usar filter() directamente. Al eliminar aristas (como en el ejemplo anterior con los “puentes”), la estructura del grafo se actualiza automáticamente.
Tip💡 Verificar la capa activa con .active()
La activación es un estado persistente. Una vez que activas nodos, el objeto se comporta como un tibble de nodos hasta indicar lo contrario. Para consultas en flujos automatizados, puedes usar .active() para verificar programáticamente qué tabla está en foco en ese momento.
whichTable <-.active(karate_tidy) # Devuelve "nodes" o "edges"
7.6.3 3. Cruzar vértices: El Pronombre .N()
A menudo, al procesar la tabla de Aristas (relaciones), necesitamos saber quién está en los extremos de esa relación.
El pronombre mágico .N() permite entrar a la tabla de Nodos sin salir de la tabla de Aristas.
Nota¿Qué son from y to?
Cuando activas las aristas (activate(edges)), tienes acceso implícito a dos variables que existen en todas las redes:
from: El ID numérico del nodo de origen.
to: El ID numérico del nodo de destino.
Al hacer .N()$Atributo[from], estamos diciendo: “Busca en la tabla de Nodos (.N()) el valor de Atributo para el nodo cuyo ID coincide con el origen (from) de esta arista”.
Imagina que quieres calcular si una arista une a dos personas de la misma facción o de facciones distintas:
grafo_evaluado <- karate_tidy %>%activate(edges) %>%mutate(# .N()$Faction lee el atributo 'Faction' de la tabla Nodos# [from] y [to] indexan al NODO DE ORIGEN y al NODO DE DESTINO de la arista actualfac_origen =.N()$Faction[from],fac_destino =.N()$Faction[to],es_cruce_bando = fac_origen != fac_destino )# Visualizamos el resultado: resaltamos en rojo las aristas que unen bandos distintosplot(grafo_evaluado, edge.color =ifelse(E(grafo_evaluado)$es_cruce_bando, "red", "gray80"),edge.width =ifelse(E(grafo_evaluado)$es_cruce_bando, 3, 1),vertex.label =NA, vertex.size =7,main ="Detección de Enlaces entre Facciones (.N)")
7.6.4 4. Transformaciones Temporales: morph()
Para operaciones analíticas complejas, tidygraph permite manipular la topología mediante un flujo de trabajo basado en Vistas Temporales. Este mecanismo, denominado morphing, permite aislar subconjuntos lógicos del grafo, operar sobre ellos y consolidar los resultados en la estructura original.
TipArquitectura de Morphing (Perspectiva de Ingeniería de Datos)
El flujo de trabajo se divide en tres fases técnicas:
Proyección (morph): Crea una Vista (View) o proyección temporal del grafo basándose en un algoritmo (ej: to_shortest_path). Esta vista es un objeto vivo que mantiene la referencia a la identidad de los nodos y aristas originales.
Operación de Estado (mutate): Se realizan transformaciones de datos o cálculos algorítmicos sobre la vista activa. Estos cambios son locales a la proyección y no alteran el grafo original de forma inmediata.
Sincronización y Commit (unmorph): Ejecuta un “merge” de los atributos calculados en la vista hacia el grafo original, sincronizando los datos por sus punteros internos. Una vez consolidada la información, la vista temporal se destruye (drop view).
NotaTipos de Transformaciones Comunes
Existen diversas funciones para generar estas vistas:
morph(to_shortest_path, from, to) → Camino Crítico: Aísla la ruta mínima entre dos nodos. Permite identificar los nodos de paso obligatorios para la transferencia de información.
7.6.4.1 Catálogo de Proyecciones Comunes (Morphers)
Función de Proyección
Resultado de la Vista Temporal
Aplicación Práctica
to_shortest_path()
Ruta mínima entre dos nodos.
Identificación de rutas críticas y mediadores.
to_local_neighborhood()
Ego-red (nodo y sus vecinos cercanos).
Análisis de influencia local de un actor.
to_contracted()
Agregación de nodos por atributo.
Colapsar comunidades o sedes en súper-nodos.
to_components()
División en subgrafos inconexos.
Análisis independiente de “islas” de la red.
to_linegraph()
Aristas convertidas en nodos.
Estudio de la topología de los vínculos.
to_minimum_spanning_tree()
Esqueleto de conectividad mínima.
Optimización de rutas y redes de distribución.
Si se requiere persistir la vista como un objeto independiente (ej: guardar subgrafos por comunidades), se utiliza la función crystallise().
7.6.4.2 Ejemplos Técnicos por Algoritmo
A. Análisis de Camino Crítico (Shortest Path): Identificación de la ruta de transferencia de información entre dos actores específicos.
# 1. Creamos la proyección del camino más corto (VIEW)# 2. Activamos y marcamos tanto nodos como aristas en la vista# 3. Consolidamos en el grafo original (COMMIT)grafo_camino <- karate_tidy %>%morph(to_shortest_path, from =1, to =34) %>%activate(nodes) %>%mutate(en_ruta_critica =TRUE) %>%activate(edges) %>%mutate(en_ruta_critica =TRUE) %>%unmorph()# Visualización: La marca 'en_ruta_critica' ahora reside en el grafo padreplot(grafo_camino, edge.color =ifelse(!is.na(E(grafo_camino)$en_ruta_critica), "red", "gray80"),edge.width =ifelse(!is.na(E(grafo_camino)$en_ruta_critica), 4, 1),vertex.color =ifelse(!is.na(V(grafo_camino)$en_ruta_critica), "red", "gold"),main ="Análisis de Camino Crítico (Morphing)")
Análisis de Camino Crítico mediante Proyección de Vistas (Morphing)
B. Ego-Red (Local Neighborhood): Resaltar contactos directos.
C. Contracción (Contracted): Análisis macro por grupos.
grafo_macro <- karate_tidy %>%morph(to_contracted, Faction) %>%mutate(nombre_grupo =paste0("Bando ", Faction)) %>%unmorph()# Visualización (El grafo resultante tiene solo 2 nodos)plot(grafo_macro, vertex.label =V(grafo_macro)$nombre_grupo,vertex.size =30,main ="Análisis Macro (Contracción de Nodos)")
D. Esqueleto (Minimum Spanning Tree): Identificación de la infraestructura mínima de conectividad global. A diferencia del camino corto, este algoritmo no busca conectar dos puntos, sino determinar qué aristas son las más importantes para que nadie en la red quede desconectado.
Extrae el “esqueleto troncal” eliminando redundancias, lo que permite identificar los vínculos vitales que mantienen la integridad de todo el sistema.
A diferencia de unmorph(), que devuelve los cambios al grafo padre, crystallise() se utiliza cuando se requiere extraer las vistas y convertirlas en objetos independientes.
Por ejemplo, si se desea segmentar una red en sus distintos componentes conexos y procesar cada uno como un grafo autónomo:
# Obtenemos una lista de grafos independientes (uno por cada componente conexo)lista_componentes <- karate_tidy %>%morph(to_components) %>%crystallise()# Inspección de la estructura resultantelista_componentes
# A tibble: 1 × 2
name graph
<chr> <list>
1 1 <tbl_grph>
Este comando genera un tibble donde cada registro almacena un objeto tbl_graph estructuralmente independiente.
7.6.5 5. Modificaciones Estructurales
Mientras que mutate y filter se utilizan para el análisis descriptivo, los verbos de topología permiten alterar la anatomía física del grafo.
7.6.5.1 A. Inserción de Datos: bind_nodes() y bind_edges()
Se utilizan para añadir nuevos elementos a una red ya existente. Es el equivalente a rbind() en tablas.
# Supongamos la inserción de un nuevo nodo (ID 35)karate_extendido <- karate_tidy %>%bind_nodes(data.frame(name ="Nuevo Socio", Faction =1)) %>%# Y lo conectamos con el líder (Mr. Hi, que es el ID 1)bind_edges(data.frame(from =35, to =1))karate_extendido
Es uno de los verbos más potentes. Permite fusionar dos grafos distintos en uno solo, uniendo los nodos que tengan el mismo identificador (normalmente la columna name).
Caso de uso: Fusión de redes con diferentes tipos de relación entre los mismos actores (redes multicapa).
# Creamos una red pequeña de 'colaboración'red_extra <- tidygraph::create_star(n =5) %>%mutate(name =paste0("Actor ", 1:5))# La fusionamos con nuestra red principalred_total <- karate_tidy %>%graph_join(red_extra, by ="name")red_total
Permite cambiar el origen (from) o el destino (to) de una relación sin tener que borrarla y crearla de nuevo.
Caso de uso: En una simulación dinámica, cuando un vínculo se desplaza hacia un nuevo objetivo.
# Movemos una arista del nodo 1 al nodo 2red_cambiada <- karate_tidy %>%activate(edges) %>%slice(1) %>%# Seleccionamos la primera aristareroute(from =1, to =2) red_cambiada
# A tbl_graph: 34 nodes and 1 edges
#
# An unrooted forest with 33 trees
#
# Edge Data: 1 × 3 (active)
from to weight
<int> <int> <dbl>
1 1 2 4
#
# Node Data: 34 × 4
Faction name label color
<dbl> <chr> <chr> <chr>
1 1 N1 H skyblue
2 1 N2 2 skyblue
3 1 N3 3 skyblue
# ℹ 31 more rows
TipAnálisis vs. Modificación Estructural
Usa verbos de análisis (mutate, activate) para la extracción de métricas, y verbos topológicos (bind, join, reroute) para la edición de la estructura o la integración de fuentes externas.
7.6.6 6. Flujo de trabajo - resumen
A continuación se presenta el flujo de trabajo recomendado para el análisis de redes moderno:
Carga y Validación: Importar con as_tbl_graph(). Verificar si es simple, dirigido y si requiere soporte de 64 bits.
Activación de Capa: Definir si el objetivo son los actores (nodes) o los vínculos (edges).
Análisis Algorítmico: Aplicar métricas siguiendo la convención snake_case.
Todas las centralidades empiezan por centrality_*.
Todos los algoritmos de comunidades empiezan por group_*.
Ventaja: Esto facilita el autocompletado en RStudio y hace el código más predecible.
Limpieza Estructural: Usar filter() con funciones como node_is_isolated().
Refinamiento Temporal:morph() para caminos cortos o contracción de comunidades.
Visualización Directa: Mapear métricas a estéticas en ggraph sin crear columnas auxiliares (NSE).
# PIPELINE INTEGRAL: Flujo de trabajo completolibrary(tidygraph)library(ggraph)# El flujo completo de un analista modernomi_red <-as_tbl_graph(karate) %>%# [PASO 1] Calcular métricas de nodos y limpiar nodos aisladosactivate(nodes) %>%mutate(degree =centrality_degree(),comunidad =group_infomap() ) %>%filter(!node_is_isolated()) %>%# [FASE 2] Identificación de ruta crítica entre actores clavemorph(to_shortest_path, from =1, to =34) %>%activate(nodes) %>%mutate(en_ruta_critica =TRUE) %>%activate(edges) %>%mutate(en_ruta_critica =TRUE) %>%unmorph()# [FASE 3] Visualización mediante Evaluación No Estándar (NSE)ggraph(mi_red, layout ="stress") +geom_edge_link(aes(alpha =centrality_edge_betweenness()), color ="gray60") +geom_node_point(aes(size = degree, color =factor(comunidad))) +scale_color_viridis_d(option ="plasma", name ="Comunidad") +labs(title ="Pipeline Integral: Análisis del Karate Club") +theme_graph()
7.7 Mapeo Declarativo con ggraph
Mientras que la función clásica plot() de igraph es útil empíricamente, las exigencias académicas y de BI modernas requieren gráficos publicables. ggraph extiende la “Gramática de Gráficos” de ggplot2 a los grafos.
Dado que ggraph está integrado nativamente con tidygraph, cualquier red tbl_graph puede pasarse directamente al motor de ploteo.
7.7.1 1. La Arquitectura de Capas de ggraph
Al igual que en ggplot2, empezamos declarando el lienzo y el algoritmo (layout), y sumamos capas visuales con el operador +.
ImportanteEl poder del NSE (Non-Standard Evaluation)
Una de las mayores ventajas de ggraph es que puedes mapear métricas directamente dentro del aes() sin haberlas calculado antes en un mutate. Por ejemplo: geom_node_point(aes(size = centrality_degree())). ggraph entiende que debe activar la tabla de nodos, calcular el grado y mapearlo al tamaño en un solo paso.
Layout: La red en un plano cartesiano. Se recomienda layout="stress" para redes generales, ya que utiliza escalamiento multidimensional (MDS) para evitar que los nodos se amontonen, siendo más estable que “fr”.
Edges (Aristas): Capas geom_edge_*.
Nodes (Nodos): Capas geom_node_*.
library(ggraph)# Ejemplo de construcción básica por capasp_base <-ggraph(karate_tidy, layout ="fr") +geom_edge_link(color ="gray80") +geom_node_point(size =5, color ="steelblue") +theme_graph() # Elimina ejes y fondo por defectop_base
7.7.1.1 Guía de Referencia: Elementos de ggraph
Tipo de Capa
Función / Opción
Uso Recomendado
Layout (Algoritmo)
layout = "stress"
Estándar: El más equilibrado para redes generales.
layout = "fr"
Clásico (Fruchterman-Reingold). Nodos más dispersos.
layout = "circle"
Estructural: Resalta la topología circular o anillos.
layout = "tree"
Jerárquico: Ideal para organigramas o taxonomías.
Edges (Aristas)
geom_edge_link()
Relaciones simples (una línea recta).
geom_edge_fan()
Multígrafos: Abre en abanico enlaces múltiples entre nodos.
geom_edge_parallel()
Redes multicapa: Desplaza lateralmente tipos de relación.
geom_edge_arc()
Estético: Enlaces curvos (muy usado en layouts circulares).
geom_edge_diagonal()
Jerárquico: Líneas suavizadas para árboles y dendrogramas.
Nodes (Nodos)
geom_node_point()
Representación estándar por puntos/círculos.
geom_node_text()
Etiquetas de texto simples (para redes pequeñas).
geom_node_label()
Etiquetas con caja de fondo (mayor legibilidad).
7.7.1.2 Batería de Ejemplos: Estética y Layout
1. Layout circle + geom_edge_arc + geom_node_point Ideal para redes densas donde queremos evitar el cruce de líneas en el centro.
2. Layout stress + geom_edge_link + geom_node_label Legibilidad máxima para identificar nodos por nombre.
ggraph(karate_tidy, layout ="stress") +geom_edge_link(alpha =0.2) +geom_node_label(aes(label = name, fill =factor(Faction)), repel =TRUE, size =3, color ="white", show.legend =FALSE) +theme_graph()
TipEl Layout “Stress” y el MDS
A diferencia de otros algoritmos basados en fuerzas físicas, stress utiliza Escalamiento Multidimensional (MDS). Esto significa que calcula las distancias lógicas (caminos más cortos) entre todos los nodos e intenta que la distancia visual en la pantalla sea proporcional a esa distancia real. Es el algoritmo más estable y fiel a la estructura matemática de la red.
3. Multígrafos: geom_edge_fan Evita el solapamiento cuando hay múltiples vínculos entre los mismos actores. Ideal cuando existen múltiples interacciones (ej: 5 correos) entre los mismos dos actores. geom_edge_fan() evita que las líneas se solapen.
# Simulamos un multígrafomultigrafo <-create_notable('bull') %>%as_tbl_graph() %>%bind_edges(data.frame(from =c(1,1,1), to =c(2,2,2)))ggraph(multigrafo, layout ="stress") +geom_edge_fan(color ="firebrick", alpha =0.5) +geom_node_point(size =5) +theme_graph()
4. Redes Multicapa: geom_edge_parallel Diferenciación visual de tipos de relación (ej: Amistad vs Trabajo). Permite ver distintos tipos de relación (ej: Amistad y Trabajo) de forma paralela.
# Definimos una red bicapa rápida para el ejemploedges_para <-data.frame(from =c(1,2,3,1,2), to =c(2,3,1,3,1), tipo =c("Amistad","Amistad","Amistad","Trabajo","Trabajo"))g_para <-as_tbl_graph(edges_para)ggraph(g_para, layout ="stress") +geom_edge_parallel(aes(color = tipo), sep =unit(5, "pt"), width =1.2) +geom_node_point(size =4) +theme_graph()
5. Layout fr + geom_node_text Anotación ligera. check_overlap = TRUE evita que los nombres se pisen. text es más limpio para redes densas; label (con fondo) es mejor para resaltar nombres clave.
7.7.2 2. El Peligro de las Conexiones Múltiples: geom_edge_fan()
Cuando dos nodos interactúan múltiples veces en la misma dirección (ej: intercambian varios correos distintos), usar geom_edge_link() dibuja las líneas una encima de otra, ocultando el volumen y peso de las interacciones. Para grafos complejos, el estándar de seguridad analítico es geom_edge_fan(), que las despliega en abanico.
# DEFINICIÓN DEL OBJETO (Evita el error 'object not found')red_correos_masivos <- tidygraph::create_notable('bull') %>% tidygraph::as_tbl_graph() %>% tidygraph::activate(edges) %>% tidygraph::bind_edges(data.frame(from =c(1,1,1), to =c(2,2,2))) # Visualizaciónggraph(red_correos_masivos, layout ="stress") +geom_edge_fan(aes(alpha =after_stat(index)), color ="firebrick", show.legend =FALSE) +geom_node_point(size =4) +theme_graph() +labs(title ="Visualización de Multígrafos (geom_edge_fan)",subtitle ="Múltiples interacciones entre los mismos nodos")
7.7.3 2b. Redes Multicapa: geom_edge_parallel()
En redes donde coexisten varios tipos de relación entre los mismos nodos (ej: amistad y colaboración laboral), ni geom_edge_link() ni geom_edge_fan() son adecuados: el primero los superpone y el segundo los infla. La solución es geom_edge_parallel(), que desplaza lateralmente las aristas de cada tipo para que sean distinguibles simultáneamente.
Motivación real: En estudios de redes organizacionales, una empresa puede tener entre sus empleados lazos de comunicación informal, jerarquía formal y colaboración en proyectos. Visualizarlas en capas separadas revela si alguien es un broker en todas las capas (SPOF crítico) o solo en alguna.
library(tidygraph)library(ggraph)library(igraph)# ── Simulamos una red bicapa: mismos nodos, dos tipos de relación ──set.seed(7)n_nodos <-8# Capa 1: lazos de AMISTAD (red dispersa)edges_amistad <-data.frame(from =c(1, 1, 2, 3, 4, 5, 6),to =c(2, 3, 4, 5, 6, 7, 8),tipo ="Amistad")# Capa 2: lazos de TRABAJO (algunos nodos comparten ambos tipos)edges_trabajo <-data.frame(from =c(1, 2, 3, 4, 7),to =c(4, 5, 6, 8, 8),tipo ="Trabajo")# Unimos en un único grafo multigraphedges_total <-rbind(edges_amistad, edges_trabajo)g_bicapa <-graph_from_data_frame(edges_total, directed =FALSE,vertices =data.frame(id =1:n_nodos))g_tidy <-as_tbl_graph(g_bicapa)# ── Visualización multicapa con geom_edge_parallel ────────────────ggraph(g_tidy, layout ="stress") +# geom_edge_parallel desplaza lateralmente cada tipo de aristageom_edge_parallel(aes(color = tipo, linetype = tipo),width =1.2, sep =unit(4, "pt"),arrow =NULL, show.legend =TRUE) +geom_node_point(size =8, color ="steelblue", alpha =0.9) +geom_node_text(aes(label = name), color ="white", size =3.5, fontface ="bold") +scale_edge_color_manual(values =c("Amistad"="#E76F51", "Trabajo"="#2A9D8F")) +scale_edge_linetype_manual(values =c("Amistad"="solid", "Trabajo"="dashed")) +labs(title ="Red Bicapa: Amistad vs. Colaboración Laboral",subtitle ="geom_edge_parallel() separa visualmente cada tipo de lazo",edge_color ="Tipo de lazo") +theme_graph()
🔍 Análisis del resultado: Los nodos que reciben aristas de ambos colores simultáneamente (naranja sólida + verde punteada) son los actores más integrados en la organización: su rol no depende de un único tipo de capital social, sino de su posición en múltiples capas. Por el contrario, un nodo con solo aristas de un tipo es especialista en esa dimensión pero vulnerable si esa capa se debilita.
Insight de negocio: En análisis de churn organizacional, un empleado que solo tiene lazos de trabajo (sin amistad) es el de mayor riesgo de abandono: si el proyecto termina, no tiene razones relacionales para quedarse.
7.7.4 3. Evaluación No Estándar (NSE)
En igraph clásico, para pintar un nodo según su importancia numérica, antes debías calcular la importancia, guardarla como columna $atributo en el objeto R, y luego referenciarla en el plot.
La característica fundamental de ggraph es su integración con tidygraph mediante Evaluación No Estándar (NSE) dentro de la función aes(). Esto permite ejecutar algoritmos matemáticos de forma dinámica durante la fase de representación, manteniendo el flujo de código optimizado.
ggraph(karate_tidy, layout ="kk") +geom_edge_link(alpha =0.2) +# CAPACIDAD NSE: Calculamos el grado (centrality_degree) de forma dinámica,# sin necesidad de guardarlo previamente en el dataframe original.geom_node_point(aes(size =centrality_degree(), color =as.factor(group_infomap()))) +scale_color_viridis_d(option ="plasma", name="Facciones") +labs(title ="Evaluación No Estándar en ggraph",subtitle ="Tamaño = Grado calculado on-the-fly | Color = Comunidad (Infomap)") +theme_graph()
7.7.5 4. Estética de Aristas Dirigidas
Para representar la direccionalidad en redes densas, el uso de puntas de flecha de gran tamaño suele saturar la visualización en grafos de gran escala. Una técnica de visualización contemporánea consiste en aplicar una transición de degradado (alpha gradient): la arista se proyecta con alta transparencia en el nodo de origen y aumenta su opacidad al alcanzar el nodo de destino.
# Convertimos el grafo original (por defecto no dirigido) a dirigido temporalmente # solo para poder visualizar la estética direccional en el ejemplo.karate_tidy_dirigido <-as_tbl_graph(as.directed(karate))ggraph(karate_tidy_dirigido, layout ="circle") +# Usar stat(index) crea ese desvanecimiento desde origen hasta destino de forma automáticageom_edge_fan(aes(alpha =after_stat(index)), color ="darkred", show.legend =FALSE) +geom_node_point(size =3) +theme_graph() +labs(title ="Direccionalidad Inteligente con Transparencia")
Al heredar de ggplot2, si tienes una red mixta (ej: clientes de Madrid vs. Barcelona, o redes de distintos años), ya no hace falta escribir 5 bloques plot() distintos combinándolos a mano. El verbo facet_edges() o facet_nodes() clona el grafo completo y resalta la parte de la sub-población asignada.
# Primero usamos dplyr para inyectar una variable aleatoria de "Sede" o Paísset.seed(42)karate_multi <- karate_tidy %>%activate(nodes) %>%mutate(Sede =sample(c("Gimnasio Norte", "Gimnasio Sur"), n(), replace =TRUE))# Ahora usamos face_nodes para generar dos mapas independientes instantaneamenteggraph(karate_multi, layout ="stress") +geom_edge_link(alpha =0.4) +geom_node_point(color ="orange", size =4) +facet_nodes(~ Sede) +theme_graph() +theme(strip.text =element_text(size =12, face ="bold"))
7.8 Exportación de Datos
Para análisis externos, el formato recomendado es GraphML (basado en XML).
write_graph(karate, "karate_club.graphml", format ="graphml")