dplyr: tutorial completo

Dplyr es uno de los paquetes principales del universo tidyverse, y uno de los más utilizados en R. Sin duda alguna, dplyr es un paquete muy potente, ya que permite manipular datos de forma muy sencilla, tanto para datos de R, como bases de datos (sin saber SQL) o con Spark.

Además, al ser una librería del universo tidyverse, es muy sencillo usar dplyr con otros paquetes del universo, como ggplot, lo que permite, por ejemplo, hacer gráficos muy chulos de forma sencilla y sin tener que haber creado objetos intermedios.

Como ves, dplyr es muy potente. Sin embargo, ¿conoces exactamente cómo funciona, todas las funciones que ofrece y por qué es tan potente? Pues en este post/tutorial te explicaré todo lo que tienes que saber sobre dplyr. ¡Vamos a ello!

Instalación y operador pipe

Lo primero de todo que tendremos que hacer es instalar dplyr. Para ello, simplemente hay que correr el siguiente código:

dplyr incluye el operador %>%, llamado pipe, que es muy útil y es aplicable a todo el ecosistema tidyverse. Este operador permite concatenar funciones de tal forma que los datos vayan pasándose de una función a otra sin tener que asignarlos a ninguna variable.

Por ejemplo, imaginemos que queremos ordenar los coches que tienen más de 100 caballos de potencia por su consumo de gasolina (variable mpg) de forma descendente. Veamos cómo lo haríamos sin usar el operador pipe.

# Sin usar el pipe
mtcars_filtered = filter(mtcars,  hp>100)
mtcars_ordered = arrange(mtcars_filtered, desc(mpg))

head(mtcars_ordered)

Ahora, veámos cómo lo haríamos con el pipe operator:

mtcars %>%
  filter(hp>100) %>%
  arrange(desc(mpg)) %>%
  head()

Como vemos, el resultado es exactamente el mismo, pero usando el pipe nos hemos evitado tener que crear objetos intermedios, por lo que si quisiéramos cambiar nuestra transformación sería muy sencillo. Además, el código es mucho más claro usando el operador pipe.

Ahora que ya sabes qué es el operador pipe, sigamos con el tutorial de dplyr, viendo poco a poco cuáles son la funciones principales de dplyr enfocadas en distintas transformaciones.

Funciones principales de dplyr

Básicamente, dplyr cuenta con 5 grupos diferentes de funciones: funciones de resumen, de agrupación, de selección/filtro, de manipulación y de combinación.

Aunque el valor de dplyr reside en poder combinar todas estas funciones para poder manipular datos de forma sencilla y limpia, para poder hacer eso primero es fundamental conocer las funciones principales que ofrece el paquete.

Así pues, vayamos paso a paso conociendo cada una de las funciones de cada grupo. ¡Vamos a ello!

Funciones de agrupación de dplyr

Las funciones de agrupación permiten agrupar los datos de tal forma que podamos aplicar ciertas funciones para cada uno de los grupos. Básicamente existen dos tipos de funciones: group_by y ungroup.

Función group_by

La función group_by permite agrupar los datos en distintos grupos en base a una o varias variables de nuestros dataframe. Cuando un dataframe está agrupado, sigue siendo un único dataframe, pero las operaciones que hagamos se aplicarán a cada uno de los grupos. Es por ello que la función group_by rara vez se usa sola, sino que seguramente se usará con otras funciones de dplyr.

Asimismo, es importante recalcar que lo más habitual es realizar agrupaciones en función de una o varias variables en formato texto o categórica, ya que rara vez tiene sentido que varias observaciones numéricas tengan el mismo (aunque puede darse el caso).

En nuestro caso, vamos a usar el dataset gapminder, el cual contiene datos sobre la esperanza de vida, población y PIB per Cápita de diferentes países para diferentes años. Este es el dataset:

library(gapminder)
glimpse(gapminder)
Rows: 1,704
Columns: 6
$ country   <fct> Afghanistan, Afghanistan, Afghanistan, Afghanistan, Afghanistan, Afghanistan, Afghanistan, Afghanistan, Afghanistan, Afghanis~
$ continent <fct> Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Asia, Europe, Europe, Europe, Europe, Europe, Europe, Europ~
$ year      <int> 1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992, 1997, 2002, 2007, 1952, 1957, 1962, 1967, 1972, 1977, 1982, 1987, 1992,~
$ lifeExp   <dbl> 28.801, 30.332, 31.997, 34.020, 36.088, 38.438, 39.854, 40.822, 41.674, 41.763, 42.129, 43.828, 55.230, 59.280, 64.820, 66.22~
$ pop       <int> 8425333, 9240934, 10267083, 11537966, 13079460, 14880372, 12881816, 13867957, 16317921, 22227415, 25268405, 31889923, 1282697~
$ gdpPercap <dbl> 779.4453, 820.8530, 853.1007, 836.1971, 739.9811, 786.1134, 978.0114, 852.3959, 649.3414, 635.3414, 726.7341, 974.5803, 1601.~

Imaginemos que queremos obtener el promedio de esperanza de vida para cada continente para cada año, de tal forma que podamos visualizar cómo ha evolucionado la esperanza de vida por continente. Para ello, agruparemos los datos por continente y año y usaremos la función summarize (la cual explicaré mas adelante) para obtener el promedio de esperanza de vida.

gapminder %>%
  group_by(continent, year) %>%
  summarize(mean(lifeExp))

Como vemos, con tan solo 3 líneas hemos conseguido realizar un resumen de los datos muy interesante. Así pues, casi todas las funciones que vamos a aprender en este tutorial de dplyr se podrán aplicar con datos agrupados.

Ejercicio de la función group_by

Para comprobar que lo has entendido, te planteo el siguiente ejercicio: calcular la esperanza de vida media a nivel mundial, para cada año.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicykiLCJzYW1wbGUiOiIjIGRwbHlyIGFuZCBnYXBtaW5kZXIgYXJlIGxvYWRlZCBhbHJlYWR5XG4jIGRwbHlyIHkgZ2FwbWluZGVyIHlhIGVzdFx1MDBlMW4gY2FyZ2Fkb3NcbnJlc3VsdFxuXG4jIFByaW50IHJlc3VsdFxucHJpbnQocmVzdWx0KSIsInNvbHV0aW9uIjoicmVzdWx0ID0gZ2FwbWluZGVyICU+JVxuICBncm91cF9ieSh5ZWFyKSAlPiVcbiAgc3VtbWFyaXplKG1lYW4obGlmZUV4cCkpXG5cbnByaW50KHJlc3VsdCkiLCJzY3QiOiJ0ZXN0X2Z1bmN0aW9uKFwiZ3JvdXBfYnlcIiwgXG4gICAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIllvdSBzaG91bGQgdXNlIGdyb3VwX2J5IGZ1bmN0aW9uLiAtIERlYmVyXHUwMGVkYXMgdXNhciBsYSBmdW5jaVx1MDBmM24gZ3JvdXBfYnkuXCIpXG50ZXN0X2Z1bmN0aW9uKFwic3VtbWFyaXplXCIsIFxuICAgICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJZb3Ugc2hvdWxkIHVzZSBzdW1tYXJpemUgZnVuY3Rpb24uIC0gRGViZXJcdTAwZWRhcyB1c2FyIGxhIGZ1bmNpXHUwMGYzbiBzdW1tYXJpemVcIilcblxudGVzdF9vYmplY3QoXCJyZXN1bHRcIixcbiAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIkluY29ycmVjdC4gLSBJbmNvcnJlY3RvLlwiXG4gICAgICAgICAgICApXG5cbnRlc3RfZXJyb3IoKVxuc3VjY2Vzc19tc2coXCJXZWxsIGRvbmUhIC0gQmllbiBoZWNobyFcIikifQ==

Función ungroup

A veces, hacemos una agrupación para obtener un dato del grupo (la suma total de la variable, por ejemplo), pero no queremos seguir realizando operaciones con los datos agrupados. En ese caso, deberemos desagrupar los datos, y esto se puede hacer con la función ungroup.

