在 ggplot2 条形图中排序条形

385

我正在尝试制作一个条形图,其中最长的条应该靠近y轴,而最短的条应该离得最远。因此,这有点像我拥有的表格。

    Name   Position
1   James  Goalkeeper
2   Frank  Goalkeeper
3   Jean   Defense
4   Steve  Defense
5   John   Defense
6   Tim    Striker

我试图构建一个条形图,根据球员的位置显示数量。

p <- ggplot(theTable, aes(x = Position)) + geom_bar(binwidth = 1)

但是图表显示门将柱形图先出现,然后是防守,最后是前锋。我希望图表的顺序是:防守柱形图最靠近y轴,然后是门将柱形图,最后是前锋柱形图。 谢谢


17
你能否在不必搞乱表格(或数据框)的情况下,让ggplot为你重新排序它们? - tumultous_rooster
3
@MattO'Brien 我觉得难以置信这不可以通过一个简单的命令完成。 - Euler_Salter
@Zimano 很遗憾,你从我的评论中得到了这样的印象。我的观察是针对 ggplot2 的创建者,而不是 OP。 - Euler_Salter
3
@Euler_Salter,谢谢你的澄清,我很抱歉像那样对你进行攻击。 我已经删除了我的原始评论。 - Zimano
ggplot2目前会忽略binwidth = 1并发出警告。为了控制条形图的宽度(并且没有间隙),您可能想使用width = 1代替。 - stragu
16个回答

270

@GavinSimpson:reorder是解决此问题的强大而有效的解决方案:

ggplot(theTable,
       aes(x=reorder(Position,Position,
                     function(x)-length(x)))) +
       geom_bar()

