GOAL

spillover tips

  • fsc, ssc are used with the useNormFilt parameter for the lympho gate
  • fsc, ssc are removed from the list of marker, whatever useNormFilter is
  • a channel called “FSC-H” will have to removed previously
  • all markers that are not needed should be removed, either explicitly using fsApply(), either implicitly using the patt parameter

SETUP

Directory architecture

# Download the FCS files from FlowRepository
# experiment FR-FCM-ZZ36 alias OMIP-018
# Uncompress the ZIP file into a folder named OMIP-018_FR-FCM-ZZ36
dir(pattern = "Compensatio.+fcs", path = "OMIP-018_FR-FCM-ZZ36")
##  [1] "Compensation Controls_Alexa Fluor 405 Stained Control.fcs"
##  [2] "Compensation Controls_Alexa Fluor 430 Stained Control.fcs"
##  [3] "Compensation Controls_Alexa Fluor 488 Stained Control.fcs"
##  [4] "Compensation Controls_APC-Cy7 Stained Control.fcs"        
##  [5] "Compensation Controls_APC Stained Control.fcs"            
##  [6] "Compensation Controls_PE-Cy5-5 Stained Control.fcs"       
##  [7] "Compensation Controls_PE-Cy5 Stained Control.fcs"         
##  [8] "Compensation Controls_PE-Cy7 Stained Control.fcs"         
##  [9] "Compensation Controls_PE-Texas Red Stained Control.fcs"   
## [10] "Compensation Controls_PE Stained Control.fcs"             
## [11] "Compensation Controls_PerCP-Cy5-5 Stained Control.fcs"    
## [12] "Compensation Controls_Qdot 605 Stained Control.fcs"       
## [13] "Compensation Controls_Qdot 655 Stained Control.fcs"       
## [14] "Compensation Controls_Qdot 800 Stained Control.fcs"       
## [15] "Compensation Controls_Unstained Control.fcs"

Libraries

library(flowCore)

Corrected version of spillover

setMethod("spillover",
          signature = signature(x = "flowSet"),
          definition = function(x, unstained = NULL, patt = NULL, fsc = "FSC-A",
                                ssc = "SSC-A", method = "median",
                                stain_match = c("intensity", "ordered", "regexpr"),
                                useNormFilt = FALSE, pregate = FALSE,
                                plot = FALSE, ...) {

            stain_match <- match.arg(stain_match)
            
            if (is.null(unstained)) {
              stop("Sorry, we don't yet support unstained cells blended ",
                   "with stained cells", " please specify the name or index of unstained sample", call. = FALSE)
            } else {
              ## We often only want spillover for a subset of the columns
              allcols <- colnames(x)
              cols <- if (is.null(patt)) {
                allcols
              } else {
                grep(patt, allcols, value = TRUE)
              }

              ## Ignore these guys if they somehow got into cols.
              cols <- cols[!(cols %in% c(fsc, ssc))]
              
              ## There has got to be a better way of doing this...
              if (!is.numeric(unstained)) {
                unstained <- match(unstained, sampleNames(x))
                if (is.na(unstained)) {
                  stop("Baseline(unstained sample) not found in this set.", call. = FALSE)
                }
              }

              ## Check to see if the unstained sample is in the list of
              ## stains. If not, we need to add it, making it the first
              ## row and adjust the unstained index accordingly.
              ## If it is there we adjust to the appropriate index.
              
              if (useNormFilt) {
                if (is.numeric(fsc)) {
                  fsc <- allcols[fsc]
                }
                if (is.numeric(ssc)) {
                  ssc <- allcols[ssc]
                }

                if (is.na(match(fsc, allcols))) {
                  stop("Could not find forward scatter parameter. ",
                       "Please set the fsc parameter", call. = FALSE)
                }
                if (is.na(match(ssc, allcols))) {
                  stop("Could not find side scatter parameter. ",
                       "Please set the ssc parameter", call. = FALSE)
                  n2f <- norm2Filter(fsc, ssc, scale.factor = 1.5)
                  x <- Subset(x, n2f)
                }
              }
              
              # Here, we match the stain channels with the compensation controls
              # if the user has specified it. Otherwise, we must "guess" below
              # based on the largest statistic for the compensation control
              # (i.e., the row).
              # If "ordered," we assume the ordering of the channels in the
              # flowSet object is the same as the ordering of the
              # compensation-control samples.
              # Another option is to use a regular expression to match the
              # channel names with the filenames of the compensation controls.
              if (stain_match == "intensity") {
                channel_order <- NA
              } else if (stain_match == "ordered") {
                channel_order <- seq_along(sampleNames(x))[-unstained]
              } else if (stain_match == "regexpr") {
                channel_order <- sapply(cols, grep, x = sampleNames(x), fixed = TRUE)
                if (!all(sapply(channel_order, length) == 1)) {
                  stop("Multiple stains match to a common compensation-control filename",
                       call. = FALSE)
                }
              }

              if (pregate) {
                if (any(is.na(channel_order))) {
                  stop("Cannot apply pregate without knowing ordering of channels. ",
                       "Match the channels to controls with 'ordered' or 'regexpr'.",
                       call. = FALSE)
                }
                require('flowStats')

                if (plot) {
                  oask <- devAskNewPage(TRUE)
                  on.exit(devAskNewPage(oask))
                }

                x_gated <- lapply(sort(channel_order), function(channel_i) {
                  flow_frame <- x[[channel_i]]
                  channel_name <- cols[which(channel_order == channel_i)]

                  # Applies flowStats:::rangeGate to select positive population
                  gate_filter <- rangeGate(flow_frame, stain = channel_name,
                                           inBetween = TRUE, borderQuant = 0,
                                           absolute = FALSE, peakNr = 2, ...)
                  if (plot) {
                    # Plots a kernel density for the current channel
                    plot(density(exprs(flow_frame)[, channel_name]),
                         xlab = channel_name, ylab = "Density",
                         main = paste("Compensation Control:", sampleNames(x)[channel_i]))

                    # Adds a vertical line to show gate
                    cutpoint <- c(gate_filter@min, gate_filter@max)
                    cutpoint <- cutpoint[is.finite(cutpoint)]
                    abline(v = cutpoint, col = "black", lwd = 3, lty = 2)
                  }
                  Subset(flow_frame, gate_filter)
                })
                x_gated <- x_gated[channel_order]
                names(x_gated) <- sampleNames(x)[channel_order]
                x <- rbind2(flowSet(x_gated), x[unstained])
              }
              if(length(x) - 1 != length(cols))
              {
                stop("the number of single stained samples provided in this set doesn't match to the number of stained channels!")
              }
              # Compute background for each channel of each file
              if (method == "mode") {
                inten <- fsApply(x, function(flow_frame) {
                  modes <- sapply(cols, function(stain) {
                    density_stain <- density(exprs(flow_frame)[, stain])
                    with(density_stain, x[which.max(y)])
                  }, USE.NAMES = TRUE)
                  modes
                })
              } else {
                inten <- fsApply(x, each_col, method)[, cols]
              }

              # background correction
              inten <- pmax(sweep(inten[-unstained, ], 2, inten[unstained, ]), 0)

              # normalize each row
              # If the channel order was not set above, then a guess is made based on the
              # largest statistic for the compensation control (i.e., the row).
              # If the channels are "ordered", the diagonal must be equal to 1.
              # If the channels are "regexpr" matched, the files (row of the matrix) must
              # be ordered first, then the diagonal must be equal to 1.
              if (stain_match == "intensity") {
                # normalize by max of each row
                inten <- sweep(inten, 1, apply(inten, 1, max), "/")
                # find max for each file
                channel_order <- apply(inten, 1, which.max)
                if (anyDuplicated(channel_order) > 0) {
                  stop("Unable to match stains with controls based on intensity: ",
                       "a single stain matches to several multiple controls. ",
                       call. = FALSE)
                }
                # order the row
                inten <- inten[order(channel_order),]
              } else {
                if (stain_match == "regexpr")  # order the row
                  inten = inten[channel_order, ]
                # normalize by the diagonal
                inten <- sweep(inten, 1, diag(inten), "/")      
              }
              
              # Updates row names
              rownames(inten) <- colnames(inten)
              inten
          }
      })
