正則表達式及R字符串處理之終結版


http://yphuang.github.io/blog/2016/03/15/regular-expression-and-strings-processing-in-R/

 

0.動機:為什么學習字符串處理

傳統的統計學教育幾乎沒有告訴過我們,如何進行文本的統計建模分析。然而,我們日常生活中接觸到的大部分數據都是以文本的形式存在。文本分析與挖掘在業界中也有着非常廣泛的應用。

由於文本數據大多屬於非結構化的數據,要想對文本數據進行傳統的統計模型分析,必須要經過層層的數據清洗與整理。

今天我們要介紹的『正則表達式及R字符串處理』就是用來干這一種臟活累活的。

與建立酷炫的模型比起來,數據的清洗與整理似乎是一種低檔次的工作。如果把建立模型類比於高級廚師的工作,那么,數據清洗無疑是類似切菜洗碗打掃衛生的活兒。然而想要成為資深的『數據玩家』,這種看似低檔次的工作是必不可少的,並且,這種工作極有可能將占據你整個建模流程的80%的時間。

如果我們能夠掌握高效的數據清洗工具,那么我們將擁有更多的時間來進行模型選擇和參數調整,使得我們的模型更加合理有效。

此外,對於不需要進行文本建模分析的同學,掌握文本處理的工具也將對減輕你的工作負擔大有益處。下面,我舉幾個我自身經歷的『文本處理工具讓生活更美好』的例子:

可見,我們可以用到文本處理工具的場景還是非常多的,如批量文件名修改、批量字符替換、大量郵件或html文件處理等。

下面,我將通過一個例子,展示R字符串處理的大致功能。接着,介紹正則表達式的基礎概念。然后,介紹R字符串處理中一個非常好用的拓展包stringr,並接着介紹一些文件編碼處理相關的函數。最后,通過一兩個案例展示字符串處理的真實應用場景。


1.A toy example ——初步認識R中的字符串處理

為了先給大家一個關於R字符串處理的大體認識,我們使用R中自帶的一個數據集USArrests進行函數功能演示。

先看看數據集的結構。

# take a glimpse head(USArrests)

字符串子集提取:獲得州的簡稱

# 獲得州名 states = rownames(USArrests) # 方法一:substr() substr(x = states, start = 1, stop = 4) # 方法二:abbreviate()  abbreviate(states,minlength = 5)

字符統計:獲得名字最長的州名

# get number of characters in each state name  state_chars <- nchar(states) # hist hist(nchar(states),main = "Histogram", xlab = "number of charaters in US State names") # longest state's name states[which(state_chars == max(state_chars))]

注意:nchar()與length()的區別

字符串匹配:含某些字母的州名

# get states names with 'w' grep(pattern = "w", x = states, value = TRUE) ###########################  # get states names with 'W' OR 'w'  ## Method 1: grep(pattern = "[wW]", x = states, value = TRUE) ## Method 2: grep(pattern = "w", x = tolower(states),value = TRUE) ## Method 3: grep(pattern = "W", x = toupper(states), value = TRUE) ## Method 4: grep(pattern = "w",x = states, ignore.case = TRUE, value = TRUE)

字符統計:某些字母個數統計

library(stringr) # total number of a's str_count(states,"a") ################## # number of vowels  # vector of vowels vowels <- c("a","e","i","o","u") # vector for storing results num_vowels <- vector(mode = "integer",length = 5) # calculate for(i in seq_along(vowels)){ num_aux <- str_count(tolower(states),vowels[i]) num_vowels[i]<-sum(num_aux) } # add names names(num_vowels)<-vowels # total number of vowels num_vowels # barplot barplot(num_vowels, main = "number of vowels in USA States names")

2.正則表達式

正則表達式是對字符串類型數據進行匹配判斷,提取等操作的一套邏輯公式。

