Skip to contents


This vignette provides detailed examples for loading gene expression and V(D)J data into a single object. For the examples shown below, we use data for splenocytes from BL6 and MD4 mice collected using the 10X Genomics scRNA-seq platform. MD4 B cells are monoclonal and specifically bind hen egg lysozyme.


Basic usage

To load and parse V(D)J data, FASTQ files must first be processed using Cell Ranger. The data used for this vignette was processed using Cell Ranger v7.0. Cell Ranger generates several files that djvdj uses for the downstream analysis. This includes the outs/filtered_contig_annotations.csv file, which contains basic information about each chain and is required for the import_vdj() function.

The simplest use case for import_vdj() involves creating an object using data from a single run.

library(djvdj)
library(Seurat)
library(dplyr)

# Create Seurat object
data_dir <- system.file("extdata/splen", package = "djvdj")

so <- file.path(data_dir, "BL6_GEX/filtered_feature_bc_matrix") |>
  Read10X() |>
  CreateSeuratObject()

# Add V(D)J data to object
so_vdj <- so |>
  import_vdj(vdj_dir = file.path(data_dir, "BL6_BCR"))

import_vdj() adds a variety of per-chain metrics to the object meta.data. Information for each chain identified for the cell is separated by a semicolon. The separator used for storing and parsing per-chain V(D)J data can be specified using the sep argument included for most djvdj functions. NAs will be included for cells that lack V(D)J data.

so_vdj |>
  slot("meta.data") |>
  filter(n_chains > 1) |>
  head(2)
#>                       orig.ident nCount_RNA nFeature_RNA  clonotype_id
#> ACACCAAAGAATTGTG-1 SeuratProject         38           21 clonotype2492
#> ACACCGGCACAAGTAA-1 SeuratProject         80           30 clonotype3124
#>                    exact_subclonotype_id  chains n_chains
#> ACACCAAAGAATTGTG-1                     1 IGH;IGK        2
#> ACACCGGCACAAGTAA-1                     1 IGH;IGK        2
#>                                          cdr3
#> ACACCAAAGAATTGTG-1    CAHGSRDFDVW;CWQGTHFPQTF
#> ACACCGGCACAAGTAA-1 CARHEGYYEAMDYW;CQQGNTLPLTF
#>                                                                                         cdr3_nt
#> ACACCAAAGAATTGTG-1          TGTGCTCACGGTAGTCGAGACTTCGATGTCTGG;TGCTGGCAAGGTACACATTTTCCTCAGACGTTC
#> ACACCGGCACAAGTAA-1 TGTGCAAGACATGAGGGGTACTACGAGGCTATGGACTACTGG;TGCCAACAGGGTAATACGCTTCCTCTCACGTTC
#>                    cdr3_length cdr3_nt_length             v_gene
#> ACACCAAAGAATTGTG-1       11;11          33;33 IGHV1-75;IGKV1-135
#> ACACCGGCACAAGTAA-1       14;11          42;33 IGHV5-15;IGKV10-96
#>                       d_gene      j_gene    c_gene isotype  reads umis
#> ACACCAAAGAATTGTG-1 None;None IGHJ1;IGKJ1 IGHM;IGKC    IGHM 20;208 2;11
#> ACACCGGCACAAGTAA-1 None;None IGHJ4;IGKJ5 IGHM;IGKC    IGHM 80;174 6;12
#>                    productive full_length paired
#> ACACCAAAGAATTGTG-1  TRUE;TRUE   TRUE;TRUE   TRUE
#> ACACCGGCACAAGTAA-1  TRUE;TRUE   TRUE;TRUE   TRUE

To modify/filter/plot per-chain data, djvdj provides a range of functions that make it easy to parse and visualize this information. For detailed examples refer to the vignettes and documentation for the following functions: filter_vdj(), mutate_vdj(), summarize_vdj(), plot_histogram(), plot_scatter().


Loading multiple runs

When combining gene expression and V(D)J data from multiple runs into the same Seurat object, we must ensure that import_vdj() is able to match the cell barcodes from the two data types. The easiest way to do this is to load the gene expression and V(D)J samples in the same order.