## [1] "spillover"

MANUAL ORDERING

# Read files
fs = read.flowSet(pattern = "Compensatio.+fcs", path = "OMIP-018_FR-FCM-ZZ36")
pData(fs)
name
Compensation Controls_Alexa Fluor 405 Stained Control.fcs Compensation Controls_Alexa Fluor 405 Stained Control.fcs
Compensation Controls_Alexa Fluor 430 Stained Control.fcs Compensation Controls_Alexa Fluor 430 Stained Control.fcs
Compensation Controls_Alexa Fluor 488 Stained Control.fcs Compensation Controls_Alexa Fluor 488 Stained Control.fcs
Compensation Controls_APC-Cy7 Stained Control.fcs Compensation Controls_APC-Cy7 Stained Control.fcs
Compensation Controls_APC Stained Control.fcs Compensation Controls_APC Stained Control.fcs
Compensation Controls_PE-Cy5-5 Stained Control.fcs Compensation Controls_PE-Cy5-5 Stained Control.fcs
Compensation Controls_PE-Cy5 Stained Control.fcs Compensation Controls_PE-Cy5 Stained Control.fcs
Compensation Controls_PE-Cy7 Stained Control.fcs Compensation Controls_PE-Cy7 Stained Control.fcs
Compensation Controls_PE-Texas Red Stained Control.fcs Compensation Controls_PE-Texas Red Stained Control.fcs
Compensation Controls_PE Stained Control.fcs Compensation Controls_PE Stained Control.fcs
Compensation Controls_PerCP-Cy5-5 Stained Control.fcs Compensation Controls_PerCP-Cy5-5 Stained Control.fcs
Compensation Controls_Qdot 605 Stained Control.fcs Compensation Controls_Qdot 605 Stained Control.fcs
Compensation Controls_Qdot 655 Stained Control.fcs Compensation Controls_Qdot 655 Stained Control.fcs
Compensation Controls_Qdot 800 Stained Control.fcs Compensation Controls_Qdot 800 Stained Control.fcs
Compensation Controls_Unstained Control.fcs Compensation Controls_Unstained Control.fcs
colnames(fs)
##  [1] "FSC-A"             "SSC-A"             "Alexa Fluor 488-A"
##  [4] "PerCP-Cy5-5-A"     "APC-A"             "APC-Cy7-A"        
##  [7] "Alexa Fluor 405-A" "Alexa Fluor 430-A" "Qdot 605-A"       
## [10] "Qdot 655-A"        "Qdot 800-A"        "PE-A"             
## [13] "PE-Texas Red-A"    "PE-Cy5-A"          "PE-Cy5-5-A"       
## [16] "PE-Cy7-A"          "Time"
# Check file names vs channels
# Remove FSC, SSC, Time
cbind(pData(fs)[-length(fs),], colnames(fs)[-c(1:2, length(colnames(fs)))])
##       [,1]                                                       
##  [1,] "Compensation Controls_Alexa Fluor 405 Stained Control.fcs"
##  [2,] "Compensation Controls_Alexa Fluor 430 Stained Control.fcs"
##  [3,] "Compensation Controls_Alexa Fluor 488 Stained Control.fcs"
##  [4,] "Compensation Controls_APC-Cy7 Stained Control.fcs"        
##  [5,] "Compensation Controls_APC Stained Control.fcs"            
##  [6,] "Compensation Controls_PE-Cy5-5 Stained Control.fcs"       
##  [7,] "Compensation Controls_PE-Cy5 Stained Control.fcs"         
##  [8,] "Compensation Controls_PE-Cy7 Stained Control.fcs"         
##  [9,] "Compensation Controls_PE-Texas Red Stained Control.fcs"   
## [10,] "Compensation Controls_PE Stained Control.fcs"             
## [11,] "Compensation Controls_PerCP-Cy5-5 Stained Control.fcs"    
## [12,] "Compensation Controls_Qdot 605 Stained Control.fcs"       
## [13,] "Compensation Controls_Qdot 655 Stained Control.fcs"       
## [14,] "Compensation Controls_Qdot 800 Stained Control.fcs"       
##       [,2]               
##  [1,] "Alexa Fluor 488-A"
##  [2,] "PerCP-Cy5-5-A"    
##  [3,] "APC-A"            
##  [4,] "APC-Cy7-A"        
##  [5,] "Alexa Fluor 405-A"
##  [6,] "Alexa Fluor 430-A"
##  [7,] "Qdot 605-A"       
##  [8,] "Qdot 655-A"       
##  [9,] "Qdot 800-A"       
## [10,] "PE-A"             
## [11,] "PE-Texas Red-A"   
## [12,] "PE-Cy5-A"         
## [13,] "PE-Cy5-5-A"       
## [14,] "PE-Cy7-A"
# Selecting and ordering by hand, FSC & SSC being the first two channels
markers.id = c(5, 6, 1, 4, 3, 13, 12, 14, 11, 10, 2, 7, 8, 9) + 2
# Check the matching
# NB: The order of the loaded with read.flowSet despends on the locale!
cbind(gsub("Compensation Controls_", "",
           gsub(" Stained Control.fcs", "",
                pData(fs)[-length(fs),])),
      colnames(fs)[markers.id])
