Skip to contents

The Challenge

Mass spectrometry-based glycomics and glycoproteomics often yield data with limited structural resolution. For example, MALDI-TOF MS-based glycomics typically provides only m/z values, while many LC-MS/MS-based glycoproteomics workflows identify basic glycan compositions but lack isomer specificity. Mass spectrometry alone frequently fails to distinguish specific monosaccharide isomers (e.g., differentiating Mannose from Galactose), or resolve linkage details (e.g., a1-3 vs b1-4).

In theory, full structural resolution is achievable by combining MS with orthogonal techniques such as enzymatic digestion or NMR; however, these approaches are often resource-intensive and time-consuming. Unfortunately, advanced downstream analyses—such as the glycan biosynthetic pathway reconstruction offered by glyenzy— require fully resolved glycan structures, including specific monosaccharide identities and linkage information.

What is glyanno?

glyanno bridges this gap. This package is designed to maximize the utility of your mass spectrometry data by annotating it with probable biological context derived from knowledge bases. The package features four core functions:

  • mz_to_comp(): Identifies possible glycan compositions from m/z values.
  • comp_to_struc(): Maps glycan compositions to potential glycan structures.
  • enhance_comp(): Refines generic glycan compositions into concrete, monosaccharide-specific compositions.
  • enhance_struc(): Resolves low-resolution glycan structures into high-resolution structures with linkage information.

library(glyanno)
library(glydb)
#> 
#> Attaching package: 'glydb'
#> The following object is masked from 'package:glyanno':
#> 
#>     struc_to_glytoucan

Note: This package relies on glyrepr and glydb. We recommend familiarizing yourself with these packages, especially glyrepr, before using glyanno. Additionally, a basic understanding of IUPAC-condensed notation is recommended for this vignette. You can refer to this tutorial.

Build the annotation cache

After installing or updating glydb, an interactive library(glyanno) call reminds you to build the persistent annotation cache:

The build uses the installed glydb data and saves the result in tools::R_user_dir("glyanno", "cache"), following the R Packages guidance on persistent user data. Calling the build function again reuses the current cache. A changed glydb version makes the cache outdated and triggers another reminder on attachment. Annotation still works if you defer building: the needed views are prepared in memory and discarded when R exits.

The cache contains:

Data Used for
Concrete and generic composition views Composition enhancement and mass annotation
Four structure views: intact/topological × concrete/generic, including parsed graphs Composition-to-structure and structure matching; de novo fallback
Exact and generic composition lookup groups, residue counts, and confidence ranks Candidate selection and stable best-match selection
Species and glycan-type membership; concrete and generic counts Database filtering without reprocessing structures
Floating-part/substituent flags Excluding unsupported candidates with the existing warnings
Residue masses for six built-in dictionaries and counts for custom dictionaries Mass annotation across derivatizations, mass types, charges, and adducts
Structure keys and GlyTouCan accessions Local accession lookup

All views come from glydb’s public getters. Their candidate order and confidence scores are preserved, including when filters are combined. Query-specific graph matching is still performed by glymotif; arbitrary user inputs cannot all be precalculated. Custom databases supplied through the deprecated db argument are not written to disk.

Use these commands to inspect, rebuild, or remove the cache:

A rebuild writes a complete replacement before replacing the previous file, so an interrupted build preserves the previous cache. After validation, the old file is moved to a temporary backup so replacement also works on Windows. A failed replacement restores that backup; a successful replacement removes it. Only one completed cache is kept. A directory lock prevents simultaneous writers. If a process is killed and leaves a lock, the next build reports the lock’s path for manual removal.

Cache inspection and package attachment read only a small metadata header and check the file size. The compressed annotation data are loaded and validated when annotation first needs them, then reused in the session. If the payload is damaged while its header remains valid, use build_glyanno_cache(force = TRUE) to repair it. Caches written in the previous format trigger a rebuild reminder.

For a different storage location, set options(glyanno.cache_dir = "/your/path") before building or annotating. The cache also checks the representation and graph-library versions, R major/minor version, collation locale, and its internal schema to avoid reusing incompatible objects. Restart R after updating dependencies. Development changes to glydb data without a version bump require an explicit rebuild with force = TRUE.

Workflow: M/z -> Composition -> Structure

Let’s demonstrate these functions step-by-step, starting with a single m/z value.

mz_to_comp(406.1325, charge = 1, adduct = "Na+")
#> # A tibble: 7 × 3
#>      mz composition       confidence
#>   <dbl> <glydb_cm>             <dbl>
#> 1  406. Gal(1)GalNAc(1)        5.32 
#> 2  406. Gal(1)GlcNAc(1)        2.71 
#> 3  406. Glc(1)GlcNAc(1)        0.693
#> 4  406. Man(1)GlcNAc(1)        2.08 
#> 5  406. GalNAc(1)L-Gal(1)     -1    
#> 6  406. GalNAc(1)Galf(1)      -1    
#> 7  406. GlcNAc(1)Galf(1)      -1

