8  dplyr - gramática para manipulación de datos

8.1 Trabajo previo

8.1.1 Lecturas

Wickham, H., & Grolemund, G. (2017). R for Data Science: Import, Tidy, Transform, Visualize, and Model Data (1st ed.). O’Reilly Media. https://r4ds.had.co.nz/

8.2 Resumen

En este capítulo se estudia el paquete dplyr de Tidyverse.

8.3 Características generales

El paquete dplyr de Tidyverse es descrito como una “gramática para la manipulación de datos, la cual proporciona un conjunto consistente de verbos que ayuda a solucionar los retos de manipulación de datos más comunes”. Los principales “verbos” (i.e. funciones) de esta gramática son:

  • select(): selecciona columnas con base en sus nombres.

  • filter(): selecciona filas con base en sus valores.

  • arrange(): cambia el orden de las filas.

  • mutate(): crea nuevas columnas, las cuales se expresan como funciones de columnas existentes.

  • summarise(): agrupa y resume valores.

Todas estas operaciones pueden combinarse con la función group_by(), la cual ejecuta cualquiera de las operaciones anteriores “en grupo”. Además, dplyr proporciona funciones adicionales para tareas más específicas.

Todas las funciones de dplyr trabajan de manera similar:

  1. El primer argumento es un data frame. Puede omitirse si la función recibe el data frame a través del operador pipe.
  2. Los argumentos siguientes describen que hacer con el data frame, utilizando los nombres de las columnas (sin comillas).
  3. El resultado es un nuevo data frame.

Las funciones de dplyr pueden encadenarse a través del operador pipe (tubo), ya sea el del paquete magrittr (%>%) o el del paquete base de R (|>). Los procesos se enlazan con pipes para formar pipelines (tuberías).

8.4 Instalación y carga

El paquete dplyr pueden instalarse junto con todos los demás paquete de Tidyverse o de manera individual:

# Instalación conjunta de Tidyverse
install.packages("tidyverse")

# Instalación individual
install.packages("dplyr")

Una vez instalado, dplyr puede cargarse con la función library():

# Carga conjunta de Tidyverse
library(tidyverse)

# Carga individual
library(dplyr)

8.5 Conjuntos de datos de ejemplo

Para los ejemplos de este capítulo, se utiliza el paquete de datos palmerpenguins.

# Carga del paquete de datos palmerpenguins
library(palmerpenguins)

Estructura del conjunto de datos penguins:

# Estructura del paquete de datos palmerpenguins
glimpse(penguins)
Rows: 344
Columns: 8
$ species           <fct> Adelie, Adelie, Adelie, Adelie, Adelie, Adelie, Adel…
$ island            <fct> Torgersen, Torgersen, Torgersen, Torgersen, Torgerse…
$ bill_length_mm    <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, …
$ bill_depth_mm     <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, …
$ flipper_length_mm <int> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186…
$ body_mass_g       <int> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, …
$ sex               <fct> male, female, female, NA, female, male, female, male…
$ year              <int> 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007, 2007…

Vista del conjunto de datos penguins:

# Vista del paquete de datos palmerpenguins
View(penguins)

8.6 Funciones

8.6.1 select()

La función select() selecciona (y opcionalmente renombra) columnas de un data frame con base en sus nombres.

# Selección de las columnas de especie, longitud del pico y sexo
penguins |>
  select(species, bill_length_mm, sex)
# A tibble: 344 × 3
   species bill_length_mm sex   
   <fct>            <dbl> <fct> 
 1 Adelie            39.1 male  
 2 Adelie            39.5 female
 3 Adelie            40.3 female
 4 Adelie            NA   <NA>  
 5 Adelie            36.7 female
 6 Adelie            39.3 male  
 7 Adelie            38.9 female
 8 Adelie            39.2 male  
 9 Adelie            34.1 <NA>  
10 Adelie            42   <NA>  
# … with 334 more rows

Cambio de nombres de columnas:

# Selección y cambio de nombre de las columnas de especie, longitud del pico y sexo
penguins |>
  select(especie = species,
         longitud_pico_mm = bill_length_mm,
         sexo = sex)
