Skip to contents

Introduction

staRgate is an automated gating pipeline to process and analyze flow cytometry data to characterize the lineage, differentiation and functional states of T-cells.

This pipeline is designed to mimic the manual gating of defining flow biomarker positive populations relative to a unimodal background population to include cells with varying intensities of marker expression. This is achieved via estimating the kernel density of the intensity distribution and corresponding derivatives. This pipeline integrates the density gating method in conjuction with some pre-processing steps achieved via the R package openCyto and an optional step with flowAI. The flow data is stored within R as a GatingSet object, which makes it easily transferable to other flow cytometry workflows available on BioConductor.

This vignette will walk through how to run the {staRgate} pipeline starting from importing an flow cytometry standard (FCS) file into R to preprocessing and gating, as well as identifying T-cell subpopulations for downstream analysis. This pipeline returns results at the single-cell level as well as the summarized sample-level data (for the percentages of T-cell cells when identifying the subpopulations).

For illustration purposes, the example FCS file used in this vignette and stored in the package data is a concatenated file limited to the first 30k events/cells acquired to reduce the run time and file size.

After running {staRgate} to gate flow cytometry data, it is recommended to perform some quality checks (QC) on the gate placements to ensure they are reasonable. We suggest to use ridgeplots in addition to the ggcyto::autoplot to visualize the density distributions per marker across samples. When examining a large batch of samples, downsampling, such as to a random sample of 10k CD3+ cells, will make the QC process more manageable. In addition, random spot checks of a few samples would also be helpful QC to detect any edge cases.

Currently in this tutorial, we do not extend to the QC steps and suggest to lean on other examples for how to put together a ridgeplot for example. In the near future, we hope to incorporate some examples for the additional QC steps as well, stay tuned!

Installation

The {staRgate} package relies on a few Biocondcutor R packages. Before installing {staRgate}, first setup Bioconductor and install all dependencies.

Below instructions are instructions for installing Bioconductor

if (!require("BiocManager", quietly = TRUE)) {
  install.packages("BiocManager")
}

The essential dependencies for running the {staRgate} package include: flowCore, and flowWorkspace.

The following packages are needed to fully run the pipeline as shown in this Tutorial, but not required for the {staRgate} code chunks to run: openCyto, flowAI, ggplot2, ggcyto, gt

# Load libraries
library(staRgate)
library(openCyto)
library(flowWorkspace)
#> As part of improvements to flowWorkspace, some behavior of
#> GatingSet objects has changed. For details, please read the section
#> titled "The cytoframe and cytoset classes" in the package vignette:
#> 
#>   vignette("flowWorkspace-Introduction", "flowWorkspace")
library(flowCore)
# Just for plotting in the vignette
library(ggplot2)

# Set up dynamic variables
pt_samp_nm <- "flow_sample_1"

## File path to the FCS file
path_fcs <- system.file("extdata", "example_fcs.fcs", package = "staRgate", mustWork = TRUE)

## File path to the compensation matrix csv file
## Expect format to match flowJo exported version
path_comp_mat <- system.file("extdata", "comp_mat_example_fcs.csv", package = "staRgate", mustWork = TRUE)

## File path for outputs/saving
# Maybe not the best sol, but create a temp dir?
path_out <- tempdir()
# Print the path_out for user to see
path_out
#> [1] "/tmp/RtmpR7t2EM"

## File path Gating template
gtFile <- system.file("extdata", "gating_template_x50_tcell.csv", package = "staRgate", mustWork = TRUE)

## File path to biexp parameters
## Expects 4 columns: full_name, ext_neg_dec, width_basis, positive_dec
## full name should contain the channel/dye name
# 3 remaining cols fill in with desired parameter values
path_biexp_params <- system.file("extdata", "biexp_transf_parameters_x50.csv", package = "staRgate", mustWork = TRUE)

## File path to positive peak thresholds
path_pos_peak_thresholds <- system.file("extdata", "pos_peak_thresholds.csv", package = "staRgate", mustWork = TRUE)

Input files and function parameters

In order to run the pipeline, the user must have the data in flow cytometry standard (FCS) format. This is usually the output from flowJo. All of these input files are expected to be comma-separated values (csv) files. Please see the inst folder of the package for examples of the formats.

  • Compensation matrix from manual gating-
    • Matrix where the column and row names correspond to the channel names, cell values correspond to the spillover correction to be applied.
    • This can be exported as a csv in flowJo’s options
  • Biexponential transformation parameters-
    • This is a table specifying the parameters (negative decades, width basis and positive decades) to be applied to the listed channels
  • Gating template
    • A gating template is required to run the pre-gating via openCyto
    • The package includes a gating template tailored for gating this panel of T-cell markers.
    • For examples of how to modify the gating template, please refer to the openCyto documentation.
  • Bin size
    • From a systematic grid search, we have found bin sizes of 40 to 50 works well for the density gating

A few things to keep in mind when debugging/iterating through gating:

  • If saving at the same path with same name (i.e., rerunning the same code), the GatingSet folder from the flowWorkspace::save_gs() command needs to be deleted for openCyto to save again, otherwise, will encounter an error related to an invalid path from the flowWorkspace::save_gs() function

Example

Below is an example of gating 1 FCS sample.

Optional: include a samp_metadata for saving out some properties of the FCS file and from the processing steps (e.g., the compensation matrix, the number of cells in the root population)

# Optional to save the samp_metadata
samp_metadata <- list()

Import FCS

# Read in gating template
dtTemplate <- data.table::fread(gtFile)

# Load the FCS
gt_tcell <- openCyto::gatingTemplate(gtFile)
#> expanding pop: -/++/-
#> Adding population:fsc_ssc_qc
#> Adding population:nonDebris
#> Adding population:singlets
#> Adding population:cd14-cd19-
#> Adding population:live
#> Adding population:cd3
#> Adding population:cd4+
#> Adding population:cd8+
#> Adding population:cd4+cd8+
#> Adding population:cd4-cd8+
#> Adding population:cd4+cd8-
#> Adding population:cd4-cd8-

cs <- flowWorkspace::load_cytoset_from_fcs(path_fcs)

# **Optional** -
# For consistency among different naming conventions for the live/dead
# change "Viability" or "L/D" "LD" or "live" to = "L_D" for consistency on gtemplates etc
{
  if (any(c("Viability", "L/D", "L_D", "live", "Live") %in% flowWorkspace::markernames(cs))) {
    aux_log <- flowWorkspace::markernames(cs) == "Viability" | flowWorkspace::markernames(cs) == "L/D" | flowWorkspace::markernames(cs) == "L_D"

    flowWorkspace::markernames(cs) <- replace(markernames(cs), aux_log, "LD")
  }
}

# Create a GatingSet of 1 sample
gs <- flowWorkspace::GatingSet(cs)

# Check- how many cells is in the FCS file?
n_root <- flowWorkspace::gh_pop_get_count(gs, "root")

n_root
#> [1] 30000

# Add n_root to samp_metadata
samp_metadata$n_root <- n_root

Compensation

# Check no comp applied
chk_cm <- flowWorkspace::gh_get_compensations(gs)

chk_cm
#> NULL

# Apply comp
gs <- getCompGS(gs, path_comp_mat = path_comp_mat)

# Can check that the comp was applied
chk_cm <- flowWorkspace::gh_get_compensations(gs)

# Not aware of an accessor that we can use for this
head(methods::slot(chk_cm, "spillover"))
#>                 AF700-A       APC-A   APC-f750-A      BB515-A    BB660-A
#> AF700-A     1.00000e+00 2.48301e-02  2.49284e-01  0.009679220 0.00268438
#> APC-A       1.32334e-01 1.00000e+00  3.37338e-02 -0.000160633 0.01598550
#> APC-f750-A  8.68539e-02 1.38732e-01  1.00000e+00 -0.001767070 0.00222729
#> BB515-A    -1.15670e-06 8.78380e-06 -3.46330e-06  1.000000000 0.00737752
#> BB660-A     8.87227e-02 5.93124e-01  2.00381e-02  0.014618200 1.00000000
#> BB700-A     1.56721e-01 1.03493e-01  5.16101e-02  0.021794700 0.23758800
#>               BB700-A     BB790-A    BUV395-A     BUV496-A     BUV563-A
#> AF700-A    0.04732480 0.017307700 0.001813870  1.81722e-03  1.45041e-03
#> APC-A      0.00443383 0.001225970 0.000471851 -5.18352e-05 -3.25396e-05
#> APC-f750-A 0.00135711 0.052727800 0.007016350 -3.55365e-04 -1.00153e-03
#> BB515-A    0.00160540 0.000259797 0.000488654  9.92731e-03  2.45334e-03
#> BB660-A    0.29925900 0.072568700 0.000750601  4.69872e-04  5.91621e-03
#> BB700-A    1.00000000 0.305046000 0.001371840  5.33863e-04  7.68645e-03
#>               BUV615-A    BUV661-A     BUV737-A     BUV805-A      BV421-A
#> AF700-A    0.002494550 0.002203370  1.38716e-01  0.033104200  5.11994e-05
#> APC-A      0.001271670 0.114363000  2.21613e-02  0.005274360 -4.09868e-04
#> APC-f750-A 0.000055023 0.013948700  3.17988e-02  0.249248000 -7.88098e-03
#> BB515-A    0.001525590 0.000265054 -1.52650e-06 -0.000011894 -3.13395e-04
#> BB660-A    0.013022400 0.199251000  3.28067e-02  0.008413150 -8.18947e-04
#> BB700-A    0.012374800 0.042845800  1.02680e-01  0.034781800 -1.40587e-03
#>                BV480-A     BV510-A      BV570-A     BV605-A     BV650-A
#> AF700-A    0.003390200 2.60901e-03  2.45899e-03 0.003585150 0.004457680
#> APC-A      0.000113668 4.37466e-05  5.85607e-05 0.000850790 0.138441000
#> APC-f750-A 0.003118890 1.52763e-03 -9.31873e-04 0.000173277 0.016350600
#> BB515-A    0.000190708 3.34812e-03  8.84301e-04 0.000579157 0.000126033
#> BB660-A    0.000388549 2.12014e-03  2.71519e-02 0.049095400 0.692174000
#> BB700-A    0.000586408 3.01256e-03  3.65599e-02 0.059112300 0.173298000
#>                BV711-A      BV750-A      BV786-A         PE-A   PE-CF594-A
#> AF700-A    2.12730e-01  8.48399e-02  4.39476e-02  0.006840740  2.92636e-02
#> APC-A      2.85134e-02  9.73321e-03  4.17643e-03  0.000280757  5.41964e-03
#> APC-f750-A 5.86523e-03  6.38759e-02  2.26547e-01 -0.002460560  7.43340e-03
#> BB515-A    8.56130e-06 -1.02317e-05 -1.94972e-05 -0.000126331 -7.83382e-05
#> BB660-A    1.61692e-01  4.23041e-02  2.02054e-02  0.000124760  1.78335e-03
#> BB700-A    5.56872e-01  1.49641e-01  8.27345e-02  0.000280215  7.49651e-04
#>                PE-Cy5-A   PE-Cy5.5-A     PE-Cy7-A
#> AF700-A     1.64829e-02  3.26051e-01  1.03233e-01
#> APC-A       2.67747e-01  8.10869e-02  2.15633e-02
#> APC-f750-A  4.93700e-02  2.25955e-02  6.09145e-01
#> BB515-A    -6.68526e-05 -3.01727e-05 -1.21999e-05
#> BB660-A     7.25687e-02  2.16664e-02  5.19117e-03
#> BB700-A     2.13325e-02  6.47322e-02  1.92574e-02

# **Optional**- save the comp mat matrix
# Can grab with the @spillover from the compensation object
samp_metadata$comp_mat <-
  methods::slot(chk_cm, "spillover")

Transformation

The transformation applied to all channels is the same: biexponential with extra negative decades = 0.5, positive decades = 4.5 and width basis = -30

The structure of the table of parameters (as .csv format) should be first column for the flurochrome names corresponding to the panel, followed by the parameters.

tbl_biexp_params <-
  utils::read.csv(path_biexp_params) |>
  janitor::clean_names(case = "all_caps")

# **Optional**-- saving out the table of parameters as format that's easier to read
# Below there is an option to save it out as the transformerList which is what
# getBiexpTransformGS() relies on to supply to flowWorkspace::transform
samp_metadata$biexp_params <- tbl_biexp_params

# The biexp table
gt::gt(tbl_biexp_params)
FULL_NAME EXT_NEG_DEC WIDTH_BASIS POSITIVE_DEC
BUV395-A 0.5 -30 4.5
BUV496-A 0.5 -30 4.5
BUV563-A 0.5 -30 4.5
BUV615-A 0.5 -30 4.5
BUV661-A 0.5 -30 4.5
BUV737-A 0.5 -30 4.5
BUV805-A 0.5 -30 4.5
BV421-A 0.5 -30 4.5
BV480-A 0.5 -30 4.5
BV510-A 0.5 -30 4.5
BV570-A 0.5 -30 4.5
BV605-A 0.5 -30 4.5
BV650-A 0.5 -30 4.5
BV711-A 0.5 -30 4.5
BV750-A 0.5 -30 4.5
BV786-A 0.5 -30 4.5
BB515-A 0.5 -30 4.5
BB660-A 0.5 -30 4.5
BB700-A 0.5 -30 4.5
BB790-A 0.5 -30 4.5
PE-A 0.5 -30 4.5
PE-CF594-A 0.5 -30 4.5
PE-Cy5-A 0.5 -30 4.5
PE-Cy5.5-A 0.5 -30 4.5
PE-Cy7-A 0.5 -30 4.5
APC-A 0.5 -30 4.5
AF700-A 0.5 -30 4.5
APC-f750-A 0.5 -30 4.5

Currently, the package only supports biexponetial transformation for all channels with the getBiexpTransformGS() function. However, the user may choose to create a transformation list explicitly if other transformations (e.g., archsin) are desired.

Note that the flowWorkspace package also allows for an automated transformation calculation “guessing” appropriate parameters. We chose to explicitly specify the biexponential transformation with fixed parameters for all channels to match the manual gating strategy used in flowJo for a more direct comparison of {staRgate} to the manual gating results.

## **Optional**-- to check what pre-transformed data against post
# Check no transformation before
chk_tf <- flowWorkspace::gh_get_transformations(gs)

chk_tf
#> list()

dat_pre_transform <-
  flowWorkspace::gh_pop_get_data(gs) |>
  flowCore::exprs()


# Apply biexp trans
gs <- getBiexpTransformGS(gs, path_biexp_params = path_biexp_params)


## **Optional**-- to check what pre-transformed data against post
# Check no transformation before
chk_tf <- flowWorkspace::gh_get_transformations(gs)

# This is how 1 transformation specification looks like
chk_tf[[1]]
#> function (x, deriv = 0) 
#> {
#>     deriv <- as.integer(deriv)
#>     if (deriv < 0 || deriv > 3) 
#>         stop("'deriv' must be between 0 and 3")
#>     if (deriv > 0) {
#>         z0 <- double(z$n)
#>         z[c("y", "b", "c")] <- switch(deriv, list(y = z$b, b = 2 * 
#>             z$c, c = 3 * z$d), list(y = 2 * z$c, b = 6 * z$d, 
#>             c = z0), list(y = 6 * z$d, b = z0, c = z0))
#>         z[["d"]] <- z0
#>     }
#>     res <- stats:::.splinefun(x, z)
#>     if (deriv > 0 && z$method == 2 && any(ind <- x <= z$x[1L])) 
#>         res[ind] <- ifelse(deriv == 1, z$y[1L], 0)
#>     res
#> }
#> <bytecode: 0x55e1ade4bf90>
#> <environment: 0x55e1ad014b08>
#> attr(,"type")
#> [1] "biexp"
#> attr(,"parameters")
#> attr(,"parameters")$channelRange
#> [1] 4096
#> 
#> attr(,"parameters")$maxValue
#> [1] 262144
#> 
#> attr(,"parameters")$neg
#> [1] 0.5
#> 
#> attr(,"parameters")$pos
#> [1] 4.5
#> 
#> attr(,"parameters")$widthBasis
#> [1] -30

