Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add plot method for eigenvalues #160

Merged
merged 13 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export(plotRDA)
export(plotRowGraph)
export(plotRowPrevalence)
export(plotRowTile)
export(plotScree)
export(plotSeries)
export(plotTaxaPrevalence)
exportMethods("colTreeData<-")
Expand All @@ -37,6 +38,7 @@ exportMethods(plotRowGraph)
exportMethods(plotRowPrevalence)
exportMethods(plotRowTile)
exportMethods(plotRowTree)
exportMethods(plotScree)
exportMethods(plotSeries)
exportMethods(plotTaxaPrevalence)
exportMethods(rowTreeData)
Expand Down
169 changes: 169 additions & 0 deletions R/plotScree.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#' Plot Scree Plot or Eigenvalues
#'
#' \code{plotScree} creates a scree plot or eigenvalues plot starting from a
#' TreeSummarizedExperiment object or a vector of eigenvalues. This visualization
#' shows how the eigenvalues decrease across components.
#'
#' @param x a
#' \code{\link[TreeSummarizedExperiment:TreeSummarizedExperiment-constructor]{TreeSummarizedExperiment}}
#' or a vector of eigenvalues.
#'
#' @param dimred \code{Character scalar} or \code{integer scalar}. Determines
#' the reduced dimension to plot. This is used when x is a TreeSummarizedExperiment
#' to extract the eigenvalues from \code{reducedDim(x, dimred)}.
#'
#' @param cumulative \code{Logical scalar}. Whether to show cumulative explained
#' variance. (Default: \code{FALSE}).
#'
#' @param names \code{Character vector}. Optional names for the components
#' that will be displayed on the x-axis. If not provided, the components
#' are labeled sequentially as 1, 2, 3, etc.
#'
#' @param ... additional parameters for plotting
#' \describe{
#' \item{\code{show.barplot}}{Logical scalar. Whether to show a barplot.
#' (Default: \code{TRUE}).}
#' \item{\code{show.points}}{Logical scalar. Whether to show points.
#' (Default: \code{TRUE}).}
#' \item{\code{show.line}}{Logical scalar. Whether to show a line connecting
#' points. (Default: \code{TRUE}).}
#' \item{\code{show.labels}}{Logical scalar. Whether to show labels for each
#' point. (Default: \code{FALSE}).}
#' }
#'
#' @details
#' \code{plotScree} creates a scree plot or eigenvalues plot, which is useful
#' for visualizing the relative importance of components in dimensionality
#' reduction techniques like PCA, RDA, or CCA. When the input is a
#' TreeSummarizedExperiment, the function extracts eigenvalues from the specified
#' reduced dimension slot. When the input is a vector, it directly uses these
#' values as eigenvalues.
#'
#' The plot can include a combination of barplot, points, connecting lines,
#' and labels, which can be controlled using the \code{show.*} parameters.
#'
#' An option to show cumulative explained variance is also available by setting
#' \code{cumulative = TRUE}.
#'
#' @return
#' A \code{ggplot2} object
#'
#' @name plotScree
#'
#' @examples
#' # Load necessary libraries
#' library(ggplot2)
#'
#' # Load dataset
#' library(miaViz)
#' data("enterotype", package = "mia")
#' tse <- enterotype
#'
#' # Run RDA and store results into TreeSE
#' tse <- addRDA(
#' tse,
#' formula = assay ~ ClinicalStatus + Gender + Age,
#' FUN = getDissimilarity,
#' distance = "bray",
#' na.action = na.exclude
#' )
#'
#' # Plot scree plot
#' plotScree(tse, "RDA")
#'
NULL

#' @rdname plotScree
#' @export
setGeneric("plotScree", signature = c("x"),
function(x, ...) standardGeneric("plotScree"))