# A tibble: 344 × 3
   especie longitud_pico_mm sexo  
   <fct>              <dbl> <fct> 
 1 Adelie              39.1 male  
 2 Adelie              39.5 female
 3 Adelie              40.3 female
 4 Adelie              NA   <NA>  
 5 Adelie              36.7 female
 6 Adelie              39.3 male  
 7 Adelie              38.9 female
 8 Adelie              39.2 male  
 9 Adelie              34.1 <NA>  
10 Adelie              42   <NA>  
# … with 334 more rows

El operador : permite seleccionar un rango de columnas continuas:

# Selección de las columnas en el rango de species a flipper_length_mm
penguins |>
  select(species:flipper_length_mm)
# A tibble: 344 × 5
   species island    bill_length_mm bill_depth_mm flipper_length_mm
   <fct>   <fct>              <dbl>         <dbl>             <int>
 1 Adelie  Torgersen           39.1          18.7               181
 2 Adelie  Torgersen           39.5          17.4               186
 3 Adelie  Torgersen           40.3          18                 195
 4 Adelie  Torgersen           NA            NA                  NA
 5 Adelie  Torgersen           36.7          19.3               193
 6 Adelie  Torgersen           39.3          20.6               190
 7 Adelie  Torgersen           38.9          17.8               181
 8 Adelie  Torgersen           39.2          19.6               195
 9 Adelie  Torgersen           34.1          18.1               193
10 Adelie  Torgersen           42            20.2               190
# … with 334 more rows

Selección de todas las columnas que cumplen una condición:

# Selección de las columnas numéricas
penguins |>
  select(where(is.numeric))
# A tibble: 344 × 5
   bill_length_mm bill_depth_mm flipper_length_mm body_mass_g  year
            <dbl>         <dbl>             <int>       <int> <int>
 1           39.1          18.7               181        3750  2007
 2           39.5          17.4               186        3800  2007
 3           40.3          18                 195        3250  2007
 4           NA            NA                  NA          NA  2007
 5           36.7          19.3               193        3450  2007
 6           39.3          20.6               190        3650  2007
 7           38.9          17.8               181        3625  2007
 8           39.2          19.6               195        4675  2007
 9           34.1          18.1               193        3475  2007
10           42            20.2               190        4250  2007
# … with 334 more rows

8.6.2 filter()

La función filter() retorna un subconjunto de un data frame con todas las filas que satisfacen una condición (i.e. expresión lógica).

Ejemplos de uso de expresiones y operadores lógicos:

# Filas de la especie 'Adelie' con longitud del pico mayor o igual a 45 mm
penguins |>
  filter(species == 'Adelie' & bill_length_mm >= 45)
# A tibble: 3 × 8
  species island    bill_length_mm bill_depth_mm flipper_l…¹ body_…² sex    year
  <fct>   <fct>              <dbl>         <dbl>       <int>   <int> <fct> <int>
1 Adelie  Torgersen           46            21.5         194    4200 male   2007
2 Adelie  Torgersen           45.8          18.9         197    4150 male   2008
3 Adelie  Biscoe              45.6          20.3         191    4600 male   2009
# … with abbreviated variable names ¹​flipper_length_mm, ²​body_mass_g
# Filas de la especie 'Adelie' o 'Gentoo'
penguins |>
  filter(species == 'Adelie' | species == 'Gentoo')
# A tibble: 276 × 8
   species island    bill_length_mm bill_depth_mm flipper_…¹ body_…² sex    year
   <fct>   <fct>              <dbl>         <dbl>      <int>   <int> <fct> <int>
 1 Adelie  Torgersen           39.1          18.7        181    3750 male   2007
 2 Adelie  Torgersen           39.5          17.4        186    3800 fema…  2007
 3 Adelie  Torgersen           40.3          18          195    3250 fema…  2007
 4 Adelie  Torgersen           NA            NA           NA      NA <NA>   2007
 5 Adelie  Torgersen           36.7          19.3        193    3450 fema…  2007
 6 Adelie  Torgersen           39.3          20.6        190    3650 male   2007
 7 Adelie  Torgersen           38.9          17.8        181    3625 fema…  2007
 8 Adelie  Torgersen           39.2          19.6        195    4675 male   2007
 9 Adelie  Torgersen           34.1          18.1        193    3475 <NA>   2007
10 Adelie  Torgersen           42            20.2        190    4250 <NA>   2007
# … with 266 more rows, and abbreviated variable names ¹​flipper_length_mm,
#   ²​body_mass_g
# Filas de especies diferentes a 'Chinstrap'
penguins |>
  filter(!(species == 'Chinstrap'))
