Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
Introduction
The tidyverse verb slice() lets you select observations by position, returning a subset of the data based on integer indices. There are also some related convenience functions, which return observations by:
slice_min()andslice_max(): the smallest or largest values of a variable.slice_head()andslice_tail(): the first or last position(s).slice_sample(): a random subset, either by count or proportion, with optional weighted sampling.
All of these support grouped operations, applying the selection within each group.
In 2023, this family of slice_* functions was added to three core tidyomics packages, tidySummarizedExperiment (commit), tidyseurat (commit), and tidySingleCellExperiment (commit). More recently, in 2026, slice() was also introduced to plyranges (commit) for genomic ranges objects.
This post walks through how slicing works across tidyomics packages.
< section id="transcriptomics-tidyseurat-tidysce-and-tidyse" class="level1">Transcriptomics: tidyseurat, tidySCE and tidySE
All three transcriptomics packages — tidyseurat, tidySingleCellExperiment, and tidySummarizedExperiment — implement slice_* in the same way, operating on cells or samples (i.e. columns of the underlying data matrix). We use tidyseurat here as a representative example; the same code patterns apply directly to the other two packages.
tidyseurat
tidyseurat lets you work with Seurat objects using familiar tidyverse verbs. We’ll use pbmc_small, a small PBMC dataset bundled with Seurat (80 cells, 230 genes).
library(Seurat)
library(tidyseurat)
data("pbmc_small")
seurat_obj <- pbmc_small |>
mutate(cell_id = seq_along(.cell)) |>
select(-contains("ident")) |>
select(-starts_with("RNA"))
With slice() you can select cells by position, just as you would rows in a tibble. In Bioconductor packages, and in Seurat, cells are represented as columns in the counts matrix.
# First 10 cells seurat_obj |> slice(1:5)
# A Seurat-tibble abstraction: 5 × 12 # Features=230 | Cells=5 | Active assay=RNA | Assays=RNA .cell nCount_RNA nFeature_RNA groups cell_id PC_1 PC_2 PC_3 PC_4 PC_5 tSNE_1 tSNE_2 <chr> <dbl> <int> <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> 1 ATGCCAG… 70 47 g2 1 -0.774 -0.900 -0.249 0.559 0.465 0.868 -8.10 2 CATGGCC… 85 52 g1 2 -0.0260 -0.347 0.665 0.418 0.585 -7.39 -8.77 3 GAACCTG… 87 50 g2 3 -0.457 0.180 1.32 2.01 -0.482 -28.2 0.241 4 TGACTGG… 127 56 g2 4 -0.812 -1.38 -1.00 0.139 -1.60 16.3 -11.2 5 AGTCAGA… 173 53 g2 5 -0.774 -0.900 -0.249 0.559 0.465 1.91 -11.2
The slice_* helpers work as expected:
# 20 randomly sampled cells seurat_obj |> slice_sample(n = 5)
# A Seurat-tibble abstraction: 5 × 12 # Features=230 | Cells=5 | Active assay=RNA | Assays=RNA .cell nCount_RNA nFeature_RNA groups cell_id PC_1 PC_2 PC_3 PC_4 PC_5 tSNE_1 tSNE_2 <chr> <dbl> <int> <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> 1 ATGCCAG… 70 47 g2 1 -0.774 -0.900 -0.249 0.559 0.465 0.868 -8.10 2 AGATATA… 187 61 g2 34 -1.12 -2.71 0.831 -0.738 0.745 21.3 -24.1 3 GGCATAT… 126 53 g1 39 -1.08 -3.68 0.122 -0.858 0.700 24.8 -21.9 4 TACAATG… 108 44 g2 43 -0.774 -0.900 -0.249 0.559 0.465 -0.795 -10.4 5 GATAGAG… 328 72 g1 68 -0.861 2.17 1.22 -0.797 -0.0620 -0.0649 23.1
# Cells with the highest RNA count seurat_obj |> slice_max(nCount_RNA, n = 5)
# A Seurat-tibble abstraction: 5 × 12 # Features=230 | Cells=5 | Active assay=RNA | Assays=RNA .cell nCount_RNA nFeature_RNA groups cell_id PC_1 PC_2 PC_3 PC_4 PC_5 tSNE_1 tSNE_2 <chr> <dbl> <int> <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> 1 TTGAGGAC… 787 88 g1 61 -0.918 1.61 -3.21 -2.07 -1.50 -18.4 22.0 2 GACATTCT… 872 96 g1 65 -1.55 1.94 -2.52 -2.40 -0.299 -16.6 22.1 3 ACGTGATG… 709 94 g2 66 -1.71 2.71 -0.742 -2.82 -0.313 -15.1 23.5 4 ATTGTAGA… 745 84 g2 67 -1.47 1.59 -0.0495 -0.455 0.303 -4.29 22.8 5 GCGTAAAC… 754 83 g1 70 -1.00 0.851 -2.56 -1.89 -0.0714 -17.1 20.7
# Cells with the lowest RNA count seurat_obj |> slice_min(nCount_RNA, n = 5)
# A Seurat-tibble abstraction: 5 × 12 # Features=230 | Cells=5 | Active assay=RNA | Assays=RNA .cell nCount_RNA nFeature_RNA groups cell_id PC_1 PC_2 PC_3 PC_4 PC_5 tSNE_1 tSNE_2 <chr> <dbl> <int> <chr> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> 1 TGGTATCT… 64 36 g1 7 -0.460 -1.19 -0.312 0.716 -1.65 17.9 -9.90 2 GATATAAC… 52 36 g1 9 -0.774 -0.900 -0.249 0.559 0.465 1.33 -9.68 3 AGGTCATG… 62 31 g2 11 -1.19 0.246 -1.86 2.33 1.43 2.45 7.33 4 CATGAGAC… 51 26 g2 14 -1.30 0.813 -1.60 -0.111 0.334 -12.9 12.2 5 CTTCATGA… 41 32 g2 44 -0.940 -2.21 -0.163 0.0448 0.430 15.8 -25.3
Grouping applies the slice within each group. Here, the top cell by count from each sample group:
seurat_obj |>
group_by(groups) |>
slice_max(nCount_RNA, n=3) |>
select(-starts_with("PC"))
tidyseurat says: A data frame is returned for independent data analysis.
# A tibble: 6 × 7 # Groups: groups [2] .cell nCount_RNA nFeature_RNA groups cell_id tSNE_1 tSNE_2 <chr> <dbl> <int> <chr> <int> <dbl> <dbl> 1 GACATTCTCCACCT 872 96 g1 65 -16.6 22.1 2 TTGAGGACTACGCA 787 88 g1 61 -18.4 22.0 3 GCGTAAACACGGTT 754 83 g1 70 -17.1 20.7 4 ATTGTAGATTCCCG 745 84 g2 67 -4.29 22.8 5 ACGTGATGCCATGA 709 94 g2 66 -15.1 23.5 6 TGAGCTGAATGCTG 387 83 g2 52 -35.5 1.71
tidySE and tidySCE
tidySummarizedExperiment (tidySE) and tidySingleCellExperiment (tidySCE) also gained slice_* support in 2023. The functions operate on samples (columns) in the same way as tidyseurat operates on cells, so the patterns above transfer directly.
Genomics: plyranges
plyranges extends dplyr-style verbs to Bioconductor GRanges objects. We construct a small ranges object to demonstrate slicing:
library(plyranges) set.seed(123) df <- data.frame( start = 1:50 * 1e6 + 1, width = 1e4, seqnames = "chr5", strand = "*", gc = runif(50), type = factor(sample(LETTERS[1:3], 50, replace = TRUE)), rng_id = 1:50 ) rng <- as_granges(df) rng
GRanges object with 50 ranges and 3 metadata columns:
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 1000001-1010000 * | 0.287578 A 1
[2] chr5 2000001-2010000 * | 0.788305 C 2
[3] chr5 3000001-3010000 * | 0.408977 A 3
[4] chr5 4000001-4010000 * | 0.883017 C 4
[5] chr5 5000001-5010000 * | 0.940467 B 5
... ... ... ... . ... ... ...
[46] chr5 46000001-46010000 * | 0.138806 C 46
[47] chr5 47000001-47010000 * | 0.233034 C 47
[48] chr5 48000001-48010000 * | 0.465962 B 48
[49] chr5 49000001-49010000 * | 0.265973 C 49
[50] chr5 50000001-50010000 * | 0.857828 A 50
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
slice() selects ranges by index; negative indices drop ranges:
# First two ranges rng |> dplyr::slice(c(1,3,5))
GRanges object with 3 ranges and 3 metadata columns:
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 1000001-1010000 * | 0.287578 A 1
[2] chr5 3000001-3010000 * | 0.408977 A 3
[3] chr5 5000001-5010000 * | 0.940467 B 5
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
# Drop the last range rng |> dplyr::slice(-c(1:45))
GRanges object with 5 ranges and 3 metadata columns:
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 46000001-46010000 * | 0.138806 C 46
[2] chr5 47000001-47010000 * | 0.233034 C 47
[3] chr5 48000001-48010000 * | 0.465962 B 48
[4] chr5 49000001-49010000 * | 0.265973 C 49
[5] chr5 50000001-50010000 * | 0.857828 A 50
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
The slice_* helpers work on metadata columns just as they do on data frames. The sampling can be in terms of n or prop (the proportion). Additionally, it can accept weight_by and a column to be used for weighted sampling.
rng |> slice_head(n = 2)
GRanges object with 2 ranges and 3 metadata columns:
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 1000001-1010000 * | 0.287578 A 1
[2] chr5 2000001-2010000 * | 0.788305 C 2
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
rng |> slice_tail(n = 2)
GRanges object with 2 ranges and 3 metadata columns:
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 49000001-49010000 * | 0.265973 C 49
[2] chr5 50000001-50010000 * | 0.857828 A 50
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
rng |> slice_max(gc)
GRanges object with 1 range and 3 metadata columns:
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 24000001-24010000 * | 0.99427 B 24
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
rng |> slice_min(gc)
GRanges object with 1 range and 3 metadata columns:
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 35000001-35010000 * | 0.0246137 B 35
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
rng |> slice_sample(prop = 0.3)
GRanges object with 15 ranges and 3 metadata columns:
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 46000001-46010000 * | 0.1388061 C 46
[2] chr5 30000001-30010000 * | 0.1471136 C 30
[3] chr5 35000001-35010000 * | 0.0246137 B 35
[4] chr5 14000001-14010000 * | 0.5726334 A 14
[5] chr5 29000001-29010000 * | 0.2891597 C 29
... ... ... ... . ... ... ...
[11] chr5 21000001-21010000 * | 0.889539 C 21
[12] chr5 37000001-37010000 * | 0.758460 A 37
[13] chr5 8000001-8010000 * | 0.892419 A 8
[14] chr5 10000001-10010000 * | 0.456615 C 10
[15] chr5 34000001-34010000 * | 0.795467 A 34
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
Grouping by type and then slicing:
by_type <- rng |> group_by(type) # Last range for each type by_type |> slice_tail()
GRanges object with 3 ranges and 3 metadata columns:
Groups: type [3]
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 48000001-48010000 * | 0.465962 B 48
[2] chr5 49000001-49010000 * | 0.265973 C 49
[3] chr5 50000001-50010000 * | 0.857828 A 50
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
# Range with highest GC content for each type by_type |> slice_max(gc)
GRanges object with 3 ranges and 3 metadata columns:
Groups: type [3]
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 21000001-21010000 * | 0.889539 C 21
[2] chr5 24000001-24010000 * | 0.994270 B 24
[3] chr5 31000001-31010000 * | 0.963024 A 31
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
# One range from each type by_type |> slice_sample(n = 1)
GRanges object with 3 ranges and 3 metadata columns:
Groups: type [3]
seqnames ranges strand | gc type rng_id
<Rle> <IRanges> <Rle> | <numeric> <factor> <integer>
[1] chr5 18000001-18010000 * | 0.0420595 A 18
[2] chr5 21000001-21010000 * | 0.8895393 C 21
[3] chr5 44000001-44010000 * | 0.3688455 B 44
-------
seqinfo: 1 sequence from an unspecified genome; no seqlengths
Session information
info <- devtools::session_info() pkgs <- info$packages |> as_tibble() attached_pkgs <- pkgs |> filter(attached) |> select(package, version=loadedversion) # 4. Print directly to your HTML report knitr::kable(attached_pkgs, format = "html", row.names = FALSE)
| package | version |
|---|---|
| BiocGenerics | 0.58.1 |
| dplyr | 1.2.1 |
| generics | 0.1.4 |
| GenomicRanges | 1.64.0 |
| ggplot2 | 4.0.3 |
| IRanges | 2.46.0 |
| plyranges | 1.32.0 |
| S4Vectors | 0.50.1 |
| Seqinfo | 1.2.0 |
| Seurat | 5.5.1 |
| SeuratObject | 5.4.0 |
| sp | 2.2-3 |
| tidyr | 1.3.2 |
| tidyseurat | 0.8.10 |
| ttservice | 0.5.3 |
© 2025 tidyomics. Content is published under Creative Commons CC-BY-4.0 License for the text and BSD 3-Clause License for any code. | R-Bloggers
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
