差不多從 7、8 年前首次聽到 R 這個名字的時候開始,就對於 R 有一個刻板印象:「很好的數值資料處理工具,但並不擅長用來處理文字資料」。
最近因為有個小任務需要把非結構文字資料轉為結構化的矩陣,趁著這個機會也更新一下自己對 R 的認識,在找資料的過程才發現即使是「中文字串」這個 Text-Mining 當中的煩人問題,現在也可以透過 R 當中的套件做快速的處理,分析者甚至不需要了解何謂程式語言中的 regular expression,簡直神奇!
這篇文章主要分享的部分是 R 2.1 版本(目前最新應該是 3.1.2 版)以後收錄的基本套件 grep 當中的幾個 function,以及搭配 substring 等其他 function 的組合用法。
文字處理與 Text-Mining
在筆者經驗當中, Text-Mining 的前置資料處理大概可以分為兩種:第一,將原始文章資料透過「辭庫」進行斷字斷詞,每一個被認定為單獨的詞將對應到一個欄位,將多篇文章斷詞完畢之後,將得到一個擁有巨量欄位的計數資料矩陣,接下來再應用 Clustering 或者 Classification 的演算法進行關聯性分析或預測。
這種方式構成 Text-Mining 給人的主要印象,但僅限使用於文章性質的資料分析,實務上會遇到的非結構化文字資料未必都是文章、留言,例如網路購物的訂單 email ,假如訂單資料庫設計時並未儲存所有訂購相關資訊,而必須收集訂單 email 中的資訊反過來重組資料庫時,前述斷字斷詞的方法就派不上用場,因為每封 email 中的系統詞彙(例如金額、姓名欄位)都是固定的,重點是如何擷取位於這些系統詞彙前後左右的個人化資料(例如實際金額的數字),這就是另一種文字資料前置處理時要做的工作(沒有 R 的話,多半得精通 regular expression 才做得出來)。
其他範例還包括時下流行的網路爬文程式,在過濾網路原始檔時也會遇到許多重複但固定的格式,這些時候必須先擷取指定的資料,組成結構化的資料矩陣後,才能進行後續的統計或斷字斷詞分析。
範例:維基百科-中華民國頁面的一部分網頁原始檔
<head>
<meta charset="UTF-8″ />
<title>中華民國 – 維基百科,自由的百科全書</title>
<meta name="generator" content="MediaWiki 1.25wmf14″ />
<link rel="alternate" href="android-app://org.wikipedia/http/zh.m.wikipedia.org/wiki/%E4%B8%AD%E8%8F%AF%E6%B0%91%E5%9C%8B" />
R 的字串處理好幫手:grep
如果你在 R 當中輸入help(grep),就可以看到在這個包底下共有 6 種函數:
- grep(pattern, x, ignore.case = FALSE, perl = FALSE, value = FALSE,fixed = FALSE, useBytes = FALSE, invert = FALSE)
- grepl(pattern, x, ignore.case = FALSE, perl = FALSE,fixed = FALSE, useBytes = FALSE)
- sub(pattern, replacement, x, ignore.case = FALSE, perl = FALSE,fixed = FALSE, useBytes = FALSE)
- gsub(pattern, replacement, x, ignore.case = FALSE, perl = FALSE,fixed = FALSE, useBytes = FALSE)
- regexpr(pattern, text, ignore.case = FALSE, perl = FALSE,fixed = FALSE, useBytes = FALSE)
- gregexpr(pattern, text, ignore.case = FALSE, perl = FALSE,fixed = FALSE, useBytes = FALSE)
- regexec(pattern, text, ignore.case = FALSE,fixed = FALSE, useBytes = FALSE)
由於這些函數格式都很類似,只要會用一種,其他也就八九不離十了,實際上要注意的是這些函數返回的「值」要如何運用以及在參數中使用「TRUE/FALSE」的時機。
為了更快速了解它們的用法,我們一邊以維基百科頁面的例子試驗,一邊說明每個函數的差異。
首先先設定範例字串「ROC」:
> ROC<-“<title>中華民國 – 維基百科,自由的百科全書</title>"
> ROC
[1] “<title>中華民國 – 維基百科,自由的百科全書</title>"
接著以「中華民國」為 pattern 參數,投入各函數當中:
> grep(“中華民國",ROC)
[1] 1
# grep返回數值的意義不難猜,即是告訴我們在「 ROC 」當中,符合”中華民國”條件的值有幾個,當然,答案是 1 。
> grepl(“中華民國",ROC)
[1] TRUE
# grepl 名字和 grep 相似,做的事情也差不多,唯一的差別在於 grepl 使用二分法 TRUE 或者 FALSE 表達在指定條件”中華民國”下,指定範圍 ROC 裡是否有符合的字元。
> regexpr(“中華民國",ROC)
[1] 8
attr(,"match.length")
[1] 4
#包括 regexpr 、 gregexpr 及 regexec 都帶有兩個值,第一個值表示符合條件的字串”中華民國”出現在指定範圍” ROC ”當中的位置是從第幾個字元開始,稍微數一下你就會有點感覺了,答案是第 8 個字元開始。第二個值「 match.length 」表示的是符合條件”中華民國”的結果有幾個字元,當然就是 4 個了。
> gregexpr(“中華民國",ROC)
[[1]]
[1] 8
attr(,"match.length")
[1] 4
#從返回值來看, regexpr、 gregexpr 及 regexec 似乎都一樣,假如有配對成功的字串的確看不出差異,沒有配對成功時則有些許不同,使用細節可參考help()當中的說明。
> regexec(“中華民國",ROC)
[[1]]
[1] 8
attr(,"match.length")
[1] 4
# 同上。> sub(“中華民國","台灣",ROC)
[1] “<title>台灣 – 維基百科,自由的百科全書</title>"
# sub 及 gsub 作用相同,跟前面幾個函數不同的是,除了指定 pattern 和 text 之外,還要多指定 replacement ,因為它就是用來取代字元的函數。在這個範例中,原先的中華民國被取代成了台灣。
> gsub(“中華民國","台灣",ROC)
[1] “<title>台灣 – 維基百科,自由的百科全書</title>"
# 同上。
grep 包的應用實例
看過這些文字處理函數之後,或許你也跟我剛開始使用的時候一樣,還是看不出來要如何應用這些函數進行結構化的處理。的確,以筆者常用的 regexpr 為例,它只是告訴我們關鍵的字元起始位置,並未幫我們把這些資訊給擷取出來,因此還要搭配字串切割的函數才能發揮威力。
這些互相搭配的函數中,必須要學會的是 substring 。這個函數的用法是:
substr(x, start, stop)
其中
x, text => a character vector.
start, first => integer. The first element to be replaced.
stop, last => integer. The last element to be replaced.
只要指定了起始以及結束的位置, substr 就能在指定的範圍 x 當中找到並切割出我們所要的資料。
但我們要如何知道關鍵資料的起始和結束位置呢?它正好就是 grep 系列函數的輸出值啊!
沿用維基百科的例子,假如現在有個大資料集包含了一萬筆資料的原始檔叫做 CountryName,每筆資料都代表一個頁面並包含唯一的 title 值(例如:<title>中華民國 – 維基百科,自由的百科全書</title>),而我們想要把這組 title 找出來並存成一個欄位,該如何做呢?
由於我們已經知道關鍵字串「中華民國」的位置,它不一定是四個字,也可能是兩個字的美國或五個字的哥斯大黎加,但它必定會出現在字串「 <title> 」起算的第 8 個字元開始到「 維基百科 」之前的 4 個字元為止,於是我們可以利用 regexpr 找出這一萬筆資料當中所有的「國名」位置,並存為 StartName 及 EndName 兩個變數,例如:
CountryName <- 原始資料
StartName<-regexpr(“<title>", CountryName)
EndName<- regexpr(“維基百科", CountryName)
接著利用 subtr ,輸入 StartName 及 EndName 兩個變數所包含的位置資料,切割出想要的國名資料,存為 ABC 變數:
ABC<-subtr(CountryName, StartName+8, EndName-4)
結果應該會長得如下:
ABC
1 中華民國
2 美國
3 哥斯大黎加
….
R 中常用的字串處理 Function 清單
除了 substr 之外,《Handling and Processing Strings in R》的作者 Gaston Sanchez 幫大家整理了一些常用的字串處理函數,蓋列如下以供參考:
- Basic functions
character() creating a character vector
is.character() test character mode
as.character() convert as character
paste() pasting
Printing related functions
print() generic printing
noquote() unquoted characters
cat() concatenate and print
format() special formats
sprintf() C-style string formatting
toString() convert to string objects
- Basic String Manipulations
char() count number of characters
tolower() convert to lower case
toupper() convert to upper case
casefold() case folding
chartr() character translation
abbreviate() abbreviation
substring() substrings of a character vector
substr() substrings of a character vector
- String Manipulations with stringr
str_c() concatenation
str_length() number of characters
str_sub() substring
str_dup() string duplication
str_pad() string padding
str_wrap() string wrapping
str_trim() trimming whitespaces
word() extract words from a sentence
- Functions for Regular Expressions
grep()
grepl()
regexpr()
gregexpr()
regexec()
sub()
gsub()
strsplit()
str_detect()
str_extract()
str_extract_all()
str_match()
str_match_all()
str_locate()
str_locate_all()
str_replace()
str_replace_all()
str_split()
str_split_fixed()
4 Comments