Plotting with ggplot2
A grammar of graphics
ggplot2
is an implementation of “The Grammar of Graphics” (Wilkinson 2005)
- The idea is that each graph is built from the same few components - like a language, where sentences consist of components such as verbs, nouns, etc.
- This is different from the “base” plotting framework in R, where each type of plot is generated with a specific function, and different plot functions can have quite different syntax, argument order etc.
Let’s make a thought experiment
- Imagine that you have a plot on your screen.
- What three things would you need to give/tell me in order for me to recreate the ‘gist’ of your plot (not bothering with details like matching the colors, labels and similar)?
Building a ggplot2 plot
- The main components used by
ggplot2
plots specify the:- Data: data source (typically a
data.frame
or similar)
- Aesthetics: which variables to use (columns in the data.frame), and for what (e.g. x-axis, shape or colour)
- Geoms: how to represent values (e.g. points, bars, etc.)
- Scales: how the data values are mapped to the visual values of an aesthetic
- Stats: whether/how the data should be summarized (e.g. bins, mean, etc.)
- Facets: creates subplots based on grouped data
- Coordinate system: how data coordinates are mapped to the plane of the graphic (e.g. Cartesian or polar)
- Theme: finer controls of the display
- Data: data source (typically a
- The plot itself if constructed as a combination of layers, combined using the + operator. Each layer combines one or more components.
From https://r.qcbs.ca/workshop03/book-en/grammar-of-graphics-gg-basics.html
A note on data formatting
As discussed last week, to use data with ggplot2
, it should be in the form of a tidy data.frame
or a tibble
.
In particular, each variable should be in a separate column, and all values of a given variable should be in the same column.
From the perspective of ggplot2
, a variable is something that you would put on one of the axes in a plot, or use for coloring the points, etc.
From https://www.rforecology.com/post/a-simple-introduction-to-ggplot2/
Example data
For this lecture, we will use the Palmer penguins data set, containing records of 344 penguins from three different species, collected from 3 islands in the Palmer Archipelago, Antarctica.
library(tidyverse)
<- read_csv("data/penguins.csv")
dat <- dat |> mutate(index = seq_along(species))
dat head(dat)
# A tibble: 6 × 9
species island bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
<chr> <chr> <dbl> <dbl> <dbl> <dbl>
1 Adelie Torge… 39.1 18.7 181 3750
2 Adelie Torge… 39.5 17.4 186 3800
3 Adelie Torge… 40.3 18 195 3250
4 Adelie Torge… NA NA NA NA
5 Adelie Torge… 36.7 19.3 193 3450
6 Adelie Torge… 39.3 20.6 190 3650
# ℹ 3 more variables: sex <chr>, year <dbl>, index <int>
Scatter plots
- Volcano plots
- Correlation plots
- MA plots
- Manhattan plots
- PCA/tSNE/UMAP plots
- Mean-variance plots
- GC bias plots
- …
Let’s start by creating the following plot:
library(ggplot2)
ggplot(data = dat) +
aes(x = flipper_length_mm) +
aes(y = bill_length_mm) +
geom_point(size = 5,
color = "cornflowerblue",
shape = 16) +
scale_x_continuous(limits = c(150, 250)) +
labs(x = "Flipper length (mm)") +
labs(y = "Bill length (mm)") +
labs(title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15)) +
theme(axis.title = element_text(size = 18)) +
theme(title = element_text(size = 20))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Lessons learned
- A ggplot is initialized by the
ggplot()
function, which is typically also where the data is provided. - Calls to
aes()
determine which columns in the data are mapped to each of the axes in the plot. - The
geom
determines what type of plot to make. It will inherit the information about which column to represent on each of the axes from the previous lines (we will see later that differentgeom
s can use different data sets and aesthetics). - In this case, the color and shape of the points are constant (independent of the data), and thus the corresponding specification is not wrapped in a call to
aes()
. We will see later how to e.g. color the points according to a column in the data. - General plot appearance is set via the
theme
family of functions.
Exercise 1: Create the following plot
library(ggplot2)
ggplot(data = dat) +
aes(x = body_mass_g) +
aes(y = bill_length_mm) +
geom_point(size = 5,
color = "green",
shape = 3) +
labs(x = "Body mass (g)") +
labs(y = "Bill length (mm)") +
labs(title = "Bill length vs body mass") +
theme_bw() +
theme(axis.text = element_text(size = 15)) +
theme(axis.title = element_text(size = 18)) +
theme(title = element_text(size = 20))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Simplifications and alternative formulations
library(ggplot2)
ggplot(data = dat) +
aes(x = flipper_length_mm) +
aes(y = bill_length_mm) +
geom_point(size = 5,
color = "cornflowerblue",
shape = 16) +
labs(x = "Flipper length (mm)") +
labs(y = "Bill length (mm)") +
labs(title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15)) +
theme(axis.title = element_text(size = 18)) +
theme(title = element_text(size = 20))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat,
aes(x = flipper_length_mm,
y = bill_length_mm)) +
geom_point(size = 5,
color = "cornflowerblue",
shape = 16) +
labs(x = "Flipper length (mm)") +
labs(y = "Bill length (mm)") +
labs(title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15)) +
theme(axis.title = element_text(size = 18)) +
theme(title = element_text(size = 20))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat) +
geom_point(aes(x = flipper_length_mm,
y = bill_length_mm),
size = 5,
color = "cornflowerblue",
shape = 16) +
labs(x = "Flipper length (mm)") +
labs(y = "Bill length (mm)") +
labs(title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15)) +
theme(axis.title = element_text(size = 18)) +
theme(title = element_text(size = 20))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat) +
geom_point(aes(x = flipper_length_mm,
y = bill_length_mm),
size = 5,
color = "cornflowerblue",
shape = 16) +
labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15)) +
theme(axis.title = element_text(size = 18)) +
theme(title = element_text(size = 20))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat) +
geom_point(aes(x = flipper_length_mm,
y = bill_length_mm),
size = 5,
color = "cornflowerblue",
shape = 16) +
labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
title = element_text(size = 20))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Lessons learned
- If the
aes()
is defined inside theggplot()
call, or separately, the subsequentgeom
s will inherit the definition (unless instructed not to). If it is defined inside thegeom
, it will only be visible to that layer.
Coloring/shaping by variable
library(ggplot2)
ggplot(data = dat) +
geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = species),
size = 5) +
scale_color_manual(
values = c(Adelie = "darkorange",
Chinstrap = "purple",
Gentoo = "cyan4"),
name = "Species"
+
) labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
title = element_text(size = 20),
legend.text = element_text(size = 18))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat) +
geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
shape = species),
size = 5) +
scale_shape_manual(
values = c(Adelie = 16,
Chinstrap = 6,
Gentoo = 3),
name = "Species"
+
) labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
title = element_text(size = 20),
legend.text = element_text(size = 18))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Lessons learned
- To color/size/shape the points (in this case) by a column in the data, we need to include the specification in the
aes()
call, as this instructsggplot2
to fetch the information from the provided data set. - The
scale_
family of functions can be used to set colors, shapes etc either manually, or by using a predefined palette (a set of colors). See e.g.scale_color_discrete()
,scale_color_continuous()
,scale_color_brewer()
,scale_color_gradient()
, …
Adding fixed lines
library(ggplot2)
ggplot(data = dat) +
geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = species),
size = 5) +
scale_color_manual(
values = c(Adelie = "darkorange",
Chinstrap = "purple",
Gentoo = "cyan4"),
name = "Species"
+
) theme_bw() +
geom_hline(yintercept = 42) +
geom_vline(xintercept = 205, linewidth = 2,
linetype = "dashed") +
geom_abline(slope = 0.25, intercept = -7,
color = "blue")
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Adding regression lines
library(ggplot2)
ggplot(data = dat,
aes(x = flipper_length_mm,
y = bill_length_mm)) +
geom_point(size = 5) +
labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
title = element_text(size = 20),
legend.text = element_text(size = 18)) +
geom_smooth(method = "lm", se = FALSE, linewidth = 2)
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat,
aes(x = flipper_length_mm,
y = bill_length_mm,
shape = species)) +
geom_point(size = 5) +
labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
title = element_text(size = 20),
legend.text = element_text(size = 18)) +
geom_smooth(method = "lm", se = FALSE, linewidth = 2)
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat,
aes(x = flipper_length_mm,
y = bill_length_mm,
color = species)) +
geom_point(size = 5) +
labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
title = element_text(size = 20),
legend.text = element_text(size = 18)) +
geom_smooth(method = "lm", se = FALSE, linewidth = 2)
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat,
aes(x = flipper_length_mm,
y = bill_length_mm)) +
geom_point(aes(color = species),
size = 5) +
labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
title = element_text(size = 20),
legend.text = element_text(size = 18)) +
geom_smooth(method = "lm", se = FALSE, linewidth = 2)
`geom_smooth()` using formula = 'y ~ x'
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Lessons learned
- Coloring or shaping the points will automatically also group them, and the grouping is inherited following the same rules as other aesthetics.
- To avoid aesthetics (e.g., such grouping) being inherited by other layers, specify the shape/color only in the layers where you want to apply it.
- The order that the
geom
layers are specified in the expression is also the order in which they will be plotted - use this to your advantage if you would like one layer to appear ‘in front of’ or ‘behind’ another.
Line plots
- Plots of time courses
- …
library(ggplot2)
ggplot(data = dat) +
aes(x = index,
y = bill_length_mm) +
geom_line() +
geom_point(size = 3)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat) +
aes(x = index,
y = bill_length_mm) +
geom_line() +
geom_point(data = dat |>
filter(species == "Adelie"),
size = 3)
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_point()`).
Box plots and violin plots
- Plots of cluster-wise expression level distributions in single-cell data
- QC plots - distribution of the number of reads, detected genes, … in single-cell data
- Plots of overall intensity distributions
- …
library(ggplot2)
ggplot(data = dat) +
aes(x = species,
y = bill_length_mm) +
geom_boxplot() +
theme_minimal() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18))
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_boxplot()`).
library(ggplot2)
ggplot(data = dat) +
aes(x = species,
y = bill_length_mm) +
geom_boxplot(outlier.size = -1) +
theme_minimal() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18)) +
geom_point(alpha = 0.5,
size = 2)
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_boxplot()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat) +
aes(x = species,
y = bill_length_mm) +
geom_boxplot(outlier.size = -1) +
theme_minimal() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18)) +
geom_jitter(alpha = 0.5,
size = 2,
height = 0,
width = 0.25)
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_boxplot()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat) +
aes(x = species,
y = bill_length_mm) +
geom_violin() +
theme_minimal() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18)) +
geom_jitter(alpha = 0.5,
size = 2,
height = 0,
width = 0.25)
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_ydensity()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Exercise 2: Create the following plot
library(ggplot2)
ggplot(data = dat,
aes(x = species,
y = bill_length_mm)) +
geom_jitter(alpha = 0.5, height = 0,
width = 0.25, color = "black") +
geom_boxplot(aes(fill = species),
outlier.size = -1,
alpha = 0.5) +
scale_fill_manual(
values = c(Adelie = "darkorange",
Chinstrap = "purple",
Gentoo = "cyan4"),
name = "Species"
+
) labs(x = "Species",
y = "Bill length (mm)",
title = "Bill length vs Species") +
theme_minimal() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
title = element_text(size = 20))
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_boxplot()`).
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Bar plots
- Plots comparing sizes of categories
- Plots comparing summary values where there is a clear baseline
- Distribution plots where only a few values are possible
- Summary plots (mean +/- sd) - show individual points wherever possible!
- …
library(ggplot2)
ggplot(data = dat |>
slice(1:10)) +
aes(x = factor(index,
levels = index),
y = bill_length_mm) +
geom_bar(stat = "identity") +
labs(x = "Penguin",
y = "Bill length (mm)") +
theme_minimal() +
theme(axis.title = element_text(size = 18),
axis.text = element_text(size = 15))
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_bar()`).
library(ggplot2)
ggplot(data = dat |>
slice(1:10)) +
aes(x = factor(index,
levels = index),
y = bill_length_mm) +
geom_col() +
labs(x = "Penguin",
y = "Bill length (mm)") +
theme_minimal() +
theme(axis.title = element_text(size = 18),
axis.text = element_text(size = 15))
Warning: Removed 1 row containing missing values or values outside the scale range
(`geom_col()`).
Lessons learned
- By default,
geom_bar()
usesstat = "count"
, which counts the number of appearances of each category on the x-axis and displays this count on the y-axis. To display pre-calculated counts/values in a bar plot, either setstat = "identity"
ingeom_bar()
, or usegeom_col()
(and in both cases, define which column to take the values from via they
aesthetic).
library(ggplot2)
ggplot(data = dat) +
aes(x = island) +
theme_minimal() +
theme(axis.title = element_text(size = 18),
axis.text = element_text(size = 15)) +
geom_bar(stat = "count")
library(ggplot2)
ggplot(data = dat) +
aes(x = island,
fill = sex) +
theme_minimal() +
theme(axis.title = element_text(size = 18),
axis.text = element_text(size = 15)) +
geom_bar(stat = "count")
library(ggplot2)
ggplot(data = dat) +
aes(x = island,
fill = sex) +
theme_minimal() +
theme(axis.title = element_text(size = 18),
axis.text = element_text(size = 15)) +
geom_bar(stat = "count",
position = "dodge") +
scale_fill_manual(
values = c(male = "orangered",
female = "cornflowerblue")
)
Density plots
library(ggplot2)
ggplot(data = dat) +
aes(x = bill_length_mm) +
geom_density() +
theme_minimal() +
theme(axis.title = element_text(size = 18),
axis.text = element_text(size = 15))
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_density()`).
library(ggplot2)
ggplot(data = dat) +
aes(x = bill_length_mm,
fill = species) +
geom_density(alpha = 0.25) +
theme_minimal() +
theme(axis.title = element_text(size = 18),
axis.text = element_text(size = 15))
Warning: Removed 2 rows containing non-finite outside the scale range
(`stat_density()`).
Facetting
library(ggplot2)
ggplot(data = dat) +
geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = species),
size = 5) +
scale_color_manual(
values = c(Adelie = "darkorange",
Chinstrap = "purple", Gentoo = "cyan4"),
name = "Species"
+
) labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
strip.text = element_text(size = 18),
title = element_text(size = 20),
legend.text = element_text(size = 18)) +
facet_wrap(~ sex, ncol = 1)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
ggplot(data = dat) +
geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = species),
size = 5) +
scale_color_manual(
values = c(Adelie = "darkorange",
Chinstrap = "purple", Gentoo = "cyan4"),
name = "Species"
+
) labs(x = "Flipper length (mm)",
y = "Bill length (mm)",
title = "Bill length vs flipper length") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
strip.text = element_text(size = 18),
title = element_text(size = 20),
legend.text = element_text(size = 18)) +
facet_grid(island ~ sex)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Exercise 3: Create the following plot
library(ggplot2)
ggplot(data = dat |>
filter(!is.na(sex)),
aes(x = bill_length_mm,
fill = sex)) +
geom_density(alpha = 0.5) +
facet_wrap(~ species) +
scale_fill_manual(
values = c(male = "yellow",
female = "forestgreen")) +
labs(x = "Bill length (mm)") +
theme_bw() +
theme(axis.text = element_text(size = 15),
axis.title = element_text(size = 18),
strip.text = element_text(size = 15))
Assigning plots to variables, and combining plots
library(ggplot2)
<- ggplot(data = dat) +
g1 geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = species),
size = 5)
g1
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
+ theme_minimal() g1
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
+ theme_dark() +
g1 scale_color_manual(
values = c(Adelie = "darkorange",
Chinstrap = "purple",
Gentoo = "cyan4"))
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
library(ggplot2)
<- ggplot(data = dat) +
g1 geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = species),
size = 3)
<- ggplot(data = dat) +
g2 geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = sex),
size = 3)
<- cowplot::plot_grid(
g3 labels = c("A", "B"),
g1, g2, nrow = 1)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
g3
library(ggplot2)
<- ggplot(data = dat) +
g1 geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = species),
size = 3)
<- ggplot(data = dat) +
g2 geom_point(aes(x = flipper_length_mm,
y = bill_length_mm,
color = sex),
size = 3)
<- cowplot::plot_grid(
g3 labels = c("A", "B"),
g1, g2, nrow = 1)
Warning: Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
Removed 2 rows containing missing values or values outside the scale range
(`geom_point()`).
<- ggplot(data = dat) +
g4 geom_line(aes(x = index,
y = bill_length_mm))
::plot_grid(
cowplotlabels = c("", "C"),
g3, g4, ncol = 1)
More resources
- Official
ggplot2
resources: - Community resources
- R Graph Gallery - ggplot2
- Top 50 ggplot2 Visualizations (with code)
- ggplot2 extensions
- ggplot2 workshop on YouTube, part 1 and part 2
- R Graph Gallery - ggplot2
- Laying out multiple plots on a page:
Session info
R version 4.4.1 (2024-06-14)
Platform: aarch64-apple-darwin20
Running under: macOS 15.0.1
Matrix products: default
BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: Europe/Zurich
tzcode source: internal
attached base packages:
[1] grid stats graphics grDevices utils datasets methods
[8] base
other attached packages:
[1] lubridate_1.9.3 forcats_1.0.0 stringr_1.5.1 dplyr_1.1.4
[5] purrr_1.0.2 readr_2.1.5 tidyr_1.3.1 tibble_3.2.1
[9] tidyverse_2.0.0 ggplot2_3.5.1 BiocStyle_2.32.1 png_0.1-8
[13] knitr_1.48
loaded via a namespace (and not attached):
[1] utf8_1.2.4 generics_0.1.3 lattice_0.22-6
[4] stringi_1.8.4 hms_1.1.3 digest_0.6.37
[7] magrittr_2.0.3 evaluate_1.0.1 timechange_0.3.0
[10] fastmap_1.2.0 Matrix_1.7-0 jsonlite_1.8.9
[13] BiocManager_1.30.25 mgcv_1.9-1 fansi_1.0.6
[16] scales_1.3.0 cli_3.6.3 rlang_1.1.4
[19] crayon_1.5.3 cowplot_1.1.3 splines_4.4.1
[22] bit64_4.5.2 munsell_0.5.1 withr_3.0.1
[25] yaml_2.3.10 tools_4.4.1 parallel_4.4.1
[28] tzdb_0.4.0 colorspace_2.1-1 vctrs_0.6.5
[31] R6_2.5.1 lifecycle_1.0.4 htmlwidgets_1.6.4
[34] bit_4.5.0 vroom_1.6.5 pkgconfig_2.0.3
[37] archive_1.1.9 pillar_1.9.0 gtable_0.3.5
[40] glue_1.8.0 xfun_0.48 tidyselect_1.2.1
[43] rstudioapi_0.16.0 farver_2.1.2 nlme_3.1-164
[46] htmltools_0.5.8.1 rmarkdown_2.28 labeling_0.4.3
[49] compiler_4.4.1