¿Cómo manipular datos con dplyr?

¿Qué es dplyr?

dplyr es un paquete de R muy potente para la exploración, transformación y resumen de datos en formato de tabla con filas (observaciones) y columnas (variables). Es un componente de un conjunto de packages llamado tidyverse desarrollados por Hadley Wickham

El paquete contiene un conjunto de funciones (o verbos) que realizan operaciones comunes para el manejo de datos tales como: filtrar filas, seleccionar columnas, re-ordenar filas, agregar o transformar columnas, resumir datos. También permite agrupar los datos facilitando la estrategia split-apply-combine, es decir, dividir (split) los datos según una variable de grupo, aplicar (apply) alguna transformación o resumen y combinar (combine) las partes para presentar los resultados.

Si bien R base tiene funciones que realizan las mismas tareas (split(), subset(), apply(), sapply(), lapply(), tapply() and aggregate()), el paquete dplyr brinda una interface más consistente que permite trabajar de manera más facil con data.frame (tabla de datos) más que con vectores.

¿Cómo conseguir dplyr?

Para instalar por primera vez en la computadora:

# Solo
install.packages("dplyr")

# O junto con la familia tidyverse
install.packages("tidyverse")

Lo anterior se debe realizar por única vez si el paquete no está previamente instalado en la máquina. Para usar las funciones en una sesion de trabajo hay que cargarlo con library():

# Solo
library("dplyr")

# O junto con la familia tidyverse
library("tidyverse")

R va a avisarnos en la consola que esta enmascarando (reemplazando) algunas funciones que ya estaban en el entorno, o bien el paquete nos devuelve algun mensaje. A menos que diga Error ..., eso está bien.

Verbos importantes de dplyr para recordar

Toda la estrategia de trabajo con datos de dplyr se basa en 6 verbos:

verbo descripción
select() selecciona columnas (variables)
filter() filtra o selecciona las filas (observaciones)
arrange() re-ordena las filas
mutate() crea nuevas columnas o modifica las preexistentes
summarise() resumen los valores de una variable
group_by() permite aplicar los verbos anteriores en subgrupos (split-apply-combine)
sample_n() para tomar muestras aleatorias con o sin reposición

En la mayoría de los casos la sintaxis es:

function(que_datos, que_hacer_con_los_datos)

En que_datos hay que poner el nombre del set de datos o data.frame (a menos que se use el operador %>% para encadenar, mas adelante) y en que_hacer_con_los_datos indicar que operación, condicion, transformacion, etc aplicar a las filas y columnas.

dplyr en acción

Para ilustrar el uso del paquete vamos a usar los datos contenidos en el archivo pesada_terneros.xlsx. Para más detalles ir a la hoja de descripción de datos.

# Cargar los datos con readxl
library(readxl)
terneros <- read_excel("./data/pesada_terneros.xlsx")

Seleccionando variables

Una tareas básicas cuando se exploran datos es la selección de columnas de interés (variables). Esto se lleva a cabo con select().

Para seleccionar las columnas Procedencia, IDV y Peso:

# Sin asignar
select(terneros, Procedencia, IDV, Peso)
# A tibble: 1,598 × 3
   Procedencia IDV        Peso
   <chr>       <chr>     <dbl>
 1 La Rosita   NR047A202   204
 2 La Rosita   GN685B267   186
 3 La Rosita   AI101A751   182
 4 La Rosita   TM603C877   186
 5 La Rosita   TM420B797   186
 6 La Rosita   LH837F500   208
 7 La Rosita   NR047A217   170
 8 La Rosita   LH837F508   188
 9 La Rosita   GN685B256   172
10 La Rosita   QW110A058   172
# … with 1,588 more rows
# Creando un nuevo set de datos
mis_columnas <- select(terneros, Procedencia, IDV, Peso)

Por defecto, si no se asigna a un nuevo objeto, el resultado de la operación se imprime en la consola con la función print() la cual por defecto muestra las 10 primeras observaciones y la cantidad de columnas que entran en la pantalla. Aquellas columnas que no entran son indicadas al pie.

Si quiero ver más registros se puede usar el argumento n = de print()

print(mis_columnas, n = 15)
# A tibble: 1,598 × 3
   Procedencia IDV        Peso
   <chr>       <chr>     <dbl>
 1 La Rosita   NR047A202   204
 2 La Rosita   GN685B267   186
 3 La Rosita   AI101A751   182
 4 La Rosita   TM603C877   186
 5 La Rosita   TM420B797   186
 6 La Rosita   LH837F500   208
 7 La Rosita   NR047A217   170
 8 La Rosita   LH837F508   188
 9 La Rosita   GN685B256   172
10 La Rosita   QW110A058   172
11 La Rosita   LH837F497   188
12 La Rosita   TM420B803   180
13 La Rosita   LH837F514   198
14 La Rosita   II641B940   200
15 La Rosita   IY735C      242
# … with 1,583 more rows

Con n = "all" se imprimen todas (no se muestra por razones obvias)

El orden en que aparecen las variables en el resultado es el orden que se utilizó al seleccionarlas.

# El orden altera el producto
select(terneros, Procedencia, IDV, Peso)
# A tibble: 1,598 × 3
   Procedencia IDV        Peso
   <chr>       <chr>     <dbl>
 1 La Rosita   NR047A202   204
 2 La Rosita   GN685B267   186
 3 La Rosita   AI101A751   182
 4 La Rosita   TM603C877   186
 5 La Rosita   TM420B797   186
 6 La Rosita   LH837F500   208
 7 La Rosita   NR047A217   170
 8 La Rosita   LH837F508   188
 9 La Rosita   GN685B256   172
10 La Rosita   QW110A058   172
# … with 1,588 more rows

También se puede usar los comnados starts_with(), ends_with(), contains(), etc (ver ?select_helpers) para más opciones). Para elegir varias columnas que tienen un patron sin tener que tipear todos los nombres.

# Selecciona columnas que empiezan con P
select(terneros, starts_with("P"))
# A tibble: 1,598 × 2
   Procedencia  Peso
   <chr>       <dbl>
 1 La Rosita     204
 2 La Rosita     186
 3 La Rosita     182
 4 La Rosita     186
 5 La Rosita     186
 6 La Rosita     208
 7 La Rosita     170
 8 La Rosita     188
 9 La Rosita     172
10 La Rosita     172
# … with 1,588 more rows

Para omitir algunas columnas en la seleccion se puede usar el - antes del nombre.

# Selecciona columnas que empiezan con P
select(terneros, -IDV, -starts_with("P"))
# A tibble: 1,598 × 5
   Sexo  `Fecha Ingreso`     Categoria Fecha              
   <chr> <dttm>              <chr>     <dttm>             
 1 Macho 2017-07-20 00:00:00 Ternero   2017-04-06 00:00:00
 2 Macho 2017-07-20 00:00:00 Ternero   2017-04-06 00:00:00
 3 Macho 2017-07-21 00:00:00 Ternero   2017-04-06 00:00:00
 4 Macho 2017-07-20 00:00:00 Ternero   2017-04-06 00:00:00
 5 Macho 2017-07-24 00:00:00 Ternero   2017-04-06 00:00:00
 6 Macho 2017-07-24 00:00:00 Ternero   2017-04-06 00:00:00
 7 Macho 2017-07-21 00:00:00 Ternero   2017-04-06 00:00:00
 8 Macho 2017-07-24 00:00:00 Ternero   2017-04-06 00:00:00
 9 Macho 2017-07-21 00:00:00 Ternero   2017-04-06 00:00:00
10 Macho 2017-07-24 00:00:00 Ternero   2017-04-06 00:00:00
# … with 1,588 more rows, and 1 more variable: Hora <dttm>

Seleccionando observaciones

Otra tarea muy frecuente es seleccionar casos o observaciones que cumplan con alguna condición. Esto se lleva a cabo con filter(). Se pueden usar los operadores booleanos ==, >, <, >=, <=, !=, %in%) para crear pruebas o condiciones lógicas.

Para seleccionar los terneros de Los Corralitos:

# Sin asignar
filter(terneros, Procedencia == 'Los Corralitos')
# A tibble: 575 × 8
   IDV       Procedencia    Sexo  `Fecha Ingreso`     Categoria
   <chr>     <chr>          <chr> <dttm>              <chr>    
 1 PO150A167 Los Corralitos Macho 2017-07-23 00:00:00 Ternero  
 2 PO150A168 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 3 PO150A169 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 4 PO150A460 Los Corralitos Macho 2017-07-23 00:00:00 Ternero  
 5 PO150A673 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 6 PO150A461 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 7 PO150A462 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 8 PO150A463 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 9 PO150A433 Los Corralitos Macho 2017-06-06 00:00:00 Ternero  
10 PO150A434 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
# … with 565 more rows, and 3 more variables: Fecha <dttm>,
#   Hora <dttm>, Peso <dbl>
# Creando un nuevo set de datos
corralitos <- filter(terneros, Procedencia == 'Los Corralitos')

La seleccion se puede hacer por más de una condicion. Por ejemplo, seleccionar los de Los Corralitos que pesen más de 200 kg:

filter(terneros, Procedencia == 'Los Corralitos', Peso > 200)
# A tibble: 260 × 8
   IDV       Procedencia    Sexo  `Fecha Ingreso`     Categoria
   <chr>     <chr>          <chr> <dttm>              <chr>    
 1 PO150A167 Los Corralitos Macho 2017-07-23 00:00:00 Ternero  
 2 PO150A168 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 3 PO150A169 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 4 PO150A673 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 5 PO150A462 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 6 PO150A463 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 7 PO150A434 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 8 PO150A435 Los Corralitos Macho 2017-07-23 00:00:00 Ternero  
 9 PO150A675 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
10 PO150A681 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
# … with 250 more rows, and 3 more variables: Fecha <dttm>,
#   Hora <dttm>, Peso <dbl>

filter() asume que cada condicion se debe cumplir en simultaneo para que la observación sea seleccionada. Esto equivale a utilizar el operador & (Y). En caso de querer seleccionar aquellos registros que cumple una u otra condicion se usa el operador | (O). Poniendo ! delante de la condicion se invierte la selección.

# Operador &
filter(terneros, Procedencia == 'Los Corralitos' & Peso > 200)
# A tibble: 260 × 8
   IDV       Procedencia    Sexo  `Fecha Ingreso`     Categoria
   <chr>     <chr>          <chr> <dttm>              <chr>    
 1 PO150A167 Los Corralitos Macho 2017-07-23 00:00:00 Ternero  
 2 PO150A168 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 3 PO150A169 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 4 PO150A673 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 5 PO150A462 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 6 PO150A463 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 7 PO150A434 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 8 PO150A435 Los Corralitos Macho 2017-07-23 00:00:00 Ternero  
 9 PO150A675 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
10 PO150A681 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
# … with 250 more rows, and 3 more variables: Fecha <dttm>,
#   Hora <dttm>, Peso <dbl>
# Operador |
filter(terneros, Procedencia == 'Los Corralitos' | Peso > 200)
# A tibble: 779 × 8
   IDV       Procedencia  Sexo  `Fecha Ingreso`     Categoria
   <chr>     <chr>        <chr> <dttm>              <chr>    
 1 NR047A202 La Rosita    Macho 2017-07-20 00:00:00 Ternero  
 2 LH837F500 La Rosita    Macho 2017-07-24 00:00:00 Ternero  
 3 IY735C    La Rosita    Macho 2017-07-22 00:00:00 Ternero  
 4 QW110A072 La Rosita    Macho 2017-07-24 00:00:00 Ternero  
 5 LH837F526 La Rosita    Macho 2017-07-20 00:00:00 Ternero  
 6 DS289A491 La Rosita    Macho 2017-07-24 00:00:00 Ternero  
 7 TL698HK39 La Rosita    Macho 2017-07-20 00:00:00 Ternero  
 8 LH837F538 La Rosita    Macho 2017-07-21 00:00:00 Ternero  
 9 IW751A017 La Rosita    Macho 2017-07-24 00:00:00 Ternero  
10 NO133A004 Las Glicinas Macho 2017-07-23 00:00:00 Ternero  
# … with 769 more rows, and 3 more variables: Fecha <dttm>,
#   Hora <dttm>, Peso <dbl>

Con el operador %in% se puede especificar un rango de valores que deben cumplir. Por ejemplo terneros de Los Corralitos, Las Glicinas y Don Alberto

# Indicando cada nombre
filter(terneros, Procedencia == 'Los Corralitos', Procedencia == 'Las Glicinas', Procedencia == 'Don Alberto')
# A tibble: 0 × 8
# … with 8 variables: IDV <chr>, Procedencia <chr>, Sexo <chr>,
#   Fecha Ingreso <dttm>, Categoria <chr>, Fecha <dttm>, Hora <dttm>,
#   Peso <dbl>
# Más resumido con %in%
filter(terneros, Procedencia %in% c('Los Corralitos', 'Las Glicinas', 'Don Alberto'))
# A tibble: 1,138 × 8
   IDV       Procedencia  Sexo  `Fecha Ingreso`     Categoria
   <chr>     <chr>        <chr> <dttm>              <chr>    
 1 SZ208I507 Las Glicinas Macho 2017-07-20 00:00:00 Ternero  
 2 SZ208H993 Las Glicinas Macho 2017-06-05 00:00:00 Ternero  
 3 SZ208H849 Las Glicinas Macho 2017-06-05 00:00:00 Ternero  
 4 SZ208H777 Las Glicinas Macho 2017-06-05 00:00:00 Ternero  
 5 GT542A562 Las Glicinas Macho 2017-07-21 00:00:00 Ternero  
 6 OQ152A550 Las Glicinas Macho 2017-07-21 00:00:00 Ternero  
 7 NO133A057 Las Glicinas Macho 2017-07-24 00:00:00 Ternero  
 8 SZ208H888 Las Glicinas Macho 2017-07-22 00:00:00 Ternero  
 9 OQ152A566 Las Glicinas Macho 2017-07-20 00:00:00 Ternero  
10 NO133A047 Las Glicinas Macho 2017-07-20 00:00:00 Ternero  
# … with 1,128 more rows, and 3 more variables: Fecha <dttm>,
#   Hora <dttm>, Peso <dbl>

Encadenando operaciones (operador %>%)

dplyr importa el operador %>% de otro paquete llamado magrittr. Este operador permite encadenar operaciones realizadas con los verbos. De este modo no hay que ir creando tablas intermedias o anidar funciones. El operador traduce como luego y se le de izquierda a derecha y se puede.

Ejemplo: Reportar los IDV y peso de los terneros con más de 250 kg. Esto implicaría seleccionar las columnas de interés y luego filtrar la tabla o vice versa.

# Creando tablas intermedias
terneros2 <- select(terneros, IDV, Peso)
terneros2
# A tibble: 1,598 × 2
   IDV        Peso
   <chr>     <dbl>
 1 NR047A202   204
 2 GN685B267   186
 3 AI101A751   182
 4 TM603C877   186
 5 TM420B797   186
 6 LH837F500   208
 7 NR047A217   170
 8 LH837F508   188
 9 GN685B256   172
10 QW110A058   172
# … with 1,588 more rows
terneros2 <- filter(terneros2, Peso > 250)
terneros2
# A tibble: 75 × 2
   IDV        Peso
   <chr>     <dbl>
 1 NO133A004   258
 2 OQ152A456   258
 3 NO133A006   256
 4 OQ152A553   256
 5 PO150A166   290
 6 PO150A674   256
 7 PO150A656   272
 8 NO133A045   264
 9 PO150A571   264
10 PO150A686   262
# … with 65 more rows
# Anidando
filter(select(terneros, IDV, Peso), Peso > 250)
# A tibble: 75 × 2
   IDV        Peso
   <chr>     <dbl>
 1 NO133A004   258
 2 OQ152A456   258
 3 NO133A006   256
 4 OQ152A553   256
 5 PO150A166   290
 6 PO150A674   256
 7 PO150A656   272
 8 NO133A045   264
 9 PO150A571   264
10 PO150A686   262
# … with 65 more rows
# Usando %>%
terneros %>%
  select(IDV, Peso) %>%
  filter(Peso >  250)
# A tibble: 75 × 2
   IDV        Peso
   <chr>     <dbl>
 1 NO133A004   258
 2 OQ152A456   258
 3 NO133A006   256
 4 OQ152A553   256
 5 PO150A166   290
 6 PO150A674   256
 7 PO150A656   272
 8 NO133A045   264
 9 PO150A571   264
10 PO150A686   262
# … with 65 more rows

Con %>% se puede omitir el nombre de la tabla sobre la que se está trabajando (bonus: menos tipeo).

La última opción se lee: _tomar la tabla terneros, luego seleccionar las columnas IDV y Peso, luego filtrar los terneros con pesos mayores a 250 kg.

El operador de encadenamiento es muy útil cuando se encadenan muchas operaciones.

Ordenar las filas

Para ordenar según algun criterio aplicado a las columnas se usa arrange(). Por ejemplo, continuar con lo anterior pero mostrar ordenadospor peso.

# Ordenar de menor a mayor
terneros %>%
  select(IDV, Peso) %>%
  filter(Peso >  250) %>%
  arrange(Peso)
# A tibble: 75 × 2
   IDV        Peso
   <chr>     <dbl>
 1 PO150A546   252
 2 NS509H081   252
 3 PO15A710    252
 4 NO133A019   254
 5 PO150A784   254
 6 IA671B182   254
 7 IA671B176   254
 8 PO150A716   254
 9 NO133A006   256
10 OQ152A553   256
# … with 65 more rows

Con decs(variable) se ordena de mayor a menor

# Ordenar de mayor a menor
terneros %>%
  select(IDV, Peso) %>%
  filter(Peso >  250) %>%
  arrange(desc(Peso))
# A tibble: 75 × 2
   IDV        Peso
   <chr>     <dbl>
 1 PO150A679   304
 2 IA671B041   294
 3 MM429A932   292
 4 PO150A166   290
 5 PO150A763   288
 6 IA671B106   288
 7 NS509G964   286
 8 PO150A821   284
 9 IA671B045   282
10 NO133A000   280
# … with 65 more rows

Crear o transformar columnas

Para crear nuevas columnas aplicando funciones a otras, o bien para transformar columnas se usa mutate(). Se pueden modificar más de una columna a la vez. Por ejemplo, suponiendo que interesa obtener el logaritmo natura de los pesos o elevar los pesos al cuadrado.

terneros %>% 
  mutate(log_peso = log(Peso), peso2 = Peso**2) %>%
  select(Peso, log_peso, peso2)                        # para que se vea mejor el resultado 
# A tibble: 1,598 × 3
    Peso log_peso peso2
   <dbl>    <dbl> <dbl>
 1   204     5.32 41616
 2   186     5.23 34596
 3   182     5.20 33124
 4   186     5.23 34596
 5   186     5.23 34596
 6   208     5.34 43264
 7   170     5.14 28900
 8   188     5.24 35344
 9   172     5.15 29584
10   172     5.15 29584
# … with 1,588 more rows

Esto no cambia el set de datos terneros ya que no se lo asignó a ningun objeto. Para sobreescribir o actualiza el set de datos terneros hay que asignarlo al mismo objeto.

terneros <- terneros %>% 
  mutate(log_peso = log(Peso), peso2 = Peso**2)

Aclaración: Si se hubiese usado select() el set de datos terneros solamente contendría las columnas seleccionadas.

Otro ejemplo más útil: calcular los z-scores de los peso (para ello se requiere calcular el promedio y desvio) y crear una columna que indique si es un outlier y luego reportar los que son outliers.

terneros %>% 
  mutate(z = (Peso - mean(Peso))/sd(Peso), outlier = ifelse(abs(z) > 3, "si", "no")) %>%
  filter(outlier == "si") %>%
  select(IDV)
# A tibble: 2 × 1
  IDV      
  <chr>    
1 PO150A679
2 IA671B041

Resmuir datos

Mediante summarise() se pueden aplicar funciones para resumir en un solo valor los valores de las columnas. Las funciones a aplicar deben devolver un único valor, por ejemplo mean(). Si usamos summary() esto devolverá 6 valores y dará error.

terneros %>%
  summarise(media = mean(Peso), sd = sd(Peso), n = n(), suma = sum(Peso), procedencias = n_distinct(Procedencia))
# A tibble: 1 × 5
  media    sd     n   suma procedencias
  <dbl> <dbl> <int>  <dbl>        <int>
1  183.  37.0  1598 291802            7

Nuevamente estos resultados pueden asignarse a otro objeto o bien encadenarse con otras operaciones.

Otro ejemplo, obtener la cantidad de terneros de cada procedencia

terneros %>%
  count(Procedencia)
# A tibble: 7 × 2
  Procedencia        n
  <chr>          <int>
1 Don Alberto       69
2 La Alameda       201
3 La Estrella      118
4 La Rosita         98
5 La Segunda        43
6 Las Glicinas     494
7 Los Corralitos   575

Otro ejemplo más, cantidad de terneros de cada procedencia separados en mayor o menor a 200 kg

terneros %>%
  count(Procedencia, Peso > 200)
# A tibble: 14 × 3
   Procedencia    `Peso > 200`     n
   <chr>          <lgl>        <int>
 1 Don Alberto    FALSE           49
 2 Don Alberto    TRUE            20
 3 La Alameda     FALSE          136
 4 La Alameda     TRUE            65
 5 La Estrella    FALSE          112
 6 La Estrella    TRUE             6
 7 La Rosita      FALSE           89
 8 La Rosita      TRUE             9
 9 La Segunda     FALSE            5
