Skip to contents

The Ontology Lookup Service (‘OLS’) is a repository of biomedical ontologies provided by the European Bioinformatics Institute (EMBL-EBI). OLSr provides an interface to the OLS service, allowing discovery of ontologies, terms, and relations. Results are presented in a ‘tidy’ framework, so that manipulation tasks are easily accomplished with verbs such as ‘filter()’, ‘select()’, and ‘mutate()’. An effort is made to integrate with other packages useful for more comprehensive biological and ontological analysis.

Written: 20 November, 2023

Installation

Install OLSr from GitHub using the remotes package.

if (!"remotes" in rownames(installed.packages()))
    install.packages("remotes", repos = "https://cran.r-project.org")
remotes::install_github("mtmorgan/OLSr")

Attach the library to the current session. Most functions in OLSr return results as a tibble, so the dplyr package is also attached.

Ontologies

Start by discovering ontologies available in the OLS.

onto <- get_ontologies()
onto
#> # A tibble: 249 × 31
#>    id        title          description version numberOfTerms numberOfProperties
#>    <chr>     <chr>          <chr>       <chr>           <int>              <int>
#>  1 ado       Alzheimer's D… Alzheimer'… 2.0.1            1963                186
#>  2 agro      Agronomy Onto… AgrO is an… NA               4162                293
#>  3 aism      Ontology for … The ontolo… 2023-0…          7443                547
#>  4 amphx     Amphioxus Dev… An ontolog… NA                403                 10
#>  5 apo       Ascomycete Ph… A structur… 2023-1…           619                 27
#>  6 apollo_sv Apollo Struct… An OWL2 on… 2023-0…          1691                366
#>  7 aro       Antibiotic Re… Antibiotic… NA               7105                 25
#>  8 bco       Biological Co… An ontolog… 2021-1…           253                472
#>  9 bfo       Basic Formal … The upper … NA                 35                 22
#> 10 bspo      Biological Sp… An ontolog… 2023-0…           169                236
#> # ℹ 239 more rows
#> # ℹ 25 more variables: numberOfIndividuals <int>, languages <list>,
#> #   loaded <chr>, updated <chr>, versionIri <chr>, namespace <chr>,
#> #   preferredPrefix <chr>, homepage <chr>, mailingList <chr>, tracker <chr>,
#> #   logo <lgl>, creators <lgl>, annotations <lgl>, fileLocation <chr>,
#> #   oboSlims <lgl>, labelProperty <chr>, definitionProperties <list>,
#> #   synonymProperties <list>, hierarchicalProperties <list>, baseUris <list>, …

This vignette uses for examples the ontology with title ‘Cell Ontology’, used by the CELLxGENE project to classify cell types. Find the record corresponding to this ontology, using glimpse() to provide a convenient view of the ontology description.

onto |>
    filter(title == "Cell Ontology" ) |>
    glimpse()
#> Rows: 1
#> Columns: 31
#> $ id                     <chr> "cl"
#> $ title                  <chr> "Cell Ontology"
#> $ description            <chr> "An ontology of cell types."
#> $ version                <chr> "2023-10-19"
#> $ numberOfTerms          <int> 16147
#> $ numberOfProperties     <int> 531
#> $ numberOfIndividuals    <int> 18
#> $ languages              <list> ["en"]
#> $ loaded                 <chr> "2023-11-27T11:39:31.284869834"
#> $ updated                <chr> "2023-11-27T11:39:31.284869834"
#> $ versionIri             <chr> "http://purl.obolibrary.org/obo/cl/releases/202…
#> $ namespace              <chr> "cl"
#> $ preferredPrefix        <chr> "CL"
#> $ homepage               <chr> "https://obophenotype.github.io/cell-ontology/"
#> $ mailingList            <chr> "https://groups.google.com/g/cl_edit"
#> $ tracker                <chr> "https://github.com/obophenotype/cell-ontology/…
#> $ logo                   <lgl> NA
#> $ creators               <lgl> NA
#> $ annotations            <lgl> NA
#> $ fileLocation           <chr> "http://purl.obolibrary.org/obo/cl.owl"
#> $ oboSlims               <lgl> FALSE
#> $ labelProperty          <chr> "http://www.w3.org/2000/01/rdf-schema#label"
#> $ definitionProperties   <list> <NULL>
#> $ synonymProperties      <list> <NULL>
#> $ hierarchicalProperties <list> <NULL>
#> $ baseUris               <list> <NULL>
#> $ hiddenProperties       <list> <NULL>
#> $ preferredRootTerms     <lgl> NA
#> $ isSkos                 <lgl> FALSE
#> $ allowDownload          <lgl> FALSE
#> $ `_links`               <list> [["https://www.ebi.ac.uk/ols4/api/ontologies/cl…

