Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

One function per facet

Tags:

r

ggplot2

I can plot a function in ggplot2, like so:

library(ggplot2)
ggplot(data.frame(x=0), aes(x)) + geom_function(fun = sin) + xlim(c(-5,5))

Can I use ggplot2's facetting to make the plot for multiple functions, one in each facet? (for example sin and cos)

like image 630
mspices Avatar asked Jun 14 '26 18:06

mspices


2 Answers

Looks like you in fact can facet by the function if you feed each layer its own data with the faceting variable specified:

library(ggplot2) # using ggplot2 3.3.5
ggplot(data.frame(x=0), aes(x)) + 
  geom_function(fun = sin, data = data.frame(x = -5:5, fun_name = "sin")) +
  geom_function(fun = cos, data = data.frame(x = -5:5, fun_name = "cos")) +
  facet_wrap(~fun_name)
  

enter image description here

like image 110
Jon Spring Avatar answered Jun 17 '26 23:06

Jon Spring


... fun is not an aesthetic ... you can make it one :)

It's a bit of an overkill, but just a quick demonstration what is possible by modifying the Geoms and Stats. The below is a quick hack and I've referenced with quite a lot of ugly :::, which you wouldn't do if you would properly package this. Also this is clearly not properly tested on many use cases. Few more comments in the code.

This was quite ... fun :)

df <- data.frame(x = 0, fun = c("sin", "cos", "tan", "mean"))

ggplot(df, aes(x)) +
  stat_function2(aes(fun = fun)) +
  xlim(c(-5,5)) +
  facet_wrap(~fun, scales = "free_y")

Modifying Geom and Stat - StatFunction2

StatFunction2 <- ggproto(NULL, StatFunction)
## removing fun from the arguments
StatFunction2$compute_group <- function (data, scales, xlim = NULL, n = 101, args = list()) 
{
  if (is.null(scales$x)) {
    ## need to change that here a bit
    range <- rlang::`%||%`(xlim, c(0, 1))
    xseq <- seq(range[1], range[2], length.out = n)
    x_trans <- xseq
  }
  else {
    ## same same
    range <- rlang::`%||%`(xlim, scales$x$dimension())
    xseq <- seq(range[1], range[2], length.out = n)
    if (scales$x$is_discrete()) {
      x_trans <- xseq
    }
    else {
      x_trans <- scales$x$trans$inverse(xseq)
    }
  }
  ## get the function, this is the trick :)
  fun <- unique(data$fun)
  if (plyr::is.formula(fun)) 
    fun <- as_function(fun)
  y_out <- do.call(fun, c(list(quote(x_trans)), args))
  if (!is.null(scales$y) && !scales$y$is_discrete()) {
    y_out <- scales$y$trans$transform(y_out)
  }
  ggplot2:::new_data_frame(list(x = xseq, y = y_out))
}
## update stat_function - remove fun argument and reference new geom_function2
stat_function2 <- function (mapping = NULL, data = NULL, geom = "function2", position = "identity", 
                            ..., fun, xlim = NULL, n = 101, args = list(), na.rm = FALSE, 
                            show.legend = NA, inherit.aes = TRUE) 
{
  if (is.null(data)) {
    ### those ::: are just for to make it work here
    data <- ggplot2:::ensure_nonempty_data
  }
  layer(data = data, mapping = mapping, stat = StatFunction2, 
        geom = geom, position = position, show.legend = show.legend, 
        ## fun needs to be removed here too.
        inherit.aes = inherit.aes, params = list(n = n, 
                                                 args = args, na.rm = na.rm, xlim = xlim, ...))
}
## This is the correct way to create copies (children) of ggproto objects
## see https://stackoverflow.com/a/70637511/7941188
GeomFunction2 <- ggproto(NULL, GeomFunction)
## change the required aesthetics - this removes the warning that aesthetics are not known
GeomFunction2$required_aes <- c("x", "y", "fun")
## update the corresponding geom (two locations in this function definition)
geom_function2 <- function (mapping = NULL, data = NULL, stat = "function2", position = "identity", 
                            ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
{
  if (is.null(data)) {
    data <- ensure_nonempty_data
  }
  layer(data = data, mapping = mapping, stat = stat, geom = GeomFunction2, 
        position = position, show.legend = show.legend, inherit.aes = inherit.aes, 
        params = list(na.rm = na.rm, ...))
}
like image 21
tjebo Avatar answered Jun 17 '26 23:06

tjebo



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!