4.1 简介
qplot()的局限性在于它只能使用一个数据集和一组图形属性映射,解决这个问题的办法就是使用图层。每个图层可以有自己的数据集和图形属性映射,附加的数据元素可通过图层添加到图形中。
一个图层由五个部分组成:
- 数据:必须是一个数据框;
- 一组图形属性映射,用来设定数据集中的变量如何映射到该图层的图形属性;
- 几何对象,用来指定在图层中用哪种几何对象来绘图;
- 统计变换,返回一个包含新变量的数据框;
- 位置调整,通过调整元素位置来避免图形重合。
4.2 创建绘图对象
当我们调用qplot()时,它其实为我们做了很多幕后工作:创建一个图形对象,添加图层并且展示结果。在整个过程中它使用了很多默认的绘图参数。如果想要手动创建图形对象,就用用到ggplot()函数。只有在新添加的图层里设定了新参数时,默认值才会被修改。
4.3 图层
layer(geom, geom_params, stat, stat_params, data, mapping, position)
p <- ggplot(diamonds, aes(x = carat)) p <- p + layer(geom = "bar", geom_params = list(fill = "steelblue"), stat = "bin", stat_params = list(binwidth = 2)) p
该代码生成一个组距为2,铁青色的直方图。
下面的快捷函数生成与上述代码完全相同的图层。
geom_histogram(binwidth = 2, fill = "steelblue")
所有这类快捷函数都由相同的形式——以geom_或者stat_开头:
- geom_XXX(mapping, data, ..., stat, position)
- stat_XXX(mapping, data, ..., geom, position)
它们的参数定义了图层的各种组件:
- mapping(可选):一个图形属性映射,通过aes()函数来设定;
- data(可选):一个数据集,它会修改默认的数据集。大部分情况下该参数被省略,默认数据集被调用;
- ...:geom或者stat的参数,例如直方图的组距(binwidth)或者loess光滑曲线的带宽(bandwidth);
- geom/stat(可选):可以修改geom默认的stat值,或者stat默认的geom值,它们是一组字符串,包含了将要使用的几何对象或统计变换的名称,使用默认值将会得到标准的图形,修改默认值会得到一些新奇的图形;
- position(可选):选择一种调整对象重合的方式。
sleep:这是哺乳动物睡眠数据集的更新和扩展版本。
> head(msleep) # A tibble: 6 x 11 name genus vore order conservation sleep_total sleep_rem sleep_cycle <chr> <chr> <chr> <chr> <chr> <dbl> <dbl> <dbl> 1 Chee~ Acin~ carni Carn~ lc 12.1 NA NA 2 Owl ~ Aotus omni Prim~ NA 17 1.8 NA 3 Moun~ Aplo~ herbi Rode~ nt 14.4 2.4 NA 4 Grea~ Blar~ omni Sori~ lc 14.9 2.3 0.133 5 Cow Bos herbi Arti~ domesticated 4 0.7 0.667 6 Thre~ Brad~ herbi Pilo~ NA 14.4 2.2 0.767 # ... with 3 more variables: awake <dbl>, brainwt <dbl>, bodywt <dbl>
- name:常用名
- genus,vore:食肉动物、杂食动物还是食草动物?
- order,conservation:动物的保护状况
- sleep_total:睡眠总量,以小时为单位
- sleep_rem:快速眼动睡眠,以小时为单位
- sleep_cycle:睡眠周期的长度,以小时为单位
- awake:醒着的时间,以小时为单位
- brainwt:脑重量(公斤)
- bodywt:体重(公斤)
图层被添加到用ggplot()或qplot()创建的图形对象上。
## 在用ggplot创建的图形对象上添加图层 ggplot(msleep, aes(sleep_rem/sleep_total, awake)) + geom_point() # 等价于 qplot(sleep_rem/sleep_total, awake, data = msleep)
# 也可以给qplot添加图层 qplot(sleep_rem/sleep_total, awake, data = msleep) + geom_smooth() # 等价于 qplot(sleep_rem/sleep_total, awake, data = msleep, geom = c("point", "smooth")) # 或 ggplot(msleep, aes(sleep_rem/sleep_total, awake)) + geom_point() + geom_smooth()
图层是普通的R对象,所以可以存储到变量里去,这有利于代码避繁就简。例如,一组图形可以先用不同的数据进行初始化,然后加上相同的图层。下面的例子创建了一个带有半透明深蓝色回归线的图层。
bestfit <- geom_smooth(method = "lm", se = F, colour = alpha("steelblue", 0.5), size = 2) qplot(sleep_rem, sleep_total, data = msleep) + bestfit qplot(bodywt, brainwt, data = msleep, log = "xy") + bestfit
4.4 数据
ggplot2对数据的要求很简单:必须是一个数据框。
要使用相同的代码,不同的数据集绘图,只需改变数据集即可。下面的例子用%+%来添加新的数据集以代替原来的数据集。
p <- ggplot(mtcars, aes(mpg, wt, colour = cyl)) + geom_point() p mtcars <- transform(mtcars, mpg = mpg^2) p %+% mtcars
数据是以副本而不是引用的形式存储到图形对象中的。
4.5 图形属性映射
aes(x = weight, y = height, colour = age)
aes()函数用来将数据变量映射到图形中,从而使变量成为可以被感知的图形属性。
注意最好不要指定数据集以外的变量(例如diamonds$carat),因为这样无法将绘图所用的数据都封装到一个对象里。每个aes()函数里的变量都包含于默认数据集或者图层数据集中,是保证ggplot2对象都是自含型的重要方式之一,这样方便存储和重复使用。但可以使用变量的函数值作为参数。
4.5.1 图和图层
默认的图形属性可以在图形对象初始化时设定,或者过后用+修改。如下例,图形对象p中默认的映射可以在新图层里扩充或修改。
## 使用默认的参数映射来添加图层 p <- ggplot(mtcars, aes(x = mpg, y = wt)) p + geom_point()
p + geom_point(aes(colour = factor(cyl)))
p + geom_point(aes(y = disp))
4.5.2 设定和映射
除了可以将一个图形属性映射到一个变量,也可以在图层的参数里将其设定为单一值。图形属性可以根据观测的不同而变化,但是参数则不行。
p <- ggplot(mtcars, aes(mpg, wt)) p + geom_point(colour = "darkblue")
# 注意这里将颜色映射到'darkblue'与上面将颜色设定给'darkblue'的区别 p + geom_point(aes(colour = "darkblue"))
上面的图将点的颜色设定为深蓝色,下面的图将colour映射到"darkblue"颜色,实际上是先创建了一个只含有"darkblue"字符的变量,然后将colour映射到这个新变量。
在使用qplot()函数的时候,可以将某个值放到I()里来实现映射,例如colour = I("darkblue")。
4.5.3 分组
图中所有离散型变量的交互作用被设定为分组的默认值,通常情况下这样可以正确地给数据分组,但是如果没能正确分组或者图中没有离散型变量,那么就需要自定义分组结构,即将group映射到一个在不同组有不同取值的变量。当现有的单个变量不能够正确地分组,而两个变量的组合可以正确分组时,可以使用interaction()函数。
> library(nlme) > head(Oxboys) Grouped Data: height ~ age | Subject Subject age height Occasion 1 1 -1.0000 140.5 1 2 1 -0.7479 143.4 2 3 1 -0.4630 144.8 3 4 1 -0.1643 147.1 4 5 1 -0.0027 147.7 5 6 1 0.2466 150.2 6
牛津男孩的身高:
- Subject:为实验中每个男孩提供唯一标识符的有序因子
- age:给出标准年龄的数字向量(无量纲)
- height:一个数字向量,给出男孩的身高(厘米)
- Occasion:把年龄从一个连续的变量转换成一个计数的结果而得到的一个有序因素,这些轻微不平稳的数据可以被认为是平稳的。
多个分组与单个图形属性
将数据分为若干组,并用相同的方式对每个组进行渲染。当从总体上来查看数据时,我们通常希望区分每个个体(group)而不是识别他们(colour)。这在含有多个个体的纵向数据中是很常见的,而这类图形也常被称为“细面图”。
p <- ggplot(Oxboys, aes(age, height, group = Subject)) p + geom_line()
不同图层上的不同分组
希望在所有男孩的年龄和身高图中添加一条光滑线条,新图层需要一个不同的分组图形属性,group=1,这样绘制出的线条就是基于整体的。
p <- ggplot(Oxboys, aes(age, height, group = Subject)) p + geom_line() + geom_smooth(aes(group = 1), method = "lm", size = 1.5, se = F) # 或 qplot(age, height, data = Oxboys, group = Subject, geom = "line") + geom_smooth(aes(group = 1), method = "lm", size = 1.5, se = F)
修改默认分组
如果图像中含有离散型变量,而我们希望绘制连接所有分组的线条,那么可以采取绘制交互作用图、轮廓图以及平行坐标图所用的策略。这里绘制各个测量时期身高的箱线图并添加个体轨迹。
ggplot(Oxboys, aes(Occasion, height)) + geom_boxplot() + geom_line(aes(group = Subject), colour = "#3366FF")
4.5.4 匹配图形属性和图像属性
线条和路径遵循差一原则:观测点比线段数目多一,第一条线段使用第一个观测的图形属性,第二条线段使用第二个观测点的图形属性,以此类推,这意味着最后一个观测的图形属性将不会被线段用到。
df <- data.frame(x = 1:3, y = 1:3, colour = c(1, 3, 5)) qplot(x, y, data = df, colour = factor(colour), size = I(5)) + geom_line(aes(group = 1), size = 2) qplot(x, y, data = df, colour = colour, size = I(5)) + geom_line(size = 2)
线段平稳地从一种图形属性变换到另一种图形属性。
## 用线性插值法做颜色渐变线条 xgrid <- with(df, seq(min(x), max(x), length = 50)) interp <- data.frame(x = xgrid, y = approx(df$x, df$y, xout = xgrid)$y, colour = approx(df$x, df$colour, xout = xgrid)$y) qplot(x, y, data = df, colour = colour, size = I(5)) + geom_line(data = interp, size = 2)
多边形可用fill参数进行填充,这是整体对象的一个属性。
## 图4.7 一个条形图(左)按组分解后得到的叠加条形图(右),两者轮廓相同。 qplot(color, data = diamonds) qplot(color, data = diamonds, fill = cut)
若不用fill而用colour,则会对边界上色,效果远不如上图明显。
qplot(color, data = diamonds, colour = cut)
4.6 几何对象
几何图形对象,简称为geom,他执行着图层的实际渲染,控制着生成的图形类型。例如,用电几何对象将会生成散点图,而用线几何对象就会生成折线图。下表列出了ggplot2中所有可用的几何对象。
每个几何对象都有一组它能识别的图形属性和一组绘图所需的值。例如,一个点含有颜色、大小和形状等图形属性,以及x和y位置坐标。一个条形含有高度、条宽、边界颜色和填充颜色等图形属性。下表中列出了所有几何对象的图形属性。
每个几何对象都有一个默认的统计变换,并且每一个统计变换都有一个默认的几何对象。这些默认值如下表所示。
名称 | 描述 | 默认的统计变换 | 图形属性 |
abline | 线,由斜率和截距决定 | abline | colour,linetype,size |
area | 面积图 | identity | colour,fill,linetype,size,x,y |
bar | 条形图,以x轴为底的矩形 | bin | colour,fill,linetype,size,weight,x |
bin2d | 2维热图 | bin2d | colour,fill,linetype,size,weight,xmax,xmin,ymax,ymin |
blank | 空白,什么也不画 | identity | |
boxplot | 箱线图 | boxplot | colour,fill,lower,middle,size,upper,weight,x,ymax,ymin |
contour | 等高线图 | contour | colour,linetype,size,weight,x,y |
crossbar | 带有水平中心线的盒子图 | identity | colour,fill,linetype,size,x,y,ymax,ymin |
density | 光滑密度曲线图 | density | colour,fill,linetype,size,weight,x,y |
density2d | 二维密度等高线图 | density2d | colour,linetype,size,weight,x,y |
dotplot | “点直方图”,用电来表示观测值的个数 | bindot | colour,fill,x,y |
errorbar | 误差棒 | indetity | colour,linetype,size,width,x,ymax,ymin |
errorbarh | 水平的误差棒 | identity | colour,linetype,size,width,y,ymax,ymin |
freqpoly | 频率多边形图 | bin | colour,linetype,size |
hex | 用六边形表示的2维热图 | binhex | colour,fill,size,x,y |
histogram | 直方图 | bin | colour,fill,linetype,size,weight,x |
hline | 水平线 | hline | colour,linetype,size |
jitter | 给点添加扰动,减轻图形重叠问题 | identity | colour,fill,shape,size,x,y |
line | 按照x坐标的大小顺序依次连接各个观测值 | identity | colour,linetype,size,x,y |
linerange | 一条代表一个区间的竖直线 | identity | colour,linetype,size,x,ymax,ymin |
map | 基准地图里的多边形 | identity | colour,fill,linetype,size,x,y,map_id |
path | 按数据的原始顺序连接各个观测值 | identity | colour,linetype,size,x,y |
point | 点,用来绘制散点图 | identity | colour,fill,shape,size,x,y |
pointrange | 用一条中间带点的竖直线代表一个区间 | identity | colour,fill,linetype,shape,size,x,y,ymax,ymin |
polygon | 多边形,相当于一个有填充的路径 | identity | colour,fill,linetype,size,x,y |
quantile | 添加分位数回归线 | quantile | colour,linetype,size,weight,x,y |
raster | 高效的矩形瓦片图 | identity | colour,fill,linetype,size,x,y |
rect | 2维的矩形图 | identity | colour,fill,linetype,size,xmax,xmin,ymax,ymin |
ribbon | 色带图,连续的x值所对应的y的范围 | identity | colour,fill,linetype,size,x,ymax,ymin |
rug | 边际地毯图 | identity | colour,linetype,size |
segment | 添加线段或箭头 | identity | colour,linetype,size,x,xend,y,yend |
smooth | 添加光滑的条件均值线 | smooth | alpha,colour,fill,linetype,size,weight,x,y |
step | 以阶梯形式连接各个观测值 | identity | colour,linetype,size,x,y |
text | 文本注释 | identity | angle,colour,hjust,label,size,vjust,x,y |
tile | 瓦片图 | identity | colour,fill,linetype,size,x,y |
violin | 小提琴图 | ydensity | weight,colour,fill,size,linetype,x,y |
vline | 竖直线 | vline | colour,linetype,size |
4.7 统计变换
统计变换,简称stat,通常以某种方式对数据信息进行汇总。例如,平滑是一个很有用的统计变换,它能在一些限制条件的约束下计算给定x值时y的平均值。下表中列出了可用的统计变换。为了阐明在图形中的意义,一个统计变换必须是一个位置尺度不变量,即$f(x + a) = f(x) + a$并且$f(b cdot x) = b cdot f(x)$,这样才能保证当改变数据的标度时,数据变换的展现形式不变。
名称 | 描述 |
bin | 计算封箱数据 |
bin2d | 计算矩形封箱内的观测值个数 |
bindot | 计算“点直方图”的封箱数据 |
binhex | 计算六边形热图的封箱数据 |
boxplot | 计算组成箱线图的各种元素值 |
contour | 三维数据的等高线 |
density | 一维密度估计 |
density2d | 二维密度估计 |
function | 添加新函数 |
identity | 不对数据进行统计变换 |
计算qq图的相关值 | |
quantile | 计算连续的分位数 |
smooth | 添加光滑曲线 |
spoke | 将角度和半径转换成xend和yend |
sum | 计算每一个单一值的频数,有助于解决散点图的图形重叠问题 |
summary | 对每个x所对应的y值做统计描述 |
summary2d | 对2维矩形封箱设定函数 |
summaryhex | 对2维六边形封箱设定函数 |
unique | 删除重复值 |
ydensity | 小提琴图,计算1维y轴方向的核密度函数估计值 |
4.8 位置调整
所谓位置调整,就是对该层中的元素的位置进行微调,位置调整一般多见于处理离散型数据。下表中列出了可用的位置调整函数。
名称 | 描述 |
dodge | 避免重叠,并排放置 |
fill | 堆叠图形元素并将高度标准化为1 |
identity | 不做任何调整 |
jitter | 给点添加扰动避免重合 |
stack | 将图形元素堆叠起来 |
在条形图中可以很好地解释不同类型的位置调整。
## 应用于条形图的三种位置调整。从左到右依次是:堆叠(stacking),填充 ## (filling)和并列(dodging) dplot <- ggplot(diamonds, aes(clarity, fill = cut)) dplot + geom_bar(position = "stack") dplot + geom_bar(position = "fill") dplot + geom_bar(position = "dodge")
4.9 整合
4.9.1 结合几何对象和统计变化
## 直方图的三种变体。频率多边形(frequency ## polygon)(左);散点图,点的大小和 ## 高度都映射给了频率(中);热图(heatmap)用颜色来表示频率。 d <- ggplot(diamonds, aes(carat)) d + stat_bin(aes(ymax = ..count..), binwidth = 0.1, geom = "area") d + stat_bin(aes(size = ..density..), binwidth = 0.1, geom = "point", position = "identity") d + stat_bin2d(aes(y = 1, fill = ..count..), binwidth = 0.1, geom = "tile", position = "identity")
4.9.2 显示已计算过的统计量
如果已有汇总过的数据,并且希望直接使用它,而不进行其他的统计变换,可以使用stat_identity(),然后将合适的变量映射到相应的图形属性中。
4.9.3 改变图形属性和数据集
个体拟合预测,并添加预测线。
model <- lme(height ~ age, data = Oxboys, random = ~1 + age | Subject) oplot <- ggplot(Oxboys, aes(age, height, group = Subject)) + geom_line() age_grid <- seq(-1, 1, length = 10) subjects <- unique(Oxboys$Subject) preds <- expand.grid(age = age_grid, Subject = subjects) preds$height <- predict(model, preds) oplot + geom_line(data = preds, colour = "#3366FF", size = 0.4)
绘制残差图,后修改模型为带二次项的拟合模型(效果较好)。
Oxboys$fitted <- predict(model) Oxboys$resid <- with(Oxboys, fitted - height) oplot %+% Oxboys + aes(y = resid) + geom_smooth(aes(group = 1)) model2 <- update(model, height ~ age + I(age^2)) Oxboys$fitted2 <- predict(model2) Oxboys$resid2 <- with(Oxboys, fitted2 - height) oplot %+% Oxboys + aes(y = resid2) + geom_smooth(aes(group = 1))
我们对图形的修改是非常容易的,更新数据并重新作图时并不需要重新运行oplot,这正是ggplot2所秉承的理念:使得反复拟合和评估模型变得轻松而自然。