##       [,1]              [,2]               
##  [1,] "Alexa Fluor 405" "Alexa Fluor 405-A"
##  [2,] "Alexa Fluor 430" "Alexa Fluor 430-A"
##  [3,] "Alexa Fluor 488" "Alexa Fluor 488-A"
##  [4,] "APC-Cy7"         "APC-Cy7-A"        
##  [5,] "APC"             "APC-A"            
##  [6,] "PE-Cy5-5"        "PE-Cy5-5-A"       
##  [7,] "PE-Cy5"          "PE-Cy5-A"         
##  [8,] "PE-Cy7"          "PE-Cy7-A"         
##  [9,] "PE-Texas Red"    "PE-Texas Red-A"   
## [10,] "PE"              "PE-A"             
## [11,] "PerCP-Cy5-5"     "PerCP-Cy5-5-A"    
## [12,] "Qdot 605"        "Qdot 605-A"       
## [13,] "Qdot 655"        "Qdot 655-A"       
## [14,] "Qdot 800"        "Qdot 800-A"
# Apply selection and ordering
fs = fsApply(fs, function(ff) ff[, markers.id])  # ordered

# Verify
colnames(fs)
##  [1] "Alexa Fluor 405-A" "Alexa Fluor 430-A" "Alexa Fluor 488-A"
##  [4] "APC-Cy7-A"         "APC-A"             "PE-Cy5-5-A"       
##  [7] "PE-Cy5-A"          "PE-Cy7-A"          "PE-Texas Red-A"   
## [10] "PE-A"              "PerCP-Cy5-5-A"     "Qdot 605-A"       
## [13] "Qdot 655-A"        "Qdot 800-A"
length(colnames(fs))
## [1] 14
length(fs)
## [1] 15
fs[[1]]
## flowFrame object 'Compensation Controls_Alexa Fluor 405 Stained Control.fcs'
## with 5000 cells and 14 observables:
##                   name desc  range minRange maxRange
## $P7  Alexa Fluor 405-A <NA> 262144  -111.00   262143
## $P8  Alexa Fluor 430-A <NA> 262144  -111.00   262143
## $P3  Alexa Fluor 488-A <NA> 262144   -81.18   262143
## $P6          APC-Cy7-A <NA> 262144  -111.00   262143
## $P5              APC-A <NA> 262144   -88.16   262143
## $P15        PE-Cy5-5-A <NA> 262144  -111.00   262143
## $P14          PE-Cy5-A <NA> 262144   -55.50   262143
## $P16          PE-Cy7-A <NA> 262144  -111.00   262143
## $P13    PE-Texas Red-A <NA> 262144  -109.50   262143
## $P12              PE-A <NA> 262144   -38.25   262143
## $P4      PerCP-Cy5-5-A <NA> 262144  -100.86   262143
## $P9         Qdot 605-A <NA> 262144  -111.00   262143
## $P10        Qdot 655-A <NA> 262144  -111.00   262143
## $P11        Qdot 800-A <NA> 262144  -111.00   262143
## 227 keywords are stored in the 'description' slot
# Process
fs.spill = spillover(fs,
                     unstained = length(fs),   # unstained is the last file
                     stain_match = "ordered")  # manual matching
