B 细节技巧

统计图形都是通过相应的图形函数生成的,R 当中很多图形函数都包含了默认的图形细节设置,这些细节对不太苛刻的用户来说大致可以满足需要,但是往往由于其它方面的要求(如排版、强调某一部分),我们可能要对图形作一些细节性的微调,比如字体、字号、图形边距、点线样式等等,这里我们仅仅介绍基础图形系统中的细节参数设置。

另外我们也在这里介绍一些统计作图上的技巧,这些技巧对于数据分析来说也许没有显著的作用,但它们可以帮我们进一步调整、组织好我们的图形输出,这些内容包括:数学公式的表示、一页多图的方法、离散变量的散点图示和各种图形设备的使用方法。

B.1 par() 函数

左上:adj 用于字符相对位置的调整,“+”表示真实坐标点的位置,通过一个长度为 2 的向量 c(x, y) 可以分别调整字符在横纵坐标上相对平移的位置;右上:坐标轴元素的边界距离,参数 mgp 的三个数值分别控制了坐标轴标题、坐标轴刻度数字以及坐标轴线到图形的距离;左下:tcl 控制了坐标轴刻度线的方向和长度,srt 控制了字符串的旋转角度;右下:线条相交处的样式。

图 B.1: 左上:adj 用于字符相对位置的调整,“+”表示真实坐标点的位置,通过一个长度为 2 的向量 c(x, y) 可以分别调整字符在横纵坐标上相对平移的位置;右上:坐标轴元素的边界距离,参数 mgp 的三个数值分别控制了坐标轴标题、坐标轴刻度数字以及坐标轴线到图形的距离;左下:tcl 控制了坐标轴刻度线的方向和长度,srt 控制了字符串的旋转角度;右下:线条相交处的样式。

四幅图形演示了不同的图形边框(上右开、上右闭、右开和上开)、字体样式(正常、粗体、斜体和粗斜体)、字体族(衬线、无衬线、等宽、符号)、坐标轴标签样式、点样式(圆圈、方框、菱形、三角)、线末端样式和线样式(实线、虚线、点线、点划线)。

图 B.2: 四幅图形演示了不同的图形边框(上右开、上右闭、右开和上开)、字体样式(正常、粗体、斜体和粗斜体)、字体族(衬线、无衬线、等宽、符号)、坐标轴标签样式、点样式(圆圈、方框、菱形、三角)、线末端样式和线样式(实线、虚线、点线、点划线)。

R 的图形参数既可以通过函数 par() 预先全局设置,也可以在具体作图函数(如 plot()lines() 等)中设置临时参数值;二者的区别在于前者的设置会一直在当前图形设备中起作用,除非将图形设备(参见 B.6 小节)关闭,而后者的设置只是临时性的,不会影响后面其它作图函数的图形效果。函数 par() 中涵盖了大部分图形参数,因此专用一节讲述。

函数 par() 可以用来设置或者获取图形参数,par() 本身(括号中不写任何参数)返回当前的图形参数设置(一个 list);若要设置图形参数,则可用 par(tag = value) 的形式, 其中 tag 的详细说明参见下面的列表,value 就是参数值, 例如:

# 设置边距参数和背景色
par(mar = c(4, 4, 1, .5), bg = "yellow")

目前 par() 函数涉及到的图形参数大约有 70 个,这里只是选取其中 40 多个常用且较易理解的参数进行解释说明如下列表,其它参数请参阅 R 帮助 ?par

adj
调整图中字符的相对位置;取值为长度为 1 的数值向量,范围通常在 \([0,1]\) 中,0 表示左对齐,1 表示右对齐;在 text() 函数中该参数的长度可以为 2,分别表示字符边界矩形框的左下角相对坐标点 (x, y) 位置的调整,向量的两个数值一般也在 \([0,1]\) 范围中(有些图形设备中也可以超出此范围),表示字符串以左下角为基准、根据自身的宽度和高度分别向左和向下移动的比例,默认为 c(0.5, 0.5)。例如 c(0, 0) 表示整个字符(串)的左下角对准设定的坐标点,而 c(1, 0) 则表示字符串横向移动了自身宽度的距离,而纵向不受影响。具体示例参见图 B.1 左上图
ask
切换到下一个新的作图设备(通常是作一幅新图)时是否需要用户输入(敲回车键或点鼠标);TRUE 表示是;FALSE 表示否。当有多幅图将逐一出现而需要按顺序一步步在图形设备上展示时很有用,这种情况下若设置 askTRUE,那么作图时每一副新图的出现都要先等待用户输入,否则所有的图将会一闪而过
bg
设置图形背景色;关于颜色值的设置请参见 3.1
bty
设置图形边框样式;取值为字符 o, l, 7, c, u, ] 之一; 这些字符本身的形状对应着边框样式,比如(默认值)o 表示四条边都显示,而 c 表示不显示右侧边,参见图 B.2 四幅图的边框样式
cex

