Preprocessing an HT-SELEX study

Introduction

High-throughput SELEX experiments enrich binding sequences over successive rounds, but public deposits commonly expose raw reads without a uniform record of primer layout, trimming decisions, or the relationship between rounds. selexprepR provides a reproducible preprocessing layer for this setting. It uses Biostrings sequence containers, returns a sparse SummarizedExperiment, and stores its inference, extraction, quality-control, and provenance records alongside the assay.

The package complements general FASTQ infrastructure in Bioconductor. Its domain-specific contribution is conservative primer inference and a manifest that makes the preprocessing decision inspectable rather than implicit. The existing SELEX package provides a complementary analysis workflow based on its own sample annotation model. selexprepR focuses on raw public-read acquisition, primer inference, and a SummarizedExperiment output that can be used by downstream Bioconductor tooling.

Installation

if (!requireNamespace("BiocManager", quietly = TRUE)) {
    install.packages("BiocManager")
}
BiocManager::install("selexprepR")

A complete in-memory workflow

For a self-contained example, create three synthetic rounds with fixed 5-prime and 3-prime constant regions. The variable regions below are distinct so the count matrix has a realistic multi-sequence shape.

library(selexprepR)

primer_5p <- "GGTAATACGACTCACTATAGGG"
primer_3p <- "CCATGCATGCATGCATGCAT"
bases <- c("A", "C", "G", "T")

make_round <- function(round, n = 500L, width = 20L) {
    variable_regions <- vapply(seq.int(0L, n - 1L), function(index) {
        paste0(bases[(index %/% 4L^(0L:(width - 1L))) %% 4L + 1L], collapse = "")
    }, character(1))
    paste0(primer_5p, variable_regions, primer_3p)
}

reads_by_round <- stats::setNames(lapply(0:2, make_round), sprintf("round_%02d", 0:2))
experiment <- run_selexprep(reads_by_round, low_total_reads = 0)
experiment
#> class: SummarizedExperiment 
#> dim: 500 3 
#> metadata(7): accession schema_version ... manifest counting_skipped
#> assays(1): counts
#> rownames(500): AAAAA AAAAC ... TTTGC TTTTA
#> rowData names(1): sequence
#> colnames(3): round_00 round_01 round_02
#> colData names(1): round

The counts assay has one column per SELEX round and one row per recovered variable sequence. It is sparse, so conventional SELEX datasets with many unobserved sequence-round combinations remain compact.

counts <- SummarizedExperiment::assay(experiment, "counts")
Matrix::colSums(counts)
#> round_00 round_01 round_02 
#>      500      500      500
head(SummarizedExperiment::rowData(experiment))
#> DataFrame with 6 rows and 1 column
#>           sequence
#>       <BStringSet>
#> AAAAA        AAAAA
#> AAAAC        AAAAC
#> AAACA        AAACA
#> AAACC        AAACC
#> AAAGA        AAAGA
#> AAAGC        AAAGC

The library report records the inferred constant regions and the decision about whether full inserts were recovered. The QC object summarizes every round and lists explicit flags instead of silently discarding a concern.

metadata <- S4Vectors::metadata(experiment)
metadata$library_report[c("primer_5p", "primer_3p", "extraction_mode", "confidence")]
#> $primer_5p
#> [1] "GGTAATACGACTCACTATAGGG"
#> 
#> $primer_3p
#> [1] "AAAAAAAAAAAAAAACCATGCATGCATGCATGCAT"
#> 
#> $extraction_mode
#> [1] "BOTH_PRIMERS_SINGLE_READ"
#> 
#> $confidence
#> [1] 1
metadata$qc$per_round
#> DataFrame with 3 rows and 14 columns
#>         round   n_reads  n_unique shannon_entropy_bits rarefied_unique
#>   <character> <numeric> <integer>            <numeric>       <integer>
#> 1    round_00       500       500              8.96578             500
#> 2    round_01       500       500              8.96578             500
#> 3    round_02       500       500              8.96578             500
#>   singleton_fraction top_1_coverage top_100_coverage modal_sequence_length
#>            <numeric>      <numeric>        <numeric>             <numeric>
#> 1                  1          0.002              0.2                     5
#> 2                  1          0.002              0.2                     5
#> 3                  1          0.002              0.2                     5
#>     mean_gc     gc_sd nonstandard_alphabet_fraction truseq_reads_fraction
#>   <numeric> <numeric>                     <numeric>             <numeric>
#> 1    0.5016  0.225028                             0                     0
#> 2    0.5016  0.225028                             0                     0
#> 3    0.5016  0.225028                             0                     0
#>   top_kmer_fraction
#>           <numeric>
#> 1                 0
#> 2                 0
#> 3                 0
metadata$qc$flags
#> DataFrame with 0 rows and 3 columns
metadata$manifest$manifest_version
#> [1] "selexprep_manifest_v2"

