# EpiTxDb: Storing and accessing epitranscriptomic information using the AnnotationDbi interface

Felix G.M. Ernst

#### 2025-07-23

#### Package

EpiTxDb 1.21.1

# 1 Installation

    if (!requireNamespace("BiocManager", quietly = TRUE))
        install.packages("BiocManager")

    BiocManager::install(c("EpiTxDb","EpiTxDb.Hs.hg38"))

# 2 Introduction

The epitranscriptome includes all post-transcriptional modifications of the RNA and describes and additional layer of information encoded on RNA. Like the term epigenome it is not about a change in nucleotide sequences, but the addition of functional elements through modifications.

With the development of high throughput detection strategies for specific RNA modifications, such as miCLIP and Pseudo-Seq amongst other, a large number of modified positions have been identified and were summarized via the RMBase project (Xuan et al. 2017; Sun et al. 2015) project.

To make these information avaialble within the Bioconductor universe `EpiTxDb` was developed, which facilitates the storage of epitranscriptomic information. More specifically, it can keep track of modification identity, position, the enzyme for introducing it on the RNA, a specifier which determines the position on the RNA to be modified and the literature references each modification is associated with.

# 3 Getting started

    library(EpiTxDb)
    library(EpiTxDb.Hs.hg38)

The class `EpiTxDb` is the class for storing the epitranscriptomic data. It inherits the inner workings of `AnnotationDb` class from the `AnnotationDbi` package.

As an example for the vignette the snoRNAdb data (Lestrade and Weber 2006) from the `EpiTxDb.Hs.hg38` package will be used. The data is stored in the `AnnotationHub` and is downloaded and cached upon the first request.

    etdb <- EpiTxDb.Hs.hg38.snoRNAdb()

    ## loading from cache

    etdb

    ## EpiTxDb object:
    ## # Db type: EpiTxDb
    ## # Supporting package: EpiTxDb
    ## # Data source: snoRNAdb
    ## # Organism: Homo sapiens
    ## # Genome: hg38
    ## # Coordinates: per Transcript
    ## # Nb of modifications: 235
    ## # Db created by: EpiTxDb package from Bioconductor
    ## # Creation time: 2020-02-26 10:34:30 +0100 (Wed, 26 Feb 2020)
    ## # EpiTxDb version at creation time: 0.99.0
    ## # RSQLite version at creation time: 2.2.0
    ## # DBSCHEMAVERSION: 1.0

As expected for an `AnnotationDb` class the general accessors are available.

    keytypes(etdb)

    ##  [1] "MODID"            "MODNAME"          "MODSTRAND"        "MODTYPE"
    ##  [5] "REF"              "REFTYPE"          "RXENSEMBL"        "RXENSEMBLTRANS"
    ##  [9] "RXENTREZID"       "RXGENENAME"       "SNID"             "SNNAME"
    ## [13] "SPECENSEMBL"      "SPECENSEMBLTRANS" "SPECENTREZID"     "SPECGENENAME"
    ## [17] "SPECTYPE"

    columns(etdb)

    ##  [1] "MODEND"           "MODID"            "MODNAME"          "MODSTART"
    ##  [5] "MODSTRAND"        "MODTYPE"          "REF"              "REFID"
    ##  [9] "REFTYPE"          "RXENSEMBL"        "RXENSEMBLTRANS"   "RXENTREZID"
    ## [13] "RXGENENAME"       "RXID"             "RXRANK"           "SNID"
    ## [17] "SNNAME"           "SPECENSEMBL"      "SPECENSEMBLTRANS" "SPECENTREZID"
    ## [21] "SPECGENENAME"     "SPECID"           "SPECTYPE"

    head(keys(etdb, "MODID"))

    ## [1] "1" "2" "3" "4" "5" "6"

    select(etdb, keys = "1",
           columns = c("MODNAME","MODTYPE","MODSTART","MODSTRAND","SNNAME",
                       "RXGENENAME","SPECTYPE","SPECGENENAME"),
           keytype = "MODID")

    ## 'select()' returned 1:1 mapping between keys and columns

The columns with the prefix `RX` or `SPEC` reference the reaction enzyme and the location specifier. This can be the same information, but for ribosomal modifications from the snoRNAdb it is of course fibrillarin and a snoRNA.

In addition the following accessor for metadata are available as well.

    species(etdb)

    ## [1] "Homo sapiens"

    organism(etdb)

    ## [1] "Homo sapiens"

    seqlevels(etdb)

    ## [1] "NR_003285" "NR_003286" "NR_003287" "NR_004430" "NR_002716" "NR_003925"
    ## [7] "NR_002756" "NR_004394" "NR_029422"