图上元素(文本和符号等)的缩放倍数;取值为一个相对于 1 的数值(默认为 1)。具体的细节缩放可以通过如下参数设置(默认值均为 1):

cex.axis
坐标轴刻度标记的缩放倍数
cex.lab
坐标轴标题的缩放倍数
cex.main
图主标题的缩放倍数
cex.sub
图副标题的缩放倍数
col

图中符号(点、线等)的颜色;取值参见 3.1 节。与 cex 参数类似,具体的细节颜色也可以通过如下参数设置:

col.axis
坐标轴刻度标记的颜色
col.lab
坐标轴标题的颜色
col.main
图主标题的颜色
col.sub
图副标题的颜色
family
设置文本的字体族(衬线、无衬线、等宽、符号字体等);标准取值有:serif, sans, mono, symbol,参见图 B.2 坐标 (2, 8) 处的文本;family = 'symbol' 的情况没有显示出来
fg
设置前景色(若后面没有指定别的颜色设置,本参数会影响几乎所有的后续图形元素颜色,若后续图形元素有指定的颜色设置,那么只是影响图形边框和坐标轴刻度线的颜色);颜色值参见 3.1 节。
font

设置文本字体样式;取值为一个整数;通常 1、2、3、4 分别表示正常、粗体、斜体和粗斜体;对于添加文本,text() 函数及其 vfont 参数可以设置更为详细的字体族和字体样式;参见这两个演示:demo(Hershey)demo(Japanese),前者演示 Hershey 向量字体,后者演示日语的表示;3.6 节有进一步的介绍,参见图 B.2 的图主标题字体