Por ejemplo, supongamos que para un año en concreto (digamos 1952) queremos mostrar un gráfico en el que comparemos cada uno de los países con la esperanza de vida promedio de su continente. Para ello, simplemente habría que:

  • Filtrar los datos por el año que queramos.
  • Agrupar los datos por continente.
  • Calcular la esperanza de vida media del continente.
  • Desagrupar los datos.
gapminder %>%
  select(-pop, -gdpPercap) %>%
  filter(year == 1952) %>%
  group_by(continent) %>%
  mutate(mean_lifeExp = mean(lifeExp)) %>%
  ungroup() 

Como vemos, todos los países del mismo continente tienen el mismo mean_lifeExp, lo cual tiene sentido, porque el promedio de esperanza de vida de un continente es igual para todos los países del continente.

Y con esto, ya conoces las funciones de agrupación. Sin embargo, ves cómo estas funciones son muy útiles cuando se usan con otras funciones. ¡Veamos cuáles son!

Funciones de selección y filtro de dplyr

Función select()

La función select es muy sencilla, te permite indicar las columnas que quieres seleccionar o dejar de utilizar. Generalmente, cuando manipulas datos creas bastantes variables auxiliares para hacer comprobaciones y son variables que no quieres en el dataset final. Por eso, se suele utilizar esta función para únicamente quedarte con las variables que quieras.

Otro caso de uso, es cuando el dataset incluye más variables de las que vas a usar para el análisis. En esos casos, quedarte desde el principio únicamente con las variables que te interesan te va a permitir (1) que el código sea más rápido y (2) que los resultados sean más fácilmente interpretables, ya que no habrá variables molestas.

Para seleccionar unas variables, simplemente debes pasar el nombre de las columnas a la función:

gapminder %>%
  select(country, year, pop)

Asimismo, la función select también permite hacer un drop, es decir, deseleccionar una o varias columnas. Para ello, simplemente se debe pasar el símbolo – delante de la variable. Ejemplo:

gapminder %>%
  select(-year, -country, -pop)
Ejercicio función select

Para practicar la función select de dplyr te planteo el siguiente ejercicio: del dataframe gapminder, elije las variables continent, year y lifeExp. Primero, debes hacerlo seleccionando las variables que te interesan.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicykiLCJzYW1wbGUiOiIjIGRwbHlyIGFuZCBnYXBtaW5kZXIgYXJlIGxvYWRlZCBhbHJlYWR5XG4jIGRwbHlyIHkgZ2FwbWluZGVyIHlhIGVzdFx1MDBlMW4gY2FyZ2Fkb3NcbnJlc3VsdCBcblxuIyBQcmludCByZXN1bHRcbnByaW50KHJlc3VsdCkiLCJzb2x1dGlvbiI6InJlc3VsdCA9IGdhcG1pbmRlciAlPiUgc2VsZWN0KGNvbnRpbmVudCwgeWVhciwgbGlmZUV4cClcbnByaW50KHJlc3VsdCkiLCJzY3QiOiJ0ZXN0X2Z1bmN0aW9uKFwic2VsZWN0XCIsIFxuICAgICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJZb3Ugc2hvdWxkIHVzZSBzZWxlY3QgZnVuY3Rpb24uIDwvYnI+IERlYmVyXHUwMGVkYXMgdXNhciBsYSBmdW5jaVx1MDBmM24gc2VsZWN0XCIpXG5cbnRlc3Rfb2JqZWN0KFwicmVzdWx0XCIsXG4gICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJJbmNvcnJlY3QuIC0gSW5jb3JyZWN0by5cIlxuICAgICAgICAgICAgKVxuXG50ZXN0X2Vycm9yKClcbnN1Y2Nlc3NfbXNnKFwiV2VsbCBkb25lISAtIEJpZW4gaGVjaG8hXCIpIn0=

Ahora, consigue el mismo resultado, pero de otra forma, usando la función select de dplyr:

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicykiLCJzYW1wbGUiOiIjIGRwbHlyIGFuZCBnYXBtaW5kZXIgYXJlIGxvYWRlZCBhbHJlYWR5XG4jIGRwbHlyIHkgZ2FwbWluZGVyIHlhIGVzdFx1MDBlMW4gY2FyZ2Fkb3NcblxucmVzdWx0IFxuXG4jIFByaW50IHJlc3VsdFxucHJpbnQocmVzdWx0KSIsInNvbHV0aW9uIjoicmVzdWx0ID0gZ2FwbWluZGVyICU+JSBzZWxlY3QoLWNvdW50cnksIC1wb3AsIC1nZHBQZXJjYXApXG5cbnByaW50KHJlc3VsdCkiLCJzY3QiOiJ0ZXN0X2Z1bmN0aW9uKFxuICBcInNlbGVjdFwiLCBcbiAgaW5jb3JyZWN0X21zZyA9IFwiWW91IHNob3VsZCB1c2Ugc2VsZWN0IGZ1bmN0aW9uLiAtIERlYmVyXHUwMGVkYXMgdXNhciBsYSBmdW5jaVx1MDBmM24gc2VsZWN0XCIpXG5cbnRlc3Rfb3V0cHV0X2NvbnRhaW5zKFwiLWNvdW50cnlcIixcbiAgICAgICAgICAgICAgICAgICAgIGluY29ycmVjdF9tc2c9XCJZb3Ugc2hvdWxkIHVzZSA8Y29kZT4tPC9jb2RlPiB0byBleGNsdWRlIHZhcmlhYmxlcy4gLSBEZWJlclx1MDBlZGFzIHVzYXIgIDxjb2RlPi08L2NvZGU+IHBhcmEgZXhjbHVpciB2YXJpYWJsZXMuXCIpXG50ZXN0X29iamVjdChcInJlc3VsdFwiLFxuICAgICAgICAgICAgaW5jb3JyZWN0X21zZyA9IFwiSW5jb3JyZWN0LiAtIEluY29ycmVjdG8uXCJcbiAgICAgICAgICAgIClcblxudGVzdF9lcnJvcigpXG5zdWNjZXNzX21zZyhcIldlbGwgZG9uZSEgLSBCaWVuIGhlY2hvIVwiKSJ9

Función filter()

La función filter permite filtrar los datos para una o varias condiciones que le pasemos. Por ejemplo, tal como hemos hecho en el ejemplo anterior, podemos filtrar los datos para quedarnos únicamente con los coches con más de 100 caballos de potencia (hp).

mtcars %>%
  filter(hp > 100)

Cuando se trata de variables numéricas, hay seisfiltros que podemos aplicar: mayor que (>), menor que (<), igual a (==), diferente a (!=), mayor o igual que (>=) y menor o igual que (<=).

t1 = mtcars %>%
  filter(hp > 100) 

t2 = mtcars %>%
  filter(hp < 100)

t3 = mtcars %>%
  filter(hp == 100)

t4 = mtcars %>%
  filter(hp != 100)

t5 = mtcars %>%
  filter(hp >= 100)

t6 = mtcars %>%
  filter(hp <= 100) 

Además, de por variables numéricas, la función filter también permite filtrar las variables que sean texto o factor. En este caso, hay tres opciones que podemos aplicar: igual a (==), diferente a (!=), está incluido en (%in% c()), no está incluido en (!valor %in% c()).

Veamos un ejemplo con la variable Species del dataset iris , el cual es un factor que cuenta con 3 valores:

# La especie es setosa 
iris %>%
  filter(Species == "setosa") 

# La especie NO es setosa  
iris %>%
  filter(Species != "setosa") 

# La especie es setosa o virginica
iris %>%
  filter(Species %in% c("setosa", "virginica")) 

# La especie NO es setosa ni virginica
iris %>%
  filter(!Species %in% c("setosa", "virginica")) 

Además, podemos combinar varios filtros, de tal forma que podamos filtrar, a la vez, por varias variables, cada una con su condición. En estos casos hay dos opciones: que se deban cumplir las dos opciones, en cuyo caso los filtros se unen por el símbolo & o que se cumpla cualquiera de las dos opciones, en cuyo caso se usa el símbolo |.

