在前一篇關(guān)于正則表達(dá)式和字符處理函數(shù)總結(jié)的博客的基礎(chǔ)上,歷時(shí)一周,我翻閱了更多的資料,進(jìn)行修改和增加了一些實(shí)例演示和案例講解,組成這一篇RClub的講座課件。本著知識(shí)共享、完全開(kāi)源的精神,在此奉獻(xiàn)給大家。 0.動(dòng)機(jī):為什么學(xué)習(xí)字符串處理傳統(tǒng)的統(tǒng)計(jì)學(xué)教育幾乎沒(méi)有告訴過(guò)我們,如何進(jìn)行文本的統(tǒng)計(jì)建模分析。然而,我們?nèi)粘I钪薪佑|到的大部分?jǐn)?shù)據(jù)都是以文本的形式存在。文本分析與挖掘在業(yè)界中也有著非常廣泛的應(yīng)用。 由于文本數(shù)據(jù)大多屬于非結(jié)構(gòu)化的數(shù)據(jù),要想對(duì)文本數(shù)據(jù)進(jìn)行傳統(tǒng)的統(tǒng)計(jì)模型分析,必須要經(jīng)過(guò)層層的數(shù)據(jù)清洗與整理。 今天我們要介紹的『正則表達(dá)式及R字符串處理』就是用來(lái)干這一種臟活累活的。 與建立酷炫的模型比起來(lái),數(shù)據(jù)的清洗與整理似乎是一種低檔次的工作。如果把建立模型類(lèi)比于高級(jí)廚師的工作,那么,數(shù)據(jù)清洗無(wú)疑是類(lèi)似切菜洗碗打掃衛(wèi)生的活兒。然而想要成為資深的『數(shù)據(jù)玩家』,這種看似低檔次的工作是必不可少的,并且,這種工作極有可能將占據(jù)你整個(gè)建模流程的80%的時(shí)間。 如果我們能夠掌握高效的數(shù)據(jù)清洗工具,那么我們將擁有更多的時(shí)間來(lái)進(jìn)行模型選擇和參數(shù)調(diào)整,使得我們的模型更加合理有效。 此外,對(duì)于不需要進(jìn)行文本建模分析的同學(xué),掌握文本處理的工具也將對(duì)減輕你的工作負(fù)擔(dān)大有益處。下面,我舉幾個(gè)我自身經(jīng)歷的『文本處理工具讓生活更美好』的例子:
可見(jiàn),我們可以用到文本處理工具的場(chǎng)景還是非常多的,如批量文件名修改、批量字符替換、大量郵件或html文件處理等。 下面,我將通過(guò)一個(gè)例子,展示R字符串處理的大致功能。接著,介紹正則表達(dá)式的基礎(chǔ)概念。然后,介紹R字符串處理中一個(gè)非常好用的拓展包 1.A toy example ——初步認(rèn)識(shí)R中的字符串處理為了先給大家一個(gè)關(guān)于R字符串處理的大體認(rèn)識(shí),我們使用R中自帶的一個(gè)數(shù)據(jù)集 先看看數(shù)據(jù)集的結(jié)構(gòu)。
注意:nchar()與length()的區(qū)別
2.正則表達(dá)式正則表達(dá)式是對(duì)字符串類(lèi)型數(shù)據(jù)進(jìn)行匹配判斷,提取等操作的一套邏輯公式。 處理字符串類(lèi)型數(shù)據(jù)方面,高效的工具有Perl和Python。如果我們只是偶爾接觸文本處理任務(wù),則學(xué)習(xí)Perl無(wú)疑成本太高;如果常用Python,則可以利用成熟的正則表達(dá)式模塊: 下面,我們先簡(jiǎn)要介紹重要并通用的正則表達(dá)式規(guī)則。接著,總結(jié)一下 元字符(Metacharacters)大部分的字母和所有的數(shù)字都是匹配他們自身的正則表達(dá)式。然而,在正則表達(dá)式的語(yǔ)法規(guī)定中,有12個(gè)字符被保留用作特殊用途。他們分別是:
如果我們直接進(jìn)行對(duì)這些特殊字符進(jìn)行匹配,是不能匹配成功的。正確理解他們的作用與用法,至關(guān)重要。
它們的作用如下:
重復(fù)
轉(zhuǎn)義如果我們想查找元字符本身,如”?”和”*“,我們需要提前告訴編譯系統(tǒng),取消這些字符的特殊含義。這個(gè)時(shí)候,就需要用到轉(zhuǎn)義字符
注:R中的轉(zhuǎn)義字符則是雙斜杠: R中預(yù)定義的字符組
代表字符組的特殊符號(hào)
3.
|
函數(shù) | 功能說(shuō)明 | R Base中對(duì)應(yīng)函數(shù) |
---|---|---|
使用正則表達(dá)式的函數(shù) | ||
str_extract() |
提取首個(gè)匹配模式的字符 | regmatches() |
str_extract_all() |
提取所有匹配模式的字符 | regmatches() |
str_locate() |
返回首個(gè)匹配模式的字符的位置 | regexpr() |
str_locate_all() |
返回所有匹配模式的字符的位置 | gregexpr() |
str_replace() |
替換首個(gè)匹配模式 | sub() |
str_replace_all() |
替換所有匹配模式 | gsub() |
str_split() |
按照模式分割字符串 | strsplit() |
str_split_fixed() |
按照模式將字符串分割成指定個(gè)數(shù) | - |
str_detect() |
檢測(cè)字符是否存在某些指定模式 | grepl() |
str_count() |
返回指定模式出現(xiàn)的次數(shù) | - |
其他重要函數(shù) | ||
str_sub() |
提取指定位置的字符 | regmatches() |
str_dup() |
丟棄指定位置的字符 | - |
str_length() |
返回字符的長(zhǎng)度 | nchar() |
str_pad() |
填補(bǔ)字符 | - |
str_trim() |
丟棄填充,如去掉字符前后的空格 | - |
str_c() |
連接字符 | paste(),paste0() |
可見(jiàn),stringr
包中的字符處理函數(shù)更豐富和完整,并且更容易記憶。
這里的文本文件指的是非表格式的文件,如純文本文件,html文件。文本文件的讀取可以使用readLines()
和scan()
函數(shù)。一般需要通過(guò)encoding =
參數(shù)設(shè)置文件內(nèi)容的編碼方式。
#假設(shè)當(dāng)前路徑有一個(gè)文件為`file.txt`
text <- readLines("file.txt", encoding = "UTF-8")
#默認(rèn)設(shè)置,每個(gè)單詞作為字符向量的一個(gè)元素
scan("file.txt", what = character(0),encoding = "UTF-8")
#設(shè)置成每一行文本作為向量的一個(gè)元素,這類(lèi)似于readLines
scan("file.txt", what = character(0), sep = "\n",encoding = "UTF-8")
#設(shè)置成每一句文本作為向量的一個(gè)元素
scan("file.txt", what = character(0), sep = ".",encoding = "UTF-8")
文本文件的寫(xiě)出可以使用cat()
和writeLines()
函數(shù)。
# 假設(shè)要保存當(dāng)前環(huán)境中的R變量text
# sep參數(shù)指定要保存向量里的元素的分割符號(hào)。
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")
##################
## 字符統(tǒng)計(jì)
# nchar
nchar(x)
# str_count
library(stringr)
str_count(x,pattern = "")
str_length(x)
######################
DNA <- "AgCTaaGGGcctTagct"
## 字符翻譯:大小寫(xiě)轉(zhuǎn)換
tolower(DNA)
toupper(DNA)
## 字符翻譯:符號(hào)替換(逐個(gè)替換)
# chartr
chartr("Tt", "Uu", DNA) #將T堿基替換成U堿基
# 注意:與str_replace()的區(qū)別
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")
字符串的查詢(xún)或者搜索應(yīng)用了正則表達(dá)式的匹配來(lái)完成任務(wù). R Base 包含的字符串查詢(xún)相關(guān)的函數(shù)有g(shù)rep(),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()能夠提供匹配替換的功能,但其替換的實(shí)質(zhì)是先創(chuàng)建一個(gè)對(duì)象,然后對(duì)原始對(duì)象進(jìn)行重新賦值,最后結(jié)果好像是“替換”了一樣。
sub()和gsub()的區(qū)別在于,前者只替換第一次匹配的字串(請(qǐng)注意輸出結(jié)果中world的首字母),而后者會(huì)替換掉所有匹配的字串。
也可以使用substr和substring對(duì)指定位置進(jìn)行替換。
#####################################
## 匹配替換
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 = "-")
##########################################
## 指定位置替換
常用到的提取函數(shù)有substr()和substring(),它們都是靠位置來(lái)進(jìn)行提取的,它們自身并不適用正則表達(dá)式,但是它們可以結(jié)合正則表達(dá)式函數(shù)regexpr(),gregexpr()和regexec()等可以方便地從文本中提取所需信息。
stringr
包中的函數(shù)str_sub
和str_dup
可以通過(guò)位置提取,而str_extract
和str_match
可以通過(guò)正則表達(dá)式提取。
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("#圍棋人機(jī)大戰(zhàn)# 【人工智能攻克圍棋 AlphaGo三比零完勝李世石】","谷歌人工智能AlphaGo與韓國(guó)棋手李世石今日進(jìn)行了第三場(chǎng)較量","最終AlphaGo戰(zhàn)勝李世石,連續(xù)取得三場(chǎng)勝利。接下來(lái)兩場(chǎng)將淪為李世石的“榮譽(yù)之戰(zhàn)。")
# 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]+")
這個(gè)內(nèi)容有點(diǎn)類(lèi)似于字符串的連接。R中相應(yīng)的函數(shù)為strtrim(),用于將字符串修剪到特定的顯示寬度。stringr
中相應(yīng)的函數(shù)為:str_pad().
strtrim()會(huì)根據(jù)width參數(shù)提供的數(shù)字來(lái)修剪字符串,若width提供的數(shù)字大于字符串的字符數(shù)的話,則該字符串會(huì)保持原樣,不會(huì)增加空格之類(lèi)的東西,若小于,則刪除部分字符。而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()會(huì)把字符串當(dāng)成一個(gè)段落來(lái)處理(不管段落中是否有換行),按照段落的格式進(jìn)行縮進(jìn)和分行,返回結(jié)果就是一行行的字符串。
而str_wrap()不對(duì)文本直接切割成向量,而是在文本內(nèi)容中插入了縮進(jìn)或分行的標(biāo)識(shí)符。
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))
windows下處理字符串類(lèi)型數(shù)據(jù)最頭疼的無(wú)疑是編碼問(wèn)題了。這里介紹幾個(gè)編碼轉(zhuǎn)換相關(guān)的函數(shù)。
函數(shù) | 功能說(shuō)明 |
---|---|
iconv() |
轉(zhuǎn)換編碼格式 |
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 |
雖然查看編碼方式已經(jīng)有Encoding()
函數(shù),但是這個(gè)函數(shù)往往在很多時(shí)候都不靈,經(jīng)常返回惱人的“Unknow”。而火狐瀏覽器進(jìn)行網(wǎng)頁(yè)文本編碼識(shí)別的一個(gè) c++ 庫(kù)universalchardet ,可以識(shí)別的編碼種類(lèi)較多。文鋒寫(xiě)了一個(gè)相應(yīng)的R包接口,專(zhuān)用于文件編碼方式檢測(cè),具體請(qǐng)參考:checkenc - 自動(dòng)文本編碼識(shí)別
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")
最后,給大家展示一個(gè)小小的爬蟲(chóng)案例:爬取豆瓣T250中的電影信息進(jìn)行分析。這里出于練習(xí)的目的刻意使用了字符串處理函數(shù),在實(shí)際的爬蟲(chóng)中,有更方便快捷的實(shí)現(xiàn)方式。
本案例改編自肖凱老師的博客在R語(yǔ)言中使用正則表達(dá)式,原博客使用R Base中的函數(shù)進(jìn)行處理字符串,這里已經(jīng)全部更改為stringr
中的函數(shù)進(jìn)行處理。
library(stringr)
library(dplyr)
url <-'http://movie.douban.com/top250?format=text'
# 獲取網(wǎng)頁(yè)原代碼,以行的形式存放在web變量中
setInternet2()
web <- readLines(url,encoding="UTF-8")
# 找到包含電影名稱(chēng)的行
name<-str_extract_all(string = web, pattern = '<span class="title">.+</span>')
movie.names_line <- unlist(name)
# 用正則表達(dá)式來(lái)提取電影名
movie.names <- str_extract(string = movie.names_line, pattern = ">[^&].+<") %>% str_replace_all(string = ., pattern = ">|<",replacement = "")
movie.names<- na.omit(movie.names)
# 獲取評(píng)價(jià)人數(shù)
Rating<- str_extract_all(string = web,pattern = '<span>[:digit:]+人評(píng)價(jià)</span>')
Rating.num_line<-unlist(Rating)
Rating.num<- str_extract(string = Rating.num_line, pattern = "[:digit:]+") %>% as.numeric(.)
#獲取評(píng)價(jià)分?jǐn)?shù)
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(.)
# 數(shù)據(jù)合并
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)
『Handling and Processing Strings in R』
『Automated Data Collection in R』
|