14.4 散点图

也许在所有图形中最常见就是散点图,它用于研究两个连续变量之间的关系。obama_vs_mccain数据集中有很多可比较的数值变量,不过我们先考虑这个问题:“选民的收入是否会影响投票率?”

14.4.1 第一种方法:base绘图法

base中用于绘制散点图的函数就是简单的plot。最近流行的编码风格最佳实践就是把所有你想要用于绘图的变量置于一个(或数个)数据框中,而不是把它们分置于几个单独的向量中。不过遗憾的是,plot比这种想法出现4,所以我们必须把它包括在with的函数调用中来访问列。

4predate这里指的是“出现得早”,而不是“捕获并吃掉”

尽管plot会简单地忽略缺失值,但是为了代码的整洁,删除那些没有Turnout值的行吧:

  1. obama_vs_mccain <- obama_vs_mccain[!is.na(obama_vs_mccain$Turnout), ]

然后,我们就可以创建一个简单的散点图,如图14-1所示。

  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")来看看不同的形状。

  1. 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显示了对数轴坐标的一些选项:

  1. with(obama_vs_mccain, plot(Income, Turnout, log = "y"))
  2. #图14-3
  3. with(obama_vs_mccain, plot(Income, Turnout, log = "xy"))
  4. #图14-4

图像说明文字 图14-3:使用base图形系统的y轴对数坐标

图像说明文字 图14-4:使用base图形系统的x和y轴对数坐标

我们可以看到,收入和投票率之间有明显的正相关关系,并且在对数坐标中这种相关性更强。下一个问题是:“这种关系在美国所有地区中是否都一样? ”要回答这个问题,可根据Region列把数据分割成最多10个标准联邦区,并把“矩阵”中的每个子集都绘制到一张图上。layout函数用来控制矩阵中多个绘图区的布局。对于以下代码段,没必须花费大多的时间去理解它的意思,它只是用来证明使用base图形系统把多个相关的图形绘制到一起是可能的。不过遗憾的是,这段代码看起来就像从传说中的丑树中跌下来一样难看,所以这种技术还是少用为好。图14-5显示了其结果:

  1. par(mar = c(3, 3, 0.5, 0.5), oma = rep.int(0, 4), mgp = c(2, 1, 0))
  2. regions <- levels(obama_vs_mccain$Region)
  3. plot_numbers <- seq_along(regions)
  4. layout(matrix(plot_numbers, ncol = 5, byrow = TRUE))
  5. for(region in regions)
  6. {
  7. regional_data <- subset(obama_vs_mccain, Region == region)
  8. with(regional_data, plot(Income, Turnout))
  9. }

图像说明文字

图14-5:使用base图形系统在同一张图中显示多个子图

14.4.2 第二种方法:lattice图形系统

lattice版本的plotxyplot。它使用了一个公式接口来指定x和y坐标变量。公式将在15.4节中深入讨论,现在你需要做的是输入yvar ~ xvar。接着,xyplot(和其他lattice函数)还需要一个data参数,此参数将告诉它从哪个数据框中寻找变量。图14-6是图14-1 的lattice版本:

  1. library(lattice)
  2. xyplot(Turnout ~ Income, obama_vs_mccain)

图像说明文字 图14-6:使用lattice系统绘制的简单散点图

很多用于改变绘图功能的选项与base系统基本相同。图14-7模仿图14-2改变了颜色和点的形状:

  1. 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的例子分别显示不同坐标的轴:

  1. xyplot(
  2. Turnout ~ Income,
  3. obama_vs_mccain,
  4. scales = list(log = TRUE) # x和y轴都是对数坐标(图14-8)
  5. )
  6. xyplot(
  7. Turnout ~ Income,
  8. obama_vs_mccain,
  9. scales = list(y = list(log = TRUE)) #y轴对数坐标(图14-9)
  10. )

图像说明文字 图14-8:使用lattice系统的x和y的对数坐标

图像说明文字 图14-9:使用点阵图形登录y轴

公式接口使按区域拆分数据变得很容易。我们只需要:追加一个|号(这是一个“管道”字符;与用于逻辑的“或”相同)和我们希望拆分的变量,这里即Region。使用参数relation = "same"意味着每个面板都将使用相同的轴。当参数alternatingTRUE (默认值)时,每个面板上轴的刻度将在绘图区的两侧交替出现,否则只出现在左侧和底部。输出如图14-10所示,请注意它对图14-5的改进 :

  1. xyplot(
  2. Turnout ~ Income | Region,
  3. obama_vs_mccain,
  4. scales = list(
  5. log = TRUE,
  6. relation = "same",
  7. alternating = FALSE
  8. ),
  9. layout = c(5, 2)
  10. )