This returns every composition in glydb matching the m/z of 406.1325. In practice, returning “all” possibilities can be overwhelming; you will often want to constrain the search space based on biological context.

The annotation functions accept filters for the built-in glydb database. For example, you might want to limit the search to only human O-GalNAc glycans.

mz_to_comp(406.1325, charge = 1, adduct = "Na+", species = "Homo sapiens", glycan_type = "O-GalNAc")
#> # A tibble: 1 × 3
#>      mz composition     confidence
#>   <dbl> <glydb_cm>           <dbl>
#> 1  406. Gal(1)GalNAc(1)       5.32

The results are now significantly more relevant to our specific context.

With the composition identified, we can proceed to determine potential structures. The m/z value 406.1325 corresponds to the composition Gal(1)GalNAc(1). What are the possible structures for this composition?

comp_to_struc("Gal(1)GalNAc(1)", species = "Homo sapiens", glycan_type = "O-GalNAc")
#> # A tibble: 3 × 3
#>   composition     structure           confidence
#>   <comp>          <glydb_st>               <dbl>
#> 1 Gal(1)GalNAc(1) Gal(b1-3)GalNAc(a1-       5.32
#> 2 Gal(1)GalNAc(1) Gal(b1-3)GalNAc(b1-       1.10
#> 3 Gal(1)GalNAc(1) Gal(a1-3)GalNAc(a1-       1.61

comp_to_struc() selects structures from glydb using the same filters.

Two structures are possible, one is Core 1, the other is Core 5. Sometimes we just want a “most possible” result. In this case, you can set return_best to TRUE:

comp_to_struc("Gal(1)GalNAc(1)", species = "Homo sapiens", glycan_type = "O-GalNAc", return_best = TRUE)
#> <glydb_structure[1]>
#> [1] Gal(b1-3)GalNAc(a1-
#> # Unique structures: 1

Core 1 is more common than Core 5, so it was kept. The function keeps the structure with most citations for multiple matches.

Note that when return_best = TRUE, the function returns a vector with the same length of the input instead of a tibble. When a glycan has no matches, <NA> will be on the corresponding position.

Note that all functions in glyanno works vectorizedly:

comp_to_struc(c("Gal(1)GalNAc(1)", "GlcNAc(1)GalNAc(1)"), species = "Homo sapiens", glycan_type = "O-GalNAc", return_best = TRUE)
#> <glydb_structure[2]>
#> [1] Gal(b1-3)GalNAc(a1-
#> [2] GlcNAc(b1-3)GalNAc(a1-
#> # Unique structures: 2

If you set return_best to TRUE, the function directly returns a vector:

comp_to_struc(c("Gal(1)GalNAc(1)", "GlcNAc(1)GalNAc(1)"), species = "Homo sapiens", glycan_type = "O-GalNAc", return_best = TRUE)
#> <glydb_structure[2]>
#> [1] Gal(b1-3)GalNAc(a1-
#> [2] GlcNAc(b1-3)GalNAc(a1-
#> # Unique structures: 2

This vector always has the same length as the input, with NA for glycans with no match.

You can also set the structure level used for matching. For example, it is possible to use a database with only topology-level structures without linkage information.

comp_to_struc(
  c("Gal(1)GalNAc(1)", "GlcNAc(1)GalNAc(1)"),
  structure_level = "topological",
  species = "Homo sapiens",
  glycan_type = "O-GalNAc"
)
#> # A tibble: 4 × 3
#>   composition        structure                 confidence
#>   <comp>             <glydb_st>                     <dbl>
#> 1 Gal(1)GalNAc(1)    Gal(??-?)GalNAc(??-             5.32
#> 2 Gal(1)GalNAc(1)    Gal(??-?)GalNAc-ol(??-          3.22
#> 3 GlcNAc(1)GalNAc(1) GlcNAc(??-?)GalNAc(??-          2.77
#> 4 GlcNAc(1)GalNAc(1) GlcNAc(??-?)GalNAc-ol(??-       2.40

Enhancing Compositions and Structures

Researchers often need to refine generic annotations into more specific ones. For instance, you may wish to convert generic compositions into specific monosaccharide lists, or assign potential linkages to a topology-only structure. enhance_comp() and enhance_struc() are designed for this purpose.

Both functions return a tibble with two columns: raw (the original input) and enhanced (the potential high-resolution candidates).

