跳转到内容

R 编程/文本处理

来自维基教科书,开放的书籍,开放的世界

此页面包含处理 R 中字符串所需的所有资料。即使您只需要执行一些简单的任务,有关正则表达式的部分可能对理解页面其余内容也很有用。

此页面可能对您有所帮助,可以:

  • 进行统计文本分析。
  • 从无格式的文本文件中收集数据。
  • 处理字符变量。

在本页面中,我们将学习如何读取文本文件以及如何使用 R 函数处理字符。处理字符的函数有两种,分别是简单函数和正则表达式。许多函数是标准 R **base** 包的一部分。

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

但是,对于所有用户来说,它们的名称和语法并不直观。Hadley Wickham 开发了 **stringr** 包,该包定义了具有类似行为的函数,但它们的名称更容易记住,语法也更系统化[1]

  • 关键字:文本挖掘自然语言处理
  • 请参阅 CRAN 上有关自然语言处理的任务视图[2]
  • 另请参见以下包 **tm**、**tau**、**languageR**、**scrapeR**。


读取和写入文本文件

[edit | edit source]

**R** 可以使用 readLines()scan() 读取任何文本文件。可以使用 readLines() 指定导入文本文件的编码。文本文件的整个内容可以读取到 R 对象中(例如,字符向量)。scan() 更加灵活。可以在第二个参数中指定预期的数据类型(例如,字符(0) 用于字符串)。

text <- readLines("file.txt",encoding="UTF-8")
scan("file.txt", character(0)) # separate each word
scan("file.txt", character(0), quote = NULL) # get rid of quotes
scan("file.txt", character(0), sep = ".") # separate each sentence
scan("file.txt", character(0), sep = "\n") # separate each line

我们可以使用 cat()writeLines() 将 R 对象的内容写入文本文件。默认情况下,cat() 在写入文本文件时会连接向量。您可以通过添加选项 sep="\n"fill=TRUE 来更改它。默认编码取决于您的计算机。

cat(text,file="file.txt",sep="\n")
writeLines(text, con = "file.txt", sep = "\n", useBytes = FALSE)

在读取文本文件之前,您可以查看其属性。nlines()(**parser** 包)和 countLines()(**R.utils** 包)计算文件中的行数。count.chars()(**parser** 包)计算文件中每行的字节数和字符数。您也可以使用 file.show() 显示文本文件。

字符编码

[edit | edit source]

R 提供了处理各种编码方案集的函数。如果您处理使用其他操作系统创建的文本文件,尤其是在语言不是英语并且包含许多重音和特定字符的情况下,这很有用。例如,Linux 中的标准编码方案是“UTF-8”,而 Windows 中的标准编码方案是“Latin1”。Encoding() 函数返回字符串的编码。iconv() 与 unix 命令 iconv 相似,并转换编码。

  • iconvlist() 提供计算机上可用的编码方案列表。
  • readLines()scan()file.show() 也具有编码选项。
  • is.utf8()(**tau**)测试编码是否为“utf8”。
  • is.locale()(**tau**)测试编码是否与计算机上的默认编码相同。
  • translate()(**tau**)将编码转换为当前区域设置。
  • fromUTF8()(**descr**)的通用性低于 iconv()
  • utf8ToInt()(**base**)

示例

[edit | edit source]

以下示例在 Windows 下运行。因此,默认编码为“latin1”。

> texte <- "Hé hé"
> Encoding(texte)
[1] "latin1"
> texte2 <-  iconv(texte,"latin1","UTF-8")
> Encoding(texte2)
[1] "UTF-8"

正则表达式

[edit | edit source]

正则表达式是字符串集中的一种特定模式。例如,可以有以下模式:2 位数字、2 个字母和 4 位数字。**R** 提供了处理正则表达式的强大函数。**R** 中使用两种类型的正则表达式[3]

  • 扩展正则表达式,由 ‘perl = FALSE’(默认值)使用,
  • Perl 风格的正则表达式,由 ‘perl = TRUE’ 使用。

还有一个名为 ‘fixed = TRUE’ 的选项,可以将其视为字面正则表达式。fixed()(**stringr**)等效于标准正则表达式函数中的 fixed=TRUE。默认情况下,这些函数区分大小写。可以通过指定选项 ignore.case = TRUE 来更改此设置。

如果您不是正则表达式专家,那么您可能会发现 glob2rx() 很有用。此函数会为特定(“glob”或“通配符”)模式提供一些正则表达式建议

