Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules/nf-core/variancepartition/dream/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ process VARIANCEPARTITION_DREAM {
tuple val(meta), path("*.dream.results.tsv") , emit: results
tuple val(meta), path("*.dream.model.txt") , emit: model
tuple val(meta), path("*.normalised_counts.tsv") , emit: normalised_counts, optional: true
path "versions.yml" , emit: versions
path "versions.yml" , emit: versions_variancepartition, topic: versions

when:
task.ext.when == null || task.ext.when
Expand Down
15 changes: 15 additions & 0 deletions modules/nf-core/variancepartition/dream/meta.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ output:
pattern: "*.normalised_counts.tsv"
ontologies:
- edam: http://edamontology.org/format_3475 # TSV
versions_variancepartition:
- versions.yml:
type: file
description: File containing software versions
pattern: "versions.yml"
ontologies:
- edam: http://edamontology.org/format_3750 # YAML
topics:
versions:
- versions.yml:
type: file
Expand All @@ -107,3 +115,10 @@ maintainers:
- "@alanmmobbs03"
- "@nschcolnicov"
- "@atrigila"
versions:
- versions.yml:
type: file
description: File containing software versions
pattern: "versions.yml"
ontologies:
- edam: http://edamontology.org/format_3750 # YAML
85 changes: 78 additions & 7 deletions modules/nf-core/variancepartition/dream/templates/dream.R
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,65 @@ is_valid_string <- function(input) {
!is.null(input) && nzchar(trimws(input))
}

#' Rewrite a contrast expression using sanitised design column names
#'
#' `makeContrasts()` requires syntactically valid coefficient names. The DREAM
#' design matrix is sanitised with `make.names()`, so a user may provide
#' contrasts using the original names (for example containing spaces). This
#' helper rewrites exact design-column matches to their sanitised equivalents.
#'
#' @param contrast_string User-provided contrast expression.
#' @param design_names Original design matrix column names.
#' original_design_names
#' @return Contrast expression compatible with `makeContrasts()`.
normalise_contrast_string <- function(contrast_string, design_names) {
if (!is_valid_string(contrast_string)) {
return(contrast_string)
}

sanitised_names <- make.names(design_names)
replacement_order <- order(nchar(design_names), decreasing = TRUE)
normalised <- contrast_string

for (idx in replacement_order) {
normalised <- gsub(
design_names[[idx]],
sanitised_names[[idx]],
normalised,
fixed = TRUE
)
}

normalised
}

#' Resolve user-supplied metadata column names against loaded metadata
#'
#' Metadata is read with `check.names = TRUE`, so columns containing spaces are
#' sanitised by R. This helper accepts either the original column name or the
#' sanitised version and returns the column present in `metadata`.
#'
#' @param column_name Column name provided by the user.
#' @param metadata Loaded metadata data frame.
#'
#' @return Resolved column name present in metadata.
resolve_metadata_column <- function(column_name, metadata) {
if (!is_valid_string(column_name)) {
return(column_name)
}

if (column_name %in% colnames(metadata)) {
return(column_name)
}

sanitised_name <- make.names(column_name)
if (sanitised_name %in% colnames(metadata)) {
return(sanitised_name)
}

column_name
}

#' Parse out options from a string without recourse to optparse
#'
#' @param x Long-form argument list like --opt1 val1 --opt2 val2
Expand Down Expand Up @@ -138,6 +197,8 @@ if (!is.null(opt\$seed)) {
# Load metadata
metadata <- read_delim_flexible(opt\$sample_file, header = TRUE, stringsAsFactors = TRUE)
rownames(metadata) <- metadata[[opt\$sample_id_col]]
contrast_variable <- NULL
blocking.vars <- c()

# Check if required parameters have been provided
if (is_valid_string(opt\$formula)) {
Expand All @@ -164,8 +225,7 @@ if (length(missing) > 0) {
}

if (!is_valid_string(opt\$formula)) {
contrast_variable <- make.names(opt\$contrast_variable)
blocking.vars <- c()
contrast_variable <- resolve_metadata_column(opt\$contrast_variable, metadata)

if (!contrast_variable %in% colnames(metadata)) {
stop(
Expand All @@ -184,7 +244,12 @@ if (!is_valid_string(opt\$formula)) {
)
)
} else if (is_valid_string(opt\$blocking_variables)) {
blocking.vars = make.names(unlist(strsplit(opt\$blocking_variables, split = ';')))
blocking.vars <- vapply(
unlist(strsplit(opt\$blocking_variables, split = ';')),
resolve_metadata_column,
character(1),
metadata = metadata
)
if (!all(blocking.vars %in% colnames(metadata))) {
missing_block <- paste(blocking.vars[! blocking.vars %in% colnames(metadata)], collapse = ',')
stop(
Expand All @@ -199,8 +264,10 @@ if (!is_valid_string(opt\$formula)) {

# Ensure contrast variable is factor, then relevel
if (!is.null(opt\$contrast_reference) && opt\$contrast_reference != "") {
metadata[[opt\$contrast_variable]] <- factor(metadata[[opt\$contrast_variable]])
metadata[[opt\$contrast_variable]] <- relevel(metadata[[opt\$contrast_variable]], ref = opt\$contrast_reference)
if (!is.null(contrast_variable) && contrast_variable %in% colnames(metadata)) {
metadata[[contrast_variable]] <- factor(metadata[[contrast_variable]])
metadata[[contrast_variable]] <- relevel(metadata[[contrast_variable]], ref = opt\$contrast_reference)
}
}

# Exclude samples in metadata if specified
Expand Down Expand Up @@ -256,7 +323,7 @@ if (as.logical(opt\$apply_voom)) {
vobjDream <- voomWithDreamWeights(dge, form, metadata, BPPARAM = bp)

# Write normalized counts matrix to a TSV file
normalized_counts <- vobjDream\$E
normalized_counts <- vobjDream\$E
if (!is.null(opt\$round_digits)) {
normalized_counts <- apply(normalized_counts, 2, function(x) round(x, opt\$round_digits))
}
Expand Down Expand Up @@ -302,7 +369,11 @@ if (!is.null(opt\$contrast_string)) {
if (is_valid_string(contrast_string)) {
cat("Using contrast string:", contrast_string, "\n")

colnames(fitmm\$design) <- make.names(colnames(fitmm\$design))
original_design_names <- colnames(fitmm\$design)
colnames(fitmm\$design) <- make.names(original_design_names)
contrast_string <- normalise_contrast_string(contrast_string, original_design_names)
cat("Normalised contrast string:", contrast_string, "\n")

contrast_matrix <- makeContrasts(contrast = contrast_string, levels = colnames(fitmm\$design))
fit2 <- contrasts.fit(fitmm, contrast_matrix)
fit2 <- eBayes(fit2, proportion = opt\$proportion,
Expand Down
12 changes: 6 additions & 6 deletions modules/nf-core/variancepartition/dream/tests/main.nf.test
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ nextflow_process {
{ assert path(process.out.results[0][1]).getText().contains("gene_id\tlogFC\tAveExpr\tt\tP.Value\tadj.P.Val\tB") },
{ assert path(process.out.results[0][1]).getText().contains("1309.83") },
{ assert path(process.out.results[0][1]).getText().contains("557\t279.88\t18.81") },
{ assert snapshot(process.out.model, process.out.versions).match() }
{ assert snapshot(process.out.model, process.out.versions_variancepartition).match() }
)
}
}
Expand Down Expand Up @@ -77,7 +77,7 @@ nextflow_process {
{ assert path(process.out.results[0][1]).getText().contains("gene_id\tlogFC\tAveExpr\tt\tP.Value\tadj.P.Val\tB") },
{ assert path(process.out.results[0][1]).getText().contains("5.07") },
{ assert path(process.out.results[0][1]).getText().contains("2.97\t3.88\t38.17") },
{ assert snapshot(process.out.model, process.out.normalised_counts, process.out.versions).match() }
{ assert snapshot(process.out.model, process.out.normalised_counts, process.out.versions_variancepartition).match() }
)
}
}
Expand Down Expand Up @@ -111,7 +111,7 @@ nextflow_process {
{ assert path(process.out.results[0][1]).getText().contains("gene_id\tlogFC\tAveExpr\tt\tP.Value\tadj.P.Val\tB") },
{ assert path(process.out.results[0][1]).getText().contains("-849.67") },
{ assert path(process.out.results[0][1]).getText().contains("-1050\t549\t-3.78") },
{ assert snapshot(process.out.model, process.out.versions).match() }
{ assert snapshot(process.out.model, process.out.versions_variancepartition).match() }
)
}
}
Expand Down Expand Up @@ -146,7 +146,7 @@ nextflow_process {
{ assert path(process.out.results[0][1]).getText().contains("gene_id\tlogFC\tAveExpr\tt\tP.Value\tadj.P.Val\tB") },
{ assert path(process.out.results[0][1]).getText().contains("1050") },
{ assert path(process.out.results[0][1]).getText().contains("849.67\t432.83\t3.8") },
{ assert snapshot(process.out.model, process.out.versions).match() }
{ assert snapshot(process.out.model, process.out.versions_variancepartition).match() }
)
}
}
Expand Down Expand Up @@ -180,7 +180,7 @@ nextflow_process {
{ assert path(process.out.results[0][1]).getText().contains("gene_id\tlogFC\tAveExpr\tt\tP.Value\tadj.P.Val\tB") },
{ assert path(process.out.results[0][1]).getText().contains("2124\t549\t4.84") },
{ assert path(process.out.results[0][1]).getText().contains("1707.33") },
{ assert snapshot(process.out.model, process.out.versions).match() }
{ assert snapshot(process.out.model, process.out.versions_variancepartition).match() }
)
}
}
Expand Down Expand Up @@ -211,7 +211,7 @@ nextflow_process {
then {
assertAll(
{ assert process.success },
{ assert snapshot(process.out.model, process.out.versions).match() },
{ assert snapshot(process.out.model, process.out.versions_variancepartition).match() },
{ assert path(process.out.results[0][1]).getText().contains("gene_id\tlogFC\tAveExpr\tt\tP.Value\tadj.P.Val\tB") },
{ assert path(process.out.results[0][1]).getText().contains("-95.67") },
{ assert path(process.out.results[0][1]).getText().contains("1050\t549\t4.16") }
Expand Down
50 changes: 25 additions & 25 deletions modules/nf-core/variancepartition/dream/tests/main.nf.test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
"versions.yml:md5,fc1f26eb2194018e99fc2916332676b7"
]
],
"timestamp": "2025-12-23T18:57:51.598745459",
"meta": {
"nf-test": "0.9.3",
"nextflow": "25.04.2"
}
},
"timestamp": "2025-12-23T18:57:51.598745459"
},
"RNAseq - Feature Counts - formula + comparison contrast string - interaction": {
"content": [
Expand All @@ -37,11 +37,11 @@
"versions.yml:md5,fc1f26eb2194018e99fc2916332676b7"
]
],
"timestamp": "2025-12-26T15:10:51.980662299",
"meta": {
"nf-test": "0.9.3",
"nextflow": "25.04.2"
}
},
"timestamp": "2025-12-26T15:10:51.980662299"
},
"Mus musculus - contrasts - matrix - no formula": {
"content": [
Expand All @@ -61,11 +61,11 @@
"versions.yml:md5,fc1f26eb2194018e99fc2916332676b7"
]
],
"timestamp": "2025-12-16T15:52:08.047223666",
"meta": {
"nf-test": "0.9.3",
"nextflow": "25.10.0"
}
},
"timestamp": "2025-12-16T15:52:08.047223666"
},
"Mus musculus - expression table - contrasts + blocking factors": {
"content": [
Expand Down Expand Up @@ -130,11 +130,11 @@
]
}
],
"timestamp": "2025-12-16T19:59:57.947286403",
"meta": {
"nf-test": "0.9.3",
"nextflow": "25.10.0"
}
},
"timestamp": "2025-12-16T19:59:57.947286403"
},
"Mus musculus - expression table - contrasts + blocking factors stub": {
"content": [
Expand Down Expand Up @@ -199,11 +199,11 @@
]
}
],
"timestamp": "2025-11-10T16:18:37.294899756",
"meta": {
"nf-test": "0.9.2",
"nextflow": "25.04.6"
}
},
"timestamp": "2025-11-10T16:18:37.294899756"
},
"Mus musculus - expression table - contrasts + formula + weighted comparison contrast string": {
"content": [
Expand All @@ -221,11 +221,11 @@
"versions.yml:md5,fc1f26eb2194018e99fc2916332676b7"
]
],
"timestamp": "2025-12-23T18:57:42.761838155",
"meta": {
"nf-test": "0.9.3",
"nextflow": "25.04.2"
}
},
"timestamp": "2025-12-23T18:57:42.761838155"
},
"Mus musculus - expression table - contrasts + formula + comparison contrast string - no intercept stub": {
"content": [
Expand Down Expand Up @@ -279,16 +279,16 @@
"treatment_mCherry_hND6.dream.results.tsv:md5,d41d8cd98f00b204e9800998ecf8427e"
]
],
"versions": [
"versions_variancepartition": [
"versions.yml:md5,03b686ec8c67a91501ebb2b2a5234e77"
]
}
],
"timestamp": "2026-01-13T15:35:07.121696674",
"meta": {
"nf-test": "0.9.3",
"nextflow": "25.04.2"
}
"nextflow": "25.10.3"
},
"timestamp": "2026-03-27T17:37:18.306234608"
},
"RNAseq - Voom - Feature Counts - formula + comparison contrast string - interaction": {
"content": [
Expand Down Expand Up @@ -316,11 +316,11 @@
"versions.yml:md5,fc1f26eb2194018e99fc2916332676b7"
]
],
"timestamp": "2026-01-13T15:53:31.743589111",
"meta": {
"nf-test": "0.9.3",
"nextflow": "25.04.2"
}
},
"timestamp": "2026-01-13T15:53:31.743589111"
},
"RNAseq - Feature Counts - formula + comparison contrast string - interaction with seed": {
"content": [
Expand Down Expand Up @@ -374,16 +374,16 @@
"genotype_WT_KO_treatment_Control_Treated.dream.results.tsv:md5,90a06e6b4945c706025582a4c9fddf2c"
]
],
"versions": [
"versions_variancepartition": [
"versions.yml:md5,fc1f26eb2194018e99fc2916332676b7"
]
}
],
"timestamp": "2026-03-25T21:03:52.327619963",
"meta": {
"nf-test": "0.9.5",
"nextflow": "25.10.4"
}
"nf-test": "0.9.3",
"nextflow": "25.10.3"
},
"timestamp": "2026-03-27T17:37:36.063080982"
},
"Mus musculus - expression table - contrasts + formula + comparison contrast string": {
"content": [
Expand All @@ -401,10 +401,10 @@
"versions.yml:md5,fc1f26eb2194018e99fc2916332676b7"
]
],
"timestamp": "2025-12-26T15:11:01.472042429",
"meta": {
"nf-test": "0.9.3",
"nextflow": "25.04.2"
}
},
"timestamp": "2025-12-26T15:11:01.472042429"
}
}
2 changes: 0 additions & 2 deletions subworkflows/nf-core/abundance_differential_filter/main.nf
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,6 @@ workflow ABUNDANCE_DIFFERENTIAL_FILTER {
inputs.samples_and_matrix.filter{index -> index[0].differential_method == 'dream' }
)

ch_versions = ch_versions.mix( VARIANCEPARTITION_DREAM.out.versions.first() )

// ----------------------------------------------------
// Collect results
// ----------------------------------------------------
Expand Down
Loading
Loading