2  Searching NCBI and Ensembl

This lab guides you through a routine session with online biological databases. The first sections give an overview of some of the existing internet databases for sequence and general information search; the last part uses R to gain programmatic access to annotation datasets through Bioconductor libraries.

By the end of this laboratory you will be able to:

NoteInstalling Bioconductor packages

This lab uses packages from Bioconductor. If your R installation is missing a package that belongs to Bioconductor (rather than CRAN), install it like this:

if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")
BiocManager::install("<packageName>") # a specific package
NoteLab at a glance

Goal: query biological databases by hand and, reproducibly, from R. Prerequisites: Introduction to R. Time: ~2 h. Packages: org.Hs.eg.db, dplyr, rentrez (and biomaRt for live Ensembl access, shown not run). Deliverable: a lab report.

2.1 NCBI

NCBI stands for the National Center for Biotechnology Information. It is a division of the National Library of Medicine, which belongs to the National Institutes of Health (NIH). Created in 1988 as a US national resource for molecular-biology information, all of its databases are public. Besides molecular-biology information, NCBI provides tools for analysing that data and is active in basic research, databases, and education.

NCBI links many databases through the Entrez search system and provides tools for sequence search, genome browsing, data download, 3D structure visualisation, and literature discovery.

Important resources include Bookshelf, Gene, Nucleotide, Protein, Assembly, Taxonomy, PubMed, GEO, SRA, dbGaP, dbSNP, ClinVar, and the Conserved Domain Database. Useful analysis and download tools include BLAST, Genome Data Viewer (GDV), NCBI Datasets, ORF Finder, and the browser-based iCn3D structure viewer.

2.1.1 Genomes

NCBI Datasets is the current entry point for finding assemblies and downloading genome, transcript, protein, and annotation files; Genome Data Viewer (GDV) provides the interactive chromosome view (the older Map Viewer was retired in 2018). A good first exercise is to compare the reference assemblies of three very different organisms.

Tip✅ Exercise 2.1 — Genomes
  1. Open the NCBI Datasets genome page.

  2. Search for the reference genomes of Caenorhabditis elegans, Severe acute respiratory syndrome coronavirus 2, and Mus musculus and fill in a comparison table like the one below (report it in your lab report):

    organism accession assembly level genome size annotated genes
    C. elegans
    SARS-CoV-2
    M. musculus

    Explain why “number of chromosomes” is not a useful field for every viral or draft assembly.

  3. Open the C. elegans or mouse reference assembly in Genome Data Viewer. Choose a chromosome, navigate to a gene, zoom from chromosome to exon scale, and identify the assembly and RefSeq annotation release shown.

  4. From NCBI Datasets, download a small data package containing the genome data report and GFF3 annotation. Describe the files and identify which one contains feature coordinates.

2.1.3 PubMed

PubMed indexes biomedical literature. Its field tags make searches reproducible and help separate words in titles/abstracts from author affiliations.

Tip✅ Exercise 2.3 — PubMed
  1. Search for what COVID-19 is in PubMed / Entrez (Publications), https://pubmed.ncbi.nlm.nih.gov/. Explain what this database is.
  2. Compare the broad query COVID-19 Wang with the author-field query COVID-19 AND Wang[Author].
  3. Entrez allows control over the search by specifying fields. Search for COVID-19[Title/Abstract] AND China[Affiliation] in PubMed. Explain what the fields mean and what other search options exist (the help button is your friend).

2.2 Ensembl

The Ensembl Project is based at the European Molecular Biology Laboratory’s European Bioinformatics Institute (EMBL-EBI). It produces and integrates genome annotations for vertebrates and other eukaryotes. Its website and APIs expose genes, transcripts, comparative genomics, variation, and regulation data.