#' @rdname plotScree
#' @export
setMethod("plotScree", signature = c(x = "SingleCellExperiment"),
function(x, dimred, cumulative = FALSE, ...) {
# Check if dimred exists
if (!dimred %in% reducedDimNames(x)) {
stop("'dimred' must specify a valid reducedDim.", call. = FALSE)
TuomasBorman marked this conversation as resolved.
Show resolved Hide resolved
}
# Get reducedDim
reduced_dim <- reducedDim(x, dimred)

# Extract eigenvalues
# Check if data is available
ind <- names(attributes(reduced_dim)) %in% c("eig", "varExplained")
if( any(ind) ){
# Add explained variance
eig <- attributes(reduced_dim)[ind][[1]]
} else{
stop("No eigenvalues found in the specified reducedDim.",
call. = FALSE)
}
Daenarys8 marked this conversation as resolved.
Show resolved Hide resolved

# Call the vector method
plotScree(as.numeric(eig), names(eig), cumulative = cumulative, ...)
Daenarys8 marked this conversation as resolved.
Show resolved Hide resolved
}
)

#' @rdname plotScree
#' @export
setMethod("plotScree", signature = c(x = "vector"),
function(x, names = NULL, cumulative = FALSE, ...) {
# Ensure 'x' is numeric
Daenarys8 marked this conversation as resolved.
Show resolved Hide resolved
if (!is.numeric(x)) {
stop("'x' must be a numeric vector.", call. = FALSE)
}
plot_data <- .prepare_data(x, names, cumulative)
# plot vector
.scree_plotter(plot_data, cumulative = cumulative, ...)
}
)

.prepare_data <- function(x, names = NULL, cumulative = FALSE) {
df <- data.frame(
Component = if (!is.null(names)) names else seq_along(x),
Eigenvalue = x
)

Daenarys8 marked this conversation as resolved.
Show resolved Hide resolved
# Calculate cumulative proportion if needed
if (cumulative) {
df$CumulativeProportion <- cumsum(df$Eigenvalue) / sum(df$Eigenvalue)
Daenarys8 marked this conversation as resolved.
Show resolved Hide resolved
}

return(df)
}

.scree_plotter <- function(df, show.barplot = TRUE,
show.points = TRUE,
show.line = TRUE, show.labels = FALSE,
cumulative = FALSE, ...) {
# Create base plot
p <- ggplot(df, aes(x = Component, y = if (cumulative)
CumulativeProportion else Eigenvalue))

# Add layers based on user preferences
if (show.barplot) {
p <- p + geom_col(fill = "lightblue", color = "black")
}
if (show.points) {
p <- p + geom_point(size = 3)
}
if (show.line) {
p <- p + geom_line()
}
if (show.labels) {
p <- p + geom_text(aes(label = round(if (cumulative)
CumulativeProportion else Eigenvalue, 2)),
vjust = -0.5)
}

# Customize appearance
p <- p + theme_minimal() +
labs(x = "Component",
Daenarys8 marked this conversation as resolved.
Show resolved Hide resolved
y = if (cumulative) "Cumulative Proportion of Variance"
else "Eigenvalue",
title = if (cumulative) "Cumulative Explained Variance"
else "Scree Plot")
Daenarys8 marked this conversation as resolved.
Show resolved Hide resolved

return(p)
}
86 changes: 86 additions & 0 deletions man/plotScree.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions tests/testthat/test-plotScree.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
test_that("plot Eigenvalues", {
data("enterotype", package = "mia")
tse <- enterotype

tse <- addRDA(
tse,
formula = assay ~ ClinicalStatus + Gender + Age,
FUN = getDissimilarity,
distance = "bray",
na.action = na.exclude
)

# Define some eigenvalues for vector-based tests
eigenvalues <- sort(runif(10), decreasing = TRUE)

# plotScree handles non-numeric eigenvalues in vector
expect_error(plotScree(c("a", "b", "c")),
"'x' must be a numeric vector.")

# missing eigenvalues in SingleCellExperiment
sce <- SingleCellExperiment(assays = list(counts = matrix(rpois(1000, 5),
ncol = 10)))

# Add reducedDim without eigenvalues
reducedDim(sce, "PCA") <- matrix(rnorm(100), ncol = 10)

expect_error(plotScree(sce, "PCA"),
"No eigenvalues found in the specified reducedDim.")

# invalid dimred input in SingleCellExperiment
expect_error(plotScree(tse, "invalid_dimred"),
"'dimred' must specify a valid reducedDim.")

p <- plotScree(eigenvalues)

# Check if a ggplot object is returned
expect_s3_class(p, "ggplot")


p <- plotScree(tse, "RDA")

# Check if a ggplot object is returned
expect_s3_class(p, "ggplot")
})
Loading