# Inspection
round(fs.spill, 3)
##                   Alexa Fluor 405-A Alexa Fluor 430-A Alexa Fluor 488-A
## Alexa Fluor 405-A             1.000             0.041             0.000
## Alexa Fluor 430-A             0.247             1.000             0.023
## Alexa Fluor 488-A             0.001             0.053             1.000
## APC-Cy7-A                     0.002             0.001             0.000
## APC-A                         0.001             0.001             0.000
## PE-Cy5-5-A                    0.003             0.012             0.001
## PE-Cy5-A                      0.000             0.000             0.001
## PE-Cy7-A                      0.001             0.000             0.002
## PE-Texas Red-A                0.000             0.001             0.001
## PE-A                          0.000             0.002             0.006
## PerCP-Cy5-5-A                 0.000             0.000             0.000
## Qdot 605-A                    0.000             0.000             0.000
## Qdot 655-A                    0.032             0.002             0.000
## Qdot 800-A                    0.000             0.000             0.000
##                   APC-Cy7-A APC-A PE-Cy5-5-A PE-Cy5-A PE-Cy7-A
## Alexa Fluor 405-A     0.000 0.000      0.000    0.000    0.000
## Alexa Fluor 430-A     0.000 0.000      0.000    0.000    0.000
## Alexa Fluor 488-A     0.000 0.000      0.000    0.000    0.000
## APC-Cy7-A             1.000 0.075      0.062    0.006    0.388
## APC-A                 0.068 1.000      0.491    0.076    0.028
## PE-Cy5-5-A            0.003 0.006      1.000    0.017    0.056
## PE-Cy5-A              0.073 1.110     10.262    1.000    0.392
## PE-Cy7-A              0.047 0.000      0.058    0.001    1.000
## PE-Texas Red-A        0.000 0.001      0.482    0.050    0.020
## PE-A                  0.000 0.000      0.425    0.048    0.014
## PerCP-Cy5-5-A         0.024 0.012      0.718    0.008    0.047
## Qdot 605-A            0.000 0.000      0.000    0.000    0.000
## Qdot 655-A            0.000 0.012      0.011    0.008    0.000
## Qdot 800-A            0.027 0.000      0.032    0.000    0.089
##                   PE-Texas Red-A  PE-A PerCP-Cy5-5-A Qdot 605-A Qdot 655-A
## Alexa Fluor 405-A          0.000 0.000         0.000      0.011      0.004
## Alexa Fluor 430-A          0.000 0.000         0.001      0.756      0.311
## Alexa Fluor 488-A          0.000 0.000         0.018      0.029      0.011
## APC-Cy7-A                  0.001 0.001         0.002      0.003      0.052
## APC-A                      0.003 0.000         0.013      0.004      0.692
## PE-Cy5-5-A                 0.041 0.028         0.274      0.019      0.017
## PE-Cy5-A                   0.043 0.023         2.448      0.009      1.427
## PE-Cy7-A                   0.019 0.011         0.022      0.005      0.003
## PE-Texas Red-A             1.000 0.118         0.125      0.210      0.113
## PE-A                       1.567 1.000         0.117      0.336      0.130
## PerCP-Cy5-5-A              0.000 0.000         1.000      0.000      0.050
## Qdot 605-A                 0.084 0.002         0.000      1.000      0.016
## Qdot 655-A                 0.002 0.000         0.006      0.023      1.000
## Qdot 800-A                 0.000 0.000         0.019      0.000      0.001
##                   Qdot 800-A
## Alexa Fluor 405-A      0.001
## Alexa Fluor 430-A      0.022
## Alexa Fluor 488-A      0.001
## APC-Cy7-A              1.105
## APC-A                  0.060
## PE-Cy5-5-A             0.032
## PE-Cy5-A               0.181
## PE-Cy7-A               0.543
## PE-Texas Red-A         0.011
## PE-A                   0.008
## PerCP-Cy5-5-A          0.548
## Qdot 605-A             0.000
## Qdot 655-A             0.001
## Qdot 800-A             1.000
cols = c("Alexa Fluor 405-A", "Alexa Fluor 430-A", "Alexa Fluor 488-A")
round(fs.spill[cols, cols], 3)
##                   Alexa Fluor 405-A Alexa Fluor 430-A Alexa Fluor 488-A
## Alexa Fluor 405-A             1.000             0.041             0.000
## Alexa Fluor 430-A             0.247             1.000             0.023
## Alexa Fluor 488-A             0.001             0.053             1.000
cols = c("APC-A", "APC-Cy7-A")
round(fs.spill[cols, cols], 3)
##           APC-A APC-Cy7-A
## APC-A     1.000     0.068
## APC-Cy7-A 0.075     1.000
cols = c("PE-A", "PE-Cy5-5-A", "PE-Cy5-A", "PE-Cy7-A", "PE-Texas Red-A")
round(fs.spill[cols, cols], 3)
##                 PE-A PE-Cy5-5-A PE-Cy5-A PE-Cy7-A PE-Texas Red-A
## PE-A           1.000      0.425    0.048    0.014          1.567
## PE-Cy5-5-A     0.028      1.000    0.017    0.056          0.041
## PE-Cy5-A       0.023     10.262    1.000    0.392          0.043
## PE-Cy7-A       0.011      0.058    0.001    1.000          0.019
## PE-Texas Red-A 0.118      0.482    0.050    0.020          1.000
# Graphical overview
# Thichk line is unstained sample
matplot(asinh(t(fsApply(fs, each_col, median))/150), typ = "l", lwd = rep(c(2, 5), times = c(14,1)), xlab = "Channels", ylab = "asinh(intensity/150)")

ALTERNATE SOLUTION

# Read compensation files
fs = read.flowSet(pattern = "Compensatio.+fcs", path = "OMIP-018_FR-FCM-ZZ36")
pData(fs)
name
Compensation Controls_Alexa Fluor 405 Stained Control.fcs Compensation Controls_Alexa Fluor 405 Stained Control.fcs
Compensation Controls_Alexa Fluor 430 Stained Control.fcs Compensation Controls_Alexa Fluor 430 Stained Control.fcs
Compensation Controls_Alexa Fluor 488 Stained Control.fcs Compensation Controls_Alexa Fluor 488 Stained Control.fcs
Compensation Controls_APC-Cy7 Stained Control.fcs Compensation Controls_APC-Cy7 Stained Control.fcs
Compensation Controls_APC Stained Control.fcs Compensation Controls_APC Stained Control.fcs
Compensation Controls_PE-Cy5-5 Stained Control.fcs Compensation Controls_PE-Cy5-5 Stained Control.fcs
Compensation Controls_PE-Cy5 Stained Control.fcs Compensation Controls_PE-Cy5 Stained Control.fcs
Compensation Controls_PE-Cy7 Stained Control.fcs Compensation Controls_PE-Cy7 Stained Control.fcs
Compensation Controls_PE-Texas Red Stained Control.fcs Compensation Controls_PE-Texas Red Stained Control.fcs
Compensation Controls_PE Stained Control.fcs Compensation Controls_PE Stained Control.fcs
Compensation Controls_PerCP-Cy5-5 Stained Control.fcs Compensation Controls_PerCP-Cy5-5 Stained Control.fcs
Compensation Controls_Qdot 605 Stained Control.fcs Compensation Controls_Qdot 605 Stained Control.fcs
Compensation Controls_Qdot 655 Stained Control.fcs Compensation Controls_Qdot 655 Stained Control.fcs
Compensation Controls_Qdot 800 Stained Control.fcs Compensation Controls_Qdot 800 Stained Control.fcs
Compensation Controls_Unstained Control.fcs Compensation Controls_Unstained Control.fcs
colnames(fs)
##  [1] "FSC-A"             "SSC-A"             "Alexa Fluor 488-A"
##  [4] "PerCP-Cy5-5-A"     "APC-A"             "APC-Cy7-A"        
##  [7] "Alexa Fluor 405-A" "Alexa Fluor 430-A" "Qdot 605-A"       
## [10] "Qdot 655-A"        "Qdot 800-A"        "PE-A"             
## [13] "PE-Texas Red-A"    "PE-Cy5-A"          "PE-Cy5-5-A"       
## [16] "PE-Cy7-A"          "Time"
# Change sampleNames that are matched against colnames (aka name of markers)
# Add -A in order to match the suffix of colnames (except Time)
sampleNames(fs) = gsub("Compensation Controls_", "", 
                       gsub(" Stained Control.fcs", "-A",
                            sampleNames(fs)))
