R語言正則表達式簡介及實例演示

2021-03-01 荷蘭心理統計聯盟

正則表達式(regular expression,以下簡稱正則)是指用抽象的字符串來描述匹配文本中特定的結構,以達到提取結構化數據的目的。許多程式語言(如R,python,C++ ,JS等等),甚至連大家熟悉的excel 都支持正則。學習正則能夠幫助數據分析者更高效的從非結構化的文本中提取信息,從而高效的解決實踐中遇到的各種與文本相關的數據問題。本文將總結正則能處理的各類問題,同時介紹在R語言中如何使用正則,並給出實例演示。最後給出一些在R語言中使用正則的tips & tricks。

我們在日常的數據分析中,可能會遇到如下問題 如:

請對下列名人名言中出現的數字(不包括中文數字)求和。                                   

所以你需要提取的數字有 30、90、80、200和69。

如果僅僅只有這一個文本,手動倒也無可厚非。但是在處理研究數據的實踐中,你可能要面對許多這樣的非結構化文本。如果全部手動,不僅會耗費很大的時間和精力,而且容易出錯。

再如,有時候數據上的小數點是逗號,而我想要把逗號都改成」.」 ,否則excel可能把逗號當成分割符,數據就出錯了。

期望的數據:

實際的數據:

同理,如果數據點非常多,則很難保證不犯錯誤。

上述問題如果使用正則就會變得非常簡單。

此外,如果你想系統學習文本分析,以及文本分析衍生出的內容分析,情感分析等等方法,正則幾乎是必學內容之一。另外,正則也是網絡爬蟲的基礎技能之一。

下面我將具體介紹如何在R語言中使用正則。

R語言中有許多可以和正則配合使用的函數,本文主要介紹stringr包中的str_extract_all 和str_replace功能 (實際上stringr包中有許多能夠實現字符抓取,替換,定位和計量的功能,但是這些函數都要通過正則來實現)。關於stringr包的詳情見:https://stringr.tidyverse.org/

2.1  基本字符串匹配

首先是最基本的字符匹配,比如我們想看看在之前提到的名人名言中出現了多少個中文「一」字。我們只需要先寫好正則,再使用stringr包中的str_extract_all 提取,然後使用unlist() 把列表拆解為向量,然後使用length得出向量長度即可。具體代碼如下:

text <- readLines("text1.txt",encoding = 'UTF-8')ming_ren_ming_yan <- stringr::str_extract_all(text,"一") %>% unlist() %>% length()

(註:讀中文需要設置編碼為『UTF-8』,另外這裡使用了「%>%」管道函數,不熟悉的同學可以參考此前公眾號發布的R相關文章)

這種簡單的字符匹配同樣適用於其他語言(比如英文)。

2.2  特殊字符與元字符

2.2.1  特殊字符與元字符簡介

在實際的數據分析中,人們常常不需要抓取給定的字符(比如給定的漢字或者英文),而是需要抓取一系列有意義的字符串。比如前文提到的問題 1(找出數字並求和),在這種情況下,我們需要藉助正則表達式的特殊字符。

所謂特殊字符,即能夠表示一系列有意義字符串的字符。比如下圖中的\d 可以代表任意數字字符(和「[:digit:]」同義)。不同的特殊字符相互組合可以靈活匹配文本內容。

而元字符則指不直接表示自身含義而用來指代其他特殊含義的字符,在正則中一共有12個元字符,它們分別是(左括弧和右括弧單獨算一個符號):

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

其具體指代的含義如下:

[]  表示字符的集合,用於匹配方括號中的其中一個字符

\  特殊標記符,可以將元字符轉義為普通字符

^ 匹配字符串開始的位置

$  匹配字符串結束的位置

.   匹配除了換行符之外的任意字符1次

|   x或y 取其中一個字符

?   貪婪模式和懶惰模式轉換符,模糊匹配轉換符,或者用於零寬斷言判斷(見下文)

*  匹配該符號前的字符任意次

+   匹配該符號前的字符1到多次

()   分組符號(見下文)


之後的實例將介紹其中一些元字符的用法,其他元字符的用法可以參考:

http://yphuang.github.io/blog/2016/03/15/regular-expression-and-strings-processing-in-R/   進行進一步學習。

下列表格總結了R中特殊字符的用法:

R語言中的特殊字符(table 8-1 & table 8-3)總結

(上述表格摘抄自:Automated Data Collection with R Chapter 8)

 

接下來我們使用元字符來回答名人名言求和的問題。

