如何在ggplot2中为具有稳定映射的分类变量分配颜色?

219

最近一个月我一直在学习R语言。

这是我的问题:

在ggplot2中,有什么好的方法可以为分类变量分配颜色,并且保持稳定的映射关系?我需要在一组具有不同子集和不同数量分类变量的图表中使用一致的颜色。

例如,

plot1 <- ggplot(data, aes(xData, yData,color=categoricaldData)) + geom_line()

其中categoricalData有5个级别。

然后

plot2 <- ggplot(data.subset, aes(xData.subset, yData.subset, 
                                 color=categoricaldData.subset)) + geom_line()

其中有3个级别。

然而,两个集合中都存在的特定级别将以不同的颜色显示,这使得一起阅读图表更加困难。

我需要在数据帧中创建一个颜色向量吗?还是有其他方法为类别分配特定的颜色?

5个回答

233

对于像OP中给出的简单情况,我认为Thierry的答案是最好的。不过,我认为指出另一种方法也很有用,特别是当您尝试在多个数据框上维护一致的颜色方案时,这些数据框并非全部都是从单个大型数据框中进行子集选择得到的。如果来自不同文件的多个数据框的因子级别需要管理,而且不是每个文件中都包含所有因子级别,则可能变得繁琐。

解决这个问题的一种方法是创建一个自定义手动颜色比例尺,如下所示:

#Some test data
dat <- data.frame(x=runif(10),y=runif(10),
        grp = rep(LETTERS[1:5],each = 2),stringsAsFactors = TRUE)

#Create a custom color scale
library(RColorBrewer)
myColors <- brewer.pal(5,"Set1")
names(myColors) <- levels(dat$grp)
colScale <- scale_colour_manual(name = "grp",values = myColors)

然后根据需要将颜色比例尺添加到图表中:

#One plot with all the data
p <- ggplot(dat,aes(x,y,colour = grp)) + geom_point()
p1 <- p + colScale

#A second plot with only four of the levels
p2 <- p %+% droplevels(subset(dat[4:10,])) + colScale

第一个图看起来是这样的:

enter image description here

而第二个图看起来是这样的:

enter image description here

这样你就不需要记住或检查每个数据框以查看它们是否具有适当的级别。


20
对于一个单独的子集来说,是的。但如果你需要处理很多数据集,而这些数据集不是通过从一个原始数据框中进行划分而创建的,我发现这种策略更加简单。 - joran
2
@joran 谢谢Joran。这对我很有用!它创建了一个具有正确因子数量的图例。我喜欢这种方法,跨不同数据集获取颜色映射值是值得三行代码的。 - wintour
3
我需要:library("RColorBrewer") - PatrickT
6
运行得非常完美!我加入了fillScale <- scale_fill_manual(name = "grp",values = myColors)以在条形图中使用它。 - pentandrous
1
这种方法还有一个额外的好处:只有出现在每个子集图中的值才会出现在图例中。如果你犹豫不决,我会默认采用这种方法。 - Nova
显示剩余7条评论

45

我和malcook他的评论中指出的情况相同:不幸的是,Thierry提供的答案在ggplot2版本0.9.3.1中无法使用。

png("figure_%d.png")
set.seed(2014)
library(ggplot2)
dataset <- data.frame(category = rep(LETTERS[1:5], 100),
    x = rnorm(500, mean = rep(1:5, 100)),
    y = rnorm(500, mean = rep(1:5, 100)))
dataset$fCategory <- factor(dataset$category)
subdata <- subset(dataset, category %in% c("A", "D", "E"))

ggplot(dataset, aes(x = x, y = y, colour = fCategory)) + geom_point()
ggplot(subdata, aes(x = x, y = y, colour = fCategory)) + geom_point()

这是第一个图:

ggplot A-E, mixed colors

和第二个数字:

ggplot ADE, mixed colors

我们可以看到颜色不是固定的,例如 E 从洋红色变成了蓝色。
正如malcookhis comment中和hadleyhis comment中建议的那样,使用limits的代码可以正常工作:
ggplot(subdata, aes(x = x, y = y, colour = fCategory)) +       
    geom_point() + 
    scale_colour_discrete(drop=TRUE,
        limits = levels(dataset$fCategory))

给出如下图,是正确的:

correct ggplot

这是sessionInfo()的输出:

R version 3.0.2 (2013-09-25)
Platform: x86_64-pc-linux-gnu (64-bit)

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] methods   stats     graphics  grDevices utils     datasets  base     

other attached packages:
[1] ggplot2_0.9.3.1

loaded via a namespace (and not attached):
 [1] colorspace_1.2-4   dichromat_2.0-0    digest_0.6.4       grid_3.0.2        
 [5] gtable_0.1.2       labeling_0.2       MASS_7.3-29        munsell_0.4.2     
 [9] plyr_1.8           proto_0.3-10       RColorBrewer_1.0-5 reshape2_1.2.2    