If the V(D)J samples are not loaded in the same order as the gene expression data, the cell barcodes will not match and you will receive an error.

# Load GEX data
gex_dirs <- c(
  file.path(data_dir, "BL6_GEX/filtered_feature_bc_matrix"),
  file.path(data_dir, "MD4_GEX/filtered_feature_bc_matrix")
)

so <- gex_dirs |>
  Read10X() |>
  CreateSeuratObject()

# Load BCR data
# note that the BL6 and MD4 paths are in the same order as the gene
# expression data
vdj_dirs <- c(
  file.path(data_dir, "BL6_BCR"),
  file.path(data_dir, "MD4_BCR")
)

so_vdj <- so |>
  import_vdj(vdj_dir = vdj_dirs)

Another way to ensure the cell barcodes from the gene expression and V(D)J data are able to be matched is to specify cell barcode prefixes for each sample. For both the Seurat::Read10X() and import_vdj() functions this can be done by passing a named vector. In this scenario, the names will be added as prefixes for the cell barcodes.

# Load GEX data
gex_dirs <- c(
  BL6 = file.path(data_dir, "BL6_GEX/filtered_feature_bc_matrix"),
  MD4 = file.path(data_dir, "MD4_GEX/filtered_feature_bc_matrix")
)

so <- gex_dirs |>
  Read10X() |>
  CreateSeuratObject()

# Load BCR data
# note that the samples are not in the same order as the gene expression data,
# but this is okay since cell prefixes are provided as names for the
# input vector
vdj_dirs <- c(
  MD4 = file.path(data_dir, "MD4_BCR"),
  BL6 = file.path(data_dir, "BL6_BCR")
)

so_vdj <- so |>
  import_vdj(vdj_dir = vdj_dirs)


Defining clonotypes

When results from multiple Cell Ranger runs are added to the object, the clonotype IDs will not match, i.e. clonotype1 will not be the same for all samples. To allow clonotypes to be directly compared between multiple samples, the clonotypes can be recalculated using the define_clonotypes argument. This argument will assign new clonotype IDs using information available for each chain. There are three options to specify how this step is performed:

  • ‘cdr3aa’: use only the CDR3 amino acid sequence
  • ‘cdr3nt’: use only the CDR3 nucleotide sequence
  • ‘cdr3_gene’: use both the CDR3 nucleotide sequence and the combination of all V(D)J genes identified for the cell
so_vdj <- so |>
  import_vdj(
    vdj_dir = vdj_dirs,
    define_clonotypes = "cdr3_gene"
  )

The clonotype IDs can also be adjusted for multiple samples using the aggregate function available with Cell Ranger. To load aggregated output files just pass the cellranger aggr output directory to the aggr_dir argument. To correctly match cell barcodes from aggregated V(D)J data with the gene expression data, the gene expression samples must be loaded into the object in the same order the samples were listed in the cellranger aggr config file.


Filtering chains

The import_vdj() function has several arguments that can be used to perform basic filtering for each chain. The filter_chains argument will only include chains with at least one productive and full length contig. However, it should be noted that in recent versions of Cell Ranger the output files are already filtered based on this criteria, so this argument is only relevant when loading data processed with earlier versions such as Cell Ranger v3.0.

The filter_paired argument will only include clonotypes with paired chains. For TCR data, this means each clonotype must have at least one TRA and TRB chain. For BCR data each clonotype must have at least one IGH chain and at least one IGK or IGL chain. It should be noted that if a clonotype has multiple chains of the same type, it will still be included, e.g. TRA;TRB;TRB or IGH;IGK;IGK will still be included. Clonotypes that include more than two chains can be filtered using filter_vdj().

vdj_dirs <- c(
  MD4 = file.path(data_dir, "MD4_BCR"),
  BL6 = file.path(data_dir, "BL6_BCR")
)

so_vdj <- so |>
  import_vdj(
    vdj_dir = vdj_dirs,
    filter_chains = TRUE,
    filter_paired = TRUE
  )