Important information includes the ‘id’ of the ontology (used in many subsequent steps), the ontology home page, and the number of terms in the ontology.

onto |>
    filter(title == "Cell Ontology") |>
    select(id, homepage, numberOfTerms)
#> # A tibble: 1 × 3
#>   id    homepage                                      numberOfTerms
#>   <chr> <chr>                                                 <int>
#> 1 cl    https://obophenotype.github.io/cell-ontology/         16147

Information on individual ontologies is also available with get_ontology(); this record includes a link to the ontology page in the OLS. The home page is a good starting place for understanding the ontology in a more visual and interactive environment.

cl <- get_ontology("cl")
glimpse(cl)
#> Rows: 1
#> Columns: 16
#> $ languages           <list> "en"
#> $ lang                <chr> "en"
#> $ ontologyId          <chr> "cl"
#> $ loaded              <chr> "2023-11-27T11:39:31.284869834"
#> $ updated             <chr> "2023-11-27T11:39:31.284869834"
#> $ status              <chr> "LOADED"
#> $ message             <chr> ""
#> $ version             <chr> "2023-10-19"
#> $ fileHash            <lgl> NA
#> $ loadAttempts        <int> 0
#> $ numberOfTerms       <int> 16147
#> $ numberOfProperties  <int> 531
#> $ numberOfIndividuals <int> 18
#> $ config              <list> "cl"
#> $ baseUris            <list> <NULL>
#> $ `_links`            <list> ["https://www.ebi.ac.uk/ols4/api/ontologies/cl?la…

cl |> pull("_links")
#> [[1]]
#> [[1]]$href
#> [1] "https://www.ebi.ac.uk/ols4/api/ontologies/cl?lang=en"

Terms

Terms are central to using an ontology. Retrieve all terms from the ‘cl’ ontology. By default, only terms defined in the ontology per se are used; add the argument all_ontologies = TRUE to this and subsequent functions to also include terms referenced in other ontologies. This query can take several minutes to complete when first executed, but the result is cached locally (as discussed in greater detail below) so subsequent use is quick.

terms <- get_terms("cl")
terms
#> # A tibble: 2,734 × 13
#>    label  obo_id description iri   synonyms annotation   has_children short_form
#>    <chr>  <chr>  <chr>       <chr> <list>   <list>       <lgl>        <chr>     
#>  1 nonke… CL:00… A nonkerat… http… <NULL>   <named list> TRUE         CL_0002635
#>  2 nonke… CL:00… A nonkerat… http… <NULL>   <named list> FALSE        CL_0002636
#>  3 kerat… CL:00… An epithel… http… <NULL>   <named list> FALSE        CL_0002637
#>  4 bronc… CL:00… A respirat… http… <list>   <named list> FALSE        CL_0002638
#>  5 amnio… CL:00… An amnioti… http… <NULL>   <named list> FALSE        CL_0002639
#>  6 amnio… CL:00… An epithel… http… <NULL>   <named list> FALSE        CL_0002640
#>  7 epith… CL:00… An epithel… http… <NULL>   <named list> FALSE        CL_0002641
#>  8 epith… CL:00… An epithel… http… <NULL>   <named list> FALSE        CL_0002642
#>  9 nonke… CL:00… An epithel… http… <NULL>   <named list> FALSE        CL_0002643
#> 10 endo-… CL:00… An endothe… http… <NULL>   <named list> FALSE        CL_0002644
#> # ℹ 2,724 more rows
#> # ℹ 5 more variables: in_subset <list>, obo_definition_citation <list>,
#> #   obo_xref <list>, obo_synonym <list>, `_links` <list>

Glimpse the second term to get a sense of available information.

terms |>
    slice(1) |>
    glimpse()
