Introduccion a ggplot2
Herramientas: ggplot2
Presentación
Este documento quiere servir de apoyo a las sesiones de Visual Analytics del Programa Big Data & Business Intelligence de la Universidad de Deusto. Como tal, es un documento en estado constante de revisión y ampliación.
Estos apuntes no pretenden ser una documentación exhaustiva de ggplot2
; la idea es transmitir los entresijos básicos del funcionamiento de ggplot2
.
ggplot2
ggplot2
es un paquete parte de Tidyverse para crear gráficos estáticos. En los siguientes ejemplos usaremos algún otro paquete del Tidyverse, por lo que cargaremos el metapaquete entero (o<tra opción sería cargar sólo los paquetes que vayamos a usar: ggplot2
, dplyr
…)
Layered Grammar of graphics
En 2010 Hadley Wickham publicó el artículo “A layered grammar of graphics”, basado en la Grammar of graphics de Wilkinson y en la que se basa ggplot
.
Dicha gramática por capas consta de los siguientes elementos:
DATA
: ggplot espera como entrada un conjunto de datos limpio (tidy data) como entrada. Además, dependiendo del tipo de gráfica que queramos crear, tendremos que crear algún resumen de datos agregados, o incluso reestructurar el conjunto de datos original. Podemos usar conjuntos de datos distintos para capas distintas.GEOM
: elemento gráfico al que se van a mapear las variables del conjunto de datos: líneas, puntos…STATS
: transformaciones estadísticas de los datos.POSTION
: ajustes para evitar que las marcas se superpongan.COORDINATE
: por defecto ggplot aplicará coordenadas cartesianas al gráfico, pero podemos modificar las escalas, así como el sistema de coordenadas (por ejemplo, a un sistema polar)FACET
: para poder crear small multiples.THEMING
: funciones y argumentos relacionados con aspectos gráficos a los datos propiamente dichos: tipografía, fondo, aspecto de los ejes…
ggplot(data = <DATA>) + <GEOM_FUNCTION>( mapping = aes(<MAPPINGS>), stat = <STAT>, position = <POSITION> ) + <COORDINATE_FUNCTION> + <FACET_FUNCTION>
Cosas que hace ggplot y cosas que no
ggplot
es una herramienta muy versátil para crear todo tipo de gráficos estáticos.
Además es extensible, por lo que existe una comunidad de extensiones que amplian las funciones existentes en ggplot
para facilitar crear determinados gráficos.
Sin embargo, ggplot no tiene:
- Interactividad: filtros… Podemos usar el paquete ggvis (en desarrollo), o montar una app con Shiny.
- Animaciones. aunque están desarrollando una Grammar of animated graphics con gganimate.
Funcionamiento básico
Carga inicial de datos
Cargamos el conjunto de datos diamonds
que incluye ggplot2
y echamos un vistazo a su estructura (más información sobre las variables):
library(ggplot2)
package 'ggplot2' was built under R version 3.5.1
str(diamonds)
## Classes 'tbl_df', 'tbl' and 'data.frame': 53940 obs. of 10 variables:
## $ carat : num 0.23 0.21 0.23 0.29 0.31 0.24 0.24 0.26 0.22 0.23 ...
## $ cut : Ord.factor w/ 5 levels "Fair"<"Good"<..: 5 4 2 4 2 3 3 3 1 3 ...
## $ color : Ord.factor w/ 7 levels "D"<"E"<"F"<"G"<..: 2 2 2 6 7 7 6 5 2 5 ...
## $ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 2 3 5 4 2 6 7 3 4 5 ...
## $ depth : num 61.5 59.8 56.9 62.4 63.3 62.8 62.3 61.9 65.1 59.4 ...
## $ table : num 55 61 65 58 58 57 57 55 61 61 ...
## $ price : int 326 326 327 334 335 336 336 337 337 338 ...
## $ x : num 3.95 3.89 4.05 4.2 4.34 3.94 3.95 4.07 3.87 4 ...
## $ y : num 3.98 3.84 4.07 4.23 4.35 3.96 3.98 4.11 3.78 4.05 ...
## $ z : num 2.43 2.31 2.31 2.63 2.75 2.48 2.47 2.53 2.49 2.39 ...
Función ggplot
ggplot(data = diamonds)
Esta función no da error, pero no vemos ninguna gráfica. Esto se debe a que no hemos indicado a ggplot
qué tiene que hacer con los datos, cómo tiene que convertirlos en elementos gráficos.
Añadir argumentos a ggplot
ggplot(data = diamonds, mapping = aes(x = cut, y = price))
En este caso, además de indicar el conjunto de datos con el que trabajar, mapeamos las variables cut
y price
a variables gráficas (posición en los ejes x
e y
respectivamente). Este mapeo se realiza gracias a la función aes()
, abreviación de aesthetics
(lista de argumentos aes
disponibles en ggplot2).
Sin embargo, seguimos sin ver ninguna gráfica (aunque ya aparecen elementos auxiliares, como los ejes, que se han podido computar porque hemos indicado las variables para dichos ejes). Esto se debe a que todavía no hemos indicado qué tipo de marca gráfica queremos utilizar para crearla.
Añadir geoms
a ggplot
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_point()
Para añadir marcas gráficas con funciones geom
(u otras funciones de ggplot
) tenemos que añadirlas con el signo +
al final de cada función.
geom
que vayamos a usar. Por ejemplo, podemos mapear el argumento estético shape
a una variable si usamosgeom_point
, pero no funciona con geom_line
Modificar los argumentos por defecto
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_point(shape=21)
Vincular correctamente variables a aesthetics
Importante: si usamos un argumento estético dentro de la función aes
, hay que vincularlo a una variable (normalmente un factor), por lo que el aspecto de ese argumento variará dependiendo del valor de la variable; por ejemplo, si tenemos una variable con los valores menor de edad|mayor de edad
y la vinculamos dentro de aes
a colour
, ggplot
asignará un color a menor de edad
y otro a mayor de edad
Sin embargo, si asignamos ese mismo parámetro fuera de aes
, ggplot
asignará un único valor a todas las observaciones.
ggplot(data = diamonds, mapping = aes(x = cut, y = price, colour = color)) +
geom_point()
En el ejemplo anterior la variable color
está correctamente vinculada con el aesthetics
colour
; en el ejemplo siguiente, sin embargo, la vinculación no es correcta:
ggplot(data = diamonds, mapping = aes(x = cut, y = price), colour = color) +
geom_point()
Usemos otros geom
s
Dependiendo del tamaño que le demos al gráfico (especialmente si le damos poca altura), puede llegar a parecer que tenemos líneas uniformes en los distintos niveles de la variable cut
, pero lo que tenemos son 53940 puntos que se superponen; estamos ante un claro ejemplo de overploting. Aunque la gramática de esta gráfica es correcta, la gráfica resultante no resulta significativa, por lo que para poder conocer mejor la relación entre las variables cut
y price
deberíamos optar por otra visualización.
Como en este caso nos interesa ver cuál es la distribución de los valores de la variable price
por cut
, podemos utilizar algunas de las gráficas diseñadas para tal efecto. En este caso, bastaría con cambiar la función geom
(lista con todos los geoms
disponibles en ggplot
)
Gráfica tipo boxplot
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_boxplot()
Gráfica tipo violin
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_violin()
Todas las funciones geom
tienen unas serie de parámetros marcados por defecto que podemos modificar, tal y como veremos más adelante.
Asimismo, las funciones geom
tienen asociada una función stat
por defecto, que se encarga de realizar los cálculos pertinentes para obtener las variables necesarias para dibujar el geom
correspondiente (por ejemplo, en el caso de geom_boxplot
, la función stat_boxplot
calcula los valores ymin
, lower
, middle
… (ver referencia online).
Ejemplos con varias capas
Tal y como indica el concepto layered, ggplot
nos permite ir añadiendo capas de elementos gráficos una sobre otra. Cada función geom
genera una nueva capa gráfica, que se van apilando una sobre la otra según aparecen en el código.
Siguiendo con el ejemplo anterior, las gráficas boxplot
o violin
no muestran todas las observaciones, sino que están diseñadas para ofrecer distintos estadísticos. Así, podemos combinar una capa que contenga una marca gráfica para cada una de las observaciones con una capa que ofrezca resúmenes estadísticos.
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_violin() +
geom_point(alpha = 0.2, position = "jitter", shape = 21)
El parámetro position = "jitter"
distribuye los puntos aleatoriamente en el eje x, sin que esa posición sea significativa. Es una opción para intentar evitar el problema de overploting
ya mencionado.
El parámetro shape = 21
cambia el tipo de punto por defecto (circulo relleno de color) por un círculo en el que sólo se dibuja el perímetro. Esta es otra opción para intentar lidiar con el problema del overploting
.
Partiendo de los mismos datos, si alteramos el orden de las capas y modificamos algunos parámetros gráficos podemos clarificar la gráfica y hacerla más fácil de interpretar.
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_point(alpha = 0.2, position = "jitter", shape = 21) +
geom_violin(color = "red", alpha = 0.2, size = 1)
Gráficas básicas
Histogramas y gráficas de barras
Histograma (una variable cuantitativa)
ggplot(data = diamonds, mapping = aes(x = price)) +
geom_histogram()
stat_bin() using bins = 30. Pick better value with binwidth.
geom_histogram
utiliza por defecto stat_bin
para dibujar la altura de las barras. Podemos modificar el tamaño de los bins
, indicando un número fijo de bins
o bien su anchura (binwidth
).
También podemos cambiar el estadístico utilizado para marcar la altura de las barras, y utilizar la densidad del total en lugar del conteo.
ggplot(data = diamonds, mapping = aes(x = price, y = ..density..)) +
geom_histogram()
stat_bin() using bins = 30. Pick better value with binwidth.
Diagrama de barras (cuentas o proporciones de un factor)
ggplot(data = diamonds, mapping = aes(x = cut)) +
geom_bar()
geom_bar
utiliza la cuenta de valores para calcular la altura de cada barra. En el caso de que queramos vincular la altura a otra variable cuantitativa, tenemos que indicar dicha variable en aes
, y cambiar el stat
por defecto. En el ejemplo, creamos una nueva variable que es la media de precio para cada corte (fun.y = mean
), y cambiamos el stat
a summary
:
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_bar(stat="summary", fun.y=mean)
Diagrama de barras (una variable cuantitativa por una variable cualitativa)
Finalmente, podemos crear un conjunto de datos que ya contenga los cálculos que necesitamos antes de crear la gráfica, e utilizar dicho conjunto de datos en ggplot
:
library(dplyr)
package 'dplyr' was built under R version 3.5.1
The following objects are masked from 'package:stats':
filter, lag
The following objects are masked from 'package:base':
intersect, setdiff, setequal, union
mediaprecio_corte <- diamonds %>%
group_by(cut) %>%
summarize(mediaPrecio = mean(price))
mediaprecio_corte
## # A tibble: 5 x 2
## cut mediaPrecio
## <ord> <dbl>
## 1 Fair 4359.
## 2 Good 3929.
## 3 Very Good 3982.
## 4 Premium 4584.
## 5 Ideal 3458.
ggplot(data = mediaprecio_corte, mapping = aes(x = cut, y = mediaPrecio)) +
geom_col()
Gráficas de dispersión (scaterplot)
ggplot(data = diamonds, mapping = aes(x = carat, y = depth)) +
geom_point()
ggplot(data = diamonds, mapping = aes(x = carat, y = depth)) +
geom_point(alpha = 0.5, shape = 21)
Podemos mapear otras variables a un scaterplot: categóricas a color o forma, cuantitativas a tamaño (normalmente nos referimos a este tipo de gráfica como bubble chart).
ggplot(data = diamonds, mapping = aes(x = x, y = table, color = cut, size = z)) +
geom_point(alpha = 0.2)
Gráficas de líneas
geom_freqpoly
hace los mismos cáculos que geom_histogram
, pero en lugar de dibujar barras dibuja líneas.
ggplot(data = diamonds, mapping = aes(x = price)) +
geom_freqpoly()
stat_bin() using bins = 30. Pick better value with binwidth.
Podemos dividir la línea a partir de los niveles de un factor (variable cualitativa).
ggplot(data = diamonds, mapping = aes(x = price, group = cut)) +
geom_freqpoly()
stat_bin() using bins = 30. Pick better value with binwidth.
O para facilitar la identificación de cada nivel, utilizar una variable gráfica como color
o linetype
(o incluso mapear la misma variable del conjunto de datos a dos variables gráficas, para reforzar la identificación de cada nivel).
ggplot(data = diamonds, mapping = aes(x = price, color = cut, linetype = cut)) +
geom_freqpoly()
stat_bin() using bins = 30. Pick better value with binwidth.
Gráficas avanzadas
Facetas o small multiples
Una posible forma de evitar el problema del overplotting es subdividir la gráfica. La función facet_wrap
permite indicar una faceta, es decir, un vector de tipo carácter (normalmente, una variable categórica del conjunto de datos).
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_point() +
facet_wrap(~ color)
Si queremos cruzar dos variables categóricas, tenemos que suar la función facet_grid
:
ggplot(data = diamonds, mapping = aes(x = cut, y = price)) +
geom_point() +
facet_grid(clarity ~ color)
Alterar la estructura de datos
Para poder crear algunas gráfica será necesario que reestructuremos el conjunto de datos para poder obtener el resultado deseado.
Heatmap
En este ejemplo, vamos a crear un heatmap que mostrará los nivels de la variable categórica cut
en el eje y
, y una serie de variables cuantitativas en el eje x
. Como solo podemos mapear una variable a cada eje, para poder mostrar más de una en el eje x
tendremos que “fundir” las variables y sus valores en pares clave-valor
, con la función ‘gather’.
Una vez modificada la estructura, podemos crear el heatmap usando el geom_tile
.
library(tidyr)
package 'tidyr' was built under R version 3.5.1
diamonds_Heatmap <- diamonds %>%
group_by(cut) %>%
summarise(depthPercent = mean(depth),
table = mean(table),
length = mean (x),
width = mean(y),
depth = mean(z)) %>%
gather(variable, valor, -cut)
diamonds_Heatmap
## # A tibble: 25 x 3
## cut variable valor
## <ord> <chr> <dbl>
## 1 Fair depthPercent 64.0
## 2 Good depthPercent 62.4
## 3 Very Good depthPercent 61.8
## 4 Premium depthPercent 61.3
## 5 Ideal depthPercent 61.7
## 6 Fair table 59.1
## 7 Good table 58.7
## 8 Very Good table 58.0
## 9 Premium table 58.7
## 10 Ideal table 56.0
## # ... with 15 more rows
ggplot(data = diamonds_Heatmap, mapping = aes(x = variable, y = cut)) +
geom_tile(aes(fill = valor), color = "white")
ggplot(data = diamonds_Heatmap, mapping = aes(x = variable, y = cut, label = sprintf("%0.2f", round(valor, digits = 2)))) +
geom_tile(aes(fill = valor), color = "white") +
geom_label()
Jugando con el sistema de coordenadas
Coordenadas polares: Pie chart y radar chart
library(tidyr)
diamonds_Pie <- diamonds %>%
group_by(cut) %>%
summarise(TotalPrice = sum(price))
diamonds_Pie
## # A tibble: 5 x 2
## cut TotalPrice
## <ord> <int>
## 1 Fair 7017600
## 2 Good 19275009
## 3 Very Good 48107623
## 4 Premium 63221498
## 5 Ideal 74513487
ggplot(data = diamonds_Pie, mapping = aes(x = "", y = TotalPrice, fill = cut)) +
geom_bar(width = 1, stat = "identity") +
coord_polar(theta = "y")
library(tidyr)
diamonds_Radar <- diamonds %>%
group_by(cut) %>%
summarise(depthPercent = mean(depth),
table = mean(table),
length = mean (x),
width = mean(y),
depth = mean(z)) %>%
gather(variable, valor, -cut)
diamonds_Radar
## # A tibble: 25 x 3
## cut variable valor
## <ord> <chr> <dbl>
## 1 Fair depthPercent 64.0
## 2 Good depthPercent 62.4
## 3 Very Good depthPercent 61.8
## 4 Premium depthPercent 61.3
## 5 Ideal depthPercent 61.7
## 6 Fair table 59.1
## 7 Good table 58.7
## 8 Very Good table 58.0
## 9 Premium table 58.7
## 10 Ideal table 56.0
## # ... with 15 more rows
ggplot(data = diamonds_Radar, mapping = aes(x = variable, y = valor)) +
geom_polygon(mapping = aes(group = cut, color = cut), fill = NA) +
coord_polar()
Coordenadas paralelas
ggplot(data = diamonds_Radar, mapping = aes(x = variable, y = valor)) +
geom_line(mapping = aes(group = cut, color = cut))
depth
, length
y width
tienen un rango de valores similares, y también las variables depthPercent
y table
. Podemos normalizar los valores.
diamonds_Radar_filtrado <- diamonds_Radar %>%
filter(variable %in% c("depth","length","width"))
ggplot(data = diamonds_Radar_filtrado, mapping = aes(x = variable, y = valor)) +
geom_line(mapping = aes(group = cut, color = cut))
Recursos de interés
- ggplot2 Reference
- ggplot2 Guía rápida (actualizado 12/16)
- Extensiones para
ggplot2
- Data Transformation with dplyr cheatsheet
- Top 50 ggplot2 visualizations - The master list (with full R code)
- The R graph gallery - ggplot2
- Graphs with ggplot2
- @\accidental__aRt: When data visualization goes beautifully wrong
- Esquisse: Addin para RStudio que provee una interfaz gráfica a la Tableau para trabajar con ggplot2 (en desarrollo).
Bibliografía
- Wickham, 2010, “A layered grammar of graphics”, Journal of Computational and Graphical Statistics, vol. 19, no. 1, pp. 3–28
- Wickham, 2014, “Tidy Data”, Journal of Satistical Software, vol. 59, issue 10
- Wickham, 2016 (2nd edition), ggplot2: Elegant Graphics for Data Analysis, Springer (el material necesario para compilar el libro está disponible en github)
- Wickham & Gromelund, 2017. R for Data Science, O’Reilly