Skip to content

Commit dcc7b10

Browse files
committed
Tighten contrast facades and add lmer support.
Require the aggregate-only facades to run on protein-level LFQData, add a peptide-level lmer facade, and align the vignettes and pkgdown metadata with the stricter facade contracts. Made-with: Cursor
1 parent a5c3faf commit dcc7b10

66 files changed

Lines changed: 767 additions & 72 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

DESCRIPTION

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ Encoding: UTF-8
2323
LazyData: true
2424
LazyDataCompression: xz
2525
VignetteBuilder: knitr
26-
URL: https://github.com/wolski/prolfqua
26+
URL: https://github.com/wolski/prolfqua,
27+
https://wolski.github.io/prolfqua/
2728
BugReports: https://github.com/wolski/prolfqua/issues
2829
Imports:
2930
bookdown,

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export(ContrastsLMFacade)
1212
export(ContrastsLMMissingFacade)
1313
export(ContrastsLimma)
1414
export(ContrastsLimmaFacade)
15+
export(ContrastsLmerFacade)
1516
export(ContrastsMissing)
1617
export(ContrastsModerated)
1718
export(ContrastsModeratedDEqMS)

R/ContrastsFacades.R

Lines changed: 118 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# ContrastsFacades -----
22

3+
.assert_aggregated_facade_input <- function(lfqdata, facade_name) {
4+
subject_id <- lfqdata$subject_Id()
5+
hierarchy_keys <- lfqdata$config$hierarchy_keys()
6+
if (!identical(subject_id, hierarchy_keys)) {
7+
stop(
8+
facade_name,
9+
" requires aggregated LFQData. ",
10+
"`lfqdata$subject_Id()` must equal `lfqdata$config$hierarchy_keys()`. ",
11+
"Aggregate first.",
12+
call. = FALSE
13+
)
14+
}
15+
}
16+
17+
.assert_nested_facade_input <- function(lfqdata, facade_name) {
18+
subject_id <- lfqdata$subject_Id()
19+
hierarchy_keys <- lfqdata$config$hierarchy_keys()
20+
if (!(all(subject_id %in% hierarchy_keys) && length(subject_id) < length(hierarchy_keys))) {
21+
stop(
22+
facade_name,
23+
" requires LFQData with additional hierarchy below `subject_Id()`. ",
24+
"`lfqdata$subject_Id()` must be a strict subset of ",
25+
"`lfqdata$config$hierarchy_keys()`. Do not aggregate first.",
26+
call. = FALSE
27+
)
28+
}
29+
}
30+
31+
.add_facade_column <- function(res, facade_name) {
32+
if (!("facade" %in% colnames(res))) {
33+
res <- dplyr::mutate(res, facade = facade_name, .before = 1)
34+
}
35+
res
36+
}
37+
338
#' Limma contrast analysis facade
439
#'
540
#' Encapsulates the pipeline: \code{\link{strategy_limma}} ->
@@ -8,9 +43,9 @@
843
#' @export
944
#' @family modelling
1045
#' @examples
11-
#' istar <- sim_lfq_data_peptide_config()
46+
#' istar <- sim_lfq_data_protein_config()
1247
#' lfqdata <- LFQData$new(istar$data, istar$config)
13-
#' lfqdata <- lfqdata$get_Transformer()$log2()$lfq
48+
#' lfqdata$rename_response("transformedIntensity")
1449
#' contrasts <- c("A_vs_Ctrl" = "group_A - group_Ctrl")
1550
#' fa <- ContrastsLimmaFacade$new(lfqdata, "~ group_", contrasts)
1651
#' head(fa$get_contrasts())
@@ -29,6 +64,7 @@ ContrastsLimmaFacade <- R6::R6Class(
2964
#' @param contrasts named character vector of contrasts
3065
#' @param ... passed to \code{\link{strategy_limma}} (e.g. trend, robust)
3166
initialize = function(lfqdata, modelstr, contrasts, ...) {
67+
.assert_aggregated_facade_input(lfqdata, "ContrastsLimmaFacade")
3268
response <- lfqdata$config$get_response()
3369
full_formula <- paste(response, modelstr)
3470
strat <- strategy_limma(full_formula, ...)
@@ -37,7 +73,9 @@ ContrastsLimmaFacade <- R6::R6Class(
3773
},
3874
#' @description get contrast results
3975
#' @param ... passed to ContrastsLimma$get_contrasts
40-
get_contrasts = function(...) self$contrast$get_contrasts(...),
76+
get_contrasts = function(...) {
77+
.add_facade_column(self$contrast$get_contrasts(...), "limma")
78+
},
4179
#' @description get ContrastsPlotter
4280
#' @param ... passed to ContrastsLimma$get_Plotter
4381
get_Plotter = function(...) self$contrast$get_Plotter(...),
@@ -57,9 +95,9 @@ ContrastsLimmaFacade <- R6::R6Class(
5795
#' @export
5896
#' @family modelling
5997
#' @examples
60-
#' istar <- sim_lfq_data_peptide_config()
98+
#' istar <- sim_lfq_data_protein_config()
6199
#' lfqdata <- LFQData$new(istar$data, istar$config)
62-
#' lfqdata <- lfqdata$get_Transformer()$log2()$lfq
100+
#' lfqdata$rename_response("transformedIntensity")
63101
#' contrasts <- c("A_vs_Ctrl" = "group_A - group_Ctrl")
64102
#' fa <- ContrastsLMFacade$new(lfqdata, "~ group_", contrasts)
65103
#' head(fa$get_contrasts())
@@ -78,6 +116,7 @@ ContrastsLMFacade <- R6::R6Class(
78116
#' @param contrasts named character vector of contrasts
79117
#' @param ... passed to \code{\link{strategy_lm}}
80118
initialize = function(lfqdata, modelstr, contrasts, ...) {
119+
.assert_aggregated_facade_input(lfqdata, "ContrastsLMFacade")
81120
response <- lfqdata$config$get_response()
82121
full_formula <- paste(response, modelstr)
83122
strat <- strategy_lm(full_formula, ...)
@@ -86,7 +125,9 @@ ContrastsLMFacade <- R6::R6Class(
86125
},
87126
#' @description get contrast results
88127
#' @param ... passed to ContrastsModerated$get_contrasts
89-
get_contrasts = function(...) self$contrast$get_contrasts(...),
128+
get_contrasts = function(...) {
129+
.add_facade_column(self$contrast$get_contrasts(...), "lm")
130+
},
90131
#' @description get ContrastsPlotter
91132
#' @param ... passed to ContrastsModerated$get_Plotter
92133
get_Plotter = function(...) self$contrast$get_Plotter(...),
@@ -97,6 +138,66 @@ ContrastsLMFacade <- R6::R6Class(
97138
)
98139

99140

141+
#' Lmer contrast analysis facade
142+
#'
143+
#' Encapsulates the pipeline: \code{\link{strategy_lmer}} ->
144+
#' \code{\link{build_model}} -> \code{\link{Contrasts}} ->
145+
#' \code{\link{ContrastsModerated}}.
146+
#'
147+
#' This facade requires data with hierarchy below the analysis subject, for
148+
#' example peptide-level measurements nested within proteins.
149+
#'
150+
#' @export
151+
#' @family modelling
152+
#' @examples
153+
#' istar <- sim_lfq_data_peptide_config()
154+
#' istar$config <- old2new(istar$config)
155+
#' lfqdata <- LFQData$new(istar$data, istar$config)
156+
#' lfqdata <- lfqdata$get_Transformer()$log2()$lfq
157+
#' contrasts <- c("A_vs_Ctrl" = "group_A - group_Ctrl")
158+
#' fa <- ContrastsLmerFacade$new(
159+
#' lfqdata,
160+
#' "~ group_ + (1 | peptide_Id) + (1 | sampleName)",
161+
#' contrasts
162+
#' )
163+
#' head(fa$get_contrasts())
164+
#' fa$to_wide()
165+
ContrastsLmerFacade <- R6::R6Class(
166+
"ContrastsLmerFacade",
167+
public = list(
168+
#' @field model Model object
169+
model = NULL,
170+
#' @field contrast ContrastsModerated object
171+
contrast = NULL,
172+
#' @description
173+
#' initialize
174+
#' @param lfqdata LFQData object
175+
#' @param modelstr model formula string (e.g. "~ group_ + (1 | peptide_Id)")
176+
#' @param contrasts named character vector of contrasts
177+
#' @param ... passed to \code{\link{strategy_lmer}}
178+
initialize = function(lfqdata, modelstr, contrasts, ...) {
179+
.assert_nested_facade_input(lfqdata, "ContrastsLmerFacade")
180+
response <- lfqdata$config$get_response()
181+
full_formula <- paste(response, modelstr)
182+
strat <- strategy_lmer(full_formula, ...)
183+
self$model <- build_model(lfqdata, strat)
184+
self$contrast <- ContrastsModerated$new(Contrasts$new(self$model, contrasts))
185+
},
186+
#' @description get contrast results
187+
#' @param ... passed to ContrastsModerated$get_contrasts
188+
get_contrasts = function(...) {
189+
.add_facade_column(self$contrast$get_contrasts(...), "lmer")
190+
},
191+
#' @description get ContrastsPlotter
192+
#' @param ... passed to ContrastsModerated$get_Plotter
193+
get_Plotter = function(...) self$contrast$get_Plotter(...),
194+
#' @description convert results to wide format
195+
#' @param ... passed to ContrastsModerated$to_wide
196+
to_wide = function(...) self$contrast$to_wide(...)
197+
)
198+
)
199+
200+
#'
100201
#' LM + missing-value imputation contrast analysis facade
101202
#'
102203
#' Encapsulates the pipeline: \code{\link{strategy_lm}} ->
@@ -136,6 +237,7 @@ ContrastsLMMissingFacade <- R6::R6Class(
136237
#' @param contrasts named character vector of contrasts
137238
#' @param ... passed to \code{\link{strategy_lm}}
138239
initialize = function(lfqdata, modelstr, contrasts, ...) {
240+
.assert_aggregated_facade_input(lfqdata, "ContrastsLMMissingFacade")
139241
response <- lfqdata$config$get_response()
140242
full_formula <- paste(response, modelstr)
141243
strat <- strategy_lm(full_formula, ...)
@@ -147,7 +249,9 @@ ContrastsLMMissingFacade <- R6::R6Class(
147249
},
148250
#' @description get contrast results
149251
#' @param ... passed to ContrastsTable$get_contrasts
150-
get_contrasts = function(...) self$contrast$get_contrasts(...),
252+
get_contrasts = function(...) {
253+
.add_facade_column(self$contrast$get_contrasts(...), "lm_missing")
254+
},
151255
#' @description get ContrastsPlotter
152256
#' @param ... passed to ContrastsTable$get_Plotter
153257
get_Plotter = function(...) self$contrast$get_Plotter(...),
@@ -189,6 +293,7 @@ ContrastsDEqMSFacade <- R6::R6Class(
189293
#' @param contrasts named character vector of contrasts
190294
#' @param ... passed to \code{\link{strategy_lm}}
191295
initialize = function(lfqdata, modelstr, contrasts, ...) {
296+
.assert_aggregated_facade_input(lfqdata, "ContrastsDEqMSFacade")
192297
response <- lfqdata$config$get_response()
193298
full_formula <- paste(response, modelstr)
194299
strat <- strategy_lm(full_formula, ...)
@@ -203,7 +308,9 @@ ContrastsDEqMSFacade <- R6::R6Class(
203308
},
204309
#' @description get contrast results
205310
#' @param ... passed to ContrastsModeratedDEqMS$get_contrasts
206-
get_contrasts = function(...) self$contrast$get_contrasts(...),
311+
get_contrasts = function(...) {
312+
.add_facade_column(self$contrast$get_contrasts(...), "deqms")
313+
},
207314
#' @description get ContrastsPlotter
208315
#' @param ... passed to ContrastsModeratedDEqMS$get_Plotter
209316
get_Plotter = function(...) self$contrast$get_Plotter(...),
@@ -249,6 +356,7 @@ ContrastsROPECAFacade <- R6::R6Class(
249356
#' @param contrasts named character vector of contrasts
250357
#' @param ... passed to \code{\link{strategy_lm}}
251358
initialize = function(lfqdata, modelstr, contrasts, ...) {
359+
.assert_nested_facade_input(lfqdata, "ContrastsROPECAFacade")
252360
response <- lfqdata$config$get_response()
253361
full_formula <- paste(response, modelstr)
254362
strat <- strategy_lm(full_formula, ...)
@@ -293,7 +401,8 @@ ContrastsROPECAFacade <- R6::R6Class(
293401
standard_cols <- c(protein_Id, "modelName", "contrast", "avgAbd",
294402
"diff", "FDR", "statistic", "std.error", "df",
295403
"p.value", "conf.low", "conf.high", "sigma")
296-
res[, standard_cols, drop = FALSE]
404+
res <- res[, standard_cols, drop = FALSE]
405+
.add_facade_column(res, "ropeca")
297406
},
298407
#' @description get ContrastsPlotter (uses standardized column names)
299408
#' @param FCthreshold fold change threshold

R/build_contrast_analysis.R

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@
1212
#' \code{lfqdata$config$get_response()}.
1313
#' @param contrasts named character vector of contrasts
1414
#' (e.g. \code{c("A_vs_B" = "group_A - group_B")})
15-
#' @param method one of \code{"lm"}, \code{"lm_missing"}, \code{"limma"},
16-
#' \code{"deqms"}, \code{"ropeca"}
15+
#' @param method one of \code{"lm"}, \code{"lmer"}, \code{"lm_missing"},
16+
#' \code{"limma"}, \code{"deqms"}, \code{"ropeca"}
1717
#' @param ... additional arguments forwarded to the underlying strategy function
1818
#' (e.g. \code{trend}, \code{robust} for \code{strategy_limma})
1919
#' @return one of \code{\link{ContrastsLimmaFacade}},
20-
#' \code{\link{ContrastsLMFacade}}, \code{\link{ContrastsLMMissingFacade}},
21-
#' \code{\link{ContrastsDEqMSFacade}}, or \code{\link{ContrastsROPECAFacade}}
20+
#' \code{\link{ContrastsLMFacade}}, \code{\link{ContrastsLmerFacade}},
21+
#' \code{\link{ContrastsLMMissingFacade}}, \code{\link{ContrastsDEqMSFacade}},
22+
#' or \code{\link{ContrastsROPECAFacade}}
2223
#' @export
2324
#' @family modelling
2425
#' @examples
25-
#' istar <- sim_lfq_data_peptide_config()
26+
#' istar <- sim_lfq_data_protein_config(Nprot = 20)
2627
#' lfqdata <- LFQData$new(istar$data, istar$config)
27-
#' lfqdata <- lfqdata$get_Transformer()$log2()$lfq
28+
#' lfqdata$rename_response("transformedIntensity")
2829
#' contrasts <- c("A_vs_Ctrl" = "group_A - group_Ctrl")
2930
#'
3031
#' fa_lm <- build_contrast_analysis(lfqdata, "~ group_", contrasts, method = "lm")
@@ -33,26 +34,36 @@
3334
#' fa_limma <- build_contrast_analysis(lfqdata, "~ group_", contrasts, method = "limma")
3435
#' head(fa_limma$get_contrasts())
3536
#'
36-
#' fa_ropeca <- build_contrast_analysis(lfqdata, "~ group_", contrasts, method = "ropeca")
37-
#' head(fa_ropeca$get_contrasts())
38-
#'
39-
#' # lm_missing requires protein-level data
40-
#' istar_prot <- sim_lfq_data_protein_config(Nprot = 20)
41-
#' lfqdata_prot <- LFQData$new(istar_prot$data, istar_prot$config)
42-
#' lfqdata_prot$rename_response("transformedIntensity")
43-
#' fa_miss <- build_contrast_analysis(lfqdata_prot, "~ group_", contrasts, method = "lm_missing")
37+
#' fa_miss <- build_contrast_analysis(lfqdata, "~ group_", contrasts, method = "lm_missing")
4438
#' head(fa_miss$get_contrasts())
4539
#'
46-
#' fa_deqms <- build_contrast_analysis(lfqdata_prot, "~ group_", contrasts, method = "deqms")
40+
#' fa_deqms <- build_contrast_analysis(lfqdata, "~ group_", contrasts, method = "deqms")
4741
#' head(fa_deqms$get_contrasts())
42+
#'
43+
#' istar_pep <- sim_lfq_data_peptide_config()
44+
#' istar_pep$config <- old2new(istar_pep$config)
45+
#' lfqdata_pep <- LFQData$new(istar_pep$data, istar_pep$config)
46+
#' lfqdata_pep <- lfqdata_pep$get_Transformer()$log2()$lfq
47+
#'
48+
#' fa_lmer <- build_contrast_analysis(
49+
#' lfqdata_pep,
50+
#' "~ group_ + (1 | peptide_Id) + (1 | sampleName)",
51+
#' contrasts,
52+
#' method = "lmer"
53+
#' )
54+
#' head(fa_lmer$get_contrasts())
55+
#'
56+
#' fa_ropeca <- build_contrast_analysis(lfqdata_pep, "~ group_", contrasts, method = "ropeca")
57+
#' head(fa_ropeca$get_contrasts())
4858
build_contrast_analysis <- function(lfqdata,
4959
modelstr,
5060
contrasts,
51-
method = c("lm", "lm_missing", "limma", "deqms", "ropeca"),
61+
method = c("lm", "lmer", "lm_missing", "limma", "deqms", "ropeca"),
5262
...) {
5363
method <- match.arg(method)
5464
switch(method,
5565
lm = ContrastsLMFacade$new(lfqdata, modelstr, contrasts, ...),
66+
lmer = ContrastsLmerFacade$new(lfqdata, modelstr, contrasts, ...),
5667
lm_missing = ContrastsLMMissingFacade$new(lfqdata, modelstr, contrasts, ...),
5768
limma = ContrastsLimmaFacade$new(lfqdata, modelstr, contrasts, ...),
5869
deqms = ContrastsDEqMSFacade$new(lfqdata, modelstr, contrasts, ...),

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![DOI](https://img.shields.io/badge/DOI-10.1021%2Facs.jproteome.2c00441-blue)](https://pubs.acs.org/doi/10.1021/acs.jproteome.2c00441)
44
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.18775726.svg)](https://doi.org/10.5281/zenodo.18775726)
55

6-
<img src="man/figures/imgfile.png" width="200">
6+
<img src="man/figures/imgfile.png" alt="prolfqua hex sticker" width="200">
77

88
# prolfqua - a comprehensive R package for Proteomics Differential Expression Analysis
99

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ navbar:
1414
github:
1515
icon: fa-github
1616
href: https://github.com/wolski/prolfqua
17+
aria-label: GitHub repository

man/Contrasts.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/ContrastsDEqMSFacade.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/ContrastsFirth.Rd

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/ContrastsLMFacade.Rd

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)