# To only include clonotypes with exactly 2 chains
so_vdj <- so_vdj |>
  filter_vdj(n_chains == 2)


Loading mutation information

Mutation information for each chain can be parsed using two additional output files from Cell Ranger:

  • outs/concat_ref.bam: this file contains alignment information used to quantify insertions, deletions, and mismatches for each chain.
  • outs/airr_rearrangement.tsv: this file contains coordinates for each V(D)J gene segment and is used to quantify mutations for each segment and/or junction.
vdj_dirs <- c(
  MD4 = file.path(data_dir, "MD4_BCR"),
  BL6 = file.path(data_dir, "BL6_BCR")
)

so_vdj <- so |>
  import_vdj(
    vdj_dir = vdj_dirs,
    include_mutations = TRUE
  )

The additional columns added to the meta.data will include the number of insertions, deletions, and mismatches (ending in ‘ins’, ‘del’, or ‘mis’) for each V(D)J segment (prefixed with ‘v’, ‘d’, ‘j’, or ‘c’). Columns containing junction information will be prefixed with either ‘vd’ or ‘dj’. Columns ending in ‘freq’ show the event frequency which is calculated as the number of events divided by the length of the region.

so_vdj |>
  slot("meta.data") |>
  filter(n_chains > 1) |>
  head(2)
#>                        orig.ident nCount_RNA nFeature_RNA  clonotype_id
#> BL6_ACACCAAAGAATTGTG-1        BL6         38           21 clonotype2492
#> BL6_ACACCGGCACAAGTAA-1        BL6         80           30 clonotype3124
#>                        exact_subclonotype_id  chains n_chains
#> BL6_ACACCAAAGAATTGTG-1                     1 IGH;IGK        2
#> BL6_ACACCGGCACAAGTAA-1                     1 IGH;IGK        2
#>                                              cdr3
#> BL6_ACACCAAAGAATTGTG-1    CAHGSRDFDVW;CWQGTHFPQTF
#> BL6_ACACCGGCACAAGTAA-1 CARHEGYYEAMDYW;CQQGNTLPLTF
#>                                                                                             cdr3_nt
#> BL6_ACACCAAAGAATTGTG-1          TGTGCTCACGGTAGTCGAGACTTCGATGTCTGG;TGCTGGCAAGGTACACATTTTCCTCAGACGTTC
#> BL6_ACACCGGCACAAGTAA-1 TGTGCAAGACATGAGGGGTACTACGAGGCTATGGACTACTGG;TGCCAACAGGGTAATACGCTTCCTCTCACGTTC
#>                        cdr3_length cdr3_nt_length             v_gene
#> BL6_ACACCAAAGAATTGTG-1       11;11          33;33 IGHV1-75;IGKV1-135
#> BL6_ACACCGGCACAAGTAA-1       14;11          42;33 IGHV5-15;IGKV10-96
#>                           d_gene      j_gene    c_gene isotype  reads
#> BL6_ACACCAAAGAATTGTG-1 None;None IGHJ1;IGKJ1 IGHM;IGKC    IGHM 20;208
#> BL6_ACACCGGCACAAGTAA-1 None;None IGHJ4;IGKJ5 IGHM;IGKC    IGHM 80;174
#>                        umis productive full_length paired v_ins v_del
#> BL6_ACACCAAAGAATTGTG-1 2;11  TRUE;TRUE   TRUE;TRUE   TRUE   2;0   0;0
#> BL6_ACACCGGCACAAGTAA-1 6;12  TRUE;TRUE   TRUE;TRUE   TRUE   0;0   0;3
#>                        v_mis d_ins d_del d_mis j_ins j_del j_mis c_ins
#> BL6_ACACCAAAGAATTGTG-1   1;0   0;0   0;0   0;0   0;0   0;3   0;0   0;0
#> BL6_ACACCGGCACAAGTAA-1   0;0   0;0   0;0   0;0   0;0   0;3   3;0   0;0
#>                        c_del c_mis all_ins all_del all_mis vd_ins vd_del
#> BL6_ACACCAAAGAATTGTG-1   0;0   0;0     2;0     0;3     1;0    0;0    0;0
#> BL6_ACACCGGCACAAGTAA-1   0;0   0;0     0;0     0;6     3;0    0;0    0;0
#>                        dj_ins dj_del v_mis_freq d_mis_freq j_mis_freq
#> BL6_ACACCAAAGAATTGTG-1    0;0    0;0 0.002525;0        0;0        0;0
#> BL6_ACACCGGCACAAGTAA-1    0;0    0;0        0;0        0;0 0.007371;0
#>                        c_mis_freq all_mis_freq
#> BL6_ACACCAAAGAATTGTG-1        0;0   0.002525;0
#> BL6_ACACCGGCACAAGTAA-1        0;0   0.007371;0


