-
Notifications
You must be signed in to change notification settings - Fork 28
/
Copy path06-operation.Rmd
2478 lines (1723 loc) · 119 KB
/
06-operation.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
# R Markdown 的操作技巧 {#rmarkdown-operation}
## 表格操作进阶 {#table-advanced}
在日常报告中,表格是展示结果的主要方式之一,例如下表展示了某中学某次考试的学生成绩:
```{r}
grade <- data.frame(
姓名 = c("张三", "李四", "王五"),
语文 = c(89, 90, 85),
数学 = c(93, 97, 91),
英语 = c(92, 85, 97)
)
knitr::kable(grade)
```
读者可以通过 `knitr::kable()` 函数的各项参数调整默认的表格外观,例如 `align` 参数可以调整表格的对齐方式,`caption` 参数可以添加表格的标题,一个改进版如表 \@ref(tab:target-table) 所示:
```{r target-table}
knitr::kable(grade, align='cccc', caption = '考试成绩')
```
除了居中操作以及添加标题外,在制作表格时还会产生各种各样的特定需求,例如合并单元格、添加底色等。本节将系统地介绍在 R Markdown 中生成表格和进一步对其个性化的方法,主要包括下列内容:
- 介绍 `knitr::kable()` 的用法和主要参数;
- 使用 **kableExtra** [@R-kableExtra] 包扩展表格样式
- 提供其它生成表格的 R 包以供读者参考
### 利用函数 `knitr::kable()` 生成复杂的表格 {#table-complex}
在 R Markdown 中,通常使用 **knitr** 包中的函数 `kable()`\index{knitr!kable()} 来快速创建一个表格。`kable()` 可以处理数据框、矩阵等“矩形数据”,快速生成表格,而表格的外观则可以通过修改函数参数来自定义,下面将对这些参数进行介绍:
```{r code=formatR::usage(knitr::kable, output=FALSE), tidy=TRUE, tidy.opts=list(width.cutoff=45), eval=FALSE}
```
#### 表格样式 {#kable-formats}
在大多数情况下,如果只需要制作一个简单表格,`knitr::kable(x)` 就足够了。其中第二个参数 `format` 会根据输出格式自动设置。它可能的取值是 `pipe`(列与列之间由短的竖线分隔的表),`simple` (仅包含横向分割线的简单表格),`latex` (LaTeX 表格),`html` (HTML 表格),和 `rst` (reStructuredText 表格)。为了展示各个取值的不同,这里直接给出了各个取值在不同编程语言中的原始代码。
对于 R Markdown 文档,`kable()` 默认使用 `pipe` 格式的表格,输出结果如下所示:
```{r, echo=FALSE}
# 以文本而非实际表格的形式打印 kable() 结果
kat = function(x, ...) {
if (length(x) > 0) x[1] = gsub('^\n+', '', x[1])
x = gsub('\n\n+', '\n', x)
cat(x, sep = '\n')
}
```
```{r}
knitr::kable(grade)
```
`knitr::kable()` 也可以生成一个(基于 Pandoc 的)简单的表格,或 HMTL、LaTeX 以及 reStructuredText 格式的表格:
```{r comment='', render=kat, class.output='md'}
knitr::kable(grade, 'simple')
```
```{r comment='', render=kat, class.output='html'}
knitr::kable(grade, 'html')
```
```{r comment='', render=kat, class.output='tex'}
knitr::kable(grade, 'latex')
```
```{r comment='', render=kat, class.output='rst'}
knitr::kable(grade, 'rst')
```
需要注意的是,只有 `pipe` 和 `simple` 格式是“可移植”的,也就是说,它们适用于任何输出文档的格式,而其他格式则只适用于特定的输出格式,例如,`format = 'latex'` 只适用于 LaTeX 输出文档。使用特定的表格格式可以带来更多的自主控制能力,但代价是牺牲了可移植性。
如果想要为文档中的所有表格统一设置格式,可以使用选项 `knitr.table.format`。例如若只需要 LaTeX 格式的表格,则可以设置:
```{r, eval=FALSE}
options(knitr.table.format = 'latex')
```
`knitr.table.format` 还可以接受一个函数处理更复杂的条件逻辑。例如,只在输出格式为 LaTeX 时使用 `latex` 格式:
```{r, eval=FALSE}
options(knitr.table.format = function() {
if (knitr::is_latex_output()) 'latex' else 'pipe'
})
```
如果函数返回 `NULL`,**knitr** 将自动决定适当的格式。
#### 修改列名
<!-- https://stackoverflow.com/questions/51432502/replace-column-names-in-kable-r-markdown/51444998#51444998 -->
在一些情况下,在数据框(data frame)中定义的列的名称可能与想要显示给读者的内容不同,需要进行修改。在使用英文时,数据的列名通常不使用空格来分隔单词,而是使用点、下划线以及大小写来进行分隔。而在制作表格时,这样的变量名会显得有些不自然。在中文环境下,虽然空格的问题较少,但也存在变量名过长的情况,在 R 中也往往使用简化的名词或对应的英文简写来代替。在这种情况下,可以使用 `col.names` 参数将列名替换为一个包含新名称的向量,即 `col.names = c(...)`。例如,可以在上文成绩表的列名中提供更多信息:
```{r}
knitr::kable(
grade,
col.names = c(paste0('第1组', colnames(grade)))
)
```
`col.names` 参数可以接受任意的字符向量(不一定是通过 `paste0()` 等函数修改的列名),只要向量的长度等于数据对象的列数即可,例如可以把列名换成英文:
```{r}
knitr::kable(
grade,
col.names = c('Name', 'Chinese', 'Math', 'English')
)
```
#### 指定列的对齐方式
如果想要改变表格中列的对齐方式,可以使用由字符 `l` (left,左对齐)、`c` (center,居中)以及 `r` (right,右对齐)组成的值向量或一个多字符的字符串来进行对齐,即 `kable(..., align = c('c', 'l'))` 和 `kable(..., align = 'cl')` 是等价的。在默认情况下,数字列是右对齐的,其他列是左对齐的。例如可以对成绩表进行调整,使得前两列右对齐,后两列左对齐:
```{r}
knitr::kable(grade, align = 'rrll')
```
而当对齐方式统一时,也可以用一个字母来代替,例如可以把成绩表所有列都居中表示:
```{r}
knitr::kable(grade, align = 'c')
```
#### 添加表格标题 {#kable-caption}
给表格添加标题需要用到 `caption` 参数,如表 \@ref(tab:kable-cap) 所示:
```{r kable-cap}
knitr::kable(grade, caption = '考试成绩')
```
正如本书 \@ref(cross-reference) 节所提到的那样,当一个表格有标题并且以 **bookdown** 来输出格式时,它可以被交叉引用,而在基础的 `html_document` 和 `pdf_document` 中则不行。
#### 调整数字格式
有的时候,表格中的数字位数很长,展示的时候需要缩短,这时可以通过 `digits` 参数(会被传递给 `round()` 函数)来设置最大的小数位数,以及通过 `format.args`(会被传递给 R 中的 `format()` 函数)来设置其他格式化参数。
首先是几个简单的 `round()` 和 `format()` 的例子,从而可以更好地理解之后的 `kable()` 中 `digits` 参数是如何工作的:
```{r, collapse=TRUE}
round(1.234567, 0)
round(1.234567, digits = 1)
round(1.234567, digits = 3)
format(1000, scientific = TRUE)
format(10000.123, big.mark = ',')
```
可以将数字四舍五入并格式化成表格:
```{r, R.options=list(digits = 7)}
d = cbind(
X1 = runif(3),
X2 = 10^c(3, 5, 7),
X3 = rnorm(3, 0, 1000)
)
# 保留最多四位小数
knitr::kable(d, digits = 4)
# 每列分别设置
knitr::kable(d, digits = c(5, 0, 2))
# 不使用科学计数法
knitr::kable(
d,
digits = 3,
format.args = list(scientific = FALSE)
)
# 给 big numbers 添加逗号
knitr::kable(
d,
digits = 3,
format.args = list(big.mark = ',', scientific = FALSE)
)
```
#### 显示缺失值
表中可能存在缺失值,如该学生没有缺考或没有选修某个课。在默认情况下,R 中缺失值(如`NA`)在表格中显示为字符串`NA`,如:
```{r}
grade2 <- data.frame(姓名 = c("张三","李四","王五"),
物理 = c(NA,90,85),
政治 = c(93,97,NA),
计算机 = c(92,NA,97))
knitr::kable(grade2) # 默认显示 NA
```
为了使表格美观,也可以使用其他的值来替换它们,或者通过使用全局 R 选项 `knitr.kable.NA` 来调整显示的内容(例如使 `NA` 对应的单元格为空)。例如,可以将下面第一个表中的 `NA` 单元格设为空,然后在第二个表中显示 `**`:
```{r}
# 用空值代替 NA
opts <- options(knitr.kable.NA = '')
knitr::kable(grade2)
# 用指定字符(**) 代替 NA
options(knitr.kable.NA = '**')
knitr::kable(grade2)
options(opts) # 恢复全局 R 选项
```
#### 转义特殊字符
HTML 和 LaTeX 中都包含了特殊字符,它们不显示为文本,而代表特定的格式,例如 HTML 里的 ` ` 和 LaTeX 里的 `$` 等。当表格文本包含这些特殊字符时,`kable()` 将默认通过参数 `escape = TRUE` 来 转义这些特殊字符,即令这些特殊字符失去其特殊含义,显示文本本身(如 `\beta` 将不会自动显示为 $\beta$)。例如对于 HTML 格式的表格,`>` 将被替换为 `>`;而对于 LaTeX 格式的表格,`_` 将被转义为 `\_` [^escape]。
[^escape]: 细心的读者可以发现,后文例子中, `_` 实际上被转义为了 `\\_`,LaTeX 中大于等于号 `\ge` 也被写为了 `\\ge`,这是因为 R 中 `\` 本身也代表转义,所以在 R 中输入文件地址时要将其替换为 `\\` 或 `/`。
如果需要特殊字符代表的格式,可以用 `escape = FALSE` 禁用转义,但要注意确保特殊字符不会在 LaTeX 或 HTML 中触发语法错误。表 \@ref(tab:latex-math-escape) 展示了转义之后的结果,表 \@ref(tab:latex-math) 则展示了一些包含特殊字符(`$`、`\` 以及 `_`)的 LaTeX 数学表达式:
```{r latex-math-escape}
# 添加数学表达式
g_range <- data.frame(
成绩范围 = c("$\\ge 90$","$\\ge 80$", "$\\ge 70$"),
语文 = c(6,10,20),
数学 = c(3,7,16),
英语 = c(5,15,20)
)
colnames(g_range) <- c("成绩范围", "$Chinese_{Jan}$", "$Math_{Jan}$", "$English_{Jan}$")
knitr::kable(
g_range,
escape = TRUE,
caption = "escape = TRUE 生成的表"
)
```
```{r latex-math}
knitr::kable(
g_range,
escape = FALSE,
caption = "escape = FALSE 生成的表"
)
```
如表 \@ref(tab:latex-math-escape) 所示,如果设置 `escape = TRUE`,特殊字符将被转义或替换。例如,在 LaTeX 格式的表格中,`$` 会被转义为 `\$`、`_` 被转义为 `\_` 以及 `\` 被替换为 `\textbackslash{}`:
```{r, render=kat, comment='', class.output='tex'}
knitr::kable(g_range[,1:2], format = 'latex', escape = TRUE)
```
其他 LaTeX 中常见的特殊字符包括 `#`、`%`、`&`、`{` 以及 `}`;HTML 中常见的特殊字符包括 `&`、`<`、 `>` 以及 `"`。在生成带有 `escape = FALSE` 的表格时,需要格外小心并确保正确地使用了特殊字符。一个常见的错误是在使用 `escape = FALSE` 时,在 LaTeX 表格的列名或标题中包含 `%` 或 `_` 等字符,而没有意识到它们是特殊的字符。
如果想知道 `escape = TRUE` 参数会如何转义特殊字符,可以通过 **knitr** 中两个内部辅助函数 `escape_latex`\index{knitr!escape\_latex()} 和 `escape_html`\index{knitr!escape\_html()} 来分别查询在 LaTeX 和 HTML 格式的表格中的转义结果:。下面是一些例子:
```{r}
knitr:::escape_latex(c('100%', '# 一个观点', '文字_1'))
knitr:::escape_html(c('<address>', 'x = "字符"', 'a & b'))
```
#### 并排多张表格
当 `kable()` 的第一个参数是包含多个数据框的列表时,它会生成多个并排放置的表格。例如,表 \@ref(tab:two-tables) 包含了之前展示过的两张表:
```{r, two-tables, tidy=FALSE}
# 数据对象 grade 和 grade2 由之前的代码块生成
knitr::kable(
list(grade, grade2),
caption = '两张表并排放置',
booktabs = TRUE, valign = 't'
)
```
需要注意的是,此功能仅适用于 HTML 和 PDF 格式的输出。
另外,如果在并排放置各个表的时候,想能够分别自定义它们,可以使用 `kables()`\index{knitr!kables()} 函数(即 `kable()` 的复数形式),并将一个对象为 `kable()` 的列表传递给它。例如,在表 \@ref(tab:kables) 中,可以更改左表中的列名,并将右表中的小数点位数设置为 4:
```{r, kables, tidy=FALSE}
# 数据对象 grade 和 d 由之前的代码块生成
knitr::kables(
list(
# 第一个 kable():修改列名
knitr::kable(
grade,
col.names = c('Name', 'Chinese', 'Math', 'English'),
valign = 't'
),
# 第二个 kable():设置 digits 选项
knitr::kable(d, digits = 4, valign = 't')
),
caption = '由 knitr::kables() 生成的两张表'
)
```
#### 利用 `for` 循环生成多个表 (\*)
结合 `for` 循环与 `kable()` 生成多张表时,有一点需要额外注意。必须在迭代中用 `print()` 显示打印 `kable()` 的结果,并应用块选项 `results = 'asis'`\index{chunk option!results},例如:
````{verbatim}
```{r, results='asis'}
for (i in 1:3) {
print(knitr::kable(grade))
}
```
````
而下面的代码块只会在文档中输出最后一张表:
````{verbatim}
```{r}
for (i in 1:3) {
knitr::kable(grade)
}
```
````
这个问题并不特定于 `kable()`,同时也存在于许多其他的 R 包中。其背后原因较为复杂,对技术细节感兴趣的读者可以参考博文 ["The Ghost Printer behind Top-level R Expressions."](https://yihui.org/en/2017/06/top-level-r-expressions/)
需要生成多个表格时,最好添加一些换行符(`\n`)或 HTML 注释(`<!-- -->`),从而清晰地分隔所有输出的元素,例如:
````{verbatim}
```{r, results='asis'}
for (i in 1:3) {
print(knitr::kable(grade, caption = '标题'))
cat('\n\n<!-- -->\n\n')
}
```
````
如果没有这些分隔符,Pandoc 可能无法检测到某些元素。例如,当一个图片之后面紧跟着一个表格时,这个表格并不会被识别到:
```md
![](logo.png)
姓名 语文 数学 英语
----- ----- ----- -----
张三 89 93 92
李四 90 97 85
王五 85 91 97
```
解决办法是用空行或注释分隔图片和表格,例如:
```md
![](logo.png)
姓名 语文 数学 英语
----- ----- ----- -----
张三 89 93 92
李四 90 97 85
王五 85 91 97
```
或者:
```md
![](logo.png)
<!-- -->
姓名 语文 数学 英语
----- ----- ----- -----
张三 89 93 92
李四 90 97 85
王五 85 91 97
```
#### 自定义 LaTeX 表格 (\*)
如果只需要 LaTeX 格式的输出时,可以在 `kable()` 中使用一些额外的选项。注意在其他类型的输出中(如 HTML),这些选项将被忽略。除非已经设置了全局的表格格式选项(见本书 \@ref(kable-formats) 节),否则必须像本节的例子那样使用 `kable()` 的 `format` 参数,例如:
```{r, eval=FALSE}
knitr::kable(grade, format = 'latex', booktabs = TRUE)
```
带标题的表格(见本书 \@ref(kable-caption) 节)会被放入 `table` 环境中,即:
```latex
\begin{table}
% 表格 (通常为 tabular 环境)
\end{table}
```
不同的环境有不同的默认排版方式,例如 LaTeX 会对 `table` 及 `figure` 等环境采用浮动布局。可以通过 `table.envir` 参数来对环境进行调整:
```{r, render=kat, comment='', class.output='tex'}
knitr::kable(grade, format = 'latex', table.envir = 'Table')
```
表格的位置由参数 `position` 来控制。例如,可以通过 `position = "!b"` 来强制将表格固定到页面的底部:
```{r, render=kat, comment='', class.output='tex'}
knitr::kable(grade, format = 'latex',
table.envir = 'table', position = '!b')
```
当表格有标题时,也可以通过 `caption.short` 参数给它分配一个短的标题,例如:
```{r, eval=FALSE}
knitr::kable(grade, caption = '一个很长很长的标题!',
caption.short = '短标题')
```
短标题将会进入 LaTeX 中 `\caption[]{}` 命令的方括号中,经常在 PDF 输出文档的表格目录中使用(如果不提供短标题,那里则会显示完整的标题)。
`booktabs = TRUE` 可以使用 LaTeX 包 [**booktabs**](https://ctan.org/pkg/booktabs){LaTeX package!booktabs} 进行表格排版。
```{r, render=kat, comment='', class.output='tex'}
knitr::kable(grade, format = 'latex', booktabs = TRUE)
```
需要注意的是,当在 R Markdown 文档中需要额外的 LaTeX 包时(如 **booktabs**),必须在 YAML 中声明这些包(请参阅第 \@ref(pdf-latex) 节了解如何声明):
<!-- pdf content -->
```{asis, echo=knitr::is_latex_output()}
`booktabs = TRUE` 的表格实现了基于 LaTeX 包 `booktabs` 的表格外观。见表 \@ref(tab:booktabs-false) 和表 \@ref(tab:booktabs-true) 。
```
```{r booktabs-false, include=knitr::is_latex_output()}
knitr::kable(grade, format = 'latex',
booktabs = FALSE,
caption = 'booktabs = FALSE 时的表格')
```
```{r booktabs-true, include=knitr::is_latex_output()}
knitr::kable(grade, format = 'latex',
booktabs = TRUE,
caption = 'booktabs = TRUE 时的表格')
```
:::::: {.cols}
::: {.col}
````{verbatim}
```{r booktabs-false, include=knitr::is_latex_output()}
knitr::kable(grade, format = 'latex',
booktabs = FALSE,
caption = 'booktabs = FALSE 时的表格')
```
```{r booktabs-true, include=knitr::is_latex_output()}
knitr::kable(grade, format = 'latex',
booktabs = TRUE,
caption = 'booktabs = TRUE 时的表格')
```
````
:::
::: {.col }
```{r, echo = FALSE, fig.cap = "booktabs 表格样式", out.width = "90%"}
knitr::include_graphics("images/06-booktabs.png")
```
:::
::::::
对于 `booktabs = FALSE`:
- 表的列由垂直线分隔。可以通过 `vline` 参数来删除垂直线,例如 `knitr::kable(grade, vline = "")` (默认值是`vline = "|"`)。也可以将这个选项设置为一个全局的 R 选项,这样就不需要为每个表设置它,例如 `options(knitr.table.vline = "")`。
- 水平线可以通过参数 `toprule`、`midrule`、`linesep` 以及 `bottomrule` 来定义,它们的默认值都是`\hline`。
对于 `booktabs = TRUE`:
- 表格中没有垂直线,但可以通过 `vline` 参数来添加。
- 表格只有标题和底部行有水平线。默认参数值是 `toprule = "\\toprule"`、`midrule = "\\midrule"` 以及 `bottomrule = "\\bottomrule"`。默认情况下,每 5 行加一个行间距(`\addlinespace`),这是由参数 `linesep` 控制的,默认值为 `c("", "", "", "", "\\addlinespace")`。如果想每 1 行加一个 `\addlinespace`,则可以这样做:
```{r, render=kat, comment='', class.output='tex'}
knitr::kable(grade, format = 'latex',
linesep = c('\\addlinespace'),
booktabs = TRUE)
```
如果想删除所有的行间距,可以使用`linesep = ''`。
有的时候,表可能比一页还长。可以使用参数 `longtable = TRUE`,该参数使用 LaTeX 包 [**longtable**](https://ctan.org/pkg/longtable) 将表跨到多个页面。
另外,当表格被包含在 `table` 环境中时(例如,当表有标题时),表格默认居中对齐。如果不想让表格居中,可以使用参数 `centering = FALSE`。
#### 自定义 HTML 表格 (\*)
<!-- https://stackoverflow.com/questions/24254552/knitr-style-table-with-css -->
如果想自定义通过 `knitr::kable(format = "html")` 生成的表,除了前面提到的常见参数外,还有一个额外的参数需要注意:`table.attr`。这个参数允许用户向 `<table>` 标签添加任意属性。例如可以向表格中添加一个类`striped`:
```{r, render=kat, comment='', class.output='html'}
knitr::kable(grade, table.attr = 'class="striped"',
format = "html")
```
然而,类的名称不足以改变表的外观,必须定义 CSS\index{CSS!striped table}[^CSS] 类的规则。例如,要制作奇数行和偶数行有不同颜色的条纹表,可以为偶数行或奇数行添加浅灰色背景:
[^CSS]: 层叠样式表,是一种用来表现 HTML 等文件样式的计算机语言。
```css
.striped tr:nth-child(even) { background: #eee; }
```
上面的 CSS 规则意味着所有 `striped` 类的元素的子元素,且具有偶数行号(`:nth-child(even)`)的行(即 `<tr>` 标签),将它们的背景颜色设置为 `#eee`。
使用一点 CSS 可以使一个普通的 HTML 表看起来好看很多。图 \@ref(fig:striped-table) 是一个 HTML 表格的截图,其中应用了以下 CSS 规则:
```css
table {
margin: auto;
border-top: 1px solid #666;
border-bottom: 1px solid #666;
}
table thead th { border-bottom: 1px solid #ddd; }
th, td { padding: 5px; }
thead, tfoot, tr:nth-child(even) { background: #eee; }
```
```{r, striped-table, fig.cap='利用 HTML 和 CSS 创建的条纹表', echo=FALSE, fig.align='center', out.width='70%'}
knitr::include_graphics('images/striped-table.png', dpi = NA)
```
### 利用 **kableExtra** 美化表格 {#kable-extra}
**kableExtra** 包[@R-kableExtra]\index{R package!kableExtra} 设计的目的为扩展 `knitr::kable()` 生成表格的基本功能(见第 \@ref(table-complex) 节)。由于 `knitr::kable()` 的设计很简单,就像很多其他的 R 包一样,它肯定有很多缺失的功能,而 **kableExtra** 完美地填补了空白,可以配合 `knitr::kable()` 生成更好看的表格。最令人惊讶的是,**kableExtra** 的大多数表格的特性都适用于 HTML 和 PDF 格式,例如,借助 **kableExtra** 包可以绘制如图 \@ref(fig:striped-table) 的条纹表。
一般情况下,**kableExtra** 包可以通过 CRAN 安装,也可以尝试 GitHub 上的开发版本 (https://github.com/haozhu233/kableExtra):
```{r, eval=FALSE}
# 通过 CRAN 安装
install.packages("kableExtra")
# 安装开发版本
remotes::install_github("haozhu233/kableExtra")
```
https://haozhu233.github.io/kableExtra/ 提供了大量的文档,介绍了很多关于如何自定义 `kable()` 的 HTML 或 LaTeX 输出结果的例子。本节只提供几个示例,更多内容可参见该文档。
另外,**kableExtra** 包支持使用管道操作符 `%>%`,可以将 `kable()` 的输出结果连到 **kableExtra** 的样式函数上,例如表 \@ref(tab:striped-table-extra):
```{r, striped-table-extra, tidy=FALSE, warning=F}
library(knitr)
library(kableExtra)
kable(grade, caption = "条纹表") %>%
kable_styling(latex_options = "striped")
```
#### 设定字体尺寸
有的时候,在展示一些表格时,需要设定字体的尺寸,如放大或缩小某些特定问题。**kableExtra**\index{kableExtra!kable\_styling()} 包中的 `kable_styling()` 函数可以帮助用户对整个表进行样式化。例如,可以指定页面上表格的对齐方式、表格的宽度和字体大小。表 \@ref(tab:little-size) 展示了一个使用小字体的例子:
```{r, little-size, tidy=FALSE}
kable(grade, booktabs = TRUE,
caption = "字体较小的表格") %>%
kable_styling(font_size = 8)
```
#### 特定的行或列的样式
有时还需要对表格的行或列的具体样式进行调整,如加粗某行等。函数 `row_spec()`\index{kableExtra!row\_spec()} 和 `column_spec()`\index{kableExtra!column\_spec()} 可分别用于样式化单独的行和列。表 \@ref(tab:style-col-row) 将第一行文字加粗并设为斜体,将第二行添加黑色背景,同时更改字体颜色为白色并旋转,给第三行文字加下划线并更改其字体,并给第四列加删除线:
```{r, style-col-row, tidy=FALSE}
kable(grade, align = 'c', booktabs = TRUE,
caption = "更改特定行或列的样式") %>%
row_spec(1, bold = TRUE, italic = TRUE) %>%
row_spec(2, color = 'white',
background = 'black', angle = 45) %>%
row_spec(3, underline = TRUE, monospace = TRUE) %>%
column_spec(4, strikeout = TRUE)
```
类似地,也可以使用 `cell_spec()` 函数\index{kableExtra!cell\_spec()}来给单个单元格设定样式。
#### 给行或列分组
回想 Excel 里的操作,对单元格进行合并的操作可以给行或列进行分组。在 R Markdown 中,行和列可以分别通过函数 `pack_rows()`\index{kableExtra!pack\_rows()} 和 `add_header_above()`\index{kableExtra!add\_header\_above()} 来进行分组。另外,也可以通过 `collapse_rows()`\index{kableExtra!collapse\_rows()} 来折叠行,这样一个单元格可以跨越多个行。表 \@ref(tab:group-col-row) 展示了一个给标题列分组后的表格:
```{r, group-col-row, tidy=FALSE}
grade3 <- data.frame(姓名 = c("张三","李四","王五"),
物理 = c(90,90,85),
化学 = c(86,92,80),
生物 = c(94,85,90),
政治 = c(93,97,95),
历史 = c(92,84,80),
地理 = c(99,89,95),
计算机 = c(92,95,97),
体育 = c(85,99,95))
kable(grade3, booktabs = TRUE,
caption = "对标题列进行分组") %>%
add_header_above(c(" " = 1, "理科" = 3,
"文科" = 3, "其它" = 2))
```
对于 `add_header_above()` 中的命名向量,其名称是显示在表头中的文本,向量的整数值表示一个名称应该跨越多少列,例如,`"理科" = 3` 表示 `理科` 应该跨越三列。
表 \@ref(tab:pack-rows) 提供了 `pack_rows()` 的示例,其中 `index` 参数的含义类似于之前解释过的 `add_header_above()` 参数:
```{r, pack-rows, tidy=FALSE}
kable(grade3, booktabs = TRUE,
caption = "对行进行折叠") %>%
pack_rows(
index = c("一班" = 1, "二班" = 2)
)
```
#### 按比例缩小 LaTeX 中的表格
有一些特性是 HTML 或 LaTeX 输出格式特有的。例如,横向打印格式只在 LaTeX 中有意义,所以 **kableExtra** 中的 `landscape()` 函数\index{kableExtra!landscape()}只对 LaTeX 格式的输出有效。对于一个比较宽的表格(表 \@ref(tab:no-scale-down)),(表 \@ref(tab:scale-down))展现了如何将表格按比例缩小以适应页面的宽度(否则该表格会太宽):
```{r, no-scale-down, tidy=FALSE}
grade4 <- merge(grade, grade3, by = "姓名")
kable(grade4,
booktabs = TRUE,
caption = "原始表格(太宽)")
```
```{r scale-down, tidy=FALSE}
kable(grade4,
booktabs = TRUE,
caption = "缩小后的表格") %>%
kable_styling(latex_options = "scale_down")
```
注意如果在浏览 HTML 版本的话,表 \@ref(tab:no-scale-down) 和表 \@ref(tab:scale-down) 表格是没有差异的。
### 其它表格包 {#else-table}
还有很多其他的 R 包可以用来生成表格\index{R package!table packages}。本节引入 `kable()` (见第 \@ref(table-complex) 节)和 **kableExtra** (见第 \@ref(kable-extra) 节)的主要原因不是它们比其他包更好,而是因为作者们只熟悉它们,而且它们的功能可以涵盖大部分的日常使用需求。接下来本节将列出一些已知的其它软件包,感兴趣的读者可以去尝试并决定哪一个最适合自己。
- **flextable** [@R-flextable] 和 **huxtable** [@R-huxtable]:**flextable** 和 **huxtable** 支持多种表格输出格式的包。它们都支持 HTML、LaTeX 以及 Office 格式,并且包含最常见的表格特性(例如条件格式化)。更多关于 **flextable** 的信息可参见:https://davidgohel.github.io/flextable/,**huxtable** 的说明文档则在:https://hughjonesd.github.io/huxtable/。
- **gt** [@R-gt]:这个 R 包允许用户将表格的不同部分组合在一起,例如表头(标题和副标题)、列标签、表格主体、行组标签以及表格的脚注,从而组成一个完整的表格,其中有些部分是可选择性添加的。还可以格式化数字,并为单元格添加背景阴影。目前 **gt** 主要支持 HTML 输出。^[如果需要支持其他输出格式,如 LaTeX 和 Word,**gtsummary** 包[@R-gtsummary]已经做了一些基于 **gt** 的扩展,可参见:https://github.com/ddsjoberg/gtsummary.]更多关于 **gt** 的信息可参见:https://gt.rstudio.com。
- **formattable** [@R-formattable]:这个 R 包提供了一些格式化数字的工具函数(如 `percent()` 和 `accounting()`),以及对列进行样式化的函数(如格式化文本,用背景阴影或颜色条注释数字,或在单元格中添加图标等等)。和 **gt** 相同,**formattable** 包也主要支持HTML格式。更多信息可参见GitHub项目:https://github.com/renkun-ken/formattable/。
- **DT** [@R-DT]:它只支持 HTML 格式。**DT** 构建在 JavaScript 库 **DataTables** 之上,它可以将静态表转换为HTML页面上的交互式表。使用者可以对表进行排序、搜索和分页。**DT** 还支持格式化单元格,与 Shiny 一起构建交互式应用程序,并包含了大量的 **DataTables** 扩展(例如,可以将表格导出到Excel,或交互式重新排列表格的列)。更多信息可参见:https://github.com/rstudio/DT/。
- **reactable** [@R-reactable]:与 **DT** 类似,这个包也基于 JavaScript 库创建交互式表。它在某些方面比 **DT** 更好(比如行分组和聚合操作,以及嵌入HTML小部件),但 **reactable** 并不包含 **DT** 全部的特性。更多信息可参见:https://glin.github.io/reactable/。
- **rhandsontable**[@R-rhandsontable]:这个包也类似于 **DT**,并且和 Excel 比较像(例如,可以直接在表中编辑数据)。更多信息可参见:https://jrowen.github.io/rhandsontable/。
- **pixiedust** [@R-pixiedust]:这个包通过 **broom** 包[@R-broom]来为为模型结果(如线性模型)创建表格,它支持 Markdown、HTML 以及 LaTeX 输出格式。更多信息可参见:https://github.com/nutterb/pixiedust/。
- **stargazer** [@R-stargazer]:格式化回归模型和汇总统计表。更多信息可参见:https://cran.r-project.org/package=stargazer/。
- **xtable** [@R-xtable];这个包可能是最早的创建表格的包,其第一次发布是在 2000 年。它同时支持LaTeX和HTML格式。该软件包可在 CRAN 上访问:https://cran.r-project.org/package=xtable/。
还有一些其它生成表格的包,这里不再进一步介绍,只是在这里列出它们,以供感兴趣者参考:**tables** [@R-tables]、**pander** [@R-pander]、**ztable** [@R-ztable] 以及 **condformat** [@R-condformat]。
## 块选项 {#chunk-options}
本节和接下来的第 \@ref(output-hook)、\@ref(other-trick) 两节进一步展示一些与 **knitr** 代码块选项相关的编程技巧。
R Markdown 支持超过 50 个块选项\index{chunk option} 用于调整 **knitr** 处理代码块的方式,完整列表可参阅在线文档<https://yihui.org/knitr/options/>`r if (knitr::is_latex_output()) ',或本书附录\\@ref(full-options)'`。
下面的几节展示了在单个代码块中使用块选项的例子,希望为全部代码块统一设置块选项的读者可以参考第 \@ref(code-block-and-inline) 节。
### 在块选项中使用变量 {#chunk-variable}
通常情况下,块选项中会使用常数(如:`fig.width = 6`),但有些时候仅使用常数无法满足需求,例如应展现的图像大小可能来源于其它代码块的结果,而非一成不变,每次根据结果来手动调整就会费时又费力。
块选项支持任意或简单或复杂的 R 表达式。一种特殊的情况是将代码块中定义的表达式\index{chunk option!variable values}传递给一个块选项。例如,为了满足图像大小变化的需求,可以在文档的一个代码块中定义关于图像宽度的变量,然后在其他代码块中使用它:
````{verbatim}
```{r}
my_width <- 7
```
```{r, fig.width=my_width}
plot(cars)
```
````
另外,块选项中也可以使用更为复杂的函数,例如可以使用 `if-else` 语句\index{chunk option!with if else logic}来调整图片大小:
````{verbatim}
```{r}
fig_small <- FALSE # 输出更大的图片需要改为 TRUE
width_small <- 4
width_large <- 8
```
```{r, fig.width=if (fig_small) width_small else width_large}
plot(cars)
```
````
不仅如此,还可以只在所需要的包可使用时才运行一个代码块(`eval=FALSE` 意味着不运行该代码):
````{verbatim}
```{r, eval=require('leaflet')}
library(leaflet)
leaflet() %>% addTiles()
```
````
需要注意的是,`require('package')` 只有当这个包已安装且可使用时才会返回 `TRUE`,否则会返回 `FALSE`。
### 允许错误 {#chunk-error}
默认情况下,如果文档中的某一个代码块运行错误,R Markdown 将终止当前编译并跳过剩余代码块。但瑕疵掩不住美玉,一次报错不妨碍整体的质量;失败是成功之母,一次报错更是珍贵的学习机会。出于种种原因,用户希望在代码块报错的时候显示错误并继续运行,可以使用块选项 `error = TRUE`\index{chunk option!error},例如:
````{verbatim}
```{r, error=TRUE}
1 + "a"
```
````
这样在编译 R Markdown 文档后,将在输出文档中看到如下的错误消息:
```{r, error=TRUE, echo=FALSE, comment=''}
1 + "a"
```
### 控制输出 {#chunk-output}
默认情况下,**knitr** 会显示代码块的所有可能输出,包括源代码、提示信息(message)、警告(warning)、文本输出和图像输出等,但有时处于种种目的,只需要部分输出。本节将详细介绍如何控制各类结果的输出。
#### 隐藏源代码、提示信息、警告、文本输出或图像输出 {#hide-one}
用户可以隐藏源代码和各类输出结果,包括信息、警告、文本和图像,可以使用相应的块选项来单独隐藏它们:
`r import_example('knitr-hide.Rmd')`
一个常见的需要隐藏的输出元素是某些包的加载信息。例如,在运行 `library(tidyverse)` 或 `library(ggplot2)` 时,可能会看到一些正在加载的 message。这类 message 也可以通过块选项 `message = FALSE` 来隐藏。
另外,还可以通过索引来有选择地显示或隐藏这些元素。下面的示例只输出了源代码的第四个和第五个表达式(注意,一个注释会被算作一个表达式)、前两个 message 以及第二个和第三个 warning:
````{verbatim}
```{r, echo=c(4, 5), message=c(1, 2), warning=2:3}
# 一种生成服从N(0,1)分布的随机数的方法
x <- qnorm(runif(10))
# 在实践中还可以使用
x <- rnorm(10)
x
for (i in 1:5) message('这是 message ', i)
for (i in 1:5) warning('这是 warning ', i)
```
````
这些选项也支持负索引,例如,`echo = -2`\index{chunk option!echo} 表示在输出中排除源代码的第二个表达式。
类似地,可以通过使用 `fig.keep` 选项\index{chunk option!fig.keep} 来选择显示或隐藏哪些图。例如,`fig.keep = 1:2` 意味着保留前两幅图。这个选项有一些快捷的方式,如 `fig.keep = "first"` 将只保留第一幅图、`fig.keep = "last"` 只保留最后的图以及 `fig.keep = "none"` 将丢弃所有的图。需要注意的是,`fig.keep = "none"` 和 `fig.show = "hide"` 这两个选项是不同的,后者将生成图像文件,只是会隐藏它们,而前者则根本不会生成图像文件。
对于 `html_document` 输出中的源代码块,如果不想完全省略它们(`echo = FALSE`),可以参考\@ref(html-code-folding)节,来学习如何在页面上折叠它们,并允许报告用户通过单击展开按钮来展开它们。
#### 隐藏代码块的所有输出 {#hide-all}
有的时候,可能希望正常执行代码块但隐藏源代码和所有输出。与使用第 \@ref(hide-one) 节中提到的单独选项不同,`include = FALSE`\index{chunk option!include} 可以同时隐藏源代码和输出:
````{verbatim}
```{r, include=FALSE}
# 任意 R 代码
```
````
注意,设置了 `include=FALSE` 的代码块仍会被运行,但读者将看不到任何的源代码、提示信息、警告、文本输出或图像输出等。
#### 将文本输出压缩到源代码块中 {#opts-collapse}
R Markdown 默认把源代码和输出放置在不同的容器中。如果想把输出和源代码放在一起,可以使用块选项 `collapse = TRUE`\index{chunk option!collapse},例如:
```{r, test-collapse, collapse=TRUE}
1 + 1
1:10
```
#### 原样输出文本为 Markdown (\*) {#results-asis}
默认情况下,代码块的文本输出与源代码的样式一致。如果希望把代码块的输出直接作为后续的 Markdown 文本,可以通过添加块选项 `results = 'asis'`\index{chunk option!results} 来解决。这个选项告诉 **knitr** 不要将文本输出逐字包装成代码块,而是“原样”对待它。当想要从 R 代码动态生成内容时,这一点特别有用。例如,可以使用选项 `results = 'asis'` 从以下代码块生成 `iris` 数据的列名列表:
```{r, iris-asis, results='asis'}
cat(paste0('- `', names(iris), '`'), sep = '\n')
```
连字符(`-`)是 Markdown 中用于生成无序列表的语法,其中反引号是可选的。若没有设置 `results = 'asis'` 选项,则上述代码块的输出为:
```{r, iris-asis}
```
下面是一个完整的例子,展示了如何在 `for` 循环中为 `mtcars` 数据的所有列生成节标题、段落和图:
`r import_example('generate-content.Rmd')`
需要注意的是,上述示例代码中添加了过多的换行符(`\n`),从而将不同的元素在 Markdown 中清晰地分开。在不同的元素之间使用过多的换行符是无害的,但是如果换行符不够,就会产生问题。例如,下面的 Markdown 文本就会产生很多的歧义:
```md
# 这是一个标题吗?
这是一个段落还是标题的一部分呢?
![这张图片呢?](foo.png)
# 这行又是什么?
```
如果产生了更多的空行(可以由`cat('\n')`生成),则可以避免歧义:
```md
# 这是一个标题!
这绝对是个段落。
![这是一张图。](foo.png)
# 这是另一个标题
```
`cat()` 函数不是唯一可以生成文本输出的函数,另一个常用的函数是 `print()`。但需要注意的是,`print()` 经常被 _隐式_ 调用来打印对象,这就是为什么在 R 控制台(console)中输入一个对象或值后会看到输出。例如,当在 R 控制台中输入 `1:5` 并按下 `Enter` 键时,会看到输出,这是因为 R 实际上隐式地调用了 `print(1:5)`。经常令人感到困惑的是,不能在表达式(例如 `for` 循环)中直接生成输出,而如果在 R 控制台上输入对象或值,它们将被正确打印出来。这个主题非常技术性,具体细节可以参看博文["The Ghost Printer behind Top-level R Expressions"](https://yihui.org/en/2017/06/top-level-r-expressions/)。如果对技术细节不感兴趣,只要记住这条规则即可:如果在 `for` 循环中没有看到输出,那么可能应该使用 `print()` 函数来显式地打印对象。
### 自动格式化源代码 {#chunk-reformat}
多人协作中,维持统一的代码风格往往是令人头疼的问题,对空格、换行、括号等元素的不同使用习惯因人而异。即便是在单人工作中,时刻手动调整代码风格仍是件苦功夫。为了解决这个问题,R Markdown 提供了一个块选项 `tidy` 可以自动美化代码块的格式。
当设置块选项 `tidy = TRUE`\index{chunk option!tidy} 时, R 的源代码将被 **formatR**\index{R package!formatR} 包 [@R-formatR]的 `tidy_source()` 函数重新格式化。`tidy_source()` 可以在几个方面重新格式化代码,比如在大多数操作符周围添加空格、适当缩进代码以及用 `<-` 替换赋值操作符 `=` 。块选项 `tidy.opts`\index{chunk option!tidy.opts} 可以是传递给 `formatR::tidy_source()` 的一个参数列表,例如:
`r import_example('tidy-opts.Rmd')`
输出结果为:
```{r, child='examples/tidy-opts.Rmd', results='hide'}
```
块选项 `out.width` 可以控制输出的宽度。如果想进一步控制源代码的宽度,则可以在设置 `tidy = TRUE` 时使用 `width.cutoff` 参数,例如:
`r import_example('tidy-width.Rmd')`
输出结果为:
```{r, child='examples/tidy-width.Rmd', results='hide'}
```
更多可能的参数可以参见帮助页 `?formatR::tidy_source`,也可以浏览 https://yihui.org/formatR/ 来了解这个函数的示例和局限性。
另外,还可以通过设定块选项 `tidy = 'styler'` 来使用 **styler**\index{R package!styler} 包[@R-styler]重新格式化 R 代码,其中的格式化函数为 `styler::style_text()`。**styler** 包比 **formatR** 具有更丰富的特性。例如,它可以对齐函数参数并使用管道操作符 `%>%`。块选项 `tidy.opts`\index{chunk option!tidy.opts} 同样可以用于将附加参数传递给 `styler::style_text()`,例如:
````{verbatim}
```{r, tidy='styler', tidy.opts=list(strict=FALSE)}
# 对齐赋值操作符
a <- 1 # 一个变量
abc <- 2 # 另一个变量
```
````
R Markdown 默认设置 `tidy = FALSE`, 不会自动格式化代码。
### 调整文本输出中的前导符号 {#chunk-leading}
默认情况下,R Markdown 会在文本输出的前面插入两个符号 `##`,那么这个符号能不能更改或者干脆删除呢?第 \@ref(results-asis) 节介绍了通过在添加 `results = 'asis'` 选项来生成 Markdown 形式输出的方法。但如果只是想调整前导符号,而仍然想将文本放在单独的块中,则可以通过块选项 `comment`\index{chunk option!comment} 来实现。如果要删除 `##`,可以使用空字符串,例如:
````{verbatim}
```{r, comment=""}
1:100
```
````
当然,可以使用任何其他的字符值,例如,`comment = "#>"`。为什么 `comment` 选项默认为 `##` 呢?这是因为 `#` 表示 R 中的注释,无论代码块的输出是什么,读者都可以直接复制整段代码并在 R 中运行。
### 为代码块添加属性 (\*) {#chunk-attr}
第 \@ref(html-css-list) 节展示了一些基于块选项 `class.source`\index{chunk option!class.source} 样式化代码块的示例。**knitr** 提供了更多类似的选项,如 `class.output`\index{chunk option!class.output}、`class.message`\index{chunk option!class.message}、`class.warning`\index{chunk option!class.warning} 以及 `class.error`\index{chunk option!class.error}。另外的一个常见类是 `.numberLines`,可以用于为代码块添加行号。演示如下:
:::::: {.cols data-latex=""}
::: {.col data-latex="{0.45\textwidth}"}
````{verbatim}
```{r, class.source='numberLines', eval=FALSE}
if (TRUE) {
x <- 1:10
x + 1
}
```
````
:::
::: {.col data-latex="{0.45\textwidth}" style="width: 100%;"}
```{r, class.source='numberLines', eval=FALSE}
if (TRUE) {
x <- 1:10
x + 1
}
```
:::
::::::
注意,为了让 `html_document` 格式显示行号,需要在 YAML 元数据中使用任意非默认的高亮选项,例如 `hightlight: pygment`。
作为一个技术性拓展,块选项 `class.*` 只是 `attr.*` 的特殊情况,例如,`class.source = 'numberLines'` 等价于 `attr.source = '.numberLines'`(注意前面的点 `.`),但 `attr.source`\index{chunk option!attr.source} 可以取任意的属性值,例如 `attr.source = c('.numberLines', 'startFrom="11"')`。同理,可以用类似的 方法设置 `attr.output`\index{chunk option!attr.output}、`attr.message`\index{chunk option!attr.message}、`attr.warning`\index{chunk option!attr.warning} 以及 `attr.error`\index{chunk option!attr.error} 等属性。
第 \@ref(html-css-list) 节介绍了 R Markdown 内置的 `"bg-primary"`,`"bg-success"`,`"bg-info"`,`"bg-warning"` 和 `"bg-danger"` 等样式类。本节的知识可以让读者定义自己的样式类,例如,可以在 CSS 中定义一个 `myClass` 类及其外观,然后通过 `class.source = 'myclass'` 来为代码块添加定义好的样式。例如:
````{verbatim}
定义 myClass 类:
```{css, echo = FALSE}
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@500&display=swap');
.myClass {
background-color: #f1f3f5;
padding: 0.4rem;
border-radius: 0.25rem;
border-left: 3px solid #31bae9;
border-bottom: none;
box-shadow: 0px 8px 5px -8px rgba(0,0,0,0.75);
}
.myClass code {
font-family: 'Fira Code', monospace;
}
```
应用 myClass 类:
```{r, class.source = "myClass", eval = FALSE}
state.x77 |>
as.data.frame() |>
tibble::rownames_to_column("State") |>
dplyr::group_by(State) |>
dplyr::summarise(avg_murder_rate = mean(Murder))
```
````
属性 `attr` 也支持自定义行内 CSS 样式,例如
:::::: {.cols data-latex=""}
::: {.col data-latex="{0.45\textwidth}"}
````{verbatim}
```{r, attr.output='style="background: pink;"'}
if (TRUE) {
x <- 1:10
x + 1
}
```
````
:::
::: {.col data-latex="{0.45\textwidth}" style="width: 100%;"}
```{r, attr.output='style="background: pink;"'}
1 + 1
```
:::
::::::
基于类和属性的选项对 HTML 输出格式是通用的,部分情况下也支持其他格式。这些属性需要被 Pandoc 或第三方的包支持。例如 Pandoc 中,`.numberLines` 属性适用于 HTML 和 LaTeX 输出;第三方的包则通常需要通过一个 Lua 过滤器,感兴趣者请参见:https://bookdown.org/yihui/rmarkdown-cookbook/lua-filters.html。
更多类与属性的例子参见第 \@ref(scrollable-output) 节。
### 同一张图的多种图像输出格式 {#chunk-multiplot}
在大多数情况下,报告可能只需要一种图像格式,例如 `png` 或 `tiff`。但有些时候,一些报告需要提交多种格式的图像,这就需要进一步了解图像格式选择的原理。
图像格式由块选项 `dev`\index{chunk option!dev}\index{figure!graphical device} 控制,即渲染图像的图像设备,这个选项的取值可以为设备名(即输出格式)的向量,例如:
````{verbatim}
```{r, dev=c('png', 'pdf', 'svg', 'tiff')}
plot(cars)
```
````
输出文档中只会呈现第一种格式的图像,但其它格式的图像也会被生成。这里需要注意的是,在默认情况下,图像文件在输出文档呈现后会立即被删除,如要保存这些文件,请参见\@ref(keep-plot)节。
### 图像的后期加工 (\*) {#chunk-process}
`fig.process`\index{chunk option!fig.process}\index{figure!post-processing} 可用于对代码块生成的图片做后期加工。`fig.process` 接受一个函数,其中的第一个参数是当前图片的路径,最终应该返回加工后的图片的路径。该函数还可以有第二个可选参数 `options`,代表一个由当前块选项组成的列表,这个列表可以用于定制图片处理的细节。
下面的例子展示了如何使用一个功能强大的 **magick**包 [@R-magick]\index{R package!magick} 来在图像中添加 R logo。首先,定义一个函数 `add_logo()`:
```{r, eval = FALSE}
install.packages("magick")
```
```{r}
add_logo = function(path, options) {
# 代码块中创建的图像
img = magick::image_read(path)
# R logo
logo = file.path(R.home("doc"), "html", "logo.jpg")
logo = magick::image_read(logo)
# 默认的重力方向为西北,用户可以通过代码块来改变它
# option magick.gravity
if (is.null(g <- options$magick.gravity)) g = 'northwest'
# 在图像中添加 logo
img = magick::image_composite(img, logo, gravity = g)
# 写入新的图像中
magick::image_write(img, path)