This vignette walks through the basic workflow of the ECLIPSE package.
Traditional analysis of single-cell TCR sequencing (scTCR-seq) data defines T cell clones based on the paired CDR3 sequences of the TCRα and TCRβ chains. However, as >50% of cells in some samples possess only 1 or >2 TCR chains, this approach is ill-suited to interpret the clonotypes of a substantial proportion of cells.
ECLIPSE builds on VDJdive
to address this limitation of standard scTCR-seq analysis. By analyzing
chain pairings across a sample, it is able to resolve clonal ambiguity,
allowing for the prediction of chains lost to dropout and the
deciphering of true biological expression of extra TCR chains from
technical artifacts. With less missing data and larger clone sizes,
users are then able to make best use of their scTCR-seq data. The
ECLIPSE workflow is also quite streamlined, typically utilizing just 2
core functions (ECLIPSE –>
tcrArtifactDetect). After completing these two functions,
users are ready for convenient downstream analysis with the scRepertoire
package.
ECLIPSE has two main inputs. The first is all or filtered contig
files that can be generated by running cellranger vdj or
cellranger multi on scTCR-seq FASTQ files. The second is a
Seurat object containing the cells in the contig files.
Alternatively, users may use SingleCellExperiment objects
(see the supplementary information section for details).
ECLIPSE has similarities with other scTCR-seq analysis packages, but
it also has many differences. Like many other packages, it defines
individual TCR chains by the CDR3 amino acid sequence and uses contig
files from cellranger for its input. However, in contrast
to other packages, for cells that don’t have a clear, unambiguous
clonotype (i.e. 1 TCRα and 1 TCRβ), ECLIPSE examines chain pairings from
other cells in the same sample to predict the most likely clonotype.
Conventional packages (such as scRepertoire)
would instead filter the cell out, leave the cell with missing chains,
or only select the highest expressed chain of each type.
Another core distinction of ECLIPSE from other packages is it is specifically designed to annotate clones with 3 TCR chains and discern true biological expression of 3 TCR chains from sequencing artifacts such as doublets or TCR contamination. Standard packages either do not allow clones to have 3 TCR chains or do not distinguish true biological expression from sequencing artifacts.
ECLIPSE shares some features with another Bioconductor scTCR-seq
analysis package, VDJdive.
VDJdive
implemented the EM algorithm to predict the clonotypes of cells with
ambiguous clonotypes. ECLIPSE uses this implementation; however, there
are some key differences between the packages. First, ECLIPSE utilizes
custom contig filtering in order to preserve as many TCR chains as
possible, while VDJdive
uses the filtered contigs from cellranger. ECLIPSE has hard
assignment of cells to clonotypes (i.e. each cell is assigned a single
clonotype), whereas VDJdive
has soft assignment (i.e. 1 cell can be split into multiple different
clonotypes). Lastly, ECLIPSE allows clones to possess 2 or 3 TCR chains
and integrates with scRepertoire
for downstream analysis and visualization, while VDJdive
permits clones to have only 2 TCR chains and has its own
visualization/analysis functionalities. Other differences exist, but
these are the most important ones to understand.
We need to first load two packages to run the vignette:
library(tidyverse) # needed for data wrangling (or use base R instead)
library(scRepertoire) # needed for downstream analysisPlease use the following code to download ECLIPSE from
Bioconductor and load it:
if (!require("BiocManager", quietly = TRUE))
install.packages("BiocManager")
BiocManager::install("ECLIPSE")Alternatively, use the following code to download the devel version
of ECLIPSE from GitHub and load it:
ECLIPSE includes a small test dataset of 1,000 cells from 2 patients (D3 and D5) with COVID-19 (derived from Zhao et al., 2021). Each patient has two samples: one from peripheral blood and one from matched bronchoalveolar lavage fluid (BALF) from the lung. To load the dataset in, we can use the following code:
ECLIPSE2 main inputs are required for ECLIPSE: a Seurat object
that contains scRNA-seq data and contig files from
cellranger vdj/multi (preferably
all_contig_annotations.csv files).
The first input is contig files from cellranger multi or
cellranger vdj, specifically
all_contig_annotations.csv or
filtered_contig_annotations.csv files. We strongly
recommend using all_contig_annotations files as the filtered
contig files remove some biologically true contigs (ex. TCRα chains that
use the TRDV1 gene segment, see here) as well
as non-productive contigs. While these non-productive chains may not
influence T cell antigen specificity, their presence in the contig files
allows for enhanced fidelity of clonal tracking by
ECLIPSE.
To feed the contig files into ECLIPSE, there are two
options:
For this option, a vector of folder paths that contain contig files
is needed. ECLIPSE will find the contig files within the
folders you provide, however it requires that each folder only contains
1 set of contig files (i.e. you cannot put the contig files from each
sample into 1 folder). In order to create a vector of folder paths, we
can use the list.dirs() function:
# Finding where the vignette data for ECLIPSE is stored
vignette_dir <- system.file("extdata/vignette", package = "ECLIPSE")
# Generates the vector of folder paths and excludes the current directory
tcr_folder_paths <- list.dirs(path = vignette_dir, full.names = TRUE)[-1]
print(tcr_folder_paths)
#> [1] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D3B"
#> [2] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D3L"
#> [3] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D5B"
#> [4] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D5L"The second option also requires a vector of paths, but instead you
specify the full path to the contig file. This option would be better if
you have many contig files from different samples in one folder. To make
the vector, here we can use list.files():
# Generates the vector of file paths
contig_paths <- list.files(path = vignette_dir,
pattern = "all_contig",
recursive = TRUE,
full.names = TRUE)
contig_paths
#> [1] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D3B/all_contig_annotations.csv.gz"
#> [2] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D3L/all_contig_annotations.csv.gz"
#> [3] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D5B/all_contig_annotations.csv.gz"
#> [4] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D5L/all_contig_annotations.csv.gz"4 arguments of ECLIPSE must be provided column names
from the Seurat meta.data: original_barcode,
batch, donor, and group. We will
create these columns now.
original_barcodeThis column must contain the original 10X barcode for each cell.
Because many scRNA-seq analysis pipelines add prefixes/suffixes to cell
barcodes, this column is required so that the TCR contigs (which contain
the original 10X barcodes) can be matched to their respective cell in
the Seurat object. Typically, barcodes will end with -1 and have no
prefixes, but this may not always be true, such as if
cellranger aggr was used. Always check your raw contig and
barcodes.tsv files to be sure!
For our dataset, the raw contig files have cell barcode suffixes of -1 with no prefix, so let’s look at the barcodes in our Seurat object to see if they will match:
# Existing cell barcodes. They aren't the original!
head(rownames(vignette_object[[]]))
#> [1] "Sar3B_ACCAGTAGTAATCACC-1" "Sar3B_GAGGTGAGTGACCAAG-1"
#> [3] "Sar3B_CAGTAACCACCTCGGA-1" "Sar3B_CCTTCGACAGAAGCAC-1"
#> [5] "Sar3B_ACTTGTTCAAGGTTCT-1" "Sar3B_AATCCAGTCGAGAACG-1"As you can see, they do not match. Let’s remove the prefix and make a
new column called cell_barcode that we can pass to the
original_barcode argument of ECLIPSE:
# Making a column with these barcodes where there is no prefix
vignette_object$cell_barcode <- str_replace(rownames(vignette_object[[]]),
pattern = "^.*_",
replacement = "")
head(vignette_object$cell_barcode) %>% as.vector()
#> [1] "ACCAGTAGTAATCACC-1" "GAGGTGAGTGACCAAG-1" "CAGTAACCACCTCGGA-1"
#> [4] "CCTTCGACAGAAGCAC-1" "ACTTGTTCAAGGTTCT-1" "AATCCAGTCGAGAACG-1"batchThis column tells ECLIPSE which contig file holds the
TCR information for each cell in the Seurat object. The position of the
contig file in the vector must be provided here (ex. 2, but not
“2”).
# Looking at the vector of file_paths
contig_paths
#> [1] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D3B/all_contig_annotations.csv.gz"
#> [2] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D3L/all_contig_annotations.csv.gz"
#> [3] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D5B/all_contig_annotations.csv.gz"
#> [4] "/tmp/Rtmp0ERlbs/Rinst1ea847c3d1f8/ECLIPSE/extdata/vignette/D5L/all_contig_annotations.csv.gz"For example, contig_paths[2] holds a path for the TCR
contigs for cells from the BALF of patient D3, so any cell from that
sample must have 2 (numeric) but not “2” (character) in this new column.
We can make this column, here called index, as such:
# Since normal tissue from sample 15 is first (S15N), it is 1.
# Tumor tissue from 15 is next (S15T), so it is 2, etc...
vignette_object[[]] <- vignette_object[[]] %>%
mutate(index = case_when(
Sample == "D3B" ~ 1,
Sample == "D3L" ~ 2,
Sample == "D5B" ~ 3,
Sample == "D5L" ~ 4))
# Checking the column
table(vignette_object$index)
#>
#> 1 2 3 4
#> 250 250 250 250donorNext, we need to tell ECLIPSE which donor (or mouse)
each cell was from. As ECLIPSE calls clonotypes by
utilizing chain pairing across a repertoire, the more cells that are
available, the more accurate this prediction will be (regardless of
time/condition/treatment). For this reason, a column is required to
provide the donor/mouse. Even if we want to compare D3 blood vs. BALF
and D5 blood vs. BALF downstream, here we must provide D3 and D5.
Fortunately, this column is already created for us, and it should be
already present in most real-world uses. Let’s look at this column,
called donor:
groupLastly, we need to tell ECLIPSE how we want the final
clonal statistics (e.g. clone cell count, frequency, etc..) to be
calculated after clones are called. For our example, either D5/D3 or
D3B/D3L/D5B/D5L would be equally valid. If a clone is only 1 cell, then
if we provide the donor, the frequency will be 1/500 vs. if we provide
the sample, then it will be 1/250. Please note that this column
can and often will be the same as the column passed to
donor. Let’s choose by sample here, which again is already
created for us through the column Sample (and typically
will be created in most real cases):
ECLIPSE functionNow we are ready to run the ECLIPSE function to analyze
our scTCR-seq data. Briefly, this function does the following:
findThreeChainClones to find clones with
biological expression of 3 total TCR chains and modifying the contigs to
address thisCTaa). If it is highly likely the cell is in 1 clone, then
that clone is used. If no clonotype is predicted with high confidence,
then clonal prediction is not performed, and the final clonotype is
instead determined by scRepertoire::combineTCR() using
parameters passed to ECLIPSE from the
combineTcrArgs argument (see supplemental information
section)For a full list of columns added by ECLIPSE and their description, please refer to the supplemental information section.
ECLIPSETo run ECLIPSE, there are two options, depending on
whether we generated a vector of folders containing contigs or contig
file paths. If we want to use a vector of folder paths, we can use the
following code:
# Option 1: Running with paths of folders containing contigs
object_after_ECLIPSE <- ECLIPSE(contig_type = "all",
contig_folder_paths = tcr_folder_paths,
seurat_object = vignette_object,
batch = "index",
donor = "donor",
group = "Sample",
original_barcode = "cell_barcode",
verbose = FALSE,
format = "None")Note that we use the contig_folder_paths argument, omit
the contig_file_paths argument, and
contig_type = "all". Together, this tells
ECLIPSE to search for
all_contig_annotations.csv files in the folders that we
provide. Additionally, this dataset stores missing data as
"None", so we use the format = "None"
argument. Newer datasets typically store missing data as
"", so this argument should usually be omitted.
If we want to provide TCR contig file paths, we can use the
contig_file_paths argument, omit the
contig_folder_paths argument, and
contig_type = "manual":
# Option 2: Running with contig paths
object_after_ECLIPSE <- ECLIPSE(contig_type = "manual",
contig_file_paths = contig_paths,
seurat_object = vignette_object,
batch = "index",
donor = "donor",
group = "Sample",
original_barcode = "cell_barcode",
format = "None")
#>
#> Reading TCR contig data in...
#> QC Filtering:
#> - 2883 initial contigs found before filtering
#> - 2883 contigs were from true cells (coming from 1000 cells)
#> - 24 contigs were removed for not being high confidence as defined by Cell Ranger
#> - 98 orphan contigs were found with no observed pairing. Cells containing these contigs are unlikely to have a clonotype predicted
#> - 554 contigs were removed for other reasons (ex. irregular CDR3, TCRγ/δ contig, duplicated, etc...)
#> - After all filtering, 2305 contigs from 1000 true cells remained
#>
#> Finding clones with extra chains...
#> Running donor 1/2 (D3):
#> 2 clones with an extra TCRα chain were detected, comprising 2% of cells (10 total cells)
#> 1 clones with an extra TCRβ chain were detected, comprising 8.4% of cells (42 total cells)
#>
#> Running donor 2/2 (D5):
#> 2 clones with an extra TCRα chain were detected, comprising 4.4% of cells (22 total cells)
#> 1 clones with an extra TCRβ chain were detected, comprising 1% of cells (5 total cells)
#>
#> Running VDJdive::clonoStats() for clone EM assignment...
#> Extracting results from VDJdive...
#> Finalizing clonal assignments...#> 21 cells were detected in clones that appear to be MAIT or iNKT cells
#> 67.6% of cells in the Seurat object were assigned a clonotype using high confidence prediction from VDJdive::clonoStats()
#> 100% of cells in the Seurat object were assigned a clonotype in the end (either from prediction or traditional analysis)
#> These final clonal annotations are stored in CTaa
#> Summary of the top 5 largest clones:
#> # A tibble: 5 × 4
#> # Groups: CTaa [5]
#> CTaa Sample Clone_Size Percent_of_Repertoire
#> <chr> <chr> <int> <dbl>
#> 1 CAMKTSYDKVIF_CASTPGRGWRKLFF:::CASSEGD… D3B 42 16.8
#> 2 CAMSANSGGYQKVTF:::CAPEAGSGNTPLVF_CASS… D5L 19 7.6
#> 3 CALTTDSWGKLQF_CSVEWDRGRSETQYF D3B 10 4
#> 4 CAVKRGGTSYGKLTF_CASSGLAGVYNEQFF D3L 10 4
#> 5 CAVGVTGTASKLTF_CASSPTTGMAHEKNIQYF D5L 8 3.2
As seen above, by default (verbose = TRUE),
ECLIPSE will print some key stats during filtering as well
as a QC plot that shows how well clonal prediction went. For this
experiment, most cells (shown in orange) received their final clonotype
by prediction. Many of these cells also were fully assigned to one clone
by the EM algorithm (i.e. y = 1), so overall the analysis went quite
well.
Let’s take a look at the final clonotypes that ECLIPSE
decided on, now stored in the CTaa column of the Seurat
meta.data:
# Final ECLIPSE clonotype
head(object_after_ECLIPSE$CTaa) %>% as.vector()
#> [1] "CAVSLLTGGGNKLTF_CASRGQDHYGYTF" "CAAHTGTASKLTF_CASSLTIFTQYF"
#> [3] "CALKGNYKLTF_CASSQPGGGGAYEQYF" "CAVIDSNYQLIW_CASSYSLAGADTQYF"
#> [5] "CALPSSGGGGADGLTF_CASSPTGPYQPQHF" "CAAWDSVNDYKLSF_CASSYQGSKETQYF"We can also compare these final clonotypes to what traditional
analysis would have determined, now stored in the scR_CTaa
column:
# Clonotype from traditional analysis
head(object_after_ECLIPSE$scR_CTaa) %>% as.vector()
#> [1] "CAVSLLTGGGNKLTF_CASRGQDHYGYTF" "CAAHTGTASKLTF_CASSLTIFTQYF"
#> [3] "CALKGNYKLTF_CASSQPGGGGAYEQYF" "CAVIDSNYQLIW_CASSYSLAGADTQYF"
#> [5] "CALPSSGGGGADGLTF_CASSPTGPYQPQHF" "CAAWDSVNDYKLSF_CASSYQGSKETQYF"ECLIPSE also provides clone sizes and clonal proportions in the
clonalFrequency and clonalProportion columns,
respectively, with matched columns having a scR_prefix to show what
would happen with traditional analysis. For a full list of columns added
by ECLIPSE, please refer to the supplemental information
section.
tcrArtifactDetectNow that the ECLIPSE function has been ran, we should
now run the 2nd core ECLIPSE function, tcrArtifactDetect,
to filter out cells with excessive TCR chains (these cells are likely a
mix of doublets and cells with TCR contamination). Here we remove any
cells with >2 TCRα chains, >2 TCRβ chains, or >4 total (α + β)
chains:
object_after_ECLIPSE <- tcrArtifactDetect(object_after_ECLIPSE,
singleChainLimit = 2,
totalChainLimit = 4)
#> 22 cells (2.2% of total) were removed due to having >2 TCRα or TCRβ chains originally
#> 0 cells (0% of total) were removed due to having >4 total chains originally
This function by default also prints plots that may be useful in setting
additional filtering thresholds. Here we can see that cells with 4 total
TCR chains have much higher RNA counts, so we may want to remove these
cells or any cells with ~ >2.5k nCount_RNA as they may be enriched
for doublets.
At this point we’re ready for downstream analysis. ECLIPSE generates
Seurat objects formatted for use with the popular scRepertoire
package, so functions introduced in that package typically can be used
here as well. One key thing to note is that since ECLIPSE
predicts clones based on CDR3 amino acid sequences, we must always use
the argument cloneCall = "aa" in scRepertoire
functions.
Shown below are a couple potential types of downstream analysis made possible with scRepertoire. For example, we can look at clonal sharing across samples:
clonalCompare(object_after_ECLIPSE,
top.clones = 5,
graph = "alluvial",
cloneCall = "aa",
group.by = "Sample") +
theme(legend.position = "none")
#> Warning: The `cloneCall` argument of `clonalCompare()` is deprecated as of scRepertoire
#> 2.10.0.
#> ℹ Please use the `clone.call` argument instead.
#> This warning is displayed once per session.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
#> generated.We can also visualize which samples have a larger fraction of expanded clonotypes:
clonalHomeostasis(object_after_ECLIPSE,
cloneCall = "aa",
group.by = "Sample")
#> Warning: The `cloneCall` argument of `clonalHomeostasis()` is deprecated as of
#> scRepertoire 2.10.0.
#> ℹ Please use the `clone.call` argument instead.
#> This warning is displayed once per session.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
#> generated.Or we can look at repertoire diversity across samples:
clonalDiversity(object_after_ECLIPSE,
metric = "shannon",
cloneCall = "aa",
group.by = "Sample")
#> Warning: The `cloneCall` argument of `clonalDiversity()` is deprecated as of
#> scRepertoire 2.10.0.
#> ℹ Please use the `clone.call` argument instead.
#> This warning is displayed once per session.
#> Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
#> generated.We hope this vignette is a helpful introduction to using ECLIPSE. If you have any questions, please email ethan.burns [@] yale.edu or use our issues page. For more details about our method, please refer to our pre-print.
Seurat objectSingleCellExperiment objectsWhile ECLIPSE uses Seurat objects,
SingleCellExperiment objects can be utilized as well. The
only difference is sceECLIPSE should be used instead of
ECLIPSE. All arguments are identical between these two
functions, except the seurat_object argument of
ECLIPSE is replaced with sce_object in
sceECLIPSE. For example:
sce_after_ECLIPSE <- sceECLIPSE(sce_object = sce_obj,
contig_type = "all",
contig_folder_paths = tcr_folder_paths,
batch = "index",
donor = "donor",
group = "Sample",
original_barcode = "cell_barcode",
verbose = TRUE,
format = "None")
#>
#> Reading TCR contig data in...
#> QC Filtering:
#> - 2883 initial contigs found before filtering
#> - 2883 contigs were from true cells (coming from 1000 cells)
#> - 24 contigs were removed for not being high confidence as defined by Cell Ranger
#> - 98 orphan contigs were found with no observed pairing. Cells containing these contigs are unlikely to have a clonotype predicted
#> - 554 contigs were removed for other reasons (ex. irregular CDR3, TCRγ/δ contig, duplicated, etc...)
#> - After all filtering, 2305 contigs from 1000 true cells remained
#>
#> Finding clones with extra chains...
#> Running donor 1/2 (D3):
#> 2 clones with an extra TCRα chain were detected, comprising 2% of cells (10 total cells)
#> 1 clones with an extra TCRβ chain were detected, comprising 8.4% of cells (42 total cells)
#>
#> Running donor 2/2 (D5):
#> 2 clones with an extra TCRα chain were detected, comprising 4.4% of cells (22 total cells)
#> 1 clones with an extra TCRβ chain were detected, comprising 1% of cells (5 total cells)
#>
#> Running VDJdive::clonoStats() for clone EM assignment...
#> Extracting results from VDJdive...
#> Finalizing clonal assignments...#> Meta data contains an 'ident' column and will likely result
#> in errors downstream.
#> Meta data contains an 'ident' column and will likely result
#> in errors downstream.
#> Meta data contains an 'ident' column and will likely result
#> in errors downstream.
#> 21 cells were detected in clones that appear to be MAIT or iNKT cells
#> 67.6% of cells in the Seurat object were assigned a clonotype using high confidence prediction from VDJdive::clonoStats()
#> 100% of cells in the Seurat object were assigned a clonotype in the end (either from prediction or traditional analysis)
#> These final clonal annotations are stored in CTaa
#> Summary of the top 5 largest clones:
#> # A tibble: 5 × 4
#> # Groups: CTaa [5]
#> CTaa Sample Clone_Size Percent_of_Repertoire
#> <chr> <chr> <int> <dbl>
#> 1 CAMKTSYDKVIF_CASTPGRGWRKLFF:::CASSEGD… D3B 42 16.8
#> 2 CAMSANSGGYQKVTF:::CAPEAGSGNTPLVF_CASS… D5L 19 7.6
#> 3 CALTTDSWGKLQF_CSVEWDRGRSETQYF D3B 10 4
#> 4 CAVKRGGTSYGKLTF_CASSGLAGVYNEQFF D3L 10 4
#> 5 CAVGVTGTASKLTF_CASSPTTGMAHEKNIQYF D5L 8 3.2
As expected, this results in the correct columns being added to colData:
### Check the original columns in colData
colnames(colData(sce_obj))
#> [1] "orig.ident" "Cell_barcode" "nCount_RNA" "donor" "Sample"
#> [6] "cell_barcode" "index" "ident"
### Check that correct columns are added to colData
colnames(colData(sce_after_ECLIPSE))
#> [1] "orig.ident" "Cell_barcode"
#> [3] "nCount_RNA" "donor"
#> [5] "Sample" "cell_barcode"
#> [7] "index" "ident"
#> [9] "highConfChains" "scR_CTgene"
#> [11] "scR_CTnt" "scR_CTaa"
#> [13] "scR_CTstrict" "scR_clonalProportion"
#> [15] "scR_clonalFrequency" "scR_cloneSize"
#> [17] "cloneCallSource" "CTaa"
#> [19] "clonalProportion" "clonalFrequency"
#> [21] "cloneSize" "cell_unconventional_subset"
#> [23] "clone_unconventional_subset"Other types of objects can still be used with ECLIPSE,
even if there is no dedicated function for them. To do so, you can use
the following steps:
Seurat objectECLIPSE on your Seurat objectSeurat
meta.data and add them to your original object. Conversion back to the
original object type is also possible, but we do not recommend this as
it is possible this may lead to some data loss.findThreeChainClonesWhile not discussed above, ECLIPSE contains a forth function that may
may be of use. This function was designed to help the
ECLIPSE function find clones with biological expression of
3 TCR chains, but can be also used to see which clones were called with
3 TCR chains and how that occurred. To see these clones we can use the
code below. First, we generate QC filtered contigs using the
returnFilteredContigs argument of ECLIPSE:
postqc_contigs <- ECLIPSE(contig_type = "all",
contig_folder_paths = tcr_folder_paths,
seurat_object = vignette_object,
batch = "index",
donor = "donor",
group = "Sample",
original_barcode = "cell_barcode",
verbose = FALSE,
returnFilteredContigs = TRUE,
format = "None")Next, we filter on a sample we are interested in, and pass the
contigs onto findThreeChainClones using
mode = "show" and chain = "a" to show clones
with 2 TCRα chains:
d3_contigs <- postqc_contigs %>% filter(donor == "D3")
three_chain_clones <- findThreeChainClones(d3_contigs,
chain = "a",
mode = "show",
printStats = TRUE)
#> Returning a list. The first table shows all beta chains predicted to have a double alpha pairing. The second table shows how/why many of these clones were called as such. Please note, the second table is pre-filtering, so not all clones may been in the first table since some may have been filtered out. In this context, the beta chain is the monotypic chain, and the alpha is the bitypic chain.This provides a list containing:
three_chain_clones[[1]]
#> # A tibble: 2 × 2
#> monotypic_chain predicted_bitypic_pairing
#> <chr> <chr>
#> 1 CSVFESDTEAFF CAESYLNSGNTPLVF;CVVNWEIGFGNVLHC
#> 2 CSVLGQGNEQYF CAIWGADKLIF;CVVNPVTDSWGKLQFthree_chain_clones[[2]]
#> total_cells cell_counts_per_bitypic_pairing monotypic_chain
#> 1 6 3 2 1 CSVFESDTEAFF
#> 2 4 2 1 1 CSVLGQGNEQYF
#> predicted_bitypic_pairing B1
#> 1 CAESYLNSGNTPLVF;CVVNWEIGFGNVLHC CAESYLNSGNTPLVF;CVVNWEIGFGNVLHC
#> 2 CAIWGADKLIF;CVVNPVTDSWGKLQF CAIWGADKLIF;CVVNPVTDSWGKLQF
#> B2 B3
#> 1 CVVNWEIGFGNVLHC CAESYLNSGNTPLVF
#> 2 CAIWGADKLIF CVVNPVTDSWGKLQFECLIPSE additional arguments| Argument | Description |
|---|---|
verbose |
By default TRUE. Prints stats such as QC
filtering efficacy, how many clones were called with 3 chains, and final
percentages of cells with a TCR called |
returnFilteredContigs |
By default FALSE. TRUE would
return the contigs after QC filtering |
allowDoubleChains |
By default "both", whereby clones can have
2 TCRα or TCRβ chains. If "alpha" or "beta" is
provided, only clones with 2 of that type will be permitted. If
"neither" is provided, then only clones with 2 chains will
be called |
combineTcrArgs |
A list that determines how
scRepertoire::combineTCR() is called. As
ECLIPSE runs combineTCR/combineExpression to
allow users to combine the predicted ECLIPSE clonotype with
a traditionally-defined clonotype, this tells which arguments
combineTCR should be run with. Specifically, it tells the 4
arguments 1) removeNA, 2) removeMulti, 3)
filterMulti, and 4) filterNonproductive in
that order. If ECLIPSE cannot call a clonotype with high
accuracy, the clonal annotation from this will be used as the final
clonotype. By default, this is
list(FALSE, FALSE, FALSE, TRUE), which calls
combineTCR with its default arguments (i.e. no
non-productive contigs are retained but clones with 1 TCR chain or >2
are allowed) |
detailedEclipseStats |
By default FALSE. This adds information
about the EM algorithm assignment to the Seurat object (e.g. what
proportion of the cell was assigned to the clone listed, etc…) |
format |
By default "Blank". This tells how missing
data is stored in contig files. Typically this is "" so
"Blank" should be specified, however older versions of Cell
Ranger use "None" instead. If this is the case, please
provide "None" here |
| Column | Description |
|---|---|
highConfChains |
all TCR chains that were sequenced in the cell and
passed QC filtering by ECLIPSE. Helpful to compare to final
clone (CTaa) used for analysis |
scR_CTgene, scR_CTnt,
scR_CTaa, scR_CTstrict,
scR_conalProportion, scR_clonalFrequency,
scR_cloneSize |
columns added by
scRepertoire::combineExpression, see scRepertoire
documentation for details. These provide information about the cell if
it was called with scRepertoire
and can be useful in comparing to the final clone (CTaa)
used for analysis. Please note the “scR_” prefix is added so ECLIPSE can
add other columns with the same name that can interact with
scRepertoire |
cloneCallSource |
whether the final clonotype list (in CTaa)
is from the prediction of VDJdive
or from traditional analysis from scRepertoire |
CTaa, clonalProportion,
clonalFrequency, cloneSize |
columns in the style of what would be added by
scRepertoire:::combineExpression. However,
CTaa here stores the final clonotype of the cell as
predicted by ECLIPSE |
cell_unconventional_subset,
clone_unconventional_subset |
cell_unconventional_subset tells if the
chains detected suggest the cell is a iNKT or MAIT cell.
clone_unconventional_subset gives similar information, but
uses the final clonotype of the cell instead of what chains were
detected in that particular cell (and therefore will capture more cells
as being a iNKT or MAIT cell). For both of these columns,
ECLIPSE searches for 3 subsets: 1) iNKT cells (must have
TRAV10 + TRAJ18 + TRBV25-1), 2) conventional MAIT cells (must have
TRAV1-2 + TRAJ33), and 3) unconventional MAIT cells (must have TRAV1-2 +
TRAJ12 or TRAV1-2 + TRAJ20. Please note that these are for human samples
only, murine T cells use different gene segments that are not searched
for by ECLIPSE |
EM_max_prop, EM_2ndmax_prop,
EM_max_2ndmax_ratio |
the proportion of the cell assigned by the EM algorithm
to the most and 2nd most likely clone. The ratio of these two
proportions (biggest/2nd biggest) is also provided. These columns are
only included if detailedEclipseStats == TRUE |
sessionInfo()
#> R version 4.6.2 (2026-10-29)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 26.04.1 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.32.so; LAPACK version 3.12.0
#>
#> locale:
#> [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
#> [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
#> [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
#> [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
#> [9] LC_ADDRESS=C LC_TELEPHONE=C
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
#>
#> time zone: Etc/UTC
#> tzcode source: system (glibc)
#>
#> attached base packages:
#> [1] stats4 stats graphics grDevices utils datasets methods
#> [8] base
#>
#> other attached packages:
#> [1] Seurat_6.0.0 SeuratObject_5.4.0
#> [3] sp_2.2-4 SingleCellExperiment_1.35.2
#> [5] SummarizedExperiment_1.43.0 Biobase_2.73.2
#> [7] GenomicRanges_1.65.4 Seqinfo_1.3.2
#> [9] IRanges_2.47.5 S4Vectors_0.51.10
#> [11] BiocGenerics_0.59.13 generics_0.1.4
#> [13] MatrixGenerics_1.25.0 matrixStats_1.5.0
#> [15] knitr_1.52 ECLIPSE_0.99.3
#> [17] scRepertoire_2.9.4 lubridate_1.9.5
#> [19] forcats_1.0.1 stringr_1.6.0
#> [21] dplyr_1.2.1 purrr_1.2.2
#> [23] readr_2.2.0 tidyr_1.3.2
#> [25] tibble_3.3.1 ggplot2_4.0.3
#> [27] tidyverse_2.0.0 BiocStyle_2.41.0
#>
#> loaded via a namespace (and not attached):
#> [1] RColorBrewer_1.1-3 ggdendro_0.2.0 sys_3.4.3
#> [4] jsonlite_2.0.0 magrittr_2.0.5 farver_2.1.2
#> [7] iNEXT_3.0.2 rmarkdown_2.32 ragg_1.5.2
#> [10] vctrs_0.7.3 memoise_2.0.1 htmltools_0.5.9
#> [13] S4Arrays_1.13.2 SparseArray_1.13.4 sctransform_0.4.3
#> [16] sass_0.4.10 parallelly_1.50.0 evmix_2.12
#> [19] KernSmooth_2.23-27 bslib_0.12.0 ica_1.0-3
#> [22] VDJdive_1.15.3 plyr_1.8.9 cachem_1.1.0
#> [25] buildtools_1.0.0 igraph_2.3.4 lifecycle_1.0.5
#> [28] pkgconfig_2.0.3 Matrix_1.7-6 R6_2.6.1
#> [31] fastmap_1.2.0 future_1.82.0 digest_0.6.39
#> [34] immApex_1.7.2 patchwork_1.3.2 irlba_2.4.1
#> [37] RSpectra_0.16-2 textshaping_1.0.5 labeling_0.4.3
#> [40] progressr_1.1.0 timechange_0.4.0 httr_1.4.9
#> [43] polyclip_1.10-7 abind_1.4-8 compiler_4.6.2
#> [46] withr_3.0.3 gsl_2.1-9 S7_0.2.2
#> [49] BiocParallel_1.47.0 viridis_0.6.5 fastDummies_1.7.6
#> [52] ggforce_0.5.0 MASS_7.3-66 quantreg_6.1
#> [55] DelayedArray_0.39.8 rjson_0.2.23 tools_4.6.2
#> [58] otel_0.3.0 future.apply_1.21.0 glue_1.8.1
#> [61] grid_4.6.2 Rtsne_0.17 cluster_2.1.8.3
#> [64] reshape2_1.4.5 gtable_0.3.6 tzdb_0.5.0
#> [67] hms_1.1.4 tidygraph_1.3.1 utf8_1.2.6
#> [70] XVector_0.53.0 RcppAnnoy_0.0.23 ggrepel_0.9.8
#> [73] RANN_2.6.3 pillar_1.11.1 spam_2.11-4
#> [76] RcppHNSW_0.7.0 splines_4.6.2 tweenr_2.0.3
#> [79] lattice_0.23-1 survival_3.8-12 SparseM_1.84-2
#> [82] tidyselect_1.2.1 pbapply_1.7-5 maketools_1.3.2
#> [85] gridExtra_2.3.1 scattermore_1.2 xfun_0.62
#> [88] pls_2.9-0 graphlayouts_1.2.5 stringi_1.8.9
#> [91] yaml_2.3.12 evaluate_1.0.5 codetools_0.2-20
#> [94] ggraph_2.2.2 BiocManager_1.30.27 cli_3.6.6
#> [97] uwot_0.2.5 systemfonts_1.3.2 jquerylib_0.1.4
#> [100] Rcpp_1.1.2 globals_0.20.0 png_0.1-9
#> [103] parallel_4.6.2 MatrixModels_0.5-4 dotCall64_1.2
#> [106] ggalluvial_0.12.6 listenv_1.1.0 viridisLite_0.4.3
#> [109] ggridges_0.5.7 scales_1.4.0 rlang_1.3.0
#> [112] cowplot_1.2.0