> glob2rx("abc.*")
[1] "^abc\\."

R 中使用正则表达式的函数

[edit | edit source]
  • sub()gsub()str_replace()(**stringr**)对字符串进行一些替换。
  • grep()str_extract()(**stringr**)提取一些值
  • grepl()str_detect()(**stringr**)检测模式是否存在。
  • 另请参见 splitByPattern()(**R.utils**)
  • 另请参见 **gsubfn** 包中的 gsubfn()

扩展正则表达式(默认值)

[edit | edit source]
  • "." 代表任何字符。
  • "[ABC]" 表示 A、B 或 C。
  • "[A-Z]" 表示 A 到 Z 之间的任何大写字母。
  • "[0-9]" 表示 0 到 9 之间的任何数字。

以下是元字符 ‘$ * + . ? [ ] ^ { } | ( ) \’ 的列表。如果您需要使用其中一个字符,请在其前面加上双反斜杠。

以下是正则表达式的一些类别:用于数字

  • ‘[:digit:]’ 数字:‘0 1 2 3 4 5 6 7 8 9’

用于字母

  • ‘[:alpha:]’ 字母字符:‘[:lower:]’‘[:upper:]’
  • ‘[:upper:]’ 大写字母。
  • ‘[:lower:]’ 小写字母。

请注意,字母字符集包含诸如 é è ê 之类的重音,这些重音在法语等一些语言中非常常见。因此,它比 "[A-Za-z]" 更通用,后者不包含带重音的字母。

用于其他字符

  • ‘[:punct:]’ 标点符号:‘! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~’
  • ‘[:space:]’ 空格字符:制表符、换行符、垂直制表符、换页符、回车符和空格。
  • ‘[:blank:]’ 空白字符:空格和制表符。
  • ‘[:cntrl:]’ 控制字符。

其他类别的组合 

  • [:alnum:] 字母数字字符:‘[:alpha:]’‘[:digit:]’
  • ‘[:graph:]’ 图形字符:‘[:alnum:]’‘[:punct:]’
  • ‘[:print:]’ 可打印字符:‘[:alnum:]’‘[:punct:]’ 和空格。
  • ‘[:xdigit:]’ 十六进制数字:‘0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f’

你可以在正则表达式后添加以下字符来量化重复次数 

  • ‘?’ 前一项是可选的,最多匹配一次。
  • ‘*’ 前一项将被匹配零次或多次。
  • ‘+’ 前一项将被匹配一次或多次。
  • ‘{n}’ 前一项被精确匹配 'n' 次。
  • ‘{n,}’ 前一项被匹配 'n' 次或更多次。
  • ‘{n,m}’ 前一项至少被匹配 'n' 次,但不超过 'm' 次。
  • ^ 强制正则表达式位于字符串的开头
  • $ 强制正则表达式位于字符串的结尾

如果你想了解更多,请查看以下两个帮助文件 

>?regexp # gives some general explanations
>?grep # help file for grep(),regexpr(),sub(),gsub(),etc

Perl 风格的正则表达式

[编辑 | 编辑源代码]

也可以使用“Perl 风格”的正则表达式。你只需要使用 perl=TRUE 选项。

如果你想删除字符串中的空格字符,可以使用 \\s Perl 宏。

sub('\\s', '',x, perl = TRUE)

字符串连接

[编辑 | 编辑源代码]
  • paste() 连接字符串。
  • str_c() (stringr) 执行类似的任务。
  • cat() 打印和连接字符串。
> paste("toto","tata",sep=' ')
[1] "toto tata"
> paste("toto","tata",sep=",")
[1] "toto,tata"
> str_c("toto","tata",sep=",")
[1] "toto,tata"
> x <- c("a","b","c")
> paste(x,collapse=" ")
[1] "a b c"
> str_c(x, collapse = " ")
[1] "a b c"
> cat(c("a","b","c"), sep = "+")
a+b+c

拆分字符串

[编辑 | 编辑源代码]
  • strsplit() : 根据字符向量 'x' 中与子字符串 'split' 的匹配结果,将 'x' 的元素拆分为子字符串。
  • 另见 str_split() (stringr)。
> unlist(strsplit("a.b.c", "\\."))
[1] "a" "b" "c"
  • tokenize() (tau) 将字符串拆分为标记。
> tokenize("abc defghk")
[1] "abc"    " "      "defghk"