# A tibble: 276 × 8
   species island    bill_length_mm bill_depth_mm flipper_…¹ body_…² sex    year
   <fct>   <fct>              <dbl>         <dbl>      <int>   <int> <fct> <int>
 1 Adelie  Torgersen           39.1          18.7        181    3750 male   2007
 2 Adelie  Torgersen           39.5          17.4        186    3800 fema…  2007
 3 Adelie  Torgersen           40.3          18          195    3250 fema…  2007
 4 Adelie  Torgersen           NA            NA           NA      NA <NA>   2007
 5 Adelie  Torgersen           36.7          19.3        193    3450 fema…  2007
 6 Adelie  Torgersen           39.3          20.6        190    3650 male   2007
 7 Adelie  Torgersen           38.9          17.8        181    3625 fema…  2007
 8 Adelie  Torgersen           39.2          19.6        195    4675 male   2007
 9 Adelie  Torgersen           34.1          18.1        193    3475 <NA>   2007
10 Adelie  Torgersen           42            20.2        190    4250 <NA>   2007
# … with 266 more rows, and abbreviated variable names ¹​flipper_length_mm,
#   ²​body_mass_g
# Filas con longitud del pico mayor o igual al promedio
#   El argumento lógico na.rm de mean() indica si los valores NA ("not available") 
#   deben ser removidos antes del cálculo
penguins |>
  filter(bill_length_mm >= mean(bill_length_mm, na.rm = TRUE))
# A tibble: 175 × 8
   species island    bill_length_mm bill_depth_mm flipper_…¹ body_…² sex    year
   <fct>   <fct>              <dbl>         <dbl>      <int>   <int> <fct> <int>
 1 Adelie  Torgersen           46            21.5        194    4200 male   2007
 2 Adelie  Dream               44.1          19.7        196    4400 male   2007
 3 Adelie  Torgersen           45.8          18.9        197    4150 male   2008
 4 Adelie  Biscoe              45.6          20.3        191    4600 male   2009
 5 Adelie  Torgersen           44.1          18          210    4000 male   2009
 6 Gentoo  Biscoe              46.1          13.2        211    4500 fema…  2007
 7 Gentoo  Biscoe              50            16.3        230    5700 male   2007
 8 Gentoo  Biscoe              48.7          14.1        210    4450 fema…  2007
 9 Gentoo  Biscoe              50            15.2        218    5700 male   2007
10 Gentoo  Biscoe              47.6          14.5        215    5400 male   2007
# … with 165 more rows, and abbreviated variable names ¹​flipper_length_mm,
#   ²​body_mass_g

Condiciones relacionadas con valores NA:

# Filas con valor NA en la columna sex
penguins |>
  select(species, island, sex) %>%
  filter(is.na(sex))
# A tibble: 11 × 3
   species island    sex  
   <fct>   <fct>     <fct>
 1 Adelie  Torgersen <NA> 
 2 Adelie  Torgersen <NA> 
 3 Adelie  Torgersen <NA> 
 4 Adelie  Torgersen <NA> 
 5 Adelie  Torgersen <NA> 
 6 Adelie  Dream     <NA> 
 7 Gentoo  Biscoe    <NA> 
 8 Gentoo  Biscoe    <NA> 
 9 Gentoo  Biscoe    <NA> 
10 Gentoo  Biscoe    <NA> 
11 Gentoo  Biscoe    <NA> 

La función drop_na() remueve las filas con valores NA en una o varias columnas.

# Filas con valor diferente a NA en la columna sex
penguins |>
  select(species,
         bill_length_mm,
         bill_depth_mm,
         flipper_length_mm,
         body_mass_g,
         sex) %>%
  drop_na(sex)
# A tibble: 333 × 6
   species bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex   
   <fct>            <dbl>         <dbl>             <int>       <int> <fct> 
 1 Adelie            39.1          18.7               181        3750 male  
 2 Adelie            39.5          17.4               186        3800 female
 3 Adelie            40.3          18                 195        3250 female
 4 Adelie            36.7          19.3               193        3450 female
 5 Adelie            39.3          20.6               190        3650 male  
 6 Adelie            38.9          17.8               181        3625 female
 7 Adelie            39.2          19.6               195        4675 male  
 8 Adelie            41.1          17.6               182        3200 female
 9 Adelie            38.6          21.2               191        3800 male  