#> Rows: 1
#> Columns: 13
#> $ label                   <chr> "nonkeratinized epithelial cell of anal column"
#> $ obo_id                  <chr> "CL:0002635"
#> $ description             <chr> "A nonkeratinized epithelial cell of the anal …
#> $ iri                     <chr> "http://purl.obolibrary.org/obo/CL_0002635"
#> $ synonyms                <list> <NULL>
#> $ annotation              <list> [["https://orcid.org/0000-0003-1980-3228"], ["…
#> $ has_children            <lgl> TRUE
#> $ short_form              <chr> "CL_0002635"
#> $ in_subset               <list> <NULL>
#> $ obo_definition_citation <list> [["A nonkeratinized epithelial cell of the ana…
#> $ obo_xref                <list> [["FMA", "263146", <NULL>, "http://purl.org/si…
#> $ obo_synonym             <list> <NULL>
#> $ `_links`                <list> [["https://www.ebi.ac.uk/ols4/api/ontologies/…

Hierarchical data

Some columns in the terms table represent hieararchical (relational, 1:many mapping) data, e.g., the in_subset column

terms |>
    select(label, obo_id, in_subset)
#> # A tibble: 2,734 × 3
#>    label                                                        obo_id in_subset
#>    <chr>                                                        <chr>  <list>   
#>  1 nonkeratinized epithelial cell of anal column                CL:00… <NULL>   
#>  2 nonkeratinized epithelial cell of inferior part of anal can… CL:00… <NULL>   
#>  3 keratinized epithelial cell of the anal canal                CL:00… <NULL>   
#>  4 bronchioalveolar stem cell                                   CL:00… <NULL>   
#>  5 amniotic stem cell                                           CL:00… <NULL>   
#>  6 amniotic epithelial stem cell                                CL:00… <NULL>   
#>  7 epithelial cell of esophageal gland proper                   CL:00… <NULL>   
#>  8 epithelial cell of esophageal cardiac gland                  CL:00… <NULL>   
#>  9 nonkeratinized cell of stratum corneum of esophageal epithe… CL:00… <NULL>   
#> 10 endo-epithelial cell of tympanic part of viscerocranial muc… CL:00… <NULL>   
#> # ℹ 2,724 more rows

Individual terms apparently belong to 0 or more subsets.

terms |>
    count(lengths(in_subset))
#> # A tibble: 4 × 2
#>   `lengths(in_subset)`     n
#>                  <int> <int>
#> 1                    0  2091
#> 2                    1   579
#> 3                    2    61
#> 4                    3     3

The R for Data Science chapter on hierarchical data suggests using tidyr functions unnest_wider() and unnest_longer() to expand such data.

terms |>
    select(in_subset, obo_id, label) |>
    tidyr::unnest_longer(in_subset)
#> # A tibble: 710 × 3
#>    in_subset                   obo_id     label                                 
#>    <chr>                       <chr>      <chr>                                 
#>  1 human_reference_atlas       CL:0002652 endothelial cell of high endothelial …
#>  2 human_reference_atlas       CL:0002677 naive regulatory T cell               
#>  3 blood_and_immune_upper_slim CL:0002679 natural helper lymphocyte             
#>  4 human_reference_atlas       CL:0004219 A2 amacrine cell                      
#>  5 human_reference_atlas       CL:0004232 starburst amacrine cell               
#>  6 human_reference_atlas       CL:0004252 medium field retinal amacrine cell    
#>  7 human_reference_atlas       CL:0004253 wide field retinal amacrine cell      
#>  8 human_reference_atlas       CL:0005006 ionocyte                              
#>  9 human_reference_atlas       CL:0005019 pancreatic epsilon cell               
#> 10 human_reference_atlas       CL:0008019 mesenchymal cell                      
#> # ℹ 700 more rows

Defined subsets and the number of terms in each can be computed as

terms |>
    tidyr::unnest_longer(in_subset) |>
    count(in_subset, sort = TRUE)
#> # A tibble: 8 × 2
#>   in_subset                         n
#>   <chr>                         <int>
#> 1 human_reference_atlas           486
#> 2 location_grouping                65
#> 3 BDS_subset                       64
#> 4 blood_and_immune_upper_slim      36
#> 5 eye_upper_slim                   24
#> 6 general_cell_types_upper_slim    24
#> 7 added_for_HCA                     8
#> 8 _upper_level                      3

Use filter() with a helper function that summarizes subsets of individual terms to identify the human_reference_atlas subset. Using vapply() (rather than, e.g., sapply()) can be considered a best practice, so that the filter function is guaranteed to return a scalar logical value regardless of input length.

is_human_reference_atlas <- function(x)
    vapply(x, \(x) any(x == "human_reference_atlas"), logical(1))

