Introduction

Flow and mass cytometry are important modern immunology tools for measuring expression levels of multiple proteins on single cells. The goal is to better understand the mechanisms of responses on a single cell basis by studying differential expression of proteins. Most current data analysis tools compare expressions across many computationally discovered cell types. Our goal is to focus on just one cell type. Differential analysis of marker expressions can be difficult due to marker correlations and inter-subject heterogeneity, particularly for studies of human immunology. We address these challenges with two multiple regression strategies: A bootstrapped generalized linear model (GLM) and a generalized linear mixed model (GLMM). Here, we illustrate the CytoGLMM R package and workflow for simulated mass cytometry data.

Prepare Simulated Data

We construct our simulated datasets by sampling from a Poisson GLM. We confirmed—with predictive posterior checks—that Poisson GLMs with mixed effects provide a good fit to mass cytometry data (Seiler et al. 2019). We consider one underlying data generating mechanisms described by a hierarchical model for the \(i\)th cell and \(j\)th donor:

\[ \begin{aligned} \boldsymbol{X}_{ij} & \sim \text{Poisson}(\boldsymbol{\lambda}_{ij}) \\ \log(\boldsymbol{\lambda}_{ij}) & = \boldsymbol{B}_{ij} + \boldsymbol{U}_j \\ \boldsymbol{B}_{ij} & \sim \begin{cases} \text{Normal}(\boldsymbol{\delta}^{(0)}, \boldsymbol{\Sigma}_B) & \text{if } Y_{ij} = 0, \text{ cell unstimulated} \\ \text{Normal}(\boldsymbol{\delta}^{(1)}, \boldsymbol{\Sigma}_B) & \text{if } Y_{ij} = 1, \text{ cell stimulated} \end{cases} \\ \boldsymbol{U}_j & \sim \text{Normal}(\boldsymbol{0}, \boldsymbol{\Sigma}_U). \end{aligned} \]

The following graphic shows a representation of the hierarchical model.

The stimulus activates proteins and induces a difference in marker expression. We define the effect size to be the difference between expected expression levels of stimulated versus unstimulated cells on the \(\log\)-scale. All markers that belong to the active set , have a non-zero effect size, whereas, all markers that are not, have a zero effect size:

\[ \begin{cases} \delta^{(1)}_p - \delta^{(0)}_p > 0 & \text{if protein } p \text{ is in activation set } p \in C \\ \delta^{(1)}_{p'} - \delta^{(0)}_{p'} = 0 & \text{if protein } p' \text{ is not in activation set } p' \notin C. \end{cases} \]

Both covariance matrices have an autoregressive structure,

\[ \begin{aligned} \Omega_{rs} & = \rho^{|r-s|} \\ \boldsymbol{\Sigma} & = \operatorname{diag}(\boldsymbol{\sigma}) \, \boldsymbol{\Omega} \, \operatorname{diag}(\boldsymbol{\sigma}), \end{aligned} \]

where \(\Omega_{rs}\) is the \(r\)th row and \(s\)th column of the correlation matrix \(\boldsymbol{\Omega}\). We regulate two separate correlation parameters: a cell-level \(\rho_B\) and a donor-level \(\rho_U\) coefficient. Non-zero \(\rho_B\) or \(\rho_U\) induce a correlation between condition and marker expression even for markers with a zero effect size.

## # A tibble: 5 × 5
##   donor condition   m01   m02   m03
##   <int> <fct>     <int> <int> <int>
## 1     1 treatment     2   127   116
## 2     1 treatment     0    96    54
## 3     1 treatment     1    14    52
## 4     1 treatment     6    20    34
## 5     1 treatment    18    30    15

We define the marker names that we will focus on in our analysis by extracting them from the simulated data frame.

protein_names = names(df)[3:12]

We recommend that marker expressions be corrected for batch effects (Nowicka et al. 2017; Chevrier et al. 2018; Schuyler et al. 2019; Van Gassen et al. 2020; Trussart et al. 2020) and transformed using variance stabilizing transformations to account for heteroskedasticity, for instance with an inverse hyperbolic sine transformation with the cofactor set to 150 for flow cytometry, and 5 for mass cytometry (Bendall et al. 2011). This transformation assumes a two-component model for the measurement error (Rocke and Lorenzato 1995; Huber et al. 2003) where small counts are less noisy than large counts. Intuitively, this corresponds to a noise model with additive and multiplicative noise depending on the magnitude of the marker expression; see (Holmes and Huber 2019) for details.

