Skip to content

Commit ae1c8f0

Browse files
dkahleyutannihilation
authored andcommitted
rlang/purrr style anonymous function specification in stat_function (#3160)
1 parent 6be7893 commit ae1c8f0

File tree

4 files changed

+92
-62
lines changed

4 files changed

+92
-62
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161

6262
* `stat_bin()` now handles data with only one unique value (@yutannihilation #3047).
6363

64+
* `stat_function()` now accepts rlang/purrr style anonymous functions for the
65+
`fun` parameter (@dkahle, #3159).
66+
6467
* `geom_polygon()` can now draw polygons with holes using the new `subgroup`
6568
aesthetic. This functionality requires R 3.6 (@thomasp85, #3128)
6669

R/stat-function.r

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
#' Compute function for each x value
22
#'
3-
#' This stat makes it easy to superimpose a function on top of an existing
4-
#' plot. The function is called with a grid of evenly spaced values along
5-
#' the x axis, and the results are drawn (by default) with a line.
3+
#' This stat makes it easy to superimpose a function on top of an existing plot.
4+
#' The function is called with a grid of evenly spaced values along the x axis,
5+
#' and the results are drawn (by default) with a line.
66
#'
77
#' @eval rd_aesthetics("stat", "function")
8-
#' @param fun function to use. Must be vectorised.
9-
#' @param n number of points to interpolate along
10-
#' @param args list of additional arguments to pass to `fun`
8+
#' @param fun Function to use. Either 1) an anonymous function in the base or
9+
#' rlang formula syntax (see [rlang::as_function()])
10+
#' or 2) a quoted or character name referencing a function; see examples. Must
11+
#' be vectorised.
12+
#' @param n Number of points to interpolate along
13+
#' @param args List of additional arguments to pass to `fun`
1114
#' @param xlim Optionally, restrict the range of the function to this range.
1215
#' @inheritParams layer
1316
#' @inheritParams geom_point
@@ -16,39 +19,41 @@
1619
#' \item{x}{x's along a grid}
1720
#' \item{y}{value of function evaluated at corresponding x}
1821
#' }
22+
#' @seealso [rlang::as_function()]
1923
#' @export
2024
#' @examples
25+
#'
26+
#' # stat_function is useful for overlaying functions
2127
#' set.seed(1492)
22-
#' df <- data.frame(
23-
#' x = rnorm(100)
24-
#' )
25-
#' x <- df$x
26-
#' base <- ggplot(df, aes(x)) + geom_density()
27-
#' base + stat_function(fun = dnorm, colour = "red")
28-
#' base + stat_function(fun = dnorm, colour = "red", args = list(mean = 3))
28+
#' ggplot(data.frame(x = rnorm(100)), aes(x)) +
29+
#' geom_density() +
30+
#' stat_function(fun = dnorm, colour = "red")
2931
#'
30-
#' # Plot functions without data
31-
#' # Examples adapted from Kohske Takahashi
32+
#' # To plot functions without data, specify range of x-axis
33+
#' base <- ggplot(data.frame(x = c(-5, 5)), aes(x))
34+
#' base + stat_function(fun = dnorm)
35+
#' base + stat_function(fun = dnorm, args = list(mean = 2, sd = .5))
3236
#'
33-
#' # Specify range of x-axis
34-
#' ggplot(data.frame(x = c(0, 2)), aes(x)) +
35-
#' stat_function(fun = exp, geom = "line")
37+
#' # The underlying mechanics evaluate the function at discrete points
38+
#' # and connect the points with lines
39+
#' base <- ggplot(data.frame(x = c(-5, 5)), aes(x))
40+
#' base + stat_function(fun = dnorm, geom = "point")
41+
#' base + stat_function(fun = dnorm, geom = "point", n = 20)
42+
#' base + stat_function(fun = dnorm, n = 20)
3643
#'
37-
#' # Plot a normal curve
38-
#' ggplot(data.frame(x = c(-5, 5)), aes(x)) + stat_function(fun = dnorm)
44+
#' # Two functions on the same plot
45+
#' base +
46+
#' stat_function(fun = dnorm, colour = "red") +
47+
#' stat_function(fun = dt, colour = "blue", args = list(df = 1))
3948
#'
40-
#' # To specify a different mean or sd, use the args parameter to supply new values
41-
#' ggplot(data.frame(x = c(-5, 5)), aes(x)) +
42-
#' stat_function(fun = dnorm, args = list(mean = 2, sd = .5))
49+
#' # Using a custom anonymous function
50+
#' base + stat_function(fun = function(.x) .5*exp(-abs(.x)))
51+
#' base + stat_function(fun = ~ .5*exp(-abs(.x)))
4352
#'
44-
#' # Two functions on the same plot
45-
#' f <- ggplot(data.frame(x = c(0, 10)), aes(x))
46-
#' f + stat_function(fun = sin, colour = "red") +
47-
#' stat_function(fun = cos, colour = "blue")
53+
#' # Using a custom named function
54+
#' f <- function(.x) .5*exp(-abs(.x))
55+
#' base + stat_function(fun = f)
4856
#'
49-
#' # Using a custom function
50-
#' test <- function(x) {x ^ 2 + x + 20}
51-
#' f + stat_function(fun = test)
5257
stat_function <- function(mapping = NULL, data = NULL,
5358
geom = "path", position = "identity",
5459
...,
@@ -97,6 +102,8 @@ StatFunction <- ggproto("StatFunction", Stat,
97102
x_trans <- scales$x$trans$inverse(xseq)
98103
}
99104

105+
if (is.formula(fun)) fun <- rlang::as_function(fun)
106+
100107
new_data_frame(list(
101108
x = xseq,
102109
y = do.call(fun, c(list(quote(x_trans)), args))

man/stat_function.Rd

Lines changed: 39 additions & 32 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test-stats-function.r

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,16 @@ test_that("works with discrete x", {
3434
expect_equal(ret$x, 1:2)
3535
expect_equal(ret$y, 1:2)
3636
})
37+
38+
test_that("works with formula syntax", {
39+
dat <- data_frame(x = 1:10)
40+
41+
base <- ggplot(dat, aes(x, group = 1)) +
42+
stat_function(fun = ~ .x^2, geom = "point", n = 5) +
43+
scale_x_continuous(limits = c(0, 10))
44+
ret <- layer_data(base)
45+
46+
s <- seq(0, 10, length.out = 5)
47+
expect_equal(ret$x, s)
48+
expect_equal(ret$y, s^2)
49+
})

0 commit comments

Comments
 (0)