# Más que 100 caballos Y el número de cilindros es igual a 6
mtcars %>%
  filter(hp > 100 & cyl == 6)


# Más que 100 caballos O el número de cilindros es igual a 6
mtcars %>%
  filter(hp > 100 | cyl == 6)

Como ves, la función filter es muy intuitiva y muy potente, ya que permite filtrar la información de una forma mucho más intuitiva y clara que los filtros por defecto de R.

Ejercicios función filter

Para practicar la función filter, te voy a pedir que, del dataset gapminder te quedes con las observaciones que: son de África, que el año sea 2007 y que la esperanza de vida sea superior a 60 años.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiLFwidGlkeXJcIilcbnNhcHBseShsaWJzWyFsaWJzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCldLCBpbnN0YWxsLnBhY2thZ2VzKVxuc2FwcGx5KGxpYnMsIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVClcbnJtKGxpYnMpIiwic2FtcGxlIjoiIyBkcGx5ciBhbmQgZ2FwbWluZGVyIGFyZSBsb2FkZWQgYWxyZWFkeVxuIyBkcGx5ciB5IGdhcG1pbmRlciB5YSBlc3RcdTAwZTFuIGNhcmdhZG9zXG5yZXN1bHRcblxuIyBQcmludCByZXN1bHRcbnByaW50KHJlc3VsdCkiLCJzb2x1dGlvbiI6InJlc3VsdCA9IGdhcG1pbmRlciAlPiUgXG4gIGZpbHRlcihjb250aW5lbnQgPT0gXCJBZnJpY2FcIiAmXG4gICAgICAgICB5ZWFyID09IDIwMDcgJlxuICAgICAgICAgICBsaWZlRXhwID4gNjApXG5cbnByaW50KHJlc3VsdCkiLCJzY3QiOiJ0ZXN0X2Z1bmN0aW9uKFwiZmlsdGVyXCIsIFxuICAgICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJZb3Ugc2hvdWxkIHVzZSBmaWx0ZXIgZnVuY3Rpb24uIC0gRGViZXJcdTAwZWRhcyB1c2FyIGxhIGZ1bmNpXHUwMGYzbiBmaWx0ZXJcIilcblxudGVzdF9vYmplY3QoXCJyZXN1bHRcIixcbiAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIkluY29ycmVjdC4gLSBJbmNvcnJlY3RvLlwiXG4gICAgICAgICAgICApXG5cbnRlc3RfZXJyb3IoKVxuc3VjY2Vzc19tc2coXCJXZWxsIGRvbmUhIC0gQmllbiBoZWNobyFcIikifQ==

Función distinct()

La función distinct es muy sencilla, ya que permite eliminar los datos duplicados de nuestro dataset. Esto es algo que en un dataset ya limpio no tiene sentido, pero cobra importancia cuando debemos limpiar y transformar los datos.

Para ello, vamos a crear un dataset nuevo, que sea iris_duplicate, que básicamente contendrá el dataset iris duplicado. Así, veremos cómo funciona esta función:

dim(iris)
[1] 150   5
iris %>%
  distinct() %>%
  dim()
[1] 149   5
iris_duplicate = bind_rows(iris, iris)

dim(iris_duplicate)
[1] 300   5
iris_duplicate %>%
  distinct() %>%
  dim()
[1] 149   5

Como vemos, el dataset iris tiene 150 observaciones, pero una de ellas debe estar duplicada (o los valores coinciden), ya que si aplicamos la función distinct nos quedaremos con 149 observaciones.

Además, aunque el dataset iris_duplicate tenga 300 observaciones, tras aplicar la función distinct nos hemos quedado con los 149 valores que son únicos.

Función slice()

La función slice permite quedarse con los datos de las posiciones que digamos. Es decir, si hacemos slice(1) nos quedaremos con el primer valor, y si hacemos slice(1:20), nos quedaremos con los primeros 20 valores:

iris %>%
  slice(1) %>%
  dim()
[1] 1 5
iris %>%
  slice(1:20) %>%
  dim()
[1] 20  5

Aunque parezca una función muy tonta, la función slice es muy útil sobre todo cuando se aplica con la función group_by, ya que nos permitirá obtener la cantidad de valores que queramos para cada uno de los grupos. De hecho, esto mismo es lo que utilicé para crear un barchart race en el post sobre cómo crear animaciones con R.

Asimismo, existen otras funciones slice que permiten realizar la misma tarea, pero con alguna modificación. Por ejemplo, las funciones slice_min y slice_max te permiten obtener las filas con el valor mínimo o máximo de una variable.

Esto, combinado con la función group_by es muy potente, ya que en dos líneas somos capaces de obtener las observaciones con valores máximos para cada grupo. Con Gapminder, por ejemplo, podemos obtener el país que más esperanza de vida (LifeExp) tiene por continente y año.

library(gapminder)

gapminder %>%
  group_by(year, continent) %>%
  slice_max(lifeExp)

Por otro lado, la función slice_sample te permite obtener una cantidad de datos aleatoria, lo cual puede ser muy útil, por ejemplo, para hacer el split de los datos entre train, test y validación, de cara a entrenar un modelo de machine-learning.

# Obtenemos 20 datos aleatorios de Gapminder
gapminder %>%
  slice_sample(n = 5)
# Obtenemos una muestra aleatoria que suponga el 1% de los datos
gapminder %>%
  slice_sample(prop = 0.01)

Por último, las funciones slice_head y slice_tail te permiten obtener la cantidad de datos que quieras empezando por arriba (head) o abajo (tail) y respetando el orden actual de los datos.

Ahora que ya sabes cómo funciona, veámos cómo utilizarlos en la práctica.

Ejercicios función slice

Usando el dataset Gapminder y alguna función de la familia slice, encuentra los países con mayor esperanza de vida de cada año.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiLFwidGlkeXJcIilcbnNhcHBseShsaWJzWyFsaWJzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCldLCBpbnN0YWxsLnBhY2thZ2VzKVxuc2FwcGx5KGxpYnMsIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVClcbnJtKGxpYnMpIiwic2FtcGxlIjoiIyBkcGx5ciBhbmQgZ2FwbWluZGVyIGFyZSBsb2FkZWQgYWxyZWFkeVxuIyBkcGx5ciB5IGdhcG1pbmRlciB5YSBlc3RcdTAwZTFuIGNhcmdhZG9zXG5yZXN1bHRcblxuIyBQcmludCByZXN1bHRcbnByaW50KHJlc3VsdCkiLCJzb2x1dGlvbiI6ImdhcG1pbmRlciAlPiUgXG4gIGZpbHRlcihjb250aW5lbnQgPT0gXCJBZnJpY2FcIiAmXG4gICAgICAgICB5ZWFyID09IDIwMDcgJlxuICAgICAgICAgICBsaWZlRXhwID4gNjApIiwic2N0IjoidGVzdF9mdW5jdGlvbihcInNsaWNlXCIsIFxuICAgICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJZb3Ugc2hvdWxkIHVzZSBmaWx0ZXIgZnVuY3Rpb24uIC0gRGViZXJcdTAwZWRhcyB1c2FyIGxhIGZ1bmNpXHUwMGYzbiBmaWx0ZXJcIilcblxudGVzdF9vYmplY3QoXCJyZXN1bHRcIixcbiAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIkluY29ycmVjdC4gLSBJbmNvcnJlY3RvLlwiXG4gICAgICAgICAgICApXG5cbnRlc3RfZXJyb3IoKVxuc3VjY2Vzc19tc2coXCJXZWxsIGRvbmUhIC0gQmllbiBoZWNobyFcIikifQ==