dat_post_transform <-
  flowWorkspace::gh_pop_get_data(gs) |>
  flowCore::exprs()

## **Optional**-- to check that the transformation worked on all provided channels!
summary(dat_pre_transform)
#>      FSC-A            FSC-H            FSC-W            SSC-A       
#>  Min.   :  1492   Min.   :  9988   Min.   : 54676   Min.   :  1033  
#>  1st Qu.: 21138   1st Qu.: 18451   1st Qu.: 99971   1st Qu.: 11850  
#>  Median : 97082   Median : 63860   Median :153544   Median : 26879  
#>  Mean   : 95408   Mean   : 62836   Mean   :152152   Mean   : 31716  
#>  3rd Qu.:137767   3rd Qu.: 90685   3rd Qu.:186615   3rd Qu.: 40599  
#>  Max.   :262144   Max.   :262144   Max.   :262144   Max.   :262144  
#>      SSC-H            SSC-W           AF700-A              APC-A          
#>  Min.   :  1231   Min.   : 21321   Min.   :-40597.32   Min.   :-83668.63  
#>  1st Qu.: 11045   1st Qu.: 70248   1st Qu.:   -89.72   1st Qu.:   -27.74  
#>  Median : 24490   Median : 86074   Median :   -50.31   Median :    80.28  
#>  Mean   : 26669   Mean   : 91965   Mean   :    51.63   Mean   :   893.85  
#>  3rd Qu.: 35312   3rd Qu.:100669   3rd Qu.:    -2.40   3rd Qu.:   442.46  
#>  Max.   :262144   Max.   :262144   Max.   :117999.96   Max.   :258429.72  
#>    APC-f750-A            BB515-A             BB660-A         
#>  Min.   :-15427.155   Min.   : -8649.57   Min.   :-81303.97  
#>  1st Qu.:   -51.782   1st Qu.:   -11.09   1st Qu.:     8.37  
#>  Median :    -0.517   Median :   105.76   Median :   100.04  
#>  Mean   :    86.131   Mean   :   234.41   Mean   :   154.55  
#>  3rd Qu.:    89.942   3rd Qu.:   244.52   3rd Qu.:   235.17  
#>  Max.   : 10246.643   Max.   :133731.45   Max.   :123396.73  
#>     BB700-A             BB790-A              BUV395-A        
#>  Min.   :-77947.38   Min.   :-171716.41   Min.   :-34770.30  
#>  1st Qu.:   -52.99   1st Qu.:    -18.98   1st Qu.:   -32.52  
#>  Median :    49.92   Median :    138.56   Median :   960.00  
#>  Mean   :   209.92   Mean   :    901.38   Mean   :  6796.03  
#>  3rd Qu.:   373.89   3rd Qu.:   1124.65   3rd Qu.: 11336.71  
#>  Max.   : 38183.27   Max.   : 196894.52   Max.   :101130.94  
#>     BUV496-A            BUV563-A            BUV615-A        
#>  Min.   :-21624.89   Min.   : -4940.99   Min.   :  -819.99  
#>  1st Qu.:   -59.50   1st Qu.:   -58.47   1st Qu.:   -25.87  
#>  Median :    72.92   Median :    29.19   Median :    94.06  
#>  Mean   :   508.88   Mean   :   131.69   Mean   :   305.05  
#>  3rd Qu.:  1018.48   3rd Qu.:   163.32   3rd Qu.:   299.30  
#>  Max.   : 12533.95   Max.   :193698.25   Max.   :163601.52  
#>     BUV661-A            BUV737-A            BUV805-A        
#>  Min.   :-19976.12   Min.   :-34474.75   Min.   : -2277.25  
#>  1st Qu.:  -117.70   1st Qu.:   -20.66   1st Qu.:   -67.60  
#>  Median :   -41.57   Median :    81.00   Median :    -7.75  
#>  Mean   :    98.23   Mean   :   860.45   Mean   :   615.80  
#>  3rd Qu.:    61.91   3rd Qu.:  1764.78   3rd Qu.:   136.72  
#>  Max.   :142146.95   Max.   : 98435.47   Max.   :111528.16  
#>     BV421-A             BV480-A             BV510-A         
#>  Min.   :-34983.16   Min.   : -1604.91   Min.   :-62086.07  
#>  1st Qu.:   -10.76   1st Qu.:   -49.19   1st Qu.:   -10.44  
#>  Median :   113.66   Median :    67.76   Median :    83.43  
#>  Mean   :  1321.77   Mean   :   664.87   Mean   :   193.69  
#>  3rd Qu.:   461.01   3rd Qu.:   227.83   3rd Qu.:   197.83  
#>  Max.   :233484.27   Max.   :228909.97   Max.   :205399.70  
#>     BV570-A             BV605-A             BV650-A         
#>  Min.   : -2557.12   Min.   :-15426.25   Min.   : -3029.89  
#>  1st Qu.:   -37.91   1st Qu.:   -21.88   1st Qu.:   -13.74  
#>  Median :    81.17   Median :   172.21   Median :   161.84  
#>  Mean   :   656.24   Mean   :   485.53   Mean   :  1616.83  
#>  3rd Qu.:   250.13   3rd Qu.:   424.23   3rd Qu.:  2921.42  
#>  Max.   :280203.25   Max.   :221790.52   Max.   :196991.55  
#>     BV711-A             BV750-A             BV786-A         
#>  Min.   : -3053.64   Min.   :-16241.38   Min.   :-13683.76  
#>  1st Qu.:   -48.11   1st Qu.:   -86.54   1st Qu.:   -59.14  
#>  Median :    33.28   Median :    15.93   Median :    21.86  
#>  Mean   :  2247.52   Mean   :  1152.19   Mean   :   393.86  
#>  3rd Qu.:   236.58   3rd Qu.:  2227.86   3rd Qu.:   750.71  
#>  Max.   :228820.50   Max.   :141663.83   Max.   :181618.50  
#>       PE-A              PE-CF594-A            PE-Cy5-A        
#>  Min.   :-158344.33   Min.   :-156548.30   Min.   :-14296.08  
#>  1st Qu.:    -74.52   1st Qu.:    -62.45   1st Qu.:   -55.40  
#>  Median :     82.19   Median :     67.09   Median :    14.74  
#>  Mean   :    367.16   Mean   :    679.63   Mean   :    97.37  
#>  3rd Qu.:    302.49   3rd Qu.:    342.16   3rd Qu.:   100.11  
#>  Max.   :  25212.26   Max.   :  95713.01   Max.   :207472.58  
#>    PE-Cy5.5-A           PE-Cy7-A              Time   
#>  Min.   :-24382.69   Min.   :  -489.16   Min.   :48  
#>  1st Qu.:     6.11   1st Qu.:   -37.00   1st Qu.:48  
#>  Median :   120.63   Median :    -2.74   Median :48  
#>  Mean   :   229.33   Mean   :   100.07   Mean   :48  
#>  3rd Qu.:   287.22   3rd Qu.:    34.86   3rd Qu.:48  
#>  Max.   :170867.83   Max.   :196990.38   Max.   :48
summary(dat_post_transform)
#>      FSC-A            FSC-H            FSC-W            SSC-A       
#>  Min.   :  1492   Min.   :  9988   Min.   : 54676   Min.   :  1033  
#>  1st Qu.: 21138   1st Qu.: 18451   1st Qu.: 99971   1st Qu.: 11850  
#>  Median : 97082   Median : 63860   Median :153544   Median : 26879  
#>  Mean   : 95408   Mean   : 62836   Mean   :152152   Mean   : 31716  
#>  3rd Qu.:137767   3rd Qu.: 90685   3rd Qu.:186615   3rd Qu.: 40599  
#>  Max.   :262144   Max.   :262144   Max.   :262144   Max.   :262144  
#>      SSC-H            SSC-W           AF700-A             APC-A         
#>  Min.   :  1231   Min.   : 21321   Min.   :-16892.6   Min.   :-35262.5  
#>  1st Qu.: 11045   1st Qu.: 70248   1st Qu.:   840.9   1st Qu.:   959.7  
#>  Median : 24490   Median : 86074   Median :   915.9   Median :  1169.3  
#>  Mean   : 26669   Mean   : 91965   Mean   :   990.0   Mean   :  1415.6  
#>  3rd Qu.: 35312   3rd Qu.:100669   3rd Qu.:  1009.3   3rd Qu.:  1682.2  
#>  Max.   :262144   Max.   :262144   Max.   :  3812.1   Max.   :  4091.4  
#>    APC-f750-A         BB515-A           BB660-A          BB700-A        
#>  Min.   :-6157.6   Min.   :-3266.9   Min.   :-34254   Min.   :-32822.4  
#>  1st Qu.:  913.1   1st Qu.:  992.3   1st Qu.:  1030   1st Qu.:   910.8  
#>  Median : 1013.0   Median : 1216.9   Median :  1206   Median :  1111.3  
#>  Mean   : 1120.5   Mean   : 1262.0   Mean   :  1245   Mean   :  1252.8  
#>  3rd Qu.: 1187.5   3rd Qu.: 1446.7   3rd Qu.:  1433   3rd Qu.:  1611.5  
#>  Max.   : 2933.3   Max.   : 3856.7   Max.   :  3828   Max.   :  3408.8  
#>     BB790-A            BUV395-A           BUV496-A          BUV563-A      
#>  Min.   :-72814.7   Min.   :-14407.4   Min.   :-8800.9   Min.   :-1685.2  
#>  1st Qu.:   976.8   1st Qu.:   950.4   1st Qu.:  898.2   1st Qu.:  900.2  
#>  Median :  1276.0   Median :  2015.1   Median : 1155.4   Median : 1071.1  
#>  Mean   :  1518.0   Mean   :  1975.3   Mean   : 1407.2   Mean   : 1139.9  
#>  3rd Qu.:  2081.6   3rd Qu.:  2970.2   3rd Qu.: 2040.1   3rd Qu.: 1318.8  
#>  Max.   :  3994.6   Max.   :  3757.0   Max.   : 3006.8   Max.   : 3988.7  
#>     BUV615-A          BUV661-A          BUV737-A           BUV805-A     
#>  Min.   :  79.94   Min.   :-8097.7   Min.   :-14281.3   Min.   :-549.1  
#>  1st Qu.: 963.39   1st Qu.:  789.3   1st Qu.:   973.6   1st Qu.: 882.7  
#>  Median :1195.18   Median :  932.8   Median :  1170.7   Median : 998.8  
#>  Mean   :1278.48   Mean   : 1032.8   Mean   :  1526.5   Mean   :1204.5  
#>  3rd Qu.:1522.04   3rd Qu.: 1134.4   3rd Qu.:  2265.8   3rd Qu.:1272.7  
#>  Max.   :3928.57   Max.   : 3878.5   Max.   :  3747.4   Max.   :3792.0  
#>     BV421-A            BV480-A          BV510-A            BV570-A      
#>  Min.   :-14498.2   Min.   :-262.4   Min.   :-26057.5   Min.   :-668.5  
#>  1st Qu.:   992.9   1st Qu.: 918.1   1st Qu.:   993.5   1st Qu.: 939.9  
#>  Median :  1231.3   Median :1145.6   Median :  1175.3   Median :1171.0  
#>  Mean   :  1445.3   Mean   :1265.6   Mean   :  1209.3   Mean   :1305.1  
#>  3rd Qu.:  1699.8   3rd Qu.:1422.0   3rd Qu.:  1375.6   3rd Qu.:1454.8  
#>  Max.   :  4055.3   Max.   :4048.2   Max.   :  4009.6   Max.   :4121.1  
#>     BV605-A           BV650-A          BV711-A          BV750-A       
#>  Min.   :-6157.2   Min.   :-870.1   Min.   :-880.3   Min.   :-6504.8  
#>  1st Qu.:  971.2   1st Qu.: 987.1   1st Qu.: 920.2   1st Qu.:  846.9  
#>  Median : 1333.7   Median :1316.3   Median :1079.1   Median : 1045.2  
#>  Mean   : 1347.4   Mean   :1626.2   Mean   :1371.0   Mean   : 1523.6  
#>  3rd Qu.: 1664.4   3rd Qu.:2463.9   3rd Qu.:1435.1   3rd Qu.: 2358.3  
#>  Max.   : 4037.0   Max.   :3994.7   Max.   :4048.1   Max.   : 3877.2  
#>     BV786-A             PE-A            PE-CF594-A          PE-Cy5-A      
#>  Min.   :-5414.0   Min.   :-67111.6   Min.   :-66345.6   Min.   :-5675.2  
#>  1st Qu.:  898.9   1st Qu.:   869.6   1st Qu.:   892.6   1st Qu.:  906.1  
#>  Median : 1056.8   Median :  1172.9   Median :  1144.3   Median : 1042.9  
#>  Mean   : 1338.9   Mean   :  1248.4   Mean   :  1320.0   Mean   : 1091.7  
#>  3rd Qu.: 1910.2   3rd Qu.:  1526.2   3rd Qu.:  1575.1   3rd Qu.: 1206.4  
#>  Max.   : 3965.8   Max.   :  3259.6   Max.   :  3737.4   Max.   : 4013.2  
#>    PE-Cy5.5-A       PE-Cy7-A           Time   
#>  Min.   :-9977   Min.   : 302.8   Min.   :48  
#>  1st Qu.: 1026   1st Qu.: 941.7   1st Qu.:48  
#>  Median : 1244   Median :1008.6   Median :48  
#>  Mean   : 1289   Mean   :1053.8   Mean   :48  
#>  3rd Qu.: 1506   3rd Qu.:1082.1   3rd Qu.:48  
#>  Max.   : 3944   Max.   :3994.7   Max.   :48

## **Optional**-- save out the transformerList
samp_metadata$transformerList <- chk_tf

Pre-gating

In this context, pre-gating is defined as gating from the root population up to CD3+, or CD4+/CD8+ subsets

Then we will pass the data to the density gating step.

The flowAI step serves as a quality control (QC) to match the first Time gate step that is typically done in manual gating. It is possible, however, that the user may choose to skip this step if flowAI excludes too many cells.

The first step of the gating template is a QC step that is especially important to include if the user chooses to exclude the flowAI step.

## Optional - flowAI step 
# set to TRUE if want to include
flowAI_yn <- FALSE

# Run flowAI if TRUE, run it
if (flowAI_yn) {
  # Check if the dir exists
  if (!dir.exists(glue::glue("{path_out}/flowAI_results"))) {
    dir.create(glue::glue("{path_out}/flowAI_results"))
  }

  # For sample
  qc_flowai <- flowAI::flow_auto_qc(flowWorkspace::gh_pop_get_data(gs),
    folder_results = here::here(glue::glue("{path_out}/flowAI_results"))
  )

  # Convert to a flowSet in order to convert to GatingSet
  qc_flowai <- flowCore::flowSet(qc_flowai)

  # Rnemae the sample
  sampleNames(qc_flowai) <- pt_samp_nm

  # convert the flowFrame obj returned in test_qc to a GatingSet to pass to openCyto
  gs_qc <- flowWorkspace::GatingSet(qc_flowai)

  # Remove to save memory
  rm(qc_flowai)

  # Save data
  save_gs(gs_qc,
    path = paste0(glue::glue("{path_out}/data/GatingSet/{pt_samp_nm}_flowAI_qc_{Sys.Date()}"))
  )
} else {
  # if no need to run flowAI, just set gs_qc as gs from previous transformation step
  gs_qc <- gs
  rm(gs)
}
# Pre-gating up to CD4/8+ with `r BiocStyle::Biocpkg("openCyto")`
## Set seed using today's date
set.seed(glue::glue({
  format(Sys.Date(), format = "%Y%m%d")
}))

