From 41bbb9886ae4769677640d1cdf501fb50e213ecf Mon Sep 17 00:00:00 2001 From: Jean-Olivier Irisson Date: Sun, 29 Jul 2018 14:15:45 +0200 Subject: [PATCH 1/2] Add facet variants called subplots Facets are meant to display different subsets of the same data. However, with data in the "long" format (i.e. several variables stacked in the same column and an additional column indicating which is which), they can also be used to create the same plot for several variables (e.g. a histogram). In that case, the scales are often free (the variables are different and hence often do not have the same unit). The problem with that approach is that the axis title is often a generic, uninformative word (e.g. "values") while the real labels for the axis are the facet labels, which are on the wrong side of the plot. These functions use `facet_wrap` and `facet_grid` to create subplots and adjust the position and aspect of the facet labels to make them look like axis titles. --- DESCRIPTION | 1 + NAMESPACE | 2 + R/subplot.R | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ man/subplot.Rd | 97 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 R/subplot.R create mode 100644 man/subplot.Rd diff --git a/DESCRIPTION b/DESCRIPTION index bcbfff5607..82e1a1ed9a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -221,6 +221,7 @@ Collate: 'stat-summary.r' 'stat-unique.r' 'stat-ydensity.r' + 'subplot.R' 'summarise-plot.R' 'summary.r' 'theme-elements.r' diff --git a/NAMESPACE b/NAMESPACE index e14b3233d1..2e81a80aa1 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -537,6 +537,8 @@ export(stat_summary_bin) export(stat_summary_hex) export(stat_unique) export(stat_ydensity) +export(subplot_grid) +export(subplot_wrap) export(summarise_coord) export(summarise_layers) export(summarise_layout) diff --git a/R/subplot.R b/R/subplot.R new file mode 100644 index 0000000000..7a1d96d673 --- /dev/null +++ b/R/subplot.R @@ -0,0 +1,111 @@ +#' Create a ribbon or grid of subplots +#' +#' `subplot_wrap` and `subplot_grid` are variations of [facet_wrap()] and +#' [facet_grid()] that are more appropriate when the facets represent +#' different variables rather than different subsets of the same variable. +#' +#' @inheritParams facet_wrap +#' @inheritParams facet_grid +#' @param axis Axis to which the variables of the subplots are mapped. +#' @param scales Either "auto", in which case only the scale of the axis +#' designated by `axis` is free, or "free" in which case both scales are +#' free. +#' @param ... Passed to [facet_wrap()] or [facet_grid()]. +#' @rdname subplot +#' @export +#' @examples +#' # Stack different variables in the same column +#' # NB: see tidyr::gather for a more practical alternative +#' cars <- stack(mtcars, select=c(disp, drat, qsec)) +#' cars$mpg <- mtcars$mpg +#' cars$cyl <- mtcars$cyl +#' head(cars) +#' +#' # inspect the distribution of all stacked variables at once +#' p <- ggplot(cars) + geom_histogram(aes(values), bins=10) +#' p + facet_wrap(~ind, scales="free_x") +#' p + subplot_wrap(~ind) +#' +#' # examine the relationship of all variables with mpg +#' p <- ggplot(cars) + geom_point(aes(values, mpg)) +#' p + facet_wrap(~ind, scales="free_x") +#' p + subplot_wrap(~ind) +#' # or in the other direction +#' p <- ggplot(cars) + geom_point(aes(mpg, values)) +#' p + facet_wrap(~ind, scales="free_y") +#' p + subplot_wrap(~ind, axis="y") +#' +#' # do the same but with an additional conditional variable, hence +#' # generating a grid of subplots +#' p <- ggplot(cars) + geom_point(aes(values, mpg)) +#' p + facet_grid(cyl~ind, scales="free_x") +#' p + subplot_grid(cyl~ind) +#' +#' # the facet labels play the role of the axis title and respect its theme +#' ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +#' theme_update(axis.title=element_text(colour="red")) +#' ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +#' # but only when the plot if created *after* global theme updates +#' p <- ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +#' theme_update(axis.title=element_text(colour="blue")) +#' p +#' p + theme(axis.title=element_text(colour="green")) +#' # here, in both cases, the facet labels stay blue, unfortunately +subplot_wrap <- function(facets, axis="x", scales="auto", ...) { + axis <- match.arg(axis, c("x", "y")) + list( + facet_wrap(facets=facets, + scales=subplot_scales(scales, axis), + strip.position=ifelse(axis=="x", "bottom", "left"), ...), + subplot_theme(axis) + ) +} + +#' @rdname subplot +#' @export +subplot_grid <- function(rows=NULL, cols=NULL, axis="x", scales="auto", ...) { + axis <- match.arg(axis, c("x", "y")) + list( + facet_grid(rows=rows, cols=cols, + scales=subplot_scales(scales, axis), switch=axis, ...), + subplot_theme(axis) + ) +} + +subplot_scales <- function(scales, axis) { + scales <- match.arg(scales, c("auto", "free")) + # with facets representing different variables, the scale of the axis + # mapped to the variables should be free + if (scales == "auto") { + scales <- paste0("free_", axis) + } + return(scales) +} + +subplot_theme <- function(axis) { + if (axis == "x") { + th <- theme( + # give the strip text the appearance of the x axis title + strip.background.x=element_blank(), + strip.text.x=calc_element("axis.title.x", theme_get()), + # TODO this gets the theme elements at the time of plot creation, rather than rendering, which means only global theme changes before the creation of the plot are respected. Ideally strip.text.* should inherit whatever is in axis.title.* at the time of rendering. Not sure if/how this is possible + # remove the x axis title + axis.title.x=element_blank() + ) + } else { + # do the same but for the y axis + th <- theme( + strip.background.y=element_blank(), + strip.text.y=calc_element("axis.title.y", theme_get()), + axis.title.y=element_blank() + ) + # and rotate the text the correct way + th$strip.text.y$angle <- -90 + } + # put the facet strips where the axis titles would be + th + theme( + strip.placement="outside", + strip.switch.pad.wrap=unit(0, "npc"), + strip.switch.pad.grid=unit(0, "npc") + ) +} diff --git a/man/subplot.Rd b/man/subplot.Rd new file mode 100644 index 0000000000..ee5b8b229f --- /dev/null +++ b/man/subplot.Rd @@ -0,0 +1,97 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/subplot.R +\name{subplot_wrap} +\alias{subplot_wrap} +\alias{subplot_grid} +\title{Create a ribbon or grid of subplots} +\usage{ +subplot_wrap(facets, axis = "x", scales = "auto", ...) + +subplot_grid(rows = NULL, cols = NULL, axis = "x", scales = "auto", + ...) +} +\arguments{ +\item{facets}{A set of variables or expressions quoted by \code{\link[=vars]{vars()}} +and defining faceting groups on the rows or columns dimension. +The variables can be named (the names are passed to \code{labeller}). + +For compatibility with the classic interface, can also be a +formula or character vector. Use either a one sided formula, `~a +\itemize{ +\item b\code{, or a character vector,}c("a", "b")`. +}} + +\item{axis}{Axis to which the variables of the subplots are mapped.} + +\item{scales}{Either "auto", in which case only the scale of the axis +designated by \code{axis} is free, or "free" in which case both scales are +free.} + +\item{...}{Passed to \code{\link[=facet_wrap]{facet_wrap()}} or \code{\link[=facet_grid]{facet_grid()}}.} + +\item{rows}{A set of variables or expressions quoted by +\code{\link[=vars]{vars()}} and defining faceting groups on the rows or columns +dimension. The variables can be named (the names are passed to +\code{labeller}). + +For compatibility with the classic interface, \code{rows} can also be +a formula with the rows (of the tabular display) on the LHS and +the columns (of the tabular display) on the RHS; the dot in the +formula is used to indicate there should be no faceting on this +dimension (either row or column).} + +\item{cols}{A set of variables or expressions quoted by +\code{\link[=vars]{vars()}} and defining faceting groups on the rows or columns +dimension. The variables can be named (the names are passed to +\code{labeller}). + +For compatibility with the classic interface, \code{rows} can also be +a formula with the rows (of the tabular display) on the LHS and +the columns (of the tabular display) on the RHS; the dot in the +formula is used to indicate there should be no faceting on this +dimension (either row or column).} +} +\description{ +\code{subplot_wrap} and \code{subplot_grid} are variations of \code{\link[=facet_wrap]{facet_wrap()}} and +\code{\link[=facet_grid]{facet_grid()}} that are more appropriate when the facets represent +different variables rather than different subsets of the same variable. +} +\examples{ +# Stack different variables in the same column +# NB: see tidyr::gather for a more practical alternative +cars <- stack(mtcars, select=c(disp, drat, qsec)) +cars$mpg <- mtcars$mpg +cars$cyl <- mtcars$cyl +head(cars) + +# inspect the distribution of all stacked variables at once +p <- ggplot(cars) + geom_histogram(aes(values), bins=10) +p + facet_wrap(~ind, scales="free_x") +p + subplot_wrap(~ind) + +# examine the relationship of all variables with mpg +p <- ggplot(cars) + geom_point(aes(values, mpg)) +p + facet_wrap(~ind, scales="free_x") +p + subplot_wrap(~ind) +# or in the other direction +p <- ggplot(cars) + geom_point(aes(mpg, values)) +p + facet_wrap(~ind, scales="free_y") +p + subplot_wrap(~ind, axis="y") + +# do the same but with an additional conditional variable, hence +# generating a grid of subplots +p <- ggplot(cars) + geom_point(aes(values, mpg)) +p + facet_grid(cyl~ind, scales="free_x") +p + subplot_grid(cyl~ind) + +# the facet labels play the role of the axis title and respect its theme +ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +theme_update(axis.title=element_text(colour="red")) +ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +# but only when the plot if created *after* global theme updates +p <- ggplot(cars) + geom_point(aes(values, mpg)) + subplot_wrap(~ind) +theme_update(axis.title=element_text(colour="blue")) +p +p + theme(axis.title=element_text(colour="green")) +# here, in both cases, the facet labels stay blue, unfortunately +} From a62346bd2d1f2b855469c092827a30f604a83559 Mon Sep 17 00:00:00 2001 From: Jean-Olivier Irisson Date: Sun, 29 Jul 2018 14:23:43 +0200 Subject: [PATCH 2/2] Add NEWS blurb --- NEWS.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.md b/NEWS.md index 85bb67443f..9a282c7464 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,8 @@ # ggplot2 3.0.0.9000 +* Add `subplot_wrap()` and `subplot_grid()` which make it easier to use + faceting as a way to create subplots of different variables (@jiho, #2786). + * `stat_contour()`, `stat_density2d()`, `stat_bin2d()`, `stat_binhex()` now calculate normalized statistics including `nlevel`, `ndensity`, and `ncount`. Also, `stat_density()` now includes the calculated statistic