Ahora, encuentra los 5 países con mayor esperanza de vida de cada año.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiLFwidGlkeXJcIilcbnNhcHBseShsaWJzWyFsaWJzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCldLCBpbnN0YWxsLnBhY2thZ2VzKVxuc2FwcGx5KGxpYnMsIHJlcXVpcmUsIGNoYXJhY3Rlci5vbmx5ID0gVClcbnJtKGxpYnMpIiwic2FtcGxlIjoiIyBkcGx5ciBhbmQgZ2FwbWluZGVyIGFyZSBsb2FkZWQgYWxyZWFkeVxuIyBkcGx5ciB5IGdhcG1pbmRlciB5YSBlc3RcdTAwZTFuIGNhcmdhZG9zXG5yZXN1bHRcblxuIyBQcmludCByZXN1bHRcbnByaW50KHJlc3VsdCkiLCJzb2x1dGlvbiI6InJlc3VsdCA9IGdhcG1pbmRlciAlPiUgXG4gIGdyb3VwX2J5KHllYXIpICU+JVxuICBzbGljZV9tYXgobGlmZUV4cCwgbiA9IDUpXG5cbnByaW50KHJlc3VsdCkiLCJzY3QiOiJ0ZXN0X2Z1bmN0aW9uKFwic2xpY2VcIiwgXG4gICAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIllvdSBzaG91bGQgdXNlIGZpbHRlciBmdW5jdGlvbi4gLSBEZWJlclx1MDBlZGFzIHVzYXIgbGEgZnVuY2lcdTAwZjNuIGZpbHRlclwiKVxuXG50ZXN0X29iamVjdChcInJlc3VsdFwiLFxuICAgICAgICAgICAgaW5jb3JyZWN0X21zZyA9IFwiSW5jb3JyZWN0LiAtIEluY29ycmVjdG8uXCJcbiAgICAgICAgICAgIClcblxudGVzdF9lcnJvcigpXG5zdWNjZXNzX21zZyhcIldlbGwgZG9uZSEgLSBCaWVuIGhlY2hvIVwiKSJ9

Función arrange

La función arrange te permite ordenar los datos en base a una o varias variables, tanto de forma ascendente como descendente. Por defecto, los datos se ordenarán de forma ascendente, para indicar que se ordenen de forma descendente, tendremos que envolver la variable con la función desc.

Por ejemplo, supongamos que queremos ordenar los coches según sus consumos (mpg):

mtcars %>%
  arrange(desc(mpg))

Como vemos, los coches están ordenados, pero hay casos en los que el valor de mpg coincide, como en el caso del Honda y del Lotus (30.4) o el Datsu y Mercedes (22.8).

En esos casos, la función ordenará según la posición en la que estaban en el dataframe. Sin embargo, podemos indicar que los ordene por otra variable, como por ejemplo, los caballos (hp) de forma descendente también:

mtcars %>%
  arrange(desc(mpg), desc(hp))

Como vemos, en este caso tanto el Lotus como el Mercedes aparecen por delante del Honda y el Datsu, ya que, a igualdad de consumo, cuentan con más caballos.

Ejercicio función arrange

Vamos a poner en práctica todo lo que hemos visto sobre la función arrange de dplyr. Para ello, te pido que ordenes el dataset gapminder por año (más nuevos primero), continente (de Z a A) y por esperanza de vida (de menor a mayor).

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicykiLCJzYW1wbGUiOiIjIGRwbHlyIGFuZCBnYXBtaW5kZXIgYXJlIGxvYWRlZCBhbHJlYWR5XG4jIGRwbHlyIHkgZ2FwbWluZGVyIHlhIGVzdFx1MDBlMW4gY2FyZ2Fkb3NcbnJlc3VsdFxuXG4jIFByaW50IHJlc3VsdFxucHJpbnQocmVzdWx0KSIsInNvbHV0aW9uIjoicmVzdWx0ID0gZ2FwbWluZGVyICU+JSBcbiAgYXJyYW5nZShkZXNjKHllYXIpLCBkZXNjKGNvbnRpbmVudCksIGxpZmVFeHApXG5cbnByaW50KHJlc3VsdCkiLCJzY3QiOiJ0ZXN0X2Z1bmN0aW9uKFwiYXJyYW5nZVwiLCBcbiAgICAgICAgICAgICAgaW5jb3JyZWN0X21zZyA9IFwiWW91IHNob3VsZCB1c2UgYXJyYW5nZSBmdW5jdGlvbi4gLSBEZWJlclx1MDBlZGFzIHVzYXIgbGEgZnVuY2lcdTAwZjNuIGFycmFuZ2VcIilcblxudGVzdF9vYmplY3QoXCJyZXN1bHRcIixcbiAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIkluY29ycmVjdC4gLSBJbmNvcnJlY3RvLlwiXG4gICAgICAgICAgICApXG5cbnRlc3RfZXJyb3IoKVxuc3VjY2Vzc19tc2coXCJXZWxsIGRvbmUhIC0gQmllbiBoZWNobyFcIikifQ==

Y con esta función tan sencilla (y práctica) ya has visto las funciones más importantes de filtrado dentro de dplyr. Como ves, una función sencilla pero muy interesante y práctica.

Sin embargo, dplyr esconde mucho más, así que sigamos con el tutorial, en este caso con funciones de resumen.

Funciones de resumen

Las funciones de resumen se suelen utilizar, casi siempre, después de una función de agrupación (group_by), ya que esto permite obtener métricas para cada uno de los grupos de una forma muy sencilla.

Función summarise

La función summarise (o summarize) permite obtener datos de cada uno de los grupos, tales como el máximo, el mínimo, la media, desviación típica, número de observaciones, etc.

Para ello, simplemente hay que:

  1. Indicar el nombre de la nueva columna que se va a crear.
  2. Pasar la función y la variable que se van a usar para obtener dicha columna.

Por ejemplo, podríamos obtener la esperanza de vida media, máxima y media de todo el dataset. Esto podemos obtenerlo de la siguiente manera:

gapminder %>%
  summarise(
    lifeExp_min = min(lifeExp),
    lifeExp_max = max(lifeExp),
    lifeExp_mean = mean(lifeExp)
  )

En este caso, como los datos no están agrupados, los cálculos se realizan sobre todo el dataset. Sin embargo, como comentaba previamente, lo más habitual es usar la función summarisejunto con la función group_by, de tal forma que los cálculos se realicen para cada uno de los grupos-

Por ejemplo, digamos que queremos obtener el valor medio, mínimo y máximo de esperanza de vida, pero en vez de para todo el dataset, lo queremos ver para cada año. Esto se podría obtener de la siguiente manera:

gapminder %>%
  group_by(year) %>%
  summarise(
    lifeExp_min = min(lifeExp),
    lifeExp_max = max(lifeExp),
    lifeExp_mean = mean(lifeExp)
  )
Ejercicio función summarise

¡Pongamos la función summarize en práctica! Usando esta función y alguna otra función que hemos visto hasta ahora, calcula el máximo, mínimo y media de la población y la esperanza de vida para cada continente y año. Las variables deberás llamarlas lifeExp_min, lifeExp_max, lifeExp_mean y pop_min, pop_max y pop_mean, en ese orden.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicykiLCJzYW1wbGUiOiIjIGRwbHlyIGFuZCBnYXBtaW5kZXIgYXJlIGxvYWRlZCBhbHJlYWR5XG4jIGRwbHlyIHkgZ2FwbWluZGVyIHlhIGVzdFx1MDBlMW4gY2FyZ2Fkb3MiLCJzb2x1dGlvbiI6InJlc3VsdCA9IGdhcG1pbmRlciAlPiUgXG4gIGdyb3VwX2J5KHllYXIsIGNvbnRpbmVudCkgJT4lXG4gIHN1bW1hcmlzZShcbiAgICAgbGlmZUV4cF9taW4gPSBtaW4obGlmZUV4cCksIFxuICAgICBsaWZlRXhwX21heCA9IG1heChsaWZlRXhwKSwgXG4gICAgIGxpZmVFeHBfbWVhbiA9IG1lYW4obGlmZUV4cCksXG4gICAgIHBvcF9taW4gPSBtaW4ocG9wKSwgXG4gICAgIHBvcF9tYXggPSBtYXgocG9wKSxcbiAgICAgcG9wX21lYW4gPSBtZWFuKHBvcClcbiAgKVxuXG5wcmludChyZXN1bHQpIiwic2N0IjoidGVzdF9mdW5jdGlvbihcInN1bW1hcmlzZVwiLCBcbiAgICAgICAgICAgICAgaW5jb3JyZWN0X21zZyA9IFwiWW91IHNob3VsZCB1c2Ugc3VtbWFyaXNlIGZ1bmN0aW9uLiAtIERlYmVyXHUwMGVkYXMgdXNhciBsYSBmdW5jaVx1MDBmM24gc3VtbWFyaXNlXCIpXG5cbnRlc3RfZnVuY3Rpb24oXCJncm91cF9ieVwiLCBcbiAgICAgICAgICAgICAgaW5jb3JyZWN0X21zZyA9IFwiWW91IHNob3VsZCB1c2UgZ3JvdXBfYnkgZnVuY3Rpb24uIC0gRGViZXJcdTAwZWRhcyB1c2FyIGxhIGZ1bmNpXHUwMGYzbiBncm91cF9ieVwiKVxuXG5cbnRlc3Rfb2JqZWN0KFwicmVzdWx0XCIsXG4gICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJJbmNvcnJlY3QuIC0gSW5jb3JyZWN0by5cIlxuICAgICAgICAgICAgKVxuXG50ZXN0X2Vycm9yKClcbnN1Y2Nlc3NfbXNnKFwiV2VsbCBkb25lISAtIEJpZW4gaGVjaG8hXCIpIn0=

