Working with annotations

Last updated on 2024-11-19 | Edit this page


Overview

Questions

  • What Bioconductor packages provides methods to efficiently fetch and use gene annotations?
  • How can I use gene annotation packages to convert between different gene identifiers?

Objectives

  • Explain how gene annotations are managed in the Bioconductor project.
  • Identify Bioconductor packages and methods available to fetch and use gene annotations.

Install packages


Before we can proceed into the following sections, we install some Bioconductor packages that we will need. First, we check that the BiocManager package is installed before trying to use it; otherwise we install it. Then we use the BiocManager::install() function to install the necessary packages.

R

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

BiocManager::install(c("biomaRt", "org.Hs.eg.db"))

Overview


Packages dedicated to query gene annotations exist in the ‘Software’ and ‘Annotation’ categories of the Bioconductor biocViews, according to their nature.

In the ‘Software’ section, we find packages that do not actually contain gene annotations, but rather dynamically query them from online resources (e.g.,Ensembl BioMart). One such Bioconductor package is biomaRt.

Instead, in the ‘Annotation’ section, we find packages that do contain annotations. Examples include org.Hs.eg.db, EnsDb.Hsapiens.v86, and TxDb.Hsapiens.UCSC.hg38.knownGene.

In this episode, we will demonstrate the two approaches:

  • Querying annotations from the Ensembl Biomart API using the biomaRt package.
  • Querying annotations from the org.Hs.eg.db annotation package.

Online resources or Bioconductor annotation packages?


Accessing the latest information

Bioconductor’s 6-month release cycle implies that packages available from the latest stable release branch will not be updated for six months (only bugfixes are allowed, not functional updates). As a result, annotation packages may contain information that is out-of-date by up to six months.

Instead, independent online resources often have different policies driving the release of updated information. Some databases are frequently updated, while others may not have been updated in years.

Accessing up-to-date information must also be balanced with reproducibility. Having downloaded the ‘latest’ information at one point is time is no good if one hasn’t recorded at least when that information was downloaded.

Storage requirements

By nature, Bioconductor annotation packages are larger than software packages. Just like any other R package, annotation packages must be installed on the user’s computer before they can be used. This can rapidly use add up to using an amount of disk space that is not negligible.

Conversely, online resources are generally accessed programmatically, and generally only require users to record code to replicate analyses reproducibly.

Internet connectivity

When using online resources, it is often a good idea to write annotations ownloaded from online resources to a local file, and refer to that local file during analyses.

If online resources were to become unavailable for any reason (e.g., downtime, loss of internet connection), analyses that use local files can carry on while those that rely on those online resources cannot.

In contrast, Bioconductor annotation packages only require internet connectivity at the time of installation. Once installed, they do not require internet connectivity, as they rely on information stored locally.

Reproducibility

Bioconductor annotation packages are naturally versioned, meaning that users can confidently report the version of the package used in their analysis. Just like software packages, users control if and when annotation packages should be updated on their computer.

Online resources have different policies to facilitate reproducible analyses. Some online resources keep archived versions of their annotations, allowing users to consistently access the same information over time. When this is not the case, it may be necessary to download a copy of the annotation at one point in time, and preciously keep that copy throughout the lifetime of the project to ensure the use of a consistent set of annotations.

Consistency

As we will see in the practical examples of this episode, Bioconductor annotation packages generally re-use a consistent set of data structures. This allows users familiar with one annotation package to rapidly get started with others.

Independent online resources often organise their data in different ways, which requires users to write custom code to access, retrieve, and process their respective data.

Querying annotations from Ensembl BioMart


The Ensembl BioMart

Ensembl BioMart is a robust data mining tool designed to facilitate access to the vast array of biological data available through the Ensembl project.

The BioMart web interface enables researchers to efficiently query and retrieve data on genes, proteins, and other genomic features across multiple species. It allows users to filter, sort, and export data based on various attributes such as gene IDs, chromosomal locations, and functional annotations.

The Bioconductor biomaRt package