# 4 Accessing RNA modifications

The specialized accessors are `modifications()` and `modificationsBy()`. `modifications()` allows for filtering results, whereas `modificationsBy()` returns all the modifications in batches separated by certain information.

    modifications(etdb, columns = c("mod_id","mod_type","mod_name",
                                    "rx_genename","spec_genename",
                                    "ref_type","ref"),
                  filter = list(mod_id = 1:3))

    ## GRanges object with 3 ranges and 7 metadata columns:
    ##        seqnames    ranges strand |    mod_id    mod_type    mod_name
    ##           <Rle> <IRanges>  <Rle> | <integer> <character> <character>
    ##   [1] NR_003285        14      + |         1          Um       Um_14
    ##   [2] NR_003285        55      + |         2           Y        Y_55
    ##   [3] NR_003285        69      + |         3           Y        Y_69
    ##                  rx_genename   spec_genename        ref_type             ref
    ##              <CharacterList> <CharacterList> <CharacterList> <CharacterList>
    ##   [1]            fibrillarin         SNORD71            PMID        16381836
    ##   [2] dyskerin pseudouridi..         SNORA72            PMID        16381836
    ##   [3] dyskerin pseudouridi..         SNORA69            PMID        16381836
    ##   -------
    ##   seqinfo: 9 sequences from hg38 genome; no seqlengths

    # split by sequence name, usually a transcipt identifier
    modificationsBy(etdb, by = "seqnames")

    ## GRangesList object of length 9:
    ## $NR_003285
    ## GRanges object with 4 ranges and 3 metadata columns:
    ##        seqnames    ranges strand |    mod_id         mod    mod_name
    ##           <Rle> <IRanges>  <Rle> | <integer> <character> <character>
    ##   [1] NR_003285        14      + |         1          Um       Um_14
    ##   [2] NR_003285        55      + |         2           Y        Y_55
    ##   [3] NR_003285        69      + |         3           Y        Y_69
    ##   [4] NR_003285        75      + |         4          Gm       Gm_75
    ##   -------
    ##   seqinfo: 9 sequences from hg38 genome; no seqlengths
    ##
    ## ...
    ## <8 more elements>

    # split modification type
    modificationsBy(etdb, by = "modtype")

    ## GRangesList object of length 5:
    ## $Am
    ## GRanges object with 39 ranges and 3 metadata columns:
    ##         seqnames    ranges strand |         mod    mod_id    mod_name
    ##            <Rle> <IRanges>  <Rle> | <character> <integer> <character>
    ##    [1] NR_003286        27      + |          Am         5       Am_27
    ##    [2] NR_003286        99      + |          Am         9       Am_99
    ##    [3] NR_003286       159      + |          Am        15      Am_159
    ##    [4] NR_003286       166      + |          Am        16      Am_166
    ##    [5] NR_003286       468      + |          Am        25      Am_468
    ##    ...       ...       ...    ... .         ...       ...         ...
    ##   [35] NR_002716        30      + |          Am       207       Am_30
    ##   [36] NR_003925        65      + |          Am       216       Am_65
    ##   [37] NR_004394        47      + |          Am       226       Am_47
    ##   [38] NR_004394        53      + |          Am       227       Am_53
    ##   [39] NR_004394        53      + |          Am       228       Am_53
    ##   -------
    ##   seqinfo: 9 sequences from hg38 genome; no seqlengths
    ##
    ## ...
    ## <4 more elements>

# 5 Shifting coordinates from genomic to transcriptomic

Since epitranscriptomic modifications by their nature can have different meaning for each of the individual transcript variants. This also introduces conflicts for saving epitranscriptomics coordinates. In the example above the coordinates are given per transcript, because of the source data.

However, not all sources report transcript coordinates. It might be of interest to shift the coordinates to transcript coordinates and at the same time taking care that with transcript variants multiple options exist for each of the transcript maturation process: From one genomic coordinate, multiple transcriptomic coordinates can be spawned.

Whether this is biologically relevant or whether biological evidence does exist for each modification on each transcript cannot be guaranteed or differentiated technically depending on the methods used. This might change with the arrival of new techniques allowing for detection of modified nucleotides per individual transcript variant.

    library(TxDb.Hsapiens.UCSC.hg38.knownGene)
    library(BSgenome.Hsapiens.UCSC.hg38)

    txdb <- TxDb.Hsapiens.UCSC.hg38.knownGene
    seqlevels(txdb) <- "chr1"
    bs <- BSgenome.Hsapiens.UCSC.hg38

    etdb <- EpiTxDb.Hs.hg38.RMBase()

    ## loading from cache

    tx <- exonsBy(txdb)
    mod <- modifications(etdb, filter = list(sn_name = "chr1"))
    length(mod)

    ## [1] 47275

