-
Notifications
You must be signed in to change notification settings - Fork 53
/
gallery.Rmd
2017 lines (1330 loc) · 162 KB
/
gallery.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# 图库 {#cha:gallery}
> 他解释说:"你要明白,我认为人的大脑原本像一间空空的屋子,必须有选择地用一些家具填满它。只有笨蛋才把他碰到的各种各样的破烂都塞进去。这样的话,那些可能用得上的知识就被挤了出来;或者,充其量也只是把那些破烂同其它东西混杂在一块儿。结果,在需要时却难得找到了。因此,一个善于工作的人,对于将什么东西纳入自己的头脑里是非常仔细的。他只会容纳那些工作时用得着的工具,而且又将这些工具分门别类,安排得井然有序。如果认为这间屋子的墙壁富有弹性,可以随意扩展,那就大错特错了。毫无疑问,总有一天,当你增加点滴知识时,却把从前熟悉的知识给忘记了。因此,不要让无用的信息挤掉那些有用的信息,这一点是至关重要的。"
>
> --- 柯南·道尔《血字的研究》
本章中,我们结合 R 语言中的相关函数以及数据实例对各种统计图形依次作出介绍。从第 \@ref(sec:hist) 节到 \@ref(sec:pie) 节的所有图形都是基于 **graphics** 包所作,其后的图形均来自于其它函数包。图形的介绍顺序大致按函数的字母序,但直方图、箱线图和散点图等常见图形放在前面,而饼图被有意安排在最后。
## 直方图 {#sec:hist}
直方图(Histogram)是展示连续数据分布最常用的工具,它本质上是对密度函数的一种估计。在介绍作图方法之前我们有必要先了解一下它的基本数学思想,本节仅作简要介绍,
详细的数学理论参见 @Scott92。
我们知道,对于连续随机变量来说,其密度函数即为分布函数的导数:
\begin{equation}
f(x)=F'(x)=\lim_{h\rightarrow0}\frac{F(x+h)-F(x)}{h}
(\#eq:density)
\end{equation}
因此我们不妨自然而然地从分布函数的估计出发得到密度函数的估计。当我们拿到一批数据 $X_1,X_2,\ldots,X_n$ 时,我们最容易想到的分布函数估计就是经验分布函数:
\begin{equation}
\hat{F}_{n}(x)=\frac{1}{n}\sum_{i=1}^{n}\mathbf{I}(X_{i}\leq x)
(\#eq:empirical)
\end{equation}
其中 $\mathbf{I}(\cdot)$ 为示性函数;结合公式 \@ref(eq:density) 和 \@ref(eq:empirical) 以及示性函数的性质,我们可以直接得到以下密度函数估计:
\begin{equation}
\hat{f}_{n}(x)=\lim_{h\rightarrow0}\frac{1}{n}\sum_{i=1}^{n}\frac{\mathbf{I}(x<X_{i}\leq x+h)}{h}
(\#eq:den-est)
\end{equation}
公式 \@ref(eq:den-est) 实际上已经给出了直方图作为密度函数估计工具的基本思想:划分区间并计数有多少数据点落入该区间。实际数据不可能无限稠密,因此 $h\rightarrow0$ 的条件往往是不可能实现的,于是我们退而求其次,只是在某一些区间段里面估计区间上的密度。首先我们将实数轴划分为若干宽度为 $h$ 的区间(我们称 $h$ 为“窗宽”):
\begin{equation}
b_{1}<b_{2}<\cdots<b_{j}<b_{j+1}<\cdots;\;b_{j+1}-b_{j}=h,\,j=1,2,\cdots
(\#eq:den-est2)
\end{equation}
然后根据以下直方图密度估计表达式计算区间 $(b_j,b_{j+1}]$ 上的密度估计值:
\begin{equation}
\hat{f}_{n}(x)=\frac{1}{nh}\sum_{i=1}^{n}\mathbf{I}(b_{j}<X_{i}\leq b_{j+1});\;x\in(b_{j},b_{j+1}]
(\#eq:den-formula)
\end{equation}
最后我们将密度估计值以矩形的形式表示出来,就完成了直方图的基本制作。当然我们没有必要使用这样原始的方式制作直方图,R 中提供了 `hist()` 函数,其默认用法如下:
```{r}
usage(hist.default)
```
其中, `x` 为欲估计分布的数值向量;`breaks` 决定了计算分段区间的方法,它可以是一个向量(依次给出区间端点),或者一个数字(决定拆分为多少段),或者一个字符串(给出计算划分区间的算法名称),或者一个函数(给出划分区间个数的方法),区间的划分直接决定了直方图的形状,因此这个参数是非常关键的; `freq` 和 `probability` 参数均取逻辑值(二者互斥),前者决定是否以频数作图,后者决定是否以概率密度作图(这种情况下矩形面积为 1); `labels` 为逻辑值,决定是否将频数的数值添加到矩形条的上方;其它参数诸如 `density` 、 `angle` 、 `border` 均可参见低层作图函数"矩形"( `rect()` ,\@ref(sec:polygon) 节)。
(ref:fig-hist-geyser-s) 喷泉间隔时间直方图
(ref:fig-hist-geyser) 喷泉间隔时间直方图:(1)使用默认参数值(作频数图);(2)概率密度直方图;(3)减小区间段数,直方图看起来更平滑(偏差大,方差小);(4)增大区间段数,直方图更突兀(偏差小,方差大)
```{r hist-geyser, fig.width=4.8, fig.height=4, fig.cap="(ref:fig-hist-geyser)", fig.scap='(ref:fig-hist-geyser-s)', fig.show='hold', fig.ncol=1, message=FALSE,out.width="70%"}
par(mfrow = c(2, 2), mar = c(2, 3, 2, .5), mgp = c(2, .5, 0))
data(geyser, package = "MASS")
hist(geyser$waiting, main = "(1) freq = TRUE", xlab = "waiting")
hist(geyser$waiting, freq = FALSE, xlab = "waiting", main = "(2) freq = FALSE")
hist(geyser$waiting, breaks = 5, density = 10, xlab = "waiting", main = "(3) breaks = 5")
hist(geyser$waiting, breaks = 40, col = "red", xlab = "waiting", main = "(4) breaks = 40")
library(ggplot2)
library(cowplot)
p <- ggplot(aes(waiting), data = geyser)
p1 <- p + geom_histogram(breaks = seq(40, 110, by = 5))
p2 <- p + geom_histogram(breaks = seq(40, 110, by = 5), aes(y = after_stat(density)))
p3 <- p + geom_histogram(breaks = seq(40, 110, by = 10))
p4 <- p + geom_histogram(breaks = seq(42, 108, by = 2), fill = "red", color = "black")
plot_grid(p1, p2, p3, p4, labels = c(
"(1) freq = TRUE",
"(2) freq = FALSE",
"(3) breaks = 5",
"(4) breaks = 40"
), ncol = 2)
```
我们以黄石国家公园喷泉数据 `geyser` [@Venables02] 为例。图 \@ref(fig:hist-geyser) 展示了喷泉喷发间隔时间的分布情况。(1)和(2)中的直方图看起来形状完全一样,区别仅仅是前者为频数图,后者为密度图。二者在统计量上仅相差一个常数倍,但密度直方图的一个便利之处在于它可以方便地添加密度曲线,用以辅助展示数据的统计分布(图 \@ref(fig:hist-density) 即为一个示例);(3)和(4)的区别在于区间划分段数,我们可以很清楚看出区间划分的多少对直方图的直接影响。关于区间划分的一些讨论可以参考 @Venables02,这里我们需要特别指出的是,直方图的理论并非想象中或看起来的那么简单,窗宽也并非可以任意选择,不同的窗宽或区间划分方法会导致不同的估计误差。关于这一点,Excel 的直方图可以说是非常不可靠的,因为它把区间的划分方法完全交给了用户去选择,这样随意制作出来的直方图很可能会导致大的估计误差、掩盖数据的真实分布情况。另外一点需要提醒的是关于直方图中的密度曲线,SPSS 软件在绘制直方图时会有选项提示是否添加正态分布密度曲线,这也是完全的误导,因为数据不一定来自正态分布,添加正态分布的密度曲线显然是不合理的,相比之下,图 \@ref(fig:hist-density) 的做法才是真正从数据本来的分布出发得到的密度曲线。
(ref:fig-hist-density-s) 直方图与密度曲线的结合
(ref:fig-hist-density) 直方图与密度曲线的结合:借助函数 `density()` 可以计算出数据的核密度估计,然后利用低层作图函数 `lines()` 将核密度估计曲线添加到直方图中。
```{r hist-density, fig.width=3.6, fig.height=3, results='hide', fig.cap='(ref:fig-hist-density)', fig.scap='(ref:fig-hist-density-s)', fig.show='hold', fig.ncol=1,out.width="35%"}
demo("hist_geyser", package = "MSG")
df <- data.frame(
x = seq(40, 110, 5), y = 0,
xend = seq(40, 110, 5), yend = ht
)
p2 + geom_density(fill = "lightgray", color = "black") +
geom_segment(aes(x = x, y = y, xend = xend, yend = yend),
data = df, lty = 3
)
```
直方图函数在作图完毕之后会有一些计算返回值,这些值对于进一步的作图或者分析很有用,例如区间划分端点、频数(或密度)、区间中点等等,这些信息可以被灵活应用在图形定制上
(例如图 \@ref(fig:layout-margin))。
由于直方图需要对连续型数据做离散分组,因此它有一个明显的缺点,就是它的形状依赖于分组的端点,例如若有好几个相同的数值正好处在分组端点上,那么我们只要稍微向左或向右移动一下分组端点,这些数据点就会被划分入不同的区间,导致矩形条的高度变化。@Scott92 提出了一种解决这种直方图不稳定性问题的办法叫“移动平均直方图”(Average Shifted Histogram,简称 ASH),它的思想是使用一系列移动的区间去划分数据,比如 $(b_1+ih/n,b_2+ih/n,\ldots,b_n+ih/n)$,$i=0,\cdots,n-1$,最后将这 $n$ 种划分方法的频数结果"平均"起来,就得到了 ASH 图,这样有效避免了边界点的归属问题。然而,在核密度估计理论已经非常完备的今天,我们几乎没有必要再用这种技巧去克服原来的问题了,毕竟 ASH 与核密度估计比起来显得还是太粗糙。图 \@ref(fig:hist-density) 的核密度曲线基于函数 `density()` 计算而来,它的参数包括核函数和窗宽等,实际应用中我们可能需要尝试不同的核函数以及窗宽值,@Venables02 第 5.6 小节介绍了一些选择的经验可供参考。
## 茎叶图
茎叶图(Stem-and-Leaf Plot)与直方图的功能类似,也是展示数据密度的一种工具,但相比之下茎叶图对密度的刻画显得非常粗略,而且对原始数据通常会作舍入处理,它只是在早期计算机尚不发达时对于手工整理数据来说比较方便。茎叶图的整体形状如同植物的茎和叶,对于一个数据,通常取其 $10^n$ 部分为茎($n$ 视所有数据的数量级而定),剩下的尾数为叶,放置于茎旁,这样每隔 $m10^n$ 就对数据作一次归类汇总,将落入区间 $[km10^{n},\ (k+1)m10^{n}]$ 的数据汇集为叶子($k,m$ 为整数,$m$ 通常取 1,$k=1,2,3,\cdots$),我们不妨称这种区间为一个“节”,节的长度与直方图的“窗宽”本质上是同样的概念。显然,叶子越长则表明该节上数据频数越高。
R 中茎叶图的函数为 `stem()`,其用法为:
```{r}
usage(stem)
```
参数 `scale` 控制着 $m$,即节与节之间的长度( `scale` 越大则 $m$ 越小); `width` 控制了茎叶图的宽度,若叶子的长度超出了这个设置,则叶子会被截取到长度 `width` ,然后以一个整数表示后面尚有多少片叶子没有被画出来。
下面我们以 **datasets** 包中 `islands` 数据为例说明茎叶图的作法。该数据记录了世界上各大陆地块的面积大小,原始数据前 10 条如下(单位:千平方英里):
```{r islands-data}
head(islands, 10)
```
可以看出,以上数据中最大的数量级为 $10^{4}$,而大部分数据的数量级集中在 $10^{1}$,因此茎上的数量级取作 $10^{3}$ 相对比较合适---更大的数量级会导致茎的节数非常少,对分布的刻画过于粗略;而更小的数量级会导致节数过多,使得茎叶图几乎退化为数据的原始表示,这样也难以看出数据的集中趋势。下图展示了 48 块大陆块面积的分布,该茎叶图窗宽为 $2\times10^{3}$,图中注明了原始数据小数点位置在 `|` 后面三位数处,因此我们从图中“还原”原始数据时,需要用(“茎的区间‘+’叶”)$\times10^{3}$。
```{r stem-islands, fig.cap="(ref:fig-stem-islands)", fig.scap="(ref:fig-stem-islands-s)"}
stem(islands)
stem(islands, width = 20)
# 可以增大窗宽 stem(islands, scale = 2) 看看效果
```
可以明显看出,这些面积数据是严重右偏的,即:少数陆地块的面积非常大,而大多数陆地块的面积相对来说都很小。事实上,主要是七大洲的大陆块面积非常大,而其它岛屿诸如海南岛、帝汶岛、九洲岛等面积都相对较小。
我们以上图为例说明一下茎叶图的制作过程及其相应解释。首先我们将原始数据除以 $10^{3}$,并四舍五入到小数点后的一位数:
```{r stem-process}
# 去掉陆地名称以便显示数据
unname(sort(round(islands / 1000, 1)))
```
然后从 0 到 $18\times10^{3}$、以 $2\times10^{3}$ 为窗宽,分段整理数据,每一段(节)中依次放置落入该段的数据的小数位,堆砌起来便形成了茎叶图的叶子。例如 $11.5$ 落入了 $[10,12]$ 的区间,我们就将尾数 5 放在 10 的右边;类似地,17.0 在 $[16,18]$ 之间,我们将 0 放在 16 右边;关于茎叶图顶部的一长串 0 的解释此处不再赘述。
下图是利用泊松分布($\lambda=10$)随机数生成的茎叶图,可以看出数据密度在 10 附近最高,这与理论相符。由于窗宽为 1,不存在舍入问题,所以图形可以还原到原始数据,请读者自行对应数据观察茎叶图。
```{r stem-poisson, fig.cap="(ref:fig-stem-poisson)", fig.scap="(ref:fig-stem-poisson-s)"}
# 均值 lambda 为 10 的泊松分布随机数
sort(x <- rpois(80, lambda = 10))
stem(x, scale = 2)
```
经过前面的说明,现在我们不妨将茎叶图简单理解为横放着的直方图,只是茎叶图通常都以某个便利的整数为窗宽,不如直方图那样精细。此外,茎叶图曾经的优势(简单、可手工绘制)在今天这个计算机时代也显得并不突出,因此,除非特殊情况,我们建议主要使用直方图作为密度函数估计工具。
## 箱线图 {#sec:boxplot}
箱线图(Box Plot 或 Box-and-Whisker Plot)主要是从四分位数的角度出发
描述数据的分布,它通过最大值($Q_4$)、上四分位数($Q_3$)、中位数($Q_2$)、下四分位数($Q_1$)和最小值($Q_0$)五处位置来获取一维数据的分布概况。我们知道,这五处位置之间依次包含了四段数据,每段中数据量均为总数据量的 $1/4$。通过每一段数据占据的长度,我们可以大致推断出数据的集中或离散趋势(长度越短,说明数据在该区间上越密集,反之则稀疏)。
R 中相应的函数为 `boxplot()`,其用法如下:
```{r boxplot-usage}
# 默认用法
usage(boxplot.default)
# 公式用法
usage(graphics:::boxplot.formula)
```
因为 `boxplot()` 是一个泛型函数,所以它可以适应不同的参数类型。目前它支持两种参数类型:公式( `formula` )和数据,后者对我们来说可能更容易理解(给一批数据、作相应的箱线图),而前者在某些情况下更为方便,后面我们会举例说明。参数 `x` 为一个数值向量或者列表,若为列表则对列表中每一个子对象依次作出箱线图; `range` 是一个延伸倍数,决定了箱线图的末端(须)延伸到什么位置,这主要是考虑到离群点的原因,在数据中存在离群点的情况下,将箱线图末端直接延伸到最大值和最小值对描述数据分布来说并不合适(图形缺乏稳健性),所以 R 中的箱线图默认只将图形延伸到离箱子两端 $\mathrm{range}\times(Q_3-Q_1)$ 处,即上下四分位数分别加/减内四分位距(Interquartile
Range,简称 $\text{IQR}\equiv Q_3-Q_1$)的倍数,超过这个范围的数据点就被视作离群点,在图中直接以点的形式表示出来; `width` 给定箱子的宽度; `varwidth` 为逻辑值,若为 `TRUE`,那么箱子的宽度与样本量的平方根成比例,这在多批数据同时画多个箱线图时比较有用,能进一步反映出样本量的大小; `notch` 也是一个有用的逻辑参数,它决定了是否在箱子上画凹槽,凹槽所表示的实际上是中位数的一个区间估计,其计算式为 $Q_2+/-1.58\mathrm{IQR}/\sqrt{n}$
[@McGill78; @Chambers83],区间置信水平为 95%,在比较两组数据中位数差异时,我们只需要观察箱线图的凹槽是否有重叠部分,若两个凹槽互不交叠,那么说明这两组数据的中位数有显著差异(P 值小于 0.05); `horizontal` 为逻辑值,设定箱线图是否水平放置; `add` 设置是否将箱线图添加到现有图形上(例:图 \@ref(fig:symbols-pop));其它参数诸如设置箱子颜色、位置、更详细的宽度等参见 `?boxplot`。
(ref:fig-insects-boxplot-s) 各种杀虫剂下昆虫数目的箱线图
(ref:fig-insects-boxplot) 昆虫数目箱线图:六种杀虫剂下昆虫的数目分布。
```{r insects-boxplot, fig.width=3.6, fig.height=3, fig.cap="(ref:fig-insects-boxplot)", fig.scap="(ref:fig-insects-boxplot-s)",fig.show='hold', fig.ncol=1}
boxplot(count ~ spray, data = InsectSprays,
col = "lightgray", horizontal = TRUE, pch = 4)
ggplot(aes(y = count, x = spray), data = InsectSprays) +
geom_boxplot(outlier.shape = 4) +
coord_flip()
```
绘制单个箱线图时只需要给 `boxplot()` 传入一个数值向量即可,如:`boxplot(rnorm(100))`;这里我们主要使用公式型的参数,以 **datasets** 包中的杀虫剂数据 `InsectSprays` 为例。该数据有两列,第一列为昆虫数目,第二列为杀虫剂种类(ABCDEF),这里是随机抽取的 10 列数据:
```{r insects-data}
InsectSprays[sample(nrow(InsectSprays), 10), ]
```
为了了解杀虫剂的效果,我们需要对各种杀虫剂下昆虫的数目作出比较。图 \@ref(fig:insects-boxplot) 是一个简单的箱线图展示,不难看出,除了 B 和 D 对应的昆虫数据呈左偏形态外,其它组均有右偏趋势,看起来各组数据的平均水平差异比较明显;另外注意观察图中的两个离群点(以 “$\times$” 表示)。总体看来,C 的效果最好。事实上,我们可以对这个数据作方差分析,检验杀虫剂类型对昆虫数目是否有显著影响:
```{r insects-aov}
insects.aov <- aov(count ~ spray, data = InsectSprays)
summary(insects.aov)
```
上述分析告诉我们杀虫剂类型有显著影响(P 值接近于 0),也印证了我们对图形的观察。
(ref:fig-rnorm-boxplot-s) 箱线图的凹槽与统计推断
(ref:fig-rnorm-boxplot) 箱线图的凹槽与统计推断:从凹槽不交叠的情况来看,两样本中位数有显著差异。
```{r rnorm-boxplot,fig.width=4.8,fig.height=2.4, tidy=FALSE, fig.cap="(ref:fig-rnorm-boxplot)", fig.scap="(ref:fig-rnorm-boxplot-s)", fig.ncol=1, fig.show='hold'}
x <- rnorm(150)
y <- rnorm(50, 0.8)
boxplot(list(x, y),
names = c("x", "y"), horizontal = TRUE,
col = 2:3, notch = TRUE, varwidth = TRUE
)
ggplot(
data = data.frame(
num = c(x, y),
idx = c(rep("x", 150), rep("y", 50))
),
aes(y = num, fill = idx)
) +
geom_boxplot(notch = TRUE) +
coord_flip()
# Wilcoxon检验的P值
wilcox.test(x, y)$p.value
```
最后我们再以一个模拟数据的例子展示箱线图凹槽的功能。这里我们分别从正态分布 $\mathrm{N}(0,1)$ 和 $\mathrm{N}(0.5,1)$ 中各自产生 150 和 50 个随机数,然后作箱线图比较两组数据中间位置的差异。图 \@ref(fig:rnorm-boxplot) 为一次模拟的结果,图中的凹槽表明了两组数据的中位数有显著差异,Wilcoxon 秩和检验也证实了这一结论。此外,该图还使用了 `varwidth` 参数以表明两组数据样本量的大小不同。
## 条形图 {#sec:barplot}
(ref:fig-vadeaths-barplot-s) 弗吉尼亚死亡率数据条形图
(ref:fig-vadeaths-barplot) 弗吉尼亚死亡率数据条形图: 堆砌和并列的条形图效果
```{r vadeaths-barplot, fig.width=4.8, fig.height=4.8, fig.cap = "(ref:fig-vadeaths-barplot)", fig.scap="(ref:fig-vadeaths-barplot-s)", fig.show="hold"}
library(RColorBrewer) # 用分类调色板
par(mfrow = c(2, 1), mar = c(3, 2.5, 0.5, 0.1))
death <- t(VADeaths)[, 5:1]
barplot(death, col = brewer.pal(4, "Set1"))
barplot(death,
col = brewer.pal(4, "Set1"), beside = TRUE,
legend.text = TRUE
)
# ggplot2
reshape_VADeaths <- transform(
expand.grid(
sex = colnames(VADeaths),
age = rownames(VADeaths)
),
rates = as.vector(t(VADeaths))
)
p <- ggplot(data = reshape_VADeaths, aes(x = age, y = rates, fill = sex))
p1 <- p + geom_col(position = "stack")
p2 <- p + geom_col(position = "dodge")
plot_grid(p1, p2, ncol = 1)
```
如同前面 \@ref(sec:begin) 节中曾经提到的,条形图目前是各种统计图形中应用最广泛的,但条形图所能展示的统计量比较贫乏:它只能以矩形条的长度展示原始数值,对数据没有任何概括或推断。
R 中条形图的函数为 `barplot()`,用法如下:
```{r barplot-usage}
usage(barplot.default)
```
条形图的主要参数是 `height`,它指定了长条的长度,这个参数可以接受一个数值向量或者一个数值矩阵作为参数,前者容易理解,后者稍有些复杂,当传入一个矩阵时,条形图针对矩阵的每一列画图,若 `beside` 为 `FALSE`,则矩阵每一列占据一条的位置,该条由若干矩形堆砌而成,这些矩形的高度对应着矩阵的行数据,若 `beside` 为 `TRUE`,这些矩形则并排排列而非堆砌; `width` 可以设置条的宽度; `space` 用以设置条之间的间距; `names.arg` 为条形图的标签,即每一条的名称; `legend.text` 参数在 `height` 为矩阵时比较有用,可以用来添加图例; `horiz` 用以设置条形图的方向(水平或垂直); `density` 、 `angle` 等参数可以参考矩形的章节(\@ref(sec:polygon) 节); `plot` 为逻辑值,决定是否将条形图添加到现有图形上。
图 \@ref(fig:vadeaths-barplot)
下图展示了参数 `beside` 和 `legend.text` 的效果。该图以 1940 年弗吉尼亚州分年龄组、分地区和分性别死亡率数据 `VADeaths` 为基础,展示了各组之间死亡率的差异,其中堆砌的条形图容易比较各年龄组总死亡率的大小,显然年龄越高死亡率越大,而并列的条形图容易比较组内的城乡和性别差异,一般说来,男性死亡率高于女性,农村男性死亡率低于城市男性,但女性的城乡差异没有明显规律。由于人眼对长度比比例更敏感(例如在区分城乡和性别差异时,图 \@ref(fig:vadeaths-barplot)
的上图就不如下图直观),所以我们制图时要考虑清楚我们想展示的是数据的哪一方面,即:将最关键的信息用最能激发视觉感知的形式表现出来。
```{r vadeaths-data}
VADeaths # 弗吉尼亚州死亡数据
```
## 散点图 {#sec:plot-default}
散点图通常用来展示两个变量之间的关系,这种关系可能是线性或非线性的。图中每一个点的横纵坐标都分别对应两个变量各自的观测值,因此散点所反映出来的趋势也就是两个变量之间的关系。
R 中散点图的函数为 `plot.default()`,但由于 `plot()` 是泛型函数(参见 \@ref(sec:plot) 小节),通常我们只需要提供两个数值型向量给 `plot()` 即可画散点图,或者提供一个两列的矩阵或数据框。函数 `plot.default()` 的用法如下:
```{r plot-default-usage}
usage(plot.default)
```
其中若 `x` 是一个两列的矩阵或数据框,则无需再提供 `y` ,否则 `x` 和 `y` 都必须是数值型向量;其它参数均已在 \@ref(sec:plot) 小节中介绍。
(ref:fig-scatter-alpha-s) 半透明散点图中的规律
(ref:fig-scatter-alpha) 半透明散点图中的规律:左图是一幅普通的散点图,图中几乎看不出数据有任何异常特征;右图中对点使用了透明度为 0.01 的红色,图中立即显示出一个深色的圆圈,表明该圆圈上集中了大量数据点。
```{r scatter-alpha, fig.width=4,fig.height=2, dev='png', dev.args=list(type = "cairo"), fig.cap="(ref:fig-scatter-alpha)", fig.scap="(ref:fig-scatter-alpha-s)",results='hide', fig.show="hold"}
demo("alphaDemo", package = "MSG")
p <- ggplot(BinormCircle, aes(V1, V2)) +
theme_void() +
theme(axis.ticks = element_blank(),
axis.text = element_blank(),
axis.title = element_blank(),
plot.background = element_rect(linetype = "solid",color = "black")
)
p1 <- p + geom_point(color = rgb(1, 0, 0))
p2 <- p + geom_point(color = rgb(1, 0, 0), alpha = 0.01)
plot_grid(p1, p2, ncol = 2)
```
图 \@ref(fig:scatter-alpha)
展示了一个人造数据的散点图:我们设计了 2 万个样本,其中有 1 万个样本点来自于两个独立的标准正态分布,另 1 万个样本点的坐标落在半径为 0.5 的圆上,最后将这 2 万个样本拼起来并打乱顺序。该数据收录在 **MSG** 包中,名为 `BinormCircle`。虽然数据只有两个变量,但我们用普通的统计模型和数值分析几乎无法找出数据的特征,例如线性回归显示两个变量 `V1` 和 `V2` 的回归系数非常不显著:
```{r BinormCircle-lm}
data(BinormCircle, package = "MSG")
head(BinormCircle) # 数据前 6 行
# 回归系数以及 P 值(不显著)
coef(summary(lm(V2 ~ V1, BinormCircle)))
```
换用高阶回归的结果也类似,无论回归阶数为多少,系数均不显著,这一点从数据的构造上就可以知道(理论上两个变量的相关系数为 0)。由于样本量太大,普通的散点图上点与点之间严重重叠,所以也很难看出散点图有何异常,而使用半透明色的散点图则很容易看出,在大量的数据点中,还隐藏着一个圆圈,说明有相当一部分数据分布有特殊规律。我们在网页 <https://yihui.org/en/2008/09/to-see-a-circle-in-a-pile-of-sand/>
上给出了其它五种不同的解决方案,都可以从图形的角度反映出这种规律。本章 \@ref(sec:smoothScatter) 小节也将以平滑散点图的方式再回顾这批数据。
## 关联图 {#sec:assocplot}
(ref:fig-assocplot-s) 眼睛颜色与头发颜色的关联图
(ref:fig-assocplot) 眼睛颜色与头发颜色的关联图
```{r assocplot,fig.width=4.8,fig.height=4, fig.cap="(ref:fig-assocplot)", fig.scap= "(ref:fig-assocplot-s)"}
(x <- margin.table(HairEyeColor, c(1, 2)))
assocplot(x)
chisq.test(x)$p.value # 卡方检验 P 值
```
关联图(Cohen-Friendly Association
Plot)是展示二维列联表数据的一种工具 [@Cohen80; @Friendly92],它主要是基于列联表的独立性检验理论(Pearson
$\chi^{2}$ 检验)生成的图形。
我们知道,对于一个 $r\times c$ 列联表,$\chi^{2}$ 统计量的定义为如下平方和形式:
\begin{equation}
\chi^{2}=\sum_{i=1}^{r}\sum_{j=1}^{c}d_{ij}^{2};\;\; d_{ij}=\frac{f_{ij}-e_{ij}}{\sqrt{e_{ij}}}
(\#eq:cap)
\end{equation}
其中,$f_{ij}$ 为单元格中的观测频数,$e_{ij}$ 为期望频数,二者相差越大,则会导致检验统计量的值越大,说明行变量和列变量越不独立。关联图所展示内容的正是这种差异,它的设计思路是,将图形同样以 $r\times c$ 的形式布局,每一个 "单元格" 中用一个矩形表示观测频数和期望频数的信息,具体来说,矩形的高度与 Pearson 残差 $f_{ij}-e_{ij}$ 成比例,宽度与期望频数 $\sqrt{e_{ij}}$ 成比例,这样一来,矩形的面积便与 $d_{ij}$ 成比例;此外,矩形自身带有方向,朝上表示残差为正,朝下则为负,不同方向的矩形同时也以不同颜色区分开来。
R 中关联图的函数为 `assocplot()`,用法如下:
```{r assocplot-usage}
usage(assocplot)
```
其中 `x` 为一个列联表数据(或者矩阵); `col` 为朝上和朝下矩形的颜色; `space` 用来设置矩形之间的间距。
图 \@ref(fig:assocplot)
是关于 `HairEyeColor` 数据的关联图。原始数据为一个三维数组,首先我们在性别维度上将数据汇总,得到眼睛颜色(棕蓝褐绿)和头发颜色(黑棕红金)人数的列联表。我们关心的问题是头发颜色与眼睛颜色之间是否存在关联,当然我们可以马上用函数 `chisq.test()` 作检验,但是检验的结果非常单一,我们只能知道零假设(独立)可否被拒绝,而图 \@ref(fig:assocplot) 则细致展示了数据的内部信息,例如从图中我们可以清楚看到,并非所有单元格都与期望频数有很大差异,只是少数几个单元格贡献了较大的 $\chi^{2}$ 值,如金发碧眼、金发棕眼等;事实上,这批数据为调查数据,眼睛颜色和头发颜色都为受访者(Delaware 大学的 592 名学生)自己填写,我们观察到金发碧眼单元格的期望频数和实际频数差异甚大,据说这背后有一则有趣的故事:由于“金发碧眼”是大家公认的美的标准,因此有些学生在填问卷时故意偏向于填写“金发碧眼”,导致“金发碧眼”的实际频数严重偏高[^assocplot-isu]。从图 \@ref(fig:assocplot) 的代码输出中我们知道,$\chi^{2}$ 检验可以拒绝零假设,眼睛的颜色与头发的颜色并不独立,二者之间存在某种关联关系,然而这种关联关系是由于生物或遗传原因引起还是受访者有意隐瞒自己的信息,则需要我们进一步斟酌了。
在 **vcd** 包 [@vcd] 中有一个类似的关联图函数 `assoc()`,但功能比本节中介绍的函数要更强大,详细介绍参见 @Meyer06
[@Zeileis07]。
[^assocplot-isu]: 本书作者在爱荷华州立大学统计系读博士期间,每周统计图形小组有一次讨论,这则消息来自于作者的一位导师 Heike Hofmann 教授
## 条件密度图 {#sec:cdplot}
条件密度图(Conditional Density Plot),顾名思义,展示的是一个变量的条件密度,确切的说是一个分类变量 $Y$ 相对一个连续变量 $X$ 的条件密度 $P(Y|X)$。假设 $Y$ 的取值为 $1,2,\cdots,k$,那么条件密度图将按照 $X$ 的取值从小到大在纵轴方向上依次展示出 $Y=i\;(i=1,2,\cdots,k)$ 的条件概率分布比例 $P_{i}=P(Y=i|X=x)$,这些比例大小沿横轴方向上以多边形表示,在任一一个 $X$ 点,所有比例之和均为 1,这个性质是显而易见的:
\begin{equation}
\sum_{i=1}^{k}P(Y=i|X=x)=1;\;\forall x
(\#eq:cdp)
\end{equation}
R 中条件密度图的函数为 `cdplot()`,它主要是基于密度函数 `density()` 完成条件密度的计算 [@Hofmann05],其用法如下:
```{r cdplot-usage}
usage(graphics:::cdplot.default)
usage(graphics:::cdplot.formula)
```
函数 `cdplot()` 是泛型函数,它可以支持两种参数类型:直接输入两个数值向量 `x` 和 `y` 或者一个公式 `y~x`。 `x` 为条件变量 $X$,它是一个数值向量, `y` 是一个因子向量,即离散变量 $Y$; `plot` 为逻辑值,决定了是否作出图形(或者仅仅是计算而不作图); `ylevels` 给出因子的取值水平(或者分类的名称), `bw` 、 `n` 、 `from` 和 `to` 都将被传递给 `density()` 函数以计算密度值,请参考 `density()` 帮助文件; `col` 给定一个颜色向量用以代表 $Y$ 的各种取值(默认为不同深浅的灰色); `border` 为多边形的边线颜色;其它参数诸如标题、坐标轴范围等此处略去。
(ref:fig-cdplot-s) 航天飞机 O 型环在不同温度下失效的条件密度图
(ref:fig-cdplot) 航天飞机 O 型环在不同温度下失效的条件密度图:随着温度升高,O 型环越来越不容易失效。
```{r cdplot,fig.width=4,fig.height=2.5,results="hide", fig.cap="(ref:fig-cdplot)", fig.scap="(ref:fig-cdplot-s)", fig.show="hold"}
demo("cdplotDemo", package = "MSG")
fail <- factor(
c(2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 2, 1,
2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1),
levels = 2:1,
labels = c("yes", "no")
)
temperature = c(53, 57, 58, 63, 66, 67, 67, 67, 68, 69, 70,
70, 70, 70, 72, 73, 75, 75, 76, 76, 78, 79, 81
)
fail_temperature <- data.frame(fail = fail, temperature = temperature)
ggplot(fail_temperature, aes(temperature, after_stat(count), fill = fail)) +
geom_density(position = "fill") +
geom_point(aes(temperature, c(0.75, 0.25)[as.integer(fail)]),
shape = 21, colour = "blue", fill = "yellow") +
scale_x_continuous(expand = c(0, 0)) +
scale_y_continuous("fail", expand = c(0, 0), breaks = c(0.25, 0.75),
labels = c("no", "yes"),
sec.axis = dup_axis(name = NULL,
breaks = seq(0, 1, 0.2),
labels = c(0, "", 0.4, "", 0.8, ""))
) +
scale_fill_manual(values = c("red", "lightblue")) +
theme_classic() +
theme(legend.position = "none")
```
这里我们以美国国家航空和宇宙航行局的一批 O 型环(O-ring,一种由橡胶或塑料制成的平环,用作垫圈)失效数据为例,这批数据有两个变量:温度变量和是否失效的变量。为了探索温度对 O 型环失效的影响,我们可以使用诸如 Logistic 回归之类的统计模型去计算、分析,而这里我们用条件密度图来展示温度的影响,如图 \@ref(fig:cdplot)。由于因变量是一个二分类变量,图中相应有两个多边形(带颜色的区域)分别表示是否失效,从图中我们可以清楚观察到,随着温度的上升,失效的可能性越来越小(下面的多边形高度越来越高),但失效的概率与温度并不是简单的线性关系,例如 55 到 65 之间的温度上升会使得失效概率迅速下降,而当气温更高的时候,失效概率下降的速度会减缓。为了更清楚地观察条件密度图的效果,我们也将原始数据以点的形式添加到图中;不难发现,O 型环失效的情况大多对应着相对较低的温度。
这里需要提醒读者注意的是,密度值的计算和估计与数据样本量大小有关系,小的样本量可能会导致密度估计的不精确,进而导致图形的误导性,因此使用条件密度图的时候务必注意样本量的问题。
## 等高图/等高线 {#sec:contour}
等高图(Contour Plot)和等高线(Contour
Line)表面上看起来是二维形式,但实际上展示的是三维数据。我们知道,三维图形往往比二维图形看起来更具有吸引力,然而在平面上展示三维图形也有其缺陷,最主要的就是视角问题,一幅三维图形可以有无数种视角,正视、侧视、俯视可能都会看到不同的信息,而且各种角度下可能都有一部分数据被前面的数据挡住而不能被看到,当然这些问题都可以通过更灵活的图形设备克服,如 **rgl** 包 [@rgl],但是在更多的情况下,我们的图形都必须展示在静态介质上(如书籍、论文等),我们不可能在纸面上拖动鼠标对图形进行交互式操作,因此,我们需要等高图这样一种以二维形式展示三维数据的工具。
(ref:fig-contour-grid-s) 网格数据的示意图
(ref:fig-contour-grid) 网格数据的示意图:体会 x、y 与 z 的对应关系
```{r contour-grid,fig.width=4.8,fig.height=2.5,echo=FALSE,fig.cap="(ref:fig-contour-grid)",fig.scap="(ref:fig-contour-grid-s)",fig.show="hold"}
par(mar = c(2.5, 3, 0.1, 0.1), mgp = c(1.5, .5, 0))
x <- 1:4
y <- 1:3
z <- rep(0, 12)
z[c(2, 3, 8, 11, 10, 5)] <- 1
z[c(6, 7)] <- 2
z[1] <- 5
z[4] <- 3
z[12] <- 4
plot(expand.grid(x, y), xaxt = "n", yaxt = "n",
type = "n", xlab = "x", ylab = "y",
xlim = c(0.5, 4.5), ylim = c(.5, 3.5), panel.first = grid())
polygon(c(1, 2, 3, 4, 3, 2), c(2, 1, 1, 2, 3, 3), lty = 2)
segments(2, 2, 3, 2)
text(rep(x, 3), rep(y, each = 4), paste("z =", z), cex = 1.5)
axis(1, 1:4, paste("x =", x))
axis(2, 1:3, paste("y =", y))
contour_grid_tidy <- transform(expand.grid(x = x, y = y), z = z)
contour_grid_polygon <- data.frame(
x = c(1, 2, 3, 4, 3, 2),
y = c(2, 1, 1, 2, 3, 3)
)
ggplot(contour_grid_tidy, aes(x, y)) +
geom_text(aes(label = paste("z =", z))) +
scale_x_continuous(limits = c(0.5, 4.5), labels = function(x) paste("x =", x)) +
scale_y_continuous(limits = c(0.5, 3.5), labels = function(x) paste("x =", x)) +
geom_polygon(data = contour_grid_polygon, fill = NA, color = "black", lty = 2) +
geom_segment(aes(x = 2, y = y, xend = 3, yend = y), data = data.frame(y = 2)) +
theme_bw()
```
首先我们需要理解等高图所展示数据的形式,因为它与我们想象的三维数据有所不同:并非三个数值向量,而是两个数值向量 x、y 和一个相应的矩阵 z。我们不妨将数据的形式想象为一座山峰,两个数值向量分别是横向和纵向的位置(如经纬度),第三维数据是每一种横纵向位置点组合上的高度,而横纵交叉组合之后形成的是一个网格,矩阵 z 则是这个网格上的高度数值,用数学式子表示这种关系就是 $z_{ij}=f(x_{i},y_{j})$。图 \@ref(fig:contour-grid) 为这种网格数据的示意图,请读者自行体会。
所谓等高线,就是将平面上对应的 z 值(高度)相等的点连接起来形成的线。同样,我们可以以一座山峰来想象:在同一海拔高度上围绕山峰一圈的线就是一条等高线。图 \@ref(fig:contour-grid)
中的连线即等高线,如实线表示的是高度为 2 的点,而虚线表示高度为 1 的点。注意等高线之间不可能相交,因为同一点不可能同时有两种高度。
等高线上通常会有数字表示高度,从这些数字我们不难想象出三维的山峰的形状,从这个意义上来说,等高图本质上也是一种三维图示方法。
R 中等高图的函数为 `contour()`,同时 **grDevices** 包中也提供了等高线的计算函数 `contourLines()`,用法分别如下:
```{r contour-usage}
usage(contour.default)
usage(contourLines)
```
参数 `x` 、 `y` 与 `z` 此处不再介绍; `nlevels` 可以设定等高线的条数、调整等高线的疏密; `levels` 设定一系列等高线的 `z` 值,只有这些值或者这些值附近的点才会被连起来; `labels` 为等高线上的标记字符串,默认是高度的数值; `xlim` 、 `ylim` 和 `zlim` 设定分别设定 `x` 、 `y` 与 `z` 的范围,默认从数据中获得; `method` 设定等高线的画法,有三种取值:`'simple'`(在等高线的末端加标签、标签与等高线重叠)、`'edge'`(在等高线的末端加标签、标签嵌在等高线内)或 `'flattest'`(在等高线最平缓的地方加标签、嵌在等高线内);其它参数用来调整等高图的外观,此处略去不介绍。
(ref:fig-contour-pop-s) 中国 31 地区国民预期寿命和高学历人数密度等高图
(ref:fig-contour-pop) 2005 年中国 31 地区国民预期寿命和高学历人数密度等高图
```{r contour-pop,fig.width=4,fig.height=3,results="hide",fig.cap="(ref:fig-contour-pop)",fig.scap="(ref:fig-contour-pop-s)",fig.showtext=TRUE,fig.show="hold"}
par(mar = c(4, 4, 0.2, 0.2))
library(MSG)
data(ChinaLifeEdu)
x <- ChinaLifeEdu
library(KernSmooth)
est <- bkde2D(x, apply(x, 2, dpik))
contour(est$x1, est$x2, est$fhat,
nlevels = 15, col = "darkgreen",
vfont = c("sans serif", "plain"),
xlab = "预期寿命",
ylab = "高学历人数"
)
points(x, pch = 20)
est_tidy <- data.frame(
life = rep(est$x1, length(est$x2)),
edu = rep(est$x2, each = length(est$x1)),
z = as.vector(est$fhat)
)
levels <- pretty(range(est_tidy$z, finite = TRUE), 15)
ggplot(est_tidy, aes(life, edu)) +
geom_contour(aes(z = z), breaks = levels, colour = "darkgreen") +
geom_point(aes(Life.Expectancy, High.Edu.NO), data = x) +
labs(x ="预期寿命", y = "高学历人数") +
theme_bw()
```
图 \@ref(fig:contour-pop)
利用等高图展示了一个聚类现象。数据来源于 2005 年中国统计年鉴,数值参见 **MSG** 包中的 `ChinaLifeEdu` 数据,这里使用了其中两个变量:人口预期寿命(实际数据来自 2000 年)和高学历人口数量(定义为大专以上学历人数)。首先我们对这二维变量利用 **KernSmooth** 包 [@KernSmooth] 进行核密度估计,得到二维核密度值(一个矩阵),然后用两个原始变量以及这个密度值矩阵作等高图。由于密度值反映的是某个位置上数据的密集程度,图 \@ref(fig:contour-pop) 所能揭示的现象是:中国 31 省市自治区在人口预期寿命和高学历人口数量上呈现出聚类的特征,图中密度值大的区域主要有中部、右上和左下三个,东中西格局比较明显,即:东部地区分布在图中右上角,中部省市分布在图中中部,西部地区集中在图中的左下角,对照图 \@ref(fig:symbols-pop) 可以知道聚类的具体地区名称,就更能理解这里"聚类"的含义了。关于这批数据的分析,我们在 \@ref(sec:symbols) 小节仍会继续,这里不再深入。
(ref:fig-contour-persp-s) 与等高图对应的三维透视图
(ref:fig-contour-persp) 与等高图对应的三维透视图:从右至左依次有三个山峰,尤其是中部山峰最为突出,对照后面 \@ref(sec:symbols) 小节中图 \@ref(fig:symbols-pop) 可知,这三个山峰分别代表东中西的省份。
```{r contour-persp, fig.height=6,fig.width=7,fig.cap="(ref:fig-contour-persp)",fig.scap="(ref:fig-contour-persp-s)"}
par(mar = rep(0, 4)) # 继续前面的例子
persp(est$x1, est$x2, est$fhat, shade = 0.75, border = NA,
col = "lightblue", phi = 20, theta = 15, box = FALSE)
```
在 **graphics** 包中还有一个类似的等高图函数 `filled.contour()`,它的原理完全类似,只是它用颜色来区分高度值的大小并且有颜色图例,看起来可能更美观一些,\@ref(sec:filled-contour) 小节中我们会详细介绍。
## 条件分割图 {#sec:coplot}
条件分割图(Conditioning Plot)的思想源自于统计学中的条件分布,即:给定某一个(或几个)变量之后看我们所关心的变量的分布情况。在条件分割图中,这种"分布"主要指的是两个变量之间的关系,通常以散点图表示。
条件分割图可以看作是对散点图的进一步深入发掘,它可以以一个或者两个条件变量作为所有数据的划分条件,条件变量在图形的边缘用灰色矩形条标记出变量的取值范围,每个矩形条对应着一幅散点图(严格来说此时应该称作"条件散点图"),这就是条件分割图的基本做法。后面我们会结合例子详细说明。
R 中条件分割图的函数为 `coplot()`,其用法如下:
```{r coplot-usage}
usage(coplot)
usage(co.intervals)
```
参数 `formula` 为一个公式,形式为 `y ~ x | a`(一个条件变量)或 `y ~ x | a * b`(两个条件变量),"`|`" 后面即为条件变量; `data` 为数据,其中包含了 `x` 、 `y` 、 `a` 和 `b` 等变量; `given.values` 指定条件变量的取值范围; `panel` 参数为该函数的关键参数,它决定了每一幅散点图的画法,默认只是画点,我们可以将其任意扩展为我们需要的图示功能,如添加回归直线等等; `rows` 和 `columns` 参数用来设定散点图的摆放行数和列数; `col` 和 `pch` 分别设定散点图中点的颜色和样式; `bar.bg` 给定条件变量指示条的填充颜色; `number` 和 `overlap` 传给 `co.intervals()` 函数用来计算划分连续变量的区间,前者设定划分段数,后者设定区间之间的重叠比例,如:
```{r co-intervals-demo}
co.intervals(1:10, number = 5, overlap = 0.5)
```
上述代码将数字 `1:10` 划分为了 5 段,每段长度为 2,重叠长度为 1,因此重叠比例为 0.5。条件分割图中散点图的顺序是从左到右、从下到上,分别与条件变量从左到右、从下到上的指示条对应。
(ref:fig-coplot-s) 给定震源深图的地震经纬度条件分割图
(ref:fig-coplot) 给定震源深图的地震经纬度条件分割图:四幅散点图有相同的坐标系,震源深度按左下、右下、左上、右上的顺序逐渐增加,可以看到地震发生地点逐渐在向斐济岛靠近。
```{r coplot,fig.width=4.8,fig.height=4.8, fig.cap="(ref:fig-coplot)", fig.scap="(ref:fig-coplot-s)"}
par(mar = rep(0, 4), mgp = c(2, .5, 0))
library(maps)
coplot(lat ~ long | depth,
data = quakes, number = 4,
ylim = c(-45, -10.72), panel = function(x, y, ...) {
map("world2",
regions = c("New Zealand", "Fiji"),
add = TRUE, lwd = 0.1, fill = TRUE, col = "lightgray"
)
text(180, -13, "Fiji", adj = 1)
text(170, -35, "NZ")
points(x, y, col = rgb(0.2, 0.2, 0.2, .5))
}
)
```
图 \@ref(fig:coplot)
展示了斐济岛(Fiji)附近的地震数据 `quakes`,数据包括地震发生地点的经纬度和震源的深度,我们想知道该地区在地震深度上分布是否均匀,因此我们令深度变量为条件变量,看在不同条件下地震发生地点(经纬度)是否有变化。从图中可以清楚看出,随着深度值的增加,地震发生地点逐渐由西向东、由南向北移动,震源较深的地震都发生在离斐济岛很近的东南侧。另外,图 \@ref(fig:coplot)
还展示了 `panel` 参数的用法,我们借助 **maps** 包 [@maps] 在散点图上添加了新西兰和斐济岛的地图作为辅助信息,关于 R 中地图的使用请参考 \@ref(sec:maps) 小节。
## 一元函数曲线图 {#sec:curve}
(ref:fig-curve-s) 函数 $f(x)=\mathrm{sin}(\mathrm{cos}(x)*\mathrm{exp}(-x/2))$ 的曲线图
(ref:fig-curve) 函数 $f(x)=\mathrm{sin}(\mathrm{cos}(x)*\mathrm{exp}(-x/2))$ 的曲线图(上)和均匀分布 $U(-1,1)$ 的特征函数图(下)。
```{r curve,fig.width=4.8,fig.height=5,fig.cap="(ref:fig-curve)",fig.scap="(ref:fig-curve-s)",dev='tikz',fig.process=to_png,fig.showtext=FALSE,small.mar=FALSE}
par(mar = c(4.5, 4, 0.2, 0.2), mfrow = c(2, 1))
chippy <- function(x) sin(cos(x) * exp(-x / 2))
curve(chippy, -8, 7, n = 2008, xlab = "$x$", ylab = "$\\mathrm{chippy}(x)$")
curve(sin(x) / x, from = -20, to = 20, n = 200,
xlab = "$t$", ylab = "$\\varphi_{X}(t)$")
```
函数曲线图没有什么特殊之处,仅仅是一条曲线而已,R 专门提供了一个函数,目的是为了节省我们去使用低层作图函数(如 `lines()`)的精力和时间。利用这个函数,我们可以方便地对任何一元函数作出它在某段定义域上的曲线。
R 中函数曲线图的函数为 `curve()`,其用法如下:
```{r curve-usage}
usage(curve)
```
参数 `expr` 为一个一元函数或者该函数的名称; `from` 和 `to` 分别定义了曲线的起点和终点; `n` 决定将定义域分成多少个小区间,以便计算函数值并连接曲线, `n` 值越大曲线越光滑; `add` 参数决定是否将曲线添加到现有图形上; `type` 参数决定了作图类型(参见 \@ref(sec:plot) 小节和图 \@ref(fig:plot-type))。注意:若对一个函数直接应用 `plot()` 函数,那么泛型函数 `plot()` 会自动调用 `curve()` 完成作图。
图 \@ref(fig:curve) 给出了函数 $f(x)=\sin(\cos(x)*\exp(-x/2))$ 的曲线以及均匀分布 $U(-1,1)$ 的特征函数曲线作为示例,其中特征函数为概率论中定义的 $\varphi_{X}(t) = \mathrm{E}[\exp(itX)]$。由于 `curve()` 与数据分析关系不甚密切,我们在此只是粗略介绍一下。
## Cleveland 点图 {#sec:dotchart}
在前面条形图(\@ref(sec:barplot) 小节)和后面饼图(\@ref(sec:pie) 小节)的章节中我们提到了点图 [@Cleveland85],事实上点图和条形图的功能非常类似:条形图通过条的长度表示数值大小,点图通过点的位置表示数值大小,二者几乎可以在任何情况下互换。
(ref:fig-dotchart-s) 弗吉尼亚死亡率数据的 Cleveland 点图
(ref:fig-dotchart) 弗吉尼亚死亡率数据的 Cleveland 点图
```{r dotchart,fig.width=4.8,fig.height=2.7,fig.cap="(ref:fig-dotchart)",fig.scap="(ref:fig-dotchart-s)"}
par(mar = c(4, 4, 0.2, 0.2))
dotchart(t(VADeaths)[, 5:1], col = brewer.pal(4, "Set1"), pch = 19, cex = .65)
```
R 中点图的函数为 `dotchart()`,用法如下:
```{r dotchart-usage}
usage(dotchart)
```
其中 `x` 与条形图的 `height` 参数相同,为一个数值向量或者矩阵; `labels` 为数据的标签;其它参数主要用来设置图形的样式如颜色、缩放倍数、点的样式等,此处略去。
图 \@ref(fig:dotchart) 再次以弗吉尼亚死亡率数据为例,给出了点图的展示。对比图 \@ref(fig:vadeaths-barplot) 不难发现点图与条形图的相通之处。相比之下,点图的图形元素更加简洁,制图时不会显得太拥挤,我们可以视情况在这二者选其一作为表达工具。
## 颜色等高图/层次图 {#sec:filled-contour}
颜色等高图,@Cleveland93 又称之为层次图(Level Plot),与等高图的原理完全类似,只是颜色等高图用不同颜色表示不同高度,并配有颜色图例,用以说明图中的颜色与高度值的对应关系。读者可以回顾 \@ref(sec:contour) 小节关于等高图的介绍。
(ref:fig-filled-contour-s) 新西兰 Maunga Whau 火山高度数据颜色等高图
(ref:fig-filled-contour) 新西兰 Maunga Whau 火山高度数据颜色等高图
```{r filled-contour,fig.height=4.3,fig.width=7,results="hide",fig.cap="(ref:fig-filled-contour)",fig.scap="(ref:fig-filled-contour-s)"}
demo("volcano", package = "MSG")
```
R 中的颜色等高图函数为 `filled.contour()`,其用法如下:
```{r filled-contour-usage}
usage(filled.contour)
```
这里面大多数参数与 `contour()` 函数完全相同,区别在于多了几个定义颜色的参数。 `color.palette` 给定一个调色板函数,用以生成一系列颜色供等高图填充使用,默认是青、白、粉调色板(回顾 \@ref(sec:palette) 小节);如果我们不指定调色板,也可以用 col 参数指定各高度水平对应的颜色; `plot.title` 、 `plot.axes` 、 `key.title` 和 `key.axes` 四个参数分别控制着等高图的标题、等高图的坐标轴、图例的标题和图例的坐标轴,它们都能接受若干语句(statement)作为参数值。
图 \@ref(fig:filled-contour)
描绘了新西兰 Maunga Whau 火山的地理数据 `volcano`,这份数据包含了在 $10\mathrm{m}\times10\mathrm{m}$ 的地理网格上测得的火山高度,是一个 $87\times61$ 的矩阵。仔细观察图 \@ref(fig:filled-contour),由于火山口的存在,颜色等高图的中部(偏左)有一小块区域的颜色并非白色,意即此处的高度比周围一圈要低。这种情况在三维图中有时未必能够迅速看出来,必须将视角调整为略向下俯视才能看到火山口。注意本图的调色板用的是"绿黄棕白"调色板,如 \@ref(sec:palette) 小节所介绍的,这种调色板比较适合展示地理数据。图 \@ref(fig:persp-pop)
提供了真实的火山立体图形。
颜色等高图中的图形布局(等高图和图例实质上都是独立的图形)是用 `layout()` 函数(\@ref(sec:multipage) 小节)完成的,这给我们带来了扩展上的不便,主要是因为颜色等高图的坐标系统与单幅统计图形的坐标系统并不一样,例如我们无法在作完一幅等高图之后再往图中添加诸如标题、坐标轴等图形元素,这种情况下,我们不妨采用另一种类似的图形--- 颜色图,参见 \@ref(sec:image) 小节。
## 四瓣图 {#sec:fourfoldplot}
四瓣图(Fourfold Plot)是用来查看 $2\times2\times k$ 列联表中两个二分变量之间关联关系的一种图示工具,它主要是基于二维列联表的检验理论而建立起来的 [@Friendly94]。
表 \@ref(tab:contingency) 是一个典型的二维列联表,通常我们想检验的是行变量与列变量是否独立;前面 \@ref(sec:assocplot) 小节中曾经利用 $\chi^{2}$ 检验构造了关联图,这里我们从优比(Odds Ratio,OR)的角度出发对列联表进行检验。
首先我们定义以下二式分别为在因素出现和不出现的情况下事件的发生率(医学上常称为风险):
\begin{equation}
\frac{P_{1}}{P_{1}+P_{3}};\;\;\frac{P_{2}}{P_{2}+P_{4}}
(\#eq:OR1)
\end{equation}
进而我们用这二式之比得到优比:
\begin{equation}
\mathrm{OR}\equiv\frac{P_{1}}{P_{1}+P_{3}}/\frac{P_{2}}{P_{2}+P_{4}}=\frac{P_{1}(P_{2}+P_{4})}{P_{2}(P_{1}+P_{3})}
(\#eq:OR)
\end{equation}
Table: (\#tab:contingency) 二维列联表的经典形式
| | 事件 | |
| ------: | :---------------------------: | ------------- |
| | 发生 不发生 | |
| 因素 有 | $P_{1}$ $P_{3}$ | $P_{1}+P_{3}$ |
| 无 | $P_{2}$ $P_{4}$ | $P_{2}+P_{4}$ |
| | $P_{1}+P_{2}$ $P_{3}+P_{4}$ | |
由于通常情况下事件发生的几率都比较小(尤其是医学上的疾病),即 $P_{1}$ 相对 $P_{3}$ 来说较小,$P_{2}$ 相对 $P_{4}$ 来说较小,因此 \@ref(eq:OR) 式可以近似用 \@ref(eq:ORapprox) 式代替:
\begin{equation}
\mathrm{OR}\approx\Psi\equiv\frac{P_{1}P_{4}}{P_{2}P_{3}}
(\#eq:ORapprox)
\end{equation}
我们记各单元格的样本实现值分别为 $a,b,c,d$;如果事件和因素相互独立,那么因素是否发生对事件是否发生(或发生率)没有影响,因此在零假设下优比的样本实现值 $ad/bc$ 应该接近于 1,换句话说,如果优比与 1 显著不同,那么很可能行列变量不独立。
四瓣图正是基于这样一个比例来完成制图的,它将优比体现在两个相邻的四分之一圆的半径之比上,如果两个扇形半径差异显著,那么说明行列变量不独立,即因素对事件有影响,这便是四瓣图最基本的用法,而背后还有关于优比置信区间的计算,并且这个置信区间也在图中用两道弧线表现了出来,四瓣图最终的读法就是观察两瓣相邻扇形的置信区间弧线是否有重叠,有则说明不能拒绝零假设,反之可以拒绝。这是基于假设检验和区间估计之间的转换关系而得以成立的。
计算置信区间需要用到 $\Psi$ 的方差以及正态性假定;$\Psi$ 的方差并不容易直接计算,但取对数之后就很容易了:
\begin{equation}
\mathrm{Var}(\mathrm{log}(\Psi))=\frac{1}{a}+\frac{1}{b}+\frac{1}{c}+\frac{1}{d}
(\#eq:ORlog)
\end{equation}
其置信区间为:
\begin{equation}
\mathrm{log}(\Psi)\pm q_{1-\alpha/2}\sqrt{\mathrm{Var}(\mathrm{log}(\Psi))}
(\#eq:ORconf)
\end{equation}
通过对 \@ref(eq:ORconf) 取指数即可还原到 $\Psi$ 本身的置信区间。关于四瓣图的数学理论就介绍到这里,感兴趣的读者可以参阅 @Friendly94 或者直接阅读 `fourfoldplot()` 的源代码(大约 200 行)。
R 中四瓣图函数 `fourfoldplot()` 的用法如下:
```{r fourfoldplot-usage}
usage(fourfoldplot)
```
其中 `x` 是一个 $2\times2\times k$ 的数组,当 $k=1$ 时,它也可以直接取一个 $2\times2$ 的矩阵; `color` 设定四分之一圆的填充颜色,处于同一对角线上的扇形颜色相同,颜色填充的顺序也反映出优比与 1 的大小; `onf.level` 为置信水平; `std` 为列联表的标准化方法,决定了标准化时分母所除的数。当 $k\geq1$ 时,该函数会依次生成 $k$ 幅四瓣图。
(ref:fig-fourfoldplot-s) 加州伯克利分校录取数据四瓣图
(ref:fig-fourfoldplot) 加州伯克利分校录取数据四瓣图
```{r fourfoldplot,fig.width=4.8,fig.height=4,fig.cap="(ref:fig-fourfoldplot)",fig.scap="(ref:fig-fourfoldplot-s)"}
ftable(UCBAdmissions) # 以紧凑形式展现出来的 UCB 录取数据
fourfoldplot(UCBAdmissions, mfcol = c(2, 3)) # 2 行 3 列排版
```
图 \@ref(fig:fourfoldplot) 是加州伯克利分校(UCB)录取数据的四瓣图,数据见于图中的代码输出。这批数据为一个 $2\times2\times6$ 的数组,我们可以分系别来看学生的录取是否与性别有关。从图中反映的情况来看,只有 A 系的四瓣图显示出了置信区间弧线不相交的情况,说明 A 系学生的录取与性别不独立,而其它系都不能拒绝零假设"录取与性别无关"。
现在我们不妨通过一些简单的 R 语言计算来证实两件事情。首先是扇形颜色的填充与优比的关系,计算优比的代码如下:
```{r UCB-OR}
(x <- apply(UCBAdmissions, 3, function(x) (x[1, 1] * x[2, 2]) / (x[1, 2] * x[2, 1])))
```
C 和 E 系的优比大于 1,观察图 \@ref(fig:fourfoldplot) 可知, `color` 参数的第一个颜色值填充第一、三象限的扇形,而优比小于 1 时,第一个颜色值填充第二、四象限。
其次我们完全可以将优比的置信区间分别算出来,看它们是否包含数值 1:
```{r UCB-CI}
y <- qnorm(0.975) * sqrt(apply(UCBAdmissions, 3, function(x) {
sum(1 / x)
}))
conf <- exp(cbind(log(x) - y, log(x) + y))
colnames(conf) <- c("2.5%", "97.5%")
conf
```
显然,这些置信区间中只有 A 系的不包含 1 在内,因此对于该系来说可以拒绝零假设。这与图形得到的结论是完全相符的。这里我们提醒读者注意上面的 R 代码与数学公式的对应关系,很多时候根据数学公式写 R 代码是非常简单的工作。最后我们可以看看卡方检验的 P 值,这些 P 值对应的结论和上面的图形和优比的结论都相同:
```{r UCB-chisq}
# 对每个系的录取数据进行卡方检验分别得到 P 值
round(apply(UCBAdmissions, 3, function(x) chisq.test(x)$p.value), 3)
```
## 颜色图 {#sec:image}
(ref:fig-mage24-s) 颜色图中色块与数值的对应关系
(ref:fig-mage24) 颜色图中色块与数值的对应关系:矩阵中数值越大,色块越趋近于白色,反之趋近红色。
```{r image24,fig.width=4,fig.height=1.2,fig.cap="(ref:fig-mage24)",fig.scap="(ref:fig-mage24-s)"}
par(mar = rep(0, 4))
x <- matrix(sample(24), 8)
image(1:8, 1:3, x, col = heat.colors(24), axes = FALSE, ann = FALSE)
text(rep(1:8, 3), rep(1:3, each = 8), as.vector(x))
```
颜色图(Color Image)与颜色等高图看起来非常类似,但是等高图需要从网格矩阵中计算等高的数据点,有时还需要一些平滑处理,而颜色图并不涉及任何背后的计算,只是简单将一个网格矩阵映射到指定的颜色序列上、以颜色方块表示数据的大小。在数据规律性较强且数据量较大的时候,这两种图形的区别可以说微乎其微,而当数据没什么规律或者数据量比较小的时候,颜色图的色块就可以很清楚地显露出来了,图 \@ref(fig:image24)
为一个简单的示意图。
R 中颜色图的函数为 `image()`,其用法如下:
```{r image-usage}
usage(image.default)
```
参数 `x` 、 `y` 、 `z` 与等高线的参数类似,不过由于该函数为泛型函数,因此也可以接受不同类型的参数,这三个参数除了可以接受两个数值向量和一个矩阵之外, `x` 还可以接受一个列表,列表中包含三个子对象:`x$x`、`x$y` 和 `x$z`,这三个子对象分别为两个数值向量和一个矩阵,这种情况下就不需要另外单独提供 `y` 和 `z` 参数了; `col` 设置一个颜色序列以便映射到不同大小的数值; `add` 为逻辑值,决定是否将颜色图添加到现有图形上; `breaks` 给定 `z` 分段的区间端点。
(ref:fig-image-s) 新西兰 Maunga Whau 火山高度数据颜色图
(ref:fig-image) 新西兰 Maunga Whau 火山高度颜色图:图 \@ref(fig:persp) 提供了真实的火山立体图形;读者可以到作者个人主页下载原图并放大 8 倍以上查看颜色图和颜色等高图的区别:颜色图是用颜色填充方块,而颜色等高图则是用颜色填充等高线之间的区域。
```{r image,fig.height=6,fig.width=6,fig.cap="(ref:fig-image)",fig.scap="(ref:fig-image-s)"}
par(mar = rep(0, 4), ann = FALSE)
x <- 10 * (1:nrow(volcano))
y <- 10 * (1:ncol(volcano))
image(x, y, volcano, col = terrain.colors(100), axes = FALSE)
contour(x, y, volcano,
levels = seq(90, 200, by = 5),
add = TRUE, col = "peru"
)
box()
```
这里我们仍然以新西兰 Maunga Whau 火山高度数据 `volcano` 为例。图 \@ref(fig:image)
是火山数据的颜色图,从外观上来看,它与前面的颜色等高图几乎无异(图 \@ref(fig:filled-contour)),但图 \@ref(fig:image) 中多了一些等高线,这也说明了颜色图较之颜色等高图的灵活性和可扩展性。在 \@ref(sec:filled-contour) 小节的最后我们曾提到颜色等高图作完之后就不容易再往图中添加图形元素,而这里颜色图只是单幅图形,作完之后仍然可以方便地添加图形元素。
统计数据中有不少是矩阵形式,例如相关系数阵、协方差阵等,我们可以将颜色图应用到这些矩阵形式数据的展示上,尤其是当矩阵行数列数较大时,我们可以借助人眼对颜色的视觉感知从颜色图中迅速找出一定的统计特征来(如很大或者很小的数值),而相比之下,对数据的直接观察并不容易找到规律或特征,因为这种形式下我们必须在脑中对数据两两比较,其速度必然会很慢。
**lattice** 包 [@Sarkar08] 中提供了一个类似的函数 `levelplot()`,展示方法更为灵活,感兴趣的读者请参考函数的帮助文件。
## 矩阵图、矩阵点、矩阵线 {#sec:matplot}
(ref:fig-matplot-s) 用矩阵图画出的一系列正弦曲线
(ref:fig-matplot) 用矩阵图画出的一系列正弦曲线:每条曲线都有不同的点线样式和颜色。
```{r matplot,fig.width=4.8,fig.height=3.5,fig.cap="(ref:fig-matplot)",fig.scap="(ref:fig-matplot-s)"}
sines <- outer(1:20, 1:4, function(x, y) sin(x / 20 * pi * y))
par(mar = c(2, 4, .1, .1))
matplot(sines, type = "b", pch = 21:24, col = 2:5, bg = 2:5)
# 数据矩阵的前 6 行
round(head(sines), 5)
```
矩阵图的名称来自于其参数类型,它可以针对一个矩阵将所有列以曲线的形式表达出来,同一元函数曲线图(\@ref(sec:curve) 小节)一样,它也没有什么特别之处,仅仅是提供了一个便利的封装,我们可以不必调用 `lines()` 等函数依次对矩阵的所有列画曲线。
R 中矩阵图的函数为 `matplot()`,矩阵点的函数为 `matpoints()`,矩阵线的函数为 `matlines()`,它们的用法如下:
```{r matplot-usage}
usage(matplot)
usage(matpoints)
usage(matlines)
```
函数 `matplot()` 为高层作图函数(创建新图形),而后两个函数均为低层作图函数(向现有图形上添加元素)。参数 `x` 和 `y` 为输入的矩阵,做图的方式是用 `x` 的列为横轴方向的变量, `y` 的列为纵轴方向的变量,然后用这些列依次作散点图( `x` 的第一列对 y 的第一列, `x` 的第二列对 `y` 的第二列,依次类推);如果这两个参数有一个缺失,那么 x 将被 `1:nrow(y)` 代替, `y` 被非缺失的参数矩阵代替;注意两个矩阵要么有一个列数为 1,要么列数相等,否则会报错;后面设置颜色、线型等样式的参数 `type` 、 `lty` 、 `lwd` 、 `pch` 、 `col` 、 `cex` 、 `bg` 等在附录 \@ref(cha:tricks) 和第 \@ref(cha:elements) 章已经讲述过多次,此处不再赘述。
图 \@ref(fig:matplot)
展示了一个正弦值矩阵的矩阵图。从图上方的代码中我们可以看到矩阵的前 6 行数值,该例中只给出了参数 `x` 而没有 `y`,所以 `matplot()` 用矩阵 `sines` 的每一列依次和 `1:20` 画曲线。
## 马赛克图 {#sec:mosaicplot}
马赛克图(Mosaic Plots)是展示多维列联表数据的工具。前面我们已经提到过两种展示列联表数据的工具(\@ref(sec:assocplot) 和 \@ref(sec:fourfoldplot) 小节),但它们都只能展示低维列联表,而马赛克图对于列联表的维数没有限制。
马赛克图的表现形式为与频数成比例的矩形块,整幅图形看起来就像是若干块马赛克放置在平面上。马赛克图背后的统计理论是对数线性模型(log-linear model),我们先回顾一个最简单的二维列联表的独立模型。
二维列联表的独立性从概率角度来说就是单元格的频率等于边际频率的乘积:
\begin{equation}
\pi_{ij}=\pi_{i\cdot}\pi_{\cdot j}
(\#eq:independence)
\end{equation}
取对数即得:
\begin{equation}
\mathrm{log}(\pi_{ij})=\mathrm{log}(\pi_{i\cdot})+\mathrm{log}(\pi_{\cdot j})
(\#eq:logindependence)
\end{equation}
根据 $\mu_{ij}=n\pi_{ij}$ 进一步写成频数的形式:
\begin{equation}
\mathrm{log}(\mu_{ij})=\lambda+\lambda_{i}^{r}+\lambda_{j}^{c}
(\#eq:freqindependence)
\end{equation}
$\lambda_{i}^{r}$ 和 $\lambda_{j}^{c}$ 分别表示行效应和列效应,$\lambda$ 为常数。 \@ref(eq:freqindependence) 式就是最普通的对数线性模型,通过计算拟合,我们可以得到行列效应的估计值。对数线性模型在马赛克图中的主要表现是单元格的残差,而单元格的残差可以有三种:似然比残差(离差,deviance)$G^{2}$、Pearson $\chi^{2}$ 残差和 Freeman-Tukey 残差,前两种定义如下式:
\begin{equation}
G^{2}=2\sum n_{ij}\mathrm{log}(\frac{n_{ij}}{\hat{\mu}_{ij}});\;\;\chi^{2}=\sum\frac{\left(n_{ij}-\hat{\mu}_{ij}\right)^{2}}{\hat{\mu}_{ij}}
(\#eq:loglinearres)
\end{equation}
残差反映的是某个单元格拟合的好坏,马赛克图用 5 级颜色表达了残差的大小,后面我们结合具体例子说明。
R 中马赛克图的函数为 `mosaicplot()` ,其用法如下:
```{r mosaicplot-usage}
usage(graphics:::mosaicplot.default)
usage(graphics:::mosaicplot.formula)
```
马赛克图函数是泛型函数,可以直接接受列联表数据或者公式作为参数,这里我们只介绍前一种情况。 `x` 为一个列联表数据(可以用函数 `table()` 生成); `main` 、 `sub` 、 `xlab` 和 `ylab` 分别设定主标题、副标题和坐标轴标题; `sort` 指定展示变量的顺序; `dir` 指定马赛克图的拆分方向(横向拆分或纵向拆分); `type` 给定残差的类型,即如前所述的三种残差。
下面我们结合泰坦尼克号数据 `Titanic` 来说明马赛克图的用法。泰坦尼克号乘客生存情况的原始数据如下:
```{r Titanic-data}
ftable(Titanic)
```
该数据给出了分舱位(一二三等舱和船员舱)、分性别(男女)、分年龄(大人小孩)的生存情况。泰坦尼克号的沉没是一件著名的历史事件,至今仍然有很多人在研究它。我们所关心的问题主要是通过一些比例看出当时救援的侧重性,如:是否头等舱的乘客生还比例最高?"女士和孩子优先" 的原则在各船舱有没有被很好遵守?......
(ref:fig-mosaicplot-s) 泰坦尼克号乘客生存数据马赛克图
(ref:fig-mosaicplot) 泰坦尼克号乘客生存数据马赛克图:按性别、年龄和船舱等级划分
```{r mosaicplot,fig=T,fig.width=5.2,fig.height=5,fig.cap="(ref:fig-mosaicplot)",fig.scap="(ref:fig-mosaicplot-s)"}
par(mar = c(2, 3.5, .1, .1))
mosaicplot(Titanic, shade = TRUE, main = "")
```
图 \@ref(fig:mosaicplot) 以马赛克图的形式将这个 $4\times2\times2\times2$ 的列联表数据展示在了同一张图中,通过矩形块(马赛克)的大小,我们可以清楚看出各舱位、不同性别、年龄的人群的生还状况。例如,对头等舱来说,无论是大人小孩或男女,下方的矩形都比上方的矩形要高(尤其是女性和小孩),这说明头等舱的生还率相对来说都比较高,很可能当时的救援是偏向头等舱的;从年龄来说,头等舱和二等舱中小孩的生存率要远高于大人,但三等舱中小孩的生存率和大人相比差异并不是太显著;但从性别角度来看,各舱位基本上还是将生存机会优先让给女性了,男性的生还率在各舱位来说都相对较低。类似地,我们还可以从图中挖掘出更多的现象,这里不再深入。另外,图中用不同颜色表示出了个单元格的残差大小,其中虚线框表示残差为负数,我们可以清楚看出哪些单元格的拟合欠佳。感兴趣的读者还可以使用 **stats** 包中的 `loglin()` 函数拟合对数线性模型、从统计模型的角度继续分析。
## 散点图矩阵 {#sec:scatterplot-matrix}
散点图矩阵(Scatterplot Matrices)是散点图的高维扩展,它的基本构成是普通散点图,只是将多个变量的两两散点图以矩阵的形式排列起来,就构成了所谓的散点图矩阵,它通常包含 $p\times p$ 个窗格($p$ 为变量个数)。散点图矩阵从一定程度上克服了在平面上展示高维数据的困难,对于我们查看变量之间的两两关系非常有用。
R 中散点图矩阵的函数为 `pairs()` ,其用法如下:
```{r pairs-usage}
usage(pairs.default)
usage(graphics:::pairs.formula)
```
散点图矩阵函数是泛型函数,可以直接接受数据矩阵或者公式作为参数。 `x` 是一个矩阵或数据框,包含了要作散点图的那些变量; `labels` 是变量名称(标签); `panel` 参数给定一个画散点图的函数,这个函数将应用在每一格图形中;有时候我们并不需要统一的散点图函数,这时可以利用 `lower.panel` 和 `upper.panel` 来分别指定上三角窗格和下三角窗格中的作图函数,也就意味着上三角和下三角窗格中的图形(不一定非得是散点图)可以不一样; `diag.panel` 和 `text.panel` 分别指定对角线窗格上的作图函数和添加文本标签的函数; `label.pos` 指定文本标签的位置; `cex.labels` 指定标签的缩放倍数; `font.labels` 指定标签的字体样式; `row1attop` 为逻辑值,指定散点图的第 1 行出现在顶部还是底部(按常规讲,前者是矩阵的形式,后者是图的形式,因为矩阵通常是从上至下、从左至右,而图的坐标是从下至上、从左至右); `gap` 设定窗格之间的间距大小。
图 \@ref(fig:pairs)
是对鸢尾花数据 `iris` 所作的散点图矩阵,注意其中的上三角和下三角作图函数是如何定义的。对角线窗格显示的是自定义的直方图,定义如下:
```{r define-pairs-panel}
# 观察如何使用 hist() 做计算并用 rect() 画图
panel.hist <- function(x, ...) {
h <- hist(x, plot = FALSE)
nB <- length(breaks <- h$breaks)
y <- h$counts / max(h$counts)
rect(breaks[-nB], 0, breaks[-1], y, col = "beige")
}
```
(ref:fig-pairs-s) 鸢尾花数据的散点图矩阵
(ref:fig-pairs) 鸢尾花数据的散点图矩阵:上三角区域为不同样式的点,对应着不同种类的鸢尾花,对角线的直方图展示了花瓣花萼长宽的一维分布,下三角区域用平滑曲线显示了变量之间的关系。
```{r pairs,fig.width=4.8,fig.height=4.8,fig.cap="(ref:fig-pairs)",fig.scap="(ref:fig-pairs-s)"}
idx <- as.integer(iris[["Species"]])
pairs(iris[1:4],
upper.panel = function(x, y, ...) {
points(x, y, pch = c(17, 16, 6)[idx], col = idx)
},
lower.panel = panel.smooth, diag.panel = panel.hist
)
```
我们可以看到,主对角线上用了直方图,从中我们可以看到四个变量各自的分布情况;上三角窗格中用不同样式的点标记出了鸢尾花的不同类型(回顾图 \@ref(fig:point-iris)
);下三角窗格中简化了点的样式,但是利用函数 `panel.smooth()` 添加了一条平滑曲线,对鸢尾花的四个变量两两之间的关系作出了一种非参数概括(散点图平滑技术,参见 @Cleveland79)。
在变量数目较多时,我们不妨将散点图矩阵作为一种探索变量之间相关关系的工具,它比起相关系数矩阵等统计指标来优势在于:散点图矩阵展示了所有原始数据,这样我们可以看到变量之间的任何关系(线性或非线性、离群点),从而避免被单一的统计指标所误导。
## 三维透视图 {#sec:persp}
```{r}
# 代码参见 example(persp)
```
(ref:fig-persp-s) 新西兰 Maunga Whau 火山的三维透视图
(ref:fig-persp) 新西兰 Maunga Whau 火山的三维透视图
```{r persp,fig.height=4,fig.width=7,echo=FALSE,fig.cap="(ref:fig-persp)",fig.scap="(ref:fig-persp-s)"}
z <- volcano
x <- 4 * (1:nrow(z))
y <- 4 * (1:ncol(z))
par(mar = rep(0, 4))
persp(x, y, z,
theta = 150, phi = 30, col = "green3",
scale = FALSE, ltheta = -120, shade = 0.75, border = NA,
box = FALSE
)
```
相比其二维平面图形来说,三维透视图(Perspective Plot)可能在视觉上更具有吸引力。三维透视图的数据基础是网格数据(回顾 \@ref(sec:contour) 小节和图 \@ref(fig:contour-grid)),它将一个矩阵中包含的高度数值用曲面连接起来,便形成了我们所看到的三维透视图。前面等高图一节中我们曾经用到过透视图,参见图 \@ref(fig:persp-pop)
。
R 中透视图的函数为 `persp()` ,其用法如下:
```{r persp-usage}
usage(graphics:::persp.default)
```