利用ggplot2画出各种漂亮图片详细教程
《使用ggplot2画图》
作者:Guangchuang Yu
原文链接:http://ygc.name/2014/05/11/use-ggplot2/
1、Why use ggplot2
ggplot2是我见过最human friendly的画图软件,这得益于Leland Wilkinson在他的著作《The Grammar of Graphics》中提出了一套图形语法,把图形元素抽象成可以自由组合的成分,Hadley Wickham把这套想法在R中实现。
为什么要学习ggplot2,可以参考ggplot2: 数据分析与图形艺术的序言(btw: 在序言的最后,我被致谢了)。
Hadley Wickham也给出一堆理由让我们说服自己,我想再补充一点,Hadley Wickham是学医出身的,做为学生物出身的人有什么理由不支持呢:)
ggplot2基本要素
- 数据(Data)和映射(Mapping)
- 几何对象(Geometric)
- 标尺(Scale)
- 统计变换(Statistics)
- 坐标系统(Coordinante)
- 图层(Layer)
- 分面(Facet)
- 主题(Theme)
这里将从这些基本要素对ggplot2进行介绍。
2、数据(Data)和映射(Mapping)
下面以一份钻石的数据为例,这份数据非常大,随机取一个子集来画图。
1 2 3 4 5 | require(ggplot2) data(diamonds) set.seed(42) small <- diamonds[sample(nrow(diamonds), 1000), ] head(small) |
1 2 3 4 5 6 7 | ## carat cut color clarity depth table pricexyz ## 493450.71 Very Good H SI162.5602096 5.68 5.75 3.57 ## 505450.79 Premium H SI161.8592275 5.97 5.91 3.67 ## 154341.03 Ideal F SI162.4576178 6.48 6.44 4.03 ## 447920.50 Ideal E VS262.2541624 5.08 5.11 3.17 ## 346140.27 Ideal E VS161.656 470 4.14 4.17 2.56 ## 279980.30 Premium E VS261.758 658 4.32 4.34 2.67 |
1 | summary(small) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ##caratcutcolorclaritydepth ##Min. :0.220 Fair : 28 D:121 SI1:258 Min. :55.2 ##1st Qu.:0.400 Good : 88 E:186 VS2:231 1st Qu.:61.0 ##Median :0.710 Very Good:227 F:164 SI2:175 Median :61.8 ##Mean :0.819 Premium:257 G:216 VS1:141 Mean :61.7 ##3rd Qu.:1.070 Ideal:400 H:154 VVS2 : 91 3rd Qu.:62.5 ##Max. :2.660 I:106 VVS1 : 67 Max. :72.2 ##J: 53 (Other): 37 ##tableprice xy ##Min. :50.1 Min. :342 Min. :3.85 Min. :3.84 ##1st Qu.:56.0 1st Qu.:990 1st Qu.:4.74 1st Qu.:4.76 ##Median :57.0 Median : 2595 Median :5.75 Median :5.78 ##Mean :57.4 Mean : 4111 Mean :5.79 Mean :5.79 ##3rd Qu.:59.0 3rd Qu.: 5495 3rd Qu.:6.60 3rd Qu.:6.61 ##Max. :65.0 Max. :18795 Max. :8.83 Max. :8.87 ## ##z ##Min. :2.33 ##1st Qu.:2.92 ##Median :3.55 ##Mean :3.57 ##3rd Qu.:4.07 ##Max. :5.58 ## |
画图实际上是把数据中的变量映射到图形属性上。以克拉(carat)数为X轴变量,价格(price)为Y轴变量。
1 | p <- ggplot(data = small, mapping = aes(x = carat, y = price)) |
上面这行代码把数据映射XY坐标轴上,需要告诉ggplot2,这些数据要映射成什么样的几何对象,下面以散点为例:
1 | p + geom_point() |
几何对象将在下面的小节介绍,这一节,关注的是数据和图形属性之间的映射。
如果想将切工(cut)映射到形状属性。只需要:
1 2 | p <- ggplot(data=small, mapping=aes(x=carat, y=price, shape=cut)) p+geom_point() |
再比如我想将钻石的颜色(color)映射颜色属性:
1 2 | p <- ggplot(data=small, mapping=aes(x=carat, y=price, shape=cut, colour=color)) p+geom_point() |
1 |
3、几何对象(Geometric)
在上面的例子中,各种属性映射由ggplot函数执行,只需要加一个图层,使用geom_point()告诉ggplot要画散点,于是所有的属性都映射到散点上。
geom_point()完成的就是几何对象的映射,ggplot2提供了各种几何对象映射,如geom_histogram用于直方图,geom_bar用于画柱状图,geom_boxplot用于画箱式图等等。
不同的几何对象,要求的属性会有些不同,这些属性也可以在几何对象映射时提供,比如上一图,也可以用以下语法来画:
1 2 | p <- ggplot(small) p+geom_point(aes(x=carat, y=price, shape=cut, colour=color)) |
ggplot2支持图层,我通常把不同的图层中共用的映射提供给ggplot函数,而某一几何对象才需要的映射参数提供给geom_xxx函数。
这一小节我们来看一下各种常用的几何对象。
直方图
直方图最容易,提供一个x变量,画出数据的分布。
1 | ggplot(small)+geom_histogram(aes(x=price)) |
同样可以根据另外的变量给它填充颜色,比如按不同的切工:
1 | ggplot(small)+geom_histogram(aes(x=price, fill=cut)) |
也可以将其分开,side-by-side地画直方图。
1 | ggplot(small)+geom_histogram(aes(x=price, fill=cut), position="dodge") |
还可以使用position="fill",按照相对比例来画。
1 | ggplot(small)+geom_histogram(aes(x=price, fill=cut), position="fill") |
柱状图
柱状图非常适合于画分类变量。在这里以透明度(clarity)变量为例。按照不同透明度的钻石的数目画柱状图。
1 | ggplot(small)+geom_bar(aes(x=clarity)) |
柱状图两个要素,一个是分类变量,一个是数目,也就是柱子的高度。数目在这里不用提供,因为ggplot2会通过x变量计算各个分类的数目。
当然你想提供也是可以的,通过stat参数,可以让geom_bar按指定高度画图,比如以下代码:
1 | ggplot()+geom_bar(aes(x=c(LETTERS[1:3]),y=1:3), stat="identity") |
柱状图和直方图是很像的,直方图把连续型的数据按照一个个等长的分区(bin)来切分,然后计数,画柱状图。而柱状图是分类数据,按类别计数。我们可以用前面直方图的参数来画side-by-side的柱状图,填充颜色或者按比例画图,它们是高度一致的。
柱状图是用来表示计数数据的,但在生物界却被经常拿来表示均值,加上误差来表示数据分布,这可以通常图层来实现,我将在图层一节中给出实例。
密度函数图
说到直方图,就不得不说密度函数图,数据和映射和直方图是一样的,唯一不同的是几何对象,geom_histogram告诉ggplot要画直方图,而geom_density则说我们要画密度函数图,在我们熟悉前面语法的情况下,很容易画出:
1 | ggplot(small)+geom_density(aes(x=price, colour=cut)) |
1 | ggplot(small)+geom_density(aes(x=price,fill=clarity)) |
colour参数指定的是曲线的颜色,而fill是往曲线下面填充颜色。
箱式图
数据量比较大的时候,用直方图和密度函数图是表示数据分布的好方法,而在数据量较少的时候,比如很多的生物实验,很多时候大家都是使用柱状图+errorbar的形式来表示,不过这种方法的信息量非常低,被Nature Methods吐槽,这种情况推荐使用boxplot。
1 | ggplot(small)+geom_boxplot(aes(x=cut, y=price,fill=color)) |
geom_boxplot将数据映射到箱式图上,上面的代码,我们应该很熟悉了,按切工(cut)分类,对价格(price)变量画箱式图,再分开按照color变量填充颜色。
ggplot2提供了很多的geom_xxx函数,可以满足我们对各种图形绘制的需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | geom_abline geom_area geom_bar geom_bin2d geom_blank geom_boxplot geom_contour geom_crossbar geom_density geom_density2d geom_dotplot geom_errorbar geom_errorbarh geom_freqpoly geom_hex geom_histogram geom_hline geom_jitter geom_line geom_linerange geom_map geom_path geom_point geom_pointrange geom_polygon geom_quantile geom_raster geom_rect geom_ribbon geom_rug geom_segment geom_smooth geom_step geom_text geom_tile geom_violin geom_vline |
4、标尺(Scale)
前面我们已经看到了,画图就是在做映射,不管是映射到不同的几何对象上,还是映射各种图形属性。这一小节介绍标尺,在对图形属性进行映射之后,使用标尺可以控制这些属性的显示方式,比如坐标刻度,可能通过标尺,将坐标进行对数变换;比如颜色属性,也可以通过标尺,进行改变。
1 | ggplot(small)+geom_point(aes(x=carat, y=price, shape=cut, colour=color))+scale_y_log10()+scale_colour_manual(values=rainbow(7)) |
以数据(Data)和映射(Mapping)一节中所画散点图为例,将Y轴坐标进行log10变换,再自己定义颜色为彩虹色。
5、统计变换(Statistics)
统计变换对原始数据进行某种计算,然后在图上表示出来,例如对散点图上加一条回归线。
1 | ggplot(small, aes(x=carat, y=price))+geom_point()+scale_y_log10()+stat_smooth() |
这里就不按颜色、切工来分了,不然ggplot会按不同的分类变量分别做回归,图就很乱,如果我们需要这样做,我们可以使用分面,这个将在后面介绍。
这里,aes所提供的参数,就通过ggplot提供,而不是提供给geom_point,因为ggplot里的参数,相当于全局变量,geom_point()和stat_smooth()都知道x,y的映射,如果只提供给geom_point(),则相当于是局部变量,geom_point知道这种映射,而stat_smooth不知道,当然你再给stat_smooth也提供x,y的映射,不过共用的映射,还是提供给ggplot好。
ggplot2提供了多种统计变换方式:
1 2 3 4 5 6 | stat_abline stat_contourstat_identity stat_summary stat_binstat_densitystat_qq stat_summary2d stat_bin2dstat_density2dstat_quantile stat_summary_hex stat_bindot stat_ecdf stat_smooth stat_unique stat_binhex stat_function stat_spokestat_vline stat_boxplotstat_hlinestat_sumstat_ydensity |
统计变换是非常重要的功能,我们可以自己写函数,基于原始数据做某种计算,并在图上表现出来,也可以通过它改变geom_xxx函数画图的默认统计参数。
比如我在Proteomic investigation of the interactome of FMNL1 in hematopoietic cells unveils a role in calcium-dependent membrane plasticity的图一中,就把boxplot的中位线替换成了平均值来作图。
6、坐标系统(Coordinante)
坐标系统控制坐标轴,可以进行变换,例如XY轴翻转,笛卡尔坐标和极坐标转换,以满足我们的各种需求。
坐标轴翻转由coord_flip()实现
1 | ggplot(small)+geom_bar(aes(x=cut, fill=cut))+coord_flip() |
而转换成极坐标可以由coord_polar()实现:
1 | ggplot(small)+geom_bar(aes(x=factor(1), fill=cut))+coord_polar(theta="y") |
这也是为什么之前介绍常用图形画法时没有提及饼图的原因,饼图实际上就是柱状图,只不过是使用极坐标而已,柱状图的高度,对应于饼图的弧度,饼图并不推荐,因为人类的眼睛比较弧度的能力比不上比较高度(柱状图)
还可以画靶心图:
1 | ggplot(small)+geom_bar(aes(x=factor(1), fill=cut))+coord_polar() |
以及风玫瑰图(windrose)
1 | ggplot(small)+geom_bar(aes(x=clarity, fill=cut))+coord_polar() |
7、图层(Layer)
photoshop流行的原因在于PS 3.0时引入图层的概念,ggplot的牛B之处在于使用+号来叠加图层,这堪称是泛型编程的典范。
在前面散点图上,我们已经见识过,加上了一个回归线拟合的图层。
有了图层的概念,使用ggplot画起图来,就更加得心应手。
做为图层的一个很好的例子是蝙蝠侠logo,batman logo由6个函数组成,在下面的例子中,我先画第一个函数,之后再加一个图层画第二个函数,不断重复这一过程,直到六个函数全部画好。
1 2 3 4 5 6 7 8 9 10 11 12 | require(ggplot2) f1data.frame(x=x,y=y) d -3*sqrt(33)/7,] return(d) } x1data.frame(x2=x2, y2=y2) p2data.frame(x3=x3, y3=y3) p3data.frame(x4=x4,y4=y4) p4data.frame(x5=x5,y5=y5) p5data.frame(x6=x6,y6=y6) p6 |
下面再以生物界中常用的柱状图+误差图为实例,展示ggplot2非常灵活的图层。以我2011年发表的文章Phosphoproteome profile of human lung cancer cell line A549中的westernblot数据为例。
1 2 | Normaldata.frame(V=c("Normal", "Cancer"), mean=m, sd=s) d$V |
8、分面(Facet)
分面可以让我们按照某种给定的条件,对数据进行分组,然后分别画图。
在统计变换一节中,提到如果按切工分组作回归线,显然图会很乱,有了分面功能,我们可以分别作图。
1 | ggplot(small, aes(x=carat, y=price))+geom_point(aes(colour=cut))+scale_y_log10() +facet_wrap(~cut)+stat_smooth() |
9、主题(Theme)
通过ggplot画图之后,我们可能还需要对图进行定制,像title, xlab, ylab这些高频需要用到的,自不用说,ggplot2提供了ggtitle(), xlab()和ylab()来实现。
比如:
1 | p |
但是这个远远满足不了需求,我们需要改变字体,字体大小,坐标轴,背景等各种元素,这需要通过theme()函数来完成。
ggplot2提供一些已经写好的主题,比如theme_grey()为默认主题,我经常用的theme_bw()为白色背景的主题,还有theme_classic()主题,和R的基础画图函数较像。
别外ggthemes包提供了一些主题可供使用,包括:
1 2 3 4 5 | theme_economist theme_economist_white theme_wsj theme_excel theme_few theme_foundation theme_igray theme_solarized theme_stata theme_tufte |
1 2 | require(ggthemes) p + theme_wsj() |
在2013年发表的文章Putative cobalt- and nickel-binding proteins and motifs in Streptococcus pneumoniae中的图3就是使用theme_stata来画的。
至于如何改变这些元素,我觉得我之前画囧字的博文可以做为例子:
1 2 3 | fdata.frame(x=x,y=y) p |
详细的说明,可以参考?theme的帮助文档。
10、二维密度图
在这个文档里,为了作图方便,我们使用diamonds数据集的一个子集,如果使用全集,数据量太大,画出来散点就糊了,这种情况可以使用二维密度力来呈现。
1 | ggplot(diamonds, aes(carat, price))+ stat_density2d(aes(fill = ..level..), geom="polygon")+ scale_fill_continuous(high='darkred',low='darkgreen') |
11、ggplot2实战
果壳知性里有帖子介绍了个猥琐邪恶的曲线,引来无数宅男用各种工具来画图,甚至于3D动态图都出来了。这里用ggplot2来画。3D版本请猛击此处。
1 2 | fdata.frame(x=c(x1,x2,x3), y=rep(y,3), type=rep(LETTERS[1:3], each=length(y))) p |
再来一个蝴蝶图,详见《Modern Applied Statistics with S-PLUS》第一章。
1 2 | theta data.frame(x=radius*sin(theta), y=radius*cos(theta)) ggplot(dd, aes(x, y))+geom_path()+theme_null()+xlab("")+ylab("") |
相关推荐:
-
***plob的定位就是收集和整理生物信息学相关各种资源,创 1970-01-01 08:00#2
-
***plob的发展还需要像您这样的高手的指点和支持啊。 1970-01-01 08:00#3
-
***3ygc 2014-05-12 13:50#4
这个网站值得支持,是中文生信的知识库啊 :)
-
***2zhhu 2014-06-07 10:55#5
老师:有个问题请教一下,您的博文说到,箱式图数据量比较大的时候,用直方图和密度函数图是表示数据分布的好方法,而在数据量较少的时候,比如很多的生物实验,很多时候大家都是使用柱状图+errorbar的形式来表示,不过这种方法的信息量非常低,被Nature Methods吐槽,这种情况推荐使用boxplot。我想问一下,boxplot对数据有什么要求?
-
***3ygc 2014-06-09 12:28#6
要问问题,请稳步到原博文。
-
***1snailQH 2014-06-05 00:08#7
写的很好!赞一个!
-
***1meilirenshengzcs 2015-10-18 10:51#8
很精彩,我想知道在哪里可以下栽ggplot 的安装包?
-
***1zhangmeng 2016-04-07 16:09#9
require(ggplot2)values=c(888672, 130436, 243200, 3680578, 263503,162848,61548,123663,14241)labels=c(“A”,”C”,”B”,”D”,”E”,”F”,”G”,”H”,”I”)colours=c(“#8dd3c7”, “#ffffb3”, “#bebada”, “#80b1d3”, “#fb8072”, “#fdb462”, “#b3de69”, “#d9d9d9”, “#fccde5”)percent_str <- paste(round(values/sum(values) * 100,1), "%", sep="")values <- data.frame(Percent = round(values/sum(values) * 100,1), Type = labels,percent=percent_str )pie <- ggplot(values, aes(x = "" ,y = percent, fill = Type)) + geom_bar(width = 3)pie = pie + coord_polar("y")pie = pie + xlab('') + ylab('') + labs(fill="Types")#pie + scale_fill_manual(values = colours)pie + scale_fill_manual(values = colours,labels = labels)pie <- ggplot(values, aes(x = "x_list" ,y = Percent, fill = Type)) + geom_bar(width = 3)
-
***一直报错提示:Error: stat_count() 1970-01-01 08:00#10
最新创建圈子
-
原料药研发及国内外注册申报
2019-01-25 10:41圈主:caolianhui 帖子:33 -
制药工程交流
2019-01-25 10:40圈主:polysciences 帖子:30 -
健康管理
2019-01-25 10:40圈主:neuromics 帖子:20 -
发酵技术
2019-01-25 10:39圈主:fitzgerald 帖子:17 -
医学肿瘤学临床试验
2019-01-25 10:39圈主:bma 帖子:58
Guangchuang兄请见谅,原标题已经在文章中注明。您这篇文章实在是写的太好了,我看完之后觉得朴实的标题无法体现丰富的内容,所以plob转载的时候换了个标题。另一方面是让这篇文章与plob上其他短篇的ggplot2文章区分,从而更多plob网友注意到这篇文章,并从中获益。