Introduction to OLSr
Martin Morgan
Roswell Park Comprehensive Cancer Center, Buffalo, NY, USSource:
vignettes/a_introduction.Rmd
a_introduction.Rmd
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")
::install_github("mtmorgan/OLSr") remotes
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: 250 × 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
#> # ℹ 240 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-30T14:00:19.698341744"
#> $ updated <chr> "2023-11-30T14:00:19.698341744"
#> $ 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-30T14:00:19.698341744"
#> $ updated <chr> "2023-11-30T14:00:19.698341744"
#> $ 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 adren… CL:00… NA http… <NULL> <named list> FALSE CL_0000109
#> 2 pepti… CL:00… A neuron t… http… <NULL> <named list> FALSE CL_0000110
#> 3 colum… CL:00… NA http… <list> <NULL> FALSE CL_0000112
#> 4 monon… CL:00… A vertebra… http… <NULL> <named list> FALSE CL_0000113
#> 5 surfa… CL:00… NA http… <list> <named list> FALSE CL_0000114
#> 6 endot… CL:00… An endothe… http… <list> <named list> TRUE CL_0000115
#> 7 pione… CL:00… Pioneer ne… http… <NULL> <named list> FALSE CL_0000116
#> 8 CNS n… CL:00… NA http… <NULL> <NULL> TRUE CL_0000117
#> 9 baske… CL:00… Basket cel… http… <NULL> <named list> TRUE CL_0000118
#> 10 cereb… CL:00… Large intr… http… <list> <named list> FALSE CL_0000119
#> # ℹ 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> "adrenergic neuron"
#> $ obo_id <chr> "CL:0000109"
#> $ description <chr> NA
#> $ iri <chr> "http://purl.obolibrary.org/obo/CL_0000109"
#> $ synonyms <list> <NULL>
#> $ annotation <list> [["MESH:D059331"]]
#> $ has_children <lgl> FALSE
#> $ short_form <chr> "CL_0000109"
#> $ in_subset <list> <NULL>
#> $ obo_definition_citation <list> <NULL>
#> $ obo_xref <list> [["MESH", "D059331", <NULL>, "http://id.nlm.ni…
#> $ obo_synonym <list> <NULL>
#> $ `_links` <list> [["https://www.ebi.ac.uk/ols4/api/ontologies/c…
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 adrenergic neuron CL:0000109 <NULL>
#> 2 peptidergic neuron CL:0000110 <NULL>
#> 3 columnar neuron CL:0000112 <NULL>
#> 4 mononuclear phagocyte CL:0000113 <list [1]>
#> 5 surface ectodermal cell CL:0000114 <NULL>
#> 6 endothelial cell CL:0000115 <list [1]>
#> 7 pioneer neuron CL:0000116 <NULL>
#> 8 CNS neuron (sensu Vertebrata) CL:0000117 <NULL>
#> 9 basket cell CL:0000118 <list [1]>
#> 10 cerebellar Golgi cell CL:0000119 <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:0000113 mononuclear phagocyte
#> 2 human_reference_atlas CL:0000115 endothelial cell
#> 3 BDS_subset CL:0000118 basket cell
#> 4 BDS_subset CL:0000122 stellate neuron
#> 5 general_cell_types_upper_slim CL:0000125 glial cell
#> 6 human_reference_atlas CL:0000125 glial cell
#> 7 human_reference_atlas CL:0000127 astrocyte
#> 8 human_reference_atlas CL:0000128 oligodendrocyte
#> 9 human_reference_atlas CL:0000129 microglial cell
#> 10 eye_upper_slim CL:0000132 corneal endothelial 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 monon… CL:00… A vertebra… http… <NULL> <named list> FALSE CL_0000113
#> 2 endot… CL:00… An endothe… http… <list> <named list> TRUE CL_0000115
#> 3 glial… CL:00… A non-neur… http… <list> <named list> TRUE CL_0000125
#> 4 astro… CL:00… A class of… http… <list> <named list> TRUE CL_0000127
#> 5 oligo… CL:00… A class of… http… <list> <named list> TRUE CL_0000128
#> 6 micro… CL:00… A central … http… <list> <named list> TRUE CL_0000129
#> 7 corne… CL:00… An hexagon… http… <NULL> <named list> FALSE CL_0000132
#> 8 fat c… CL:00… A fat-stor… http… <list> <named list> TRUE CL_0000136
#> 9 simpl… CL:00… NA http… <NULL> <named list> TRUE CL_0000146
#> 10 melan… CL:00… A pigment … http… <list> <named list> TRUE CL_0000148
#> # ℹ 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 CL:0000236 A lymphocyte of B lineage …
#> 2 B cell, CD19-positive CL:0001201 A B cell that is CD19-posi…
#> 3 B cell of appendix CL:0009032 A B cell that is located i…
#> 4 B cell of medullary sinus of lymph node CL:0009045 A B cell found in the lymp…
#> 5 B cell of anorectum CL:0009050 A B cell that is located i…
#> 6 B cell zone reticular cell CL:0009104 A fibroblastic reticular c…
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: 99 × 5
#> file size mtime ctime atime
#> <chr> <dbl> <dttm> <dttm> <dttm>
#> 1 03d5b9987d… 808 2023-12-01 17:28:56 2023-12-01 17:28:56 2023-12-01 17:28:56
#> 2 06f6277f34… 154 2023-12-01 17:28:49 2023-12-01 17:28:49 2023-12-01 17:28:49
#> 3 09405ddbab… 768 2023-12-01 17:28:55 2023-12-01 17:28:55 2023-12-01 17:28:55
#> 4 09f0d0cb75… 719 2023-12-01 17:28:39 2023-12-01 17:28:39 2023-12-01 17:28:39
#> 5 1019c9f790… 154 2023-12-01 17:28:50 2023-12-01 17:28:50 2023-12-01 17:28:50
#> 6 1152719177… 12851 2023-12-01 17:28:36 2023-12-01 17:28:36 2023-12-01 17:28:36
#> 7 13938f8890… 6502 2023-12-01 17:29:04 2023-12-01 17:29:04 2023-12-01 17:29:04
#> 8 1d41ffdf45… 154 2023-12-01 17:28:54 2023-12-01 17:28:54 2023-12-01 17:28:54
#> 9 1d4576da3a… 1400 2023-12-01 17:28:46 2023-12-01 17:28:46 2023-12-01 17:28:46
#> 10 236f616b89… 154 2023-12-01 17:28:47 2023-12-01 17:28:47 2023-12-01 17:28:47
#> # ℹ 89 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: 99 × 5
#> file size mtime ctime atime
#> <chr> <dbl> <dttm> <dttm> <dttm>
#> 1 03d5b9987d… 808 2023-12-01 17:28:56 2023-12-01 17:28:56 2023-12-01 17:28:56
#> 2 06f6277f34… 154 2023-12-01 17:28:49 2023-12-01 17:28:49 2023-12-01 17:28:49
#> 3 09405ddbab… 768 2023-12-01 17:28:55 2023-12-01 17:28:55 2023-12-01 17:28:55
#> 4 09f0d0cb75… 719 2023-12-01 17:28:39 2023-12-01 17:28:39 2023-12-01 17:28:39
#> 5 1019c9f790… 154 2023-12-01 17:28:50 2023-12-01 17:28:50 2023-12-01 17:28:50
#> 6 1152719177… 12851 2023-12-01 17:28:36 2023-12-01 17:28:36 2023-12-01 17:28:36
#> 7 13938f8890… 6502 2023-12-01 17:29:04 2023-12-01 17:29:04 2023-12-01 17:29:04
#> 8 1d41ffdf45… 154 2023-12-01 17:28:54 2023-12-01 17:28:54 2023-12-01 17:28:54
#> 9 1d4576da3a… 1400 2023-12-01 17:28:46 2023-12-01 17:28:46 2023-12-01 17:28:46
#> 10 236f616b89… 154 2023-12-01 17:28:47 2023-12-01 17:28:47 2023-12-01 17:28:47
#> # ℹ 89 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 4e9479c7fcf… 154 2023-12-01 17:29:04 2023-12-01 17:29:04 2023-12-01 17:29:04
## total cache size
cache_info() |>
summarize(size = sum(size)) |>
pull(size) |>
structure(class = "object_size") |>
format(units = "auto")
#> [1] "4.1 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.3 dplyr_1.1.4 BiocStyle_2.28.1
#>
#> loaded via a namespace (and not attached):
#> [1] jsonlite_1.8.7 compiler_4.3.2 BiocManager_1.30.22
#> [4] BiocBaseUtils_1.2.0 tidyselect_1.2.0 stringr_1.5.1
#> [7] tidyr_1.3.0 jquerylib_0.1.4 systemfonts_1.0.5
#> [10] textshaping_0.3.7 yaml_2.3.7 fastmap_1.1.1
#> [13] R6_2.5.1 rjsoncons_1.0.0.9200 generics_0.1.3
#> [16] curl_5.1.0 httr2_1.0.0 knitr_1.45
#> [19] tibble_3.2.1 bookdown_0.36 desc_1.4.2
#> [22] rprojroot_2.0.4 bslib_0.6.1 pillar_1.9.0
#> [25] rlang_1.1.2 utf8_1.2.4 cachem_1.0.8
#> [28] stringi_1.8.2 xfun_0.41 fs_1.6.3
#> [31] sass_0.4.7 memoise_2.0.1 cli_3.6.1
#> [34] withr_2.5.2 pkgdown_2.0.7 magrittr_2.0.3
#> [37] digest_0.6.33 rappdirs_0.3.3 lifecycle_1.0.4
#> [40] vctrs_0.6.4 evaluate_0.23 glue_1.6.2
#> [43] ragg_1.2.6 fansi_1.0.5 rmarkdown_2.25
#> [46] purrr_1.0.2 tools_4.3.2 pkgconfig_2.0.3
#> [49] htmltools_0.5.7