7.2 字符串

文本数据存储在字符向量中(或字符数组中,虽然这较少见)。重要的是,字符向量中的每个元素都是字符串,而非单独的字符。在R中,“字符串”是个常用的非正式术语,因为正式的“字符向量元素”读起来相当拗口。

文本的基本单位是字符向量,这意味着大部分字符串处理函数也能用于字符串向量,这与数学运算的向量化方式相同。

7.2.1 创建和打印字符串

如你所见,字符向量可用c函数创建。我们可以用单引号或双引号把字符串引用起来,只要引号之间匹配即可。不过,使用双引号更为标准:

  1. c(
  2. "You should use double quotes most of the time",
  3. 'Single quotes are better for including " inside the string'
  4. )
  5. ## [1] "You should use double quotes most of the time"
  6. ## [2] "Single quotes are better for including \" inside the string"

paste函数能将不同字符串组合在起来。在它传入的参数向量中,每个元素都能自我循环以达到最长的矢量长度,然后字符串就被拼接在一起,中间以空格分开。可以使用参数sep更改分隔符,或使用相关的paste0函数去掉分隔符。所有的字符串被组合后,可使用collapse参数把结果收缩成一个包含所有元素的字符串:

  1. paste(c("red", "yellow"), "lorry")
  2. ## [1] "red lorry" "yellow lorry"
  3. paste(c("red", "yellow"), "lorry", sep = "-")
  4. ## [1] "red-lorry" "yellow-lorry"
  5. paste(c("red", "yellow"), "lorry", collapse = ", ")
  6. ## [1] "red lorry, yellow lorry"
  7. paste0(c("red", "yellow"), "lorry")
  8. ## [1] "redlorry" "yellowlorry"

toString函数是paste的变种,它在打印向量时非常有用。它使用逗号和空格分隔每个元素,且可限制打印的数量。在下例中,width = 40将输出限制为40个字符:

  1. x <- (1:15) ^ 2
  2. toString(x)
  3. ## [1] "1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225"
  4. toString(x, width = 40)
  5. ## [1] "1, 4, 9, 16, 25, 36, 49, 64, 81, 100...."

cat是一个低级函数,工作原理类似于paste,不过格式功能更少。你将很少直接调用它。不过,你应该对它有所了解,因为它是大部分print函数的基础。cat也可接受一个file参数1,将输出写入文件:

1 再学究气点,它接受一个由file函数返回的文件的路径或连接。

  1. cat(c("red", "yellow"), "lorry")
  2. ## red yellow lorry

通常情况下,当字符串打印到控制台时,它们会以双引号括起来。如果对它们使用noquote函数,就可以去掉这些引号。有时,这会使文本更具可读性:

  1. x <- c(
  2. "I", "saw", "a", "saw", "that", "could", "out",
  3. "saw", "any", "other", "saw", "I", "ever", "saw"
  4. )
  5. y <- noquote(x)
  6. x
  7. ## [1] "I" "saw" "a" "saw" "that" "could" "out" "saw"
  8. ## [9] "any" "other" "saw" "I" "ever" "saw"
  9. y
  10. ## [1] I saw a saw that could out saw any other saw
  11. ## [12] I ever saw

7.2.2 格式化数字

有几个函数可用于数字的格式化。formatC可让你使用C语言的格式化风格来指定使用固定型或科学型的格式、小数的位数以及输出的宽度。无论使用哪种选项,输入都应该是numeric类型(包括数组),且输出是character字符向量或数组:

  1. pow <- 1:3
  2. (powers_of_e <- exp(pow))
  3. ## [1] 2.718 7.389 20.086
  4. formatC(powers_of_e)
  5. ## [1] "2.718" "7.389" "20.09"
  6. formatC(powers_of_e, digits = 3) #指定三个数字
  7. ## [1] "2.72" "7.39" "20.1"
  8. formatC(powers_of_e, digits = 3, width = 10) #前面加上一个空格
  9. ## [1] " 2.72" " 7.39" " 20.1"
  10. formatC(powers_of_e, digits = 3, format = "e") #科学格式
  11. ## [1] "2.718e+00" "7.389e+00" "2.009e+01"
  12. formatC(powers_of_e, digits = 3, flag = "+") #前面加上+
  13. ## [1] "+2.72" "+7.39" "+20.1"

R还提供了更通用的C风格的格式化函数sprintf。与sprintf在其他任何语言中的工作方式一样:第一个参数包含字符串或数字变量的占位符,其他参数则将逐个代入这些占位符。不过请记住,R中大部分的数值是浮点值而非整数。

