第 6 章 字符串操作

字符和字符串类型的数据值得单独拿出来讲,不仅因为内容多,而且比较难,应用范围最广,特别是面对文本类型的数据时,几乎是避不开的!R 的前身是 S,S 的前身是一些 Fortran 和 C 子程序,最早在贝尔实验室是用于文本分析领域,因此在 R 基础包中提供了丰富的字符串处理函数,你可以在R控制台中执行如下一行命令查看

help.search(keyword = "character", package = "base")

本章主要介绍 R 内置的字符串操作函数

6.1 字符数统计

nchar 函数统计字符串向量中每个元素的字符个数,注意与函数length 的差别,它统计向量中元素的个数,即向量的长度。

nchar(c("Hello", "world", "!"))
#> [1] 5 5 1
R.version.string
#> [1] "R version 3.6.1 (2019-07-05)"
nchar(R.version.string)
#> [1] 28
deparse(base::mean)
#> [1] "function (x, ...) "  "UseMethod(\"mean\")"
nchar(deparse(base::mean))
#> [1] 18 17

一些特殊的情况

nchar("")
#> [1] 0
nchar(NULL)
#> integer(0)
nchar(0)
#> [1] 1
pi
#> [1] 3.141593
nchar(pi)
#> [1] 16
exp(1)
#> [1] 2.718282
nchar(exp(1))
#> [1] 16
nchar(NA)
#> [1] NA

6.2 字符串翻译

tolower 将字符串或字符串向量中含有的大写字母全都转化为小写, toupper 函数正好与之相反.

tolower(c("HELLO", "Hello, R", "hello"))
#> [1] "hello"    "hello, r" "hello"
toupper(c("HELLO", "Hello, R", "hello"))
#> [1] "HELLO"    "HELLO, R" "HELLO"

6.3 字符串连接

paste 函数设置参数 sep 作为连接符,设置参数 collapse 可以将字符串拼接后连成一个字符串

paste("A", "B", sep = "")
#> [1] "AB"
paste(c("A", "B", "C"), 1:3, sep = "-")
#> [1] "A-1" "B-2" "C-3"
paste(c("A", "B", "C"), 1:3, sep = "-", collapse = ";")
#> [1] "A-1;B-2;C-3"

paste0 相当于 sep 设为空,没有连接符

paste0("A", "B")
#> [1] "AB"
paste0(c("A", "B", "C"), 1:3)
#> [1] "A1" "B2" "C3"
paste0(c("A", "B", "C"), 1:3, collapse = ";")
#> [1] "A1;B2;C3"

6.4 字符串拆分

strsplit(x, split, fixed = FALSE, perl = FALSE, useBytes = FALSE)

strsplit 函数用于字符串拆分,参数 x 是被拆分的字符串向量,其每个元素都会被拆分,而参数 split 表示拆分的位置,可以用正则表达式来描述位置,拆分的结果是一个列表。

参数 fixed 默认设置 fixed = FALSE 表示正则表达式匹配,而 fixed = TRUE 表示正则表达式的精确匹配或者按文本字符的字面意思匹配,即按普通文本匹配。我们知道按普通文本匹配速度快。

当启用 perl = TRUE 时,由 PCRE_use_JIT 控制细节。perl 参数的设置与 Perl 软件版本有关,如果正则表达式很长,除了正确设置正则表达式,使用 perl = TRUE 可以提高运算速度

参数 useBytes 设置是否按照逐个字节地进行匹配,默认设置为 FALSE,即按照字符而不是字节进行匹配

x <- c(as = "asfef", qu = "qwerty", "yuiop[", "b", "stuff.blah.yech")
# 按字母 e 拆分字符串向量 x
strsplit(x, "e")
#> $as
#> [1] "asf" "f"  
#> 
#> $qu
#> [1] "qw"  "rty"
#> 
#> [[3]]
#> [1] "yuiop["
#> 
#> [[4]]
#> [1] "b"
#> 
#> [[5]]
#> [1] "stuff.blah.y" "ch"

