Seleccionar filas por posición con slice() de dplyr

Seleccionar filas por posición o valores máximos con slice de dplyr

La familia de funciones slice de dplyr selecciona filas de un data frame según su posición en lugar de según una condición. slice utiliza números de fila, slice_head y slice_tail devuelven las primeras o las últimas filas, slice_min y slice_max devuelven las filas con los valores más bajos o más altos de una variable y slice_sample extrae una muestra aleatoria. En este tutorial aprenderás a usar cada una de ellas, a obtener las N primeras filas de cada grupo, cómo se tratan los empates y los valores faltantes y qué función elegir en cada caso.

Datos de ejemplo

En este tutorial utilizaremos un subconjunto del conjunto de datos starwars de dplyr: los personajes de tres especies (humanos, droides y gungans) cuyo peso es conocido. El data frame resultante contiene 26 filas y cuatro columnas. El código utiliza el operador pipe %>% para encadenar los pasos.

library(dplyr)

df <- starwars %>%
  select(name, species, height, mass) %>%
  filter(species %in% c("Human", "Droid", "Gungan"), !is.na(mass))

df
# A tibble: 26 × 4
   name               species height  mass
   <chr>              <chr>    <int> <dbl>
 1 Luke Skywalker     Human      172    77
 2 C-3PO              Droid      167    75
 3 R2-D2              Droid       96    32
 4 Darth Vader        Human      202   136
 5 Leia Organa        Human      150    49
 6 Owen Lars          Human      178   120
 7 Beru Whitesun Lars Human      165    75
 8 R5-D4              Droid       97    32
 9 Biggs Darklighter  Human      183    84
10 Obi-Wan Kenobi     Human      182    77
# ℹ 16 more rows

Seleccionar filas por posición con slice

La función slice recibe un vector de números de fila y devuelve esas filas en el orden que indiques. Funciona como df[c(1, 3, 5), ] en R base, pero encaja en un flujo con pipes y siempre devuelve un tibble:

df %>%
  slice(c(1, 3, 5))
# A tibble: 3 × 4
  name           species height  mass
  <chr>          <chr>    <int> <dbl>
1 Luke Skywalker Human      172    77
2 R2-D2          Droid       96    32
3 Leia Organa    Human      150    49

Puedes utilizar la función n(), que devuelve el número de filas, para referirte a la última fila sin conocer el tamaño de los datos. Por ejemplo, slice(n()) devuelve la última fila y slice((n() - 2):n()) las tres últimas. Si repites una posición, la fila se duplica: slice(c(1, 1, 2)) devuelve dos veces a Luke Skywalker.

Eliminar filas con índices negativos

Las posiciones negativas eliminan filas en lugar de seleccionarlas. Es la forma más sencilla de eliminar la primera fila de un data frame, por ejemplo cuando la primera línea de un archivo contiene una segunda cabecera o las unidades:

# Eliminar la primera fila
df %>%
  slice(-1)
# A tibble: 25 × 4
   name               species height  mass
   <chr>              <chr>    <int> <dbl>
 1 C-3PO              Droid      167    75
 2 R2-D2              Droid       96    32
 3 Darth Vader        Human      202   136
 4 Leia Organa        Human      150    49
 5 Owen Lars          Human      178   120
 6 Beru Whitesun Lars Human      165    75
 7 R5-D4              Droid       97    32
 8 Biggs Darklighter  Human      183    84
 9 Obi-Wan Kenobi     Human      182    77
10 Anakin Skywalker   Human      188    84
# ℹ 15 more rows

Para eliminar varias filas utiliza un vector negativo, como slice(-(1:3)) para quitar las tres primeras o slice(-n()) para quitar la última. No olvides los paréntesis: -1:3 es la secuencia -1, 0, 1, 2, 3, no -1, -2, -3.

Posiciones fuera de rango y signos mezclados

Hay dos comportamientos de slice que sorprenden a mucha gente. El primero es que las posiciones mayores que el número de filas se ignoran sin avisar. Los datos tienen 26 filas, así que al pedir las filas 25 a 30 solo obtienes dos, sin ninguna advertencia:

df %>%
  slice(25:30)
# A tibble: 2 × 4
  name            species height  mass
  <chr>           <chr>    <int> <dbl>
1 Jango Fett      Human      183    79
2 Raymus Antilles Human      188    79

El segundo es que no puedes mezclar posiciones positivas y negativas en la misma llamada:

df %>%
  slice(-1, 2)
Error in `slice()`:
! Can't compute indices.
Caused by error:
! Can't subset elements with `-1`.
✖ Negative and positive locations can't be mixed.
ℹ Subscript `-1` has a positive value at location 2.

Si lo que quieres es seleccionar filas según sus valores y no según su posición, utiliza la función filter.

Primeras y últimas filas: slice_head y slice_tail

slice_head devuelve las primeras filas y slice_tail las últimas filas de los datos. El número de filas se indica con el argumento n. Ten en cuenta que tienes que escribir su nombre: slice_head(3) da un error, mientras que slice_head(n = 3) funciona.

df %>%
  slice_head(n = 3)

df %>%
  slice_tail(n = 2)
# A tibble: 3 × 4
  name           species height  mass
  <chr>          <chr>    <int> <dbl>
1 Luke Skywalker Human      172    77
2 C-3PO          Droid      167    75
3 R2-D2          Droid       96    32

# A tibble: 2 × 4
  name            species height  mass
  <chr>           <chr>    <int> <dbl>
1 Jango Fett      Human      183    79
2 Raymus Antilles Human      188    79

En lugar de un número de filas, puedes indicar una proporción con el argumento prop. El resultado se redondea hacia abajo: el 10% de 26 filas es 2.6, así que obtienes dos filas.

df %>%
  slice_head(prop = 0.1)
# A tibble: 2 × 4
  name           species height  mass
  <chr>          <chr>    <int> <dbl>
1 Luke Skywalker Human      172    77
2 C-3PO          Droid      167    75

slice_head y slice_tail equivalen a las funciones head y tail de R base, con la ventaja de que también funcionan por grupo, como verás más adelante. Ambas utilizan el orden actual de las filas, así que si quieres las primeras filas según una variable, ordena antes los datos con arrange o utiliza slice_min y slice_max.

Filas con los valores más bajos o más altos: slice_min y slice_max

slice_min y slice_max devuelven las filas con los valores más bajos o más altos de una variable, ordenadas según esa variable. El siguiente código devuelve los tres personajes más altos:

df %>%
  slice_max(height, n = 3)
# A tibble: 3 × 4
  name         species height  mass
  <chr>        <chr>    <int> <dbl>
1 Roos Tarpals Gungan     224    82
2 Darth Vader  Human      202   136
3 IG-88        Droid      200   140

El resultado es el mismo que con df %>% arrange(desc(height)) %>% slice_head(n = 3), pero el código es más corto y se lee mejor. slice_max también admite prop, de modo que slice_max(mass, prop = 0.1) devuelve el 10% de personajes más pesados.

Empates: el argumento with_ties

Por defecto, slice_min y slice_max conservan todas las filas empatadas, por lo que la salida puede tener más filas que n. R2-D2 y R5-D4 pesan 32 kg, así que al pedir el personaje más ligero obtienes dos filas:

df %>%
  slice_min(mass, n = 1)
# A tibble: 2 × 4
  name  species height  mass
  <chr> <chr>    <int> <dbl>
1 R2-D2 Droid       96    32
2 R5-D4 Droid       97    32

Si necesitas exactamente n filas, indica with_ties = FALSE. En ese caso se conserva la primera fila empatada que aparece en los datos:

df %>%
  slice_min(mass, n = 1, with_ties = FALSE)
# A tibble: 1 × 4
  name  species height  mass
  <chr> <chr>    <int> <dbl>
1 R2-D2 Droid       96    32

¿Cuál deberías elegir? Mantén los empates cuando el resultado sea una clasificación que vas a leer o presentar (no sería justo dejar fuera a R5-D4). Utiliza with_ties = FALSE cuando el siguiente paso requiera un número fijo de filas, por ejemplo para unir exactamente una fila por grupo a otra tabla.

Si el empate debe romperse con una segunda variable, ordena antes los datos y luego utiliza slice_head. Por ejemplo, df %>% arrange(mass, desc(height)) %>% slice_head(n = 1) devuelve el personaje más ligero y, en caso de empate, el más alto (R5-D4).

Valores faltantes

slice_min y slice_max colocan los valores NA al final, pero no los eliminan. Si n es mayor que el número de valores no faltantes, la salida incluye filas con NA y, como todos los NA se consideran empatados, puedes obtener incluso más filas de las esperadas:

d <- tibble(id = 1:5, valor = c(3, NA, 1, 5, NA))

d %>% slice_max(valor, n = 4)
d %>% slice_max(valor, n = 4, na_rm = TRUE)
# A tibble: 5 × 2
     id valor
  <int> <dbl>
1     4     5
2     1     3
3     3     1
4     2    NA
5     5    NA

# A tibble: 3 × 2
     id valor
  <int> <dbl>
1     4     5
2     1     3
3     3     1

Con na_rm = TRUE los valores faltantes se eliminan antes de seleccionar las filas.

Las N primeras filas de cada grupo

Todas las funciones slice_* trabajan por grupo. Puedes agrupar los datos con group_by o, desde dplyr 1.1.0, utilizar el argumento by, que agrupa solo para esa operación y devuelve un data frame sin agrupar. Fíjate en que en las funciones slice_* el argumento se llama by y no .by como en mutate o summarise. El siguiente código devuelve el personaje más alto de cada especie:

df %>%
  slice_max(height, n = 1, by = species)
# A tibble: 3 × 4
  name         species height  mass
  <chr>        <chr>    <int> <dbl>
1 Darth Vader  Human      202   136
2 IG-88        Droid      200   140
3 Roos Tarpals Gungan     224    82

Los empates también se aplican dentro de cada grupo. Al pedir el personaje más ligero de cada especie obtienes cuatro filas, porque los dos droides más ligeros están empatados:

df %>%
  slice_min(mass, n = 1, by = species)
# A tibble: 4 × 4
  name          species height  mass
  <chr>         <chr>    <int> <dbl>
1 Padmé Amidala Human      185    45
2 R2-D2         Droid       96    32
3 R5-D4         Droid       97    32
4 Jar Jar Binks Gungan     196    66

Con by los grupos aparecen en el orden en el que aparecen por primera vez en los datos. Con group_by se ordenan alfabéticamente y el resultado sigue agrupado, así que recuerda llamar a ungroup después.

Primera y última fila de cada grupo

Para seleccionar la primera fila de cada grupo utiliza slice_head(n = 1) por grupo, y slice_tail(n = 1) para la última. Esto es especialmente útil cuando los datos están ordenados por fecha o por otra variable y necesitas el primer registro o el más reciente de cada sujeto:

df %>%
  slice_head(n = 1, by = species)

df %>%
  slice_tail(n = 1, by = species)
# A tibble: 3 × 4
  name           species height  mass
  <chr>          <chr>    <int> <dbl>
1 Luke Skywalker Human      172    77
2 C-3PO          Droid      167    75
3 Jar Jar Binks  Gungan     196    66

# A tibble: 3 × 4
  name            species height  mass
  <chr>           <chr>    <int> <dbl>
1 Raymus Antilles Human      188    79
2 IG-88           Droid      200   140
3 Roos Tarpals    Gungan     224    82

Con datos agrupados también puedes usar slice con posiciones, que en ese caso son relativas a cada grupo. group_by(species) %>% slice(1) devuelve la primera fila de cada especie y slice(n()) la última.

Filas aleatorias con slice_sample

slice_sample selecciona filas al azar. Igual que con las demás funciones, puedes indicar un número de filas con n o una proporción con prop. Llama a set.seed antes de muestrear si quieres obtener las mismas filas cada vez que ejecutes el código:

set.seed(123)

df %>%
  slice_sample(n = 3)
# A tibble: 3 × 4
  name         species height  mass
  <chr>        <chr>    <int> <dbl>
1 Boba Fett    Human      183  78.2
2 Qui-Gon Jinn Human      193  89
3 Palpatine    Human      170  75

Por defecto la muestra es sin reemplazo, de modo que cada fila solo puede aparecer una vez. Si n es mayor que el número de filas no obtendrás ningún error: slice_sample(n = 30) simplemente devuelve las 26 filas en orden aleatorio. Indica replace = TRUE para muestrear con reemplazo, como en un bootstrap. Ahora la misma fila puede aparecer varias veces:

set.seed(2)

df %>%
  slice_sample(n = 5, replace = TRUE)
# A tibble: 5 × 4
  name          species height  mass
  <chr>         <chr>    <int> <dbl>
