R语言中正则表达式
内容概览
有时候我们要处理的是非结构化的数据,例如网页或是电邮资料,那么就需要用R来抓取所需的字符串,整理为进一步处理的数据形式。R语言中有一整套可以用来处理字符的函数,在之前的 博文 中已经有所涉及。但真正的要用好字符处理函数,则不得不用到正则表达式。 正则表达式(Regular Expression、regexp) 是指一种用来描述一定数量文本的模式。熟练掌握正则表达式能使你随心所欲的操作文本来达成目标。其实学习正则表达式并没有想像中的那么困难。最好方法是从例子开始,然后多练习,多使用。网络上已经有许多不错的参考资料,例如 这篇 或 那篇 。本文假设你对正则表达式有了基本的了解,下面我们来看看如何在R里面来使用它。
R语言处理文本的能力虽然不强,但适当用用还是可以大幅提高工作效率的,而且有些文本操作还不得不用。高效处理文本少不了正则表达式(regular expression),虽然R在这方面先天不高效,但它处理字符串的绝大多数函数都使用正则表达式。
-
正则表达式简介:
正则表达式不是R的专属内容,这里也只简单介绍,更详细的内容请查阅其他文章。正则表达式是用于描述/匹配一个文本集合的表达式。
1. 所有英文字母、数字和很多可显示的字符本身就是正则表达式,用于匹配它们自己。比如 \'a\' 就是匹配字母 \'a\' 的正则表达式 2. 一些特殊的字符在正则表达式中不在用来描述它自身,它们在正则表达式中已经被“转义”,这些字符称为“元字符”。perl类型的正则表达式中被转义的字符有:. \ | ( ) [ ] { } ^ $ * + ?。被转义的字符已经有特殊的意义,如点号 . 表示任意字符;方括号表示选择方括号中的任意一个(如[a-z] 表示任意一个小写字符);^ 放在表达式开始出表示匹配文本开始位置,放在方括号内开始处表示非方括号内的任一字符;大括号表示前面的字符或表达式的重复次数;| 表示可选项,即 | 前后的表达式任选一个。 3. 如果要在正则表达式中表示元字符本身,比如我就要在文本中查找问号‘?’, 那么就要使用引用符号(或称换码符号),一般是反斜杠 \'\\'。需要注意的是,在R语言中得用两个反斜杠即 ‘\\’,如要匹配括号就要写成 ’\\(\\)‘ 4. 不同语言或应用程序(事实上很多规则都通用)定义了一些特殊的元字符用于表示某类字符,如 \d 表示数字0-9, \D 表示非数字,\s 表示空白字符(包括空格、制表符、换行符等),\S 表示非空白字符,\w 表示字(字母和数字),\W 表示非字,\< 和 \> 分别表示以空白字符开始和结束的文本。 5. 正则表达式符号运算顺序:圆括号括起来的表达式最优先,然后是表示重复次数的操作(即:* + {} ),接下来是连接运算(其实就是几个字符放在一起,如abc),最后是表示可选项的运算(|)。所以 \'foot|bar\' 可以匹配’foot‘或者’bar‘,但是 \'foot|ba{2}r\'匹配的是’foot‘或者’baar‘。
-
关于正则表达式
正则表达式是编程语言的特色,也是一大难点,几乎各类编程语言都有数据自己的正则表达式,但方法都大同小异,R里面有关正则表达式里面选择采用具有特色的perl语言风格的正则表达式。掌握好这些正则表达式的使用方法,就可以轻易地完成字符串处理任务。正则表达式,又称正规表示法、常规表示法,它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。下面列出正则表达式常用模式:
[0-9] 匹配所有数字字符 [^0-9] 匹配所有非数字字符 [^a-z] 匹配所有非小写字母字符 ^ 匹配字符开头的字符 $ 匹配字符结尾的字符 \d 匹配一个数字的字符 \d+ 匹配多个数字字符串 \D 非数字,其他同 \d \w 英文字母或数字的字符串,和 [a-zA-Z0-9] 语法一样 \W 非英文字母或数字的字符串 \s 空格 \S 非空格 \b 匹配以英文字母,数字为边界的字符串 \B 匹配不以英文字母,数值为边界的字符串 . 匹配除换行符以外的所有单个字符 […] 字符组 匹配单个列出的字符 x? 匹配 0 次或一次 x 字符串 x* 匹配 0 次或多次 x 字符串,但匹配可能的最少次数 x+ 匹配 1 次或多次 x 字符串,但匹配可能的最少次数 .* 匹配 0 次或一次的任何字符 .+ 匹配 1 次或多次的任何字符 {m} 匹配刚好是 m 个 的指定字符串 {m,n} 匹配在 m个 以上 n个 以下 的指定字符串 {m,} 匹配 m个 以上 的指定字符串 [] 匹配符合 [] 内的字符 [^] 匹配不符合 [] 内的字符
-
常用表达式语法结构
grep, grepl, regexpr, gregexpr 函数在字符串向量中寻找特定的匹配模式pattern,具体区别在参数的选择。sub, gsub 分别用于替换单个或者全部的匹配模式,这里g意味着global。
详细语法: 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) 参数说明: pattern : 用于匹配的正则表达式。只接受一个元素。 x, text : 被匹配的字符串(向量)。 ignore.case : 是否大小写敏感,默认为FALSE perl : 是否兼容Perl语言的正则表达式,默认FALSE value : 是否返回匹配的值。默认FALSE,那么将会返回向量的索引indice; 如果为TRUE,则返回被匹配的字符串 fixed : 如果TRUE,则匹配整个元素。默认FALSE useBytes : 是否使用byte-by-byte还是character-by-character,默认FALSE invert : 是否取反,如果TRUE,则返回未匹配的索引indices或值values replacement : 适用于sub和gsub。只接受一个元素。 grep(value = FALSE) 返回匹配向量的索引(若invert = TRUE,情况相反) grep(value = TRUE) 返回匹配向量的原始值 grepl 返回布尔向量,包含是否匹配的信息 sub 和 gsub 返回和原先向量同样长度的新向量. regexpr 返回一个和初始向量text长度保持一致的向量数组,元素为第一次匹配的起点位置(如果没有匹配成功则显示-1)同时还附有匹配长度(匹配显示匹配长度,否则显示-1),如果想计算bytes的长度,请使用 useBytes = TRUE gregexpr和regexec 均返回一个list列表,经测试内容和regexpr保持一致,但不知其具体区别,望告知
-
Example_1
假设我们有一个字符向量,包括了三个字符串。我们的目标是从中抽取电邮地址。R语言中很多字符函数都能识别正则表达式,而最重要的函数就是gregexpr()。该函数的第一个参数是正则表达式,前后需要用引号,对元字符进行转义时要用\。第二个参数是等待处理的文本。那么用如下三行代码,我们从word字符向量中得到一个列表,其中第一项元素中的5表示电邮地址从第5个字符位置开始,24表示电邮地址长度为24。
> word <- c(\'abc [email protected]\',\'text with no email\',\'first [email protected] also [email protected]\') > pattern <- \'[-A-Za-z0-9_.%]+@[-A-Za-z0-9_.%]+\\.[A-Za-z]+\' > pattern [1] "[-A-Za-z0-9_.%]+@[-A-Za-z0-9_.%]+\\.[A-Za-z]+" > (gregout <- gregexpr(pattern,word)) [[1]] [1] 5 attr(,"match.length") [1] 24 attr(,"useBytes") [1] TRUE [[2]] [1] -1 attr(,"match.length") [1] -1 attr(,"useBytes") [1] TRUE [[3]] [1] 7 27 attr(,"match.length") [1] 14 17 attr(,"useBytes") [1] TRUE >
下一步我们需要将电邮地址抽取出来,此时配合substr函数,即可根据需要字符串的位置来提取子集。
> substr(word[1],gregout[[1]],gregout[[1]]+attr(gregout[[1]],\'match.length\')-1) [1] "[email protected]" >
更方便的使用方式是根据上述方法建立一个自定义函数getcontent,参数s表示待处理的文本,参数g表示的是通过gregexpr函数处理后的结果。这个函数我们在后面还会用到。
> getcontent <- function(s,g){ + substring(s,g,g+attr(g,\'match.length\')-1) + } > getcontent(word[1],gregout[[1]]) [1] "[email protected]" > getcontent(word[2],gregout[[2]]) [1] "" > getcontent(word[3],gregout[[3]]) [1] "[email protected]" "[email protected]" >
-
Example_2
regexpr、gregexpr和regexec 这三个函数返回的结果包含了匹配的具体位置和字符串长度信息,可以用于字符串的提取操作。用函数获得位置信息后再进行字符串提取的操作可以自己试试看。
> text <- c("Hellow, Adam!", "Hi, Adam!", "How are you, Adam.") > regexpr("Adam", text) [1] 9 5 14 attr(,"match.length") [1] 4 4 4 attr(,"useBytes") [1] TRUE > gregexpr("Adam", text) [[1]] [1] 9 attr(,"match.length") [1] 4 attr(,"useBytes") [1] TRUE [[2]] [1] 5 attr(,"match.length") [1] 4 attr(,"useBytes") [1] TRUE [[3]] [1] 14 attr(,"match.length") [1] 4 attr(,"useBytes") [1] TRUE > regexec("Adam", text) [[1]] [1] 9 attr(,"match.length") [1] 4 [[2]] [1] 5 attr(,"match.length") [1] 4 [[3]] [1] 14 attr(,"match.length") [1] 4
-
Example_3
这里出现的replacement参数,在x中搜索pattern,并以文本replacement将其替换;其他的各个参数和grep的作用相同。
> p="Who wins the prize?" > sub("Who",replacement="James",sub("[\\?]","!",p,perl=T)) [1] "James wins the prize!"
gsub和sub函数的不同之处在于sub函数只替换其匹配文本中第一次出现的匹配,而gsub为globe sub全局匹配替换,即替换匹配到的所有匹配值。
> txt <- "a test of capitalizing" > gsub("(\\w)(\\w*)", "\\U\\1\\L\\2", txt, perl=TRUE) [1] "A Test Of Capitalizing" > gsub("\\b(\\w)", "\\U\\1", txt, perl = T) [1] "A Test Of Capitalizing"
perl正则表达式中,\为转义符,\w为元字符,匹配数字型的字符;* 为匹配0个或多个x,\U:大写字符,直到字符串末尾或碰到\E,\L:小写字符,直到字符串末尾或碰到\E,\b:匹配以英文字母,数字为边界的字符串, \1,\2分别为匹配第一组括号和第二组括号。使用以上的gsub替换,可以将文本每个单词的首字母大写。
regexpr,gregexpr,regexec,它们可以返回和text相同长度的向量,包括不匹配的值
> m=c("e","the","end","of","line") > regexpr("e",m) [1] 1 3 1 -1 4 attr(,"match.length") [1] 1 1 1 -1 1 attr(,"useBytes") [1] TRUE
-
Example_4
从regexpr()的返回结果看,返回结果是个整数型向量,但是它还具有两个额外的属性(attributes),分别是匹配字段的长度和是否按字节进行匹配;regexpr()的返回结果为-1和1,其中-1表示没有匹配上,1表示text中第2个元素中的第一个字符被匹配上,且匹配字符的长度为2(属性值中提供);gregexpr()的返回结果中包含了全部的匹配结果的位置信息,而regexpr()只返回了向量text里每个元素中第一个匹配的位置信息,gregexpr()的返回结果类型是list类型对象;regexec()的返回结果基本与regexpr()类似,只返回了第一个匹配的位置信息,但其结果是一个list类型的对象,并且列表里面的元素少了一个属性值,即attr(,“useBytes”)。
#### grep 和 grepl text <- c("We are the world", "we are the children") grep("We", text) #向量text中的哪些元素匹配了单词\'We\' ## [1] 1 grep("We", text, invert = T) #向量text中的哪些元素没有匹配单词\'We\' ## [1] 2 grep("we", text, ignore.case = T) #匹配时忽略大小写 ## [1] 1 2 grepl("are", text) #向量text中的每个元素是否匹配了单词\'We\',即只返回TRUE或FALSE ## [1] TRUE TRUE #### regexpr、gregexpr和regexec text <- c("We are the world", "we are the children") regexpr("e", text) ## [1] 2 2 ## attr(,"match.length") ## [1] 1 1 ## attr(,"useBytes") ## [1] TRUE class(regexpr("e", text)) ## [1] "integer" gregexpr("e", text) ## [[1]] ## [1] 2 6 10 ## attr(,"match.length") ## [1] 1 1 1 ## attr(,"useBytes") ## [1] TRUE ## ## [[2]] ## [1] 2 6 10 18 ## attr(,"match.length") ## [1] 1 1 1 1 ## attr(,"useBytes") ## [1] TRUE class(gregexpr("e", text)) ## [1] "list" regexec("e", text) ## [[1]] ## [1] 2 ## attr(,"match.length") ## [1] 1 ## ## [[2]] ## [1] 2 ## attr(,"match.length") ## [1] 1 class(regexec("e", text)) ## [1] "list"
除了上面的字符串的查询,有时还会用到完全匹配,这是会用到match(),其命令形式如下: match(x, table, nomatch= NAinteger, incomparables)只有参数x的内容被完全匹配,函数才会返回参数x所在table参数中的下标,否则的话会返回nomatch参数中定义的值(默认是NA)。
text <- c("We are the world", "we are the children", "we") match("we", text) ## [1] 3 match(2, c(3, 4, 2, 8)) ## [1] 3 match("xx", c("abc", "xxx", "xx", "xx")) #只会返回第一个完全匹配的元素的下标 ## [1] 3 match(2, c(3, 4, 2, 8, 2)) ## [1] 3 match("xx", c("abc", "xxx")) # 没有完全匹配的,因此返回NA ## [1] NA
此外还有一个charmatch(),其命令形式类似于match,但从下面的例子来看其行为有些古怪。同样该函数也会返回其匹配字符串所在table中的下标,该函数在进行匹配时,会从table里字符串的最左面(即第一个字符)开始匹配,如果起始位置没有匹配则返回NA;如果同时部分匹配和完全匹配,则会优先选择完全匹配;如果同时有多个完全匹配或者多个部分匹配时,则会返回0;如果以上三个都没有,则返回NA。另外还有一个pmatch(),其功能同charmatch()一样,仅仅写法不同。
charmatch("xx", c("abc", "xxa")) ## [1] 2 charmatch("xx", c("abc", "axx")) # 从最左面开始匹配 ## [1] NA charmatch("xx", c("xxa", "xxb")) # 不唯一 ## [1] 0 charmatch("xx", c("xxa", "xxb", "xx")) # 优先选择完全匹配,尽管有两个部分匹配 ## [1] 3 charmatch(2, c(3, 4, 2, 8)) ## [1] 3 charmatch(2, c(3, 4, 2, 8, 2)) ## [1] 0
不知道这样一个奇怪的函数在那里能够用到,真是有点期待!
-
Example_5
正则匹配是一个非常常用的字符搜索手段,在数据挖掘中起着非常重要的作用。所以虽然它是一种常规手段,但我还是另起一段来专门讲述这个概念。
在R当中,可以使用三种正则: 扩展正则 基本正则 Perl风格正则 正则的使用主要涉汲以下7个函数:grep, grepl, sub, gsub, regexpr, gregrexpr, regexec。而象strsplit, apropos以及browseEnv都是基于这7个函数基础之上的。 我们先从正则讲起。
假设我们现在需要从一堆字符当中找到一个符合一定规则的字符串,比如说从一个表格中找到所有人的email地址,或者说找到一段文字中所有的URL地址,你会如何做呢?嗯,回答肯定是正则了。正则就是做这个用的。我们知道一个email地址通常都是这样的(最简单情行),[email protected],其中,xxxxxx可能是任意字母,数字,以及下划线,点等组成,而ppp.ddd就是一个域名地址。它们之间以@相隔。在正则下是这样表示,[1]+@[A-Za-z0-9\.-]+\.[A-Za-z]{2,4}$
> pattern="^[A-Za-z0-9\\._%+-]+@[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,4}$" > str<-c("abc","[email protected]","efg","[email protected]","[email protected]") > #grepl会返回一个逻辑值,l就代表logical, g就代表global > grepl(pattern,str) [1] FALSE TRUE FALSE TRUE TRUE > #grep会返回匹配的id > grep(pattern,str) [1] 2 4 5 > #regexpr会返回一个数字,1表示匹配,-1表示不匹配,还会返回两个属性,匹配的长度以及是否使用useBytes。useBytes一般很少会使用到false,因为我们不处理宽字符。 > regexpr(pattern,str) [1] -1 1 -1 1 1 attr(,"match.length") [1] -1 20 -1 20 17 attr(,"useBytes") [1] TRUE > #regexec会返回一个list,下面的内容是第一个匹配及其长度 > regexec("\\w+@\\w+\\.[a-zA-Z]{2,4}","[email protected],[email protected]") [[1]] [1] 1 attr(,"match.length") [1] 16 > #gregexpr也会返回一个list, 下面的内容是每一个匹配及其长度以及useBytes。g就代表global > gregexpr("\\w+@\\w+\\.[a-zA-Z]{2,4}","[email protected],[email protected]") [[1]] [1] 1 18 attr(,"match.length") [1] 16 16 attr(,"useBytes") [1] TRUE > #sub和gsub都用来做正则替换,其区别只在于g所代表的global。sub只替换遇到的第一个匹配,而gsub会替换所有的匹配。 > #需要注意的是,这里的匹配都是对应的一个字符串而言的,如果是多个字符串,要区别为每一个来单独对待。 > sub("\\w+@\\w+\\.[a-zA-Z]{2,4}","sub_function","[email protected],[email protected]") [1] "sub_function,[email protected]" > gsub("\\w+@\\w+\\.[a-zA-Z]{2,4}","gsub_function","[email protected],[email protected]") [1] "gsub_function,gsub_function" </c> 正则的使用我们已经看到了,但是让人看不明白就是字符. \ | ( ) [ { ^ $ * + - ? 这些符号都是什么意思啊? 下面我就先来仔细讲讲perl中的正则符号 <pre lang="c"> . # 除了换行以外的任意字符 ^ # 一行字符串的起始,它并不代表第一个字符,只代表这里开始新的一行字符串。 $ # 一行字符串的结束,它并不代表最后一个字符(因为换行符并不会被包含进匹配当中),只代表一行字符串到这里结束。 * # 零个或者多个之前的字符 + # 一个或者多个之前的字符 ? # 零个或者一个之前的字符 # 示例部分 t.e # t后面跟任意一个非换行字符然后跟字符e # 它可以匹配 the # tre # tle # 但是不匹配 te # tale ^f # 一行字符以f起始 ^ftp # 一行字符以ftp起始 e$ # 一行字符以e结尾 tle$ # 一行字符以tle结尾 und* # un跟着零个或者多个d字符 # 它会匹配 un # und # undd # unddd (etc) .* # 任意一个无换行的字符串, # . 匹配任何一个非换行字符 # * 将匹配一个或者多个之前的字符. ^$ # 一个空行. # 在正则中有方括号[],代表可以匹配其中任何一个字符。而^在[]中就有了新的意义,代表“非”, -代表了“之间” [qjk] # q,j,k中任意一个字符 [^qjk] # 非q,j,k的任意其它字符 [a-z] # a至z中任意一个小写字符 [^a-z] # 非任意一个a至z小写字符的其它字符(可以是大写字符) [a-zA-Z] # 任意一个英文字母 [a-z]+ # 一个或者多个小写英文字母 # |代表或者 小括号(...)可以把或者的部分括起来。注意小括号可能还有别的用途,但是在R当中先不使用。 jelly|cream # jelly或者cream (eg|le)gs # eggs或者legs (da)+ # da或者dada或者dadada 或者更多个da的重复 # 大括号括住1至2个数字,代表重复次数。 * # 零个或者多个之前的字符 + # 一个或者多个之前的字符 ? # 零个或者一个之前的字符 {n} # n个之前的字符 {n,} # 大于等于n个之前的字符 {n,m} # n至m个之前的字符 # 下面的是一些字符被转义符\转义会赋以了一些新的(有可能是约定俗成的)意义 \n # 换行符 \t # tab \w # 任意字母(包括下划线)或者数字 # 等同于[a-zA-Z0-9_] \W # \w的反义. # 等同于[^a-zA-Z0-9_] \d # 任意一个数字,等同于[0-9] \D # \d的反义,等同于[^0-9] \s # 任意一个空格,比如, # space, tab, newline, 等 \S # \s的反义,任意一个非空格 \b # 单词的边界, 不能使用在[]内 \B # \b的反义 # 很明显,对于保留字符$, |, [, ), \, / 等等都需要转义字符\来转义表示: \| # 竖线 \[ # \[左方括号 \]右方括号 \) # \(左小括号 \)右小括号 \* # 星号 \^ # 开号 \/ # 反斜杠 \\ # 斜杠
接下来再讲一下POSIX中定义的一些特殊意义的字符(R中预定义的字符组)
[:alnum:] # [:alpha:]和[:digit:]的组合 [:alpha:] # [:lower:]和[:upper:]的组合 [:blank:] # 空格(space, tab),不包括换行符 [:cntrl:] # 控制符,在ASCII码中000~037或者177 [:digit:] # 任意数字:0-9 [:graph:] # [:alnum:]和[:punct:]的组合 [:lower:] # 当前字符集的小写字母(小写字母:a-z) [:print:] # 可打印出来的字符,[:graph:]以及空格(即:[:alnum:],[:punct:]和[:space:]) [:punct:] # 标点符号,包括:^ ! " # $ % & \' ( ) * + - . / : ; < = > ? @ [ ] \ _ { } ` ~ [:space:] # 空格,包括tab, newline, vertical tab, form feed, carriage return, and space [:upper:] # 当前字符集的大写字母(大写字母:A-Z) [:xdigit:] # 16进制数 0-9a-fA-F
代表字符组的特殊符号
代码 含义说明 \w 字符串,等价于[:alnum:] \W 非字符串,等价于[^[:alnum:]] \s 空格字符,等价于[:blank:] \S 非空格字符,等价于[^[:blank:]] \d 数字,等价于[:digit:] \D 非数字,等价于[^[:digit:]] \b Word edge(单词开头或结束的位置) \B No Word edge(非单词开头或结束的位置) \< Word beginning(单词开头的位置) \> Word end(单词结束的位置)
还有两个锚点特殊字符
^ # 一行字符串的起始,它并不代表第一个字符,只代表这里开始新的一行字符串。 $ # 一行字符串的结束,它并不代表最后一个字符(因为换行符并不会被包含进匹配当中),只代表一行字符串到这里结束。 \< # 单词左边界 \> # 单词右边界
弄清楚了这些正则符号,我们再回过头来看一点之前的
pattern <- "^[A-Za-z0-9\\._%+-]+@[A-Za-z0-9\\.-]+\\.[A-Za-z]{2,4}$" 可以改写为 pattern <- "^[\\w\\._%+-]+@[\\w\\.-]+\\.[A-Za-z]{2,4}$" 或者 pattern <- "^[[:alnum:]\\._%+-]+@[[:alnum:]\\.-]+\\.[[:alpha:]]{2,4}$"
有人会问了,为什么转义字符都要写两次啊?因为R本身也把 \ 当成转义字符,所以在写pattern的时候,就需要使用
\\
来表示转义字符。还有一种办法就是设置fixed为TRUE。那么参数中perl是什么意思呢?其实就是指是否使用PCRE的算法,我们来看实例:> regexpr("foo|foobar","myfoobar") [1] 3 attr(,"match.length") [1] 6 attr(,"useBytes") [1] TRUE > regexpr("foo|foobar","myfoobar", perl=TRUE) [1] 3 attr(,"match.length") [1] 3 attr(,"useBytes") [1] TRUE
参考资料
A-Za-z0-9\._%+- ↩︎
请发表评论