Skip to contents

biobouncer validates biological identifiers and inputs. It answers one question, “is this a valid identifier?”, with the same verdict in R and Python. This vignette walks through the sources, the checking modes, and the validation adapters using only offline data, so every chunk here runs without a network.

What can be checked

sources() lists the source databases biobouncer knows about, and source_info() returns a table with a short description of each.

sources()
#>  [1] "bto"             "cdd"             "chebi"           "chembl"         
#>  [5] "cl"              "clinvar"         "complexportal"   "cosmic"         
#>  [9] "dbsnp"           "doid"            "drugbank"        "ec"             
#> [13] "eco"             "efo"             "ensembl"         "go"             
#> [17] "hgnc"            "hgvs"            "hp"              "inchikey"       
#> [21] "interpro"        "mirbase"         "mirbase_hairpin" "mondo"          
#> [25] "mp"              "ncbifam"         "ncbitaxon"       "ncit"           
#> [29] "opentargets"     "orphanet"        "panther"         "pato"           
#> [33] "pdb"             "pfam"            "pharmgkb"        "prints"         
#> [37] "prosite"         "reactome"        "refseq"          "rfam"           
#> [41] "smart"           "so"              "uberon"          "uniparc"        
#> [45] "uniprot"         "wikipathways"

Pattern mode: is the string well-formed?

pattern mode is offline and deterministic. It checks the shape of an identifier against the source’s pattern. It does not check that the identifier exists.

check_id() returns a tibble with one row per input. Note that it preserves the order and length of the input, and never collapses an invalid value into a bare FALSE: the row tells you why.

check_id(
  c("MONDO:0005148", "mondo:5148", "GO:0006915"),
  source_db = "mondo"
)
#> # A tibble: 3 × 9
#>   input        valid normalized suggestion source_db version species how   error
#>   <chr>        <lgl> <chr>      <chr>      <chr>     <chr>   <chr>   <chr> <chr>
#> 1 MONDO:00051… TRUE  MONDO:000… NA         mondo     NA      NA      patt… NA   
#> 2 mondo:5148   FALSE NA         MONDO:000… mondo     NA      NA      patt… NA   
#> 3 GO:0006915   FALSE NA         NA         mondo     NA      NA      patt… NA

The normalized column holds the canonical form of a valid input. The suggestion column holds a best-effort correction for an invalid but mappable input, such as the lowercase prefix above.

For just the verdict, use is_valid_id(), which returns a logical vector.

is_valid_id(c("P04637", "p04637"), source_db = "uniprot")
#> [1]  TRUE FALSE

Cache mode: does the identifier exist in a snapshot?

cache mode checks existence against a pinned, offline snapshot. A small sample snapshot ships with the package, so the examples below need no downloads. A real analysis pins a dated snapshot instead.

check_id(
  c("MONDO:0005148", "MONDO:9999999"),
  source_db = "mondo",
  how = "cache",
  version = "sample"
)
#> # A tibble: 2 × 9
#>   input        valid normalized suggestion source_db version species how   error
#>   <chr>        <lgl> <chr>      <chr>      <chr>     <chr>   <chr>   <chr> <chr>
#> 1 MONDO:00051… TRUE  MONDO:000… NA         mondo     sample  NA      cache NA   
#> 2 MONDO:99999… FALSE NA         NA         mondo     sample  NA      cache NA

MONDO:9999999 is well-formed, so it passes pattern mode, but it is not in the snapshot, so it fails cache mode. Choosing the mode is choosing how strict you want the check to be.

Snapshots are managed with a few helpers:

biobouncer_snapshots()
#> # A tibble: 14 × 4
#>    source version    n_ids location
#>    <chr>  <chr>      <int> <chr>   
#>  1 bto    sample         7 bundled 
#>  2 chebi  sample         5 bundled 
#>  3 cl     sample         7 bundled 
#>  4 doid   sample         7 bundled 
#>  5 efo    sample         5 bundled 
#>  6 go     sample         6 bundled 
#>  7 hgnc   2026-07-07 45019 bundled 
#>  8 hgnc   sample         7 bundled 
#>  9 hp     sample         7 bundled 
#> 10 mondo  sample         6 bundled 
#> 11 mp     sample         7 bundled 
#> 12 pato   sample         7 bundled 
#> 13 so     sample         7 bundled 
#> 14 uberon sample         7 bundled

Remote mode

remote mode checks existence live against a source API. It needs a network, so it is not run in this vignette, but the call looks like this:

# Live check against the Ensembl REST API.
check_id("ENSG00000139618", source_db = "ensembl", how = "remote")

# existence mode uses a snapshot when one is available and otherwise
# falls back to remote.
check_id("MONDO:0005148", source_db = "mondo", how = "existence")

A network failure in remote mode raises an error. It never returns a silent FALSE, so a failed lookup cannot be mistaken for an absent identifier.

Species and version awareness

Some sources are species-aware. For Ensembl, the species is encoded in the id, so pattern mode can reject a well-formed id that belongs to the wrong species.

# ENSMUSG is a mouse gene id.
is_valid_id("ENSMUSG00000059552", source_db = "ensembl", species = "mus_musculus")
#> [1] TRUE
is_valid_id("ENSMUSG00000059552", source_db = "ensembl", species = "homo_sapiens")
#> [1] FALSE

species accepts a name such as "homo_sapiens" or an NCBI taxon id such as 9606. A source that is not species-aware ignores the argument.