處理字符串類型數據方面,高效的工具有Perl和Python。如果我們只是偶爾接觸文本處理任務,則學習Perl無疑成本太高;如果常用Python,則可以利用成熟的正則表達式模塊:re庫;如果常用R,則使用Hadley大神開發的stringr包則已經能夠游刃有余。

下面,我們先簡要介紹重要並通用的正則表達式規則。接着,總結一下stringr包中需要輸入正則表達式參數的字符處理函數。

元字符(Metacharacters)

大部分的字母和所有的數字都是匹配他們自身的正則表達式。然而,在正則表達式的語法規定中,有12個字符被保留用作特殊用途。他們分別是:

[ ] \ ^ $ . | ? * + ( ) 

如果我們直接進行對這些特殊字符進行匹配,是不能匹配成功的。正確理解他們的作用與用法,至關重要。

library(stringr) metaChar = c("$","*","+",".","?","[","^","{","|","(","\\") grep(pattern="$", x=metaChar, value=TRUE) grep(pattern="\\", x=metaChar, value=TRUE) grep(pattern="(", x=metaChar, value=TRUE) gsub(pattern="|", replacement=".", "gsub|uses|regular|expressions") strsplit(x="strsplit.aslo.uses.regular.expressions", split=".")

它們的作用如下:

  • [ ]:括號內的任意字符將被匹配;
# example grep(pattern = "[wW]", x = states, value = TRUE)
  • \:具有兩個作用:
    • 1.對元字符進行轉義(后續會有介紹)
    • 2.一些以\開頭的特殊序列表達了一些字符串組
strsplit(x="strsplit.aslo.uses.regular.expressions", split=".") # compare strsplit(x="strsplit.aslo.uses.regular.expressions", split="\\.") ################ # function 2: library(stringr) str_extract_all(string = "my cridit card number: 34901358932236",pattern = "\\d")
  • ^:匹配字符串的開始.將^置於character class的首位表達的意思是取反義。如[^5]表示匹配除了”5”以外的任何字符。
# function 1 test_vector<-c("123","456","321") library(stringr) str_extract_all(test_vector,"3") str_extract_all(test_vector,"^3") # function 2 str_extract_all(test_vector,"[^3]")
  • $:匹配字符串的結束。但將它置於character class內則消除了它的特殊含義。如[akm$]將匹配’a’,’k’,’m’或者’$’.
# function 1 test_vector<-c("123","456$","321") library(stringr) str_extract_all(test_vector,"3$") # function 2 str_extract_all(test_vector,"[3$]")
  • .:匹配除換行符以外的任意字符。
str_extract_all(string = c("regular.expressions\n","\n"), pattern ="\\.")
  • |:或者
test_vector2<-c("AlphaGo實在厲害!","alphago是啥","阿爾法狗是一條很凶猛的狗。") str_extract_all(string = test_vector2, pattern ="AlphaGo|阿爾法狗")
  • ?:前面的字符(組)是可有可無的,並且最多被匹配一次
str_extract_all(string = c("abc","ac","bc"),pattern = "ab?c")
  • *:前面的字符(組)將被匹配零次或多次
str_extract_all(string = c("abababab","abc","ac"),pattern = "(ab)*")
  • +:前面的字符(組)將被匹配一次或多次
str_extract_all(string = c("abababab","abc","ac"),pattern = "(ab)+")
  • ( ):表示一個字符組,括號內的字符串將作為一個整體被匹配。
str_extract_all(string = c("ababc","ac","cde"),pattern = "(ab)?c") str_extract_all(string = c("abc","ac","cde"),pattern = "ab?c")

重復

代碼 含義說明
? 重復零次或一次
* 重復零次或多次
+ 重復一次或多次
{n} 重復n次
{n,} 重復n次或更多次
{n,m} 重復n次到m次
str_extract_all(string = c("abababab","ababc","ababababc"),pattern = "(ab){2,3}")

轉義

如果我們想查找元字符本身,如”?”和”*“,我們需要提前告訴編譯系統,取消這些字符的特殊含義。這個時候,就需要用到轉義字符\,即使用\?\*.當然,如果我們要找的是\,則使用\\進行匹配。