首先,需要構建正則,我們需要提取數字,所以輸入數字的特殊字符(注,在R中使用特殊字符需要在特殊字符前多加一個「\」)。代碼如下:

這裡「\\d」 表示匹配數字字符,後跟「+」 表示至少匹配一個數字字符(也就是個位數),所以連起來的意思是匹配整數數字(包括十位,百位,千位數等等,但不包括小數,因為沒有匹配小數點)。

這裡同樣可以用上述表格中提到的「[:digit:]「來構建正則,代碼如下:

接下來使用str_extract_all函數來提取數字(註:text 為已經讀入R的名人名言),代碼如下:

numbers <- stringr::str_extract_all(text,pattern) %>% unlist()

然後再使用purrr包中的reduce一鍵求和,代碼如下(這裡由於提取的是字符,所以需要轉換為數字):

purrr::reduce(as.numeric(numbers),sum)

所以求和的結果應該是:

2.2.2  匹配元字符本身

匹配元字符本身這種情況比較少出現,但是不排除這種情況會出現。如果需要匹配元字符,比如匹配問號」?」,則需要在斜槓前增加轉義符(也就是兩個斜槓,轉義符的意思是把元字符變為普通字符),所以正則寫出來是這樣:

Pattern <- 「\\?」

比如從下列字符中匹配問號(註:中文的問號和英文問號編碼不同,故中文問號不是元字符,可以不加轉義符直接匹配,這裡出於演示目的使用英文問號):

代碼如下:

a <- 「這好嗎?這不好。」stringr::str_extract_all(a,Pattern) %>% unlist()

結果如下:

2.3  正則表達式進階

2.3.1  懶惰模式與模糊匹配

R語言正則默認是貪婪匹配模式,意思是正則會匹配儘量長的字符串。通過添加一個問號「?」 就可以將貪婪模式改為懶惰模式,即提取滿足正則的最短的字符串 。需要注意的是,關閉貪婪模式只能在「*」,「+」和「{n}」 等匹配次數限制符號之後加「?」,懶惰模式通常和後文提到的零寬斷言一起使用,用來匹配第一次出現xx字符之前的所有內容。例子請參考零寬斷言的實例。若問號 「?」 出現在了非次數限制符號之後,則表示模糊匹配,意為可以與問號「?」(英文問號)前的字符匹配也可不匹配。實例如下:

Pattern <- "年輕?" stringr::str_extract_all(text,Pattern) %>% unlist()

所以上述代碼可能匹配「年輕」也可能匹配「年」。

抓取結果如下:

2.3.2  分組與引用

元字符中有一些可以用來重複單個字符,比如「+」,「{}」 等等,但如果要重複多個字符組,就需要使用分組了。在正則中,分組可以使用 「()」 將一組正則符號括起來,這樣就可以進一步使用「+」 和「{}」等符號來匹配若干個「組」了(所以括號也是元字符,如果要匹配括弧,也需要使用轉義符)。在進行分組之後,你還可以對括號分組的正則進行引用。這一方法通常用來修改抓取的字符串上。具體實例如下

比如我們截取上述名人名言裡兩個年輕人對馬大師使用的前兩招:

前兩招在名人名言中還是很直觀的:

正則如下:

Pattern <- "(一個[左|右]\\S{0,1}[拳|腿|蹬])(,)"

這裡「[左|右]」表示字符「左」或者字符「右」,「\\S」表示非空格任意字符,連上「{0,1}」表示「\\S」所佔的字符位置可能沒有字符,也可能有一個非空格字符。然後使用str_replace 分別配上解說:

stringr::str_replace("一個左正蹬,一個右鞭腿",  pattern=Pattern,  replacement = "(第一招)\\1  \\2 (第二招)")

結果如下:

這裡的replacement 參數中的\\1 和\\2 即是對分組結果的引用。\\1表示第一個括號內正則抓取的內容。

 

2.3.3  零寬斷言(Zero-Length Assertions)

在實際進行數據提取時,也會常常遇到這種情況,即需要提取的字符串本身沒有明顯的規律,但是在這些字符串前面或者後面出現的字符串卻有規律。這樣我們就可以通過正則添加限定條件,即匹配這些有規律字符之後或者之前的字符,來完成字符匹配。這裡正則僅僅是佔了一個位置,並不直接匹配目標內容, 故稱為零寬斷言。

具體用法為:

匹配某某字符串之後的內容:(?<= 某某字符串)

匹配某某字符串之前的內容:(?= 某某字符串)

此外,我們還可以加入否定邏輯,具體用法為:

匹配非某某字符串之後的內容:(?<!某某字符串)

匹配非某某字符串之前的內容:(?! 某某字符串)

比如我們想把APA格式中出現的文章標題全部提取出來。

文章標題相對而言並沒有什麼規律可言,空格,數字,英文字母都可能出現。但是,APA格式的文章標題卻都出現在「(2020)」 這樣的年份之後,並且以標點符號(一般為「.」 或者「?」)結尾。這種規律使得我們可以通過零寬斷言來提取標題。

比如我們需要從下列10個APA格式的參考文獻中提取標題。

首先讀入數據:

papers1 <- readLines("paper.txt")

正則如下:

pattern2 <- "(?<=\\d{4}\\)\\.\\s)(.+?)(?=[\\.|\\?])"

解讀:該正則分為三個組

第一組:表示4個數字後有一個反括號(年份及反括號,注意轉義符「\\」)然後接一個句點(注意轉義符「\\」)和一個空格「\\s」 。這一組正則的意義如下:

提取」 任意4位數字).空格「後面的字符

第二組:「(.+?)」問號出現在限制元字符之後,表示懶惰模式,匹配除回車鍵換行以外的任意字符(就是標題)。但因為開了懶惰模式,所以還要看第三組正則表達的內容才能決定匹配的字符的長度。

第三組:首先是一組零寬斷言,表示匹配句點「.」或者「?「之前的內容(即文章標題結尾的標點符號)

 

所以整體上來看,這一組正則表達的意思是:

提取」任意4位數字). 「後面,且第一個」.」或者「?」之前的除了回車鍵換行之外的所有的字符,也就是標題了。

 提取的代碼如下:

str_extract_all(papers1,pattern = pattern2) %>% unlist()

 結果如下:

3.1  使用Rebus 包

可能各位看官也注意到了,正則的一大缺點就是可讀性實在感人。如果不加注釋,可能幾天之後就完全忘了之前寫的正則是什麼意思了。這樣給後續的修改工作帶來了極大的麻煩。為了讓人更好理解正則表達的意思,有人將正則表達式的元字符和特殊字符全部函數化(也就是變成了R語言的一個function)也就造就了Rebus 包。Rebus包可以通過install.packages或者在Rstudio中的package面板中點擊install,找到Rebus之後直接進行安裝。

Rebus和正則的具體教程(取自datacamp 的官方slides)如下:

連結:https://pan.baidu.com/s/1SX7FbC57PhFScObc73OwdQ

提取碼:hzwz

 

但是,Rebus包也有一些缺點,比如函數太繁雜,掌握這些函數的用法非常花時間等等。

3.2  使用glue 包

相對於Rebus, 我更推薦使用glue包來編寫正則。glue包提供了關於粘貼和注釋字符的強大解決方案,實際上許多R包的底層代碼中(特別是要輸出某些內容時)都會用到glue包。glue包的其他用法見:

https://github.com/tidyverse/glue

本文接下來主要講述如何配合glue包使用正則。

以剛才的APA格式抽取文章標題為例,使用glue包再寫一遍正則,代碼如下:

pattern2 <- glue::glue_collapse(c(  "year"="(?<=\\d{4}\\)\\.\\s)",  "title"="(.+?)",  "punctuation_once"="(?=[\\.|\\?])"))

這裡主要用到了glue_collapse這個函數,它能同時把正則拆分成了若干個向量值,每個向量值都有注釋,比如正則的第一個組表示匹配「year」 (可以自己決定寫什麼樣的注釋)。然後再把這個向量拼起來成一個完整的正則。

然後我們使用str_extract_all 再看一下提取結果:

str_extract_all(papers1,pattern = pattern2) %>% unlist()

由上例可以看出,通過使用glue包,能更加清楚的標明正則每個部分的功能,方便後續修改和閱讀。

3.3 tidyverse 全家桶中與正則相關的函數

其實大家平時常用的tidyverse全家桶中就有許多封裝了正則相關的函數。

比如常用的dplyr包中的filter,select就可以通過str_detect,start_with,end_with等函數作為參數來選取特定內容,詳情見:

https://www.codenong.com/22850026/

再如,tidyr包中的unite 和separate 函數,它們可以通過正則捕捉數據中出現的特定字符串,並按照這些字符串將多列數據合併為一列,或者將一列數據分裂為多列。詳情見:

https://tidyr.tidyverse.org/reference/unite.html

歡迎大家在評論區補充其他類似的函數!

 