Tip✅ Exercise 2.4 — Ensembl
  1. Point your browser to https://www.ensembl.org.
  2. Look for the gene LDLR in Homo sapiens.
  3. Within the gene entry, navigate to Sequence in the left bar; observe the sequence and the marked exonic regions.
  4. Open the transcript Ensembl marks as canonical and record its stable transcript and protein IDs and lengths. Do not use “the first transcript”: display order can change between releases.
  5. Export one exon in FASTA format and record the Ensembl release and human assembly so your answer remains reproducible.

2.3 Protein databases

PubMed can retrieve protein information, but dedicated protein databases are also useful. A good example is UniProt (Swiss-Prot).

2.3.1 UniProtKB (Swiss-Prot)

We will inspect the reviewed UniProtKB/Swiss-Prot record for the SARS-CoV-2 spike glycoprotein. The protein is viral; Homo sapiens is its host, not its source organism.

Tip✅ Exercise 2.5 — UniProtKB
  1. Point a browser to https://www.uniprot.org/.
  2. Search for accession P0DTC2 (SPIKE_SARS2) and confirm that the organism is SARS-CoV-2. (P59594 is the related spike protein from the 2003 SARS-CoV.)
  3. Explain the function of the SARS-CoV-2 spike protein and distinguish the S1 receptor-binding and S2 membrane-fusion roles.
  4. Retrieve the amino-acid protein sequence in FASTA format.
  5. What is the molecular weight (mass) of the protein, in kDa?

2.3.2 Research Collaboratory for Structural Bioinformatics (RCSB)

RCSB PDB stores experimentally determined macromolecular structures and links to computed structure models. Connect at https://www.rcsb.org.

Tip✅ Exercise 2.6 — RCSB
  1. What does RCSB mean?
  2. Search for PDB entry 6M0J, the SARS-CoV-2 spike receptor-binding domain bound to human ACE2.
  3. Describe the differences between UniProtKB and the PDB. What type of information can you obtain from RCSB, apart from the protein sequence?
  4. Identify the chains corresponding to viral spike and human ACE2 (UniProt Q9BYF1), the experimental method, resolution, and biological assembly. Hint: the complex is a 1:1 construct (one RBD chain bound to one ACE2 chain), which helps you tell spike apart from ACE2 when more than two chains are present in the asymmetric unit.
  5. Inspect the interface in the browser-based Mol* viewer. Download the mmCIF file (the archival standard); optionally compare it with the legacy PDB format in PyMOL.
NoteAbout the PDB file format

The primary information stored in the PDB archive consists of coordinate files for biological molecules, listing the atoms in each protein and their 3D locations. The PDB archive’s standard text format is PDBx/mmCIF; the older fixed-column PDB format remains available for compatible structures. A typical legacy PDB-formatted file has a large header section summarising the protein, citation information, and the details of the structure solution, followed by the sequence and a long list of atoms and their coordinates. The archive also contains the experimental observations used to determine the coordinates. Further information about the format is available here.

2.4 Programmatic access from R

Everything so far happened in a browser. In this section we move the same queries into R, which is what makes them reproducible and automatable. Through Bioconductor, R offers many local annotation packages (see the AnnotationData BiocViews), while services such as biomaRt and rentrez provide version-aware access to live databases. We start with a local snapshot and finish with live queries.

2.4.1 org.Hs.eg.db

org.Hs.eg.db is a Bioconductor package providing genome-wide annotation for Homo sapiens, primarily based on mappings using Entrez Gene identifiers. Common package families include organism-level mappings such as org.Hs.eg.db and org.Mm.eg.db, assay annotation such as hgu133plus2.db, ontology metadata in GO.db, transcript models in TxDb.*, and Ensembl-based models in EnsDb.*. Always record the package version and genome assembly; identifier mappings and annotations change between releases.

Most annotation packages use four standard access methods: columns(), keytypes(), keys(), and select().

Load org.Hs.eg.db and inspect its contents.1