sprint的第一个参数指定了一个格式化字符串,其中包括其他值的占位符。例如:%s代表另一个字符串,%f%e分别代表固定型格式和科学型格式的浮点数,%d表示整数。其他参数的值将替换占位符。与paste函数类似,较短长度的输入将循环自身以匹配最长的输入:

  1. sprintf("%s %d = %f", "Euler's constant to the power", pow, powers_of_e)
  2. ## [1] "Euler's constant to the power 1 = 2.718282"
  3. ## [2] "Euler's constant to the power 2 = 7.389056"
  4. ## [3] "Euler's constant to the power 3 = 20.085537"
  5. sprintf("To three decimal places, e ^ %d = %.3f", pow, powers_of_e)
  6. ## [1] "To three decimal places, e ^ 1 = 2.718"
  7. ## [2] "To three decimal places, e ^ 2 = 7.389"
  8. ## [3] "To three decimal places, e ^ 3 = 20.086"
  9. sprintf("In scientific notation, e ^ %d = %e", pow, powers_of_e)
  10. ## [1] "In scientific notation, e ^ 1 = 2.718282e+00"
  11. ## [2] "In scientific notation, e ^ 2 = 7.389056e+00"
  12. ## [3] "In scientific notation, e ^ 3 = 2.008554e+01"

其他格式化数字的方法有formatprettyNum这两个函数。format提供的格式化字符串的语法稍有不同,与formatC的用法基本类似。而prettyNum则非常适合于格式化那些非常大或非常小的数字:

  1. format(powers_of_e)
  2. ## [1] " 2.718" " 7.389" "20.086"
  3. format(powers_of_e, digits = 3) #至少三个数字
  4. ## [1] " 2.72" " 7.39" "20.09"
  5. format(powers_of_e, digits = 3, trim = TRUE) #去掉多余的0
  6. ## [1] "2.72" "7.39" "20.09"
  7. format(powers_of_e, digits = 3, scientific = TRUE)
  8. ## [1] "2.72e+00" "7.39e+00" "2.01e+01"
  9. prettyNum(
  10. c(1e10, 1e-20),
  11. big.mark = ",",
  12. small.mark = " ",
  13. preserve.width = "individual",
  14. scientific = FALSE
  15. )
  16. ## [1] "10,000,000,000" "0.00000 00000 00000 00001"

7.2.3 特殊字符

有一些特殊的字符可以被包含在字符串中。例如,我们可以通过\t插入一个制表符。在下例中,我们使用cat而非print,因为print执行的额外的转换动作会把制表符\t转换成反斜杠和一个“t.”。cat的参数fill = TRUE使光标在一行结束后移动到下一行:

  1. cat("foo\tbar", fill = TRUE)
  2. ## foo bar

将光标移动到下一行是通过打印换行符\n完成的(这在所有平台上都一样。在R中,不要使用\r\r\n来打印换行符,因为\r会将光标移动到当前行的开始并覆盖你所写的内容):

  1. cat("foo\nbar", fill = TRUE)
  2. ## foo
  3. ## bar

在R的内部代码中,空字符\0用于终止字符串。然而,显式地把它们包含在字符串中是错误的(旧版本R会丢弃字符串中空字符之后的内容):

  1. cat("foo\0bar", fill = TRUE) #这会抛出一个错误

打印反斜杠符时需要连续输入两个反斜杠符,以免被误认为特殊字符。在下例中,输入两个反斜杠,打印时只会看到一个:

  1. cat("foo\\bar", fill = TRUE)
  2. ## foo\bar

如果我们需要在字符串中使用双引号,那么双引号符前必须加一个反斜杠来转义。同样地,如果要在字符串中使用单引号,则单引号需要被转义:

  1. cat("foo\"bar", fill = TRUE)
  2. ## foo"bar
  3. cat('foo\'bar', fill = TRUE)
  4. ## foo'bar

与之相反,如果在被双引号引用的字符串中使用单引号,或在被单引号引用的字符串中使用双引号,则并不需要对其进行转义:

  1. cat("foo'bar", fill = TRUE)
  2. ## foo'bar
  3. cat('foo"bar', fill = TRUE)
  4. ## foo"bar

通过打印报警符\a能让我们的电脑发出提示声(beep),不过alarm函数也能完成此功能且可读性更好。当想要程序在一个耗时很长的分析任务结束后主动通知你(你不在开放式的办公室),这个函数就能派上用场:

  1. cat("\a")
  2. alarm()

7.2.4 更改大小写

使用touppertolower函数能把字符串中的字符全部转换为大写或小写:

  1. toupper("I'm Shouting")
  2. ## [1] "I'M SHOUTING"
  3. tolower("I'm Whispering")
  4. ## [1] "i'm whispering"

7.2.5 截取字符串