Loading additional sequence information

By default the only sequence information loaded by import_vdj() will be for the CDR3 region. Newer versions of Cell Ranger will include additional sequences in the filtered_contig_annotations.csv file. This includes the FWR1, CDR1, FWR2, CDR2, FWR3, and FWR4 regions. These additional sequences can be loaded using the data_cols argument.

so_vdj <- so |>
  import_vdj(
    vdj_dir   = vdj_dirs,
    data_cols = c("cdr1", "cdr1_nt", "cdr2", "cdr2_nt")
  )

so_vdj |>
  slot("meta.data") |>
  head(3)
#>                        orig.ident nCount_RNA nFeature_RNA clonotype_id
#> BL6_AAACGGGGTTCTGTTT-1        BL6        202           25         <NA>
#> BL6_AAAGATGCAACAACCT-1        BL6         42           20   clonotype7
#> BL6_AACGTTGCACGACTCG-1        BL6         32           19  clonotype32
#>                        exact_subclonotype_id chains n_chains
#> BL6_AAACGGGGTTCTGTTT-1                    NA   <NA>       NA
#> BL6_AAAGATGCAACAACCT-1                     1    IGK        1
#> BL6_AACGTTGCACGACTCG-1                     1    IGK        1
#>                                    cdr1
#> BL6_AAACGGGGTTCTGTTT-1             <NA>
#> BL6_AAAGATGCAACAACCT-1 RSSQSIVHSNGNTYLE
#> BL6_AACGTTGCACGACTCG-1      KASQNVGTNVA
#>                                                                 cdr1_nt
#> BL6_AAACGGGGTTCTGTTT-1                                             <NA>
#> BL6_AAAGATGCAACAACCT-1 AGATCTAGTCAGAGCATTGTACATAGTAATGGAAACACCTATTTAGAA
#> BL6_AACGTTGCACGACTCG-1                AAGGCCAGTCAGAATGTGGGTACTAATGTAGCC
#>                           cdr2               cdr2_nt        cdr3
#> BL6_AAACGGGGTTCTGTTT-1    <NA>                  <NA>        <NA>
#> BL6_AAAGATGCAACAACCT-1 KVSNRFS AAAGTTTCCAACCGATTTTCT CFQGSHVPWTF
#> BL6_AACGTTGCACGACTCG-1 SASYRYS TCGGCATCCTACCGGTACAGT CQQYNSYPLTF
#>                                                  cdr3_nt cdr1_length
#> BL6_AAACGGGGTTCTGTTT-1                              <NA>        <NA>
#> BL6_AAAGATGCAACAACCT-1 TGCTTTCAAGGTTCACATGTTCCGTGGACGTTC          16
#> BL6_AACGTTGCACGACTCG-1 TGTCAGCAATATAACAGCTATCCTCTCACGTTC          11
#>                        cdr1_nt_length cdr2_length cdr2_nt_length
#> BL6_AAACGGGGTTCTGTTT-1           <NA>        <NA>           <NA>
#> BL6_AAAGATGCAACAACCT-1             48           7             21
#> BL6_AACGTTGCACGACTCG-1             33           7             21
#>                        cdr3_length cdr3_nt_length    v_gene d_gene j_gene
#> BL6_AAACGGGGTTCTGTTT-1        <NA>           <NA>      <NA>   <NA>   <NA>
#> BL6_AAAGATGCAACAACCT-1          11             33 IGKV1-117   None  IGKJ1
#> BL6_AACGTTGCACGACTCG-1          11             33  IGKV6-15   None  IGKJ5
#>                        c_gene isotype reads umis productive full_length
#> BL6_AAACGGGGTTCTGTTT-1   <NA>    <NA>  <NA> <NA>       <NA>        <NA>
#> BL6_AAAGATGCAACAACCT-1   IGKC    None   352   21       TRUE        TRUE
#> BL6_AACGTTGCACGACTCG-1   IGKC    None   342   18       TRUE        TRUE
#>                        paired
#> BL6_AAACGGGGTTCTGTTT-1     NA
#> BL6_AAAGATGCAACAACCT-1  FALSE
#> BL6_AACGTTGCACGACTCG-1  FALSE


