使用ggplot绘制条形图,分类顺序依赖于facet的顺序。

24

我看到许多问题(通常与在ggplot2条形图中排序酒吧链接)关于如何(重新)排序条形图中的类别。

我所追求的略有不同,但我还没有找到一个好的方法来实现它:我有一个多面条形图,并且我想要独立地为每个面按另一个变量(在我的例子中,该变量只是y值本身,即我只希望在每个面中以递增长度放置酒吧)对x轴进行排序。

以下是一个简单的示例,遵循例如在ggplot2条形图中排序酒吧

df <- data.frame(name=c('foo','bar','foo','bar'),period=c('old','old','recent','recent'),val=c(1.23,2.17,4.15,3.65))
p = ggplot(data = df, aes(x = reorder(name, val), y = val))
p = p + geom_bar(stat='identity')
p = p + facet_grid(~period)
p

我们得到的是以下内容: 在此输入图片描述

而我想要的是: 在此输入图片描述


12
哦我的天啊!你是在写《如何撒谎使用统计数据》的后续吗? - John
2
唯一的方法是制作单独的图形,然后使用gridExtra包中的grid.arrange。但我同意这通常不会得到一个非常好的图形。(在ggplot中你会发现很多这样的情况;如果某些事情真的很难做,那可能是因为它试图阻止你做一些愚蠢的事情。并非总是如此,但大多数情况下是这样的...) - joran
是的,谢谢,虽然不是特别有用,但还是谢谢。在我们使用它的上下文中,这是一个重要的情节,类别的排序非常有意义。在这里,我简化成了一个最简单的例子,但在我们的应用程序中,我们根据它们的实际可加性对大约十几个信号进行排序,如果某个方面的柱状图到处乱跑将是不可接受的。 - Pierre D
1
我理解你的动机,只是大多数人误解了为什么要设计 facets。它们明确用于当每个面板共享相同比例时。有些情况下,您可能希望有几个图形不共享公共比例,但这时 faceting 不是正确的工具。您基本上在谈论多个单独的图形,因此需要使用 grid.arrange。但是大多数人只是假设 faceting = 排列多个通常相似的图形。 - joran
3
说实话,“离散比例尺”的分类顺序(例如,按字母顺序或按y的平均值排序)在某种程度上是任意的,因此,多个细分面必须共享相同的分类比例尺的想法对我来说有点人为。在我看来,更有意义的做法是决定x按某个指标进行排名,并让标签在每个细分面中自行确定位置,尽管它显示类别。在这种意义下,跨越所有细分面共享的公共比例尺就是数值指标。这有点像在散点图中绘制文本标签。 - Pierre D
4个回答

25

好的,撇开所有的哲学思辨不谈,如果有人感兴趣的话,这里有一个丑陋的技巧来实现它。思路是使用不同的标签(类似于paste(period, name),只是我将句号替换为0空格、1空格等,以便它们不会显示)。因为我可能想共享一个公共的图例等原因,所以我需要这个绘图,而且我不想安排各种grobs之类的东西。

之前给出的原子示例变成了:

df <- data.frame(name=c('foo','bar','foo','bar'),
  period=c('old','old','recent','recent'),
  val=c(1.23,2.17,4.15,3.65),
  stringsAsFactors=F)
df$n = as.numeric(factor(df$period))
df = ddply(df,.(period,name),transform, x=paste(c(rep(' ',n-1), name), collapse=''))
df$x = factor(df$x, levels=df[order(df$val), 'x'])
p = ggplot(data = df, aes(x = x, y = val))
p = p + geom_bar(stat='identity')
p = p + facet_grid(~period, scale='free_x')
p

输入图像描述 另一个例子,虽然有点傻但更接近于我的实际用例,是:

df <- ddply(mpg, .(year, manufacturer), summarize, mixmpg = mean(cty+hwy))
df$manufacturer = as.character(df$manufacturer)
df$n = as.numeric(factor(df$year))
df = ddply(df, .(year,manufacturer), transform,
     x=paste(c(rep(' ',n-1), manufacturer), collapse=''))
df$x = factor(df$x, levels=df[order(df$mixmpg), 'x'])
p = ggplot(data = df, aes(x = x, y = mixmpg))
p = p + geom_bar(stat='identity')
p = p + facet_grid(~year, scale='free_x')
p = p + theme(axis.text.x=element_text(angle=90,hjust=1,vjust=.5,colour='gray50'))
p

在此输入图像描述 闭上眼睛,想象帝国,并尽情享受。


我增加了答案,因为我认为它很酷,可以不使用grid.arrange完成。但是,我仍然相信这可能非常棘手,因为我们对分组图的期望是在各个方面上分类别将被以相同的方式排列。这可能是一种固有或历史性期望,但无论如何期望是存在的,并且违反它可能会误导。 - Tyler Rinker
我完全同意@TylerRinker的观点,并相应地投票。另一个选择(在我看来)可能会更少令人困惑,那就是完全禁用轴标签,只使用填充美学(如果只有几个条形图),或者在每个条形图上方的图表内标记它们。 - joran
谢谢。实际上,您建议将x作为排名(这是一个一致的数字值),并在每个条形图内部某处绘制类别文本,而不是作为标签。如果某些类别的条形图很小,这可能会成为问题,但我始终欢迎不同的意见。也许您可以举个例子,例如使用“mpg”数据,以便我们可以看到它的外观。作为Tufte的信徒,使用条形图不会是我的首选,但它符合Tyler所说的“历史预期”(在这种情况下,是我们公司的预期)... - Pierre D

10
这是一个旧问题,但它被用作重复目标。因此,建议使用ggplot2软件包的最新增强功能,即在scale_x_discrete()中使用labels参数来提供解决方案。这避免了使用重复级别(已弃用)或通过添加不同数量的空格来操作因子标签

