scale_continuous_fill()中的测试名称参数

时间:2019-01-17 16:16:01

标签: r ggplot2 testthat

问题:我正在使用testthat软件包来测试ggplot2数字。我找不到图例名称的位置(即name的{​​{1}}参数)。 scale_fill_continuous()保存在哪里? (有关具体示例,请参见文章末尾的可复制示例)。

我的搜索者:我已经搜索过SO,但是其他带有name[testthat]标签的问题没有帮助(例如this one和{{ 3}})。我也浏览了this one,但找不到答案。

可复制的示例:我正在寻找[ggplot]的位置,以便可以测试并确保其正确。

expression("Legend name"^2)

1 个答案:

答案 0 :(得分:2)

简短回答

假设您的ggplot对象命名为p,并且已在比例尺中指定了name参数,那么它将在p$scales$scales[[i]]$name中找到(其中i对应于秤的顺序)。

长答案

以下是关于如何找到它的漫长旅程。不必回答这个问题,但是下次您想在ggplot中查找内容时,它可能会对您有所帮助。

起点:通常,将ggplot对象转换为grob对象很有用,因为后者允许我们执行各种我们不容易在ggplot中入侵的事情(例如,绘制一个绘制区域边缘的几何图形而不会被切除,请使用不同的颜色为不同的构面条上色,为每个构面手动设定构面宽度,将图例作为自定义注释添加到另一幅地图上,等等。

ggplot2软件包具有函数ggplotGrob,该函数执行转换。这意味着,如果我们逐步研究这些步骤,则应该能够找到一个在ggplot对象中找到比例标题的步骤,以便将其转换为某种textGrob。

反过来,这意味着我们将采用以下单行代码,并逐层向下浏览,直到我们了解幕后情况:

ggplotGrob(my_plot)

第1层ggplotGrob本身只是两个函数ggplot_buildggplot_gtable的包装。

> ggplotGrob
function (x) 
{
    ggplot_gtable(ggplot_build(x))
}

来自?ggplot_build

  

ggplot_build取得绘图对象,并执行所有必要的步骤   产生可以渲染的对象。该功能输出两个   件:数据帧列表(每层一个)和一个面板   对象,其中包含有关轴限制,中断等的所有信息。

来自?ggplot_gtable

  

此函数将构建显示绘图所需的所有杂点,并且   将它们存储在称为gtable()的特殊数据结构中。这个   您可以通过编程操作来处理对象   (例如)将图例框设为2厘米宽,或将多个图块组合成一个   单一显示,可保留各图的宽高比。

第2层ggplot_buildggplot_gtable都只是在输入到控制台后才返回通用的UseMethod("<function name>",并且实际的相关函数不会从中导出ggplot2软件包。不过,您仍然可以在GitHub(link)上找到它们,或者仍然可以使用三元冒号:::来访问它们。

> ggplot2:::ggplot_build.ggplot
function (plot) 
{
    plot <- plot_clone(plot)
    # ... omitted for space
    layout <- create_layout(plot$facet, plot$coordinates)
    data <- layout$setup(layer_data, plot$data, plot$plot_env)
    # ... omitted for space
    structure(list(data = data, layout = layout, plot = plot), 
        class = "ggplot_built")
}

> ggplot2:::ggplot_gtable.ggplot_built
function (data) 
{
    plot <- data$plot
    layout <- data$layout
    data <- data$data
    theme <- plot_theme(plot)
    # ... omitted for space
    position <- theme$legend.position %||% "right"
    # ... omitted for space
    legend_box <- if (position != "none") {
        build_guides(plot$scales, plot$layers, plot$mapping, 
            position, theme, plot$guides, plot$labels)
    }
    # ... omitted for space
}

我们看到ggplot2:::ggplot_gtable.ggplot_built中有一个代码块似乎可以创建图例框:

    legend_box <- if (position != "none") {
        build_guides(plot$scales, plot$layers, plot$mapping, 
            position, theme, plot$guides, plot$labels)
    }

让我们测试一下是否确实如此:

g.build <- ggplot_build(my_plot)

legend.box <- ggplot2:::build_guides(
  g.build$plot$scales, 
  g.build$plot$layers, 
  g.build$plot$mapping, 
  "right", 
  ggplot2:::plot_theme(g.build$plot), 
  g.build$plot$guides, 
  g.build$plot$labels)

grid::grid.draw(legend.box)

legend box

的确如此。让我们放大以查看ggplot2:::build_guides的作用。

第3层:在ggplot2:::build_guides中,我们看到在处理图例框的位置和对齐方式的某些代码行之后,通过以下方式生成了指南定义(gdefs):一个名为guides_train的函数:

> ggplot2:::build_guides
function (scales, layers, default_mapping, position, theme, guides, 
    labels) 
{
    # ... omitted for space
    gdefs <- guides_train(scales = scales, theme = theme, guides = guides, 
        labels = labels)
    # .. omitted for space
}

像以前一样,我们可以为每个参数插入适当的值,并检查这些指南定义的内容:

gdefs <- ggplot2:::guides_train(
  scales = g.build$plot$scales, 
  theme = ggplot2:::plot_theme(g.build$plot),
  guides = g.build$plot$guides,
  labels = g.build$plot$labels
  )

> gdefs    
[[1]]
$title
expression("Legend name"^2)

$title.position
NULL
#... omitted for space

是的,我们期望有一个秤名称:expression("Legend name"^2)ggplot2:::guides_train(或其中的某些功能)已将其从g.build$plot$<something> / ggplot2:::plot_theme(g.build$plot)中抽出,但我们必须更深入地研究以了解哪种方式。

第4层:在ggplot2:::guides_train中,我们找到了一行代码,这些代码从多个可能的位置之一获取图例标题:

> guides_train
function (scales, theme, guides, labels) 
{
    gdefs <- list()
    for (scale in scales$scales) {
        for (output in scale$aesthetics) {
            guide <- guides[[output]] %||% scale$guide
            # ... omitted for space
            guide$title <- scale$make_title(guide$title %|W|% 
                scale$name %|W|% labels[[output]])
            # ... omitted for space
        }
    }
    gdefs
}

({ggplot2:::%||%ggplot2:::%|W|%是软件包中未导出的函数。它们采用两个值,如果已定义/未放弃则返回第一个值,否则返回第二个。)

Annnnnnnnnnd我们突然间从寻找传奇人物的地方太少而变成了太多。按优先顺序排列:

  1. 如果定义了g.build$plot$guides[["fill"]]并且g.build$plot$guides[["fill"]]$title的值不是waiver()g.build$plot$guides[["fill"]]$title;
  2. 否则,如果g.build$plot$scales$scales[[1]]$guide$title的值不是waiver()g.build$plot$scales$scales[[1]]$guide$title;
  3. 否则,如果g.build$plot$scales$scales[[1]]$name的值不是waiver()g.build$plot$scales$scales[[1]]$name;
  4. 其他:g.build$plot$labels[["fill"]]

通过检查ggplot2:::ggplot_build.ggplot后面的代码,我们还知道g.build$plot与最初输入的my_plot基本相同,因此您可以在上面带有g.build$plot的列表。

旁注:如果ggplot对象存在某种身份危机,并且包含为同一比例定义的多个图例标题,则这是优先级相同的列表。下图:

my_plot

multiple titles for the same scale

摘要:既然我们已经从兔子洞中爬出来了,我们知道根据您为图例定义标题的位置,可以将其存储在相应的位置在您的ggplot对象中。但是,该标题是否真正在情节中可见?取决于您是否还定义了另一个具有更高优先级的标题...

base.plot <- ggplot(df, 
         aes(x = x, y = y, group = group, fill = z )) +
  geom_polygon()

cowplot::plot_grid(
  # plot 1: title defined in guides() overrides titles defined in `scale_...`
  base.plot + ggtitle("1") +
    scale_fill_continuous(
      name = "scale",
      low = "skyblue", high = "orange",
      guide = guide_colorbar(title = "guide in scale")) +
    guides(fill = guide_colorbar(title = "guide")),
  # plot 2: title defined in scale_...'s guide overrides scale_...'s name
  base.plot + ggtitle("2") +
    scale_fill_continuous(
      name = "scale",
      low = "skyblue", high = "orange",
      guide = guide_colorbar(title = "guide in scale")),
  # plot 3: title defined in `scale_...'s name
  base.plot + ggtitle("3") +
    scale_fill_continuous(
      name = "scale",
      low = "skyblue", high = "orange"),
  # plot 4: with no title defined anywhere, defaults to variable name
  base.plot + ggtitle("4") +
    scale_fill_continuous(
      low = "skyblue", high = "orange"),
  nrow = 2
)