human_reference_atlas <-
    terms |>
    filter(is_human_reference_atlas(in_subset))

human_reference_atlas
#> # A tibble: 486 × 13
#>    label  obo_id description iri   synonyms annotation   has_children short_form
#>    <chr>  <chr>  <chr>       <chr> <list>   <list>       <lgl>        <chr>     
#>  1 endot… CL:00… A venule e… http… <list>   <named list> FALSE        CL_0002652
#>  2 naive… CL:00… A regulato… http… <list>   <named list> TRUE         CL_0002677
#>  3 A2 am… CL:00… A bistrati… http… <NULL>   <named list> FALSE        CL_0004219
#>  4 starb… CL:00… An amacrin… http… <NULL>   <named list> FALSE        CL_0004232
#>  5 mediu… CL:00… An amicrin… http… <NULL>   <named list> TRUE         CL_0004252
#>  6 wide … CL:00… An amicrin… http… <NULL>   <named list> TRUE         CL_0004253
#>  7 ionoc… CL:00… Specialize… http… <NULL>   <named list> TRUE         CL_0005006
#>  8 pancr… CL:00… Ghrelin se… http… <list>   <named list> FALSE        CL_0005019
#>  9 mesen… CL:00… A non-pola… http… <list>   <named list> TRUE         CL_0008019
#> 10 pancr… CL:00… An endocri… http… <NULL>   <named list> TRUE         CL_0008024
#> # ℹ 476 more rows
#> # ℹ 5 more variables: in_subset <list>, obo_definition_citation <list>,
#> #   obo_xref <list>, obo_synonym <list>, `_links` <list>

There are 486 terms in the human_reference_atlas subset.

Individual terms

Suppose we are interested in B cells. Discover term labels starting with "B cell", selecting useful fields for display. For subsequent work, create a variable capturing the obo_id of the "B cell" term.

terms |>
    filter(startsWith(label, "B cell")) |>
    select(label, obo_id, description)
#> # A tibble: 6 × 3
#>   label                                   obo_id     description                
#>   <chr>                                   <chr>      <chr>                      
#> 1 B cell of appendix                      CL:0009032 A B cell that is located i…
#> 2 B cell of medullary sinus of lymph node CL:0009045 A B cell found in the lymp…
#> 3 B cell of anorectum                     CL:0009050 A B cell that is located i…
#> 4 B cell zone reticular cell              CL:0009104 A fibroblastic reticular c…
#> 5 B cell                                  CL:0000236 A lymphocyte of B lineage …
#> 6 B cell, CD19-positive                   CL:0001201 A B cell that is CD19-posi…
b_cell <- # "CL:0000236"
    terms |>
    filter(label == "B cell") |>
    pull(obo_id)

Slightly richer information on individual terms is available with get_term(), using the ontology id and term obo_id as keys.

get_term("cl", b_cell) |>
    glimpse()
#> Rows: 1
#> Columns: 22
#> $ obo_id                  <chr> "CL:0000236"
#> $ label                   <chr> "B cell"
#> $ description             <chr> "A lymphocyte of B lineage that is capable of …
#> $ iri                     <chr> "http://purl.obolibrary.org/obo/CL_0000236"
#> $ lang                    <chr> "en"
#> $ synonyms                <list> ["B lymphocyte", "B-cell", "B-lymphocyte"]
#> $ annotation              <list> [["BTO:0000776", "CALOHA:TS-0068", "FMA:62869"…
#> $ ontology_name           <chr> "cl"
#> $ ontology_prefix         <chr> "CL"
#> $ ontology_iri            <chr> "http://purl.obolibrary.org/obo/cl.owl"
#> $ is_obsolete             <lgl> FALSE
#> $ term_replaced_by        <lgl> NA
#> $ is_defining_ontology    <lgl> TRUE
#> $ has_children            <lgl> TRUE
#> $ is_root                 <lgl> FALSE
#> $ short_form              <chr> "CL_0000236"
#> $ in_subset               <list> ["blood_and_immune_upper_slim", "general_cell_…
#> $ obo_definition_citation <list> [["A lymphocyte of B lineage that is capable o…
#> $ obo_xref                <list> [["BTO", "0000776", <NULL>, "http://purl.oboli…
#> $ obo_synonym             <lgl> NA
#> $ is_preferred_root       <lgl> FALSE
#> $ `_links`                <list> [["https://www.ebi.ac.uk/ols4/api/ontologies/…

