【问题标题】:One function per facet每个方面一个功能
【发布时间】:2022-06-29 18:36:48
【问题描述】:

我可以在 ggplot2 中绘制一个函数,如下所示:

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

我可以使用 ggplot2 的 facetting 为多个函数绘制绘图,每个 facet 一个吗? (例如 sin 和 cos)

【问题讨论】:

  • fun= 不是美学,因此您不能将其嵌入数据中或轻易地由数据确定。一种替代方法是创建不同的图(每个图一个函数),然后使用 patchwork 包对它们进行仿造。
  • 添加了一个解决方案,该解决方案依赖于为每一层提供其自己的数据,其中指定了 faceting 变量。
  • @r2evans it now is an aesthetic :) - 不过我承认有点矫枉过正。

标签: r ggplot2


【解决方案1】:

如果您使用指定的分面变量为每一层提供自己的数据,那么您实际上可以通过该函数进行分面:

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)
  

【讨论】:

    【解决方案2】:

    ...乐趣不是一种审美 ...你可以做到:)

    这有点矫枉过正,但只是快速演示通过修改 Geoms 和 Stats 可以实现什么。下面是一个快速的 hack,我引用了很多丑陋的 :::,如果你能正确打包它,你就不会这样做。此外,这显然没有在许多用例上进行适当的测试。代码中还有几个 cmets。

    这很……有趣:)

    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")
    

    修改 Geom 和 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, ...))
    }
    

    【讨论】:

      【解决方案3】:

      因为fun= 不是美学,我认为你不能在它上面刻画。但是,您可以使用 patchwork 包进行仿造。

      library(ggplot2)
      gsin <- ggplot(data.frame(x=0), aes(x)) +
        geom_function(fun = sin) +
        xlim(c(-5,5)) +
        labs(title = "sin()")
      gtan <- ggplot(data.frame(x=0), aes(x)) +
        geom_function(fun = tan) +
        xlim(c(-5,5)) +
        labs(title = "tan()")
      gsin + gtan
      

      如果您更喜欢ggplot2 分面的“外观”,您可以选择此方法:

      gsin <- ggplot(data.frame(x=0, fun="sin"), aes(x)) +
        facet_wrap(~fun) +
        geom_function(fun = sin) +
        xlim(c(-5,5))
      gtan <- ggplot(data.frame(x=0, fun="tan"), aes(x)) +
        facet_wrap(~fun) +
        geom_function(fun = tan) +
        xlim(c(-5,5))
      gsin + gtan
      

      到目前为止,所有这些都具有facet_*(scales="free_y")效果(因为我们修复了xlim(.))。如果你想更接近地模拟 faceting,你需要控制所有 facet 的限制:

      ylims <- c(-1, 1)
      gsin <- ggplot(data.frame(x=0, fun="sin"), aes(x)) +
        facet_wrap(~fun) +
        geom_function(fun = sin) +
        xlim(c(-5,5)) +
        scale_y_continuous(limits = ylims)
      gtan <- ggplot(data.frame(x=0, fun="tan"), aes(x)) +
        facet_wrap(~fun) +
        geom_function(fun = tan) +
        xlim(c(-5,5)) +
        scale_y_continuous(name = NULL, guide = NULL, limits = ylims)
      gsin + gtan
      # Warning: Removed 22 row(s) containing missing values (geom_path).
      

      从技术上讲,您不需要在这里设置所有的 y 限制,但是...除非您知道确定不受约束的 y 轴的限制会是你所需要的,它们可能会稍微偏离。例如,如果您将初始函数(在快速破解中)更改为 2*sin(x) 但忘记更新剩余构面的 y 限制,那么您的绘图将是谎言。最好在单个位置 (ylims &lt;- ...) 设置限制并在 所有 图中引用。

      【讨论】:

        猜你喜欢
        • 2022-01-15
        • 1970-01-01
        • 1970-01-01
        • 2015-10-19
        • 1970-01-01
        • 2020-04-12
        • 1970-01-01
        • 2011-09-20
        • 2014-07-07
        相关资源
        最近更新 更多