openCyto::gt_gating(gt_tcell, gs_qc)
#> Gating for 'fsc_ssc_qc'
#> done!
#> done.
#> Gating for 'nonDebris'
#> done!
#> done.
#> Gating for 'singlets'
#> done!
#> done.
#> Gating for 'cd14-cd19-'
#> done!
#> done.
#> Gating for 'live'
#> done!
#> done.
#> Gating for 'cd3'
#> done!
#> done.
#> Gating for 'cd8+'
#> done!
#> done.
#> Gating for 'cd4+'
#> done!
#> done.
#> Population 'cd4-cd8-'
#> done.
#> Population 'cd4+cd8-'
#> done.
#> Population 'cd4-cd8+'
#> done.
#> Population 'cd4+cd8+'
#> done.
#> finished.
## Check autoplot
ggcyto::autoplot(gs_qc[[1]])

Extract intensity matrix

# Extract intensity matrix from GatingSet object
## Grab marker names from GatingSet for labeling col names in intensity matrix
## Can skip this step if you know the exact namings of the marker names in your FCS files
# In that case, supply a string is fine:
# marker_chnl_names = c("CD45RA", "CCR7", "LAG3", ...)
marker_chnl_names <-
  flowWorkspace::gh_pop_get_data(gs_qc) |>
  flowWorkspace::markernames() |>
  data.frame("marker_full" = _) |>
  tibble::rownames_to_column(var = "chnl") |>
  # clean up the names
  dplyr::mutate(
    marker_full = janitor::make_clean_names(marker_full, replace = c("-" = "", "_" = "", " " = "")) |> toupper()
  ) |>
  # Reorder the marker channel names to start with CD3, CD4, CD8 then the rest
  dplyr::arrange(match(marker_full, c("CD3", "CD4", "CD8"))) |> 
  # Clean up
  dplyr::mutate(marker_full = 
                  dplyr::case_when(marker_full == "FOX_P3" ~ "FOXP3",
                                   .default = marker_full))

# For our Tcell panel, we only want to apply the density gating on
# the `markers_to_gate` markers
# Again, this can be made specified explicitly as string
markers_to_gate <-
  marker_chnl_names$marker_full |> (\(x){
    x[!(x %in% c("CD3", "CD4", "CD8", "LD", "CD1419"))]  
  })()

## Grab the intensity matrix from GatingSet
## the gh_pop_get_indices grabs the 0/1 for whether gated as CD3
intensity_dat <-
  # grab all indices from opencyto gating to be complete
  flowWorkspace::gh_pop_get_indices(gs_qc, y = "fsc_ssc_qc") |>
  cbind(
    flowWorkspace::gh_pop_get_data(gs_qc) |>
      flowCore::exprs(),
    "fsc_ssc_qc" = _
  ) |>
  # Add nondebris
  cbind(
    "nonDebris" = flowWorkspace::gh_pop_get_indices(gs_qc, y = "nonDebris")
  ) |>
  # singlets
  cbind(
    "singlets" = flowWorkspace::gh_pop_get_indices(gs_qc, y = "singlets")
  ) |>
  # cd14-19-
  cbind(
    "cd14_neg_19_neg" = flowWorkspace::gh_pop_get_indices(gs_qc, y = "cd14-cd19-")
  ) |>
  # add live
  cbind(
    "live" = flowWorkspace::gh_pop_get_indices(gs_qc, y = "live")
  ) |>
  # Add cd3+
  cbind(
    "cd3_pos" = flowWorkspace::gh_pop_get_indices(gs_qc, y = "cd3")
  ) |>
  # add on the cd4 and cd8 0/1s
  cbind(
    "cd4_pos" = flowWorkspace::gh_pop_get_indices(gs_qc, y = "cd4+")
  ) |>
  cbind(
    "cd8_pos" = flowWorkspace::gh_pop_get_indices(gs_qc, y = "cd8+")
  ) |>
  tibble::as_tibble() |>
  # Rename with marker names
  dplyr::rename(stats::setNames(marker_chnl_names$chnl, as.character(marker_chnl_names$marker_full))) |>
  dplyr::mutate(
    cd4_pos_cd8_pos = dplyr::case_when(
      cd3_pos == 1 & cd4_pos == 1 & cd8_pos == 1 ~ "cd4_pos_cd8_pos",
      cd3_pos == 1 & cd4_pos == 1 & cd8_pos == 0 ~ "cd4_pos_cd8_neg",
      cd3_pos == 1 & cd4_pos == 0 & cd8_pos == 1 ~ "cd4_neg_cd8_pos",
      cd3_pos == 1 & cd4_pos == 0 & cd8_pos == 0 ~ "cd4_neg_cd8_neg"
    )
  )

## Preview of intensity matrix
head(intensity_dat)
#> # A tibble: 6 × 44
#>   `FSC-A` `FSC-H` `FSC-W` `SSC-A` `SSC-H` `SSC-W`  KI67  TBET GZM_B   PD1  LAG3
#>     <dbl>   <dbl>   <dbl>   <dbl>   <dbl>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1  14579.  12925.  91156.   6783.   6899.  59440.  898.  995.  949.  922. 1184.
#> 2  47535.  36802. 124831.  21010.  20507.  79210.  732. 1178.  775.  914. 1169.
#> 3 117789.  81878. 183615.  41468.  38820.  93061.  814. 2613. 1960. 1372. 1330.
#> 4 230540.  99239. 192036.  48270.  23680. 129099.  821. 1006. 1156. 1675. 1581.
#> 5  65184.  43167. 138985.  25413.  21602.  88339.  654. 1252. 1004. 1069. 1257.
#> 6 138285.  96765. 182364.  37571.  34888.  93352.  705. 2678.  984. 1034. 1011.
#> # ℹ 33 more variables: CD127 <dbl>, CD38 <dbl>, CD45RA <dbl>, CD4 <dbl>,
#> #   ICOS <dbl>, CD25 <dbl>, TIM3 <dbl>, CD27 <dbl>, CD8 <dbl>, CD57 <dbl>,
#> #   CXCR5 <dbl>, LD <dbl>, CD1419 <dbl>, CCR4 <dbl>, CCR7 <dbl>, HLADR <dbl>,
#> #   CD3 <dbl>, CD28 <dbl>, TIGIT <dbl>, EOMES <dbl>, CTLA4 <dbl>, FOXP3 <dbl>,
#> #   GITR <dbl>, Time <dbl>, fsc_ssc_qc <dbl>, nonDebris <dbl>, singlets <dbl>,
#> #   cd14_neg_19_neg <dbl>, live <dbl>, cd3_pos <dbl>, cd4_pos <dbl>,
#> #   cd8_pos <dbl>, cd4_pos_cd8_pos <chr>

Apply density gating

The suggested strategy is based on all CD3+, but this can be customized based on the string corresponding to the column name supplied to subset_col in the get_density_gates function

The suggested number of bins for density estimation is 40.

For illustration purposes, we will only apply density gating on a few markers.

# Density gating parameters
peak_r <- 10
bin_i <- 40

neg_intensity_thres <- -1000

# select a few markers to gate
example_markers <- c("LAG3", "CCR7", "CD45RA", "EOMES")

# Read in positive peak thresholds
pos_thres <- utils::read.csv(path_pos_peak_thresholds)
# Show the exact output from the get_density_gates() function
dens_gates_pre <-
  dplyr::filter(intensity_dat, cd3_pos %in% c(0, 1)) |>
  getDensityGates(
    intens_dat = _,
    marker = example_markers,
    subset_col = "cd3_pos",
    bin_n = bin_i,
    peak_detect_ratio = peak_r,
    pos_peak_threshold = pos_thres,
    neg_intensity_threshold = neg_intensity_thres
  )

dens_gates_pre
#> # A tibble: 2 × 5
#>   cd3_pos  LAG3  CCR7 CD45RA EOMES
#>     <dbl> <dbl> <dbl>  <dbl> <dbl>
#> 1       0 1328. 1452.  1375. 1533.
#> 2       1 1634. 1768.  1486. 1741.

# Since we apply density gating on CD3+ cells but
# Would like to calculate subpopulations with CD4+ and CD8+ as
# the starting parent population, we need to add corresponding rows
dens_gates <-
  dplyr::filter(dens_gates_pre, cd3_pos == 1) |>
  tibble::add_row() |>
  tibble::add_row() |>
  tibble::add_row() |>
  dplyr::mutate(cd4_pos_cd8_pos = c("cd4_neg_cd8_neg", "cd4_pos_cd8_neg", "cd4_neg_cd8_pos", "cd4_pos_cd8_pos")) |>
  tidyr::fill(cd3_pos, dplyr::all_of(example_markers), .direction = "down")


# View updated gates with the col for CD4/CD8
dens_gates
#> # A tibble: 4 × 6
#>   cd3_pos  LAG3  CCR7 CD45RA EOMES cd4_pos_cd8_pos
#>     <dbl> <dbl> <dbl>  <dbl> <dbl> <chr>          
#> 1       1 1634. 1768.  1486. 1741. cd4_neg_cd8_neg
#> 2       1 1634. 1768.  1486. 1741. cd4_pos_cd8_neg
#> 3       1 1634. 1768.  1486. 1741. cd4_neg_cd8_pos
#> 4       1 1634. 1768.  1486. 1741. cd4_pos_cd8_pos

# try get indicator col
example_intensity_gated <-
  getGatedDat(intensity_dat,
    subset_col = "cd4_pos_cd8_pos",
    cutoffs = dens_gates
  )


# Plot the gate for visual
intensity_dat |>
  dplyr::filter(cd3_pos == 1) |>
  # additional step to remove large intensity values only when density gating.
  # Still kept in the data
  dplyr::filter(!(dplyr::if_any(dplyr::all_of(markers_to_gate), ~ .x < neg_intensity_thres))) |>
  ggplot() +
  geom_density(aes(LAG3)) +
  geom_vline(
    data = dens_gates,
    aes(xintercept = LAG3),
    color = "blue",
    linetype = "dashed"
  ) +
  labs(subtitle = "Distribution of LAG3 intensity on all CD3+. Gate identifed by {staRgate} in blue.")


# If by CD4/CD8,
intensity_dat |>
  dplyr::filter(cd3_pos == 1) |>
  # additional step to remove large intensity values only when density gating.
  # Still kept in the data
  dplyr::filter(!(dplyr::if_any(dplyr::all_of(markers_to_gate), ~ .x < neg_intensity_thres))) |>
  ggplot() +
  geom_density(aes(LAG3)) +
  geom_vline(
    data = dens_gates,
    aes(xintercept = LAG3),
    color = "blue",
    linetype = "dashed"
  ) +
  labs(subtitle = "Distribution of LAG3 intensity on all CD3+. Gate identifed by {staRgate} in blue.") +
  facet_wrap(~cd4_pos_cd8_pos)



# For CCR7
intensity_dat |>
  dplyr::filter(cd3_pos == 1) |>
  # additional step to remove large intensity values only when density gating.
  # Still kept in the data
  dplyr::filter(!(dplyr::if_any(dplyr::all_of(markers_to_gate), ~ .x < neg_intensity_thres))) |>
  ggplot() +
  geom_density(aes(CCR7)) +
  geom_vline(
    data = dens_gates,
    aes(xintercept = CCR7),
    color = "blue",
    linetype = "dashed"
  ) +
  labs(subtitle = "Distribution of CCR7 intensity on all CD3+. Gate identifed by {staRgate} in blue.")


# If by CD4/CD8,
intensity_dat |>
  dplyr::filter(cd3_pos == 1) |>
  # additional step to remove large intensity values only when density gating.
  # Still kept in the data
  dplyr::filter(!(dplyr::if_any(dplyr::all_of(markers_to_gate), ~ .x < neg_intensity_thres))) |>
  ggplot() +
  geom_density(aes(CCR7)) +
  geom_vline(
    data = dens_gates,
    aes(xintercept = CCR7),
    color = "blue",
    linetype = "dashed"
  ) +
  labs(subtitle = "Distribution of CCR7 intensity on all CD3+. Gate identifed by {staRgate} in blue.") +
  facet_wrap(~cd4_pos_cd8_pos)


# For CD45RA
intensity_dat |>
  dplyr::filter(cd3_pos == 1) |>
  # additional step to remove large intensity values only when density gating.
  # Still kept in the data
  dplyr::filter(!(dplyr::if_any(dplyr::all_of(markers_to_gate), ~ .x < neg_intensity_thres))) |>
  ggplot() +
  geom_density(aes(CD45RA)) +
  geom_vline(
    data = dens_gates,
    aes(xintercept = CD45RA),
    color = "blue",
    linetype = "dashed"
  ) +
  labs(subtitle = "Distribution of CD45RA intensity on all CD3+. Gate identifed by {staRgate} in blue.")


# If by CD4/CD8,
intensity_dat |>
  dplyr::filter(cd3_pos == 1) |>
  # additional step to remove large intensity values only when density gating.
  # Still kept in the data
  dplyr::filter(!(dplyr::if_any(dplyr::all_of(markers_to_gate), ~ .x < neg_intensity_thres))) |>
  ggplot() +
  geom_density(aes(CD45RA)) +
  geom_vline(
    data = dens_gates,
    aes(xintercept = CD45RA),
    color = "blue",
    linetype = "dashed"
  ) +
  labs(subtitle = "Distribution of CD45RA intensity on all CD3+. Gate identifed by {staRgate} in blue.") +
  facet_wrap(~cd4_pos_cd8_pos)

Getting percentage data

We can then summarize the single-cell level data to counts and percentages of cells for all combinations of markers.

For the subpopulations, the denominator is defined as the parent population and numerator is the population of interest out of the parent population. The ndn_d refers to the number of markers considered for the denominator and nn for the number of markers considered for the numerator.

For the 29-marker panel, if the denominator is specified as the CD4 and CD8 subsets, then nd=2n_d = 2 and n=23n = 23 for the markers of interest.

The getPerc function allows user to list the markers of interest for the numerator and denominator

In the example below, we will consider CD4 and CD8 subsets as the key parent populations of interest (denominator) and the three markers we gated on using c("LAG3", "CCR7", "CD45RA") (numerator markers).

The additional arguments expand_num and expand_denom generates different lists of subpopulations to calculate counts/percentages for:

  • expand_num: should calculations consider up to pairs of numerator markers included?,
  • expand_denom: should the calculations consider combinations of each numerator marker and parent populations specified in the denominator?

Currently, we support the four scenarios listed below:

expand_num expand_denom The subpopulations to generate Examples Expected number of subpopulations1 Expected number of subpopulations
for the 29-marker T-cell panel
FALSE FALSE
  • Numerator: Positive and negative for each marker specified
  • Denominator: Combinations of positive and negative for markers specified
Numerator specified to (“LAG3”) and denominator specified as (“CD4”, “CD8”):
  • LAG3+/- of CD4- & CD8-
  • LAG3+/- of CD4+ & CD8-
  • LAG3+/- of CD4+ & CD8+
  • LAG3+/- of CD4- & CD8+
2n*(2nd) 184
TRUE FALSE
  • Numerator: Positive and negative for each marker specified, and combinations of positive/negative for pairs of markers
    • At least 2 markers must be included in the numerator
  • Denominator: Combinations of positive and negative for marker(s)
