Las redes relacionales son una manera de visualizar información que resulta muy útil para datos cualitativos y cuantitativos.
Como su nombre lo indica, este tipo de redes son utilizadas para mostrar relaciones entre datos, generalmente nominales (nombres, categorías, etiquetas). Por ejemplo, la afinidad entre los integrantes de un equipo de trabajo, los principales clientes de diferentes centros de distribución, dependencias entre procesos y muchas otras cosas más.
Formalmente podríamos decir que este tipo de visualizaciones es un grafo, cuyas características son descritas usando como marco de referencia la teoría de grafos:
Esta teoría puede resultar un poco compleja, pero para fines prácticos, un grafo es una estructura que describe un conjunto de vértices, nodos o puntos y que están unidos por aristas, arcos o líneas.
Si visualizamos esta estructura, obtendremos algo como lo siguiente:
Para esta entrada, usaremos como ejemplo datos de la serie de juegos Pokémon, así que para empezar, hablaremos de de qué trata este juego y después veremos cómo crear, paso a paso, una red relacional usando los paquetes ggraph e igraph de R.
Una introducción muy general a Pokémon
Pokémon es una franquicia de videojuegos portátiles que tuvo su origen en 1996, en la consola Game Boy de Nintendo.
Este es un juego de rol (RPG) en el que los jugadores asumen el papel de un entrenador de los titulares pokemon (pocket monsters), criaturas muy diversas con habilidades especiales, cuya meta es capturar y entrenar a estos simpáticos bichos para hacerlos luchar unos contra otros. O hacerlos concursar en certámenes de belleza, utilizarlos como medio de transporte, criarlos como mascotas y muchas cosas más.
Veinte años de videojuegos han dado mucho material, que ha dado lugar a juguetes, películas, música, ropa, libros y todo lo que sea posible imaginar decorado con los personajes de Pokémon.
Al momento de escribir esto, existen 802 especies diferentes de pokemon y cada una de ellas tiene un tipo que determina sus estadísticas de ataque y defensa así como las habilidades que puede aprender.
Por ejemplo, el famosísimo Pikachu es, en realidad, un miembro de toda una especie de pokemon de tipo eléctrico, mientras que Bidoof, un mítico pokemon con características cuasi-divinas, es de tipo normal.
Estos tipos de pokemon determinan fortalezas y debilidades de cada especie con respecto a otra en un complejo sistema, similar al juego “Piedra, papel y tijera”.
Por ejemplo, un pokemon de tipo agua es fuerte contra uno de tipo fuego estos, a su vez, son fuertes contra los de tipo planta, y este último tipo de pokemon es fuerte contra los de tipo agua. Más precisamente, son fuertes o débiles ante ataques de un tipo particular, pero para no complicarnos, pensemos en términos de especie y tipo.
Lo anterior equivale a decir que hay una relación fortaleza – debilidad entre los tipos de pokemon Esta sería fácil de esquematizar si sólo tuviéramos los cuatro tipos arriba listados, pero no es así. Actualmente, existen dieciocho tipos diferentes de pokemon y cada uno puede ser fuerte o débil a más de un tipo. En algunos casos, la fortaleza es recíproca y hay tipos que son fuertes hacía sí mismos.
Lo que haremos es representar estas relaciones complejas de una manera intuitiva de comprender: una red relacional.
Y para crearla, preparemos nuestro espacio de trabajo.
Paquetes necesarios
Utilizaremos los siguientes paquetes para este análisis.
- tidyverse – Una familia de paquetes para importar, procesar, visualizar y exportar datos de forma ordenada.
- igraph – Este paquete nos ayudará a transformar data frames en datos apropiados para generar redes relacionales.
- ggraph – Un agregado al paquete ggplot2 de tidyverse que contiene las funciones necesarias para visualizar redes relacionales.
Como siempre, si no cuentas con alguno de estos paquetes puedes usar la función install.packages()
para instalarlos.
Cargamos los paquetes a nuestro espacio de trabajo con library()
.
library(tidyverse)
library(igraph)
library(ggraph)
Descarga de datos
Usaremos la copia de un conjunto de datos publicado originalmente en Kaggle. Este conjunto contiene datos de 800 especies de pokemon distintas con varias características para cada uno de ellos.
The Complete Pokémon Dataset:
Descargaremos este conjunto de datos desde Github y obtenemos como resultado el archivo “pokemon.csv”.
download.file(
url = "https://raw.githubusercontent.com/jboscomendoza/rpubs/master/pokemon_red/pokemon.csv",
destfile = "pokemon.csv")
Lectura de datos
Como tenemos un archivo con extensión .csv, usamos la función read_csv
.
pokemon <- read_csv(file = "pokemon.csv")
Veamos nuestro resultado.
pokemon
## # A tibble: 801 x 41
## abilities against_bug against_dark against_dragon against_electric
## <chr> <dbl> <dbl> <dbl> <dbl>
## 1 ['Overgr~ 1 1 1 0.5
## 2 ['Overgr~ 1 1 1 0.5
## 3 ['Overgr~ 1 1 1 0.5
## 4 ['Blaze'~ 0.5 1 1 1
## 5 ['Blaze'~ 0.5 1 1 1
## 6 ['Blaze'~ 0.25 1 1 2
## 7 ['Torren~ 1 1 1 2
## 8 ['Torren~ 1 1 1 2
## 9 ['Torren~ 1 1 1 2
## 10 ['Shield~ 1 1 1 1
## # ... with 791 more rows, and 36 more variables: against_fairy <dbl>,
## # against_fight <dbl>, against_fire <dbl>, against_flying <dbl>,
## # against_ghost <dbl>, against_grass <dbl>, against_ground <dbl>,
## # against_ice <dbl>, against_normal <dbl>, against_poison <dbl>,
## # against_psychic <dbl>, against_rock <dbl>, against_steel <dbl>,
## # against_water <dbl>, attack <dbl>, base_egg_steps <dbl>,
## # base_happiness <dbl>, base_total <dbl>, capture_rate <chr>,
## # classfication <chr>, defense <dbl>, experience_growth <dbl>,
## # height_m <dbl>, hp <dbl>, japanese_name <chr>, name <chr>,
## # percentage_male <dbl>, pokedex_number <dbl>, sp_attack <dbl>,
## # sp_defense <dbl>, speed <dbl>, type1 <chr>, type2 <chr>,
## # weight_kg <dbl>, generation <dbl>, is_legendary <dbl>
Procesamiento de datos
Ahora sí, empezamos con lo interesante, procesar los datos.
¿Por dónde empezamos?
Recordemos que nuestra intención es mostrar las relaciones entre tipos de pokemon, así nuestro procesamiento debe facilitar esto.
Para construir una red relacional necesitamos una tabla de datos como la siguiente:
Desde | Hacia | Característica1 | Característica2 |
---|---|---|---|
Uno | Dos | Fuerza | Non |
Dos | Uno | Debilidad | Par |
Uno | Tres | Fuerza | Non |
Dos | Tres | Debilidad | Par |
Cada renglón describe una relación y necesitamos al menos dos columnas, una con el origen de la relación (desde donde surge) y otra con el destino de ella (hacia donde termina).
Podemos tener múltiples columnas adicionales que describan las características de la relación en cada renglón.
Considerando lo anterior, nuestra meta mínima es obtener una tabla como la siguiente.
Tipo (origen) | Tipo (destino) | Relación |
---|---|---|
Agua | Fuego | Fuerza |
Fuego | Agua | Debilidad |
Planta | Agua | Fuerza |
Planta | Fuego | Debilidad |
Nuestro objeto pokemon
tiene dos columnas con información de tipo de pokemon: type1 y type2. Esto es porque los pokemon pueden tener hasta dos tipos.
Como nos interesan las relaciones entre tipos individuales, los
primero que hacemos es filtrar a todos los pokemon que tienen más de un
tipo con la función filter()
. Reconocemos a estos caso porque tienen un NA
en la columna type2.
Vamos a guardar nuestros resultados en un data frame llamado poke_fuerza
.
poke_fuerza <-
pokemon %>%
filter(is.na(type2))
La información de fortalezas y debilidades se encuentra en todas las columnas que inician con “against_” expresado como un multiplicador.
Por ejemplo, si tenemos un pokemon de tipo fire (fuego) y vemos la columna “against_ground” (contra tierra), veremos un valor de 2.
Esto nos indica que un pokemon de tipo fuego recibe el doble de daño de un pokemon tipo tierra, es decir, fuego es debil contra tierra este tipo, o expresado de manera inversa, tierra es fuerte contra fuego.
Si el valor en la columna es 0.5, entonces este tipo de pokemon recibe la mitad de daño, es fuerte contra él y si el valor es 1, entonces no hay diferencia, no es ni fuerte ni débil contra ese tipo.
Vamos a quedarnos sólo con estas columnas y la que contiene el tipo de pokemon, usando la función select()
de dplyr. De una vez, cambiamos el nombre de la columna “type1” a “tipo”.
poke_fuerza <-
poke_fuerza %>%
select(tipo = type1, starts_with("against"))
El formato en el que se encuentran nuestros datos es conocido como un formato “ancho” (wide), pues tenemos distintos valores de un mismo atributo (fortaleza – debilidad) en columnas separadas (las columnas “against_”.
Necesitamos cambiar este formato a uno alto (tall) para generar una tabla apropiada para generar redes relacionales.
Usamos la función gather()
de tidyr para esta
tarea. Vamos a consolidar todas las columnas “against_” en dos columnas,
una llamada “enemigo” que contenga el nombre de las columnas originales
y otra llamada “modificador” con los valores numéricos.
poke_fuerza <-
poke_fuerza %>%
gather("enemigo", "modificador", against_bug:against_water)
Quitamos “against_” de los datos en la columna «enemigo usando la función gsub()
dentro de mutate()
.
poke_fuerza <-
poke_fuerza %>%
mutate() %>%
mutate(enemigo = gsub("against_", "", enemigo))
Veamos cuáles tipos tenemos en las columnas tipo y enemigo.
unique(poke_fuerza$tipo)
## [1] "fire" "water" "bug" "poison" "electric" "fairy"
## [7] "fighting" "psychic" "ground" "normal" "grass" "dragon"
## [13] "rock" "dark" "ghost" "ice" "steel" "flying"
unique(poke_fuerza$enemigo)
## [1] "bug" "dark" "dragon" "electric" "fairy" "fight"
## [7] "fire" "flying" "ghost" "grass" "ground" "ice"
## [13] "normal" "poison" "psychic" "rock" "steel" "water"
Tenemos un tipo con un nombre inconsistente en “enemigo”. “fight” no
es un nombre válido de tipo de pokemon, debemos reemplazarlo por
“fighting” usando gsub()
poke_fuerza <-
poke_fuerza %>%
mutate(enemigo = gsub("fight", "fighting", enemigo))
Ya que los tipos son correctos en las columnas “tipo” y “enemigo”,
nos quedamos sólo con los renglones únicos, quitando los repetidos con
la función distinct()
de dplyr.
poke_fuerza <-
distinct(poke_fuerza)
Por último, transformamos los valores en la columna modificador en etiquetas, que asignamos a una nueva columna llamada “relacion”.
Nos guiamos con las reglas que vimos más arriba, pero debemos considerar la manera en que la relación que describamos será expresada y qué tan clara es.
Retomemos el ejemplo de un pokemon tipo fuego. Si este tiene un valor de modificar de 2 contra un pokemon tipo agua, podríamos representarlo de la siguiente manera.
fuego -- debil -> agua
No hay ningún problema con esto, comunica la relación que existe entre estos dos elementos. Sin embargo, veamos que pasa si cambiamos el orden de los elementos, convirtiendo agua en el origen de la relación y fuego en el destino, y con ello cambiando la relación de “débil” a “fuerte”.
agua -- fuerte -> fuego
Es la misma información representada de una manera diferente, probablemente más clara. Después de todo, explicamos el juego de piedra, papel y tijera como piedra le gana a tijera, tijera le gana a papel, y papel le gana a piedra; no al revés: piedra pierde contra papel, papel pierde contra tijera, y tijera pierde contra piedra.
De nuevo, es la misma información, pero la primera manera de presentarla me parece más intuitiva.
Cambiaremos el orden de las columnas, “enemigo” será la columna de origen y “tipo” la de destino, usando la función select()
.
Con esto en mente, entonces las reglas para etiquetar las relaciones
serán: Si la columna modificador es menor que uno, entonces asignamos la
etiqueta “Débil”, si es mayor que uno será “Fuerte”.
Para etiquetar usamos la función ifelse()
dentro de mutate()
.
Por último, como no son de nuestro interés las relaciones neutrales, utilizamos filter()
para quitarlas, antes de etiquetar.
poke_fuerza <-
poke_fuerza %>%
select(enemigo, tipo, modificador) %>%
filter(modificador != 1) %>%
mutate(Relacion = ifelse(modificador < 1, "Fuerte", "Débil"))
Nuestro resultado es el siguiente.
poke_fuerza
## # A tibble: 120 x 4
## enemigo tipo modificador Relacion
## <chr> <chr> <dbl> <chr>
## 1 bug fire 0.5 Fuerte
## 2 bug poison 0.5 Fuerte
## 3 bug fairy 0.5 Fuerte
## 4 bug fighting 0.5 Fuerte
## 5 bug psychic 2 Débil
## 6 bug grass 2 Débil
## 7 bug dark 2 Débil
## 8 bug ghost 0.5 Fuerte
## 9 bug steel 0.5 Fuerte
## 10 bug flying 0.5 Fuerte
## # ... with 110 more rows
Para facilitar la legibilidad de las gráficas que generemos más
adelante, vamos a cambiar los valores de “tipo” y “enemigo” a mayúsculas
y los truncaremos a sus primeros tres caracteres. Hacemos esto con mutate_at()
de dplyr.
poke_fuerza <-
poke_fuerza %>%
mutate_at(c("enemigo", "tipo"), ~toupper(.) %>%
substr(start = 1, stop = 3))
Finalmente, transformamos este data frame en un objeto apropiado para generar redes relacionales, con la función graph_from_data_frame()
del paquete igraph.
Esta función siempre toma las primeras dos columnas de un data frame como el origen y destino de las relaciones, por lo que debes verificar que la tabla que uses tenga la estructura correcta.
poke_graph <-
graph_from_data_frame(d = poke_fuerza)
Veamos el resultado y la clase del objeto que hemos creado con graph_from_data_frame()
.
poke_graph
## IGRAPH cfdd704 DN-- 18 120 --
## + attr: name (v/c), modificador (e/n), Relacion (e/c)
## + edges from cfdd704 (vertex names):
## [1] BUG->FIR BUG->POI BUG->FAI BUG->FIG BUG->PSY BUG->GRA BUG->DAR
## [8] BUG->GHO BUG->STE BUG->FLY DAR->FAI DAR->FIG DAR->PSY DAR->DAR
## [15] DAR->GHO DRA->FAI DRA->DRA DRA->STE ELE->WAT ELE->ELE ELE->GRO
## [22] ELE->GRA ELE->DRA ELE->FLY FAI->FIR FAI->POI FAI->FIG FAI->DRA
## [29] FAI->DAR FAI->STE FIG->BUG FIG->POI FIG->FAI FIG->PSY FIG->NOR
## [36] FIG->ROC FIG->DAR FIG->GHO FIG->ICE FIG->STE FIG->FLY FIR->FIR
## [43] FIR->WAT FIR->BUG FIR->GRA FIR->DRA FIR->ROC FIR->ICE FIR->STE
## [50] FLY->BUG FLY->ELE FLY->FIG FLY->GRA FLY->ROC FLY->STE GHO->PSY
## + ... omitted several edges
# Clase
class(poke_graph)
## [1] "igraph"
Podemos realizar todo lo anterior en un sólo proceso.
poke_graph <-
pokemon %>%
filter(is.na(type2)) %>%
select(tipo = type1, starts_with("against")) %>%
gather("enemigo", "modificador", against_bug:against_water) %>%
mutate(enemigo = gsub("against_", "", enemigo)) %>%
mutate(enemigo = gsub("fight", "fighting", enemigo)) %>%
distinct() %>%
select(enemigo, tipo, modificador) %>%
filter(modificador != 1) %>%
mutate(relacion = ifelse(modificador < 1, "Fuerte", "Débil")) %>%
mutate_at(c("enemigo", "tipo"), ~toupper(.) %>%
substr(start = 1, stop = 3)) %>%
graph_from_data_frame()
Ahora que tenemos un objeto de clase igraph que podemos crear visualizaciones de redes relacionales. Para ello, tenemos dos métodos:
- La función plot()
- El paquete ggraph
Revisemos ambos métodos.
Generación de redes relacionales usando plot()
Para crear la visualización de una red relacional con plot(), debemos cargar primero el paquete igraph, de este modos se anexan nuevos métodos a esta función.
Hecho esto, basta con usar nuestro objeto igraph como argumento para plot()
.
plot(poke_graph)
Obtenemos una visualización bastante aceptable considerando el mínimo esfuerzo invertido en crearla. Tenemos una buena representación de las uniones y nodos de nuestra red, así como de la direccionalidad de las uniones.
Podemos ajustar la apariencia de una gráfica generada con este método usando distintos parámetros. Los parámetros que modifican las uniones entre nodos inician con la palabra “edge” y las que afectan a los nodos, con la palabra “vertex”.
Por ejemplo, cambiamos el color de las uniones con los argumentos edge.color
y el tamaño de la cabeza de flecha en ellas con edge.arrow.size
. Modificamos el color de los nodos con vertex.color
, el texto en ellos con vertex.label.color
y su tamaño con vertex.size
.
plot(poke_graph, edge.color = "#3366ff", edge.arrow.size = 0.25,
vertex.color = "white", vertex.label.color = "black",
vertex.size = 25)
Podemos cambiar la manera en que están distribuidos los nodos usando como argumento layout
una de las funciones deigraph específica para esta tarea.
Por ejemplo, para graficar con una distribución en forma de círculo usamos la función layout.circle()
.
plot(poke_graph, layout = layout.circle(poke_graph))
También contamos con una distribución con forma de cuadrícula.
plot(poke_graph, layout = layout.grid(poke_graph))
Podemos usar la información de nuestro objeto para colorear nodos y uniones, usando las funciones V()
y E()
de igraph. Aunque esto nos da un control muy fino sobre cómo presentar nuestras gráficas es también un proceso bastante complicado.
Una manera más sencilla de generar estas gráficas, con el mismo nivel de detalle, es usar ggraph.
Generación de redes relacionales con ggraph
Todas las redes relacionales generadas con ggraph necesitan de especificaciones las uniones (edges) y los nodos (nodes). Es indispensable contar con al menos una de estas especificaciones para generar una red, pero no es absolutamente necesario contar con ambas.
La “gramática” que usamos es la misma que para ggplot2, por lo que si conoces cómo usar este paquete, ggraph es sencillo de comprender.
Primero, usamos la función ggraph()
dándole como argumento un objeto de tipo igraph. Con esto generamos lo que conceptualmente sería el lienzo de nuestra gráfica.
A esta gráfica agregamos funciones de tipo geoms usando el operador +
.
Estas funciones agregan diferentes elementos gráficos, que a su vez tienen parámetros que podemos controlar.
Para el caso de redes relacionales, usamos dos “familias” de geoms:
- geom_node_ : Para los nodos. Por ejemplo,
geom_node_point()
,geom_node_label()
,geom_node_text()
. - geom_edge_ : Para las uniones. Por ejemplo,
geom_edge_link()
,geom_edge_arc()
,geom_edge_density
.
Además, es posible agregar otras funciones heredadas de ggplot2 para ajustar el aspecto.
Por ejemplo, así luce una visualización usando únicamente nodos, con la función geom_node_point()
para crear nodos con forma de punto.
poke_graph %>%
ggraph() +
geom_node_point() +
theme_graph()
## Using `nicely` as default layout
Ahora veamos qué pasa si generamos una visualización sólo con uniones, usando la función geom_edge_link()
para crear uniones de línea recta.
poke_graph%>%
ggraph() +
geom_edge_link() +
theme_graph()
## Using `nicely` as default layout
Dependiendo de las relaciones que quieras visualizar, alguna de las dos opciones anteriores puede ser suficiente. Sin embargo, lo más usual es utilizar tanto nodos como uniones.
Una visualización con estas características se verá como una combinación de las dos anteriores.
poke_graph%>%
ggraph() +
geom_edge_link() +
geom_node_point() +
theme_graph()
## Using `nicely` as default layout
Usaremos geom_node_label()
para mostrar etiquetas en los nodos. Para que funcione correctamente, debemos dar aes(label = name)
como argumentos a esta función.
poke_graph%>%
ggraph() +
geom_edge_link() +
geom_node_label(aes(label = name)) +
theme_graph()
## Using `nicely` as default layout
Habrás notado que aparece el siguiente mensaje al generar las redes anteriores: ## Using
nicelyas default layout
.
Al crear una red podemos elegir entre diferentes formas para distribuir nuestra red o “layouts”. nicely es un layout que intenta distribuir de manera uniforme los nodos y uniones de nuestras redes, pero tenemos más opciones.
El layout linear ordena todos los nodos en una línea recta.
poke_graph %>%
ggraph(layout = "linear") +
geom_edge_link() +
geom_node_label(aes(label = name)) +
theme_graph()
El resultado es una gráfica poco informativa, pues las uniones entre nodos no son visibles.
Para solucionar este problema, cambiamos geom_edge_link()
por geom_edge_arc()
. De esta manera, reemplazamos uniones con líneas rectas por uniones en forma de arco.
poke_graph%>%
ggraph(layout = "linear") +
geom_edge_arc() +
geom_node_label(aes(label = name)) +
theme_graph()
Esta es una visualización que resulta útil cuando tenemos muchas relaciones de reciprocidad, difíciles de colocar de manera uniforme en un plano.
Otro layout es circle, que coloca a todos los nodos formando un círculo.
poke_graph %>%
ggraph(layout = "circle") +
geom_edge_link() +
geom_node_label(aes(label = name)) +
theme_graph()
Visualizando la dirección y tipo de relación
Las visualizaciones que hasta ahora hemos generado son de utilidad, pero no resuelven el problema de mostrar el tipo de relación entre nodos.
A diferencia de las visualizaciones creadas con igraph, las gráficas que hemos creado no incluyen las punta de flecha que muestran la direccionalidad de las relaciones.
Para agregarla, usamos la función arrow()
como el argumento arrow de los geom_edge_
de nuestra gráfica. Además necesitamos agregar dentro de aes()
el el argumento end_cap
, con la función label_rect()
. De este modo, aseguramos que las puntas de las flechas sean visibles y las etiquetas de los nodos no las cubran.
poke_graph%>%
ggraph() +
geom_edge_link(arrow = arrow(type = "closed", length = unit(1.5, "mm")),
aes(end_cap = label_rect(node2.name))) +
geom_node_label(aes(label = name)) +
theme_graph()
## Using `nicely` as default layout
Si además queremos ver no sólo la unión, sino algún atributo de esta unión, podemos usar colores, como un aes()
de ggraph.
poke_graph%>%
ggraph() +
geom_edge_link(arrow = arrow(type = "closed", length = unit(1.5, "mm")),
aes(color = relacion,
end_cap = label_rect(node2.name))) +
geom_node_label(aes(label = name)) +
theme_void()
## Using `nicely` as default layout
Esta es una visualización mucho más clara. A simple vista es posible reconocer la relación entre los diferentes tipos de pokemon, sin embargo, aún puede mejorar.
Como estamos usando líneas rectas, cuando hay relaciones de reciprocidad, habrá traslape entre ellas, lo cual dificulta la interpretación.
Cambiamos geom_edge_link()
por geom_edge_fan()
, con sus mismos argumentos, para obtener líneas curvas de unión entre nodos.
poke_graph%>%
ggraph() +
geom_edge_fan(arrow = arrow(type = "closed", length = unit(1.5, "mm")),
aes(color = relacion,
end_cap = label_rect(node2.name))) +
geom_node_label(aes(label = name)) +
theme_void()
## Using `nicely` as default layout
Como lo mencionamos anteriormente, hay tipos de pokemon que son
fuertes o débiles hacia sí mismos. Para que estas relaciones sean
visibles, usamos geom_edge_loop()
. Al igual que con los demás geom_edge_
, especificamos aes(color = relacion)
para distinguir el tipo de relación.
Aprovechamos para cambiar de posición la leyenda y especificar los colores de las uniones con theme()
y scale_edge_color_manual()
para dar los toques finales a nuestra visualización
poke_graph %>%
ggraph() +
geom_edge_loop(aes(color = relacion)) +
geom_edge_fan(arrow = arrow(type = "closed", length = unit(1.5, "mm")),
aes(color = relacion,
end_cap = label_rect(node2.name))) +
geom_node_label(aes(label = name)) +
theme_void() +
theme(legend.position = "top") +
scale_edge_color_manual(values = c("#ffaa00", "#00aaff"))
## Using `nicely` as default layout
Excelente! Creo que nos ha quedado bastante bien.
Por último, también podemos usar facet_wrap()
o facet_grid()
para crear paneles de visualizaciones. Por ejemplo, una para fortaleza y otra para debilidad
poke_graph %>%
ggraph() +
geom_edge_loop(aes(color = relacion)) +
geom_edge_fan(arrow = arrow(type = "closed", length = unit(1.5, "mm")),
aes(color = relacion,
end_cap = label_rect(node2.name))) +
geom_node_label(aes(label = name)) +
theme_void() +
theme(legend.position = "top") +
scale_edge_color_manual(values = c("#ffaa00", "#00aaff")) +
facet_wrap("relacion")
## Using `nicely` as default layout
Esta no es la única red relacional que podemos generar con nuestros datos, para ilustrar, un ejemplo que muestra qué cuáles son las habilidades más comunes por tipo de pokemon.
En esta visualización el color no indica el tipo de relación, sino la intensidad.
pokemon %>%
select(name, type1, type2, abilities) %>%
mutate_at("abilities", function(x) {
gsub("(\\[|\\'|\\]|,)+", "_", x) %>%
gsub("^_|_$", "", .) %>%
gsub("_ _", "_", .) %>%
trimws()
}) %>%
separate(col = "abilities", into = c(letters[1:6]), sep = "_") %>%
mutate_at(letters[1:6], as.factor) %>%
gather("Orden_H", "Habilidad", a:f) %>%
gather("Orden_T", "Tipo", type1, type2) %>%
select(Tipo, Habilidad, name) %>%
na.omit() %>%
mutate_at(c("Tipo"), ~toupper(.)) %>%
count(Tipo, Habilidad) %>%
group_by(Tipo) %>%
mutate(prop = n / sum(n)) %>%
filter(prop >= .05) %>%
graph_from_data_frame() %>%
ggraph() +
geom_edge_link(aes(end_cap = circle(radius = 2, unit = "mm"),
color = prop, alpha = prop, edge_width = prop),
arrow = arrow(type = "open", length = unit(1.5, "mm")),
show.legend = FALSE) +
scale_edge_color_continuous(low = "#666666", high = "#00ccaa") +
scale_edge_width(range = c(.01, 1.3)) +
scale_edge_alpha(range = c(.6, 1)) +
geom_node_point(size = 3) +
geom_node_text(aes(label = name), repel = TRUE) +
theme_void()
## Warning: Expected 6 pieces. Missing pieces filled with `NA` in 788 rows [1,
## 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 21, 22, ...].
## Warning: attributes are not identical across measure variables;
## they will be dropped
## Using `nicely` as default layout
Conclusión
En esta entrada revisamos cómo crear redes relacionales y sus visualizaciones con R. Es un proceso muy similar a crear cualquier tipo de gráfico en R y con el paquete ggraph podemos usar la misma lógica que con ggplot2 para trabajar.
Estas redes son muy útiles no sólo para aprender más sobre Pokémon, sino también para tener una idea intuitiva de todo tipo de relaciones, que además es muy atractiva a la vista.
Por ejemplo, son usados en ciencias sociales para crear sociogramas, que representan las dinámicas dentro de un grupo de personas y en minería de texto para generar redes semánticas, de lo cual ya hemos hablado aquí.
Puedes leer más sobre este relacionales en los siguientes enlaces:
- Network Visualization with R
- Introduction to Network Analysis with R
- Announcing ggraph: A grammar of graphics for relational data
- ggraph: ggplot for graphs
Consultas, dudas, comentarios y correcciones son bienvenidas:
El código y los datos usados en este documento se encuentran en Github:
También publicado en: