rguides

dplyr::slice()

slice(.data, ...)

The dplyr slice functions provide flexible ways to select rows from a data frame or tibble based on position, random sampling, or rank. While slice() selects rows by integer positions, slice_head() and slice_tail() take the first or last rows, slice_sample() randomly samples rows, and slice_min()/slice_max() select rows with extreme values in a specified column.

These functions are designed for readability and integration with the tidyverse pipeline (%>%). They are particularly useful for data exploration, creating training/test splits, and extracting top/bottom records.

Syntax

slice(.data, ...)
slice_head(.data, n, prop)
slice_tail(.data, n, prop)
slice_sample(.data, n, prop, replace = FALSE, weight_by = NULL)
slice_min(.data, order_by, n, prop, with_ties = TRUE)
slice_max(.data, order_by, n, prop, with_ties = TRUE)

Parameters

ParameterTypeDefaultDescription
.datatibble / data.frameRequiredThe tibble or data frame to slice
...integerRequired (for slice())Row positions to select (positive integers select rows, negative integers drop rows)
nintegerOptional (for slice_head/tail/sample/min/max)Number of rows to select. Either n or prop must be supplied
propnumericOptionalProportion of rows to select (0 < prop ≤ 1). Either n or prop must be supplied
replacelogicalFALSE (for slice_sample)Sample with replacement?
weight_bynumeric vectorNULL (for slice_sample)Sampling weights; must be same length as number of rows
order_byunquoted column nameRequired (for slice_min/max)Column to rank rows by
with_tieslogicalTRUE (for slice_min/max)Keep ties? If TRUE, all rows with the same extreme value are returned

Examples

Basic slice() usage

slice() selects rows by their integer position within the data frame. You can pass multiple row numbers to pick specific rows, use ranges like 1:5 to grab a contiguous block, or use negative indices to drop rows. This is the most fundamental row-subsetting verb in dplyr:

library(dplyr)

df <- tibble(
  id = 1:10,
  value = rnorm(10)
)

# Select rows 1, 3, 5
df %>% slice(1, 3, 5)

# Select first 5 rows (equivalent to head)
df %>% slice(1:5)

# Drop rows 2 and 4 (negative indexing)
df %>% slice(-2, -4)

slice_head() and slice_tail()

These convenience functions mirror head() and tail() but work naturally inside dplyr pipelines. slice_head(n = 3) takes the first three rows, while slice_tail(prop = 0.2) takes the last 20%. Using prop instead of n abstracts the row count away, which is useful in programmatic contexts where the data size varies:

# First 3 rows
df %>% slice_head(n = 3)

# Last 20% of rows
df %>% slice_tail(prop = 0.2)

# slice_tail with negative n (drops last rows)
df %>% slice_tail(n = -5)  # drops last 5 rows, returns first (nrow - 5) rows

Random sampling with slice_sample()

slice_sample() draws rows at random, with or without replacement. The weight_by argument lets you bias the selection toward rows with larger absolute values in a particular column, which is useful for importance sampling or creating representative subsets from skewed distributions:

# Randomly select 5 rows without replacement
df %>% slice_sample(n = 5)

# Randomly select 10% of rows with replacement
df %>% slice_sample(prop = 0.1, replace = TRUE)

# Weighted sampling (e.g., probability proportional to value)
df %>% slice_sample(n = 2, weight_by = abs(value))

Selecting extreme values with slice_min() and slice_max()

These functions select rows with the smallest or largest values of a specified column. The with_ties argument controls whether rows sharing the same rank as the nth extreme value are included. Setting with_ties = FALSE guarantees exactly n rows are returned, which is important when you need a fixed-size output:

# 3 rows with smallest 'value'
df %>% slice_min(value, n = 3)

# Top 20% of rows by 'value'
df %>% slice_max(value, prop = 0.2)

# When ties exist, with_ties controls whether all ties are kept
df %>% slice_min(value, n = 1, with_ties = FALSE)  # returns exactly 1 row

Common patterns

Creating training/test splits

slice_sample() makes a clean train/test split in one line by drawing a random fraction of rows. Setting the seed beforehand ensures reproducibility. The test set is the complement, drawn without specifying n or prop since it takes the remaining rows:

set.seed(123)
train <- df %>% slice_sample(prop = 0.7)
test  <- df %>% slice_sample(prop = 0.3)

Removing the first/last rows after grouping

When data is grouped, negative indexing in slice() drops rows within each group independently. slice(-1) removes the first row of every group, which is a common pattern for discarding header rows or removing leading observations flagged as artifacts in the raw data:

df %>%
  group_by(category) %>%
  slice(-1)  # drop first row of each group

Selecting top n per group

Combining slice_max() with group_by() selects the top rows by some metric within each group. This replaces the older pattern of top_n() and is more flexible because it supports both n for an absolute count and prop for a relative fraction of each group’s rows:

df %>%
  group_by(category) %>%
  slice_max(value, n = 3)  # top 3 rows per group

Random permutation of rows

Calling slice_sample(prop = 1) without replacement shuffles every row into a random order. This is the pipe-friendly alternative to base R’s sample(nrow(df)) indexing and works correctly inside grouped pipelines where each group’s rows are permuted independently of the others:

df %>% slice_sample(prop = 1)  # reshuffle all rows (no replacement)

Sampling with stratification

When you need a fixed number of samples from each level of a categorical variable, group by that variable and call slice_sample(n = 5). Every group contributes exactly the requested number of rows, which guarantees proportional representation regardless of the original group sizes:

df %>%
  group_by(stratum) %>%
  slice_sample(n = 5)  # sample 5 rows from each stratum

Performance notes

  • slice() is very fast; it uses integer indexing.
  • slice_sample() with replacement can be slower on large datasets.
  • For grouped operations, each group is sliced independently, which can be memory-intensive with many groups.
  • When using slice_min()/slice_max() on unsorted data, dplyr performs a partial sort; consider pre-sorting if performance is critical.

dplyr::slice() in practice

slice() selects rows by integer position. Unlike filter(), which selects by condition, slice() selects by index. slice(df, 1:5) returns the first five rows. slice(df, n()) returns the last row (n() evaluates to the row count within a slice context). Negative indices drop rows: slice(df, -1) removes the first row.

The helper functions slice_head(), slice_tail(), slice_min(), slice_max(), and slice_sample() are specialized variants. slice_head(df, n = 10) returns the first 10 rows. slice_max(df, order_by = col, n = 5) returns the 5 rows with the highest values in col. All helpers accept prop as an alternative to n for proportional selection: slice_sample(df, prop = 0.1) returns a random 10% sample.

When combined with group_by(), slice() and its variants operate per group. df |> group_by(category) |> slice_max(score, n = 1) returns the top-scoring row from each category — a common pattern for leaderboards and “best per group” queries.

slice_sample() with weight_by enables weighted sampling: rows with higher values in the weight column are more likely to be selected. This is useful for oversampling rare categories or implementing importance sampling in simulation workflows.

slice() selects rows by integer position. slice_head(n = 5) and slice_tail(n = 5) replace head() and tail() in pipelines. slice_sample() replaces sample_n() / sample_frac(). When used with group_by(), slicing operates within each group independently, making it easy to select the top-N rows per category.

See also