diff --git a/.github/workflows/R-CMD-check.yaml b/.github/workflows/R-CMD-check.yaml index c29d88f..d026c43 100644 --- a/.github/workflows/R-CMD-check.yaml +++ b/.github/workflows/R-CMD-check.yaml @@ -57,7 +57,7 @@ jobs: - name: Cache R packages if: runner.os != 'Windows' && matrix.config.image == null - uses: actions/cache@v1 + uses: actions/cache@v4 with: path: ${{ env.R_LIBS_USER }} key: ${{ runner.os }}-bioc-${{ matrix.config.bioc }}-${{ hashFiles('depends.Rds') }} diff --git a/NAMESPACE b/NAMESPACE index 584b4a3..2b4a2ed 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -53,6 +53,7 @@ importFrom(clusterProfiler,enrichGO) importFrom(ggforce,geom_sina) importFrom(ggplot2,aes) importFrom(ggplot2,coord_cartesian) +importFrom(ggplot2,element_text) importFrom(ggplot2,geom_boxplot) importFrom(ggplot2,geom_hline) importFrom(ggplot2,geom_point) @@ -64,6 +65,7 @@ importFrom(ggplot2,ggplot) importFrom(ggplot2,ggtitle) importFrom(ggplot2,labs) importFrom(ggplot2,position_jitter) +importFrom(ggplot2,rel) importFrom(ggplot2,scale_color_discrete) importFrom(ggplot2,scale_color_manual) importFrom(ggplot2,scale_colour_manual) @@ -72,6 +74,7 @@ importFrom(ggplot2,scale_x_discrete) importFrom(ggplot2,scale_y_continuous) importFrom(ggplot2,scale_y_log10) importFrom(ggplot2,stat_summary) +importFrom(ggplot2,theme) importFrom(ggplot2,theme_bw) importFrom(ggplot2,theme_classic) importFrom(ggplot2,xlab) diff --git a/R/color_mgmt.R b/R/color_mgmt.R index 7cdbdaf..f273553 100644 --- a/R/color_mgmt.R +++ b/R/color_mgmt.R @@ -189,19 +189,39 @@ de_table_painter <- function(res_de, ) { ## Checks on the input parameters - res_de <- res_de[order(res_de$padj), ] - my_dt <- DT::datatable( - as.data.frame(res_de), - escape = FALSE, - options = list( - scrollX = TRUE, - scrollY = "400px", - pageLength = 25, - columnDefs = list( - list(className = "dt-center", targets = "_all") + if (is(res_de, "DESeqResults")) { + res_de <- res_de[order(res_de$padj), ] + my_dt <- DT::datatable( + as.data.frame(res_de), + escape = FALSE, + options = list( + scrollX = TRUE, + scrollY = "400px", + pageLength = 25, + columnDefs = list( + list(className = "dt-center", targets = "_all") + ) ) ) - ) + } else if ((is(res_de, "DGELRT")) | (is(res_de, "DGEEXACT"))) { + # TODO: any conversion + # TODO: possibly also re-change the default values of the column names where to find things in + message("Working with edgeR objects, remove me later") + } else { + res_de <- res_de[order(res_de$padj), ] + my_dt <- DT::datatable( + as.data.frame(res_de), + escape = FALSE, + options = list( + scrollX = TRUE, + scrollY = "400px", + pageLength = 25, + columnDefs = list( + list(className = "dt-center", targets = "_all") + ) + ) + ) + } if (!is.null(rounding_digits)) { my_dt <- formatRound( diff --git a/R/de2df.R b/R/de2df.R index 0c1d338..04b7a57 100644 --- a/R/de2df.R +++ b/R/de2df.R @@ -39,3 +39,14 @@ deresult_to_df <- function(res_de, FDR = NULL) { } res } + +.deresult_to_df.DESeq <- function() { + +} + +.deresult_to_df.edgeR <- function() { + +} + + + diff --git a/R/expression_summaries.R b/R/expression_summaries.R index d8573e1..6d7ebcc 100644 --- a/R/expression_summaries.R +++ b/R/expression_summaries.R @@ -71,6 +71,3 @@ pair_corr <- function(df, log = TRUE, method = "pearson", use_subset = TRUE) { xlim = c(rangeMin, rangeMax), ylim = c(rangeMin, rangeMax)) } - - -## couple more TODO diff --git a/R/gene_annotation.R b/R/gene_annotation.R index 019b99d..e76aad6 100644 --- a/R/gene_annotation.R +++ b/R/gene_annotation.R @@ -38,6 +38,15 @@ get_annotation_orgdb <- function(de_container, orgdb_package, id_type, key_for_genenames = "SYMBOL") { + + ## TODO: see if we need any checks + if (is(de_container, "DESeqDataSet")) { + ## TODO + # all we need is rownames + } + + + if (is.null(orgdb_package)) stop("Select an annotation package to generate the corresponding annotation") diff --git a/R/gene_plot.R b/R/gene_plot.R index 1144291..e5f1778 100644 --- a/R/gene_plot.R +++ b/R/gene_plot.R @@ -35,6 +35,7 @@ #' @param return_data Logical, whether the function should just return the #' data.frame of expression values and covariates for custom plotting. Defaults #' to FALSE. +#' @param color_by TODO - if we keep this in for the final version? #' #' @return A `ggplot` object #' @export @@ -42,7 +43,7 @@ #' @importFrom stats median #' @importFrom ggplot2 ggplot aes geom_point geom_boxplot geom_violin #' geom_text position_jitter scale_color_discrete scale_x_discrete scale_y_log10 -#' scale_y_continuous stat_summary theme_bw labs +#' scale_y_continuous stat_summary theme theme_bw labs element_text rel #' @importFrom ggrepel geom_text_repel #' @importFrom ggforce geom_sina #' @importFrom rlang .data @@ -80,41 +81,78 @@ #' annotation_obj = anno_df #' ) gene_plot <- function(de_container, - gene, - intgroup = NULL, - assay = "counts", - annotation_obj = NULL, - normalized = TRUE, - transform = TRUE, - labels_display = TRUE, - labels_repel = TRUE, - plot_type = "auto", - return_data = FALSE) { - if (!is(de_container, "DESeqDataSet")) { - stop("The provided `de_container` is not a DESeqDataSet object, please check your input parameters.") + gene, + intgroup = NULL, + assay = "counts", + annotation_obj = NULL, + normalized = TRUE, + transform = TRUE, + labels_display = TRUE, + labels_repel = TRUE, + plot_type = "auto", + return_data = FALSE, + color_by = NULL) { + if (!is(de_container, "DESeqDataSet") && !is(de_container, "EList") && !is(de_container, "DGEList")) { + stop("The provided `de_container` must be a DESeqDataSet, EList, or DGEList object. Please check your input parameters.") } if (is.null(intgroup)) { - # gently fall back to the first colData element if it is there - if (length(names(colData(de_container))) > 0) { - intgroup <- names(colData(de_container))[1] - message("Defaulting to '", intgroup, "' as the `intgroup` parameter...") - } else { - stop("No colData has been provided, therefore `intgroup` cannot be selected properly") + if (is(de_container, "DESeqDataSet")) { + if (length(names(colData(de_container))) > 0) { + intgroup <- names(colData(de_container))[1] + message("Defaulting to '", intgroup, "' as the `intgroup` parameter...") + } else { + stop("No colData has been provided, therefore `intgroup` cannot be selected properly.") + } + } else if (is(de_container, "EList") || is(de_container, "DGEList")) { + if (length(names(de_container$samples)) > 0) { + intgroup <- names(de_container$samples)[1] + message("Defaulting to '", intgroup, "' as the `intgroup` parameter...") + } else { + stop("No sample information has been provided, therefore `intgroup` cannot be selected properly.") + } } } + if (is.null(color_by)) { + color_by <- intgroup + } + plot_type <- match.arg( plot_type, c("auto", "jitteronly", "boxplot", "violin", "sina") ) - if (!(all(intgroup %in% colnames(colData(de_container))))) { - stop( - "`intgroup` not found in the colData slot of the de_container object", - "\nPlease specify one of the following: \n", - paste0(colnames(colData(de_container)), collapse = ", ") - ) + if (is(de_container, "DESeqDataSet")) { + if (!(all(intgroup %in% colnames(colData(de_container))))) { + stop( + "`intgroup` not found in the colData slot of the de_container object", + "\nPlease specify one of the following: \n", + paste0(colnames(colData(de_container)), collapse = ", ") + ) + } + if (!(color_by %in% colnames(colData(de_container)))) { + stop( + "`color_by` not found in the colData slot of the de_container object", + "\nPlease specify one of the following: \n", + paste0(colnames(colData(de_container)), collapse = ", ") + ) + } + } else if (is(de_container, "EList") || is(de_container, "DGEList")) { + if (!(all(intgroup %in% colnames(de_container$samples)))) { + stop( + "`intgroup` not found in the samples slot of the de_container object", + "\nPlease specify one of the following: \n", + paste0(colnames(de_container$samples), collapse = ", ") + ) + } + if (!(color_by %in% colnames(de_container$samples))) { + stop( + "`color_by` not found in the samples slot of the de_container object", + "\nPlease specify one of the following: \n", + paste0(colnames(de_container$samples), collapse = ", ") + ) + } } df <- get_expr_values( @@ -136,28 +174,23 @@ gene_plot <- function(de_container, df$plotby <- interaction(onlyfactors) min_by_groups <- min(table(df$plotby)) - # depending on this, use boxplots/nothing/violins/sina if (return_data) { return(df) } - p <- ggplot(df, aes(x = .data$plotby, y = .data$exp_value, col = .data$plotby)) + + p <- ggplot(df, aes(x = .data$plotby, y = .data$exp_value, col = .data[[color_by]])) + scale_x_discrete(name = "") + scale_color_discrete(name = "Experimental\ngroup") + theme_bw() - # for connected handling of jittered points AND labels jit_pos <- position_jitter(width = 0.2, height = 0, seed = 42) - # somewhat following the recommendations here - # https://www.embopress.org/doi/full/10.15252/embj.201694659 if (plot_type == "jitteronly" || (plot_type == "auto" & min_by_groups <= 3)) { p <- p + geom_point(aes(x = .data$plotby, y = .data$exp_value), - position = jit_pos + position = jit_pos ) - # do nothing - or add a line for the median? } else if (plot_type == "boxplot" || (plot_type == "auto" & (min_by_groups > 3 & min_by_groups < 10))) { p <- p + geom_boxplot(outlier.shape = NA) + @@ -179,17 +212,16 @@ gene_plot <- function(de_container, ) } - # handling the labels if (labels_display) { if (labels_repel) { p <- p + ggrepel::geom_text_repel(aes(label = .data$sample_id), - min.segment.length = 0, - position = jit_pos + min.segment.length = 0, + position = jit_pos ) } else { p <- p + geom_text(aes(label = .data$sample_id), - hjust = -0.1, vjust = 0.1, - position = jit_pos + hjust = -0.1, vjust = 0.1, + position = jit_pos ) } } @@ -204,24 +236,38 @@ gene_plot <- function(de_container, assay } - # handling y axis transformation if (transform) { p <- p + scale_y_log10(name = paste0(y_label, " (log10 scale)")) } else { p <- p + scale_y_continuous(name = y_label) } - # handling the displayed names and ids if (!is.null(annotation_obj)) { p <- p + labs(title = paste0(genesymbol, " - ", gene)) } else { p <- p + labs(title = paste0(gene)) } + p <- p + stat_summary( + fun = mean, geom = "line", aes(group = 1), + color = "grey80", linewidth = 0.8 + ) + + theme_bw() + + theme( + axis.title.x = element_text(size = rel(1.6)), + axis.title.y = element_text(size = rel(1.6)), + axis.text.x = element_text(size = rel(1.6)), + axis.text.y = element_text(size = rel(1.6)), + strip.text.x = element_text(size = rel(1.6)), + strip.text.y = element_text(size = rel(1.6)), + legend.text = element_text(size = rel(1.4)), + legend.title = element_text(size = rel(1.4)), + plot.title = element_text(size = rel(1.6)) + ) + return(p) } - #' Get expression values #' #' Extract expression values, with the possibility to select other assay slots @@ -245,9 +291,6 @@ gene_plot <- function(de_container, #' #' @export #' -#' @importFrom DESeq2 counts estimateSizeFactors sizeFactors normalizationFactors -#' @importFrom SummarizedExperiment colData assays -#' #' @examples #' library("macrophage") #' library("DESeq2") @@ -273,6 +316,43 @@ get_expr_values <- function(de_container, intgroup, assay = "counts", normalized = TRUE) { + + # TODO: generic checks could go here: + # e.g. on gene, intgroup, assay (they have to be strings) + + # + if (is(de_container, "DESeqDataSet")) { + exp_df <- .get_expr_values.DESeqDataSet(de_container = de_container, + gene = gene, + intgroup = intgroup, + assay = assay, + normalized = normalized) + } else if (is(de_container, "DGEList")) { + # TODO + ## add the behavior here (can be as schematic as the call above, the real + ## behavior is defined in the function itself - below) + exp_df <- .get_expr_values.DGEList() + } else { + # TODO + ## return an error to say the object has to be one of the supported types + stop("The object provided ... TODO") + } + + return(exp_df) +} + + +#' get_expr_values, from a DESeqDataSet object +#' +#' @importFrom DESeq2 counts estimateSizeFactors sizeFactors normalizationFactors +#' @importFrom SummarizedExperiment colData assays +#' +#' @keywords internal +.get_expr_values.DESeqDataSet <- function(de_container, + gene, + intgroup, + assay, + normalized) { if (!(assay %in% names(assays(de_container)))) { stop( "Please specify a name of one of the existing assays: \n", @@ -298,3 +378,57 @@ get_expr_values <- function(de_container, return(exp_df) } + +#' get_expr_values, from an Elist object +#' +#' @importFrom DESeq2 counts estimateSizeFactors sizeFactors normalizationFactors +#' @importFrom SummarizedExperiment colData assays +#' +#' @keywords internal +.get_expr_values.EList <- function(## define the parameters "cleverly" + ) { + # TODO + ## add the behavior here +} + +#' get_expr_values, from a DGEList object +#' +#' @importFrom DESeq2 counts estimateSizeFactors sizeFactors normalizationFactors +#' @importFrom SummarizedExperiment colData assays +#' +#' @keywords internal +.get_expr_values.DGEList <- function(## define the parameters "cleverly" + ) { + # TODO + ## add the behavior here +} + + +## This is the ongoing implementation: + +### # Function to get expression values based on the container type +### get_expr_values <- function(de_container, gene, intgroup, assay, normalized) { +### if (is(de_container, "DESeqDataSet")) { +### if (assay == "counts") { +### values <- counts(de_container, normalized = normalized) +### } else { +### values <- assay(de_container, assay) +### } +### intgroup_values <- colData(de_container)[, intgroup, drop = FALSE] +### color_by_values <- colData(de_container)[, color_by, drop = FALSE] +### } else if (is(de_container, "EList")) { +### values <- de_container$E[gene, , drop = FALSE] +### intgroup_values <- de_container$samples[, intgroup, drop = FALSE] +### color_by_values <- de_container$samples[, color_by, drop = FALSE] +### } else if (is(de_container, "DGEList")) { +### values <- de_container$counts[gene, , drop = FALSE] +### if (normalized) { +### values <- cpm(de_container, normalized.lib.sizes = TRUE)[gene, , drop = FALSE] +### } +### intgroup_values <- de_container$samples[, intgroup, drop = FALSE] +### color_by_values <- de_container$samples[, color_by, drop = FALSE] +### } +### df <- data.frame(exp_value = as.numeric(values), intgroup_values, color_by_values) +### rownames(df) <- colnames(values) +### return(df) +### } diff --git a/man/dot-get_expr_values.DESeqDataSet.Rd b/man/dot-get_expr_values.DESeqDataSet.Rd new file mode 100644 index 0000000..c10edb1 --- /dev/null +++ b/man/dot-get_expr_values.DESeqDataSet.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gene_plot.R +\name{.get_expr_values.DESeqDataSet} +\alias{.get_expr_values.DESeqDataSet} +\title{get_expr_values, from a DESeqDataSet object} +\usage{ +.get_expr_values.DESeqDataSet(de_container, gene, intgroup, assay, normalized) +} +\description{ +get_expr_values, from a DESeqDataSet object +} +\keyword{internal} diff --git a/man/dot-get_expr_values.DGEList.Rd b/man/dot-get_expr_values.DGEList.Rd new file mode 100644 index 0000000..ed3723f --- /dev/null +++ b/man/dot-get_expr_values.DGEList.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gene_plot.R +\name{.get_expr_values.DGEList} +\alias{.get_expr_values.DGEList} +\title{get_expr_values, from a DGEList object} +\usage{ +.get_expr_values.DGEList() +} +\description{ +get_expr_values, from a DGEList object +} +\keyword{internal} diff --git a/man/dot-get_expr_values.EList.Rd b/man/dot-get_expr_values.EList.Rd new file mode 100644 index 0000000..403bf7f --- /dev/null +++ b/man/dot-get_expr_values.EList.Rd @@ -0,0 +1,12 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/gene_plot.R +\name{.get_expr_values.EList} +\alias{.get_expr_values.EList} +\title{get_expr_values, from an Elist object} +\usage{ +.get_expr_values.EList() +} +\description{ +get_expr_values, from an Elist object +} +\keyword{internal} diff --git a/man/gene_plot.Rd b/man/gene_plot.Rd index 82130e8..f1ab6dc 100644 --- a/man/gene_plot.Rd +++ b/man/gene_plot.Rd @@ -15,7 +15,8 @@ gene_plot( labels_display = TRUE, labels_repel = TRUE, plot_type = "auto", - return_data = FALSE + return_data = FALSE, + color_by = NULL ) } \arguments{ @@ -58,6 +59,8 @@ samples in the smallest group defined via \code{intgroup}} \item{return_data}{Logical, whether the function should just return the data.frame of expression values and covariates for custom plotting. Defaults to FALSE.} + +\item{color_by}{TODO - if we keep this in for the final version?} } \value{ A \code{ggplot} object