Relatives

The functions get_parents() and get_children() retrieve parent and child terms of individual terms; get_ancestors() and get_descendants() return all ancestors (to the root of the ontology) or descendants (where the final term repreents a leaf). For B cells the ancestors are

b_cell_ancestors <-
    get_ancestors("cl", b_cell)

Ancestors and descendants also contain the in_subset column, so these groups are easily filtered to a particlur subset, e.g.,

b_cell_ancestors |>
    filter(is_human_reference_atlas(in_subset))
#> # A tibble: 3 × 14
#>   obo_id     label  description iri   synonyms annotation   has_children is_root
#>   <chr>      <chr>  <chr>       <chr> <list>   <list>       <lgl>        <lgl>  
#> 1 CL:0000945 lymph… A lymphocy… http… <NULL>   <named list> TRUE         FALSE  
#> 2 CL:0000542 lymph… A lymphocy… http… <NULL>   <named list> TRUE         FALSE  
#> 3 CL:0000738 leuko… An achroma… http… <list>   <named list> TRUE         FALSE  
#> # ℹ 6 more variables: short_form <chr>, in_subset <list>,
#> #   obo_definition_citation <list>, obo_xref <list>, obo_synonym <list>,
#> #   `_links` <list>

Terms without children or descendants return a tibble without any rows.

get_children("cl", "CL:0002350")
#> # A tibble: 0 × 0
get_descendants("cl", "CL:0002350")
#> # A tibble: 0 × 0

Graphs

See the Case Study: CELLxGENE Ontologies vignette for illustration of ancestors and descendants graphs.

Cache management

OLSr uses an on-disk cache to improve performance and reliability. Each query to the OLS server is cached on first use, so that subsequent responses can be read from the local disk. This greatly increases performance, and allows use of previously retrieved resources when off-line.

OLSr uses the memoise and cachem packages to implement the cache. The disk cache is implemented using cachem’s cache_disk() function using the default cache size (1 Gb) and "lsu" (‘least recently used’) eviction policy. Objects remain in the cache for 30 days.

A risk of using a cache is that the cache contains an ‘old’ version of the response. Circumvent this risk by manually manipulating the cache, e.g., removing objects that a known to be too old.

Use cache_directory() to discover the location of the cache.

cache_directory()
#> [1] "/home/runner/.cache/R/OLSr"

This location follows conventions of tools::R_user_dir(). All files in this directory have been created by OLSr.

cache_info() is a convenience function to summarize files in the cache.

cache_info()
#> # A tibble: 1,465 × 5
#>    file         size mtime               ctime               atime              
#>    <chr>       <dbl> <dttm>              <dttm>              <dttm>             
#>  1 00504af55f…  6242 2023-11-28 23:19:57 2023-11-28 23:19:57 2023-11-28 23:19:57
#>  2 00645082a7…   154 2023-11-28 23:20:01 2023-11-28 23:20:01 2023-11-28 23:20:01
#>  3 006b7a52f7…   154 2023-11-28 23:19:39 2023-11-28 23:19:39 2023-11-28 23:19:39
#>  4 0082f3196e…   154 2023-11-28 23:21:47 2023-11-28 23:21:47 2023-11-28 23:21:47
#>  5 008b469362…   154 2023-11-28 23:20:47 2023-11-28 23:20:47 2023-11-28 23:20:47
#>  6 00988f2114…  2827 2023-11-28 23:20:43 2023-11-28 23:20:43 2023-11-28 23:20:43
#>  7 00d13d1836…   154 2023-11-28 23:20:12 2023-11-28 23:20:12 2023-11-28 23:20:12
#>  8 0123b83386…   154 2023-11-28 23:12:15 2023-11-28 23:12:15 2023-11-28 23:12:15
#>  9 0127f18a69…  2856 2023-11-28 23:19:47 2023-11-28 23:19:47 2023-11-28 23:19:47
#> 10 014dd2384c…  7430 2023-11-28 23:20:08 2023-11-28 23:20:08 2023-11-28 23:20:08
#> # ℹ 1,455 more rows

Each file represents the response to a single memoised function call. The file name is a hash of the function definition and arguments; it is not possible to associate individual files with specific function calls. Nonetheless, it can be informative to identify files modified more than, say 1 day ago, or the most recently modified file, or perhaps the total size of files in the cache.

