Skip to content

Function as data #1527

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

Merged
merged 14 commits into from
Feb 1, 2016
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ S3method(format,facet)
S3method(format,ggproto)
S3method(format,ggproto_method)
S3method(fortify,"NULL")
S3method(fortify,"function")
S3method(fortify,Line)
S3method(fortify,Lines)
S3method(fortify,Polygon)
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# ggplot2 2.0.0.9000

* `layer()` now accepts a function as the data argument. The function will be
applied to the data passed to the `ggplot()` function and must return a
data.frame (#1527).

* The theme can now modify the margins of legend title and text (#1502).

* `scale_size()` warns when used with categorical data.
Expand Down
2 changes: 2 additions & 0 deletions R/fortify.r
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ fortify.data.frame <- function(model, data, ...) model
#' @export
fortify.NULL <- function(model, data, ...) waiver()
#' @export
fortify.function <- function(model, data, ...) model
#' @export
fortify.default <- function(model, data, ...) {
stop(
"ggplot2 doesn't know how to deal with data of class ",
Expand Down
41 changes: 41 additions & 0 deletions R/layer.r
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
#' Create a new layer
#'
#' A layer is a combination of data, stat and geom with a potential position
#' adjustment. Usually layers are created using \code{geom_*} or \code{stat_*}
#' calls but it can also be created directly using the \code{layer} function.
#'
#' @details
#' The data in a layer can be specified in one of three ways:
#'
#' \itemize{
#' \item{If the data argument is \code{NULL} (the default) the data is
#' inherited from the global plot data as specified in the call to
#' \code{\link{ggplot}}.}
#' \item{If the data argument is a function, that function is called with the
#' global data as the only argument and the return value is used as the layer
#' data. The function must return a data.frame.}
#' \item{Any other type of value passed to \code{data} will be passed through
#' \code{\link{fortify}}, and there must thus be a \code{fortify} method
#' defined for the class of the value. Passing a data.frame is a special case
#' of this as \code{fortify.data.frame} returns the data.frame untouched.}
#' }
#'
#' @export
#' @inheritParams geom_point
#' @param geom,stat,position Geom, stat and position adjustment to use in
Expand All @@ -16,6 +36,13 @@
#' layer(geom = "point", stat = "identity", position = "identity",
#' params = list(na.rm = FALSE)
#' )
#'
#' # use a function as data to plot a subset of global data
#' ggplot(mpg, aes(displ, hwy)) +
#' layer(geom = "point", stat = "identity", position = "identity",
#' data = head, params = list(na.rm = FALSE)
#' )
#'
layer <- function(geom = NULL, stat = NULL,
data = NULL, mapping = NULL,
position = NULL, params = list(),
Expand Down Expand Up @@ -101,6 +128,20 @@ Layer <- ggproto("Layer", NULL,
cat(snakeize(class(self$position)[[1]]), "\n")
},

layer_data = function(self, plot_data) {
if (is.waive(self$data)) {
data <- plot_data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can drop the data <- in all the alternatives

} else if (is.function(self$data)) {
data <- self$data(plot_data)
if (!is.data.frame(data)) {
stop("Data function must return a data.frame", call. = FALSE)
}
} else {
data <- self$data
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be better with no returns and a final else block

data
},

compute_aesthetics = function(self, data, plot) {
# For annotation geoms, it is useful to be able to ignore the default aes
if (self$inherit.aes) {
Expand Down
4 changes: 1 addition & 3 deletions R/panel.r
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,8 @@ train_layout <- function(panel, facet, data, plot_data) {
# @param panel a trained panel object
# @param the facetting specification
# @param data list of data frames (one for each layer)
# @param plot_data default plot data frame
map_layout <- function(panel, facet, data, plot_data) {
map_layout <- function(panel, facet, data) {
lapply(data, function(data) {
if (is.waive(data)) data <- plot_data
facet_map_layout(facet, data, panel$layout)
})
}
Expand Down
4 changes: 2 additions & 2 deletions R/plot-build.r
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ ggplot_build <- function(plot) {
}

layers <- plot$layers
layer_data <- lapply(layers, function(y) y$data)
layer_data <- lapply(layers, function(y) y$layer_data(plot$data))

scales <- plot$scales
# Apply function to layer and matching data
Expand All @@ -39,7 +39,7 @@ ggplot_build <- function(plot) {

panel <- new_panel()
panel <- train_layout(panel, plot$facet, layer_data, plot$data)
data <- map_layout(panel, plot$facet, layer_data, plot$data)
data <- map_layout(panel, plot$facet, layer_data)

# Compute aesthetics to produce data with generalised variable names
data <- by_layer(function(l, d) l$compute_aesthetics(d, plot))
Expand Down
27 changes: 26 additions & 1 deletion man/layer.Rd

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

13 changes: 13 additions & 0 deletions tests/testthat/test-layer.r
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,16 @@ test_that("strip_dots remove dots around calculated aesthetics", {
expect_equal(strip_dots(aes(sapply(..density.., function(x) mean(x)))$x),
quote(sapply(density, function(x) mean(x))))
})

# Data extraction ---------------------------------------------------------

test_that("layer_data returns a data.frame", {
l <- geom_point()
expect_equal(l$layer_data(mtcars), mtcars)
l <- geom_point(data = head(mtcars))
expect_equal(l$layer_data(mtcars), head(mtcars))
l <- geom_point(data = head)
expect_equal(l$layer_data(mtcars), head(mtcars))
l <- geom_point(data = nrow)
expect_error(l$layer_data(mtcars), "Data function must return a data.frame")
})