From c8a62b1fda47d5b03506dc2e49f658a8fc208433 Mon Sep 17 00:00:00 2001 From: LTLA Date: Thu, 8 Jan 2026 13:09:16 +1100 Subject: [PATCH] Added plotXY to re-use scater's aesthetics for arbitrary x/y plots. This reduces the hassle of re-assembling the ggplot2 calls manually. --- NAMESPACE | 1 + R/plotXY.R | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++ man/plotXY.Rd | 108 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 249 insertions(+) create mode 100644 R/plotXY.R create mode 100644 man/plotXY.Rd diff --git a/NAMESPACE b/NAMESPACE index fc2b010..255597b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -67,6 +67,7 @@ export(plotRowData) export(plotScater) export(plotTSNE) export(plotUMAP) +export(plotXY) export(projectReducedDim) export(quickPerCellQC) export(readSparseCounts) diff --git a/R/plotXY.R b/R/plotXY.R new file mode 100644 index 0000000..e0d327d --- /dev/null +++ b/R/plotXY.R @@ -0,0 +1,140 @@ +#' Plot arbitrary x- and y-coordinates +#' +#' Reuse \pkg{scater}'s plotting machinery for arbitrary x- and y-coordinates. +#' This allows users to maintain the same aesthetic style when creating plots outside of a \link[SingleCellExperiment]{SingleCellExperiment}. +#' +#' @param x Vector containing the x-coordinates. +#' Alternatively, a data.frame with a single column. +#' Alternatively \code{NULL}, in which case all points are assumed to have the same categorical x-coordinate. +#' @param y Vector containing the y-coordinates, of same length as \code{x}. +#' Alternatively, a data.frame with a single column. +#' @param colour_by Vector containing a variable to colour by, of the same length as \code{x}. +#' Alternatively, a data.frame with a single column. +#' @param shape_by Vector containing a variable to shape by, of the same length as \code{x}. +#' Alternatively, a data.frame with a single column. +#' @param size_by Vector containing a variable to shape by, of the same length as \code{x}. +#' Alternatively, a data.frame with a single column. +#' @param other_fields A data frame of additional columns to add to the data.frame passed to \pkg{ggplot2}. +#' This should have number of rows equal to the length of \code{y}. +#' Alternatively, a named list of vectors of length equal to that of \code{y}. +#' @inheritParams plotColData +#' +#' @return A \link{ggplot} object. +#' +#' @author Aaron Lun +#' +#' @examples +#' x <- runif(100) +#' y <- rnorm(100) +#' +#' # Manually add labels. +#' plotXY(x, y) + ggplot2::xlab("X") + ggplot2::ylab("Y") +#' +#' # Insert labels via data frames. +#' plotXY(data.frame(foo=x), data.frame(bar=y)) +#' +#' col <- sample(LETTERS, length(x), replace=TRUE) +#' plotXY(x, y, colour_by=col) + guides(color=guide_legend("stuff")) +#' +#' shape <- sample(LETTERS[1:5], length(x), replace=TRUE) +#' plotXY(x, y, shape=data.frame(FOO=shape)) +#' +#' size <- runif(length(x)) +#' plotXY(x, y, size=data.frame(BAR=size)) +#' +#' plotXY(x, y, other_fields=data.frame(stuff = rbinom(100, 1, 0.5) > 0)) + +#' ggplot2::facet_grid(~stuff) +#' +#' @export +plotXY <- function( + x, + y, + colour_by = color_by, + shape_by = NULL, + size_by = NULL, + order_by = NULL, + other_fields = list(), + swap_rownames = NULL, + color_by = NULL, + point_fun = NULL, + scattermore = FALSE, + bins = NULL, + summary_fun = "sum", + hex = FALSE, + ... +) { + ylab <- NULL + if (is.data.frame(y) || is(y, "DataFrame")) { + stopifnot(ncol(y) == 1L) + ylab <- colnames(y) + y <- y[,1] + } + + df_to_plot <- data.frame(Y = y) + + xlab <- NULL + if (is.null(x)) { + df_to_plot$X <- factor(character(nrow(df_to_plot))) + } else if (is.data.frame(x) || is(x, "DataFrame")) { + stopifnot(ncol(y) == 1L) + xlab <- colnames(x) + df_to_plot$X <- x[,1] + } else { + df_to_plot$X <- x + } + + if (!is.null(colour_by)) { + if (is.data.frame(colour_by) || is(colour_by, "DataFrame")) { + stopifnot(ncol(colour_by) == 1) + df_to_plot$colour_by <- colour_by[,1] + colour_by <- colnmames(colour_by) + } else { + df_to_plot$colour_by <- colour_by + colour_by <- "colour" + } + } + + if (!is.null(size_by)) { + if (is.data.frame(size_by) || is(size_by, "DataFrame")) { + stopifnot(ncol(size_by) == 1) + df_to_plot$size_by <- size_by[,1] + size_by <- colnames(size_by) + } else { + df_to_plot$size_by <- size_by + size_by <- "size" + } + } + + if (!is.null(shape_by)) { + if (is.data.frame(shape_by) || is(shape_by, "DataFrame")) { + stopifnot(ncol(shape_by) == 1) + df_to_plot$shape_by <- shape_by[,1] + shape_by <- colnames(shape_by) + } else { + df_to_plot$shape_by <- shape_by + shape_by <- "shape" + } + } + + if (length(other_fields)) { + if (!is.data.frame(other_fields) && !is(other_fields, "DataFrame")) { + other_fields <- do.call(data.frame, other_fields) + } + df_to_plot <- cbind(df_to_plot, other_fields) + } + + .central_plotter( + df_to_plot, + xlab = xlab, + ylab = ylab, + colour_by = colour_by, + size_by = size_by, + shape_by = shape_by, + scattermore = scattermore, + bins = bins, + summary_fun = summary_fun, + hex = hex, + ..., + point_FUN = point_fun + ) +} diff --git a/man/plotXY.Rd b/man/plotXY.Rd new file mode 100644 index 0000000..8540749 --- /dev/null +++ b/man/plotXY.Rd @@ -0,0 +1,108 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plotXY.R +\name{plotXY} +\alias{plotXY} +\title{Plot arbitrary x- and y-coordinates} +\usage{ +plotXY( + x, + y, + colour_by = color_by, + shape_by = NULL, + size_by = NULL, + order_by = NULL, + other_fields = list(), + swap_rownames = NULL, + color_by = NULL, + point_fun = NULL, + scattermore = FALSE, + bins = NULL, + summary_fun = "sum", + hex = FALSE, + ... +) +} +\arguments{ +\item{x}{Vector containing the x-coordinates. +Alternatively, a data.frame with a single column. +Alternatively \code{NULL}, in which case all points are assumed to have the same categorical x-coordinate.} + +\item{y}{Vector containing the y-coordinates, of same length as \code{x}. +Alternatively, a data.frame with a single column.} + +\item{colour_by}{Vector containing a variable to colour by, of the same length as \code{x}. +Alternatively, a data.frame with a single column.} + +\item{shape_by}{Vector containing a variable to shape by, of the same length as \code{x}. +Alternatively, a data.frame with a single column.} + +\item{size_by}{Vector containing a variable to shape by, of the same length as \code{x}. +Alternatively, a data.frame with a single column.} + +\item{order_by}{Specification of a column metadata field or a feature to order points by, see the \code{by} argument in \code{?\link{retrieveCellInfo}} for possible values.} + +\item{other_fields}{A data frame of additional columns to add to the data.frame passed to \pkg{ggplot2}. +This should have number of rows equal to the length of \code{y}. +Alternatively, a named list of vectors of length equal to that of \code{y}.} + +\item{swap_rownames}{Column name of \code{rowData(object)} to be used to +identify features instead of \code{rownames(object)} when labelling plot +elements.} + +\item{color_by}{Alias to \code{colour_by}.} + +\item{point_fun}{Function used to create a geom that shows individual cells. +Should take \code{...} args and return a ggplot2 geom. For example, +\code{point_fun=function(...) geom_quasirandom(...)}.} + +\item{scattermore}{Logical, whether to use the \code{scattermore} package to +greatly speed up plotting a large number of cells. Use \code{point_size = +0} for the most performance gain.} + +\item{bins}{Number of bins, can be different in x and y, to bin and summarize +the points and their values, to avoid overplotting. If \code{NULL} +(default), then the points are plotted without binning. Only used when both +x and y are numeric.} + +\item{summary_fun}{Function to summarize the feature value of each point +(e.g. gene expression of each cell) when the points binned, defaults to +\code{sum}. Can be either the name of the function or the function itself.} + +\item{hex}{Logical, whether to use \code{\link{geom_hex}}.} + +\item{...}{Additional arguments for visualization, see +\code{?"\link{scater-plot-args}"} for details.} +} +\value{ +A \link{ggplot} object. +} +\description{ +Reuse \pkg{scater}'s plotting machinery for arbitrary x- and y-coordinates. +This allows users to maintain the same aesthetic style when creating plots outside of a \link[SingleCellExperiment]{SingleCellExperiment}. +} +\examples{ +x <- runif(100) +y <- rnorm(100) + +# Manually add labels. +plotXY(x, y) + ggplot2::xlab("X") + ggplot2::ylab("Y") + +# Insert labels via data frames. +plotXY(data.frame(foo=x), data.frame(bar=y)) + +col <- sample(LETTERS, length(x), replace=TRUE) +plotXY(x, y, colour_by=col) + guides(color=guide_legend("stuff")) + +shape <- sample(LETTERS[1:5], length(x), replace=TRUE) +plotXY(x, y, shape=data.frame(FOO=shape)) + +size <- runif(length(x)) +plotXY(x, y, size=data.frame(BAR=size)) + +plotXY(x, y, other_fields=data.frame(stuff = rbinom(100, 1, 0.5) > 0)) + + ggplot2::facet_grid(~stuff) + +} +\author{ +Aaron Lun +}