准备数据

在这里,使用mpg数据集进行比较此答案。对于数据操作,此处使用data.table软件包,但可以随意使用您喜欢的任何软件包来完成此操作。
library(data.table)   # version 1.10.4
library(ggplot2)      # version 2.2.1
# aggregate data
df <- as.data.table(mpg)[, .(mixmpg = mean(cty + hwy)), by = .(year, manufacturer)]
# create dummy var which reflects order when sorted alphabetically
df[, ord := sprintf("%02i", frank(df, mixmpg, ties.method = "first"))]

创建绘图

# `ord` is plotted on x-axis instead of `manufacturer`
ggplot(df, aes(x = ord, y = mixmpg)) +
  # geom_col() is replacement for geom_bar(stat = "identity")
  geom_col() +
  # independent x-axis scale in each facet, 
  # drop absent factor levels (actually not required here)
  facet_wrap(~ year, scales = "free_x", drop = TRUE) +
  # use named character vector to replace x-axis labels
  scale_x_discrete(labels = df[, setNames(as.character(manufacturer), ord)]) + 
  # replace x-axis title
  xlab(NULL) +
  # rotate x-axis labels
  theme(axis.text.x = element_text(angle = 90, hjust=1, vjust=.5))

enter image description here


同样的解决方案,但使用dplyr而不是data.table: https://gist.github.com/holgerbrandl/2b216b2e3ec51d48b2be4d9f46f0ff5e - Holger Brandl

8
“有几种不同的方法可以实现 OP 的目标,详见 this answer
(1) 使用 reorder_within() 函数在 period 分面内重新排序 name。”
library(tidyverse)
library(forcats)

df <- data.frame(
  name = c("foo", "bar", "foo", "bar"),
  period = c("old", "old", "recent", "recent"),
  val = c(1.23, 2.17, 4.15, 3.65)
)

reorder_within <- function(x, by, within, fun = mean, sep = "___", ...) {
  new_x <- paste(x, within, sep = sep)
  stats::reorder(new_x, by, FUN = fun)
}

scale_x_reordered <- function(..., sep = "___") {
  reg <- paste0(sep, ".+$")
  ggplot2::scale_x_discrete(labels = function(x) gsub(reg, "", x), ...)
}

ggplot(df, aes(reorder_within(name, val, period), val)) +
  geom_col() +
  scale_x_reordered() +
  facet_grid(period ~ ., scales = "free", space = "free") +
  coord_flip() +
  theme_minimal() +
  theme(panel.grid.major.y = element_blank()) 

或者(2)类似的想法。
### https://trinkerrstuff.wordpress.com/2016/12/23/ordering-categories-within-ggplot2-facets/
df %>% 
  mutate(name = reorder(name, val)) %>%
  group_by(period, name) %>% 
  arrange(desc(val)) %>% 
  ungroup() %>% 
  mutate(name = factor(paste(name, period, sep = "__"), 
                       levels = rev(paste(name, period, sep = "__")))) %>%
  ggplot(aes(name, val)) +
  geom_col() +
  facet_grid(period ~., scales = "free", space = 'free') +
  scale_x_discrete(labels = function(x) gsub("__.+$", "", x)) +
  coord_flip() +
  theme_minimal() +
  theme(panel.grid.major.y = element_blank()) + 
  theme(axis.ticks.y = element_blank())

或者(3)对整个数据框进行排序,并且对每个分面组内的类别(`period`)进行排序!
  ### https://drsimonj.svbtle.com/ordering-categories-within-ggplot2-facets
  # 
  df2 <- df %>% 
  # 1. Remove any grouping
  ungroup() %>% 
  # 2. Arrange by
  #   i.  facet group (period)
  #   ii. value (val)
  arrange(period, val) %>%
  # 3. Add order column of row numbers
  mutate(order = row_number())
df2
#>   name period  val order
#> 1  foo    old 1.23     1
#> 2  bar    old 2.17     2
#> 3  bar recent 3.65     3
#> 4  foo recent 4.15     4

ggplot(df2, aes(order, val)) +
  geom_col() +
  facet_grid(period ~ ., scales = "free", space = "free") +
  coord_flip() +
  theme_minimal() +
  theme(panel.grid.major.y = element_blank()) 

# To finish we need to replace the numeric values on each x-axis 
# with the appropriate labels
ggplot(df2, aes(order, val)) +
  geom_col() +
  scale_x_continuous(
    breaks = df2$order,
    labels = df2$name) +
  # scale_y_continuous(expand = c(0, 0)) +
  facet_grid(period ~ ., scales = "free", space = "free") +
  coord_flip() +
  theme_minimal() +
  theme(panel.grid.major.y = element_blank()) + 
  theme(legend.position = "bottom",
        axis.ticks.y = element_blank())

2018年11月05日创建,使用reprex包(v0.2.1.9000)


facet_wrap 中的 space 参数似乎不再存在。 - Herman Toothrot

2

试试这个,它非常简单(只需忽略警告)

df <-data.frame(name = c('foo', 'bar', 'foo', 'bar'),
                period = c('old', 'old', 'recent', 'recent'),
                val = c(1.23, 2.17, 4.15, 3.65))

d1 <- df[order(df$period, df$val), ]
sn <- factor(x = 1:4, labels = d1$name)
d1$sn <- sn
p <- ggplot(data = d1, aes(x = sn, y = val))
p <- p + geom_bar(stat = 'identity')
p <- p + facet_wrap(~ period, scale = 'free_x')
p

1
为了完整起见:要忽略的警告如下:“因子中的重复级别已被弃用”。 - Uwe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接