Curso: Análisis Estadístico de Redes
Profesor: Juan Sosa, Ph.D.
Bogotá D.C., Colombia
13 de September de 2026
La presente relatoría presenta un compendio de las clases del curso Análisis Estadístico de Redes Sociales, correspondiente al período académico 2026-II de la Maestría en Ciencias Estadísticas. Su objetivo es detallar algunos de los conceptos fundamentales de la teoría de grafos abordados durante las clases y presentes en el material del curso, disponible en El material del curso se encuentra disponible en Conceptos fundamentales de la teoría de grafos,elaborado por el profesor Juan Camilo Sosa. Estos contenidos se complementan con ejemplos y bibliografía adicional con el propósito de facilitar la comprensión de los conceptos estudiados.
En esta relatoría se abordará el tercer capítulo del curso, en el cual se presentan y desarrollan conceptos fundamentales de la teoría de grafos que serán necesarios para comprender los temas que se estudiarán posteriormente en el curso. Se hará énfasis en las definiciones, propiedades y representaciones de los grafos, acompañadas de ejemplos que permitan ilustrar su aplicación en el contexto del análisis de redes.
Un \(\textbf{grafo}\) es un par \(G=(V,E)\) formado por dos conjuntos, donde \(E\subseteq [V]^2\). Esto significa que los elementos de \(E\) son subconjuntos de \(V\) que contienen exactamente dos elementos. Para evitar ambigüedades en la notación, se supone siempre que \(V\cap E=\varnothing\) (Diestel 2017).
Los elementos de \(V\) se denominan \(\textbf{vértices}\) (también llamados nodos o puntos) del grafo \(G\).
Los elementos de \(E\) se conocen como \(\textbf{aristas}\) (o enlaces).
Una forma habitual de representar gráficamente un grafo consiste en dibujar un punto por cada vértice y conectar mediante una línea aquellos pares de puntos cuyos vértices correspondientes forman una arista. Lo fundamental es determinar \(\textbf{qué pares de vértices están conectados por una arista y cuáles no}\).
Ejemplo tomado de Diestel, Graph Theory, quinta edición, 2017. pg.2
En este ejemplo podemos observar un grafo con 7 vértices, cuyo conjunto de vértices está dado por \(V=\{1,2,3,4,5,6,7\}.\)
El conjunto de aristas corresponde a los siguientes enlaces: \(E=\{\{1,2\},\{1,5\},\{2,5\},\{3,4\},\{5,7\}\}.\)
Sea \(G'=(V',E')\). Decimos que \(G'\) es un \(\textbf{subgrafo}\) de \(G\) si, para \(G=(V,E)\), se cumple que \(V'\subseteq V\) y, al mismo tiempo, \(E'\subseteq E\)
De manera equivalente podemos deinir la noción de \(\textbf{supergrafo}\), donde \(G\) es un super grafo de \(G'\) por el hecho de que \(V'\subseteq V\) y \(E'\subseteq E\)
Nota: Por facilidad de notación se usa \(G′ \subset G\) para indicar que \(G′\) es un subgrafo de \(G\).
Sea \(S\subset V\) un subconjunto de vértices. El \(\textbf{subgrafo inducido}\) por \(S\) es el grafo \(G_S=(S,E_S)\) que contiene exactamente los vértices de \(S\) y todas y solo las aristas del grafo original cuyos extremos pertenecen a \(S\) Formalmente, \[E_S=\{\{u,v\}\in E:u\in S,v\in S\}.\]
En otras palabras, un subgrafo inducido se obtiene al seleccionar un subconjunto de vértices \(S\subseteq V\) y conservar $$. Por lo tanto, una vez seleccionado el conjunto de vértices, el conjunto de aristas queda completamente determinado.
Ejemplo tomado de Diestel, Graph Theory, quinta edición, 2017. pg.4
En este ejemplo \(G\) es supergrafo de \(G'\) y \(G''\) (lo que es equivalente a decir que \(G′\) and \(G′′\) son subgrafos de \(G\) ) Tenemos que \(G′\) es un grafo inducido de \(G\), pero \(G′′\) no lo es.
Vamos a usar la librería igraph (Csardi and Nepusz 2006) para todos los ejemplos a utilizar.
#Carga de librerias
suppressMessages(suppressWarnings(library(sand)))
suppressMessages(suppressWarnings(library(igraph)))
suppressMessages(suppressWarnings(library(RColorBrewer)))
suppressMessages(suppressWarnings(library(corrplot)))
suppressMessages(suppressWarnings(library(dplyr)))
suppressMessages(suppressWarnings(library(jsonlite)))
suppressMessages(suppressWarnings(library(readxl)))
suppressMessages(suppressWarnings(library(igraphdata)))
Vamos a crear un grafo binario no dirigido simple, desce cero con vértices \(V=\{1,2,3,4,5,6,7\}\) y aristas \(E= \left\{ \{1,2\}, \{1,3\},\{2,3\}, \{2,4\}, \{3,5\}, \{4,5\},\{3,4\}, \{3,6\}, \{4,6\}, \{4,7\}, \{5,6\}, \{6,7\} \right\}\)
Este grafo fue usado en el libro guía: Statistical Analysis of Network Data (Kolaczyk and Csárdi 2020)
# Grafo G
g <- graph_from_literal(
1-2, 1-3, 2-3, 2-4, 3-5, 4-5,
3-4, 3-6, 4-6, 4-7, 5-6, 6-7
)
# Subgrafo inducido H por los vértices 1,...,5
h <- induced_subgraph(g, 1:5)
# Configuración de la figura
par(mfrow = c(1, 2), mar = c(1, 1, 3, 1))
# Graficar G
set.seed(123)
plot(
g,
layout = layout_with_fr(g),
vertex.color = "#4C78A8",
vertex.frame.color = "white",
vertex.frame.width = 2,
vertex.size = 30,
vertex.label.color = "white",
vertex.label.cex = 1.2,
edge.color = "#777777",
edge.width = 2,
main = "Grafo G"
)
# Graficar H
set.seed(123)
plot(
h,
layout = layout_with_fr(h),
vertex.color = "#F58518",
vertex.frame.color = "white",
vertex.frame.width = 2,
vertex.size = 30,
vertex.label.color = "white",
vertex.label.cex = 1.2,
edge.color = "#777777",
edge.width = 2,
main = "Subgrafo inducido H"
)
# Restaurar configuración
par(mfrow = c(1, 1))
Sean \(u,v \in V\)
Adyacencia Se dice que dos vértices son adyacentes (denotado como \(u \sim v\)), si estan unidos por alguna arista de \(E\), es decir si \(\{u,v\} \in E\) (relación vértice - vértice)
Incidencia Un vértice \(v\) es incidente con una arista \(e \in E\) si \(e=\{u,v\}\) para algún \(u\in V.\)
Vecindad: Es el conjunto de vértices adyacentes a \(v\) , se denota como \(N_v= \{u \in V : u\sim v\}.\)
El grado de un vértice \(v\) es el número de aristas incidentes en \(v\) y coincide con la cardinalidad de su vecindad: \(d_v = |N_v|.\) Es una medida de centralidad de la red que permite definir una noción de importancia de los vértices: los nodos más importantes de la red serán aquellos que tengan un mayor grado.
Un vértice \(v\in V\) se llama aislado si \(v\nsim u \hspace{0.1cm} \forall_{u\in V}\). Note que esto es equivalente a que \(d_v=0\).
Sea \(G=(V,E)\) podemos definir \(\delta_G := \min \{d_v | v \in V \}\) así como \(\Delta_G := \max \{d_v | v \in V \}\), que corresponden al grado mínimo y al grado máximo de \(G\), respectivamente. Estas medidas permiten identificar los vértices menos conectados y más conectados de la red.
Otra cantidad de interés es el grado promedio de \(G\), denotado por \(\bar{d}_G:= \frac{1}{|V|} \sum_{v \in V} d_v\)
Mientras que el grado de un vértice describe su nivel de conectividad (lo veremos en un momento) de manera local, el grado promedio resume esta información a nivel global para todo el grafo.
En un digrafo, podemos distinguir entre la vecindad de entrada y la vecindad de salida de un vértice \(v \in V\). La vecindad de entrada de \(v\) es el conjunto de vértices que tienen una arista dirigida hacia \(v\): \(N_v^{in}=\{u\in V:(u,v)\in E\}.\)
A partir de este conjunto, se define el grado de entrada de \(v\) como : \(d_v^{in}=|N_v^{in}|,\) que corresponde al número de aristas que llegan a \(v\). Decimos que un nodo es popular si su grado de entrada es, en promedio, mayor que el de los demás nodos de la red.
De manera análoga, la vecindad de salida de \(v\) es el conjunto de vértices hacia los cuales \(v\) tiene una arista dirigida:\(N_v^{out}=\{u\in V:(v,u)\in E\}.\)
El grado de salida de \(v\) se define como \(d_v^{out}=|N_v^{out}|\) y corresponde al número de aristas que salen de \(v\).Decimos que un nodo es social si su grado de salida es, en promedio, mayor que el de los demás nodos de la red.
Considérese el siguiente digrafo:
dg <- graph_from_literal(
1-+2, 1-+3, 1-+4,
2-+3, 2-+5,
3-+5,
4-+2,
6-+3,
7
)
# Tamaño , Orden , Tipo de red
cat(
"Orden (número de nodos):", vcount(dg), "\n",
"Tamaño (número de aristas):", ecount(dg), "\n",
"¿Es dirigida?:", is_directed(dg), "\n",
"¿Es ponderada?:", is_weighted(dg), "\n",
"¿Es simple?:", is_simple(dg), "\n")
## Orden (número de nodos): 7
## Tamaño (número de aristas): 8
## ¿Es dirigida?: TRUE
## ¿Es ponderada?: FALSE
## ¿Es simple?: TRUE
par(mfrow = c(1,1), mar = c(1, 1, 2, 1))
set.seed(123)
plot(
dg,
vertex.color = "skyblue",
vertex.size = 30,
vertex.label.cex = 1.2,
edge.arrow.size = 0.4,
main = "Digrafo"
)
# Grado de entrada
degree(dg, mode = "in")
## 1 2 3 4 5 6 7
## 0 2 3 1 2 0 0
# Grado de salida
degree(dg, mode = "out")
## 1 2 3 4 5 6 7
## 3 2 1 1 0 1 0
#Grado Maximo y Promedio:
cat("Grado promedio:",mean_degree(dg),"\n", "Grado maximo:", max_degree(dg))
## Grado promedio: 1.142857
## Grado maximo: 4
par(mfrow = c(1,2))
#Distribución de grados de entrada vs grados de salida en dg
hist(
degree(dg, mode = "in"),
breaks = seq(-0.5, max(degree(dg, mode="in"))+0.5, 1),
col = "#F58518",
main = "Grado de entrada",
xlab = "d_in"
)
hist(
degree(dg, mode = "out"),
breaks = seq(-0.5, max(degree(dg, mode="out"))+0.5, 1),
col = "#4C78A8",
main = "Grado de salida",
xlab = "d_out"
)
Ahora vamos a convertir a ese grafo dirigido en uno no-dirigido:
g_ud <- as_undirected(dg)
Encontremos entonces vértices adyacentes, aislados y aristas incidentes
# Vértices aislados
V(g_ud)[degree(g_ud) == 0]
## + 1/7 vertex, named, from 123b596:
## [1] 7
# Vértices adyacentes a 2
(nb2 <- neighbors(g_ud, v = 2))
## + 4/7 vertices, named, from 123b596:
## [1] 1 3 4 5
# Aristas incidentes en el vértice 2
(ic2 <- incident(g_ud, v = 2, mode = "all"))
## + 4/8 edges from 123b596 (vertex names):
## [1] 1--2 2--3 2--4 2--5
# Colores de vértices
v_col <- rep("lightblue", vcount(g_ud))
v_col[2] <- "khaki"
v_col[as.numeric(nb2)] <- "khaki"
# Colores de aristas
e_col <- rep("grey70", ecount(g_ud))
e_col[ic2] <- "khaki"
# Visualización
par(mfrow = c(1,1), mar = c(1,1,2,1))
par(mfrow = c(1,1), mar = c(1, 1, 2, 1))
set.seed(123)
plot(
g_ud,
layout = layout_with_fr(g_ud),
vertex.color = v_col,
vertex.size = 30,
vertex.label.cex = 1.2,
vertex.frame.color = "white",
edge.color = e_col,
edge.width = ifelse(e_col == "khaki", 3, 1),
main = "Vecindad del vértice 2"
)
Una caminata (walk) de \(v_0\) a \(v_\ell\) de longitud \(\ell\) es una secuencia alternante de vértices y aristas \(\{v_0,e_1,v_1,e_2,v_2,\ldots,v_{\ell-1},e_\ell,v_\ell\}\) tal que, para cada \(i = 1,\ldots,\ell\), los extremos de \(e_i\) son \(\{v_{i-1},v_i\}\). En una caminata se permiten vértices y aristas repetidos. Notemos que el orden de una caminata de longitud \(\ell\) coincide con el número de aristas en la caminata.
Una caminata cerrada es aquella en la que el vértice inicial coincide con el vértice final, es decir, \(v_0=v_{\ell}\), por ejemplo: \[1 \rightarrow 2 \rightarrow 3 \rightarrow 2 \rightarrow 4 \rightarrow 2 \rightarrow 1\] .
En caso contrario decimos que es una caminata abierta, por ejemplo: \[1 \rightarrow 2 \rightarrow 3 \rightarrow 2 \rightarrow 4\].
Un Recorrido (trail) es una caminata abierta en la que no se repite ninguna arista (aunque si pueden repetirse vértices).
Un Camino (path) es un recorrido en el que no se repite ningún vértice. (Es equivalente a una caminata abierta en la que No se pueden repetir vértices ni aristas)
Se dice que un vértice \(v\) es accesible (reachable) desde otro vértice \(u\) si existe al menos una caminata desde \(u\) hasta \(v\).(Kolaczyk and Csárdi 2020)
Se dice que un grafo está conectado (connected) si cada vértice es accesible desde cualquier otro. (Kolaczyk and Csárdi 2020), es decir si entre cada par de vértices \(u,v\), existe una caminata que los une.
Un dígrafo está débilmente conectado (weakly connected) si su grafo subyacente, obtenido al ignorar la dirección de las aristas, es conectado.
Un dígrafo está fuertemente conectado (strongly connected) si, para todo par de vértices \(u,v\) existe una caminata dirigida de \(u\) a \(v\) (\(u\) → \(v\)) y de \(v\) a \(u\) (\(v\) → \(u\)). Es decir, si cada vértice es accesible desde cualquier otro mediante aristas dirigidas.
Una componente conexa (connected component) o simplemente componente de un grafo es un subgrafo conexo maximal, es decir, un subgrafo conexo al que no se le puede añadir ningún otro vértice del grafo sin perder la conectividad (o, equivalentemente, sin dejar de ser conexo). Que un subgrafo sea conexo máximal no implica que todos los vértices esten conectados entre todos.
Un grafo con tres componentes conexas, Ejemplo tomado de Diestel, Graph Theory, quinta edición, 2017. pg.11
La componente gigante (giant component) de un grafo es la componente conexa de mayor tamaño, es decir, la componente que contiene el mayor número de vértices. En otras palabras La componente gigante es el subgrafo más grande de una red, en el que todos los nodos son accesibles desde cualquier otro mediante algún camino. En grafos grandes, suele ser la componente que agrupa una fracción significativa del total de vértices y concentra la mayor parte de los caminos del grafo.
Las Asignaciones (Memberships) de las componentes indican la pertenencia de cada nodo a una componente. En particular, \(\xi_i=k\) significa que el (i)-ésimo nodo pertenece a la componente \(k\), donde \(i=1,2,\ldots,n=|V|\) y \(\quad k=1,2,\ldots,K\), siendo \(K\) el número total de componentes.
\[ \boldsymbol{\xi} = \begin{pmatrix} \xi_1\\ \xi_2\\ \vdots\\ \xi_n \end{pmatrix} \]
De manera equivalente, la componente \(C_k\) se puede definir como el conjunto de nodos cuya asignación es \(k\) :\(\quad C_k= \left\{i \in V: \xi_i =k \right\}\)
library(igraph)
# 1. Red
set.seed(123)
g <- graph_from_literal(
A--B, A--C, A--D, B--C, B--E, B--F,
C--D, C--F, D--F, D--G, E--F, E--H,
F--G, F--H, G--H, G--I, H--I, H--J,
I--J, I--K, J--K, J--L, K--L, K--M,
L--M, M--N, M--O,
P--Q, P--R, Q--R, Q--S, R--S, S--T,
U,
V
)
is_connected(g)
## [1] FALSE
# 2. Encontrar la componente gigante
comp <- components(g)
comp
## $membership
## A B C D E F G H I J K L M N O P Q R S T U V
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 4
##
## $csize
## [1] 15 5 1 1
##
## $no
## [1] 4
giant_id <- which.max(comp$csize)
set.seed(123)
g_giant <- induced_subgraph(
g,
vids = V(g)[comp$membership == giant_id]
)
# 3. Calcular layouts
set.seed(123)
lay_g <- layout_with_fr(g)
set.seed(123)
lay_giant <- layout_with_fr(g_giant)
# 4. Comparar la red completa y la componente gigante
par(
mfrow = c(1, 2),
mar = c(1, 1, 3, 1)
)
set.seed(123)
plot(
g,
layout = lay_g,
vertex.size = 10,
vertex.color = "steelblue",
vertex.frame.color = "white",
vertex.label = NA,
edge.color = "grey60",
edge.width = 1.5,
main = "Red completa"
)
set.seed(123)
plot(
g_giant,
layout = lay_giant,
vertex.size = 10,
vertex.color = "firebrick2",
vertex.frame.color = "white",
vertex.label = NA,
edge.color = "grey60",
edge.width = 1.5,
main = "Componente gigante"
)
La red representa el patrón de citación entre 146 blogs relacionados con el VIH/sida, los pacientes y sus redes de apoyo, recopilados por Suchi Gopal (Gopal 2007) durante un período de tres días seleccionado aleatoriamente en agosto de 2005. Los vértices representan los blogs y las aristas dirigidas representan las relaciones de referencia entre ellos: una arista desde un blog hacia otro indica que el primero incluye un enlace al segundo en su página web, específicamente dentro de su lista de enlaces o blogroll.
data(aidsblog)
g <- aidsblog
g$name <- "AIDS Blog Network"
## This graph was created by an old(er) igraph version.
## ℹ Call `igraph::upgrade_graph()` on it to use with the current igraph version.
## For now we convert it on the fly...
# Tamaño , Orden , Tipo de red
cat(
"Orden (número de nodos):", vcount(g), "\n",
"Tamaño (número de aristas):", ecount(g), "\n",
"¿Es dirigida?:", is_directed(g), "\n",
"¿Es ponderada?:", is_weighted(g), "\n",
"¿Es simple?:", is_simple(g), "\n")
## Orden (número de nodos): 146
## Tamaño (número de aristas): 187
## ¿Es dirigida?: TRUE
## ¿Es ponderada?: FALSE
## ¿Es simple?: FALSE
cat("Red Conectada débilmente?:", is_connected(graph = g, mode = "weak"), "\n",
"Red Conectada fuertemente?:", is_connected(graph = g, mode = "strong"))
## Red Conectada débilmente?: TRUE
## Red Conectada fuertemente?: FALSE
set.seed(123)
lay <- layout_with_fr(g)
plot(
g,
layout = lay,
vertex.size = 7,
vertex.label = NA,
vertex.color = V(g)$color,
vertex.frame.color = "white",
edge.color = "gray70",
edge.arrow.size = 0.15,
edge.width = 1,
main = "Red de Blogs sobre VIH/SIDA"
)
# Componentes débilmente conectadas
components(g, mode = "weak")
## $membership
## [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [75] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [112] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##
## $csize
## [1] 146
##
## $no
## [1] 1
# Componentes débilmente conectadas
components(g, mode = "strong")
## $membership
## [1] 143 142 141 140 139 138 28 65 27 137 26 78 136 135 64 25 37 63
## [19] 134 24 62 23 77 76 133 132 100 131 130 61 75 60 74 99 73 22
## [37] 1 98 129 128 97 127 72 10 59 21 126 71 125 70 124 9 58 20
## [55] 96 69 95 123 68 67 19 57 18 122 17 66 8 94 93 16 92 36
## [73] 50 91 121 90 89 120 35 119 118 88 87 49 56 48 7 6 15 34
## [91] 33 117 47 55 54 46 116 115 86 5 14 85 32 114 84 45 113 83
## [109] 82 81 80 13 112 111 12 44 11 2 4 110 3 53 43 109 52 108
## [127] 107 106 105 42 41 40 39 28 38 104 51 103 102 31 101 28 28 79
## [145] 30 29
##
## $csize
## [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 1 1 1 1 1 1 1 1 1
## [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [75] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [112] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
##
## $no
## [1] 143
Una noción común de distancia entre dos vértices \(u\) y \(v\) de un grafo, denotada por \(d(u,v)\), se define como la longitud del camino más corto entre ellos. Si no existe ningún camino que conecte los dos vértices, la distancia se define como infinita, es decir, \(d(u,v) = \infty\). A menudo, esta distancia se denomina distancia geodésica, y geodésico es el término utilizado para referirse a un camino más corto (Kolaczyk and Csárdi 2020).
El diámetro de un grafo \(G=(V,E)\), denotado por \(\operatorname{diam}(G)\), es la mayor distancia geodésica finita entre cualquier par de vértices \(u,v\in V\): \(\operatorname{diam}(G)=\max_{u,v\in V}d(u,v)\).
La distancia geodésica promedioes el promedio de las distancias geodésicas entre pares de vértices conectados y proporciona una medida del grado de separación global de los vértices del grafo.
La excentricidad de un vértice \(v\) se define como \(\epsilon(v) = \operatorname{max}_u d(u,v)\) , y el diámetro es el máximo de las excentricidades.
El conjunto de datos French Political Blogs corresponde a una subred de blogs políticos franceses extraída de una recopilación superior a 1.100 blogs observados durante octubre de 2006.
Los blogs fueron clasificados según su afiliación política por el proyecto Observatoire Présidentielle. La red contiene:
summary(fblog)
## This graph was created by an old(er) igraph version.
## ℹ Call `igraph::upgrade_graph()` on it to use with the current igraph version.
## For now we convert it on the fly...
## IGRAPH 3e87bca UN-- 192 1431 --
## + attr: name (v/c), PolParty (v/c)
V(fblog)$PolParty <- trimws(V(fblog)$PolParty)
party.names <- unique(V(fblog)$PolParty)
head(party.names)
## [1] "Les Verts" "UMP"
## [3] "UDF" "PS"
## [5] "Parti Radical de Gauche" "PCF - LCR"
La variable PolParty contiene la afiliación política de
cada blog y será utilizada para diferenciar visualmente los nodos
mediante colores.
Para generar una representación adecuada de la red se utiliza el algoritmo de distribución de Kamada-Kawai, ampliamente empleado en análisis de redes por su capacidad para ubicar nodos relacionados en posiciones cercanas.
La Visualización muestra la estructura relacional de los blogs políticos franceses. Cada color representa una afiliación política distinta.
Veamos la componente gigante de G
#Componente de g
comp <- components(fblog)
#Componente gigante
giant_id <- which.max(comp$csize)
g_giant <- induced_subgraph(
fblog,
vids = V(fblog)[comp$membership == giant_id]
)
# Tamaño , Orden , Tipo de red
cat(
"Orden (número de nodos):", vcount(g_giant), "\n",
"Tamaño (número de aristas):", ecount(g_giant), "\n",
"Diametro componente gigante:",diameter(g_giant, directed = TRUE), "\n",
"Distancia promedio componente gigante:",mean_distance(g_giant, directed = TRUE), "\n",
"Conectada componente gigante?:", is_connected(g_giant)
)
## Orden (número de nodos): 192
## Tamaño (número de aristas): 1431
## Diametro componente gigante: 5
## Distancia promedio componente gigante: 2.538667
## Conectada componente gigante?: TRUE
# Partidos políticos y colores asociados
party_colors <- c(
"Les Verts" = "forestgreen",
"UMP" = "royalblue",
"UDF" = "deepskyblue3",
"PS" = "red3",
"Parti Radical de Gauche" = "deeppink",
"PCF - LCR" = "darkred",
"liberaux" = "steelblue",
"Commentateurs Analystes" = "gray50",
"Cap21" = "darkorange"
)
# Asignar color a cada nodo según su partido
V(g_giant)$color <- party_colors[V(g_giant)$PolParty]
set.seed(42)
# Layout
lg <- layout_with_kk(g_giant)
plot(
g_giant,
layout = lg,
vertex.label = NA,
vertex.color = V(g_giant)$color,
vertex.size = 8,
edge.color = "gray70",
edge.width = 0.5,
main = "Red de Blogs Políticos Franceses"
)
# Leyenda
legend(
"topleft",
legend = names(party_colors),
fill = party_colors,
bty = "n",
cex = 0.7,
y.intersp = 0.8
)
Red de Blogs Políticos Franceses
Esta red representa las interacciones en Twitter entre los miembros del 117.º Congreso de los Estados Unidos, incluyendo tanto la Cámara de Representantes como el Senado. Los datos fueron recopilados mediante la API de Twitter y se utilizaron para estimar las probabilidades empíricas de transmisión entre congresistas, calculadas a partir de la frecuencia con la que un miembro retuiteó, citó, respondió o mencionó las publicaciones de otro. Para conocer más detalles sobre la metodología empleada y el análisis realizado, se recomienda consultar la publicación original. (Fink et al. 2023)
edges <- read.table(
"/cloud/project/Relatoria 2/congress.edgelist",
header = FALSE
)
edges[,4] <- gsub("}", "", edges[,4])
g <- graph_from_data_frame(
d = edges[,1:2],
directed = TRUE
)
E(g)$weight <- as.numeric(edges[,4])
Sin embargo no hay suficiente información nodal de interés, con la ayuda de la inteligencia artificial Gemini 3.6 Flash (Google DeepMind 2026)y la lista de miembros del 117 congreso:Lista de Miembros del 117 Congreso de USA vamos a agregar información nodal como el probable partido politico de pertenencia, recordemos que nuestros nodos son cuentas de Twitter (ahora X).
nodal_info_joined<-read_excel("/cloud/project/Relatoria 2/usuarios_congreso_actualizado.xlsx")
tail(nodal_info_joined)
Añadiendo esta información adicional a nuestra red:
#añadiendo info del partido
V(g)$party <- trimws(nodal_info_joined$partido)
#añadiendo info de la camara (senate or house of representatives)
V(g)$chamber<-trimws(nodal_info_joined$camara)
#añadiendo nombres de congresistas
V(g)$congressperson<-trimws(nodal_info_joined$congresista)
# Tamaño , Orden , Tipo de red, Diametro, Distancia promedio
cat(
"Orden (número de nodos):", vcount(g), "\n",
"Tamaño (número de aristas):", ecount(g), "\n",
"¿Es dirigida?:", is_directed(g), "\n",
"¿Es ponderada?:", is_weighted(g), "\n",
"¿Es simple?:", is_simple(g), "\n",
"Diametro",round(diameter(g, directed = TRUE),3), "\n",
"Distancia promedio", round(mean_distance(g, directed = TRUE),3), "\n")
## Orden (número de nodos): 475
## Tamaño (número de aristas): 13289
## ¿Es dirigida?: TRUE
## ¿Es ponderada?: TRUE
## ¿Es simple?: TRUE
## Diametro 0.031
## Distancia promedio 0.006
set.seed(123)
# ============================================================
# 1. Seleccionar el 10% de nodos con mayor grado
# ============================================================
deg <- degree(g, mode = "all")
umbral <- quantile(deg, 0.90)
g_top <- induced_subgraph(
g,
vids = which(deg >= umbral)
)
# ============================================================
# 2. Atributos visuales de los nodos
# ============================================================
# 1. Atributos visuales de los nodos
# ============================================================
# Colores según partido político
party_colors <- c(
"Demócrata" = "dodgerblue3",
"Republicano" = "firebrick2",
"Independiente" = "gold"
)
V(g_top)$color <- party_colors[V(g_top)$party]
V(g_top)$color[is.na(V(g_top)$color)] <- "grey70"
# Forma según cámara
V(g_top)$shape <- ifelse(
V(g_top)$chamber == "Senado",
"square",
"circle"
)
# Tamaño según grado
deg_top <- degree(g_top)
V(g_top)$size <- 3 * sqrt(deg_top)
# ============================================================
# 3. Escalar los pesos de las aristas DE g_top
# ============================================================
w <- E(g_top)$weight
# Evitar problemas si todos los pesos son iguales
if (max(w, na.rm = TRUE) == min(w, na.rm = TRUE)) {
w_scaled <- rep(0.5, length(w))
} else {
w_scaled <- (w - min(w, na.rm = TRUE)) /
(max(w, na.rm = TRUE) - min(w, na.rm = TRUE))}
# ============================================================
# 4. Color de las aristas
# ============================================================
edge_cols <- colorRampPalette(
c("grey90", "grey75", "grey50")
)(100)
edge_color_base <- edge_cols[
pmax(
1,
pmin(
100,
round(1 + 99 * w_scaled)
)
)
]
edge_rgb <- col2rgb(edge_color_base) / 255
# Transparencia
edge_alpha <- 0.05 + 0.22 * w_scaled
edge_color <- rgb(
edge_rgb[1, ],
edge_rgb[2, ],
edge_rgb[3, ],
alpha = edge_alpha
)
# ============================================================
# 5. Etiquetar solo los 2 nodos más importantes
# ============================================================
top2 <- order(
deg_top,
decreasing = TRUE
)[1:min(2, length(deg_top))]
labels <- rep(
"",
vcount(g_top)
)
labels[top2] <- V(g_top)$congressperson[top2]
# ============================================================
# 6. Layout
# ============================================================
set.seed(123)
lay <- layout_with_fr(g_top)
# ============================================================
# 7. Gráfico
# ============================================================
par(
mfrow = c(1, 1),
mar = c(0, 0, 2, 0)
)
plot(
g_top,
layout = lay,
# -------------------------
# Nodos
# -------------------------
vertex.color = V(g_top)$color,
vertex.shape = V(g_top)$shape,
vertex.size = V(g_top)$size,
vertex.label = labels,
vertex.label.cex = 0.9,
vertex.label.color = "black",
vertex.frame.color = "white",
# -------------------------
# Aristas
# -------------------------
edge.width = 0.3 + 4 * w_scaled,
edge.color = edge_color,
# Flechas
edge.arrow.size = 0.15 + 0.45 * w_scaled,
# Curvatura
edge.curved = 0.08,
# -------------------------
# Título
# -------------------------
main = "Congresistas más influyentes en Twitter\n(Top 10% por grado)"
)
# 8. Leyenda
legend( "topleft", legend = c("Demócrata", "Republicano", "Independiente"), col = c("dodgerblue3", "firebrick2", "gold"), pch = 19, pt.cex = 1.5, bty = "n", title = "Partido" )
legend( "topright", legend = c("Cámara", "Senado"), pch = c(21, 22), pt.bg = "black", pt.cex = 1.5, bty = "n", title = "Corporación" )
cat( "Conectado débilmente?",
is_connected(graph = g_top, mode = "weak") , "\n",
"Conectado fuertemente?",
is_connected(graph = g_top, mode = "strong"))
## Conectado débilmente? TRUE
## Conectado fuertemente? TRUE
Supongamos que tenemos interés por un nodo particular, en este caso los tuits del Senador Bernie Sanders uno de los pocos Senadores Independientes, del congreso de Estados Unidos.
# Matriz de distancias
d <- distances(g)
d[!is.finite(d)] <- NA
# Mayores distancias desde Sanders
x <- which(
d[V(g)$congressperson == "SenSanders", ] ==
max(d[V(g)$congressperson == "SenSanders", ], na.rm = TRUE)
)
data.frame(
Node = x,
Congressperson = V(g)$congressperson[x],
Distance = d[V(g)$congressperson == "SenSanders", x]
)
Dentro de la lista de vértices con mayor distancia geodésica respecto a Sanders encontramos al representante John Wilson, republicano por Carolina del Sur. Analizar la relación entre ambos dentro del grafo resulta de interés debido a la marcada diferencia entre sus posiciones políticas. Wilson se identifica con posiciones cercanas al movimiento MAGA, mientras que Sanders es una de las figuras más representativas del ala progresista. Esta diferencia permite explorar cómo la distancia ideológica entre ambos se refleja en la estructura de la red y qué actores permiten conectar sus respectivos sectores políticos.
from <- which(V(g)$congressperson == "SenSanders")
to <- which(V(g)$congressperson == "RepWilson")
# Caminata
walk<-shortest_paths(
g,
from = from,
to = to,
weights = NA
)$vpath[[1]]
paste0(
as.numeric(walk),
" - ",
V(g)$congressperson[as.numeric(walk)]
)
## [1] "71 - SenSanders" "145 - JudgeCarter" "399 - RepSarbanes"
## [4] "470 - RepWilson"
La relación entre Wilson y Sanders resulta de interés debido a la marcada distancia ideológica que representan dentro del espectro político estadounidense. Analizar la distancia geodésica entre ambos permite explorar hasta qué punto esta separación ideológica se refleja en la estructura de la red. Además, los nodos que aparecen en el camino geodésico más corto pueden proporcionar información sobre los actores que conectan comunidades políticas estructuralmente diferenciadas.
En este caso, el camino más corto está compuesto por Bernie
Sanders (D) → John Carter (R) → John Sarbanes (D) → Wilson (R).
John Carter aparece en la red como JudgeCarter,
correspondiente a su cuenta de Twitter, mientras que John Sarbanes
aparece como RepSarbanes. Resulta particularmente llamativa
la alternancia entre representantes demócratas y republicanos
(D–R–D–R) a lo largo del camino. Esta estructura sugiere que, a
pesar de la distancia ideológica entre los extremos del camino, la red
presenta conexiones que atraviesan distintas comunidades partidistas.
Los actores intermediarios, en particular Carter y Sarbanes, pueden ser
analizados como posibles puentes estructurales entre
estos sectores de la red.
# Caminos
all_shortest_paths(graph = g, from = from, to = to)$res
## [[1]]
## + 5/475 vertices, named, from 6a6792d:
## [1] 111 215 195 264 395
# Distribución de las distancias
distance_table(g)
## $res
## [1] 13289 119441 86580 2883 112 1
##
## $unconnected
## [1] 2844
# Gráfico de distancias geodésicas
caminos <- distance_table(g_top)$res
distancias <- 1:length(caminos)
barplot(
prop.table(caminos),
names.arg = distancias,
xlab = "Distancia geodésica",
ylab = "Frecuencia relativa",
main = "Distribución de distancias geodésicas",
col = "steelblue",
border = NA,
ylim = c(0, max(prop.table(caminos)) * 1.1)
)