# Annotating Genomic Variants

Valerie Obenchain1 and Martin Morgan

1Fred Hutchinson Cancer Research Center, 1100 Fairview Ave. N., P.O. Box 19024, Seattle, WA, USA 98109-1024

#### 5 Sept 2017

# Contents

# 1 Version Info

R version: R version 4.6.0 RC (2026-04-17 r89917)
Bioconductor version: 3.24
Package version: 1.37.0
Created: 5 Sept 2017
Revised: 7 January 2021

# 2 Background

The VariantAnnotation package has facilities for reading in all or subsets of Variant Call Format (VCF) files. These text files contain meta-information lines, a header line and data lines each containing information about a position in the genome. The format also may also contain genotype information on samples for each position. More on the file format can be found in the VCF specs.

The ‘locateVariants’ function in the VariantAnnotation package identifies where a variant is located with respect to the gene model (e.g., exon, intron, splice site, etc.). The ‘predictCoding’ function reports the amino acid change for non-synonymous coding variants. Consequences of the coding changes can be investigated with the SIFT and PolyPhen database packages. We’ll use these functions to learn about variants located on the TRPV gene on chromosome

# 3 Set Up

This workflow requires several different Bioconductor packages. Usage of each will be described in detail in the following sections.

    library(VariantAnnotation)
    library(org.Hs.eg.db)
    library(TxDb.Hsapiens.UCSC.hg19.knownGene)
    library(BSgenome.Hsapiens.UCSC.hg19)
    library(PolyPhen.Hsapiens.dbSNP131)

Use BiocManager::install() to get the packages you don’t have installed:

    if (!"BiocManager" %in% rownames(installed.packages()))
         install.packages("BiocManager")
    BiocManager::install("mypackage")

# 4 Exploring variants in the TRPV gene family

This workflow focuses on variants located in the Transient Receptor Potential Vanilloid (TRPV) gene family on chromosome 17. We use a VCF file included in the “variants” package and representing “Complete Genomics Diversity” panel data for chromosome 17 on 46 individuals for a single individual from the CEU population.

    file <- system.file("vcf", "NA06985_17.vcf.gz", package = "variants")

## 4.1 Examine header data in a vcf file

To get an idea of what data are in the file we look at the header. scanVcfHeader() parses the file header into a VCFHeader object and the info() and geno() accessors extract field-specific data.

    hdr <- scanVcfHeader(file)

    info(hdr)

    ## DataFrame with 3 rows and 3 columns
    ##         Number        Type            Description
    ##    <character> <character>            <character>
    ## NS           1     Integer Number of Samples Wi..
    ## DP           1     Integer            Total Depth
    ## DB           0        Flag dbSNP membership, bu..

    geno(hdr)

    ## DataFrame with 12 rows and 3 columns
    ##             Number        Type            Description
    ##        <character> <character>            <character>
    ## GT               1      String               Genotype
    ## GQ               1     Integer       Genotype Quality
    ## DP               1     Integer             Read Depth
    ## HDP              2     Integer   Haplotype Read Depth
    ## HQ               2     Integer      Haplotype Quality
    ## ...            ...         ...                    ...
    ## mRNA             .      String        Overlaping mRNA
    ## rmsk             .      String     Overlaping Repeats
    ## segDup           .      String Overlaping segmentat..
    ## rCov             1       Float      relative Coverage
    ## cPd              1      String   called Ploidy(level)

Variants in the VCF have been aligned to NCBI genome build GRCh37:

    meta(hdr)

    ## DataFrameList of length 6
    ## names(6): fileDate fileformat phasing reference source contig

[ Back to top ]

## 4.2 Convert gene symbols to gene ids

Use the org.Hs.eg.db package to convert gene symbols to gene ids.

    ## get entrez ids from gene symbols
    genesym <- c("TRPV1", "TRPV2", "TRPV3")
    geneid <- select(
        org.Hs.eg.db, keys=genesym, keytype="SYMBOL", columns="ENTREZID"
    )

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

    geneid

    ##   SYMBOL ENTREZID
    ## 1  TRPV1     7442
    ## 2  TRPV2    51393
    ## 3  TRPV3   162514