参数 split 支持通过正则表达式的方式指明拆分位置

# 默认将点号 . 看作一个正则表达式,它是一个元字符,匹配任意字符
strsplit("a.b.c", ".")
#> [[1]]
#> [1] "" "" "" "" ""
# 这才是按点号拆分
strsplit("a.b.c", ".", fixed = TRUE)
#> [[1]]
#> [1] "a" "b" "c"
# 或者
strsplit("a.b.c", "[.]")
#> [[1]]
#> [1] "a" "b" "c"
# 或者转义点号,去掉元字符的特殊意义
strsplit("a.b.c", "\\.")
#> [[1]]
#> [1] "a" "b" "c"

这里介绍一个将字符串逆序的函数 str_rev

str_rev <- function(x)
        sapply(lapply(strsplit(x, NULL), rev), paste, collapse = "")
str_rev(c("abc", "Statistics"))
#> [1] "cba"        "scitsitatS"

为了加深理解,再举几个例子

# 最后一个空字符没有产生
strsplit(paste(c("", "a", ""), collapse="#"), split="#")
#> [[1]]
#> [1] ""  "a"
# 空字符只有有定义的时候才会产生
strsplit("", " ")
#> [[1]]
#> character(0)
strsplit(" ", " ")
#> [[1]]
#> [1] ""

6.5 字符串匹配

agrepagrepl 函数做近似(模糊)匹配 (Approximate Matching or Fuzzy Matching) ,对于匹配,考虑到参数 pattern 在参数 x 中匹配时,允许参数值x存在最小可能的插入、删除和替换,这种修改叫做Levenshtein 编辑距离,max.distance 控制其细节

agrep(pattern, x, max.distance = 0.1, costs = NULL,
      ignore.case = FALSE, value = FALSE, fixed = TRUE,
      useBytes = FALSE)

agrepl(pattern, x, max.distance = 0.1, costs = NULL,
       ignore.case = FALSE, fixed = TRUE, useBytes = FALSE)

agrep 函数返回 pattern 在 x 中匹配到的一个位置向量,agrepl 返回一个逻辑向量,这一点类似 grepgrepl 这对函数,下面举例子说明

agrep("lasy", "1 lazy 2")
#> [1] 1
# sub = 0 表示匹配时不考虑替换
agrep("lasy", c(" 1 lazy 2", "1 lasy 2"), max = list(sub = 0))
#> [1] 2
# 默认设置下,匹配时区分大小写
agrep("laysy", c("1 lazy", "1", "1 LAZY"), max = 2)
#> [1] 1
# 返回匹配到值,而不是位置下标,类似 grep(..., value = TRUE) 的返回值
agrep("laysy", c("1 lazy", "1", "1 LAZY"), max = 2, value = TRUE)
#> [1] "1 lazy"
# 不区分大小写
agrep("laysy", c("1 lazy", "1", "1 LAZY"), max = 2, ignore.case = TRUE)
#> [1] 1 3
startsWith(x, prefix)
  endsWith(x, suffix)

startsWithendsWith 函数用来匹配字符串的前缀和后缀,返回值是一个逻辑向量,参数 prefix 和 suffix 不要包含特殊的正则表达式字符,如点号.,举例子

# 字符串向量
search()
#>  [1] ".GlobalEnv"        "package:knitr"     "package:stats"    
#>  [4] "package:graphics"  "package:grDevices" "package:utils"    
#>  [7] "package:datasets"  "package:methods"   "Autoloads"        
#> [10] "package:base"
# 匹配以 package: 开头的字符串
startsWith(search(), "package:")
#>  [1] FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE
# 或者
grepl("^package:", search())
#>  [1] FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE

当前目录下,列出扩展名为 .Rmd 的文件

