Manipulación de dataframes con dplyr

Resumen

Enseñando: 30 min
Ejercicios: 10 min
Preguntas
  • ¿Cómo puedo manipular los dataframe sin repetirme?

Objectivos
  • Ser capaz de utilizar los 6 principales ‘verbos’ para la manipulación de dataframes con pipes en dplyr.

  • Entender cómo group_by() y summarize() pueden combinarse para resumir datasets.

  • Ser capaz de analizar un subconjunto de datos usando filtros lógicos.

La manipulación de dataframes significa muchas cosas para los investigadores, solemos seleccionar varias observaciones (filas) o variables (columnas), nosotros siempre agrupamos los datos por ciertas variable(s), o si incluso calculamos estadísticas de resumen. Podemos hacer esas operaciones usando operaciones de R base normal.

mean(gapminder[gapminder$continent == "Africa", "gdpPercap"])
[1] 2193.755
mean(gapminder[gapminder$continent == "Americas", "gdpPercap"])
[1] 7136.11
mean(gapminder[gapminder$continent == "Asia", "gdpPercap"])
[1] 7902.15

Pero esto no es muy eficiente, y puede volverse tedioso rapidamente porque hay bastante repetición. Repetirte a ti misma te va a costar tiempo, ahora y luego, y potencialmente puede introducir algunos errores.

El paquete dplyr

Afortunadamente, el paquete dplyr provee un número muy útil de funciones para manipular dataframes en una forma que reduce la repetición antes mencionada, asi como la probabilidad de cometer errores y posiblemente también te ahorre cierto tipeo. Como bonus, vas a encontrar la gramática de dplyr incluso más fácil de leer.

Vamos a cubrir 6 de las funciones más comunes usando pipes (%>%) para combinarlas.

  1. select()
  2. filter()
  3. group_by()
  4. summarize()
  5. mutate()

Si aún no tienes instalado este paquete, por favor hazlo ahora:

install.packages('dplyr')

Ahora vamos a cargar el paquete:

library("dplyr")

Usando select()

Si, por ejemplo, queremos seguir solo con unas pocas de las variables en nuestro dataframe, podemos usar la función select(). Esto conservara solo las variables que seleccionas.

year_country_gdp <- select(gapminder, year, country, gdpPercap)

Si nosotros abrimos year_country_gdp vamos a ver que solo contiene año, país y gdpPercap. Hasta ahora hemos usado una gramática normal, pero las fortaleza de dplyr recae en combinar varias funciones usando pipes. Como la gramática de las pipes es diferente a todo lo que hemos visto en R hasta ahora, vamos a repertir que hemos hecho arriba usando pipes.

year_country_gdp <- gapminder %>% select(year,country,gdpPercap)

Para ayudarte a entender por qué lo escribimos en esa forma, vamos a recorrerlo paso a paso. Primero convocamos a la base de datos gapminder y le pasamos, usando el símbolo pipe %>%, al siguiente paso, que es la función select (). En este caso no le especificamos que objeto de datos usamos en la función select () ya que la obtiene del pipe anterior. Dato curioso: Puedes haberte encontrado pipes antes en la terminal. En R, el símbolo de pipe es %>% mientras que en la terminal es I pero el concepto es el mismo!

Usando filter()

Si ahora queremos avanzar con lo anterior, pero solo los países Europeos, podemos combinar select y filter

year_country_gdp_euro <- gapminder %>%
  filter(continent == "Europe") %>%
  select(year, country, gdpPercap)

Desafío 1

Escribe un solo comando (que puede abarcar varias líneas e incluir pipes) que producirá un marco de datos que tenga los valores africanos para lifeExp, country y year, pero no para los otros continentes. ¿Cuántas filas tiene tu marco de datos y por qué?

Solución al desafío 1

year_country_lifeExp_Africa <- gapminder %>%
                           filter(continent=="Africa") %>%
                           select(year,country,lifeExp)

Como la vez pasada, primero pasamos el marco de datos gapminder a la función filter(), luego pasamos la versión filtrada del marco de datos gapminder a la función select(). Nota: El order en las operaciones es muy importante en este caso. Si utilizamos ‘select’ primero, filter no podría encontrar la variable continent ya que la habríamos removido en el paso anterior.

Usando group_by() y summarize()

Ahora, se suponía que debíamos reducir la repetición propensa a errores de lo que se puede hacer con base R, pero hasta ahora no lo hemos hecho, ya que tendríamos que repetir lo anterior para cada continente. En lugar de filter(), que sólo pasará observaciones que cumplan con nuestros criterios (en lo anterior: continent=="Europe"), podemos usar group_by(), que esencialmente usará todos los criterios únicos que podrías haber usado en filter.

str(gapminder)
'data.frame':\t1704 obs. of  6 variables:
 $ country  : chr  "Afghanistan" "Afghanistan" "Afghanistan" "Afghanistan" ...
 $ year     : int  1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
 $ pop      : num  8425333 9240934 10267083 11537966 13079460 ...
 $ continent: chr  "Asia" "Asia" "Asia" "Asia" ...
 $ lifeExp  : num  28.8 30.3 32 34 36.1 ...
 $ gdpPercap: num  779 821 853 836 740 ...
gapminder %>% group_by(continent) %>% str()
tibble [1,704 × 6] (S3: grouped_df/tbl_df/tbl/data.frame)
 $ country  : chr [1:1704] "Afghanistan" "Afghanistan" "Afghanistan" "Afghanistan" ...
 $ year     : int [1:1704] 1952 1957 1962 1967 1972 1977 1982 1987 1992 1997 ...
 $ pop      : num [1:1704] 8425333 9240934 10267083 11537966 13079460 ...
 $ continent: chr [1:1704] "Asia" "Asia" "Asia" "Asia" ...
 $ lifeExp  : num [1:1704] 28.8 30.3 32 34 36.1 ...
 $ gdpPercap: num [1:1704] 779 821 853 836 740 ...
 - attr(*, "groups")= tibble [5 × 2] (S3: tbl_df/tbl/data.frame)
  ..$ continent: chr [1:5] "Africa" "Americas" "Asia" "Europe" ...
  ..$ .rows    : list<int> [1:5] 
  .. ..$ : int [1:624] 25 26 27 28 29 30 31 32 33 34 ...
  .. ..$ : int [1:300] 49 50 51 52 53 54 55 56 57 58 ...
  .. ..$ : int [1:396] 1 2 3 4 5 6 7 8 9 10 ...
  .. ..$ : int [1:360] 13 14 15 16 17 18 19 20 21 22 ...
  .. ..$ : int [1:24] 61 62 63 64 65 66 67 68 69 70 ...
  .. ..@ ptype: int(0) 
  ..- attr(*, ".drop")= logi TRUE

Notarás que la estructura del marco de datos donde usamos group_by() (grouped_df) no es igual que el (data.frame) gapminder original. grouped_df puede considerarse como una lista donde cada elemento de la lista es un data.frame que contiene solo las filas que corresponden a un valor particular de continente (al menos en el ejemplo anterior).

Usando summarize()

Lo anterior fue sin complicaciones, pero group_by () es mucho más emocionante junto con summarize(). Esto nos permitirá crear nuevas variable(s) mediante el uso de funciones que se repiten para cada marco de datos específico continente. Es decir, usando la función group_by(), dividimos nuestro marco de datos original en varias piezas, entonces podemos ejecutar las funciones (por ejemplo, mean() o sd()) dentro de summarize(). (e.g. mean() or sd()) within.

gdp_bycontinents <- gapminder %>%
  group_by(continent) %>%
  summarize(mean_gdpPercap = mean(gdpPercap))
`summarise()` desgrupa el resultado (sobreescribe el argumento `.groups` )
gdp_bycontinents
# A tibble: 5 x 2
  continent mean_gdpPercap
  <chr>              <dbl>
1 Africa             2194.
2 Americas           7136.
3 Asia               7902.
4 Europe            14469.
5 Oceania           18622.

Eso nos permite calcular la media de gdpPercap para cada continente, pero esto se pone mejor aún.

Desafío 2

Calcula la esperanza de vida media por país. ¿Qué país tiene la esperanza de vida más larga and qué país tiene la esperanza de vida más corta?

Solución al Desafío 2

lifeExp_bycountry <- gapminder %>%
   group_by(country) %>%
   summarize(mean_lifeExp=mean(lifeExp))
`summarise()` ungrouping output (override with `.groups` argument)
lifeExp_bycountry %>%
   filter(mean_lifeExp == min(mean_lifeExp) | mean_lifeExp == max(mean_lifeExp))
# A tibble: 2 x 2
 country      mean_lifeExp
 <chr>               <dbl>
1 Iceland              76.5
2 Sierra Leone         36.8

Otra manera de hacer esto, es usar del paquete dplyr la función arrange(), que ordena las filas en un data frame según el orden de una o más variables del data frame. Tiene una sintaxis similar a otras funciones del paquete dplyr . Puedes usar desc() dentro de arrange() para ordenar en forma descendiente.

lifeExp_bycountry %>%
   arrange(mean_lifeExp) %>%
   head(1)
# A tibble: 1 x 2
 country      mean_lifeExp
 <chr>               <dbl>
1 Sierra Leone         36.8
lifeExp_bycountry %>%
   arrange(desc(mean_lifeExp)) %>%
   head(1)
# A tibble: 1 x 2
 country mean_lifeExp
 <chr>          <dbl>
1 Iceland         76.5

La función group_by() nos permite agrupar por múltiples variables. Vamos a agrupar por year y continent.

gdp_bycontinents_byyear <- gapminder %>%
  group_by(continent, year) %>%
  summarize(mean_gdpPercap = mean(gdpPercap))
`summarise()` regrouping output by 'continent' (override with `.groups` argument)

Eso ya es bastante poderoso, ¡pero se pone aún mejor! No estás limitado a definir 1 nueva variable en summarize().

gdp_pop_bycontinents_byyear <- gapminder %>%
  group_by(continent,year) %>%
  summarize(mean_gdpPercap = mean(gdpPercap),
            sd_gdpPercap = sd(gdpPercap),
            mean_pop = mean(pop),
            sd_pop = sd(pop))
`summarise()` regrouping output by 'continent' (override with `.groups` argument)

count() y n()

Una operación muy común es contar el número de observaciones para cada grupo. El paquete dplyr tiene dos funciones relacionadas que ayudan con eso.

Por ejemplo, si quisiésemos verificar el number de países incluidos en el dataset para el año 2002, podríamos usar la función count() . Toma los nombres de una o más columnas que contienen los grupos que nos interesan, y opcionalmente podemos ordenar de manera descendente los resultados al agregar sort=TRUE:

gapminder %>%
    filter(year == 2002) %>%
    count(continent, sort = TRUE)
  continent  n
1    Africa 52
2      Asia 33
3    Europe 30
4  Americas 25
5   Oceania  2

Si necesitamos utilizar el número de observaciones en cálculos, la función n() es útil. Por ejemplo, si quisiésemos obtener el error estándar de la esperanza de vida por continente:

gapminder %>%
    group_by(continent) %>%
    summarize(se_le = sd(lifeExp)/sqrt(n()))
`summarise()` desgrupa el resultado (sobreescribe el argumento `.groups` )
# A tibble: 5 x 2
  continent se_le
  <chr>     <dbl>
1 Africa    0.366
2 Americas  0.540
3 Asia      0.596
4 Europe    0.286
5 Oceania   0.775

También puedes encadenar varias operaciones de resumen; en este caso calculando el minimum, maximum, mean and se (mínimo, máximo, promedio y error estándar) de la esperanza de vida por país para cada continente:

gapminder %>%
    group_by(continent) %>%
    summarize(
      mean_le = mean(lifeExp),
      min_le = min(lifeExp),
      max_le = max(lifeExp),
      se_le = sd(lifeExp)/sqrt(n()))
`summarise()` desgrupa el resultado (sobreescribe el argumento `.groups` )
# A tibble: 5 x 5
  continent mean_le min_le max_le se_le
  <chr>       <dbl>  <dbl>  <dbl> <dbl>
1 Africa       48.9   23.6   76.4 0.366
2 Americas     64.7   37.6   80.7 0.540
3 Asia         60.1   28.8   82.6 0.596
4 Europe       71.9   43.6   81.8 0.286
5 Oceania      74.3   69.1   81.2 0.775

Usando mutate

También podemos crear nuevas variables previas a (o incluso luego) de resumir información usando mutate()

gdp_pop_bycontinents_byyear <- gapminder %>%
  mutate(gdp_billion = gdpPercap*pop/10^9) %>%
  group_by(continent, year) %>%
  summarize(mean_gdpPercap = mean(gdpPercap),
            sd_gdpPercap = sd(gdpPercap),
            mean_pop = mean(pop),
            sd_pop = sd(pop),
            mean_gdp_billion = mean(gdp_billion),
            sd_gdp_billion = sd(gdp_billion))
`summarise()` regrouping output by 'continent' (override with `.groups` argument)

Otros recursos geniales

Puntos Clave

  • Usar el paquete dplyr para manipular dataframes.

  • Usar select() para elegir variables dentro de un dataframe.

  • Usar filter() to elegir datos basados en valores.

  • Usar group_by() y summarize() para trabajar con subconjuntos de datos.

  • Usar mutate() para crear nuevas variables.