Chapter P2 — R & RStudio
R1 is the statistical language that powers most of this workshop — Seurat, Bioconductor, DESeq2, and the tidyverse all live here. This chapter is a focused refresher on the R language, the RStudio interface, R’s core data structures, writing and calling functions, and the tidyverse grammar that you will see in every tutorial. The shell/command-line environment is covered in Chapter P1.
This is the reading for Chapter P2 — R & RStudio, the second hour of the optional 2-hour Tuesday refresher. It pairs with the P2 lecture slides. If you are new to R, read this before Day 1 so the hands-on tutorials assume a common foundation. Everything here is display-only — open an R session or RStudio and try the examples yourself.
Keep a cheat sheet open. The Cheat Sheets tab collects one-page Base R, dplyr/tidyverse, ggplot2, and Unix references that pair with this module. You’ll put all of this to work starting in Module 01.
Why R?
R1 is the lingua franca of statistical genomics, and the reason this workshop is built on it: the Seurat and Bioconductor ecosystems live here.
- Good general scripting tool for statistics and mathematics
- Powerful, flexible, and free
- Runs on all computer platforms
- New enhancements coming out all the time
- Superb data management and graphics capabilities
- Reproducibility — keep your scripts to see exactly what was done
- You can write your own functions
- Lots of online help available
- Can use a nice GUI front end such as RStudio
- Can embed your R analyses in dynamic, polished files using Quarto Markdown
RStudio — the four-pane interface
When RStudio opens you see four panes:
- Source (top-left) — where you edit scripts (
.R) and Quarto documents (.qmd). Run a line or selection with Ctrl/Cmd + Enter. - Console (bottom-left) — the live R session. Type here and press Enter to run immediately.
- Environment / History (top-right) — every object (variable, data frame) you have created this session.
- Files / Plots / Packages / Help (bottom-right) — file browser, your plots, installed packages, and help pages.
Write code in the Source pane so it is saved and reproducible; use the Console for quick throwaway experiments.
R Scripts and Quarto Markdown
- Often we want to write scripts that can just be run — R scripts (
.Rfiles) - We can also embed code in Quarto Markdown files for richer annotations
- This improves interpretability and reproducibility
- You can insert R code chunks into Quarto Markdown documents
- A series of R commands that will be executed, with comments using
# - Can have pipes (
|>) to connect one step to the next
R Basics
Basic Math in R
4 * 4
(4 + 3 * 2^2)- Commands can be submitted through the terminal, console, or scripts
- R follows the normal priority of mathematical evaluation (PEMDAS)
- Parentheses control order of operations just as in algebra
Assigning Variables
x <- 2
x * 3
y <- x * 3
y - 2- Variables are assigned values using the
<-operator - Variable names must begin with a letter
- R is case sensitive (
xandXare different variables) - The result of calculations can be assigned to new variables
- Names like
3y <- 3will not work
Every object you create with <- lives in the global environment for the duration of your R session. When you restart R, the workspace is cleared. This is why the top of every workshop tutorial has a library(...) block and file-loading lines — the session always starts fresh, and scripts must be self-contained.
Arithmetic Operations and Functions
# Common math functions
x + 2
x^2
log(x) # natural log (built-in function)
sqrt(x)
log(x, base = 10) # arguments modify behavior
# Assigning results
y <- 67
print(y)
x <- 124
z <- (x * y)^2
print(z)logis a built-in function — the object goes in parentheses- Functions can take arguments that modify behavior (e.g.,
log(x, base = 10)) - The outcome of calculations can be assigned to new variables
- Use
print()to display values explicitly
Getting Help in R
help(mean) # open help page
?mean # same as help()
example(mean) # show examples from the help page
help.search("mean") # search help pages
apropos("mean") # find functions with "mean" in name
args(mean) # show function arguments- Help pages exist for all functions explaining parameters and usage
- The
example()function is especially useful for seeing how functions work - When in doubt, check the help page!
Data Types and Structures
Strings
x <- "I Love"
print(x)
y <- "Bioengineering"
print(y)
z <- c(x, y) # c() stands for concatenate
print(z)- Characters must be set off by quotation marks
- The
c()function concatenates values into a vector - Reusing variable names overwrites previous assignments
Vectors
R “thinks in vectors”: a vector is an ordered set of values of one type. Mathematical operations and most built-in functions apply element-wise to every value at once — no loop needed. This design choice makes single-cell code expressive: nFeature_RNA > 200 on a 14,000-element vector returns a 14,000-element logical mask in one expression, and you then pass that mask back to subset the data. Wherever you see code that processes thousands of genes or cells in one expression, vectorization is doing the work.
# Create a vector with c()
x <- c(2, 3, 4, 2, 1, 2, 4, 5, 10, 8, 9)
print(x)
# Operations work element-wise
y <- x^2
print(y)
# Plot vectors
plot(x, y)Types of Vectors
| Type | Description | Example |
|---|---|---|
int |
Integers | 1L, 2L, 3L |
dbl |
Doubles (real numbers) | 3.14, 2.718 |
chr |
Character strings | "hello", "DNA" |
lgl |
Logical (TRUE/FALSE) | TRUE, FALSE, NA |
fctr |
Factors (categorical variables) | factor(c("a","b","a")) |
dttm |
Date-times | Sys.time() |
date |
Dates | Sys.Date() |
Special values to know:
- Logical vectors can take:
FALSE,TRUE, orNA(not available) - Doubles have four special values:
NA,NaN(not a number),Inf,-Inf - Integer and double vectors are collectively known as numeric vectors
- In R, numbers are doubles by default
# Character and logical vectors — common in scRNA-seq metadata
genes <- c("CD3D", "MS4A1", "LYZ", "NKG7") # character
expressed <- c(TRUE, TRUE, FALSE, TRUE) # logical
genes[expressed] # keep only elements where TRUE — logical subsettingBasic Statistics on Vectors
mean(x)
median(x)
var(x)
sd(x) # standard deviation
log(x)
sqrt(x)
sum(x)
length(x)
sample(x, replace = TRUE) # random sample with replacement- Many built-in functions operate on entire vectors
sample()takes an argumentreplace = TRUE— arguments modify function behavior- Use
?function_nameto see the help page for any function
Creating Vectors with Sequences
# Ascending sequence
seq_1 <- seq(0.0, 10.0, by = 0.1)
print(seq_1)
# Descending sequence
seq_2 <- seq(10.0, 0.0, by = -0.1)
# Integer shorthand
1:10 # same as seq(1, 10, by = 1)
# Element-wise operations
seq_square <- (seq_2)^2
print(seq_square)seq()creates sequences with a specified start, end, and step size- Operations on sequences work element-wise, just like simple vectors
Sampling from Distributions
# Draw from normal distribution
x <- rnorm(10000, 0, 10)
hist(x)
# Overlay the theoretical density curve
x <- rnorm(1000, 0, 100)
hist(x, xlim = c(-500, 500), probability = TRUE)
curve(dnorm(x, 0, 100), xlim = c(-500, 500), add = TRUE, col = 'red', lwd = 2)
# Draw uniform random integers
y <- sample(1:10000, 10000, replace = TRUE)
# Combine and plot
xy <- cbind(x, y)
plot(x, y)rnorm(n, mean, sd)drawsnrandom values from a normal distributiondnorm()generates the probability density function for overlayingcurve()plots a function;add = TRUEoverlays on an existing plotcbind()binds columns together into a matrix
Data Frames
A data frame is the workhorse for tabular data in R. It is a rectangular table where each column is a vector (all one type) and each row is an observation. Unlike a matrix, different columns can hold different types — one column of cell barcodes (character), another of UMI counts (numeric), another of cluster labels (factor). In the single-cell world, per-cell metadata (seu@meta.data) is a data frame: one row per cell, columns for nCount_RNA, nFeature_RNA, percent.mt, cluster, sample, and so on. Every filter and summary in the QC tutorial operates on this data frame.
Creating Data Frames
# Build a data frame from individual vectors
cells <- data.frame(
barcode = c("AAAC", "AAAG", "AAAT", "AACA"),
sample = c("CTRL", "CTRL", "STIM", "STIM"),
nCount_RNA = c(8500, 12000, 6400, 9100),
nFeature = c(2300, 3100, 1900, 2500)
)
cells # print the whole thing
str(cells) # structure: column names, types, first values
summary(cells) # per-column summary statistics
nrow(cells); ncol(cells)
colnames(cells)data.frame()takes named vectors as columnsstr()is your first diagnostic — it shows column types and a previewsummary()provides min, quartiles, mean, max, and NA counts
The hydrogel_concentration example below shows a data frame with a factor column — a common pattern for experimental design variables:
hydrogel_concentration <- factor(c("low", "high", "high", "high",
"medium", "medium", "medium", "low"))
compression <- c(3.4, 3.4, 8.4, 3, 5.6, 8.1, 8.3, 4.5)
conductivity <- c(0, 9.2, 3.8, 5, 5.6, 4.1, 7.1, 5.3)
mydata <- data.frame(hydrogel_concentration, compression, conductivity)
row.names(mydata) <- c("Sample_1", "Sample_2", "Sample_3", "Sample_4",
"Sample_5", "Sample_6", "Sample_7", "Sample_8")
print(mydata)Reading and Writing Data
# Comma-separated files
YourFile <- read.table('yourfile.csv', header = TRUE, row.names = 1, sep = ',')
# OR use the convenience function:
YourFile <- read.csv('yourfile.csv')
# Tab-separated files
YourFile <- read.table('yourfile.txt', header = TRUE, row.names = 1, sep = '\t')
# Tidyverse versions (return tibbles — recommended)
YourFile <- read_csv('yourfile.csv')
YourFile <- read_tsv('yourfile.txt')
# Excel files
library(readxl)
YourFile <- read_excel('yourfile.xlsx')read_csv() column types
read_csv() (from readr) infers column types from the first 1,000 rows and prints a message showing what it guessed. To override — for example, to force a barcode column to stay as character rather than be coerced to something else — use the col_types argument:
read_csv("counts.csv", col_types = cols(barcode = col_character(),
nCount_RNA = col_double()))This matters whenever IDs look numeric (e.g., "1", "2", …) but should remain text.
# Write to CSV
write.table(YourFile, "yourfile.csv", quote = FALSE, row.names = TRUE, sep = ",")
# OR:
write.csv(YourFile, "yourfile.csv")
# Write to TSV
write.table(YourFile, "yourfile.txt", quote = FALSE, row.names = TRUE, sep = "\t")
# Tidyverse version
write_csv(YourFile, "yourfile.csv")header = TRUEindicates the first row contains column namesrow.names = 1indicates the first column contains row namessepspecifies the delimiter (,for CSV,\tfor TSV)read_csv()/read_tsv()return tibbles — enhanced data frames with friendlier printing
Indexing Data Frames
# Access by column number
print(mydata[, 2])
# Access by column name (preferred)
print(mydata$compression)
# Access by row number
print(mydata[2, ])
# Access a specific cell
print(mydata[2, 3])
# Subset based on a condition
mydata[mydata$compression > 5, ]
# Subset using a character condition (common for metadata filtering)
cells[cells$sample == "STIM", ]
# Plot two columns against each other
plot(mydata$compression, mydata$conductivity)[row, column]notation accesses specific elements$notation accesses columns by name (preferred — more readable and tab-completes in RStudio)- Leaving a dimension empty selects all (e.g.,
[, 2]= all rows, column 2) - Logical conditions return
TRUE/FALSEvectors for subsetting — the same vectorization as above
Other R Data Structures
The earlier sections cover vectors and data frames, which carry most of the workflow. Three other structures appear regularly in scRNA-seq code; you should recognize them on sight.
Lists — the “pocket that holds anything”
A list is the most flexible R container: each slot can hold a different kind of thing — a vector of numbers, a string, another list, a data frame, a function. Seurat objects, SingleCellExperiment objects, and most output of lapply() / purrr::map() are technically lists under the hood.
experiment <- list(
sample_id = "donor_01_CTRL",
age = 42,
cell_types = c("B cell", "CD4 T", "Monocyte", "NK"),
qc_metrics = data.frame(
metric = c("nCount_RNA", "nFeature_RNA", "percent.mt"),
median = c(8500, 2300, 4.2)
)
)
# Access by name with $:
experiment$sample_id
experiment$cell_types
experiment$qc_metrics$median
# Or by [[index]]:
experiment[[3]] # third element (cell_types)
experiment[["cell_types"]] # same, by name — clearer[] vs [[]] on a list. experiment[1] returns a length-1 list (a sub-list); experiment[[1]] returns the content of the first slot. The double-bracket form is what you usually want when accessing list elements in code.
Matrices — like data frames, but one type only
A matrix is a 2-D structure where every cell is the same type — typically all numeric. Faster than a data frame for math, more limited because you cannot mix text and numbers. Most scRNA-seq counts matrices are matrices — specifically sparse matrices of class dgCMatrix from the Matrix package, which is a memory-efficient format that only stores non-zero values.
m <- matrix(1:12, nrow = 3, ncol = 4)
m
# [,1] [,2] [,3] [,4]
# [1,] 1 4 7 10
# [2,] 2 5 8 11
# [3,] 3 6 9 12
# Indexing — same [row, col] notation as data frames
m[2, 3] # 8
m[, 2] # column 2 as a vector
m[1, ] # row 1 as a vector
# The 10x counts matrix is a (sparse) matrix:
# library(Seurat)
# counts <- Read10X("data/filtered_feature_bc_matrix/")
# class(counts) # "dgCMatrix"
# dim(counts) # ~33538 features × ~1222 cells, mostly zeroA typical 10x dataset is ~95% zeros. A dense matrix of 30,000 genes × 14,000 cells in double precision would be ~3.4 GB. The same counts as a dgCMatrix (which stores only non-zero entries plus their coordinates) is ~150 MB. Every Seurat, Scanpy, and Bioconductor workflow uses sparse matrices internally for this reason.
Factors — categorical variables with fixed levels
A factor is R’s representation of categorical data: a character vector plus a defined, ordered set of allowed values called levels. Without a factor, R treats condition labels as arbitrary text with no notion of a reference. With a factor, the first level is the reference in any statistical model. Getting the level order right is not cosmetic — in DESeq2, a model term ~ condition compares all other levels against the first one. Swap the levels and the sign of every fold-change flips.
# Plain character vector — no reference concept
condition_chars <- c("CTRL", "STIM", "CTRL", "STIM", "STIM")
# As a factor — explicit levels: CTRL is the reference
condition <- factor(condition_chars, levels = c("CTRL", "STIM"))
condition
# [1] CTRL STIM CTRL STIM STIM
# Levels: CTRL STIM
table(condition)
levels(condition)
nlevels(condition)factor(levels = )
factor(x, levels = c("CTRL", "STIM")) sets the reference to "CTRL". Omit levels and R sorts alphabetically, which may or may not be what you want. In downstream analysis:
# Change the reference after creation
condition <- relevel(condition, ref = "CTRL")
# Drop levels that are no longer present (after subsetting)
condition <- droplevels(condition)
# Ordered factor for ordinal data
dose <- factor(c("low", "med", "high"), levels = c("low", "med", "high"),
ordered = TRUE)In Seurat objects, Idents() is a factor; meta.data$stim is a factor; cluster assignments (seurat_clusters) are factors. Knowing how to manipulate them — relevel() to change the reference, droplevels() to remove unused levels, factor(..., ordered = TRUE) for ordinal data — is fundamental for downstream analysis.
relevel() is used in Tutorial 06 — Pseudobulk DE to set "untreated" as the DESeq2 reference level — getting this wrong flips the sign of every fold-change. Factor levels on Idents() also control which cell type is compared by default in Tutorial 03 marker finding.
Tidy Data Principles
- Store data in nonproprietary formats (plain ASCII text / flat files)
- Leave an uncorrected original file when doing analyses
- Use descriptive names for your data files and variables
- Include a header line with descriptive variable names
- Maintain effective metadata about the data (data dictionary)
- When you add observations, add rows
- When you add variables, add columns
- A column of data should contain only one data type
Types of Data
| Class | Subtype | Example | R type |
|---|---|---|---|
| Categorical | Ordinal (ordered) | small, medium, large | ordered factor |
| Categorical | Nominal (unordered) | apples, oranges | factor / character |
| Quantitative | Ratio (true zero) | kilograms, dollars | numeric |
| Quantitative | Interval (no true zero) | temperature, calendar year | numeric / integer |
Factor is the R type for categorical variables with a fixed, known set of possible values.
Base R Visualization
Basic Plotting
seq_1 <- seq(0.0, 10.0, by = 0.1)
plot(seq_1,
xlab = "space",
ylab = "function of space",
type = "p", # "p" = points, "l" = lines, "b" = both
col = "red",
main = "Basic R Plot")plot()is the main high-level plotting function in base Rtypeoptions:"p"(points),"l"(lines),"b"(both),"h"(vertical spike lines — not a histogram; usehist()for that)- Most workshop graphics use
ggplot2(see below) for publication quality
Multi-Panel Figures
par(mfrow = c(2, 2)) # 2 rows, 2 columns
plot(seq_1, xlab = "time", ylab = "pop 1", type = "p", col = 'red')
plot(seq_2, xlab = "time", ylab = "pop 2", type = "p", col = 'green')
plot(seq_square, xlab = "time", ylab = "pop 2 ^2", type = "p", col = 'blue')
plot(seq_1^2, xlab = "time", ylab = "pop 1 ^2", type = "l", col = 'orange')par(mfrow = c(rows, cols))sets up a multi-panel layout- Subsequent
plot()calls fill panels left-to-right, top-to-bottom
Summary Statistics and Figures
# Summary statistics
summary(mydata$compression)
# Histogram
hist(mydata$compression)
# Boxplot by group — formula notation: y ~ x
boxplot(compression ~ hydrogel_concentration, data = mydata,
col = "steelblue",
ylab = "Compression",
xlab = "Hydrogel Concentration",
main = "Compression by Hydrogel Concentration")- The formula
y ~ xmeans “y grouped by x” — used byboxplot(),lm(),DESeq2, and many others - Boxplots show median, IQR, and potential outliers
Functions in Depth
You have already used many R functions (mean(), c(), read.csv(), …). Two practical points are worth knowing.
Named vs. positional arguments
# Positional — values are assigned to arguments in declaration order
round(3.14159, 2) # 3.14
round(2, 3.14159) # 2 — argument order matters!
# Named — explicit, order-independent, far more readable
round(x = 3.14159, digits = 2)
round(digits = 2, x = 3.14159) # same result
# Recommended in real code: positional for the first one or two obvious args,
# named for the rest
seq(from = 1, to = 10, by = 0.5)
# In Seurat / scRNA-seq code, almost everything uses named args
# because the function signatures are long:
# CreateSeuratObject(counts = mat, project = "study1",
# min.cells = 3, min.features = 200)The rule: use named arguments whenever there is any ambiguity. They are self-documenting, survive package version bumps better, and are easier for someone — including future-you — to read.
Defining your own functions
# A function that computes the coefficient of variation of a numeric vector
cv <- function(x, na.rm = TRUE) {
if (na.rm) x <- x[!is.na(x)]
sd(x) / mean(x)
}
cv(c(1, 2, 3, 4, 5)) # 0.527
cv(c(1, 2, NA, 4, 5)) # 0.609 (after dropping NA)You will write helper functions all the time in scRNA-seq pipelines — typically as 5–15-line wrappers around Seurat/Bioconductor calls. Tutorial 06 — Pseudobulk DE defines a run_de(ct) function in exactly this pattern.
library() vs. install.packages()
This is the single most common point of confusion for new R users:
You install a package once; you load it every session.
install.packages("pkg")downloads the package onto your machine. Do it once (from CRAN); for Bioconductor packages useBiocManager::install("pkg").library(pkg)makes that installed package’s functions available in your current R session. Do it every time you start R.
install.packages("tidyverse") # ONCE — downloads and installs
library(tidyverse) # EVERY SESSION — makes its functions usableRestarting R does not require re-installing — but it does require re-library()-ing. That is why every workshop tutorial starts with a library(...) block. You can also call one function without loading the whole package via package::function(), e.g. muscData::Kang18_8vs8().
The canonical, up-to-date package install block (CRAN + Bioconductor + GitHub packages for all workshop modules) lives in Tutorial 00 — Get Up and Running. Do not install from memory — copy the block from that page so you get the correct setdiff() guard and the exact package versions the workshop expects.
The tidyverse — dplyr Data Manipulation
Most modern R for biology is written in tidyverse style2 — a coherent family of packages (dplyr, tidyr, ggplot2, readr, and more) that share a consistent, pipe-friendly grammar. Six dplyr verbs cover ~80% of what you will do with data frames:
| Verb | What it does |
|---|---|
filter() |
Keep rows that match a condition |
select() |
Keep / drop / reorder columns |
mutate() |
Add or modify columns |
arrange() |
Sort rows |
summarise() |
Collapse to one row per group (paired with group_by()) |
group_by() |
Set the grouping for downstream summarise / mutate |
|>
The pipe |> (built into R since 4.1; the older %>% from magrittr also works) passes the result of one expression as the first argument of the next. This chains dplyr verbs into pipelines that read top-to-bottom — the same direction you think about data transformations:
result <- data |>
filter(condition) |>
mutate(new_col = ...) |>
group_by(group_var) |>
summarise(n = n())Read it as: “take data, then filter, then add a column, then group, then summarise.” Without the pipe, you would either nest calls inside each other (hard to read) or create many intermediate variables (clutters the environment). Every tutorial from Module 01 onward uses this style.
library(tidyverse)
# Suppose `meta` is per-cell metadata: barcode, sample, cluster, nCount_RNA, nFeature_RNA
top10_clusters_per_sample <- meta |>
filter(nFeature_RNA > 200, nFeature_RNA < 2500) |> # QC filter
group_by(sample, cluster) |>
summarise(
n_cells = n(),
median_umi = median(nCount_RNA),
.groups = "drop"
) |>
arrange(sample, desc(n_cells)) |>
group_by(sample) |>
slice_head(n = 10)Read top-to-bottom: “take meta, filter by QC thresholds, group by sample × cluster, summarise to one row per group, sort, take top 10 per sample.” This style appears throughout the workshop tutorials.
| Skill | First used in |
|---|---|
filter() on per-cell QC metadata |
Tutorial 01 QC filtering |
mutate() to add derived columns |
Tutorial 01 percent.mt |
group_by() + summarise() to aggregate counts per donor × condition |
Tutorial 06 pseudobulk DE |
arrange(), select() |
Throughout Tutorials 01–08 |
The |> pipe |
Every tutorial from Tutorial 01 onward |
For the canonical free reference: R for Data Science (2e) — Wickham, Çetinkaya-Rundel & Grolemund.
Visualization with ggplot2
The Base R plot() examples above work but are limited. ggplot2 is the standard for publication-quality figures and is what you will see throughout the workshop. The key insight is the grammar of graphics: a plot is built from layered components.
| Component | What it is | Examples |
|---|---|---|
| Data | The data frame | mpg, seu@meta.data, … |
Aesthetics (aes()) |
Mappings: which column to which visual property | aes(x = pc1, y = pc2, color = celltype) |
| Geoms | The visual mark | geom_point(), geom_bar(), geom_violin(), geom_tile() |
| Stats | Pre-plot transformations | stat_smooth(), stat_summary() |
| Scales | Color, axis, size scales | scale_color_brewer(), scale_x_log10() |
| Facets | Sub-plots by group | facet_wrap(~sample), facet_grid(condition ~ celltype) |
| Themes | Non-data display | theme_minimal(), theme(legend.position = "bottom") |
A minimum-viable ggplot:
library(ggplot2)
# A scatter — one point per cell, colored by cell type
ggplot(seu@meta.data, aes(x = nCount_RNA, y = nFeature_RNA, color = celltype)) +
geom_point(alpha = 0.5, size = 0.6) +
scale_x_log10() +
scale_y_log10() +
labs(title = "QC: counts vs features",
x = "Total UMI per cell (log)",
y = "Detected genes per cell (log)") +
theme_minimal()Build up by adding layers with +:
# Add a smoothed regression line and facet by sample
ggplot(seu@meta.data, aes(x = nCount_RNA, y = nFeature_RNA)) +
geom_point(alpha = 0.4, size = 0.6, color = "#34495e") +
geom_smooth(method = "lm", color = "#c0392b") +
facet_wrap(~ stim) +
scale_x_log10() +
scale_y_log10() +
labs(title = "Counts × features per sample") +
theme_bw(base_size = 12)geom_point()— UMAP / PCA scatter, QC scattergeom_violin()+geom_jitter()—VlnPlot()in Seurat is built on thesegeom_tile()— heatmaps, e.g. cell-type × marker-gene dot plotsgeom_bar(stat = "identity")/geom_col()— proportion bar plotsgeom_smooth()— adding regression lines to QC scatters
Parallel reading. scNotebooks Module 02 — Introduction to R + ggplot2 covers this same material as a runnable notebook (with R kernel) on Google Colab. Open it alongside this chapter if you want a runnable companion.
Prerequisites and Skills Summary
| Skill covered here | First used in |
|---|---|
| Assignment, vectors, basic math | Every tutorial — vectors underpin all data manipulation |
filter(), mutate() on per-cell metadata |
Tutorial 01 — QC filtering, adding percent.mt |
group_by() + summarise() |
Tutorial 06 — pseudobulk aggregation per donor × condition |
factor() / relevel() for reference levels |
Tutorial 06 — DESeq2 design; wrong level flips all fold-change signs |
read_csv() / write_csv(), file paths |
Throughout Tutorials 01–08 (.rds hand-off files, result tables) |
The |> pipe |
Every tutorial from Tutorial 01 onward |
ggplot2: geom_point(), geom_violin(), facet_wrap() |
Tutorial 01 QC plots; used in every tutorial through 08 |
install.packages() once vs library() every session |
Tutorial 00 §1; required at the top of every tutorial |
Additional Resources
- R for Data Science (2e) — Wickham, Çetinkaya-Rundel & Grolemund — the standard free book for the tidyverse workflow.
- Advanced R — Wickham — how R really works (environments, S3/S4, functionals), for when you are ready to go deeper.
- ggplot2: Elegant Graphics for Data Analysis — Wickham et al. — the comprehensive free book on
ggplot2. - Software Carpentry — Programming with R — gentle, hands-on lessons.
- Quick reference: the Cheat Sheets (Base R, dplyr, tidyr, ggplot2).
- Statistical background: Appendix B — Statistical Foundations covers the distributions, hypothesis testing, and multiple-comparison corrections that underpin DESeq2 and marker finding.
The foundational citations for the tools this chapter covers are the R language itself1 and the tidyverse2.