【问题标题】:ggplot2 custom stat not shown when facetting刻面时未显示ggplot2自定义统计信息
【发布时间】:2019-08-13 10:31:08
【问题描述】:

我正在尝试为ggplot2 编写自定义stat_*,我想使用瓷砖为2D 黄土表面着色。当我start from the extension guide 时,我可以像他们一样写一个 stat_chull:

stat_chull = function(mapping = NULL, data = NULL, geom = "polygon",
                       position = "identity", na.rm = FALSE, show.legend = NA, 
                       inherit.aes = TRUE, ...) {

  chull = ggproto("chull", Stat,
    compute_group = function(data, scales) {
      data[chull(data$x, data$y), , drop = FALSE]
    },
    required_aes = c("x", "y")
  )

  layer(
    stat = chull, data = data, mapping = mapping, geom = geom, 
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )
}

这适用于简单的调用和分面包装:

ggplot(mpg, aes(x=displ, y=hwy)) + 
  geom_point() + 
  stat_chull()
# optionally + facet_wrap(~ class)

当我写我的stat_loess2d 时,我还可以可视化所有类或单个类:

stat_loess2d = function(mapping = NULL, data = NULL, geom = "tile",
                       position = "identity", na.rm = FALSE, show.legend = NA, 
                       inherit.aes = TRUE, ...) {

  loess2d = ggproto("loess2d", Stat,
    compute_group = function(data, scales) {
      dens = MASS::kde2d(data$x, data$y)
      lsurf = loess(fill ~ x + y, data=data)
      df = data.frame(x = rep(dens$x, length(dens$y)),
                      y = rep(dens$y, each=length(dens$x)),
                      dens = c(dens$z))
      df$fill = predict(lsurf, newdata=df[c("x", "y")])
      df
    },
    required_aes = c("x", "y", "fill")
  )

  layer(
    stat = loess2d, data = data, mapping = mapping, geom = geom, 
    position = position, show.legend = show.legend, inherit.aes = inherit.aes,
    params = list(na.rm = na.rm, ...)
  )
}

ggplot(mpg, aes(x=displ, y=hwy, fill=year)) + 
  geom_point(aes(color=year)) + 
  stat_loess2d()

ggplot(mpg[mpg$class == "compact",], aes(x=displ, y=hwy, fill=year)) + 
  geom_point(aes(color=year)) + 
  stat_loess2d()

但是,当我尝试处理上述内容时,不再显示图块:

ggplot(mpg, aes(x=displ, y=hwy, fill=year)) + 
  geom_point(aes(color=year)) + 
  stat_loess2d() +
  facet_wrap(~ class)

谁能告诉我我在这里做错了什么?

【问题讨论】:

  • 您的代码给我的警告似乎与数据稀疏性有关。
  • 我认为这在这里无关紧要,因为拟合/绘图适用于各个方面(拆分数据并单独绘制)但在使用 facet_wrap(...) 时不起作用。

标签: r ggplot2 ggproto


【解决方案1】:

说明

我在这里看到的主要问题实际上超出了您所做的,并且与geom_tile 在特定 x / y 轴值显着不同时如何处理不同方面的图块创建有关。 older question 展示了类似的现象:geom_tile 可以单独处理每个方面的数据,但是将它们放在一起,并且图块会缩小以匹配不同方面值之间的最小差异。这会在绘图层中留下大量空白,并且通常会随着每个额外的方面而变得更糟,直到图块本身变得几乎不可见。

为了解决这个问题,我将在每个方面的密度/黄土计算之后添加一个数据处理步骤,以标准化所有方面的 x 和 y 值的范围。

如果您对compute_layercompute_panelcompute_group 之间的关系不太熟悉,请进行一些详细说明(当我开始搞乱 ggproto 对象时,我当然不是...):

  • 本质上,所有Stat* 对象都具有这三个功能,以弥合给定数据帧(在本例中为mpg)与Geom* 接收到的数据之间的差距。

  • 三者中,compute_layer 是顶层函数,通常触发compute_panel 为每个构面/面板计算单独的数据框(导出函数中使用的术语是构面,但底层包代码与面板相同;我也不知道为什么)。反过来,compute_panel 触发 compute_group 为每个组计算单独的数据帧(由 group / colour / fill / 等美学参数定义)。

  • compute_group 的结果返回到compute_panel 并合并为一个数据帧。同样,compute_layer 从每个方面的compute_panel 接收一个数据帧,并将它们再次组合在一起。然后将合并的数据框传递给Geom* 进行绘制。