df %<>% dplyr::mutate_at(protein_names, function(x) asinh(x/5))

GLM

The goal of the CytoGLMM::cytoglm function is to find protein expression patterns that are associated with the condition of interest, such as a response to a stimulus. We set up the GLM to predict the experimental condition (condition) from protein marker expressions (protein_names), thus our experimental conditions are response variables and marker expressions are explanatory variables.

glm_fit = CytoGLMM::cytoglm(df,
                            protein_names = protein_names,
                            condition = "condition",
                            group = "donor",
                            num_cores = 1,
                            num_boot = 1000)
glm_fit
## 
## #######################
## ## paired analysis ####
## #######################
## 
## number of bootstrap samples: 1000 
## 
## number of cells per group and condition:   
##     control treatment
##   1    1000      1000
##   2    1000      1000
##   3    1000      1000
##   4    1000      1000
##   5    1000      1000
##   6    1000      1000
##   7    1000      1000
##   8    1000      1000
## 
## proteins included in the analysis:
##  m01 m02 m03 m04 m05 m06 m07 m08 m09 m10 
## 
## condition compared: condition 
## grouping variable: donor

We plot the maximum likelihood estimates with 95% confidence intervals for the fixed effects \(\boldsymbol{\beta}\). The estimates are on the \(\log\)-odds scale. We see that markers m1, m2, and m3 are strong predictors of the treatment. This means that one unit increase in the transformed marker expression makes it more likely to be a cell from the treatment group, while holding the other markers constant.

plot(glm_fit)

The summary function returns a table about the model fit with unadjusted and Benjamini-Hochberg (BH) adjusted \(p\)-values.

summary(glm_fit)
## # A tibble: 10 × 3
##    protein_name pvalues_unadj pvalues_adj
##    <chr>                <dbl>       <dbl>
##  1 m03                  0.002       0.02 
##  2 m02                  0.004       0.02 
##  3 m01                  0.018       0.06 
##  4 m06                  0.356       0.867
##  5 m04                  0.496       0.867
##  6 m08                  0.6         0.867
##  7 m05                  0.684       0.867
##  8 m09                  0.694       0.867
##  9 m10                  0.868       0.962
## 10 m07                  0.962       0.962

We can extract the proteins below an False Discovery Rate (FDR) of 0.05 from the \(p\)-value table by filtering the table.

summary(glm_fit) %>% dplyr::filter(pvalues_adj < 0.05)
## # A tibble: 2 × 3
##   protein_name pvalues_unadj pvalues_adj
##   <chr>                <dbl>       <dbl>
## 1 m03                  0.002        0.02
## 2 m02                  0.004        0.02

In this simulated dataset, the markers m2 and m3 are below an FDR of 0.05.

GLMM

In the CytoGLMM::cytoglmm function, we make additional modeling assumptions by adding a random effect term in the standard logistic regression model to account for the subject effect (group). In paired experimental design—when the same donor provides two samples, one for each condition—CytoGLMM::cytoglmm is statistically more powerful.

glmm_fit = CytoGLMM::cytoglmm(df,
                              protein_names = protein_names,
                              condition = "condition",
                              group = "donor",
                              num_cores = 1)
glmm_fit
## number of cells per group and condition:   
##     control treatment
##   1    1000      1000
##   2    1000      1000
##   3    1000      1000
##   4    1000      1000
##   5    1000      1000
##   6    1000      1000
##   7    1000      1000
##   8    1000      1000
## 
## proteins included in the analysis:
##  m01 m02 m03 m04 m05 m06 m07 m08 m09 m10 
## 
## condition compared: condition 
## grouping variable: donor

We plot the method of moments estimates with 95% confidence intervals for the fixed effects \(\boldsymbol{\beta}\).

plot(glmm_fit)