[ Back to top ]

## 4.3 Create gene ranges

We use the hg19 known gene track from UCSC to identify the TRPV gene ranges. These ranges will eventually be used to extract variants from a regions in the VCF file.

Load the annotation package.

    txdb <- TxDb.Hsapiens.UCSC.hg19.knownGene
    txdb

    ## TxDb object:
    ## # Db type: TxDb
    ## # Supporting package: GenomicFeatures
    ## # Data source: UCSC
    ## # Genome: hg19
    ## # Organism: Homo sapiens
    ## # Taxonomy ID: 9606
    ## # UCSC Table: knownGene
    ## # UCSC Track: GENCODE V48lift37
    ## # Resource URL: https://genome.ucsc.edu/
    ## # Type of Gene ID: Entrez Gene ID
    ## # Full dataset: yes
    ## # Nb of transcripts: 381987
    ## # Db created by: txdbmaker package from Bioconductor
    ## # Creation time: 2025-10-13 16:17:01 -0700 (Mon, 13 Oct 2025)
    ## # txdbmaker version at creation time: 1.5.6
    ## # RSQLite version at creation time: 2.4.3
    ## # DBSCHEMAVERSION: 1.2

Our VCF file was aligned to a genome from NCBI and the known gene track was from UCSC. These institutions have different naming conventions for the chromosomes. In order to use these two pieces of data in a matching or overlap operation the chromosome names (also called sesqlevels) need to match. We will modify the txdb to match the VCF file.

    txdb <- keepSeqlevels(txdb, "chr17")

Create a list of transcripts by gene:

    txbygene <- transcriptsBy(txdb, "gene")

Create the gene ranges for the TRPV genes

    gnrng <- unlist(range(txbygene[geneid$ENTREZID]), use.names=FALSE)
    names(gnrng) <- geneid$SYMBOL

[ Back to top ]

## 4.4 Extract variant subsets

A ScanVcfParam object is used to retrieve data subsets. This object can specify genomic coordinates (ranges) or individual VCF elements. Extractions of ranges (vs fields) requires a tabix index. See ?indexTabix for details.

    seqinfo(gnrng)

    ## Seqinfo object with 1 sequence from hg19 genome:
    ##   seqnames seqlengths isCircular genome
    ##   chr17      81195210      FALSE   hg19

    seqlevels(gnrng) <- sub("chr", "", seqlevels(gnrng))
    genome(gnrng) <- "B37"
    seqinfo(gnrng)

    ## Seqinfo object with 1 sequence from B37 genome:
    ##   seqnames seqlengths isCircular genome
    ##   17         81195210      FALSE    B37

    ## seqlevelsStyle(gnrng) <- "NCBI"
    param <- ScanVcfParam(which = gnrng, info = "DP", geno = c("GT", "cPd"))
    param

    ## class: ScanVcfParam
    ## vcfWhich: 1 elements
    ## vcfFixed: character() [All]
    ## vcfInfo: DP
    ## vcfGeno: GT cPd
    ## vcfSamples:

    ## Extract the TRPV ranges from the VCF file
    vcf <- readVcf(file, param = param)
    ## Inspect the VCF object with the 'fixed', 'info' and 'geno' accessors
    vcf

    ## class: CollapsedVCF
    ## dim: 403 1
    ## rowRanges(vcf):
    ##   GRanges with 5 metadata columns: paramRangeID, REF, ALT, QUAL, FILTER
    ## info(vcf):
    ##   DataFrame with 1 column: DP
    ## info(header(vcf)):
    ##       Number Type    Description
    ##    DP 1      Integer Total Depth
    ## geno(vcf):
    ##   List of length 2: GT, cPd
    ## geno(header(vcf)):
    ##        Number Type   Description
    ##    GT  1      String Genotype
    ##    cPd 1      String called Ploidy(level)

    head(fixed(vcf))

    ## DataFrame with 6 rows and 4 columns
    ##              REF                ALT      QUAL      FILTER
    ##   <DNAStringSet> <DNAStringSetList> <numeric> <character>
    ## 1              A                  G       120        PASS
    ## 2              A                            0        PASS
    ## 3          AAAAA                            0        PASS
    ## 4             AA                            0        PASS
    ## 5              C                  T        59        PASS
    ## 6              T                  C       157        PASS

    geno(vcf)

    ## List of length 2
    ## names(2): GT cPd

    seqinfo(vcf)

    ## Seqinfo object with 25 sequences from B37 genome; no seqlengths:
    ##   seqnames seqlengths isCircular genome
    ##   1              <NA>       <NA>    B37
    ##   2              <NA>       <NA>    B37
    ##   3              <NA>       <NA>    B37
    ##   4              <NA>       <NA>    B37
    ##   5              <NA>       <NA>    B37
    ##   ...             ...        ...    ...
    ##   21             <NA>       <NA>    B37
    ##   22             <NA>       <NA>    B37
    ##   X              <NA>       <NA>    B37
    ##   Y              <NA>       <NA>    B37
    ##   M              <NA>       <NA>    B37

    genome(vcf) <- "hg19"
    seqlevels(vcf) <- sub("([[:digit:]]+)", "chr\\1", seqlevels(vcf))
    seqinfo(vcf)

    ## Seqinfo object with 25 sequences from hg19 genome; no seqlengths:
    ##   seqnames seqlengths isCircular genome
    ##   chr1           <NA>       <NA>   hg19
    ##   chr2           <NA>       <NA>   hg19
    ##   chr3           <NA>       <NA>   hg19
    ##   chr4           <NA>       <NA>   hg19
    ##   chr5           <NA>       <NA>   hg19
    ##   ...             ...        ...    ...
    ##   chr21          <NA>       <NA>   hg19
    ##   chr22          <NA>       <NA>   hg19
    ##   X              <NA>       <NA>   hg19
    ##   Y              <NA>       <NA>   hg19
    ##   M              <NA>       <NA>   hg19

    ## seqlevelsStyle(vcf) <- "UCSC"
    vcf <- keepSeqlevels(vcf, "chr17")

