Site icon R-bloggers

Slicing in tidyomics

[This article was first published on tidyomicsBlog, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
< section id="introduction" class="level1">

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:

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.

< section id="tidyseurat" class="level2">

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
< section id="tidyse-and-tidysce" class="level2">

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.

< section id="genomics-plyranges" class="level1">

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
< section id="session-information" class="level1">

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

To leave a comment for the author, please follow the link and comment on their blog: tidyomicsBlog.

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.
Exit mobile version