# list.files(path = ".", pattern = "\\.Rmd$")
# 而不是 endsWith(list.files(), "\\.Rmd")
endsWith(list.files(), ".Rmd")
#>  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [12] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE
#> [23]  TRUE  TRUE FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE FALSE FALSE
#> [34] FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE
#> [45]  TRUE FALSE  TRUE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE
#> [56] FALSE  TRUE  TRUE FALSE  TRUE  TRUE FALSE FALSE  TRUE FALSE FALSE
#> [67] FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
#> [78] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
#> [89] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
# 或者
grepl("\\.Rmd$", list.files())
#>  [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
#> [12] FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE
#> [23]  TRUE  TRUE FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE FALSE FALSE
#> [34] FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE
#> [45]  TRUE FALSE  TRUE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE
#> [56] FALSE  TRUE  TRUE FALSE  TRUE  TRUE FALSE FALSE  TRUE FALSE FALSE
#> [67] FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
#> [78] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE
#> [89] FALSE FALSE FALSE  TRUE FALSE FALSE FALSE

部分匹配(Partial String Matching)

match(x, table, nomatch = NA_integer_, incomparables = NULL)
x %in% table
charmatch(x, table, nomatch = NA_integer_)
pmatch(x, table, nomatch = NA_integer_, duplicates.ok = FALSE)

这几个 match 函数的返回值都是一个向量,每个元素是参数x在参数table中第一次匹配到的位置,charmatchpmatch(x, table, nomatch = NA_integer_, duplicates.ok = TRUE) 类似,所以 pmatch 在默认 duplicates.ok = FALSE 的情况下,若x在第二个参数table中有多次匹配就会返回 NA,因此,实际上 pmatch 只允许在第二个参数中匹配一次

match("xx", c("abc", "xx", "xxx", "xx"))
#> [1] 2
1:10 %in% c(1,3,5,9)
#>  [1]  TRUE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE

# charmatch 就比较奇怪,规则太多
charmatch("", "")                             # returns 1
#> [1] 1
# 多个精确匹配到,或者多个部分匹配到,则返回 0
charmatch("m",   c("mean", "median", "mode", "quantile")) # returns 0
#> [1] 0
# med 只在table参数值的第二个位置部分匹配到,所以返回2
charmatch("med", c("mean", "median", "mode", "quantile")) # returns 2
#> [1] 2

charmatch("xx", "xx")
#> [1] 1
charmatch("xx", "xxa")
#> [1] 1
charmatch("xx", "axx")
#> [1] NA
# 注意比较与 charmatch 的不同
pmatch("", "")                             # returns NA
#> [1] NA
pmatch("m",   c("mean", "median", "mode")) # returns NA
#> [1] NA
pmatch("med", c("mean", "median", "mode")) # returns 2
#> [1] 2

6.7 字符串替换

chartr 支持正则表达式的替换,chartr 是对应字符的替换操作

x <- "MiXeD cAsE 123"
# 将字符 iXs 替换为 why
chartr("iXs", "why", x)
#> [1] "MwheD cAyE 123"
# 将字符串 a-cX 中的字符挨个对应地替换为 D-Fw
chartr("a-cX", "D-Fw", x)
#> [1] "MiweD FAsE 123"

两个 *sub 函数的区别:sub 替换第一次匹配到的结果,gsub 替换所有匹配的结果

sub(" .*", "", extSoftVersion()["PCRE"])
#>   PCRE 
#> "8.39"

参数 replacement 的值是正则表达式,其包含反向引用的用法, \\1 即引用表达式 ([ab])

gsub(pattern =  "([ab])", replacement = "\\1_\\1_", x = "abc and ABC")
#> [1] "a_a_b_b_c a_a_nd ABC"

6.8 字符串提取

substr(x, start, stop)
substring(text, first, last = 1000000L)

substrsubstring 函数通过位置进行字符串的拆分和提取,它们本身不使用正则表达式,结合其他正则表达式函数regexpr, gregexprregexec,可以很方便地从大量文本中提取所需的信息。作用类似之前提到的 regmatches 函数

参数设置基本相同

  • x/text 是要拆分的字符串向量
  • start/first 截取的起始位置向量
  • stop/last 截取的终止位置向量