Numerator specified as (“LAG3”, “KI67”) and denominator as (“CD4”):
  • LAG3+/- of CD4+/-
  • KI67+/- of CD4+/-
  • LAG3- KI67- of CD4+/-
  • LAG3+ KI67- of CD4+/-
  • LAG3- KI67+ of CD4+/-
  • LAG3+ KI67+ of CD4+/-
2n*(2nd) +
2nd*(n choose 2)*(22)
4232
FALSE TRUE
  • Numerator: Positive and negative for each marker specified
    • At least 2 markers must be included in the numerator
  • Denominator: Combinations of positive and negative for marker(s), and combinations of positive and negative for marker(s) in denominator with one marker from the numerator
Numerator specified as (“LAG3”, “KI67”) and denominator as (“CD4”)
  • LAG3+/- of CD4+/-
  • KI67+/- of CD4+/-
  • LAG3+/- of CD4-KI67-
  • LAG3+/- of CD4-KI67+
  • LAG3+/- of CD4+KI67-
  • LAG3+/- of CD4+KI67+
  • KI67+/- of CD4-LAG3-
  • KI67+/- of CD4-LAG3+
  • KI67+/- of CD4+LAG3-
  • KI67+/- of CD4+LAG3+
2n*(2nd) +
4*2nd*n*(n-1)
8280
TRUE TRUE
  • Numerator: Positive and negative for each marker specified, and combinations of positive/negative for pairs of markers
    • At least 3 markers must be included in the numerator
  • Denominator: Combinations of positive and negative for marker(s), and combinations of positive and negative for marker(s) in denominator with one marker from the numerator
Numerator specified as (“LAG3”, “KI67”, “CTLA4”) and denominator as (“CD4”)
  • LAG3+/- of CD4+/-
  • KI67+/- of CD4+/-
  • CTLA4+/- of CD4+/-
  • LAG3+/- of CD4-KI67-
  • LAG3+/- of CD4-KI67+
  • LAG3+/- of CD4+KI67-
  • LAG3+/- of CD4+KI67+
  • KI67+/- of CD4-LAG3-
  • KI67+/- of CD4-LAG3+
  • KI67+/- of CD4+LAG3-
  • KI67+/- of CD4+LAG3+
  • ….
  • LAG3- KI67- of CD4+/-
  • LAG3+ KI67- of CD4+/-
  • LAG3- KI67+ of CD4+/-
  • LAG3+ KI67+ of CD4+/-
  • LAG3+ KI67+ of CD4+/- & CTLA4+/-
2n*(2nd) +
2nd*(n choose 2)*(22) +
4*2nd*n*(n-1) +
22*n*((n-1) choose 2)*(2nd+1)
182344
1 n, number of markers specified in the numerator; nd, number of markers specified in the denominator

The keep_indicators argument provides the 0/1 for which marker is considered in the numerator and denominator for each subpopulation. This is especially useful when merging onto other data that does not have the same format.

For example, when matching strings: “CD4+ & CD8- of CD3+” is different from “CD8- & CD4+ of CD3+” and “CD4+ and CD8- of CD3+”

Below is we show examples of each of the four combinations for the expand_num and expand_denom arguments.

For the example of when expand_num = FALSE and expand_denom = FALSE, keep_indicators = TRUE to illustrate the columns we get for the _POS and _POS_D. Other examples use keep_indicators = FALSE.

Expand example for expand_num = FALSE and expand_denom = FALSE, and keep_indicators = TRUE
example_perc1 <-
  # Should only count the CD3+ cells
  dplyr::filter(example_intensity_gated, cd3_pos == 1) |> 
  getPerc(
    intens_dat = _,
    num_marker = example_markers,
    denom_marker = c("CD4", "CD8"),
    expand_num = FALSE,
    expand_denom = FALSE,
    keep_indicators = TRUE
  )

# For display only, group based on the denominators and
# simplify the names to be numerators
example_perc1 |> 
  tidyr::separate_wider_delim(subpopulation,
    delim = "_OF_",
    names = c("num", "denom"),
    cols_remove = FALSE
  ) |>
  dplyr::mutate(denom = paste("Denom = ", denom)) |>
  dplyr::group_by(denom) |>
  dplyr::select(-subpopulation) |>
  gt::gt() |>
  gt::fmt_number(
    columns = "perc",
    decimals = 1
  )
num n_num n_denom perc LAG3_POS CCR7_POS CD45RA_POS EOMES_POS CD4_POS_D CD8_POS_D
Denom = CD4_NEG_CD8_NEG
LAG3_NEG 196 271 72.3 0 NA NA NA 0 0
CCR7_NEG 220 271 81.2 NA 0 NA NA 0 0
CD45RA_NEG 21 271 7.7 NA NA 0 NA 0 0
EOMES_NEG 59 271 21.8 NA NA NA 0 0 0
LAG3_POS 75 271 27.7 1 NA NA NA 0 0
CCR7_POS 51 271 18.8 NA 1 NA NA 0 0
CD45RA_POS 250 271 92.3 NA NA 1 NA 0 0
EOMES_POS 212 271 78.2 NA NA NA 1 0 0
Denom = CD4_NEG_CD8_POS
LAG3_NEG 1711 2302 74.3 0 NA NA NA 0 1
CCR7_NEG 1699 2302 73.8 NA 0 NA NA 0 1
CD45RA_NEG 413 2302 17.9 NA NA 0 NA 0 1
EOMES_NEG 648 2302 28.1 NA NA NA 0 0 1
LAG3_POS 591 2302 25.7 1 NA NA NA 0 1
CCR7_POS 603 2302 26.2 NA 1 NA NA 0 1
CD45RA_POS 1889 2302 82.1 NA NA 1 NA 0 1
EOMES_POS 1654 2302 71.9 NA NA NA 1 0 1
Denom = CD4_POS_CD8_NEG
LAG3_NEG 8289 8615 96.2 0 NA NA NA 1 0
CCR7_NEG 1395 8615 16.2 NA 0 NA NA 1 0
CD45RA_NEG 1830 8615 21.2 NA NA 0 NA 1 0
EOMES_NEG 7800 8615 90.5 NA NA NA 0 1 0
LAG3_POS 326 8615 3.8 1 NA NA NA 1 0
CCR7_POS 7220 8615 83.8 NA 1 NA NA 1 0
CD45RA_POS 6785 8615 78.8 NA NA 1 NA 1 0
EOMES_POS 815 8615 9.5 NA NA NA 1 1 0
Denom = CD4_POS_CD8_POS
LAG3_NEG 139 276 50.4 0 NA NA NA 1 1
CCR7_NEG 152 276 55.1 NA 0 NA NA 1 1
CD45RA_NEG 35 276 12.7 NA NA 0 NA 1 1
EOMES_NEG 71 276 25.7 NA NA NA 0 1 1
LAG3_POS 137 276 49.6 1 NA NA NA 1 1
CCR7_POS 124 276 44.9 NA 1 NA NA 1 1
CD45RA_POS 241 276 87.3 NA NA 1 NA 1 1
EOMES_POS 205 276 74.3 NA NA NA 1 1 1
Expand example for expand_num = TRUE and expand_denom = FALSE, and keep_indicators = FALSE
example_perc2 <-
  # Should only count the CD3+ cells
  dplyr::filter(example_intensity_gated, cd3_pos == 1) |>
  getPerc(
    intens_dat = _,
    num_marker = example_markers,
    denom_marker = c("CD4", "CD8"),
    expand_num = TRUE,
    expand_denom = FALSE,
    keep_indicators = FALSE
  )

# For display only, group based on the denominators and
# simplify the names to be numerators
example_perc2 |>
  tidyr::separate_wider_delim(subpopulation,
    delim = "_OF_",
    names = c("num", "denom"),
    cols_remove = FALSE
  ) |>
  dplyr::mutate(denom = paste("Denom = ", denom)) |>
  dplyr::group_by(denom) |>
  dplyr::select(-subpopulation) |>
  gt::gt() |>
  gt::fmt_number(
    columns = "perc",
    decimals = 1
  )
num n_num n_denom perc
Denom = CD4_NEG_CD8_NEG
LAG3_NEG 196 271 72.3
CCR7_NEG 220 271 81.2
CD45RA_NEG 21 271 7.7
EOMES_NEG 59 271 21.8
LAG3_POS 75 271 27.7
CCR7_POS 51 271 18.8
CD45RA_POS 250 271 92.3
EOMES_POS 212 271 78.2
LAG3_NEG_CCR7_NEG 147 271 54.2
LAG3_NEG_CD45RA_NEG 17 271 6.3
LAG3_NEG_EOMES_NEG 58 271 21.4
LAG3_NEG_CCR7_POS 49 271 18.1
LAG3_NEG_CD45RA_POS 179 271 66.1
LAG3_NEG_EOMES_POS 138 271 50.9
CCR7_NEG_CD45RA_NEG 17 271 6.3
CCR7_NEG_EOMES_NEG 20 271 7.4
CCR7_NEG_LAG3_POS 73 271 26.9
CCR7_NEG_CD45RA_POS 203 271 74.9
CCR7_NEG_EOMES_POS 200 271 73.8
CD45RA_NEG_EOMES_NEG 7 271 2.6
CD45RA_NEG_LAG3_POS 4 271 1.5
CD45RA_NEG_CCR7_POS 4 271 1.5
CD45RA_NEG_EOMES_POS 14 271 5.2
EOMES_NEG_LAG3_POS 1 271 0.4
EOMES_NEG_CCR7_POS 39 271 14.4
EOMES_NEG_CD45RA_POS 52 271 19.2
LAG3_POS_CCR7_POS 2 271 0.7
LAG3_POS_CD45RA_POS 71 271 26.2
LAG3_POS_EOMES_POS 74 271 27.3
CCR7_POS_CD45RA_POS 47 271 17.3
CCR7_POS_EOMES_POS 12 271 4.4
CD45RA_POS_EOMES_POS 198 271 73.1
Denom = CD4_NEG_CD8_POS
LAG3_NEG 1711 2302 74.3
CCR7_NEG 1699 2302 73.8
CD45RA_NEG 413 2302 17.9
EOMES_NEG 648 2302 28.1
LAG3_POS 591 2302 25.7
CCR7_POS 603 2302 26.2
CD45RA_POS 1889 2302 82.1
EOMES_POS 1654 2302 71.9
LAG3_NEG_CCR7_NEG 1165 2302 50.6
LAG3_NEG_CD45RA_NEG 344 2302 14.9
LAG3_NEG_EOMES_NEG 587 2302 25.5
LAG3_NEG_CCR7_POS 546 2302 23.7
LAG3_NEG_CD45RA_POS 1367 2302 59.4
LAG3_NEG_EOMES_POS 1124 2302 48.8
CCR7_NEG_CD45RA_NEG 329 2302 14.3
CCR7_NEG_EOMES_NEG 258 2302 11.2
CCR7_NEG_LAG3_POS 534 2302 23.2
CCR7_NEG_CD45RA_POS 1370 2302 59.5
CCR7_NEG_EOMES_POS 1441 2302 62.6
CD45RA_NEG_EOMES_NEG 125 2302 5.4
CD45RA_NEG_LAG3_POS 69 2302 3.0
CD45RA_NEG_CCR7_POS 84 2302 3.6
CD45RA_NEG_EOMES_POS 288 2302 12.5
EOMES_NEG_LAG3_POS 61 2302 2.6
EOMES_NEG_CCR7_POS 390 2302 16.9
EOMES_NEG_CD45RA_POS 523 2302 22.7
LAG3_POS_CCR7_POS 57 2302 2.5
LAG3_POS_CD45RA_POS 522 2302 22.7
LAG3_POS_EOMES_POS 530 2302 23.0
CCR7_POS_CD45RA_POS 519 2302 22.5
CCR7_POS_EOMES_POS 213 2302 9.3
CD45RA_POS_EOMES_POS 1366 2302 59.3
Denom = CD4_POS_CD8_NEG
LAG3_NEG 8289 8615 96.2
CCR7_NEG 1395 8615 16.2
CD45RA_NEG 1830 8615 21.2
EOMES_NEG 7800 8615 90.5
LAG3_POS 326 8615 3.8
CCR7_POS 7220 8615 83.8
CD45RA_POS 6785 8615 78.8
EOMES_POS 815 8615 9.5
LAG3_NEG_CCR7_NEG 1221 8615 14.2
LAG3_NEG_CD45RA_NEG 1700 8615 19.7
LAG3_NEG_EOMES_NEG 7627 8615 88.5
LAG3_NEG_CCR7_POS 7068 8615 82.0
LAG3_NEG_CD45RA_POS 6589 8615 76.5
LAG3_NEG_EOMES_POS 662 8615 7.7
CCR7_NEG_CD45RA_NEG 927 8615 10.8
CCR7_NEG_EOMES_NEG 811 8615 9.4
CCR7_NEG_LAG3_POS 174 8615 2.0
CCR7_NEG_CD45RA_POS 468 8615 5.4
CCR7_NEG_EOMES_POS 584 8615 6.8
CD45RA_NEG_EOMES_NEG 1461 8615 17.0
CD45RA_NEG_LAG3_POS 130 8615 1.5
CD45RA_NEG_CCR7_POS 903 8615 10.5
CD45RA_NEG_EOMES_POS 369 8615 4.3
EOMES_NEG_LAG3_POS 173 8615 2.0
EOMES_NEG_CCR7_POS 6989 8615 81.1
EOMES_NEG_CD45RA_POS 6339 8615 73.6
LAG3_POS_CCR7_POS 152 8615 1.8
LAG3_POS_CD45RA_POS 196 8615 2.3
LAG3_POS_EOMES_POS 153 8615 1.8
CCR7_POS_CD45RA_POS 6317 8615 73.3
CCR7_POS_EOMES_POS 231 8615 2.7
CD45RA_POS_EOMES_POS 446 8615 5.2
Denom = CD4_POS_CD8_POS
LAG3_NEG 139 276 50.4
CCR7_NEG 152 276 55.1
CD45RA_NEG 35 276 12.7
EOMES_NEG 71 276 25.7
LAG3_POS 137 276 49.6
CCR7_POS 124 276 44.9
CD45RA_POS 241 276 87.3
EOMES_POS 205 276 74.3
LAG3_NEG_CCR7_NEG 79 276 28.6
LAG3_NEG_CD45RA_NEG 26 276 9.4
LAG3_NEG_EOMES_NEG 50 276 18.1
LAG3_NEG_CCR7_POS 60 276 21.7
LAG3_NEG_CD45RA_POS 113 276 40.9
LAG3_NEG_EOMES_POS 89 276 32.2
CCR7_NEG_CD45RA_NEG 19 276 6.9
CCR7_NEG_EOMES_NEG 33 276 12.0
CCR7_NEG_LAG3_POS 73 276 26.4
CCR7_NEG_CD45RA_POS 133 276 48.2
CCR7_NEG_EOMES_POS 119 276 43.1
CD45RA_NEG_EOMES_NEG 21 276 7.6
CD45RA_NEG_LAG3_POS 9 276 3.3
CD45RA_NEG_CCR7_POS 16 276 5.8
CD45RA_NEG_EOMES_POS 14 276 5.1
EOMES_NEG_LAG3_POS 21 276 7.6
EOMES_NEG_CCR7_POS 38 276 13.8
EOMES_NEG_CD45RA_POS 50 276 18.1
LAG3_POS_CCR7_POS 64 276 23.2
LAG3_POS_CD45RA_POS 128 276 46.4
LAG3_POS_EOMES_POS 116 276 42.0
CCR7_POS_CD45RA_POS 108 276 39.1
CCR7_POS_EOMES_POS 86 276 31.2
CD45RA_POS_EOMES_POS 191 276 69.2
Expand example for expand_num = FALSE and expand_denom = TRUE, and keep_indicators = FALSE
example_perc3 <-
  # Should only count the CD3+ cells
  dplyr::filter(example_intensity_gated, cd3_pos == 1) |>
  getPerc(
    intens_dat = _,
    num_marker = example_markers,
    denom_marker = c("CD4", "CD8"),
    expand_num = FALSE,
    expand_denom = TRUE,
    keep_indicators = FALSE
  )