# Check that there is only 1 match per compensated marker
sapply(colnames(fs), grep, x = sampleNames(fs), fixed = TRUE)
## $`FSC-A`
## integer(0)
## 
## $`SSC-A`
## integer(0)
## 
## $`Alexa Fluor 488-A`
## [1] 3
## 
## $`PerCP-Cy5-5-A`
## [1] 11
## 
## $`APC-A`
## [1] 5
## 
## $`APC-Cy7-A`
## [1] 4
## 
## $`Alexa Fluor 405-A`
## [1] 1
## 
## $`Alexa Fluor 430-A`
## [1] 2
## 
## $`Qdot 605-A`
## [1] 12
## 
## $`Qdot 655-A`
## [1] 13
## 
## $`Qdot 800-A`
## [1] 14
## 
## $`PE-A`
## [1] 10
## 
## $`PE-Texas Red-A`
## [1] 9
## 
## $`PE-Cy5-A`
## [1] 7
## 
## $`PE-Cy5-5-A`
## [1] 6
## 
## $`PE-Cy7-A`
## [1] 8
## 
## $Time
## integer(0)
# Check the pattern 
grep("-A$", colnames(fs), value = TRUE)
##  [1] "FSC-A"             "SSC-A"             "Alexa Fluor 488-A"
##  [4] "PerCP-Cy5-5-A"     "APC-A"             "APC-Cy7-A"        
##  [7] "Alexa Fluor 405-A" "Alexa Fluor 430-A" "Qdot 605-A"       
## [10] "Qdot 655-A"        "Qdot 800-A"        "PE-A"             
## [13] "PE-Texas Red-A"    "PE-Cy5-A"          "PE-Cy5-5-A"       
## [16] "PE-Cy7-A"
# Process
# fsc, ssc are declared in oreder to remove them from markers to compensate
fs.spill = spillover(fs,
                     unstained = length(fs),   # unstained is the last file
                     patt = "-A$",             # all parameters end with "-A"
                     fsc = colnames(fs)[1],    # FSC name is at the 1st column
                     ssc = colnames(fs)[2],    # SSC name is at the 2nd column
                     stain_match = "regexpr")  # automagic matching