biomaRt is a Bioconductor software package that enables retrieval of large amounts of data from Ensembl BioMart tables directly from an R session where those annotations can be used.

Let us first load the package:

R

library(biomaRt)

Listing available marts

Ensembl BioMart organises its diverse biological information into four databases also known as ‘marts’ or ‘biomarts’. Each mart focuses on a different type of data.

Users must select the mart corresponds to the type of data they are interested in before they can query any information from it.

The function listMarts() can be used to display the names of those marts. This is convenient as users do not need to memorise the name of the marts, and the function will also return an updated list of names if any mart is renamed, added, or removed.

R

listMarts()

OUTPUT

               biomart                version
1 ENSEMBL_MART_ENSEMBL      Ensembl Genes 113
2   ENSEMBL_MART_MOUSE      Mouse strains 113
3     ENSEMBL_MART_SNP  Ensembl Variation 113
4 ENSEMBL_MART_FUNCGEN Ensembl Regulation 113

In this demonstration, we will use the biomart called ENSEMBL_MART_ENSEMBL, which contains the Ensembl gene set.

Notably, the version columns also indicates the version of the biomart. The Ensembl BioMart is updated regularly (multiple times per year). By default, biomaRt functions access the latest version of each biomart. This is not ideal for reproducibility.

Thankfully, Ensembl BioMart archives past versions of its mars in a way that is accessible both programmatically, and on its website.

The function listEnsemblArchives() can be used to display all the versions of Ensembl Biomart accessible.

R

listEnsemblArchives()

OUTPUT

             name     date                                 url version
1  Ensembl GRCh37 Feb 2014          https://grch37.ensembl.org  GRCh37
2     Ensembl 113 Oct 2024 https://oct2024.archive.ensembl.org     113
3     Ensembl 112 May 2024 https://may2024.archive.ensembl.org     112
4     Ensembl 111 Jan 2024 https://jan2024.archive.ensembl.org     111
5     Ensembl 110 Jul 2023 https://jul2023.archive.ensembl.org     110
6     Ensembl 109 Feb 2023 https://feb2023.archive.ensembl.org     109
7     Ensembl 108 Oct 2022 https://oct2022.archive.ensembl.org     108
8     Ensembl 107 Jul 2022 https://jul2022.archive.ensembl.org     107
9     Ensembl 106 Apr 2022 https://apr2022.archive.ensembl.org     106
10    Ensembl 105 Dec 2021 https://dec2021.archive.ensembl.org     105
11    Ensembl 104 May 2021 https://may2021.archive.ensembl.org     104
12    Ensembl 103 Feb 2021 https://feb2021.archive.ensembl.org     103
13    Ensembl 102 Nov 2020 https://nov2020.archive.ensembl.org     102
14    Ensembl 101 Aug 2020 https://aug2020.archive.ensembl.org     101
15    Ensembl 100 Apr 2020 https://apr2020.archive.ensembl.org     100
16     Ensembl 99 Jan 2020 https://jan2020.archive.ensembl.org      99
17     Ensembl 98 Sep 2019 https://sep2019.archive.ensembl.org      98
18     Ensembl 80 May 2015 https://may2015.archive.ensembl.org      80
19     Ensembl 77 Oct 2014 https://oct2014.archive.ensembl.org      77
20     Ensembl 75 Feb 2014 https://feb2014.archive.ensembl.org      75
21     Ensembl 54 May 2009 https://may2009.archive.ensembl.org      54
   current_release
1
2                *
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21                

In the output above, the key piece of information is the url column, which provides the URL that biomaRt functions will need to access data from the corresponding snapshot of the Ensembl BioMart.

At the time of writing, the current release is Ensembl 112, so let us use the corresponding url https://may2024.archive.ensembl.org to ensure reproducible results no matter when this lesson is delivered.

Connecting to a biomart

The two pieces of information collected above – the name of a biomart and the URL of a snapshot – is all that is needed to connect to a BioMart database reproducibly.

The function useMart() can then be used to create a connection. The connection is traditionally stored in an object called mart, to be reused in subsequent steps for querying information from the online mart.

