ggplot2工具箱

简介

本文列举了ggplot2中大量几何对象和统计变换的一部分,并把它们按照用途进行了细分。

图层叠加的总体策略

总体来说,图层有三种用途:

  1. 用以展示数据本身。这一层不必多说,几乎在每一幅图形上都会出现。
  2. 用以展示数据的统计摘要。我们可以从数据摘要中进一步理解数据,同时对模型作出评价。本层通常绘制在数据层之上。
  3. 用以添加额外的元数据(metadata)、上下文信息和注解。元数据层展示了背景的上下文信息,也可以为原始数据赋予有现实意义的注解。

基本图形类型

以下几何对象是ggplot2图形的基本组成部分。每种几何对象自身即可独立构建图形,同时也可以组合起来构建更复杂的几何对象。

  • geom_area()用于绘制面积图(area plot),即在普通线图的基础上,依y轴方向填充了下方面积的图形。对于分组数据,各组将按照依次堆积的方式绘制。
  • geom_bar(stat = “identity”)绘制条形图。需要指定stat = “identity”,因为默认的统计变换将自动对“值”进行计数。而统计变换identity将保持数据不变。
  • geom_line()绘制线条图。
  • geom_point()绘制散点图。
  • geom_path()绘制路径图。
  • geom_polygon()绘制多边形,即填充后的路径。
  • geom_text()可在指定点处添加标签。它是这些几何对象中唯一一个需要额外图形属性的:它需要指定label参数。
  • geom_tile()用来绘制色深图(image plot)或水平图(level plot)。所有的瓦片(tile)构成了对平面的一个规则切分,且往往将fill图形属性映射至另一个变量。

代码举例:

df <- data.frame(x = c(3, 1, 5), y = c(2, 4, 6), label = c("a", "b", "c"))
p <- ggplot(df, aes(x, y)) + xlab(NULL) + ylab(NULL)
p + geom_point() + labs(title = "geom_point")  // 散点图

id

p + geom_bar(stat = "identity") + labs(title = "geom_bar(stat=\"identity\")")       // 条形图

id

p + geom_line() + labs(title = "geom_line")     // 线条图

id

p + geom_area() + labs(title = "geom_area")     // 面积图

id

p + geom_path() + labs(title = "geom_path")     // 路径图

id

p + geom_text(aes(label = label)) + labs(title = "geom_text")   //含标签的散点图

id

p + geom_tile() + labs(title = "geom_tile")     // 色深图or水平图

id

p + geom_polygon() + labs(title = "geom_polygon")   // 多边形图

id

展示数据分布

有一些几何对象可以用于展示数据的分布,具体使用哪种取决于分布的维度、分布是连续型或是离散型,以及我们感兴趣的是条件分布还是联合分布。

有多种方式可以用来进行分布的跨组比较:

  • 同时绘制多个小的直方图,facets = . ~ var;
  • 使用条件密度图,position = “fill”;
  • 使用频率多边形(frequency polygon),geom = “freqpoly”。
depth_dist <- ggplot(diamonds, aes(depth)) + xlim(58, 68)
depth_dist + geom_histogram(aes(y = ..density..), binwidth = 0.1) + facet_grid(cut ~ .)

id

depth_dist + geom_histogram(aes(fill = cut), binwidth = 0.1, position = "fill")

id

depth_dist + geom_freqpoly(aes(y = ..density.., colour = cut), binwidth = 0.1)

id

和分布相关的许多几何对象都是以几何对象(geom)/统计变换(stat)的形式成对出现的。这些几何对象中大多数的本质都是别名(alias):一个基本几何对象结合一个统计变换,即可绘制出想要的图形。

  • geom_boxplot = stat_boxplot + geom_boxplot: 箱线图,即一个连续型变量针对一个类别型变量取条件所得的图形。当类别型变量有许多独立的取值时,这种图形比较有用。箱线图也可对连续型变量取条件,前提是数据预先经过巧妙地封箱(binning)处理。下面给出例子:
library(plyr)
qplot(cut, depth, data = diamonds, geom = "boxplot")

id

qplot(carat, depth, data = diamonds, geom = "boxplot", group = round_any(carat, 0.1, floor), xlim = c(0, 3))

id

  • geom_jitter = position_jitter + geom_point: 通过在离散型分布上添加随机噪声以避免遮盖绘制问题,这是一种较为粗糙的方法。下面的例子:
qplot(class, cty, data = mpg, geom = "jitter")

id

qplot(calss, drv, data = mpg, geom = "jitter")

id

  • geom_density = stat_density + geom_area: 基于核平滑方法进行平滑后得到的频率多边形。给出下面的例子:
qplot(depth, data = diamonds, geom = "density", xlim = c(54, 70))

id

qplot(depth, data = diamonds, geom = "density", xlim = c(54, 70), fill = cut, alpha = I(0.2))

id

处理遮盖绘制问题