font.axis
坐标轴刻度标签的字体样式
font.lab
坐标轴标题的字体样式
font.main
图主标题的字体样式
font.sub
图副标题的字体样式
lab
设置坐标轴刻度数目(R 会尽量自动“取整”,即尽量向 0.5、1 或 10 的幂次靠近);取值形式 c(x, y, len)xy 分别设置两轴的刻度数目,len 目前在 R 中尚未生效,因此设置任意值都不会有影响(但用到 lab 参数时必须写上这个参数)
las
坐标轴标签样式;取 0、1、2、3 四个整数之一,分别表示“总是平行于坐标轴”、“总是水平”、“总是垂直于坐标轴”和“总是竖直”。仔细观察图 B.2 中四幅图的不同坐标轴标签方向
lend
线条末端的样式(圆或方形);取值为整数 0、1、2 之一(或相应的字符串 'round', 'mitre', 'bevel'),注意后两者的细微区别(仔细观察图 B.2 中宽线条中黑点的位置,在画线时,这些线条的起点和终点都是选择同样的坐标位置!)
lheight
图中文本行高;取值为一个倍数,默认为 1
ljoin
线条相交处的样式;取值为整数 0、1、2 之一(或相应的字符串 'round', 'mitre', 'bevel'),分别表示画圆角、画方角和切掉顶角,观察图 B.1 的三个直角的顶点
lty
线条虚实样式:\(0\Rightarrow\) 不画线,\(1\Rightarrow\) 实线,\(2\Rightarrow\) 虚线,\(3\Rightarrow\) 点线,\(4\Rightarrow\) 点划线,\(5\Rightarrow\) 长划线,\(6\Rightarrow\) 点长划线;或者相应设置如下字符串(分别对应前面的数字):'blank', 'solid', 'dashed', 'dotted', 'dotdash', 'longdash', 'twodash';还可以用由十六进制的数字组成的字符串表示线上实线和空白的相应长度,如 'F624',详细解释请参见 3.3 一节。
lwd
线条宽度;默认为 1
mar
设置图形边界空白宽度;按照“下、左、上、右”的顺序,默认为 c(5, 4, 4, 2) + 0.1
mex
设置坐标轴的边界宽度缩放倍数;默认为 1,本参数会影响到 mgp 参数
mfrow, mfcol
设置一页多图;取值形式 c(nrow, ncol) 长度为 2 的向量,分别设置行数和列数,参见附录 B.4
mgp
设置坐标轴的边界宽度;取值长度为 3 的数值向量,分别表示坐标轴标题、坐标轴刻度线标签和坐标轴线的边界宽度(受 mex 的影响),默认为 c(3, 1, 0),意思是坐标轴标题、坐标轴刻度线标签和坐标轴线离作图区域的距离分别为 3、1、0;参见图 B.1 右上方小图
oma
设置外边界(Outer Margin)宽度;类似 mar,默认为 c(0, 0, 0, 0),当一页上只放一张图时,该参数与 mar 不好区分,但在一页多图的情况下就容易可以看出与 mar 的区别
pch
点的符号;pch = 19\(\Rightarrow\) 实圆点、pch = 20\(\Rightarrow\) 小实圆点、pch = \(21\Rightarrow\) 圆圈、pch = 22\(\Rightarrow\) 正方形、pch = 23\(\Rightarrow\) 菱形、pch = 24\(\Rightarrow\) 正三角尖、pch = 25\(\Rightarrow\) 倒三角尖,其中,21-25 可以填充颜色(用 bg 参数),参见图 3.3
pty
设置作图区域的形状;默认为 'm':尽可能最大化作图区域;另外一种取值 's' 表示设置作图区域为正方形
srt
字符串的旋转角度;取一个角度数值,参见图 B.1 左下方小图中分别旋转 \(30\,^{\circ}\)\(120\,^{\circ}\) 的字符串
tck
坐标轴刻度线的高度;取值为与图形宽高的比例值(0 到 1 之间);正值表示向内画刻度线,负值表示向外;默认为不使用它(设为 NA),而使用 tcl 参数
tcl
坐标轴刻度线的高度;取一个与文本行高的比例值;正负值意义类似 tck,默认值为 -0.5,即向外画线,高度为半行文本高;观察图 B.1 左下角小图的坐标轴刻度线
usr
作图区域的范围限制,取值长度为 4 的数值向量 c(x1, x2, y1, y2),分别表示作图区域内 x 轴的左右极限和 y 轴的下上极限;注意,若坐标取了对数(参见 xlog, ylog 两个参数),那么实际上设置的极限都是 10 的相应幂次
xaxs, yaxs
坐标轴范围的计算方式;默认 'r':先把原始数据的范围向外扩大 4%,然后用这个范围画坐标轴;另外一种取值 ‘i’ 表示直接使用原始数据范围;实际上还有其它的坐标轴范围计算方式,但是鉴于它们目前在 R 中都尚未生效,所以暂不加介绍
xaxt, yaxt
坐标轴样式;默认 's' 为标准样式;另外一种取值 'n' 意思是不画坐标轴
xlog, ylog
坐标是否取对数;默认 FALSE
xpd
对超出边界的图形的处理方式;取值 FALSE:把图形限制在作图区域内,出界的图形截去;取值 TRUE:把图形限制在图形区域内,出界的图形截去;取值 NA:把图形限制在设备区域内。这些区域的说明参见下文和图 B.3
图形的各种区域和边界说明:作图区域(当前作图区域)、图形区域和设备区域;图形边界和外边界。

图 B.3: 图形的各种区域和边界说明:作图区域(当前作图区域)、图形区域和设备区域;图形边界和外边界。

整个作图设备实际上可以分为三个区域,分别是:“作图区域(Plot Region)”、“图形区域(Figure Margin)”和 “设备区域(Device Region)”,这三个区域也对应着两种边界:“图形边界(Figure Margin)”和 “外边界(Outer Margin)”,这些概念对于初学者来说可能会感到迷惑,然而图 B.3 是一个很好的说明。R 中的图形都是作在一个图形设备中,最常见的图形设备就是一个图形窗口,也可以在其它设备中(参见附录 B.6);整个设备内的区域就称为设备区域,也就是图 B.3 中最大的灰色区域,图形区域是设备区域内的白色实框方形区域,最里面的灰色虚框区域就是作图区域,我们的图形实体部分就作在这个区域;从设备区域的边界向内,到图形区域之间这一段称为外边界(用 oma 参数设定),图形区域边界再向内到作图区域的边界称为图形边界(用 mar 参数设定)。图 B.3 的左图是一页一图的展示,右图则是一页多图的展示,该展示更清楚地说明了 omamar 参数的区别,因为一页一图的情况下,外边界和图形边界完全融合在一起,很难分辨。

下面列表中的九组参数只能通过 par() 函数调用,而在其它作图函数中不可设置(否则会导致错误或者被忽略),与此对应的是,有些参数同样可以在别的作图函数中调用, 如 las 参数:plot(..., las = 1);但要提醒读者注意,在其它函数中即使调用与 par() 相同的参数,也可能会有不同效果,典型的如 colpch 等参数,请注意查看相应函数帮助:

  • ask
  • fig, fin
  • lheight
  • mai, mar, mex, mfcol, mfrow, mfg
  • new
  • oma, omd, omi
  • pin, plt, ps, pty
  • usr
  • xlog, ylog

