【问题标题】:ggplot2 geom_point with binned x-axis for binary dataggplot2 geom_point 与二进制数据的分箱 x 轴
【发布时间】:2015-07-06 09:58:06
【问题描述】:

我正在尝试为二进制数据创建一个带有分箱 x 轴的散点图。当我将geom_point 与二进制 y 一起使用时,该图毫无用处(见图 1)。如图 2 所示,我想根据 x 轴的值对数据进行分箱,然后使用 geom_point 绘制每个箱内的 avg x 和 avg y(将每个箱中的 obs 数量映射到大小点)。我可以通过聚合数据来做到这一点,但我想知道 ggplot 是否可以直接做到这一点。我玩过stat_bindot 等,但找不到解决方案。有任何想法吗?下面是一些代码。

谢谢!

# simulate data
n=1000
y=rbinom(n,1,0.5)
x=runif(n)
data=data.frame(x,y)

# figure 1 - geom_point with binary data, pretty useless!
ggplot(data,aes(x=x,y=y)) + geom_point() + ylim(0,1)

# let's create an aggregated dataset with bins
bin=cut(data$x,seq(0,1,0.05))
# I am sure the aggregation can be done in a better way...
data.bin=aggregate(data,list(bin),function(x) { return(c(mean(x),length(x)))})

# figure 2 - geom_point with binned x-axis, much nicer!
ggplot(data.bin,aes(x=x[,1],y=y[,1],size=x[,2])) + geom_point() + ylim(0,1)

图 1 和图 2:

【问题讨论】:

  • 在 ggplot2 中没有直接的方法可以做到这一点。您的代码看起来很简单。

标签: r ggplot2


【解决方案1】:

为此我编写了一个新的 Stat 函数。

它接受nbinsbin_varbin_funsummary_fun 作为参数,所有四个都是默认值。

  • nbins 的默认值取决于数据点的数量。
  • bin_var 的默认值为“x”。您也可以将其设置为“y”。这指定了馈送到bin_fun 的变量。
  • bin_fun 是分箱功能。默认情况下,它是seq_cut,我为此编写了它。您也可以编写自己的分箱函数。它只需要将 data 和 nbins 作为参数。
  • summary_fun 是用于聚合 bin 的汇总函数。默认情况下,它是mean。您还可以使用 fun.xfun.y 分别为 x 和 y 指定聚合函数。
  • 如果使用以yminymax为美学的geom,还可以指定fun.yminfun.ymax

请注意,如果您指定 aes(group = your_bins),bin_fun 将被忽略,而是使用分组变量。另请注意,它将创建一个计数变量,可以作为..count.. 访问。

在你的情况下,你可以这样使用它:

p <- ggplot(data, aes(x, y)) +
  geom_point(aes(size = ..count..), stat = "binner") +
  ylim(0, 1)

在这种情况下不是很有用(尽管这证明了同方差性并且方差在 0.25 左右,符合 Bern(0.5) 变量的假设),但仅用于示例:

p + geom_linerange(stat = "binner",
                   fun.ymin = function(y) mean(y) - var(y) / 2,
                   fun.ymax = function(y) mean(y) + var(y) / 2)

代码:

library(proto)

stat_binner <- function (mapping = NULL, data = NULL, geom = "point", position = "identity", ...) {
  StatBinner$new(mapping = mapping, data = data, geom = geom, position = position, ...)
}

StatBinner <- proto(ggplot2:::Stat, {
  objname <- "binner"

  default_geom <- function(.) GeomPoint
  required_aes <- c("x", "y")

  calculate_groups <- function(., data, scales, bin_var = "x", nbins = NULL, bin_fun = seq_cut, summary_fun = mean,
                       fun.data = NULL, fun.y = NULL, fun.ymax = NULL, fun.ymin = NULL,
                       fun.x = NULL, fun.xmax = NULL, fun.xmin = NULL, na.rm = FALSE, ...) {
    data <- remove_missing(data, na.rm, c("x", "y"), name = "stat_binner")

    # Same rules as binnedplot in arm package
    n <- nrow(data)
    if (is.null(nbins)) {
      nbins <- if (n >= 100) floor(sqrt(n))
              else if (n > 10 & n < 100) 10
              else floor(n/2)
    }

    if (length(unique(data$group)) == 1) {
      data$group <- bin_fun(data[[bin_var]], nbins)
    }

    if (!missing(fun.data)) {
      # User supplied function that takes complete data frame as input
      fun.data <- match.fun(fun.data)
      fun <- function(df, ...) {
        fun.data(df$y, ...)
      }
    } else {
      if (!is.null(summary_fun)) {
        if (!is.null(fun.x)) message("fun.x overriden by summary_fun")
        if (!is.null(fun.y)) message("fun.y overriden by summary_fun")
        fun.x <- fun.y <- summary_fun
      }

      # User supplied individual vector functions
      fs_x <- compact(list(xmin = fun.x, x = fun.x, xmax = fun.xmax))
      fs_y <- compact(list(ymin = fun.ymin, y = fun.y, ymax = fun.ymax))

      fun <- function(df, ...) {
        res_x <- llply(fs_x, function(f) do.call(f, list(df$x, ...)))
        res_y <- llply(fs_y, function(f) do.call(f, list(df$y, ...)))
        names(res_y) <- names(fs_y)
        names(res_x) <- names(fs_x)
        as.data.frame(c(res_y, res_x))
      }
    }
    summarise_by_x_and_y(data, fun, ...)
  }


})

summarise_by_x_and_y <- function(data, summary, ...) {
  summary <- ddply(data, "group", summary, ...)
  count <- ddply(data, "group", summarize, count = length(y))

  unique <- ddply(data, "group", ggplot2:::uniquecols)
  unique$y <- NULL
  unique$x <- NULL

  res <- merge(merge(summary, unique, by = "group"), count, by = "group")

  # Necessary for, eg, colour aesthetics
  other_cols <- setdiff(names(data), c(names(summary), names(unique)))
  if (length(other_cols) > 0) {
    other <- ddply(data[, c(other_cols, "group")], "group", numcolwise(mean))
    res <- merge(res, other, by = "group")
  }

  res
}


seq_cut <- function(x, nbins) {
  bins <- seq(min(x), max(x), length.out = nbins)
  findInterval(x, bins, rightmost.closed = TRUE)
}

【讨论】:

    【解决方案2】:

    正如@Kohske 所说,ggplot2 中没有直接的方法可以做到这一点;您必须预先汇总数据并将其传递给ggplot。您的方法有效,但我会稍有不同,使用 plyr 包而不是 aggregate

    library("plyr")
    data$bin <- cut(data$x,seq(0,1,0.05))
    data.bin <- ddply(data, "bin", function(DF) {
      data.frame(mean=numcolwise(mean)(DF), length=numcolwise(length)(DF))
    })
    ggplot(data.bin,aes(x=mean.x,y=mean.y,size=length.x)) + geom_point() + 
      ylim(0,1)
    

    在我看来,优点是您可以通过这种方式获得一个名称更好的简单数据框,而不是某些列是矩阵的数据框。但这可能是个人风格问题,而不是正确性问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-07-31
      • 2016-05-10
      • 2020-07-31
      • 2018-11-11
      • 2021-12-06
      • 1970-01-01
      • 2014-04-12
      • 1970-01-01
      相关资源
      最近更新 更多