From dcec3a9417960d1f0aa318e219ff157c11cbdc6f Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 15 Feb 2023 17:45:31 -0800 Subject: [PATCH 01/30] Base CEV report Rmd --- inst/CEV.report.Rmd | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 inst/CEV.report.Rmd diff --git a/inst/CEV.report.Rmd b/inst/CEV.report.Rmd new file mode 100644 index 0000000..8817dda --- /dev/null +++ b/inst/CEV.report.Rmd @@ -0,0 +1,16 @@ +--- +params: + title: + author: + date: +title: '`r params$title`' +author: '`r params$author`' +date: '`r params$date`' +output: pdf_document +--- + +# Tree + +# Mutation Assignment Heatmap + +# Clone Summary Heatmap From fb93f4eb4b3f51baa59630e8ed5aa5511af77e91 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 15 Feb 2023 17:45:46 -0800 Subject: [PATCH 02/30] Basic report function --- R/CEV.report.R | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 R/CEV.report.R diff --git a/R/CEV.report.R b/R/CEV.report.R new file mode 100644 index 0000000..b12b432 --- /dev/null +++ b/R/CEV.report.R @@ -0,0 +1,18 @@ +CEV.report <- function( + output.filename, + title, + author, + date = NULL + ) { + report.params <- list( + title = title, + author = author, + date = if (!is.null(date)) date else Sys.Date() + ); + + rmarkdown::render( + 'inst/test.Rmd', + output_file = output.filename, + params = report.params + ); + } From 4ca62d148e95fd61cac004efc8c81587ed18950f Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 15 Feb 2023 17:53:33 -0800 Subject: [PATCH 03/30] Update changelog --- DESCRIPTION | 4 ++-- NEWS | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index a73855f..957aa89 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: CancerEvolutionVisualization Title: Publication Quality Phylogenetic Tree Plots -Version: 1.0.1 -Date: 2022-10-03 +Version: 1.1.0 +Date: 2023-02-15 Authors@R: c( person("Paul Boutros", role = "cre", email = "PBoutros@mednet.ucla.edu"), person("Adriana Salcedo", role = "aut"), diff --git a/NEWS b/NEWS index 111a773..3146a72 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,10 @@ +CancerEvolutionVisualization 1.1.0 2023-02-15 (Dan Knight)i + +ADDED +* CEV report template and public function + + +-------------------------------------------------------------------------- CancerEvolutionVisualization 1.0.1 2022-10-03 (Dan Knight) UPDATE From 88fafa8220cf3b6e9308394b25b0319c75028a4b Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 15 Feb 2023 17:57:46 -0800 Subject: [PATCH 04/30] Update dependencies --- DESCRIPTION | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 957aa89..a24945d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -19,7 +19,8 @@ Imports: plyr, grDevices, utils, - stringr + stringr, + rmarkdown Suggests: testthat, knitr From 634cb28c91b24294f8f70187d520cf1585dde9d3 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Fri, 24 Feb 2023 18:22:42 -0800 Subject: [PATCH 05/30] Define report inputs --- R/CEV.report.R | 9 +++++++++ R/prep.report.R | 15 +++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 R/prep.report.R diff --git a/R/CEV.report.R b/R/CEV.report.R index b12b432..0d3401a 100644 --- a/R/CEV.report.R +++ b/R/CEV.report.R @@ -1,9 +1,18 @@ CEV.report <- function( + phylogeny, + SNV.assignment, + SNV.counts, + CCF.values, output.filename, title, author, date = NULL ) { + phylogeny <- prep.phylogeny(phylogeny); + SNV.assignment <- prep.SNV.assignment(SNV.assignment); + SNV.counts <- prep.SNV.counts(SNV.counts); + CCF.values <- prep.CCF.values(CCF.values); + report.params <- list( title = title, author = author, diff --git a/R/prep.report.R b/R/prep.report.R new file mode 100644 index 0000000..5115b9f --- /dev/null +++ b/R/prep.report.R @@ -0,0 +1,15 @@ +prep.phylogeny <- function(phylogeny) { + return(phylogeny); + } + +prep.SNV.assignment <- function(SNV.assignment) { + return(SNV.assignment); + }; + +prep.SNV.counts <- function(SNV.counts) { + return(SNV.counts); + }; + +prep.CCF.values <- function(CCF.values) { + return(CCF.values); + }; From a7a7c7ca047c55ce036eab0b9955802aecbea30f Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Mon, 27 Feb 2023 14:46:24 -0800 Subject: [PATCH 06/30] Validate input column exist --- R/prep.report.R | 37 +++++++++++++++++++++++++++++++++++++ R/utility.R | 20 ++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/R/prep.report.R b/R/prep.report.R index 5115b9f..b5391a1 100644 --- a/R/prep.report.R +++ b/R/prep.report.R @@ -1,15 +1,52 @@ prep.phylogeny <- function(phylogeny) { + phylogeny.data.name <- "Phylogeny"; + + if (!is.data.frame(phylogeny)) { + stop(paste(phylogeny.data.name, 'input is not a data.frame.')); + } + + check.column.exists(phylogeny, "clone.id", phylogeny.data.name); + check.column.exists(phylogeny, "parent", phylogeny.data.name); + return(phylogeny); } prep.SNV.assignment <- function(SNV.assignment) { + SNV.assignment.data.name <- "SNV Assignment"; + + if (!is.data.frame(SNV.assignment)) { + stop(paste(SNV.assignment.data.name, 'input is not a data.frame.')); + } + + check.column.exists(SNV.assignment, 'snv.id', SNV.assignment.data.name); + check.column.exists(SNV.assignment, 'clone.id', SNV.assignment.data.name); + return(SNV.assignment); }; prep.SNV.counts <- function(SNV.counts) { + SNV.count.data.name <- 'SNV Count'; + if (!is.data.frame(SNV.counts)) { + stop(paste(SNV.count.data.name, 'input is not a data.frame.')); + } + + check.column.exists(SNV.counts, 'clone.id', SNV.count.data.name); + check.column.exists(SNV.counts, 'num.SNV', SNV.count.data.name); + check.column.exists(SNV.counts, 'CP', SNV.count.data.name); + return(SNV.counts); }; prep.CCF.values <- function(CCF.values) { + CCF.value.data.name <- 'CCF'; + + if (!is.data.frame(CCF.values)) { + stop(paste(CCF.value.data.name, 'input is not a data.frame.')); + } + + check.column.exists(CCF.values, 'sample.id', CCF.value.data.name); + check.column.exists(CCF.values, 'SNV.id', CCF.value.data.name); + check.column.exists(CCF.values, 'CCF', CCF.value.data.name); + return(CCF.values); }; diff --git a/R/utility.R b/R/utility.R index bac2ce1..aefc865 100644 --- a/R/utility.R +++ b/R/utility.R @@ -18,3 +18,23 @@ reindex.column <- function(column.values, new.value.index) { use.names = FALSE )); } + +check.column.exists <- function( + df, + column.name, + data.name = NULL + ) { + + result <- column.name %in% colnames(df); + + if (!result) { + message <- paste( + 'No column', + paste0('"', column.name, '"'), + "found in", + if (!is.null(data.name)) data.name else "data" + ); + + stop(message); + } + } From bbbd2b4839a3c13794c61ac3dc884f93cf2f3d43 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Mon, 27 Feb 2023 14:51:00 -0800 Subject: [PATCH 07/30] Wrapper report data prep function --- R/CEV.report.R | 10 ++++++---- R/prep.report.R | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/R/CEV.report.R b/R/CEV.report.R index 0d3401a..20777b4 100644 --- a/R/CEV.report.R +++ b/R/CEV.report.R @@ -8,10 +8,12 @@ CEV.report <- function( author, date = NULL ) { - phylogeny <- prep.phylogeny(phylogeny); - SNV.assignment <- prep.SNV.assignment(SNV.assignment); - SNV.counts <- prep.SNV.counts(SNV.counts); - CCF.values <- prep.CCF.values(CCF.values); + inputs <- prep.report( + phylogeny, + SNV.assignment, + SNV.counts, + CCF.values + ); report.params <- list( title = title, diff --git a/R/prep.report.R b/R/prep.report.R index b5391a1..97bfcff 100644 --- a/R/prep.report.R +++ b/R/prep.report.R @@ -1,3 +1,22 @@ +prep.report <- function( + phylogeny, + SNV.assignment, + SNV.counts, + CCF.values + ) { + phylogeny <- prep.phylogeny(phylogeny); + SNV.assignment <- prep.SNV.assignment(SNV.assignment); + SNV.counts <- prep.SNV.counts(SNV.counts); + CCF.values <- prep.CCF.values(CCF.values); + + return(list( + phylogeny = phylogeny, + SNV.assignment = SNV.assignment, + SNV.counts = CCF.values, + CCF.values = CCF.values + )); + } + prep.phylogeny <- function(phylogeny) { phylogeny.data.name <- "Phylogeny"; From 44fb5519ec370f98e0f8dcd1f15119b79562aef7 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Fri, 3 Mar 2023 15:52:23 -0800 Subject: [PATCH 08/30] Column validation unit tests --- R/prep.report.R | 4 +- tests/testthat/test-prep.report.R | 90 +++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 tests/testthat/test-prep.report.R diff --git a/R/prep.report.R b/R/prep.report.R index 97bfcff..7aab968 100644 --- a/R/prep.report.R +++ b/R/prep.report.R @@ -50,7 +50,7 @@ prep.SNV.counts <- function(SNV.counts) { } check.column.exists(SNV.counts, 'clone.id', SNV.count.data.name); - check.column.exists(SNV.counts, 'num.SNV', SNV.count.data.name); + check.column.exists(SNV.counts, 'num.snv', SNV.count.data.name); check.column.exists(SNV.counts, 'CP', SNV.count.data.name); return(SNV.counts); @@ -64,7 +64,7 @@ prep.CCF.values <- function(CCF.values) { } check.column.exists(CCF.values, 'sample.id', CCF.value.data.name); - check.column.exists(CCF.values, 'SNV.id', CCF.value.data.name); + check.column.exists(CCF.values, 'snv.id', CCF.value.data.name); check.column.exists(CCF.values, 'CCF', CCF.value.data.name); return(CCF.values); diff --git a/tests/testthat/test-prep.report.R b/tests/testthat/test-prep.report.R new file mode 100644 index 0000000..2c5c731 --- /dev/null +++ b/tests/testthat/test-prep.report.R @@ -0,0 +1,90 @@ +test_that('prep.phylogeny errors on missing clone.id column', { + invalid.phylogeny <- data.frame(clone.id = c(1)); + + expect_error( + prep.phylogeny(invalid.phylogeny), + regexp = 'parent' + ); + }); + +test_that('prep.phylogeny errors on missing parent column', { + invalid.phylogeny <- data.frame(parent = c(1)); + + expect_error( + prep.phylogeny(invalid.phylogeny), + regexp = 'clone.id' + ); + }); + + +test_that('prep.SNV.assignment errors on missing SNV.id column', { + invalid.SNV.assignment <- data.frame(clone.id = c(1)); + + expect_error( + prep.SNV.assignment(invalid.SNV.assignment), + regexp = 'snv.id' + ); + }); + +test_that('prep.SNV.assignment errors on missing clone.id column', { + invalid.SNV.assignment <- data.frame(snv.id = c(1)); + + expect_error( + prep.SNV.assignment(invalid.SNV.assignment), + regexp = 'clone.id' + ); + }); + +test_that('prep.SNV.counts errors on missing clone.id column', { + invalid.SNV.counts <- data.frame(num.snv = c(1), CP = c(1)); + + expect_error( + prep.SNV.counts(invalid.SNV.counts), + regexp = 'clone.id' + ); + }); + +test_that('prep.SNV.counts errors on missing num.snv column', { + invalid.SNV.counts <- data.frame(clone.id = c(1), CP = c(1)); + + expect_error( + prep.SNV.counts(invalid.SNV.counts), + regexp = 'num.snv' + ); + }); + +test_that('prep.SNV.counts errors on missing CP column', { + invalid.SNV.counts <- data.frame(clone.id = c(1), num.snv = c(1)); + + expect_error( + prep.SNV.counts(invalid.SNV.counts), + regexp = 'CP' + ); + }); + +test_that('prep.CCF.values errors on missing sample.id column', { + invalid.CCF.values <- data.frame(snv.id = c(1), CCF = c(1)); + + expect_error( + prep.CCF.values(invalid.CCF.values), + regexp = 'sample.id' + ); + }); + +test_that('prep.CCF.values errors on missing snv.id column', { + invalid.CCF.values <- data.frame(sample.id = c(1), CCF = c(1)); + + expect_error( + prep.CCF.values(invalid.CCF.values), + regexp = 'snv.id' + ); + }); + +test_that('prep.CCF.values errors on missing CCF column', { + invalid.CCF.values <- data.frame(sample.id = c(1), snv.id = c(1)); + + expect_error( + prep.CCF.values(invalid.CCF.values), + regexp = 'CCF' + ); + }); From ca17d5766850b7208c84e15eb27da5e6301933bf Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Mon, 27 Mar 2023 11:56:20 -0700 Subject: [PATCH 09/30] Heatmap default colours --- R/heatmap.R | 3 +++ R/plot.ccf.hm.R | 7 +++++-- R/plot.cluster.hm.R | 9 +++++---- R/plot.summary.ccf.hm.R | 6 ++++-- 4 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 R/heatmap.R diff --git a/R/heatmap.R b/R/heatmap.R new file mode 100644 index 0000000..97db251 --- /dev/null +++ b/R/heatmap.R @@ -0,0 +1,3 @@ +default.heatmap.colours <- function() { + return(c('white', 'blue')) + } \ No newline at end of file diff --git a/R/plot.ccf.hm.R b/R/plot.ccf.hm.R index 87e5068..33beda6 100644 --- a/R/plot.ccf.hm.R +++ b/R/plot.ccf.hm.R @@ -4,7 +4,7 @@ plot.ccf.hm <- function( cls.dim = 'both', cls.method = 'complete', dist.method = 'euclidean', - hm.cols = c('white', 'blue'), + hm.cols = NULL, xaxis.lab = NULL, xlab.label = 'Mutations', ... @@ -14,6 +14,8 @@ plot.ccf.hm <- function( hm.array[hm.array <= ccf.thres] <- 0; } col.labels <- seq(0, 1, .2); + + heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); hm <- BoutrosLab.plotting.general::create.heatmap( filename = NULL, @@ -34,7 +36,7 @@ plot.ccf.hm <- function( yaxis.cex = 0.6, yaxis.fontface = 1, colourkey.cex = 0.6, - colour.scheme = hm.cols, + colour.scheme = heatmap.colours, left.padding = 1, right.padding = 1, resolution = 3000, @@ -43,5 +45,6 @@ plot.ccf.hm <- function( colourkey.labels.at = col.labels, ... ); + return(hm); } diff --git a/R/plot.cluster.hm.R b/R/plot.cluster.hm.R index be7266c..2e289ef 100644 --- a/R/plot.cluster.hm.R +++ b/R/plot.cluster.hm.R @@ -15,16 +15,17 @@ plot.cluster.hm <- function( snv.order <- unique(DF[, c('snv.id', 'clone.id')]); cls.colours <- get.colours(DF$clone.id, return.names = TRUE); arr <- arr[snv.order$snv.id, levels(DF$ID)]; - + + heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); + if (!is.null(xaxis.col)) { xaxis.label <- unique(DF[DF$snv.id %in% rownames(arr), xaxis.col]); } hm <- plot.ccf.hm( hm.array = arr, - fname = NULL, cls.dim = 'none', - hm.cols = hm.cols, + hm.cols = heatmap.colours, ... ); @@ -48,7 +49,7 @@ plot.cluster.hm <- function( legend = list( title = 'CCF', labels = c(min(arr), max(arr)), - colours = if (is.null(hm.cols)) c('white', 'blue') else hm.cols, + colours = heatmap.colours, border = 'black', continuous = TRUE, size = 0.6 diff --git a/R/plot.summary.ccf.hm.R b/R/plot.summary.ccf.hm.R index 8d60001..f94b412 100644 --- a/R/plot.summary.ccf.hm.R +++ b/R/plot.summary.ccf.hm.R @@ -15,6 +15,8 @@ plot.summary.ccf.hm <- function( clone.df <- unique(DF[, c('clone.id', 'total.snv')]); sample.df <- aggregate(CCF ~ ID, data = DF[DF$CCF > 0, ], FUN = length); names(sample.df)[2] <- 'nsnv'; + + heatmap.colours <- default.heatmap.colours(); if (!is.null(clone.order) & !is.null(sample.order)) { arr <- arr[clone.order, sample.order]; @@ -59,7 +61,7 @@ plot.summary.ccf.hm <- function( yaxis.cex = 0.6, yaxis.fontface = 1, print.colour.key = FALSE, - colour.scheme = c('white', 'blue'), + colour.scheme = heatmap.colours, left.padding = 1, right.padding = 1, width = 9, @@ -71,7 +73,7 @@ plot.summary.ccf.hm <- function( legend = list( title = 'CCF', labels = c(min(arr), max(arr)), - colours = c('white', 'blue'), + colours = heatmap.colours, border = 'black', continuous = TRUE, size = 0.6 From e6837650d1dab57fb0e33dda269fc42c5ed5ad12 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Mon, 27 Mar 2023 11:57:49 -0700 Subject: [PATCH 10/30] Move heatmap functions --- R/heatmap.R | 259 ++++++++++++++++++++++++++++++++++++++++ R/plot.ccf.hm.R | 50 -------- R/plot.cluster.hm.R | 86 ------------- R/plot.summary.ccf.hm.R | 120 ------------------- 4 files changed, 259 insertions(+), 256 deletions(-) delete mode 100644 R/plot.ccf.hm.R delete mode 100644 R/plot.cluster.hm.R delete mode 100644 R/plot.summary.ccf.hm.R diff --git a/R/heatmap.R b/R/heatmap.R index 97db251..149c9d8 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -1,3 +1,262 @@ +plot.ccf.hm <- function( + hm.array, + ccf.thres = NULL, + cls.dim = 'both', + cls.method = 'complete', + dist.method = 'euclidean', + hm.cols = NULL, + xaxis.lab = NULL, + xlab.label = 'Mutations', + ... + ) { + + if (!is.null(ccf.thres)) { + hm.array[hm.array <= ccf.thres] <- 0; + } + col.labels <- seq(0, 1, .2); + + heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); + + hm <- BoutrosLab.plotting.general::create.heatmap( + filename = NULL, + x = hm.array, + force.clustering = TRUE, + cluster.dimensions = cls.dim, + clustering.method = cls.method, + rows.distance.method = dist.method, + cols.distance.method = dist.method, + xaxis.lab = xaxis.lab, + xlab.label = xlab.label, + xlab.cex = 1, + xaxis.cex = 0.6, + xaxis.fontface = 1, + xaxis.rot = 90, + yaxis.lab = colnames(hm.array), + ylab.cex = 1, + yaxis.cex = 0.6, + yaxis.fontface = 1, + colourkey.cex = 0.6, + colour.scheme = heatmap.colours, + left.padding = 1, + right.padding = 1, + resolution = 3000, + width = 9, + height = 5, + colourkey.labels.at = col.labels, + ... + ); + + return(hm); + } + +plot.cluster.hm <- function( + DF, + plt.height = 6, + plt.width = 11, + hm.cols = NULL, + xaxis.col = NULL, + ... + ) { + + if (is.null(levels(DF$ID))) { + DF$ID <- factor(DF$ID, levels = sort(unique(DF$ID))); + } + DF <- droplevels(DF)[order(DF$clone.id, -abs(DF$CCF)), ]; + arr <- data.frame.to.array(DF); + snv.order <- unique(DF[, c('snv.id', 'clone.id')]); + cls.colours <- get.colours(DF$clone.id, return.names = TRUE); + arr <- arr[snv.order$snv.id, levels(DF$ID)]; + + heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); + + if (!is.null(xaxis.col)) { + xaxis.label <- unique(DF[DF$snv.id %in% rownames(arr), xaxis.col]); + } + + hm <- plot.ccf.hm( + hm.array = arr, + cls.dim = 'none', + hm.cols = heatmap.colours, + ... + ); + + cov <- BoutrosLab.plotting.general::create.heatmap( + x = t(cls.colours[snv.order$clone.id]), + input.colours = TRUE, + clustering.method = 'none', + grid.col = FALSE, + print.colour.key = FALSE, + resolution = 5000 + ); + + legend.clone <- BoutrosLab.plotting.general::legend.grob( + list( + legend = list( + title = 'Clones', + labels = names(cls.colours), + colours = cls.colours, + border = 'black' + ), + legend = list( + title = 'CCF', + labels = c(min(arr), max(arr)), + colours = heatmap.colours, + border = 'black', + continuous = TRUE, + size = 0.6 + ) + ), + size = 1, + title.cex = 0.75, + label.cex = 0.6 + ); + + return(BoutrosLab.plotting.general::create.multiplot( + filename = NULL, + plot.objects = list(cov, hm), + plot.layout = c(1, 2), + panel.heights = c(1, 0.05), + xaxis.lab = if (!is.null(xaxis.col)) xaxis.label else NULL, + xaxis.cex = 0.6, + xaxis.rot = 90, + xaxis.fontface = 1, + xaxis.tck = 0, + yaxis.lab = list(NULL, colnames(arr)), + yaxis.cex = 0.6, + yaxis.tck = 0, + yaxis.fontface = 1, + y.spacing = 0.5, + left.padding = 17, + print.new.legend = TRUE, + legend = list(right = list( + fun = legend.clone + )), + height = plt.height, + width = plt.width + )); + } + +plot.summary.ccf.hm <- function( + DF, + ccf.thres = 0, + clone.order = NULL, + sample.order = NULL + ) { + + arr <- data.frame.to.array( + DF = DF, + value = 'median.ccf.per.sample', + x.axis = 'clone.id' + ); + arr[arr <= ccf.thres] <- 0; + + clone.df <- unique(DF[, c('clone.id', 'total.snv')]); + sample.df <- aggregate(CCF ~ ID, data = DF[DF$CCF > 0, ], FUN = length); + names(sample.df)[2] <- 'nsnv'; + + heatmap.colours <- default.heatmap.colours(); + + if (!is.null(clone.order) & !is.null(sample.order)) { + arr <- arr[clone.order, sample.order]; + clone.df$clone.id <- factor(clone.df$clone.id, levels = clone.order); + sample.df$ID <- factor(sample.df$ID, levels = sample.order); + } + + clone.bar <- BoutrosLab.plotting.general::create.barplot( + formula = total.snv ~ clone.id, + data = clone.df, + yaxis.cex = 0, + xaxis.lab = rep('', nrow(arr)), + xaxis.cex = 0, + ylimits = c( - max(clone.df$total.snv) * 0.05, max(clone.df$total.snv) * 1.05), + resolution = 50 + ); + + sample.bar <- BoutrosLab.plotting.general::create.barplot( + formula = ID ~ nsnv, + data = sample.df, + xlab.label = 'SNV per sample', + xlimits = c( - max(sample.df$nsnv) * 0.05, max(sample.df$nsnv) * 1.05), + ylab.label = NULL, + yaxis.lab = rep('', length(arr)), + yaxis.cex = 0, + resolution = 50, + plot.horizontal = TRUE + ); + + hm <- BoutrosLab.plotting.general::create.heatmap( + x = arr, + cluster.dimensions = 'none', + xlab.cex = 1, + xlab.label = 'Clone ID', + xaxis.lab = rownames(arr), + xaxis.cex = 0.6, + xaxis.fontface = 1, + xaxis.rot = 90, + ylab.cex = 1, + ylab.label = 'Sample ID', + yaxis.lab = colnames(arr), + yaxis.cex = 0.6, + yaxis.fontface = 1, + print.colour.key = FALSE, + colour.scheme = heatmap.colours, + left.padding = 1, + right.padding = 1, + width = 9, + height = 5 + ); + + legend.ccf <- BoutrosLab.plotting.general::legend.grob( + list( + legend = list( + title = 'CCF', + labels = c(min(arr), max(arr)), + colours = heatmap.colours, + border = 'black', + continuous = TRUE, + size = 0.6 + ) + ), + size = 1, + title.cex = 0.75, + label.cex = 0.6 + ); + + return(BoutrosLab.plotting.general::create.multiplot( + filename = NULL, + plot.objects = list(hm, sample.bar, clone.bar), + plot.layout = c(2, 2), + layout.skip = c(FALSE, FALSE, FALSE, TRUE), + panel.heights = c(0.3, 1), + panel.widths = c(1, 0.2), + plot.labels.to.retrieve = 1:3, + xlab.label = c('\t', 'Clone ID', '\t', '\t', 'SNV per sample'), + xlab.cex = 0.7, + xaxis.cex = 0.6, + xaxis.tck = 0.4, + xaxis.rot = 90, + xaxis.fontface = 1, + xlab.to.xaxis.padding = - 0.5, + ylab.label = c( 'SNV per clone', '\t', '\t', 'Sample ID', '\t'), + ylab.padding = 13, + ylab.cex = 0.7, + yaxis.cex = 0.6, + yaxis.tck = 0.4, + yaxis.fontface = 1, + x.spacing = c(- 3), + y.spacing = c(- 1.5), + left.padding = 10, + bottom.padding = 3, + # merge.legends = FALSE, + print.new.legend = TRUE, + legend = list(right = list( + fun = legend.ccf + )), + height = 6, + width = 11 + )); + } + default.heatmap.colours <- function() { return(c('white', 'blue')) } \ No newline at end of file diff --git a/R/plot.ccf.hm.R b/R/plot.ccf.hm.R deleted file mode 100644 index 33beda6..0000000 --- a/R/plot.ccf.hm.R +++ /dev/null @@ -1,50 +0,0 @@ -plot.ccf.hm <- function( - hm.array, - ccf.thres = NULL, - cls.dim = 'both', - cls.method = 'complete', - dist.method = 'euclidean', - hm.cols = NULL, - xaxis.lab = NULL, - xlab.label = 'Mutations', - ... - ) { - - if (!is.null(ccf.thres)) { - hm.array[hm.array <= ccf.thres] <- 0; - } - col.labels <- seq(0, 1, .2); - - heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); - - hm <- BoutrosLab.plotting.general::create.heatmap( - filename = NULL, - x = hm.array, - force.clustering = TRUE, - cluster.dimensions = cls.dim, - clustering.method = cls.method, - rows.distance.method = dist.method, - cols.distance.method = dist.method, - xaxis.lab = xaxis.lab, - xlab.label = xlab.label, - xlab.cex = 1, - xaxis.cex = 0.6, - xaxis.fontface = 1, - xaxis.rot = 90, - yaxis.lab = colnames(hm.array), - ylab.cex = 1, - yaxis.cex = 0.6, - yaxis.fontface = 1, - colourkey.cex = 0.6, - colour.scheme = heatmap.colours, - left.padding = 1, - right.padding = 1, - resolution = 3000, - width = 9, - height = 5, - colourkey.labels.at = col.labels, - ... - ); - - return(hm); - } diff --git a/R/plot.cluster.hm.R b/R/plot.cluster.hm.R deleted file mode 100644 index 2e289ef..0000000 --- a/R/plot.cluster.hm.R +++ /dev/null @@ -1,86 +0,0 @@ -plot.cluster.hm <- function( - DF, - plt.height = 6, - plt.width = 11, - hm.cols = NULL, - xaxis.col = NULL, - ... - ) { - - if (is.null(levels(DF$ID))) { - DF$ID <- factor(DF$ID, levels = sort(unique(DF$ID))); - } - DF <- droplevels(DF)[order(DF$clone.id, -abs(DF$CCF)), ]; - arr <- data.frame.to.array(DF); - snv.order <- unique(DF[, c('snv.id', 'clone.id')]); - cls.colours <- get.colours(DF$clone.id, return.names = TRUE); - arr <- arr[snv.order$snv.id, levels(DF$ID)]; - - heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); - - if (!is.null(xaxis.col)) { - xaxis.label <- unique(DF[DF$snv.id %in% rownames(arr), xaxis.col]); - } - - hm <- plot.ccf.hm( - hm.array = arr, - cls.dim = 'none', - hm.cols = heatmap.colours, - ... - ); - - cov <- BoutrosLab.plotting.general::create.heatmap( - x = t(cls.colours[snv.order$clone.id]), - input.colours = TRUE, - clustering.method = 'none', - grid.col = FALSE, - print.colour.key = FALSE, - resolution = 5000 - ); - - legend.clone <- BoutrosLab.plotting.general::legend.grob( - list( - legend = list( - title = 'Clones', - labels = names(cls.colours), - colours = cls.colours, - border = 'black' - ), - legend = list( - title = 'CCF', - labels = c(min(arr), max(arr)), - colours = heatmap.colours, - border = 'black', - continuous = TRUE, - size = 0.6 - ) - ), - size = 1, - title.cex = 0.75, - label.cex = 0.6 - ); - - return(BoutrosLab.plotting.general::create.multiplot( - filename = NULL, - plot.objects = list(cov, hm), - plot.layout = c(1, 2), - panel.heights = c(1, 0.05), - xaxis.lab = if (!is.null(xaxis.col)) xaxis.label else NULL, - xaxis.cex = 0.6, - xaxis.rot = 90, - xaxis.fontface = 1, - xaxis.tck = 0, - yaxis.lab = list(NULL, colnames(arr)), - yaxis.cex = 0.6, - yaxis.tck = 0, - yaxis.fontface = 1, - y.spacing = 0.5, - left.padding = 17, - print.new.legend = TRUE, - legend = list(right = list( - fun = legend.clone - )), - height = plt.height, - width = plt.width - )); - } diff --git a/R/plot.summary.ccf.hm.R b/R/plot.summary.ccf.hm.R deleted file mode 100644 index f94b412..0000000 --- a/R/plot.summary.ccf.hm.R +++ /dev/null @@ -1,120 +0,0 @@ -plot.summary.ccf.hm <- function( - DF, - ccf.thres = 0, - clone.order = NULL, - sample.order = NULL - ) { - - arr <- data.frame.to.array( - DF = DF, - value = 'median.ccf.per.sample', - x.axis = 'clone.id' - ); - arr[arr <= ccf.thres] <- 0; - - clone.df <- unique(DF[, c('clone.id', 'total.snv')]); - sample.df <- aggregate(CCF ~ ID, data = DF[DF$CCF > 0, ], FUN = length); - names(sample.df)[2] <- 'nsnv'; - - heatmap.colours <- default.heatmap.colours(); - - if (!is.null(clone.order) & !is.null(sample.order)) { - arr <- arr[clone.order, sample.order]; - clone.df$clone.id <- factor(clone.df$clone.id, levels = clone.order); - sample.df$ID <- factor(sample.df$ID, levels = sample.order); - } - - clone.bar <- BoutrosLab.plotting.general::create.barplot( - formula = total.snv ~ clone.id, - data = clone.df, - yaxis.cex = 0, - xaxis.lab = rep('', nrow(arr)), - xaxis.cex = 0, - ylimits = c( - max(clone.df$total.snv) * 0.05, max(clone.df$total.snv) * 1.05), - resolution = 50 - ); - - sample.bar <- BoutrosLab.plotting.general::create.barplot( - formula = ID ~ nsnv, - data = sample.df, - xlab.label = 'SNV per sample', - xlimits = c( - max(sample.df$nsnv) * 0.05, max(sample.df$nsnv) * 1.05), - ylab.label = NULL, - yaxis.lab = rep('', length(arr)), - yaxis.cex = 0, - resolution = 50, - plot.horizontal = TRUE - ); - - hm <- BoutrosLab.plotting.general::create.heatmap( - x = arr, - cluster.dimensions = 'none', - xlab.cex = 1, - xlab.label = 'Clone ID', - xaxis.lab = rownames(arr), - xaxis.cex = 0.6, - xaxis.fontface = 1, - xaxis.rot = 90, - ylab.cex = 1, - ylab.label = 'Sample ID', - yaxis.lab = colnames(arr), - yaxis.cex = 0.6, - yaxis.fontface = 1, - print.colour.key = FALSE, - colour.scheme = heatmap.colours, - left.padding = 1, - right.padding = 1, - width = 9, - height = 5 - ); - - legend.ccf <- BoutrosLab.plotting.general::legend.grob( - list( - legend = list( - title = 'CCF', - labels = c(min(arr), max(arr)), - colours = heatmap.colours, - border = 'black', - continuous = TRUE, - size = 0.6 - ) - ), - size = 1, - title.cex = 0.75, - label.cex = 0.6 - ); - - return(BoutrosLab.plotting.general::create.multiplot( - filename = NULL, - plot.objects = list(hm, sample.bar, clone.bar), - plot.layout = c(2, 2), - layout.skip = c(FALSE, FALSE, FALSE, TRUE), - panel.heights = c(0.3, 1), - panel.widths = c(1, 0.2), - plot.labels.to.retrieve = 1:3, - xlab.label = c('\t', 'Clone ID', '\t', '\t', 'SNV per sample'), - xlab.cex = 0.7, - xaxis.cex = 0.6, - xaxis.tck = 0.4, - xaxis.rot = 90, - xaxis.fontface = 1, - xlab.to.xaxis.padding = - 0.5, - ylab.label = c( 'SNV per clone', '\t', '\t', 'Sample ID', '\t'), - ylab.padding = 13, - ylab.cex = 0.7, - yaxis.cex = 0.6, - yaxis.tck = 0.4, - yaxis.fontface = 1, - x.spacing = c(- 3), - y.spacing = c(- 1.5), - left.padding = 10, - bottom.padding = 3, - # merge.legends = FALSE, - print.new.legend = TRUE, - legend = list(right = list( - fun = legend.ccf - )), - height = 6, - width = 11 - )); - } From 9e6171b97eea1c539b209515d265854963617d8a Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Tue, 28 Mar 2023 23:37:15 -0700 Subject: [PATCH 11/30] Refactor summary heatmap - Tweaked plot parameters - Refactored inputs/aggregate functionality for clearer use --- R/heatmap.R | 63 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 24 deletions(-) diff --git a/R/heatmap.R b/R/heatmap.R index 149c9d8..9535c88 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -138,45 +138,60 @@ plot.cluster.hm <- function( plot.summary.ccf.hm <- function( DF, - ccf.thres = 0, - clone.order = NULL, - sample.order = NULL + ccf.thres = 0 ) { + + median.ccf <- aggregate( + DF$CCF, + by = list(DF$ID, DF$clone.id), + FUN = median + ); + + colnames(median.ccf) <- c('ID', 'clone.id', 'median.CCF'); arr <- data.frame.to.array( - DF = DF, - value = 'median.ccf.per.sample', - x.axis = 'clone.id' + DF = median.ccf, + value = 'median.CCF', + x.axis = 'clone.id', + y.axis = 'ID' ); arr[arr <= ccf.thres] <- 0; - clone.df <- unique(DF[, c('clone.id', 'total.snv')]); - sample.df <- aggregate(CCF ~ ID, data = DF[DF$CCF > 0, ], FUN = length); - names(sample.df)[2] <- 'nsnv'; + filtered.CCFs <- DF$CCF > 0; + SNV.per.clone <- aggregate(snv.id ~ clone.id, DF[filtered.CCFs, ], FUN = length); + colnames(SNV.per.clone) <- c('clone.id', 'num.SNV'); + + SNV.per.sample <- aggregate(snv.id ~ ID, DF[filtered.CCFs, ], FUN = length); + colnames(SNV.per.sample) <- c('ID', 'num.SNV'); heatmap.colours <- default.heatmap.colours(); + barplot.padding.percentage <- 0.05; - if (!is.null(clone.order) & !is.null(sample.order)) { - arr <- arr[clone.order, sample.order]; - clone.df$clone.id <- factor(clone.df$clone.id, levels = clone.order); - sample.df$ID <- factor(sample.df$ID, levels = sample.order); - } + max.clone.SNV <- max(SNV.per.clone$num.SNV); clone.bar <- BoutrosLab.plotting.general::create.barplot( - formula = total.snv ~ clone.id, - data = clone.df, + formula = num.SNV ~ clone.id, + data = SNV.per.clone, yaxis.cex = 0, xaxis.lab = rep('', nrow(arr)), xaxis.cex = 0, - ylimits = c( - max(clone.df$total.snv) * 0.05, max(clone.df$total.snv) * 1.05), + ylimits = c( + -(max.clone.SNV * barplot.padding.percentage), + max.clone.SNV * (1 + barplot.padding.percentage) + ), resolution = 50 ); + max.sample.SNV <- max(SNV.per.sample$num.SNV); + sample.bar <- BoutrosLab.plotting.general::create.barplot( - formula = ID ~ nsnv, - data = sample.df, + formula = ID ~ num.SNV, + data = SNV.per.sample, xlab.label = 'SNV per sample', - xlimits = c( - max(sample.df$nsnv) * 0.05, max(sample.df$nsnv) * 1.05), + xlimits = c( + -(max.sample.SNV * barplot.padding.percentage), + max.sample.SNV * (1 + barplot.padding.percentage) + ), ylab.label = NULL, yaxis.lab = rep('', length(arr)), yaxis.cex = 0, @@ -238,16 +253,16 @@ plot.summary.ccf.hm <- function( xaxis.fontface = 1, xlab.to.xaxis.padding = - 0.5, ylab.label = c( 'SNV per clone', '\t', '\t', 'Sample ID', '\t'), - ylab.padding = 13, + ylab.padding = 8, ylab.cex = 0.7, yaxis.cex = 0.6, yaxis.tck = 0.4, yaxis.fontface = 1, - x.spacing = c(- 3), - y.spacing = c(- 1.5), + x.spacing = c(0), + y.spacing = c(-0.5), left.padding = 10, bottom.padding = 3, - # merge.legends = FALSE, + merge.legends = FALSE, print.new.legend = TRUE, legend = list(right = list( fun = legend.ccf From 0d301074b0b5f188049ad4239e20e54c5fbb970f Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 29 Mar 2023 23:23:13 -0700 Subject: [PATCH 12/30] Tests and test data for heatmap functions --- tests/testthat/data/multisample.test.data.Rda | Bin 0 -> 895 bytes tests/testthat/test-heatmap.R | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 tests/testthat/data/multisample.test.data.Rda create mode 100644 tests/testthat/test-heatmap.R diff --git a/tests/testthat/data/multisample.test.data.Rda b/tests/testthat/data/multisample.test.data.Rda new file mode 100644 index 0000000000000000000000000000000000000000..829b88a4daa532597b267b45ea3303a494383b78 GIT binary patch literal 895 zcmb2|=3oE==C@I{9x{$1a^>G8k!Iw{;oCj?Dya8;10YHa>(xILX| zndfAy(o0pPt3<-YJZG7(Gdi+N^x*kvoRqwJ=H9va3HQ?i4*lFRqqT6~_h)Zszu#MF z{QA{7zq6^r#hi*Q+H7+fAM$x>w8`C>zb{rONV%2KP{S>ye(%1J?C`%uwHw1cw`uL0 zGKu?XStQ5vtn)rQ9PKr3i)!8r+HP6b#klmm%+5NiqgyvE=-*XK1G4sdm^ z*>HsG_|FB8)6W6r)$qtm2J|sLl?-TP6qO9x!Qp|Eq79Rk7%+ zSKI!G{qmnKe_DUw?I*rRAM3ufrElF|?zP{%Ohb8xVU(|lFUUjqoRa2(R-8Z|_Ua>;%_3C5E|0W!tf3fg{Vg2mD+MJlD(Z54~ zUbWro{?Cl}&x7~X8Oy)EZu{DL?}`6U$Jp$>E9dRjy089mvCRg#E$24wlfGE-#p09O zV^;ep_m1t^H}=PG-rN1@!ef)T55{xZPsp#G{{QU!mFK3c`EGmn{|^42O>)mg-&kM# zbL5-;xAx5chwNkHk9c1f|Ly#<_b0P`RDCvE-G}s5;&b%B*gau>uJwlR#1GEB->w_J zF%LSw@w3S1S)bgWAN-ltCH`~qzYN(ouICG*(yL_t_?%b1B3NtpaNpewnOlAD;xGS7 z`oHRVl1|(a*Pk=bRX^b`^}WvgoN=xF2lqWD@g@HyKS_Lczkaa9{u5iBiT{oLU;ezF z7ogwK{A_)8Mc+gI1=AF~LUHN&>inoidAFTXn z{dUi^`}WaKl;16M+Zp%K_5Edzm0VM&|2cL!rPR^$++EGqJ10+O?rAu8eO9kmg3F(? z*H78~ah%Cv$L(*IcXDONPmk3 Date: Wed, 29 Mar 2023 23:23:29 -0700 Subject: [PATCH 13/30] Suppress three-colour warning in create.heatmap --- R/heatmap.R | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/R/heatmap.R b/R/heatmap.R index 9535c88..8b935d3 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -80,14 +80,15 @@ plot.cluster.hm <- function( ... ); - cov <- BoutrosLab.plotting.general::create.heatmap( + # Suppress "three-colour scheme" warning with 3 clones. + cov <- suppressWarnings(BoutrosLab.plotting.general::create.heatmap( x = t(cls.colours[snv.order$clone.id]), input.colours = TRUE, clustering.method = 'none', grid.col = FALSE, print.colour.key = FALSE, resolution = 5000 - ); + )); legend.clone <- BoutrosLab.plotting.general::legend.grob( list( From 33528a494d0ac73af3bb2def11ee845a4ebbe534 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 29 Mar 2023 23:25:41 -0700 Subject: [PATCH 14/30] Fix linting errors --- R/heatmap.R | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/R/heatmap.R b/R/heatmap.R index 8b935d3..38e1a16 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -14,7 +14,7 @@ plot.ccf.hm <- function( hm.array[hm.array <= ccf.thres] <- 0; } col.labels <- seq(0, 1, .2); - + heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); hm <- BoutrosLab.plotting.general::create.heatmap( @@ -66,9 +66,9 @@ plot.cluster.hm <- function( snv.order <- unique(DF[, c('snv.id', 'clone.id')]); cls.colours <- get.colours(DF$clone.id, return.names = TRUE); arr <- arr[snv.order$snv.id, levels(DF$ID)]; - + heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); - + if (!is.null(xaxis.col)) { xaxis.label <- unique(DF[DF$snv.id %in% rownames(arr), xaxis.col]); } @@ -141,13 +141,13 @@ plot.summary.ccf.hm <- function( DF, ccf.thres = 0 ) { - + median.ccf <- aggregate( DF$CCF, by = list(DF$ID, DF$clone.id), FUN = median ); - + colnames(median.ccf) <- c('ID', 'clone.id', 'median.CCF'); arr <- data.frame.to.array( @@ -164,7 +164,7 @@ plot.summary.ccf.hm <- function( SNV.per.sample <- aggregate(snv.id ~ ID, DF[filtered.CCFs, ], FUN = length); colnames(SNV.per.sample) <- c('ID', 'num.SNV'); - + heatmap.colours <- default.heatmap.colours(); barplot.padding.percentage <- 0.05; @@ -275,4 +275,4 @@ plot.summary.ccf.hm <- function( default.heatmap.colours <- function() { return(c('white', 'blue')) - } \ No newline at end of file + } From c519eaba339588de7db2d907c5e24de8bf29663a Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 29 Mar 2023 23:29:12 -0700 Subject: [PATCH 15/30] Update function imports --- NAMESPACE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NAMESPACE b/NAMESPACE index de2066f..3fe7373 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -8,6 +8,6 @@ importFrom("graphics", "par", "strheight", "strwidth") importFrom("grDevices", "dev.list", "rainbow") importFrom("utils", "read.table", "vi", "head") importFrom("stringr", "str_replace_all") -importFrom("stats", "setNames", "aggregate", "reshape") +importFrom("stats", "setNames", "median", "aggregate", "reshape") export(SRCGrob) From 32ac3640648b0c265cf497ad52e34cbecbdbb080 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 29 Mar 2023 23:30:07 -0700 Subject: [PATCH 16/30] Update changelog --- DESCRIPTION | 2 +- NEWS | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index c58c736..a11388b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: CancerEvolutionVisualization Title: Publication Quality Phylogenetic Tree Plots Version: 1.1.1 -Date: 2023-03-24 +Date: 2023-03-30 Authors@R: c( person("Paul Boutros", role = "cre", email = "PBoutros@mednet.ucla.edu"), person("Adriana Salcedo", role = "aut"), diff --git a/NEWS b/NEWS index ebaca32..10102bb 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,4 @@ -CancerEvolutionVisualization 1.1.1 2022-11-18 (Helena Winata) +CancerEvolutionVisualization 1.1.1 2023-03-29 (Helena Winata, Dan Knight) ADDED * Generic functions to generate accompanying heatmaps From 78ded728af27b46da5d4b9c1fe6286e3c49cabfc Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Thu, 30 Mar 2023 09:32:21 -0700 Subject: [PATCH 17/30] Update heatmap argument names --- R/heatmap.R | 89 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/R/heatmap.R b/R/heatmap.R index 38e1a16..c5b70d5 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -1,28 +1,33 @@ plot.ccf.hm <- function( - hm.array, - ccf.thres = NULL, - cls.dim = 'both', - cls.method = 'complete', + CCF.df, + CCF.threshold = NULL, + cluster.dim = 'both', + cluster.method = 'complete', dist.method = 'euclidean', - hm.cols = NULL, + colour.scheme = NULL, xaxis.lab = NULL, xlab.label = 'Mutations', ... ) { - if (!is.null(ccf.thres)) { - hm.array[hm.array <= ccf.thres] <- 0; + if (!is.null(CCF.threshold)) { + CCF.df[CCF.df <= CCF.threshold] <- 0; } col.labels <- seq(0, 1, .2); + sample.names <- colnames(CCF.df); - heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); + heatmap.colours <- if (!is.null(colour.scheme)) { + colour.scheme; + } else { + default.heatmap.colours(); + } hm <- BoutrosLab.plotting.general::create.heatmap( filename = NULL, - x = hm.array, + x = CCF.df, force.clustering = TRUE, - cluster.dimensions = cls.dim, - clustering.method = cls.method, + cluster.dimensions = cluster.dim, + clustering.method = cluster.method, rows.distance.method = dist.method, cols.distance.method = dist.method, xaxis.lab = xaxis.lab, @@ -31,7 +36,7 @@ plot.ccf.hm <- function( xaxis.cex = 0.6, xaxis.fontface = 1, xaxis.rot = 90, - yaxis.lab = colnames(hm.array), + yaxis.lab = sample.names, ylab.cex = 1, yaxis.cex = 0.6, yaxis.fontface = 1, @@ -50,39 +55,47 @@ plot.ccf.hm <- function( } plot.cluster.hm <- function( - DF, + cluster.df, plt.height = 6, plt.width = 11, - hm.cols = NULL, + colour.scheme = NULL, xaxis.col = NULL, ... ) { - if (is.null(levels(DF$ID))) { - DF$ID <- factor(DF$ID, levels = sort(unique(DF$ID))); + if (is.null(levels(cluster.df$ID))) { + cluster.df$ID <- factor( + cluster.df$ID, + levels = sort(unique(cluster.df$ID)) + ); } - DF <- droplevels(DF)[order(DF$clone.id, -abs(DF$CCF)), ]; - arr <- data.frame.to.array(DF); - snv.order <- unique(DF[, c('snv.id', 'clone.id')]); - cls.colours <- get.colours(DF$clone.id, return.names = TRUE); - arr <- arr[snv.order$snv.id, levels(DF$ID)]; - heatmap.colours <- if (!is.null(hm.cols)) hm.cols else default.heatmap.colours(); + cluster.df <- droplevels(cluster.df)[order(cluster.df$clone.id, -abs(cluster.df$CCF)), ]; + arr <- data.frame.to.array(cluster.df); + snv.order <- unique(cluster.df[, c('snv.id', 'clone.id')]); + cluster.colours <- get.colours(cluster.df$clone.id, return.names = TRUE); + arr <- arr[snv.order$snv.id, levels(cluster.df$ID)]; + + heatmap.colours <- if (!is.null(colour.scheme)) { + colour.scheme; + } else { + default.heatmap.colours(); + } if (!is.null(xaxis.col)) { - xaxis.label <- unique(DF[DF$snv.id %in% rownames(arr), xaxis.col]); + xaxis.label <- unique(cluster.df[cluster.df$snv.id %in% rownames(arr), xaxis.col]); } hm <- plot.ccf.hm( - hm.array = arr, - cls.dim = 'none', - hm.cols = heatmap.colours, + CCF.df = arr, + cluster.dim = 'none', + colour.scheme = heatmap.colours, ... ); # Suppress "three-colour scheme" warning with 3 clones. cov <- suppressWarnings(BoutrosLab.plotting.general::create.heatmap( - x = t(cls.colours[snv.order$clone.id]), + x = t(cluster.colours[snv.order$clone.id]), input.colours = TRUE, clustering.method = 'none', grid.col = FALSE, @@ -94,8 +107,8 @@ plot.cluster.hm <- function( list( legend = list( title = 'Clones', - labels = names(cls.colours), - colours = cls.colours, + labels = names(cluster.colours), + colours = cluster.colours, border = 'black' ), legend = list( @@ -138,31 +151,31 @@ plot.cluster.hm <- function( } plot.summary.ccf.hm <- function( - DF, - ccf.thres = 0 + mutation.df, + CCF.threshold = 0 ) { median.ccf <- aggregate( - DF$CCF, - by = list(DF$ID, DF$clone.id), + mutation.df$CCF, + by = list(mutation.df$ID, mutation.df$clone.id), FUN = median ); colnames(median.ccf) <- c('ID', 'clone.id', 'median.CCF'); arr <- data.frame.to.array( - DF = median.ccf, + median.ccf, value = 'median.CCF', x.axis = 'clone.id', y.axis = 'ID' ); - arr[arr <= ccf.thres] <- 0; + arr[arr <= CCF.threshold] <- 0; - filtered.CCFs <- DF$CCF > 0; - SNV.per.clone <- aggregate(snv.id ~ clone.id, DF[filtered.CCFs, ], FUN = length); + filtered.CCFs <- mutation.df$CCF > 0; + SNV.per.clone <- aggregate(snv.id ~ clone.id, mutation.df[filtered.CCFs, ], FUN = length); colnames(SNV.per.clone) <- c('clone.id', 'num.SNV'); - SNV.per.sample <- aggregate(snv.id ~ ID, DF[filtered.CCFs, ], FUN = length); + SNV.per.sample <- aggregate(snv.id ~ ID, mutation.df[filtered.CCFs, ], FUN = length); colnames(SNV.per.sample) <- c('ID', 'num.SNV'); heatmap.colours <- default.heatmap.colours(); From 36d753ddbbfc2aff7aca8977b4288bfc9a803e62 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 5 Apr 2023 02:15:08 -0700 Subject: [PATCH 18/30] Validate clone IDs in report input --- R/prep.report.R | 25 ++++++++++++++++++++++++- R/utility.R | 38 +++++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/R/prep.report.R b/R/prep.report.R index 7aab968..df0f57e 100644 --- a/R/prep.report.R +++ b/R/prep.report.R @@ -9,7 +9,9 @@ prep.report <- function( SNV.counts <- prep.SNV.counts(SNV.counts); CCF.values <- prep.CCF.values(CCF.values); - return(list( + validate.clone.ids(phylogeny, SNV.assignment, SNV.counts); + + return(list( phylogeny = phylogeny, SNV.assignment = SNV.assignment, SNV.counts = CCF.values, @@ -69,3 +71,24 @@ prep.CCF.values <- function(CCF.values) { return(CCF.values); }; + +validate.clone.ids <- function( + phylogeny, + SNV.assignment, + SNV.counts + ) { + + reference.clone.ids <- unique(phylogeny$clone.id); + + get.clone.error.message <- function(input.name) { + return(paste(input.name, 'clone IDs do not match phylogeny clone IDs.')) + } + + if (!column.contains.all(reference.clone.ids, SNV.assignment$clone.id)) { + stop(get.clone.error.message('SNV Assignment')); + } + + if (!column.contains.all(reference.clone.ids, SNV.counts$clone.id)) { + stop(get.clone.error.message('SNV Count')) + } + } diff --git a/R/utility.R b/R/utility.R index d3b6815..ab1d108 100644 --- a/R/utility.R +++ b/R/utility.R @@ -26,17 +26,49 @@ check.column.exists <- function( ) { result <- column.name %in% colnames(df); - + if (!result) { message <- paste( 'No column', paste0('"', column.name, '"'), - "found in", - if (!is.null(data.name)) data.name else "data" + 'found in', + if (!is.null(data.name)) data.name else 'data' ); stop(message); } + } + +column.contains.all <- function(reference.column, checked.column) { + vector.error.message <- function(column.type) { + return(paste(column.type, 'must be a vector.')) + } + + if (!is.vector(reference.column)) { + stop(vector.error.message('Reference')); + } + + if (!is.vector(checked.column)) { + stop(vector.error.message('Checked')); + } + + if (is.list(reference.column)) { + reference.column <- unlist(reference.column); + } + + reference.values <- sapply( + reference.column, + FUN = function(column.name) TRUE, + USE.NAMES = TRUE + ); + + values.in.reference <- all(sapply( + checked.column, + FUN = function(column.name) !is.na(reference.values[column.name]) + )); + + return(values.in.reference); + } data.frame.to.array <- function( DF, From 0ff8553ba460852651cb762798e7a2292b6a529d Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 5 Apr 2023 02:21:12 -0700 Subject: [PATCH 19/30] Fix DESCRIPTION --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 078992f..e1a90da 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -20,7 +20,7 @@ Imports: grDevices, utils, stringr, - rmarkdown + rmarkdown, BoutrosLab.plotting.general Suggests: testthat, From 31e820a8f904ff5eddee499ef97c6b7dcaef27e2 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 5 Apr 2023 04:00:13 -0700 Subject: [PATCH 20/30] Column validation unit tests --- tests/testthat/test-prep.report.R | 27 +++++++++++++++++++++++++++ tests/testthat/test-utility.R | 19 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/testthat/test-prep.report.R b/tests/testthat/test-prep.report.R index 2c5c731..c4047cf 100644 --- a/tests/testthat/test-prep.report.R +++ b/tests/testthat/test-prep.report.R @@ -88,3 +88,30 @@ test_that('prep.CCF.values errors on missing CCF column', { regexp = 'CCF' ); }); + +test_that('validate.clone.ids handles valid clone IDs', { + input <- data.frame(clone.ids = c('A', 'B', 'C')); + expect_silent(validate.clone.ids(input, input, input)); + }); + +test_that('validate.clone.ids handles invalid SNV assignment clone IDs', { + reference.clone.ids <- c('B', 'A', 'B', 'C'); + invalid.clone.ids <- sapply(reference.clone.ids, function(x) paste0(x, 'B')); + + phylogeny <- SNV.counts <- data.frame(clone.ids = reference.clone.ids); + SNV.assignment <- data.frame(clone.ids = invalid.clone.ids); + + expect_error( + validate.clone.ids(phylogeny, SNV.assignment, SNV.counts), 'clone'); + }); + +test_that('validate.clone.ids handles invalid SNV count clone IDs', { + reference.clone.ids <- c('1', '3', 'B', '4'); + invalid.clone.ids <- sapply(reference.clone.ids, function(x) paste0(x, 'A')); + + phylogeny <- SNV.assignment <- data.frame(clone.ids = reference.clone.ids); + SNV.counts <- data.frame(clone.ids = invalid.clone.ids); + + expect_error( + validate.clone.ids(phylogeny, SNV.assignment, SNV.counts), 'clone'); + }); diff --git a/tests/testthat/test-utility.R b/tests/testthat/test-utility.R index 940d2f8..e2e9fcb 100644 --- a/tests/testthat/test-utility.R +++ b/tests/testthat/test-utility.R @@ -32,3 +32,22 @@ test_that( expect_equal(as.numeric(reindexed), expected.values); }); + +test_that('column.contains.all handles valid case', { + reference <- c('1', '2'); + expect_true(column.contains.all(reference, reference)); + }); + +test_that('column.contains.all errors on non-vector reference input', { + reference <- NULL; + checked <- 1:10; + + expect_error(column.contains.all(reference, checked), 'vector'); + }); + +test_that('column.contains.all errors on non-vector checked input', { + reference <- c('A', 'C', 'D'); + other <- NULL; + + expect_error(column.contains.all(reference, other), 'vector'); + }); From 558af784d384ab2ca0e8b61d40ebc80e7c51c2a4 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 5 Apr 2023 04:34:07 -0700 Subject: [PATCH 21/30] Prep report plot data --- R/prep.report.R | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/R/prep.report.R b/R/prep.report.R index df0f57e..73c658f 100644 --- a/R/prep.report.R +++ b/R/prep.report.R @@ -10,12 +10,13 @@ prep.report <- function( CCF.values <- prep.CCF.values(CCF.values); validate.clone.ids(phylogeny, SNV.assignment, SNV.counts); + + summary.tree.input <- create.report.summary.tree.input(phylogeny, SNV.counts); + heatmap.input <- create.report.heatmap.input(SNV.assignment, CCF.values); return(list( - phylogeny = phylogeny, - SNV.assignment = SNV.assignment, - SNV.counts = CCF.values, - CCF.values = CCF.values + summary.tree.input = summary.tree.input, + heatmap.input = heatmap.input )); } @@ -92,3 +93,25 @@ validate.clone.ids <- function( stop(get.clone.error.message('SNV Count')) } } + +create.report.summary.tree.input <- function(phylogeny, SNV.counts) { + clone.ids <- phylogeny$clone.id; + rownames(SNV.counts) <- SNV.counts$clone.id; + + return(data.frame( + node.id = clone.ids, + parent = phylogeny$parent, + length1 = SNV.counts[clone.ids, 'num.snv'] + )); + } + +create.report.heatmap.input <- function(SNV.assignment, CCF.values) { + rownames(SNV.assignment) <- SNV.assignment$SNV.id; + + return(data.frame( + ID = CCF.values$sample.id, + snv.id = CCF.values$SNV.id, + CCF = CCF.values$CCF, + clone.id = SNV.assignment[CCF.values$SNV.id, 'clone.id'] + )); + } From e6197c7a631c18555f1c15c86c8386e4c57c562b Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 5 Apr 2023 04:34:29 -0700 Subject: [PATCH 22/30] Tests for report plot data --- tests/testthat/data/report.data.Rda | Bin 0 -> 1065 bytes tests/testthat/test-prep.report.R | 33 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/testthat/data/report.data.Rda diff --git a/tests/testthat/data/report.data.Rda b/tests/testthat/data/report.data.Rda new file mode 100644 index 0000000000000000000000000000000000000000..233e2a7f248a2c5e2bfd6c0bc850ef1bfada40a9 GIT binary patch literal 1065 zcmV+^1lIc>iwFP!000001MON(NEA^VpV`sOg(4_AdRDN-o&7%895(D$X&-p#vgoG5 z?rN?Y+MzC;JJ(fD9XfRo0*UIszxQVT<5Lm#on}Dh_x|s_ z-~Z$N-kWupfop?_=wQ?^49jSVn1R$e43p!J zVwleSM40bdt@e-l7%CO2@Bw3VxG+_SJ(zUoyR8E<<^1g%_hRm(v5`udMIw`A936aA zq}n4;oFl6m?~%}BwIxhQn3OOjVOqkBgjor5!mp&YrC(e6wLJr*Ut9XMrC(e6wWVKM z`n9Fsgg{AwQUav~$_SJdD90D9gNp+KvB+QK5Sw+x9FyQ9+HAEWHL5$d>aNZX)ty!I z%B&GRv$ufdWXg&%A4B8Qm8mLMm38Khtwf;NF7p#)FE|99a5i)+W(8y`;T7pP`v5L z|Ieug7}pAItuUb#CbhzpR+!cbGg@I*E6nMG^3_xGkpHEY1{l{0ZLKh&6(+U9lvbG5 z3Nu<^Rx8ZugWgxx%|l5ID5U|VHK2?Jl+}Q8O+@uq{${uHac=Ya>r0QFt;XHpm-G48 zvyR379cN>)pCN->&bRl^?|1HhaQ28DeAK=g?VXrkjXGcE8Cq}_h(FkWvr)Ydg`Ky- z7r5^>;uGzkjCO7l`8eO}-2;N}4Y%)f?z*bkf?Y2I@vS$;EA!g**Gbsd{eDD0 zj|=e%`;tGOfd3VLT%vzNF%S7rJJy%?2l!COAO3!{1AJ?Kd!V1!Tk!JwiaQ;T`mHJA z1K)OIoUpGb))#gaALoItgKBbe^wWG1pXxk7{it3}TR)Y~cw6jyy8kf`^@1Je6~-_4>l4_&*dMC>3x2x3$d|4! z_6O$;@F6~`za>R{;8W=Z=L?;Odf;5exINN;%w6y;DE1rCbKpFMUse66ADnNhd{IBj zM|H5{KgX%gL_gwFJ&&Lx`h3E?Eq@+j9NoXF^~L_>&&5E#$cx6OI`2_G=$ftv&I77H z#E*Pwz3Bc^)G@$E*FiNObwhl#PT0Tn{98UoFUW(|Z&~pi#qSfE1&N>?Y9#LD@Og>3FF{D_xvt Date: Wed, 5 Apr 2023 04:39:29 -0700 Subject: [PATCH 23/30] Fix code style --- R/CEV.report.R | 2 +- R/prep.report.R | 14 ++++++------ R/utility.R | 4 ++-- tests/testthat/test-prep.report.R | 36 +++++++++++++++---------------- tests/testthat/test-utility.R | 4 ++-- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/R/CEV.report.R b/R/CEV.report.R index 20777b4..d7c8cc4 100644 --- a/R/CEV.report.R +++ b/R/CEV.report.R @@ -14,7 +14,7 @@ CEV.report <- function( SNV.counts, CCF.values ); - + report.params <- list( title = title, author = author, diff --git a/R/prep.report.R b/R/prep.report.R index 73c658f..caad21e 100644 --- a/R/prep.report.R +++ b/R/prep.report.R @@ -8,9 +8,9 @@ prep.report <- function( SNV.assignment <- prep.SNV.assignment(SNV.assignment); SNV.counts <- prep.SNV.counts(SNV.counts); CCF.values <- prep.CCF.values(CCF.values); - + validate.clone.ids(phylogeny, SNV.assignment, SNV.counts); - + summary.tree.input <- create.report.summary.tree.input(phylogeny, SNV.counts); heatmap.input <- create.report.heatmap.input(SNV.assignment, CCF.values); @@ -21,20 +21,20 @@ prep.report <- function( } prep.phylogeny <- function(phylogeny) { - phylogeny.data.name <- "Phylogeny"; + phylogeny.data.name <- 'Phylogeny'; if (!is.data.frame(phylogeny)) { stop(paste(phylogeny.data.name, 'input is not a data.frame.')); } - check.column.exists(phylogeny, "clone.id", phylogeny.data.name); - check.column.exists(phylogeny, "parent", phylogeny.data.name); + check.column.exists(phylogeny, 'clone.id', phylogeny.data.name); + check.column.exists(phylogeny, 'parent', phylogeny.data.name); return(phylogeny); } prep.SNV.assignment <- function(SNV.assignment) { - SNV.assignment.data.name <- "SNV Assignment"; + SNV.assignment.data.name <- 'SNV Assignment'; if (!is.data.frame(SNV.assignment)) { stop(paste(SNV.assignment.data.name, 'input is not a data.frame.')); @@ -88,7 +88,7 @@ validate.clone.ids <- function( if (!column.contains.all(reference.clone.ids, SNV.assignment$clone.id)) { stop(get.clone.error.message('SNV Assignment')); } - + if (!column.contains.all(reference.clone.ids, SNV.counts$clone.id)) { stop(get.clone.error.message('SNV Count')) } diff --git a/R/utility.R b/R/utility.R index ab1d108..b899feb 100644 --- a/R/utility.R +++ b/R/utility.R @@ -60,13 +60,13 @@ column.contains.all <- function(reference.column, checked.column) { reference.column, FUN = function(column.name) TRUE, USE.NAMES = TRUE - ); + ); values.in.reference <- all(sapply( checked.column, FUN = function(column.name) !is.na(reference.values[column.name]) )); - + return(values.in.reference); } diff --git a/tests/testthat/test-prep.report.R b/tests/testthat/test-prep.report.R index 8c53962..60d9dff 100644 --- a/tests/testthat/test-prep.report.R +++ b/tests/testthat/test-prep.report.R @@ -2,7 +2,7 @@ load('data/report.data.Rda'); test_that('prep.phylogeny errors on missing clone.id column', { invalid.phylogeny <- data.frame(clone.id = c(1)); - + expect_error( prep.phylogeny(invalid.phylogeny), regexp = 'parent' @@ -11,7 +11,7 @@ test_that('prep.phylogeny errors on missing clone.id column', { test_that('prep.phylogeny errors on missing parent column', { invalid.phylogeny <- data.frame(parent = c(1)); - + expect_error( prep.phylogeny(invalid.phylogeny), regexp = 'clone.id' @@ -21,7 +21,7 @@ test_that('prep.phylogeny errors on missing parent column', { test_that('prep.SNV.assignment errors on missing SNV.id column', { invalid.SNV.assignment <- data.frame(clone.id = c(1)); - + expect_error( prep.SNV.assignment(invalid.SNV.assignment), regexp = 'snv.id' @@ -30,7 +30,7 @@ test_that('prep.SNV.assignment errors on missing SNV.id column', { test_that('prep.SNV.assignment errors on missing clone.id column', { invalid.SNV.assignment <- data.frame(snv.id = c(1)); - + expect_error( prep.SNV.assignment(invalid.SNV.assignment), regexp = 'clone.id' @@ -39,7 +39,7 @@ test_that('prep.SNV.assignment errors on missing clone.id column', { test_that('prep.SNV.counts errors on missing clone.id column', { invalid.SNV.counts <- data.frame(num.snv = c(1), CP = c(1)); - + expect_error( prep.SNV.counts(invalid.SNV.counts), regexp = 'clone.id' @@ -48,7 +48,7 @@ test_that('prep.SNV.counts errors on missing clone.id column', { test_that('prep.SNV.counts errors on missing num.snv column', { invalid.SNV.counts <- data.frame(clone.id = c(1), CP = c(1)); - + expect_error( prep.SNV.counts(invalid.SNV.counts), regexp = 'num.snv' @@ -57,7 +57,7 @@ test_that('prep.SNV.counts errors on missing num.snv column', { test_that('prep.SNV.counts errors on missing CP column', { invalid.SNV.counts <- data.frame(clone.id = c(1), num.snv = c(1)); - + expect_error( prep.SNV.counts(invalid.SNV.counts), regexp = 'CP' @@ -66,7 +66,7 @@ test_that('prep.SNV.counts errors on missing CP column', { test_that('prep.CCF.values errors on missing sample.id column', { invalid.CCF.values <- data.frame(snv.id = c(1), CCF = c(1)); - + expect_error( prep.CCF.values(invalid.CCF.values), regexp = 'sample.id' @@ -75,7 +75,7 @@ test_that('prep.CCF.values errors on missing sample.id column', { test_that('prep.CCF.values errors on missing snv.id column', { invalid.CCF.values <- data.frame(sample.id = c(1), CCF = c(1)); - + expect_error( prep.CCF.values(invalid.CCF.values), regexp = 'snv.id' @@ -84,7 +84,7 @@ test_that('prep.CCF.values errors on missing snv.id column', { test_that('prep.CCF.values errors on missing CCF column', { invalid.CCF.values <- data.frame(sample.id = c(1), snv.id = c(1)); - + expect_error( prep.CCF.values(invalid.CCF.values), regexp = 'CCF' @@ -99,10 +99,10 @@ test_that('validate.clone.ids handles valid clone IDs', { test_that('validate.clone.ids handles invalid SNV assignment clone IDs', { reference.clone.ids <- c('B', 'A', 'B', 'C'); invalid.clone.ids <- sapply(reference.clone.ids, function(x) paste0(x, 'B')); - + phylogeny <- SNV.counts <- data.frame(clone.ids = reference.clone.ids); SNV.assignment <- data.frame(clone.ids = invalid.clone.ids); - + expect_error( validate.clone.ids(phylogeny, SNV.assignment, SNV.counts), 'clone'); }); @@ -110,10 +110,10 @@ test_that('validate.clone.ids handles invalid SNV assignment clone IDs', { test_that('validate.clone.ids handles invalid SNV count clone IDs', { reference.clone.ids <- c('1', '3', 'B', '4'); invalid.clone.ids <- sapply(reference.clone.ids, function(x) paste0(x, 'A')); - + phylogeny <- SNV.assignment <- data.frame(clone.ids = reference.clone.ids); SNV.counts <- data.frame(clone.ids = invalid.clone.ids); - + expect_error( validate.clone.ids(phylogeny, SNV.assignment, SNV.counts), 'clone'); }); @@ -129,22 +129,22 @@ test_that('create.report.heatmap.input result can create CCF heatmap', { input <- data.frame.to.array( create.report.heatmap.input(SNV.assignment, CCF.values) ); - + heatmap <- plot.ccf.hm(input); - + expect_is(heatmap, 'trellis'); }); test_that('create.report.heatmap.input result can create summary heatmap', { input <- create.report.heatmap.input(SNV.assignment, CCF.values); heatmap <- plot.summary.ccf.hm(input); - + expect_is(heatmap, 'trellis'); }); test_that('create.report.heatmap.input result can create cluster heatmap', { input <- create.report.heatmap.input(SNV.assignment, CCF.values); heatmap <- plot.cluster.hm(input); - + expect_is(heatmap, 'trellis'); }); diff --git a/tests/testthat/test-utility.R b/tests/testthat/test-utility.R index e2e9fcb..253691d 100644 --- a/tests/testthat/test-utility.R +++ b/tests/testthat/test-utility.R @@ -41,13 +41,13 @@ test_that('column.contains.all handles valid case', { test_that('column.contains.all errors on non-vector reference input', { reference <- NULL; checked <- 1:10; - + expect_error(column.contains.all(reference, checked), 'vector'); }); test_that('column.contains.all errors on non-vector checked input', { reference <- c('A', 'C', 'D'); other <- NULL; - + expect_error(column.contains.all(reference, other), 'vector'); }); From 5048d7f78507db7e1f79e4959a144846862b94a1 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 5 Apr 2023 05:24:26 -0700 Subject: [PATCH 24/30] Fix report column names --- R/CEV.report.R | 6 ++++-- R/heatmap.R | 10 +++++----- R/prep.report.R | 6 +++--- R/utility.R | 2 +- tests/testthat/test-prep.report.R | 12 ++++++------ 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/R/CEV.report.R b/R/CEV.report.R index d7c8cc4..bed92ee 100644 --- a/R/CEV.report.R +++ b/R/CEV.report.R @@ -18,11 +18,13 @@ CEV.report <- function( report.params <- list( title = title, author = author, - date = if (!is.null(date)) date else Sys.Date() + date = if (!is.null(date)) date else Sys.Date(), + summary.tree.data = inputs$summary.tree.input, + heatmap.data = inputs$heatmap.input ); rmarkdown::render( - 'inst/test.Rmd', + 'inst/CEV.report.Rmd', output_file = output.filename, params = report.params ); diff --git a/R/heatmap.R b/R/heatmap.R index c5b70d5..a333929 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -72,9 +72,9 @@ plot.cluster.hm <- function( cluster.df <- droplevels(cluster.df)[order(cluster.df$clone.id, -abs(cluster.df$CCF)), ]; arr <- data.frame.to.array(cluster.df); - snv.order <- unique(cluster.df[, c('snv.id', 'clone.id')]); + snv.order <- unique(cluster.df[, c('SNV.id', 'clone.id')]); cluster.colours <- get.colours(cluster.df$clone.id, return.names = TRUE); - arr <- arr[snv.order$snv.id, levels(cluster.df$ID)]; + arr <- arr[snv.order$SNV.id, levels(cluster.df$ID)]; heatmap.colours <- if (!is.null(colour.scheme)) { colour.scheme; @@ -83,7 +83,7 @@ plot.cluster.hm <- function( } if (!is.null(xaxis.col)) { - xaxis.label <- unique(cluster.df[cluster.df$snv.id %in% rownames(arr), xaxis.col]); + xaxis.label <- unique(cluster.df[cluster.df$SNV.id %in% rownames(arr), xaxis.col]); } hm <- plot.ccf.hm( @@ -172,10 +172,10 @@ plot.summary.ccf.hm <- function( arr[arr <= CCF.threshold] <- 0; filtered.CCFs <- mutation.df$CCF > 0; - SNV.per.clone <- aggregate(snv.id ~ clone.id, mutation.df[filtered.CCFs, ], FUN = length); + SNV.per.clone <- aggregate(SNV.id ~ clone.id, mutation.df[filtered.CCFs, ], FUN = length); colnames(SNV.per.clone) <- c('clone.id', 'num.SNV'); - SNV.per.sample <- aggregate(snv.id ~ ID, mutation.df[filtered.CCFs, ], FUN = length); + SNV.per.sample <- aggregate(SNV.id ~ ID, mutation.df[filtered.CCFs, ], FUN = length); colnames(SNV.per.sample) <- c('ID', 'num.SNV'); heatmap.colours <- default.heatmap.colours(); diff --git a/R/prep.report.R b/R/prep.report.R index caad21e..fb5fbd7 100644 --- a/R/prep.report.R +++ b/R/prep.report.R @@ -40,7 +40,7 @@ prep.SNV.assignment <- function(SNV.assignment) { stop(paste(SNV.assignment.data.name, 'input is not a data.frame.')); } - check.column.exists(SNV.assignment, 'snv.id', SNV.assignment.data.name); + check.column.exists(SNV.assignment, 'SNV.id', SNV.assignment.data.name); check.column.exists(SNV.assignment, 'clone.id', SNV.assignment.data.name); return(SNV.assignment); @@ -67,7 +67,7 @@ prep.CCF.values <- function(CCF.values) { } check.column.exists(CCF.values, 'sample.id', CCF.value.data.name); - check.column.exists(CCF.values, 'snv.id', CCF.value.data.name); + check.column.exists(CCF.values, 'SNV.id', CCF.value.data.name); check.column.exists(CCF.values, 'CCF', CCF.value.data.name); return(CCF.values); @@ -110,7 +110,7 @@ create.report.heatmap.input <- function(SNV.assignment, CCF.values) { return(data.frame( ID = CCF.values$sample.id, - snv.id = CCF.values$SNV.id, + SNV.id = CCF.values$SNV.id, CCF = CCF.values$CCF, clone.id = SNV.assignment[CCF.values$SNV.id, 'clone.id'] )); diff --git a/R/utility.R b/R/utility.R index b899feb..1cb1c25 100644 --- a/R/utility.R +++ b/R/utility.R @@ -73,7 +73,7 @@ column.contains.all <- function(reference.column, checked.column) { data.frame.to.array <- function( DF, value = 'CCF', - x.axis = 'snv.id', + x.axis = 'SNV.id', y.axis = 'ID' ) { diff --git a/tests/testthat/test-prep.report.R b/tests/testthat/test-prep.report.R index 60d9dff..f17fc69 100644 --- a/tests/testthat/test-prep.report.R +++ b/tests/testthat/test-prep.report.R @@ -24,12 +24,12 @@ test_that('prep.SNV.assignment errors on missing SNV.id column', { expect_error( prep.SNV.assignment(invalid.SNV.assignment), - regexp = 'snv.id' + regexp = 'SNV.id' ); }); test_that('prep.SNV.assignment errors on missing clone.id column', { - invalid.SNV.assignment <- data.frame(snv.id = c(1)); + invalid.SNV.assignment <- data.frame(SNV.id = c(1)); expect_error( prep.SNV.assignment(invalid.SNV.assignment), @@ -65,7 +65,7 @@ test_that('prep.SNV.counts errors on missing CP column', { }); test_that('prep.CCF.values errors on missing sample.id column', { - invalid.CCF.values <- data.frame(snv.id = c(1), CCF = c(1)); + invalid.CCF.values <- data.frame(SNV.id = c(1), CCF = c(1)); expect_error( prep.CCF.values(invalid.CCF.values), @@ -73,17 +73,17 @@ test_that('prep.CCF.values errors on missing sample.id column', { ); }); -test_that('prep.CCF.values errors on missing snv.id column', { +test_that('prep.CCF.values errors on missing SNV.id column', { invalid.CCF.values <- data.frame(sample.id = c(1), CCF = c(1)); expect_error( prep.CCF.values(invalid.CCF.values), - regexp = 'snv.id' + regexp = 'SNV.id' ); }); test_that('prep.CCF.values errors on missing CCF column', { - invalid.CCF.values <- data.frame(sample.id = c(1), snv.id = c(1)); + invalid.CCF.values <- data.frame(sample.id = c(1), SNV.id = c(1)); expect_error( prep.CCF.values(invalid.CCF.values), From 37757c8bfc5cfe92661a6ddebfe5da002f625212 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 5 Apr 2023 05:24:44 -0700 Subject: [PATCH 25/30] Add plots to CEV report Rmarkdown template --- inst/CEV.pdf | Bin 0 -> 111704 bytes inst/CEV.report.Rmd | 14 +++++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 inst/CEV.pdf diff --git a/inst/CEV.pdf b/inst/CEV.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6097fa2d52d00a19097638766f11aa91067b25fc GIT binary patch literal 111704 zcmeFZWpE`u(k5zVX2v!%Gqc;w%-CjTW@ct)w%g3i%-m*fGuzkw&dlBK-r0z6Hez>w zy(i+FQ%a>&%1o6?dLC7g$O((mFwn92Y%icZwr($UzSfKJp>-_cmu*wDtv7>bt{%E8gzSll4 zcy}#AoH(I)?+-Uj)OZYX(6I<@V%xq{$2w?=A68P8YPayWwP!@HHIIAjQvGh-R_Y|p zG&IjK`l&qD^X&M(5cB9{4h3jgrZY)GI0D$B5GrA_2t2@ z-@h~%py=e3MAT@%Txu+%Z)HqCPD>{wqM~c^m3E*rF*kLxH>UaHW>-1~Co3y`dpDXd zFB^Zw%GSt);-7`(^i7RroU9DKV)C#0B&stI zn8obUzCUsP8<)@%3L(4iac_L{Y;zpHn9!`eZyP*N z_Q{=74>fQ*M(gy2Og5Az$VT9I8oD>!?sbc|5lPX&QZ>n4Tr$9f8efsPew$q-}c8XfY@QBZo zduY+q{apS0!+63SR1UX6R6PQKLHRci*I=6U1w;<~>SC|8w9VH0EgSR-lcTBy* zE5Df(Czux9vs;#w#GNDOG-O}(E#5OCC*J4!)t;BuG}x!}Zi0V^ zC7}uulWEaPc0`>WU=e+{mP4p|uA2qWJvPEz$F=PUB9{zyf{7nKH@yTpXX-YH!hty< z=%iJM$-#AcVeu-h{oa|>l5Z^XI!_8ycP#THPbyt!tnyhPjkTNn_K++K@-|~Ca)b2F z1B0QR34Dgbp@WxsSN0Xyq@KfDt|;e8DDGVx--x525Z}(eyC0zEZIy2zHnZDx7;Er3 zriF|Rzn__}qU3qao!MtS=0@L>oNp}Uw#c2`r#$7h%-hy`k#4H_W%OCKo9-xJYNG$b z*X!W!>dfHrl9&>@*#~Ho{UYl34eil8b_j8+7j$<|&F+JvXG;}{Pd(;Q+>7;;jkON# zU42Fb^XEX-i#tH-+o;XwV{rIP=E+Xj`M|zK!elk=C%??VV|4tE+6{Cj0 zj4Hs^@lrx06^0IFXqpQBta4puWBY3-`ZD4pR{`6llQdb3Ly^mKrVj30MQ7EV6+bwP zjdifPs)h^a)>dTeKsEmwX1IWK_L;``b}*+ofl6)j}CldV%O9w9t&#==YiNF26P zJdT!<0TMy7Cl-K+RanVZSU#>8f{}d3w?HVajwn0`P`>UA1XFmy&K3nbS;D9abGyUs zYBFOR|Iynbr*nGK^vY|g4IuU{0JKj^4bm4TPYq@FXfQM?!c-3&5au@kuqD8bZ9{_~ z+LI_y8j$Boq-7fN6MAPG9KrJ|~Z z!p^}?Z$)F(MZVyp{gx5`=$xz*=yQoFjQ`aCpz56l3s75Z+_d@pNhM1qI#l)k_YXkC3kCv_jYB)q%e2Z326| zBZH6*o3)91vGBmi)Gym-n8TcqCw%Y^Cp?X+flv9dkAabUKz{ZhwgABI00Rk-hl1GY zG3@xG!~zNvpriOUQv8OR z;714y8bLr+2yrA38o{X#Qj5jj2L=^Fl*37cAQ2K22SVjP%T*}FDGxr)Gdb2gCU?YY z1ArB3&XqSqdO>vuC#Hui?$xM8v;=L?lVyj94n*%A{msaZPwU^@lVl6*iEtSx+n=(7 zs0PL7SAyt^fXJC2L63;mCn^}{`VA71up$m=vRJno=P5TLAgA*TvjRXg7O0S52$-YX;F*($=Ru607q(0%&Z7nQPtdx8QkNB zW5hf57gVr-Ji|zaXbc}I98%NcO3o#mvvAl!`&t7_hM!O# z2p&itpj^>j0mQ!?ZGW=sYH`biQVl5YXxNh3s@fvAV0IvFAzVem58nPBzczjn^@R6^ z;ET>7Bu9>ftPT_xq|~R$i=Po?|K9T5Rp1-aeW=WUZN6_yu$lz7Ft;oYNf(K{xXCwG z3cqiJR)iO1Swx#eodgM^qDJuc-0D(Wg4U!tgxh4@^0}01lpy54QB!QiHae8+m zX}fO=Xk&68a+h}xa=&nYyn}q9e*gB4`_lQM2*wFU2nGsP1=b9E9ZcPi(ytx-9$XU@ z8D$X3is6LG!>GapM?b}8&C+Ed$ZE%?ZZfB5KZ#;E#wg9SY305O!`i_1ouPsOn=xb( zcLMaVeKLe*22BP{Faj(BTMB7DA+6uiI9g+lCV&Q-rky%R9aC*k)2iXL`Nho2Cd+!$ z%xgH<0NG5@Y~Jv;qAHDM`RCBXn%0fhc5B6v^R$<>sr7L4x}nQ__78l6?%uBDkAQ%J zfRVgO{khp5hS|EMYp{qRC_^igEEhp8H;yW2&cnN<)n4^;&y{C%^y2U7-y_Bg+=gzS zZn2J{T2EW;wa&DQtYMmkn>(!)T(n)HJpx{V;l1Fq#S(@COUg{kj|8Sob(VC9YT#;? zElnRJA1G`F)~DMCJZ3_EAs;ASIBcs~>^DS!nUeaQDNqdQ$4Q>N&6Mr>EcO?6*l{ATrN77k(E1Ns*SGGdjjZzPq z5_ERSOUY7EmkhM@dd>p{4{0Y4a7tXNosu|WG2|;_6VokYNW(-!cthcMX3B*YD+v@5 zi?PsZM1A`p^Xl}1?|^W<4rBUWvSPw5DI4dbc(#2uy^UV$>W%a`*^Sq2w~n7iulB5F zu4e5|J;$P>u@~i;@}dfN)w3E}4W6zcn@PW>z$SAGAM!?8YC6wWS`%&AIF;G-{JMMZ zGtCIpaFIba))vds_0To)74{bM%i?lr{O_(L=-;cpQ%P>y99nlhB~7X(>3XfjHyYPY zE8V^1y&Fo?v?7r@ruVP z%J$$h`O?-ZQBc-+((k?Nec~4i>@^uZIZ2aFV_aohQRsA|*Vj9~?dmhXIb>O3Q_`G* z2Nd!x1kWeq>Ega$AjBi4HoDQv^hw(ays4lN`BZC*V=-xtJfjQN5w(wz;zX}FDxH9N9HgmlQ+rf^yFlJ-mvq#`&DKbk3$t*hs|%xnbq4P&*`j_ z$8$cnt^2n+=qdDQ9V6}Ywtd?&haT_TJ@8k2PB2_Bt`MA%HaaftOU!IG&tKuMN%N|m zHQDWGwik0FZ*jY4^2;_`;w^-oqwUYfDlb(>%?{Sn8|n_|Ud{X2bDiO@>5tgn%4dAygQBwpY;a=r-W~b*TsfOy~ybJbe>$_B$g7=N(T9; zKDAya_E%lHUEdh#QMp(?wC>*KYch08yH`HvE{(4oSMJ_bF>6e8Qaa>)9Cm2VtUlXQ zU67etuj;O-_)@)(pE2&PFH0h2bG(5c18-mdClgWD|1uH%*GiU!{P|IY zU*E$u1$XW7YtNFgw!?x(ornC%k-|Y?&px5^gBHIfOmGCq0r#owvZd%4V-xeYCU4L` zajAiQFO{c$!p=yT!!evWQU5qzN;fbp0F5d>7jIxv>tqtrJqgcachR418Sl$ztz z3rq@|^MlnyeDRB$Y`Kumr{kbbqi zSVH1ss`BLkfllYn zef-=hNau<)xNqk@1pMMx^Dz9?=NrOwYV;@X+dIA(`^Pgv%+=v^Nk|K??b``e>C0#dgJYZ!(j*^~yi?nL`H72W(hs?UX-=$%!TfJoi zO{6NzN*a^4`=Y@&WRy?nowy)XAZ4ObMAtxRnx@Qg*2cS6dG-?7AZ(avtU^NBK*^P+ zOfgzHCS;;~!%)E(t)65u@PpY<bU#RHZo&TOn9w<##Nzk^IA85Z^L6 zsvL%{3kfCRbkAUMwUladZ6(u9?ip0SFT|eCnzwFVi}PsR{Pv05@AjUtcmY#-tK+ z{RZauYQg2POXC{4V%Q@OAX2m5mO;$wG3A8s@PK4Mwm>*e@*qEPuS)d*`V#*lnyL96V)xKU|dhvWBa3-@`?) zh0JwaC$j($Kaqliu((;E2t71caT6$97LS=`s32_Bops0#&WMpL{p`^+Wm3sRI z`o#34OcT~A`Ubn;RJJccXy3#T zoC&KleM4P9dJ^u0^hXL}PK49#3^b()uSd=F7={w|JGKBnls&Ck?0C&tU3s^E4e7sckfs`bE^p#;kD7 z?~D24b{w6j01MHo{CcOD1%ebr=hE`ZQTlG85b>`bph~yi=m_gSDYtC9Zsb&Cce`{n zN`8K8WJ#JoX`*WzwbI3mHLQ(mGQYXV!gj0OyU7HIXmvQPqx%&J&mm9BwqZ|7|0mW% zMa1xV6JFHb)xp#&=s<>?aDvmw=jvmt!}k2}ej=nRUC{WOMp)nzP)oo_sG|d##GVsi+oW;z}~#h_ayW>G(Br-#pH#gk-(!u)+d= zpFxNa3&nLfG)D6?@0)NToDjg%?+aZbb)!Um-?DBCEdje_E)=YbmTQy&6V~Q-I+oaC z6U`^B@nyld`F@E6EU!(dD@Kk*FIx~=;?F&~s1oRgrOs`Q3c` zn^)Sy?es^Inh$ro5aCKWqnJF5s}XGzD~9s5}9N$smFY*GH=P>I9Nc8N&n^N1rbsDkUH6_Mucx)U+Vgz%<9wtanusah@pq}V22V{ zeB2(tFXa>Uv&-r84MZK?IKe|-pq@DeXF(G*gAh@f3}kZXWt+lFpm<>2?jS=0zeIvL ztAIr6AuMu=nhzeSSAB8n{CihtXdmdAo}OY`w}qjb7BUPcUOoKQp*MHvaD_(3nLu|%2X z+O|VL-4_akDL=QCLMWdYD~567sFF#hFYNc_Pr%#6AVf??1M66ziG-!T8-DCWiZNRR z6Mf;}$l59l1oPJ7-q@qo9ABb85^|C3GvIupY^X19Lfs}Fj5pW|eiir%mQKNq};v`=1NaT+t5*Tsx#Od6v(84$DMyK@qQI^<^rzw;$Zp2(o z-y+3R5xo|7V#V<~w8Y3MzqG$^ry^?O34`VrHW_2w$oz!;t323h83~2VH#Lpae5hNL zqDBOLbNz6z2}Kl9;fYX-Mc_DbNod4mw550Y)D7~;&{Rb9)iMzZ8euh!QiU!*qH7RL z`GW-UM4jX^8(~zqKJua|eSG8mR+&eHE)HX>8--5N}ei+ElDW?OC%O|!UpRcey_?5Ue1Q7I=6%Du%1-|~B3 ze}x6b>TD6&zH`69fA_2o|MvwQ(|=vj{Y|3$O``lwqWn#w{7s_#O``lwqWn#w{7s_# zO``nYB2k$BY(@OnZk~UUD9o(?If*h^fz(Hj5TxHNYiC3y&bG=*n9Ri<%nlak=TC@& zfzZK21M0WNVelDa2V^Mo)>*(mI05BYR@ex(3t_s$OobL=+rx^Tlkq(Zen5HkFioMO z`=zdQk9g3c3z{k&tS!zu0%j9*0Fy*0#4Fz*4J09W>9hFy_n4+3mx7woAYPHYMn-m$ z?9<~skWF4V#o@nCwHg0V1pl(|{{VUN&mExu%LEGZUzErHu1%oKf6*p?2o&bOD3Sk^ zKw=-8y*Adcg^Z|Z zPa@wehWPlnH@{s>H*XOno|E&5usGQHF48i#43kZ-JYZPH4?*QV|kDs(BM8El`K4}QClQ(WudEfws~5?{Q? z5O~%hUzdf7dK4_Eg~nvwghn+e&q}@NoLOU@MAf(q1lv}Tsdy9=awr2YnPh$+b|Vm5 zp3B8mpEW$y#v4U7kf*BwI&(zU3(JiiqO6r+^W#!5@VKmDGmY(NGKj;jnGNOu%o9MO z2!@1GWeu$NbzK^ZdGiJ~l#R(6#J(+!@p28AzZnl|K0dF4R)fW&tCvy}h%=YR+JQvz zRO#9PXK^!^*bfvWjjeRrS{6>wS3NX#OF~#ux=+)vUOv3=t1Z^w>LMMJ=u|yk<3^-| z5g0k>@9;vHW1d(CA5{_HM_Frf+dm+|w0RV6wn1l+TJ7|1nIRupXBSec8=jKtNd{J` zN(MbxQwJ{%(2K8>(+}k;aPy@ksR%+;cg?9@6jU$tqT!C7s5QQN*&W*!!Cfc1@$}%W zLA%UT(R`?e#z&39d9}6r9*c@;dt)z$qO3O#2z7#mhL;P+$S@NAEwkHF7)=o5D?Q@? z3;ifJ-FmR?vDv}Vz0$%w+CWI2LA`qI(<^CYrb&gE+@|Kqq)a5FS0xzgu+nYBD(@v51d{Xp!O_jrN$u69`@ws_)nw98a2qt2S+=rB=ap zbL}Ke>F@d=cp%y8_Ca_x09nqtMFmlvd^6B$1_`?_qSwhAeF4<16+@4wiLBaOI6}vz z$op))XZgBHBs&z(EGBa5%!{lk!0%jUnxKM~y`|c4sZdBQXqoe-W~pyuAa~W%iHWz4 zF2%)v^}5dDaYSSuWsDq$W{OPRO7A~_K7>4-UHcnsru-iCcSrI=MBjXkc`aNZ`ynYi zhfqo7W|wup9k+Kgw!P%kotbawK83^Qc#*iT`+`xfU2T*hYTec_&y&mTpGDgF@^CG= z#Z4e-TZX32${Np5hB1j&BysJTp+WZ&5RIUO45H3ZNerNDECDihQ9jD#*O&{EtCY+u zCoRX#{V@kXDgDPBqCdt^nKdlyl*MFh-B05|>}0KqxI7a0Aqe6@5&D9r#Eqs3#1!<%9eM#2O3U6_qAHq3Ak3IB4z zV0pSD2!VwaQLLSCAP!+J$R5fC42#K^3yr@4k?aq~a1h?2z-CV`hF~#970b;9g#uT~g&^-`ud@R&j2>}z1m^!Mn)$ko zgt31_(!v zi6z9J{_%q=N#hvbOfTAOEmw@n5Q8+YdC5Pd>QD8(*kZ0Dh(LWw>3@lW(qA5{0E}ouzTyoz~zOo+9`2bIW5{v)$ z>FU?q`+rPV|3-%YMuz`JhW|!}|3-%YMuz`JhW|!}|3-%YMuz`yA;YYH@X3GSrvE^O z8NQx1Xa0Jc^B*Zj0!BtAc8-4~zhK9I9e%x-fQ60eFAVv=-H5%`=w|3xxc<}GG9xQ| zGJ3YoiVJo9WaW6}=WN|91J}Uu0!!tADm{V&hpha{BJhhv@wp%>Y0g6H9Zol|x+}0LDrhKmn#627rUB z89ooxcWeqx4}_Vmkri|$4M@os>uWhLPh#xb65mno&&bj*re}=q;lVZhxl2@8nNKML zj)0h`hzJU>cnt8Xe2mhESK0R`{@dg+fITJt3!fV=%xBAZcsWG58D%IV(}y~+FXj%E zqhtM3!b5k9yeB>ITgF;QMOi`Cq%I_Y(&5z9!$xGJwY4>j39ZZ&jB~UJ%)^q;wIV~a zEl|rUhXx>Tbu_=lj&6`$9m_L$Ndw34D*zu2px8Qkur*G=Pxi3Y_o~Kc@`I51kd0^E z0bZ!xH?rdU?Lz=Eg5P{|so0$F2UG>Q1i-ZnjkQi-YglVoJ{X+XobBv@Fdy+=9e$*V zKFArxhFqFt&}611 z-NZnE#b`@CeCJsocN2XNbZM2jnT4sP^-!G?!#k^U<7-|@!^yp?o6mG-eAgf%MlFDu zQs)3Bv?>7e!?EFO=@9FyE1tE^{;!I>h!HWI9P1qJ^dHMgnyYKAtL`7o3k!Wq19!5E z(pu-3%2qTuTE6i_pN6l5{;$mwFvl=@KmfP^{4RzLLwsc)>4|&>hkUnb0 zV5_oxx6q1vVxTii67r}JcFvE1?jGJse(@pVFoE|B^sPJ_j_|QUZRpODSV><1X!%6& zrM}gi`mD*`FOwF1tmKh8|6IZYK+dJWDag#a9j2Fi|6DcvO!_`CxTY~R115f-1Nv@5 zC4>!a5%byjN{8&UgI})n8PX7xkQ&4_Bi!4!y7G2a{y~%C$ohuiE}+R|u_Gq-fmHUf zDz=0V{Azcs_Qufwa5sTt`C0UuDq={bnVW#@oApkI`nv$^bMUd?x6cX?hREQ6u?VX9 zr-jxBBT^H~Po}zB7U0_2I$#)FTv(o+R9+L`peWd#(Km)MQvS#8V33{>J&U8aU7%VQ zy9cn;HIBY_^KV?(zVg3gj%@}2^-w<0f9V0zOTJ-y9UFZHv8Mq|!3 z`{FI<_KmLc{M^hGRH>Q%xh3VjUwCO|b+%7C+xLj#)Px$A%!`nxZy%wE$mT0lEmBMt zvH1Qhq@c#zx4P{kC1oBI!NH>IW|u4uAynaDQgK9I+2YiNU@COHTVsT-UGZy7@*p5>^|V$&FqQ&5 z(_0tyqAqWbpWafjka0GZKKTur{`jt`l>hKVHz`JW#o16BfT#C@Zex_PkC8FbEhGcD z2#KPDE)Yzc*~iwN)PcmB9Kx_ur!H^vwD%)FJsD{!bt&dH-W;l!WSd6<%vRQg#Jg5L z=6Kcz3%ZclqcsWAK9rk@x$MRe`!n(P^Y-L2Am7fmzkA{549FcqwjR_DrTy|GDa0`hKG!9%aXS_37d37r*JU=Rm-12?uW zh0SdqRVdlN`L!b)u$PhbGz0h|>Csu(9z3ir`JXvHR0NF_jT!k1{bv%Rro5U3zzwZK z2(J^%?3Eu0pj1c0@K3XE1Oj5aba)ZDZ-V1-Z}3-i8|PI|UUa^5_K=y41@LE&kWRC3 z>DEpja5aRKnwC~dDB3!xq%X<;5Sx71@Ipe>3$CNoh~-Bg#>A#cgi_|;+pzIZlJbV^ zb%Wn?L!HC*p0Kdia3YS{J>m37!pQ<+(8@yQZwp2o;}K#NFiJOWP_K)%X{uL@X_bGF zNc1*F8=Lx6lM09y0SnEh1|JfoT*yTHx3q#76Y_FR2?aE85$mFnfbJ_aR>p|Frn%_fJ)__^lmp%Cx)y4iHxSDMe?s0xr2lUUg~pKn~cIgls@1)q8Ga-!wDClZ(9rAvvY z3Kv;D%m(AIZ}8-zcJnP?n{h_~CuL1}CMCo9@D?s07{S{f!&=QS5buTTiJ+oR`&%Yk z6eG)i(y82-zYlG&vyO1UVISPr2D%6gc)Mwf6-6X-w7Nd1!Sf6HG9^r-h#?e4_MMh4 z(#e653cW5Gl9;7ya&KzPB=V*~byIJha*&G7!kcxH>+#G>D4Bxp#goE-F*{^I#L(V% zLXj^|-ty+xGt4A=gYtee>G9aX;c~w^5VdmSthE@XFQB*BI$=yg*}3D_*tClVURz8) z7oX}e79H2VhCpss;k6njm6YQU7@WiJ6Esx z;SBG{_>?KLg*zEwYbFyMiQ;yb9ZM5s3d5XKUTZt}nSPSCAfcTQ2M~i{yO@$NyGdEH zgwG;c@5u1OYQh1b>cqJZJ@`f>)SUiNv!AI?bTku#3#!vq#FEO8FwFK>%8L;GSyyR; z^Gh-<+an0oYl&6xS{6H=>e<`RE;8k;pGg&U?T4O~DIj*@sBHvNd2sp4;ftHU!%hOT zAU47#SQAo^uRjj*Y%P1MxaW?l6J^KW@O1Qs{p2nurX-PsC*^HwoHWk!m9_Ag*bY_# zj0*18dPh50O;MOl+r++IExmO6ezTO_A{NP&2xTGG7P~pGXPJdvys5mJ|B*wta6lt^cxVT- z+!;#FzB!ni02ATLK+d~cgg8!#eqj=PI=-SN3g&`92MC>~fPONi5F8 z{y1-Fi@W@}_*Y}K8o$Eg#0qA*noq%fnAe{Axdw?j|9xp;JK%K$@^MK2TriabYfZpZRur;Ft zPl+%BoR>{%cmhnkQy&H}APKaUs?ps@ut=S_g9n$y&w|<#zUtp}V-HD4e@%*tLgCMT zDCVI{5qFA#{@^Vr!6xT_6lvzR-7NXNM*z~?3y4~~uj1=fYI`sd(TsI}_bX)NBVW2a zgHqSm&at3e`3@sRBi4@Jfp1miXi32mH&Ca_U`Uy82;}90zeCdili*Uuz|f|uJQtMZ z5!>8;(Td)QXyk{h0oOlyXls{$apw%g4PkF#~(E%mj+e&uI zM50X{dL|PuttOmVWaHqauGC@d@?X?;Q-NP9gY4dz+qZa|sIK+A^7OorgYGM5 zlpHw&J^`Dqx1^FqjlG;v6F=1xCEm8;M455KLPZw(%_w2KD`Y8+Um7sC`N#fHv)P_+ zJ=9{7b2Z*SQX?@#Nu?eXk!oOm{;)C$Z-CcqBLt%uIduPJOf@*)@y>R(WV&;v&8Wyi zej^*hCep*zP?(xc=!Mh}RTAwva!`I$X`S{pf!`{PePHQgD=upuUF-pxQ%2`ULc}4%P8SaZ(R79Lhf9 zpiPhkuto(?T8^5p28lb&Te;$!q#(x0&qCjY_m|gcLPIJ=*5Ox`Knof+d8zzms@6!hsN*otpu*@rj%tVvQ?hmhyuMH3>u(PtNqm+ES6 zxGUDX`Mvnu^i8Cc{2dJBqcV<2bEZ1ek^TNlPl>1)Lf$|FdD8?vFHJROShF&DDy7iEV#=WGQecw^&E8em1Xm)gf1s&sD z(Dc(!aAC`1$Z(E2wE0~fkL*MZo5&Vef*WkFOrMCgE{g{XyI$fHXOO;WKj&ndp><=G z;ba{vDDqZvMLsolm?+SAv9jH>YKit==<}*Jy`)i`ds>PtM--Nspcok_l%Pq1)l?gA z-eXHb#)K_`17day;1an{g>mORh1v#|Aa~65dSjtSi$YdS!WbjWXM|v<}7qL}21u(b&tC@Y4>oBDtRq>s`q>Zo0Ts z3^GDHa}r1TCd>`wH^(G2ai+6OvKgpE=h&HY!=$V@+Aw@tK<%0}3Y z)({)5BlgmEM^q^u$RI*nSTNS_*}b-#$zi}*=0xPrzN22)Vvd)&--nLyn|=2Pm1SGa z;^~U_u5YpA7^{U~lxqs%?npB_R&_w|AnH=Ql|^@PnhP50ZRY+6=q7L7UCF02Ke_+D z(Pz_>vh%9LCNrm$Vq@_Fx0m=vZ8CG+9P;9(sX8(o(;C?54O%qbQ_yHUpP-FtrOoH-d=T{EJztyb$o4luBipzX;OP%-`6X{~7kLxqTEjj1hM z$ure6ulLLqni`Zo?~eNj0=B+i@W-)2InwX&6FHSUHQ4^Rfw8${5kuXMe$gOD(!4^T zD^~GgI7tP=hY>6Sj&ywpUPy@B`aE7Zdz`1u8^dwUL&KF8ad!_X>`0bVEtl{JtgsB- z$cI=;@5Az&#UX88HoW;h`U4L_+l-1_T&D$!5ab;1=@>X3Bv2Nvj#mq2}+TC%ly(-=$yP+;j+|4UBvk zR9df4PL^LnB~^af3TVXg#)sb>gx)}H1^8UVD3k&nXx|Wx<^cL~1Kt7pvADLO4pEZC34NqGDADqdb22>Q5CAFq-_g$_Z%2zhW;DpzFY{eoLDKp$I9L^?=g_M&Hh ziVG=}Dx1n+qhrk42wNnBX+Scd^QYgog>BD;baZDxXb4;PDl=nt?T=dVVnUVALe|MrhXn+Afobgo57V(Lq^DYcUj%pNyzk zVmf3Tj*7f2q_VcPoc7#a@l{VqfW@?}qZf>in!oT84Yc0GB3su<91HqgGm7d2Bv_H`69gq89gJQ~vb|G~P;(;f`wAxU{j*j>n?`*7bYpK+ zv|83VvD2)(`lqr>wgsC(@hq^7v0e&ZSa8QQ4p#t**1Pm1Kr=lhfOMGN=C(G>2QJD; z4EY$sJv3)Hgm82yrcX0teCqO5LA7p9TQulK3C)`pO{BS>M)-(Y)3>yX9^PT6ZW
NnJG@nY= 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�DF_@LHnp3o8cLXlp}ThG$onV+$9q?DA;i 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 Date: Wed, 5 Apr 2023 05:26:53 -0700 Subject: [PATCH 26/30] Update changelog --- DESCRIPTION | 4 ++-- NEWS | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e1a90da..b366725 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: CancerEvolutionVisualization Title: Publication Quality Phylogenetic Tree Plots -Version: 1.1.0 -Date: 2023-03-30 +Version: 2.0.0 +Date: 2023-04-05 Authors@R: c( person("Paul Boutros", role = "cre", email = "PBoutros@mednet.ucla.edu"), person("Adriana Salcedo", role = "aut"), diff --git a/NEWS b/NEWS index 456a5d5..480f384 100644 --- a/NEWS +++ b/NEWS @@ -1,8 +1,9 @@ -CancerEvolutionVisualization 1.1.0 2023-03-29 (Helena Winata, Dan Knight) +CancerEvolutionVisualization 2.0.0 2023-04-05 (Helena Winata, Dan Knight) ADDED * CEV report template and public function -* Generic functions to generate accompanying heatmaps +* Generic functions to generate accompanying heatmaps for summarizing + CCF and clustering datas -------------------------------------------------------------------------- From 4218d74e217eff84001ef732388f4bc274c56870 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Wed, 5 Apr 2023 05:41:14 -0700 Subject: [PATCH 27/30] Fix report template filepath --- R/CEV.report.R | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/R/CEV.report.R b/R/CEV.report.R index bed92ee..4da3a0b 100644 --- a/R/CEV.report.R +++ b/R/CEV.report.R @@ -23,8 +23,13 @@ CEV.report <- function( heatmap.data = inputs$heatmap.input ); + template.path <- system.file( + 'CEV.report.Rmd', + package = 'CancerEvolutionVisualization' + ); + rmarkdown::render( - 'inst/CEV.report.Rmd', + template.path, output_file = output.filename, params = report.params ); From d9d6b142861e04d34091318d7a7c2a12b08cd7d1 Mon Sep 17 00:00:00 2001 From: Dan Knight Date: Thu, 6 Apr 2023 15:09:43 -0700 Subject: [PATCH 28/30] Fix test data --- tests/testthat/data/multisample.test.data.Rda | Bin 895 -> 896 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/testthat/data/multisample.test.data.Rda b/tests/testthat/data/multisample.test.data.Rda index 829b88a4daa532597b267b45ea3303a494383b78..db939f887cc03855e9a4c029f15a1ae493041a9e 100644 GIT binary patch literal 896 zcmb2|=3oE==C@I{9x{$1^KO4_l9OQKT;iRpVEOLcLOM(jFL2A;Sy|}M zw9Ip|Rq3Uw(p4g1VxF^1*clyJCVKGv+}+W;Htk#4zQ)?wj-0=?%xEp#_x;)1+3)vO z8oz$!*XR2@__C&Ai}tTMEd`T>x}G%ue!N}VDQn3Df!ZX8Z`Wsg_lDhFZze9a^-I#( z=|NT7Zmr^a81r@}r}>NDDZ7=9M!zyUe%@fgo0-bj&!fn(_s_h3nN{$nd*4g`Kel_qT{S0sL%#cG z;oJLf$S?cL{lBDEP(L>Q4gXW~XZ(LM{;rk$ad7_1xPpBbeox?^vwFjA^~cA*+={=w z;l79M@pHjvvQNp+Wq&?fGwwUfKX=U)-^+Kd z`@ZM%t$+UARFW@Lf4=JMEd8qaZXTg4_L!8uPuyj8BXtBys(IKNSFFty!AlG zkJ*_awLhATL~F$6)os#PuJnw#`}6rr3k)QDzB&D~u(FDoem61Wjf`OV-?K}@wua81 S&z*bZVSJw3F3GKW3=9CYlG26% literal 895 zcmb2|=3oE==C@I{9x{$1a^>G8k!Iw{;oCj?Dya8;10YHa>(xILX| zndfAy(o0pPt3<-YJZG7(Gdi+N^x*kvoRqwJ=H9va3HQ?i4*lFRqqT6~_h)Zszu#MF z{QA{7zq6^r#hi*Q+H7+fAM$x>w8`C>zb{rONV%2KP{S>ye(%1J?C`%uwHw1cw`uL0 zGKu?XStQ5vtn)rQ9PKr3i)!8r+HP6b#klmm%+5NiqgyvE=-*XK1G4sdm^ z*>HsG_|FB8)6W6r)$qtm2J|sLl?-TP6qO9x!Qp|Eq79Rk7%+ zSKI!G{qmnKe_DUw?I*rRAM3ufrElF|?zP{%Ohb8xVU(|lFUUjqoRa2(R-8Z|_Ua>;%_3C5E|0W!tf3fg{Vg2mD+MJlD(Z54~ zUbWro{?Cl}&x7~X8Oy)EZu{DL?}`6U$Jp$>E9dRjy089mvCRg#E$24wlfGE-#p09O zV^;ep_m1t^H}=PG-rN1@!ef)T55{xZPsp#G{{QU!mFK3c`EGmn{|^42O>)mg-&kM# zbL5-;xAx5chwNkHk9c1f|Ly#<_b0P`RDCvE-G}s5;&b%B*gau>uJwlR#1GEB->w_J zF%LSw@w3S1S)bgWAN-ltCH`~qzYN(ouICG*(yL_t_?%b1B3NtpaNpewnOlAD;xGS7 z`oHRVl1|(a*Pk=bRX^b`^}WvgoN=xF2lqWD@g@HyKS_Lczkaa9{u5iBiT{oLU;ezF z7ogwK{A_)8Mc+gI1=AF~LUHN&>inoidAFTXn z{dUi^`}WaKl;16M+Zp%K_5Edzm0VM&|2cL!rPR^$++EGqJ10+O?rAu8eO9kmg3F(? z*H78~ah%Cv$L(*IcXDONPmk3 Date: Tue, 11 Apr 2023 12:18:11 -0700 Subject: [PATCH 29/30] option to save heatmaps by specifying filename --- R/heatmap.R | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/R/heatmap.R b/R/heatmap.R index a333929..502a567 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -7,6 +7,7 @@ plot.ccf.hm <- function( colour.scheme = NULL, xaxis.lab = NULL, xlab.label = 'Mutations', + filename = NULL, ... ) { @@ -23,7 +24,7 @@ plot.ccf.hm <- function( } hm <- BoutrosLab.plotting.general::create.heatmap( - filename = NULL, + filename = filename, x = CCF.df, force.clustering = TRUE, cluster.dimensions = cluster.dim, @@ -60,6 +61,7 @@ plot.cluster.hm <- function( plt.width = 11, colour.scheme = NULL, xaxis.col = NULL, + filename = NULL, ... ) { @@ -125,8 +127,8 @@ plot.cluster.hm <- function( label.cex = 0.6 ); - return(BoutrosLab.plotting.general::create.multiplot( - filename = NULL, + plt <- BoutrosLab.plotting.general::create.multiplot( + filename = filename, plot.objects = list(cov, hm), plot.layout = c(1, 2), panel.heights = c(1, 0.05), @@ -147,12 +149,14 @@ plot.cluster.hm <- function( )), height = plt.height, width = plt.width - )); + ); + return(plt); } plot.summary.ccf.hm <- function( mutation.df, - CCF.threshold = 0 + CCF.threshold = 0, + filename = NULL ) { median.ccf <- aggregate( @@ -251,8 +255,8 @@ plot.summary.ccf.hm <- function( label.cex = 0.6 ); - return(BoutrosLab.plotting.general::create.multiplot( - filename = NULL, + plt <- BoutrosLab.plotting.general::create.multiplot( + filename = filename, plot.objects = list(hm, sample.bar, clone.bar), plot.layout = c(2, 2), layout.skip = c(FALSE, FALSE, FALSE, TRUE), @@ -283,7 +287,8 @@ plot.summary.ccf.hm <- function( )), height = 6, width = 11 - )); + ) + return(plt); } default.heatmap.colours <- function() { From ae9b1b3b46f1dea5a9991d80dcac391ea3bd8ba4 Mon Sep 17 00:00:00 2001 From: whelena Date: Tue, 11 Apr 2023 12:22:30 -0700 Subject: [PATCH 30/30] rename CCF.df to CCF.arr for clarity --- R/heatmap.R | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/R/heatmap.R b/R/heatmap.R index 502a567..a07e723 100644 --- a/R/heatmap.R +++ b/R/heatmap.R @@ -1,5 +1,5 @@ plot.ccf.hm <- function( - CCF.df, + CCF.arr, CCF.threshold = NULL, cluster.dim = 'both', cluster.method = 'complete', @@ -12,10 +12,10 @@ plot.ccf.hm <- function( ) { if (!is.null(CCF.threshold)) { - CCF.df[CCF.df <= CCF.threshold] <- 0; + CCF.arr[CCF.arr <= CCF.threshold] <- 0; } col.labels <- seq(0, 1, .2); - sample.names <- colnames(CCF.df); + sample.names <- colnames(CCF.arr); heatmap.colours <- if (!is.null(colour.scheme)) { colour.scheme; @@ -25,7 +25,7 @@ plot.ccf.hm <- function( hm <- BoutrosLab.plotting.general::create.heatmap( filename = filename, - x = CCF.df, + x = CCF.arr, force.clustering = TRUE, cluster.dimensions = cluster.dim, clustering.method = cluster.method, @@ -89,7 +89,7 @@ plot.cluster.hm <- function( } hm <- plot.ccf.hm( - CCF.df = arr, + CCF.arr = arr, cluster.dim = 'none', colour.scheme = heatmap.colours, ...