dplyr slice_min() in R: Bottom N Rows by Column Value
The slice_min() function in dplyr returns the rows with the SMALLEST values of a specified column, optionally per group. It is the mirror of slice_max().
slice_min(df, mpg, n = 5) # 5 lowest by mpg slice_min(df, mpg, prop = 0.1) # bottom 10% slice_min(df, mpg, n = 3, by = cyl) # bottom 3 per cyl slice_min(df, mpg, n = 3, with_ties = FALSE)# strict 3 (no ties) slice_min(df, mpg, n = -3) # all but bottom 3 arrange(df, mpg) |> slice_head(n = 5) # equivalent older form
Need explanation? Read on for examples and pitfalls.
What slice_min() does in one sentence
slice_min(.data, order_by, n) sorts by order_by ascending and returns the bottom n rows. On a grouped tibble (or with by = g), it returns the bottom n per group.
This is the cleanest way to answer "bottom N by metric" questions, and the natural mirror of slice_max().
Syntax
slice_min(.data, order_by, n = NULL, prop = NULL, by = NULL, with_ties = TRUE, na_rm = FALSE).
slice_min() is more explicit than arrange(x) |> head(n). It says exactly what it does: "bottom n by x". Reach for it whenever you mean "lowest N by metric".Five common patterns
1. Bottom n by a column
By default, ties are included.
2. Bottom n per group
by = cyl returns one row per cyl group.
3. Bottom fraction (prop)
10% of 32 = 3 rows (or more with ties).
4. Strict bottom n (no ties)
with_ties = FALSE returns exactly n rows; ties broken arbitrarily.
5. Earliest record per group
The mirror of "latest per group": earliest event per user via slice_min on timestamp.
slice_min() and slice_max() are exact mirrors. Anything you can do with one, you can do with the other by reversing the column sign or swapping the function. They share argument names, defaults, and group behavior.slice_min() vs slice_max() vs arrange + slice_head vs base which.min
Four ways to grab "bottom n" in R, with different ergonomics.
| Function | Sorts | Per group | Output | |
|---|---|---|---|---|
slice_min(col, n) |
Yes (asc) | Yes | n rows | |
slice_max(col, n) |
Yes (desc) | Yes | n rows | |
| `arrange(col) | > slice_head(n)` | Yes | Yes if grouped | n rows, sorted |
which.min(col) |
No | No | Single index |
When to use which:
slice_minfor bottom-n-by-metric in dplyr.slice_maxfor top-n-by-metric.arrange + slice_headif you need the sorted intermediate state.which.minfor the single index of the minimum (base R, returns 1 element).
A practical slice_min workflow
Use slice_min for "worst N", "earliest N", or "cheapest N" patterns.
Common variations:
- Worst 1 per group →
slice_min(metric, n = 1, by = g) - Earliest record per user →
slice_min(timestamp, n = 1, by = user) - Cheapest item per category →
slice_min(price, n = 1, by = category) - 10 worst-rated movies →
slice_min(rating, n = 10)
The pattern parallels slice_max: pick the column, pick n, pick the group.
Common pitfalls
Pitfall 1: ties expand the result. slice_min(mpg, n = 3) returns 4 rows if two share rank 3. Use with_ties = FALSE for strict n.
Pitfall 2: NAs and ascending order. With na_rm = FALSE (default), NAs sort last in ASC order, so they don't appear in slice_min output unless n is very large. Set na_rm = TRUE to be explicit.
slice_min() returns rows by ASCENDING column value; the original row order is NOT preserved. Output may appear in different order than input. Chain with arrange(col) if you want sorted output explicitly.Try it yourself
Try it: Find the 2 cars with the lowest qsec (quarter-mile time) for each cyl group. Save to ex_fast.
Click to reveal solution
Explanation: slice_min(qsec, n = 2, by = cyl) finds the 2 lowest qsec per cylinder group. Lower qsec = faster acceleration.
Related slice functions
After mastering slice_min, look at:
slice_max(): top n by column (mirror)slice_head()/slice_tail(): first/last n by row orderslice_sample(): random n rowsslice(): specific row indexesarrange(): sort the entire framewhich.min()/which.max(): base R, returns single index
For symmetric "top + bottom" reports, combine slice_max and slice_min results with bind_rows.
FAQ
What is the difference between slice_min and slice_max?
slice_min returns the LOWEST n rows by column value; slice_max returns the HIGHEST. Argument names and behavior are otherwise identical mirrors.
How does slice_min handle ties?
Default with_ties = TRUE includes all tied rows (may return more than n). Set with_ties = FALSE for exactly n rows, with ties broken arbitrarily.
How do I get the bottom n per group?
Pass by = group_col (dplyr 1.1+): slice_min(df, col, n = 3, by = g). Or df |> group_by(g) |> slice_min(col, n = 3) |> ungroup().
How does slice_min handle NA values?
With na_rm = FALSE (default), NAs sort to the END of ascending order, so they appear in slice_min output only if n is large enough. Set na_rm = TRUE to filter them.
What is the difference between slice_min and which.min?
slice_min is dplyr; returns N rows of a data frame. which.min is base R; returns a SINGLE INTEGER index of the row with the minimum value. Different output shapes for different use cases.