strsplit(x="strsplit.aslo.uses.regular.expressions", split=".") # compare strsplit(x="strsplit.aslo.uses.regular.expressions", split="\\.")

注:R中的轉義字符則是雙斜杠:\\

R中預定義的字符組

代碼 含義說明
[:digit:] 數字:0-9
[:lower:] 小寫字母:a-z
[:upper:] 大寫字母:A-Z
[:alpha:] 字母:a-z及A-Z
[:alnum:] 所有字母及數字
[:punct:] 標點符號,如. , ;
[:graph:] Graphical characters,即[:alnum:]和[:punct:]
[:blank:] 空字符,即:Space和Tab
[:space:] Space,Tab,newline,及其他space characters
[:print:] 可打印的字符,即:[:alnum:],[:punct:]和[:space:]
library(stringr) str_extract_all(string = "my cridit card number: 34901358932236",pattern = "\\d")

代表字符組的特殊符號

代碼 含義說明
\w 字符串,等價於[:alnum:]
\W 非字符串,等價於[^[:alnum:]]
\s 空格字符,等價於[:blank:]
\S 非空格字符,等價於[^[:blank:]]
\d 數字,等價於[:digit:]
\D 非數字,等價於[^[:digit:]]
\b Word edge(單詞開頭或結束的位置)
\B No Word edge(非單詞開頭或結束的位置)
\< Word beginning(單詞開頭的位置)
\> Word end(單詞結束的位置)

3.stringr字符串處理函數對比學習

stringr包中的重要函數

函數 功能說明 R Base中對應函數
使用正則表達式的函數    
str_extract() 提取首個匹配模式的字符 regmatches()
str_extract_all() 提取所有匹配模式的字符 regmatches()
str_locate() 返回首個匹配模式的字符的位置 regexpr()
str_locate_all() 返回所有匹配模式的字符的位置 gregexpr()
str_replace() 替換首個匹配模式 sub()
str_replace_all() 替換所有匹配模式 gsub()
str_split() 按照模式分割字符串 strsplit()
str_split_fixed() 按照模式將字符串分割成指定個數 -
str_detect() 檢測字符是否存在某些指定模式 grepl()
str_count() 返回指定模式出現的次數 -
其他重要函數    
str_sub() 提取指定位置的字符 regmatches()
str_dup() 丟棄指定位置的字符 -
str_length() 返回字符的長度 nchar()
str_pad() 填補字符 -
str_trim() 丟棄填充,如去掉字符前后的空格 -
str_c() 連接字符 paste(),paste0()

可見,stringr包中的字符處理函數更豐富和完整,並且更容易記憶。

文本文件的讀寫

這里的文本文件指的是非表格式的文件,如純文本文件,html文件。文本文件的讀取可以使用readLines()scan()函數。一般需要通過encoding = 參數設置文件內容的編碼方式。

#假設當前路徑有一個文件為`file.txt` text <- readLines("file.txt", encoding = "UTF-8") #默認設置,每個單詞作為字符向量的一個元素 scan("file.txt", what = character(0),encoding = "UTF-8") #設置成每一行文本作為向量的一個元素,這類似於readLines scan("file.txt", what = character(0), sep = "\n",encoding = "UTF-8") #設置成每一句文本作為向量的一個元素 scan("file.txt", what = character(0), sep = ".",encoding = "UTF-8")

文本文件的寫出可以使用cat()writeLines()函數。

# 假設要保存當前環境中的R變量text # sep參數指定要保存向量里的元素的分割符號。 cat(text, file = "file.txt", sep = "\n") writeLines(text, con = "file.txt", sep = "\n", useBytes = F)

字符統計及字符翻譯

