直方图:在 ggplot2 中组合连续值和离散值

Histogram: Combine continuous and discrete values in ggplot2

我想在直方图上绘制一组时间。 玩具示例:

df <- data.frame(time = c(1,2,2,3,4,5,5,5,6,7,7,7,9,9, ">10"))

问题是一个值是“>10”,是指观察到超过10秒的次数。其他时间点都是以实际时间为准的数字。现在,我想创建一个直方图,将所有数字视为数字,并在适当的时候将它们组合在箱子中,同时在分布的一侧绘制“>10”的计数,但不在单独的图中。我曾尝试调用 geom_histogram 两次,一次使用连续数据,一次使用单独列中的离散数据,但这给了我以下错误:

Error: Discrete value supplied to continuous scale

很高兴听到建议!

或许,这就是您要找的:

df1 <- data.frame(x=sample(1:12,50,rep=T))

df2 <- df1 %>%  group_by(x) %>% 
        dplyr::summarise(y=n()) %>% subset(x<11)

df3 <- subset(df1, x>10) %>% dplyr::summarise(y=n()) %>% mutate(x=11)

df <- rbind(df2,df3 )
label <- ifelse((df$x<11),as.character(df$x),">10")
  
p <- ggplot(df, aes(x=x,y=y,color=x,fill=x)) + 
  geom_bar(stat="identity", position = "dodge") +
  scale_x_continuous(breaks=df$x,labels=label) 
p

你得到以下输出:

请注意,有时您可能会缺少一些条形图,具体取决于样本。

这是一种复杂的解决方案,但我相信它能最好地回答您的问题,即您希望在典型的直方图旁边放置一个代表“>10”值(或 non-numeric). 至关重要的是,您要确保保持与直方图关联的“分箱”,这意味着您不希望简单地使您的比例尺成为离散比例尺并用典型的直方图表示条形图。

数据

既然你想保留直方图特征,我将使用一个比你给我们的更复杂的示例数据集。我将指定一个均匀分布 (n=100),其中包含 20 个“>10”值。

set.seed(123)
df<- data.frame(time=c(runif(100,0,10), rep(">10",20)))

如前所述,df$time 是一个字符向量,但对于直方图,我们需要它是数字。我们只是将其强制为数字并接受“>10”值将被强制为 NA。这很好,因为最后我们只是要计算这些 NA 值并用条形图表示它们。当我这样做的时候,我正在创建 df 的一个子集,它将用于使用 count() 函数创建代表我们的 NAs (">10") 的条,returns 由一行和一列组成的数据框:在本例中为 df$n = 20

library(dplyr)
df$time <- as.numeric(df$time)  #force numeric and get NA for everything else
df_na <- count(subset(df, is.na(time)))

情节

对于实际绘图,您要求创建 (1) 直方图和 (2) 条形图的组合。这些不是同一个图,但更重要的是,它们 不能 共享同一个轴,因为根据定义,直方图需要一个连续的轴,而“NA”值或“>10”不是numeric/continuous 值。这里的解决方案是制作两个单独的地块,然后通过 cowplot.

将它们结合起来。

直方图的创建非常容易。我正在保存垃圾箱的数量以供稍后演示。这是基本情节:

bin_num <- 12  # using this later

p1 <- ggplot(df, aes(x=time)) + theme_classic() +
  geom_histogram(color='gray25', fill='blue', alpha=0.3, bins=bin_num)

感谢之前的子集化,NA 值的条形图也很简单:

p2 <- ggplot(df_na, aes(x=">10", y=n)) + theme_classic() +
  geom_col(color='gray25', fill='red', alpha=0.3)

哎呀!这看起来很糟糕,但请耐心等待。

将它们拼接在一起

你可以简单地 运行 plot_grid(p1, p2) 并且你得到了一些可行的东西......但是还有很多不足之处:

这里有问题。我将列举它们,然后向您展示我如何处理它们的最终代码:

  1. 需要从 NA 条形图中删除一些元素。即,y 轴完全和 x 轴的标题(但它不能是 NULL 或 x 轴不会正确排列)。这些 theme() 元素可以通过 ggplot.

    轻松删除
  2. NA 条形图占用了太多空间。需要减少宽度。我们通过访问 plot_grid()rel_widths= 参数来解决这个问题。简单易行。

  3. 我们怎么知道怎么设置y刻度上限呢?这有点复杂,因为它将取决于 p1..count.. 统计数据以及 NA 值的数量。您可以 access the maximum count for a histogram 使用 ggplot_build(),它是 ggplot2.

    的一部分

因此,最终代码需要创建基本的 p1p2 图,然后添加它们以修复限制。我还向 p1 添加了 bin 数量的注释,以便我们可以跟踪上限设置的效果。这是代码和一些示例图,其中 bin_num 分别设置为 12 和 5:

# basic plots
p1 <- ggplot(df, aes(x=time)) + theme_classic() +
  geom_histogram(color='gray25', fill='blue', alpha=0.3, bins=bin_num)

p2 <- ggplot(df_na, aes(x=">10", y=n)) + theme_classic() +
  geom_col(color='gray25', fill='red', alpha=0.3) +
  labs(x="") +  theme(axis.line.y=element_blank(), axis.text.y=element_blank(),
    axis.title.y=element_blank(), axis.ticks.y=element_blank()
  ) +
  scale_x_discrete(expand=expansion(add=1))

#set upper y scale limit
max_count <- max(c(max(ggplot_build(p1)$data[[1]]$count), df_na$n))

# fix limits for plots
p1 <- p1 + scale_y_continuous(limits=c(0,max_count), expand=expansion(mult=c(0,0.15))) +
  annotate('text', x=0, y=max_count, label=paste('Bins:', bin_num))  # for demo purposes
p2 <- p2 + scale_y_continuous(limits=c(0,max_count), expand=expansion(mult=c(0,0.15)))

plot_grid(p1, p2, rel_widths=c(1,0.2))

所以,我们的上限修复成功了。您可以真正疯狂地使用定位等和 plot_grid() 函数,但我认为它以这种方式工作得很好。