(以上是顶级Stat 中定义的通用设置。从Stat 继承的其他Stat* 对象可能会覆盖任何步骤中的行为。例如,@987654347 @的compute_layer按原样返回原始数据帧,根本不触发compute_panel/compute_group,因为对于未更改的数据不需要这样做。)

对于这个用例,我们可以修改compute_layer中的代码,在从compute_panel/compute_group返回结果并组合在一起后,将与每个方面关联的值插入到公共箱中。因为普通垃圾箱 = 漂亮的大瓷砖,中间没有空白。

修改

以下是我编写 loess2d ggproto 对象的方式,并为 compute_layer 附加定义:

loess2d = ggproto("loess2d", Stat,
                  compute_group = function(data, scales) {
                    dens = MASS::kde2d(data$x, data$y)
                    lsurf = loess(fill ~ x + y, data=data)
                    df = data.frame(x = rep(dens$x, length(dens$y)),
                                    y = rep(dens$y, each=length(dens$x)),
                                    dens = c(dens$z))
                    df$fill = predict(lsurf, newdata=df[c("x", "y")])
                    df
                  },
                  compute_layer = function(self, data, params, layout) {
                    # no change from Stat$compute_layer in this chunk, except
                    # for liberal usage of `ggplot2:::` to utilise un-exported
                    # functions from the package
                    ggplot2:::check_required_aesthetics(self$required_aes, 
                                                        c(names(data), names(params)), 
                                                        ggplot2:::snake_class(self))
                    data <- remove_missing(data, params$na.rm, 
                                           c(self$required_aes, self$non_missing_aes), 
                                           ggplot2:::snake_class(self),
                                           finite = TRUE)
                    params <- params[intersect(names(params), self$parameters())]
                    args <- c(list(data = quote(data), scales = quote(scales)), params)
                    df <- plyr::ddply(data, "PANEL", function(data) {
                      scales <- layout$get_scales(data$PANEL[1])
                      tryCatch(do.call(self$compute_panel, args), 
                               error = function(e) {
                                 warning("Computation failed in `", ggplot2:::snake_class(self), 
                                         "()`:\n", e$message, call. = FALSE)
                                 data.frame()
                               })
                    })

                    # define common x/y grid range across all panels
                    # (length = 25 chosen to match the default value for n in MASS::kde2d)
                    x.range <- seq(min(df$x), max(df$x), length = 25)
                    y.range <- seq(min(df$y), max(df$y), length = 25)

                    # interpolate each panel's data to a common grid,
                    # with NA values for regions where each panel doesn't
                    # have data (this can be changed via the extrap
                    # parameter in akima::interp, but I think  
                    # extrapolating may create misleading visuals)
                    df <- df %>% 
                      tidyr::nest(-PANEL) %>%
                      mutate(data = purrr::map(data, 
                                               ~akima::interp(x = .x$x, y = .x$y, z = .x$fill,
                                                              xo = x.range, yo = y.range,
                                                              nx = 25, ny = 25) %>%
                                                 akima::interp2xyz(data.frame = TRUE) %>%
                                                 rename(fill = z))) %>%
                      tidyr::unnest()

                    return(df)
                  },
                  required_aes = c("x", "y", "fill")
)

用法:

ggplot(mpg,
       aes(x=displ, y=hwy, fill=year)) + 
  stat_loess2d() +
  facet_wrap(~ class)
# this does trigger warnings (not errors) because some of the facets contain
# really very few observations. if we filter for facets with more rows of data
# in the original dataset, this wouldn't be an issue

ggplot(mpg %>% filter(!class %in% c("2seater", "minivan")),
       aes(x=displ, y=hwy, fill=year)) + 
  stat_loess2d() +
  facet_wrap(~ class)
# no warnings triggered

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多