1 Jar Jar Binks Gungan     196  66
2 Boba Fett     Human      183  78.2
3 Owen Lars     Human      178 120
4 Owen Lars     Human      178 120
5 R5-D4         Droid       97  32

El argumento weight_by asigna a cada fila una probabilidad de ser seleccionada proporcional a una variable. Con weight_by = mass los personajes más pesados tienen más probabilidad de aparecer en la muestra:

set.seed(1)

df %>%
  slice_sample(n = 3, weight_by = mass)
# A tibble: 3 × 4
  name             species height  mass
  <chr>            <chr>    <int> <dbl>
1 Anakin Skywalker Human      188    84
2 Roos Tarpals     Gungan     224    82
3 Lando Calrissian Human      177    79

Por último, con by obtienes una muestra estratificada, es decir, una muestra aleatoria de cada grupo. slice_sample(n = 1, by = species) devuelve un personaje al azar de cada especie. Para muestrear vectores en lugar de filas, utiliza la función sample de R base.

Con by, n es el número de filas por grupo, no el total. Si un grupo tiene menos filas que n, se devuelven todas sus filas sin ningún aviso, así que los grupos pequeños acaban con menos filas que el resto. Por ejemplo, slice_sample(n = 3, by = species) devuelve 8 filas en lugar de 9, porque solo hay dos gungans. Comprueba el tamaño de cada grupo con count() antes de muestrear.

¿Y la función top_n?

En código antiguo encontrarás top_n para obtener las filas con los valores más altos. Esta función está reemplazada por slice_min y slice_max y ya no se recomienda. Además de tener una sintaxis menos intuitiva (un n negativo devuelve los valores más bajos), no ordena el resultado:

df %>%
  top_n(2, mass)
# A tibble: 2 × 4
  name        species height  mass
  <chr>       <chr>    <int> <dbl>
1 Darth Vader Human      202   136
2 IG-88       Droid      200   140

Darth Vader aparece primero porque aparece antes en los datos, aunque IG-88 pesa más. El código moderno equivalente es slice_max(mass, n = 2), que devuelve primero a IG-88. Lo mismo ocurre con top_frac, que se sustituye por el argumento prop.

Equivalentes en R base

También puedes seleccionar filas por posición sin dplyr. Las primeras y las últimas filas se obtienen con head y tail, y las filas con los valores más altos ordenando los datos con la función order y quedándote con las n primeras:

# Tres primeras filas
head(df, 3)

# Tres personajes más pesados
df[order(df$mass, decreasing = TRUE), ][1:3, ]
# A tibble: 3 × 4
  name           species height  mass
  <chr>          <chr>    <int> <dbl>
1 Luke Skywalker Human      172    77
2 C-3PO          Droid      167    75
3 R2-D2          Droid       96    32

# A tibble: 3 × 4
  name        species height  mass
  <chr>       <chr>    <int> <dbl>
1 IG-88       Droid      200   140
2 Darth Vader Human      202   136
3 Owen Lars   Human      178   120

Ten en cuenta que en R base no se tienen en cuenta los empates: siempre obtienes exactamente tres filas. Para obtener la primera fila de cada grupo en R base puedes utilizar df[!duplicated(df$species), ], como se explica en el tutorial sobre cómo eliminar duplicados.

¿Qué función slice deberías usar?

La siguiente tabla resume las funciones de la familia slice y sus equivalentes en R base:

Función Selecciona Argumentos principales Equivalente en R base
slice() Filas por posición Posiciones positivas o negativas df[c(1, 3), ], df[-1, ]
slice_head() Primeras filas n, prop, by head()
slice_tail() Últimas filas n, prop, by tail()
slice_min() Filas con los valores más bajos order_by, n, prop, with_ties, na_rm, by df[order(x), ][1:n, ]
slice_max() Filas con los valores más altos order_by, n, prop, with_ties, na_rm, by df[order(-x), ][1:n, ]
slice_sample() Filas aleatorias n, prop, replace, weight_by, by df[sample(nrow(df), n), ]

En resumen: usa slice cuando conozcas los números de fila, slice_head y slice_tail cuando el orden de los datos tenga sentido, slice_min y slice_max para clasificaciones y para las N primeras filas de cada grupo, y slice_sample para muestras aleatorias.