enhance_comp("Hex(1)HexNAc(1)")
#> # A tibble: 7 × 3
#>   raw             enhanced          confidence
#>   <comp>          <glydb_cm>             <dbl>
#> 1 Hex(1)HexNAc(1) Gal(1)GalNAc(1)        5.32 
#> 2 Hex(1)HexNAc(1) Gal(1)GlcNAc(1)        2.71 
#> 3 Hex(1)HexNAc(1) Glc(1)GlcNAc(1)        0.693
#> 4 Hex(1)HexNAc(1) Man(1)GlcNAc(1)        2.08 
#> 5 Hex(1)HexNAc(1) GalNAc(1)L-Gal(1)     -1    
#> 6 Hex(1)HexNAc(1) GalNAc(1)Galf(1)      -1    
#> 7 Hex(1)HexNAc(1) GlcNAc(1)Galf(1)      -1
enhance_struc("Gal(??-?)GalNAc(??-")
#> # A tibble: 9 × 3
#>   raw                 enhanced            confidence
#>   <struct>            <glydb_st>               <dbl>
#> 1 Gal(??-?)GalNAc(??- Gal(b1-3)GalNAc(a1-       5.32
#> 2 Gal(??-?)GalNAc(??- Gal(a1-3)GalNAc(b1-      -1   
#> 3 Gal(??-?)GalNAc(??- Gal(b1-3)GalNAc(b1-       1.10
#> 4 Gal(??-?)GalNAc(??- Gal(b1-4)GalNAc(b1-      -1   
#> 5 Gal(??-?)GalNAc(??- Gal(a1-6)GalNAc(a1-      -1   
#> 6 Gal(??-?)GalNAc(??- Gal(b1-6)GalNAc(a1-      -1   
#> 7 Gal(??-?)GalNAc(??- Gal(b1-6)GalNAc(b1-      -1   
#> 8 Gal(??-?)GalNAc(??- Gal(a1-3)GalNAc(a1-       1.61
#> 9 Gal(??-?)GalNAc(??- Gal(b1-4)GalNAc(a1-      -1

Similarly, filters narrow down results to biologically relevant candidates.

enhance_comp("Hex(1)HexNAc(1)", species = "Homo sapiens", glycan_type = "O-GalNAc")
#> # A tibble: 1 × 3
#>   raw             enhanced        confidence
#>   <comp>          <glydb_cm>           <dbl>
#> 1 Hex(1)HexNAc(1) Gal(1)GalNAc(1)       5.32
enhance_struc("Gal(??-?)GalNAc(??-", species = "Homo sapiens", glycan_type = "O-GalNAc")
#> # A tibble: 3 × 3
#>   raw                 enhanced            confidence
#>   <struct>            <glydb_st>               <dbl>
#> 1 Gal(??-?)GalNAc(??- Gal(b1-3)GalNAc(a1-       5.32
#> 2 Gal(??-?)GalNAc(??- Gal(b1-3)GalNAc(b1-       1.10
#> 3 Gal(??-?)GalNAc(??- Gal(a1-3)GalNAc(a1-       1.61

You can set return_best to TRUE as well.

enhance_struc(
  "Gal(??-?)GalNAc(??-",
  species = "Homo sapiens",
  glycan_type = "O-GalNAc",
  return_best = TRUE
)
#> <glycan_structure[1]>
#> [1] Gal(b1-3)GalNAc(a1-
#> # Unique structures: 1

De novo enhancement of N-glycans

N-glycans have many possible branching patterns, but databases of human- detected glycans cover only a fraction of this potential structure space. enhance_struc_denovo() reconstructs generic topological N-glycans from their conserved core and branches, returning one concrete topological candidate per input. It preserves optional core fucose and bisecting GlcNAc residues. Inputs that cannot be reconstructed are matched against a topological fallback database.

glycan <- "NeuAc(??-?)Hex(??-?)HexNAc(??-?)Hex(??-?)HexNAc(??-?)Hex(??-?)[HexNAc(??-?)[NeuAc(??-?)]Hex(??-?)HexNAc(??-?)Hex(??-?)]Hex(??-?)HexNAc(??-?)[dHex(??-?)]HexNAc(??-"
enhance_struc_denovo(glycan)
#> <glycan_structure[1]>
#> [1] Neu5Ac(??-?)Gal(??-?)GlcNAc(??-?)Gal(??-?)GlcNAc(??-?)Man(??-?)[Neu5Ac(??-?)[GalNAc(??-?)]Gal(??-?)GlcNAc(??-?)Man(??-?)]Man(??-?)GlcNAc(??-?)[Fuc(??-?)]GlcNAc(??-
#> # Unique structures: 1

All non-missing inputs to enhance_struc_denovo() must contain only generic residues and must be topological. Missing values are allowed and preserved.

Bidirectional Conversion

While glyanno focuses on inferring high-resolution information from low-resolution data, the glycoverse ecosystem also provides complementary functions to perform the reverse operations—simplifying detailed structures back to their lower-resolution representations.

A summary of these relationships:

Enhance Back
mz_to_comp() calculate_mz()
comp_to_struc() glyrepr::as_glycan_composition()
enhance_comp() glyrepr::convert_to_generic()
enhance_struc() glyrepr::remove_linkages() and glyrepr::convert_to_generic()