--- title: "ggplot2 学习笔记" author: "hungerzs" date: "2016年8月24日" output: html_document: default pdf_document: includes: in_header: header.tex keep_tex: yes latex_engine: xelatex word_document: default --- ```{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) ``` ## 1 qplot函数参数 qplot 即“快速作图”(quick plot)参数: ``` qplot(x, y = NULL, ..., data, facets = NULL, margins = FALSE, geom = "auto", stat = list(NULL), position = list(NULL), xlim = c(NA, NA), ylim = c(NA, NA), log = "", main = NULL, xlab = deparse(substitute(x)), ylab = deparse(substitute(y)), asp = NA) ``` + x, y: 意义明确,不用说了 + data: 数据框(data.frame)类型;如果有这个参数,那么x,y的名称必需对应数据框中某列变量的名称 + facets: 图形/数据的分面。这是ggplot2作图比较特殊的一个概念,它把数据按某种规则进行分类,每一类数据做一个图形,所以最终效果就是一页多图 + margins: 是否显示边界 + geom: 图形的几何类型(geometry),这又是ggplot2的作图概念。ggplot2用几何类型表示图形类别,比如point表示散点图、line表示曲线图、bar表示柱形图等。 + stat: 统计类型(statistics),这个更加特殊。直接将数据统计和图形结合,这是ggplot2强大和受欢迎的原因之一。 + position: 图形或者数据的位置调整,这不算太特殊,但对于图形外观很重要 + xlim, ylim, xlab, ylab, asp: 初步可以按照plot函数的相应参数来理解 ## 2 qplot做散点图 ### 2.1 使用向量数据 和plot函数一样,如果不指定图形的类型,qplot默认做出散点图。对于给定的x和y向量做散点图,qplot用法也和plot函数差不多: ```{r qplot} library(ggplot2) x <- 1:1000 y <- rnorm(1000) plot(x, y, main="Scatter plot by plot()") qplot(x,y, main="Scatter plot by qplot()") ``` ### 2.2 使用数据框数据 虽然可以直接使用向量数据,但ggplot2更倾向于使用数据框类型的数据作图。使用数据框有几个好处:数据框可以用来存储数值、字符串、因子等不同类型等数据;把数据放在同一个R数据框对象中可以避免使用过程中数据关系的混乱;数据外观的整理和转换方便。ggplot2中使用数据框作图的最直接的一个效果就是:你可以直接用数据的分类特性(数据框中的列变量)来决定图形元素的外观,这个过程在ggplot2中称为映射(mapping),是自动的。 在演示使用数据框作图的好处之前我们先了解以下ggplot2提供的一组有关钻石的示范数据 diamonds: ```{r} str(diamonds) ``` 可以看到这是数据框(data.frame)类型,有10个变量(列),每个变量有53940个测量值(行)。第一列为钻石的克拉数(carat),为数字型数据;第二列为钻石的切工好坏(cut),为因子类型数据,有5个水平;第三列为钻石颜色(color),为7水平的因子;后面还有其他数据。由于数据太多,我们只取前7列的100个随机观测值。数据基本就是我们平时记录原始数据的样式: ```{r} set.seed(1000) # 设置随机种子,使随机取样具有可重复性 datax<- diamonds[sample(53940, 100), seq(1,7)] head(datax, 4) ``` 如果要做钻石克拉和价格关系的曲线图,用plot和qplot函数都差不多 ```{r} plot(x=datax$carat, y=datax$price, xlab="Carat", ylab="Price", main="plot function") qplot(x=carat, y=price, data=datax, xlab="Carat", ylab="Price", main="qplot function") ``` 但如果要按切工进行分类作图,plot函数的处理就复杂了,你首先得将数据进行分类提取,然后再一个个作图。虽然可以用循环完成,但作图后图标的添加还得非常小心,你得自己保证数据和图形外观之间的对应关系: ```{r} plot(x=datax$carat, y=datax$price, xlab="Carat", ylab="Price", main="plot function", type='n') cut.levels <- levels(datax$cut) cut.n <- length(cut.levels) for(i in seq(1,cut.n)){ subdatax <- datax[datax$cut==cut.levels[i], ] points(x=subdatax$carat, y=subdatax$price, col=i, pch=i) } legend("topleft", legend=cut.levels, col=seq(1,cut.n), pch=seq(1,cut.n), box.col="transparent", cex=0.8) ``` 但用ggplot2作图你需要考虑数据分类和图形元素方面的问题就很少,你只要告诉它用做分类的数据就可以了: ```{r} qplot(x=carat, y=price, data=datax, color=cut, shape=cut, main="qplot function") ``` 如果不喜欢它默认的图形背景,要改变也相当简单,ggplot2预置了几个模板,这些内容我们在后面再详细说: ```{r} theme_set(theme_bw()) qplot(x=carat, y=price, data=datax, color=cut, shape=cut, main="qplot function") ``` 数据框可以存储不同的数据,而这些数据是有类型差别的。ggplot2作图对各类数据的要求也非常严格,用于分类的数据必需是因子类型,否则就出错,例如下面的语句就会出错: ``` qplot(x=carat, y=price, data=datax, shape=depth) ``` ### 3qplot做曲线图 和plot函数一样,qplot也可以通过设置合适的参数产生曲线图,这个参数就是geom(几何类型)。图形的组合非常直接,组合表示几何类型的向量即可: ```{r} qplot(x=carat, y=price, data=datax, color=cut, geom="line", main="geom=\"line\"") qplot(x=carat, y=price, data=datax, color=cut, geom=c("line", "point"), main="geom=c(\"line\", \"point\")") ``` ### 4 qplot做统计图 qplot是名副其实的qplot(quick plot)函数,通过改变几何类型geom参数的值你可以获得各种图形。geom参数可以设置的值和意义是: + point:散点图 + line:曲线图 + smooth:平滑曲线 + jitter:另一种散点图 + boxplot:箱线图 + histogram:直方图 + density:密度分布图 + bar:柱状图 前两种我们看过了,bar类型下面另讲,jitter以后有机会再说,看看其他4种类型: ```{r} qplot(carat, price, data = diamonds, color=cut, geom = "smooth", main = "smooth") qplot(cut, price, data = diamonds, fill=cut, geom = "boxplot", main = "boxplot") qplot(price, data = diamonds, fill=cut, geom = "histogram", main = "histogram") qplot(price, data = diamonds, color=cut, geom = "density", main = "density") ``` 能做什么样的图形取决于数据,这点我们都很清楚,所以不同类型的图使用的数据有所不同,参数也有变化。前面我们说ggplot2可以整合不同类型的图形到一个图中,但很重要的一个前提是要组合的这些形状要能共享一组数据和参数。 ### 5 qplot做柱形图 做柱形图很少直接用原始数据,一般都要通过计算变换如求平均值后再做。这其实是一个统计过程,所以多数柱形图应该也是统计类型的图。ggplot2对柱形图的处理体现了这一思想:柱形图是一种特殊的直方图。所以ggplot2可以直接用原始数据做出柱形图,这是它的优点之一。下面按钻石切工对价格求平均值后做柱形图: ``` qplot(x=cut, y=price, data = diamonds, fill=cut, geom = "histogram", stat="summary", fun.y="mean") ``` ## ggplot图形对象 前面我们使用qplot函数对ggplot2做图的方法进行了初步的了解,并比较了qplot和plot函数的用法。从最终得到的结果(图形)来看,除了外观不同外好像qplot函数和plot函数并没有什么本质的差别。这其实是一个骗局! 我们知道,R语言基本做图方法都是通过函数完成的(如果不了解请参考本博客《散点图》一文),这些函数直接在输出设备上执行一些列操作得到图形。很多函数没有返回值,即使有返回值也不会反映绘图操作的整个过程。但ggplot2不一样,它用图形对象存储做图的细节,通过输出图形对象获得图形。一行一行地运行下面代码可以发现这一点: ```{r} library(ggplot2) theme_set(theme_bw()) x <- 1:100 y <- rnorm(100) p1 <- plot(x, y) p2 <- qplot(x, y) ``` 很显然执行最后一行代码不会得到想要的图形。下面再看看 p1 和 p2 分别是什么: ```{r} class(p1) ``` ```{r} class(p2) typeof(p2) str(p2) ``` plot函数的返回值p1的class属性为NULL(空),而qplot函数的返回值p2的calss属性有两个“gg” 和 “ggplot”,其本质是长度为9的列表对象。打印这个对象就会得到图形: ```{r} print(p2) ``` qplot函数的作用是产生一个ggplot对象,但获得ggplot对象的更一般方法是使用对象类型的同名函数ggplot。它的用法是: ``` ggplot(df, aes(x, y, )) ggplot(df) ggplot() ``` ggplot函数用于初始化一个ggplot对象,即使不指定任何做图相关的内容,它的结构也是完整的: ```{r} length(p2) ## [1] 9 length(ggplot()) ## [1] 9 ``` ### 2 ggplot图形对象组成 ggplot图形对象是由9个元素组成的列表,这点已经清楚。元素的名称为: ```{r} names(p2) ``` ggplot2是Wilkinson做图理论 Grammer of Graphics 的R语言实现。 #### 2.1 数据 data 似乎就是数据。但是如果试图查看上面p2对象的数据: ```{r} p2$data ``` 是空的。但如果使用qplot函数时指定了data,情况就不一样了: ```{r} p3 <- qplot(carat, price, data=diamonds, color=cut, shape=cut) head(p3$data, 3) ``` 列表对象的data元素存储了整个diamnods数据框的数据。用ggplot函数可以单独指定data项: ```{r} p4 <- ggplot(diamonds) head(p4$data, 3) ``` ### 2.2 映射 mapping ggplot对象的data项存储了整个数据框的内容,而“映射”则确定如何使用这些数据。在ggplot2中,图形的可视属性如形状、颜色、透明度等称为美学属性(或艺术属性),确定数据与美学属性之间对应关系的过程称为映射,通常使用aes函数完成(qplot函数中使用参数设置映射): ```{r} str(p3$mapping) str(p4$mapping) p4 <- p4 + aes(x=carat, y=price, color=color, shape=cut) str(p4$mapping) p4 <- p4 + aes(x=carat, y=price, color=cut, shape=cut) str(p4$mapping) ``` ## 2.3 图层 layers ### 2.3.1 图层组成 在ggplot的列表结构里面看不到我们指定的图形类型参数。这些设置被分派到layers里面了: ```{r} p3$layers ``` 一个图层包含了至少3个东西:geom、stat和position + 几何类型 geom:是数据在图上的展示形式,即点、线、面等。在ggplot2里面有很多预定义的几何类型。 + 统计类型 stat:是对数据所应用的统计类型/方法。上面的p2和p3对象我们并没有指定统计类型,但是自动获得了identity,因为ggplot2为每一种几何类型指定了一种默认的统计类型,反之亦然。所以如果仅指定geom或stat中的一个,另外一个会自动获取。 + 位置 position:几何对象在图像上的位置调整,这也有默认设定。 不指定几何类型或统计类型的ggplot对象的图层是空列表,即没有图层,不会输出图像: ```{r} p4$layers print(p4) ``` 也就是说,指定了几何类型或统计类型,图层就会产生: ```{r} (p4 + geom_point())$layers (p4 + stat_identity())$layers ``` 为什么图层说“至少”包含3个内容呢?如果你把整个图层的内容转成列表结构显示以下就会发现更多: ```{r} as.list((p4 + geom_point())$layers[[1]]) ``` ### 2.3.2 图层加法 从图层的结构可以看到它在ggplot对象中是一个多重列表,如果对ggplot对象做图层加法运算,是增加图层而不是替换图层: ```{r} (p4 + geom_point() + geom_line())$layers ``` ### 2.3.3 图层顺序 ggplot2按“层”做图,所以图层的顺序对于图形的表现会有影响,如果几何对象有叠加,那么后面图层的对象会覆盖前面图层的对象。下面只是调换了两个图层的顺序,但由于数据点是相同的,所以图形的颜色完全不一样: ```{r} p4 + geom_point(color="red") + geom_point(color="blue") p4 + geom_point(color="blue") + geom_point(color="red") ``` ## 2.4 标尺 scales 这是ggplot2中比较复杂的一个概念: ```{r} p4$scales ``` 在ggplot2中,一个标尺就是一个函数,它使用一系列参数将我们的数据(如钻石价格、克拉)转成计算机能够识别的数据(如像素、颜色值),然后展示在图像上。使用标尺对数据进行处理的过程称为缩放(scaling)。坐标的产生和图形美学属性的处理都需要使用标尺对数据进行缩放。这个过程比较复杂,尤其是美学属性与数据的关联,因为和美学属性相关的数据不仅有连续型还有离散型,多组数据之间还要相互关照。但这些过程我们都可以不管,ggplot2也替我们做了。 标尺是函数,它的反函数用于产生坐标刻度标签和图表的图例等,这样我们才能把图形外观、位置等信息和数据对应起来。 ## 2.5 坐标 coordinates 这都知道,用于确定采用的坐标系统和坐标轴的范围。 ```{r} p4$coordinates ``` ## 2.6 主题 theme 标题文字(字体、字号、颜色)、图形边框和底纹等跟数据无关的一些图形元素的设置都可以归到“主题”这一类。ggplot2提供了4个成套主题:theme_gray(), theme_bw() , theme_minimal() 和 theme_classic()。其中theme_gray()为默认主题,灰背景;后两个是0.9.3版才增加的。 ```{r} p5 <- p4 + geom_point(color="blue") p5 + theme_gray() + ggtitle("theme_gray()") p5 + theme_bw() + ggtitle("theme_bw()") p5 + theme_minimal() + ggtitle("theme_minimal()") p5 + theme_classic() + ggtitle("theme_classic()") ``` ## 2.7 分面 facet: 一页多图,跟图层好像没有直接关系。以后再说。 ## 2.8 标签 labels: ```{r} str(p4$labels) ``` # 映射(mapping) 作图前的数据准备工作不仅仅指原始数据的收集,还包括数据外观的整理,这些工作对后续的作图无疑十分重要。和其他作图方法相比,ggplot2的优点之一就是把数据整理融合到了作图过程中,替用户分担了数据整型的部分工作。ggplot2数据层面的操作包括映射和分面。先说映射。 ## 1 映射的类型 前面我们已经了解到ggplot对象的data项存储了整个数据框的内容,而“映射”则确定如何使用这些数据。 ggplot2按照图形属性提供了以下可用映射类型: + 颜色类型映射: 包括 color(颜色或边框颜色)、fill(填充颜色)和 alpha(透明度) + 形状类型映射: 包括 linetype(线型)、size(点的大小或线的宽度)和 shape(形状) + 位置类型映射: 包括 x, y, xmin, xmax, ymin, ymax, xend, yend + group和order: 指定数据分组和顺序的映射。 +字符串映射: 字符串映射与其他映射不同。其他映射均可以用aes函数赋值,字符串映射用单独的函数aes_string赋值。 ## 2 颜色和形状类型映射 把数据框的变量和图形的美学属性对应起来。 ### 2.1 映射的过程 先看下面ggplot2的数据diamonds: ```{r} library(ggplot2) set.seed(100) d.sub <- diamonds[sample(nrow(diamonds), 500), ] head(d.sub, 4) ``` 作图首先要指定x和y数据,即建立数据框变量和x/y之间的映射: ```{r} p<- ggplot(data=d.sub, aes(x=carat, y=price)) ``` 作出散点图: ```{r} theme_set(theme_bw()) p + geom_point() ``` 如果还要建立其他映射,比如用钻石颜色(color)分类数据确定点的颜色,图形外观就会发生变化(为方便说明,先去掉图例): ```{r} p + geom_point() + aes(color=color) + theme(legend.position=c(2,2)) ``` ggplot2映射的过程可以用plot函数作图步骤进行分解,它包含三方面的操作(不包括图形页面设置): 数据分组,设定颜色标尺,按颜色标尺指定每组数据的颜色 ```{r} # 设定颜色标尺 levs <- levels(d.sub$color) cl <- rainbow(length(levs)) # 页面设置 par(mar=c(3,3,0.5,0.5), mgp=c(1.5, 0.5, 0), bg="white") plot(d.sub$carat, d.sub$price, type='n', xlab="carat", ylab="price") i <- 1 for(lev in levs){ # 数据分组 datax <- d.sub[d.sub$color==lev, ] # 作图并指定数据点颜色 points(datax$carat, datax$price, pch=20, col=cl[i]) i <- i + 1 } ``` 上面对数据的分组只设定了一个变量,如果增加数据分组的变量,ggplot2中只需要增加映射的类型就可以了,比如在颜色分类的基础上加钻石切工(cut)进行分类: ```{r} p + geom_point() + aes(color=color, shape=cut) + theme(legend.position=c(2,2)) ``` ## 2.2 映射的标尺 用plot函数作图我们得自己考虑使用什么颜色表示不同组的数据,也就是使用什么标尺(或比例尺)。ggplot2则自动应用标尺,这是一个隐含过程。标尺大体可以分为两类: + 离散型(或枚举型)标尺。 罗列出所有数据分类并将其与美学属性一一对应,处理过程包含数据分类,如上例。 + 连续型(或区间型)标尺。 看下面例子: ```{r} p + geom_point() + aes(size= x*y*z ) + theme(legend.position=c(2,2)) ``` 上图中点的大小反映钻石的x*y*z值,相当于钻石的大小。用plot函数也可以实现: ```{r} cex <- d.sub[,"x"]*d.sub[,"y"]*d.sub[,"z"] cex <- cex/max(cex)*4 par(mar=c(3,3,0.5,0.5), mgp=c(1.5, 0.5, 0), bg="white") plot(d.sub$carat, d.sub$price, type='n', xlab="carat", ylab="price") points(d.sub[, "carat"], d.sub[, "price"], pch=20, cex=cex) ``` 可以看到作图过程也需要手动建立数据和图形属性间的对应关系,但没有对数据分类。 ggplot2对映射应用的标尺可以修改,ggplot提供了一大批 scale_xxxxxxxx 类型的函数,比如 scale_color_xxxx 类型函数用户修改颜色标尺,scale_shape_xxxx 修改形状,scale_linetype_xxxx 修改线型等。 按照数据的类型,这些函数还有4种基本类型: continuous:连续型 discrete:离散型 identity:和数据取值相同 manual:手工指定 有关颜色和坐标轴标尺设定的函数较多,适应不同需要。 ```{r} cls <- terrain.colors(length(levels(d.sub$color))) p + geom_point() + aes(color=color) + scale_color_manual(values=cls) ``` ## 2.3 映射与图例 映射还有一个作用:产生图例。这在ggplot2也是自动的隐含过程,但在plot函数作图中是一个体力活,制作过程就不举例说明了,可以参考legend函数。 ## 2.4 图形颜色和形状的非映射设置 除了通过映射设置几何图形的图形颜色和形状属性外,ggplot2还提供了直接设定方式。和映射方式设置不一样的是:直接设定方式不会在图例上有反映。比较下面两图: ```{r} p + geom_boxplot(aes(x=cut, fill=cut)) + scale_fill_manual(values=rep("cyan", length(levels(d.sub$cut)))) p + geom_boxplot(aes(x=cut), fill="cyan") ``` 虽然填充色都是青色,但前者用的映射对数据进行了分组,所以会出现分组图例。注意:直接设定方法的参数名称和映射设定是一样的,但是不放在aes函数内部。 在qplot函数中如果要进行美学属性的非映射设定得用 “I” 函数,否则将被当成映射设置。 ```{r} qplot(x=cut, y=price, data=d.sub, geom="boxplot", fill="cyan") qplot(x=cut, y=price, data=d.sub, geom="boxplot", fill=I("cyan")) ``` “I”函数表示设为固定值,如果不是在qplot函数中可以不用它。 透明度属性虽然包含在映射类型中,但一般情况下都是直接设定而非映射设定: ```{r} p + geom_point(data=d.sub, aes(alpha=carat/100)) p + geom_point(data=d.sub, alpha=0.05) ``` ## 3 位置类型映射 x和y映射的用法很明确,就不再罗嗦了。xmin, xmax, ymin, ymax, xend, yend这几种映射属于特殊类型。 ### 3.1 ymin/ymax映射的用法 下面代码通过直线拟合产生了钻石切工和价格的关系数据(预测的价格和标准差) ```{r} dmod <- lm(price ~ cut, data = diamonds) cuts <- data.frame(cut = unique(diamonds$cut), predict(dmod, data.frame(cut = unique(diamonds$cut)), se = TRUE)) cuts ``` 通过设定ymin/ymax映射,用pointrange几何类型可以直接做出带误差线的散点图,无需使用errorbar设置: ```{r} se <- ggplot(cuts, aes(x = cut, y = fit, ymin = fit - se.fit, ymax = fit + se.fit, colour = cut)) se + geom_pointrange() ``` 当然也可以先画点再做误差线,这样思路明确些。或者做其他类型的图如柱形图: ```{r} se + geom_point() + geom_errorbar(width=0.2) se + geom_bar(stat="identity", aes(fill=cut)) + geom_errorbar(width=0.2) ``` min/ymax可以用来改变几何类型的坐标轴范围,但 这不是标准用法 ,最简单的是用ylim函数: ```{r} se + geom_point(aes(ymin = 3000, ymax = 4800)) + geom_errorbar(width=0.2) se + geom_point() + geom_errorbar(width=0.2) + ylim(3000,4800) # 或者用: scale_y_continuous(limits=c(3000,5000)) ``` 但以上方法都不适用于柱形图,得用下面的方法,具体原因以后章节再说: ```{r} se + geom_bar(stat="identity", aes(fill=cut)) + geom_errorbar(width=0.2) + coord_cartesian(ylim=c(3000,4800)) ``` 如果几何类型的设置参数中就包含位置类型映射,情况是很不一样的。所谓“太极”,有点道理: ```{r} px <- ggplot(mtcars, aes(wt, mpg)) + geom_point() px + annotate("rect", xmin = 2, xmax = 3.5, ymin = 2, ymax = 25, fill = "dark grey", alpha = .5) library(grid) px + geom_segment(aes(x = 5, y = 30, xend = 3.5, yend = 25), arrow = arrow(length = unit(0.5, "cm"))) ``` ## 4 特殊类型映射 在ggplot2作图过程中,映射的处理先于其他绘图步骤,也先于统计运算。颜色和形状类型的映射会对数据进行分组,统计运算使用的也是分组后的数据: ```{r} ggplot(d.sub, aes(x=carat, y=price, color=cut)) + geom_point() + geom_smooth(method="lm", se=FALSE) ``` 上面图中平滑曲线的统计处理是按cut进行分类后的数据进行的,分别作出了每一类数据的曲线。但如果要按分组前的数据做统计,就得把数据设为一个整体组;如果要按其他分组方式进行统计,也可以灵活设置: ```{r} ggplot(d.sub, aes(x=carat, y=price, color=cut)) + geom_point() + geom_smooth(aes(group=1), method="lm", se=FALSE) ggplot(d.sub, aes(x=carat, y=price, color=cut)) + geom_point() + geom_smooth(aes(group=color, linetype=color), method="lm", se=FALSE, color="black") ``` 特殊类型映射有特殊应用。曲线图和柱形图对于x轴数据类型的要求是不一样的,曲线图要用连续型数据,而柱形图要用因子型(或离散型)数据,这两类图形如果不经特殊处理就不能放在一起。group映射可以轻松搞定它: ```{r} se + geom_bar(stat="identity", aes(fill=cut)) + geom_errorbar(width=0.2) + coord_cartesian(ylim=c(3000,4800)) + geom_line(aes(group=1), color="black") ``` order映射用于改变数据类型的排序。注意是在图层中的排列顺序而不是图例的先后顺序。看下面图形颜色的差别: ```{r} p + geom_point(aes(color=cut, order=cut)) library(plyr) # 使用desc函数 p + geom_point(aes(color=cut, order=desc(cut))) ``` # 图层语法和图形组合 图层设置是ggplot2作图的关键。通过查看ggplot图形对象的数据结构我们了解到一个图层至少包含几何类型、统计类型和位置调整三方面的东西,当 然数据和映射得首先建立。 ## 1 图层的几何和统计类型 ### 1.1 几何/统计类型设置函数 在ggplot2中,每种几何类型都有对应的(默认)统计类型,反之亦然。两者不分家,所以得放在一起来说。几何类型的设置函数全部为geom_xxx形式,而统计类型设置函数全部为stat_xxx的形式: ```{r} library(ggplot2) ls("package:ggplot2", pattern="^geom_.+") ls("package:ggplot2", pattern="^stat_.+") ``` 下面看一下几何函数geom_point和统计函数stat_identity的参数: ``` geom_point(mapping = NULL, data = NULL, stat = "identity", position = "identity", na.rm = FALSE, ...) stat_identity(mapping = NULL, data = NULL, geom = "point", position = "identity", width = NULL, height = NULL, ...) ``` 有4个参数是一样的:映射(mapping)、数据(data)、位置(position)和点点点(Dot-dot-dot: …)。H.W.特别强调了mapping和data参数的先后顺序在几何/统计类型设定函数和ggplot函数中的差别:ggplot函数先设定数据,再 设定映射;而几何/统计类型函数则相反,因为确定作图或统计之前一般都已经有数据,只需指定映射即可。如果不写参数名,它们的用法是这样的: ``` ggplot(数据, 映射) geom_xxx(映射, 数据) stat_xxx(映射, 数据) ``` "点点点"参数是R语言非常特殊的一个数据类型,用在函数的参数中表示任意参数,在这里表示传递给图层的任意参数如color, shape, alpha等。 取ggplot2的diamonds数据集的一部分数据: ```{r} library(ggplot2) set.seed(100) d.sub <- diamonds[sample(nrow(diamonds), 500),] ``` 前面我们一直用geom_point来做散点图,其实完全可以用stat_identity来做,得到的图形是完全相同的: ```{r} p <- ggplot(d.sub, aes(x=carat, y=price)) theme_set(theme_bw()) p + stat_identity() p + geom_point() ``` 查看图层组成可以看到两者都有geom_point、stat_identity和position_identity: ```{r} (p + stat_identity())$layers (p + geom_point())$layers ``` ## 1.2 图层对象 geom_xxx和stat_xxx可以指定数据、映射、几何类型和统计类型。一般来说,有这些东西我们就可以作图了。但实际情况是这些函数不可以直接出图,因为它不是完整的ggplot对象: ```{r} p <- geom_point(mapping=aes(x=carat, y=price), data=d.sub) class(p) p ``` 未输出图形。 图层只是存储类型为environment的R语言对象,它只有建立在ggplot结构的基础上才会成为图形,在这里哪怕是一个空的ggplot对象框架都很有用。这好比仓库里的帐篷,如果你找不到地方把它们支起来,这些东西顶多是一堆货物: ```{r} ggplot() + p ``` 前面说过多个图层的相加是有顺序的,图层和ggplot对象的加法也是有顺序的,这一点特别需要强调。如果把ggplot对象加到图层上就没有意义。这种规则同样适用于映射和ggplot的相加: ```{r} p + ggplot() class(aes(x=carat, y=price) + ggplot(d.sub)) class(ggplot(d.sub) + aes(x=carat, y=price)) ``` ## 2 图层的位置调整参数 这和位置映射没有关系,当前版ggplot2只有5种: dodge:“避让”方式,即往旁边闪,如柱形图的并排方式就是这种。 fill:填充方式, 先把数据归一化,再填充到绘图区的顶部。 identity:原地不动,不调整位置 jitter:随机抖一抖,让本来重叠的露出点头来 stack:叠罗汉 ```{r} p <- ggplot(d.sub, aes(x=cut, y=price, fill=color)) p + geom_bar(stat="summary", fun.y="mean", position="stack") p + geom_bar(stat="summary", fun.y="mean", position="fill") p + geom_bar(stat="summary", fun.y="mean", position="dodge") p + geom_bar(stat="summary", fun.y="mean", position="jitter") ``` 上面柱形图的抖动很没道理,但散点图里面就有效果了 ```{r} p + geom_point(position="identity") p + geom_point(position="jitter") ``` 再看看x轴数据连续的图形: ```{r} p <- ggplot(d.sub, aes(x=price, fill=cut, color=cut)) p + stat_density(position="stack") p + stat_density(position="fill") p + stat_density(position="identity") p + stat_density(position="identity", fill="transparent") ``` # 3 图层组合 图层的组合不是连续使用几个几何或统计类型函数那么简单。ggplot函数可以设置数据和映射,每个图层设置函数(geom_xxx和stat_xxx) 也都可以设置数据和映射,这虽然给组合图制作带来很大便利,但也可能产生一些混乱。如果不同图层设置的数据和映射不同,将会产生什么后果?得了解规则。 ## 3.1 简单组合 不同的图层使用同一套数据,只是几何类型或统计类型有差别。这是最简单也是最常用的,用ggplot函数设置好数据和映射,把几个图层加起来即可: ```{r} datax <- data.frame(x=1:10, y=rnorm(10)+1:10) p <- ggplot(datax, aes(x=x, y=y)) p + geom_point() + geom_line() p + geom_point() + geom_smooth(method="lm") ``` ggplot2的图层设置函数对映射的数据类型是有较严格要求的,比如geom_point和geom_line函数要求x映射的数据类型为数值向量,而 geom_bar函数要使用因子型数据。如果数据类型不符合映射要求就得做类型转换,在组合图形时还得注意图层的先后顺序: ```{r} p <- ggplot(datax, aes(x=factor(x), y=y)) + xlab("x") p + geom_bar(stat="identity", fill="gray") + geom_line(aes(group=1), size=2) + geom_point(color="red") p + geom_bar(stat="identity", fill="gray") + geom_smooth(aes(group=1), method="lm", se=FALSE, size=2) ``` 作图过程应先设置x映射为因子型(分类坐标轴),用于作柱形图,因为它要求x映射是因子型数据,不能使用连续型数值坐标轴。x映射为因子的数据作散点 图的调整步骤相对简单,设置group映射即可。 ## 3.2 不同映射的组合 映射反映的是数据变量。多数情况下一个图形中使用的是同一个数据集,只是变量不同。通常情况下x,y轴至少有一个是相同的,可以用不同图层叠加不同的数据变量: ```{r} p <- ggplot(d.sub, aes(x=carat)) + ylab("depth (blue) / table (red)") p + geom_point(aes(y=depth), color="blue") + geom_point(aes(y=table), color="red") ``` 但是为什么要这么做呢?预先处理一下数据再作图会更好,图标都已经帮你设好了: ```{r} library(reshape2) datax <- melt(d.sub, id.vars="carat", measure.vars=c("depth", "table")) ggplot(datax, aes(x=carat, y=value, color=variable)) + geom_point() ``` # 6 标尺(scale)设置 标尺是ggplot2作图必需的元素,在映射一节提到了它的概念并简单进行了设置。在数据分析阶段,为避免陷入数据无关的垃圾坑,我们只需要设置映射,ggplot2自动配置合适的标尺并产生坐标和图例。这是ggplot2适合数据可视化分析的原因之一。 在图形美化阶段,我们可以通过修改标尺改善图形外观。标尺设置一般不会对数据产生影响,但坐标轴标尺除外。 ## 1 标尺设定函数 ggplot2修改标尺的函数有一大堆: ```{r} library(ggplot2) scalex <- ls("package:ggplot2", pattern = "^scale.+") length(scalex) ``` 提取函数名的第二个字段,对这些函数的作用进行分类: ```{R} scalex <- scalex[grep("([^_]+_){2}.+", scalex)] unique(gsub("(([^_]+_){2}).+", "\\1***", scalex)) ``` 可以看到标尺设置的内容有8种(颜色color/colour算一种):线条颜色、填充色、透明度、线型、形状、大小,x和y轴。标尺设置的内容都有对应 的映射设置类型,但映射比标尺多了xmin, xmax, ymin, ymax, xend, yend,group和string等(请参看映射一节)。 虽然设置函数很多,但不管是函数用法还是函数名称上都是很有规律的: ```{r} # 线条颜色 scalexx <- scalex[grepl("scale_color.+", scalex)] unique(gsub("(([^_]+_){2})(.+)", "\\3", scalexx)) # 填充色 scalexx <- scalex[grepl("scale_fill.+", scalex)] unique(gsub("(([^_]+_){2})(.+)", "\\3", scalexx)) # 大小 scalexx <- scalex[grepl("scale_size.+", scalex)] unique(gsub("(([^_]+_){2})(.+)", "\\3", scalexx)) # 透明度 scalexx <- scalex[grepl("scale_alpha.+", scalex)] unique(gsub("(([^_]+_){2})(.+)", "\\3", scalexx)) # 线型 scalexx <- scalex[grepl("scale_linetype.+", scalex)] unique(gsub("(([^_]+_){2})(.+)", "\\3", scalexx)) # 形状 scalexx <- scalex[grepl("scale_shape.+", scalex)] unique(gsub("(([^_]+_){2})(.+)", "\\3", scalexx)) # x轴 scalexx <- scalex[grepl("scale_x.+", scalex)] unique(gsub("(([^_]+_){2})(.+)", "\\3", scalexx)) # y轴 scalexx <- scalex[grepl("scale_y.+", scalex)] unique(gsub("(([^_]+_){2})(.+)", "\\3", scalexx)) ``` 除坐标轴外,其它标尺都有四种基本设置函数:"continuous","discrete","identity"和"manual"。结合标尺的作用和设定方法两个标准,H.W把它们分为4种类型的标尺:位置、颜色、无变换和人工设置类型。颜色设置相关的函数较多,线条 颜色和填充色设置的函数类型一样,而x轴和y轴设置的函数类型也一样。 由于标尺函数的命名和用法很有规律,下面仅介绍颜色和坐标轴设置函数的一些用法。 ## 2 颜色标尺设置 ### 2.1 连续型颜色标尺 ggplot2提供了10个填充色设置的标尺函数(线条颜色也一样): ```{r} ls("package:ggplot2", pattern = "^scale_fill.+") ``` 先看看“continuous”的用法。对于数据为非因子型的填充色映射,ggplot2自动使用“continuous”类型颜色标尺表示连续颜色空 间。如果要修改默认颜色就要使用scale_fill_continuous函数进行修改,这个函数最有用的参数是low和high,分别表示低端和高端 数据的颜色,中间颜色根据颜色空间space自动计算: ``` theme_set(theme_bw()) df <- expand.grid(1:30, 1:30) colnames(df) <- c("x", "y") df$z <- rnorm(900) p <- ggplot(data = df, aes(x = x, y = y, fill = z)) p + geom_raster() p + geom_raster() + scale_fill_con ``` 颜色可以使用预设颜色名称,也可以使用十六进制表示。颜色空间可以是rgb或Lab,两者差别还比较大: ``` p + geom_raster() + scale_fill_continuous(low = "#000099", high = "#FF0000", space = "rgb") p + geom_raster() + scale_fill_continuous(low = "#000099", high = "#FF0000", space = "Lab") ``` 连续填充色设置函数还有scale_fill_gradient,scale_fill_gradient2和 scale_fill_gradientn,其中scale_fill_gradient的用法和作用和scale_fill_continuous完全 相同(其实ggplot2早期版本连续颜色标尺默认使用scale_fill_gradient,没有scale_fill_continuous函数; 后者可能是H.W头脑清楚以后加进去的,相当于前者的别名)。scale_fill_gradient2增加了中间点和中间颜色的设置,效果相当不错: ``` p + geom_raster() + scale_fill_gradient2(low = "darkgreen", high = "red", mid = "yellow") p + geom_raster() + scale_fill_gradient2(low = "darkgreen", high = "red", mid = "yellow", midpoint = 1) ``` 而scale_fill_gradientn可以使用colours参数设置多个中间颜色,配合其它颜色参数函数使用也很不错 ``` p + geom_raster() + scale_fill_gradientn(colours = c("blue", "green", "yellow", "red")) p + geom_raster() + scale_fill_gradientn(colours = terrain.colors(100)) ``` 注意参数名称为colours,不是colors,美人们可能不习惯,不过问题不大。 ### 2.2 离散(间断)型颜色标尺 如果数据是因子型的颜色映射,颜色标尺则是离散型的,修改标尺需要使用相应的离散型颜色标尺如scale_color_discrete或 scale_color_hue。这两个函数只是别名函数,早期版本只有scale_color_hue。它们通过设置色调范围(h)、饱和度(c)和亮 度(l)获取颜色,不太容易掌握: ```{r} set.seed(100) dt <- diamonds[sample(nrow(diamonds), 500), ] p <- ggplot(data = dt, aes(x = carat, y = price, color = cut)) p + geom_point() + scale_color_discrete() p + geom_point() + scale_color_discrete(h = c(150, 350), c = 100, l = 60) ``` 注意:因为映射是color,所以标尺设置也得相应用scale_color而不是scale_fill。 scale_color_discrete或scale_color_hue设置颜色不是很直观,如果想要啥来啥,那就用manual类型函数: ```{r} p + geom_point() + scale_color_manual(values = c("blue", "cyan", "yellow", "orange", "red")) p + geom_point() + scale_color_manual(values = rainbow(5)) ``` grey灰度标尺函数是设置离散型颜色的另外一类函数,用法很简单。 ```{r} p + geom_point() + scale_color_grey(start = 0, end = 0.8) ``` brewer类型函数则可以直接使用RColorBrewer包预定义的一些调色板,那些调色板最多能设置8-11种颜色不等,如果超过最大颜色数就不合适了。 ``` x <- sample(LETTERS, 13) y <- 1:13 qplot(x = x, y = y, fill = x, geom = "bar") + scale_fill_brewer(palette = "YlOrRd") x <- x[1:8] y = y[1:8] qplot(x = x, y = y, fill = x, geom = "bar") + scale_fill_brewer(palette = "YlOrRd") ``` ### 2.3 identity标尺 如果数据本身就是可以用作标尺的取值,那当然可以直接使用: ``` set.seed(2) col <- sample(colors(), 4) val <- abs(rnorm(4)) * 10 qplot(x = col, y = val, fill = col, geom = "bar") + scale_fill_identity() qplot(x = carat, y = price, data = dt, size = x) + scale_size_identity() ``` ## 3 坐标轴标尺设置 ggplot2为x或y轴标尺的设置分别提供了7个函数: ```{r} ls("package:ggplot2", pattern = "^scale_x.+") ``` 这些函数最基本的是continuous和discrete两个,通过设置它们的参数可以实现其它函数的效果。 ### 3.1 breaks, labels, limits参数 连续型(continuous)和离散型(discrete)坐标轴标尺设定函数中最常用的参数是breaks、labels和limits,分别用于设置刻度位置、刻度标签和坐标轴范围。先看看前两个参数: ```{r} p <- ggplot(data = dt, aes(x = carat, y = price)) + geom_point() bks <- pretty(range(dt$price), 10) p + scale_y_continuous(breaks = bks) bks <- c(0, 2000, 10000, 15500, 18000) p + scale_y_continuous("Price (*1000)", breaks = bks, labels = bks/1000) ``` 如果xy轴都是非因子数据,limits的设置比较顺利,但另外一个坐标轴还不能自动调整,可能需要改进: ```{r} p + scale_x_continuous(limits = c(0.5, 1.5)) p + scale_y_continuous(limits = c(500, 1000)) ``` ### V3.2 trans参数 连续数据的坐标轴可以设置trans参数,它应该是通过调用scales包的相应trans类型实现的,比如scales中有log10_trans,ggplot2中可以直接设置trans='log10',它其实就是scale_x_log10函数的效果: ```{r} p + scale_y_continuous(trans = "log10") + ggtitle("scale_y_continuous(trans='log10')") p + scale_y_log10() + ggtitle("scale_y_log10()") ``` ### 3.3 坐标轴标尺转换与数据映射 如果你认为坐标轴标尺只是改变了坐标轴的外观那就错了,它还影响到用于建立映射的数据。 下面我们分别用坐标轴标尺应用之前和之后的数据获得拟合直线: ```{r} lmx <- lm(dt$price ~ dt$carat) gs1 <- geom_line(aes(y = lmx$fitted.values), size = 3, color = "red") gs2 <- geom_smooth(aes(group = 1), method = "lm", se = FALSE, size = 1.5) p + gs1 + gs2 p + gs1 + gs2 + scale_x_log10() ``` # 主题(theme)设置 1 theme函数及其参数 让使用者在数据分析阶段能专注于数据而不是图形细节,这是数据可视化分析工具是否合格的标准之一。某些作图软件(或自以为有作图能力的软件)给出的初始图 形简直惨不忍睹,不花时间修改字体、边距、底纹这些东西就恶心得没法继续分析数据。ggplot2做得还可以:即使不做任何设置,多数情况下作出的图形都 还不错,不丑陋也不妖艳,不会分散用户的注意力。这得益于ggplot2的几个预设主题。 ggplot2的四个预设主题我们在前面已经预览过了,下面我们看看主题都包含了哪些东西。这很容易,把ggplot2默认主题的设置函数theme_gray()的代码拿出来看看就知道了: ```{r} library(ggplot2) theme_gray ``` 它无非是一个具有两个参数的函数:base_size和base_family。其主题部分直接应用了另外一个函数:theme。它就是ggplot2的主题设置函数。这个theme函数的产生看起来非常简单: ``` te = FALSE) ``` 但dotdotdot(···)参数却内涵丰富,它可以设置很多内容。 参数 设置内容 继承自 line 所有线属性 rect 所有矩形区域属性 text 所有文本相关属性 title 所有标题属性 axis.title 坐标轴标题 text axis.title.x x轴属性 axis.title axis.title.y y轴属性 axis.title axis.text 坐标轴刻度标签属性 text axis.text.x 属性和继承和前面类似,不再重复 axis.text.y axis.ticks 坐标轴刻度线 line axis.ticks.x axis.ticks.y axis.ticks.length 刻度线长度 axis.ticks.margin 刻度线和刻度标签之间的间距 axis.line 坐标轴线 line axis.line.x axis.line.y legend.background 图例背景 rect legend.margin 图例边界 legend.key 图例符号 legend.key.size 图例符号大小 legend.key.height 图例符号高度 legend.key.width 图例符号宽度 legend.text 图例文字标签 legend.text.align 图例文字标签对齐方式 0为左齐,1为右齐 legend.title 图例标题 text legend.title.align 图例标题对齐方式 legend.position 图例位置 left, right, bottom, top, 两数字向量 legend.direction 图例排列方向 "horizontal" or "vertical" legend.justification 居中方式 center或两数字向量 legend.box 多图例的排列方式 "horizontal" or "vertical" legend.box.just 多图例居中方式 panel.background 绘图区背景 rect panel.border 绘图区边框 rect panel.margin 分面绘图区之间的边距 panel.grid 绘图区网格线 line panel.grid.major 主网格线 panel.grid.minor 次网格线 panel.grid.major.x panel.grid.major.y panel.grid.minor.x panel.grid.minor.y plot.background 整个图形的背景 plot.title 图形标题 plot.margin 图形边距 top, right, bottom, left strip.background 分面标签背景 rect strip.text 分面标签文本 text strip.text.x strip.text.y 除一些尺寸设置有关的内容外(需要用grid包的unit函数设置),几乎所有元素都在theme函数内使用 element_line,element_rect,element_text和element_blank函数设置,使用方法参考这几个函数的参数说 明即可,这里不再一一举例说明。 text, line, rect和title是最顶层的元素,理论上可以做全局设定,但当前版本ggplot2还没有实现,可以根据情况做一些调整: ```{r} x <- LETTERS[1:10] y <- abs(rnorm(10)) p <- qplot(x = x, y = y, color = x, fill = x, geom = c("line", "point"), group = 1) + labs(title = "The figure title.", xlab = "Factor", ylab = "Value") + theme(text = element_text(color = "red", size = 16), line = element_line(color = "blue"), rect = element_rect(fill = "white")) p + theme(panel.background = element_rect(fill = "transparent", color = "gray"), legend.key = element_rect(fill = "transparent", color = "transparent"), axis.text = element_text(color = "red")) ``` ## 2 自定义主题 图形细节设置虽然繁琐,但是在R中可以相当简单。由于自己使用的或者杂志要求的图形外观一般都很固定,我们可以使用ggplot2的theme函数非常方便地定义自己的图形主题。下面是我自用的一个主题函数,主要作的改动是坐标轴刻度朝向和统一了图形各个区域的背景颜色: ```{r} ##' A nice-looking ggplot2 theme: inward axis ticks, legend title excluded, and uniform background. ##' @title A nice-looking ggplot2 theme ##' @param ... ##' Parameters passed to theme_classic() function. ##' @param bg ##' Color string (default 'white') for user defined uniform background. ##' @return ##' ggplot2 theme object. ##' @examples ##' library(ggplot2) ##' qplot(x=carat, y=price, color=cut, data=diamonds) + theme_zg() ##' @author ZGUANG ##' @export theme_zg <- function(..., bg='white') { require(grid) theme_classic(...) + theme(rect=element_rect(fill=bg), plot.margin=unit(rep(0.5,4), 'lines'),panel.background=element_rect(fill='transparent', color='black'),panel.border=element_rect(fill='transparent', color='transparent'), panel.grid=element_blank(), axis.title = element_text(color='black', vjust=0.1), axis.ticks.length = unit(-0.4,"lines"), axis.ticks = element_line(color='black'), axis.ticks.margin = unit(0.8,"lines"), legend.title=element_blank(), legend.key=element_rect(fill='transparent', color='transparent')) } ``` 自定义的主题可以编入自己的R语言包中,方便调用。如果觉得你的主题很有代表性,可以发给ggplot2的作者H.W.,让他加到ggplot2的下一个发行版中。比如上面上面函数加入ggplot2后就可以直接调用: ```{r} p <- qplot(x = x, y = y, color = x, fill = x, geom = c("line", "point"), group = 1) + labs(title = "The figure title.", xlab = "Factor", ylab = "Value") p + theme_zg() p + theme_zg(base_size = 16, bg = "gray90") ```