Módulo 5 — Actores clave: pivotes logísticos

En los módulos 3 y 4 caracterizamos y validamos la red. Ahora identificamos a los actores clave: los que mueven el dinero y los que conectan circuitos.

Code
library(igraph); library(ggraph); library(dplyr); library(ggplot2); library(scales); library(readr); library(leaflet); library(plotly); library(DT)
chronnet <- readRDS("../outputs/chronnet.rds")
cen <- read_csv("../outputs/centralidad.csv", show_col_types = FALSE)
sub <- readRDS("../outputs/subgrafo_claves.rds")
knitr::opts_chunk$set(message = FALSE, warning = FALSE)

1. Tres medidas, tres preguntas

Medida Pregunta Qué captura
Grado \(k_i\) ¿Con cuántos bancos comparte ventanas temporales? Alcance en la secuencia
Fuerza \(s_i\) ¿Cuánto dinero mueve? Volumen (atributo desde los reportes)
Intermediación \(C_i^{betw} = \sum_{j,k} \frac{n_i^{jk}}{g^{jk}}\) ¿Cuántos caminos mínimos del chronnet pasan por él? Puente entre circuitos

La intermediación, palabra por palabra:

  • Para cada par de bancos \((j, k)\), cuenta cuántos de los caminos más cortos entre ellos pasan por \(i\) (\(n_i^{jk}\)), dividido entre el total (\(g^{jk}\)).
  • Suma sobre todos los pares.

Interpretación:

  • Un vértice con intermediación alta no es el que más dinero mueve ni el que más conexiones tiene.
  • Es el que conecta circuitos que de otro modo quedarían aislados en la secuencia temporal.
  • Quitar un puente no es lo más rápido en fragmentar aristas, pero deja comunidades enteras desconectadas del flujo de actividad.

Lectura criminal

  • Un banco con intermediación alta es el “corresponsal de los corresponsales”: puede mover poco dinero propio, pero está en medio del camino temporal de todos los demás.
  • En la práctica, son los nodos que una red pone entre sus circuitos para que no operen en los mismos momentos.
  • Es exactamente el papel que un investigador quiere desbaratar.

2. Top bancos por fuerza (monto movido)

  • Tabla interactiva con filtros — busca por país, rango de monto o nombre.
  • El grado es topológico (chronnet); la fuerza es dinero reportado (atributo).
Code
cen %>%
  slice_max(fuerza, n = 50) %>%
  select(bank_name, country, grado, actividad, fuerza, intermediacion) %>%
  mutate(fuerza = round(fuerza, 0), actividad = round(actividad, 0),
         intermediacion = round(intermediacion, 1)) %>%
  datatable(filter = "top", options = list(pageLength = 10),
            colnames = c("Banco", "País", "Grado", "Actividad", "Fuerza (USD)", "Intermediación"))

3. Grado vs. fuerza: acumuladores y distribuidores

  • Cada punto es un banco; el tamaño del punto es su intermediación.
  • Pasa el mouse para ver todos sus datos:
Code
nodos <- tibble(
  nombre = V(chronnet)$bank_name, pais = V(chronnet)$country,
  grado = degree(chronnet), fuerza = V(chronnet)$monto_total,
  intermediacion = betweenness(chronnet, directed = TRUE),
  monto_total = V(chronnet)$monto_total, tx_total = V(chronnet)$tx_total,
  first = V(chronnet)$first, last = V(chronnet)$last
) %>% filter(grado > 0)

p <- ggplot(nodos, aes(grado, fuerza)) +
  geom_point(aes(size = intermediacion), alpha = 0.4, color = "#0D9488") +
  scale_x_log10(labels = comma) + scale_y_log10(labels = dollar) +
  geom_smooth(method = "lm", se = FALSE, color = "#D4883A") +
  labs(x = "Grado (log)", y = "Fuerza / monto (log)", size = "Intermediación") +
  theme_minimal()
ggplotly(p, tooltip = c("nombre", "pais", "grado", "fuerza", "intermediacion", "monto_total", "tx_total", "first", "last"))

Lectura de los cuadrantes:

  • Arriba de la recta (fuerza ≫ grado) = acumuladores: pocos contactos temporales, mucho dinero — cuentas de paso y centros de concentración.
  • A la derecha (grado alto, fuerza moderada) = distribuidores: comparten ventanas con muchos bancos, fraccionan fondos.
  • Cortar un acumulador detiene dinero; cortar un distribuidor fragmenta la secuencia.

4. Subred de actores clave

Qué medida define la subred:

  • Nodos por encima del percentil 90 de la fuerza \(s_i\) (monto total movido, entrada + salida).
  • De los 2.275 bancos del chronnet entran los ~230 de mayor fuerza — el 10% que concentra la mayor parte del dinero reportado.
  • No usamos grado ni intermediación aquí: la fuerza responde “¿quién maneja el volumen?”, y para una primera lectura de la élite es la pregunta correcta.
  • (Las otras dos medidas se usan en el módulo 6 para decidir a quién quitar.)