# Inspection
round(fs.spill, 3)
##                   Alexa Fluor 488-A PerCP-Cy5-5-A APC-A APC-Cy7-A
## Alexa Fluor 488-A             1.000         0.018 0.000     0.000
## PerCP-Cy5-5-A                 0.000         1.000 0.012     0.024
## APC-A                         0.000         0.013 1.000     0.068
## APC-Cy7-A                     0.000         0.002 0.075     1.000
## Alexa Fluor 405-A             0.000         0.000 0.000     0.000
## Alexa Fluor 430-A             0.023         0.001 0.000     0.000
## Qdot 605-A                    0.000         0.000 0.000     0.000
## Qdot 655-A                    0.000         0.006 0.012     0.000
## Qdot 800-A                    0.000         0.019 0.000     0.027
## PE-A                          0.006         0.117 0.000     0.000
## PE-Texas Red-A                0.001         0.125 0.001     0.000
## PE-Cy5-A                      0.001         2.448 1.110     0.073
## PE-Cy5-5-A                    0.001         0.274 0.006     0.003
## PE-Cy7-A                      0.002         0.022 0.000     0.047
##                   Alexa Fluor 405-A Alexa Fluor 430-A Qdot 605-A
## Alexa Fluor 488-A             0.001             0.053      0.029
## PerCP-Cy5-5-A                 0.000             0.000      0.000
## APC-A                         0.001             0.001      0.004
## APC-Cy7-A                     0.002             0.001      0.003
## Alexa Fluor 405-A             1.000             0.041      0.011
## Alexa Fluor 430-A             0.247             1.000      0.756
## Qdot 605-A                    0.000             0.000      1.000
## Qdot 655-A                    0.032             0.002      0.023
## Qdot 800-A                    0.000             0.000      0.000
## PE-A                          0.000             0.002      0.336
## PE-Texas Red-A                0.000             0.001      0.210
## PE-Cy5-A                      0.000             0.000      0.009
## PE-Cy5-5-A                    0.003             0.012      0.019
## PE-Cy7-A                      0.001             0.000      0.005
##                   Qdot 655-A Qdot 800-A  PE-A PE-Texas Red-A PE-Cy5-A
## Alexa Fluor 488-A      0.011      0.001 0.000          0.000    0.000
## PerCP-Cy5-5-A          0.050      0.548 0.000          0.000    0.008
## APC-A                  0.692      0.060 0.000          0.003    0.076
## APC-Cy7-A              0.052      1.105 0.001          0.001    0.006
## Alexa Fluor 405-A      0.004      0.001 0.000          0.000    0.000
## Alexa Fluor 430-A      0.311      0.022 0.000          0.000    0.000
## Qdot 605-A             0.016      0.000 0.002          0.084    0.000
## Qdot 655-A             1.000      0.001 0.000          0.002    0.008
## Qdot 800-A             0.001      1.000 0.000          0.000    0.000
## PE-A                   0.130      0.008 1.000          1.567    0.048
## PE-Texas Red-A         0.113      0.011 0.118          1.000    0.050
## PE-Cy5-A               1.427      0.181 0.023          0.043    1.000
## PE-Cy5-5-A             0.017      0.032 0.028          0.041    0.017
## PE-Cy7-A               0.003      0.543 0.011          0.019    0.001
##                   PE-Cy5-5-A PE-Cy7-A
## Alexa Fluor 488-A      0.000    0.000
## PerCP-Cy5-5-A          0.718    0.047
## APC-A                  0.491    0.028
## APC-Cy7-A              0.062    0.388
## Alexa Fluor 405-A      0.000    0.000
## Alexa Fluor 430-A      0.000    0.000
## Qdot 605-A             0.000    0.000
## Qdot 655-A             0.011    0.000
## Qdot 800-A             0.032    0.089
## PE-A                   0.425    0.014
## PE-Texas Red-A         0.482    0.020
## PE-Cy5-A              10.262    0.392
## PE-Cy5-5-A             1.000    0.056
## PE-Cy7-A               0.058    1.000
cols = c("Alexa Fluor 405-A", "Alexa Fluor 430-A", "Alexa Fluor 488-A")
round(fs.spill[cols, cols], 3)
##                   Alexa Fluor 405-A Alexa Fluor 430-A Alexa Fluor 488-A
## Alexa Fluor 405-A             1.000             0.041             0.000
## Alexa Fluor 430-A             0.247             1.000             0.023
## Alexa Fluor 488-A             0.001             0.053             1.000
cols = c("APC-A", "APC-Cy7-A")
round(fs.spill[cols, cols], 3)
##           APC-A APC-Cy7-A
## APC-A     1.000     0.068
## APC-Cy7-A 0.075     1.000
cols = c("PE-A", "PE-Cy5-5-A", "PE-Cy5-A", "PE-Cy7-A", "PE-Texas Red-A")
round(fs.spill[cols, cols], 3)
##                 PE-A PE-Cy5-5-A PE-Cy5-A PE-Cy7-A PE-Texas Red-A
## PE-A           1.000      0.425    0.048    0.014          1.567
## PE-Cy5-5-A     0.028      1.000    0.017    0.056          0.041
## PE-Cy5-A       0.023     10.262    1.000    0.392          0.043
## PE-Cy7-A       0.011      0.058    0.001    1.000          0.019
## PE-Texas Red-A 0.118      0.482    0.050    0.020          1.000

ALTERNATE SOLUTION with normFilt

# Read compensation files
fs = read.flowSet(pattern = "Compensatio.+fcs", path = "OMIP-018_FR-FCM-ZZ36")
pData(fs)
name
Compensation Controls_Alexa Fluor 405 Stained Control.fcs Compensation Controls_Alexa Fluor 405 Stained Control.fcs
Compensation Controls_Alexa Fluor 430 Stained Control.fcs Compensation Controls_Alexa Fluor 430 Stained Control.fcs
Compensation Controls_Alexa Fluor 488 Stained Control.fcs Compensation Controls_Alexa Fluor 488 Stained Control.fcs
Compensation Controls_APC-Cy7 Stained Control.fcs Compensation Controls_APC-Cy7 Stained Control.fcs
Compensation Controls_APC Stained Control.fcs Compensation Controls_APC Stained Control.fcs
Compensation Controls_PE-Cy5-5 Stained Control.fcs Compensation Controls_PE-Cy5-5 Stained Control.fcs
Compensation Controls_PE-Cy5 Stained Control.fcs Compensation Controls_PE-Cy5 Stained Control.fcs
Compensation Controls_PE-Cy7 Stained Control.fcs Compensation Controls_PE-Cy7 Stained Control.fcs
Compensation Controls_PE-Texas Red Stained Control.fcs Compensation Controls_PE-Texas Red Stained Control.fcs
Compensation Controls_PE Stained Control.fcs Compensation Controls_PE Stained Control.fcs
Compensation Controls_PerCP-Cy5-5 Stained Control.fcs Compensation Controls_PerCP-Cy5-5 Stained Control.fcs
Compensation Controls_Qdot 605 Stained Control.fcs Compensation Controls_Qdot 605 Stained Control.fcs
Compensation Controls_Qdot 655 Stained Control.fcs Compensation Controls_Qdot 655 Stained Control.fcs
Compensation Controls_Qdot 800 Stained Control.fcs Compensation Controls_Qdot 800 Stained Control.fcs
Compensation Controls_Unstained Control.fcs Compensation Controls_Unstained Control.fcs
colnames(fs)
##  [1] "FSC-A"             "SSC-A"             "Alexa Fluor 488-A"
##  [4] "PerCP-Cy5-5-A"     "APC-A"             "APC-Cy7-A"        
##  [7] "Alexa Fluor 405-A" "Alexa Fluor 430-A" "Qdot 605-A"       
## [10] "Qdot 655-A"        "Qdot 800-A"        "PE-A"             
## [13] "PE-Texas Red-A"    "PE-Cy5-A"          "PE-Cy5-5-A"       
## [16] "PE-Cy7-A"          "Time"
# Change sampleNames that are matched against colnames (aka name of markers)
# Add -A in order to match the suffix of colnames (except Time)
sampleNames(fs) = gsub("Compensation Controls_", "", 
                       gsub(" Stained Control.fcs", "-A",
                            sampleNames(fs)))