The summary function returns a table about the model fit with unadjusted and BH adjusted \(p\)-values.

summary(glmm_fit)
## # A tibble: 10 × 3
##    protein_name pvalues_unadj pvalues_adj
##    <chr>                <dbl>       <dbl>
##  1 m03              0.0000682    0.000682
##  2 m02              0.000163     0.000813
##  3 m01              0.000681     0.00227 
##  4 m06              0.155        0.388   
##  5 m05              0.371        0.646   
##  6 m08              0.388        0.646   
##  7 m04              0.512        0.731   
##  8 m09              0.859        0.954   
##  9 m10              0.874        0.954   
## 10 m07              0.954        0.954

We can extract the proteins below an FDR of 0.05 from the \(p\)-value table by filtering the table.

summary(glmm_fit) %>% dplyr::filter(pvalues_adj < 0.05)
## # A tibble: 3 × 3
##   protein_name pvalues_unadj pvalues_adj
##   <chr>                <dbl>       <dbl>
## 1 m03              0.0000682    0.000682
## 2 m02              0.000163     0.000813
## 3 m01              0.000681     0.00227

In this simulated dataset, the markers m1, m2 and m3 are below an FDR of 0.05. This illustrates the improved power for paired samples as CytoGLMM::cytoglmm correctly detects all three differentially expressed proteins. We performed extensive power simulations and comparisons to other methods in Seiler et al. (2021).

Session Info

## R version 4.1.0 (2021-05-18)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Big Sur 10.16
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRblas.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.1/Resources/lib/libRlapack.dylib
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] magrittr_2.0.1   CytoGLMM_1.3.0   BiocStyle_2.22.0
## 
## loaded via a namespace (and not attached):
##   [1] bigmemory_4.5.36     bigmemory.sri_0.1.3  minqa_1.2.4         
##   [4] colorspace_2.0-2     ellipsis_0.3.2       class_7.3-19        
##   [7] modeltools_0.2-23    rprojroot_2.0.2      fs_1.5.0            
##  [10] rstudioapi_0.13      listenv_0.8.0        farver_2.1.0        
##  [13] ggrepel_0.9.1        flexmix_2.3-17       prodlim_2019.11.13  
##  [16] fansi_0.5.0          lubridate_1.8.0      codetools_0.2-18    
##  [19] splines_4.1.0        logging_0.10-108     doParallel_1.0.16   
##  [22] cachem_1.0.6         knitr_1.36           speedglm_0.3-3      
##  [25] nloptr_1.2.2.2       pROC_1.18.0          caret_6.0-90        
##  [28] pheatmap_1.0.12      BiocManager_1.30.16  compiler_4.1.0      
##  [31] assertthat_0.2.1     Matrix_1.3-4         fastmap_1.1.0       
##  [34] strucchange_1.5-2    cli_3.1.0            htmltools_0.5.2     
##  [37] tools_4.1.0          gtable_0.3.0         glue_1.4.2          
##  [40] reshape2_1.4.4       dplyr_1.0.7          Rcpp_1.0.7          
##  [43] jquerylib_0.1.4      pkgdown_1.6.1        vctrs_0.3.8         
##  [46] nlme_3.1-153         iterators_1.0.13     mbest_0.6           
##  [49] timeDate_3043.102    gower_0.2.2          xfun_0.27           
##  [52] stringr_1.4.0        globals_0.14.0       lme4_1.1-27.1       
##  [55] lifecycle_1.0.1      future_1.22.1        factoextra_1.0.7    
##  [58] MASS_7.3-54          zoo_1.8-9            scales_1.1.1        
##  [61] ipred_0.9-12         ragg_1.1.3           parallel_4.1.0      
##  [64] sandwich_3.0-1       RColorBrewer_1.1-2   yaml_2.2.1          
##  [67] memoise_2.0.0        ggplot2_3.3.5        rpart_4.1-15        
##  [70] stringi_1.7.5        highr_0.9            desc_1.4.0          
##  [73] foreach_1.5.1        boot_1.3-28          BiocParallel_1.28.0 
##  [76] lava_1.6.10          rlang_0.4.12         pkgconfig_2.0.3     
##  [79] systemfonts_1.0.3    evaluate_0.14        lattice_0.20-45     
##  [82] purrr_0.3.4          recipes_0.1.17       labeling_0.4.2      
##  [85] cowplot_1.1.1        tidyselect_1.1.1     parallelly_1.28.1   
##  [88] plyr_1.8.6           bookdown_0.24        R6_2.5.1            
##  [91] generics_0.1.1       DBI_1.1.1            pillar_1.6.4        
##  [94] withr_2.4.2          survival_3.2-13      abind_1.4-5         
##  [97] nnet_7.3-16          tibble_3.1.5         future.apply_1.8.1  
## [100] crayon_1.4.2         utf8_1.2.2           rmarkdown_2.11      
## [103] grid_4.1.0           data.table_1.14.2    ModelMetrics_1.2.2.2
## [106] digest_0.6.28        tidyr_1.1.4          textshaping_0.3.6   
## [109] stats4_4.1.0         munsell_0.5.0