[ Back to top ]

## 4.5 Variant location in the gene model

The locateVariants function identifies where a variant falls with respect to gene structure, e.g., exon, utr, splice site, etc. We use the gene model from the TxDb.Hsapiens.UCSC.hg19.knownGene package loaded eariler.

    ## Use the 'region' argument to define the region
    ## of interest. See ?locateVariants for details.
    cds <- locateVariants(vcf, txdb, CodingVariants())
    five <- locateVariants(vcf, txdb, FiveUTRVariants())
    splice <- locateVariants(vcf, txdb, SpliceSiteVariants())
    intron <- locateVariants(vcf, txdb, IntronVariants())

    all <- locateVariants(vcf, txdb, AllVariants())

    ## Warning in valid.GenomicRanges.seqinfo(x, suggest.trim = TRUE): GRanges object contains 39 out-of-bound ranges located on sequences 303245,
    ##   303251, 311574, 311575, 311576, 311578, and 311581. Note that ranges located
    ##   on a sequence whose length is unknown (NA) or on a circular sequence are not
    ##   considered out-of-bound (use seqlengths() and isCircular() to get the lengths
    ##   and circularity flags of the underlying sequences). You can use trim() to
    ##   trim these ranges. See ?`trim,GenomicRanges-method` for more information.

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

    ## Warning in valid.GenomicRanges.seqinfo(x, suggest.trim = TRUE): GRanges object contains 1 out-of-bound range located on sequence 311574. Note
    ##   that ranges located on a sequence whose length is unknown (NA) or on a
    ##   circular sequence are not considered out-of-bound (use seqlengths() and
    ##   isCircular() to get the lengths and circularity flags of the underlying
    ##   sequences). You can use trim() to trim these ranges. See
    ##   ?`trim,GenomicRanges-method` for more information.

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

