IRanges 2.34.0
When analyzing sequences, we are often interested in particular consecutive
subsequences. For example, the vector could be considered a sequence of
lower-case letters, in alphabetical order. We would call the first five letters
(a to e) a consecutive subsequence, while the subsequence containing only
the vowels would not be consecutive. It is not uncommon for an analysis task to
focus only on the geometry of the regions, while ignoring the underlying
sequence values. A list of indices would be a simple way to select a
subsequence. However, a sparser representation for consecutive subsequences
would be a range, a pairing of a start position and a width, as used when
extracting sequences with window.
Two central classes are available in Bioconductor for representing ranges: the IRanges class defined in the IRanges package for representing ranges defined on a single space, and the GRanges class defined in the GenomicRanges package for representing ranges defined on multiple spaces.
In this vignette, we will focus on IRanges objects. We will rely on simple, illustrative example datasets, rather than large, real-world data, so that each data structure and algorithm can be explained in an intuitive, graphical manner. We expect that packages that apply IRanges to a particular problem domain will provide vignettes with relevant, realistic examples.
The IRanges package is available at bioconductor.org and can be
downloaded via BiocManager::install:
if (!require("BiocManager"))
install.packages("BiocManager")
BiocManager::install("IRanges")
library(IRanges)
To construct an object, we call the IRanges constructor. Ranges are normally
specified by passing two out of the three parameters: start, end and width (see
help(IRanges) for more information).
ir1 <- IRanges(start=1:10, width=10:1)
ir1
## IRanges object with 10 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 10 10
## [2] 2 10 9
## [3] 3 10 8
## [4] 4 10 7
## [5] 5 10 6
## [6] 6 10 5
## [7] 7 10 4
## [8] 8 10 3
## [9] 9 10 2
## [10] 10 10 1
ir2 <- IRanges(start=1:10, end=11)
ir3 <- IRanges(end=11, width=10:1)
identical(ir1, ir2) && identical(ir1, ir3)
## [1] FALSE
ir <- IRanges(c(1, 8, 14, 15, 19, 34, 40),
width=c(12, 6, 6, 15, 6, 2, 7))
ir
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 12 12
## [2] 8 13 6
## [3] 14 19 6
## [4] 15 29 15
## [5] 19 24 6
## [6] 34 35 2
## [7] 40 46 7
All of the above calls construct the same IRanges object, using different
combinations of the start, end and width parameters.
Accessing the starts, ends and widths is supported via the start, end and
width getters:
start(ir)
## [1] 1 8 14 15 19 34 40
end(ir)
## [1] 12 13 19 29 24 35 46
width(ir)
## [1] 12 6 6 15 6 2 7
Subsetting an IRanges object is supported, by numeric and logical indices:
ir[1:4]
## IRanges object with 4 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 12 12
## [2] 8 13 6
## [3] 14 19 6
## [4] 15 29 15
ir[start(ir) <= 15]
## IRanges object with 4 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 12 12
## [2] 8 13 6
## [3] 14 19 6
## [4] 15 29 15
In order to illustrate range operations, we’ll create a function to plot ranges.
plotRanges <- function(x, xlim=x, main=deparse(substitute(x)),
col="black", sep=0.5, ...)
{
height <- 1
if (is(xlim, "IntegerRanges"))
xlim <- c(min(start(xlim)), max(end(xlim)))
bins <- disjointBins(IRanges(start(x), end(x) + 1))
plot.new()
plot.window(xlim, c(0, max(bins)*(height + sep)))
ybottom <- bins * (sep + height) - height
rect(start(x)-0.5, ybottom, end(x)+0.5, ybottom + height, col=col, ...)
title(main)
axis(1)
}
plotRanges(ir)
Figure 1: Plot of original ranges
Sometimes, it is necessary to formally represent a subsequence, where no elements are repeated and order is preserved. Also, it is occasionally useful to think of an IRanges object as a set of integers, where no elements are repeated and order does not matter.
The NormalIRanges class formally represents a set of integers. By definition an IRanges object is said to be normal when its ranges are: (a) not empty (i.e. they have a non-null width); (b) not overlapping; (c) ordered from left to right; (d) not even adjacent (i.e. there must be a non empty gap between 2 consecutive ranges).
There are three main advantages of using a normal IRanges object: (1) it guarantees a subsequence encoding or set of integers, (2) it is compact in terms of the number of ranges, and (3) it uniquely identifies its information, which simplifies comparisons.
The resduce function reduces any IRanges object to a NormalIRanges by
merging redundant ranges.
reduce(ir)
## IRanges object with 3 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 29 29
## [2] 34 35 2
## [3] 40 46 7
plotRanges(reduce(ir))
Figure 2: : Plot of reduced ranges
It is common to manipulate collections of IRanges objects during an analysis. Thus, the IRanges package defines some specific classes for working with multiple IRanges objects.
The IRangesList class asserts that each element is an IRanges object and
provides convenience methods, such as start, end and width accessors that
return Interlist objects, aligning with the IRangesList object. Note that
IntegerList objects will be covered later in more details in the “Lists of
Atomic Vectors” section of this document.
To explicitly construct an IRangesList, use the IRangesList function.
rl <- IRangesList(ir, rev(ir))
start(rl)
## IntegerList of length 2
## [[1]] 1 8 14 15 19 34 40
## [[2]] 40 34 19 15 14 8 1
As the elements of an IRanges object encode consecutive subsequences, they may
be used directly in sequence extraction. Note that when a normal IRanges is
given as the index, the result is a subsequence, as no elements are repeated or
reordered. If the sequence is a Vector subclass (i.e. not an ordinary
vector), the canonical [ function accepts an IRanges object.
set.seed(0)
lambda <- c(rep(0.001, 4500), seq(0.001, 10, length=500),
seq(10, 0.001, length=500))
xVector <- rpois(1e7, lambda)
yVector <- rpois(1e7, lambda[c(251:length(lambda), 1:250)])
xRle <- Rle(xVector)
yRle <- Rle(yVector)
irextract <- IRanges(start=c(4501, 4901) , width=100)
xRle[irextract]
## integer-Rle of length 200 with 159 runs
## Lengths: 12 1 1 1 2 1 1 1 1 2 1 ... 1 1 1 1 1 1 1 1 1 1
## Values : 0 1 0 2 0 1 0 1 0 1 0 ... 18 9 12 6 5 10 9 6 9 12
The function findOverlaps detects overlaps between two IRanges objects.
ol <- findOverlaps(ir, reduce(ir))
as.matrix(ol)
## queryHits subjectHits
## [1,] 1 1
## [2,] 2 1
## [3,] 3 1
## [4,] 4 1
## [5,] 5 1
## [6,] 6 2
## [7,] 7 3
The function coverage counts the number of ranges over each position.
cov <- coverage(ir)
plotRanges(ir)
cov <- as.vector(cov)
mat <- cbind(seq_along(cov)-0.5, cov)
d <- diff(cov) != 0
mat <- rbind(cbind(mat[d,1]+1, mat[d,2]), mat)
mat <- mat[order(mat[,1]),]
lines(mat, col="red", lwd=4)
axis(2)
Figure 3: Plot of ranges with accumulated coverage
The nearest function finds the nearest neighbor ranges (overlapping is zero
distance). The precede and follow functions find the non-overlapping nearest
neighbors on a specific side.
Utilities are available for transforming an IRanges object in a variety of
ways. Some transformations, like reduce introduced above, can be dramatic,
while others are simple per-range adjustments of the starts, ends or widths.
Perhaps the simplest transformation is to adjust the start values by a scalar
offset, as performed by the shift function. Below, we shift all ranges forward
10 positions.
shift(ir, 10)
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 11 22 12
## [2] 18 23 6
## [3] 24 29 6
## [4] 25 39 15
## [5] 29 34 6
## [6] 44 45 2
## [7] 50 56 7
There are several other ways to transform ranges. These include narrow,
resize, flank, reflect, restrict, and threebands. For example narrow
supports the adjustment of start, end and width values, which should be relative
to each range. These adjustments are vectorized over the ranges. As its name
suggests, the ranges can only be narrowed.
narrow(ir, start=1:5, width=2)
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 2 2
## [2] 9 10 2
## [3] 16 17 2
## [4] 18 19 2
## [5] 23 24 2
## [6] 34 35 2
## [7] 41 42 2
The restrict function ensures every range falls within a set of bounds. Ranges
are contracted as necessary, and the ranges that fall completely outside of but
not adjacent to the bounds are dropped, by default.
restrict(ir, start=2, end=3)
## IRanges object with 1 range and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 2 3 2
The threebands function extends narrow so that the remaining left and right
regions adjacent to the narrowed region are also returned in separate IRanges
objects.
threebands(ir, start=1:5, width=2)
## $left
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 0 0
## [2] 8 8 1
## [3] 14 15 2
## [4] 15 17 3
## [5] 19 22 4
## [6] 34 33 0
## [7] 40 40 1
##
## $middle
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 2 2
## [2] 9 10 2
## [3] 16 17 2
## [4] 18 19 2
## [5] 23 24 2
## [6] 34 35 2
## [7] 41 42 2
##
## $right
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 3 12 10
## [2] 11 13 3
## [3] 18 19 2
## [4] 20 29 10
## [5] 25 24 0
## [6] 36 35 0
## [7] 43 46 4
The arithmetic operators +, - and * change both the start and the
end/width by symmetrically expanding or contracting each range. Adding or
subtracting a numeric (integer) vector to an IRanges causes each range to be
expanded or contracted on each side by the corresponding value in the numeric
vector.
ir + seq_len(length(ir))
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 0 13 14
## [2] 6 15 10
## [3] 11 22 12
## [4] 11 33 23
## [5] 14 29 16
## [6] 28 41 14
## [7] 33 53 21
The * operator symmetrically magnifies an IRanges object by a factor, where
positive contracts (zooms in) and negative expands (zooms out).
ir * -2 # double the width
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] -5 18 24
## [2] 5 16 12
## [3] 11 22 12
## [4] 7 36 30
## [5] 16 27 12
## [6] 33 36 4
## [7] 36 49 14
WARNING: The semantic of these arithmetic operators might be revisited at some point. Please restrict their use to the context of interactive visualization (where they arguably provide some convenience) but avoid to use them programmatically.
A more complex type of operation is making a set of ranges disjoint, i.e.
non-overlapping. For example, threebands returns a disjoint set of three
ranges for each input range.
The disjoin function makes an IRanges object disjoint by fragmenting it into
the widest ranges where the set of overlapping ranges is the same.
disjoin(ir)
## IRanges object with 10 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 1 7 7
## [2] 8 12 5
## [3] 13 13 1
## [4] 14 14 1
## [5] 15 18 4
## [6] 19 19 1
## [7] 20 24 5
## [8] 25 29 5
## [9] 34 35 2
## [10] 40 46 7
plotRanges(disjoin(ir))
Figure 4: Plot of disjoined ranges
A variant of disjoin is disjointBins, which divides the ranges into bins,
such that the ranges in each bin are disjoint. The return value is an integer
vector of the bins.
disjointBins(ir)
## [1] 1 2 1 2 3 1 1
Other transformations include reflect and flank. The former “flips” each
range within a set of common reference bounds.
reflect(ir, IRanges(start(ir), width=width(ir)*2))
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 13 24 12
## [2] 14 19 6
## [3] 20 25 6
## [4] 30 44 15
## [5] 25 30 6
## [6] 36 37 2
## [7] 47 53 7
The flank returns ranges of a specified width that flank, to the left
(default) or right, each input range. One use case of this is forming promoter
regions for a set of genes.
flank(ir, width=seq_len(length(ir)))
## IRanges object with 7 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 0 0 1
## [2] 6 7 2
## [3] 11 13 3
## [4] 11 14 4
## [5] 14 18 5
## [6] 28 33 6
## [7] 33 39 7
Sometimes, it is useful to consider an IRanges object as a set of integers,
although there is always an implicit ordering. This is formalized by
NormalIRanges, above, and we now present versions of the traditional
mathematical set operations complement, union, intersect, and difference
for IRanges objects. There are two variants for each operation. The first
treats each IRanges object as a set and returns a normal value, while the
other has a “parallel” semantic like pmin/pmax and performs the operation
for each range pairing separately.
The complement operation is implemented by the gaps and pgap functions. By
default, gaps will return the ranges that fall between the ranges in the
(normalized) input. It is possible to specify a set of bounds, so that flanking
ranges are included.
gaps(ir, start=1, end=50)
## IRanges object with 3 ranges and 0 metadata columns:
## start end width
## <integer> <integer> <integer>
## [1] 30 33 4
## [2] 36 39 4
## [3] 47 50 4
plotRanges(gaps(ir, start=1, end=50), c(1,50))
Figure 5: Plot of gaps from ranges
pgap considers each parallel pairing between two IRanges objects and finds
the range, if any, between them. Note that the function name is singular,
suggesting that only one range is returned per range in the input.
The remaining operations, union, intersect and difference are implemented
by the [p]union, [p]intersect and [p]setdiff functions, respectively.
These are relatively self-explanatory.
The IRanges package provides the virtual Views class, which stores a vector-like object, referred to as the “subject”, together with an IRanges object defining ranges on the subject. Each range is said to represent a view onto the subject.
Here, we will demonstrate the RleViews class, where the subject is of class Rle. Other Views implementations exist, such as XStringViews in the Biostrings package.
There are two basic constructors for creating views: the Views function based
on indicators and the slice based on numeric boundaries.
xViews <- Views(xRle, xRle >= 1)
xViews <- slice(xRle, 1)
xRleList <- RleList(xRle, 2L * rev(xRle))
xViewsList <- slice(xRleList, 1)
Note that RleList objects will be covered later in more details in the “Lists of Atomic Vectors” section of this document.
While sapply can be used to loop over each window, the native functions
viewMaxs, viewMins, viewSums, and viewMeans provide fast looping to
calculate their respective statistical summaries.
head(viewSums(xViews))
## [1] 1 1 1 1 1 2
viewSums(xViewsList)
## IntegerList of length 2
## [[1]] 1 1 1 1 1 2 1 1 2 3 1 6 1 3 4 1 ... 8 11 12 6 37 10 8 11 6 4 5 1 1 5 1 1
## [[2]] 2 2 10 2 2 10 8 12 22 16 20 74 12 24 22 ... 6 2 12 2 6 4 2 2 4 2 2 2 2 2
head(viewMaxs(xViews))
## [1] 1 1 1 1 1 2
viewMaxs(xViewsList)
## IntegerList of length 2
## [[1]] 1 1 1 1 1 2 1 1 1 2 1 2 1 2 3 1 1 1 ... 5 4 3 5 2 5 6 2 8 3 2 2 1 1 2 1 1
## [[2]] 2 2 4 2 2 4 4 6 16 4 12 10 4 10 6 8 ... 2 6 4 2 4 2 4 2 2 2 4 2 2 2 2 2
In addition to the range-based objects described in the previous sections, the IRanges package provides containers for storing lists of atomic vectors such as integers or Rle objects. The IntegerList and RleList classes represent lists of integer vectors and Rle objects respectively. They are subclasses of the AtomicList virtual class which is itself a subclass of the List virtual class defined in the S4Vectors package.
showClass("RleList")
## Virtual Class "RleList" [package "IRanges"]
##
## Slots:
##
## Name: elementType elementMetadata metadata
## Class: character DataFrame_OR_NULL list
##
## Extends:
## Class "AtomicList", directly
## Class "List", by class "AtomicList", distance 2
## Class "Vector", by class "AtomicList", distance 3
## Class "list_OR_List", by class "AtomicList", distance 3
## Class "Annotated", by class "AtomicList", distance 4
## Class "vector_OR_Vector", by class "AtomicList", distance 4
##
## Known Subclasses: "SimpleRleList", "RleViews", "CompressedRleList"
As the class definition above shows, the RleList class is virtual with subclasses SimpleRleList and CompressedRleList. A SimpleRleList class uses an ordinary R list to store the underlying elements and CompressedRleList the class stores the elements in an unlisted form and keeps track of where the element breaks are. The former “simple list” class is useful when the Rle elements are long and the latter “compressed list” class is useful when the list is long and/or sparse (i.e. a number of the list elements have length 0).
In fact, all of the atomic vector types (logical, integer, numeric, complex, character, raw, and factor) have similar list classes that derive from the List virtual class. For example, there is an IntegerList virtual class with subclasses SimpleIntegerList and CompressedIntegerList.
Each of the list classes for atomic sequences, be they stored as vectors or
Rle objects, have a constructor function with a name of the appropriate list
virtual class, such as IntegerList, and an optional argument compress that
takes an argument to specify whether or not to create the simple list object
type or the compressed list object type. The default is to create the compressed
list object type.
args(IntegerList)
## function (..., compress = TRUE)
## NULL
cIntList1 <- IntegerList(x=xVector, y=yVector)
cIntList1
## IntegerList of length 2
## [["x"]] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## [["y"]] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
sIntList2 <- IntegerList(x=xVector, y=yVector, compress=FALSE)
sIntList2
## IntegerList of length 2
## [["x"]] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## [["y"]] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## sparse integer list
xExploded <- lapply(xVector[1:5000], function(x) seq_len(x))
cIntList2 <- IntegerList(xExploded)
sIntList2 <- IntegerList(xExploded, compress=FALSE)
object.size(cIntList2)
## 33208 bytes
object.size(sIntList2)
## 294016 bytes
The length function returns the number of elements in a Vector-derived
object and, for a List-derived object like “simple list” or “compressed list”,
the lengths function returns an integer vector containing the lengths of each
of the elements:
length(cIntList2)
## [1] 5000
Rle(lengths(cIntList2))
## integer-Rle of length 5000 with 427 runs
## Lengths: 780 1 208 1 1599 1 883 ... 1 1 1 1 1 1
## Values : 0 1 0 1 0 1 0 ... 5 10 9 6 9 12
Just as with ordinary R objects, List-derived object support the [[ for
element extraction, c for concatenating, and lapply/sapply for looping.
When looping over sparse lists, the “compressed list” classes can be much faster
during computations since only the non-empty elements are looped over during the
lapply/sapply computation and all the empty elements are assigned the
appropriate value based on their status.
system.time(sapply(xExploded, mean))
## user system elapsed
## 0.022 0.000 0.022
system.time(sapply(sIntList2, mean))
## user system elapsed
## 0.023 0.000 0.023
system.time(sapply(cIntList2, mean))
## user system elapsed
## 0.023 0.000 0.022
identical(sapply(xExploded, mean), sapply(sIntList2, mean))
## [1] TRUE
identical(sapply(xExploded, mean), sapply(cIntList2, mean))
## [1] TRUE
Unlike ordinary R list objects, AtomicList objects support the Ops(e.g.
+, ==, &), (e.g. log, sqrt), Math2(e.g. round, signif),
Summary(e.g. min, max, sum), and Complex(e.g. Re, Im) group
generics.
xRleList > 0
## RleList of length 2
## [[1]]
## logical-Rle of length 10000000 with 197127 runs
## Lengths: 780 1 208 1 1599 ... 5 1 91 1 927
## Values : FALSE TRUE FALSE TRUE FALSE ... FALSE TRUE FALSE TRUE FALSE
##
## [[2]]
## logical-Rle of length 10000000 with 197127 runs
## Lengths: 927 1 91 1 5 ... 1599 1 208 1 780
## Values : FALSE TRUE FALSE TRUE FALSE ... FALSE TRUE FALSE TRUE FALSE
yRleList <- RleList(yRle, 2L * rev(yRle))
xRleList + yRleList
## RleList of length 2
## [[1]]
## integer-Rle of length 10000000 with 1957707 runs
## Lengths: 780 1 208 1 13 1 413 1 ... 1 5 1 91 1 507 1 419
## Values : 0 1 0 1 0 1 0 1 ... 2 0 1 0 1 0 1 0
##
## [[2]]
## integer-Rle of length 10000000 with 1957707 runs
## Lengths: 419 1 507 1 91 1 5 1 ... 1 413 1 13 1 208 1 780
## Values : 0 2 0 2 0 2 0 4 ... 2 0 2 0 2 0 2 0
sum(xRleList > 0 | yRleList > 0)
## [1] 2105185 2105185
Since these atomic lists inherit from List, they can also use the looping
function endoapply to perform endomorphisms.
safe.max <- function(x) { if(length(x)) max(x) else integer(0) }
endoapply(sIntList2, safe.max)
## IntegerList of length 5000
## [[1]] integer(0)
## [[2]] integer(0)
## [[3]] integer(0)
## [[4]] integer(0)
## [[5]] integer(0)
## [[6]] integer(0)
## [[7]] integer(0)
## [[8]] integer(0)
## [[9]] integer(0)
## [[10]] integer(0)
## ...
## <4990 more elements>
endoapply(cIntList2, safe.max)
## IntegerList of length 5000
## [[1]] integer(0)
## [[2]] integer(0)
## [[3]] integer(0)
## [[4]] integer(0)
## [[5]] integer(0)
## [[6]] integer(0)
## [[7]] integer(0)
## [[8]] integer(0)
## [[9]] integer(0)
## [[10]] integer(0)
## ...
## <4990 more elements>
endoapply(sIntList2, safe.max)[[1]]
## integer(0)
Here is the output of sessionInfo() on the system on which this document was
compiled:
## R version 4.3.0 (2023-04-21)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.6 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## time zone: America/New_York
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] IRanges_2.34.0 S4Vectors_0.38.1 BiocGenerics_0.46.0
## [4] BiocStyle_2.28.0
##
## loaded via a namespace (and not attached):
## [1] cli_3.6.1 knitr_1.43 rlang_1.1.1
## [4] magick_2.7.4 xfun_0.39 highr_0.10
## [7] jsonlite_1.8.5 htmltools_0.5.5 sass_0.4.6
## [10] rmarkdown_2.22 evaluate_0.21 jquerylib_0.1.4
## [13] fastmap_1.1.1 yaml_2.3.7 bookdown_0.34
## [16] BiocManager_1.30.21 compiler_4.3.0 Rcpp_1.0.10
## [19] rstudioapi_0.14 digest_0.6.31 R6_2.5.1
## [22] magrittr_2.0.3 bslib_0.5.0 tools_4.3.0
## [25] cachem_1.0.8