In the following example we will focus on shifting the coordinates to individual mature transcripts. However, keep in mind, that premature transcript might be of interest as well and this can be controlled via the `tx` arguments of `shiftGenomicToTranscript()`

    mod_tx <- shiftGenomicToTranscript(mod, tx)

    ## Warning: Coordinates for 898 ranges of 'subject' not found:
    ## 'chr1:139005:-','chr1:139020:-','chr1:139040:-','chr1:139127:-','chr1:629454:+','chr1:629456:+','chr1:629465:+','chr1:629595:+','chr1:629602:+','chr1:629617:+'
    ## and more ...

    length(mod_tx)

    ## [1] 214766

Due to multiple matches for each transcript variant the number of modifications has increased.

With the we can plot the relative positions of modifications by type on `chr1` transcripts.

    mod_tx <- split(mod_tx,seqnames(mod_tx))
    names <- Reduce(intersect,list(names(mod_tx),names(tx)))

    # Getting the corresponding 5'-UTR and 3'-UTR annotations
    fp <- fiveUTRsByTranscript(txdb)
    tp <- threeUTRsByTranscript(txdb)
    tx <- tx[names]
    mod_tx <- mod_tx[names]
    fp_m <- match(names,names(fp))
    fp_m <- fp_m[!is.na(fp_m)]
    tp_m <- match(names,names(tp))
    tp_m <- tp_m[!is.na(tp_m)]
    fp <- fp[fp_m]
    tp <- tp[tp_m]

    # Getting lengths of transcripts, 5'-UTR and 3'-UTR
    tx_lengths <- sum(width(tx))
    fp_lengths <- rep(0L,length(tx))
    names(fp_lengths) <- names
    fp_lengths[names(fp)] <- sum(width(fp))
    tp_lengths <- rep(0L,length(tx))
    names(tp_lengths) <- names
    tp_lengths[names(tp)] <- sum(width(tp))

    # Rescale modifications
    # CDS start is at position 1L and cds end at position 1000L
    from <- IRanges(fp_lengths+1L, tx_lengths - tp_lengths)
    to <- IRanges(1L,1000L)
    mod_rescale <- rescale(mod_tx, to, from)

    # Construct result data.frame
    rel_pos <- data.frame(mod_type = unlist(mcols(mod_rescale,level="within")[,"mod_type"]),
                          rel_pos = unlist(start(mod_rescale)))
    rel_pos <- rel_pos[rel_pos$rel_pos < 1500 & rel_pos$rel_pos > -500,]

    library(ggplot2)
    ggplot(rel_pos[rel_pos$mod_type %in% c("m6A","m1A","Y"),],
           aes(x = rel_pos, colour = mod_type)) +
      geom_density()

