【问题标题】:ggplot2: Adding sample size information to x-axis tick labelsggplot2:将样本大小信息添加到 x 轴刻度标签
【发布时间】:2017-02-27 09:28:54
【问题描述】:

这个问题与 Create custom geom to compute summary statistics and display them *outside* the plotting region (注意:所有功能都已简化;不对正确的对象类型、NA 等进行错误检查)

在基础 R 中,创建一个生成带状图的函数非常容易,其样本大小在分组变量的每个级别下方指示:您可以使用 mtext() 函数添加样本大小信息:

stripchart_w_n_ver1 <- function(data, x.var, y.var) {
    x <- factor(data[, x.var])
    y <- data[, y.var]
# Need to call plot.default() instead of plot because 
# plot() produces boxplots when x is a factor.
    plot.default(x, y, xaxt = "n",  xlab = x.var, ylab = y.var)
    levels.x <- levels(x)
    x.ticks <- 1:length(levels(x))
    axis(1, at = x.ticks, labels = levels.x)
    n <- sapply(split(y, x), length)
    mtext(paste0("N=", n), side = 1, line = 2, at = x.ticks)
}

stripchart_w_n_ver1(mtcars, "cyl", "mpg")

或者您可以使用axis() 函数将样本量信息添加到 x 轴刻度标签:

stripchart_w_n_ver2 <- function(data, x.var, y.var) {
    x <- factor(data[, x.var])
    y <- data[, y.var]
# Need to set the second element of mgp to 1.5 
# to allow room for two lines for the x-axis tick labels.
    o.par <- par(mgp = c(3, 1.5, 0))
    on.exit(par(o.par))
# Need to call plot.default() instead of plot because 
# plot() produces boxplots when x is a factor.
    plot.default(x, y, xaxt = "n", xlab = x.var, ylab = y.var)
    n <- sapply(split(y, x), length)
    levels.x <- levels(x)
    axis(1, at = 1:length(levels.x), labels = paste0(levels.x, "\nN=", n))
}

stripchart_w_n_ver2(mtcars, "cyl", "mpg")

虽然这在基础 R 中是一项非常简单的任务,但在 ggplot2 中却非常复杂,因为很难获取用于生成绘图的数据,而且虽然有与 axis() 等效的函数(例如, scale_x_discrete 等)没有与 mtext() 等效的方法,可让您轻松地将文本放置在边距内的指定坐标处。

我尝试使用内置的stat_summary() 函数来计算样本大小(即fun.y = "length"),然后将该信息放在 x 轴刻度标签上,但据我所知,你不能提取样本大小,然后使用函数scale_x_discrete() 以某种方式将它们添加到 x 轴刻度标签,您必须告诉stat_summary() 您希望它使用什么几何图形。你可以设置geom="text",但是你必须提供标签,关键是标签应该是样本大小的值,这是stat_summary()正在计算但你无法得到的(和您还必须指定要放置文本的位置,而且很难确定放置文本的位置,使其位于 x 轴刻度标签的正下方)。