# For display only, group based on the denominators and
# simplify the names to be numerators
example_perc3 |> 
  tidyr::separate_wider_delim(subpopulation,
    delim = "_OF_",
    names = c("num", "denom"),
    cols_remove = FALSE
  ) |>
  dplyr::mutate(denom = paste("Denom = ", denom)) |>
  dplyr::group_by(denom) |>
  dplyr::select(-subpopulation) |>
  gt::gt() |>
  gt::fmt_number(
    columns = "perc",
    decimals = 1
  )
num n_num n_denom perc
Denom = CD4_NEG_CD8_NEG
LAG3_NEG 196 271 72.3
CCR7_NEG 220 271 81.2
CD45RA_NEG 21 271 7.7
EOMES_NEG 59 271 21.8
LAG3_POS 75 271 27.7
CCR7_POS 51 271 18.8
CD45RA_POS 250 271 92.3
EOMES_POS 212 271 78.2
Denom = CD4_NEG_CD8_POS
LAG3_NEG 1711 2302 74.3
CCR7_NEG 1699 2302 73.8
CD45RA_NEG 413 2302 17.9
EOMES_NEG 648 2302 28.1
LAG3_POS 591 2302 25.7
CCR7_POS 603 2302 26.2
CD45RA_POS 1889 2302 82.1
EOMES_POS 1654 2302 71.9
Denom = CD4_POS_CD8_NEG
LAG3_NEG 8289 8615 96.2
CCR7_NEG 1395 8615 16.2
CD45RA_NEG 1830 8615 21.2
EOMES_NEG 7800 8615 90.5
LAG3_POS 326 8615 3.8
CCR7_POS 7220 8615 83.8
CD45RA_POS 6785 8615 78.8
EOMES_POS 815 8615 9.5
Denom = CD4_POS_CD8_POS
LAG3_NEG 139 276 50.4
CCR7_NEG 152 276 55.1
CD45RA_NEG 35 276 12.7
EOMES_NEG 71 276 25.7
LAG3_POS 137 276 49.6
CCR7_POS 124 276 44.9
CD45RA_POS 241 276 87.3
EOMES_POS 205 276 74.3
Denom = CD4_NEG_CD8_NEG_LAG3_NEG
CCR7_NEG 147 196 75.0
CD45RA_NEG 17 196 8.7
EOMES_NEG 58 196 29.6
CCR7_POS 49 196 25.0
CD45RA_POS 179 196 91.3
EOMES_POS 138 196 70.4
Denom = CD4_NEG_CD8_POS_LAG3_NEG
CCR7_NEG 1165 1711 68.1
CD45RA_NEG 344 1711 20.1
EOMES_NEG 587 1711 34.3
CCR7_POS 546 1711 31.9
CD45RA_POS 1367 1711 79.9
EOMES_POS 1124 1711 65.7
Denom = CD4_POS_CD8_NEG_LAG3_NEG
CCR7_NEG 1221 8289 14.7
CD45RA_NEG 1700 8289 20.5
EOMES_NEG 7627 8289 92.0
CCR7_POS 7068 8289 85.3
CD45RA_POS 6589 8289 79.5
EOMES_POS 662 8289 8.0
Denom = CD4_POS_CD8_POS_LAG3_NEG
CCR7_NEG 79 139 56.8
CD45RA_NEG 26 139 18.7
EOMES_NEG 50 139 36.0
CCR7_POS 60 139 43.2
CD45RA_POS 113 139 81.3
EOMES_POS 89 139 64.0
Denom = CD4_NEG_CD8_NEG_CCR7_NEG
LAG3_NEG 147 220 66.8
CD45RA_NEG 17 220 7.7
EOMES_NEG 20 220 9.1
LAG3_POS 73 220 33.2
CD45RA_POS 203 220 92.3
EOMES_POS 200 220 90.9
Denom = CD4_NEG_CD8_POS_CCR7_NEG
LAG3_NEG 1165 1699 68.6
CD45RA_NEG 329 1699 19.4
EOMES_NEG 258 1699 15.2
LAG3_POS 534 1699 31.4
CD45RA_POS 1370 1699 80.6
EOMES_POS 1441 1699 84.8
Denom = CD4_POS_CD8_NEG_CCR7_NEG
LAG3_NEG 1221 1395 87.5
CD45RA_NEG 927 1395 66.5
EOMES_NEG 811 1395 58.1
LAG3_POS 174 1395 12.5
CD45RA_POS 468 1395 33.5
EOMES_POS 584 1395 41.9
Denom = CD4_POS_CD8_POS_CCR7_NEG
LAG3_NEG 79 152 52.0
CD45RA_NEG 19 152 12.5
EOMES_NEG 33 152 21.7
LAG3_POS 73 152 48.0
CD45RA_POS 133 152 87.5
EOMES_POS 119 152 78.3
Denom = CD4_NEG_CD8_NEG_CD45RA_NEG
LAG3_NEG 17 21 81.0
CCR7_NEG 17 21 81.0
EOMES_NEG 7 21 33.3
LAG3_POS 4 21 19.0
CCR7_POS 4 21 19.0
EOMES_POS 14 21 66.7
Denom = CD4_NEG_CD8_POS_CD45RA_NEG
LAG3_NEG 344 413 83.3
CCR7_NEG 329 413 79.7
EOMES_NEG 125 413 30.3
LAG3_POS 69 413 16.7
CCR7_POS 84 413 20.3
EOMES_POS 288 413 69.7
Denom = CD4_POS_CD8_NEG_CD45RA_NEG
LAG3_NEG 1700 1830 92.9
CCR7_NEG 927 1830 50.7
EOMES_NEG 1461 1830 79.8
LAG3_POS 130 1830 7.1
CCR7_POS 903 1830 49.3
EOMES_POS 369 1830 20.2
Denom = CD4_POS_CD8_POS_CD45RA_NEG
LAG3_NEG 26 35 74.3
CCR7_NEG 19 35 54.3
EOMES_NEG 21 35 60.0
LAG3_POS 9 35 25.7
CCR7_POS 16 35 45.7
EOMES_POS 14 35 40.0
Denom = CD4_NEG_CD8_NEG_EOMES_NEG
LAG3_NEG 58 59 98.3
CCR7_NEG 20 59 33.9
CD45RA_NEG 7 59 11.9
LAG3_POS 1 59 1.7
CCR7_POS 39 59 66.1
CD45RA_POS 52 59 88.1
Denom = CD4_NEG_CD8_POS_EOMES_NEG
LAG3_NEG 587 648 90.6
CCR7_NEG 258 648 39.8
CD45RA_NEG 125 648 19.3
LAG3_POS 61 648 9.4
CCR7_POS 390 648 60.2
CD45RA_POS 523 648 80.7
Denom = CD4_POS_CD8_NEG_EOMES_NEG
LAG3_NEG 7627 7800 97.8
CCR7_NEG 811 7800 10.4
CD45RA_NEG 1461 7800 18.7
LAG3_POS 173 7800 2.2
CCR7_POS 6989 7800 89.6
CD45RA_POS 6339 7800 81.3
Denom = CD4_POS_CD8_POS_EOMES_NEG
LAG3_NEG 50 71 70.4
CCR7_NEG 33 71 46.5
CD45RA_NEG 21 71 29.6
LAG3_POS 21 71 29.6
CCR7_POS 38 71 53.5
CD45RA_POS 50 71 70.4
Denom = CD4_NEG_CD8_NEG_LAG3_POS
CCR7_NEG 73 75 97.3
CD45RA_NEG 4 75 5.3
EOMES_NEG 1 75 1.3
CCR7_POS 2 75 2.7
CD45RA_POS 71 75 94.7
EOMES_POS 74 75 98.7
Denom = CD4_NEG_CD8_POS_LAG3_POS
CCR7_NEG 534 591 90.4
CD45RA_NEG 69 591 11.7
EOMES_NEG 61 591 10.3
CCR7_POS 57 591 9.6
CD45RA_POS 522 591 88.3
EOMES_POS 530 591 89.7
Denom = CD4_POS_CD8_NEG_LAG3_POS
CCR7_NEG 174 326 53.4
CD45RA_NEG 130 326 39.9
EOMES_NEG 173 326 53.1
CCR7_POS 152 326 46.6
CD45RA_POS 196 326 60.1
EOMES_POS 153 326 46.9
Denom = CD4_POS_CD8_POS_LAG3_POS
CCR7_NEG 73 137 53.3
CD45RA_NEG 9 137 6.6
EOMES_NEG 21 137 15.3
CCR7_POS 64 137 46.7
CD45RA_POS 128 137 93.4
EOMES_POS 116 137 84.7
Denom = CD4_NEG_CD8_NEG_CCR7_POS
LAG3_NEG 49 51 96.1
CD45RA_NEG 4 51 7.8
EOMES_NEG 39 51 76.5
LAG3_POS 2 51 3.9
CD45RA_POS 47 51 92.2
EOMES_POS 12 51 23.5
Denom = CD4_NEG_CD8_POS_CCR7_POS
LAG3_NEG 546 603 90.5
CD45RA_NEG 84 603 13.9
EOMES_NEG 390 603 64.7
LAG3_POS 57 603 9.5
CD45RA_POS 519 603 86.1
EOMES_POS 213 603 35.3
Denom = CD4_POS_CD8_NEG_CCR7_POS
LAG3_NEG 7068 7220 97.9
CD45RA_NEG 903 7220 12.5
EOMES_NEG 6989 7220 96.8
LAG3_POS 152 7220 2.1
CD45RA_POS 6317 7220 87.5
EOMES_POS 231 7220 3.2
Denom = CD4_POS_CD8_POS_CCR7_POS
LAG3_NEG 60 124 48.4
CD45RA_NEG 16 124 12.9
EOMES_NEG 38 124 30.6
LAG3_POS 64 124 51.6
CD45RA_POS 108 124 87.1
EOMES_POS 86 124 69.4
Denom = CD4_NEG_CD8_NEG_CD45RA_POS
LAG3_NEG 179 250 71.6
CCR7_NEG 203 250 81.2
EOMES_NEG 52 250 20.8
LAG3_POS 71 250 28.4
CCR7_POS 47 250 18.8
EOMES_POS 198 250 79.2
Denom = CD4_NEG_CD8_POS_CD45RA_POS
LAG3_NEG 1367 1889 72.4
CCR7_NEG 1370 1889 72.5
EOMES_NEG 523 1889 27.7
LAG3_POS 522 1889 27.6
CCR7_POS 519 1889 27.5
EOMES_POS 1366 1889 72.3
Denom = CD4_POS_CD8_NEG_CD45RA_POS
LAG3_NEG 6589 6785 97.1
CCR7_NEG 468 6785 6.9
EOMES_NEG 6339 6785 93.4
LAG3_POS 196 6785 2.9
CCR7_POS 6317 6785 93.1
EOMES_POS 446 6785 6.6
Denom = CD4_POS_CD8_POS_CD45RA_POS
LAG3_NEG 113 241 46.9
CCR7_NEG 133 241 55.2
EOMES_NEG 50 241 20.7
LAG3_POS 128 241 53.1
CCR7_POS 108 241 44.8
EOMES_POS 191 241 79.3
Denom = CD4_NEG_CD8_NEG_EOMES_POS
LAG3_NEG 138 212 65.1
CCR7_NEG 200 212 94.3
CD45RA_NEG 14 212 6.6
LAG3_POS 74 212 34.9
CCR7_POS 12 212 5.7
CD45RA_POS 198 212 93.4
Denom = CD4_NEG_CD8_POS_EOMES_POS
LAG3_NEG 1124 1654 68.0
CCR7_NEG 1441 1654 87.1
CD45RA_NEG 288 1654 17.4
LAG3_POS 530 1654 32.0
CCR7_POS 213 1654 12.9
CD45RA_POS 1366 1654 82.6
Denom = CD4_POS_CD8_NEG_EOMES_POS
LAG3_NEG 662 815 81.2
CCR7_NEG 584 815 71.7
CD45RA_NEG 369 815 45.3
LAG3_POS 153 815 18.8
CCR7_POS 231 815 28.3
CD45RA_POS 446 815 54.7
Denom = CD4_POS_CD8_POS_EOMES_POS
LAG3_NEG 89 205 43.4
CCR7_NEG 119 205 58.0
CD45RA_NEG 14 205 6.8
LAG3_POS 116 205 56.6
CCR7_POS 86 205 42.0
CD45RA_POS 191 205 93.2
Expand example for expand_num = TRUE and expand_denom = TRUE , and keep_indicators = FALSE
example_perc4 <-
  # Should only count the CD3+ cells
  dplyr::filter(example_intensity_gated, cd3_pos == 1) |>
  getPerc(
    intens_dat = _,
    num_marker = example_markers,
    denom_marker = c("CD4", "CD8"),
    expand_num = TRUE,
    expand_denom = TRUE,
    keep_indicators = FALSE
  )

# For display only, group based on the denominators and
# simplify the names to be numerators
example_perc4 |>
  tidyr::separate_wider_delim(subpopulation,
    delim = "_OF_",
    names = c("num", "denom"),
    cols_remove = FALSE
  ) |>
  dplyr::mutate(denom = paste("Denom = ", denom)) |>
  dplyr::group_by(denom) |>
  dplyr::select(-subpopulation) |>
  gt::gt() |>
  gt::fmt_number(
    columns = "perc",
    decimals = 1
  )
