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.
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): roundThe 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 AAAGCThe 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"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.
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.
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