14.4 散点图
也许在所有图形中最常见就是散点图,它用于研究两个连续变量之间的关系。obama_vs_mccain
数据集中有很多可比较的数值变量,不过我们先考虑这个问题:“选民的收入是否会影响投票率?”
14.4.1 第一种方法:base
绘图法
base
中用于绘制散点图的函数就是简单的plot
。最近流行的编码风格最佳实践就是把所有你想要用于绘图的变量置于一个(或数个)数据框中,而不是把它们分置于几个单独的向量中。不过遗憾的是,plot
比这种想法出现4,所以我们必须把它包括在with
的函数调用中来访问列。
4predate这里指的是“出现得早”,而不是“捕获并吃掉”
尽管plot
会简单地忽略缺失值,但是为了代码的整洁,删除那些没有Turnout
值的行吧:
obama_vs_mccain <- obama_vs_mccain[!is.na(obama_vs_mccain$Turnout), ]
然后,我们就可以创建一个简单的散点图,如图14-1所示。
with(obama_vs_mccain, plot(Income, Turnout))
图14-1:使用base图形系统画的简单散点图
plot
有很多参数可以用于自定义输出格式,其中一些比其他的更直观。col
能改变点的颜色。它可以接受任何通过colors
返回的已命名的颜色,或者像"#1234556"
的HTML风格的十六进制值。你可使用pch
(即plot character绘图字符的缩写 )来改变点的形状5。图14-2显示了更新后的散点图,它把颜色变成紫色,并使用点状来填充圈点。
5阅读?points
的帮助页面,然后试试plot(1:25, pch = 1:25, bg = "blue")
来看看不同的形状。
with(obama_vs_mccain, plot(Income, Turnout, col = "violet", pch = 20))
图14-2: 使用base图形系统设置颜色和点的形状
可通过log
参数来设置对数坐标。log = "x"
表示使用x轴为对数坐标,log = "y"
表示使用y轴为对数坐标,而log = "xy"
则表示同时使用x和y轴作为对数坐标。图14-3和图14-4显示了对数轴坐标的一些选项:
with(obama_vs_mccain, plot(Income, Turnout, log = "y"))
#图14-3
with(obama_vs_mccain, plot(Income, Turnout, log = "xy"))
#图14-4
图14-3:使用base图形系统的y轴对数坐标
图14-4:使用base图形系统的x和y轴对数坐标
我们可以看到,收入和投票率之间有明显的正相关关系,并且在对数坐标中这种相关性更强。下一个问题是:“这种关系在美国所有地区中是否都一样? ”要回答这个问题,可根据Region
列把数据分割成最多10个标准联邦区,并把“矩阵”中的每个子集都绘制到一张图上。layout
函数用来控制矩阵中多个绘图区的布局。对于以下代码段,没必须花费大多的时间去理解它的意思,它只是用来证明使用base
图形系统把多个相关的图形绘制到一起是可能的。不过遗憾的是,这段代码看起来就像从传说中的丑树中跌下来一样难看,所以这种技术还是少用为好。图14-5显示了其结果:
par(mar = c(3, 3, 0.5, 0.5), oma = rep.int(0, 4), mgp = c(2, 1, 0))
regions <- levels(obama_vs_mccain$Region)
plot_numbers <- seq_along(regions)
layout(matrix(plot_numbers, ncol = 5, byrow = TRUE))
for(region in regions)
{
regional_data <- subset(obama_vs_mccain, Region == region)
with(regional_data, plot(Income, Turnout))
}
图14-5:使用base图形系统在同一张图中显示多个子图
14.4.2 第二种方法:lattice图形系统
lattice
版本的plot
是xyplot
。它使用了一个公式接口来指定x和y坐标变量。公式将在15.4节中深入讨论,现在你需要做的是输入yvar ~ xvar
。接着,xyplot
(和其他lattice
函数)还需要一个data
参数,此参数将告诉它从哪个数据框中寻找变量。图14-6是图14-1 的lattice
版本:
library(lattice)
xyplot(Turnout ~ Income, obama_vs_mccain)
图14-6:使用lattice系统绘制的简单散点图
很多用于改变绘图功能的选项与base
系统基本相同。图14-7模仿图14-2改变了颜色和点的形状:
xyplot(Turnout ~ Income, obama_vs_mccain, col = "violet", pch = 20)
图14-7:使用lattice系统设置颜色和点的形状
但是,轴的尺度需要以不同的方式指定。lattice
绘图接受一个scales
参数,而且它必须是一个列表。这个列表里的内容必须是name = value
对,例如,log = TRUE
为x和y轴设置了对数坐标。scales
列表还可以接受其他命名为x和y的(子)列表参数来指定其中所需的轴的设置。不要紧张,它没有看上去那么复杂。图14-8和14-9的例子分别显示不同坐标的轴:
xyplot(
Turnout ~ Income,
obama_vs_mccain,
scales = list(log = TRUE) # x和y轴都是对数坐标(图14-8)
)
xyplot(
Turnout ~ Income,
obama_vs_mccain,
scales = list(y = list(log = TRUE)) #y轴对数坐标(图14-9)
)
图14-8:使用lattice系统的x和y的对数坐标
图14-9:使用点阵图形登录y轴
公式接口使按区域拆分数据变得很容易。我们只需要:追加一个|
号(这是一个“管道”字符;与用于逻辑的“或”相同)和我们希望拆分的变量,这里即Region
。使用参数relation = "same"
意味着每个面板都将使用相同的轴。当参数alternating
为TRUE
(默认值)时,每个面板上轴的刻度将在绘图区的两侧交替出现,否则只出现在左侧和底部。输出如图14-10所示,请注意它对图14-5的改进 :
xyplot(
Turnout ~ Income | Region,
obama_vs_mccain,
scales = list(
log = TRUE,
relation = "same",
alternating = FALSE
),
layout = c(5, 2)
)
图14-10:使用lattice系统在同一张图中绘制多条曲线
lattice
系统的另一个好处是它能把绘图存储在变量中,( base
绘图与此相反,它只能把图绘制在窗口中),因而可在之后更改它们。图14-12中显示了图14-11的更改版本:
(lat1 <- xyplot(
Turnout ~ Income | Region,
obama_vs_mccain
)) #图14-11
(lat2 <- update(lat1, col = "violet", pch = 20))
#图14-12
图14-11:此绘图被存储为变量,它会在图14-12中被重用
图14-12:此图重用图14-11中的lattice系统变量
14.4.3 第三种方法:ggplot2图形系统
ggplot2
(“2”是因为尝试了多次才得到理想的版本)从lattice
系统中吸收了很多好点子,并基于它所建立。因此,将绘图拆分成多个面板非常容易,且能够按顺序创建图形。除此之外,ggplot2
有它自己的一些特殊技巧。最重要的是,它本质上是“语法的”,这意味着它由众多小的组件构成,因此它更容易创建全新的绘图类型,如果你特想这么做的话。
它的语法与其他系统的代码非常不同,要做好思想准备来接受新事物。每个绘图由ggplot
函数创建,它的第一个参数是一个数据框,第二个是aesthetic。其实就是把x
和y
列变量传递给aes
函数。然后,我们再添加一个geom让图形系统显示一些点。图14-13显示了结果:
library(ggplot2)
ggplot(obama_vs_mccain, aes(Income, Turnout)) +
geom_point()
图14-13: 使用ggplot2制图法绘制简单的散点图
ggplot2
不仅能识别来自base
图形系统的命令来改变点的颜色和形状,而且也有它自己的一套更加可读的名称。在图14-14中,shape取代了pch,且颜色可使用color或colour来指定:
ggplot(obama_vs_mccain, aes(Income, Turnout)) +
geom_point(color = "violet", shape = 20)
图14-14:使用ggplot2绘图法设置点的颜色和形状
为了设置一个对数坐标,我们需要为每个轴添加一个标度,如图14-15所示。break
参数指定了轴刻度值的位置。它是可选的,但这里用来复制base
和+lattice
系统范例中的行为:
ggplot(obama_vs_mccain, aes(Income, Turnout)) +
geom_point() +
scale_x_log10(breaks = seq(2e4, 4e4, 1e4)) +
scale_y_log10(breaks = seq(50, 75, 5))
图14-15:使用ggplot2绘制对数坐标
为了把绘图分割成不同的面板,我们添加一个切面(facet)。与lattice
中的绘图类似,切面带有一个公式参数。图14-16显示了facet_wrap
函数的行为。为方便阅读,x轴的刻度已被旋转了30度,并用theme
函数使其右对齐:
ggplot(obama_vs_mccain, aes(Income, Turnout)) +
geom_point() +
scale_x_log10(breaks = seq(2e4, 4e4, 1e4)) +
scale_y_log10(breaks = seq(50, 75, 5)) +
facet_wrap(~ Region, ncol = 4)
图14-16:使用ggplot2把多个图形画在同一张图中
为了将多个变量分开来,我们把公式指定为类似~ var1 + var2 + var3
的形式。对于只有两个变量的特殊情况,facet_grid
提供了另一种方法来分开它们,一个置于行中,而另一个置于列中。
与lattice
类似,ggplots
可以把图形存储到变量中,然后继续往它上面添加东西。下例重新绘制了图14-13并把它存储为一个变量。和往常一样,把表达式置于括号中能使它自动打印出来:
(gg1 <- ggplot(obama_vs_mccain, aes(Income, Turnout)) +
geom_point() )
图14-17显示了其输出。我们可以使用以下代码更新它,并在图14-18显示其结果:
(gg2 <- gg1 +
facet_wrap(~ Region, ncol = 5) +
theme(axis.text.x = element_text(angle = 30, hjust = 1))
)
图14-17:此图被存储为变量并将在图14-18中重用
图14-18:此图重用了从图14-17中得到的ggplot2变量
14.4.4 线图
如果想研究连续变量如何随时间变化,线图往往比散点图更加容易理解,因为它能显示邻近数值之间的联系。下例将在crab_tag
数据集中研究螃蟹一年中的情况,看看它们曾游到北海多深的位置。
在base
图形系统中,线图与散点图的创建方式一样,不同的是线图采用参数type = "l"
。为了避免在维度上的混淆,我们把深度画成负数,而不是使用给定数据集中的绝对值。
绘图区范围将被默认设为数据的区间范围(但会加多一点点,更多细节请参见?par
帮助页面的xaxs
一节)。为了获得更好的透视感,我们会通过传统ylim
参数来手动设置y轴的大小,使它的范围为螃蟹在海中走到的最深点到海平面之间。图14-19显示了这个线图:
with(
crab_tag$daylog,
plot(Date, -Max.Depth, type = "l", ylim = c(-max(Max.Depth), 0))
)
图14-19:使用base制图法绘制的线图
现在,故事才只讲了一半。Max.Depth
参数是螃蟹在某一天所达到的大海最深处。我们还需要为线图添加一个Min.Depth
,目的是为了方便查看螃蟹每天到达的最浅处。附加线可使用lines
函数在现有的绘图中重叠绘出。对于散点图,类似的函数是points
。图14-20显示了另一条线:
with(
crab_tag$daylog,
lines(Date, -Min.Depth, col = "blue") )
图14-20:使用base制图法添加另一条线
lattice
与base
系统遵循类似的模式。与散点图一样,lattice
也使用xyplot
来画线图,但同样也要使用type ="l"
的参数。使用公式接口能轻而易举地指定多行。注意,公式中的+
号常用于创建类似于图14-21的图:
xyplot(-Min.Depth + -Max.Depth ~ Date, crab_tag$daylog, type = "l")
图14-21:使用lattice图形系统绘制的线图
在ggplot2
中从散点图切换到线图非常简单,只要把geom_plot
替换为geom_line
即可(图14-22显示了结果):
ggplot(crab_tag$daylog, aes(Date, -Min.Depth)) +
geom_line()
图14-22:使用ggplot2系统绘制的线图
不过,在绘制多条线时会有一点复杂。当你在ggplot
指定aesthetics时,你会为所有的geom
都指定了这个aesthetics。也就是说,aesthetics对绘图来说其影响是“全局的”。在下例中,我们希望在一条线上指定最大深度,而在另一条线上指定最小深度,如图14-23所示。一种解决方法是在每个geom_line
函数中指定一个y-aesthetic:
ggplot(crab_tag$daylog, aes(Date)) +
geom_line(aes(y = -Max.Depth)) +
geom_line(aes(y = -Min.Depth))
图14-23:使用ggplot2绘制的两条具有独立geoms的线
然而,这有点笨拙,因为需要调用geom_line
两次,且实际上这也并非惯常的做法。在ggplot2
中,更“恰当的”方式是将数据熔化(melt)成长表,然后把线分组(group),如图14-24所示:
library(reshape2)
crab_long <- melt(
crab_tag$daylog,
id.vars = "Date",
measure.vars = c("Min.Depth", "Max.Depth")
)
ggplot(crab_long, aes(Date, -value, group = variable)) +
geom_line()
图14-24:使用ggplot2和分组绘制两条线
对于只有两条线的场景,有一个更好的解决方案,它不需要任何的数据操作。geom_ribbon
将绘制两条直线以及它们中间的内容。为了使图形更美观,可把color
和fill
参数传递给geom,以此指定线的颜色和填充形式。图14-25显示了结果:
ggplot(crab_tag$daylog, aes(Date, ymin = -Min.Depth, ymax = -Max.Depth)) +
geom_ribbon(color = "black", fill = "white")
图14-25:使用ggplot2图形的彩带图(ribbon plot)
无论你使用哪种系统来绘图,螃蟹的行为都是非常明确的。9月,它生活在浅水域准备交配,然后花了几个月的时间迁移到深水区。在整个冬季、春季和夏季它都愉快地行走在北海海底(除了在6月初,它在水平面有一次奇怪而短暂的旅行——不知道是数据错误,还是它从渔船中捡回一条命)。之后,在7月中旬它显然从“悬崖”掉了下来,在爬回浅水区开始下一轮的交配之前被捕获了。