## cache objects modified in the last 24 hours
cache_info() |>
    filter(mtime > Sys.time() - 24 * 60 * 60)
#> # A tibble: 1,465 × 5
#>    file         size mtime               ctime               atime              
#>    <chr>       <dbl> <dttm>              <dttm>              <dttm>             
#>  1 00504af55f…  6242 2023-11-28 23:19:57 2023-11-28 23:19:57 2023-11-28 23:19:57
#>  2 00645082a7…   154 2023-11-28 23:20:01 2023-11-28 23:20:01 2023-11-28 23:20:01
#>  3 006b7a52f7…   154 2023-11-28 23:19:39 2023-11-28 23:19:39 2023-11-28 23:19:39
#>  4 0082f3196e…   154 2023-11-28 23:21:47 2023-11-28 23:21:47 2023-11-28 23:21:47
#>  5 008b469362…   154 2023-11-28 23:20:47 2023-11-28 23:20:47 2023-11-28 23:20:47
#>  6 00988f2114…  2827 2023-11-28 23:20:43 2023-11-28 23:20:43 2023-11-28 23:20:43
#>  7 00d13d1836…   154 2023-11-28 23:20:12 2023-11-28 23:20:12 2023-11-28 23:20:12
#>  8 0123b83386…   154 2023-11-28 23:12:15 2023-11-28 23:12:15 2023-11-28 23:12:15
#>  9 0127f18a69…  2856 2023-11-28 23:19:47 2023-11-28 23:19:47 2023-11-28 23:19:47
#> 10 014dd2384c…  7430 2023-11-28 23:20:08 2023-11-28 23:20:08 2023-11-28 23:20:08
#> # ℹ 1,455 more rows

## most recently modified file
cache_info() |>
    arrange(desc(mtime)) |>
    slice(1)
#> # A tibble: 1 × 5
#>   file          size mtime               ctime               atime              
#>   <chr>        <dbl> <dttm>              <dttm>              <dttm>             
#> 1 374a15c0c73…   154 2023-11-28 23:22:05 2023-11-28 23:22:05 2023-11-28 23:22:05

## total cache size
cache_info() |>
    summarize(size = sum(size)) |>
    pull(size) |>
    structure(class = "object_size") |>
    format(units = "auto")
#> [1] "17.5 Mb"

In special cases it may be useful to invalidate the cache using unlink() on the cache_directory(), or on invalidate particular records using unlink() on file paths created using file.path(), cache_directory() and individual file names from cache_info().

Session information

sessionInfo()
#> R version 4.3.2 (2023-10-31)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Ubuntu 22.04.3 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] OLSr_0.0.2       dplyr_1.1.4      BiocStyle_2.28.1
#> 
#> loaded via a namespace (and not attached):
#>  [1] rappdirs_0.3.3       sass_0.4.7           utf8_1.2.4          
#>  [4] generics_0.1.3       tidyr_1.3.0          stringi_1.8.1       
#>  [7] hms_1.1.3            digest_0.6.33        magrittr_2.0.3      
#> [10] evaluate_0.23        bookdown_0.36        fastmap_1.1.1       
#> [13] rprojroot_2.0.4      jsonlite_1.8.7       progress_1.2.2      
#> [16] BiocManager_1.30.22  purrr_1.0.2          fansi_1.0.5         
#> [19] httr2_1.0.0          textshaping_0.3.7    jquerylib_0.1.4     
#> [22] cli_3.6.1            rlang_1.1.2          crayon_1.5.2        
#> [25] withr_2.5.2          cachem_1.0.8         yaml_2.3.7          
#> [28] BiocBaseUtils_1.2.0  tools_4.3.2          memoise_2.0.1       
#> [31] curl_5.1.0           vctrs_0.6.4          rjsoncons_1.0.0.9200
#> [34] R6_2.5.1             lifecycle_1.0.4      stringr_1.5.1       
#> [37] fs_1.6.3             ragg_1.2.6           pkgconfig_2.0.3     
#> [40] desc_1.4.2           pkgdown_2.0.7        pillar_1.9.0        
#> [43] bslib_0.6.0          glue_1.6.2           systemfonts_1.0.5   
#> [46] xfun_0.41            tibble_3.2.1         tidyselect_1.2.0    
#> [49] knitr_1.45           htmltools_0.5.7      rmarkdown_2.25      
#> [52] compiler_4.3.2       prettyunits_1.2.0