library(learnr)
library(ggplot2)
library(dplyr)
knitr::opts_chunk$set(echo = FALSE, comment = "")

gapminder <- dataviz::gapminder

# asignando nivel de ingresos en base a
# https://blogs.worldbank.org/es/opendata/nueva-clasificacion-de-los-paises-segun-el-nivel-de-ingresos-para-2019-y-2020
# y ajuste por inflación
gapminder_2007 <- gapminder %>% 
  # uso across(3, ) en lugar del mas legible "año == 2007" para no escribir "ñ", que causa error en Windows :(
  filter(across(3, ~ . == 2007)) %>% 
    mutate(nivIngreso = case_when(PBIpc > 12375 * 0.8 ~ "alto",
                                  PBIpc > 3996 * 0.8  ~ "medio alto",
                                  PBIpc > 1026 * 0.8  ~ "medio bajo",
                                  TRUE                ~ "bajo"),
           nivIngreso = ordered(nivIngreso, levels = rev(c("bajo", "medio bajo", "medio alto", "alto"))))

gapminder_2007_americas <- gapminder_2007 %>% 
  filter(continente == "Americas")

paises_longevos <- gapminder_2007 %>% 
  group_by(continente) %>% 
  filter(expVida == max(expVida)) %>% 
  select(pais, continente, expVida)

Introducción

Una objetivo típico al visualizar datos es el de mostrar la magnitud de un conjunto de números y categorías: la cantidad de medallas olímpicas que han ganado distintos países, la variación anual en venta de pasajes de distintas aerolíneas, los votos recibidos por cada partido político que se presentó a elecciones, etc.

Cuando queremos mostrar números absolutos ("el partido A obtuvo 500.000 votos") visualizamos cantidades.

Cuando nos interesa entender qué parte del total representan esos números ("el partido A obtuvo un 25% del total de votos") visualizamos proporciones.

Cuando nos interesa entender como se reparten los valores de una variable, que tan habituales o inusuales son mostramos una distribución. Para ésto último un ejemplo es la distribución etaria de votantes del partido A: cuántos votos ha recibido de las personas que se presentaron a los comicios, de cada edad.

Por lo simple y efectiva, la herramienta inescapable para visualizar cantidades, proporciones y distribuciones es el gŕafico de barras. A los seres humanos nos resulta muchísimo más fácil comparar "a ojo" diferencias de largo, como la altura a la que llegan las barras, que diferencias de ángulo y área como las que requieren opciones como el gráfico de torta. Abajo se ilustra el punto: el gŕafico de torta a) muestra los mismos valores que el gráfico de barras b), pero en el primero es muy difícil discernir diferencias, mientras que con el segundo se puede lograr de inmediato.

knitr::include_graphics("https://bitsandbricks.github.io/img/torta_vs_barras.png")

Gráfico de torta vs. gráfico de barras, mostrando los mismos valores

De tan común, el gráfico de barras se vuelve víctima de su propio éxito. A veces resulta repetitivo, aburrido, sobre todo si es el único recurso usado para múltiples visualizaciones que se presentan juntas. De allí la frase de Amanda Cox, editora en jefe de periodismo de datos en el New York Times:

Hay una corriente en el mundo de la visualización de datos que argumenta que todo podría resolverse con un gráfico de barras.

Eso bien podría ser cierto, pero también sería un mundo sin gracia.

En las siguientes secciones veremos como resolver la visualización de cantidades, proporcionesy distribuciones con barras y sin ellas... ¡e intentado ponerle gracia al asunto!

Cantidades y proporciones

Mostrando cantidades

Ya que tanto hablamos de ellos, comencemos con los gráficos de barras. ggplot2 nos ofrece dos funciones para generarlos: geom_col() y geom_bar(). geom_col() se usa cuando queremos mostrar una variable numérica, y geom_bar() cuando queremos mostrar la frecuencia con la que aparecen las clases de una variable categórica. Con ejemplos va a quedar más clara la diferencia.

Para disponer de las funciones de visualización, activamos ggplot2:

library(ggplot2)

Volvamos a nuestros datos de Gapminder. Tenemos aquí los países con mayor expectativa de vida en 2007, por cada continente:

paises_longevos

Tenemos una variable numérica a representar con barras, asi que usamos geom_col(). Hgamaos un gráfico de barras de expVida versus pais. O sea, que expVida va en el eje de las $y$ y pais en el de las $x$:

ggplot(paises_longevos, aes(x = ___, y = ___)) +
  ___()
ggplot(paises_longevos, aes(x = ___, y = ___)) +
  geom_col()
ggplot(paises_longevos, aes(x = pais, y = expVida)) +
  geom_col()

Los resultados son bastante parejos. Si quisiéramos mostrar a que continente pertenece cada país (cada barra) con un color distinto, intuitivamente pensamos en asignar el atributo estético de "color" al continente. Probemos:

ggplot(paises_longevos, aes(x = ___, y = ___)) +
  ___()
ggplot(paises_longevos, aes(x = ___, y = ___, color = ___)) +
  geom_col()
ggplot(paises_longevos, aes(x = pais, y = expVida, color = continente)) +
  geom_col()

¡Ups! Cuando usamos el atributo "color" con geometrías de área (como las barras), lo que controlamos es el color de su línea externa. Si bien el recurso funciona para diferenciar categorías, es poco legible. El atributo que necesitamos aquí es "fill", el color de relleno para nuestras barras. Vamos:

ggplot(paises_longevos, aes(x = ___, y = ___)) +
  ___()
ggplot(paises_longevos, aes(x = ___, y = ___, fill = ___)) +
  geom_col()
ggplot(paises_longevos, aes(x = pais, y = expVida, fill = continente)) +
  geom_col()

Así está mejor.

Veamos que pasa cuando tenemos una cantidad bastante mayor de barras. Por ejemplo, para comprarar la expectativa de vida para todos los países en las Américas. En este caso, mostramos las barras en forma horizontal poniendo la variable categórica en las $y$ y la numérica en las \$x%:

ggplot(gapminder_2007_americas, aes(x = expVida, y = pais)) +
  geom_col()

(prueben invertir las variables asignadas a x e y para ver como vambia la orientación de las barras pero no la información mostrada)

Aquí los valores desordenados (o en todo caso, ordenados por país al que corresponden y no por magnitud) hacen difícil leer el gráfico.

Por suerte este tipo de problema tiene arreglo inmediato: hay que ordenar las barras por tamaño. Una forma de hacerlo es con la función fct_reorder(), incluida en el paquete forcats que trae toda clase de herramientas para trabajar con data categórica. Usando la función con ggplot, usamos dos parámetros: una variable a mostrar, y otra variable por la cual reordenar la variable a mostrar. Para que los países se grafiquen en el orden de expectativa de vida, sería:

# Activamos el paquete que incluye la función fct_reorder()
library(forcats)

ggplot(gapminder_2007_americas, aes(x = expVida, y = fct_reorder(pais, expVida))) +
  geom_col()

Ahora, vamos a un caso en el cual no queremos mostrar una variable numérica, sino conteos: cuantas veces aparece cada valor en en una variable categórica. Este es el dominio de geom_bar().

Para practicar, aquí tenemos los datos de Gapminder para 200. Aquí aparecen con una variable adicional, "nivel de ingreso", que usaremos luego para visualizar subcategorías.

gapminder_2007

Visualicemos la cantidad de países en cada continente. O dicho de otra forma, cuantas veces aparece registrado cada continente. Para usar geom_bar() asignamos a las $x$ la variable a contar. No hace falta asignar una variable a las $y$, porque la altura de cada barra no depende de los valores de alguna otra columna, sino del conteo de subcategorías en la variable de las $x$.

ggplot(gapminder_2007, aes(___)) +
  ___()
ggplot(gapminder_2007, aes(x = ___)) +
  geom_bar()
ggplot(gapminder_2007, aes(x = continente)) +
  geom_bar()

Vemos que geom_bar() se encarga de contar cuantas veces aparece cada categoría de la variable de interés. Pero eso no es todo: también podemos mostrar la distribución de sub-categorías. Para hacerlo mas claro con un ejemplo: mostrar cuantos países hay por continente, y además cuantos de cada categoría de ingresos por continente.