Each row in cds represents a variant-transcript match so multiple rows per variant are possible. If we are interested in gene-centric questions the data can be summarized by gene regardless of transcript.

    ## Did any variants match more than one gene?
    geneByQuery <- sapply(split(all$GENEID, all$QUERYID), unique)
    table(lengths(geneByQuery) > 1)

    ##
    ## FALSE  TRUE
    ##   198    92

    ## Summarize the number of variants by gene:
    queryByGene <- sapply(split(all$QUERYID, all$GENEID), unique)
    table(lengths(queryByGene) > 1)

    ##
    ## FALSE  TRUE
    ##     1     8

    sapply(queryByGene, length)

    ## 119086082    125144    162514     23729    353149     51393      7442     84690
    ##         2         1        63        12         6        37        30        16
    ##      9123
    ##       132

    ## Summarize variant location by gene:
    sapply(names(queryByGene), function(nm) {
        d <- all[all$GENEID %in% nm, c("QUERYID", "LOCATION")]
        table(d$LOCATION[duplicated(d) == FALSE])
    })

    ##            119086082 125144 162514 23729 353149 51393 7442 84690 9123
    ## spliceSite         0      0      2     1      0     0    1     0    0
    ## intron             2      0      0     0      6     0    0     0  132
    ## fiveUTR            0      0      0     0      0     1    5     5    0
    ## threeUTR           0      0     27    11      0     1    2     0    0
    ## coding             0      0      6     0      0     3    8     0    0
    ## intergenic         0      0      0     0      0     0    0     0    0
    ## promoter           0      1     30     0      0    32   14    11    0

[ Back to top ]

## 4.6 Amino acid coding changes in non-synonymous variants

Amino acid coding for non-synonymous variants can be computed with the function predictCoding. The BSgenome.Hsapiens.UCSC.hg19 package is used as the source of the reference alleles. Variant alleles are provided by the user.

    aa <- predictCoding(vcf, txdb, Hsapiens)

    ## Warning in valid.GenomicRanges.seqinfo(x, suggest.trim = TRUE): GRanges object contains 39 out-of-bound ranges located on sequences 303245,
    ##   303251, 311574, 311575, 311576, 311578, and 311581. Note that ranges located
    ##   on a sequence whose length is unknown (NA) or on a circular sequence are not
    ##   considered out-of-bound (use seqlengths() and isCircular() to get the lengths
    ##   and circularity flags of the underlying sequences). You can use trim() to
    ##   trim these ranges. See ?`trim,GenomicRanges-method` for more information.

    ## Warning in .predictCodingGRangesList(query, cache[["cdsbytx"]], seqSource, :
    ## records with missing 'varAllele' were ignored

    ## Warning in .predictCodingGRangesList(query, cache[["cdsbytx"]], seqSource, :
    ## 'varAllele' values with 'N', '.', '+' or '-' were not translated

predictCoding returns results for coding variants only. As with locateVariants, the output has one row per variant-transcript match so multiple rows per variant are possible.

    ## Did any variants match more than one gene?
    aageneByQuery <- split(aa$GENEID, aa$QUERYID)
    table(lengths(sapply(aageneByQuery, unique)) > 1)

    ##
    ## FALSE
    ##    17

    ## Summarize the number of variants by gene:
    aaaqueryByGene <- split(aa$QUERYID, aa$GENEID, drop=TRUE)
    sapply(aaaqueryByGene, length)

    ## 162514  51393   7442
    ##     27      7     63

    ## Summarize variant consequence by gene:
    sapply(names(aaaqueryByGene), function(nm) {
        d <- aa[aa$GENEID %in% nm, c("QUERYID","CONSEQUENCE")]
        table(d$CONSEQUENCE[duplicated(d) == FALSE])
    })

    ##                162514 51393 7442
    ## nonsynonymous       3     0    2
    ## not translated      1     0    5
    ## synonymous          2     3    1

The variants ‘not translated’ are explained by the warnings thrown when predictCoding was called. Variants that have a missing varAllele or have an ‘N’ in the varAllele are not translated. If the varAllele substitution had resulted in a frameshift the consequence would be ‘frameshift’. See ?predictCoding for details.

[ Back to top ]

# 5 Exploring Package Content

Packages have extensive help pages, and include vignettes highlighting common use cases. The help pages and vignettes are available from within R. After loading a package, use syntax like

    help(package="VariantAnnotation")
    ?predictCoding

to obtain an overview of help on the `VariantAnnotation` package, and the `predictCoding` function. View the package vignette with

    browseVignettes(package="VariantAnnotation")