7
确实如此,尤其是在这种情况下,我们可以利用数字进行逻辑排序。如果我们考虑类别的随意排序,并且不想按字母顺序排列,那么直接指定级别同样容易(甚至更容易)。 - Gavin Simpson
3
这是最简洁的方式。使原始数据框无需修改即可消除需要修改的必要性。 - Sweepy Dodo
3
亲爱的,我注意到你可以更简洁地完成这件事,如果你只想按长度函数排序且升序排列就可以了,这是我经常想做的事情:ggplot(theTable, aes(x = reorder(Position, Position, length)) + geom_bar() - postylem

255

排序的关键是按照你想要的顺序设置因子的级别。有序因子不是必需的;有序因子中的额外信息并不是必需的,如果这些数据在任何统计模型中使用,错误的参数化可能会导致多项式对比与此类名义数据不匹配。

## set the levels in order we want
theTable <- within(theTable, 
                   Position <- factor(Position, 
                                      levels=names(sort(table(Position), 
                                                        decreasing=TRUE))))
## plot
ggplot(theTable,aes(x=Position))+geom_bar(binwidth=1)

条形图示意图

从最普遍的意义上讲,我们只需要将因子水平设置为所需的顺序即可。如果未指定,因子的水平将按字母顺序排序。您也可以在调用因子时指定级别顺序,还有其他可能的方法。

theTable$Position <- factor(theTable$Position, levels = c(...))

1
@Gavin:两个简化:既然你已经在使用“within”,就不需要使用“theTable$Position”了,而且你可以直接使用“sort(-table(...))”来进行降序排序。 - Prasad Chalasani
2
@Prasad 前者是测试遗留下来的,感谢指出。对于后者,我更喜欢显式地要求反向排序,而不是使用 -,因为从 decreasing = TRUE 更容易理解意图,而不是在所有其他代码中注意到 - - Gavin Simpson
2
@GavinSimpson; 我认为 levels(theTable$Position) <- c(...) 这部分会导致不希望出现的行为,即数据框实际条目被重新排序,而不仅仅是因子的水平。请参见此问题。也许你应该修改或删除这些行? - Anton
2
非常同意Anton的观点。我刚看到了这个问题,并查找了一下他们得到错误建议使用levels<-的来源。我打算暂时将该部分删除。 - Gregor Thomas
2
@Anton 感谢您的建议(也感谢 Gregor 的编辑);我今天绝不会通过 levels<-() 来做这件事。这是8年前的事情,我不记得当时情况是否有所不同或者我只是错了,但无论如何,这是错误的,应该被删除!谢谢! - Gavin Simpson
显示剩余5条评论

204

使用scale_x_discrete (limits = ...)来指定条形图的顺序。

positions <- c("Goalkeeper", "Defense", "Striker")
p <- ggplot(theTable, aes(x = Position)) + scale_x_discrete(limits = positions)

14
您的解决方案最适合我的情况,因为我希望编程绘制图表时,使用一个在数据框中用变量表示的任意列作为x轴。其他建议会更难通过涉及该变量的表达式来表达x的排序顺序。谢谢!如果感兴趣,我可以分享使用您的建议得到的解决方案。只是还有一个问题,添加scale_x_discrete(limits = ...)后,我发现在图表右侧有一段与柱状图同等宽度的空白空间。如何去掉这个没有任何用途的空白空间? - Yu Shen
1
这似乎是为了对直方图条进行排序而必要的。 - geotheory
11
QIBIN:哇……这里的其他答案都可以,但是你的回答似乎不仅最简洁优雅,而且在从ggplot的框架内思考时,也是最显而易见的。谢谢你。 - dancow
当我尝试使用这个解决方案时,在我的数据上,它没有绘制NA值。是否有一种方法可以使用这个解决方案并绘制NA值呢? - user2460499
这个解决方案对我有效,而上面的其他方法则无效。 - Lauren Fitch

106

我认为已经提供的解决方案过于啰嗦了。使用ggplot绘制频率排序的条形图的更简洁方法是:

ggplot(theTable, aes(x=reorder(Position, -table(Position)[Position]))) + geom_bar()

这类似于Alex Brown的建议,但更短,并且不需要匿名函数定义。

更新

我认为我的旧解决方案在当时很好,但现在我宁愿使用forcats::fct_infreq,它通过频率对因子水平进行排序:

require(forcats)

ggplot(theTable, aes(fct_infreq(Position))) + geom_bar()

我不理解reorder函数的第二个参数是什么以及它的作用是什么。您能否请详细解释一下正在发生的事情? - Ashok K Harnal
1
@user3282777 你尝试过这个文档吗?https://stat.ethz.ch/R-manual/R-devel/library/stats/html/reorder.factor.html - Holger Brandl
1
很棒的解决方案!很高兴看到其他人使用tidyverse解决方案! - Mike

42

与Alex Brown答案中的reorder()类似,我们也可以使用forcats::fct_reorder()。它基本上会根据第二个参数应用指定函数(默认值为中位数,在此我们只有一个值)来排序在第一个参数中指定的因子。

遗憾的是,在OP的问题中,所需的顺序也是按字母顺序排列的,因为这是创建因子时的默认排序顺序,所以它将隐藏此函数实际执行的操作。为了使其更清晰,我将用“Zoalkeeper”替换“Goalkeeper”。

library(tidyverse)
library(forcats)

theTable <- data.frame(
                Name = c('James', 'Frank', 'Jean', 'Steve', 'John', 'Tim'),
                Position = c('Zoalkeeper', 'Zoalkeeper', 'Defense',
                             'Defense', 'Defense', 'Striker'))

theTable %>%
    count(Position) %>%
    mutate(Position = fct_reorder(Position, n, .desc = TRUE)) %>%
    ggplot(aes(x = Position, y = n)) + geom_bar(stat = 'identity')

在这里输入图片描述


1
以我的看法,像dplyr一样,forcats也是一个tidyverse软件包中的最佳解决方案。 - c0bra
2
Zookeeper 真不错,点个赞! - otwtm

31

使用 reorder 函数对因子的水平进行排序,可以选择按照计数升序(n)或降序(-n)。该方法与 forcats 包中的 fct_reorder 函数非常相似:

按照降序排序

df %>%
  count(Position) %>%
  ggplot(aes(x = reorder(Position, -n), y = n)) +
  geom_bar(stat = 'identity') +
  xlab("Position")

图片描述

升序排列

df %>%
  count(Position) %>%
  ggplot(aes(x = reorder(Position, n), y = n)) +
  geom_bar(stat = 'identity') +
  xlab("Position")

在此输入图片描述

数据帧:

df <- structure(list(Position = structure(c(3L, 3L, 1L, 1L, 1L, 2L), .Label = c("Defense", 
"Striker", "Zoalkeeper"), class = "factor"), Name = structure(c(2L, 
1L, 3L, 5L, 4L, 6L), .Label = c("Frank", "James", "Jean", "John", 
"Steve", "Tim"), class = "factor")), class = "data.frame", row.names = c(NA, 
-6L))

1
在我看來,提前添加計數是最簡單的方法。 - Kenan

29

使用基于dplyr的简单因子重新排序可以解决这个问题:

library(dplyr)

#reorder the table and reset the factor to that ordering
theTable %>%
  group_by(Position) %>%                              # calculate the counts
  summarize(counts = n()) %>%
  arrange(-counts) %>%                                # sort by counts
  mutate(Position = factor(Position, Position)) %>%   # reset factor
  ggplot(aes(x=Position, y=counts)) +                 # plot 
    geom_bar(stat="identity")                         # plot histogram

20

您只需指定Position列为一个按其计数排序的有序因子即可:

theTable <- transform( theTable,
       Position = ordered(Position, levels = names( sort(-table(Position)))))
(注意,table(Position)可以生成Position列的频数计算。)
然后你的ggplot函数将按计数的递减顺序显示条形图。 我不知道在geom_bar中是否有一种选项可以在不显式创建有序因子的情况下执行此操作。

我没有完全解析你上面的代码,但我相信来自统计库的reorder()函数可以完成相同的任务。 - Chase
@Chase你如何建议在这种情况下使用reorder()函数?需要重新排序的因子需要根据自身的某个函数进行重新排序,我很难找到一个好的方法来做到这一点。 - Gavin Simpson
好的,with(theTable, reorder(Position, as.character(Position), function(x) sum(duplicated(x)))) 是一种方法,另一种是 with(theTable, reorder(Position, as.character(Position), function(x) as.numeric(table(x)))) 但这些都很复杂... - Gavin Simpson
我稍微简化了答案,使用了 sort 而不是 order - Prasad Chalasani
@Gavin - 或许我误解了Prasad的原始代码(我没有在这台机器上安装R来测试...),但看起来他是根据频率重新排序类别,而reorder非常擅长这样做。对于这个问题,我同意需要更深入的处理。抱歉造成困惑。 - Chase
这个方案不能在我今天在其他评论中提供的数据集上运行。 - Léo Léopold Hertz 준영

20
除了@HolgerBrandl提到的forcats::fct_infreq之外,还有一个可以反转因子顺序的函数forcats::fct_rev。
theTable <- data.frame(
    Position= 
        c("Zoalkeeper", "Zoalkeeper", "Defense",
          "Defense", "Defense", "Striker"),
    Name=c("James", "Frank","Jean",
           "Steve","John", "Tim"))

p1 <- ggplot(theTable, aes(x = Position)) + geom_bar()
p2 <- ggplot(theTable, aes(x = fct_infreq(Position))) + geom_bar()
p3 <- ggplot(theTable, aes(x = fct_rev(fct_infreq(Position)))) + geom_bar()

gridExtra::grid.arrange(p1, p2, p3, nrow=3)             

enter image description here


"fct_infreq(Position)" 这个小东西真的很厉害,谢谢! - Paul

14

如果图表的列来自于数据框中的数字变量,您可以使用更简单的解决方案:

ggplot(df, aes(x = reorder(Colors, -Qty, sum), y = Qty)) 
+ geom_bar(stat = "identity")  

在排序变量(-Qty)前的减号控制排序方向(升序/降序)

以下是一些用于测试的数据:

df <- data.frame(Colors = c("Green","Yellow","Blue","Red","Yellow","Blue"),  
                 Qty = c(7,4,5,1,3,6)
                )

**Sample data:**
  Colors Qty
1  Green   7
2 Yellow   4
3   Blue   5
4    Red   1
5 Yellow   3
6   Blue   6

当我找到这个帖子时,那就是我正在寻找的答案。希望对其他人有用。


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