Para mostrar la composición de grupos y subgrupos, usamos de nuevo geom_bar() y asignamos el grupo adicional al atributo estético fill. Para nuestro ejemplo, volvemos a usar "continente" para las $x$, y asignamos "nivIngreso" a "fill".

ggplot(gapminder_2007, aes(x = continente, fill = ___)) +
  geom_bar()
ggplot(gapminder_2007, aes(x = continente, fill = nivIngreso)) +
  geom_bar()

Si no aclaramos nada, geom_bar() apila las cantidades de cada subgrupo en la categoría que les corresponda. Para mostrar las subcategorías en barras separadas, usamos la opción position = "dodge"

ggplot(gapminder_2007, aes(x = continente, fill = ___)) +
  geom_bar(___)
ggplot(gapminder_2007, aes(x = continente, fill = nivIngreso)) +
  geom_bar(position = ___)
ggplot(gapminder_2007, aes(x = continente, fill = nivIngreso)) +
  geom_bar(position = "dodge")

Mostrando proporciones

Podemos usar geom_col y geom_bar() para que muestren partes de un todo en lugar de cantidades o conteos de frecuencia.

Por ejemplo, nuestro gráfico con la cantidad de países observados por cada continente lucía así:

ggplot(gapminder_2007, aes(x = continente)) +
  geom_bar()

Para convertirlo en uno que muestre la proporción de países en el total, hacemos dos ajustes:

ggplot(gapminder_2007, aes(x = ___, fill = continente)) +
  geom_bar(___)
ggplot(gapminder_2007, aes(x = "", fill = ___)) +
  geom_bar(position = ___)
ggplot(gapminder_2007, aes(x = "", fill = continente)) +
  geom_bar(position = "fill")

Aquí podemos aprovechar para mostrar como controlar la orientación de las barras (horizontales vs. verticales) y su ancho. Para que las barras se dibujen en forma horizontal usamos "y" en lugar de "x". Para controlar su tamaño, usamos el parámetro width. Con valores de width menores a 1 se dibujan barras más delgadas, y con mayores a 1 se generan barras más gruesas. Probemos con distintos valores de width para ver que pasa:

ggplot(gapminder_2007, aes(y = "", fill = continente)) +
  geom_bar(position = "fill", width = ___)

Convertir nuestro gráfico con subgrupos para que pase de mostrar cantidades a proporciones es aún más directo: basta con usar el parámetro position = "fill". Intentémoslo:

ggplot(gapminder_2007, aes(x = continente, fill = nivIngreso)) +
  geom_bar(___)
ggplot(gapminder_2007, aes(x = continente, fill = nivIngreso)) +
  geom_bar(position = ___)
ggplot(gapminder_2007, aes(x = continente, fill = nivIngreso)) +
  geom_bar(position = "fill")

Distribuciones

Un clásico: el histograma

La visualización por excelencia para mostrar distribuciones es el histograma, que en la práctica es una variante de... ¡gráfico de barras!.

Hacer un histograma es simple. Por ejemplo, para mostrar cómo se distribuye la variable "PBIpc" (PBI per cápita), la asignamos a las x y usamos geom_histogram():

ggplot(gapminder_2007, aes(___)) +
  ___()
ggplot(gapminder_2007, aes(x = ___)) +
  geom_histogram()
ggplot(gapminder_2007, aes(x = PBIpc)) +
  geom_histogram()

El histograma muestra el rango que toman los valores (del mínimo al máximo que alcanzan), y con que frecuencia se los observa en cada rango. Por ejemplo, el PBI per cápita de las naciones en 2007 llegar hasta casi 50.000 USD en algún caso, pero los casos por encima de 10.000 USD son infrecuentes. Y de hecho, los valores más comunes rondan los 2.500 USD por habitante.

Algo para advertir sobre los histogramas es que su aspecto (y con ello, el mensaje que dan) puede cambiar de acuerdo a la cantidad de intervalos que se usen. Por defecto geom_histogram() divide al rango de valores en 30 "bins", o intervalos iguales (por ejemplo "de 0 a 10", "de 10 a 20", etc) y cuenta cuántas observaciones caen en cada uno. Podemos controlar la cantidad de intervalos con el parámetro bins. podemos aumentar el nivel de detalle del histograma incrementando la cantidad de intervalos, a costa de perder generalización. Y a la inversa: si reducimos el nivel de intervalos, mostramos una distribución más resumida de la data, a costa de perder detalle.