图像说明文字

图14-10:使用lattice系统在同一张图中绘制多条曲线

lattice系统的另一个好处是它能把绘图存储在变量中,( base绘图与此相反,它只能把图绘制在窗口中),因而可在之后更改它们。图14-12中显示了图14-11的更改版本:

  1. (lat1 <- xyplot(
  2. Turnout ~ Income | Region,
  3. obama_vs_mccain
  4. )) #图14-11
  5. (lat2 <- update(lat1, col = "violet", pch = 20))
  6. #图14-12

图像说明文字 图14-11:此绘图被存储为变量,它会在图14-12中被重用 图像说明文字 图14-12:此图重用图14-11中的lattice系统变量

14.4.3 第三种方法:ggplot2图形系统

ggplot2(“2”是因为尝试了多次才得到理想的版本)从lattice系统中吸收了很多好点子,并基于它所建立。因此,将绘图拆分成多个面板非常容易,且能够按顺序创建图形。除此之外,ggplot2有它自己的一些特殊技巧。最重要的是,它本质上是“语法的”,这意味着它由众多小的组件构成,因此它更容易创建全新的绘图类型,如果你特想这么做的话。

它的语法与其他系统的代码非常不同,要做好思想准备来接受新事物。每个绘图由ggplot函数创建,它的第一个参数是一个数据框,第二个是aesthetic。其实就是把xy列变量传递给aes函数。然后,我们再添加一个geom让图形系统显示一些点。图14-13显示了结果:

  1. library(ggplot2)
  2. ggplot(obama_vs_mccain, aes(Income, Turnout)) +
  3. geom_point()

图像说明文字 图14-13: 使用ggplot2制图法绘制简单的散点图

ggplot2不仅能识别来自base图形系统的命令来改变点的颜色和形状,而且也有它自己的一套更加可读的名称。在图14-14中,shape取代了pch,且颜色可使用color或colour来指定:

  1. ggplot(obama_vs_mccain, aes(Income, Turnout)) +
  2. geom_point(color = "violet", shape = 20)

图像说明文字 图14-14:使用ggplot2绘图法设置点的颜色和形状

为了设置一个对数坐标,我们需要为每个轴添加一个标度,如图14-15所示。break参数指定了轴刻度值的位置。它是可选的,但这里用来复制base+lattice系统范例中的行为:

  1. ggplot(obama_vs_mccain, aes(Income, Turnout)) +
  2. geom_point() +
  3. scale_x_log10(breaks = seq(2e4, 4e4, 1e4)) +
  4. scale_y_log10(breaks = seq(50, 75, 5))

图像说明文字

图14-15:使用ggplot2绘制对数坐标

为了把绘图分割成不同的面板,我们添加一个切面(facet)。与lattice中的绘图类似,切面带有一个公式参数。图14-16显示了facet_wrap函数的行为。为方便阅读,x轴的刻度已被旋转了30度,并用theme函数使其右对齐:

  1. ggplot(obama_vs_mccain, aes(Income, Turnout)) +
  2. geom_point() +
  3. scale_x_log10(breaks = seq(2e4, 4e4, 1e4)) +
  4. scale_y_log10(breaks = seq(50, 75, 5)) +
  5. facet_wrap(~ Region, ncol = 4)

图像说明文字

图14-16:使用ggplot2把多个图形画在同一张图中

为了将多个变量分开来,我们把公式指定为类似~ var1 + var2 + var3的形式。对于只有两个变量的特殊情况,facet_grid提供了另一种方法来分开它们,一个置于行中,而另一个置于列中。

lattice类似,ggplots可以把图形存储到变量中,然后继续往它上面添加东西。下例重新绘制了图14-13并把它存储为一个变量。和往常一样,把表达式置于括号中能使它自动打印出来:

  1. (gg1 <- ggplot(obama_vs_mccain, aes(Income, Turnout)) +
  2. geom_point() )

图14-17显示了其输出。我们可以使用以下代码更新它,并在图14-18显示其结果:

  1. (gg2 <- gg1 +
  2. facet_wrap(~ Region, ncol = 5) +
  3. theme(axis.text.x = element_text(angle = 30, hjust = 1))
  4. )

图像说明文字

图14-17:此图被存储为变量并将在图14-18中重用

图像说明文字

图14-18:此图重用了从图14-17中得到的ggplot2变量

14.4.4 线图