统计字符串中字符的数量

[编辑 | 编辑源代码]
  • nchar() 给出字符串的长度。注意,对于非 ASCII 编码,存在多种测量长度的方法。
  • 另见 str_length() (stringr)
> nchar("abcdef")
[1] 6
> nchar(NA)
[1] NA
> nchar("René")
[1] 4
> nchar("René", type = "bytes")
[1] 5

检测子字符串的存在

[编辑 | 编辑源代码]

检测字符串中的模式 ?

[编辑 | 编辑源代码]
  • grepl() 返回一个逻辑表达式 (TRUE 或 FALSE)。
  • str_detect() (stringr) 执行类似的任务。
> string <- "23 mai 2000"
> string2 <- "1 mai 2000"
> regexp <- "([[:digit:]]{2}) ([[:alpha:]]+) ([[:digit:]]{4})"
> grepl(pattern = regexp, x = string)
[1] TRUE
> str_detect(string, regexp)
[1] TRUE
> grepl(pattern = regexp, x = string2)
[1] FALSE

第一个为真,第二个为假,因为第一个数字中只有一个数字。

统计字符串中每个模式出现的次数 ?

[编辑 | 编辑源代码]
  • textcnt() (tau) 统计文本中每个模式或每个词出现的次数。
> string <- "blabla 23 mai 2000 blabla 18 mai 2004"
> textcnt(string,n=1L,method="string")
blabla    mai 
     2      2 
attr(,"class")
[1] "textcnt"

提取字符串中子字符串或模式的位置

[编辑 | 编辑源代码]

提取子字符串的位置 ?

[编辑 | 编辑源代码]
  • cpos() (cwhmisc) 返回子字符串在字符串中的位置。
  • substring.location() (cwhmisc) 执行相同的任务,但返回第一个和最后一个位置。
 
> cpos("abcdefghijklmnopqrstuvwxyz","p",start=1)
[1] 16
> substring.location("abcdefghijklmnopqrstuvwxyz","def")
$first
[1] 4

$last
[1] 6

提取字符串中模式的位置 ?

[编辑 | 编辑源代码]
  • regexpr() 返回正则表达式的起始位置。str_locate() (stringr) 执行相同的任务。gregexpr() 类似于 regexpr(),但返回每个匹配项的起始位置。str_locate_all() (stringr) 执行相同的任务。
> regexp <- "([[:digit:]]{2}) ([[:alpha:]]+) ([[:digit:]]{4})"
> string <- "blabla 23 mai 2000 blabla 18 mai 2004"
> regexpr(pattern = regexp, text = string)
[1] 8
attr(,"match.length")
[1] 11
> gregexpr(pattern = regexp, text = string)
[[1]]
[1]  8 27
attr(,"match.length")
[1] 11 11
> str_locate(string,regexp)
     start end
[1,]     8  18
> str_locate_all(string,regexp)
[[1]]
     start end
[1,]     8  18
[2,]    27  37

从字符串中提取子字符串

[编辑 | 编辑源代码]

提取固定宽度子字符串 ?

[编辑 | 编辑源代码]
  • substr() 获取子字符串。
  • str_sub() (stringr) 类似。
> substr("simple text",1,3)
[1] "sim"
> str_sub("simple text",1,3)
[1] "sim"

提取字符串中的第一个词 ?

[编辑 | 编辑源代码]
  • first.word() Hmisc 包中字符串或表达式的第一个词
> first.word("abc def ghk")
[1] "abc"

提取字符串中的模式 ?

[编辑 | 编辑源代码]
  • 如果 value=T,则 grep() 返回正则表达式的值,如果 value=F,则返回其位置。
> grep(pattern = regexp, x = string , value = T) 
[1] "23 mai 2000"
> grep(pattern = regexp, x = string2 , value = T) 
character(0)
> grep(pattern = regexp, x = string , value = F) 
[1] 1
> grep(pattern = regexp, x = string2 , value = F) 
integer(0)
  • str_extract()str_extract_all()str_match()str_match_all() (stringr) 和 m() (caroline 包) 类似于 grep()str_extract()str_extract_all() 返回一个向量。str_match()str_match_all() 返回一个矩阵,而 m() 返回一个数据框。
