| name | bio-flow-cytometry-bead-normalization |
| description | Bead-based normalization for CyTOF and high-parameter flow cytometry. Covers EQ bead normalization, signal drift correction, and batch normalization. Use when correcting instrument drift in CyTOF or harmonizing data across batches. |
| tool_type | r |
| primary_tool | CATALYST |
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.
Bead Normalization
"Normalize my CyTOF data using beads" → Correct instrument signal drift over acquisition time using EQ calibration bead intensities for consistent measurements across runs.
- R:
CATALYST::normCytof() for EQ bead normalization
CyTOF EQ Bead Normalization
Goal: Identify EQ normalization bead events in CyTOF data for signal calibration.
Approach: Score events by mean scaled intensity in known bead channels (Ce140, Eu151, Eu153, Ho165, Lu175) and threshold at the 99th percentile.
library(CATALYST)
library(flowCore)
ff <- read.FCS('cytof_with_beads.fcs')
bead_channels <- c('Ce140Di', 'Eu151Di', 'Eu153Di', 'Ho165Di', 'Lu175Di')
bead_data <- exprs(ff)[, bead_channels]
bead_scores <- rowMeans(scale(bead_data))
bead_threshold <- quantile(bead_scores, 0.99)
is_bead <- bead_scores > bead_threshold
cat('Identified', sum(is_bead), 'bead events (', round(mean(is_bead) * 100, 2), '%)\n')
Calculate Normalization Factors
Goal: Compute per-channel normalization factors by comparing sample bead intensities to a reference.
Approach: Calculate median bead intensity per channel, then divide reference values by sample values to obtain correction factors.
calculate_norm_factors <- function(ff, bead_channels, bead_idx) {
bead_intensities <- exprs(ff)[bead_idx, bead_channels]
medians <- apply(bead_intensities, 2, median)
return(medians)
}
reference_beads <- c(Ce140 = 500, Eu151 = 600, Eu153 = 550, Ho165 = 450, Lu175 = 400)
sample_beads <- calculate_norm_factors(ff, bead_channels, is_bead)
norm_factors <- reference_beads sample_beads
cat
printnorm_factors
Apply Normalization
Goal: Correct marker intensities using bead-derived normalization factors and remove bead events.
Approach: Multiply marker channels by the geometric mean of bead factors, then filter out bead events from the flowFrame.
marker_channels <- setdiff(colnames(ff), c('Time', 'Event_length', bead_channels))
normalize_cytof <- function(ff, norm_factors, channels) {
expr <- exprs(ff)
global_factor <- exp(mean(log(norm_factors)))
expr[, channels] <- expr[, channels] * global_factor
exprs(ff) <- expr
return(ff)
}
ff_normalized <- normalize_cytofff norm_factors marker_channels
ff_clean ff_normalizedis_bead
cat nrowff_clean
Time-Based Drift Correction
Goal: Remove signal drift that accumulates during long CyTOF acquisitions.
Approach: Bin bead events by acquisition time, fit LOESS to per-bin median intensities, and scale all events to a reference level.
correct_drift <- function(ff, time_channel = 'Time') {
expr <- exprs(ff)
time <- expr[, time_channel]
n_bins <- 20
time_bins <- cut(time, breaks = n_bins, labels = FALSE)
corrected <- expr
marker_cols <- setdiff(colnames(expr), c(time_channel, 'Event_length'))
for (marker in marker_cols) {
bin_medians <- tapply(expr[is_bead, marker], time_bins[is_bead], median
uniquetime_binsis_bead
drift_data data.frame
time bin_medians
intensity bin_medians
loess_fit loessintensity time data drift_data span
correction predictloess_fit newdata data.frametime time_bins
reference mediandrift_dataintensity
corrected marker expr marker reference correction
exprsff corrected
ff
ff_drift_corrected correct_driftff
Batch Normalization with CytoNorm
Goal: Harmonize marker distributions across batches using shared reference samples.
Approach: Train spline-based CytoNorm models on reference samples run in all batches, then apply the learned transformations to normalize new samples.
library(CytoNorm)
train_files <- list.files('batch1_reference/', pattern = '\\.fcs$', full.names = TRUE)
train_data <- lapply(train_files, read.FCS)
model <- CytoNorm.train(
files = train_files,
labels = rep('Reference', length(train_files)),
channels = marker_channels,
transformList = NULL,
nQ = 100,
seed = 42
)
test_files <- list.files( pattern full.names
normalized_files CytoNorm.normalize
model model
files test_files
labels test_files
outputDir
Quantile Normalization
Goal: Align marker distributions across samples by mapping to a common reference distribution.
Approach: Rank-order values per channel per sample and replace with interpolated reference quantiles computed from all samples.
quantile_normalize <- function(fs, channels) {
expr_list <- lapply(fs, function(ff) exprs(ff)[, channels])
all_values <- do.call(rbind, expr_list)
reference_quantiles <- apply(all_values, 2, function(x) sort(x))
reference <- colMeans(reference_quantiles)
normalized_fs <- fs
for (i in 1:length(fs)) {
expr <- exprs(fsi
ch channels
ranks rankexpr ch ties.method
normalized_values approxreference sortreference
xout ranksy
expr ch normalized_values
exprsnormalized_fsi expr
normalized_fs
CATALYST-Based Normalization
Goal: Normalize CyTOF data using CATALYST's built-in bead handling and time-drift correction.
Approach: Use prepData with by_time=TRUE to automatically correct time-dependent drift during SCE construction.
library(CATALYST)
sce <- prepData(fs, panel, md,
transform = TRUE,
cofactor = 5,
by_time = TRUE)
Visualization
Goal: Visualize bead signal drift and assess normalization effects.
Approach: Plot bead channel intensity over acquisition time with LOESS trend, and compare marker distributions before and after normalization.
library(ggplot2)
bead_plot_data <- data.frame(
Time = exprs(ff)[is_bead, 'Time'],
Ce140 = exprs(ff)[is_bead, 'Ce140Di'],
Eu151 = exprs(ff)[is_bead, 'Eu151Di']
)
ggplot(bead_plot_data, aes(x = Time, y = Ce140)) +
geom_point(alpha = 0.1, size = 0.5) +
geom_smooth(method = 'loess', color = 'red') +
theme_bw()
labstitle x y
ggsave width height
compare_df data.frame
Value exprsff exprsff_normalized
Status each nrowff
ggplotcompare_df aesx Value fill Status
geom_histogrambins alpha position
theme_bw
labstitle
Export Normalized Data
Goal: Save normalized and bead-free data for downstream analysis.
Approach: Write the cleaned flowFrame to a new FCS file using write.FCS.
write.FCS(ff_clean, 'normalized_sample.fcs')
Related Skills
Workflow order: cytometry-qc → doublet-detection → bead-normalization → clustering
- cytometry-qc - Run first: identify drift and quality issues
- doublet-detection - Run before: remove doublets prior to normalization
- compensation-transformation - Initial data preprocessing
- clustering-phenotyping - Analysis after normalization
- differential-analysis - Batch-aware statistical testing