返回值有差别

  • substr 返回的字串个数等于第一个参数 x 的长度
  • substring 返回字串个数等于三个参数中最长向量长度,短向量循环使用。
x <- "123456789"
substr(x, c(2, 4), c(4, 5, 8))
#> [1] "234"
substring(x, c(2, 4), c(4, 5, 8))
#> [1] "234"     "45"      "2345678"

substr("abcdef", 2, 4)
#> [1] "bcd"
substring("abcdef", 1:6, 1:6)
#> [1] "a" "b" "c" "d" "e" "f"

因为 x 的向量长度为1,所以 substr 获得的结果只有1个字串,即第2和第3个参数向量只用了第一个组合:起始位置2,终止位置4。而 substring 的语句三个参数中最长的向量为 c(4,5,8),执行时按短向量循环使用的规则第一个参数事实上就是c(x,x,x),第二个参数就成了c(2,4,2),最终截取的字串起始位置组合为:2-4, 4-5和2-8。

x <- c("123456789", "abcdefghijklmnopq")
substr(x, c(2, 4), c(4, 5, 8))
#> [1] "234" "de"
substring(x, c(2, 4), c(4, 5, 8))
#> [1] "234"     "de"      "2345678"

6.9 其它操作

6.9.1 strwrap

strwrap(x, width = 0.9 * getOption("width"), indent = 0,
        exdent = 0, prefix = "", simplify = TRUE, initial = prefix)

该函数把一个字符串当成一个段落的文字(不管字符串中是否有换行符),按照段落的格式(缩进和长度)和断字方式进行分行,每一行是结果中的一个字符串。