Loading TCR and BCR data

To add both BCR and TCR data to the object, run import_vdj() separately for each data type. To distinguish between columns containing BCR or TCR data, use the prefix argument to add unique column names.

bcr_dirs <- c(
  MD4 = file.path(data_dir, "MD4_BCR"),
  BL6 = file.path(data_dir, "BL6_BCR")
)

tcr_dirs <- c(
  MD4 = file.path(data_dir, "MD4_TCR"),
  BL6 = file.path(data_dir, "BL6_TCR")
)

so_vdj <- so |>
  import_vdj(bcr_dirs, prefix = "bcr_") |>
  import_vdj(tcr_dirs, prefix = "tcr_")

This results in two sets of new columns being added to the meta.data. When performing downstream analysis using other djvdj functions, be sure to specify the correct columns, i.e. ‘bcr_clonotype_id’ or ‘tcr_clonotype_id’.

so_vdj |>
  slot("meta.data") |>
  head(3)
#>                        orig.ident nCount_RNA nFeature_RNA
#> BL6_AAACGGGGTTCTGTTT-1        BL6        202           25
#> BL6_AAAGATGCAACAACCT-1        BL6         42           20
#> BL6_AACGTTGCACGACTCG-1        BL6         32           19
#>                        bcr_clonotype_id bcr_exact_subclonotype_id
#> BL6_AAACGGGGTTCTGTTT-1             <NA>                        NA
#> BL6_AAAGATGCAACAACCT-1       clonotype7                         1
#> BL6_AACGTTGCACGACTCG-1      clonotype32                         1
#>                        bcr_chains bcr_n_chains    bcr_cdr3
#> BL6_AAACGGGGTTCTGTTT-1       <NA>           NA        <NA>
#> BL6_AAAGATGCAACAACCT-1        IGK            1 CFQGSHVPWTF
#> BL6_AACGTTGCACGACTCG-1        IGK            1 CQQYNSYPLTF
#>                                              bcr_cdr3_nt bcr_cdr3_length
#> BL6_AAACGGGGTTCTGTTT-1                              <NA>            <NA>
#> BL6_AAAGATGCAACAACCT-1 TGCTTTCAAGGTTCACATGTTCCGTGGACGTTC              11
#> BL6_AACGTTGCACGACTCG-1 TGTCAGCAATATAACAGCTATCCTCTCACGTTC              11
#>                        bcr_cdr3_nt_length bcr_v_gene bcr_d_gene
#> BL6_AAACGGGGTTCTGTTT-1               <NA>       <NA>       <NA>
#> BL6_AAAGATGCAACAACCT-1                 33  IGKV1-117       None
#> BL6_AACGTTGCACGACTCG-1                 33   IGKV6-15       None
#>                        bcr_j_gene bcr_c_gene bcr_isotype bcr_reads
#> BL6_AAACGGGGTTCTGTTT-1       <NA>       <NA>        <NA>      <NA>
#> BL6_AAAGATGCAACAACCT-1      IGKJ1       IGKC        None       352
#> BL6_AACGTTGCACGACTCG-1      IGKJ5       IGKC        None       342
#>                        bcr_umis bcr_productive bcr_full_length bcr_paired
#> BL6_AAACGGGGTTCTGTTT-1     <NA>           <NA>            <NA>         NA
#> BL6_AAAGATGCAACAACCT-1       21           TRUE            TRUE      FALSE
#> BL6_AACGTTGCACGACTCG-1       18           TRUE            TRUE      FALSE
#>                        tcr_clonotype_id tcr_chains tcr_n_chains tcr_cdr3
#> BL6_AAACGGGGTTCTGTTT-1             <NA>       <NA>           NA     <NA>
#> BL6_AAAGATGCAACAACCT-1             <NA>       <NA>           NA     <NA>
#> BL6_AACGTTGCACGACTCG-1             <NA>       <NA>           NA     <NA>
#>                        tcr_cdr3_nt tcr_cdr3_length tcr_cdr3_nt_length
#> BL6_AAACGGGGTTCTGTTT-1        <NA>            <NA>               <NA>
#> BL6_AAAGATGCAACAACCT-1        <NA>            <NA>               <NA>
#> BL6_AACGTTGCACGACTCG-1        <NA>            <NA>               <NA>
#>                        tcr_v_gene tcr_d_gene tcr_j_gene tcr_c_gene
#> BL6_AAACGGGGTTCTGTTT-1       <NA>       <NA>       <NA>       <NA>
#> BL6_AAAGATGCAACAACCT-1       <NA>       <NA>       <NA>       <NA>
#> BL6_AACGTTGCACGACTCG-1       <NA>       <NA>       <NA>       <NA>
#>                        tcr_reads tcr_umis tcr_productive tcr_full_length
#> BL6_AAACGGGGTTCTGTTT-1      <NA>     <NA>           <NA>            <NA>
#> BL6_AAAGATGCAACAACCT-1      <NA>     <NA>           <NA>            <NA>
#> BL6_AACGTTGCACGACTCG-1      <NA>     <NA>           <NA>            <NA>
#>                        tcr_paired
#> BL6_AAACGGGGTTCTGTTT-1         NA
#> BL6_AAAGATGCAACAACCT-1         NA
#> BL6_AACGTTGCACGACTCG-1         NA