[13] scales_0.2.3       stringr_0.6.2 

3
请将此作为一个新问题发布,引用此问题并说明这里的解决方案为何无法奏效。 - Brian Diggs
1
我知道这已经过时了,但我想知道是否有一种方法可以在图例中不使用额外的颜色来完成这个操作。 - goryh
为了从图例中删除未使用的级别,现在应该添加limit=force。https://github.com/tidyverse/ggplot2/issues/4556 - Marinka

38

这是一篇旧文章,但我正在寻找答案,与此相同的问题,

为什么不尝试类似这样的东西:

scale_color_manual(values = c("foo" = "#999999", "bar" = "#E69F00"))
如果您有分类值,我不认为这种方法行不通。

6
这实际上就是Joran的回答所做的,但使用myColors <- brewer.pal(5,"Set1"); names(myColors) <- levels(dat$grp)来避免手动编码水平级别。 - Axeman
3
然而,Joran的答案并没有硬编码颜色值。在某些情况下,您需要为给定因素使用特定的颜色值。 - René Nyffenegger
1
虽然我理解在某些情况下“硬编码”的缺点,但我认为开发人员/编码人员添加的抽象层次太多会使他们的工作变得不易访问,而不是更易访问。在这种情况下,意图是100%清晰的。此外,很容易想到如何创建一个实用函数来扩展此示例,返回特定颜色的命名向量。 - Matt Barstead

20
基于joran的非常有用的答案,我成功地想出了这个解决方案,用于一个布尔因子(TRUEFALSE)的稳定色标。
boolColors <- as.character(c("TRUE"="#5aae61", "FALSE"="#7b3294"))
boolScale <- scale_colour_manual(name="myboolean", values=boolColors)

ggplot(myDataFrame, aes(date, duration)) + 
  geom_point(aes(colour = myboolean)) +
  boolScale

由于ColorBrewer在二元色标尺方面并不十分有用,因此需要手动定义所需的两种颜色。

这里mybooleanmyDataFrame中包含TRUE/FALSE变量的列名。 dateduration是本示例中要映射到图表的x轴和y轴的列名。


另一种方法是将 "as.character()" 应用于该列。这将使其成为一个字符串列,可以很好地与 scale_*_manual 一起使用。 - Sahir Moosvi

18

最简单的解决方案是在进行子集操作之前将您的分类变量转换为因子变量。最重要的是,您需要一个因子变量,在所有子集中具有完全相同的水平。

library(ggplot2)
dataset <- data.frame(category = rep(LETTERS[1:5], 100), 
    x = rnorm(500, mean = rep(1:5, 100)), y = rnorm(500, mean = rep(1:5, 100)))
dataset$fCategory <- factor(dataset$category)
subdata <- subset(dataset, category %in% c("A", "D", "E"))

使用一个字符变量

ggplot(dataset, aes(x = x, y = y, colour = category)) + geom_point()
ggplot(subdata, aes(x = x, y = y, colour = category)) + geom_point()

使用因子变量

ggplot(dataset, aes(x = x, y = y, colour = fCategory)) + geom_point()
ggplot(subdata, aes(x = x, y = y, colour = fCategory)) + geom_point()

12
最简单的方法是使用极限。 - hadley
2
能否在这个情境下提供一个例子,Hadley?我不确定如何在因子中使用限制。 - Thierry
@Thierry 谢谢。我很高兴在我的第一篇帖子中得到回复。感谢 Thierry 添加可重现的代码,因为我应该在我的帖子中添加它...我的分类变量是正确的类型 - 因子。另一个问题是我希望图例不显示未使用的因子。R 在构建图例时忽略未使用的字符变量。但是,未使用的因子仍然存在。如果我使用以下方法删除它们:subdata$category <- factor(subdata$category)[drop=TRUE],那么图例就有了正确数量的因子,但是失去了映射。 - wintour
15
在我的手中,使用ggplot2_0.9.3.1版本时,这种方法似乎不再有效了;在两张图之间分配给fCategory的颜色是不同的。然而,令人高兴的是,我明白@hadley是建议使用 + scale_colour_discrete(drop=TRUE,limits = levels(dataset$fCategory)) 来保留颜色|因子关联,这个方法有效,但是,在我的手中,drop=TRUE 没有被遵守 (我期望它从图例中删除级别)。糟糕...还是我的问题? - malcook
2
@malcook,你需要通过“breaks”指定要保留哪些级别,而不是使用drop = TRUE:https://github.com/hadley/ggplot2/issues/1433 - Eric
显示剩余2条评论

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