10 Adelie            34.6          21.1               198        4400 male  
# … with 323 more rows
# Filas con valor diferente a NA en cualquier columna
penguins |>
  select(species,
         bill_length_mm,
         bill_depth_mm,
         flipper_length_mm,
         body_mass_g,
         sex) %>%
  drop_na()
# A tibble: 333 × 6
   species bill_length_mm bill_depth_mm flipper_length_mm body_mass_g sex   
   <fct>            <dbl>         <dbl>             <int>       <int> <fct> 
 1 Adelie            39.1          18.7               181        3750 male  
 2 Adelie            39.5          17.4               186        3800 female
 3 Adelie            40.3          18                 195        3250 female
 4 Adelie            36.7          19.3               193        3450 female
 5 Adelie            39.3          20.6               190        3650 male  
 6 Adelie            38.9          17.8               181        3625 female
 7 Adelie            39.2          19.6               195        4675 male  
 8 Adelie            41.1          17.6               182        3200 female
 9 Adelie            38.6          21.2               191        3800 male  
10 Adelie            34.6          21.1               198        4400 male  
# … with 323 more rows

8.6.3 arrange()

La función arrange() cambia el orden de las filas de un data frame de acuerdo con los valores de las columnas seleccionadas.

# Ordenamiento ascendente por las columnas 'bill_lenght_mm' y 'bill_depth_mm'
penguins |>
  arrange(bill_length_mm, bill_depth_mm)
# A tibble: 344 × 8
   species island    bill_length_mm bill_depth_mm flipper_…¹ body_…² sex    year
   <fct>   <fct>              <dbl>         <dbl>      <int>   <int> <fct> <int>
 1 Adelie  Dream               32.1          15.5        188    3050 fema…  2009
 2 Adelie  Dream               33.1          16.1        178    2900 fema…  2008
 3 Adelie  Torgersen           33.5          19          190    3600 fema…  2008
 4 Adelie  Dream               34            17.1        185    3400 fema…  2008
 5 Adelie  Torgersen           34.1          18.1        193    3475 <NA>   2007
 6 Adelie  Torgersen           34.4          18.4        184    3325 fema…  2007
 7 Adelie  Biscoe              34.5          18.1        187    2900 fema…  2008
 8 Adelie  Torgersen           34.6          17.2        189    3200 fema…  2008
 9 Adelie  Torgersen           34.6          21.1        198    4400 male   2007
10 Adelie  Biscoe              35            17.9        190    3450 fema…  2008
# … with 334 more rows, and abbreviated variable names ¹​flipper_length_mm,
#   ²​body_mass_g

Por defecto, las columnas se ordenan de manera acendente. Si se desea un orden descendente, puede utilizarse la función desc().

# Ordenamiento descendente por las columnas 'bill_lenght_mm' y 'bill_depth_mm'
penguins |>
  arrange(desc(bill_length_mm), desc(bill_depth_mm))
# A tibble: 344 × 8
   species   island bill_length_mm bill_depth_mm flipper_l…¹ body_…² sex    year
   <fct>     <fct>           <dbl>         <dbl>       <int>   <int> <fct> <int>
 1 Gentoo    Biscoe           59.6          17           230    6050 male   2007
 2 Chinstrap Dream            58            17.8         181    3700 fema…  2007
 3 Gentoo    Biscoe           55.9          17           228    5600 male   2009
 4 Chinstrap Dream            55.8          19.8         207    4000 male   2009
 5 Gentoo    Biscoe           55.1          16           230    5850 male   2009
 6 Gentoo    Biscoe           54.3          15.7         231    5650 male   2008
 7 Chinstrap Dream            54.2          20.8         201    4300 male   2008
 8 Chinstrap Dream            53.5          19.9         205    4500 male   2008
 9 Gentoo    Biscoe           53.4          15.8         219    5500 male   2009
10 Chinstrap Dream            52.8          20           205    4550 male   2008
# … with 334 more rows, and abbreviated variable names ¹​flipper_length_mm,
#   ²​body_mass_g

Nótese que los valores NA se ubican al final de cualquier ordenamiento.

La función across() aplica una función en múltiples columnas.