num n_num n_denom perc
Denom = CD4_NEG_CD8_NEG
LAG3_NEG 196 271 72.3
CCR7_NEG 220 271 81.2
CD45RA_NEG 21 271 7.7
EOMES_NEG 59 271 21.8
LAG3_POS 75 271 27.7
CCR7_POS 51 271 18.8
CD45RA_POS 250 271 92.3
EOMES_POS 212 271 78.2
LAG3_NEG_CCR7_NEG 147 271 54.2
LAG3_NEG_CD45RA_NEG 17 271 6.3
LAG3_NEG_EOMES_NEG 58 271 21.4
LAG3_NEG_CCR7_POS 49 271 18.1
LAG3_NEG_CD45RA_POS 179 271 66.1
LAG3_NEG_EOMES_POS 138 271 50.9
CCR7_NEG_CD45RA_NEG 17 271 6.3
CCR7_NEG_EOMES_NEG 20 271 7.4
CCR7_NEG_LAG3_POS 73 271 26.9
CCR7_NEG_CD45RA_POS 203 271 74.9
CCR7_NEG_EOMES_POS 200 271 73.8
CD45RA_NEG_EOMES_NEG 7 271 2.6
CD45RA_NEG_LAG3_POS 4 271 1.5
CD45RA_NEG_CCR7_POS 4 271 1.5
CD45RA_NEG_EOMES_POS 14 271 5.2
EOMES_NEG_LAG3_POS 1 271 0.4
EOMES_NEG_CCR7_POS 39 271 14.4
EOMES_NEG_CD45RA_POS 52 271 19.2
LAG3_POS_CCR7_POS 2 271 0.7
LAG3_POS_CD45RA_POS 71 271 26.2
LAG3_POS_EOMES_POS 74 271 27.3
CCR7_POS_CD45RA_POS 47 271 17.3
CCR7_POS_EOMES_POS 12 271 4.4
CD45RA_POS_EOMES_POS 198 271 73.1
Denom = CD4_NEG_CD8_POS
LAG3_NEG 1711 2302 74.3
CCR7_NEG 1699 2302 73.8
CD45RA_NEG 413 2302 17.9
EOMES_NEG 648 2302 28.1
LAG3_POS 591 2302 25.7
CCR7_POS 603 2302 26.2
CD45RA_POS 1889 2302 82.1
EOMES_POS 1654 2302 71.9
LAG3_NEG_CCR7_NEG 1165 2302 50.6
LAG3_NEG_CD45RA_NEG 344 2302 14.9
LAG3_NEG_EOMES_NEG 587 2302 25.5
LAG3_NEG_CCR7_POS 546 2302 23.7
LAG3_NEG_CD45RA_POS 1367 2302 59.4
LAG3_NEG_EOMES_POS 1124 2302 48.8
CCR7_NEG_CD45RA_NEG 329 2302 14.3
CCR7_NEG_EOMES_NEG 258 2302 11.2
CCR7_NEG_LAG3_POS 534 2302 23.2
CCR7_NEG_CD45RA_POS 1370 2302 59.5
CCR7_NEG_EOMES_POS 1441 2302 62.6
CD45RA_NEG_EOMES_NEG 125 2302 5.4
CD45RA_NEG_LAG3_POS 69 2302 3.0
CD45RA_NEG_CCR7_POS 84 2302 3.6
CD45RA_NEG_EOMES_POS 288 2302 12.5
EOMES_NEG_LAG3_POS 61 2302 2.6
EOMES_NEG_CCR7_POS 390 2302 16.9
EOMES_NEG_CD45RA_POS 523 2302 22.7
LAG3_POS_CCR7_POS 57 2302 2.5
LAG3_POS_CD45RA_POS 522 2302 22.7
LAG3_POS_EOMES_POS 530 2302 23.0
CCR7_POS_CD45RA_POS 519 2302 22.5
CCR7_POS_EOMES_POS 213 2302 9.3
CD45RA_POS_EOMES_POS 1366 2302 59.3
Denom = CD4_POS_CD8_NEG
LAG3_NEG 8289 8615 96.2
CCR7_NEG 1395 8615 16.2
CD45RA_NEG 1830 8615 21.2
EOMES_NEG 7800 8615 90.5
LAG3_POS 326 8615 3.8
CCR7_POS 7220 8615 83.8
CD45RA_POS 6785 8615 78.8
EOMES_POS 815 8615 9.5
LAG3_NEG_CCR7_NEG 1221 8615 14.2
LAG3_NEG_CD45RA_NEG 1700 8615 19.7
LAG3_NEG_EOMES_NEG 7627 8615 88.5
LAG3_NEG_CCR7_POS 7068 8615 82.0
LAG3_NEG_CD45RA_POS 6589 8615 76.5
LAG3_NEG_EOMES_POS 662 8615 7.7
CCR7_NEG_CD45RA_NEG 927 8615 10.8
CCR7_NEG_EOMES_NEG 811 8615 9.4
CCR7_NEG_LAG3_POS 174 8615 2.0
CCR7_NEG_CD45RA_POS 468 8615 5.4
CCR7_NEG_EOMES_POS 584 8615 6.8
CD45RA_NEG_EOMES_NEG 1461 8615 17.0
CD45RA_NEG_LAG3_POS 130 8615 1.5
CD45RA_NEG_CCR7_POS 903 8615 10.5
CD45RA_NEG_EOMES_POS 369 8615 4.3
EOMES_NEG_LAG3_POS 173 8615 2.0
EOMES_NEG_CCR7_POS 6989 8615 81.1
EOMES_NEG_CD45RA_POS 6339 8615 73.6
LAG3_POS_CCR7_POS 152 8615 1.8
LAG3_POS_CD45RA_POS 196 8615 2.3
LAG3_POS_EOMES_POS 153 8615 1.8
CCR7_POS_CD45RA_POS 6317 8615 73.3
CCR7_POS_EOMES_POS 231 8615 2.7
CD45RA_POS_EOMES_POS 446 8615 5.2
Denom = CD4_POS_CD8_POS
LAG3_NEG 139 276 50.4
CCR7_NEG 152 276 55.1
CD45RA_NEG 35 276 12.7
EOMES_NEG 71 276 25.7
LAG3_POS 137 276 49.6
CCR7_POS 124 276 44.9
CD45RA_POS 241 276 87.3
EOMES_POS 205 276 74.3
LAG3_NEG_CCR7_NEG 79 276 28.6
LAG3_NEG_CD45RA_NEG 26 276 9.4
LAG3_NEG_EOMES_NEG 50 276 18.1
LAG3_NEG_CCR7_POS 60 276 21.7
LAG3_NEG_CD45RA_POS 113 276 40.9
LAG3_NEG_EOMES_POS 89 276 32.2
CCR7_NEG_CD45RA_NEG 19 276 6.9
CCR7_NEG_EOMES_NEG 33 276 12.0
CCR7_NEG_LAG3_POS 73 276 26.4
CCR7_NEG_CD45RA_POS 133 276 48.2
CCR7_NEG_EOMES_POS 119 276 43.1
CD45RA_NEG_EOMES_NEG 21 276 7.6
CD45RA_NEG_LAG3_POS 9 276 3.3
CD45RA_NEG_CCR7_POS 16 276 5.8
CD45RA_NEG_EOMES_POS 14 276 5.1
EOMES_NEG_LAG3_POS 21 276 7.6
EOMES_NEG_CCR7_POS 38 276 13.8
EOMES_NEG_CD45RA_POS 50 276 18.1
LAG3_POS_CCR7_POS 64 276 23.2
LAG3_POS_CD45RA_POS 128 276 46.4
LAG3_POS_EOMES_POS 116 276 42.0
CCR7_POS_CD45RA_POS 108 276 39.1
CCR7_POS_EOMES_POS 86 276 31.2
CD45RA_POS_EOMES_POS 191 276 69.2
Denom = CD4_NEG_CD8_NEG_LAG3_NEG
CCR7_NEG 147 196 75.0
CD45RA_NEG 17 196 8.7
EOMES_NEG 58 196 29.6
CCR7_POS 49 196 25.0
CD45RA_POS 179 196 91.3
EOMES_POS 138 196 70.4
CCR7_NEG_CD45RA_NEG 15 196 7.7
CCR7_NEG_EOMES_NEG 19 196 9.7
CCR7_NEG_CD45RA_POS 132 196 67.3
CCR7_NEG_EOMES_POS 128 196 65.3
CD45RA_NEG_EOMES_NEG 7 196 3.6
CD45RA_NEG_CCR7_POS 2 196 1.0
CD45RA_NEG_EOMES_POS 10 196 5.1
EOMES_NEG_CCR7_POS 39 196 19.9
EOMES_NEG_CD45RA_POS 51 196 26.0
CCR7_POS_CD45RA_POS 47 196 24.0
CCR7_POS_EOMES_POS 10 196 5.1
CD45RA_POS_EOMES_POS 128 196 65.3
Denom = CD4_NEG_CD8_POS_LAG3_NEG
CCR7_NEG 1165 1711 68.1
CD45RA_NEG 344 1711 20.1
EOMES_NEG 587 1711 34.3
CCR7_POS 546 1711 31.9
CD45RA_POS 1367 1711 79.9
EOMES_POS 1124 1711 65.7
CCR7_NEG_CD45RA_NEG 269 1711 15.7
CCR7_NEG_EOMES_NEG 203 1711 11.9
CCR7_NEG_CD45RA_POS 896 1711 52.4
CCR7_NEG_EOMES_POS 962 1711 56.2
CD45RA_NEG_EOMES_NEG 115 1711 6.7
CD45RA_NEG_CCR7_POS 75 1711 4.4
CD45RA_NEG_EOMES_POS 229 1711 13.4
EOMES_NEG_CCR7_POS 384 1711 22.4
EOMES_NEG_CD45RA_POS 472 1711 27.6
CCR7_POS_CD45RA_POS 471 1711 27.5
CCR7_POS_EOMES_POS 162 1711 9.5
CD45RA_POS_EOMES_POS 895 1711 52.3
Denom = CD4_POS_CD8_NEG_LAG3_NEG
CCR7_NEG 1221 8289 14.7
CD45RA_NEG 1700 8289 20.5
EOMES_NEG 7627 8289 92.0
CCR7_POS 7068 8289 85.3
CD45RA_POS 6589 8289 79.5
EOMES_POS 662 8289 8.0
CCR7_NEG_CD45RA_NEG 837 8289 10.1
CCR7_NEG_EOMES_NEG 736 8289 8.9
CCR7_NEG_CD45RA_POS 384 8289 4.6
CCR7_NEG_EOMES_POS 485 8289 5.9
CD45RA_NEG_EOMES_NEG 1371 8289 16.5
CD45RA_NEG_CCR7_POS 863 8289 10.4
CD45RA_NEG_EOMES_POS 329 8289 4.0
EOMES_NEG_CCR7_POS 6891 8289 83.1
EOMES_NEG_CD45RA_POS 6256 8289 75.5
CCR7_POS_CD45RA_POS 6205 8289 74.9
CCR7_POS_EOMES_POS 177 8289 2.1
CD45RA_POS_EOMES_POS 333 8289 4.0
Denom = CD4_POS_CD8_POS_LAG3_NEG
CCR7_NEG 79 139 56.8
CD45RA_NEG 26 139 18.7
EOMES_NEG 50 139 36.0
CCR7_POS 60 139 43.2
CD45RA_POS 113 139 81.3
EOMES_POS 89 139 64.0
CCR7_NEG_CD45RA_NEG 11 139 7.9
CCR7_NEG_EOMES_NEG 22 139 15.8
CCR7_NEG_CD45RA_POS 68 139 48.9
CCR7_NEG_EOMES_POS 57 139 41.0
CD45RA_NEG_EOMES_NEG 19 139 13.7
CD45RA_NEG_CCR7_POS 15 139 10.8
CD45RA_NEG_EOMES_POS 7 139 5.0
EOMES_NEG_CCR7_POS 28 139 20.1
EOMES_NEG_CD45RA_POS 31 139 22.3
CCR7_POS_CD45RA_POS 45 139 32.4
CCR7_POS_EOMES_POS 32 139 23.0
CD45RA_POS_EOMES_POS 82 139 59.0
Denom = CD4_NEG_CD8_NEG_CCR7_NEG
LAG3_NEG 147 220 66.8
CD45RA_NEG 17 220 7.7
EOMES_NEG 20 220 9.1
LAG3_POS 73 220 33.2
CD45RA_POS 203 220 92.3
EOMES_POS 200 220 90.9
LAG3_NEG_CD45RA_NEG 15 220 6.8
LAG3_NEG_EOMES_NEG 19 220 8.6
LAG3_NEG_CD45RA_POS 132 220 60.0
LAG3_NEG_EOMES_POS 128 220 58.2
CD45RA_NEG_EOMES_NEG 6 220 2.7
CD45RA_NEG_LAG3_POS 2 220 0.9
CD45RA_NEG_EOMES_POS 11 220 5.0
EOMES_NEG_LAG3_POS 1 220 0.5
EOMES_NEG_CD45RA_POS 14 220 6.4
LAG3_POS_CD45RA_POS 71 220 32.3
LAG3_POS_EOMES_POS 72 220 32.7
CD45RA_POS_EOMES_POS 189 220 85.9
Denom = CD4_NEG_CD8_POS_CCR7_NEG
LAG3_NEG 1165 1699 68.6
CD45RA_NEG 329 1699 19.4
EOMES_NEG 258 1699 15.2
LAG3_POS 534 1699 31.4
CD45RA_POS 1370 1699 80.6
EOMES_POS 1441 1699 84.8
LAG3_NEG_CD45RA_NEG 269 1699 15.8
LAG3_NEG_EOMES_NEG 203 1699 11.9
LAG3_NEG_CD45RA_POS 896 1699 52.7
LAG3_NEG_EOMES_POS 962 1699 56.6
CD45RA_NEG_EOMES_NEG 81 1699 4.8
CD45RA_NEG_LAG3_POS 60 1699 3.5
CD45RA_NEG_EOMES_POS 248 1699 14.6
EOMES_NEG_LAG3_POS 55 1699 3.2
EOMES_NEG_CD45RA_POS 177 1699 10.4
LAG3_POS_CD45RA_POS 474 1699 27.9
LAG3_POS_EOMES_POS 479 1699 28.2
CD45RA_POS_EOMES_POS 1193 1699 70.2
Denom = CD4_POS_CD8_NEG_CCR7_NEG
LAG3_NEG 1221 1395 87.5
CD45RA_NEG 927 1395 66.5
EOMES_NEG 811 1395 58.1
LAG3_POS 174 1395 12.5
CD45RA_POS 468 1395 33.5
EOMES_POS 584 1395 41.9
LAG3_NEG_CD45RA_NEG 837 1395 60.0
LAG3_NEG_EOMES_NEG 736 1395 52.8
LAG3_NEG_CD45RA_POS 384 1395 27.5
LAG3_NEG_EOMES_POS 485 1395 34.8
CD45RA_NEG_EOMES_NEG 642 1395 46.0
CD45RA_NEG_LAG3_POS 90 1395 6.5
CD45RA_NEG_EOMES_POS 285 1395 20.4
EOMES_NEG_LAG3_POS 75 1395 5.4
EOMES_NEG_CD45RA_POS 169 1395 12.1
LAG3_POS_CD45RA_POS 84 1395 6.0
LAG3_POS_EOMES_POS 99 1395 7.1
CD45RA_POS_EOMES_POS 299 1395 21.4
Denom = CD4_POS_CD8_POS_CCR7_NEG
LAG3_NEG 79 152 52.0
CD45RA_NEG 19 152 12.5
EOMES_NEG 33 152 21.7
LAG3_POS 73 152 48.0
CD45RA_POS 133 152 87.5
EOMES_POS 119 152 78.3
LAG3_NEG_CD45RA_NEG 11 152 7.2
LAG3_NEG_EOMES_NEG 22 152 14.5
LAG3_NEG_CD45RA_POS 68 152 44.7
LAG3_NEG_EOMES_POS 57 152 37.5
CD45RA_NEG_EOMES_NEG 9 152 5.9
CD45RA_NEG_LAG3_POS 8 152 5.3
CD45RA_NEG_EOMES_POS 10 152 6.6
EOMES_NEG_LAG3_POS 11 152 7.2
EOMES_NEG_CD45RA_POS 24 152 15.8
LAG3_POS_CD45RA_POS 65 152 42.8
LAG3_POS_EOMES_POS 62 152 40.8
CD45RA_POS_EOMES_POS 109 152 71.7
Denom = CD4_NEG_CD8_NEG_CD45RA_NEG
LAG3_NEG 17 21 81.0
CCR7_NEG 17 21 81.0
EOMES_NEG 7 21 33.3
LAG3_POS 4 21 19.0
CCR7_POS 4 21 19.0
EOMES_POS 14 21 66.7
LAG3_NEG_CCR7_NEG 15 21 71.4
LAG3_NEG_EOMES_NEG 7 21 33.3
LAG3_NEG_CCR7_POS 2 21 9.5
LAG3_NEG_EOMES_POS 10 21 47.6
CCR7_NEG_EOMES_NEG 6 21 28.6
CCR7_NEG_LAG3_POS 2 21 9.5
CCR7_NEG_EOMES_POS 11 21 52.4
EOMES_NEG_LAG3_POS 0 21 0.0
EOMES_NEG_CCR7_POS 1 21 4.8
LAG3_POS_CCR7_POS 2 21 9.5
LAG3_POS_EOMES_POS 4 21 19.0
CCR7_POS_EOMES_POS 3 21 14.3
Denom = CD4_NEG_CD8_POS_CD45RA_NEG
LAG3_NEG 344 413 83.3
CCR7_NEG 329 413 79.7
EOMES_NEG 125 413 30.3
LAG3_POS 69 413 16.7
CCR7_POS 84 413 20.3
EOMES_POS 288 413 69.7
LAG3_NEG_CCR7_NEG 269 413 65.1
LAG3_NEG_EOMES_NEG 115 413 27.8
LAG3_NEG_CCR7_POS 75 413 18.2
LAG3_NEG_EOMES_POS 229 413 55.4
CCR7_NEG_EOMES_NEG 81 413 19.6
CCR7_NEG_LAG3_POS 60 413 14.5
CCR7_NEG_EOMES_POS 248 413 60.0
EOMES_NEG_LAG3_POS 10 413 2.4
EOMES_NEG_CCR7_POS 44 413 10.7
LAG3_POS_CCR7_POS 9 413 2.2
LAG3_POS_EOMES_POS 59 413 14.3
CCR7_POS_EOMES_POS 40 413 9.7
Denom = CD4_POS_CD8_NEG_CD45RA_NEG
LAG3_NEG 1700 1830 92.9
CCR7_NEG 927 1830 50.7
EOMES_NEG 1461 1830 79.8
LAG3_POS 130 1830 7.1
CCR7_POS 903 1830 49.3
EOMES_POS 369 1830 20.2
LAG3_NEG_CCR7_NEG 837 1830 45.7
LAG3_NEG_EOMES_NEG 1371 1830 74.9
LAG3_NEG_CCR7_POS 863 1830 47.2
LAG3_NEG_EOMES_POS 329 1830 18.0
CCR7_NEG_EOMES_NEG 642 1830 35.1
CCR7_NEG_LAG3_POS 90 1830 4.9
CCR7_NEG_EOMES_POS 285 1830 15.6
EOMES_NEG_LAG3_POS 90 1830 4.9
EOMES_NEG_CCR7_POS 819 1830 44.8
LAG3_POS_CCR7_POS 40 1830 2.2
LAG3_POS_EOMES_POS 40 1830 2.2
CCR7_POS_EOMES_POS 84 1830 4.6
Denom = CD4_POS_CD8_POS_CD45RA_NEG
LAG3_NEG 26 35 74.3
CCR7_NEG 19 35 54.3
EOMES_NEG 21 35 60.0
LAG3_POS 9 35 25.7
CCR7_POS 16 35 45.7
EOMES_POS 14 35 40.0
LAG3_NEG_CCR7_NEG 11 35 31.4
LAG3_NEG_EOMES_NEG 19 35 54.3
LAG3_NEG_CCR7_POS 15 35 42.9
LAG3_NEG_EOMES_POS 7 35 20.0
CCR7_NEG_EOMES_NEG 9 35 25.7
CCR7_NEG_LAG3_POS 8 35 22.9
CCR7_NEG_EOMES_POS 10 35 28.6
EOMES_NEG_LAG3_POS 2 35 5.7
EOMES_NEG_CCR7_POS 12 35 34.3
LAG3_POS_CCR7_POS 1 35 2.9
LAG3_POS_EOMES_POS 7 35 20.0
CCR7_POS_EOMES_POS 4 35 11.4
Denom = CD4_NEG_CD8_NEG_EOMES_NEG
LAG3_NEG 58 59 98.3
CCR7_NEG 20 59 33.9
CD45RA_NEG 7 59 11.9
LAG3_POS 1 59 1.7
CCR7_POS 39 59 66.1
CD45RA_POS 52 59 88.1
LAG3_NEG_CCR7_NEG 19 59 32.2
LAG3_NEG_CD45RA_NEG 7 59 11.9
LAG3_NEG_CCR7_POS 39 59 66.1
LAG3_NEG_CD45RA_POS 51 59 86.4
CCR7_NEG_CD45RA_NEG 6 59 10.2
CCR7_NEG_LAG3_POS 1 59 1.7
CCR7_NEG_CD45RA_POS 14 59 23.7
CD45RA_NEG_LAG3_POS 0 59 0.0
CD45RA_NEG_CCR7_POS 1 59 1.7
LAG3_POS_CCR7_POS 0 59 0.0
LAG3_POS_CD45RA_POS 1 59 1.7
CCR7_POS_CD45RA_POS 38 59 64.4
Denom = CD4_NEG_CD8_POS_EOMES_NEG
LAG3_NEG 587 648 90.6
CCR7_NEG 258 648 39.8
CD45RA_NEG 125 648 19.3
LAG3_POS 61 648 9.4
CCR7_POS 390 648 60.2
CD45RA_POS 523 648 80.7
LAG3_NEG_CCR7_NEG 203 648 31.3
LAG3_NEG_CD45RA_NEG 115 648 17.7
LAG3_NEG_CCR7_POS 384 648 59.3
LAG3_NEG_CD45RA_POS 472 648 72.8
CCR7_NEG_CD45RA_NEG 81 648 12.5
CCR7_NEG_LAG3_POS 55 648 8.5
CCR7_NEG_CD45RA_POS 177 648 27.3
CD45RA_NEG_LAG3_POS 10 648 1.5
CD45RA_NEG_CCR7_POS 44 648 6.8
LAG3_POS_CCR7_POS 6 648 0.9
LAG3_POS_CD45RA_POS 51 648 7.9
CCR7_POS_CD45RA_POS 346 648 53.4
Denom = CD4_POS_CD8_NEG_EOMES_NEG
LAG3_NEG 7627 7800 97.8
CCR7_NEG 811 7800 10.4
CD45RA_NEG 1461 7800 18.7
LAG3_POS 173 7800 2.2
CCR7_POS 6989 7800 89.6
CD45RA_POS 6339 7800 81.3
LAG3_NEG_CCR7_NEG 736 7800 9.4
LAG3_NEG_CD45RA_NEG 1371 7800 17.6
LAG3_NEG_CCR7_POS 6891 7800 88.3
LAG3_NEG_CD45RA_POS 6256 7800 80.2
CCR7_NEG_CD45RA_NEG 642 7800 8.2
CCR7_NEG_LAG3_POS 75 7800 1.0
CCR7_NEG_CD45RA_POS 169 7800 2.2
CD45RA_NEG_LAG3_POS 90 7800 1.2
CD45RA_NEG_CCR7_POS 819 7800 10.5
LAG3_POS_CCR7_POS 98 7800 1.3
LAG3_POS_CD45RA_POS 83 7800 1.1
CCR7_POS_CD45RA_POS 6170 7800 79.1
Denom = CD4_POS_CD8_POS_EOMES_NEG
LAG3_NEG 50 71 70.4
CCR7_NEG 33 71 46.5
CD45RA_NEG 21 71 29.6
LAG3_POS 21 71 29.6
CCR7_POS 38 71 53.5
CD45RA_POS 50 71 70.4
LAG3_NEG_CCR7_NEG 22 71 31.0
LAG3_NEG_CD45RA_NEG 19 71 26.8
LAG3_NEG_CCR7_POS 28 71 39.4
LAG3_NEG_CD45RA_POS 31 71 43.7
CCR7_NEG_CD45RA_NEG 9 71 12.7
CCR7_NEG_LAG3_POS 11 71 15.5
CCR7_NEG_CD45RA_POS 24 71 33.8
CD45RA_NEG_LAG3_POS 2 71 2.8
CD45RA_NEG_CCR7_POS 12 71 16.9
LAG3_POS_CCR7_POS 10 71 14.1
LAG3_POS_CD45RA_POS 19 71 26.8
CCR7_POS_CD45RA_POS 26 71 36.6
Denom = CD4_NEG_CD8_NEG_LAG3_POS
CCR7_NEG 73 75 97.3
CD45RA_NEG 4 75 5.3
EOMES_NEG 1 75 1.3
CCR7_POS 2 75 2.7
CD45RA_POS 71 75 94.7
EOMES_POS 74 75 98.7
CCR7_NEG_CD45RA_NEG 2 75 2.7
CCR7_NEG_EOMES_NEG 1 75 1.3
CCR7_NEG_CD45RA_POS 71 75 94.7
CCR7_NEG_EOMES_POS 72 75 96.0
CD45RA_NEG_EOMES_NEG 0 75 0.0
CD45RA_NEG_CCR7_POS 2 75 2.7
CD45RA_NEG_EOMES_POS 4 75 5.3
EOMES_NEG_CCR7_POS 0 75 0.0
EOMES_NEG_CD45RA_POS 1 75 1.3
CCR7_POS_CD45RA_POS 0 75 0.0
CCR7_POS_EOMES_POS 2 75 2.7
CD45RA_POS_EOMES_POS 70 75 93.3
Denom = CD4_NEG_CD8_POS_LAG3_POS
CCR7_NEG 534 591 90.4
CD45RA_NEG 69 591 11.7
EOMES_NEG 61 591 10.3
CCR7_POS 57 591 9.6
CD45RA_POS 522 591 88.3
EOMES_POS 530 591 89.7
CCR7_NEG_CD45RA_NEG 60 591 10.2
CCR7_NEG_EOMES_NEG 55 591 9.3
CCR7_NEG_CD45RA_POS 474 591 80.2
CCR7_NEG_EOMES_POS 479 591 81.0
CD45RA_NEG_EOMES_NEG 10 591 1.7
CD45RA_NEG_CCR7_POS 9 591 1.5
CD45RA_NEG_EOMES_POS 59 591 10.0
EOMES_NEG_CCR7_POS 6 591 1.0
EOMES_NEG_CD45RA_POS 51 591 8.6
CCR7_POS_CD45RA_POS 48 591 8.1
CCR7_POS_EOMES_POS 51 591 8.6
CD45RA_POS_EOMES_POS 471 591 79.7
Denom = CD4_POS_CD8_NEG_LAG3_POS
CCR7_NEG 174 326 53.4
CD45RA_NEG 130 326 39.9
EOMES_NEG 173 326 53.1
CCR7_POS 152 326 46.6
CD45RA_POS 196 326 60.1
EOMES_POS 153 326 46.9
CCR7_NEG_CD45RA_NEG 90 326 27.6
CCR7_NEG_EOMES_NEG 75 326 23.0
CCR7_NEG_CD45RA_POS 84 326 25.8
CCR7_NEG_EOMES_POS 99 326 30.4
CD45RA_NEG_EOMES_NEG 90 326 27.6
CD45RA_NEG_CCR7_POS 40 326 12.3
CD45RA_NEG_EOMES_POS 40 326 12.3
EOMES_NEG_CCR7_POS 98 326 30.1
EOMES_NEG_CD45RA_POS 83 326 25.5
CCR7_POS_CD45RA_POS 112 326 34.4
CCR7_POS_EOMES_POS 54 326 16.6
CD45RA_POS_EOMES_POS 113 326 34.7
Denom = CD4_POS_CD8_POS_LAG3_POS
CCR7_NEG 73 137 53.3
CD45RA_NEG 9 137 6.6
EOMES_NEG 21 137 15.3
CCR7_POS 64 137 46.7
CD45RA_POS 128 137 93.4
EOMES_POS 116 137 84.7
CCR7_NEG_CD45RA_NEG 8 137 5.8
CCR7_NEG_EOMES_NEG 11 137 8.0
CCR7_NEG_CD45RA_POS 65 137 47.4
CCR7_NEG_EOMES_POS 62 137 45.3
CD45RA_NEG_EOMES_NEG 2 137 1.5
CD45RA_NEG_CCR7_POS 1 137 0.7
CD45RA_NEG_EOMES_POS 7 137 5.1
EOMES_NEG_CCR7_POS 10 137 7.3
EOMES_NEG_CD45RA_POS 19 137 13.9
CCR7_POS_CD45RA_POS 63 137 46.0
CCR7_POS_EOMES_POS 54 137 39.4
CD45RA_POS_EOMES_POS 109 137 79.6
Denom = CD4_NEG_CD8_NEG_CCR7_POS
LAG3_NEG 49 51 96.1
CD45RA_NEG 4 51 7.8
EOMES_NEG 39 51 76.5
LAG3_POS 2 51 3.9
CD45RA_POS 47 51 92.2
EOMES_POS 12 51 23.5
LAG3_NEG_CD45RA_NEG 2 51 3.9
LAG3_NEG_EOMES_NEG 39 51 76.5
LAG3_NEG_CD45RA_POS 47 51 92.2
LAG3_NEG_EOMES_POS 10 51 19.6
CD45RA_NEG_EOMES_NEG 1 51 2.0
CD45RA_NEG_LAG3_POS 2 51 3.9
CD45RA_NEG_EOMES_POS 3 51 5.9
EOMES_NEG_LAG3_POS 0 51 0.0
EOMES_NEG_CD45RA_POS 38 51 74.5
LAG3_POS_CD45RA_POS 0 51 0.0
LAG3_POS_EOMES_POS 2 51 3.9
CD45RA_POS_EOMES_POS 9 51 17.6
Denom = CD4_NEG_CD8_POS_CCR7_POS
LAG3_NEG 546 603 90.5
CD45RA_NEG 84 603 13.9
EOMES_NEG 390 603 64.7
LAG3_POS 57 603 9.5
CD45RA_POS 519 603 86.1
EOMES_POS 213 603 35.3
LAG3_NEG_CD45RA_NEG 75 603 12.4
LAG3_NEG_EOMES_NEG 384 603 63.7
LAG3_NEG_CD45RA_POS 471 603 78.1
LAG3_NEG_EOMES_POS 162 603 26.9
CD45RA_NEG_EOMES_NEG 44 603 7.3
CD45RA_NEG_LAG3_POS 9 603 1.5
CD45RA_NEG_EOMES_POS 40 603 6.6
EOMES_NEG_LAG3_POS 6 603 1.0
EOMES_NEG_CD45RA_POS 346 603 57.4
LAG3_POS_CD45RA_POS 48 603 8.0
LAG3_POS_EOMES_POS 51 603 8.5
CD45RA_POS_EOMES_POS 173 603 28.7
Denom = CD4_POS_CD8_NEG_CCR7_POS
LAG3_NEG 7068 7220 97.9
CD45RA_NEG 903 7220 12.5
EOMES_NEG 6989 7220 96.8
LAG3_POS 152 7220 2.1
CD45RA_POS 6317 7220 87.5
EOMES_POS 231 7220 3.2
LAG3_NEG_CD45RA_NEG 863 7220 12.0
LAG3_NEG_EOMES_NEG 6891 7220 95.4
LAG3_NEG_CD45RA_POS 6205 7220 85.9
LAG3_NEG_EOMES_POS 177 7220 2.5
CD45RA_NEG_EOMES_NEG 819 7220 11.3
CD45RA_NEG_LAG3_POS 40 7220 0.6
CD45RA_NEG_EOMES_POS 84 7220 1.2
EOMES_NEG_LAG3_POS 98 7220 1.4
EOMES_NEG_CD45RA_POS 6170 7220 85.5
LAG3_POS_CD45RA_POS 112 7220 1.6
LAG3_POS_EOMES_POS 54 7220 0.7
CD45RA_POS_EOMES_POS 147 7220 2.0
Denom = CD4_POS_CD8_POS_CCR7_POS
LAG3_NEG 60 124 48.4
CD45RA_NEG 16 124 12.9
EOMES_NEG 38 124 30.6
LAG3_POS 64 124 51.6
CD45RA_POS 108 124 87.1
EOMES_POS 86 124 69.4
LAG3_NEG_CD45RA_NEG 15 124 12.1
LAG3_NEG_EOMES_NEG 28 124 22.6
LAG3_NEG_CD45RA_POS 45 124 36.3
LAG3_NEG_EOMES_POS 32 124 25.8
CD45RA_NEG_EOMES_NEG 12 124 9.7
CD45RA_NEG_LAG3_POS 1 124 0.8
CD45RA_NEG_EOMES_POS 4 124 3.2
EOMES_NEG_LAG3_POS 10 124 8.1
EOMES_NEG_CD45RA_POS 26 124 21.0
LAG3_POS_CD45RA_POS 63 124 50.8
LAG3_POS_EOMES_POS 54 124 43.5
CD45RA_POS_EOMES_POS 82 124 66.1
Denom = CD4_NEG_CD8_NEG_CD45RA_POS
LAG3_NEG 179 250 71.6
CCR7_NEG 203 250 81.2
EOMES_NEG 52 250 20.8
LAG3_POS 71 250 28.4
CCR7_POS 47 250 18.8
EOMES_POS 198 250 79.2
LAG3_NEG_CCR7_NEG 132 250 52.8
LAG3_NEG_EOMES_NEG 51 250 20.4
LAG3_NEG_CCR7_POS 47 250 18.8
LAG3_NEG_EOMES_POS 128 250 51.2
CCR7_NEG_EOMES_NEG 14 250 5.6
CCR7_NEG_LAG3_POS 71 250 28.4
CCR7_NEG_EOMES_POS 189 250 75.6
EOMES_NEG_LAG3_POS 1 250 0.4
EOMES_NEG_CCR7_POS 38 250 15.2
LAG3_POS_CCR7_POS 0 250 0.0
LAG3_POS_EOMES_POS 70 250 28.0
CCR7_POS_EOMES_POS 9 250 3.6
Denom = CD4_NEG_CD8_POS_CD45RA_POS
LAG3_NEG 1367 1889 72.4
CCR7_NEG 1370 1889 72.5
EOMES_NEG 523 1889 27.7
LAG3_POS 522 1889 27.6
CCR7_POS 519 1889 27.5
EOMES_POS 1366 1889 72.3
LAG3_NEG_CCR7_NEG 896 1889 47.4
LAG3_NEG_EOMES_NEG 472 1889 25.0
LAG3_NEG_CCR7_POS 471 1889 24.9
LAG3_NEG_EOMES_POS 895 1889 47.4
CCR7_NEG_EOMES_NEG 177 1889 9.4
CCR7_NEG_LAG3_POS 474 1889 25.1
CCR7_NEG_EOMES_POS 1193 1889 63.2
EOMES_NEG_LAG3_POS 51 1889 2.7
EOMES_NEG_CCR7_POS 346 1889 18.3
LAG3_POS_CCR7_POS 48 1889 2.5
LAG3_POS_EOMES_POS 471 1889 24.9
CCR7_POS_EOMES_POS 173 1889 9.2
Denom = CD4_POS_CD8_NEG_CD45RA_POS
LAG3_NEG 6589 6785 97.1
CCR7_NEG 468 6785 6.9
EOMES_NEG 6339 6785 93.4
LAG3_POS 196 6785 2.9
CCR7_POS 6317 6785 93.1
EOMES_POS 446 6785 6.6
LAG3_NEG_CCR7_NEG 384 6785 5.7
LAG3_NEG_EOMES_NEG 6256 6785 92.2
LAG3_NEG_CCR7_POS 6205 6785 91.5
LAG3_NEG_EOMES_POS 333 6785 4.9
CCR7_NEG_EOMES_NEG 169 6785 2.5
CCR7_NEG_LAG3_POS 84 6785 1.2
CCR7_NEG_EOMES_POS 299 6785 4.4
EOMES_NEG_LAG3_POS 83 6785 1.2
EOMES_NEG_CCR7_POS 6170 6785 90.9
LAG3_POS_CCR7_POS 112 6785 1.7
LAG3_POS_EOMES_POS 113 6785 1.7
CCR7_POS_EOMES_POS 147 6785 2.2
Denom = CD4_POS_CD8_POS_CD45RA_POS
LAG3_NEG 113 241 46.9
CCR7_NEG 133 241 55.2
EOMES_NEG 50 241 20.7
LAG3_POS 128 241 53.1
CCR7_POS 108 241 44.8
EOMES_POS 191 241 79.3
LAG3_NEG_CCR7_NEG 68 241 28.2
LAG3_NEG_EOMES_NEG 31 241 12.9
LAG3_NEG_CCR7_POS 45 241 18.7
LAG3_NEG_EOMES_POS 82 241 34.0
CCR7_NEG_EOMES_NEG 24 241 10.0
CCR7_NEG_LAG3_POS 65 241 27.0
CCR7_NEG_EOMES_POS 109 241 45.2
EOMES_NEG_LAG3_POS 19 241 7.9
EOMES_NEG_CCR7_POS 26 241 10.8
LAG3_POS_CCR7_POS 63 241 26.1
LAG3_POS_EOMES_POS 109 241 45.2
CCR7_POS_EOMES_POS 82 241 34.0
Denom = CD4_NEG_CD8_NEG_EOMES_POS
LAG3_NEG 138 212 65.1
CCR7_NEG 200 212 94.3
CD45RA_NEG 14 212 6.6
LAG3_POS 74 212 34.9
CCR7_POS 12 212 5.7
CD45RA_POS 198 212 93.4
LAG3_NEG_CCR7_NEG 128 212 60.4
LAG3_NEG_CD45RA_NEG 10 212 4.7
LAG3_NEG_CCR7_POS 10 212 4.7
LAG3_NEG_CD45RA_POS 128 212 60.4
CCR7_NEG_CD45RA_NEG 11 212 5.2
CCR7_NEG_LAG3_POS 72 212 34.0
CCR7_NEG_CD45RA_POS 189 212 89.2
CD45RA_NEG_LAG3_POS 4 212 1.9
CD45RA_NEG_CCR7_POS 3 212 1.4
LAG3_POS_CCR7_POS 2 212 0.9
LAG3_POS_CD45RA_POS 70 212 33.0
CCR7_POS_CD45RA_POS 9 212 4.2
Denom = CD4_NEG_CD8_POS_EOMES_POS
LAG3_NEG 1124 1654 68.0
CCR7_NEG 1441 1654 87.1
CD45RA_NEG 288 1654 17.4
LAG3_POS 530 1654 32.0
CCR7_POS 213 1654 12.9
CD45RA_POS 1366 1654 82.6
LAG3_NEG_CCR7_NEG 962 1654 58.2
LAG3_NEG_CD45RA_NEG 229 1654 13.8
LAG3_NEG_CCR7_POS 162 1654 9.8
LAG3_NEG_CD45RA_POS 895 1654 54.1
CCR7_NEG_CD45RA_NEG 248 1654 15.0
CCR7_NEG_LAG3_POS 479 1654 29.0
CCR7_NEG_CD45RA_POS 1193 1654 72.1
CD45RA_NEG_LAG3_POS 59 1654 3.6
CD45RA_NEG_CCR7_POS 40 1654 2.4
LAG3_POS_CCR7_POS 51 1654 3.1
LAG3_POS_CD45RA_POS 471 1654 28.5
CCR7_POS_CD45RA_POS 173 1654 10.5
Denom = CD4_POS_CD8_NEG_EOMES_POS
LAG3_NEG 662 815 81.2
CCR7_NEG 584 815 71.7
CD45RA_NEG 369 815 45.3
LAG3_POS 153 815 18.8
CCR7_POS 231 815 28.3
CD45RA_POS 446 815 54.7
LAG3_NEG_CCR7_NEG 485 815 59.5
LAG3_NEG_CD45RA_NEG 329 815 40.4
LAG3_NEG_CCR7_POS 177 815 21.7
LAG3_NEG_CD45RA_POS 333 815 40.9
CCR7_NEG_CD45RA_NEG 285 815 35.0
CCR7_NEG_LAG3_POS 99 815 12.1
CCR7_NEG_CD45RA_POS 299 815 36.7
CD45RA_NEG_LAG3_POS 40 815 4.9
CD45RA_NEG_CCR7_POS 84 815 10.3
LAG3_POS_CCR7_POS 54 815 6.6
LAG3_POS_CD45RA_POS 113 815 13.9
CCR7_POS_CD45RA_POS 147 815 18.0
Denom = CD4_POS_CD8_POS_EOMES_POS
LAG3_NEG 89 205 43.4
CCR7_NEG 119 205 58.0
CD45RA_NEG 14 205 6.8
LAG3_POS 116 205 56.6
CCR7_POS 86 205 42.0
CD45RA_POS 191 205 93.2
LAG3_NEG_CCR7_NEG 57 205 27.8
LAG3_NEG_CD45RA_NEG 7 205 3.4
LAG3_NEG_CCR7_POS 32 205 15.6
LAG3_NEG_CD45RA_POS 82 205 40.0
CCR7_NEG_CD45RA_NEG 10 205 4.9
CCR7_NEG_LAG3_POS 62 205 30.2
CCR7_NEG_CD45RA_POS 109 205 53.2
CD45RA_NEG_LAG3_POS 7 205 3.4
CD45RA_NEG_CCR7_POS 4 205 2.0
LAG3_POS_CCR7_POS 54 205 26.3
LAG3_POS_CD45RA_POS 109 205 53.2
CCR7_POS_CD45RA_POS 82 205 40.0

