Corrects fluorophore spillover (conventional compensation) or spectral overlap (spectral unmixing) and applies variance-stabilizing transforms (logicle/biexponential, arcsinh, log) for flow and mass cytometry. Covers spillover-matrix estimation from single-stain controls, AutoSpill, the spillover spreading matrix and why panel design (not compensation) bounds resolution, compensate-then-transform ordering, and arcsinh cofactor choice (5 for CyTOF, ~150 for fluorescence, per-channel via flowVS). Use when correcting spectral overlap, preparing data for gating/clustering, choosing logicle vs arcsinh, deciding a cofactor, or distinguishing compensation from spectral unmixing.
Corrects fluorophore spillover (conventional compensation) or spectral overlap (spectral unmixing) and applies variance-stabilizing transforms (logicle/biexponential, arcsinh, log) for flow and mass cytometry. Covers spillover-matrix estimation from single-stain controls, AutoSpill, the spillover spreading matrix and why panel design (not compensation) bounds resolution, compensate-then-transform ordering, and arcsinh cofactor choice (5 for CyTOF, ~150 for fluorescence, per-channel via flowVS). Use when correcting spectral overlap, preparing data for gating/clustering, choosing logicle vs arcsinh, deciding a cofactor, or distinguishing compensation from spectral unmixing.
Before using code patterns, verify installed versions match. If versions differ:
R: packageVersion('<pkg>') then ?function_name to verify parameters
Notes that bite: estimateLogicle() lives in flowWorkspace (not flowCore). flowCore::spillover() on a flowFrame returns a LIST of keyword matrices (index [[1]]); flowStats::spillover() on single-stain controls returns the matrix DIRECTLY (not a list) - do not index it with $.
If code throws an error, introspect the installed package and adapt rather than retrying.
Compensation and Transformation
"Compensate and transform my cytometry data" -> Remove spillover (matrix subtraction, conventional) or unmix the full spectrum (least squares, spectral), then apply a transform so populations separate.
R (conventional): flowCore::compensate() then flowWorkspace::estimateLogicle() + flowCore::transform()
R (CyTOF/mass): CATALYST::prepData(..., transform=TRUE, cofactor=5) (arcsinh)
R (spectral): linear UNMIXING, not compensation - see the taxonomy
The Single Most Important Modern Insight -- Compensation Corrects the Mean; It Cannot Remove Spreading Error
Conventional compensation inverts a square spillover matrix (peak-channel subtraction); spectral cytometry solves an OVERDETERMINED least-squares unmix over all detectors, with autofluorescence modeled as an extra "fluorophore." Both correct the population MEAN. Neither removes spreading error - the widening of a negative population in a spillover detector that arises from the Poisson counting statistics of the spilled-in photons (Roederer 2001 Cytometry 45:194; Nguyen 2013 Cytometry A 83:306). Compensation does not INTRODUCE spreading; it makes the pre-existing variance visible by re-centering means. The corollaries are load-bearing: (1) a smeared negative cannot be fixed by tuning the matrix - over-compensating to flatten it is data falsification; (2) spreading is fixed at PANEL DESIGN (the Spillover Spreading Matrix identifies which detector pairs to avoid for co-expressed/dim markers), never downstream; (3) calling spectral unmixing "compensation" is a category error - it is a different, overdetermined model.
Method Taxonomy
Method
What it does
When to use
Fails when
Acquisition-recorded $SPILLOVER
applies the cytometer-computed matrix
trustworthy single-stain setup at acquisition
controls were wrong/missing
Computed compensation (flowStats::spillover)
estimates spillover from single-stain controls (medians)
conventional flow, controls available
poor/dim/contaminated controls
AutoSpill (Roca 2021 Nat Commun 12:2890)
robust-regression matrix + iterative refinement; AF as endogenous dye
high-parameter panels; messy controls
reference implementation/setup unavailable
Spectral unmixing (OLS/WLS/Poisson)
least-squares unmix full spectrum vs reference spectra + AF
compute via flowStats::spillover from single-stains (or AutoSpill)
controls drive the matrix; AutoSpill for >12 colors
Spectral cytometer
UNMIX (do NOT compensate), then arcsinh at ~150/per-channel (NOT 5)
overdetermined system; spectral data is fluorescence-scale, not ion counts
CyTOF / mass
arcsinh cofactor 5; spillover via CATALYST compCytof if needed
metals barely spill (~1-4%), but oxide/impurity is real
Dim marker driving a borderline call
test per-channel cofactor (flowVS)
a fixed cofactor can manufacture/erase the population
Compensate-Then-Transform Ordering (load-bearing)
Compensation/unmixing is LINEAR and must run on untransformed data; applying it after a nonlinear transform is mathematically invalid. estimateLogicle() must run on ALREADY-COMPENSATED data so the w/a parameters reflect the post-compensation negative spread. Negative values after compensation are expected and meaningful - do NOT clip to zero before transforming (handling negatives is the entire reason logicle/arcsinh exist; log cannot).
Apply or Compute Compensation
Goal: Apply the recorded matrix, or estimate one from single-stain controls.
Approach:compensate() takes a compensation object built from the matrix; flowStats::spillover() estimates from single-stain controls and returns the matrix directly.
Arcsinh Transform (CyTOF cofactor 5; fluorescence/spectral ~150 or per-channel)
Goal: Variance-stabilize mass-cytometry counts (or any pipeline feeding clustering).
Approach:asinh(x/cofactor); flowCore's arcsinhTransform is asinh(a + b*x) + c, so set b=1/cofactor. CATALYST prepData defaults cofactor=5.
COFACTOR <- 5 # standard CyTOF cofactor, codified in the CATALYST workflow (Nowicka 2017); ~150 for fluorescence
asinhT <- arcsinhTransform(transformationId ='asinh', a =0, b =1/COFACTOR,c=0)
fcs_t <- transform(fcs, transformList(marker_channels, asinhT))# CATALYST path (CyTOF): cofactor=5 default; OVERRIDE for fluorescence/spectral
sce <- CATALYST::prepData(fs, panel, md, transform =TRUE, cofactor = COFACTOR)
Per-Method Failure Modes
Over-compensation (negative pull-down)
Trigger: matrix slope over-estimated from dim controls. Mechanism: subtraction overshoots. Symptom: negative population pulled below zero, "comma" shape. Fix: controls at least as bright as the sample; AutoSpill regression; never hand-tune to flatten spread.
Wrong logicle width clips negatives
Trigger: fixed w instead of estimateLogicle. Mechanism: linear region too narrow. Symptom: negative population piled on the axis. Fix: estimate w on compensated data.
Cofactor compresses a dim marker
Trigger: cofactor 5 on fluorescence (or 150 on CyTOF). Mechanism: linear region mismatched to the noise band. Symptom: dim-positive collapses into the negative; clusters don't reproduce. Fix: 5 for CyTOF, ~150 for fluorescence; per-channel via flowVS::estParamFlowVS.
Compensating spectral data
Trigger: treating Aurora data as conventional. Mechanism: subtraction is the wrong model for an overdetermined system. Symptom: residual spread, false positives. Fix: unmix against single-stain reference spectra + unstained AF.
Quantitative Thresholds
Threshold
Source
Rationale
arcsinh cofactor = 5 (mass)
Nowicka 2017 F1000Res 6:748 (CATALYST workflow)
matches CyTOF ion-count near-zero noise band
arcsinh cofactor ~150 (fluorescence)
community/CATALYST convention (not a derived optimum)
PMT photon scale is far larger; per-channel flowVS supersedes
comp control >= sample brightness
Roederer 2001 Cytometry 45:194
slope estimated over the widest lever arm; extrapolation amplifies error
spreading is intensity-dependent (~sqrt of signal)
Nguyen 2013 Cytometry A 83:306
SSM is normalized to be gain-independent for panel design