有两个函数可用于从字符串中截取子串:substringsubstr。在大多数情况下,你可以随便选一个使用。不过,如果你传入了不同长度的向量参数,它们的行为会略有不同。对substring来说,输出的长度与最长的输入一样;而对substr来说,输出的长度只与第一个输入的相等:

  1. woodchuck <- c(
  2. "How much wood would a woodchuck chuck",
  3. "If a woodchuck could chuck wood?",
  4. "He would chuck, he would, as much as he could",
  5. "And chuck as much wood as a woodchuck would",
  6. "If a woodchuck could chuck wood."
  7. )
  8. substring(woodchuck, 1:6, 10)
  9. ## [1] "How much w" "f a woodc" " would c" " chuck " " woodc"
  10. ## [6] "uch w"
  11. substr(woodchuck, 1:6, 10)
  12. ## [1] "How much w" "f a woodc" " would c" " chuck " " woodc"

7.2.6 分割字符串

paste及其相关函数能把字符串组合在一起。strsplit则正好相反,它在指定的某些点上分割字符串。我们可以把上例中的土拨鼠绕口字符串按空格分开。在下例中,fixed = TRUE意味着split的参数是固定长度的字符串而非正则表达式:

  1. strsplit(woodchuck, " ", fixed = TRUE)
  2. ## [[1]]
  3. ## [1] "How" "much" "wood" "would" "a" "woodchuck"
  4. ## [7] "chuck"
  5. ##
  6. ## [[2]]
  7. ## [1] "If" "a" "woodchuck" "could" "chuck" "wood?"
  8. ##
  9. ## [[3]]
  10. ## [1] "He" "would" "chuck," "he" "would," "as" "much"
  11. ## [8] "as" "he" "could"
  12. ##
  13. ## [[4]]
  14. ## [1] "And" "chuck" "as" "much" "wood" "as"
  15. ## [7] "a" "woodchuck" "would"
  16. ##
  17. ## [[5]]
  18. ## [1] "If" "a" "woodchuck" "could" "chuck" "wood."

请注意,strsplit返回的是列表(而非字符向量或矩阵)。这是因为它的结果可能由不同长度的字符向量组成。当你只传入一个字符串时,这种情况很容易被忽视。请小心!

在我们的例子中,某些词最后的逗号有些烦人。最好的方法是在空格分割符后加一个可选的逗号,使用正则表达式就很容易搞定。?意味着“前面的字符可选”:

  1. strsplit(woodchuck, ",? ")
  2. ## [[1]]
  3. ## [1] "How" "much" "wood" "would" "a" "woodchuck"
  4. ## [7] "chuck"
  5. ##
  6. ## [[2]]
  7. ## [1] "If" "a" "woodchuck" "could" "chuck" "wood?"
  8. ##
  9. ## [[3]]
  10. ## [1] "He" "would" "chuck" "he" "would" "as" "much" "as"
  11. ## [9] "he" "could"
  12. ##
  13. ## [[4]]
  14. ## [1] "And" "chuck" "as" "much" "wood" "as"
  15. ## [7] "a" "woodchuck" "would"
  16. ##
  17. ## [[5]]
  18. ## [1] "If" "a" "woodchuck" "could" "chuck" "wood."

7.2.7 文件路径

R有一个工作目录,默认为文件被读写的地方。我们可以使用getwd查看到它的位置,并使用setwd来改变它:

  1. getwd()
  2. ## [1] "d:/workspace/LearningR"
  3. setwd("c:/windows")
  4. getwd()
  5. ## [1] "c:/windows"

请注意,每个路径的目录部分由正斜杠分隔,即使在Windows下也是这样。为了保持可移植性,在R中你可以始终对路径使用正斜杠。根据操作系统的不同,文件处理函数能够魔术般地把它们自动替换为反斜杠。

你也可以使用双反斜杠来表示Windows的路径,不过正斜杠仍为首选:

  1. "c:\\windows" #记住使用两个斜杠
  2. "\\\\myserver\\mydir" #对于UNC的命名法,需在开始使用四个斜杠

或者,你可以使用file.path来从各个目录中创建文件路径。它会自动地在目录名称之间插入正斜杠。它就像一个更加简单快速的为处理路径而定制的paste函数:

  1. file.path("c:", "Program Files", "R", "R-devel")
  2. ## [1] "c:/Program Files/R/R-devel"
  3. R.home() #同样也是R的安装目录
  4. ## [1] "C:/PROGRA~1/R/R-devel"

路径可以是绝对路径(从驱动器名称或网络共享处开始)或相对目录(相对于当前工作目录)。在后一种情况中,.用于当前目录而..用于父目录。~代表当前用户主目录。path.expand能将相对路径转换为绝对路径:

  1. path.expand(".")
  2. ## [1] "."
  3. path.expand("..")
  4. ## [1] ".."
  5. path.expand("~")
  6. ## [1] "C:\\Users\\richie\\Documents"

basename只返回文件名,而不包括前面的目录位置。与之相反,dirname只返回文件的目录:

  1. file_name <- "C:/Program Files/R/R-devel/bin/x64/RGui.exe" basename(file_name)
  2. ## [1] "RGui.exe"
  3. dirname(file_name)
  4. ## [1] "C:/Program Files/R/R-devel/bin/x64"