如果想研究连续变量如何随时间变化,线图往往比散点图更加容易理解,因为它能显示邻近数值之间的联系。下例将在crab_tag数据集中研究螃蟹一年中的情况,看看它们曾游到北海多深的位置。

base图形系统中,线图与散点图的创建方式一样,不同的是线图采用参数type = "l"。为了避免在维度上的混淆,我们把深度画成负数,而不是使用给定数据集中的绝对值。

绘图区范围将被默认设为数据的区间范围(但会加多一点点,更多细节请参见?par帮助页面的xaxs一节)。为了获得更好的透视感,我们会通过传统ylim参数来手动设置y轴的大小,使它的范围为螃蟹在海中走到的最深点到海平面之间。图14-19显示了这个线图:

  1. with(
  2. crab_tag$daylog,
  3. plot(Date, -Max.Depth, type = "l", ylim = c(-max(Max.Depth), 0))
  4. )

图像说明文字 图14-19:使用base制图法绘制的线图

现在,故事才只讲了一半。Max.Depth参数是螃蟹在某一天所达到的大海最深处。我们还需要为线图添加一个Min.Depth,目的是为了方便查看螃蟹每天到达的最浅处。附加线可使用lines函数在现有的绘图中重叠绘出。对于散点图,类似的函数是points。图14-20显示了另一条线:

  1. with(
  2. crab_tag$daylog,
  3. lines(Date, -Min.Depth, col = "blue") )

图像说明文字 图14-20:使用base制图法添加另一条线

latticebase系统遵循类似的模式。与散点图一样,lattice也使用xyplot来画线图,但同样也要使用type ="l"的参数。使用公式接口能轻而易举地指定多行。注意,公式中的+号常用于创建类似于图14-21的图:

  1. xyplot(-Min.Depth + -Max.Depth ~ Date, crab_tag$daylog, type = "l")

图像说明文字 图14-21:使用lattice图形系统绘制的线图

ggplot2中从散点图切换到线图非常简单,只要把geom_plot替换为geom_line即可(图14-22显示了结果):

  1. ggplot(crab_tag$daylog, aes(Date, -Min.Depth)) +
  2. geom_line()

图像说明文字 图14-22:使用ggplot2系统绘制的线图

不过,在绘制多条线时会有一点复杂。当你在ggplot指定aesthetics时,你会为所有的geom都指定了这个aesthetics。也就是说,aesthetics对绘图来说其影响是“全局的”。在下例中,我们希望在一条线上指定最大深度,而在另一条线上指定最小深度,如图14-23所示。一种解决方法是在每个geom_line函数中指定一个y-aesthetic:

  1. ggplot(crab_tag$daylog, aes(Date)) +
  2. geom_line(aes(y = -Max.Depth)) +
  3. geom_line(aes(y = -Min.Depth))

图像说明文字 图14-23:使用ggplot2绘制的两条具有独立geoms的线

然而,这有点笨拙,因为需要调用geom_line两次,且实际上这也并非惯常的做法。在ggplot2中,更“恰当的”方式是将数据熔化(melt)成长表,然后把线分组(group),如图14-24所示:

  1. library(reshape2)
  2. crab_long <- melt(
  3. crab_tag$daylog,
  4. id.vars = "Date",
  5. measure.vars = c("Min.Depth", "Max.Depth")
  6. )
  7. ggplot(crab_long, aes(Date, -value, group = variable)) +
  8. geom_line()

图像说明文字 图14-24:使用ggplot2和分组绘制两条线

对于只有两条线的场景,有一个更好的解决方案,它不需要任何的数据操作。geom_ribbon将绘制两条直线以及它们中间的内容。为了使图形更美观,可把colorfill参数传递给geom,以此指定线的颜色和填充形式。图14-25显示了结果:

  1. ggplot(crab_tag$daylog, aes(Date, ymin = -Min.Depth, ymax = -Max.Depth)) +
  2. geom_ribbon(color = "black", fill = "white")

图像说明文字 图14-25:使用ggplot2图形的彩带图(ribbon plot)

无论你使用哪种系统来绘图,螃蟹的行为都是非常明确的。9月,它生活在浅水域准备交配,然后花了几个月的时间迁移到深水区。在整个冬季、春季和夏季它都愉快地行走在北海海底(除了在6月初,它在水平面有一次奇怪而短暂的旅行——不知道是数据错误,还是它从渔船中捡回一条命)。之后,在7月中旬它显然从“悬崖”掉了下来,在爬回浅水区开始下一轮的交配之前被捕获了。