介绍完上面的参数之后,我们顺便提一下关于 par() 的常用技巧。本节开头提到过,这个函数会“永久性”改变作图设置,我们有时并不想要这种功能,特别是在一幅图作完之后到准备下一幅图时,我们可能希望之前的参数可以被“还原”回来,此时,我们就需要在一幅图开始之前先把作图参数保存到一个对象中,比如 op = par(),然后我们可以 在作这幅图的过程中用 par() 函数任意更改设置以适合需要,作完这一幅图之后,我们再用 par(op) 语句把之前保存的参数设置“释放”出来,这样,中间过程对图形参数的更改就不再会影响到下一幅图。当然,也可以每作完一幅图都把图形设备关掉,然后再作下一幅图,这样也能达到目的,只是稍显麻烦而已,尤其是有时候对一幅图形反复重作、调整、比较,那时不断关闭、打开图形设备就显得更繁琐了。

B.2 plot() 函数

R 中最普通的作图函数就是 plot() 函数,它是一个泛型函数(参见 A.1 小节),可以接受很多不同类的对象作为它的作图对象参数;我们这里要解释的只是其中的图形参数,而非作图对象参数。

先介绍 plot() 的通用参数:

type
图形样式类型,有九种可能的取值,分别代表不同的样式:'p'\(\Rightarrow\) 画点;'l'\(\Rightarrow\) 画线 13'b'\(\Rightarrow\) 同时画点和线,但点线不相交;'c'\(\Rightarrow\)type = 'b' 中的点去掉,只剩下相应的线条部分;'o'\(\Rightarrow\) 同时画点和线, 且相互重叠,这是它与 type = 'b' 的区别;'h'\(\Rightarrow\) 画铅垂线;'s'\(\Rightarrow\) 画阶梯线,从一点到下一点时,先画水平线,再画垂直线;'S'\(\Rightarrow\) 也是画阶梯线,但从一点到下一点是先画垂直线,再画水平线;'n'\(\Rightarrow\) 作一幅空图,没有任何内容,但坐标轴、标题等其它元素都照样显示(除非用别的设置特意隐藏了)。图 B.4 的九幅图清楚说明了这九种类型
main
主标题;也可以在作图之后用数 title() 添加上,参见 3.6
sub
副标题;同上
xlab
x 轴标题;同上
ylab
y 轴标题;同上
asp
图形纵横比,即 y 轴上的 1 单位长度和 x 轴上 1 单位长度的比率;通常情况下这个比率不是 1,有些情况下需要设置以显示更好的图形效果,例如需要从角度表现直线的斜率:若 asp 不等于 1,那么 \(45^{\circ}\) 的角可能看起来并不像真实的 \(45^{\circ}\)
par(mfrow = c(3, 3), mar = c(2, 2.5, 3, 2))
for (i in c("p", "l", "b", "c", "o", "h", "s", "S", "n")) {
  plot(c(1:5, 5:1), xlab = "", type = i,
    main = paste("Plot type: \"", i, "\"", sep = "")
  )
}
plot() 作图的九种样式类型:点、线、点线(不相接)、擦掉点的线、点线(相接)、垂线、阶梯(水平起步)、阶梯(垂直起步)、无。

图 B.4: plot() 作图的九种样式类型:点、线、点线(不相接)、擦掉点的线、点线(相接)、垂线、阶梯(水平起步)、阶梯(垂直起步)、无。

然后我们看看默认的散点图函数 plot.default()。对于一般的散点图(两个数值变量之间),我们只需要调用 plot() 即可,如 plot(x, y),而不必写明 plot.default(x, y),原因就是 plot() 是泛型函数,它会 自动判断传给它的数据类型从而采取不同的作图方式。plot.default() 的参数当然包含了前面介绍的 plot() 中那些参数,此外还有:

x, y
欲作散点图的两个向量;如果 y 缺失,那么就用 x 对它的元素位置(1:n 的整数)作散点图
xlim, ylim
设置坐标系的界限,两个参数都取长度为 2 的向量,它们的作用类似 par() 中的 usr 参数但我们可以通过 par()$usr 获得一幅图的坐标系界限,而这里的两个参数就没有这个功能了,因为一般来说作图函数不会返回任何值(或者说返回值为空:NULL
log
坐标是否取对数,取值 'x' 表示横坐标取对数,'y' 纵坐标取对数,'xy' 两个坐标轴都取对数
ann
一些默认的标记是否显示,如坐标轴标题和图标题
axes
是否画坐标轴;注意只会影响是否画出坐标轴线和刻度,不会影响坐标轴标题
frame.plot
是否给图形加框;可以查阅 box() 函数,作用类似但功能更详细
panel.first
在作图前要完成的工作;这个参数常常被用来在作图之前添加背景网格(参见 3.5 节)或者添加散点的平滑曲线,比如 panel.first = grid()
panel.last
作图之后要完成的工作;与上一个参数类似
...

其它常用参数如下:

col, pch, cex, lty, lwd
这些参数的意思与 par() 中的参数基本相同,有所区别的是,par() 中这些参数只能设置一个单值,而这里可以对它们设置一个向量,这个向量的值将依次运用到各个元素上,若向量长度短于元素个数,那么向量会被循环使用,直到所有的元素都被画出来,事实上,向量的循环使用也是 R 图形参数的一大特点
bg
背景色;注意与 par() 不同的是,这里设置的只是可以画背景色的点的背景色,而不是设置整幅图形的背景色! 3.2 节中说明了什么类型的点可以画背景色

至此,我们基本上已经介绍完所有常用的图形参数,但这些参数的作用没有必要全都烂熟于心;本章可以仅作为参考资料,需要时查阅即可。本书很多章节中我们都能看到一些参数在图形元素和统计图形中的微调作用。

B.3 数学公式

# 本代码为“伪代码”,下图由 tikzDevice 生成
plot(seq(-3, 3, 0.1), dnorm(seq(-3, 3, 0.1)), type = "l", xlab = "x", ylab = expression(phi(x)))
text(-3, 0.37, adj = c(0, 1), cex = 1.2, 
     expression(phi(x) == frac(1, sqrt(2 * pi)) ~ e^-frac(x^2, 2)))
arrows(-2, 0.27, -1.3, dnorm(-1.3) + 0.02)
abline(v = qnorm(0.95), lty = 2)
text(0, dnorm(qnorm(0.95)), expression(integral(phi(x) * dx, -infinity, 1.65) %~~% 0.95))
正态分布密度函数公式的表示

图 B.5: 正态分布密度函数公式的表示

由于统计理论中经常需要用到数学符号,所以向统计图形中添加一些数学说明不仅会使得图形看起来更专业,对图形背后的理论也是一种重要补充。

R 的 grDevices 包 中提供了一系列数学公式的表达符号,例如运算符(加、减、乘、除、乘方、开方等)、比较符(等号、不等号、大于、小于号等)、微积分符号、希腊字母(大小写 \(\alpha\)\(\omega\))、上标下标等等。这些数学符号的使用与 LaTeX 数学公式非常类似,因此如果读者对 LaTeX 公式比较熟悉的话,用起 R 中的数学表达式来也会很顺手。

如果想向图中添加数学表达式的文本标签,只需要将文本设置为表达式(expression)的类型即可。 图 B.5 展示了向正态曲线上添加正态分布密度函数表达式的方法,可以看到,表达式中的公式都是 LaTeX 与 R 的混合语法。另外,我们也可以设置符号的外形,如斜体、粗体等。详情参见 ?plotmath 或者运行代码 demo(plotmath) 观看 R 提供的数学公式演示。

注意本书中的所有图形均由 tikzDevice(Sharpsteen and Bracken 2023) 生成,其中的数学公式数学公式为原始 LaTeX 代码,其质量比 R 自身的数学公式质量高很多,因此图 B.5 并没有采用 demo(plotmath) 中的写法生成数学公式,图上展示的代码为“伪代码”。

B.4 一页多图

有时候我们需要将多幅图形放在同一页图中,以便对这些图形作出对比,或者使图形的排列更加美观。这种情况下,我们至少可以有三种选择:

B.4.1 设置图形参数

在前面 B.1 小节中我们曾经讲到过 mfrowmfcol 两个参数,如果我们在 par() 函数中给这两个参数中的一者提供一个长度为 2 的向量,那么接下来的图形就会按照这两个参数所设定的行数和列数依次生成图形。本书的图形中有很多用到过这两个参数,如图 B.44.4 等,另外有一些统计图形函数也利用了这两个参数设置它们的图形版面,如四瓣图、条件分割图等。

这两个参数的限制在于它们只能将图形区域拆分为网格状,每一格的长和宽都分别必须相等,而且每一格中必须有一幅图形,不能实现一幅图形占据多格的功能。下面的两个函数则灵活许多。

B.4.2 设置图形版面

layout(matrix(c(1, 2, 1, 3), 2), c(1, 3), c(1, 2))
layout.show(2)
函数 layout() 的版面设置示意图

图 B.6: 函数 layout() 的版面设置示意图

R 提供了 layout()函数作为设置图形版面拆分的工具,其用法如下:

usage(layout)
## layout(mat, widths = rep.int(1, ncol(mat)), heights = rep.int(1, nrow(mat)),
##   respect = FALSE)
usage(layout.show)
## layout.show(n = 1)

其中 mat 参数为一个矩阵,提供了作图的顺序以及图形版面的安排;widthsheights 提供了各个矩形作图区域的长和宽的比例;respect 控制着各图形内的横纵轴刻度长度的比例尺是否一样;n 为欲显示的区域的序号。

mat 矩阵中的元素为数字 1 到 n,矩阵行列中数字的顺序和图形方格的顺序是一样的。图 B.6 解释了这种顺序,该图的矩阵为:

matrix(c(1, 2, 1, 3), 2)
##      [,1] [,2]
## [1,]    1    1
## [2,]    2    3

由于这种设置,使得第 1 幅图占据了 (1, 1)(1, 2) 的位置,接下来第 2、3 幅图分别在 (2, 1)(2, 2) 的位置;加上长度和宽度的设置,便产生了图 B.6 的效果。

前面图 4.274.24 曾经使用该函数设置了图形版面,使得不同方格中的图形长宽不一样。图 B.7layout() 安排展示了二元变量的边际分布以及回归直线。

demo("layout_margin", package = "MSG")
回归模型中边际分布的展示:左下方图中展示了散点图和回归直线,上方和右方的直方图分别展示 了自变量和因变量的密度分布。

图 B.7: 回归模型中边际分布的展示:左下方图中展示了散点图和回归直线,上方和右方的直方图分别展示 了自变量和因变量的密度分布。

B.4.3 拆分设备屏幕

R 中还有另外一种拆分屏幕的方法,即 split.screen()。这种方法比前两种方法更灵活,它不仅可以像前两种方法一样设定将作图区域拆分为若干行列,也可以随意指定作图区域在屏幕上的位置。该函数及相关函数用法如下:

usage(split.screen)
## split.screen(figs, screen, erase = TRUE)
usage(screen)
## screen(n = cur.screen, new = TRUE)
usage(erase.screen)
## erase.screen(n = cur.screen)
usage(close.screen)
## close.screen(n, all.screens = FALSE)

拆分后的屏幕由若干个区域构成,每个区域有一个编号,即 screen,我们可以用函数 screen() 指定要作图的区域号,或者用 erase.screen() 擦除该区域的图形,而 split.screen() 的用法主要由 figs 参数控制,该参数既可以取值为一个长度为 2 的向量(指定行列的数目),也可以是一个 4 列的数值矩阵,制定图形区域的坐标位置,后一种用法比较灵活,它可以将图形作在屏幕的任意位置上,这里的 4 列矩阵分别给定区域横坐标的左和右以及纵坐标的下和上的位置,即给定了区域左下角和右上角的坐标,这样就可以划分出一块矩形作图区域来。注意这里的坐标值应该在 [0, 1] 范围内,整个屏幕左下角坐标为 (0, 0),右上角坐标为 (1, 1)

B.8 给出了用矩阵指定作图区域位置的示例,该矩阵的取值为:

matrix(
  c(
    0, 0.1, 0.4, 0.3,
    0.5, 0.8, 0.9, 1,
    0, 0.2, 0.3, 0.5,
    0.4, 0.7, 0.8, 1
  ),
  4, 4
)
##      [,1] [,2] [,3] [,4]
## [1,]  0.0  0.5  0.0  0.4
## [2,]  0.1  0.8  0.2  0.7
## [3,]  0.4  0.9  0.3  0.8
## [4,]  0.3  1.0  0.5  1.0

矩阵一共四行,因此制定了四个屏幕作图区域,四列给定了区域的位置,例如第 1 个区域的位置在点 (0.0, 0.0) 与点 (0.5, 0.4) 之间。该示例中,整个屏幕中划分出了 4 块有重叠的区域,并分别画出了 4 幅散点图。

拆分屏幕区域方法的灵活性还在于它可以在拆分的区域中继续拆分(类似于“递归”的做法),而前两节中提到的办法是无法做到这一点的,因此三种方法中这种方法的功能是最强大的,但大多数情况下我们其实用不着如此灵活的定制方法,网格式拆分已经足够使用。

split.screen(matrix(c(
  0, 0.1, 0.4, 0.3, 0.5, 0.8,
  0.9, 1, 0, 0.2, 0.3, 0.5, 0.4, 0.7, 0.8, 1
), 4, 4))
for (i in 1:4) {
  screen(i)
  par(mar = c(0, 0, 0, 0), mgp = c(0, 0, 0), cex.axis = 0.7)
  plot(sort(runif(30)), sort(runif(30)), col = i, 
       pch = c(19, 21, 22, 24)[i], ann = FALSE, axes = FALSE)
  box(col = "gray")
  axis(1, tcl = 0.3, labels = NA)
  axis(2, tcl = 0.3, labels = NA)
}
拆分作图设备屏幕区域的示例:本图展示了如何用一个矩阵参数控制作图区域在屏幕上的的位置。

图 B.8: 拆分作图设备屏幕区域的示例:本图展示了如何用一个矩阵参数控制作图区域在屏幕上的的位置。

B.5 交互操作

R 的图形设备可以支持简单的交互式操作,包括支持对鼠标和键盘输入的响应等,这主要由 graphicsgrDevices 包中的以下几个函数来完成:

B.5.1 获取鼠标位置的坐标

graphics 包中的函数 locator() 可以获取当前鼠标在图形坐标系统中的位置坐标,其用法为:

usage(locator)
## locator(n = 512, type = "n", ...)

当我们在图形窗口中创建了一幅图形后,我们可以调用该函数并通过点击鼠标获得坐标。参数 n 表示鼠标点击的次数,type 为点击鼠标之后生成的图形类型,可以边点鼠标边画点或画线,后面的参数为一些图形参数,设定点或线的样式。

该函数在点击鼠标事件结束之后会返回一个包含坐标数据的列表,列表中 x 和 y 分别表示横坐标和纵坐标的位置。如下例:

plot(1)
# 任意点击三下鼠标
locator(3)
# 返回坐标(结果取决于用户点击的位置)
# $x
# [1] 0.6121417 0.8046955 1.2561452
# $y
# [1] 0.9562884 0.8710420 1.1648702

借助 locator() 返回的坐标数据,我们可以更方便地向图中添加一些图形元素,尤其是图例。因为 R 的图形设备大多都不支持图形元素的鼠标拖拽,所以事先使用 locator() 在图上“探探路”对画图还是很有帮助的。

B.5.2 识别鼠标附近的数据

graphics 包中的函数 identify() 可以通过鼠标点击一幅散点图识别鼠标周围的数据点,并且可以给辨识出的数据添加标签,其默认用法如下:

usage(identify, "default")
## identify(x, ...)

xy 给出散点图的原始数据,以便鼠标位置坐标与原始数据进行距离匹配,labels 为数据的标签,默认用数据的序号 1、2、3……。

当数据的散点图呈现出异常现象时,如存在离群点等等,我们可以很方便地通过 identify() 函数找出该数据的名称或者序号。

B.5.3 响应鼠标键盘的动作

demo('mouse_move', package='MSG')
鼠标在图形窗口中移动的效果图

图 B.9: 鼠标在图形窗口中移动的效果图

grDevices 包中的函数 getGraphicsEvent() 则提供了更灵活的交互,它可以捕获三种鼠标事件(鼠标按下、鼠标移动和鼠标弹起)和一种键盘事件(键盘输入)。用法如下:

usage(getGraphicsEvent)
## getGraphicsEvent(prompt = "Waiting for input", onMouseDown = NULL,
##   onMouseMove = NULL, onMouseUp = NULL, onKeybd = NULL, onIdle = NULL,
##   consolePrompt = prompt)

后面四个参数分别定义了鼠标和键盘事件所对应的行为(通过给定函数实现),具体解释和示例请参见其帮助文件,这里我们只是给出一个例子说明。图 B.9 演示了鼠标移动的效果:我们在黑色背景的窗口中画了一批数据点,然后通过鼠标的移动在鼠标周围生成一个矩形框,框内的点变成黄色且放大的样式,而框外的点为红色的小点。随着鼠标的移动,矩形框也会在屏幕上移动,从而会框住不同的点。

事实上当今已经有很多类似的交互式图形系统,例如 GGobi 系统 (Cook and Swayne 2007)、Java 的图形系统、OpenGL 等,R 中也有相应的基于这些系统的函数包如 rggobi (Wickham et al. 2018)iplots (Urbanek and Wichtrey 2018)rgl (Adler and Murdoch 2024) 等;感兴趣的读者可以结合 5.4 小节去研究这些图形系统以及函数包。

B.6 图形设备

利用 grDevices 包中的若干图形设备,我们可以将 R 的图形输出为各种格式的文件,包括位图文件(BMP、JPEG、PNG、TIFF)和矢量图文件(PDF、EPS)以及 TeX 或 LaTeX 文件。本书中除了第 1 章中的历史图形以外,其它大部分图形都是使用 tikzDevice(Sharpsteen and Bracken 2023) 中的 tikz() 图形设备生成的(其本质是 LaTeX)。

基本的图形设备函数有位图设备 bmp()jpeg()png()tiff(),以及矢量图设备 svg()postscript()pdf(),打开图形设备之后,所有的 R 图形都会被生成在该图形设备中,而不会再在窗口中显示,直到图形设备被关闭。详细信息请读者自行查阅相应的帮助文件。

## 图形设备的大致用法
png("my-plot.png", width = 600, height = 400) # 开启
plot(rnorm(100))
dev.off() # 关闭设备,图形被保存在文件 my-plot.png 中
pdf("another-plot.pdf", width = 7, height = 5) # PDF 图形
plot(iris)
dev.off()

注意位图设备可以支持在图形中使用中文或其它 CJK 字符,但是在矢量图设备中使用中文字符时则需要设定字体族参数 family,否则中文不会被显示出来(例如简体中文应该用 pdf(family = 'GB1'))。关于非标准字符在图形设备中的使用,请参考 Murrell and Ripley (2006)

最后补充关于图形的一点基础知识:位图文件的图形是由一个个像素点构成的,因此放大之后会变成晶格状从而不太清晰,而矢量图是由内部的数值矢量构成,这些矢量仅仅定义图形元素的始末位置以及其它属性,放大之后清晰度不变。例如一条直线在位图中由若干个点组成,而在矢量图中则是由两个点构成(给定起点和终点),图形放大之后位图的点之间可能会出现空隙,而矢量图随着放大会自动填充两点之间的空隙。为了得到高质量的打印输出,大多数情况下我们建议使用矢量图。

B.7 思考与练习

  1. R 默认的点的样式为 19,即空心点。你认为这个默认设置是否合理?在数据量很小和很大的时候,你认为什么样的默认设置更好?7.1.1 小节中详细讨论了这个问题。

  2. 一幅图形的纵横比(aspect ratio)有什么作用?这个设置可能会给读者带来怎样的陷阱或假象?

  3. 当我们基础图形系统时,图例的位置常常是一个麻烦问题,因为我们需要将图例放在空白的地方,避免遮挡图中其它元素。前面 3.7 小节介绍了图例函数的用法,请结合 B.5 小节中介绍的 locator() 函数实现用鼠标点击的方式添加图例。

  4. 在 Windows 下我们可以通过菜单点选的方式保存图形窗口中的图,这样做和用图形设备函数表面上没什么区别,但图形设备函数有两大好处:可以精确控制图形的大小、可以摆脱用户的干预。你能否构思出一些基于图形设备的程序应用?例如开发在线作图系统,用户只需要提交数据并给定一些绘图参数,服务器便可以返回相应的图片。

  5. 分别用 png() 设备和 jpeg() 设备保存任意一幅图形,它们得到的图片质量和文件大小有何区别?在必须使用位图的情况下,为什么我们通常推荐使用 PNG 图形?

  6. 矢量图相比起位图有什么劣势?运行以下代码并查看结果:

    x <- rnorm(10000)
    pdf("PDF-plot.pdf")
    plot(x)
    dev.off()
    png("PNG-plot.png")
    plot(x)
    dev.off()

参考文献

Adler, Daniel, and Duncan Murdoch. 2024. Rgl: 3D Visualization Using OpenGL. https://github.com/dmurdoch/rgl.
Cook, Dianne, and Deborah F. Swayne. 2007. Interactive and Dynamic Graphics for Data Analysis with r and GGobi. Springer.
Murrell, Paul, and Brian Ripley. 2006. “Non-Standard Fonts in PostScript and PDF Graphics.” R News 6 (2): 41–47. http://cran.r-project.org/doc/Rnews/Rnews_2006-2.pdf.
Sharpsteen, Charlie, and Cameron Bracken. 2023. tikzDevice: R Graphics Output in LaTeX Format. https://github.com/daqana/tikzDevice.
Urbanek, Simon, and Tobias Wichtrey. 2018. Iplots: iPlots - Interactive Graphics for r. https://CRAN.R-project.org/package=iplots.
Wickham, Hadley, Duncan Temple Lang, Debby Swayne, and Michael Lawrence. 2018. rggobi: Interface Between R and GGobi. https://CRAN.R-project.org/package=rggobi.

  1. 注意是字母 l(表示“line”), 不是数字 1!同样后面的字母 o(表示“overplotted”)也不要误认为是数字 0↩︎