References

Bendall, Sean C, Erin F Simonds, Peng Qiu, D Amir El-ad, Peter O Krutzik, Rachel Finck, Robert V Bruggner, et al. 2011. “Single-Cell Mass Cytometry of Differential Immune and Drug Responses Across a Human Hematopoietic Continuum.” Science 332 (6030): 687–96.
Chevrier, Stéphane, Helena L Crowell, Vito RT Zanotelli, Stefanie Engler, Mark D Robinson, and Bernd Bodenmiller. 2018. “Compensation of Signal Spillover in Suspension and Imaging Mass Cytometry.” Cell Systems 6 (5): 612–20.
Holmes, Susan, and Wolfgang Huber. 2019. Modern Statistics for Modern Biology. Cambridge University Press.
Huber, Wolfgang, Anja von Heydebreck, Holger Sültmann, Annemarie Poustka, and Martin Vingron. 2003. “Parameter Estimation for the Calibration and Variance Stabilization of Microarray Data.” Statistical Applications in Genetics and Molecular Biology 2 (1).
Nowicka, M, C Krieg, LM Weber, FJ Hartmann, S Guglietta, B Becher, MP Levesque, and MD Robinson. 2017. “CyTOF Workflow: Differential Discovery in High-Throughput High-Dimensional Cytometry Datasets [Version 2; Referees: 2 Approved].” F1000Research 6 (748).
Rocke, David M, and Stefan Lorenzato. 1995. “A Two-Component Model for Measurement Error in Analytical Chemistry.” Technometrics 37 (2): 176–84.
Schuyler, Ronald P., Conner Jackson, Josselyn E. Garcia-Perez, Ryan M. Baxter, Sidney Ogolla, Rosemary Rochford, Debashis Ghosh, Pratyaydipta Rudra, and Elena W. Y. Hsieh. 2019. “Minimizing Batch Effects in Mass Cytometry Data.” Frontiers in Immunology 10: 2367.
Seiler, Christof, Anne-Maud Ferreira, Lisa M Kronstad, Laura J Simpson, Mathieu Le Gars, Elena Vendrame, Catherine A Blish, and Susan Holmes. 2021. “CytoGLMM: Conditional Differential Analysis for Flow and Mass Cytometry Experiments.” BMC Bioinformatics 22 (137): 1–14.
Seiler, Christof, Lisa M Kronstad, Laura J Simpson, Mathieu Le Gars, Elena Vendrame, Catherine A Blish, and Susan Holmes. 2019. “Uncertainty Quantification in Multivariate Mixed Models for Mass Cytometry Data.” arXiv:1903.07976.
Trussart, Marie, Charis E Teh, Tania Tan, Lawrence Leong, Daniel HD Gray, and Terence P Speed. 2020. “Removing Unwanted Variation with CytofRUV to Integrate Multiple CyTOF Datasets.” eLife 9 (September): e59630.
Van Gassen, Sofie, Brice Gaudilliere, Martin S Angst, Yvan Saeys, and Nima Aghaeepour. 2020. CytoNorm: A Normalization Algorithm for Cytometry Data.” Cytometry Part A 97 (3): 268–78.