R

mart <- useMart(biomart = "ENSEMBL_MART_ENSEMBL", host = "https://may2024.archive.ensembl.org")

Listing available data sets

Each biomart contains a number of data sets.

The function listDatasets() can be used to display information about those data sets. This is convenient as users do not need to memorise the name of the data sets, and the information returned by the function includes a short description of each data set, as well as its version.

In the example below, we restrict the output table to the first few rows, as the full table comprises 214 rows.

R

head(listDatasets(mart))

OUTPUT

                       dataset                           description
1 abrachyrhynchus_gene_ensembl Pink-footed goose genes (ASM259213v1)
2     acalliptera_gene_ensembl      Eastern happy genes (fAstCal1.3)
3   acarolinensis_gene_ensembl       Green anole genes (AnoCar2.0v2)
4    acchrysaetos_gene_ensembl       Golden eagle genes (bAquChr1.2)
5    acitrinellus_gene_ensembl        Midas cichlid genes (Midas_v5)
6    amelanoleuca_gene_ensembl       Giant panda genes (ASM200744v2)
      version
1 ASM259213v1
2  fAstCal1.3
3 AnoCar2.0v2
4  bAquChr1.2
5    Midas_v5
6 ASM200744v2

In the output above, the key piece of information is the dataset column, which provides the identifier that biomaRt functions will need to access data from the corresponding biomart table.

In this demonstration, we will use the Ensembl gene set for Homo sapiens, which is not visible in the output above.

Given the number of data sets available, let us programmatically filter the table of information using pattern matching rather than searching the table manually:

R

subset(listDatasets(mart), grepl("sapiens", dataset))

OUTPUT

                 dataset              description    version
80 hsapiens_gene_ensembl Human genes (GRCh38.p14) GRCh38.p14

From the output above, we identify the desired data set identifier as hsapiens_gene_ensembl.

Connecting to a data set

Having chosen the data set that we want to use, we need to call the function useMart() again, this time specifying the selected data set.

Typically, one would copy paste the previous call to useMart() and edit as needed. It is also common practice to replace the mart object with the new connection.

R

mart <- useMart(
  biomart = "ENSEMBL_MART_ENSEMBL",
  dataset = "hsapiens_gene_ensembl",
  host = "https://may2024.archive.ensembl.org")

Listing information available in a data set

BioMart tables contain many pieces of information also known as ‘attributes’. So many, in fact, that they have been grouped into categories also known as ‘pages’.

The function listAttributes() can be used to display information about those attributes. This is convenient as users do not need to memorise the name of the attributes, and the information returned by the function includes a short description of each attribute, as well as its page categorisation.

In the example below, we restrict the output table to the first few rows, as the full table comprises 3157 rows.

R

head(listAttributes(mart))

OUTPUT

                           name                  description         page
1               ensembl_gene_id               Gene stable ID feature_page
2       ensembl_gene_id_version       Gene stable ID version feature_page
3         ensembl_transcript_id         Transcript stable ID feature_page
4 ensembl_transcript_id_version Transcript stable ID version feature_page
5            ensembl_peptide_id            Protein stable ID feature_page
6    ensembl_peptide_id_version    Protein stable ID version feature_page

In the output above, the key piece of information is the name column, which provides the identifier that biomaRt functions will need to query that information from the corresponding biomart data set.

The choice of attributes to query now depends on what it is we wish to achieve.

For instance, let us imagine that we have a set of gene identifiers, for which we wish to query:

  • The gene symbol
  • The name of the chromosome where the gene is located
  • The start and end position of the gene on that chromosome
  • The strand on which the gene is encoded

Users would often manually explore the full table of attributes to identify the ones they wish to include in their query. It is also possible to programmatically filter the table of attribute, based on experience and intuition, to narrow down the search:

R

subset(listAttributes(mart), grepl("position", name) & grepl("feature", page))

OUTPUT

             name     description         page
10 start_position Gene start (bp) feature_page
11   end_position   Gene end (bp) feature_page