x<- c("I love R","I'm fascinated by Statisitcs") ################## ## 字符統計  # nchar nchar(x) # str_count library(stringr) str_count(x,pattern = "") str_length(x) ######################  DNA <- "AgCTaaGGGcctTagct" ## 字符翻譯:大小寫轉換 tolower(DNA) toupper(DNA) ## 字符翻譯:符號替換(逐個替換) # chartr chartr("Tt", "Uu", DNA) #將T鹼基替換成U鹼基  # 注意:與str_replace()的區別  library(stringr) str_replace_all(string = DNA,pattern = "T",replacement = "U") %>% str_replace_all(string = .,pattern = "t",replacement = "u")

字符串連接

# paste paste("control",1:3,sep = "_") # str_c() library(stringr) str_c("control",1:3,sep = "_")

字符串拆分

# strsplit text <- "I love R.\nI'm fascinated by Statisitcs." cat(text) strsplit(text,split = " ") strsplit(text,split = "\\s") # str_split library(stringr) str_split(text,pattern = "\\s")

字符串查詢

字符串的查詢或者搜索應用了正則表達式的匹配來完成任務. R Base 包含的字符串查詢相關的函數有grep(),grepl(),regexpr(),gregexpr()和regexec()等。

################################# ## 包含匹配  # grep x<- c("I love R","I'm fascinated by Statisitcs","I") grep(pattern = "love",x = x) grep(pattern = "love",x = x,value = TRUE) grepl(pattern = "love",x = x) # str_detect  str_detect(string = x, pattern = "love") ################################# # # match,完全匹配, 常用的 %in% 由match()定義 match(x = "I",table = x) "I'm" %in% x

字符串替換

sub()和gsub()能夠提供匹配替換的功能,但其替換的實質是先創建一個對象,然后對原始對象進行重新賦值,最后結果好像是“替換”了一樣。

sub()和gsub()的區別在於,前者只替換第一次匹配的字串(請注意輸出結果中world的首字母),而后者會替換掉所有匹配的字串。

也可以使用substr和substring對指定位置進行替換。

##################################### ## 匹配替換  test_vector3<-c("Without the vowels,We can still read the word.") # sub sub(pattern = "[aeiou]",replacement = "-",x = test_vector3) # gsub gsub(pattern = "[aeiou]",replacement = "-",x = test_vector3) # str_replace_all  str_replace_all(string = test_vector3,pattern = "[aeiou]", replacement = "-") ########################################## ## 指定位置替換 

字符串提取

常用到的提取函數有substr()和substring(),它們都是靠位置來進行提取的,它們自身並不適用正則表達式,但是它們可以結合正則表達式函數regexpr(),gregexpr()和regexec()等可以方便地從文本中提取所需信息。

stringr包中的函數str_substr_dup可以通過位置提取,而str_extractstr_match可以通過正則表達式提取。

substr("abcdef", start = 2, stop = 4) substring("abcdef", first = 1:6, last = 2:7) str_sub("abcdef",start = 2, end = 4) str_sub("abcdef",start = 1:6, end = 1:6) ################################  text_weibo<- c("#圍棋人機大戰# 【人工智能攻克圍棋 AlphaGo三比零完勝李世石】","谷歌人工智能AlphaGo與韓國棋手李世石今日進行了第三場較量","最終AlphaGo戰勝李世石,連續取得三場勝利。接下來兩場將淪為李世石的“榮譽之戰。") # str_match_all,返回的列表中的元素為矩陣  str_match_all(text_weibo,pattern = "#.+#") str_match_all(text_weibo, pattern = "[a-zA-Z]+") # str_extract_all,返回的列表中的元素為向量 str_extract_all(text_weibo,pattern = "#.+#") str_extract_all(text_weibo, pattern = "[a-zA-Z]+")

字符串定制輸出

這個內容有點類似於字符串的連接。R中相應的函數為strtrim(),用於將字符串修剪到特定的顯示寬度。stringr中相應的函數為:str_pad().

strtrim()會根據width參數提供的數字來修剪字符串,若width提供的數字大於字符串的字符數的話,則該字符串會保持原樣,不會增加空格之類的東西,若小於,則刪除部分字符。而str_pad()則相反。