Optional: Adding density gates back to GatingSet

Let’s add the gate for LAG3 of CD4+ and CD8+. This is a good visualization to see all sequential gating steps applied to the sample.

# Grab gate as a numeric
current_gate <-
  dens_gates |>
  dplyr::filter(cd4_pos_cd8_pos == "cd4_neg_cd8_pos") |>
  dplyr::pull(LAG3)

# Apply using gs_add_gating-method and
# We want a boundary gate
openCyto::gs_add_gating_method(
  gs_qc,
  alias = "lag3_cd8",
  pop = "+",
  parent = "cd4-cd8+",
  dims = "LAG3",
  gating_method = "boundary",
  gating_args = list(min = current_gate, max = Inf)
)

current_gate <-
  dens_gates |>
  dplyr::filter(cd4_pos_cd8_pos == "cd4_pos_cd8_neg") |>
  dplyr::pull(LAG3)

openCyto::gs_add_gating_method(
  gs_qc,
  alias = "lag3_cd4",
  pop = "+",
  parent = "cd4+cd8-",
  dims = "LAG3",
  gating_method = "boundary",
  gating_args = list(min = current_gate, max = Inf)
)

ggcyto::autoplot(gs_qc[[1]])

After running {staRgate} to gate flow cytometry data, it is recommended to perform some quality checks (QC) on the gate placements to ensure they are reasonable. We suggest to use ridgeplots in addition to the ggcyto::autoplot function to visualize the density distributions per marker across samples. When examining a large batch of samples, downsampling, such as to a random sample of 10k CD3+ cells, will make the QC process more manageable. In addition, random spot checks of a few samples would also be helpful QC to detect any edge cases.