散点图是研究两个连续型变量间关系的重要工具。但是当数据量很大时,这些点经常会出现重叠现象,从而掩盖真实的关系。在极端情况下,甚至只能看到数据所在的大致范围,根据这种图形作出的任何结论都是值得怀疑的。这种问题被称为遮盖绘制(overplotting),对付它有许多方法:

  • 小规模的遮盖绘制问题可以通过绘制更小的点加以缓解,或者使用中空的符号。下面给个例子,数据是从两个独立的正态分布中抽样所得的200个点:
df <- data.frame(x = rnorm(2000), y = rnorm(2000))
norm <- ggplot(df, aes(x, y))
norm + geom_point()

id

norm + geom_point(shape = 1)

id

norm + geom_point(shape = ".")

id

  • 对于更大的数据集产生的更为严重的遮盖绘制问题,我们可以使用alpha让点呈现透明效果。下面给出代码:
norm + geom_point(colour = "black", alpha = 1/3)

id

norm + geom_point(colour = "black", alpha = 1/5)

id

norm + geom_point(colour = "black", alpha = 1/10)

id

  • 如果数据存在一定的离散性,我们可以通过在点上增加随机扰动来减轻重叠。特别是在与透明度一起使用时,这种方法很有效。下面给出代码:
td <- ggplot(diamonds, aes(table, depth)) + xlim(50, 70) + ylim(50, 70)
td + geom_point()

id

td + geom_jitter()

id

jit <- position_jitter(width = 0.5)
td + geom_jitter(position = jit)

id

td + geom_jitter(position = jit, colour = "black", alpha = 1/10)

id

td + geom_jitter(position = jit, colour = "black", alpha = 1/50)

id

td + geom_jitter(position = jit, colour = "black", alpha = 1/200)

id

从上面可以得到启发,可以认为遮盖绘制问题是一种二维核密度估计问题,下面再给出两种方法:

  • 将点分箱并统计每个箱中点的数量,然后通过某种方式可视化这个数量(直方图的二维推广)。
d <- ggplot(diamonds, aes(carat, price)) + xlim(1, 3) + theme(legend.position = "none")
d + stat_bin2d()

id

d + stat_bin2d(bins = 10)

id

d + stat_bin2d(binwidth = c(0.02, 200))

id

  • 使用stat_density2d作二维密度估计,并将等高线添加到散点图中,或以着色瓦片(color3d tiles)直接展示密度,或使用大小与分布密度成比例的点进行展示。
d + geom_point() + geom_density2d()

id

d + stat_density2d(geom = "point", aes(size = ..density..), contour = F) + scale_size_area()

id

d + stat_density2d(geom = "tile", aes(fill = ..density..), contour = F)

id

last_plot() + scale_fill_gradient(limits = c(1e-5, 8e-4))

id

曲面图

ggplot2暂不支持真正的三维曲面图。但具有在二维平面上展现三维曲面的常见工具:等高线图,着色瓦片(colored tiles)以及气泡图。

绘制地图

ggplot2中的maps包只有美国本土等地图,要想绘制中国地图,参考《用R软件绘制中国分省市地图》一文。下面给出绘制美图地图的例子:

library(maps)
data(us.cities)
big_cities <- subset(us.cities, pop > 500000)   //美国(2006年1月)五十万人口以上的城市
qplot(long, lat, data = big_cities) + borders("state", size = 0.5)

id

library(maps)
states <- map_data("state")
arrests <- USArrests
names(arrests) <- tolower(names(arrests))
arrests$region <- tolower(rownames(USArrests))
choro <- merge(states, arrests, by = "region")
choro <- choro[order(choro$order), ]
qplot(long, lat, data = choro, group = group, fill = assault, geom = "polygon")     //美国各州人身伤害案件的数量

id

qplot(long, lat, data = choro, group = group, fill = assault / murder, geom = "polygon")    //人身伤害和谋杀类案件的比率

id

揭示不确定性

不论是从模型所得或是对分布的假设而得,如果已经知道了一些关于数据中不确定性的信息,那么对这些信息加以展示通常是很重要的。ggplot2中有四类几何对象用于这项工作,它们均假设我们对给定x时y的条件分布感兴趣,并且都使用了图形属性ymin和ymax来确定y的值域。请看下表:

变量X类型 仅展示区间 同时展示区间和中间值
连续型 geom_ribbon geom_smooth(stat = “identity”
离散型 geom_errorbar geom_crossbar
geom_linerange geom_pointrange

下面的例子拟合了一个双因素含交互效应回归模型,并且展示了如何提取边际效应(marginal effects)和条件效应(conditional effects),以及如何将其可视化:

d <- subset(diamonds, carat < 2.5 & rbinom(nrow(diamonds), 1, 0.2) == 1)
d$lcarat <- log10(d$carat)
d$lprice <- log10(d$price)

# 剔除整体的线性趋势
detrend <- lm(lprice ~ lcarat, data = d)
d$lprice2 <- resid(detrend)

mod <- lm(lprice2 ~ lcarat * color, data = d)

library(effects)

effectdf <- function(...) {
    suppressWarnings(as.data.frame(effect(...)))
}
color <- effectdf("color", mod)
both1 <- effectdf("lcarat:color", mod)
carat <- effectdf("lcarat", mod, default.levels = 50)
both2 <- effectdf("lcarat:color", mod, default.levels = 3)
# 进行数据变换以移除显而易见的效应
qplot(lcarat, lprice, data = d, colour = color)     //对x轴和y轴的数据均取以10为底的对数以剔除非线性性

id

qplot(lcarat, lprice2, data = d, colour = color)    //剔除了主要的线性趋势

id

fplot <- ggplot(mapping = aes(y = fit, ymin = lower, ymax = upper)) + ylim(range(both2$lower, both2$upper))
# 展示模型估计结果中变量color的不确定性
fplot %+% color + aes(x = color) + geom_point() + geom_errorbar()    //color的边际效应

id

fplot %+% both2 + aes(x = color, colour = lcarat, group = interaction(color, lcarat)) + geom_errorbar() + geom_line(aes(group = lcarat)) + scale_colour_gradient()      //针对变量carat的不同水平(level),变量color的条件效应

id

# 展示模型估计结果中变量carat的不确定性
fplot %+% carat + aes(x = lcarat) + geom_smooth(stat = "identity")      //caret的边际效应

id

ends <- subset(both1, lcarat == max(lcarat))
fplot %+% both1 + aes(x = lcarat, colour = color) + geom_smooth(stat = "identity") + scale_colour_hue() + theme(legend.position = "none") + geom_text(aes(label = color, x = lcarat + 0.02), ends)      //针对变量color的不同水平,变量caret的条件效应

id

统计摘要

对于每个x的取值,计算对应y值得统计摘要通常是很有用的。ggplot2是用stat_summary()来完成。

使用stat_summary()时,既可以为每个参数单独地指定摘要计算函数,也可以用一个统一的函数对它们进行组合。

添加图形注解

记住一点:这些注解仅仅是额外的数据而已。两种方式:逐个添加或批量添加。下面的例子中,分别用以上两种方式,向经济数据中加入了有关美国总统的信息。

(unemp <- qplot(date, unemploy, data = economics, geom = "line", xlab = "", ylab = "No. unemployed (1000s)"))

id

presidential <- presidential[-(1:3), ]
yrng <- range(economics$unemploy)
xrng <- range(economics$date)
unemp + geom_vline(aes(xintercept = as.numeric(start)), data = presidential)

id

library(scales)
unemp + geom_rect(aes(NULL, NULL, xmin = start, xmax = end, fill = party), ymin = yrng[1], ymax = yrng[2], data = presidential, alpha = 0.2) + scale_fill_manual(values = c("blue", "red"))

id

last_plot() + geom_text(aes(x = start, y = yrng[1], label = name), data = presidential, size = 3, hjust = 0, vjust = 0)

id

caption <- paste(strwrap("Unemployment rates in the US have varied a lot over the years", 40), collapse="\n")
unemp + geom_text(aes(x, y, label = caption), data = data.frame(x = xrng[2], y = yrng[2]), hjust = 1, vjust = 1, size = 4)

id

highest <- subset(economics, unemploy == max(unemploy))
unemp + geom_point(data = highest, size = 3, colour = "red", alpha = 0.5)

id

  • geom_text 可添加文字叙述或为点添加标签。(使用取子集的方式)抽取部分观测添加标签可能会非常有用————往往希望标注出离群点或其他重要的点。
  • geom_vline,geom_hline: 向图形添加垂直线或水平线。
  • geom_abline: 向图形添加任意斜率和截距的直线。
  • geom_rect 可强调图形中感兴趣的矩形区域。
  • geom_line,geom_path 和 geom_segment 都可以添加直线。

含权数据

这里以2000年美国人口普查中,中西部各州的统计数据为例。此数据中主要包含的是比例型数据(例如白种人比例、贫困线以下人口比例、有大学学历的人口比例)和每个郡的基本信息(面积、人口总数、人口密度)。

首先,对于线和点这类简单的几何对象,我们可以根据点的数量调整图形属性size来改变点的大小。

qplot(percwhite, percbelowpoverty, data = midwest)      //无权重

id

qplot(percwhite, percbelowpoverty, data = midwest, size = poptotal / 1e6) + scale_size_area("Population\n(millions)", breaks = c(0.5, 1, 2, 4))     //以人口数量为权重

id

qplot(percwhite, percbelowpoverty, data = midwest, size = area) + scale_size_area()     //以面积为权重

id

lm_smooth <- geom_smooth(method = lm, size = 1)
qplot(percwhite, percbelowpoverty, data = midwest) + lm_smooth      //未考虑权重的最优拟合曲线

id

qplot(percwhite, percbelowpoverty, data = midwest, weight = popdensity, size = popdensity) + lm_smooth      //以人口数量作为权重的最优拟合曲线

id

qplot(percbelowpoverty, data = midwest, binwidth = 1)       //不含权重信息的直方图

id

qplot(percbelowpoverty, data = midwest, weight = poptotal, binwidth = 1) + ylab("population")       //含权重信息的直方图

id

参考文献

[1][ggplot2:数据分析与图形艺术]