This vignette demonstrates how two conditions, e.g., treatment vs. control, can be compared and the differences statistically tested. We use simulated protein abundances of an LFQ experiment with two treatment groups (A, B) and a control group (Ctrl). After first examining the data using QC plots and then normalizing the data, we compare each treatment group with the control. The output of the comparison is the difference in the mean intensities for quantified proteins (log2 fold-change) in each group, along with statistical parameters such as degrees of freedom, standard errors, p-value and the FDR.
library(prolfqua)
sim_lfq_data_protein_config() simulates protein abundances in long format, one row per protein and sample, and returns them together with a matching AnalysisConfiguration. To read your own data, see the article Importing your data.
xx <- prolfqua::sim_lfq_data_protein_config(Nprot = 100, weight_missing = 0.5)
xx
## $data
## # A tibble: 1,200 × 8
## sample sampleName group_ isotopeLabel protein_Id abundance qValue nrPeptides
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 A_V1 A_V1 A light 0EfVhX~3967 20.4 0 2
## 2 A_V1 A_V1 A light 0YSKpy~2865 NA NA NA
## 3 A_V1 A_V1 A light 0m5WN4~6025 16.4 0 7
## 4 A_V1 A_V1 A light 3QLHfm~8938 23.0 0 1
## 5 A_V1 A_V1 A light 3QYop0~7543 23.1 0 7
## 6 A_V1 A_V1 A light 76k03k~7094 25.5 0 4
## 7 A_V1 A_V1 A light 7QuTub~1867 18.4 0 4
## 8 A_V1 A_V1 A light 7cbcrd~7351 27.1 0 2
## 9 A_V1 A_V1 A light 7soopj~5352 19.5 0 2
## 10 A_V1 A_V1 A light 7zeekV~7127 20.6 0 8
## # ℹ 1,190 more rows
##
## $config
## <AnalysisConfiguration>
## Public:
## annotation_vars: function ()
## bin_resp:
## clone: function (deep = FALSE)
## factor_depth: 1
## factor_keys: function ()
## factor_keys_depth: function ()
## factors: list
## file_name: sample
## get_response: function ()
## hierarchy: list
## hierarchy_depth: 1
## hierarchy_keys: function ()
## hierarchy_keys_depth: function (names = TRUE)
## id_vars: function ()
## ident_q_value: qValue
## ident_score:
## is_response_transformed: FALSE
## isotope_label: isotopeLabel
## min_peptides_protein: 2
## norm_value: NULL
## nr_children: nrPeptides
## opt_mz:
## opt_rt:
## opt_se:
## pattern_contaminants: NULL
## pattern_decoys: NULL
## pop_response: function ()
## sample_name: sampleName
## sep: ~
## set_response: function (col_name)
## value_vars: function ()
## work_intensity: abundance
Create the LFQData class instance and remove zero or very small intensities, which most quantification software reports for missing values.
lfqdata <- prolfqua::LFQData$new(xx$data, xx$config)
lfqdata$remove_small_intensities()
lfqdata$factors()
## # A tibble: 12 × 3
## sample sampleName group_
## <chr> <chr> <chr>
## 1 A_V1 A_V1 A
## 2 A_V2 A_V2 A
## 3 A_V3 A_V3 A
## 4 A_V4 A_V4 A
## 5 B_V1 B_V1 B
## 6 B_V2 B_V2 B
## 7 B_V3 B_V3 B
## 8 B_V4 B_V4 B
## 9 Ctrl_V1 Ctrl_V1 Ctrl
## 10 Ctrl_V2 Ctrl_V2 Ctrl
## 11 Ctrl_V3 Ctrl_V3 Ctrl
## 12 Ctrl_V4 Ctrl_V4 Ctrl
You can always convert the data into wide format.
lfqdata$data_wide()$data[1:3,1:7]
## # A tibble: 3 × 7
## protein_Id isotopeLabel A_V1 A_V2 A_V3 A_V4 B_V1
## <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0EfVhX~3967 light 20.4 23.8 22.3 21.5 NA
## 2 0YSKpy~2865 light NA 17.7 16.7 17.7 18.2
## 3 0m5WN4~6025 light 16.4 NA NA 18.2 24.3
After this first setting up of the analysis we show now how to normalize the proteins and the effect of normalization. Furthermore we use some functions to visualize the missing values in our data.
lfqplotter <- lfqdata$get_Plotter()
density_nn <- lfqplotter$intensity_distribution_density()
lfqplotter$na_heatmap()
Figure 1: Heatmap of missing protein intensities, black - missing, white - present
lfqdata$get_Summariser()$plot_missingness_per_group()
Figure 2: # of proteins with 0,1,…N missing values
lfqplotter$missigness_histogram()
Figure 3: Intensity distribution of proteins depending on # of missing values
Other important statistics such as coefficient of variation, means and standard deviations can be easily calculated using the get_Stats function and visualized with a violin plot.
stats <- lfqdata$get_Stats()
stats$violin()
Figure 4: Violin plots of CVs in the different groups and among all groups
prolfqua::table_facade( stats$stats_quantiles()$wide, paste0("quantile of ",stats$stat ))
| probs | A | B | Ctrl | All |
|---|---|---|---|---|
| 0.10 | 2.121145 | 1.445998 | 1.499604 | 3.416437 |
| 0.25 | 2.603169 | 2.734033 | 2.135081 | 4.316819 |
| 0.50 | 3.860492 | 3.487257 | 3.744767 | 5.077622 |
| 0.75 | 5.552086 | 5.449944 | 5.701197 | 7.229931 |
| 0.90 | 7.745671 | 6.931290 | 8.396967 | 10.211265 |
stats$density_median()
Figure 5: Distribution of CV’s for top 50% and bottom 50% proteins by intensity
We normalize the data by \(\log_2\) transforming and then \(z-scaling\).
lt <- lfqdata$get_Transformer()
transformed <- lt$log2()$robscale()$lfq
transformed$is_transformed()
## [1] TRUE
pl <- transformed$get_Plotter()
density_norm <- pl$intensity_distribution_density()
gridExtra::grid.arrange(density_nn, density_norm)
Figure 6: Distribution of intensities before and after normalization
pl$pairs_smooth()
Figure 7: Scatterplot matrix
## NULL
p <- pl$heatmap_cor()
p
Figure 8: Heatmap, Rows - proteins, Columns - samples
We compare each treatment group with the control. build_contrast_analysis() fits one model per protein and computes the contrasts in one call. We use the method lm_impute: a linear model per protein, with the protein variances moderated by the empirical Bayes method so that proteins with few observations borrow strength from all proteins. Proteins without observations in one group are refitted with the missing values set to the limit of detection (LOD). The article Filling the gaps uses these models to complete the data for heatmaps and PCA. The vignette All modelling methods at a glance lists the other methods.
contr_spec <- c(
"AvsC" = "group_A - group_Ctrl",
"BvsC" = "group_B - group_Ctrl"
)
fa <- build_contrast_analysis(transformed, "~ group_", contr_spec, method = "lm_impute")
contrdf <- fa$get_contrasts()
The column estimate_type tells how each estimate was obtained: observed from the measured intensities, lod_imputed from the refit at the LOD.
dplyr::count(contrdf, contrast, estimate_type)
## # A tibble: 4 × 3
## contrast estimate_type n
## <chr> <chr> <int>
## 1 AvsC lod_imputed 7
## 2 AvsC observed 93
## 3 BvsC lod_imputed 7
## 4 BvsC observed 93
plotter <- fa$get_Plotter()
plotter$volcano()$FDR
Figure 9: Volcano plots of the two contrasts
plotter$ma_plot()
Figure 10: MA plot showing the dependency of mean abundance with respect to the difference
The proteins with FDR < 0.1 in at least one contrast:
sig <- dplyr::filter(contrdf, FDR < 0.1)
signif <- transformed$get_copy()
signif$set_data(signif$data_long() |> dplyr::filter(protein_Id %in% sig$protein_Id))
hmSig <- signif$get_Plotter()$heatmap()
hmSig
Figure 11: Heatmap of the proteins with FDR < 0.1 in at least one contrast
myProteinIDS <- head(unique(contrdf$protein_Id), 3)
dplyr::filter(contrdf, protein_Id %in% myProteinIDS)
## # A tibble: 6 × 16
## modelName estimate_type protein_Id contrast diff avgAbd
## <chr> <chr> <chr> <chr> <dbl> <dbl>
## 1 lm_impute observed 0EfVhX~3967 AvsC 0.0808 4.43
## 2 lm_impute observed 0YSKpy~2865 AvsC 0.0192 4.16
## 3 lm_impute observed 0m5WN4~6025 AvsC -0.225 4.23
## 4 lm_impute observed 0EfVhX~3967 BvsC -0.119 4.33
## 5 lm_impute observed 0YSKpy~2865 BvsC -0.0640 4.11
## 6 lm_impute observed 0m5WN4~6025 BvsC 0.162 4.42
## # ℹ 10 more variables: std.error.unmoderated <dbl>, df.unmoderated <dbl>,
## # std.error <dbl>, statistic <dbl>, df <dbl>, p.value <dbl>, conf.low <dbl>,
## # conf.high <dbl>, sigma <dbl>, FDR <dbl>
Protein and contrast pairs that lm_impute could not estimate, if any:
fa$get_missing()
## # A tibble: 0 × 2
## # ℹ 2 variables: protein_Id <chr>, contrast <chr>
After computing contrasts, proteins can be ranked by log2 fold change or t-statistic and subjected to gene set enrichment analysis. The typical workflow is:
prolfqua::get_uniprot_id_from_fasta_header()AnnotationDbi::mapIds() with a species-specific annotation packageclusterProfiler::gseGO() or clusterProfiler::gseKEGG()For details see:
The prolfqua package is described in (Wolski et al. 2023).
sessionInfo()
## R version 4.6.1 (2026-06-24)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.5 LTS
##
## Matrix products: default
## BLAS: /home/biocbuild/bbs-3.24-bioc/R/lib/libRblas.so
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0 LAPACK version 3.12.0
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_GB LC_COLLATE=C
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## time zone: America/New_York
## tzcode source: system (glibc)
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] prolfqua_0.99.8 dplyr_1.2.1 BiocStyle_2.41.0
##
## loaded via a namespace (and not attached):
## [1] Rdpack_2.6.6 gridExtra_2.3.1 sandwich_3.1-3
## [4] rlang_1.3.0 magrittr_2.0.5 clue_0.3-68
## [7] GetoptLong_1.1.1 multcomp_1.4-32 otel_0.2.0
## [10] matrixStats_1.5.0 compiler_4.6.1 mgcv_1.9-5
## [13] png_0.1-9 vctrs_0.7.3 pkgconfig_2.0.3
## [16] shape_1.4.6.1 crayon_1.5.3 fastmap_1.2.0
## [19] magick_2.9.1 backports_1.5.1 labeling_0.4.3
## [22] utf8_1.2.6 promises_1.5.0 rmarkdown_2.32
## [25] nloptr_2.2.1 UpSetR_1.4.1 tinytex_0.61
## [28] purrr_1.2.2 xfun_0.62 glmnet_5.2
## [31] jomo_2.7-6 logistf_1.26.1 cachem_1.1.0
## [34] jsonlite_2.0.0 progress_1.2.3 later_1.4.8
## [37] pan_2.0 prettyunits_1.2.0 broom_1.0.13
## [40] parallel_4.6.1 cluster_2.1.8.3 R6_2.6.1
## [43] stringi_1.8.9 bslib_0.12.0 RColorBrewer_1.1-3
## [46] limma_3.99.4 boot_1.3-32 rpart_4.1.27
## [49] jquerylib_0.1.4 estimability_2.0.0 Rcpp_1.1.2
## [52] bookdown_0.48 iterators_1.0.14 knitr_1.52
## [55] zoo_1.9-1 IRanges_2.47.5 httpuv_1.6.17
## [58] Matrix_1.7-6 splines_4.6.1 nnet_7.3-21
## [61] tidyselect_1.2.1 dichromat_2.0-1 yaml_2.3.12
## [64] doParallel_1.0.17 codetools_0.2-20 lattice_0.23-1
## [67] tibble_3.3.1 plyr_1.8.9 shiny_1.14.0
## [70] withr_3.0.3 S7_0.2.2 coda_0.19-4.1
## [73] evaluate_1.0.5 survival_3.8-12 circlize_0.4.18
## [76] pillar_1.11.1 BiocManager_1.30.27 mice_3.19.0
## [79] KernSmooth_2.23-27 foreach_1.5.2 stats4_4.6.1
## [82] reformulas_0.4.4 plotly_4.12.1 generics_0.1.4
## [85] hms_1.1.4 S4Vectors_0.51.10 ggplot2_4.0.3
## [88] scales_1.4.0 minqa_1.2.8 xtable_1.8-8
## [91] glue_1.8.1 emmeans_2.0.4 tools_4.6.1
## [94] data.table_1.18.6.1 lme4_2.1-0 forcats_1.0.1
## [97] mvtnorm_1.4-2 Cairo_1.7-0 grid_4.6.1
## [100] tidyr_1.3.2 crosstalk_1.2.2 rbibutils_2.4.1
## [103] colorspace_2.1-4 nlme_3.1-172 formula.tools_1.7.1
## [106] cli_3.6.6 viridisLite_0.4.3 ComplexHeatmap_2.29.0
## [109] gtable_0.3.6 sass_0.4.10 digest_0.6.39
## [112] operator.tools_1.6.3.1 BiocGenerics_0.59.12 ggrepel_0.9.8
## [115] TH.data_1.1-5 htmlwidgets_1.6.4 rjson_0.2.23
## [118] farver_2.1.2 htmltools_0.5.9 lifecycle_1.0.5
## [121] httr_1.4.9 mime_0.13 GlobalOptions_0.1.4
## [124] mitml_0.4-5 statmod_1.5.2 MASS_7.3-66