Exploración rápida con variable dependiente categórica usando ggplot2 y gridExtra
En las primeras fases del análisis exploratorio, una necesidad común es visualizar de forma rápida la relación de la variable dependiente con cada una de las variables independientes.
En R existen varias opciones para visualizar múltiples gráficos para representar las relaciones entre variables de un data.frame de forma automática. Una función conocida es ggpairs() de la librería GGally. Aquí un ejemplo con el data.frame de ejemplo mtcars.
library(GGally)
library(dplyr)
mtcars %>%
ggpairs()
La función es bastante últil para un primer análisis visual de relaciones dos a dos, aunque cuando se alcanza un número considerable de variables, puede ser dificil apreciar algunos patrones. Además, la visualización de las relaciones de variables independientes entre sí se convierte en ruido, si en este primer paso interesa analizar solamente la relación de la variable dependiente con cada una de estas.
En proyectos de análisis multivariante con variable categórica, me he encontrado con la necesidad de tener un display de gráficos que relacione la variable dependiente con el resto de variables del dataset sin incluir las relaciones entre estas últimas, y que sirviera tanto como instrumento exploratorio como para presentar.
Combinando ggplot2 con las librerías grid y grid.extra, se puede conseguir esto. Es algo parecido a usar un gráfico con facets, pero aquí, los small multiples estarían compuestos por cada una de las visualizaciones de la variable dependiente categórica contra la indepentidente.
El primer paso es crear una función para visualizar la relación entre dependiente categórica y la variable independiente. Aquí es esencial que el tipo de gráfico cambie en función de la clasificación de la variable independiente. Si la variable es numerica, he optado por un diagrama de caja. Si es categórica, la visualización será un gráfico de barras
library(dplyr)
library(ggplot2)
library(grid)
library(gridExtra)
library(viridis)
library(purrr)
grafica_por_tipo <- function(datos, dep, indep) {
data.frame, character, character -> ggplot
## Dibuja un grafico dada una variable categórica "dep" presente en un data.frame,
## contra otra de las variables indep del midmo data.frame.
## El tipo de grafico depende del tipo de variable
## ASUME: dep es variable categorica (character, factor o logical)
aislamiento de la variable independiente elegida, para detectar su clase
var_indep <- datos[[indep]]
se identifica la clase de la variable dependiente como categórica o no
es_categorica <- map_lgl(list(is.factor, is.character, is.logical),
~.x(var_indep)) %>%
any()
transformacion de la clase de la variable dependiente en categórica
datos[, dep] <- as.factor(datos[[dep]])
se guarda grafico de barras si la variable explicativa es factor
if (es_categorica) {
g <- datos %>%
ggplot(aes_string(x = indep)) +
geom_bar(aes_string(fill = dep), position = "dodge")
}
else {
# se guarda diagrama de caja si la variable explicativa es numerica
g <- datos %>%
ggplot(aes_string(x = dep)) +
geom_boxplot(aes_string(y = indep, fill = dep))
}
devolucion de grafico añadiendo elementos de tema
g +
theme_bw() +
theme(axis.title = element_blank(),
plot.title =element_text(color = "grey55"),
panel.border = element_blank(),
axis.line = element_line(),
axis.text = element_text(color = "#002855"))
}
La función identifica la categoría de la variable dependiente, y dependiendo de cual es esta categoría, devuelve uno u otro tipo de gráfico. Varias cosas a destacar aquí
- Se emplea map_lgl de la libreria purrr para identificar la categoria de la variable independiente, haciendo menos redundante el código: map_lgl pasa un predicado por los elementos de una lista y devuelve un vector logical. No hay nada que impida que los elementos de esta lista sean funciones. Se llama a estas funciones anónimamente con el formato formula (~).
- Se consideran las clases numeric e integer como numéricas, mientras que logical, factor y character se consideran categóricas.
- Los argumentos de la funcion para identificar la variable dependiente e independiente son character. Por esta razon se usa la funcion aes_string en lugar de aes cuando se crea el gráfico con ggplot (no se emplea en este caso el non standard evaluation, típico de las funciones de tidyverse).
- He añadido elementos de tema al final para dar algo de estilo a la visualización, pero se pueden suprimir sin ningún problema.
Aquí comprobamos el resultado graficando la variable cut del dataset diamonds, primero contra una variable categrórica y luego contra una numérica
grafica_por_tipo(diamonds, "cut", "carat")
grafica_por_tipo(diamonds, "cut", "clarity")
El siguiente paso es ejecutar esta función para todas las variables del data.frame y dibujar los gráficos en un canvas. Haremos esto con grid.arrange, de la librería gridExtra y map, de la librería purrr.
grid.arrange toma como input una lista de objetos gráficos y los dibuja en un canvas. Con map podemos crear esa lista, pasando grafica_por_tipo como función.
Graficamos a continuación la variable cut contra el resto de variables del data.frame
"cut"])
lista_graficos <- map(variables_independientes,
~grafica_por_tipo(diamonds, "cut", .x))
grid.arrange(grobs = lista_graficos, ncol = 3)
variables_independientes <- names(diamonds[names(diamonds) !=
Ya tenemos los gráficos en un solo canvas, y cada tipo de gráfico está en función de la variable dependiente. Pero hay cosas que se pueden mejorar: en primer lugar, tener una leyenda en cada gráfico es redundante y reduce el espacio, dificultando la visualización (además de tener dimensiones demasiado pequeñas, el texto del eje horizontal se superpone). Y siendo más detallista, los graficos no están del todo alineados verticalmente.
Utilzaremos la libería grid para mejorar esto. Crearemos una función para dibujar la lista de gráficos automáticamente, introduciendo las siguientes mejoras
- Sustitución de las leyendas individuales por gráfico, por una leyenda general lateral.
- Alineación vertical de los gráficos.
- Adicion del nombre de la variable en el título del gráfico.
La siguiente función hace uso de la que ya se ha construido, grafica_por_tipo, para crear una lista de objetos gráficos que visualizan la relación de una variable categorica de un data.frame contra el resto de variables, y compartimentaliza esta lista en una estructura de tabla con arrangeGrob. Luego se construye otro objeto gráfico para crear la leyenda, con legendGrob. Se dibujan ambos objetos, uno al lado del otro, con grid.arrange.
Antes de eso, para alinear los gráficos verticalmente, de la lista de objetos gráficos inicial se guarda el ancho del primero y se asigna este ancho al resto de gráficos.
agrupa_graficos_tipo <- function(datos, dep, n, titulo = NULL) {
data.frame, character, integer, character -> gtable
## Crea una tabla de graficos que relaciona una variable categorica dep, con
## el resto de variables en datos. n permite seleccionar el número de columnas
## para el display de los gráficos
## ASUME: dep es character, logical o factor.
creacion de la paleta de colores. Depende del numero de categorias de la
variable dependiente
categories <- unique(as.character(datos[[dep]]))
paleta <- viridis(n = length(categories))
seleccion de variables independientes
vars_indep <- names(datos[names(datos) != dep])
creacion de lista de objetos graficos
lista_graficos <- map2(rep(dep, length(vars_indep)), vars_indep,
~ggplotGrob(grafica_por_tipo(datos, .x, .y)))
# Alineacion vertical de graficos igualando su ancho ##
extracción del ancho del primer grafico de la lista
plot_widths <- lista_graficos[[1]]$widths
se asigna a todos los graficos el ancho del primer gráfico
lista_graficos_alineados <- map(lista_graficos,
function(x) {
x$widths <- plot_widths
return(x)
}
)
# Estructura de objetos graficos ##
creacion de estructura de tabla de objetos graficos
a partir de la lista de graficos alineados
plot_grobs <- arrangeGrob(grobs = lista_graficos_alineados, ncol = n)
creacion de la leyenda del grafico
legend_grob <- legendGrob(label = categories,
pch = 21, gp = gpar(fill = paleta))
dibujo de la lista de graficos con la leyenda incluida
grid.arrange(plot_grobs, legend_grob,
widths = c(7, 1),
top = textGrob(titulo, gp = gpar(color = "#002855")))
}
Por último, hace falta modificar la funcion incial, grafica_por_tipo, para eliminar la leyenda del gráfico. Simplemente se añade la linea guides(fill = FALSE)
grafica_por_tipo <- function(datos, dep, indep) {
# data.frame, character, character -> ggplot
### Dibuja un grafico dada una variable categórica "dep" presente en un data.frame,
### contra otra de las variables indep del midmo data.frame.
### El tipo de grafico depende del tipo de variable en indep
### ASUME: dep es variable categorica (character, factor o logical)
# aislamiento de la variable independiente elegida para detectar su clase
var_indep <- datos[[indep]]
es_categorica <- map_lgl(list(is.factor, is.character, is.logical), ~.x(var_indep)) %>% any()
# transformación de la clase de la variable dependiente en categórica
datos[, dep] <- as.factor(datos[[dep]])
# se guarda grafico de barras si la variable explicativa es factor
if (es_categorica) {
g <- datos %>%
ggplot(aes_string(x = indep)) +
geom_bar(aes_string(fill = dep), position = "dodge")
}
else {
# se guarda diagrama de caja si la variable explicativa es numerica
g <- datos %>%
ggplot(aes_string(x = dep)) +
geom_boxplot(aes_string(y = indep, fill = dep))
}
# devolucion de grafico con elementos de tema
g +
labs(title = indep) + # el titulo de cada grafico es la varible independiente
# tema del grafico
theme_bw() +
guides(fill = F) + # eliminacion de la leyenda
theme(axis.title = element_blank(),
plot.title =element_text(color = "grey35"),
panel.border = element_blank(),
axis.line = element_line(),
axis.text = element_text(color = "#002855"))
}
Se obtiene el siguiente display cuando se ejecuta la función principal, agrupa_graficos_tipo
agrupa_graficos_tipo(diamonds, "cut", n = 3, "Cut against the other variables in diamonds dataset")
Los graficos se ven bastante mejor ahora que se han eliminado las leyendas individuales, y además están alineados verticalmente, lo cual hace a esta visualización mas apta para ser utilizada en una presentación o comunicación. La variable independiente aparece como título de cada gráfico individual.
Tras esta primera aproximación, dejo como próximo paso para una nueva entrada, aumentar las opciones para los gráficos (por ejemplo, cuando la variable independiente es numérica, permitir elegir entre diagrama de caja y gráfico de densidad).
Tambíen habría que aumentar la flexibilidad en cuanto al control de los temas del gráfico, sobre todo los aspectos referidos al tamaño de las fuentes, porque el tamaño ideal cambia dependiendo del número de gráficos y el tamaño del display.