> library("stringr")
> regexp <- "([[:digit:]]{2}) ([[:alpha:]]+) ([[:digit:]]{4})"
> string <- "blabla 23 mai 2000 blabla 18 mai 2004"
> str_extract(string,regexp)
[1] "23 mai 2000"
> str_extract_all(string,regexp)
[[1]]
[1] "23 mai 2000" "18 mai 2004"

> str_match(string,regexp)
     [,1]          [,2] [,3]  [,4]  
[1,] "23 mai 2000" "23" "mai" "2000"
> str_match_all(string,regexp)
[[1]]
     [,1]          [,2] [,3]  [,4]  
[1,] "23 mai 2000" "23" "mai" "2000"
[2,] "18 mai 2004" "18" "mai" "2004"
> library("caroline")
> m(pattern = regexp, vect = string, names = c("day","month","year"), types = rep("character",3))
  day month year
1  18   mai 2004
  • 命名捕获正则表达式可用于在正则表达式中定义列名(这也用于记录正则表达式)。通过 devtools::install_github("tdhock/namedCapture") 安装 namedCapture 包以使用 str_match_all_named()。它使用基本函数 gregexpr(perl=TRUE) 来解析 Perl 兼容正则表达式,并返回一个包含带列名的匹配矩阵的列表
> named.regexp <- paste0(
+   "(?<day>[[:digit:]]{2})",
+   " ",
+   "(?<month>[[:alpha:]]+)",
+   " ",
+   "(?<year>[[:digit:]]{4})")
> namedCapture::str_match_all_named(string, named.regexp)
[[1]]
     day  month year  
[1,] "23" "mai" "2000"
[2,] "18" "mai" "2004"

在字符串中进行一些替换

[编辑 | 编辑源代码]

在字符串中替换模式

[编辑 | 编辑源代码]
  • sub()进行替换。
  • gsub()类似于sub(),但它替换模式的所有出现,而sub()只替换第一次出现。
  • str_replace() (stringr) 类似于 sub,str_replace_all() (stringr) 类似于 gsub。

在下面的例子中,我们有一个法语日期。正则表达式模式如下:2 位数字,一个空格,一些字母,一个空格,4 位数字。我们使用[[:digit:]]{2}表达式捕获 2 位数字,使用[[:alpha:]]+捕获字母,使用[[:digit:]]{4}捕获 4 位数字。这三个子字符串中的每一个都被括号包围。第一个子字符串存储在"\\1"中,第二个存储在"\\2"中,第三个存储在"\\3"中。

string <- "23 mai 2000"
regexp <- "([[:digit:]]{2}) ([[:alpha:]]+) ([[:digit:]]{4})"
sub(pattern = regexp, replacement = "\\1", x = string) # returns the first part of the regular expression
sub(pattern = regexp, replacement = "\\2", x = string) # returns the second part
sub(pattern = regexp, replacement = "\\3", x = string) # returns the third part

在下面的例子中,我们比较了sub()gsub()的结果。第一个删除了第一个空格,而第二个删除了文本中的所有空格。

> text <- "abc def ghk"
> sub(pattern = " ", replacement = "",  x = text)
[1] "abcdef ghk"
> gsub(pattern = " ", replacement = "",  x = text)
[1] "abcdefghk"

替换字符串中的字符?

[编辑 | 编辑源代码]
  • chartr()替换表达式中的字符。它代表“字符转换”。
  • replacechar() (cwhmisc) 执行相同的工作...
  • 以及str_replace_all() (stringr)。
> chartr(old="a",new="o",x="baba")
[1] "bobo"
> chartr(old="ab",new="ot",x="baba")
[1] "toto"
> replacechar("abc.def.ghi.jkl",".","_")
[1] "abc_def_ghi_jkl"
> str_replace_all("abc.def.ghi.jkl","\\.","_")
[1] "abc_def_ghi_jkl"

将字母转换为小写或大写

[编辑 | 编辑源代码]
  • tolower()将大写字母转换为小写。
  • toupper()将小写字母转换为大写。
  • capitalize() (Hmisc) 将字符串的第一个字母大写
  • 另请参阅 cwhmisc 包中的cap()capitalize()lower()lowerize()CapLeading()
> tolower("ABCdef")
[1] "abcdef"
> toupper("ABCdef")
[1] "ABCDEF"
> capitalize("abcdef")
[1] "Abcdef"

用某些字符填充字符串

[编辑 | 编辑源代码]
  • padding() (cwhmisc) 用某些字符填充字符串以适应给定的长度。另请参阅 str_pad() (stringr)。
