Saturation effect
March 1, 2018 ยท View on GitHub
Helena L Crowell et al.
Aim
Support of our hypothesis that the difference observed in spillover between single-stained beads and multiplexed-stained cells is due to the difference in total ion load, with high loads leading to detector saturation effects (Figure 2, D+E).
Procedure
To simulate higher ion loads, we succesively increased amount of barcoding. In the script below, we plot the spillover in percent and absolute counts for the indicated relationships assessed on cells stained with increasing amount of barcoding reagents and identical antibody concentration. A linear model was fitted to each relationship, and increasing ion loads could be associated with a progressive decrease of spillover.
# load required packages
library(flowCore)
library(ggplot2)
library(matrixStats)
library(reshape2)
# source helpers
source('plotting-wrappers.R')
# read in titration series
fcs_path <- '../data/Figure_2-3/BC_titration'
fcs_files <- list.files(fcs_path, '.fcs', full.names=TRUE)
ffs <- lapply(fcs_files, read.FCS)
# get expressions for each group
cd36 <- lapply(ffs[grep("CD36.fcs", fcs_files)], exprs)
cd15 <- lapply(ffs[grep("CD15.fcs", fcs_files)], exprs)
cd3 <- lapply(ffs[grep("CD3.fcs", fcs_files)], exprs)
es <- list(cd36, cd15, cd3)
# get indicies of channels that may receive spill
chs <- colnames(ffs[[1]])
ms <- get_ms(chs)
mets <- get_mets(chs)
spill_cols <- CATALYST:::get_spill_cols(ms, mets)
ss_ms <- c(144, 158, 173)
# get interaction labels
interactions <- sapply(ss_ms, function(i) {
j <- which(ms == i)
from <- chs[j]
to <- chs[spill_cols[[j]]]
paste0(from, '->', to)
})
# get total ion loads, spill counts & percentage
loads <- lapply(es, function(i) sapply(i,
function(j) median(rowSums(j[, !is.na(ms)]))))
counts <- sapply(seq_along(es), function(i) {
data <- es[[i]]
cols <- spill_cols[[which(ms == ss_ms[i])]]
unlist(sapply(seq_along(data), function(j)
colMedians(data[[j]][, cols])))
})
spill <- sapply(seq_along(es), function(i) {
data <- es[[i]]
idx <- which(ms == ss_ms[i])
unlist(sapply(seq_along(data), function(j)
colMedians(data[[j]][, spill_cols[[idx]]] / data[[j]][, idx])))
})
# reformat for plotting
for (i in seq_along(counts)) {
rownames(counts[[i]]) <- rownames(spill[[i]]) <- interactions[[i]]
colnames(counts[[i]]) <- colnames(spill[[i]]) <- loads[[i]]
}
df <- data.frame(melt(counts)[-4], melt(spill)[3])
colnames(df) <- c("interaction", "load", "count", "spill")
df$spill <- df$spill * 100
fil <- df$interaction %in% c(
"Yb173Di->Yb174Di", "Yb173Di->Yb172Di",
"Gd158Di->Gd160Di", "Nd144Di->Gd160Di")
df <- df[fil, ]
# fit linear model for load vs. spill %/count
fits <- sapply(c("spill", "count"), function(data) {
lapply(unique(df$interaction), function(interaction) {
inds <- df$interaction == interaction
summary(lm(df[inds, data] ~ df[inds, "load"]))
})
})
# get R-squared & slopes
R2 <- apply(fits, 2, function(i) sapply(i, function(fit) fit$r.squared))
R2 <- apply(R2, 2, function(r2) paste(sprintf("%.5s", round(r2, 3)), collapse=", "))
beta <- apply(fits, 2, function(i) sapply(i, function(fit) fit$coefficients[2]))
beta <- apply(beta, 2, function(b) paste(sprintf("%.8s", round(b, 5)), collapse=", "))
plot_load_vs_spill(df, y="spill",
R2=R2["spill"], beta=beta["spill"], ylab="Spillover [%]",
yscale=scale_y_continuous(limits=c(-.5, 5.5), breaks=seq(-2.5,5,2.5), expand=c(0,0)))
plot_load_vs_spill(df, y="count",
R2=R2["count"], beta=beta["count"], ylab="Spillover [counts]",
yscale=scale_y_continuous(limits=c(-5, 35), breaks=seq(0,30,10), expand=c(0,0)))