Reading FASTQ and public metadata

For local FASTQ files, the file bridge assembles rounds and retains file sizes, MD5 values, and SHA-256 hashes in the experiment metadata and manifest.

experiment <- run_selexprep_files(c(
    round_00 = "round_00.fastq.gz",
    round_01 = "round_01.fastq.gz"
))

When several rounds share one inline-barcoded R1 stream, provide the barcode map explicitly. Barcodes are checked for sufficient pairwise Hamming distance; the package does not infer them.

inputs <- selexprep_demultiplex(
    read_selexprep_fastq("multiplexed.fastq.gz"),
    c(AAAAA = 0L, TTTTT = 1L)
)
experiment <- run_selexprep(inputs)

The catalog is distributed as a package dataset and can be queried without a network connection. Its metadata records the exact snapshot and immutable source locations.

data("selexprep_public_catalog", package = "selexprepR")
selexprep_catalog("FGF-9")
#> DataFrame with 2 rows and 19 columns
#>   bioproject_id      source            study_title        study_type
#>     <character> <character>            <character>       <character>
#> 1    PRJDB19098         ena HT-SELEX against reo.. aptamer_selection
#> 2    PRJDB19138         ena HT-SELEX against reo.. aptamer_selection
#>   study_type_curation                 target target_curation target_class
#>           <character>            <character>     <character>  <character>
#> 1          concordant recombinant human FG..      concordant           NA
#> 2          concordant recombinant human FG..      concordant           NA
#>   target_class_curation   chemistry chemistry_curation    n_random
#>             <character> <character>        <character> <character>
#> 1            not_stated         RNA         concordant          NA
#> 2            not_stated         RNA         concordant      30-mer
#>   n_random_curation    n_rounds n_rounds_curation selection_format
#>         <character> <character>       <character>      <character>
#> 1        not_stated  six rounds        concordant         HT-SELEX
#> 2        concordant  six rounds        concordant         HT-SELEX
#>   selection_format_curation counter_selection counter_selection_curation
#>                 <character>       <character>                <character>
#> 1                concordant                NA                 not_stated
#> 2                concordant                NA                 not_stated
S4Vectors::metadata(selexprep_public_catalog)$snapshot_version
#> [1] "v0.2.1-dual-extraction-2026-07-03"

ENA inspection and download are separate, so the planned files and checksums can be reviewed before any data transfer.

inspection <- inspect_selexprep_accession("PRJDB19098")
fetch_selexprep_reads(inspection, "raw/PRJDB19098")

References

Tuerk C, Gold L (1990). Systematic evolution of ligands by exponential enrichment: RNA ligands to bacteriophage T4 DNA polymerase. Science, 249(4968), 505-510. doi:10.1126/science.2200121.

Session information

sessionInfo()
#> R version 4.6.1 (2026-06-24)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.32.so;  LAPACK version 3.12.0
#> 
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] selexprepR_0.99.3 BiocStyle_2.41.0 
#> 
#> loaded via a namespace (and not attached):
#>  [1] Matrix_1.7-5                jsonlite_2.0.0             
#>  [3] compiler_4.6.1              BiocManager_1.30.27        
#>  [5] crayon_1.5.3                SummarizedExperiment_1.43.0
#>  [7] Biobase_2.73.1              GenomicRanges_1.65.1       
#>  [9] Biostrings_2.81.3           jquerylib_0.1.4            
#> [11] IRanges_2.47.2              Seqinfo_1.3.0              
#> [13] yaml_2.3.12                 fastmap_1.2.0              
#> [15] lattice_0.22-9              R6_2.6.1                   
#> [17] XVector_0.53.0              S4Arrays_1.13.0            
#> [19] generics_0.1.4              knitr_1.51                 
#> [21] BiocGenerics_0.59.10        DelayedArray_0.39.3        
#> [23] MatrixGenerics_1.25.0       maketools_1.3.2            
#> [25] bslib_0.11.0                rlang_1.3.0                
#> [27] cachem_1.1.0                xfun_0.60                  
#> [29] sass_0.4.10                 sys_3.4.3                  
#> [31] otel_0.2.0                  SparseArray_1.13.2         
#> [33] cli_3.6.6                   withr_3.0.3                
#> [35] digest_0.6.39               grid_4.6.1                 
#> [37] lifecycle_1.0.5             S4Vectors_0.51.5           
#> [39] evaluate_1.0.5              buildtools_1.0.0           
#> [41] abind_1.4-8                 stats4_4.6.1               
#> [43] rmarkdown_2.31              matrixStats_1.5.0          
#> [45] tools_4.6.1                 htmltools_0.5.9