> library("cwhmisc")
> padding("abc",10," ","center") # adds blanks such that the length of the string is 10.
[1] "   abc    "
> str_pad("abc",width=10,side="center", pad = "+")
[1] "+++abc++++"
> str_pad(c("1","11","111","1111"),3,side="left",pad="0") 
[1] "001"  "011"  "111"  "1111"

请注意,str_pad()非常慢。例如,对于长度为 10,000 的向量,计算时间非常长。padding()似乎无法处理字符向量,但最好的解决方案可能是将sapply()padding()函数一起使用。

>library("stringr")
>library("cwhmisc")
>a <- rep(1,10^4)
> system.time(b <- str_pad(a,3,side="left",pad="0"))
utilisateur     système      écoulé 
     50.968       0.208      73.322 
> system.time(c <- sapply(a, padding, space = 3, with = "0", to = "left"))
utilisateur     système      écoulé 
      7.700       0.020      12.206

删除前导空格和尾随空格

[编辑 | 编辑源代码]
  • trimws() (memisc 包) 修剪前导空格和尾随空格。
  • trim() (gdata 包) 执行相同的工作。
  • 另请参阅 str_trim() (stringr)
> library("memisc")
> trimws("  abc def   ")
[1] "abc def" 
> library("gdata")
> trim(" abc def ")
[1] "abc def"
> str_trim("  abd def  ")
[1] "abd def"

比较两个字符串

[编辑 | 编辑源代码]

评估它们是否相同

[编辑 | 编辑源代码]
  • ==如果两个字符串相同则返回 TRUE,否则返回 false。
> "abc"=="abc"
[1] TRUE
> "abc"=="abd"
[1] FALSE

计算字符串之间的距离

[编辑 | 编辑源代码]

很少有包实现了Levenshtein 距离,即两个字符串之间的距离。

  • 基本包 utils 中的adist()
  • MiscPsycho 中的stringMatch()
  • stringdist 中的stringdist()
  • RecordLinkage 中的levenshteinDist()

此处提供了比较levenshteinDist()stringdist()速度的基准测试:[1]

使用 utils 的示例

[编辑 | 编辑源代码]
> adist("test","tester")
[1] 2

使用 MiscPsycho 的示例

[编辑 | 编辑源代码]

stringMatch() (MiscPsycho) 计算如果normalize="YES",则 Levenshtein 距离将除以每个字符串的最大长度。

> library("MiscPsycho")
> stringMatch("test","tester",normalize="NO",penalty=1,case.sensitive = TRUE)
[1] 2

近似匹配

[编辑 | 编辑源代码]

agrep()使用Levenshtein 距离搜索近似匹配。

  • 如果 'value = TRUE',则返回字符串的值
  • 如果 'value = FALSE',则返回字符串的位置
  • max返回最大 Levenshtein 距离。
>  agrep(pattern = "laysy", x = c("1 lazy", "1", "1 LAZY"), max = 2, value = TRUE)
[1] "1 lazy"
>  agrep("laysy", c("1 lazy", "1", "1 LAZY"), max = 3, value = TRUE)
[1] "1 lazy"
  • deparse():将未评估的表达式转换为字符字符串。
  • char.expand() (base) 根据目标扩展字符串。
  • pmatch() (base) 和 charmatch() (base) 在第二个参数中查找第一个参数元素的匹配项。
> pmatch(c("a","b","c","d"),table = c("b","c"), nomatch = 0)
[1] 0 1 2 0
  • make.unique()使字符字符串唯一。如果要使用字符串作为数据中的标识符,这将很有用。
> make.unique(c("a", "a", "a"))
[1] "a"   "a.1" "a.2"

参考资料

[编辑 | 编辑源代码]
  1. Hadley Wickham "stringr: modern, consistent string processing" The R Journal, December 2010, Vol 2/2, http://journal.r-project.org/archive/2010-2/RJournal_2010-2_Wickham.pdf
  2. https://cran.r-project.org.cn/web/views/NaturalLanguageProcessing.html
  3. 在以前版本(< 2.10)中,我们还在 R 中拥有基本正则表达式:
    • 扩展正则表达式,由 extended = TRUE(默认值)使用,
    • 基本正则表达式,由 extended = FALSE(在 R 2.10 中已弃用)使用。
    由于基本正则表达式 (‘extended = FALSE’) 现在已弃用,因此 extended 选项在 2.11 版中已弃用。
华夏公益教科书