Por qué existe esta sección:

  • La red completa (2.275 nodos) no se puede leer.
  • La subred conserva los flujos dominantes y deja ver quién opera con quién dentro de la élite.

Los labels:

  • En el gráfico anterior salían todos montados e ilegibles; la solución no es etiquetar todo, es filtrar.
  • Abajo, dos lecturas: primero sin etiquetas (los filtros de la tabla identifican nodos), después solo los 30 más grandes — donde caben.
Code
# El subgrafo guardado no trae comunidad: la traemos del chronnet principal
V(sub)$comunidad <- V(chronnet)$comunidad[match(V(sub)$name, V(chronnet)$name)]
ggraph(sub, layout = "stress") +
  geom_edge_link(aes(width = monto), alpha = 0.3, color = "grey55") +
  geom_node_point(aes(size = monto_total,
                      color = as.factor(comunidad)), alpha = 0.9) +
  scale_size(range = c(1, 10)) + scale_edge_width(range = c(0.2, 3), guide = "none") +
  theme_graph() + theme(legend.position = "none")

Code
# Solo etiquetamos los 30 nodos de mayor fuerza: donde las etiquetas caben
top30 <- tibble(name = V(sub)$name, fuerza = V(sub)$monto_total) %>%
  slice_max(fuerza, n = 30) %>% pull(name)

ggraph(sub, layout = "stress") +
  geom_edge_link(aes(width = monto), alpha = 0.25, color = "grey70") +
  geom_node_point(aes(size = monto_total,
                      color = as.factor(comunidad)), alpha = 0.9) +
  geom_node_text(data = NULL,
                 aes(label = ifelse(name %in% top30, bank_name, "")),
                 repel = TRUE, size = 2.8, max.overlaps = Inf) +
  scale_size(range = (c(1, 10))) + scale_edge_width(range = c(0.2, 3), guide = "none") +
  theme_graph() + theme(legend.position = "none")

Los filtros que le dan sentido:

  • La tabla del punto 2 filtra la élite por país o rango de monto.
  • Este gráfico responde: “¿quién de los que filtré opera con quién?”.
  • Flujo de trabajo del analista: tabla → filtro → gráfico.

Lectura criminal

  • La subred es la foto de los nodos de mayor volumen: los bancos entre los que circula la mayor parte del dinero reportado como sospechoso.
  • El grosor de las aristas sigue al dinero documentado entre el par.
  • Cuando dos entidades de la élite transaron una y otra vez, el grosor lo registra: esa repetición distingue una relación estructural de una transacción aislada.

5. Mapa arriba/debajo del umbral

  • Naranja = actores clave (percentil 90 de fuerza).
  • Gris = resto de la red.
  • Pasa el mouse sobre los nodos para ver sus datos:
Code
sub_ids <- V(sub)$name
mapa_df <- cen %>%
  filter(!is.na(lat), !is.na(lon)) %>%
  left_join(tibble(bank_id = V(chronnet)$name, monto_total = V(chronnet)$monto_total,
                   tx_total = V(chronnet)$tx_total, first = V(chronnet)$first, last = V(chronnet)$last),
            by = "bank_id") %>%
  mutate(clave = bank_id %in% sub_ids,
         radio = 3 + 10 * scales::rescale(log1p(fuerza)))

tooltip <- function(df) lapply(paste0(
  "<b>", df$bank_name, "</b> (", df$country, ")<br>",
  "💰 Total dinero: ", dollar(df$monto_total), "<br>",
  "🔢 Total transacciones: ", comma(df$tx_total), "<br>",
  "📍 ", round(df$lat, 3), ", ", round(df$lon, 3), "<br>",
  "🗓️ Activo: ", df$first, " → ", df$last), htmltools::HTML)

leaflet(mapa_df) %>%
  addProviderTiles(providers$OpenStreetMap) %>%
  addCircleMarkers(data = mapa_df %>% filter(!clave),
                   lng = ~lon, lat = ~lat, radius = 2, group = "Resto de la red",
                   color = "#64748B", stroke = FALSE, fillOpacity = 0.25) %>%
  addCircleMarkers(data = mapa_df %>% filter(clave),
                   lng = ~lon, lat = ~lat, radius = ~radio, group = "Actores clave",
                   color = "#D4883A", stroke = FALSE, fillOpacity = 0.9,
                   label = tooltip(mapa_df %>% filter(clave))) %>%
  addLayersControl(overlayGroups = c("Actores clave", "Resto de la red"),
                   options = layersControlOptions(collapsed = FALSE)) %>%
  setView(lng = 0, lat = 20, zoom = 2)

Lectura criminal

  • El mapa muestra la geografía de la élite: los hubs naranjas coinciden con los centros financieros globales y las jurisdicciones de paso conocidas.
  • Un actor clave aislado geográficamente del resto de su propia comunidad (mismo color en el gráfico anterior, distancia en este mapa) es un puente transnacional.
  • Es el tipo de nodo que conecta circuitos de continentes distintos.