| Title: | A Unified Data Layer for Large-Scale Single-Cell, Spatial and Bulk Immunomics |
|---|---|
| Description: | Provides a unified data layer for single-cell, spatial and bulk T-cell and B-cell immune receptor repertoire data. Think AnnData or SeuratObject, but for AIRR data, a.k.a. Adaptive Immune Receptor Repertoire, VDJ-seq, RepSeq, or VDJ sequencing data. |
| Authors: | Vadim I. Nazarov [aut, cre] (ORCID: <https://orcid.org/0000-0003-3659-2709>) |
| Maintainer: | Vadim I. Nazarov <[email protected]> |
| License: | Apache License (>= 2) |
| Version: | 0.1.0 |
| Built: | 2026-09-25 11:55:08 UTC |
| Source: | https://github.com/immunomind/immundata |
agg_receptors() is a low-level function used during AIRR data ingestion. It
decides which sequence rows represent the same biological receptor and adds
package-standard identifiers and counts to the input table.
A receptor can be one chain or a pair of chains from the same cell. The
schema argument defines which sequence features and loci make two receptors
identical.
This function works with a prepared duckplyr table and returns a duckplyr
table. It does not accept or return an ImmunData object. Most analysis
workflows should provide the same arguments to read_repertoires(), which
calls agg_receptors() during import.
agg_receptors( dataset, schema, barcode_col = NULL, count_col = NULL, locus_col = NULL, umi_col = NULL, verbose = getOption("immundata.verbose", TRUE) )agg_receptors( dataset, schema, barcode_col = NULL, count_col = NULL, locus_col = NULL, umi_col = NULL, verbose = getOption("immundata.verbose", TRUE) )
dataset |
A duckplyr table containing AIRR sequence data, with one row
per chain or bulk clonotype. It must contain the columns named in |
schema |
Definition of receptor identity. Supply either:
A schema can contain at most two chain entries. Use syntax such as
|
barcode_col |
Name of the column containing cell barcodes. Supply this
for single-cell data. |
count_col |
Name of the column containing non-negative abundance values
in bulk repertoire data. These values are copied to |
locus_col |
Name of the column containing loci such as |
umi_col |
Name of the column containing per-chain UMI or read counts.
It is required when |
verbose |
Whether to print information about the selected processing
mode and loci. Defaults to |
The receptor features are the columns that define the identity of one chain. Two chains with the same values in all feature columns receive the same receptor identity in a single-chain analysis. Common features include V gene, J gene, and CDR3 amino acid sequence.
The function supports three input modes:
Uncounted sequence table: If neither barcode_col nor count_col is
supplied, every input row is treated as one observed chain. A synthetic
barcode is created for each row, and imd_n_chains is set to 1.
Bulk repertoire: If count_col is supplied, every input row receives a
synthetic barcode and its abundance is copied to imd_n_chains.
Single-cell repertoire: If barcode_col is supplied, rows are grouped
by cell. umi_col is required and imd_n_chains is set to 1 for every
retained cell-chain observation.
When one chain is specified in schema, only that locus is retained. If a
cell contains several chains from that locus, the row with the highest value
in umi_col is retained. If the highest values are tied, the first row is
retained.
When two chains are specified, only cells containing both requested loci are
retained. The selected chains are paired by barcode, and both rows receive
the same imd_receptor_id. Cells with incomplete pairs are excluded.
A relaxed pair such as c("IGH", "IGL|IGK") requires IGH and exactly one of
the two alternative light-chain loci. Cells containing both IGL and IGK are
excluded.
Numeric imd_receptor_id values identify receptors within the returned
table. The particular number assigned to a receptor is not a biological
identifier and may change when the data are aggregated again.
A duckplyr table containing the retained input rows and these package-standard columns:
imd_receptor_id: links rows that belong to the same receptor.
imd_barcode: contains the input cell barcode, or a synthetic row-level
barcode for uncounted and bulk data.
imd_chain_id: identifies an individual retained chain row.
imd_n_chains: contains 1 for uncounted and single-cell data, or the
value from count_col for bulk data.
imd_count: initialized to 0; receptor counts are calculated later by
agg_repertoires().
read_repertoires(), make_receptor_schema(), agg_repertoires(),
ImmunData
Use agg_repertoires() to define which receptor observations belong to the
same biological repertoire and calculate receptor abundance within each
repertoire.
Use this function after importing data without repertoire definitions, or
when you want to redefine repertoires using sample information. One
repertoire usually represents one biological sample. It can also represent
one sample and time-point combination. The columns in schema define these
groups.
The unit being defined is the repertoire. The function does not remove chain rows or redefine cells or receptors. It returns a new ImmunData object. The original object is not changed.
agg_repertoires( idata, schema = "repertoire_id", verbose = getOption("immundata.verbose", TRUE) )agg_repertoires( idata, schema = "repertoire_id", verbose = getOption("immundata.verbose", TRUE) )
idata |
An ImmunData object containing receptor observations and the
columns named in |
schema |
A non-empty character vector. One or more column names that
together define a repertoire. For example, |
verbose |
A logical value. Accepted for consistency with other
aggregation functions. It currently does not change the output. Defaults to
|
The function calculates summaries at repertoire and receptor levels while keeping the original chain rows.
A new ImmunData object with repertoire definitions and abundance
statistics. Its repertoire summary contains the schema columns,
imd_repertoire_id, n_barcodes, and n_receptors. Its chain rows also
contain imd_repertoire_id, imd_count, imd_proportion, and
n_repertoires.
The returned repertoire summary contains one row for each repertoire:
imd_repertoire_id: a new integer identifier for the repertoire.
n_barcodes: the number of observed cells for single-cell data, or the
total abundance for bulk data.
n_receptors: the number of distinct receptors in the repertoire.
The function also adds these values to each chain row:
imd_repertoire_id: the repertoire containing the row.
imd_count: the number of cells carrying that receptor in single-cell
data, or its summed abundance in bulk data, within the repertoire.
imd_proportion: the receptor's fraction of the repertoire, calculated as
imd_count / n_barcodes.
n_repertoires: the number of repertoires in which the receptor occurs.
Values calculated for a receptor are repeated on all chain rows belonging to that receptor in the same repertoire.
Calling agg_repertoires() again replaces previous repertoire definitions,
receptor counts, proportions, and related strata summaries.
Large-table calculations run on the duckplyr annotation table. The annotation data remain lazy when the input is lazy. The small repertoire summary is collected and stored in the returned object.
Aggregation can be expensive for a large dataset. After checking the result,
consider saving it so later analyses do not repeat the calculation. Use
write_immundata(idata, tag = "by-sample") to create a managed snapshot in
the object's project home. Managed snapshots are versioned, so another write
with the same tag creates a new version and keeps the earlier version.
Use write_immundata(idata, output_folder = "path/to/result") when you need a
standalone saved state in a specific folder, for example to share it or to
choose a new storage location. Unlike a managed snapshot, writing to an
existing explicit folder replaces the ImmunData files in that folder. Both
forms materialize pending duckplyr calculations and return a disk-backed
object that can be reopened with read_immundata().
read_repertoires(), agg_strata(), write_immundata(), ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Create a small bulk T-cell receptor dataset from two biological samples bulk_data <- tibble( Sample = c("Tumor", "Tumor", "Blood", "Blood"), cdr3_aa = c("CASSA", "CASSB", "CASSA", "CASSC"), v_call = c("TRBV1", "TRBV2", "TRBV1", "TRBV3"), abundance = c(20L, 5L, 4L, 6L) ) bulk_file <- tempfile(fileext = ".tsv") readr::write_tsv(bulk_data, bulk_file) # Import receptors without defining repertoires idata <- read_repertoires( path = bulk_file, schema = c("cdr3_aa", "v_call"), count_col = "abundance", repertoire_schema = NULL, output_folder = tempfile("immundata-example-") ) # Define one repertoire for each biological sample sample_repertoires <- idata |> agg_repertoires(schema = "Sample") sample_repertoires$repertoires |> select(Sample, n_barcodes, n_receptors) |> arrange(Sample) # Expected result: # Sample n_barcodes n_receptors # Blood 10 2 # Tumor 25 2 # For example, CASSA forms 80% of the Tumor repertoire and 40% of the # Blood repertoire. It occurs in two repertoires. # For a large dataset, save the result as a managed snapshot so this # aggregation does not need to run again. saved_repertoires <- write_immundata( sample_repertoires, tag = "by-sample" )library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Create a small bulk T-cell receptor dataset from two biological samples bulk_data <- tibble( Sample = c("Tumor", "Tumor", "Blood", "Blood"), cdr3_aa = c("CASSA", "CASSB", "CASSA", "CASSC"), v_call = c("TRBV1", "TRBV2", "TRBV1", "TRBV3"), abundance = c(20L, 5L, 4L, 6L) ) bulk_file <- tempfile(fileext = ".tsv") readr::write_tsv(bulk_data, bulk_file) # Import receptors without defining repertoires idata <- read_repertoires( path = bulk_file, schema = c("cdr3_aa", "v_call"), count_col = "abundance", repertoire_schema = NULL, output_folder = tempfile("immundata-example-") ) # Define one repertoire for each biological sample sample_repertoires <- idata |> agg_repertoires(schema = "Sample") sample_repertoires$repertoires |> select(Sample, n_barcodes, n_receptors) |> arrange(Sample) # Expected result: # Sample n_barcodes n_receptors # Blood 10 2 # Tumor 25 2 # For example, CASSA forms 80% of the Tumor repertoire and 40% of the # Blood repertoire. It occurs in two repertoires. # For a large dataset, save the result as a managed snapshot so this # aggregation does not need to run again. saved_repertoires <- write_immundata( sample_repertoires, tag = "by-sample" )
Use agg_strata() to place sample repertoires into biological comparison
groups, such as treatment arms, tissues, or disease groups.
Use this function after agg_repertoires() when several repertoires should be
analysed as one group. A stratum contains every repertoire with the same
value, or the same combination of values, in schema.
The unit being grouped is a whole repertoire. The function returns a new ImmunData object. The original object is not changed.
agg_strata(idata, schema, prefix = "Strata")agg_strata(idata, schema, prefix = "Strata")
idata |
An ImmunData object with repertoires already defined. Use
|
schema |
A non-empty character vector. One or more repertoire-level
columns that define a stratum. For example, use |
prefix |
A non-empty character string. Prefix for the
automatic stratum labels. The default, |
If schema contains several columns, a separate stratum is created for each
observed combination. For example, c("Tissue", "Therapy") can define
separate blood and tumour strata within each treatment arm.
Calling agg_strata() again replaces the existing strata with groups defined
by the new schema.
A new ImmunData object in which every repertoire belongs to one
stratum. The $strata table lists the strata, their defining biological
values, and their automatic labels. Repertoire definitions and summary
statistics are preserved.
imd_strata_id is an internal identifier and can change when strata are
rebuilt. It is added to the repertoire table and to the underlying chain
annotations. strata_name is stored only in the smaller repertoire and
strata tables.
Calling agg_repertoires() again rebuilds the repertoires, so it removes the
existing strata. Call agg_strata() again after redefining repertoires.
agg_repertoires(), rename_strata(), ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Define sample repertoires using the biological metadata in the test data idata <- get_test_idata() |> agg_repertoires(c("Response", "Therapy")) # Group the sample repertoires into treatment arms treatment_groups <- idata |> agg_strata(schema = "Therapy") treatment_groups$repertoires |> select(Therapy, Response, imd_strata_id, strata_name) |> arrange(imd_strata_id) # Expected result: # Therapy Response imd_strata_id strata_name # CAR-T PR 1 Strata1 # ICI FR 2 Strata2 # Each repertoire is now assigned to its treatment stratum. Any additional # repertoire with the same Therapy value would receive the same stratum ID.library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Define sample repertoires using the biological metadata in the test data idata <- get_test_idata() |> agg_repertoires(c("Response", "Therapy")) # Group the sample repertoires into treatment arms treatment_groups <- idata |> agg_strata(schema = "Therapy") treatment_groups$repertoires |> select(Therapy, Response, imd_strata_id, strata_name) |> arrange(imd_strata_id) # Expected result: # Therapy Response imd_strata_id strata_name # CAR-T PR 1 Strata1 # ICI FR 2 Strata2 # Each repertoire is now assigned to its treatment stratum. Any additional # repertoire with the same Therapy value would receive the same stratum ID.
Copy selected columns from idata$annotations to adata$obs, matching by
cell barcode (adata$obs_names).
annotate_anndata(idata, adata, cols)annotate_anndata(idata, adata, cols)
idata |
An ImmunData object. |
adata |
An anndataR::AbstractAnnData object. |
cols |
Character vector with column names to transfer from
|
The updated AnnData object.
Use the annotate_*() functions to add information stored in another data
frame to an ImmunData object. For example, you can add cell types from a
single-cell analysis, antigen labels for receptors, or clinical information
for samples.
When matching identifiers are unique, the functions keep every row in
idata. When a row has no match in annotations, the new columns contain
NA. Each function returns a new ImmunData object. The original object is
not changed.
annotate_immundata( idata, annotations, by, keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") ) annotate( idata, annotations, by, keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") ) annotate_receptors( idata, annotations, annot_col = imd_schema("receptor"), keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") ) annotate_barcodes( idata, annotations, annot_col = "<rownames>", keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") ) annotate_chains( idata, annotations, annot_col = imd_schema("chain"), keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") )annotate_immundata( idata, annotations, by, keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") ) annotate( idata, annotations, by, keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") ) annotate_receptors( idata, annotations, annot_col = imd_schema("receptor"), keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") ) annotate_barcodes( idata, annotations, annot_col = "<rownames>", keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") ) annotate_chains( idata, annotations, annot_col = imd_schema("chain"), keep_repertoires = TRUE, remove_limit = FALSE, conflicts = c("error", "replace") )
idata |
An ImmunData object. |
annotations |
A data frame containing the information to add. It must contain the columns used for matching and at most one row for each matching identifier or combination of identifiers. |
by |
A named character vector describing how columns are matched. Names
are columns in |
keep_repertoires |
Whether to preserve existing repertoire and strata
summaries without recalculation. The default is |
remove_limit |
Whether to allow an annotation table with 100 or more
columns. The default is |
conflicts |
How to handle new annotation columns whose names already
exist in |
annot_col |
Name of the identifier column in |
The functions differ in how they select the columns used for matching. The rules for duplicate identifiers, column conflicts, and preserved summaries are the same for all functions.
A new ImmunData object containing the added annotation columns.
Existing repertoire and strata summaries are preserved when
keep_repertoires = TRUE.
Use the function that matches the type of information you want to add:
annotate_barcodes() matches cell or barcode identifiers.
annotate_receptors() matches receptor identifiers. All rows belonging to
a matched receptor receive the new information.
annotate_chains() matches chain identifiers.
annotate() matches any one or more columns that you specify in by.
The first three functions select the correct ImmunData identifier for you.
annotate_immundata() is an alternative name for annotate().
For annotate_barcodes(), annotate_receptors(), and annotate_chains(),
annot_col names the identifier column in annotations. For example,
annot_col = "barcode" matches the barcode column in annotations with
the barcode identifier in idata.
For general matching, supply by in the form
c(immundata_column = "annotation_column"). For example,
by = c(Response = "response_code") matches the Response column in
idata with the response_code column in annotations. To match columns
with the same name, use a value such as by = c(Response = "Response").
You can include more than one pair of columns in by.
Columns from annotations that are not used for matching are added to the
result. Rows in idata without a match receive NA. Rows in annotations
without a match are ignored.
annotations must contain at most one row for each identifier, or each
combination of identifiers when matching several columns. For example, a
barcode annotation table must contain at most one row per barcode.
The function does not check this rule because the annotation table may be
very large. If an identifier occurs several times, the corresponding rows in
idata are repeated. This can make receptor counts, proportions, and other
summaries incorrect.
By default, the function stops if a new annotation column has the same name
as a column already present in idata. This prevents accidental replacement.
Use conflicts = "replace" to replace existing annotation columns. Columns
that define receptors, repertoires, strata, or other ImmunData state are
protected and cannot be replaced. The old column is removed before matching,
so rows without a new match receive NA.
With the default keep_repertoires = TRUE, existing repertoire and strata
summaries are copied to the new object without recalculation. Use this option
when you are only adding information and the matching identifiers in
annotations are unique.
Set keep_repertoires = FALSE when you plan to filter rows or define new
repertoires using the added information. This removes existing repertoire and
strata summaries and their derived columns. After annotation and filtering,
use agg_repertoires() to define the new repertoires.
By default, the function stops when annotations contains 100 or more
columns. Adding a very wide table, such as a complete gene-expression matrix,
can be slow and require a large amount of memory. If you understand this cost,
set remove_limit = TRUE to allow the operation.
dplyr::left_join(), agg_repertoires(), filter_immundata(),
mutate_immundata(), ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Load data included with immundata idata <- get_test_idata() # Add cell types by matching barcode identifiers cell_labels <- tibble( barcode = c("S1_1", "S1_2"), cell_type = c("CD8 T cell", "CD4 T cell") ) idata_with_cells <- idata |> annotate_barcodes( annotations = cell_labels, annot_col = "barcode" ) idata_with_cells |> collect() |> filter(imd_barcode %in% c("S1_1", "S1_2", "S1_3")) |> select(imd_barcode, cell_type) |> arrange(imd_barcode) # Expected result: # imd_barcode cell_type # S1_1 CD8 T cell # S1_2 CD4 T cell # S1_3 NA # Add antigen labels to selected receptors receptor_labels <- tibble( receptor_id = c(738L, 1567L), antigen = c("CMV", "CMV") ) idata_with_antigens <- idata |> annotate_receptors( annotations = receptor_labels, annot_col = "receptor_id" ) idata_with_antigens |> collect() |> filter(!is.na(antigen)) |> distinct(imd_receptor_id, cdr3_aa, antigen) |> arrange(imd_receptor_id) # Expected result: # imd_receptor_id cdr3_aa antigen # 738 ASRAGAGTGELF CMV # 1567 ASFPVLSPYNEQF CMV # Match columns with different names response_info <- tibble( response_code = c("FR", "PR"), response_label = c("Full response", "Partial response") ) idata_with_response <- idata |> annotate( annotations = response_info, by = c(Response = "response_code") ) idata_with_response |> collect() |> distinct(Response, response_label) |> arrange(Response) # Expected result: # Response response_label # FR Full response # PR Partial response # Replace an annotation column intentionally revised_cell_labels <- tibble( barcode = c("S1_1", "S1_2"), cell_type = c("Cytotoxic T cell", "Helper T cell") ) idata_with_revised_cells <- idata_with_cells |> annotate_barcodes( annotations = revised_cell_labels, annot_col = "barcode", conflicts = "replace" ) # Remove old repertoire summaries before defining repertoires by cell type cell_repertoires <- idata |> annotate_barcodes( annotations = cell_labels, annot_col = "barcode", keep_repertoires = FALSE ) |> filter(!is.na(cell_type)) |> agg_repertoires(schema = "cell_type") cell_repertoires$repertoires |> arrange(cell_type) # Expected result: # imd_repertoire_id cell_type n_barcodes n_receptors # 1 CD4 T cell 1 1 # 2 CD8 T cell 1 1library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Load data included with immundata idata <- get_test_idata() # Add cell types by matching barcode identifiers cell_labels <- tibble( barcode = c("S1_1", "S1_2"), cell_type = c("CD8 T cell", "CD4 T cell") ) idata_with_cells <- idata |> annotate_barcodes( annotations = cell_labels, annot_col = "barcode" ) idata_with_cells |> collect() |> filter(imd_barcode %in% c("S1_1", "S1_2", "S1_3")) |> select(imd_barcode, cell_type) |> arrange(imd_barcode) # Expected result: # imd_barcode cell_type # S1_1 CD8 T cell # S1_2 CD4 T cell # S1_3 NA # Add antigen labels to selected receptors receptor_labels <- tibble( receptor_id = c(738L, 1567L), antigen = c("CMV", "CMV") ) idata_with_antigens <- idata |> annotate_receptors( annotations = receptor_labels, annot_col = "receptor_id" ) idata_with_antigens |> collect() |> filter(!is.na(antigen)) |> distinct(imd_receptor_id, cdr3_aa, antigen) |> arrange(imd_receptor_id) # Expected result: # imd_receptor_id cdr3_aa antigen # 738 ASRAGAGTGELF CMV # 1567 ASFPVLSPYNEQF CMV # Match columns with different names response_info <- tibble( response_code = c("FR", "PR"), response_label = c("Full response", "Partial response") ) idata_with_response <- idata |> annotate( annotations = response_info, by = c(Response = "response_code") ) idata_with_response |> collect() |> distinct(Response, response_label) |> arrange(Response) # Expected result: # Response response_label # FR Full response # PR Partial response # Replace an annotation column intentionally revised_cell_labels <- tibble( barcode = c("S1_1", "S1_2"), cell_type = c("Cytotoxic T cell", "Helper T cell") ) idata_with_revised_cells <- idata_with_cells |> annotate_barcodes( annotations = revised_cell_labels, annot_col = "barcode", conflicts = "replace" ) # Remove old repertoire summaries before defining repertoires by cell type cell_repertoires <- idata |> annotate_barcodes( annotations = cell_labels, annot_col = "barcode", keep_repertoires = FALSE ) |> filter(!is.na(cell_type)) |> agg_repertoires(schema = "cell_type") cell_repertoires$repertoires |> arrange(cell_type) # Expected result: # imd_repertoire_id cell_type n_barcodes n_receptors # 1 CD4 T cell 1 1 # 2 CD8 T cell 1 1
Copy selected columns from idata$annotations to Seurat metadata using
the cell barcode. This is the simplest way to transfer data from immundata
to Seurat object, e.g., for plotting data on UMAP.
annotate_seurat(idata, sdata, cols)annotate_seurat(idata, sdata, cols)
idata |
An ImmunData object. |
sdata |
A Seurat object (cells are columns; barcodes are |
cols |
Character vector with column names to transfer from |
See functions annotate_clonality_rank and annotate_clonality_prop in immunarch package.
The updated Seurat object with new metadata columns.
ImmunData, SeuratObject::AddMetaData
## Not run: # After annotating receptors: idata <- annotate_clonality_prop(idata) # Transfer the clonality bin to Seurat and plot: sdata <- annotate_seurat(idata, sdata, cols = "clonal_prop_bin") Seurat::DimPlot(sdata, reduction = "umap", group.by = "clonal_prop_bin", shuffle = TRUE) # Alternative: rank bins idata <- annotate_clonality_rank(idata, bins = c(10, 100)) sdata <- annotate_seurat(idata, sdata, cols = "clonal_rank_bin") ## End(Not run)## Not run: # After annotating receptors: idata <- annotate_clonality_prop(idata) # Transfer the clonality bin to Seurat and plot: sdata <- annotate_seurat(idata, sdata, cols = "clonal_prop_bin") Seurat::DimPlot(sdata, reduction = "umap", group.by = "clonal_prop_bin", shuffle = TRUE) # Alternative: rank bins idata <- annotate_clonality_rank(idata, bins = c(10, 100)) sdata <- annotate_seurat(idata, sdata, cols = "clonal_rank_bin") ## End(Not run)
Collects annotations from an ImmunData object and returns them as a tibble.
Factor columns are converted to character.
## S3 method for class 'ImmunData' collect(x, ...)## S3 method for class 'ImmunData' collect(x, ...)
x |
ImmunData object. |
... |
Additional arguments passed to |
A tibble with collected annotations.
Materializes the annotation table of an ImmunData object via
dplyr::compute() and returns a new ImmunData.
## S3 method for class 'ImmunData' compute(x, ...)## S3 method for class 'ImmunData' compute(x, ...)
x |
ImmunData object. |
... |
Additional arguments passed to |
A new ImmunData object with computed annotations and the input
repertoire, strata, schema, and provenance state preserved.
Use count() to find how many chain rows are stored in an ImmunData
object.
Use this method for a quick check of dataset size. The unit counted is one retained chain row. Each retained cell with a paired receptor usually contributes two rows, one for each chain. The same receptor can therefore contribute two rows for every cell carrying it. For bulk data with an abundance column, this method counts table rows rather than the summed sequence abundance.
The function returns a one-row duckplyr table. The original object is not changed.
## S3 method for class 'ImmunData' count(x, ..., wt = NULL, sort = FALSE, name = NULL)## S3 method for class 'ImmunData' count(x, ..., wt = NULL, sort = FALSE, name = NULL)
x |
An ImmunData object. |
... |
Additional arguments. Accepted for compatibility with
|
wt |
Any value or |
sort |
A logical value. Accepted for compatibility with
|
name |
A character string or |
This method currently provides only the total row count. The grouping,
weighting, sorting, and result-name arguments of dplyr::count() are
accepted for method compatibility but are not applied.
The calculation runs on the duckplyr annotation table and can remain in
DuckDB. Use dplyr::pull() or dplyr::collect() to bring the small result
into R.
A one-row duckplyr table with an integer column named n. This value
is the number of rows in the chain-level annotation table.
dplyr::count(), dplyr::collect(), ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) idata <- get_test_idata() idata |> count() # Expected result: # n # 1902 # The result means that the object contains 1,902 retained chain rows. # It does not mean that it contains 1,902 unique receptors.library(immundata) library(dplyr) options(immundata.verbose = FALSE) idata <- get_test_idata() idata |> count() # Expected result: # n # 1902 # The result means that the object contains 1,902 retained chain rows. # It does not mean that it contains 1,902 unique receptors.
Returns dimension names for an ImmunData object so that
colnames(idata) maps to annotation column names.
## S3 method for class 'ImmunData' dimnames(x)## S3 method for class 'ImmunData' dimnames(x)
x |
ImmunData object. |
A list with NULL row names and annotation column names.
Disallows replacing dimension names on ImmunData.
## S3 replacement method for class 'ImmunData' dimnames(x) <- value## S3 replacement method for class 'ImmunData' dimnames(x) <- value
x |
ImmunData object. |
value |
Not used. |
Use downsample_immundata() to reduce every repertoire to the same number or
fraction of observed cells or bulk sequence counts before comparing
repertoires. So, it is just a downsampling.
Use this function when different sequencing depths could affect a comparison of repertoire diversity or composition. In single-cell data, the sampling unit is a cell barcode and all selected chains from that cell stay together. In bulk data with abundance values, the sampling unit is one sequence count.
The function returns a new ImmunData object. The original object is not changed.
downsample_immundata(idata, n, seed = NULL)downsample_immundata(idata, n, seed = NULL)
idata |
An ImmunData object. For comparisons between repertoires, its
repertoires should already be defined with |
n |
A number. Sampling depth. Use a value strictly between 0 and 1 for a fraction, or a whole number greater than or equal to 1 for an absolute number of cells or bulk sequence counts. |
seed |
A non-negative integer or |
A new ImmunData object containing the sampled chain observations. If the input has repertoires or strata, their summaries are recalculated for the sampled data.
n for single-cell data0 < n < 1 keeps floor(n * number of cells) cells from each repertoire.
n >= 1 keeps n cells from each repertoire.
Cell barcodes are sampled without replacement. For paired receptors, all retained chains belonging to a selected cell stay together.
n for bulk data0 < n < 1 keeps floor(n * total abundance) sequence counts from each
repertoire.
n >= 1 keeps a total abundance of n from each repertoire.
Counts are sampled without replacement according to their observed
abundance. A retained receptor can therefore have a smaller abundance than
it had before downsampling. For example, n = 1000 makes the total retained
abundance equal to 1000 in every repertoire that originally contained at
least 1000 counts.
If a requested whole-number n is larger than a repertoire, that repertoire
is returned unchanged and the function gives a warning. If a fraction is so
small that it selects zero units in any repertoire, the function stops and
asks for a larger value.
When repertoires are defined, the function recalculates receptor counts, proportions, repertoire sizes, and the number of repertoires containing each receptor. Existing strata are also rebuilt, and their labels are retained. When repertoires are not defined, the complete dataset is treated as one sampling group and no repertoire summary is added.
Chain-level selection and reconstruction use the duckplyr annotation table.
The small table of sampling units is collected into R for random sampling.
The function does not overwrite the stored input object. Use
write_immundata() to save the returned object.
agg_repertoires(), filter_immundata(), write_immundata()
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Create two small bulk T-cell repertoires with different total abundances. bulk_data <- tibble( Sample = c("Tumor", "Tumor", "Blood", "Blood"), cdr3_aa = c("CASSA", "CASSB", "CASSA", "CASSC"), v_call = c("TRBV1", "TRBV2", "TRBV1", "TRBV3"), abundance = c(20L, 5L, 4L, 6L) ) bulk_file <- tempfile(fileext = ".tsv") readr::write_tsv(bulk_data, bulk_file) idata <- read_repertoires( path = bulk_file, schema = c("cdr3_aa", "v_call"), count_col = "abundance", repertoire_schema = "Sample", preprocess = NULL, postprocess = NULL, rename_columns = NULL, output_folder = tempfile("immundata-downsample-") ) before <- idata$repertoires |> select(Sample, n_barcodes) |> rename(before = n_barcodes) sampled <- downsample_immundata(idata, n = 5, seed = 42) before |> left_join( sampled$repertoires |> select(Sample, n_barcodes) |> rename(after = n_barcodes), by = "Sample" ) |> arrange(Sample) # Expected result: # Sample before after # Blood 10 5 # Tumor 25 5 # Each returned repertoire has five sequence counts. `idata` still has its # original repertoire sizes of 10 and 25.library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Create two small bulk T-cell repertoires with different total abundances. bulk_data <- tibble( Sample = c("Tumor", "Tumor", "Blood", "Blood"), cdr3_aa = c("CASSA", "CASSB", "CASSA", "CASSC"), v_call = c("TRBV1", "TRBV2", "TRBV1", "TRBV3"), abundance = c(20L, 5L, 4L, 6L) ) bulk_file <- tempfile(fileext = ".tsv") readr::write_tsv(bulk_data, bulk_file) idata <- read_repertoires( path = bulk_file, schema = c("cdr3_aa", "v_call"), count_col = "abundance", repertoire_schema = "Sample", preprocess = NULL, postprocess = NULL, rename_columns = NULL, output_folder = tempfile("immundata-downsample-") ) before <- idata$repertoires |> select(Sample, n_barcodes) |> rename(before = n_barcodes) sampled <- downsample_immundata(idata, n = 5, seed = 42) before |> left_join( sampled$repertoires |> select(Sample, n_barcodes) |> rename(after = n_barcodes), by = "Sample" ) |> arrange(Sample) # Expected result: # Sample before after # Blood 10 5 # Tumor 25 5 # Each returned repertoire has five sequence counts. `idata` still has its # original repertoire sizes of 10 and 25.
Use filter() to keep selected rows in an ImmunData object. For example,
you can keep rows from one response group, rows using a selected V gene, or
receptors containing a CDR3 sequence similar to a reference sequence.
The function returns a new ImmunData object. The original object is not changed.
This function is a direct implementation of dplyr::filter. Alternative
function name is filter_immundata.
Use filter_barcodes() to keep selected cell barcodes and
filter_receptors() to keep selected receptor identifiers.
filter_immundata(idata, ..., seq_options = NULL, keep_repertoires = TRUE) ## S3 method for class 'ImmunData' filter( .data, ..., .by = NULL, .preserve = FALSE, seq_options = NULL, keep_repertoires = TRUE ) filter_barcodes(idata, barcodes, keep_repertoires = TRUE) filter_receptors(idata, receptors, keep_repertoires = TRUE)filter_immundata(idata, ..., seq_options = NULL, keep_repertoires = TRUE) ## S3 method for class 'ImmunData' filter( .data, ..., .by = NULL, .preserve = FALSE, seq_options = NULL, keep_repertoires = TRUE ) filter_barcodes(idata, barcodes, keep_repertoires = TRUE) filter_receptors(idata, receptors, keep_repertoires = TRUE)
idata, .data
|
An ImmunData object. |
... |
One or more conditions used to keep rows. Refer to annotation
columns directly by name. Multiple conditions are combined with |
seq_options |
Options for matching sequences with reference sequences or
patterns. Create these options with |
keep_repertoires |
If |
.by, .preserve
|
Accepted for compatibility with |
barcodes |
A character, integer, or numeric vector of cell barcodes to
keep with |
receptors |
A character, integer, or numeric vector of receptor
identifiers to keep with |
You can filter an ImmunData object in three ways:
Supply conditions in ... to filter using annotation columns. Refer to
columns directly by name. For example, Response == "FR" keeps rows from
the FR response group.
Supply seq_options, created with make_seq_options(), to find receptors
containing a sequence that matches one or more reference sequences or
patterns.
Use filter_barcodes() or filter_receptors() when you already have the
identifiers that you want to keep.
Conditions in ... are applied before sequence matching. Sequence matching
then identifies receptors from the remaining rows. When one chain matches,
all remaining chains belonging to the same receptor are kept. A chain removed
by a condition in ... is not added back by sequence matching.
Sequence matching methods are:
"exact": the sequence must be identical to one of the references.
"regex": the sequence must match a regular-expression pattern. This is
an advanced option for matching text patterns.
"lev": the Levenshtein distance counts the substitutions, insertions, or
deletions needed to change one sequence into the other.
"hamm": the Hamming distance counts different positions between
sequences of the same length. Sequences of different lengths do not match.
For "lev" and "hamm", provide max_dist. A sequence is accepted when
its distance from at least one reference is less than or equal to this value.
A distance of 0 means an exact match, and smaller values mean more similar
sequences.
By default, existing repertoire summaries are recalculated from the filtered
data. Existing strata are also rebuilt, and their labels are retained. Set
keep_repertoires = FALSE to return an object without repertoire or strata
summaries.
A new ImmunData object containing the selected rows and receptors. If requested, repertoire and strata summaries are recalculated for the selected data.
dplyr::filter(), make_seq_options(), mutate_immundata(),
agg_repertoires(), ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Load data included with immundata idata <- get_test_idata() # Keep rows from one response group fr_response <- idata |> filter(Response == "FR") fr_response |> collect() |> summarise( n_rows = n(), n_receptors = n_distinct(imd_receptor_id) ) # Expected result: # n_rows n_receptors # 955 871 # Keep receptors containing one reference CDR3 sequence reference_cdr3 <- "ASFPVLSPYNEQF" exact_match <- idata |> filter( seq_options = make_seq_options( query_col = "cdr3_aa", patterns = reference_cdr3, method = "exact" ) ) exact_match |> collect() |> select(cdr3_aa, v_call, Response) # Expected result: # cdr3_aa v_call Response # ASFPVLSPYNEQF TRBV28*01 FR # Keep receptors within four sequence changes of the reference similar_sequences <- idata |> filter( seq_options = make_seq_options( query_col = "cdr3_aa", patterns = reference_cdr3, method = "lev", max_dist = 4 ) ) similar_sequences |> collect() |> distinct(cdr3_aa) |> arrange(cdr3_aa) # Expected result: # cdr3_aa # ASFPVLSPYNEQF # ASSPDSPSYNEQF # ASSPGLAAYNEQF # ASSPTLYNEQF # Keep two selected cell barcodes selected_barcodes <- c("S1_1", "S1_2") selected_cells <- idata |> filter_barcodes(selected_barcodes) selected_cells |> collect() |> distinct(imd_barcode) # Expected result: # imd_barcode # S1_1 # S1_2 # The same approach can keep selected receptor identifiers selected_receptors <- idata |> collect() |> distinct(imd_receptor_id) |> slice_head(n = 2) |> pull(imd_receptor_id) selected_receptors_data <- idata |> filter_receptors(selected_receptors)library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Load data included with immundata idata <- get_test_idata() # Keep rows from one response group fr_response <- idata |> filter(Response == "FR") fr_response |> collect() |> summarise( n_rows = n(), n_receptors = n_distinct(imd_receptor_id) ) # Expected result: # n_rows n_receptors # 955 871 # Keep receptors containing one reference CDR3 sequence reference_cdr3 <- "ASFPVLSPYNEQF" exact_match <- idata |> filter( seq_options = make_seq_options( query_col = "cdr3_aa", patterns = reference_cdr3, method = "exact" ) ) exact_match |> collect() |> select(cdr3_aa, v_call, Response) # Expected result: # cdr3_aa v_call Response # ASFPVLSPYNEQF TRBV28*01 FR # Keep receptors within four sequence changes of the reference similar_sequences <- idata |> filter( seq_options = make_seq_options( query_col = "cdr3_aa", patterns = reference_cdr3, method = "lev", max_dist = 4 ) ) similar_sequences |> collect() |> distinct(cdr3_aa) |> arrange(cdr3_aa) # Expected result: # cdr3_aa # ASFPVLSPYNEQF # ASSPDSPSYNEQF # ASSPGLAAYNEQF # ASSPTLYNEQF # Keep two selected cell barcodes selected_barcodes <- c("S1_1", "S1_2") selected_cells <- idata |> filter_barcodes(selected_barcodes) selected_cells |> collect() |> distinct(imd_barcode) # Expected result: # imd_barcode # S1_1 # S1_2 # The same approach can keep selected receptor identifiers selected_receptors <- idata |> collect() |> distinct(imd_receptor_id) |> slice_head(n = 2) |> pull(imd_receptor_id) selected_receptors_data <- idata |> filter_receptors(selected_receptors)
The from_immunarch() function takes an immunarch object (as returned by
immunarch::repLoad()), writes each repertoire to a TSV file with an added
internal filename column in a specified folder, and then imports those files into
an ImmunData object via read_repertoires().
from_immunarch( imm, output_folder, schema = c("CDR3.aa", "V.name"), temp_folder = file.path(tempdir(), "temp_folder") )from_immunarch( imm, output_folder, schema = c("CDR3.aa", "V.name"), temp_folder = file.path(tempdir(), "temp_folder") )
imm |
A list returned by
|
output_folder |
Path to the output directory where the resulting ImmunData Parquet files will be stored. This directory will be created if it does not already exist. |
schema |
Character vector of column names that together define unique
receptors (for example, |
temp_folder |
Path to a directory where intermediate TSV files will
be written. Defaults to |
An ImmunData object containing all repertoires from the input
immunarch object, with data saved under output_folder.
read_repertoires(), read_immundata(), ImmunData
## Not run: imm <- immunarch::repLoad("/path/to/your/files") idata <- from_immunarch(imm, schema = c("CDR3.aa", "V.name"), temp_folder = tempdir(), output_folder = "/path/to/immundata_out" ) ## End(Not run)## Not run: imm <- immunarch::repLoad("/path/to/your/files") idata <- from_immunarch(imm, schema = c("CDR3.aa", "V.name"), temp_folder = tempdir(), output_folder = "/path/to/immundata_out" ) ## End(Not run)
Use these helpers to inspect or customize the column renaming and removal
presets used by read_repertoires().
imd_rename_cols() returns mappings from standard output names to source
names. imd_drop_cols() returns technical columns that can usually be
removed before receptors are defined. These functions return definitions
only; they do not change input files or an ImmunData object.
imd_rename_cols(format = "default") imd_drop_cols(format = "airr")imd_rename_cols(format = "default") imd_drop_cols(format = "airr")
format |
A character string. The input format preset. For
|
imd_rename_cols() returns a named character vector in the form
c(new_name = "source_name"). imd_drop_cols() returns a character
vector of source columns to remove.
read_repertoires(), make_default_preprocessing(), imd_schema()
imd_rename_cols("10x") # Includes c(v_call = "v_gene", locus = "chain"). head(imd_drop_cols("10x"), 3) # Expected result: # "full_length" "is_cell" "contig_id" # Keep the 10x `contig_id` column while dropping the other default columns. columns_to_drop <- setdiff(imd_drop_cols("10x"), "contig_id") custom_preprocessing <- list( exclude_columns = make_exclude_columns(columns_to_drop), filter_nonproductive = make_productive_filter( truthy = c("TRUE", "true", "1") ) )imd_rename_cols("10x") # Includes c(v_call = "v_gene", locus = "chain"). head(imd_drop_cols("10x"), 3) # Expected result: # "full_length" "is_cell" "contig_id" # Keep the 10x `contig_id` column while dropping the other default columns. columns_to_drop <- setdiff(imd_drop_cols("10x"), "contig_id") custom_preprocessing <- list( exclude_columns = make_exclude_columns(columns_to_drop), filter_nonproductive = make_productive_filter( truthy = c("TRUE", "true", "1") ) )
Use imd_schema() when code needs the standard column name for an
ImmunData identifier or calculated value, such as the cell barcode,
receptor identifier, repertoire identifier, count, or proportion.
Use this helper in reusable analysis code or package extensions instead of
writing an internal name such as "imd_barcode" directly. It only returns
names; it does not inspect or change an ImmunData object.
imd_schema(key = NULL)imd_schema(key = NULL)
key |
A character string or |
If key is supplied, one character string containing the standard
column name. If key = NULL, a named list of all schema keys and column
names.
make_receptor_schema(), imd_rename_cols(), ImmunData
imd_schema("barcode") # Expected result: "imd_barcode" imd_schema("receptor") # Expected result: "imd_receptor_id" # Use a returned name for programmatic selection. barcode_column <- imd_schema("barcode") get_test_idata() |> dplyr::collect() |> dplyr::select(dplyr::all_of(barcode_column)) |> head(2)imd_schema("barcode") # Expected result: "imd_barcode" imd_schema("receptor") # Expected result: "imd_receptor_id" # Use a returned name for programmatic selection. barcode_column <- imd_schema("barcode") get_test_idata() |> dplyr::collect() |> dplyr::select(dplyr::all_of(barcode_column)) |> head(2)
ImmunData stores adaptive immune receptor repertoire (AIRR) data and the rules
used to turn observed sequences or cells into data units for analysis. Think AnnData
or SeuratObject, but for immune repertoires.
You work with an ImmunData object after importing bulk or single-cell AIRR-seq
data. The major idea behind ImmunData is that because sequencing provides only
information about sequences and, for single-cell data, cell
barcodes, the your responsibility is to determine, which sequences you want to treat as the
same receptor, repertoire, or stratum (group of repertoires). You define these analysis units with
schemas. A schema is a stored set of column names and chain-selection rules
that tells ImmunData how to group observations. Those definitions are kept
inside ImmunData to ensure that downstream functions count, filter, and compare
the same units consistently. Repertoire and strata schemas can be changed later to re-aggregate
repertoires differently, e.g., merge receptors from different clusters into
per-patient clusters. Receptor schema is fixed once and for all, so if you want
to work with a different receptor definition, e.g., use "CDR3aa + V gene" instead of
just "CDR3aa" as a definiton for a unique receptor, you will need to create
a separate ImmunData object.
ImmunData is immutable, meaning that functions that transform an ImmunData
object return a new object, and the original object is not changed. Due to multiple
optimisations on the backend, it does not mean that you re-create the whole
dataset each time you run a, let's stay, a filter. However, it does affect analysis workflow
significantly. You can read about it more on the website and in tutorials.
ImmunData connects observed records to user-defined analysis units:
A chain observation is an observed receptor-chain sequence, such as a TRA, TRB, or IGH sequence. Chain observations form the main table.
A barcode is an observed identifier for a cell in single-cell data. It links chains found in the same cell.
A receptor is a virtual analysis unit that you define. For example, you may define it by CDR3 sequence alone, by CDR3 and V gene, or as a paired TRA-TRB receptor. The receptor schema records which chain features and loci must match for observations to receive the same receptor identifier.
A repertoire is a virtual collection of receptors that you define from annotation columns. For example, one repertoire may contain all receptors from one sample, or from one donor at one time point.
A stratum is a virtual collection of repertoires for a comparison. For example, one stratum may contain all repertoires from one treatment arm.
These definitions do not change the observed sequences. They determine how
observations are grouped and counted during analysis. The resulting
hierarchy is chain observations and barcodes -> receptors -> repertoires -> strata.
Print an object for a compact overview. Use $receptors for the receptor
table, $repertoires for one summary row per repertoire, and $strata for
one row per stratum. Most analysis functions accept the complete
ImmunData object directly.
Common transformations include:
filter_immundata() to keep selected chains, cells, or receptors;
mutate_immundata() to calculate annotation columns;
annotate() to add external biological information;
agg_repertoires() to define repertoires; and
agg_strata() to group repertoires into strata.
Create an ImmunData object with read_repertoires(), or reopen a saved
object with read_immundata(). Do not call the $new() constructor in
analysis code. Direct construction is reserved for package developers.
The chain-level table uses duckplyr and can remain on disk. Filtering,
mutation, and aggregation stay lazy when possible, so large datasets do not
need to be loaded fully into R memory. Downstream analysis functions in the
immunarch package are designed to accept lazy ImmunData objects. Pass the
object directly; you usually do not need to call dplyr::collect(). Collect
data only when another function explicitly requires an in-memory data frame
or when you want to inspect a small table in R.
Objects created by read_repertoires() are backed by files in their output
folder. Keep that folder while you use the object. Use write_immundata() to
save a transformed object and read_immundata() to reopen it.
schema_receptorA named list defining the virtual receptor unit.
The features element names the chain columns used to group
observations, such as CDR3 sequence and V gene. The chains element
selects one chain or a paired set of chains.
schema_repertoireA character vector naming annotation columns
whose unique combinations define one repertoire, such as sample_id
or c("donor_id", "timepoint"). It is NULL when repertoires have
not been defined.
schema_strataA character vector naming repertoire-level columns
whose unique combinations define one stratum, such as treatment. It
is NULL when strata have not been defined.
receptorsA derived duckplyr table of distinct receptors. For a paired receptor, the selected chain features are shown side by side.
annotationsThe lazy duckplyr table of retained chain
observations and their biological annotations. For most tasks, pass the
complete ImmunData object to a transformation function or use
collect(idata) to inspect this table in memory.
repertoiresA small table with one row per repertoire, the columns
that define it, and summary statistics such as n_barcodes and
n_receptors. It is NULL when repertoires have not been defined.
strataA small table with one row per stratum, its label, and the
repertoire-level columns that define it. It is NULL when strata have
not been defined.
provenanceRead-only named list describing the snapshot origin
and storage context carried by this object. Retrieve the complete list with
idata$provenance, or one field with, for example,
idata$provenance$current_path. The fields are:
home_path: project home used for managed snapshots and artifacts.
The original ingestion snapshot is stored directly in this folder;
it is NULL for an object with no persisted home.
current_path: exact folder of the most recently loaded or written
snapshot. Transformations preserve this source path until the
transformed object is written as another snapshot; it is NULL for
an object that has never been loaded from or written to disk.
snapshot_root: derived managed-snapshot root,
home_path/snapshots, or NULL when home_path is NULL.
artifacts_root: derived project-level root for optional external
tool outputs, home_path/artifacts, or NULL when home_path is
NULL.
artifacts_path: derived namespace for artifacts associated with the
most recently loaded or written snapshot. It is
artifacts_root/root for the original
ingestion, artifacts_root/<tag>/vNNN for a managed snapshot, and
artifacts_root/by-id/<snapshot_id> for a detached explicit snapshot.
External tools can append <tool>/<run> and create that directory;
artifact contents are not part of ImmunData. Write a transformed
object as a new snapshot before storing artifacts that should be
associated with the transformed data.
snapshot_id: unique identifier generated when the snapshot is
written; NULL for an in-memory object that has never been written.
lineage: ordered list of ingestion and snapshot events leading to
the current snapshot.
The accessor is read-only; assigning to idata$provenance is an error.
ImmunData$new()Low-level constructor for package developers. Analysis code
must create an ImmunData object with read_repertoires() or reopen one
with read_immundata().
ImmunData$new( schema, annotations, repertoires = NULL, provenance = NULL, strata = NULL )
schemaA character vector or named list. A character vector names
the features used to define a chain-agnostic receptor. A named list is
created by make_receptor_schema() and can also select receptor chains.
annotationsA duckplyr table. It contains retained chain observations, receptor identifiers, and biological annotations.
repertoiresA data frame or NULL. It contains one row per
repertoire and its summary statistics and is usually created by
agg_repertoires().
provenanceA list or NULL. It contains internal storage and
snapshot history.
strataA data frame or NULL. It contains one row per stratum,
its label, and the repertoire-level columns that define it.
ImmunData$clone()The objects of this class are cloneable with this method.
ImmunData$clone(deep = FALSE)
deepWhether to make a deep clone.
read_repertoires(), read_immundata(), write_immundata(),
agg_repertoires(), agg_strata(), filter_immundata(),
mutate_immundata(), annotate()
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Load the small dataset included with immundata, then define one repertoire # for each treatment-response group. idata <- get_test_idata() |> agg_repertoires(schema = "Response") idata$repertoires |> select(Response, n_barcodes, n_receptors) |> arrange(Response) # Expected result: # Response n_barcodes n_receptors # FR 955 871 # PR 947 867 # Under the current receptor definition, the full-response (FR) repertoire # contains 955 chain observations grouped into 871 receptor units. # Keep only the full-response repertoire. filter() returns a new object. fr_only <- idata |> filter(Response == "FR") tibble( original_repertoires = nrow(idata$repertoires), filtered_repertoires = nrow(fr_only$repertoires) ) # Expected result: # original_repertoires filtered_repertoires # 2 1 # The original object still contains both repertoires.library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Load the small dataset included with immundata, then define one repertoire # for each treatment-response group. idata <- get_test_idata() |> agg_repertoires(schema = "Response") idata$repertoires |> select(Response, n_barcodes, n_receptors) |> arrange(Response) # Expected result: # Response n_barcodes n_receptors # FR 955 871 # PR 947 867 # Under the current receptor definition, the full-response (FR) repertoire # contains 955 chain observations grouped into 871 receptor units. # Keep only the full-response repertoire. filter() returns a new object. fr_only <- idata |> filter(Response == "FR") tibble( original_repertoires = nrow(idata$repertoires), filtered_repertoires = nrow(fr_only$repertoires) ) # Expected result: # original_repertoires filtered_repertoires # 2 1 # The original object still contains both repertoires.
Use these functions to preprocess or postprocess rows of the input data before
returning the final ImmunData object to the session. A couple of example
use cases: keep productive receptor chains, remove technical
columns, or make cell barcodes unique while importing repertoire files with
read_repertoires().
The defaults provide steps for common AIRR or 10x inputs. Use an individual step
when your files need only one operation or when you are building a custom
preprocess or postprocess list.
Preprocessing changes chain rows before receptors are defined. Barcode prefixing changes the cell identifier after receptor and manifest information are combined. The input files and input table are not changed: every step returns a new duckplyr table.
make_default_preprocessing(format = c("default", "airr", "10x")) make_default_postprocessing() make_exclude_columns(cols = imd_drop_cols("airr")) make_productive_filter(col_name = c("productive"), truthy = TRUE) make_barcode_prefix(prefix_col = "Prefix")make_default_preprocessing(format = c("default", "airr", "10x")) make_default_postprocessing() make_exclude_columns(cols = imd_drop_cols("airr")) make_productive_filter(col_name = c("productive"), truthy = TRUE) make_barcode_prefix(prefix_col = "Prefix")
format |
A character string. One input format: |
cols |
A character vector. Columns to remove. The default is
|
col_name |
A character string. Column containing the productive-chain
indicator. The default is |
truthy |
A vector. Values that mean the chain is productive. Values are
compared as text. The default is |
prefix_col |
A character vector. One or more candidate columns
containing the text to place before each cell barcode. The first candidate
present in the data is used. The default is |
make_default_preprocessing() and
make_default_postprocessing() return named lists of processing functions.
The other functions return one processing function. Each processing
function accepts a duckplyr table as its first argument, accepts unused
arguments through ..., and returns a new duckplyr table.
make_default_preprocessing() returns two steps. The first removes common
technical columns. The second keeps rows whose productive value indicates
a productive chain. If the productive column is absent, the filtering
step gives a warning and keeps all rows.
make_default_postprocessing() returns one step that adds a sample-specific
prefix to cell barcodes. If the prefix column is absent, the step gives a
warning and leaves barcodes unchanged.
make_exclude_columns() creates one step that removes the columns in
cols. Column names that are not present are ignored.
make_productive_filter() creates one step that keeps rows whose value in
col_name matches any value in truthy.
make_barcode_prefix() creates one step that joins a prefix, such as
"Tumor_", to the start of each imd_barcode value.
read_repertoires() applies functions in list order. You can therefore add,
remove, or reorder steps in a custom list.
For make_default_preprocessing(), format = "default" removes the union of
the standard AIRR and 10x technical columns. Use format = "airr" or
format = "10x" to remove only the columns expected for that format. All
three defaults recognize common text representations of a productive value,
including "TRUE", "true", "yes", and "1".
read_repertoires(), imd_drop_cols(), imd_rename_cols()
library(immundata) library(dplyr) # Three 10x chain rows from two samples. One chain is non-productive. chains <- duckplyr::duckdb_tibble( imd_barcode = c("AAAC-1", "AAAG-1", "AATT-1"), cdr3_aa = c("CASSA", "CASSB", "CASSC"), productive = c("TRUE", "FALSE", "TRUE"), full_length = c(TRUE, TRUE, TRUE), Prefix = c("Tumor_", "Tumor_", "Blood_") ) # read_repertoires() performs these calls for you. They are shown here to # make the effect of each list clear. prepared <- Reduce( function(data, step) step(data), make_default_preprocessing("10x"), init = chains ) prepared <- Reduce( function(data, step) step(data), make_default_postprocessing(), init = prepared ) prepared |> collect() |> select(imd_barcode, cdr3_aa, productive) # Expected result: # imd_barcode cdr3_aa productive # Tumor_AAAC-1 CASSA TRUE # Blood_AATT-1 CASSC TRUE # The non-productive chain was removed, `full_length` was dropped, and the # sample prefixes made the retained cell barcodes unique.library(immundata) library(dplyr) # Three 10x chain rows from two samples. One chain is non-productive. chains <- duckplyr::duckdb_tibble( imd_barcode = c("AAAC-1", "AAAG-1", "AATT-1"), cdr3_aa = c("CASSA", "CASSB", "CASSC"), productive = c("TRUE", "FALSE", "TRUE"), full_length = c(TRUE, TRUE, TRUE), Prefix = c("Tumor_", "Tumor_", "Blood_") ) # read_repertoires() performs these calls for you. They are shown here to # make the effect of each list clear. prepared <- Reduce( function(data, step) step(data), make_default_preprocessing("10x"), init = chains ) prepared <- Reduce( function(data, step) step(data), make_default_postprocessing(), init = prepared ) prepared |> collect() |> select(imd_barcode, cdr3_aa, productive) # Expected result: # imd_barcode cdr3_aa productive # Tumor_AAAC-1 CASSA TRUE # Blood_AATT-1 CASSC TRUE # The non-productive chain was removed, `full_length` was dropped, and the # sample prefixes made the retained cell barcodes unique.
Use make_receptor_schema() to define a biological receptor from sequence
features and one or two receptor chains.
Use this function when reading single-cell data with one selected chain, when pairing chains such as TRA-TRB, or when accepting alternative light chains such as IGK or IGL. The unit being defined is the receptor. Creating a schema does not change any data or an existing ImmunData object.
make_receptor_schema(features, chains = NULL) assert_receptor_schema(schema) test_receptor_schema(schema)make_receptor_schema(features, chains = NULL) assert_receptor_schema(schema) test_receptor_schema(schema)
features |
A non-empty character vector. Column names containing the
chain fields that must match, such as
|
chains |
A character vector of length one or two, or |
schema |
A non-empty character vector or receptor-schema list. An object
to check. A schema created by |
make_receptor_schema() returns a list with character elements
features and chains; chains is NULL when loci are not selected.
assert_receptor_schema() returns TRUE for accepted input and otherwise
stops with an error. test_receptor_schema() returns one logical value.
features names the fields that define chain identity. Common choices are
the CDR3 amino acid sequence, V gene, and J gene. Two observations represent
the same receptor only when the relevant chain loci and every selected
feature match.
With one chain, such as chains = "TRB", only that locus is used. Two TRB
observations are the same receptor when all their selected feature values
match.
With a strict pair, such as chains = c("TRA", "TRB"), chains are first
paired within each cell barcode. Receptors from two cells are the same only
when every selected TRA feature and every selected TRB feature match.
With an alternative second chain, such as
chains = c("IGH", "IGK|IGL"), each receptor must contain IGH and exactly
one of IGK or IGL. Cells containing both IGK and IGL are excluded. The
light-chain locus and all selected heavy- and light-chain features must
match for two observations to be the same receptor.
A barcode determines which chains belong to the same cell; it does not by
itself define receptor identity across cells. During single-cell import,
read_repertoires() uses umi_col to choose one chain when a cell contains
several observations from the same locus.
Use chains = NULL for chain-agnostic bulk or pre-filtered data. In that
case, only the values in features define receptor identity.
assert_receptor_schema() stops with an error if schema is not accepted.
Use it inside another function when invalid input must stop the calculation.
test_receptor_schema() returns one TRUE or FALSE value and is useful in
conditional code.
A receptor schema is a small R list containing features and chains. It
stores no sequence data. read_repertoires() and agg_receptors() apply the
schema to chain observations using duckplyr.
read_repertoires(), agg_receptors(), imd_schema()
# Single-chain TCR: compare TRB observations by CDR3, V gene, and J gene. trb_schema <- make_receptor_schema( features = c("junction_aa", "v_call", "j_call"), chains = "TRB" ) trb_schema # Expected result: # $features: "junction_aa" "v_call" "j_call" # $chains: "TRB" # Paired alpha-beta TCR: all selected fields must match on both TRA and TRB. ab_tcr_schema <- make_receptor_schema( features = c("junction_aa", "v_call", "j_call"), chains = c("TRA", "TRB") ) ab_tcr_schema # The result defines one receptor as a matched TRA-TRB pair from one cell. # BCR: require IGH and accept either an IGK or IGL light chain. bcr_schema <- make_receptor_schema( features = c("junction_aa", "v_call", "j_call"), chains = c("IGH", "IGK|IGL") ) bcr_schema # The result accepts IGH-IGK and IGH-IGL receptors, while keeping the two # light-chain loci biologically distinct. test_receptor_schema(bcr_schema) # Expected result: TRUE# Single-chain TCR: compare TRB observations by CDR3, V gene, and J gene. trb_schema <- make_receptor_schema( features = c("junction_aa", "v_call", "j_call"), chains = "TRB" ) trb_schema # Expected result: # $features: "junction_aa" "v_call" "j_call" # $chains: "TRB" # Paired alpha-beta TCR: all selected fields must match on both TRA and TRB. ab_tcr_schema <- make_receptor_schema( features = c("junction_aa", "v_call", "j_call"), chains = c("TRA", "TRB") ) ab_tcr_schema # The result defines one receptor as a matched TRA-TRB pair from one cell. # BCR: require IGH and accept either an IGK or IGL light chain. bcr_schema <- make_receptor_schema( features = c("junction_aa", "v_call", "j_call"), chains = c("IGH", "IGK|IGL") ) bcr_schema # The result accepts IGH-IGK and IGH-IGL receptors, while keeping the two # light-chain loci biologically distinct. test_receptor_schema(bcr_schema) # Expected result: TRUE
Create sequence comparison options for the seq_options argument of
filter_immundata() or mutate_immundata(). Use these options to compare a
sequence column with one or more reference sequences or patterns.
make_seq_options( query_col, patterns, method = c("exact", "lev", "hamm", "regex"), max_dist = NA, name_type = c("index", "pattern") )make_seq_options( query_col, patterns, method = c("exact", "lev", "hamm", "regex"), max_dist = NA, name_type = c("index", "pattern") )
query_col |
Name of the sequence column to compare, such as |
patterns |
One or more reference sequences or regular-expression patterns. |
method |
Comparison method: |
max_dist |
Maximum distance accepted by |
name_type |
How result columns created by |
A named list for the seq_options argument of filter_immundata() or
mutate_immundata().
filter_immundata(), mutate_immundata(), annotate_receptors()
Use mutate() to add information to each row of an ImmunData object. For
example, you can calculate CDR3 length, mark sequences of interest, or compare
receptor sequences with reference sequences.
The function returns a new ImmunData object. The original object is not changed.
This function is a direct implementation of dplyr::mutate. Alternative
function name is mutate_immundata.
mutate_immundata(idata, ..., .by = NULL, seq_options = NULL) ## S3 method for class 'ImmunData' mutate(.data, ..., .by = NULL, seq_options = NULL)mutate_immundata(idata, ..., .by = NULL, seq_options = NULL) ## S3 method for class 'ImmunData' mutate(.data, ..., .by = NULL, seq_options = NULL)
idata, .data
|
An ImmunData object. |
... |
One or more named calculations in the form
|
.by |
Optional columns used to form temporary groups for this operation.
For example, |
seq_options |
Options for comparing sequences with reference sequences or
patterns. Create these options with |
You can use mutate() in three ways:
Supply named calculations in ... to create annotation columns from
existing data. For example, cmv_specific = cdr3_aa %in% cmv_cdr3s adds a
column containing TRUE or FALSE for each row.
Supply .by to perform calculations separately for temporary groups. The
number of rows does not change. A group statistic is repeated for all rows
in that group.
Supply seq_options, created with make_seq_options(), to compare a
sequence column with one or more reference sequences or patterns. One result
column is added for each reference.
Named calculations in ... are performed before sequence comparisons.
Most grouped calculations are translated directly to DuckDB. Some group
statistics, such as n_distinct(), are not available as DuckDB window
calculations when a large dataset must stay on disk. In that case, mutate()
automatically calculates one summary row per group and joins the values back
to the annotation rows. This remains lazy and does not load the full dataset
into R memory.
The automatic fallback works when every calculation in the call produces one
value per group. If a call combines a row-level calculation with a group
statistic that needs the fallback, use two mutate() calls. Also use a second
call when a later calculation refers to a group statistic created by the
fallback. See the examples below.
Columns used to identify receptors or repertoires, and identifiers managed by
ImmunData, are protected. This prevents accidental changes that would make
the object inconsistent. You can add new columns and change other annotation
columns.
Sequence comparison methods are:
"exact": TRUE when the sequence is identical to the reference.
"regex": TRUE when the sequence matches a regular-expression pattern.
This is an advanced option for matching text patterns.
"lev": the number of substitutions, insertions, or deletions needed to
change one sequence into the other.
"hamm": the number of different positions between sequences of the same
length. Sequences with different lengths receive NA.
For the distance methods, 0 means an exact match and smaller values mean
more similar sequences. With name_type = "index", the result columns have
short names such as imd_sim_exact_1 or imd_sim_lev_1. With
name_type = "pattern", each column name includes its reference pattern.
max_dist is used by filter_immundata() but has no effect here because
mutate() reports every calculated distance.
Existing repertoire and strata summaries are carried to the new object without modification.
A new ImmunData object containing the added or changed annotation columns. Existing repertoire and strata summaries are preserved.
dplyr::mutate(), make_seq_options(), filter_immundata(),
annotate_receptors(), agg_repertoires(), ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Load data included with immundata idata <- get_test_idata() # Add the length of each CDR3 amino acid sequence idata_with_length <- idata |> mutate(cdr3_length = dd$length(cdr3_aa)) idata_with_length |> collect() |> select(cdr3_aa, cdr3_length) |> slice_head(n = 3) # Expected result: # cdr3_aa cdr3_length # ASFPVLSPYNEQF 13 # ASRAGAGTGELF 12 # ASSPGQGLDTQY 12 # Compare CDR3 sequences with one reference sequence reference_cdr3 <- "ASFPVLSPYNEQF" idata_with_matches <- idata |> mutate( seq_options = make_seq_options( query_col = "cdr3_aa", patterns = reference_cdr3, method = "exact" ) ) idata_with_matches |> collect() |> count(imd_sim_exact_1) # Expected result: # imd_sim_exact_1 n # FALSE 1901 # TRUE 1 # Calculate Levenshtein distance from the reference sequence idata_with_distance <- idata |> mutate( seq_options = make_seq_options( query_col = "cdr3_aa", patterns = reference_cdr3, method = "lev" ) ) idata_with_distance |> collect() |> select(cdr3_aa, imd_sim_lev_1) |> arrange(imd_sim_lev_1, cdr3_aa) |> slice_head(n = 3) # Expected result: # cdr3_aa imd_sim_lev_1 # ASFPVLSPYNEQF 0 # ASSPDSPSYNEQF 4 # ASSPGLAAYNEQF 4 # Mark selected sequences cmv_cdr3s <- c( "ASFPVLSPYNEQF", "ASRAGAGTGELF" ) marked_sequences <- idata |> mutate( cmv_specific = cdr3_aa %in% cmv_cdr3s ) marked_sequences |> collect() |> count(cmv_specific) # Expected result: # cmv_specific n # FALSE 1900 # TRUE 2 # Mark selected receptor identities cmv_hits <- tibble( imd_receptor_id = c(1L, 105L), cmv_specific = TRUE ) marked_receptors <- idata |> annotate_receptors(cmv_hits) |> mutate( cmv_specific = coalesce(cmv_specific, FALSE) ) marked_receptors |> collect() |> count(cmv_specific) # Expected result: # cmv_specific n # FALSE 1898 # TRUE 4 # Add response-level statistics to every annotation row # `.by` means: calculate separately for each response. response_stats <- idata |> mutate( response_n_rows = n(), response_n_receptors = n_distinct(imd_receptor_id), .by = Response ) response_stats |> collect() |> distinct(Response, response_n_rows, response_n_receptors) |> arrange(Response) # Expected result: # Response response_n_rows response_n_receptors # FR 955 871 # PR 947 867 # A grouped calculation can also produce a different value for every row. response_centered <- idata |> mutate( centered_counts = counts - mean(counts, na.rm = TRUE), .by = Response ) # Do not combine that row-level calculation with a statistic that needs the # automatic summary fallback in the same call: # idata |> # mutate( # centered_counts = counts - mean(counts, na.rm = TRUE), # response_n_receptors = n_distinct(imd_receptor_id), # .by = Response # ) # Use two mutate calls instead. The work remains lazy in DuckDB. response_details <- idata |> mutate( centered_counts = counts - mean(counts, na.rm = TRUE), .by = Response ) |> mutate( response_n_receptors = n_distinct(imd_receptor_id), .by = Response ) # Also use a second call when a new calculation uses a statistic created by # the fallback. response_details <- idata |> mutate( response_n_receptors = n_distinct(imd_receptor_id), .by = Response ) |> mutate( twice_response_n_receptors = response_n_receptors * 2 )library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Load data included with immundata idata <- get_test_idata() # Add the length of each CDR3 amino acid sequence idata_with_length <- idata |> mutate(cdr3_length = dd$length(cdr3_aa)) idata_with_length |> collect() |> select(cdr3_aa, cdr3_length) |> slice_head(n = 3) # Expected result: # cdr3_aa cdr3_length # ASFPVLSPYNEQF 13 # ASRAGAGTGELF 12 # ASSPGQGLDTQY 12 # Compare CDR3 sequences with one reference sequence reference_cdr3 <- "ASFPVLSPYNEQF" idata_with_matches <- idata |> mutate( seq_options = make_seq_options( query_col = "cdr3_aa", patterns = reference_cdr3, method = "exact" ) ) idata_with_matches |> collect() |> count(imd_sim_exact_1) # Expected result: # imd_sim_exact_1 n # FALSE 1901 # TRUE 1 # Calculate Levenshtein distance from the reference sequence idata_with_distance <- idata |> mutate( seq_options = make_seq_options( query_col = "cdr3_aa", patterns = reference_cdr3, method = "lev" ) ) idata_with_distance |> collect() |> select(cdr3_aa, imd_sim_lev_1) |> arrange(imd_sim_lev_1, cdr3_aa) |> slice_head(n = 3) # Expected result: # cdr3_aa imd_sim_lev_1 # ASFPVLSPYNEQF 0 # ASSPDSPSYNEQF 4 # ASSPGLAAYNEQF 4 # Mark selected sequences cmv_cdr3s <- c( "ASFPVLSPYNEQF", "ASRAGAGTGELF" ) marked_sequences <- idata |> mutate( cmv_specific = cdr3_aa %in% cmv_cdr3s ) marked_sequences |> collect() |> count(cmv_specific) # Expected result: # cmv_specific n # FALSE 1900 # TRUE 2 # Mark selected receptor identities cmv_hits <- tibble( imd_receptor_id = c(1L, 105L), cmv_specific = TRUE ) marked_receptors <- idata |> annotate_receptors(cmv_hits) |> mutate( cmv_specific = coalesce(cmv_specific, FALSE) ) marked_receptors |> collect() |> count(cmv_specific) # Expected result: # cmv_specific n # FALSE 1898 # TRUE 4 # Add response-level statistics to every annotation row # `.by` means: calculate separately for each response. response_stats <- idata |> mutate( response_n_rows = n(), response_n_receptors = n_distinct(imd_receptor_id), .by = Response ) response_stats |> collect() |> distinct(Response, response_n_rows, response_n_receptors) |> arrange(Response) # Expected result: # Response response_n_rows response_n_receptors # FR 955 871 # PR 947 867 # A grouped calculation can also produce a different value for every row. response_centered <- idata |> mutate( centered_counts = counts - mean(counts, na.rm = TRUE), .by = Response ) # Do not combine that row-level calculation with a statistic that needs the # automatic summary fallback in the same call: # idata |> # mutate( # centered_counts = counts - mean(counts, na.rm = TRUE), # response_n_receptors = n_distinct(imd_receptor_id), # .by = Response # ) # Use two mutate calls instead. The work remains lazy in DuckDB. response_details <- idata |> mutate( centered_counts = counts - mean(counts, na.rm = TRUE), .by = Response ) |> mutate( response_n_receptors = n_distinct(imd_receptor_id), .by = Response ) # Also use a second call when a new calculation uses a statistic created by # the fallback. response_details <- idata |> mutate( response_n_receptors = n_distinct(imd_receptor_id), .by = Response ) |> mutate( twice_response_n_receptors = response_n_receptors * 2 )
Disallows replacing names on ImmunData.
## S3 replacement method for class 'ImmunData' names(x) <- value## S3 replacement method for class 'ImmunData' names(x) <- value
x |
ImmunData object. |
value |
Not used. |
Use print() to inspect the receptor table, chain annotations, and biological
schemas stored in an ImmunData object.
Use this method for a quick overview after reading, filtering, or aggregating repertoire data. It displays the units available in the object: receptors, chain rows, repertoires, and strata. It also shows the feature and chain definitions used to construct receptors.
Printing is read-only. It does not collect the complete dataset into R and does not change the original object. The object is returned invisibly so it can still be assigned or used in a pipeline.
## S3 method for class 'ImmunData' print(x, ...)## S3 method for class 'ImmunData' print(x, ...)
x |
An ImmunData object to display. |
... |
Additional arguments. Currently not used. |
A section is shown only when that information is available. An object without repertoire definitions, for example, has no repertoire schema or repertoire summary section. Duckplyr prints a preview of large tables rather than every row.
x, invisibly. The displayed output is a human-readable overview;
no data are modified.
ImmunData, dplyr::collect(), dplyr::count()
library(immundata) options(immundata.verbose = FALSE) idata <- get_test_idata() print(idata) # Expected output contains these sections: # ImmunData # Receptors # Annotations # Receptor schema # Repertoire schema # List of repertoires # `Receptors` previews distinct biological receptor definitions. # `Annotations` previews the retained chain rows and sample information. # The schema sections explain how receptors and repertoires were defined.library(immundata) options(immundata.verbose = FALSE) idata <- get_test_idata() print(idata) # Expected output contains these sections: # ImmunData # Receptors # Annotations # Receptor schema # Repertoire schema # List of repertoires # `Receptors` previews distinct biological receptor definitions. # `Annotations` previews the retained chain rows and sample information. # The schema sections explain how receptors and repertoires were defined.
Continue an analysis later by reopening an ImmunData dataset saved on disk.
Use read_immundata() after restarting R, in another script, or when another
person gives you a dataset created by write_immundata() or
read_repertoires(). It is that simple, just don't forget to save the
ImmunData object first!
The unit restored retains all information: chain rows, cell and receptor identifiers, repertoire and stratum definitions, and provenance. The function does not change these biological units or the saved files. It returns a new ImmunData object.
read_immundata( path, tag = NULL, version = NULL, prudence = "stingy", verbose = getOption("immundata.verbose", TRUE) )read_immundata( path, tag = NULL, version = NULL, prudence = "stingy", verbose = getOption("immundata.verbose", TRUE) )
path |
A character string. Path to a saved dataset directory. The
directory must contain |
tag |
A character string or |
version |
A non-negative integer or |
prudence |
A character string. Memory protection used while reading the
Parquet data. This controls whether duckplyr may convert an intermediate
result from DuckDB-managed memory to an R data frame: |
verbose |
A logical value. Whether to print progress and summary
messages. Defaults to |
Read either a dataset directory directly or a versioned snapshot within its project home.
A new, disk-backed ImmunData object representing the selected saved state. Its provenance records the directory that was read.
To reopen a dataset saved directly in a folder, supply that folder as path
and leave tag and version as NULL.
To reopen a managed snapshot, supply the project home as path and its tag.
By default, the latest version for that tag is read. Supply version when
you need an exact earlier state.
annotations.parquet stores the retained chain-level annotation table.
It is reopened as a lazy duckplyr table, so the complete table does not need
to be loaded into R memory. metadata.json stores the format and package
versions, receptor, repertoire, and stratum schemas, the repertoire
table, the snapshot identifier, lineage events, and provenance paths.
Receptor and stratum views are reconstructed from this serialized state; they are not stored as separate files. Please also mind, that the saved files is an ImmunData-specific serialization, not an RDS file.
write_immundata() for saving an analysis, read_repertoires() for
importing AIRR-seq files, ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Create a project home and save a filtered biological state as a snapshot idata <- get_test_idata() project_dir <- tempfile("immundata-project-") project_idata <- write_immundata( idata, output_folder = project_dir, rehome = TRUE ) fr_response <- project_idata |> filter(Response == "FR") write_immundata(fr_response, tag = "fr-response") # Read the exact first version of this snapshot continued_fr <- read_immundata( project_dir, tag = "fr-response", version = 1 ) continued_fr |> collect() |> summarise( n_chains = n(), n_receptors = n_distinct(imd_receptor_id) ) # Expected result: the snapshot contains the 955 chain rows and 871 # receptors from the FR response group. # n_chains n_receptors # 955 871 list.files(file.path(project_dir, "snapshots", "fr-response")) # Expected result: "v001" unlink(project_dir, recursive = TRUE)library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Create a project home and save a filtered biological state as a snapshot idata <- get_test_idata() project_dir <- tempfile("immundata-project-") project_idata <- write_immundata( idata, output_folder = project_dir, rehome = TRUE ) fr_response <- project_idata |> filter(Response == "FR") write_immundata(fr_response, tag = "fr-response") # Read the exact first version of this snapshot continued_fr <- read_immundata( project_dir, tag = "fr-response", version = 1 ) continued_fr |> collect() |> summarise( n_chains = n(), n_receptors = n_distinct(imd_receptor_id) ) # Expected result: the snapshot contains the 955 chain rows and 871 # receptors from the FR response group. # n_chains n_receptors # 955 871 list.files(file.path(project_dir, "snapshots", "fr-response")) # Expected result: "v001" unlink(project_dir, recursive = TRUE)
This function loads a manifest from either a file path or a data frame, validates the presence of a column with repertoire file paths, and converts all file paths to absolute paths. It is used to support flexible pipelines for loading bulk or single-cell immune repertoire data across samples.
If the input is a file path, the function reads it with readr::read_delim.
If the input is a data frame, it checks whether file paths are absolute;
relative paths are only allowed when the manifest is loaded from a file.
It warns the user if many of the files listed in the manifest are missing, and stops execution if none of the files exist.
The column with file paths is normalized into the internal filename schema.
read_manifest( manifest, file_col = "file", delim = NULL, ..., verbose = getOption("immundata.verbose", TRUE) )read_manifest( manifest, file_col = "file", delim = NULL, ..., verbose = getOption("immundata.verbose", TRUE) )
manifest |
A manifest table. Can be either:
|
file_col |
A string specifying the name of the column in the manifest
that contains paths to repertoire files. Defaults to |
delim |
Delimiter used to read the manifest file. If |
... |
Additional arguments passed to |
verbose |
Logical(1). Whether to print informative messages. Defaults to
|
A validated and updated manifest data frame with absolute file paths
and an additional internal column named imd_filename.
read_repertoires() is the main function for importing AIRR-seq data. It
reads one or more repertoire files, defines biological receptors, adds
sample information from an optional manifest, and returns an ImmunData
object.
The function saves the processed data in output_folder. This lets you work
with large datasets without loading everything into memory and reopen the
result later with read_immundata().
read_repertoires( path, schema, manifest = NULL, barcode_col = NULL, count_col = NULL, locus_col = NULL, umi_col = NULL, preprocess = make_default_preprocessing(), postprocess = make_default_postprocessing(), rename_columns = imd_rename_cols("10x"), enforce_schema = TRUE, manifest_file_col = "file", output_folder = NULL, repertoire_schema = "<auto>", verbose = getOption("immundata.verbose", TRUE), prematerialize = TRUE, prematerialize_folder = NULL )read_repertoires( path, schema, manifest = NULL, barcode_col = NULL, count_col = NULL, locus_col = NULL, umi_col = NULL, preprocess = make_default_preprocessing(), postprocess = make_default_postprocessing(), rename_columns = imd_rename_cols("10x"), enforce_schema = TRUE, manifest_file_col = "file", output_folder = NULL, repertoire_schema = "<auto>", verbose = getOption("immundata.verbose", TRUE), prematerialize = TRUE, prematerialize_folder = NULL )
path |
One or more repertoire file paths, or a glob pattern such as
Use |
schema |
Definition of receptor identity. Supply either:
Use column names as they appear after |
manifest |
An optional data frame with one row per repertoire file and
columns containing sample, donor, tissue, treatment, or other information.
Use |
barcode_col |
Name of the column containing cell barcodes. Supplying it
selects single-cell processing, requires |
count_col |
Name of the column containing non-negative abundance values
for bulk repertoire data. It cannot be used with |
locus_col |
Name of the column containing receptor loci such as |
umi_col |
Name of the column containing per-chain UMI or read counts.
It is required whenever |
preprocess |
A named list of functions applied in order before receptors
are defined. Each function must accept a duckplyr table as its first
argument and return a duckplyr table. By default,
|
postprocess |
A named list of functions applied in order after receptors
are defined and manifest information is added. Each function must accept
and return a duckplyr table. By default, |
rename_columns |
An optional named character vector in the form
|
enforce_schema |
Whether multiple input files must have the same columns
and column types. The default is |
manifest_file_col |
Name of the manifest column containing repertoire
file paths when |
output_folder |
Directory in which to write |
repertoire_schema |
Definition of repertoires. Supply one of:
|
verbose |
Whether to print progress and summary messages. Defaults to
|
prematerialize |
Whether CSV, TSV, and compressed text inputs should be
combined into a temporary Parquet file before receptor processing. This
avoids repeatedly scanning text input during downstream lazy queries.
Existing Parquet input is used directly. The default is |
prematerialize_folder |
Directory in which to create the temporary
combined Parquet file. If |
The required arguments depend on how receptor observations are represented in the input files.
A disk-backed ImmunData object containing the retained chain rows,
receptor definitions, manifest annotations, and ingestion provenance. If
repertoire_schema is not NULL, it also contains repertoire definitions
and summary statistics calculated by agg_repertoires().
Uncounted repertoire table: Supply schema. Leave barcode_col and
count_col as NULL. Each retained row represents one observed chain.
Bulk repertoire with abundance: Supply schema and count_col. The
abundance values are preserved for later repertoire statistics.
Single-cell, one selected chain: Use make_receptor_schema() with one
chain and supply barcode_col, locus_col, and umi_col.
Single-cell, paired chains: Use make_receptor_schema() with two chains
and supply barcode_col, locus_col, and umi_col. Only cells containing
both requested chains are retained.
Single-cell, relaxed paired chains: Use a schema such as
chains = c("IGH", "IGL|IGK") with barcode_col, locus_col, and
umi_col. This accepts either an IGH-IGL or IGH-IGK receptor.
In single-cell data, the chain with the highest umi_col value is retained
when a cell contains several chains from the same locus.
Unless you override the relevant arguments, read_repertoires():
temporarily combines text input into Parquet before processing;
standardizes common 10x column names;
removes selected technical columns;
keeps productive sequences when productivity information is present;
prefixes barcodes when a manifest Prefix column is present;
creates repertoires automatically; and
writes the completed dataset to disk.
Set rename_columns, preprocess, postprocess, or repertoire_schema to
NULL to disable the corresponding behavior.
The function:
finds and reads the input files as one duckplyr table;
temporarily combines non-Parquet input into one Parquet file when
prematerialize = TRUE;
renames columns;
applies preprocessing;
defines receptors using schema;
adds manifest information;
applies postprocessing;
defines repertoires when requested; and
writes and reopens the completed ImmunData dataset.
A manifest annotates each input file with biological information. The
repertoire_schema argument chooses which annotation columns define a
repertoire and therefore determine receptor counts and proportions.
With path = "<manifest>" and the default repertoire_schema = "<auto>",
all manifest columns are used and each manifest row becomes one repertoire.
With an explicit file path or vector of paths, "<auto>" creates one
repertoire per input file.
The output folder is not a temporary cache. The returned object reads its
receptor annotations from annotations.parquet, while metadata.json stores
its schemas, repertoire summaries, and provenance. Keep this folder for as
long as you need the object, or reopen it later with read_immundata().
Important: Reusing the same output_folder replaces the existing
annotations.parquet and metadata.json without creating a new version.
read_manifest(), make_receptor_schema(), agg_receptors(),
agg_repertoires(), make_default_preprocessing(),
make_default_postprocessing(), read_immundata(), write_immundata(),
ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Read one bulk AIRR file and preserve its abundance column bulk_file <- system.file( "extdata/tsv", "sample_0_1k.tsv", package = "immundata" ) bulk_idata <- read_repertoires( path = bulk_file, schema = c("cdr3_aa", "v_call"), count_col = "counts", output_folder = tempfile("immundata-bulk-") ) tibble( n_records = bulk_idata |> count() |> pull(n), n_receptors = bulk_idata$receptors |> count() |> collect() |> pull(n), n_repertoires = nrow(bulk_idata$repertoires) ) # Expected result: # n_records n_receptors n_repertoires # 955 871 1 # Read multiple files and their sample information from a manifest manifest_path <- system.file( "extdata/tsv", "manifest.csv", package = "immundata" ) manifest <- read_manifest(manifest_path) manifest_idata <- read_repertoires( path = "<manifest>", manifest = manifest, schema = c("cdr3_aa", "v_call"), count_col = "counts", output_folder = tempfile("immundata-manifest-") ) manifest_idata$repertoires |> select(Therapy, Response, n_barcodes, n_receptors) |> arrange(Response) # Expected result: # Therapy Response n_barcodes n_receptors # ICI FR 4725 871 # CAR-T PR 4758 867 # Read paired TRA-TRB receptors from a small single-cell table paired_input <- tibble( cell_id = c("cell1", "cell1", "cell2", "cell2", "cell3"), locus = c("TRA", "TRB", "TRA", "TRB", "TRA"), v_call = c("TRAV1", "TRBV1", "TRAV1", "TRBV1", "TRAV2"), j_call = c("TRAJ1", "TRBJ1", "TRAJ1", "TRBJ1", "TRAJ2"), junction_aa = c("CAVA", "CASSB", "CAVA", "CASSB", "CAVC"), umi_count = c(10L, 8L, 12L, 9L, 7L) ) paired_file <- tempfile(fileext = ".tsv") readr::write_tsv(paired_input, paired_file) paired_idata <- read_repertoires( path = paired_file, schema = make_receptor_schema( features = c("v_call", "j_call", "junction_aa"), chains = c("TRA", "TRB") ), barcode_col = "cell_id", locus_col = "locus", umi_col = "umi_count", repertoire_schema = NULL, output_folder = tempfile("immundata-paired-") ) tibble( n_chains = paired_idata |> count() |> pull(n), n_cells = paired_idata |> collect() |> distinct(imd_barcode) |> nrow(), n_receptors = paired_idata$receptors |> count() |> collect() |> pull(n) ) # Expected result: # n_chains n_cells n_receptors # 4 2 1library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Read one bulk AIRR file and preserve its abundance column bulk_file <- system.file( "extdata/tsv", "sample_0_1k.tsv", package = "immundata" ) bulk_idata <- read_repertoires( path = bulk_file, schema = c("cdr3_aa", "v_call"), count_col = "counts", output_folder = tempfile("immundata-bulk-") ) tibble( n_records = bulk_idata |> count() |> pull(n), n_receptors = bulk_idata$receptors |> count() |> collect() |> pull(n), n_repertoires = nrow(bulk_idata$repertoires) ) # Expected result: # n_records n_receptors n_repertoires # 955 871 1 # Read multiple files and their sample information from a manifest manifest_path <- system.file( "extdata/tsv", "manifest.csv", package = "immundata" ) manifest <- read_manifest(manifest_path) manifest_idata <- read_repertoires( path = "<manifest>", manifest = manifest, schema = c("cdr3_aa", "v_call"), count_col = "counts", output_folder = tempfile("immundata-manifest-") ) manifest_idata$repertoires |> select(Therapy, Response, n_barcodes, n_receptors) |> arrange(Response) # Expected result: # Therapy Response n_barcodes n_receptors # ICI FR 4725 871 # CAR-T PR 4758 867 # Read paired TRA-TRB receptors from a small single-cell table paired_input <- tibble( cell_id = c("cell1", "cell1", "cell2", "cell2", "cell3"), locus = c("TRA", "TRB", "TRA", "TRB", "TRA"), v_call = c("TRAV1", "TRBV1", "TRAV1", "TRBV1", "TRAV2"), j_call = c("TRAJ1", "TRBJ1", "TRAJ1", "TRBJ1", "TRAJ2"), junction_aa = c("CAVA", "CASSB", "CAVA", "CASSB", "CAVC"), umi_count = c(10L, 8L, 12L, 9L, 7L) ) paired_file <- tempfile(fileext = ".tsv") readr::write_tsv(paired_input, paired_file) paired_idata <- read_repertoires( path = paired_file, schema = make_receptor_schema( features = c("v_call", "j_call", "junction_aa"), chains = c("TRA", "TRB") ), barcode_col = "cell_id", locus_col = "locus", umi_col = "umi_count", repertoire_schema = NULL, output_folder = tempfile("immundata-paired-") ) tibble( n_chains = paired_idata |> count() |> pull(n), n_cells = paired_idata |> collect() |> distinct(imd_barcode) |> nrow(), n_receptors = paired_idata$receptors |> count() |> collect() |> pull(n) ) # Expected result: # n_chains n_cells n_receptors # 4 2 1
Use rename_strata() to replace automatic stratum labels with names that are
clear in figures and result tables, such as "Control", "Treated", or
"Tumour tissue".
Use this function after agg_strata() when labels such as "Strata1" do not
describe the biological groups. The unit being changed is the stratum label.
Stratum membership and the repertoires, receptors, cells, and chains remain
unchanged.
The function returns a new ImmunData object. The original object is not changed.
rename_strata( idata, names, unnamed = c("error", "auto", "keep"), auto_prefix = "Strata" )rename_strata( idata, names, unnamed = c("error", "auto", "keep"), auto_prefix = "Strata" )
idata |
An ImmunData object with strata already created by
|
names |
A named character vector or a data frame. New labels matched to
Every new label must be non-empty and unique. |
unnamed |
A character string. What to do when |
auto_prefix |
A non-empty character string. Prefix used to generate
labels when |
The names of a named character vector are the stratum IDs, not the current
labels. Inspect idata$strata to find the ID for each biological group.
The mapping cannot contain unknown or repeated IDs, and the resulting labels must be unique across strata.
A new ImmunData object with the requested labels in its $strata
and $repertoires tables. All biological group assignments and repertoire
summaries are preserved.
The readable strata_name is stored in the repertoire and strata tables. The
underlying chain annotations keep only imd_strata_id, so renaming a stratum
does not rewrite or regroup chain-level data.
agg_strata(), agg_repertoires()
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Create treatment strata for the sample repertoires in the test data treatment_groups <- get_test_idata() |> agg_repertoires(c("Response", "Therapy")) |> agg_strata(schema = "Therapy") # Replace automatic labels with names suitable for a figure labeled_groups <- treatment_groups |> rename_strata( names = c("1" = "CAR-T arm", "2" = "ICI arm") ) labeled_groups$strata |> select(Therapy, imd_strata_id, strata_name) |> arrange(imd_strata_id) # Expected result: # Therapy imd_strata_id strata_name # CAR-T 1 CAR-T arm # ICI 2 ICI arm # Only the labels changed. Each sample repertoire remains in the same # treatment stratum.library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Create treatment strata for the sample repertoires in the test data treatment_groups <- get_test_idata() |> agg_repertoires(c("Response", "Therapy")) |> agg_strata(schema = "Therapy") # Replace automatic labels with names suitable for a figure labeled_groups <- treatment_groups |> rename_strata( names = c("1" = "CAR-T arm", "2" = "ICI arm") ) labeled_groups$strata |> select(Therapy, imd_strata_id, strata_name) |> arrange(imd_strata_id) # Expected result: # Therapy imd_strata_id strata_name # CAR-T 1 CAR-T arm # ICI 2 ICI arm # Only the labels changed. Each sample repertoire remains in the same # treatment stratum.
Save ImmunData to disk so you can close R and continue the work later (I cannot
believe it, but it works, I tried it). Use
write_immundata() after importing or transforming repertoire data, or when
you want a named snapshot before the next analysis step.
The unit saved is the complete ImmunData object. This includes retained chain rows, cell and receptor identifiers, repertoire and stratum definitions, and provenance. Saving does not add, remove, or change any biological unit.
write_immundata( idata, output_folder = NULL, tag = NULL, rehome = FALSE, compression = "zstd", compression_level = 9, verbose = getOption("immundata.verbose", TRUE) )write_immundata( idata, output_folder = NULL, tag = NULL, rehome = FALSE, compression = "zstd", compression_level = 9, verbose = getOption("immundata.verbose", TRUE) )
idata |
An ImmunData object you want to save. |
output_folder |
A character string or |
tag |
A character string or |
rehome |
A logical value. Whether an explicit |
compression |
A character string or |
compression_level |
A number or |
verbose |
A logical value. Whether to print progress and summary
messages. Defaults to |
Save to an explicit folder for a direct saved state, or use the object's home to create a versioned managed snapshot.
Invisibly returns a newly reopened, disk-backed ImmunData object
with provenance for the new save. The input idata remains unchanged.
Supply output_folder to save a standalone state in a specific directory.
This is useful when sharing a dataset or choosing its first project home.
If the directory already contains an ImmunData dataset, its
annotations.parquet and metadata.json are replaced.
Leave output_folder = NULL to create a managed snapshot. The function uses
the object's home path and writes the next version under
snapshots/<tag>/vNNN, for example snapshots/baseline/v001. Later writes
with the same tag create v002, v003, and so on; earlier versions remain
available. Use read_immundata() with tag and version to reopen one.
Every save receives a new snapshot identifier and appends a provenance event. The returned object records the new saved directory as its current path.
The retained chain-level annotation table is materialized as compressed
annotations.parquet. Materialization executes any pending lazy duckplyr
calculations. metadata.json serializes format and package versions,
receptor, repertoire, and stratum schemas, the small repertoire table, the
snapshot identifier, lineage events, and provenance paths.
Receptor and stratum views are not written as separate files; they can be reconstructed from the annotation table and metadata. This Parquet and JSON pair is an ImmunData-specific serialization, not an RDS file.
read_immundata() for continuing a saved analysis,
read_repertoires() for importing AIRR-seq files, ImmunData
library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Save a small immune-repertoire analysis idata <- get_test_idata() save_dir <- tempfile("saved-immundata-") saved_idata <- write_immundata(idata, save_dir) list.files(save_dir) # Expected result: the analysis is serialized as two files. # [1] "annotations.parquet" "metadata.json" # Continue the analysis from the saved files continued_idata <- read_immundata(save_dir) continued_idata |> collect() |> summarise( n_chains = n(), n_receptors = n_distinct(imd_receptor_id) ) # Expected result: all 1,902 chain rows and 1,668 receptors are restored. # n_chains n_receptors # 1902 1668 unlink(save_dir, recursive = TRUE)library(immundata) library(dplyr) options(immundata.verbose = FALSE) # Save a small immune-repertoire analysis idata <- get_test_idata() save_dir <- tempfile("saved-immundata-") saved_idata <- write_immundata(idata, save_dir) list.files(save_dir) # Expected result: the analysis is serialized as two files. # [1] "annotations.parquet" "metadata.json" # Continue the analysis from the saved files continued_idata <- read_immundata(save_dir) continued_idata |> collect() |> summarise( n_chains = n(), n_receptors = n_distinct(imd_receptor_id) ) # Expected result: all 1,902 chain rows and 1,668 receptors are restored. # n_chains n_receptors # 1902 1668 unlink(save_dir, recursive = TRUE)