Currently in this tutorial, we do not extend to the QC steps and suggest to lean on other examples for how to put together a ridgeplot for example.

In the near future, we hope to incorporate some examples for the additional QC steps as well, stay tuned!

Session info

sessionInfo()
#> R version 4.4.2 (2024-10-31)
#> Platform: x86_64-pc-linux-gnu
#> Running under: Ubuntu 22.04.5 LTS
#> 
#> Matrix products: default
#> BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
#> LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0
#> 
#> locale:
#>  [1] LC_CTYPE=C.UTF-8       LC_NUMERIC=C           LC_TIME=C.UTF-8       
#>  [4] LC_COLLATE=C.UTF-8     LC_MONETARY=C.UTF-8    LC_MESSAGES=C.UTF-8   
#>  [7] LC_PAPER=C.UTF-8       LC_NAME=C              LC_ADDRESS=C          
#> [10] LC_TELEPHONE=C         LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C   
#> 
#> time zone: UTC
#> tzcode source: system (glibc)
#> 
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#> 
#> other attached packages:
#> [1] ggplot2_3.5.1        flowCore_2.18.0      flowWorkspace_4.18.0
#> [4] openCyto_2.18.0      staRgate_0.99.3      BiocStyle_2.34.0    
#> 
#> loaded via a namespace (and not attached):
#>  [1] gtable_0.3.6        xfun_0.49           bslib_0.8.0        
#>  [4] htmlwidgets_1.6.4   lattice_0.22-6      Biobase_2.66.0     
#>  [7] vctrs_0.6.5         tools_4.4.2         generics_0.1.3     
#> [10] stats4_4.4.2        parallel_4.4.2      tibble_3.2.1       
#> [13] fansi_1.0.6         pkgconfig_2.0.3     data.table_1.16.2  
#> [16] RColorBrewer_1.1-3  desc_1.4.3          S4Vectors_0.44.0   
#> [19] gt_0.11.1           graph_1.84.0        lifecycle_1.0.4    
#> [22] farver_2.1.2        compiler_4.4.2      stringr_1.5.1      
#> [25] textshaping_0.4.0   munsell_0.5.1       janitor_2.2.0      
#> [28] snakecase_0.11.1    htmltools_0.5.8.1   sass_0.4.9         
#> [31] yaml_2.3.10         hexbin_1.28.5       pillar_1.9.0       
#> [34] pkgdown_2.1.1       jquerylib_0.1.4     tidyr_1.3.1        
#> [37] cachem_1.1.0        RProtoBufLib_2.18.0 commonmark_1.9.2   
#> [40] tidyselect_1.2.1    digest_0.6.37       stringi_1.8.4      
#> [43] dplyr_1.1.4         purrr_1.0.2         bookdown_0.41      
#> [46] labeling_0.4.3      flowClust_3.44.0    fastmap_1.2.0      
#> [49] grid_4.4.2          colorspace_2.1-1    cli_3.6.3          
#> [52] magrittr_2.0.3      ncdfFlow_2.52.0     RBGL_1.82.0        
#> [55] XML_3.99-0.17       utf8_1.2.4          withr_3.0.2        
#> [58] scales_1.3.0        lubridate_1.9.3     timechange_0.3.0   
#> [61] rmarkdown_2.29      matrixStats_1.4.1   gridExtra_2.3      
#> [64] cytolib_2.18.0      ragg_1.3.3          evaluate_1.0.1     
#> [67] knitr_1.49          markdown_1.13       rlang_1.1.4        
#> [70] Rcpp_1.0.13-1       glue_1.8.0          xml2_1.3.6         
#> [73] Rgraphviz_2.50.0    BiocManager_1.30.25 BiocGenerics_0.52.0
#> [76] jsonlite_1.8.9      plyr_1.8.9          R6_2.5.1           
#> [79] ggcyto_1.34.0       systemfonts_1.1.0   fs_1.6.5           
#> [82] zlibbioc_1.52.0

References

G. Finak, J. Frelinger, W. Jiang, E. W. Newell, J. Ramey, M. M. Davis, S. A. Kalams, S. C. De Rosa and R. Gottardo, “OpenCyto: An Open Source Infrastructure for Scalable, Robust, Reproducible, and Automated, End-to-End Flow Cytometry Data Analysis,” PLoS Computational Biology, vol. 10, p. e1003806, August 2014.

G. Finak and M. Jiang, “flowWorkspace: Infrastructure for representing and interacting with gated and ungated cytometry data sets.,” 2023.

G. Finak, W. Jiang and R. Gottardo, “CytoML for cross-platform cytometry data sharing,” Cytometry Part A, vol. 93, pp. -7, 2018.

G. Monaco, H. Chen, M. Poidinger, J. Chen, J. P. de Magalhães and A. Larbi, “flowAI: automatic and interactive anomaly discerning tools for flow cytometry data,” Bioinformatics, vol. 32, p. 2473–2480, April 2016. P. Van, W. Jiang, R. Gottardo and G. Finak, “ggcyto: Next-generation open-source visualization software for cytometry,” Bioinformatics, 2018.