# Ordenamiento ascendente por las columnas que empiezan con 'bill'
penguins |>
  arrange(across(starts_with('bill')))
# A tibble: 344 × 8
   species island    bill_length_mm bill_depth_mm flipper_…¹ body_…² sex    year
   <fct>   <fct>              <dbl>         <dbl>      <int>   <int> <fct> <int>
 1 Adelie  Dream               32.1          15.5        188    3050 fema…  2009
 2 Adelie  Dream               33.1          16.1        178    2900 fema…  2008
 3 Adelie  Torgersen           33.5          19          190    3600 fema…  2008
 4 Adelie  Dream               34            17.1        185    3400 fema…  2008
 5 Adelie  Torgersen           34.1          18.1        193    3475 <NA>   2007
 6 Adelie  Torgersen           34.4          18.4        184    3325 fema…  2007
 7 Adelie  Biscoe              34.5          18.1        187    2900 fema…  2008
 8 Adelie  Torgersen           34.6          17.2        189    3200 fema…  2008
 9 Adelie  Torgersen           34.6          21.1        198    4400 male   2007
10 Adelie  Biscoe              35            17.9        190    3450 fema…  2008
# … with 334 more rows, and abbreviated variable names ¹​flipper_length_mm,
#   ²​body_mass_g
# Ordenamiento ascendente por las columnas que contienen la hilera 'lenght'
penguins |>
  arrange(across(contains('length')))
# A tibble: 344 × 8
   species island    bill_length_mm bill_depth_mm flipper_…¹ body_…² sex    year
   <fct>   <fct>              <dbl>         <dbl>      <int>   <int> <fct> <int>
 1 Adelie  Dream               32.1          15.5        188    3050 fema…  2009
 2 Adelie  Dream               33.1          16.1        178    2900 fema…  2008
 3 Adelie  Torgersen           33.5          19          190    3600 fema…  2008
 4 Adelie  Dream               34            17.1        185    3400 fema…  2008
 5 Adelie  Torgersen           34.1          18.1        193    3475 <NA>   2007
 6 Adelie  Torgersen           34.4          18.4        184    3325 fema…  2007
 7 Adelie  Biscoe              34.5          18.1        187    2900 fema…  2008
 8 Adelie  Torgersen           34.6          17.2        189    3200 fema…  2008
 9 Adelie  Torgersen           34.6          21.1        198    4400 male   2007
10 Adelie  Biscoe              35            17.9        190    3450 fema…  2008
# … with 334 more rows, and abbreviated variable names ¹​flipper_length_mm,
#   ²​body_mass_g

8.6.4 mutate()

La función mutate() crea o modifica columnas en un data frame.

# Creación de la columna 'body_mass_kg' con el valor de 'body_mass_g' expresado en kg
penguins |>
  select(species, body_mass_g) |>
  mutate(body_mass_kg = body_mass_g/1000)
# A tibble: 344 × 3
   species body_mass_g body_mass_kg
   <fct>         <int>        <dbl>
 1 Adelie         3750         3.75
 2 Adelie         3800         3.8 
 3 Adelie         3250         3.25
 4 Adelie           NA        NA   
 5 Adelie         3450         3.45
 6 Adelie         3650         3.65
 7 Adelie         3625         3.62
 8 Adelie         4675         4.68
 9 Adelie         3475         3.48
10 Adelie         4250         4.25
# … with 334 more rows
# Creación de la columnas 'body_mass_g_mean' (promedio de masa) 
# y 'body_mass_g_normalized' (proporción con respecto al promedio)
penguins |>
  select(species, body_mass_g) |>
  mutate(body_mass_g_mean = mean(body_mass_g, na.rm = TRUE)) |>
  mutate(body_mass_g_normalized = body_mass_g / body_mass_g_mean)
# A tibble: 344 × 4
   species body_mass_g body_mass_g_mean body_mass_g_normalized
   <fct>         <int>            <dbl>                  <dbl>
 1 Adelie         3750            4202.                  0.892
 2 Adelie         3800            4202.                  0.904
 3 Adelie         3250            4202.                  0.773
 4 Adelie           NA            4202.                 NA    
 5 Adelie         3450            4202.                  0.821
 6 Adelie         3650            4202.                  0.869
 7 Adelie         3625            4202.                  0.863
 8 Adelie         4675            4202.                  1.11 
 9 Adelie         3475            4202.                  0.827