10 La Segunda     TRUE            38
11 Las Glicinas   FALSE          428
12 Las Glicinas   TRUE            66
13 Los Corralitos FALSE          315
14 Los Corralitos TRUE           260

Agrupar (último pero no menos importante)

El verbo group_by() es muy útil para aplicar operaciones en subgrupos y presentar todo junto (split-apply-combine). Lo que hace es indicar que en el data.frame hay una o más variables que conforman los grupos. Luego cada operación se aplica a esos subgrupos.

Ejemplo: calcular media, desvio, n y suma para cada procedencia.

terneros %>%
  group_by(Procedencia) %>%
  summarise(media = mean(Peso), sd = sd(Peso), n = n(), suma = sum(Peso))
# A tibble: 7 × 5
  Procedencia    media    sd     n   suma
  <chr>          <dbl> <dbl> <int>  <dbl>
1 Don Alberto     179.  26.9    69  12354
2 La Alameda      190.  39.5   201  38192
3 La Estrella     179.  14.2   118  21138
4 La Rosita       180.  17.4    98  17620
5 La Segunda      229.  24.8    43   9868
6 Las Glicinas    160.  33.7   494  79254
7 Los Corralitos  197.  35.2   575 113376

Muestrear

El verbo sample_n() and sample_frac() son útiles para tomar muestras aleatorias (con o sin reposición) de un conjunto de observaciones. También se puede hacer por subgrupo!

# Una muestra de 50 novillos
muestra50 <- terneros %>%
  sample_n(50)
muestra50
# A tibble: 50 × 10
   IDV       Procedencia    Sexo  `Fecha Ingreso`     Categoria
   <chr>     <chr>          <chr> <dttm>              <chr>    
 1 JM173A252 La Alameda     Macho 2017-07-24 00:00:00 Ternero  
 2 CW828Z158 La Rosita      Macho 2017-07-20 00:00:00 Ternero  
 3 JM173A274 La Alameda     Macho 2017-07-21 00:00:00 Ternero  
 4 OH874D871 La Alameda     Macho 2017-07-21 00:00:00 Ternero  
 5 NS509G975 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
 6 SZ208I511 Las Glicinas   Macho 2017-07-21 00:00:00 Ternero  
 7 IA671B212 La Alameda     Macho 2017-07-24 00:00:00 Ternero  
 8 PO150A642 Los Corralitos Macho 2017-07-20 00:00:00 Ternero  
 9 SZ208H840 Las Glicinas   Macho 2017-07-24 00:00:00 Ternero  
10 NS509H025 Los Corralitos Macho 2017-07-22 00:00:00 Ternero  
# … with 40 more rows, and 5 more variables: Fecha <dttm>,
#   Hora <dttm>, Peso <dbl>, log_peso <dbl>, peso2 <dbl>
# Una muestra de 10 novillos de cada procedencia
muestra_procedencia <- terneros %>%
  group_by(Procedencia) %>%
  sample_n(10)
muestra_procedencia
# A tibble: 70 × 10
# Groups:   Procedencia [7]
   IDV       Procedencia Sexo  `Fecha Ingreso`     Categoria
   <chr>     <chr>       <chr> <dttm>              <chr>    
 1 GH738B338 Don Alberto Macho 2017-06-16 00:00:00 Ternero  
 2 GH738B354 Don Alberto Macho 2017-07-21 00:00:00 Ternero  
 3 GH738B326 Don Alberto Macho 2017-07-20 00:00:00 Ternero  
 4 GH738B381 Don Alberto Macho 2017-07-22 00:00:00 Ternero  
 5 GH738B368 Don Alberto Macho 2017-06-16 00:00:00 Ternero  
 6 GH738B380 Don Alberto Macho 2017-07-21 00:00:00 Ternero  
 7 GH738B350 Don Alberto Macho 2017-07-22 00:00:00 Ternero  
 8 GH738B330 Don Alberto Macho 2017-06-16 00:00:00 Ternero  
 9 GH738B386 Don Alberto Macho 2017-07-21 00:00:00 Ternero  
10 GH738B357 Don Alberto Macho 2017-06-16 00:00:00 Ternero  
# … with 60 more rows, and 5 more variables: Fecha <dttm>,
#   Hora <dttm>, Peso <dbl>, log_peso <dbl>, peso2 <dbl>