从头开始创建geom / stat

时间:2018-09-27 14:24:46

标签: r ggplot2 boxplot ggproto

不久前我才刚开始使用R,现在我正在尝试增强我的可视化技能。我要做的是用平均钻石作为顶层创建框线图(请参阅下面链接中的图片)。我还没有找到执行此操作的函数,所以我想我必须自己创建它。

Link: Boxplot and mean diamonds

我希望做的是创建一个几何图形或统计数据,以使类似这样的事情起作用:

ggplot(data, aes(...))) + 
   geom_boxplot(...) +
   geom_meanDiamonds(...)

我不知道从哪里开始构建这个新功能。我知道平均菱形(均值和置信区间)需要哪些值,但是我不知道如何构建从ggplot()获取数据的geom / stat,计算每个组的均值和CI,以及在每个箱形图的顶部绘制平均菱形。

我已经搜索了有关如何从头开始构建这些类型的函数的详细说明,但是,我没有发现任何真正从底层开始的内容。如果有人可以向我指出一些有用的指南,我将不胜感激。

谢谢!

1 个答案:

答案 0 :(得分:4)

我目前正在学习自己编写Geoms,因此在我思考过程中,从Stats方面解开Geom方面(创建多边形和线段)(计算),这将是一个相当漫长而漫不经心的文章。这些多边形和线段应该在哪里)。

免责声明:我对这种情节并不熟悉,并且Google并没有提出很多权威性指南。我对此处如何计算/使用置信区间的理解可能不正确。

第0步。了解geom / stat和图层功能之间的关系。

geom_boxplotstat_boxplot是图层功能的示例。如果将它们输入R控制台,则会看到它们相对较短,并且不包含用于计算箱线图箱形/晶须的实际代码。相反,geom_boxplot包含一行geom = GeomBoxplot,而stat_boxplot包含一行stat = StatBoxplot(如下所示)。