# Check that there is only 1 match per compensated marker
sapply(colnames(fs), grep, x = sampleNames(fs), fixed = TRUE)
## $`FSC-A`
## integer(0)
## 
## $`SSC-A`
## integer(0)
## 
## $`Alexa Fluor 488-A`
## [1] 3
## 
## $`PerCP-Cy5-5-A`
## [1] 11
## 
## $`APC-A`
## [1] 5
## 
## $`APC-Cy7-A`
## [1] 4
## 
## $`Alexa Fluor 405-A`
## [1] 1
## 
## $`Alexa Fluor 430-A`
## [1] 2
## 
## $`Qdot 605-A`
## [1] 12
## 
## $`Qdot 655-A`
## [1] 13
## 
## $`Qdot 800-A`
## [1] 14
## 
## $`PE-A`
## [1] 10
## 
## $`PE-Texas Red-A`
## [1] 9
## 
## $`PE-Cy5-A`
## [1] 7
## 
## $`PE-Cy5-5-A`
## [1] 6
## 
## $`PE-Cy7-A`
## [1] 8
## 
## $Time
## integer(0)
# Check the pattern 
grep("-A$", colnames(fs), value = TRUE)
##  [1] "FSC-A"             "SSC-A"             "Alexa Fluor 488-A"
##  [4] "PerCP-Cy5-5-A"     "APC-A"             "APC-Cy7-A"        
##  [7] "Alexa Fluor 405-A" "Alexa Fluor 430-A" "Qdot 605-A"       
## [10] "Qdot 655-A"        "Qdot 800-A"        "PE-A"             
## [13] "PE-Texas Red-A"    "PE-Cy5-A"          "PE-Cy5-5-A"       
## [16] "PE-Cy7-A"
# Process
fs.spill = spillover(fs,
                     unstained = length(fs),   # unstained is the last file
                     patt = "-A$",             # all parameters end with "-A"
                     fsc = colnames(fs)[1],    # FSC name is at the 1st column
                     ssc = colnames(fs)[2],    # SSC name is at the 2nd column
                     stain_match = "regexpr",  # automagic matching
                     useNormFilt = TRUE)
# Inspection
round(fs.spill, 3)
##                   Alexa Fluor 488-A PerCP-Cy5-5-A APC-A APC-Cy7-A
## Alexa Fluor 488-A             1.000         0.018 0.000     0.000
## PerCP-Cy5-5-A                 0.000         1.000 0.012     0.024
## APC-A                         0.000         0.013 1.000     0.068
## APC-Cy7-A                     0.000         0.002 0.075     1.000
## Alexa Fluor 405-A             0.000         0.000 0.000     0.000
## Alexa Fluor 430-A             0.023         0.001 0.000     0.000
## Qdot 605-A                    0.000         0.000 0.000     0.000
## Qdot 655-A                    0.000         0.006 0.012     0.000
## Qdot 800-A                    0.000         0.019 0.000     0.027
## PE-A                          0.006         0.117 0.000     0.000
## PE-Texas Red-A                0.001         0.125 0.001     0.000
## PE-Cy5-A                      0.001         2.448 1.110     0.073
## PE-Cy5-5-A                    0.001         0.274 0.006     0.003
## PE-Cy7-A                      0.002         0.022 0.000     0.047
##                   Alexa Fluor 405-A Alexa Fluor 430-A Qdot 605-A
## Alexa Fluor 488-A             0.001             0.053      0.029
## PerCP-Cy5-5-A                 0.000             0.000      0.000
## APC-A                         0.001             0.001      0.004
## APC-Cy7-A                     0.002             0.001      0.003
## Alexa Fluor 405-A             1.000             0.041      0.011
## Alexa Fluor 430-A             0.247             1.000      0.756
## Qdot 605-A                    0.000             0.000      1.000
## Qdot 655-A                    0.032             0.002      0.023
## Qdot 800-A                    0.000             0.000      0.000
## PE-A                          0.000             0.002      0.336
## PE-Texas Red-A                0.000             0.001      0.210
## PE-Cy5-A                      0.000             0.000      0.009
## PE-Cy5-5-A                    0.003             0.012      0.019
## PE-Cy7-A                      0.001             0.000      0.005
##                   Qdot 655-A Qdot 800-A  PE-A PE-Texas Red-A PE-Cy5-A
## Alexa Fluor 488-A      0.011      0.001 0.000          0.000    0.000
## PerCP-Cy5-5-A          0.050      0.548 0.000          0.000    0.008
## APC-A                  0.692      0.060 0.000          0.003    0.076
## APC-Cy7-A              0.052      1.105 0.001          0.001    0.006
## Alexa Fluor 405-A      0.004      0.001 0.000          0.000    0.000
## Alexa Fluor 430-A      0.311      0.022 0.000          0.000    0.000
## Qdot 605-A             0.016      0.000 0.002          0.084    0.000
## Qdot 655-A             1.000      0.001 0.000          0.002    0.008
## Qdot 800-A             0.001      1.000 0.000          0.000    0.000
## PE-A                   0.130      0.008 1.000          1.567    0.048
## PE-Texas Red-A         0.113      0.011 0.118          1.000    0.050
## PE-Cy5-A               1.427      0.181 0.023          0.043    1.000
## PE-Cy5-5-A             0.017      0.032 0.028          0.041    0.017
## PE-Cy7-A               0.003      0.543 0.011          0.019    0.001
##                   PE-Cy5-5-A PE-Cy7-A
## Alexa Fluor 488-A      0.000    0.000
## PerCP-Cy5-5-A          0.718    0.047
## APC-A                  0.491    0.028
## APC-Cy7-A              0.062    0.388
## Alexa Fluor 405-A      0.000    0.000
## Alexa Fluor 430-A      0.000    0.000
## Qdot 605-A             0.000    0.000
## Qdot 655-A             0.011    0.000
## Qdot 800-A             0.032    0.089
## PE-A                   0.425    0.014
## PE-Texas Red-A         0.482    0.020
## PE-Cy5-A              10.262    0.392
## PE-Cy5-5-A             1.000    0.056
## PE-Cy7-A               0.058    1.000
cols = c("Alexa Fluor 405-A", "Alexa Fluor 430-A", "Alexa Fluor 488-A")
round(fs.spill[cols, cols], 3)
##                   Alexa Fluor 405-A Alexa Fluor 430-A Alexa Fluor 488-A
## Alexa Fluor 405-A             1.000             0.041             0.000
## Alexa Fluor 430-A             0.247             1.000             0.023
## Alexa Fluor 488-A             0.001             0.053             1.000
cols = c("APC-A", "APC-Cy7-A")
round(fs.spill[cols, cols], 3)
##           APC-A APC-Cy7-A
## APC-A     1.000     0.068
## APC-Cy7-A 0.075     1.000
cols = c("PE-A", "PE-Cy5-5-A", "PE-Cy5-A", "PE-Cy7-A", "PE-Texas Red-A")
round(fs.spill[cols, cols], 3)
##                 PE-A PE-Cy5-5-A PE-Cy5-A PE-Cy7-A PE-Texas Red-A
## PE-A           1.000      0.425    0.048    0.014          1.567
## PE-Cy5-5-A     0.028      1.000    0.017    0.056          0.041
## PE-Cy5-A       0.023     10.262    1.000    0.392          0.043
## PE-Cy7-A       0.011      0.058    0.001    1.000          0.019
## PE-Texas Red-A 0.118      0.482    0.050    0.020          1.000

