By the end of this practical you will be able to:
B cells are the antibody-producing cells of the adaptive immune system. Each B cell expresses a unique B cell receptor (BCR) that determines its antigen specificity. This specificity is generated during the B cell development in a process called V(D)J recombination. The receptor genes are assembled by randomly selecting and joining gene segments from three pools — Variable (V), Diversity (D), and Joining (J) — with additional nucleotides inserted at the junctions.
CDR3 region (hypervariable):
--|V segment|--|N|--|D segment|--|N|--|J segment|--
The resulting CDR3 (Complementarity Determining Region 3) is the most variable part of the BCR and the main determinant of antigen binding. Because the nucleotide additions at the junctions are random, each B cell carries a unique CDR3 — the “molecular fingerprint” of that cell and its descendants.
A complete BCR (and the secreted antibody) is a heterodimer: one heavy chain (encoded by IGH, assembled from V, D, and J segments) paired with one light chain (encoded by IGK or IGL, assembled from V and J segments only). Both chains contribute a CDR3, but the heavy-chain CDR3 is by far the more variable and is the primary focus of repertoire analysis.
When a B cell recognises its cognate antigen with sufficient affinity, it enters a germinal centre reaction in which two crucial processes occur:
B cells originating from the same VDJ rearrangement event are defined by the same V, D, J genes and highly similar CDR3 nucleotides. These B cell groups are called clones — they all descend from a single progenitor B cell and share the same antigen specificity.
10x Genomics’ V(D)J enrichment protocol captures full-length BCR sequences from single cells alongside their gene expression profiles. This allows us to:
10x Chromium (5' library GEX + VDJ)
↓
Cell Ranger (alignment, UMI counting, VDJ assembly)
↓
IgBLAST / MakeDb (germline assignment, AIRR format)
↓
┌──────────────────────────────────────────────────┐
│ This practical (R / Bioconductor) │
│ Quality control → Isotype analysis → │
│ Clonal inference → Diversity → SHM → Trees │
└──────────────────────────────────────────────────┘
For a workflow description on how to convert Cell Ranger output into AIRR standard format see: https://changeo.readthedocs.io/en/stable/examples/10x.html The conversion has already been performed on the data that is made available for the CSAMA lab.
The Adaptive Immune Receptor Repertoire (AIRR) Community has defined a standard data format for representing BCR/TCR sequences. Each row in an AIRR TSV file represents one rearranged receptor sequence, with standardised column names such as:
| Column | Description |
|---|---|
sequence_id |
Unique identifier for this sequence |
cell_id |
Cell barcode |
locus |
IGH, IGK, IGL, TRA, TRB, etc. |
v_call, d_call, j_call |
Assigned V, D, J genes |
junction |
CDR3 nucleotide sequence |
junction_aa |
CDR3 amino acid sequence |
c_call |
Constant region gene (isotype) |
productive |
Whether the rearrangement is productive |
sequence_alignment |
V(D)J sequence aligned to germline |
germline_alignment |
Inferred germline sequence |
We will use the Immcantation suite of R packages, which provides a comprehensive toolkit for BCR/TCR repertoire analysis:
| Package | Purpose |
|---|---|
airr |
Read and write AIRR-format files |
alakazam |
Core repertoire analysis: V gene usage, diversity, clonal abundance |
shazam |
Mutation analysis, clonal threshold identification |
scoper |
Single-cell-aware clonal grouping |
dowser |
Phylogenetic lineage tree construction and visualisation |
library(airr)
library(alakazam)
library(shazam)
library(scoper)
library(dowser)
library(ggplot2)
library(dplyr)
library(tidyr)
library(patchwork)
library(stringr)The data in this practical come from a single-cell multiomics experiment of peripheral blood B cells from three donors. Cells were captured using the 10x Genomics Chromium 5’ platform, which simultaneously captures gene expression (GEX) and V(D)J BCR sequences from the same cell.
Preprocessing (already done):
AssignGenes.py pipelineMakeDb.pyWe therefore start from an AIRR TSV file and a metadata table containing cluster annotations derived from the gene expression data.
The airr::read_rearrangement() function reads an
AIRR-format TSV and returns a data frame.
receptors <- airr::read_rearrangement("../data/BCR_data_sequences_igblast_db-pass.tsv")
dim(receptors)## [1] 3965 57
## [1] "sequence_id" "sequence" "rev_comp"
## [4] "productive" "v_call" "d_call"
## [7] "j_call" "sequence_alignment" "germline_alignment"
## [10] "junction" "junction_aa" "v_cigar"
## [13] "d_cigar" "j_cigar" "stop_codon"
## [16] "vj_in_frame" "locus" "c_call"
## [19] "junction_length" "np1_length" "np2_length"
## [22] "v_sequence_start" "v_sequence_end" "v_germline_start"
## [25] "v_germline_end" "d_sequence_start" "d_sequence_end"
## [28] "d_germline_start" "d_germline_end" "j_sequence_start"
## [31] "j_sequence_end" "j_germline_start" "j_germline_end"
## [34] "v_score" "v_identity" "v_support"
## [37] "d_score" "d_identity" "d_support"
## [40] "j_score" "j_identity" "j_support"
## [43] "fwr1" "fwr2" "fwr3"
## [46] "fwr4" "cdr1" "cdr2"
## [49] "cdr3" "cell_id" "consensus_count"
## [52] "umi_count" "v_call_10x" "d_call_10x"
## [55] "j_call_10x" "junction_10x" "junction_10x_aa"
##
## IGH IGK IGL
## 1913 1154 898
Each B cell contributes up to three sequences: one heavy chain (IGH) and one or two light chains (IGK or IGL). Note that a productive B cell should have exactly one functional heavy chain — cells with two heavy chain sequences are likely doublets or ather artifacts and need to be removed.
The metadata table contains one row per cell barcode. It was derived
from the gene expression analysis (SingleCellExperiment) and includes
cluster annotations computed based on the gene expression data. The
bcell_group column assigns each cell to a broad B cell
population (naive, memory, activated), while bcell_subtype
provides finer-grained annotations based on GEX clustering.
## [1] 1819 17
##
## activated memory naive
## patient_A 3 14 0
## patient_B 22 46 17
## patient_C 337 856 524
The bcell_group annotation assigns each cell to one of
three broad B cell states:
We join on cell_id (BCR data) and Barcode
(metadata). Using all.x = TRUE retains all BCR sequences,
with NA values in metadata columns for cells not found in
the metadata.
db <- merge(receptors, metadata,
by.x = "cell_id",
by.y = "Barcode",
all.x = TRUE)
cat("Sequences before filtering:", nrow(db), "\n")## Sequences before filtering: 3965
## Sequences with metadata annotation: 3627
Note: Not all barcodes in the BCR data will have a match in the metadata — some cells may have been filtered out during the gene expression quality control steps. We will remove unmatched rows in the next section.
Exercise: How many unique cells (cell barcodes) are in the merged dataset? How does this compare to the number of rows?
Repertoire data requires several filtering steps to remove technical artefacts and focus on biologically meaningful sequences.
A rearrangement is productive if it is in-frame and contains no stop codons — only productive rearrangements encode a functional receptor protein.
## Non-productive sequences: 1
## After productive filter: 3626 sequences
Each B cell should have exactly one productive heavy chain. Cells with two or more heavy chain sequences are likely doublets (two cells captured together) or sequencing artefacts, and should be excluded.
# Find cells with multiple heavy chains
heavy_counts <- db %>%
filter(locus == "IGH") %>%
count(cell_id, name = "n_heavy")
multi_heavy_cells <- heavy_counts$cell_id[heavy_counts$n_heavy > 1]
cat("Cells with multiple heavy chains:", length(multi_heavy_cells), "\n")## Cells with multiple heavy chains: 29
db <- db[!db$cell_id %in% multi_heavy_cells, ]
cat("After multi-heavy filter:", nrow(db), "sequences\n")## After multi-heavy filter: 3515 sequences
Exercise: How many unique cells (cell barcodes) remain in the merged dataset? How many sequences for each locus (IGH, IGK, IGL)?
For the downstream analyses focused on heavy chains, we work with the IGH subset:
## Heavy chain sequences: 1691
The isotype (IgM, IgD, IgG, IgA, IgE) of the BCR heavy chain reflects the cell’s history of class switch recombination. IgM and IgD are the default isotypes of naive B cells; IgG, IgA, and IgE require germinal centre reactions.
The c_call column contains specific isotype subclass
calls (e.g., IGHG1, IGHG2, IGHA1). We group these into four main
families.
db_heavy <- db_heavy %>%
mutate(isotype = case_when(
str_starts(c_call, "IGHM") ~ "IgM",
str_starts(c_call, "IGHD") ~ "IgD",
str_starts(c_call, "IGHG") ~ "IgG",
str_starts(c_call, "IGHA") ~ "IgA",
str_starts(c_call, "IGHE") ~ "IgE",
TRUE ~ "Unknown"
))
table(db_heavy$isotype)##
## IgA IgD IgG IgM Unknown
## 168 97 505 916 5
isotype_counts <- db_heavy %>%
filter(isotype != "Unknown")
isotype_cols <- c(IgM = "#4575b4", IgD = "#91bfdb", IgG = "#d73027",
IgA = "#fc8d59", IgE = "#fee090")
ggplot(isotype_counts, aes(x = sample_id, fill = isotype)) +
geom_bar(position = "fill") +
scale_fill_manual(values = isotype_cols) +
scale_y_continuous(labels = scales::percent) +
labs(x = "Patient", y = "Proportion", fill = "Isotype",
title = "Isotype composition per patient") +
theme_bw(base_size = 13)subclass_counts <- db_heavy %>%
filter(!is.na(c_call), c_call != "")
ggplot(subclass_counts, aes(x = sample_id, fill = c_call)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(x = "Patient", y = "Proportion", fill = "Subclass",
title = "Isotype subclass composition per patient") +
theme_bw(base_size = 13) +
theme(axis.text.x = element_text(angle = 30, hjust = 1))One advantage of single-cell data is the ability to link BCR properties to cell state annotations from the gene expression analysis.
iso_group <- db_heavy %>%
filter(isotype != "Unknown")
ggplot(iso_group, aes(x = bcell_group, fill = isotype)) +
geom_bar(position = "fill") +
scale_fill_manual(values = isotype_cols) +
scale_y_continuous(labels = scales::percent) +
labs(x = "B cell group", y = "Proportion", fill = "Isotype",
title = "Isotype distribution per B cell population") +
theme_bw(base_size = 13)Discussion: Based on the isotype distribution, which B cell population has undergone the most class switching? Does this match your biological expectation?
Exercise 2: Plot the isotype subclass composition separately for IgG-positive cells within each B cell group. Are certain IgG subclasses enriched in particular populations?
# Your code here
# Hint: filter to IgG subclasses using str_starts(c_call, "IGHG"), then group by bcell_groupThe V gene determines much of the antigen-binding surface shape. Examining V gene usage frequencies reveals biases in the pre-immune repertoire and potential antigen-driven selection.
# countGenes from alakazam calculates gene segment usage frequencies (independent of alleles)
v_usage <- countGenes(db_heavy, gene = "v_call", groups = "sample_id",
mode = "gene")
# Top 15 most used V genes overall
top_v <- v_usage %>%
group_by(gene) %>%
summarise(total = sum(seq_count), .groups = "drop") %>%
slice_max(total, n = 15) %>%
pull(gene)
v_usage %>%
filter(gene %in% top_v) %>%
group_by(sample_id) %>%
dplyr::mutate(pct = 100 * seq_freq) %>%
ggplot(aes(x = reorder(gene, pct), y = pct, fill = sample_id)) +
geom_col(position = "dodge") +
coord_flip() +
labs(x = "V gene", y = "Usage (%)", fill = "Patient",
title = "Top 15 IGHV gene usage per patient")Two B cells belong to the same clone if they derived from the same ancestor B cell and therefore share: - The same V and J gene assignments - The same CDR3 length - Highly similar CDR3 sequences (allowing for SHM-introduced mutations)
The standard approach (Immcantation) is hierarchical agglomerative clustering on CDR3 Hamming distances, with a threshold determined from the data.
shazam::distToNearest() computes, for each heavy chain
sequence, the nearest-neighbour distance (normalised
Hamming distance) to the most similar sequence within the same V/J
gene and CDR3 length group.
The resulting distance distribution typically shows two populations: - Clonally related sequences (within-clone): distances close to 0 - Unrelated sequences (between clones): distances around 0.2–0.6
The valley between these two populations is the optimal threshold.
# Use one patient for threshold estimation (computationally lighter)
# Use cross = "sample_id" to also estimate between-donor (non-clonal) distances
dist_nn <- distToNearest(
filter(db_heavy, !is.na(junction), nchar(junction) > 0),
cellIdColumn = "cell_id",
nproc = 1
)
ggplot(filter(dist_nn, !is.na(dist_nearest)),
aes(x = dist_nearest)) +
geom_histogram(binwidth = 0.02, fill = "steelblue", color = "white") +
geom_vline(xintercept = 0.2, linetype = "dashed", color = "red") +
scale_x_continuous(breaks = seq(0, 1, 0.1)) +
labs(x = "Nearest-neighbour Hamming distance",
y = "Count",
title = "CDR3 nearest-neighbour distance distribution")shazam::findThreshold() fits a mixture model to identify
the threshold that separates within-clone from between-clone distances.
The Gaussian-Gamma mixture model (gamma-norm) works well
for typical BCR data.
Using cross-subject distances (distances between sequences from different donors, which cannot be clonally related) as a negative control greatly improves threshold accuracy.
dist_cross <- distToNearest(
filter(db_heavy, !is.na(junction), nchar(junction) > 0),
nproc = 1,
cross = "sample_id",
cellIdColumn = "cell_id"
)threshold_result <- shazam::findThreshold(
dist_nn$dist_nearest
)
threshold_val <- threshold_result@threshold
cat("Estimated clonal threshold:", round(threshold_val, 3), "\n")## Estimated clonal threshold: 0.135
plot(threshold_result, binwidth = 0.02,
cross = dist_cross$cross_dist_nearest,
silent = TRUE) +
theme_bw(base_size = 13)The dashed vertical line marks the chosen threshold. Sequences within a clone should have CDR3 distances below this value.
scoper::hierarchicalClones() performs the hierarchical
clustering using the estimated threshold. Setting
fields = "sample_id" ensures that clones are defined within
each donor separately (cross-donor clones are biologically
implausible).
clones <- hierarchicalClones(
filter(db_heavy, !is.na(junction), nchar(junction) > 0),
cell_id = "cell_id",
threshold = threshold_val,
only_heavy = TRUE,
split_light = FALSE,
summarize_clones = FALSE,
fields = "sample_id"
)
cat("Sequences assigned to clones:", nrow(clones), "\n")## Sequences assigned to clones: 1691
## Unique clones: 1610
clone_sizes <- countClones(clones, groups = "sample_id")
ggplot(clone_sizes, aes(x = seq_count)) +
geom_bar(fill = "steelblue") +
facet_wrap(~sample_id, scales = "free_y") +
scale_x_continuous(breaks = 1:15) +
scale_y_continuous(trans = "log1p") +
labs(x = "Number of sequences per clone", y = "Number of clones",
title = "Clone size distribution") +
theme_bw(base_size = 12)Discussion: What fraction of clones are singletons (size = 1)? What does this tell us about the depth of sampling and the extent of clonal expansion in these donors?
Exercise 3: Calculate the fraction of cells that
belong to expanded clones (clone size ≥ 2) in each patient. How does
this relate to the bcell_group composition?
Somatic hypermutation introduces mutations into V gene sequences during the germinal centre reaction. By comparing the observed sequence to the inferred germline (unmutated ancestor), we can estimate the mutation frequency — a proxy for the extent of affinity maturation.
shazam::observedMutations() computes mutation
frequencies by comparing sequence_alignment to
germline_alignment within defined regions of the V
gene.
Note: In this practical we use the germline sequences already present in the data (from IgBLAST). In a production analysis you would typically re-construct germlines using
dowser::createGermlines()with IMGT reference databases, which performs D-region masking to avoid incorrectly counting D-segment nucleotides as V-gene mutations.
data_mut <- shazam::observedMutations(
clones,
sequenceColumn = "sequence_alignment",
germlineColumn = "germline_alignment",
regionDefinition = IMGT_V,
frequency = TRUE,
combine = TRUE,
nproc = 1
)
ggplot(data_mut, aes(x = mu_freq)) +
geom_histogram(binwidth = 0.005, fill = "steelblue", color = "white") +
labs(x = "V-gene mutation frequency", y = "Count",
title = "Distribution of V-gene somatic hypermutation frequency") +
theme_bw(base_size = 13)IgM sequences should show little to no SHM (they have not undergone germinal centre reactions), while IgG and IgA should show higher mutation frequencies.
data_mut %>%
ggplot(aes(x = isotype, y = mu_freq, fill = isotype)) +
geom_violin(alpha = 0.7, trim = TRUE) +
geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white") +
labs(x = "Isotype", y = "V-gene mutation frequency",
title = "SHM frequency by isotype") +
theme_bw(base_size = 13) +
theme(legend.position = "none")ggplot(filter(data_mut, !is.na(bcell_group)),
aes(x = bcell_group, y = mu_freq, fill = bcell_group)) +
geom_violin(alpha = 0.7, trim = TRUE) +
geom_boxplot(width = 0.12, outlier.shape = NA, fill = "white") +
labs(x = "B cell group", y = "V-gene mutation frequency",
title = "SHM frequency by B cell population") +
theme_bw(base_size = 13) +
theme(legend.position = "none")Discussion: Which B cell population shows the highest mutation frequencies? Does this fit the expected biology?
During affinity maturation, selection pressure concentrates mutations in the antigen-contacting CDR loops (CDR1, CDR2, CDR3) relative to the framework regions (FWR1–4). A high CDR:FWR mutation ratio is a signature of antigen-driven selection.
# combine=FALSE keeps per-region columns (CDR1, CDR2, FWR1–3)
# each split into replacement (_r) and silent (_s) mutation frequencies
data_mut_reg <- shazam::observedMutations(
clones,
sequenceColumn = "sequence_alignment",
germlineColumn = "germline_alignment",
regionDefinition = IMGT_V_BY_REGIONS,
frequency = TRUE,
combine = FALSE,
nproc = 1
)
# Sum replacement mutation frequencies across all CDR / FWR columns
cdr_cols <- grep("^mu_freq_cdr", names(data_mut_reg), value = TRUE)
fwr_cols <- grep("^mu_freq_fwr", names(data_mut_reg), value = TRUE)
data_mut_reg <- data_mut_reg %>%
mutate(
cdr_freq = rowSums(across(all_of(cdr_cols)), na.rm = TRUE),
fwr_freq = rowSums(across(all_of(fwr_cols)), na.rm = TRUE)
)
data_mut_long <- data_mut_reg %>%
select(sequence_id, isotype, bcell_group, cdr_freq, fwr_freq) %>%
tidyr::pivot_longer(c(cdr_freq, fwr_freq),
names_to = "region", values_to = "mu_freq") %>%
mutate(region = recode(region, cdr_freq = "CDR", fwr_freq = "FWR"))
ggplot(data_mut_long, aes(x = isotype, y = mu_freq, fill = region)) +
geom_boxplot(outlier.shape = NA, alpha = 0.8) +
scale_fill_manual(values = c(CDR = "#e41a1c", FWR = "#377eb8")) +
labs(x = "Isotype", y = "Mutation frequency", fill = "Region",
title = "CDR vs. framework region mutation frequencies") +
theme_bw(base_size = 13)Exercise 4: Calculate the CDR:FWR mutation frequency ratio for each sequence and compare it between IgG and IgM B cells. Which isotype shows stronger evidence of antigen selection?
# Your code here
# Hint: use data_mut_reg which already has cdr_freq and fwr_freq columns
# Calculate ratio: cdr_freq / (fwr_freq + 1e-9) to avoid division by zeroA key advantage of single-cell data is the ability to overlay BCR properties onto the GEX-based UMAP embedding. Here we demonstrate this conceptually — in a full analysis you would load the Seurat or SingleCellExperiment object.
data_mut %>%
group_by(bcell_subtype, isotype) %>%
ggplot(aes(x = bcell_subtype, y = mu_freq)) +
geom_violin(position = "dodge") +
coord_flip() +
labs(x = "B cell subtype", y = "V-gene mutation frequency",
title = "SHM by B cell subtype and isotype") +
theme_bw(base_size = 12)Are clones shared between B cell groups (e.g., does the same clone have naive and memory members)?
clonal_groups <- clones %>%
filter(!is.na(clone_id), !is.na(bcell_group)) %>%
group_by(clone_id) %>%
summarise(
n_cells = n(),
groups_present = paste(sort(unique(bcell_group)), collapse = "/"),
.groups = "drop"
) %>%
count(groups_present, name = "n_clones") %>%
arrange(desc(n_clones))
print(clonal_groups)## # A tibble: 6 × 2
## groups_present n_clones
## <chr> <int>
## 1 memory 773
## 2 naive 520
## 3 activated 306
## 4 activated/memory 7
## 5 memory/naive 3
## 6 activated/memory/naive 1
Phylogenetic trees of BCR sequences within a clone reveal the evolutionary history of affinity maturation — from the unmutated germinal centre founder to the most mutated descendants.
Within a clone, sequences are related by shared mutations. By building a maximum parsimony or maximum likelihood tree rooted at the inferred germline sequence, we can reconstruct the mutational trajectory of the clone.
The dowser package provides: -
formatClones(): prepare clone data for tree building -
getTrees(): build phylogenetic trees (using phangorn/ML
methods) - plotTrees(): visualise trees with trait
annotations
For robust tree building, the germline sequence need to be identical for all members of a clone to avoid alignment artefacts. This requires IMGT germline databases, which must be downloaded separately (not part of this practical):
# Run only if IMGT references are available
# references <- readIMGT(dir = "/path/to/imgt/human/vdj/")
# clones_with_gl <- createGermlines(clones, references, fields = "sample_id")For this practical we illustrate the tree-building workflow using the existing germline sequences. One important preparation step is described below.
formatClones() requires all sequences within a clone to
carry the same germline sequence. IgBLAST assigns
germlines independently to each sequence and can produce slightly
different assignments within a clone. The proper solution is
createGermlines(), which reconstructs a single consensus
germline per clone using IMGT references. As a practical workaround when
IMGT references are unavailable, we standardise by taking the most
frequently assigned germline within each clone.
# Select well-represented clones (≥ 5 sequences) for tree building
large_clones <- clones %>%
filter(!is.na(clone_id), !is.na(germline_alignment)) %>%
count(clone_id) %>%
filter(n >= 4) %>%
pull(clone_id)
cat("Clones with ≥ 4 sequences:", length(large_clones), "\n")
clones_tree_input <- clones %>%
filter(clone_id %in% large_clones,
!is.na(isotype),
!is.na(germline_alignment))
# Standardise germlines within each clone (workaround needed because of missing germline reconstruction step with IMGT reference)
# Better approach (not feasible in this tutorial: createGermlines(clones, references, fields = "sample_id")
clones_tree_input <- clones_tree_input %>%
group_by(clone_id) %>%
mutate(germline_alignment = {
tbl <- table(germline_alignment)
# use most common germline sequence
names(tbl)[which.max(tbl)]
}) %>%
ungroup()
clone_objs <- formatClones(
clones_tree_input,
traits = c("isotype", "bcell_group", "sample_id"),
minseq = 4,
nproc = 1,
germ = "germline_alignment"
)
trees <- getTrees(clone_objs, build = "pml", nproc = 1)## R version 4.6.0 (2026-04-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.4 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0 LAPACK version 3.12.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=de_DE.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=de_DE.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=de_DE.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=de_DE.UTF-8 LC_IDENTIFICATION=C
##
## time zone: Europe/Berlin
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] stringr_1.6.0 patchwork_1.3.2 tidyr_1.3.2 dplyr_1.2.1
## [5] dowser_2.4.1 scoper_1.5.0 shazam_1.3.2 alakazam_1.4.3
## [9] ggplot2_4.0.3 airr_1.6.1
##
## loaded via a namespace (and not attached):
## [1] bitops_1.0-9 gridExtra_2.3
## [3] phangorn_2.12.1 rlang_1.2.0
## [5] magrittr_2.0.5 ade4_1.7-24
## [7] otel_0.2.0 matrixStats_1.5.0
## [9] compiler_4.6.0 systemfonts_1.3.2
## [11] vctrs_0.7.3 pwalign_1.8.0
## [13] quadprog_1.5-8 pkgconfig_2.0.3
## [15] crayon_1.5.3 fastmap_1.2.0
## [17] XVector_0.52.0 labeling_0.4.3
## [19] utf8_1.2.6 Rsamtools_2.28.0
## [21] rmarkdown_2.31 phylotate_1.3
## [23] markdown_2.0 tzdb_0.5.0
## [25] bit_4.6.0 purrr_1.2.2
## [27] xfun_0.57 cachem_1.1.0
## [29] cigarillo_1.2.0 seqinr_4.2-44
## [31] aplot_0.2.9 jsonlite_2.0.0
## [33] progress_1.2.3 DelayedArray_0.38.1
## [35] BiocParallel_1.46.0 parallel_4.6.0
## [37] prettyunits_1.2.0 R6_2.6.1
## [39] bslib_0.10.0 stringi_1.8.7
## [41] RColorBrewer_1.1-3 GenomicRanges_1.64.0
## [43] jquerylib_0.1.4 diptest_0.77-2
## [45] Rcpp_1.1.1-1.1 Seqinfo_1.2.0
## [47] SummarizedExperiment_1.42.0 iterators_1.0.14
## [49] knitr_1.51 readr_2.2.0
## [51] IRanges_2.46.0 Matrix_1.7-5
## [53] igraph_2.3.1 tidyselect_1.2.1
## [55] rstudioapi_0.18.0 abind_1.4-8
## [57] yaml_2.3.12 doParallel_1.0.17
## [59] codetools_0.2-20 lattice_0.22-9
## [61] tibble_3.3.1 treeio_1.36.1
## [63] Biobase_2.72.0 withr_3.0.2
## [65] S7_0.2.2 evaluate_1.0.5
## [67] gridGraphics_0.5-1 Biostrings_2.80.0
## [69] pillar_1.11.1 ggtree_4.2.0
## [71] MatrixGenerics_1.24.0 KernSmooth_2.23-26
## [73] foreach_1.5.2 stats4_4.6.0
## [75] ggfun_0.2.0 generics_0.1.4
## [77] vroom_1.7.1 S4Vectors_0.50.0
## [79] hms_1.1.4 scales_1.4.0
## [81] tidytree_0.4.7 glue_1.8.1
## [83] gdtools_0.5.0 lazyeval_0.2.3
## [85] tools_4.6.0 data.table_1.18.4
## [87] GenomicAlignments_1.48.0 ggiraph_0.9.6
## [89] fs_2.1.0 fastcluster_1.3.0
## [91] fastmatch_1.1-8 grid_4.6.0
## [93] ape_5.8-1 nlme_3.1-169
## [95] cli_3.6.6 rappdirs_0.3.4
## [97] fontBitstreamVera_0.1.1 S4Arrays_1.12.0
## [99] gtable_0.3.6 yulab.utils_0.2.4
## [101] sass_0.4.10 digest_0.6.39
## [103] fontquiver_0.2.1 BiocGenerics_0.58.0
## [105] SparseArray_1.12.2 ggplotify_0.1.3
## [107] htmlwidgets_1.6.4 farver_2.1.2
## [109] htmltools_0.5.9 lifecycle_1.0.5
## [111] bit64_4.8.0 fontLiberation_0.1.0
## [113] MASS_7.3-65