> stat_boxplot
function (mapping = NULL, data = NULL, geom = "boxplot", position = "dodge2", 
    ..., coef = 1.5, na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
{
    layer(data = data, mapping = mapping, stat = StatBoxplot, 
        geom = geom, position = position, show.legend = show.legend, 
        inherit.aes = inherit.aes, params = list(na.rm = na.rm, 
            coef = coef, ...))
}

GeomBoxplotStatBoxplot是ggproto对象。它们就是神奇的地方。

步骤1。确认ggproto()的{​​{1}}参数是您的朋友。

不要重新发明轮子。由于我们要创建与箱形图很好地重叠的内容,因此我们可以从用于该内容的Geom / Stat中引用,仅更改必要的内容。

_inherit

第2步。修改统计信息。

StatBoxplot中定义了3个功能:StatMeanDiamonds <- ggproto( `_class` = "StatMeanDiamonds", `_inherit` = StatBoxplot, ... # add functions here to override those defined in StatBoxplot ) GeomMeanDiamonds <- ggproto( `_class` = "GeomMeanDiamonds", `_inherit` = GeomBoxplot, ... # as above ) setup_datasetup_params。您可以参考Github(上面的链接)上的代码以获取详细信息,或通过输入例如compute_group来查看它们。

StatBoxplot$compute_group函数为与每个组关联的所有y值(即每个唯一的x值)计算ymin /下/中/上/ ymax值,用于绘制箱形图。我们可以用一个代替它的方法来计算其置信区间和平均值:

compute_group

(可选)StatBoxplot规定用户可以将# ci is added as a parameter, to allow the user to specify different confidence intervals compute_group_new <- function(data, scales, width = NULL, ci = 0.95, na.rm = FALSE){ a <- mean(data$y) s <- sd(data$y) n <- sum(!is.na(data$y)) error <- qt(ci + (1-ci)/2, df = n-1) * s / sqrt(n) stats <- c("lower" = a - error, "mean" = a, "upper" = a + error) if(length(unique(data$x)) > 1) width <- diff(range(data$x)) * 0.9 df <- as.data.frame(as.list(stats)) df$x <- if(is.factor(data$x)) data$x[1] else mean(range(data$x)) df$width <- width df } 作为美观映射。我们也可以通过替换:

weight

具有:

  a <- mean(data$y)
  s <- sd(data$y)
  n <- sum(!is.na(data$y))

无需更改StatBoxplot中的其他功能。因此,我们可以如下定义StatMeanDiamonds:

  if(!is.null(data$weight)) {
    a <- Hmisc::wtd.mean(data$y, weights = data$weight)
    s <- sqrt(Hmisc::wtd.var(data$y, weights = data$weight))
    n <- sum(data$weight[!is.na(data$y) & !is.na(data$weight)])
  } else {
    a <- mean(data$y)
    s <- sd(data$y)
    n <- sum(!is.na(data$y))
  }

第3步。修改几何。

GeomBoxplot具有3个功能:StatMeanDiamonds <- ggproto( `_class` = "StatMeanDiamonds", `_inherit` = StatBoxplot, compute_group = compute_group_new ) setup_datadraw_group。它还包括draw_keydefault_aes()的定义。

由于我们已经更改了上游数据源(StatMeanDiamonds生成的数据包含计算出的列“ lower” /“ mean” /“ upper”,而StatBoxplot生成的数据将包含计算出的列“ ymin” / (“下” /“中” /“上” /“ ymax”),请检查下游required_aes()功能是否也受到影响。 (在这种情况下,setup_data未引用受影响的列,因此此处无需进行任何更改。)

GeomBoxplot$setup_data函数接收由StatMeanDiamonds生成并由draw_group设置的数据,并生成多个数据帧。 “公用”包含所有几何图形共有的美学映射。 “ diamond.df”表示有助于钻石多边形的映射,“ segment.df”表示有助于平均水平线段的映射。然后将数据帧分别传递到GeomPolygon和GeomSegment的setup_data函数,以生成实际的多边形/线段。

draw_panel

draw_group_new = function(data, panel_params, coord, varwidth = FALSE){ common <- data.frame(colour = data$colour, size = data$size, linetype = data$linetype, fill = alpha(data$fill, data$alpha), group = data$group, stringsAsFactors = FALSE) diamond.df <- data.frame(x = c(data$x, data$xmax, data$x, data$xmin), y = c(data$upper, data$mean, data$lower, data$mean), alpha = data$alpha, common, stringsAsFactors = FALSE) segment.df <- data.frame(x = data$xmin, xend = data$xmax, y = data$mean, yend = data$mean, alpha = NA, common, stringsAsFactors = FALSE) ggplot2:::ggname("geom_meanDiamonds", grid::grobTree( GeomPolygon$draw_panel(diamond.df, panel_params, coord), GeomSegment$draw_panel(segment.df, panel_params, coord) )) } 函数用于在需要时为该层创建图例。由于GeomMeanDiamonds继承自GeomBoxplot,因此默认值为draw_key,因此我们没有对其进行更改。保持不变不会破坏代码。但是,我认为诸如draw_key = draw_key_boxplot之类的简单图例显示的外观不太混乱。

GeomBoxplot的draw_key_polygon规范看起来不错。但是我们需要更改default_aes,因为我们期望从StatMeanDiamonds获得的数据是不同的(“下” /“中” /“上”,而不是“ ymin” /“下” /“中” /“上” “ /” ymax“)。

我们现在准备定义GeomMeanDiamonds:

required_aes

第4步。定义图层功能。

这是无聊的部分。我直接从GeomMeanDiamonds <- ggproto( "GeomMeanDiamonds", GeomBoxplot, draw_group = draw_group_new, draw_key = draw_key_polygon, required_aes = c("x", "lower", "upper", "mean") ) / geom_boxplot复制,删除了对stat_boxplot中离群值的所有引用,更改为geom_meanDiamonds / geom = GeomMeanDiamonds,并向其中添加了stat = StatMeanDiamonds ci = 0.95

stat_meanDiamonds

第5步。检查输出。

geom_meanDiamonds <- function(mapping = NULL, data = NULL,
                              stat = "meanDiamonds", position = "dodge2",
                              ..., varwidth = FALSE, na.rm = FALSE, show.legend = NA,
                              inherit.aes = TRUE){
  if (is.character(position)) {
    if (varwidth == TRUE) position <- position_dodge2(preserve = "single")
  } else {
    if (identical(position$preserve, "total") & varwidth == TRUE) {
      warning("Can't preserve total widths when varwidth = TRUE.", call. = FALSE)
      position$preserve <- "single"
    }
  }
  layer(data = data, mapping = mapping, stat = stat,
        geom = GeomMeanDiamonds, position = position,
        show.legend = show.legend, inherit.aes = inherit.aes,
        params = list(varwidth = varwidth, na.rm = na.rm, ...))
}

stat_meanDiamonds <- function(mapping = NULL, data = NULL,
                         geom = "meanDiamonds", position = "dodge2",
                         ..., ci = 0.95,
                         na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) {
  layer(data = data, mapping = mapping, stat = StatMeanDiamonds,
        geom = geom, position = position, show.legend = show.legend,
        inherit.aes = inherit.aes,
        params = list(na.rm = na.rm, ci = ci, ...))
}

plot