Como ves, la función summarise es muy muy potente. Sobre todo, si se combina con la siguiente función que vamos a ver, ¡sigamos con nuestro tutorial de dplyr completo!

Función Across

Podría darse el caso de que quisiéramos calcular una serie de funciones (mínimo, máximo, media y número de casos, por ejemplo) a varias columnas. Con lo que hemos visto hasta ahora, una opción sería indicar uno a uno cada una de las operaciones (máximo, mínimo, etc.) para cada columna. Sin embargo esto no parece muy eficiente…

En esos casos, una mejor opción es usar la función across. La función across permite aplicar las funciones que indiquemos a las columnas que indiquemos. Aunque el resultado final es el mismo, usar la función across es mucho más óptimo, ya que (1) tardarás menos, (2) es menos probable que cometas fallos y además (3) el código quedará mucho más limpio.

Imaginemos que queremos calcular el máximo, mínimo y media para la esperanza de vida y la población. Sin usar la función across lo haríamos de la siguiente manera:

gapminder %>%
  group_by(year) %>%
  summarise(

    # Esperanza de vida
    lifeExp_min = min(lifeExp),
    lifeExp_max = max(lifeExp),
    lifeExp_mean = mean(lifeExp),

    # Poblacion
    pop_min = min(pop),
    pop_max = max(pop),
    pop_mean = mean(pop),

    # GDP
    gpd_min = min(gdpPercap),
    gpd_max = max(gdpPercap),
    gpd_mean = mean(gdpPercap)

  )

Como ves, el código anterior funciona, pero es bastante engorroso. Ahora, probemos a hacerlo con la función across:

gapminder %>%
  group_by(year) %>%
  summarise(
    across(
      c(lifeExp,pop, gdpPercap), 
      list(min, max, mean)
         )
  )

Como vemos, obtenemos el mismo resultado en muchas menos líneas de código, es mucho más fácil de leer y de modificar.

De hecho, con la función across podemos pasar condicionales, como por ejemplo, que la columna sea numérica, o factor. Veamos un ejemplo, obteniendo la moda de las variables categóricas y la meda de las variables numéricas:

Nota: para usar la moda, he utilizado la función indicada en este post.

Mode <- function(x) {
  ux <- unique(x)
  ux[which.max(tabulate(match(x, ux)))]
}

gapminder %>%
  group_by(year) %>%
  summarise(
    across(is.numeric, mean),
    across(is.factor, Mode)
  )
Ejercicio función across

Como ves, la función across es muy útil, y nos habría ahorrado mucho tiempo en el ejercicio anterior. Es por eso, que te vuelvo a pedir que calcules el mínimo, máximo y promedio de la esperanza de vida y la población de cada continente en cada año, pero esta vez usando la función across.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicykiLCJzYW1wbGUiOiIjIGRwbHlyIGFuZCBnYXBtaW5kZXIgYXJlIGxvYWRlZCBhbHJlYWR5XG4jIGRwbHlyIHkgZ2FwbWluZGVyIHlhIGVzdFx1MDBlMW4gY2FyZ2Fkb3NcbnJlc3VsdCAgXG5cbiMgUHJpbnQgcmVzdWx0XG5wcmludChyZXN1bHQpIiwic29sdXRpb24iOiJyZXN1bHQgPSBnYXBtaW5kZXIgJT4lIFxuICBncm91cF9ieSh5ZWFyLCBjb250aW5lbnQpICU+JVxuICBzdW1tYXJpc2UoXG4gICAgYWNyb3NzKGMobGlmZUV4cCxwb3ApLCBsaXN0KG1pbiwgbWF4LCBtZWFuKSlcbiAgKVxuXG5wcmludChyZXN1bHQpIiwic2N0IjoidGVzdF9mdW5jdGlvbihcInN1bW1hcmlzZVwiLCBcbiAgICAgICAgICAgICAgaW5jb3JyZWN0X21zZyA9IFwiWW91IHNob3VsZCB1c2Ugc3VtbWFyaXNlIGZ1bmN0aW9uLiAtIERlYmVyXHUwMGVkYXMgdXNhciBsYSBmdW5jaVx1MDBmM24gc3VtbWFyaXNlXCIpXG5cbnRlc3RfZnVuY3Rpb24oXCJncm91cF9ieVwiLCBcbiAgICAgICAgICAgICAgaW5jb3JyZWN0X21zZyA9IFwiWW91IHNob3VsZCB1c2UgZ3JvdXBfYnkgZnVuY3Rpb24uIC0gRGViZXJcdTAwZWRhcyB1c2FyIGxhIGZ1bmNpXHUwMGYzbiBncm91cF9ieVwiKVxuXG5cbnRlc3Rfb2JqZWN0KFwicmVzdWx0XCIsXG4gICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJJbmNvcnJlY3QuIC0gSW5jb3JyZWN0by5cIlxuICAgICAgICAgICAgKVxuXG50ZXN0X2Vycm9yKClcbnN1Y2Nlc3NfbXNnKFwiV2VsbCBkb25lISAtIEJpZW4gaGVjaG8hXCIpIn0=

Función count

Por último, otra cuestión muy recurrente es obtener el número de casos/observaciones que hay en cada grupo. Aunque esto se podría obtener con la función summarise, otra forma más rápida de obtenerlo es con la función count.

Veamos un ejemplo suponiendo que queremos obtener el número de países para los que tenemos datos para cada uno de los años. Usando la función summarise, se haría de la siguiente manera:

gapminder %>%
  group_by(year) %>%
  summarise(cases = n())

Sin embargo, otra opción más rápida es usar directamente la función count:

gapminder %>%
  group_by(year) %>%
  count()

Como vemos, nos ahorramos unas pocas líneas, de nuevo, hacen que nuestro código sea más fácilmente entendible.

¡Vistas las funciones de resumen! Sigamos con este tutorial de dplyr viendo las funciones de manipulación.

Funciones de manipulación de datos de dplyr

Las funciones de manipulación permiten modificar las columnas actuales, ya sea creando nuevas o cambiando el nombre de las columnas actuales.

Dentro de las funciones de manipulación de datos encontramos las funciones: mutate, transmute, add_column, rename. Además, aunque no sean del paquete dplyr, sino del paquete tidyr, comentaré las funciones pivot_wider y pivot_longer, puesto que también son muy frecuentes en pipes de dplyr.

Función mutate

La función mutate permite ir creando nuevas columnas, que serán resultado de aplicar una función. Por tanto, dentro de la función de mutate, siempre deberemos tener dos elementos: el nombre de la nueva columna y la función que tendrá como resultado los valores de la columna.

Por ejemplo, podemos usar la función mutate para comprobar si el nombre del pasajero del Titanic incluia Mr.:

titanic = read.csv("https://gist.githubusercontent.com/michhar/2dfd2de0d4f8727f873422c5d959fff5/raw/fa71405126017e6a37bea592440b4bee94bf7b9e/titanic.csv")

titanic %>%
  mutate(
    contiene_mr = grepl("Mr.", Name, fixed = T)
  ) %>%
  select(Name, contiene_mr)

Como vemos, la función mutate es muy simple, pero a la vez muy útil, ya que evita tener que realizar muchas asignaciones y/o correcciones de las que después es saber si se han realizado o no.

En este sentido, una función muy utilizada junto con mutate es la función case_when, la cual permite crear condicionales de una forma muy sencilla.

Por ejemplo, podríamos crear una nueva variable llamada Gender que devuelva Man si el nombre incluye Mr. o Master, Woman si incluye Ms., Mrs. o Miss, por ejemplo.

Además, puede darse el caso que un nombre no caiga en ninguna de las anterior categorías. En este caso podríamos indicar que se trata de un NA, por ejemplo.

Hacer esto con la función case_when es muy sencillo:

titanic %>%
  mutate(
    gender = case_when(
      grepl("Mr.|Master", Name) ~ 'Man',
      grepl("Ms.|Miss.|Mrs.", Name) ~ 'Woman',
      TRUE ~ NA_character_ # this indicates missclasified ones
    )
  ) %>%
  select(Name, gender)
Ejercicios mutate

Usando el dataset titanic, crea una nueva columna booleana, llamada adult, que devuelve TRUE si la persona es mayor de edad o FALSE si no lo es.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicylcblxudGl0YW5pYyA9IHJlYWQuY3N2KFwiaHR0cHM6Ly9naXN0LmdpdGh1YnVzZXJjb250ZW50LmNvbS9taWNoaGFyLzJkZmQyZGUwZDRmODcyN2Y4NzM0MjJjNWQ5NTlmZmY1L3Jhdy9mYTcxNDA1MTI2MDE3ZTZhMzdiZWE1OTI0NDBiNGJlZTk0YmY3YjllL3RpdGFuaWMuY3N2XCIpIiwic2FtcGxlIjoiIyBkcGx5ciBhbmQgZ2FwbWluZGVyIGFyZSBsb2FkZWQgYWxyZWFkeVxuIyBkcGx5ciB5IGdhcG1pbmRlciB5YSBlc3RcdTAwZTFuIGNhcmdhZG9zXG4jIHRpdGFuaWMgaXMgYWxyZWFkeSBsb2FkZWQgLSB0aXRhbmljIHlhIGVzdFx1MDBlMSBjYXJnYWRvXG5cbnJlc3VsdCAgXG5cbiMgUHJpbnQgcmVzdWx0XG5wcmludChyZXN1bHQpIiwic29sdXRpb24iOiJyZXN1bHQgPSB0aXRhbmljICU+JVxuICBtdXRhdGUoXG4gICAgYWR1bHQgPSBpZmVsc2UoQWdlPj0xOCxULEYpXG4gIClcblxucHJpbnQocmVzdWx0KSIsInNjdCI6InRlc3RfZnVuY3Rpb24oXCJtdXRhdGVcIiwgXG4gICAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIllvdSBzaG91bGQgdXNlIG11dGF0ZSBmdW5jdGlvbi4gLSBEZWJlclx1MDBlZGFzIHVzYXIgbGEgZnVuY2lcdTAwZjNuIG11dGF0ZVwiKVxuXG5cbnRlc3Rfb2JqZWN0KFwicmVzdWx0XCIsXG4gICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJJbmNvcnJlY3QuIC0gSW5jb3JyZWN0by5cIlxuICAgICAgICAgICAgKVxuXG50ZXN0X2Vycm9yKClcbnN1Y2Nlc3NfbXNnKFwiV2VsbCBkb25lISAtIEJpZW4gaGVjaG8hXCIpIn0=

Como vemos, realizar modificaciones con mutate es muy sencillo, es más rápido que hacer una asignación normal y hace el código más entendible.

Sin embargo, habrá ocasiones en las que simplemente queramos crear un par de nuevas variables y olvidarnos del resto. Aunque eso lo podríamos hacer con una función mutate seguida de un select, una forma más fácil es usar la función

Función transmute

La función transmute permite crear nuevas variables y deshacernos del resto de variables. Por ejemplo, imagínate que queremos analizar la relación entre la variable sexo y el nombre de la persona, para comprobar cuántas veces la variable por defecto Gender es correcta (es decir, queremos analizar la coherencia interna de los datos).

Para ello, podríamos usar, por ejemplo, una función mutate seguido de un filter:

titanic %>%
  mutate(
        gender_name = case_when(
      grepl("Mr.|Master", Name) ~ 'Man',
      grepl("Ms.|Miss.|Mrs.", Name) ~ 'Woman',
      TRUE ~ NA_character_ # this indicates missclasified ones
    )
  ) %>%
  select(gender_name, Sex)

Pero, otra opción más sencilla y hacerlo todo dentro de la misma función, usando transmute.

titanic %>%
  transmute(
        gender_name = case_when(
      grepl("Mr.|Master", Name) ~ 'Man',
      grepl("Ms.|Miss.|Mrs.", Name) ~ 'Woman',
      TRUE ~ NA_character_ # this indicates missclasified ones
    ),
    Sex
  )

Función rename

Otro caso muy habitual es cuando queremos cambiar el nombre de una columna. Con lo que hemos visto hasta ahora, esto podría hacerse con la función mutate. Sin embargo, una forma más sencilla y clara es hacerlo con la función rename .

La modificación del nombre con mutate se haría así:

gapminder %>%
  mutate(life_expectancy = lifeExp, lifeExp = NULL )

Mientras que con la función rename, el proceso es algo más simple:

gapminder %>%
  rename(life_expectancy = lifeExp)

Funciones pivot_longer y pivot_wider

Aunque estas dos funciones no sean de la librería dplyr, sino de la librería tidyr, son muy útiles y, generalmente, muy utilizadas en los pipes de dplyr.

La idea de estas funciones es sencilla: pasar de tener varias variables en una columna a tener una variable por columna o, al revés, pasar de una variable por columna a una columna con varias variables.

En el primer caso (de varias variables en una columna a una columna por variable), usaremos la función pivot_wider.

En esta función simplemente se debe indicar:

  1. Las columnas que no deben modificarse.
  2. Qué columna pasará a crear las columnas (en nuestro caso, el año).
  3. Qué variable se usará para llenar las nuevas columnas (en nuestro caso, lifeExp).
library(tidyr)

gapminder %>%
  select(-pop, gdpPercap) %>%
  pivot_wider(
    id_cols = c("continent", "country"),
    names_from = "year",
    values_from = "lifeExp"
    )

Esta función es útil, sobre todo, cuando hay redundancia en los datos, como en este caso ocurre con la columna año.

Por el contrario, podemos pasar de unos datos anchos a unos más estrechos, haciendo que varias variables se incluyan dentro de una misma columna.

Esto lo podemos lograr con la función pivot_longer, en la que únicamente debemos elegir las columnas a pivotar, el nombre de la columna donde irán los nombres de las variables y el nombre de la columna donde irá el valor:

gapminder %>%
  pivot_longer(
    cols = c("pop","gdpPercap", "lifeExp"),
    names_to = "variable",
    values_to = "value"
    )

Vistas las funciones de manipulación de datos, solo nos quedarían una familia de funciones: las funciones de combinación de datos.

Funciones de combinación de datos

Las funciones de combinación de datos sirve, precisamente, para eso: para combinar información de dos dataframes o tibbles. Estas combinaciones pueden ser de dos tipos:

  • Combinación horizontal: cuando un dataframe añade más columnas de las ya existen. Dentro de estas funciones encontraríamos la función bind_cols, los joins: left_join, right_join, inner_join y full_join, así como las funciones de filtro en función de join, como son semi_joiny anti_join.
  • Combinación vertical: cuando un dataframe incluye más filas. Dentro de este tipo de funciones encontraríamos: bind_rows, union, intersect, setdiff y setequal.