# 6 Session info

    sessionInfo()

    ## R version 4.5.1 (2025-06-13 ucrt)
    ## Platform: x86_64-w64-mingw32/x64
    ## Running under: Windows Server 2022 x64 (build 20348)
    ##
    ## Matrix products: default
    ##   LAPACK version 3.12.1
    ##
    ## locale:
    ## [1] LC_COLLATE=C
    ## [2] LC_CTYPE=English_United States.utf8
    ## [3] LC_MONETARY=English_United States.utf8
    ## [4] LC_NUMERIC=C
    ## [5] LC_TIME=English_United States.utf8
    ##
    ## time zone: America/New_York
    ## tzcode source: internal
    ##
    ## attached base packages:
    ## [1] stats4    stats     graphics  grDevices utils     datasets  methods
    ## [8] base
    ##
    ## other attached packages:
    ##  [1] ggplot2_3.5.2
    ##  [2] BSgenome.Hsapiens.UCSC.hg38_1.4.5
    ##  [3] BSgenome_1.77.1
    ##  [4] rtracklayer_1.69.1
    ##  [5] BiocIO_1.19.0
    ##  [6] GenomeInfoDb_1.45.9
    ##  [7] TxDb.Hsapiens.UCSC.hg38.knownGene_3.21.0
    ##  [8] GenomicFeatures_1.61.5
    ##  [9] EpiTxDb.Hs.hg38_0.99.7
    ## [10] AnnotationHub_3.99.6
    ## [11] BiocFileCache_2.99.5
    ## [12] dbplyr_2.5.0
    ## [13] EpiTxDb_1.21.1
    ## [14] Modstrings_1.25.0
    ## [15] Biostrings_2.77.2
    ## [16] XVector_0.49.0
    ## [17] AnnotationDbi_1.71.0
    ## [18] Biobase_2.69.0
    ## [19] GenomicRanges_1.61.1
    ## [20] Seqinfo_0.99.2
    ## [21] IRanges_2.43.0
    ## [22] S4Vectors_0.47.0
    ## [23] BiocGenerics_0.55.0
    ## [24] generics_0.1.4
    ## [25] BiocStyle_2.37.0
    ##
    ## loaded via a namespace (and not attached):
    ##  [1] DBI_1.2.3                   bitops_1.0-9
    ##  [3] httr2_1.2.1                 tRNAdbImport_1.27.0
    ##  [5] biomaRt_2.65.0              rlang_1.1.6
    ##  [7] magrittr_2.0.3              matrixStats_1.5.0
    ##  [9] compiler_4.5.1              RSQLite_2.4.2
    ## [11] png_0.1-8                   vctrs_0.6.5
    ## [13] txdbmaker_1.5.6             stringr_1.5.1
    ## [15] pkgconfig_2.0.3             crayon_1.5.3
    ## [17] fastmap_1.2.0               magick_2.8.7
    ## [19] labeling_0.4.3              Rsamtools_2.25.1
    ## [21] rmarkdown_2.29              UCSC.utils_1.5.0
    ## [23] tinytex_0.57                purrr_1.1.0
    ## [25] bit_4.6.0                   xfun_0.52
    ## [27] cachem_1.1.0                Structstrings_1.25.0
    ## [29] jsonlite_2.0.0              progress_1.2.3
    ## [31] blob_1.2.4                  DelayedArray_0.35.2
    ## [33] BiocParallel_1.43.4         parallel_4.5.1
    ## [35] prettyunits_1.2.0           R6_2.6.1
    ## [37] bslib_0.9.0                 stringi_1.8.7
    ## [39] RColorBrewer_1.1-3          jquerylib_0.1.4
    ## [41] Rcpp_1.1.0                  bookdown_0.43
    ## [43] SummarizedExperiment_1.39.1 knitr_1.50
    ## [45] Matrix_1.7-3                tidyselect_1.2.1
    ## [47] dichromat_2.0-0.1           abind_1.4-8
    ## [49] yaml_2.3.10                 codetools_0.2-20
    ## [51] curl_6.4.0                  tRNA_1.27.0
    ## [53] lattice_0.22-7              tibble_3.3.0
    ## [55] withr_3.0.2                 KEGGREST_1.49.1
    ## [57] evaluate_1.0.4              xml2_1.3.8
    ## [59] pillar_1.11.0               BiocManager_1.30.26
    ## [61] filelock_1.0.3              MatrixGenerics_1.21.0
    ## [63] rex_1.2.1                   RCurl_1.98-1.17
    ## [65] BiocVersion_3.22.0          hms_1.1.3
    ## [67] scales_1.4.0                glue_1.8.0
    ## [69] tools_4.5.1                 GenomicAlignments_1.45.1
    ## [71] XML_3.99-0.18               grid_4.5.1
    ## [73] restfulr_0.0.16             cli_3.6.5
    ## [75] rappdirs_0.3.3              S4Arrays_1.9.1
    ## [77] dplyr_1.1.4                 gtable_0.3.6
    ## [79] sass_0.4.10                 digest_0.6.37
    ## [81] SparseArray_1.9.1           rjson_0.2.23
    ## [83] farver_2.1.2                memoise_2.0.1
    ## [85] htmltools_0.5.8.1           lifecycle_1.0.4
    ## [87] httr_1.4.7                  bit64_4.6.0-1

# References

Lestrade, Laurent, and Michel J. Weber. 2006. “snoRNA-LBME-db, a comprehensive database of human H/ACA and C/D box snoRNAs.” Nucleic Acids Research 34 (January): D158–D162. https://doi.org/10.1093/nar/gkj002.

Sun, Wen-Ju, Jun-Hao Li, Shun Liu, Jie Wu, Hui Zhou, Liang-Hu Qu, and Jian-Hua Yang. 2015. “RMBase: a resource for decoding the landscape of RNA modifications from high-throughput sequencing data.” Nucleic Acids Research 44 (D1): D259–D265. https://doi.org/10.1093/nar/gkv1036.

Xuan, Jia-Jia, Wen-Ju Sun, Peng-Hui Lin, Ke-Ren Zhou, Shun Liu, Ling-Ling Zheng, Liang-Hu Qu, and Jian-Hua Yang. 2017. “RMBase v2.0: deciphering the map of RNA modifications from epitranscriptome sequencing data.” Nucleic Acids Research 46 (D1): D327–D334. https://doi.org/10.1093/nar/gkx934.