HGVS variant syntax

The hgvs source checks the syntax of an HGVS sequence variant name. This is a grammar check in pattern mode. It confirms the shape of the variant. It does not check coordinates or that the variant exists.

is_valid_id(
  c(
    "NM_004006.2:c.4375C>T",
    "NP_003997.1:p.(Gly56Ala)",
    "NM_004006.2:c.76insG"
  ),
  source_db = "hgvs"
)
#> [1]  TRUE  TRUE FALSE

The last one is invalid: an insertion must sit between two flanking positions, so it needs a range such as c.76_77insG.

Clean a column

The most common job is not “is this one id valid” but “here is a column, which values are wrong, and can you fix the ones you can”. report_id() runs a check over a whole column and returns a table that prints with a one-line summary of how many values are valid, repairable, invalid, or missing.

genes <- c("TP53", "MLL", "notagene", NA)
report_id(genes, "hgnc", how = "cache")
#> # biobouncer report on hgnc (cache mode): 1 valid, 1 repairable, 1 invalid, 1 missing of 4
#> # A tibble: 4 × 9
#>   input    valid normalized suggestion source_db version    species how   error
#>   <chr>    <lgl> <chr>      <chr>      <chr>     <chr>      <chr>   <chr> <chr>
#> 1 TP53     TRUE  TP53       NA         hgnc      2026-07-07 NA      cache NA   
#> 2 MLL      FALSE NA         KMT2A      hgnc      2026-07-07 NA      cache NA   
#> 3 notagene FALSE NA         NA         hgnc      2026-07-07 NA      cache NA   
#> 4 NA       NA    NA         NA         hgnc      2026-07-07 NA      cache NA

MLL is a withdrawn gene symbol, so it is invalid but repairable: its successor is KMT2A. repair_id() substitutes the fixable values and leaves valid, unmappable, and missing values untouched, so it keeps the column’s length and order and drops into dplyr::mutate():

repair_id(genes, "hgnc", how = "cache")
#> [1] "TP53"     "KMT2A"    "notagene" NA

Calling summary() on a report returns the counts as a one-row table.

Validation framework adapters

The adapters wrap the core classifier so it plugs into common validation frameworks. They never reimplement any checks. The checkmate-style adapters work out of the box:

# TRUE when all are valid, otherwise a message.
check_valid_id(c("MONDO:0005148", "mondo:5148"), "mondo")
#> [1] "Must be valid mondo identifiers (pattern mode), but 1 of 2 failed, for example 'mondo:5148'"

# A single logical.
test_valid_id("MONDO:0005148", "mondo")
#> [1] TRUE

id_predicate() returns an elementwise predicate for data-frame validation with assertr or validate:

is_mondo <- id_predicate("mondo")
ids <- c("MONDO:0005148", "mondo:5148", "MONDO:0018076")
ids[is_mondo(ids)]
#> [1] "MONDO:0005148" "MONDO:0018076"

For Shiny apps, sv_biobouncer() returns a shinyvalidate rule. The rule returns NULL for a valid input and a message otherwise:

rule <- sv_biobouncer("mondo")
rule("MONDO:0005148")
#> NULL
rule("mondo:5148")
#> [1] "Not a valid mondo identifier"

Generating test data

synthesize_ids() builds a labeled “messy column” for any source, so you can exercise a validation pipeline without hand-writing test ids. Each row carries the input, its category (valid, repairable, invalid, or missing), and the verdict fields the checker returned for it.

rows <- synthesize_ids("mondo")
rows[, c("input", "category", "suggestion")]
#> # A tibble: 5 × 3
#>   input          category   suggestion   
#>   <chr>          <chr>      <chr>        
#> 1 MONDO:0005148  valid      NA           
#> 2 mondo:0005148  repairable MONDO:0005148
#> 3 MONDO:0005148! invalid    NA           
#> 4 NA             missing    NA           
#> 5 MONDO:0005149  valid      NA

# Feed the column straight into a report.
report_id(rows$input, "mondo")
#> # biobouncer report on mondo (pattern mode): 2 valid, 1 repairable, 1 invalid, 1 missing of 5
#> # A tibble: 5 × 9
#>   input        valid normalized suggestion source_db version species how   error
#>   <chr>        <lgl> <chr>      <chr>      <chr>     <chr>   <chr>   <chr> <chr>
#> 1 MONDO:00051… TRUE  MONDO:000… NA         mondo     NA      NA      patt… NA   
#> 2 mondo:00051… FALSE NA         MONDO:000… mondo     NA      NA      patt… NA   
#> 3 MONDO:00051… FALSE NA         NA         mondo     NA      NA      patt… NA   
#> 4 NA           NA    NA         NA         mondo     NA      NA      patt… NA   
#> 5 MONDO:00051… TRUE  MONDO:000… NA         mondo     NA      NA      patt… NA

The column is deterministic and offline, and the Python synthesize() produces the same one. ec, hgvs, and hgnc have no repairable form, so they omit that category. Pass how = "cache" for a snapshot-mode column, where a repairable value can be a retired id that maps to its successor.

Summary

  • pattern mode checks shape, cache and remote check existence, and existence uses a snapshot first and remote as a fallback.
  • report_id() and repair_id() validate and clean a whole column in one call.
  • Results are vectorized and preserve input order and length. Errors are explicit.
  • The same inputs give the same verdicts in the Python package, which is enforced by a shared conformance corpus.