Así pues, ¡veámos cómo funcionan estas funciones!

Función bind_rows y bind_cols

Las funciones bind_rows y bind_cols son similares a las funciones rbind y cbind, aunque tienen algunas de las principales diferencias son:

  • bind_rows gestiona directamente las listas, mientras que rbind debes usar la función do.call.
  • bind_rows es mucho más eficiente que rbind, aunque bind_cols no es más eficiente que cbind.
  • bind_rows gestiona las columnas que no coinciden, incluyendo NAs, mientras que rbind dará error.

A modo de ejemplo, podemos comparar la eficiencia del cbind con bind_cols:

microbenchmark(
  cbind = cbind(mtcars, mtcars),
  bind_cols = bind_cols(mtcars, mtcars)
)

Y lo mismo podemos hacer con las funciones rbind y bind_rows:

microbenchmark(
  rbind = rbind(mtcars, mtcars),
  bind_rows = bind_rows(mtcars, mtcars)
)

Como vemos, en ambos casos las funciones del paquete dplyr son más eficientes. Si bien esto con pocos datos no se nota, es algo que cobra importancia cuando empezamos a trabajar con datasets más grandes.

Ahora, veámos cómo funcionan las funciones join.

Funciones join: left_join, right_join, inner_join, full_join, semi_join y anti_join

Las funciones join permiten juntar los datos de forma horizontal. Cada una de las funciones, devolverá un resultado diferente. Por ejemplo:

  • left_join: devuelve todas la celdas de la tabla de la izquierda, aunque no hagan match con la tabla de la derecha.
  • right_join: devuelve los datos de la tabla de la derecha, aunque no haga match con los datos de la tabla de la izquierda.
  • inner_join: solo devuelve los datos que hacen match en ambas tablas.
  • semi_join: devuelve los datos de X (solo X) que tienen coinciden con la clave de Y.
  • anti_join: muestra los datos de X (solo X) que no tienen ninguna coincidencia con la clave de Y.
  • full_join: devuelve los datos de X e Y, aunque no coincidan.

Una buena forma de verlo es mediante ejemplos. Para ello, vamos a ver un caso típico de análisis de compras, ya que la información suele almacenarse cada compra en una tabla. Para ello, voy a usar la librería fakir.

clients = read.csv("https://github.com/anderfernandez/datasets/blob/main/telcom-support/clients.csv")
clients
orders = read.csv("https://github.com/anderfernandez/datasets/blob/main/telcom-support/orders.csv")
orders

Si quisiéramos obtener los clientes y sus datos de pedidos, simplemente deberíamos hacer una left_join:

clients %>%
  left_join(orders, by = c("num_client" = "num_client")) %>%
  dim()
[1] 21 38

En este caso, tendremos 38 columnas, (14 de clients, 25 de orders – la columna de unión). Además, a pesar de que solo haya 21 clientes, contamos con 22 filas, ya que un mismo cliente puede haber hecho más de una compra.

Asimismo, podríamos obtener los datos de pedidos solo de de aquellos clientes que han hecho compras, para lo cual usaremos el inner_join:

clients %>%
  inner_join(orders, by = c("num_client" = "num_client")) %>%
  dim()
[1] 10 38

También podría darse el caso de que queramos los datos de los clientes que han hecho pedidos, pero que no nos interese que se incluyan los datos de los pedidos, sino que simplemente queremos saber qué clientes son. Para ello, podemos usar la función semi_join.

clients %>%
  semi_join(orders, by = c("num_client" = "num_client")) %>%
  dim()
[1]  9 14

Como vemos, solo hay 8 clientes de nuestra tabla de clientes que hayan hecho pedidos. O lo que es lo mismo, hay 9 personas de la tabla de clientes que no han hecho pedidos. Así pues, si quisiéramos identificar a estos usuarios, podríamos usar la función anti_join:

clients %>%
  anti_join(orders, by = c("num_client" = "num_client")) %>%
  dim()
[1] 11 14

Efectivamente, la función devuelve esos 11 clientes de la tabla de clientes que no han hecho pedidos.

Quizás puede que no veas grandes diferencias entre usar semi_join y/o anti_joinrespecto a la función filter. Si bien es cierto que para estos casos podríamos obtener el mismo resultado con la función filter , la función filter solo sirve para aquellos casos en los que hay una única clave, mientras que las funciones semi_join y anti_join sirven independientemente del número de claves que tenga la tabla.

Ejercicios Join

Enriquece las llamadas que tienen cliente, usando la función inner_join.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicylcblxuY2xpZW50cyA9IHJlYWQuY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYW5kZXJmZXJuYW5kZXovZGF0YXNldHMvbWFpbi90ZWxjb20tc3VwcG9ydC9jbGllbnRzLmNzdicpXG5cbm9yZGVycyA9IHJlYWQuY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYW5kZXJmZXJuYW5kZXovZGF0YXNldHMvbWFpbi90ZWxjb20tc3VwcG9ydC9vcmRlcnMuY3N2JykiLCJzYW1wbGUiOiIjIGNsaWVudHMgYW5kIG9yZGVycyBhcmUgbG9hZGVkIGFscmVhZHlcbiMgY2xpZW50cyB5IG9yZGVycyB5YSBlc3RcdTAwZTFuIGNhcmdhZG9zXG5cblxucmVzdWx0XG5cbiMgUHJpbnQgcmVzdWx0XG5wcmludChyZXN1bHQpIiwic29sdXRpb24iOiJyZXN1bHQgPSBjbGllbnRzICU+JVxuICBpbm5lcl9qb2luKG9yZGVycywgIGJ5ID0gYyhcIm51bV9jbGllbnRcIiA9IFwibnVtX2NsaWVudFwiKSkgXG4gIFxuXG5wcmludChyZXN1bHQpIiwic2N0IjoidGVzdF9mdW5jdGlvbihcImlubmVyX2pvaW5cIiwgXG4gICAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIllvdSBzaG91bGQgdXNlIGlubmVyX2pvaW4gZnVuY3Rpb24uIC0gRGViZXJcdTAwZWRhcyB1c2FyIGxhIGZ1bmNpXHUwMGYzbiBpbm5lcl9qb2luXCIpXG5cblxudGVzdF9vYmplY3QoXCJyZXN1bHRcIixcbiAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIkluY29ycmVjdC4gLSBJbmNvcnJlY3RvLlwiXG4gICAgICAgICAgICApXG5cbnRlc3RfZXJyb3IoKVxuc3VjY2Vzc19tc2coXCJXZWxsIGRvbmUhIC0gQmllbiBoZWNobyFcIikifQ==

Ahora, realiza el mismo proceso, pero usando la función left_join. Importante: el left_join quizás devuelva datos que no te interesan, por lo que deberás usar otra función que hemos aprendido para resolverlo.