# 读取一段文本
x <- paste(readLines(file.path(R.home("doc"), "THANKS")), collapse = "\n")
## 将文本拆分为段落,且移除前三段
x <- unlist(strsplit(x, "\n[ \t\n]*\n"))[-(1:3)]
# 每一段换两行
x <- paste(x, collapse = "\n\n")
# 每一行的宽度设定为60个字符
writeLines(strwrap(x, width = 60))
#> J. D. Beasley, David J. Best, Richard Brent, Kevin Buhr,
#> Michael A. Covington, Bill Cleveland, Robert Cleveland,, G.
#> W. Cran, C. G. Ding, Ulrich Drepper, Paul Eggert, J. O.
#> Evans, David M. Gay, H. Frick, G. W. Hill, Richard H.
#> Jones, Eric Grosse, Shelby Haberman, Bruno Haible, John
#> Hartigan, Andrew Harvey, Trevor Hastie, Min Long Lam,
#> George Marsaglia, K. J. Martin, Gordon Matzigkeit, C. R.
#> Mckenzie, Jean McRae, Cyrus Mehta, Fionn Murtagh, John C.
#> Nash, Finbarr O'Sullivan, R. E. Odeh, William Patefield,
#> Nitin Patel, Alan Richardson, D. E. Roberts, Patrick
#> Royston, Russell Lenth, Ming-Jen Shyu, Richard C.
#> Singleton, S. G. Springer, Supoj Sutanthavibul, Irma
#> Terpenning, G. E. Thomas, Rob Tibshirani, Wai Wan Tsang,
#> Berwin Turlach, Gary V. Vaughan, Michael Wichura, Jingbo
#> Wang, M. A. Wong, and the Free Software Foundation (for
#> autoconf code and utilities). See also files under
#> src/extras.
#> 
#> Many more, too numerous to mention here, have contributed
#> by sending bug reports and suggesting various improvements.
....
# 每一段的段首缩进5个字符
writeLines(strwrap(x, width = 60, indent = 5))
#>      J. D. Beasley, David J. Best, Richard Brent, Kevin
#> Buhr, Michael A. Covington, Bill Cleveland, Robert
#> Cleveland,, G. W. Cran, C. G. Ding, Ulrich Drepper, Paul
#> Eggert, J. O. Evans, David M. Gay, H. Frick, G. W. Hill,
#> Richard H. Jones, Eric Grosse, Shelby Haberman, Bruno
#> Haible, John Hartigan, Andrew Harvey, Trevor Hastie, Min
#> Long Lam, George Marsaglia, K. J. Martin, Gordon
#> Matzigkeit, C. R. Mckenzie, Jean McRae, Cyrus Mehta, Fionn
#> Murtagh, John C. Nash, Finbarr O'Sullivan, R. E. Odeh,
#> William Patefield, Nitin Patel, Alan Richardson, D. E.
#> Roberts, Patrick Royston, Russell Lenth, Ming-Jen Shyu,
#> Richard C. Singleton, S. G. Springer, Supoj Sutanthavibul,
#> Irma Terpenning, G. E. Thomas, Rob Tibshirani, Wai Wan
#> Tsang, Berwin Turlach, Gary V. Vaughan, Michael Wichura,
#> Jingbo Wang, M. A. Wong, and the Free Software Foundation
#> (for autoconf code and utilities). See also files under
#> src/extras.
#> 
#>      Many more, too numerous to mention here, have
#> contributed by sending bug reports and suggesting various
....
# 除了段首,每一段的余下诸行都缩进5个字符
writeLines(strwrap(x, width = 60, exdent = 5))
#> J. D. Beasley, David J. Best, Richard Brent, Kevin Buhr,
#>      Michael A. Covington, Bill Cleveland, Robert
#>      Cleveland,, G. W. Cran, C. G. Ding, Ulrich Drepper,
#>      Paul Eggert, J. O. Evans, David M. Gay, H. Frick, G.
#>      W. Hill, Richard H. Jones, Eric Grosse, Shelby
#>      Haberman, Bruno Haible, John Hartigan, Andrew Harvey,
#>      Trevor Hastie, Min Long Lam, George Marsaglia, K. J.
#>      Martin, Gordon Matzigkeit, C. R. Mckenzie, Jean McRae,
#>      Cyrus Mehta, Fionn Murtagh, John C. Nash, Finbarr
#>      O'Sullivan, R. E. Odeh, William Patefield, Nitin
#>      Patel, Alan Richardson, D. E. Roberts, Patrick
#>      Royston, Russell Lenth, Ming-Jen Shyu, Richard C.
#>      Singleton, S. G. Springer, Supoj Sutanthavibul, Irma
#>      Terpenning, G. E. Thomas, Rob Tibshirani, Wai Wan
#>      Tsang, Berwin Turlach, Gary V. Vaughan, Michael
#>      Wichura, Jingbo Wang, M. A. Wong, and the Free
#>      Software Foundation (for autoconf code and utilities).
#>      See also files under src/extras.
#> 
#> Many more, too numerous to mention here, have contributed
....
# 在输出的每一行前面添加前缀
writeLines(strwrap(x, prefix = "THANKS> "))
#> THANKS> J. D. Beasley, David J. Best, Richard Brent, Kevin Buhr,
#> THANKS> Michael A. Covington, Bill Cleveland, Robert Cleveland,,
#> THANKS> G. W. Cran, C. G. Ding, Ulrich Drepper, Paul Eggert, J. O.
#> THANKS> Evans, David M. Gay, H. Frick, G. W. Hill, Richard H.
#> THANKS> Jones, Eric Grosse, Shelby Haberman, Bruno Haible, John
#> THANKS> Hartigan, Andrew Harvey, Trevor Hastie, Min Long Lam,
#> THANKS> George Marsaglia, K. J. Martin, Gordon Matzigkeit, C. R.
#> THANKS> Mckenzie, Jean McRae, Cyrus Mehta, Fionn Murtagh, John C.
#> THANKS> Nash, Finbarr O'Sullivan, R. E. Odeh, William Patefield,
#> THANKS> Nitin Patel, Alan Richardson, D. E. Roberts, Patrick
#> THANKS> Royston, Russell Lenth, Ming-Jen Shyu, Richard C.
#> THANKS> Singleton, S. G. Springer, Supoj Sutanthavibul, Irma
#> THANKS> Terpenning, G. E. Thomas, Rob Tibshirani, Wai Wan Tsang,
#> THANKS> Berwin Turlach, Gary V. Vaughan, Michael Wichura, Jingbo
#> THANKS> Wang, M. A. Wong, and the Free Software Foundation (for
#> THANKS> autoconf code and utilities). See also files under
#> THANKS> src/extras.
#> THANKS> 
#> THANKS> Many more, too numerous to mention here, have contributed
#> THANKS> by sending bug reports and suggesting various
....