To view vignettes providing a more comprehensive introduction to package functionality use

    help.start()

[ Back to top ]

# 6 sessionInfo()

    sessionInfo()

    ## R version 4.6.0 RC (2026-04-17 r89917)
    ## Platform: x86_64-pc-linux-gnu
    ## Running under: Ubuntu 24.04.4 LTS
    ##
    ## Matrix products: default
    ## BLAS:   /home/biocbuild/bbs-3.24-bioc/R/lib/libRblas.so
    ## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  LAPACK version 3.12.0
    ##
    ## locale:
    ##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
    ##  [3] LC_TIME=en_GB              LC_COLLATE=C
    ##  [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: America/New_York
    ## tzcode source: system (glibc)
    ##
    ## attached base packages:
    ## [1] stats4    stats     graphics  grDevices utils     datasets  methods
    ## [8] base
    ##
    ## other attached packages:
    ##  [1] variants_1.37.0
    ##  [2] PolyPhen.Hsapiens.dbSNP131_1.0.2
    ##  [3] RSQLite_3.52.0
    ##  [4] BSgenome.Hsapiens.UCSC.hg19_1.4.3
    ##  [5] BSgenome_1.81.0
    ##  [6] rtracklayer_1.73.0
    ##  [7] BiocIO_1.23.3
    ##  [8] TxDb.Hsapiens.UCSC.hg19.knownGene_3.22.1
    ##  [9] GenomicFeatures_1.65.0
    ## [10] org.Hs.eg.db_3.23.1
    ## [11] AnnotationDbi_1.75.0
    ## [12] VariantAnnotation_1.59.0
    ## [13] Rsamtools_2.29.0
    ## [14] Biostrings_2.81.1
    ## [15] XVector_0.53.0
    ## [16] SummarizedExperiment_1.43.0
    ## [17] Biobase_2.73.1
    ## [18] GenomicRanges_1.65.0
    ## [19] MatrixGenerics_1.25.0
    ## [20] matrixStats_1.5.0
    ## [21] GenomeInfoDb_1.49.0
    ## [22] Seqinfo_1.3.0
    ## [23] IRanges_2.47.1
    ## [24] S4Vectors_0.51.2
    ## [25] BiocGenerics_0.59.2
    ## [26] generics_0.1.4
    ## [27] BiocStyle_2.41.0
    ##
    ## loaded via a namespace (and not attached):
    ##  [1] KEGGREST_1.53.0          rjson_0.2.23             xfun_0.57
    ##  [4] bslib_0.11.0             lattice_0.22-9           vctrs_0.7.3
    ##  [7] tools_4.6.0              bitops_1.0-9             curl_7.1.0
    ## [10] parallel_4.6.0           blob_1.3.0               pkgconfig_2.0.3
    ## [13] BiocBaseUtils_1.15.1     Matrix_1.7-5             cigarillo_1.3.0
    ## [16] lifecycle_1.0.5          compiler_4.6.0           codetools_0.2-20
    ## [19] htmltools_0.5.9          sass_0.4.10              RCurl_1.98-1.18
    ## [22] yaml_2.3.12              crayon_1.5.3             jquerylib_0.1.4
    ## [25] BiocParallel_1.47.0      DelayedArray_0.39.2      cachem_1.1.0
    ## [28] abind_1.4-8              digest_0.6.39            restfulr_0.0.16
    ## [31] bookdown_0.46            fastmap_1.2.0            grid_4.6.0
    ## [34] cli_3.6.6                SparseArray_1.13.2       S4Arrays_1.13.0
    ## [37] XML_3.99-0.23            UCSC.utils_1.9.0         bit64_4.8.0
    ## [40] rmarkdown_2.31           httr_1.4.8               bit_4.6.0
    ## [43] otel_0.2.0               png_0.1-9                memoise_2.0.1
    ## [46] evaluate_1.0.5           knitr_1.51               rlang_1.2.0
    ## [49] DBI_1.3.0                BiocManager_1.30.27      jsonlite_2.0.0
    ## [52] R6_2.6.1                 GenomicAlignments_1.49.0

[ Back to top ]