FlowJo results

A FlowJo workspace (.jo file) is attached with the OMIP-018 dataset. Although I don’t master FlowJo (v10.4), it’s quite easy to carry out compensation. Put the .jo file with the FCS file, open it with FowJo and click the compensation icon. The result that I get without any tweak is the following. It matches nearly exactly to the flowCore results.

FlowJo full matrix

FlowJo full matrix

FlowJo full matrix

FlowJo full matrix

Here are some closeup details.

Alexa trio, APC duo

FlowJo Alexa trio, APC duo

FlowJo Alexa trio, APC duo

PE quintet

FlowJo PE quintet

FlowJo PE quintet

INTENSITY POST-MATCHING

I don’t recommend such an un-controlled matching.

# Read compensation files
fs = read.flowSet(path = "OMIP-018_FR-FCM-ZZ36", files = c(
  dir(pattern = "Unstained.+fcs", path = "OMIP-018_FR-FCM-ZZ36"),
  dir(pattern = "Compensatio.+Alexa.+fcs", path = "OMIP-018_FR-FCM-ZZ36")))
pData(fs)
name
Compensation Controls_Unstained Control.fcs Compensation Controls_Unstained Control.fcs
Compensation Controls_Alexa Fluor 405 Stained Control.fcs Compensation Controls_Alexa Fluor 405 Stained Control.fcs
Compensation Controls_Alexa Fluor 430 Stained Control.fcs Compensation Controls_Alexa Fluor 430 Stained Control.fcs
Compensation Controls_Alexa Fluor 488 Stained Control.fcs Compensation Controls_Alexa Fluor 488 Stained Control.fcs
colnames(fs)
##  [1] "FSC-A"             "SSC-A"             "Alexa Fluor 488-A"
##  [4] "PerCP-Cy5-5-A"     "APC-A"             "APC-Cy7-A"        
##  [7] "Alexa Fluor 405-A" "Alexa Fluor 430-A" "Qdot 605-A"       
## [10] "Qdot 655-A"        "Qdot 800-A"        "PE-A"             
## [13] "PE-Texas Red-A"    "PE-Cy5-A"          "PE-Cy5-5-A"       
## [16] "PE-Cy7-A"          "Time"
# Check the pattern 
grep("Alexa.+-A$", colnames(fs), value = TRUE)
## [1] "Alexa Fluor 488-A" "Alexa Fluor 405-A" "Alexa Fluor 430-A"
# Process
# default FSC, SSC channels are ignored if useNormFilt is FALSE (default)
fs.spill = spillover(fs,
                     unstained = 1,            # unstained is the 1st file
                     patt = "Alexa.+-A$",      # all parameters end with "-A"
                     stain_match = "intensity")
# Inspection
round(fs.spill, 3)
##                   Alexa Fluor 488-A Alexa Fluor 405-A Alexa Fluor 430-A
## Alexa Fluor 488-A             1.000             0.001             0.053
## Alexa Fluor 405-A             0.000             1.000             0.041
## Alexa Fluor 430-A             0.023             0.247             1.000
cols = c("Alexa Fluor 405-A", "Alexa Fluor 430-A", "Alexa Fluor 488-A")
round(fs.spill[cols, cols], 3)
##                   Alexa Fluor 405-A Alexa Fluor 430-A Alexa Fluor 488-A
## Alexa Fluor 405-A             1.000             0.041             0.000
## Alexa Fluor 430-A             0.247             1.000             0.023
## Alexa Fluor 488-A             0.001             0.053             1.000

Session information

sessionInfo()
## R version 3.3.3 (2017-03-06)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 7 x64 (build 7601) Service Pack 1
## 
## locale:
## [1] LC_COLLATE=English_United Kingdom.1252 
## [2] LC_CTYPE=English_United Kingdom.1252   
## [3] LC_MONETARY=English_United Kingdom.1252
## [4] LC_NUMERIC=C                           
## [5] LC_TIME=English_United Kingdom.1252    
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] flowCore_1.40.6
## 
## loaded via a namespace (and not attached):
##  [1] graph_1.52.0        Rcpp_0.12.13        knitr_1.17         
##  [4] cluster_2.0.6       magrittr_1.5        BiocGenerics_0.20.0
##  [7] lattice_0.20-35     rrcov_1.4-3         pcaPP_1.9-72       
## [10] highr_0.6           stringr_1.2.0       tools_3.3.3        
## [13] parallel_3.3.3      grid_3.3.3          Biobase_2.34.0     
## [16] corpcor_1.6.9       htmltools_0.3.6     matrixStats_0.52.2 
## [19] yaml_2.1.14         rprojroot_1.2       digest_0.6.12      
## [22] robustbase_0.92-7   evaluate_0.10.1     rmarkdown_1.6      
## [25] stringi_1.1.5       DEoptimR_1.0-8      backports_1.1.0    
## [28] stats4_3.3.3        mvtnorm_1.0-6