再举一个烧脑的例子

x <- paste(sapply(
  sample(10, 100, replace = TRUE), # 从1-10个数字中有放回的随机抽取100个数
  function(x) substring("aaaaaaaaaa", 1, x)
), collapse = " ")
sapply(
  10:40,
  function(m)
    c(target = m, actual = max(nchar(strwrap(x, m))))
)
#>        [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10] [,11] [,12]
#> target   10   11   12   13   14   15   16   17   18    19    20    21
#> actual   10   10   11   12   13   14   15   16   17    18    19    20
#>        [,13] [,14] [,15] [,16] [,17] [,18] [,19] [,20] [,21] [,22] [,23]
#> target    22    23    24    25    26    27    28    29    30    31    32
#> actual    21    22    23    24    25    26    27    28    29    30    31
#>        [,24] [,25] [,26] [,27] [,28] [,29] [,30] [,31]
#> target    33    34    35    36    37    38    39    40
#> actual    32    33    34    35    36    36    38    39

6.9.2 strtrim

strtrim(x, width)

strtrim 函数将字符串x修剪到特定的显示宽度,返回的字符串向量的长度等于字符串向量 x 的长度,如果 width 的参数值(它是一个整型向量)的长度小于 x 的,就循环补齐。

strtrim(c("abcdef", "abcdef", "abcdef"), c(1, 5, 10))
#> [1] "a"      "abcde"  "abcdef"

6.9.3 strrep

strrep(x, times)

以给定的次数重复字符串向量中每个元素的个数,并连接字符串的各个副本

strrep("ABC", 2)
#> [1] "ABCABC"
strrep(c("A", "B", "C"), 1 : 3)
#> [1] "A"   "BB"  "CCC"
# 创建一个字符串向量,指定每个元素中空格的数量
strrep(" ", 1 : 5)
#> [1] " "     "  "    "   "   "    "  "     "

6.9.4 trimws

trimws(x, which = c("both", "left", "right"), whitespace = "[ \t\r\n]")

trimws 函数用于移除字符串中的空格,这种空格可以来自制表符、回车符和换行符,位置可以位于字符串的开头或者结尾,which 参数指定空格的大致位置。举例如下

x <- "  Some text. "
x
#> [1] "  Some text. "
trimws(x)
#> [1] "Some text."
trimws(x, "l")
#> [1] "Some text. "
trimws(x, "r")
#> [1] "  Some text."

6.10 运行环境

xfun::session_info()
#> R version 3.6.1 (2019-07-05)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Debian GNU/Linux 10 (buster)
#> 
#> Locale:
#>   LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>   LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>   LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
#>   LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>   LC_ADDRESS=C               LC_TELEPHONE=C            
#>   LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#> 
#> Package version:
#>   base64enc_0.1.3  bookdown_0.12    codetools_0.2-16 compiler_3.6.1  
#>   curl_4.0         digest_0.6.20    evaluate_0.14    glue_1.3.1      
#>   graphics_3.6.1   grDevices_3.6.1  highr_0.8        htmltools_0.3.6 
#>   jsonlite_1.6     knitr_1.23       magrittr_1.5     markdown_1.0    
#>   methods_3.6.1    mime_0.7         Rcpp_1.0.2       rmarkdown_1.14  
#>   stats_3.6.1      stringi_1.4.3    stringr_1.4.0    tinytex_0.14    
#>   tools_3.6.1      utils_3.6.1      xfun_0.8         yaml_2.2.0