Skip to content

Add external information to ImmunData

Source code

Description

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.

Usage

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")
)

Arguments

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 idata; values are the corresponding columns in annotations. For example, c(Response = “response_code”).
keep_repertoires Whether to preserve existing repertoire and strata summaries without recalculation. The default is TRUE. If FALSE, these summaries and their derived annotation columns are removed.
remove_limit Whether to allow an annotation table with 100 or more columns. The default is FALSE, which stops the operation for such tables. Set to TRUE only when the wide join is intentional.
conflicts How to handle new annotation columns whose names already exist in idata. “error”, the default, stops the operation. “replace” replaces existing columns that are not protected by ImmunData.
annot_col Name of the identifier column in annotations. For annotate_receptors() and annotate_chains(), the default is the standard ImmunData receptor or chain identifier. For annotate_barcodes(), the default “\ uses the row names of annotations. Supplying an explicit barcode column is usually clearer.

Details

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.

Value

A new ImmunData object containing the added annotation columns. Existing repertoire and strata summaries are preserved when keep_repertoires = TRUE.

Choose a function

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().

How matching works

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.

Annotation identifiers must be unique

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.

Existing annotation columns

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.

Repertoire and strata summaries

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.

Very wide annotation tables

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.

See Also

dplyr::left_join(), agg_repertoires(), filter_immundata(), mutate_immundata(), ImmunData

Examples

library("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)
#> # A tibble: 3 × 2
#>   imd_barcode cell_type 
#> * <chr>       <chr>     
#> 1 S1_1        CD8 T cell
#> 2 S1_2        CD4 T cell
#> 3 S1_3        <NA>
# 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)
#> # A tibble: 2 × 3
#>   imd_receptor_id cdr3_aa       antigen
#> *           <int> <chr>         <chr>  
#> 1             738 SVWTSGGNNEQF  CMV    
#> 2            1567 ASSLEMEGTGELF CMV
# 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)
#> # A tibble: 2 × 2
#>   Response response_label  
#> * <chr>    <chr>           
#> 1 FR       Full response   
#> 2 PR       Partial 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)
#> # A tibble: 2 × 4
#>   imd_repertoire_id cell_type  n_barcodes n_receptors
#> *             <int> <chr>           <dbl>       <int>
#> 1                 1 CD4 T cell          1           1
#> 2                 2 CD8 T cell          1           1
# Expected result:
#   imd_repertoire_id cell_type  n_barcodes n_receptors
#                   1 CD4 T cell          1           1
#                   2 CD8 T cell          1           1