Juguemos con los valores de bins para ver como cambia el resultado. ¿Y cual es el número ideal de intervalos? Depende de las características de cada dataset, y de lo que queremos mostrar... ¡no hay una sola respuesta correcta!

ggplot(gapminder_2007, aes(x = PBIpc, fill = continente)) +
  geom_histogram(bins = ___)

El gráfico de densidad

Los gráficos de densidad son descendientes directos de los histogramas. Pero en lugar de conteos de observaciones por rango de valores, lo que muestran es la distribución de probabilidad de la variable, es decir que tan probable es encontrar un valor determinado si tomaramos al azar una de las observaciones. A diferencia de los histogramas, que llevan un par de siglos en uso porque porque son relativamente fáciles de hacer a mano, los (antes) trabajosos gráficos de densidad se han popularizado con la llegada de software y computadoras capaces de realizarlos en un instante.

Los gráficos de densidad se hacen de forma análoga a los histogramas, reemplazando geom_histogram() geom_density(). Probemos con los valores de "PBIpc"

ggplot(gapminder_2007, aes(___)) +
  ___()
ggplot(gapminder_2007, aes(x = ___)) +
  geom_density()
ggplot(gapminder_2007, aes(x = PBIpc)) +
  geom_density()

Los resultados de geom_density() se interpretan de forma similar a los de geom_density(): Notamos el rango que toman los datos, y que tan comunes son en un rango en comparación con otro. Una ventaja de usar densidad en lugar de histograma se pone de relieve al comparar resultados según categorías internas de la data. Por ejemplo, si queremoos mostrar las distintas distribuciones de PBI según continente, usamos el atributo estético "fill" igual que con los gráficos de barras. Con histogramas, sería así.

ggplot(gapminder_2007, aes(x = PBIpc, fill = continente)) +
  geom_histogram()

¡El resultado es un lío! Las barras apiladas no son ideales para interpretar distribuciones. Las cosas mejoran un poco si usamos position = "dodge", pero no mucho:

ggplot(gapminder_2007, aes(x = PBIpc, fill = continente)) +
  geom_histogram(position = "dodge")

Probemos ahora usando geom_density():

ggplot(gapminder_2007, aes(___)) +
  ___()
ggplot(gapminder_2007, aes(x = PBIpc, fill = ___)) +
  geom_density()
ggplot(gapminder_2007, aes(x = PBIpc, fill = continente)) +
  geom_density()

El resultado es mucho más interpretable. Por ejemplo, por como salta a la vista la bimodalidad de Oceanía, que tiene exactamente dos países en la data. Para revelar cualquier "sorpresa" que pudiera haber quedado oculta en las distribuciones tapadas por otras, ajustamos el atributo de alpha, que por cierto puede usarse con cualquier geometría de ggplot para controlar su grado de transparencia. Intentemos asignando una transparencia del 50% con alpha = 0.5,:

ggplot(gapminder_2007, aes(x = PBIpc, fill = continente), ___) +
  geom_density(___)
ggplot(gapminder_2007, aes(x = PBIpc, fill = continente)) +
  geom_density(alpha = ___)
ggplot(gapminder_2007, aes(x = PBIpc, fill = continente)) +
  geom_density(alpha = 0.5)

Escapando de las barras

Así como el gráfico de densidad reemplaza las barras por curvas, existen otros recursos de visualización que también traen variedad al representar cantidades, proporciones y distribuciones. En algunos casos, no sólo son una alternativa a las barras sino una opción preferible.

"Dotplots"

Los dotplots son el equivalente a marcar con un punto donde termina cada barra... y luego borrar las barras. Expresan la misma información de forma más minimalista. Son útiles como reemplazo a gráficos de barras que muestran una gran cantidad de categorías y resultan visualmente "pesados". Recordemos el gráfico con el que mostramos la expectativa de vida en los países de las Américas:

ggplot(gapminder_2007_americas, aes(x = expVida, y = fct_reorder(pais, expVida))) +
  geom_col()

