vignettes/Comparing2Groups.Rmd
Comparing2Groups.Rmd
This vignette demonstrates how two conditions, e.g., treatment vs. control, can be compared and the differences statistically tested. We will again use the Ionstar dataset as an example of an LFQ experiment. This dataset was preprocessed with the MaxQuant software. After first examining the data using QC plots and then normalizing the data, we compare groups of replicates with the different dilutions. 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.
Specify the path to the MaxQuant proteinGroups.txt
file.
The function tidyMQ_ProteinGroups
will read the
proteinGroups.txt
file and convert it into a tidy table
xx <- prolfqua::sim_lfq_data_protein_config(Nprot = 100)
xx
## $data
## # A tibble: 1,200 × 8
## sample sampleName group_ isotopeLabel protein_Id abundance qValue nr_peptides
## <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl>
## 1 A_V1 A_V1 A light 0EfVhX~39… 20.4 0 2
## 2 A_V1 A_V1 A light 0m5WN4~60… 16.4 0 7
## 3 A_V1 A_V1 A light 0YSKpy~28… 18.2 0 2
## 4 A_V1 A_V1 A light 3QLHfm~89… 23.0 0 1
## 5 A_V1 A_V1 A light 3QYop0~75… 23.1 0 7
## 6 A_V1 A_V1 A light 76k03k~70… 25.5 0 4
## 7 A_V1 A_V1 A light 7cbcrd~73… 27.1 0 2
## 8 A_V1 A_V1 A light 7QuTub~18… 18.4 0 4
## 9 A_V1 A_V1 A light 7soopj~53… 19.5 0 2
## 10 A_V1 A_V1 A light 7zeekV~71… 20.6 0 8
## # ℹ 1,190 more rows
##
## $config
## <AnalysisConfiguration>
## Public:
## clone: function (deep = FALSE)
## initialize: function (analysisTableAnnotation, analysisParameter = AnalysisParameters$new())
## parameter: AnalysisParameters, R6
## sep: ~
## table: AnalysisTableAnnotation, R6
Create the LFQData
class instance and remove zeros from
data (MaxQuant encodes missing values with zero).
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$to_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 19.1
## 2 0m5WN4~6025 light 16.4 NA 16.1 18.2 24.3
## 3 0YSKpy~2865 light 18.2 17.7 16.7 17.7 18.2
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()
lfqdata$get_Summariser()$plot_missingness_per_group()
lfqplotter$missigness_histogram()
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()
## [1] "group_"
## NULL
stats$violin()
prolfqua::table_facade( stats$stats_quantiles()$wide, paste0("quantile of ",stats$stat ))
probs | A | All | B | Ctrl |
---|---|---|---|---|
0.10 | 2.014611 | 3.451035 | 1.993591 | 1.762209 |
0.25 | 2.711702 | 4.370724 | 2.956419 | 2.882970 |
0.50 | 3.655067 | 5.296948 | 3.700140 | 4.261204 |
0.75 | 5.338899 | 7.411380 | 5.462486 | 5.904953 |
0.90 | 7.541564 | 9.912513 | 7.118907 | 7.471854 |
stats$density_median()
We normalize the data by \(\log_2\) transforming and then \(z-scaling\).
lt <- lfqdata$get_Transformer()
transformed <- lt$log2()$robscale()$lfq
transformed$config$table$is_response_transformed
## [1] TRUE
pl <- transformed$get_Plotter()
density_norm <- pl$intensity_distribution_density()
gridExtra::grid.arrange(density_nn, density_norm)
pl$pairs_smooth()
## NULL
p <- pl$heatmap_cor()
p
For fitting linear models to the transformed intensities for all our proteins we have to first specify the model function and define the contrasts that we want to calculate.
transformed$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
formula_Condition <- strategy_lm("transformedIntensity ~ group_")
# specify model definition
modelName <- "Model"
Contrasts <- c("AvsC" = "group_A - group_Ctrl",
"BvsC" = "group_B - group_Ctrl")
Here we have to build the model for each protein.
mod <- prolfqua::build_model(
transformed$data,
formula_Condition,
subject_Id = transformed$config$table$hierarchy_keys() )
In this plot we can see what factors in our model are mostly responsible for the adjusted p-values calculated from an analysis of variance.
mod$anova_histogram("FDR")
## $plot
##
## $name
## [1] "Anova_p.values_Model.pdf"
One also look what proteins do show different abundances in any of our five dilutions by looking at the FDR values of the analysis of variane.
aovtable <- mod$get_anova()
head(aovtable)
## # A tibble: 6 × 10
## protein_Id isSingular nrcoef factor Df Sum.Sq Mean.Sq F.value p.value
## <chr> <lgl> <int> <chr> <int> <dbl> <dbl> <dbl> <dbl>
## 1 0EfVhX~3967 FALSE 3 group_ 2 0.0626 0.0313 5.18 0.0416
## 2 0m5WN4~6025 FALSE 3 group_ 2 0.324 0.162 29.4 0.000205
## 3 0YSKpy~2865 FALSE 3 group_ 2 0.00753 0.00376 0.605 0.569
## 4 3QLHfm~8938 FALSE 3 group_ 2 0.0637 0.0319 13.2 0.00213
## 5 3QYop0~7543 FALSE 3 group_ 2 0.00121 0.000603 0.178 0.840
## 6 76k03k~7094 FALSE 3 group_ 2 0.0264 0.0132 3.06 0.0968
## # ℹ 1 more variable: FDR <dbl>
dim(aovtable)
## [1] 98 10
xx <- aovtable |> dplyr::filter(FDR < 0.2)
signif <- transformed$get_copy()
signif$data <- signif$data |> dplyr::filter(protein_Id %in% xx$protein_Id)
hmSig <- signif$get_Plotter()$heatmap()
hmSig
Next we do calculate the statistics for our defined contrasts for all
the proteins. For this we can use the Contrasts
function.
contr <- prolfqua::Contrasts$new(mod, Contrasts)
v1 <- contr$get_Plotter()$volcano()
Alternatively, we can moderate the variance and using the
Experimental Bayes method implemented in
ContrastsModerated
.
contr <- prolfqua::ContrastsModerated$new(contr)
contrdf <- contr$get_contrasts()
In the next figure it can be seen WEWinputNEEDED.
plotter <- contr$get_Plotter()
v2 <- plotter$volcano()
gridExtra::grid.arrange(v1$FDR,v2$FDR, ncol = 1)
plotter$ma_plotly()
#myProteinIDS <- c("sp|Q12246|LCB4_YEAST", "sp|P38929|ATC2_YEAST", "sp|Q99207|NOP14_YEAST")
myProteinIDS <- c("sp|P0AC33|FUMA_ECOLI", "sp|P28635|METQ_ECOLI", "sp|Q14C86|GAPD1_HUMAN")
dplyr::filter(contrdf, protein_Id %in% myProteinIDS)
## # A tibble: 0 × 13
## # ℹ 13 variables: modelName <chr>, protein_Id <chr>, contrast <chr>,
## # diff <dbl>, std.error <dbl>, avgAbd <dbl>, statistic <dbl>, df <dbl>,
## # p.value <dbl>, conf.low <dbl>, conf.high <dbl>, sigma <dbl>, FDR <dbl>
Use this method if there proteins with no observations in one of the
groups. With the ContrastsMissing
function, we can estimate
difference in mean for proteins that are not observed in one group or
condition. For this we are using the average expression at percentile
0.05 of the group where the protein is not quantified.
mC <- ContrastsMissing$new(lfqdata = transformed, contrasts = Contrasts)
colnames(mC$get_contrasts())
## [1] "group_"
## [1] "modelName" "protein_Id"
## [3] "meanAbundanceImp_group_1" "meanAbundanceImp_group_2"
## [5] "diff" "group_1_name"
## [7] "group_2_name" "contrast"
## [9] "avgAbd" "indic"
## [11] "nrMeasured_group_1" "nrMeasured_group_2"
## [13] "df" "sigma"
## [15] "std.error" "statistic"
## [17] "p.value" "conf.low"
## [19] "conf.high" "FDR"
Finally we are merging the results and give priority to the results where we do not have missing values in one group.
merged <- prolfqua::merge_contrasts_results(prefer = contr,add = mC)$merged
plotter <- merged$get_Plotter()
tmp <- plotter$volcano()
tmp$FDR
Look at proteins which could not be fitted using the linear model, if any.
merged <- prolfqua::merge_contrasts_results(prefer = contr,add = mC)
moreProt <- transformed$get_copy()
moreProt$data <- moreProt$data |> dplyr::filter(protein_Id %in% merged$more$contrast_result$protein_Id)
moreProt$get_Plotter()$raster()
# here we do not get anything because there is nothing imputed!
We can rank the proteins based on the log2FC or the t-statistic and subject them them to gene set enrichment analysis (see GSEA).
This example will run only if the following packages are installed on you machine:
#evalAll <- require("clusterProfiler") & require("org.Sc.sgd.db") & require("prora")
evalAll <- require("clusterProfiler") & require("org.Sc.sgd.db2") & require("prora")
library(clusterProfiler)
library(org.Sc.sgd.db)
bb <- prolfqua::get_UniprotID_from_fasta_header(merged$merged$get_contrasts(),
idcolumn = "protein_Id")
bb <- prora::map_ids_uniprot(bb)
ranklist <- bb$statistic
names(ranklist) <- bb$P_ENTREZGENEID
res <- clusterProfiler::gseGO(
sort(ranklist, decreasing = TRUE),
OrgDb = org.Sc.sgd.db,
ont = "ALL")
ridgeplot( res )
dotplot(res , showCategory = 30)
enrichplot::upsetplot(res)
The prolfqua
package is described in (Wolski et al. 2022).
## R version 4.3.2 (2023-10-31)
## Platform: aarch64-apple-darwin20 (64-bit)
## Running under: macOS Sonoma 14.5
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.11.0
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Europe/Zurich
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] prolfqua_1.2.5
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.5 xfun_0.43 bslib_0.7.0 ggplot2_3.5.1
## [5] htmlwidgets_1.6.4 ggrepel_0.9.5 vctrs_0.6.5 tools_4.3.2
## [9] crosstalk_1.2.1 generics_0.1.3 tibble_3.2.1 fansi_1.0.6
## [13] highr_0.10 pkgconfig_2.0.3 pheatmap_1.0.12 KernSmooth_2.23-22
## [17] data.table_1.15.4 RColorBrewer_1.1-3 desc_1.4.3 lifecycle_1.0.4
## [21] compiler_4.3.2 farver_2.1.1 stringr_1.5.1 textshaping_0.3.7
## [25] progress_1.2.3 munsell_0.5.1 httpuv_1.6.15 htmltools_0.5.8.1
## [29] sass_0.4.9 yaml_2.3.8 lazyeval_0.2.2 plotly_4.10.4
## [33] later_1.3.2 pillar_1.9.0 pkgdown_2.0.9 crayon_1.5.2
## [37] jquerylib_0.1.4 tidyr_1.3.1 MASS_7.3-60.0.1 cachem_1.0.8
## [41] mime_0.12 tidyselect_1.2.1 conflicted_1.2.0 digest_0.6.35
## [45] stringi_1.8.3 dplyr_1.1.4 purrr_1.0.2 labeling_0.4.3
## [49] forcats_1.0.0 fastmap_1.1.1 grid_4.3.2 colorspace_2.1-0
## [53] cli_3.6.2 magrittr_2.0.3 utf8_1.2.4 withr_3.0.0
## [57] promises_1.3.0 prettyunits_1.2.0 scales_1.3.0 rmarkdown_2.26
## [61] httr_1.4.7 gridExtra_2.3 ragg_1.3.0 hms_1.1.3
## [65] shiny_1.8.1.1 memoise_2.0.1 evaluate_0.23 knitr_1.46
## [69] viridisLite_0.4.2 rlang_1.1.3 Rcpp_1.0.12 xtable_1.8-4
## [73] glue_1.7.0 jsonlite_1.8.8 R6_2.5.1 systemfonts_1.0.6
## [77] fs_1.6.4