Stata:正则表达式
The following article is from 数量经济学 Author 数量经济学
什么是正则表达式,如何在Stata中使用它们?
正则表达式使用一种符号系统,允许以最小的努力匹配复杂的文本模式。虽然对于正则表达式的语法没有正式的标准化,但是对于语法的基本元素有一个普遍的共识。Stata在其正则表达式函数中实现了这一核心语法。
正则表达式只是字面值和操作符的混合字符串。例如,如果你只是想测试“xyz”的子字符串是否存在于另一个字符串中,你可以使用字面量“xyz”作为你的正则表达式。当然,它不是很强大,但它是一个合法的表达。混合使用操作符允许您匹配更复杂的模式。以下是Stata正则表达式解析器支持的核心操作符:
Counting | |
---|---|
* | 星号表示匹配前一个表达式的零个或多个。 |
+ | 加号表示匹配前一个表达式中的一个或多个。 |
? | ? 问号的意思是匹配前一个表达式0或1次数 |
Characters | |
a–z | 破折号表示“匹配一个字符或数字范围”。“a”和“z”只是一个例子。也可以是0-9、5-8、F-M等。 |
. | 表示“匹配任何字符”。 |
\ | 反斜杠用作转义字符,以匹配否则将被解释为正则表达式操作符的字符。 |
Anchors | |
^ | 当插入符号被放在正则表达式的开头时,它表示“匹配字符串开头的表达式”。这个字符可以被认为是一个“锚”字符,因为它不直接匹配一个字符,只匹配匹配的位置, |
$ | 当美元符号被放在正则表达式的末尾时,它表示“匹配表达式在字符串的末尾”。这是另一个锚字符。 |
Groups | |
| | 表示一个逻辑的“或”,通常用于字符集(参见下面的方括号)。 |
[ ] | 方括号表示在匹配中使用的一组允许字符/表达式,例如用于所有字母数字字符的[a- za - z0 -9]。 |
( ) | ()圆括号必须匹配并表示子表达式组。 |
其他流行的正则表达式语法包括POSIX标准和Perl的标准。两者都在这些基本操作符的基础上进行了扩展,包括计数操作符、元字符(通常为:alpha:等形式)和其他特定于语法的添加。
当需要选择采用哪种正则表达式语法时,Stata有几个选项。不同的操作系统提供了自己的正则表达式解析器供应用程序使用,但不能保证这些解析器是一致的。Stata通过使用自己的解析器来避免这种歧义。在这样做的过程中,Stata确保了计算机平台之间的一致性,即使存在其他正则表达式解析器中缺少的一些语法特性。然而,这些额外的语法元素中的大多数都不是关键的,可以用Stata当前的解析器表示,尽管是以较长的形式表示。
现在我们已经讨论了一些正则表达式语法,让我们来看一些匹配一些常见字符串的表达式示例。
示例1:日期
正则表达式的一个常见用法是(不仅仅是Stata)匹配时间和数据信息。让我们假设我们有一个具有以下形式的字符串数据的变量:
12jan2003
1April1995
17may1977
02September2000
...
假设这些日期代表整体,我们可以看到所有日期都以一系列数字开始。我们应该总是找到一个数字,而且它应该总是在开头。知道了这些,我们可以用
^ [0 - 9] +
在“天”后面是“月”,但是“月”似乎可以缩写或拼写出来。同时使用大写和小写字母。接下来,我们可以寻找一系列大小写混合的字母字符:
^ [0 - 9] + [a-zA-Z] +
在月之后,我们有年,另一系列数字。我们将读取年份的数字,直到到达字符串的末尾,因此使用了美元符号。
^ [0 - 9] + [a-zA-Z] + [0 - 9] + $
这不是解析该字符串的唯一方法。我们可以忽略^和美元字符,它们分别表示字符串的开始和结束。但是,如果我们有一个字符串,比如“12octo1996 4Jun1997”,没有^和$字符,我们将得到一个正匹配,但只针对第一个日期。这种行为在这种情况下可能没问题,但也可能不行。
你可能已经注意到,我们也不关心我们读了多少位数的年份(或任何部分)。如果我们知道我们将总是寻找四位数,我们可以使用以下正则表达式:
^[0-9]+[a-zA-Z]+[0-9][0-9][0-9][0-9]$
任何带有两位数年份的字符串都不会被匹配。在匹配数据时使用的正则表达式的限制或松散程度实际上取决于您。有时,数据会迫使您构造冗长的正则表达式,以便您可以精确地匹配您想要的内容。
尽管前面的正则表达式在解析日期时可以很好地告诉我们,但在分离字符串的日期、月和年数据组件时,它没有帮助。为此,我们使用正则表达式工具,称为“子表达式”。
子表达式是一种对正则表达式的子模式进行分组的方法,以便可以将它们匹配的数据提取为子字符串。例如,如果我们想要检索日期、月和年的数据,我们应该像这样将它们各自的正则表达式部分括在括号中:
^([0-9]+)([a-zA-Z]+)([0-9][0-9][0-9][0-9])$
注意:下面添加的表达式与上面的表达式相同。
在确认这个正则表达式已经匹配了一个字符串之后,我们可以使用某些函数来检索感兴趣的子表达式。子表达式1指的是日期,2指的是月份,3指的是年份。子表达式0总是返回正则表达式匹配的整个字符串作为一个整体。例如,匹配字符串"12jan2003"将会得到以下子表达式:
我们可以通过
list var if regexm(var,"^[0-9]+[a-zA-Z]+[0-9]+$")
例2:电话号码
假设我们有一个用于电话号码的变量,并且我们希望获得区号。数据有多种格式:
(979) 123 - 1234
979-121-1231
...
我们有两种不同的格式需要解析。正则表达式将会更短一些,因为我们只关心获取区域代码,但是如果我们要处理电话号码的其余部分,同样的原则也会适用。
^ \ (? [0 - 9] [0 - 9] [0 - 9] ) ?
分解这个正则表达式,我们看到^,它表示我们想要匹配字符串的开头,然后后面跟着"(?".左括号前面有一个反斜杠,因为在正常的正则表达式语法中,左括号意味着开始一个子表达式,而这里我们正在寻找一个字面左括号。反斜杠用于将任何字符转换为文本字符,否则将是正则表达式操作符。
问号操作符表示我们正在寻找左括号或右括号,或者根本没有括号。
直到字符串的末尾才进行匹配。我们知道区域代码总是我们看到的前三个数字,所以不需要补全正则表达式以匹配整个行。事实上,如果我们确信区域代码总是前三位数字,我们的最后一个正则表达式甚至不需要测试最后一个括号。它可以简化为:
^(?[0-9][0-9][0-9]
如果我们混合了其他数据,这些数据以三位数字开始,但没有指定区号,那么我们必须构造一个限制性更强的正则表达式来区分这两种类型的数据(如果可能的话)。
正则表达式操作应用
一个相对简单、灵活的搜索字符串的方法。你可以使用它们来搜索任何字符串(例如变量,宏)。
在Stata中,有三个使用正则表达式的函数。
正则表达式并不是所有涉及字符串的问题的解决方案。在大多数情况下,Stata中内置的字符串函数至少可以完成同样好的工作,而且花费的精力更少,出错的概率也更低。
regexm (s,re)允许您搜索正则表达式中描述的字符串。如果字符串与表达式匹配,它的计算结果为1。
regexs (n)返回由regexm匹配的表达式中的第n个子字符串(因此,regexm必须总是在regexm之前运行)。
regexr (s1,re,s2)在字符串(s1)中搜索re,并用一个新的字符串(s2)替换匹配部分。
在Stata中,它们总是用引号括起来。它们可以包含您希望精确匹配的字符串和更灵活的查找内容的描述。
直接输入的字符串完全匹配(字面量),例如。"a"只有匹配“a”。
操作符是出现在方括号中的字符(即[ and ]),它们可以更灵活地匹配,或者是其他描述如何匹配它们的字符。
. * + ?^ $ |()[]
括号内的值可以包括范围,例如0-9,a-z, A-Z, f-x, 0-3.
例如,如果我们想在电话号码列表中查找区域代码,我们可以使用:
"^[(]?[0-9][0-9][0-9]"
Example 1: Extracting zip codes
我们有一个地址列表存储在一个字符串变量中,我们想提取邮政编码。
我们想要搜索什么?
一个五位数([0-9][0-9][0-9][0-9][0-9])
例2:清洗数据
在一项在线调查中,调查对象被问及上个月他们从事某项活动的天数。
一些受访者按照自己的意愿只输入了一个数字。其他受访者输入了其他值。-999用于表示缺失的值。
需要做的事情:
将“never”和“never”改为0。
把“every day”改为30(或31)。
"days"出现的地方删除掉单词“days”。
去掉“+”和“plus”。
将-999替换为一个缺失的值。
将非法值(如35)更改为其他值
达到如下效果
days | days2 |
---|---|
never | 0 |
0 | 0 |
20+ | 20 |
-999 | . |
0 | 0 |
35 | . |
0 | 0 |
Never | 0 |
8+ | 8 |
every day | 30 |
15 days | 15 |
8 | 8 |
-999 | . |
3 | 3 |
12-14 | 12 |
我们可以使用代码为:
* Create a variable that will equal 0 if there is a legal numeric
* value (0-31) and nothing else for the variable days, and 1 otherwise.
gen flag1 = 1
replace flag1=0 if regexm(days, "(^[0-9]$)|(^[1-2][0-9]$)|(^30$)|(^31$)")
* -999 is a missing value, so these don’t need to be flagged either.
replace flag1=0 if(days=="-999")
* generate a new variable to contain the cleaned (numeric only) values.
gen days2 = .
* If days contains a legal numeric value, set days2 = days
replace days2 = real(days) if(flag1==0&days!="-999")
* List the values that days takes on when it is not a numeric value.
list days if flag1==1
* replace "never" or "zero" with zero
replace days2 = 0 if(regexm(days, "[Nn]ever|[Zz]ero"))
* For cases containing "days" or "times" look for numbers
* a valid number at the start of a line
replace days2 = real(regexs(1)) /*
*/ if(regexm(days, "(^[0-9]+)[ ]*(times|days)"))
* If the respondent reported a range of numeric values,
* return only the first.
replace days2 = real(regexs(1)) if(regexm(days, "([0-9]+)(\-[0-9]+)"))
replace days2 = real(regexs(1)) /*
*/ if(regexm(days, "([0-9]+)[ ]*to[ ]*([0-9]+)"))
* replace +, plus, and or more with reported value
replace days2 = real(regexs(1)) /*
*/ if(regexm(days, "([0-9]+)[ ]*(\+|plus|or more)"))
* Replace "every day" with 30
replace days2 = 30 if(regexm(days, "[eE]very[ ]*[dD]ay[.]*"))
* Check to make sure all values of days2 are believable,
* and filled in as much as possible
list id days days2 if(days2<0|days2>31|days2==.)
参考资料:
The official Stata FAQ on regular expressions: http://www.stata.com/support/faqs/data/regex.html
Using regular expressions for data management in Stata, Rose Anne Medeiros ,2007 West Coast Stata Users Group meeting