10 Adelie         4250            4202.                  1.01 
# … with 334 more rows

La función group_by() agrupa una o más columnas.

# Creación de la columnas 'body_mass_g_mean_species' (promedio de masa de la especie) 
# y 'body_mass_g_species_normalized' (proporción con respecto al promedio de masa de la especie)
penguins |>
  select(species, body_mass_g) |>
  group_by(species) |>
  mutate(body_mass_g_mean_species = mean(body_mass_g, na.rm = TRUE)) |>
  mutate(body_mass_g_species_normalized = body_mass_g / body_mass_g_mean_species)
# A tibble: 344 × 4
# Groups:   species [3]
   species body_mass_g body_mass_g_mean_species body_mass_g_species_normalized
   <fct>         <int>                    <dbl>                          <dbl>
 1 Adelie         3750                    3701.                          1.01 
 2 Adelie         3800                    3701.                          1.03 
 3 Adelie         3250                    3701.                          0.878
 4 Adelie           NA                    3701.                         NA    
 5 Adelie         3450                    3701.                          0.932
 6 Adelie         3650                    3701.                          0.986
 7 Adelie         3625                    3701.                          0.980
 8 Adelie         4675                    3701.                          1.26 
 9 Adelie         3475                    3701.                          0.939
10 Adelie         4250                    3701.                          1.15 
# … with 334 more rows

8.6.5 summarise()

La función summarise() crea un nuevo data frame con una (o más filas), correspondientes a combinaciones de las columnas usadas en una agrupación. Esta función frecuentemente se usa en combinación con group_by(). Si no hay agrupación, se retorna una sola fila que sumariza todas las observaciones de la entrada.

Sumarización sin agrupamiento:

# Creación de un data frame con las columnas sumarizadas 'body_mass_g_mean' (promedio de masa) 
# y 'n' (cantidad de individuos)
penguins |>
  summarise(body_mass_g_mean = mean(body_mass_g, na.rm = TRUE),
            n = n())
# A tibble: 1 × 2
  body_mass_g_mean     n
             <dbl> <int>
1            4202.   344

La función n() cuenta la cantidad de filas en un grupo.

Sumarización con agrupamiento:

# Creación de un data frame con las columnas sumarizadas de mínimo, máximo y promedio de masa,
# y cantidad de individuos para cada especie
penguins |>
  group_by(species) |>
  summarise(
    body_mass_g_min = min(body_mass_g, na.rm = TRUE),
    body_mass_g_max = max(body_mass_g, na.rm = TRUE),
    body_mass_g_mean = mean(body_mass_g, na.rm = TRUE),
    n = n()
  )
# A tibble: 3 × 5
  species   body_mass_g_min body_mass_g_max body_mass_g_mean     n
  <fct>               <int>           <int>            <dbl> <int>
1 Adelie               2850            4775            3701.   152
2 Chinstrap            2700            4800            3733.    68
3 Gentoo               3950            6300            5076.   124

8.6.6 Otras

8.6.6.1 count()

Una forma alternativa (a summarise()) de realizar un conteo es con la función count():

# Creación de un data frame con el conteo de individuos por especie
penguins |>
  count(species)
# A tibble: 3 × 2
  species       n
  <fct>     <int>
1 Adelie      152
2 Chinstrap    68
3 Gentoo      124

8.7 Ejercicios

Utilice las funciones de dplyr para responder a las siguientes preguntas sobre el conjunto de datos penguins:

  1. ¿Cuántos individuos de cada sexo hay en cada especie?
  2. ¿Cuál es el mínimo, máximo y promedio de masa corporal (peso) por especie y sexo?
  3. ¿Cuántos individuos se observaron durante cada año?
  4. ¿Cuántos individuos de cada especie se observaron durante cada año?
  5. ¿Cuántos individuos de cada especie y cada sexo se observaron durante cada año?
  6. ¿Cuál es el promedio de masa corporal (peso) por año?
  7. ¿Cuál es el promedio de masa corporal (peso) por año para cada especie?

8.8 Recursos de interés

RStudio. (2017). Data transformation with dplyr::Cheat Sheet. https://github.com/rstudio/cheatsheets/blob/45c1e642468695830fd8b724587ccfe8901e2185/data-transformation.pdf