Other object types

In addition to Seurat objects, djvdj also works with SingleCellExperiment objects and with data.frames. If no input object is provided to import_vdj(), a data.frame containing V(D)J information will be returned. This data.frame can be used with other djvdj functions to perform further downstream analysis.

vdj_dirs <- c(
  MD4 = file.path(data_dir, "MD4_BCR"),
  BL6 = file.path(data_dir, "BL6_BCR")
)

# This will load V(D)J data and return a data.frame
df_vdj <- import_vdj(vdj_dir = vdj_dirs)


Session info

#> R version 4.3.1 (2023-06-16)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Ubuntu 22.04.3 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] dplyr_1.1.3        SeuratObject_4.1.4 Seurat_4.4.0      
#> [4] djvdj_0.1.0       
#> 
#> loaded via a namespace (and not attached):
#>   [1] RColorBrewer_1.1-3      jsonlite_1.8.7         
#>   [3] magrittr_2.0.3          spatstat.utils_3.0-3   
#>   [5] rmarkdown_2.25          zlibbioc_1.46.0        
#>   [7] fs_1.6.3                ragg_1.2.6             
#>   [9] vctrs_0.6.4             ROCR_1.0-11            
#>  [11] Rsamtools_2.16.0        memoise_2.0.1          
#>  [13] spatstat.explore_3.2-5  RCurl_1.98-1.12        
#>  [15] htmltools_0.5.6.1       sass_0.4.7             
#>  [17] sctransform_0.4.1       parallelly_1.36.0      
#>  [19] KernSmooth_2.23-21      bslib_0.5.1            
#>  [21] htmlwidgets_1.6.2       desc_1.4.2             
#>  [23] ica_1.0-3               plyr_1.8.9             
#>  [25] plotly_4.10.3           zoo_1.8-12             
#>  [27] cachem_1.0.8            igraph_1.5.1           
#>  [29] mime_0.12               lifecycle_1.0.3        
#>  [31] pkgconfig_2.0.3         Matrix_1.6-1.1         
#>  [33] R6_2.5.1                fastmap_1.1.1          
#>  [35] GenomeInfoDbData_1.2.10 fitdistrplus_1.1-11    
#>  [37] future_1.33.0           shiny_1.7.5.1          
#>  [39] digest_0.6.33           colorspace_2.1-0       
#>  [41] S4Vectors_0.38.2        patchwork_1.1.3        
#>  [43] rprojroot_2.0.3         tensor_1.5             
#>  [45] irlba_2.3.5.1           GenomicRanges_1.52.1   
#>  [47] textshaping_0.3.7       progressr_0.14.0       
#>  [49] fansi_1.0.5             spatstat.sparse_3.0-2  
#>  [51] httr_1.4.7              polyclip_1.10-6        
#>  [53] abind_1.4-5             compiler_4.3.1         
#>  [55] withr_2.5.1             bit64_4.0.5            
#>  [57] BiocParallel_1.34.2     MASS_7.3-60            
#>  [59] tools_4.3.1             lmtest_0.9-40          
#>  [61] httpuv_1.6.12           future.apply_1.11.0    
#>  [63] goftest_1.2-3           glue_1.6.2             
#>  [65] nlme_3.1-162            promises_1.2.1         
#>  [67] grid_4.3.1              Rtsne_0.16             
#>  [69] cluster_2.1.4           reshape2_1.4.4         
#>  [71] generics_0.1.3          gtable_0.3.4           
#>  [73] spatstat.data_3.0-1     tzdb_0.4.0             
#>  [75] tidyr_1.3.0             data.table_1.14.8      
#>  [77] hms_1.1.3               XVector_0.40.0         
#>  [79] sp_2.1-1                utf8_1.2.4             
#>  [81] BiocGenerics_0.46.0     spatstat.geom_3.2-7    
#>  [83] RcppAnnoy_0.0.21        ggrepel_0.9.4          
#>  [85] RANN_2.6.1              pillar_1.9.0           
#>  [87] stringr_1.5.0           vroom_1.6.4            
#>  [89] later_1.3.1             splines_4.3.1          
#>  [91] lattice_0.21-8          bit_4.0.5              
#>  [93] survival_3.5-5          deldir_1.0-9           
#>  [95] tidyselect_1.2.0        Biostrings_2.68.1      
#>  [97] miniUI_0.1.1.1          pbapply_1.7-2          
#>  [99] knitr_1.44              gridExtra_2.3          
#> [101] IRanges_2.34.1          scattermore_1.2        
#> [103] stats4_4.3.1            xfun_0.40              
#> [105] matrixStats_1.0.0       stringi_1.7.12         
#> [107] lazyeval_0.2.2          yaml_2.3.7             
#> [109] evaluate_0.22           codetools_0.2-19       
#> [111] tibble_3.2.1            cli_3.6.1              
#> [113] uwot_0.1.16             xtable_1.8-4           
#> [115] reticulate_1.34.0       systemfonts_1.0.5      
#> [117] munsell_0.5.0           jquerylib_0.1.4        
#> [119] GenomeInfoDb_1.36.4     Rcpp_1.0.11            
#> [121] globals_0.16.2          spatstat.random_3.2-1  
#> [123] png_0.1-8               parallel_4.3.1         
#> [125] ellipsis_0.3.2          pkgdown_2.0.7          
#> [127] ggplot2_3.4.4           readr_2.1.4            
#> [129] bitops_1.0-7            listenv_0.9.0          
#> [131] viridisLite_0.4.2       scales_1.2.1           
#> [133] ggridges_0.5.4          crayon_1.5.2           
#> [135] leiden_0.4.3            purrr_1.0.2            
#> [137] rlang_1.1.1             cowplot_1.1.1