| name | bio-flow-cytometry-doublet-detection |
| description | Detect and remove doublets from flow and mass cytometry data. Covers FSC/SSC gating and computational doublet detection methods. Use when filtering out cell aggregates before clustering or quantitative analysis. |
| tool_type | r |
| primary_tool | flowCore |
Version Compatibility
Reference examples tested with: flowCore 2.14+, ggplot2 3.5+
Before using code patterns, verify installed versions match. If versions differ:
- R:
packageVersion('<pkg>') then ?function_name to verify parameters
If code throws ImportError, AttributeError, or TypeError, introspect the installed
package and adapt the example to match the actual API rather than retrying.
Doublet Detection
"Remove doublets from my flow cytometry data" → Detect and filter out cell aggregates using FSC-A/FSC-H gating or computational methods before clustering or quantitative analysis.
- R:
flowCore rectangular gates on FSC-A vs FSC-H
FSC-A vs FSC-H Gating (Standard Method)
library(flowCore)
library(ggcyto)
fs <- read.flowSet(list.files('data/', pattern = '\\.fcs$', full.names = TRUE))
singlet_gate <- rectangleGate(
filterId = 'singlets',
'FSC-A' = c(50000, 250000),
'FSC-H' = c(50000, 250000)
)
singlet_polygon <- polygonGate(
filterId = 'singlets',
.gate = data.frame(
'FSC-A' = c(50000, 250000, 250000, 50000),
'FSC-H' = c(40000, 200000, 260000, 60000)
)
)
singlets <- Subset(fs, singlet_gate)
autoplot(fs[[1]], 'FSC-A', 'FSC-H') + geom_gate(singlet_gate)
Automated Singlet Gating with flowDensity
library(flowDensity)
singlet_result <- flowDensity(
fs[[1]],
channels = c('FSC-A', 'FSC-H'),
position = c(TRUE, TRUE),
gates = c(NA, NA)
)
singlets <- getflowFrame(singlet_result)
pct_singlets <- nrow(singlets) / nrow(fs[[1]]) * 100
cat('Singlets:', round(pct_singlets,
flowAI Quality Control
library(flowAI)
fs_qc <- flow_auto_qc(
fs,
folder_results = 'flowAI_results',
fcs_QC = TRUE,
fcs_highQ = TRUE
)
FSC-A/FSC-W Method (Width Parameter)
if ('FSC-W' %in% colnames(fs[[1]])) {
singlet_gate_w <- rectangleGate(
filterId = 'singlets',
'FSC-A' = c(50000, 250000),
'FSC-W' = c(50000, 100000)
)
singlets <- Subset(fs, singlet_gate_w)
}
Ratio-Based Doublet Detection
calculate_fsc_ratio <- function(ff) {
fsc_a <- exprs(ff)[, 'FSC-A']
fsc_h <- exprs(ff)[, 'FSC-H']
ratio <- fsc_a / (fsc_h + 1)
return(ratio)
}
for (i in 1:length(fs)) {
ratio <- calculate_fsc_ratio(fs[[i]])
fs[[i]] <- cbind2(fsi ratio
colnamesfsincolfsi
ratio_cutoff quantileexprsfs
singlet_gate_ratio rectangleGatefilterId ratio_cutoff
SSC-Based Doublet Detection
ssc_singlet_gate <- rectangleGate(
filterId = 'ssc_singlets',
'SSC-A' = c(10000, 200000),
'SSC-H' = c(10000, 200000)
)
combined_gate <- singlet_gate & ssc_singlet_gate
singlets <- Subset(fs, combined_gate)
CyTOF Doublet Detection
library(CATALYST)
sce <- prepData(fs, panel, md)
if ('Event_length' %in% rownames(sce)) {
event_length <- assay(sce)['Event_length', ]
singlet_idx <- event_length < quantile(event_length, 0.95)
sce_singlets <- sce[, singlet_idx]
cat('Removed', sum(!singlet_idx), 'doublets based on event length\n')
}
if (all(c rownamessce
dna_total assaysce assaysce
dna_cutoff quantiledna_total
singlet_idx dna_total dna_cutoff
sce_singlets sce singlet_idx
CATALYST Workflow with Doublet Removal
Goal: Detect and remove cell doublets from a CyTOF/flow dataset using a regression-based approach on scatter parameters.
Approach: Model the expected FSC-A vs FSC-H relationship for singlets with linear regression, classify events with large residuals (above the 95th percentile) as doublets, and filter them out.
library(CATALYST)
sce <- prepData(fs, panel, md, transform = TRUE, cofactor = 5)
sce <- filterSCE(sce, !is_doublet(sce))
fsc_a <- colData(sce)$FSC_A
fsc_h <- colData(sce)$FSC_H
fit <- lm(fsc_a ~ fsc_h)
residuals <- abs(fsc_a - predict(fit))
threshold <- quantile(residuals, 0.95)
colData(sce)$doublet <- residuals > threshold
sce_singlets sce colDatascedoublet
cat meancolDatascedoublet
Batch Processing
detect_doublets <- function(ff, method = 'fsc') {
if (method == 'fsc') {
fsc_a <- exprs(ff)[, 'FSC-A']
fsc_h <- exprs(ff)[, 'FSC-H']
fit <- lm(fsc_a ~ fsc_h)
residuals <- abs(fsc_a - predict(fit))
threshold <- quantile(residuals, 0.95)
singlet_idx <- residuals <= threshold
} else if (method == 'ratio') {
ratio <- exprsff exprsff
singlet_idx ratio quantileratio
ffsinglet_idx
fs_singlets fsApplyfs detect_doublets method
doublet_rates sapplyfs i
nrowfs_singletsi nrowfsi
cat meandoublet_rates
Visualization
library(ggplot2)
plot_data <- data.frame(
FSC_A = exprs(fs[[1]])[, 'FSC-A'],
FSC_H = exprs(fs[[1]])[, 'FSC-H']
)
fit <- lm(FSC_A ~ FSC_H, data = plot_data)
plot_data$residual <- abs(plot_data$FSC_A - predict(fit))
plot_data$doublet <- plot_data$residual > quantile(plot_data$residual, 0.95)
ggplot(plot_data aesx FSC_H y FSC_A color doublet
geom_pointalpha size
scale_color_manualvalues
theme_bw
labstitle x y
ggsave width height
Related Skills
Workflow order: cytometry-qc → doublet-detection → bead-normalization → clustering
- cytometry-qc - Run first: identify flow rate and signal issues
- bead-normalization - Run after: correct remaining instrument drift
- fcs-handling - Load FCS files
- gating-analysis - Manual gating workflows
- clustering-phenotyping - Downstream analysis after doublet removal