Code
suppressPackageStartupMessages({
  library(org.Hs.eg.db)
})
ls("package:org.Hs.eg.db")
 [1] "org.Hs.eg"                "org.Hs.eg_dbconn"        
 [3] "org.Hs.eg_dbfile"         "org.Hs.eg_dbInfo"        
 [5] "org.Hs.eg_dbschema"       "org.Hs.eg.db"            
 [7] "org.Hs.egACCNUM"          "org.Hs.egACCNUM2EG"      
 [9] "org.Hs.egALIAS2EG"        "org.Hs.egCHR"            
[11] "org.Hs.egCHRLENGTHS"      "org.Hs.egCHRLOC"         
[13] "org.Hs.egCHRLOCEND"       "org.Hs.egENSEMBL"        
[15] "org.Hs.egENSEMBL2EG"      "org.Hs.egENSEMBLPROT"    
[17] "org.Hs.egENSEMBLPROT2EG"  "org.Hs.egENSEMBLTRANS"   
[19] "org.Hs.egENSEMBLTRANS2EG" "org.Hs.egENZYME"         
[21] "org.Hs.egENZYME2EG"       "org.Hs.egGENENAME"       
[23] "org.Hs.egGENETYPE"        "org.Hs.egGO"             
[25] "org.Hs.egGO2ALLEGS"       "org.Hs.egGO2EG"          
[27] "org.Hs.egMAP"             "org.Hs.egMAP2EG"         
[29] "org.Hs.egMAPCOUNTS"       "org.Hs.egOMIM"           
[31] "org.Hs.egOMIM2EG"         "org.Hs.egORGANISM"       
[33] "org.Hs.egPATH"            "org.Hs.egPATH2EG"        
[35] "org.Hs.egPFAM"            "org.Hs.egPMID"           
[37] "org.Hs.egPMID2EG"         "org.Hs.egPROSITE"        
[39] "org.Hs.egREFSEQ"          "org.Hs.egREFSEQ2EG"      
[41] "org.Hs.egSYMBOL"          "org.Hs.egSYMBOL2EG"      
[43] "org.Hs.egUCSCKG"          "org.Hs.egUNIPROT"        

To learn which kinds of data are retrievable via select(), list the available columns:

Code
columns(org.Hs.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT" 
 [5] "ENSEMBLTRANS" "ENTREZID"     "ENZYME"       "EVIDENCE"    
 [9] "EVIDENCEALL"  "GENENAME"     "GENETYPE"     "GO"          
[13] "GOALL"        "IPI"          "MAP"          "OMIM"        
[17] "ONTOLOGY"     "ONTOLOGYALL"  "PATH"         "PFAM"        
[21] "PMID"         "PROSITE"      "REFSEQ"       "SYMBOL"      
[25] "UCSCKG"       "UNIPROT"     

You can retrieve help for a particular column with, e.g., help("GO").

Query keys are available through keytypes():

Code
keytypes(org.Hs.eg.db)
 [1] "ACCNUM"       "ALIAS"        "ENSEMBL"      "ENSEMBLPROT" 
 [5] "ENSEMBLTRANS" "ENTREZID"     "ENZYME"       "EVIDENCE"    
 [9] "EVIDENCEALL"  "GENENAME"     "GENETYPE"     "GO"          
[13] "GOALL"        "IPI"          "MAP"          "OMIM"        
[17] "ONTOLOGY"     "ONTOLOGYALL"  "PATH"         "PFAM"        
[21] "PMID"         "PROSITE"      "REFSEQ"       "SYMBOL"      
[25] "UCSCKG"       "UNIPROT"     

Get keys of a particular type with keys() — for example the first gene symbols, OMIM entries, and GO entries:

Code
head(keys(org.Hs.eg.db, keytype = "SYMBOL"))
[1] "A1BG"  "A2M"   "A2MP1" "NAT1"  "NAT2"  "NATP" 
Code
head(keys(org.Hs.eg.db, keytype = "OMIM"))
[1] "138670" "103950" "108345" "243400" "612182" "107280"
Code
head(keys(org.Hs.eg.db, keytype = "GO"))
[1] "GO:0002764" "GO:0005576" "GO:0005615" "GO:0005886" "GO:0031093"
[6] "GO:0034774"

Now define a general query on the org.Hs.eg.db object through select().2 You specify: keys (what to look up), columns (what to return), and keytype (the type of key you pass in). For example, query all Gene Ontology entries for coagulation Factor V, whose gene symbol is F5:

Code
select(org.Hs.eg.db, keys = "F5",
       columns = c("GO", "GENENAME"), keytype = "SYMBOL")

select() scales to the whole genome, and the shape of the result teaches you something about annotation itself. As a worked example, count how many OMIM records each UniProt accession links to, and look at the distribution (Figure 2.1): most proteins carry none or a few cross-references, while a long tail of heavily curated proteins links to dozens of OMIM entries.

Code
library(dplyr)
omim_uniprot <- AnnotationDbi::select(org.Hs.eg.db,
                       keys = keys(org.Hs.eg.db, keytype = "OMIM"),
                       columns = "UNIPROT", keytype = "OMIM") |>
  filter(!is.na(UNIPROT)) |>
  distinct(OMIM, UNIPROT) |>
  count(UNIPROT, name = "n_omim")

all_uniprot <- data.frame(UNIPROT = keys(org.Hs.eg.db, keytype = "UNIPROT")) |>
  left_join(omim_uniprot, by = "UNIPROT")
all_uniprot$n_omim[is.na(all_uniprot$n_omim)] <- 0

ggplot(all_uniprot, aes(n_omim)) +
  geom_histogram(bins = 19, fill = "#0e8d7c") +
  scale_y_log10() +
  labs(x = "OMIM cross-references per UniProt accession",
       y = "proteins (log scale)")
Histogram with a tall spike at zero cross-references and a long right tail up to 18 OMIM entries per protein.
Figure 2.1: Number of distinct OMIM identifiers cross-referenced per UniProt accession (log scale).
NoteA note on name clashes

dplyr also has a select() function, and whichever package was loaded last wins. Once library(dplyr) has run, call the annotation query with its full name AnnotationDbi::select(...) (or org.Hs.eg.db’s own select) to be unambiguous — this is the standard way R resolves name conflicts between packages.

Tip✅ Exercise 2.7 — org.Hs.eg.db

Using the genome-wide annotation for Human package (org.Hs.eg.db), work through these questions in order — each builds on the previous one. As you go, keep a single tidy data frame of OMIM × UniProt (or OMIM × SYMBOL) pairs and reuse it rather than re-querying the package:

  1. How many distinct OMIM identifiers does the annotation package contain? (Note that OMIM identifiers can represent gene and phenotype records.)
  2. What fraction of the UniProt accessions in org.Hs.eg.db have at least one OMIM cross-reference? And what fraction of gene symbols (SYMBOL) do?
  3. List the five proteins linked to the largest number of distinct OMIM identifiers, with their gene names. What do these proteins have in common?
  4. Compute the number of genes (SYMBOL) and the number of proteins (UNIPROT) in the package. Why do the two counts differ — and why is neither count “the number of genes in the human genome”?
  5. Build a table of the gene–protein correspondence. Which are the 10 genes with the largest number of related proteins? Find all UniProt IDs for the first gene in that list, and explain why it has so many.

Start from all OMIM keys, and build the OMIM × UniProt cross-reference once:

Code
library(dplyr)
omim <- keys(org.Hs.eg.db, keytype = "OMIM")
length(omim)                                         # 1. number of OMIM entries
[1] 24131
Code
uniprot <- keys(org.Hs.eg.db, keytype = "UNIPROT")
g <- AnnotationDbi::select(org.Hs.eg.db, keys = omim,
                           columns = "UNIPROT", keytype = "OMIM") |>
  filter(!is.na(UNIPROT)) |>
  distinct(OMIM, UNIPROT)
# 2. fraction of annotated proteins with at least one OMIM cross-reference
n_distinct(g$UNIPROT) / length(uniprot)
[1] 0.9131682

So about 91 % of UniProt accessions carry at least one OMIM link — OMIM coverage of reviewed, disease curated proteins is extensive, but a 10 % tail has none (housekeeping and poorly characterised proteins).

Code
# 3. proteins associated with the most OMIM identifiers
top <- g |>
  count(UNIPROT, name = "n_omim", sort = TRUE) |>
  slice_head(n = 5)
top |>
  left_join(AnnotationDbi::select(org.Hs.eg.db, keys = top$UNIPROT,
                                  columns = "SYMBOL", keytype = "UNIPROT"),
            by = "UNIPROT")

They are all COL2A1 isoforms — collagen II is heavily cross-referenced because a large number of skeletal-dysplasia phenotypes map to the same protein product.

Code
# 4. genes vs proteins
n_sym <- length(keys(org.Hs.eg.db, keytype = "SYMBOL"))
n_up  <- length(uniprot)
c(genes = n_sym, proteins = n_up)
   genes proteins 
  193329   116766 
Code
# 5. gene-protein correspondence, top-10 genes by number of proteins
gp <- AnnotationDbi::select(org.Hs.eg.db,
                            keys = keys(org.Hs.eg.db, keytype = "ENTREZID"),
                            columns = c("SYMBOL", "UNIPROT"),
                            keytype = "ENTREZID") |>
  filter(!is.na(SYMBOL), !is.na(UNIPROT)) |>
  distinct()
gp |>
  count(SYMBOL, name = "n_uniprot", sort = TRUE) |>
  slice_head(n = 10)
Code
# all UniProt accessions for the top gene
gp |> filter(SYMBOL == "HLA-DRB1") |> pull(UNIPROT)
  [1] "A0A098GK79" "A0A0A1IBA8" "A0A0A8LFF3" "A0A7G2CTJ8" "A0A7R8NBZ3"
  [6] "A0A7R8NBZ9" "A0A7R8NC96" "A0A7R8ND07" "A0A8F6GU52" "A8K634"    
 [11] "Q56FP5"     "Q5Y7D1"     "A0A7S4PRB8" "A0A7S4U9P6" "D7RIG5"    
 [16] "Q5XKU0"     "A0A455R0R3" "A0A7S4V2F4" "A2BFX2"     "Q56FN8"    
 [21] "Q70Q10"     "X5CF47"     "A0A0A8LDX0" "A0A224MKH6" "A0A455QYV7"
 [26] "A0A6C0NSJ1" "A0A6V7B690" "A0A6V7B8E1" "A0MWF2"     "A0N0W1"    
 [31] "A2ICT1"     "A2TGX3"     "A4F5N0"     "A4ZXA5"     "A4ZXA6"    
 [36] "A4ZY86"     "A5H000"     "A5HKN8"     "A7DZP9"     "A7LA26"    
 [41] "A7UHG2"     "A7X5B1"     "A7X5B7"     "A7X5E0"     "A7X5E6"    
 [46] "A7X5H8"     "A7X5J4"     "A7X5K7"     "A8K098"     "A8YQE9"    
 [51] "A9JPG0"     "B0BK85"     "B0LUZ6"     "B0UYW1"     "B1GWE7"    
 [56] "B2CR03"     "B2LVF9"     "B2NJ29"     "B2ZCY1"     "B3VTP8"    
 [61] "B3VTQ3"     "B5A8Y2"     "B5A8Y3"     "B5B8U0"     "B5B9V5"    
 [66] "B5B9V6"     "B5LZ25"     "B5QSK8"     "B6VCX2"     "B6VEL9"    
 [71] "B7UDB2"     "B9VRA4"     "B9X248"     "C0LAB5"     "D7RIH8"    
 [76] "G0YWD3"     "O02876"     "O02930"     "O19585"     "O19717"    
 [81] "O19718"     "O19739"     "O19788"     "O46699"     "O46793"    
 [86] "O46872"     "O62869"     "O62889"     "O77969"     "O78047"    
 [91] "O78210"     "O98212"     "P01911"     "P01912"     "P01914"    
 [96] "P04229"     "P13758"     "P13759"     "P13760"     "P13761"    
[101] "P20039"     "P79545"     "Q06662"     "Q0PGR5"     "Q0PQ39"    
[106] "Q14280"     "Q14QT2"     "Q155F7"     "Q19AF2"     "Q19K86"    
[111] "Q1AP33"     "Q1G0Z9"     "Q1JRP3"     "Q1KLJ6"     "Q27PR6"    
[116] "Q27PR7"     "Q29673"     "Q29720"     "Q29722"     "Q29734"    
[121] "Q29770"     "Q29771"     "Q29772"     "Q29790"     "Q29792"    
[126] "Q29800"     "Q29806"     "Q29833"     "Q29874"     "Q29875"    
[131] "Q29886"     "Q29968"     "Q29974"     "Q29975"     "Q2A120"    
[136] "Q2HZE5"     "Q2L9H4"     "Q2LE76"     "Q2MF40"     "Q2MJA6"    
[141] "Q2MZ92"     "Q2VQU1"     "Q2YHQ2"     "Q30006"     "Q30108"    
[146] "Q30112"     "Q30115"     "Q30116"     "Q30117"     "Q30120"    
[151] "Q30134"     "Q30142"     "Q30145"     "Q30149"     "Q30159"    
[156] "Q30166"     "Q30167"     "Q30200"     "Q307W5"     "Q31636"    
[161] "Q32MY7"     "Q3HUP9"     "Q3KTM1"     "Q3LA84"     "Q3LA87"    
[166] "Q3LA88"     "Q3LA89"     "Q3LA90"     "Q3LA91"     "Q3LA92"    
[171] "Q3LA93"     "Q3LA94"     "Q3LA95"     "Q3LA96"     "Q3LA97"    
[176] "Q3LA98"     "Q3LA99"     "Q3LAA0"     "Q3LAA1"     "Q3LAA2"    
[181] "Q3MQ60"     "Q3T919"     "Q4PRC3"     "Q4PRC5"     "Q4VZY7"    
[186] "Q53IG1"     "Q56FN9"     "Q56FP1"     "Q56FP2"     "Q56FP3"    
[191] "Q58F52"     "Q5BM92"     "Q5EER6"     "Q5K3W2"     "Q5NDB9"    
[196] "Q5U9W6"     "Q5UBA2"     "Q5UT58"     "Q5W3L4"     "Q5Y7A7"    
[201] "Q5Y7B0"     "Q5Y7B9"     "Q5Y7E9"     "Q5Y7G0"     "Q683P7"    
[206] "Q6REE2"     "Q6T865"     "Q6U387"     "Q701T1"     "Q70GL2"    
[211] "Q70Q85"     "Q768U2"     "Q768U4"     "Q7M2H4"     "Q7YNY9"    
[216] "Q7YP03"     "Q7YP04"     "Q7YQ26"     "Q7YQA3"     "Q7YQA5"    
[221] "Q860D8"     "Q860D9"     "Q860E5"     "Q860H8"     "Q860S0"    
[226] "Q860Z3"     "Q861G6"     "Q861H0"     "Q861H4"     "Q861H5"    
[231] "Q861H7"     "Q861H8"     "Q8HWQ6"     "Q8MH59"     "Q8MH60"    
[236] "Q8WLU3"     "Q8WMA0"     "Q95348"     "Q95383"     "Q95389"    
[241] "Q95461"     "Q95HK1"     "Q95HL0"     "Q95HL1"     "Q95IE3"    
[246] "Q95IG2"     "Q95IT6"     "Q96HZ9"     "Q9BCL7"     "Q9BCP0"    
[251] "Q9BCP1"     "Q9BCP2"     "Q9BCP5"     "Q9BD21"     "Q9BD33"    
[256] "Q9BD40"     "Q9GIK5"     "Q9GIL5"     "Q9GIL6"     "Q9GIP3"    
[261] "Q9GIX8"     "Q9GIX9"     "Q9GIY0"     "Q9GIY1"     "Q9GIY2"    
[266] "Q9GIY3"     "Q9GIY4"     "Q9GJ25"     "Q9GJ56"     "Q9GJ57"    
[271] "Q9GJ58"     "Q9GJ60"     "Q9GJF8"     "Q9GJF9"     "Q9GJG0"    
[276] "Q9MXZ0"     "Q9MXZ5"     "Q9MY13"     "Q9MY45"     "Q9MY56"    
[281] "Q9MYF5"     "Q9TPB6"     "Q9TPW1"     "Q9TPW3"     "Q9TPW9"    
[286] "Q9TPX4"     "Q9TQ37"     "Q9TQ91"     "Q9TQE0"     "Q9UBY1"    
[291] "Q9UIM9"     "Q9UIN0"     "Q9XRX1"     "Q9XRY4"     "Q9XRY5"    
[296] "Q9Y453"     "Q9Y4H7"     "S6B225"     "X5DNQ0"    

The counts differ for two reasons: one gene can map to several UniProt accessions (isoforms, reviewed vs unreviewed entries) and — the bigger effect in the other direction — many SYMBOL keys correspond to pseudogenes, RNA genes, and other loci that have no protein product at all. Neither number is “the number of genes in the human genome”: the package aggregates several databases, each with its own scope. The top-10 list is dominated by the HLA locus: HLA-DRB1 alone has hundreds of accessions because each serotype and allele variant of the highly polymorphic HLA proteins gets its own reviewed UniProt entry.

2.4.2 Live database access with rentrez

org.Hs.eg.db is a local snapshot: it installs once, works offline, and is perfectly reproducible — but it only knows what was true at its release date, and NCBI and Ensembl keep moving. For live queries use the packages that drive the same web APIs you clicked through above: rentrez speaks the NCBI E-utilities behind Entrez, and biomaRt speaks to the Ensembl BioMart behind the Ensembl browser. A pragmatic workflow is to prototype live, then pin a snapshot (package version or API date) for anything you publish.

rentrez follows the web workflow you did by hand: entrez_search() finds records, entrez_summary() fetches their metadata, entrez_fetch() downloads full records. Let us revisit the LDLR gene from Exercise 2.2 — this time from R:

Code
library(rentrez)
ldlr <- entrez_search(db = "gene", term = "LDLR[Gene Name] AND Homo sapiens[Organism]")
ldlr$ids
[1] "3949"
Code
entrez_summary("gene", id = ldlr$ids)
esummary result with 20 items:
 [1] uid                name               description       
 [4] status             currentid          chromosome        
 [7] geneticsource      maplocation        otheraliases      
[10] otherdesignations  nomenclaturesymbol nomenclaturename  
[13] nomenclaturestatus mim                genomicinfo       
[16] geneweight         summary            chrsort           
[19] chrstart           organism          

The summary list behaves like a data frame: extract_from_esummary() pulls the fields you need. Note how the live answer — description, chromosome, coordinates, last update — matches what you found on the web page:

Code
ldlr_sum <- entrez_summary("gene", id = ldlr$ids)
extract_from_esummary(ldlr_sum, c("name", "description", "chromosome"))
$name
[1] "LDLR"

$description
[1] "low density lipoprotein receptor"

$chromosome
[1] "19"

The full record (the equivalent of the web page’s Genomic regions, transcripts, and products table) comes from entrez_fetch():

Code
ldlr_tab <- entrez_fetch("gene", id = ldlr$ids, rettype = "gene_table", retmode = "text")
substr(ldlr_tab, 1, 400)   # first lines of the gene table
[1] "LDLR low density lipoprotein receptor[Homo sapiens]\nGene ID: 3949, updated on 13-Sep-2026\n\n\nReference GRCh38.p14 Primary Assembly NC_000019.10  from: 11089463 to: 11133820\nmRNA transcript variant 1 NM_000527.5, 18 exons,  total annotated spliced exon length: 5173\nprotein isoform 1 precursor NP_000518.1 (CCDS12254.1), 18 coding  exons,  annotated AA length: 860\n\nExon table for  mRNA  NM_000527.5 an"

The same search pattern drives PubMed. The query from Exercise 2.3 becomes an entrez_search() call, and retmax caps how many records come back:

Code
es <- entrez_search(db = "pubmed",
                    term = "COVID-19[Title/Abstract] AND China[Affiliation]",
                    retmax = 3)
es$count               # total number of matching publications
[1] 46621
Code
es$ids                 # the first 3 PMIDs
[1] "42750016" "42747775" "42746260"
Tip✅ Exercise 2.8 — rentrez
  1. Fetch the titles and first authors of the three publications found above (extract_from_esummary() on the PubMed summaries; the fields are title and sortfirstauthor).
  2. The first rentrez query in this section used a search. Repeat it the other way round: fetch the full gene_table record for Entrez Gene ID 3949 directly with entrez_fetch() (hint: id = 3949).
  3. es$count changes daily. Which of the two numbers you computed in this section would you report in a paper, and how would you make it reproducible?
Code
pm <- entrez_summary("pubmed", id = es$ids)
extract_from_esummary(pm, c("sortfirstauthor", "title"))
                42750016                                                                                                         
sortfirstauthor "Cao JK"                                                                                                         
title           "Comparison of prevalence and outcomes of SARS-CoV-2 infection between preterm and full-term childre" [truncated]
                42747775                                                                                                         
sortfirstauthor "Nejatian S"                                                                                                     
title           "Changes in positive mental health before and after the COVID-19 pandemic and its associated sociode" [truncated]
                42746260                                                                                                         
sortfirstauthor "Tadmor CT"                                                                                                      
title           "A conditional threat-regulation model: When deliberative processing of ongoing threat reduces inter" [truncated]
Code
# 2. direct fetch by known ID — no search step
entrez_fetch("gene", id = 3949, rettype = "gene_table", retmode = "text") |>
  substr(1, 200)
[1] "LDLR low density lipoprotein receptor[Homo sapiens]\nGene ID: 3949, updated on 13-Sep-2026\n\n\nReference GRCh38.p14 Primary Assembly NC_000019.10  from: 11089463 to: 11133820\nmRNA transcript variant 1 NM"
  1. Never report es$count bare: it grows with every indexed paper. Report the query string, the database, the date of the query (and ideally the API version), or snapshot the retrieved records so the analysis is fixed.

biomaRt is the Ensembl counterpart (it needs network access and an Ensembl mirror, so it is shown here rather than run). It queries the same Ensembl database you browsed in Exercise 2.4:

library(biomaRt)
mart <- useEnsembl("genes", dataset = "hsapiens_gene_ensembl")
getBM(attributes = c("hgnc_symbol", "chromosome_name", "start_position", "end_position"),
      filters = "hgnc_symbol", values = "LDLR", mart = mart)

2.5 Further reading

Worked solutions for the exercises are in the collapsed callouts below each exercise.

2.6 Report

Write a laboratory report following the structure of this text. It should contain an introduction, the goals of each section, and a description of the results of each part. Include any graphics relevant to making your work understandable. The lab should be delivered within two weeks.


  1. suppressPackageStartupMessages() hides the package start-up messages, which is convenient when generating a document.↩︎

  2. Use mapIds() if you need only a single column.↩︎