Querying information from a BioMart table

We have now all the information that we need to perform the actual query:

  • A connection to a BioMart data set
  • The list of attributes available in that data set

The function getBM() is the main biomaRt query function. Given a set of filters and corresponding values, it retrieves the attributes requested by the user from the BioMart data set it is connected to.

In the example below, we manually create a vector of arbitrary gene identifiers for our query. In practice, the query will often originate from an earlier analysis (e.g., differential gene expression).

The example below also queries attributes that we have not introduced yet. In the previous section, we described how one may search the table of attributes returned by listAttributes() to identify attributes to include in their query.

R

query_gene_ids <- c(
  "ENSG00000133101",
  "ENSG00000145386",
  "ENSG00000134057",
  "ENSG00000157456",
  "ENSG00000147082"
)
getBM(
  attributes = c(
    "ensembl_gene_id",
    "hgnc_symbol",
    "chromosome_name",
    "start_position",
    "end_position",
    "strand"
  ),
  filters = "ensembl_gene_id",
  values = query_gene_ids,
  mart = mart
)

ERROR

Error in .processResults(postRes, mart = mart, hostURLsep = sep, fullXmlQuery = fullXmlQuery, : Query ERROR: caught BioMart::Exception::Database: Error during query execution: Table 'ensembl_mart_112.hsapiens_gene_ensembl__ox_hgnc__dm' doesn't exist

Note that we also included the filtering attribute ensembl_gene_id to the attributes retrieved from the data set. This is key to reliably match the newly retrieved attributes to those used in the query.

Querying annotations from annotation packages


Families of annotation packages

To balance the need for comprehensive information while maintaining reasonable package sizes, Bioconductor annotation packages are organised by release, data type, and species.

The major families of Bioconductor annotation packages are:

  • OrgDb packages provide mapping between various types of gene identifiers and pathway information.
  • EnsDb packages provide individual releases of Ensembl annotations.
  • TxDb packages provide individual releases of UCSC annotations.

All those families of annotations derive from the AnnotationDb base class defined in the AnnotationDbi package. As a result, any of those annotation packages can be accessed using the same set of R functions, as demonstrated in the following sections.

Using an OrgDb package

In this example, we will use the org.Hs.eg.db package to demonstrate the use of gene annotations for the human species.

Let us first load the package:

R

library(org.Hs.eg.db)

Each OrgDb package contains an object named identically to the package itself. That object contains the annotations that the package is meant to disseminate.

Aside from querying information, the whole object can be called to print information about the annotations it contains, including the date at which the snapshots of annotations that it contains were made.

R

org.Hs.eg.db

OUTPUT

OrgDb object:
| DBSCHEMAVERSION: 2.1
| Db type: OrgDb
| Supporting package: AnnotationDbi
| DBSCHEMA: HUMAN_DB
| ORGANISM: Homo sapiens
| SPECIES: Human
| EGSOURCEDATE: 2024-Mar12
| EGSOURCENAME: Entrez Gene
| EGSOURCEURL: ftp://ftp.ncbi.nlm.nih.gov/gene/DATA
| CENTRALID: EG
| TAXID: 9606
| GOSOURCENAME: Gene Ontology
| GOSOURCEURL: http://current.geneontology.org/ontology/go-basic.obo
| GOSOURCEDATE: 2024-01-17
| GOEGSOURCEDATE: 2024-Mar12
| GOEGSOURCENAME: Entrez Gene
| GOEGSOURCEURL: ftp://ftp.ncbi.nlm.nih.gov/gene/DATA
| KEGGSOURCENAME: KEGG GENOME
| KEGGSOURCEURL: ftp://ftp.genome.jp/pub/kegg/genomes
| KEGGSOURCEDATE: 2011-Mar15
| GPSOURCENAME: UCSC Genome Bioinformatics (Homo sapiens)
| GPSOURCEURL:
| GPSOURCEDATE: 2024-Feb29
| ENSOURCEDATE: 2023-Nov22
| ENSOURCENAME: Ensembl
| ENSOURCEURL: ftp://ftp.ensembl.org/pub/current_fasta
| UPSOURCENAME: Uniprot
| UPSOURCEURL: http://www.UniProt.org/
| UPSOURCEDATE: Thu Apr 18 21:39:39 2024

OUTPUT


Please see: help('select') for usage information

That same object is the one that needs to be supplied to AnnotationDbi functions for running queries and retrieving annotations.

Listing information available in an annotation package

The function columns() can be used to display the annotations available in the object.

Here, the word ‘column’ refers to columns of tables used to store information in database, the very same concept as ‘attributes’ in BioMart. In other words, columns represent all the types of annotations that may be retrieved from the object.

This is convenient as users do not need to memorise the names of the columns of annotations available in the package.

R

columns(org.Hs.eg.db)

OUTPUT

 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT"  "ENSEMBLTRANS"
 [6] "ENTREZID"     "ENZYME"       "EVIDENCE"     "EVIDENCEALL"  "GENENAME"
[11] "GENETYPE"     "GO"           "GOALL"        "IPI"          "MAP"
[16] "OMIM"         "ONTOLOGY"     "ONTOLOGYALL"  "PATH"         "PFAM"
[21] "PMID"         "PROSITE"      "REFSEQ"       "SYMBOL"       "UCSCKG"
[26] "UNIPROT"     

Listing keys and key types

In database terminology, keys are the values by which information may be queried from a database table.

Information being organised in columns, key types are the names of the columns in which the key values are stored.

Given the variable number of columns in database tables, some tables may allow information to be queried by more than one key. As a result, it is crucial to specify both the keys and the type of key as part of the query.

The function keytypes() can be used to display the names of the columns that may be used to query information from the object.

R

keytypes(org.Hs.eg.db)

OUTPUT

 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT"  "ENSEMBLTRANS"
 [6] "ENTREZID"     "ENZYME"       "EVIDENCE"     "EVIDENCEALL"  "GENENAME"
[11] "GENETYPE"     "GO"           "GOALL"        "IPI"          "MAP"
[16] "OMIM"         "ONTOLOGY"     "ONTOLOGYALL"  "PATH"         "PFAM"
[21] "PMID"         "PROSITE"      "REFSEQ"       "SYMBOL"       "UCSCKG"
[26] "UNIPROT"     

The function keys() can be used to display all the possible values for a given key type.

It is generally better practice to specify the type of key being queried (to avoid ambiguity), although database tables typically have a ‘primary key’ used if users do not specify a type themselves.

In the example below, we restrict the list of gene symbol keys to the first few values, as the full set comprises 193279 values.

R

head(keys(org.Hs.eg.db, keytype = "SYMBOL"))

OUTPUT

[1] "A1BG"  "A2M"   "A2MP1" "NAT1"  "NAT2"  "NATP" 

Querying information from an annotation package

The function select() is the main AnnotationDbi query function. Given an AnnotationDb object, key values, and columns (and optionally the type of key supplied if not the primary key), it retrieves the columns requested by the user from the annotation object.

In the example below, we re-use the vector of arbitrary gene identifiers used in the BioMart example a few sections above.

As you can see from the output of the columns() function, the annotation object does not contain some of the attributes that we queried in the Biomart example. In this case, let us query:

  • the gene symbol
  • the gene name
  • the gene type

R

select(
  x = org.Hs.eg.db,
  keys = query_gene_ids,
  columns = c(
    "SYMBOL",
    "GENENAME",
    "GENETYPE"
  ),
  keytype = "ENSEMBL"
)

OUTPUT

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

OUTPUT

          ENSEMBL SYMBOL  GENENAME       GENETYPE
1 ENSG00000133101  CCNA1 cyclin A1 protein-coding
2 ENSG00000145386  CCNA2 cyclin A2 protein-coding
3 ENSG00000134057  CCNB1 cyclin B1 protein-coding
4 ENSG00000157456  CCNB2 cyclin B2 protein-coding
5 ENSG00000147082  CCNB3 cyclin B3 protein-coding

One small but notable difference with biomaRt is that the output of select() automatically contains the column that correspond to the key type used in the query. In other words, there is no need to specify the key type(s) again in the column(s) to retrieve.

Vectorized 1:1 mapping

It is sometimes possible for annotations to display 1-to-many relationships. For instance, individual genes typically have a unique Ensembl gene identifier, while they may be known under multiple gene name aliases.

The select() function demonstrated in the previous section automatically returns all values in the columns requested, for the key specified. This is possible thanks to the tabular format in which annotations are returned; rows are added, repeating values as necessary to display them on the same row as every other values they are associated with.

In some cases, that behaviour is not desirable. Instead, users may wish to retrieve a single value for each key that they input. One common scenario arises during differential gene expression (DGE), where gene identifiers are used to uniquely identify genes throughout the analysis, while gene symbols are added to the final table of DGE statistics, to provide more readable human-friendly gene identifiers. However, it is not desirable to duplicate rows of DGE statistics, and thus only a single gene symbol is required to annotate each gene.

The function mapIds() can be used for this purpose. A major difference between the functions mapIds() and select() are their arguments column (singular) and columns (plural), respectively. The function mapIds() accepts a single column name and returns a named character vector where names are the input query values, and values are the corresponding values in the requested column.

To deal with 1-to-many relationships, the function mapIds() has an argument multiVals which can be used to specify how the function should handle multiple values. The default is to take the first value and ignore any other value.

In the example below, we query the gene symbol for a set of Ensembl gene identifiers.

R

mapIds(
  x = org.Hs.eg.db,
  keys = query_gene_ids,
  column = "SYMBOL",
  keytype = "ENSEMBL"
)

OUTPUT

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

OUTPUT

ENSG00000133101 ENSG00000145386 ENSG00000134057 ENSG00000157456 ENSG00000147082
        "CCNA1"         "CCNA2"         "CCNB1"         "CCNB2"         "CCNB3" 

Challenge

Load the packages EnsDb.Hsapiens.v86 and TxDb.Hsapiens.UCSC.hg38.knownGene. Then, display the columns of annotations available in those packages.

R

library(EnsDb.Hsapiens.v86)
columns(EnsDb.Hsapiens.v86)

OUTPUT

 [1] "ENTREZID"            "EXONID"              "EXONIDX"
 [4] "EXONSEQEND"          "EXONSEQSTART"        "GENEBIOTYPE"
 [7] "GENEID"              "GENENAME"            "GENESEQEND"
[10] "GENESEQSTART"        "INTERPROACCESSION"   "ISCIRCULAR"
[13] "PROTDOMEND"          "PROTDOMSTART"        "PROTEINDOMAINID"
[16] "PROTEINDOMAINSOURCE" "PROTEINID"           "PROTEINSEQUENCE"
[19] "SEQCOORDSYSTEM"      "SEQLENGTH"           "SEQNAME"
[22] "SEQSTRAND"           "SYMBOL"              "TXBIOTYPE"
[25] "TXCDSSEQEND"         "TXCDSSEQSTART"       "TXID"
[28] "TXNAME"              "TXSEQEND"            "TXSEQSTART"
[31] "UNIPROTDB"           "UNIPROTID"           "UNIPROTMAPPINGTYPE" 

R

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

OUTPUT

 [1] "CDSCHROM"   "CDSEND"     "CDSID"      "CDSNAME"    "CDSPHASE"
 [6] "CDSSTART"   "CDSSTRAND"  "EXONCHROM"  "EXONEND"    "EXONID"
[11] "EXONNAME"   "EXONRANK"   "EXONSTART"  "EXONSTRAND" "GENEID"
[16] "TXCHROM"    "TXEND"      "TXID"       "TXNAME"     "TXSTART"
[21] "TXSTRAND"   "TXTYPE"    

Key Points

  • Bioconductor provides a wide range annotation packages.
  • Some Bioconductor software packages can be used to programmatically access online resources.
  • Users should carefully choose their source of annotations based on their needs and expectations.