無論是專門從事文本類的數據分析,還是其他類型的數據分析,實踐中經常會有從不規則的文本中提取結構化數據的需求。而正則可以專門用來應對這樣的需求,而且還是可遷移的知識(幾乎每種程式語言都包含正則)。總之一句話,學了不虧。

 

參考文獻:

Munzert, S., Rubba, C., Meißner, P., & Nyhuis, D. (2014). Automated data collection with R: A practical guide to web scraping and text mining. John Wiley & Sons.

獲取名人名言和APA格式提取樣例

請在後臺回復(hzwz)

相關焦點

  • 乾貨收藏 R語言之正則表達式
    正則表達式表通常被用來檢索、替換那些符合某個模式(規則)的文本。在我看來,正則表達式的主要用途有兩種:①查找特定的信息②查找並編輯特定的信息,也就是我們經常用的替換。。比如我們要在Word,記事本等裡面使用快捷鍵Ctrl+F,進行查找一個特定的字符,或者替換一個字符,這就使用了正則表達式。
  • Python中的正則表達式及其常用匹配函數用法簡介
    Python正則表達式初識(七)8. Python正則表達式初識(八)9. Python正則表達式初識(九)10. Python正則表達式初識(十)附正則表達式總結11.Python正則表達式的簡單應用和示例演示    這次給大家主要是介紹Python中的正則表達式,及其相關函數的基本使用方法,並且捎帶一些正則表達式給我們帶來的便利。/2 簡介/    Python 自1.5版本起增加了re 模塊,它提供 Perl風格的正則表達式模式。
  • Python正則表達式急速入門
    在閱讀這篇文章前你需要掌握 Python 基礎知識,或者具有其他開發語言的基礎知識也可以,因為基本上每種語言使用正則表達式的方式都是類似的。零、正則表達式基礎1.提取字符(串)有時我們需要從一個字符串中獲取一段內容,這段內容可能是一個字符也可能是一段字符串,如果用逐字對比遍歷的話不僅耗時耗力而且還容易出錯。那麼這個時候我們就可以用到正則表達式中的 字符匹配功能。
  • Java的正則表達式和捕獲組
    正則表達式定義了字符串的模式。正則表達式可以用來搜索、編輯或處理文本。正則表達式並不僅限於某一種語言,在Java、JavaScript等語言都存在,但是在每種語言中有細微的差別。Java正則表達式正則表達式實例一個字符串其實就是一個簡單的正則表達式,例如 Hello World正則表達式匹配 "Hello World" 字符串。.
  • java正則表達式入坑指南
    在日常開發工作中,無論你使用的語言是java、python、shell、golang還是C#, 正則表達式是程式語言中幾乎繞不開的話題。有了它,可以幫你快速定位到符合條件的文本內容。今天小編帶大家一起來學習下正則表達式,相信通過這篇文章的介紹,能為以後的工作提供一個更清晰的思路。
  • 看完你就會正則表達式了
    最近看了一篇關於正則表達式的學習筆記,覺得講的非常好,更有圖形化的神器相助,想不學會都難,所以想轉給大家看看。話說不是開發為啥要學正則表達式這種看似很晦澀的東西呢,因為現在很多搜索的場景都是支持正則表達式的,學會了正則表達式就有如一把利劍在手。本文較長,建議抽40分鐘完整的時間一次讀完再慢慢消化。
  • 大數據挖掘—(九):爬蟲利器 _正則表達式
    Python 正則表達式  re 模塊使 Python 語言擁有全部的正則表達式功能。  compile 函數根據一個模式字符串和可選的標誌參數生成一個正則表達式對象。num is: 086-0532-8888the phone num is : 08605328888re.compile 函數  compile 函數用於編譯正則表達式,生成一個正則表達式( Pattern )對象,供 match() 和 search() 這兩個函數使用。
  • Python 正則表達式
    正則表達式(regular expression)是可以匹配文本片段的模式。最簡單的正則表達式就是普通字符串,可以匹配其自身。比如,正則表達式 『hello』 可以匹配字符串 『hello』。要注意的是,正則表達式並不是一個程序,而是用於處理字符串的一種模式,如果你想用它來處理字符串,就必須使用支持正則表達式的工具,比如 Linux 中的 awk, sed, grep,或者程式語言 Perl, Python, Java 等等。
  • python正則表達式
    就是說, 不論這段文本在不在, 正則表達式都會認為匹配。 75字符?表明它前面的分組在這個模式中是可選的。 76''' 77batregex = re.compile(r"Bat(wo)?例如,正則表達式(Ha){3,5}將匹配'HaHaHa'、 'HaHaHaHa'和119'HaHaHaHaHa'。也可以不寫花括號中的第一個或第二個數字, 不限定最小值或最大值。例如,120(Ha){3,}將匹配 3 次或更多次實例, (Ha){,5}將匹配 0 到 5 次實例。
  • Python正則表達式總結
    正則表達式 的起源、發展、流派、語法、引擎、優化等相關知識,今天我們主要來學習一下 正則表達式在 Python語言 中的應用!大多數程式語言的正則表達式設計都師從Perl,所以語法基本相似,不同的是每種語言都有自己的函數去支持正則,今天我們就來學習 Python中關於 正則表達式的函數。re模塊主要定義了9個常量、12個函數、1個異常,每個常量和函數豬哥都會通過實際代碼案例講解,讓大家能更直觀的了解其作用!註:為避免出現代碼格式錯亂,豬哥儘量使用代碼截圖演示哦。
  • 正則表達式
    在我看來,正則表達式的主要用途有兩種:①查找特定的信息②查找並編輯特定的信息,也就是我們經常用的替換。。比如我們要在Word,記事本等裡面使用快捷鍵Ctrl+F,進行查找一個特定的字符,或者替換一個字符,這就使用了正則表達式。         正則表達式的功能非常強大,尤其是在文本數據進行處理中顯得更加突出。
  • Perl教程 - 正則表達式
    ,它與Tcl等語言中的正則表達式有一定的相似之處,因此如果學習過相關正則表達式的話這個不會很難。Perl的正則表達式中如果出現(),則發生匹配或替換後()內的模式被Perl解釋器自動依次賦給系統$1,$2.
  • Python中的正則表達式
    正則表達式基本語法下面我們通過下表來學習一下正則表達式中的元字符和含義:*注意事項:(1)數量詞的貪婪模式與非貪婪模式正則表達式通常用於在文本中查找匹配的字符串。Python裡數量詞默認是貪婪的(在少數語言裡也可能是默認非貪婪),總是嘗試匹配儘可能多的字符;非貪婪的則相反,總是嘗試匹配儘可能少的字符。
  • 正則表達式語法
    原子是正則表達式中最基本的組成單位,每個正則表達式中至少包含一個原子。
  • 5分鐘完全掌握正則表達式
    什麼是正則表達式正則表達式(regular expression)描述了一種字符串匹配的模式(pattern),聽起來確實不是很好理解。我們從這個定義中抽出三個關鍵詞:模式:模式其實就是規則,這就是正則表達式的核心,這裡的規則是人為定義好的,可以是字符,數字和字母。
  • Python正則表達式,這一篇就夠了!
    之前我們講解了 正則表達式 的起源、發展、流派、語法、引擎、優化等相關知識,今天我們主要來學習一下 正則表達式在 Python語言 中的應用
  • 還不會正則?
    不知道別的語言是如何使用正則表達式的,在Python中需要通過正則表達式對字符串進行匹配的時候,直接使用Python的內置模塊re即可。下面通過一些簡單的實例來看幾個用法,這一部分很簡單,剩餘的就不做過多的演示,大家可以參考上邊的表格自己去實操一下import re# 匹配任意一個字符串In [9]: ret = re.match("l.h","lph")In [10]: ret.group()Out[10]: 'lph'
  • Python正則表達式入門到入魔
    正則表達式被作為用來描述其稱之為「正則集的代數」的一種表達式,因而採用了「正則表達式」這個術語。3、20世紀60年代C語言之父、UNIX之父肯·湯普森把這個「正則表達式」的理論成果用於做一些搜索算法的研究,他描述了一種正則表達式的編譯器,於是出現了應該算是最早的正則表達式的編譯器qed(這也就成為後來的grep編輯器)。
  • Python正則表達式實操!
    上篇「正則表達式入門」我們講到元字符. ^ $ * + ? { } [ ] \ | ( ),以及各種結構:字符組、多選結構、反向引用等等,但沒有涉及代碼實操。為了讓廣大Python愛好者使用正則表達式時得心應手,今天鴿婆奉上re模塊的知識集錦!
  • 代碼詳解:Python正則表達式的終極使用指南
    ----------------------------19本文討論的是最常用的正則表達式模式,以及一些經常使用的正則表達式函數。4.加號和星形運算符點算符只是用於獲取任何字符的單個實例。如果想找出更多實例要怎麼做呢?加號+用於表示最左邊字符的一個或多個實例。星號*用於表示最左邊字符的0個或多個實例。