小插图“扩展 ggplot2”(http://docs.ggplot2.org/dev/vignettes/extending-ggplot2.html) 向您展示了如何创建自己的 stat 函数,让您可以直接获取数据,但问题是您始终必须定义一个 geom 以配合您的 stat功能(即ggplot 认为您想在绘图内绘制此信息,而不是在边缘);据我所知,您不能获取您在自定义统计函数中计算的信息,也不能在绘图区域中绘制任何内容,而是将信息传递给像 scale_x_discrete() 这样的比例函数。这是我尝试这样做的方法;我能做的最好的就是将样本量信息放在每组的最小值 y 处:

StatN <- ggproto("StatN", Stat,
    required_aes = c("x", "y"), 
    compute_group = function(data, scales) {
    y <- data$y
    y <- y[!is.na(y)]
    n <- length(y)
    data.frame(x = data$x[1], y = min(y), label = paste0("n=", n))
    }
)

stat_n <- function(mapping = NULL, data = NULL, geom = "text", 
    position = "identity", inherit.aes = TRUE, show.legend = NA, 
        na.rm = FALSE, ...) {
    ggplot2::layer(stat = StatN, mapping = mapping, data = data, geom = geom, 
        position = position, inherit.aes = inherit.aes, show.legend = show.legend, 
        params = list(na.rm = na.rm, ...))
}

ggplot(mtcars, aes(x = factor(cyl), y = mpg)) + geom_point() + stat_n()

我以为我已经解决了这个问题,只需为ggplot 创建一个包装函数:

ggstripchart <- function(data, x.name, y.name,  
    point.params = list(), 
    x.axis.params = list(labels = levels(x)), 
    y.axis.params = list(), ...) {
    if(!is.factor(data[, x.name]))
    data[, x.name] <- factor(data[, x.name])
    x <- data[, x.name]
    y <- data[, y.name]
    params <- list(...)
    point.params    <- modifyList(params, point.params)
    x.axis.params   <- modifyList(params, x.axis.params)
    y.axis.params   <- modifyList(params, y.axis.params)

    point <- do.call("geom_point", point.params)

    stripchart.list <- list(
        point, 
        theme(legend.position = "none")
    )

    n <- sapply(split(y, x), length)
    x.axis.params$labels <- paste0(x.axis.params$labels, "\nN=", n)
    x.axis <- do.call("scale_x_discrete", x.axis.params)
    y.axis <- do.call("scale_y_continuous", y.axis.params)
    stripchart.list <- c(stripchart.list, x.axis, y.axis)           

    ggplot(data = data, mapping = aes_string(x = x.name, y = y.name)) + stripchart.list
}


ggstripchart(mtcars, "cyl", "mpg")

但是,此功能无法与刻面一起正常工作。例如:

ggstripchart(mtcars, "cyl", "mpg") + facet_wrap(~am)

显示每个方面的两个方面的样本大小。我必须在包装函数中构建分面,这与尝试使用 ggplot 必须提供的所有内容的意义相悖。

如果有人对此问题有任何见解,我将不胜感激。非常感谢您的宝贵时间!

【问题讨论】:

    标签: r ggplot2 ggproto


    【解决方案1】:

    我已更新EnvStats 包以包含一个名为 stat_n_textstat,它将在每个唯一 x 值下方添加样本大小(唯一 y 值的数量)。有关更多信息和示例列表,请参阅help file for stat_n_text。下面是一个简单的例子:

    library(ggplot2)
    library(EnvStats)
    
    p <- ggplot(mtcars, 
      aes(x = factor(cyl), y = mpg, color = factor(cyl))) + 
      theme(legend.position = "none")
    
    p + geom_point() + 
      stat_n_text() + 
      labs(x = "Number of Cylinders", y = "Miles per Gallon")
    

    【讨论】:

    • 嗨史蒂夫,有没有办法删除“n=”?我只想显示数字。
    【解决方案2】:

    如果您关闭剪裁,您可以使用geom_text 打印 x 轴标签下方的计数,但您可能需要调整位置。我在下面的代码中包含了一个“微调”参数。此外,以下方法适用于所有分面(如果有)都是列分面的情况。

    我知道您最终希望代码能够在新的 geom 中工作,但也许下面的示例可以适应在 geom 中使用。

    library(ggplot2)
    library(dplyr)
    
    pgg = function(dat, x, y, facet=NULL, nudge=0.17) {
    
      # Convert x-variable to a factor
      dat[,x] = as.factor(dat[,x])
    
      # Plot points
      p = ggplot(dat, aes_string(x, y)) +
        geom_point(position=position_jitter(w=0.3, h=0)) + theme_bw() 
    
      # Summarise data to get counts by x-variable and (if present) facet variables
      dots = lapply(c(facet, x), as.symbol)
      nn = dat %>% group_by_(.dots=dots) %>% tally
    
      # If there are facets, add them to the plot
      if (!is.null(facet)) {
        p = p + facet_grid(paste("~", paste(facet, collapse="+")))
      }
    
      # Add counts as text labels
      p = p + geom_text(data=nn, aes(label=paste0("N = ", nn$n)),
                        y=min(dat[,y]) - nudge*1.05*diff(range(dat[,y])), 
                        colour="grey20", size=3.5) +
        theme(axis.title.x=element_text(margin=unit(c(1.5,0,0,0),"lines")))
    
      # Turn off clipping and return plot
      p <- ggplot_gtable(ggplot_build(p))
      p$layout$clip[p$layout$name=="panel"] <- "off"
      grid.draw(p)
    
    }
    
    pgg(mtcars, "cyl", "mpg")
    pgg(mtcars, "cyl", "mpg", facet=c("am","vs"))
    

    另一个可能更灵活的选项是将计数添加到绘图面板的底部。例如:

    pgg = function(dat, x, y, facet_r=NULL, facet_c=NULL) {
    
      # Convert x-variable to a factor
      dat[,x] = as.factor(dat[,x])
    
      # Plot points
      p = ggplot(dat, aes_string(x, y)) +
        geom_point(position=position_jitter(w=0.3, h=0)) + theme_bw() 
    
      # Summarise data to get counts by x-variable and (if present) facet variables
      dots = lapply(c(facet_r, facet_c, x), as.symbol)
      nn = dat %>% group_by_(.dots=dots) %>% tally
    
      # If there are facets, add them to the plot
      if (!is.null(facet_r) | !is.null(facet_c)) {
    
        facets = paste(ifelse(is.null(facet_r),".",facet_r), " ~ " , 
                       ifelse(is.null(facet_c),".",facet_c))
    
        p = p + facet_grid(facets)
      }
    
      # Add counts as text labels
      p + geom_text(data=nn, aes(label=paste0("N = ", nn$n)),
                    y=min(dat[,y]) - 0.15*min(dat[,y]), colour="grey20", size=3) +
        scale_y_continuous(limits=range(dat[,y]) + c(-0.1*min(dat[,y]), 0.01*max(dat[,y])))
    }
    
    pgg(mtcars, "cyl", "mpg")
    pgg(mtcars, "cyl", "mpg", facet_c="am")
    pgg(mtcars, "cyl", "mpg", facet_c="am", facet_r="vs")
    

    【讨论】:

    • 非常感谢您的帮助!根据您的第二个建议,在我发布我的问题后,我已经弄清楚如何将样本大小放在绘图面板的底部。我几乎已经完成了新的统计函数和几何图形,它们可以满足我的需求,并将它们合并到我的 EnvStats 包的下一个版本中(当我这样做时会在这里发布)。再次感谢您的帮助和建议!
    【解决方案3】:

    我的解决方案可能有点简单,但效果很好。

    举个例子,我从创建标签开始 使用paste\n

    mtcars2 <- mtcars %>% 
      group_by(cyl, am) %>% mutate(n = n()) %>% 
      mutate(label = paste0(cyl,'\nN = ',n))
    

    然后我在 ggplot 代码中使用这些标签而不是 cyl

    ggplot(mtcars2,
       aes(x = factor(label), y = mpg, color = factor(label))) + 
      geom_point() + 
      xlab('cyl') + 
      facet_wrap(~am, scales = 'free_x') +
      theme(legend.position = "none")
    

    制作如下图所示的东西。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-06-04
      • 1970-01-01
      • 2016-02-05
      • 1970-01-01
      • 2014-10-07
      • 2011-03-28
      相关资源
      最近更新 更多