Un problema que no atendimos antes es que la gran cantidad de barras ocupa casi toda el área disponible, distrayendo del objetivo del gráfico que es resaltar las diferencias entre países. La versión "dotplot" del gráfico resuelve ese problema, y se obtiene con sólo usar geom_point() en lugar de geom_col():

ggplot(gapminder_2007_americas, aes(x = expVida, y = fct_reorder(pais, expVida))) +
  ___
ggplot(gapminder_2007_americas, aes(x = expVida, y = fct_reorder(pais, expVida))) +
  geom_point()

Boxplots

Volvamos a nuestras visualizaciones de distribución. Ya hicimos histogramas (con geom_histogram()) y gráficos de densidad (con geom_density()), pero ahora vamos a usar un "boxplot". Los boxplots pueden interpretarse como un histograma al que se lo ha colgado "de cabeza", y luego resumido mostrando su hitos:

knitr::include_graphics("https://bitsandbricks.github.io/img/leer_un_boxplot.png")

Anatomía de un boxplot

Para hacer un boxplot básico, alcanza con definir un atributo estético: el eje sobre el cual se representarán los valores de la variable de interés; el de las $x$ para que el boxplot se oriente en forma horizontal, o el de las $y$ para el más usado formato vertical. Visualicemos entonces entonces el PBI ("PBIpc") de los países en el eje de las $y$, llamando a geom_boxplot()

ggplot(gapminder_2007, aes(___) +
  ___
ggplot(gapminder_2007, aes(y = ___)) +
  geom_boxplot()
ggplot(gapminder_2007, aes(y = PBIpc)) +
  geom_boxplot()

La gracia de los boxplots es que permiten mostrar diferencias por categoría de forma más prolija que los histogramas, e incluso que los gráficos de densidad. Para separar por categorías, asignemos una variable categórica, "continente" al eje de las $x$:

ggplot(gapminder_2007, aes(___) +
  ___
ggplot(gapminder_2007, aes(y = ___)) +
  geom_boxplot()
ggplot(gapminder_2007, aes(x = continente, y = PBIpc)) +
  geom_boxplot()

Usando como guía el ejemplo la "anatomía de un boxplot" que mostramos antes, deberíamos ser capaces de describir:

Mientras nos entusiasmamos con tanta información útil y fácilmente legible para el ojo entrenado, vale una aclaración: quienes tienen afinidad con la estadística o la visualización de datos se llevan muy bien con los boxplots, pero para el público general suelen ser indigestos. Y bueno... cuando haga falta ser simples y directos siempre tendremos a nuestras barras.

¡Y mas!

Existen muchas, muchas otras formas de comparar cantidades, distribuciones y proporciones. Tenemos "treemaps", "enjambres de abejas", ¡gráficos de piruleta!, gráficos de waffle, gráficos de violín, "ridgeline plots", mapas de calor...

Pero no podemos cerrar la clase sin impartir un saber prohibido: cómo hacer gráficos de torta. ¡Si! Los poco prácticos, inferiores frente a la implacable barra, mal vistos por la comunidad visualizadora, polémicos gráficos de torta.

Si hacer una barra que muestre la proporción de valores por categoría es cuestión de:

ggplot(gapminder_2007, aes(y = "", fill = continente)) +
  geom_bar(position = "fill")

Un gráfico de torta no es otra cosa que un gráfico de barras que usa un sistema de coordenadas polares en lugar de unas cartesianas. Por defecto, ggplot representa los datos usando coordenadas cartesianas, pero permite pasar a polares usando coord_polar() (y el tema "void" se usa sólo para limpiar todo elemento del plot que no sea deliciosa torta):

ggplot(gapminder_2007, aes(y = "", fill = continente)) +
  geom_bar(position = "fill") +
  coord_polar() +
  theme_void()

¡Ahora ya saben!

Para seguir leyendo sobre la visualización de cantidades, proporciones y distribuciones tienen nada menos que los capítulos 7, 8, 9, 10 y 11 de Fundamentals of Data Visualization por Claus Wilke.



bitsandbricks/dataviz documentation built on May 3, 2022, 5:28 p.m.