跳轉到內容

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. http://cran.r-project.org/web/views/NaturalLanguageProcessing.html
  3. 在以前版本(< 2.10)中,我們還在 R 中擁有基本正則表示式:
    • 擴充套件正則表示式,由 extended = TRUE(預設值)使用,
    • 基本正則表示式,由 extended = FALSE(在 R 2.10 中已棄用)使用。
    由於基本正則表示式 (‘extended = FALSE’) 現在已棄用,因此 extended 選項在 2.11 版中已棄用。
華夏公益教科書