eyJsYW5ndWFnZSI6InIiLCJwcmVfZXhlcmNpc2VfY29kZSI6ImxpYnMgPSBjKFwiZHBseXJcIixcImdhcG1pbmRlclwiKVxuc2FwcGx5KGxpYnNbIWxpYnMgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKV0sIGluc3RhbGwucGFja2FnZXMpXG5zYXBwbHkobGlicywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUKVxucm0obGlicylcblxuY2xpZW50cyA9IHJlYWQuY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYW5kZXJmZXJuYW5kZXovZGF0YXNldHMvbWFpbi90ZWxjb20tc3VwcG9ydC9jbGllbnRzLmNzdicpXG5cbm9yZGVycyA9IHJlYWQuY3N2KCdodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYW5kZXJmZXJuYW5kZXovZGF0YXNldHMvbWFpbi90ZWxjb20tc3VwcG9ydC9vcmRlcnMuY3N2JykiLCJzYW1wbGUiOiIjIGNsaWVudHMgYW5kIG9yZGVycyBhcmUgbG9hZGVkIGFscmVhZHlcbiMgY2xpZW50cyB5IG9yZGVycyB5YSBlc3RcdTAwZTFuIGNhcmdhZG9zXG5cblxucmVzdWx0XG5cbiMgUHJpbnQgcmVzdWx0XG5wcmludChyZXN1bHQpIiwic29sdXRpb24iOiJjbGllbnRzICU+JVxuICBsZWZ0X2pvaW4ob3JkZXJzLCAgYnkgPSBjKFwibnVtX2NsaWVudFwiID0gXCJudW1fY2xpZW50XCIpKSAlPiVcbiAgZmlsdGVyKCFpcy5uYShlbnRyeV9kYXRlLnkpKVxuICBcblxucHJpbnQocmVzdWx0KSIsInNjdCI6InRlc3RfZnVuY3Rpb24oXCJsZWZ0X2pvaW5cIiwgXG4gICAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIllvdSBzaG91bGQgdXNlIGxlZnRfam9pbiBmdW5jdGlvbi4gLSBEZWJlclx1MDBlZGFzIHVzYXIgbGEgZnVuY2lcdTAwZjNuIGxlZnRfam9pblwiKVxuXG50ZXN0X2Z1bmN0aW9uKFwiZmlsdGVyXCIsIFxuICAgICAgICAgICAgICBpbmNvcnJlY3RfbXNnID0gXCJZb3Ugc2hvdWxkIHVzZSBmaWx0ZXIgZnVuY3Rpb24uIC0gRGViZXJcdTAwZWRhcyB1c2FyIGxhIGZ1bmNpXHUwMGYzbiBmaWx0ZXJcIilcblxudGVzdF9vYmplY3QoXCJyZXN1bHRcIixcbiAgICAgICAgICAgIGluY29ycmVjdF9tc2cgPSBcIkluY29ycmVjdC4gLSBJbmNvcnJlY3RvLlwiXG4gICAgICAgICAgICApXG5cbnRlc3RfZXJyb3IoKVxuc3VjY2Vzc19tc2coXCJXZWxsIGRvbmUhIC0gQmllbiBoZWNobyFcIikifQ==

Con esto ya hemos en nuestra guía completa de dplyr ya hemos visto todas las familias de funciones que tiene el paquete. Ahora veamos unos casos prácticos en las que pueden ser interesantes.

Ejemplos prácticos de la librería dplyr

Visualización de datos de gapminder con dplyr y ggplot

Un caso de uso clásico de dplyr es para, a partir de un dataframe, crear una visualización chula con ggplot. Y es que, las funciones de dplyr que hemos aprendido en el tutorial junto con la funciones pivot_wider y pivot_longerofrecen prácticamente todo lo que necesitas para poder hacer todo tipo de visualizaciones.

Por ejemplo, en este caso, partiendo del dataset original de gapminder, crearemos un pipe de dplyr que nos permita visualizar la evolución media, máxima y mínima para cada uno de los continentes y cada una de las variables que tenemos.

Para ello, utilizaremos las funciones group_by, summarise y across para calcular el máximo, mínimo y media para cada uno de los continentes en cada año.

Una vez hecho esto, transformaremos los datos en formato largo con pivot_longer. Así, podremos usar la función mutate para tener por separado la función que se ha utilizado y el valor de la función.

Una vez tengamos esto, volveremos a pivotar los datos, esta vez en formato ancho usando pivot_wider, de tal forma que tengamos los datos listos para pintarlos con ggplot2.

library(ggplot2)

gapminder %>%
  group_by(year, continent) %>%
  summarise(across(is.numeric, .fns = list(mean = mean, max = max,min =  min))) %>%
  pivot_longer(
    cols = -c("year", "continent"), 
    names_to = "variable", 
    values_to = "valor" 
    ) %>%
  mutate(
    funcion = gsub(".*_","", variable),
    variable = gsub("_.*", "", variable)
  ) %>%
  pivot_wider(names_from = funcion, values_from = valor) %>%
  ggplot(aes(x= year)) + 
  geom_line(aes(y = mean), group = 1) +
  geom_ribbon(aes(ymax = max, ymin = min), alpha = 0.6, fill = "skyblue") +
  facet_grid(variable~continent, scales = "free_y") +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)
    )

Como resultado, vemos un gráfico bastante interesante y útil que nos permite ver cosas curiosas, como el crecimiento de la población en en Asía en los últimos años, la convergencia de la esperanza de vida y el incremento de las de las diferencias en términos de PIB per Cápita en Europa,el incremento generalizado de la esperanza d vida en todos los continentes.

Y todo esto se ha obtenido utilizando únicamente 4 funciones dplyr, 2 funciones de tidyr y la librería ggplot2, todo ello en un código bastante sencillo y amigable.

Manipulación de datos de flujo con dplyr

Un caso típico de los pipes de dplyr es cuando la observaciones se refieren a distintas acciones dentro de un flujo, como por ejemplo, las posiciones de un taxi o las páginas visitadas por usuarios en la web.

Aunque no haya datasets típicos de este tipo de datos, usaremos el dataset gapminder para mostrar cómo se usaría en este tipo de casos. Para ello, haremos una visualización Sankey que muestre la evolución de los países con mayor PIB per Capita para diferentes años, siendo el dato a mostrar la diferencia respecto al siguiente país.

Para ello, de nuevo utilizaremos los paquetes, dplyr, ggplot2 y alluvial, el cual permite realizar gráficos sankey en ggplot2. Vamos a crear un diagrama Sankey que nos permita ver cómo ha evolucionado la desviación respecto a la media en términos de PIB per Cápita de los diez países con más PIB per Cápita de cada año.

Para ello, simplemente vamos a agrupar los datos por año y ordenarlos por PIB per Cápita de forma descendente. De esta forma, con mutate podremos: (1) obtener la posición de cada país para ese año, (2), para cada país obtener la desviación respecto al PIB per Cápita medio de ese año.

library(ggalluvial)

gapminder %>%
  select(-pop, -lifeExp) %>%
  group_by(year) %>%
  arrange(desc(gdpPercap)) %>%
  mutate(
    mean_gdp = mean(gdpPercap),
    index = row_number(),
    percent_better = (gdpPercap/mean_gdp) -1
  ) %>%
  slice_min(index, n = 10) %>%
  select(index, country, year, percent_better) %>%
  ggplot(aes(as.factor(year), 
             stratum = reorder(country, -index), 
             y = percent_better,
             label = country, 
             alluvium = index, 
             fill = country
             )) +
  geom_flow(stat = "alluvium", lode.guidance = "frontback",
            color = "darkgray") +
  geom_stratum(alpha = .5) +
  #geom_text(stat = "stratum", size = 2.5) +
  theme_minimal() +
  theme(
    legend.position = "bottom",
    legend.key.size = unit(0.5, "cm"),
    legend.key.width = unit(0.5,"cm") 
    ) +
  labs(
    x = "", 
    y = "% better than mean gdpPerCap",
    fill = "") +
  scale_y_reverse()

Como vemos, con unas pocas funciones de dplyr hemos podido transformar los datos para tenerlos de tal forma que fácilmente he podido crear el gráfico Sankey.

Conclusiones del tutorial de dplyr

Si dplyr es uno de los paquetes más utilizados de R, es porque se trata de una librería muy sencilla de entender y con la que se consigue un código muy entendible y muy eficiente.

Además, al ser parte de uno de los paquetes del mundo tidyverse, su integración con otros paquetes del “universo”, como ggplot2 o tidyr hace que sea muy práctico, pudiendo crear visualizaciones complejas y visualmente impactantes sin la necesidad de guardar ningún objeto intermedio.

Así pues, espero que este tutorial completo sobre dplyr te haya servido para conocer mejor el paquete. Además, no olvides echar un ojo al post sobre cómo crear animaciones en R con gganimate, el cual se complementa muy bien con este post.

Si quieres estar al tanto de los posts que publico, te recomiendo que te suscribas a mi blog. Y, si te ha gusto y puedes permitirlo, te agradecería si pudieras contribuir con una donación para permitirme que siga creando más contenido como este. En cualquier caso, ¡nos vemos en el siguiente post!