strtrim(c("abcde", "abcde", "abcde"),width = c(1, 5, 10)) str_pad(string = c("abcde", "abcde", "abcde"),width = c(1, 5, 10),side = "right")

strwrap()會把字符串當成一個段落來處理(不管段落中是否有換行),按照段落的格式進行縮進和分行,返回結果就是一行行的字符串。

而str_wrap()不對文本直接切割成向量,而是在文本內容中插入了縮進或分行的標識符。

string <- "Each character string in the input is first split into\n paragraphs (or lines containing whitespace only). The paragraphs are then formatted by breaking lines at word boundaries." strwrap(x = string, width = 30) #str_wrap str_wrap(string = string,width = 30) cat(str_wrap(string = string, width = 30))

4.字符編碼相關的重要函數

windows下處理字符串類型數據最頭疼的無疑是編碼問題了。這里介紹幾個編碼轉換相關的函數。

函數 功能說明
iconv() 轉換編碼格式
Encoding() 查看編碼格式;或者指定編碼格式
tau::is.locale() tests if the components of a vector of character are in the encoding of the current locale
tau::is.ascii()  
tau::is.utf8() tests if the components of a vector of character are true UTF-8 strings

雖然查看編碼方式已經有Encoding()函數,但是這個函數往往在很多時候都不靈,經常返回惱人的“Unknow”。而火狐瀏覽器進行網頁文本編碼識別的一個 c++ 庫universalchardet ,可以識別的編碼種類較多。文鋒寫了一個相應的R包接口,專用於文件編碼方式檢測,具體請參考:checkenc - 自動文本編碼識別

devtools::install_github("qinwf/checkenc") library(checkenc) checkenc("2016-03-10-regular-expression-and-strings-processing-in-R.html") Encoding("2016-03-10-regular-expression-and-strings-processing-in-R.html")

5.應用案例

最后,給大家展示一個小小的爬蟲案例:爬取豆瓣T250中的電影信息進行分析。這里出於練習的目的刻意使用了字符串處理函數,在實際的爬蟲中,有更方便快捷的實現方式。

本案例改編自肖凱老師的博客在R語言中使用正則表達式,原博客使用R Base中的函數進行處理字符串,這里已經全部更改為stringr中的函數進行處理。

library(stringr) library(dplyr) url <-'http://movie.douban.com/top250?format=text' # 獲取網頁原代碼,以行的形式存放在web變量中 setInternet2() web <- readLines(url,encoding="UTF-8") # 找到包含電影名稱的行 name<-str_extract_all(string = web, pattern = '<span class="title">.+</span>') movie.names_line <- unlist(name) # 用正則表達式來提取電影名 movie.names <- str_extract(string = movie.names_line, pattern = ">[^&].+<") %>% str_replace_all(string = ., pattern = ">|<",replacement = "") movie.names<- na.omit(movie.names) # 獲取評價人數 Rating<- str_extract_all(string = web,pattern = '<span>[:digit:]+人評價</span>') Rating.num_line<-unlist(Rating) Rating.num<- str_extract(string = Rating.num_line, pattern = "[:digit:]+") %>% as.numeric(.) #獲取評價分數 Score_line<-str_extract_all(string = web, pattern = '<span class="rating_num" property="v:average">[\\d\\.]+</span>') Score_line<- unlist(Score_line) Score<- str_extract(string = Score_line, pattern = '\\d\\.\\d') %>% as.numeric(.) # 數據合並  MovieData<- data.frame(MovieName = movie.names, RatingNum = Rating.num, Score = Score, Rank = seq(1,25),stringsAsFactors = FALSE) View(MovieData) #可視化 library(ggplot2) ggplot(data = MovieData,aes(x = Rank,y = Score)) + geom_point(aes(size = RatingNum))+ geom_text(aes(label = MovieName), colour = "blue",size = 4,vjust = -0.6)

深入學習

參考文獻


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM