13.2 清理字符串
早在第7章,我们了解到一些简单的字符串操作任务,如使用paste
把字符串合并在一起,以及使用substring
提取部分字符串等。
一个非常普遍的问题是:逻辑值何时会被编码成R不理解的值。在alpe_d_huez
循环数据集中,DrugUse
列(指出每个车手是否曾被指控服用违禁药)中数值被编码为“Y”和“N",而不是TRUE或FALSE。对于这种简单的匹配关系,我们可以直接使用正确的逻辑值替换掉每个字符串:
yn_to_logical <- function(x)
{
y <- rep.int(NA, length(x))
y[x == "Y"] <- TRUE
y[x == "N"] <- FALSE
y
}
默认把值设为NA
可以让我们处理那些不能匹配“Y”或“N”的字符串。我们可以显式地调用函数:
alpe_d_huez$DrugUse <- yn_to_logical(alpe_d_huez$DrugUse)
这种直接将一个字符串替换为另一个的方法,对于那些需要替换许多字符串的来说扩展性不是很好。如果你有一万个可能的输入,那么使用一个函数来逐个替换会很难避免书写出错,这种代码也很难维护。
幸运的是,有其他更巧妙的方法可以相对容易地检测、提取和替换掉与模式相匹配的部分字符串。R有一系列(大致上)基于Unix grep
工具的内置函数能处理这些任务。它们接受一个要操作的字符串以及要匹配的正则表达式。正如在第1章所说的,正则表达式是一种模式,它能非常灵活地描述字符串的内容。在匹配诸如电话号码或电子邮件地址这种复杂的字符串数据类型时,它们非常有用1。
1参见assertive
包为此预装的一些正则表达式。
grep
、grepl
和regexpr
函数都能找到与模式相匹配的字符串,sub
和gsub
函数能替换匹配的字符串。在经典的R风格中,这些函数都是无比准确和非常强大的,但由于历史的原因,它的命名、参数的顺序和返回值都比较奇怪。幸好,就像plyr
为apply
函数,lubridate
为日期-时间函数提供了一致的封装一样,stringr
包对字符串操作函数也提供了一致的封装。不同之处在于,偶尔需要使用基本的apply
函数或日期-时间函数时,stringr
已足够先进,你根本无须再使用grep
。因此,浏览一下?grep
的帮助页面即可,无需投入太多精力。
下例使用learning
包中的english_monarchs
数据集。它包含了从后罗马时代(5世纪)英格兰被分裂为七王国直到13世纪初英国接管了爱尔兰这段时期的统治者的名字和日期:
data(english_monarchs, package = "learningr")
head(english_monarchs)
## name house start.of.reign end.of.reign domain
## 1 Wehha Wuffingas NA 571 East Anglia
## 2 Wuffa Wuffingas 571 578 East Anglia
## 3 Tytila Wuffingas 578 616 East Anglia
## 4 R?dwald Wuffingas 616 627 East Anglia
## 5 Eorpwald Wuffingas 627 627 East Anglia
## 6 Ricberht Wuffingas 627 630 East Anglia
## length.of.reign.years reign.was.more.than.30.years
## 1 NA NA
## 2 7 FALSE
## 3 38 TRUE
## 4 11 FALSE
## 5 0 FALSE
## 6 3 FALSE
历史的问题之一是它的数据实在太多了。幸好,古怪或杂乱的数据会将你引向历史中有意思的那部分,即我们可以压缩数据而聚焦到有趣的部分。例如,尽管英格兰地区有七个王国,但它们的边界却极不确定,有时一个王国会征服另一个。我们可以通过在domain
列中搜索逗号找到这些交叉点。为了检测出相关的模式,我们使用str_detect
函数。fixed
函数告诉str_detect
:我们正在寻找一个固定字符串(逗号)而非正则表达式。str_detect
将返回一个可用于索引的逻辑向量:
library(stringr)
multiple_kingdoms <- str_detect(english_monarchs$domain, fixed(","))
english_monarchs[multiple_kingdoms, c("name", "domain")]
## name domain
## 17 Offa East Anglia, Mercia
## 18 Offa East Anglia, Kent, Mercia
## 19 Offa and Ecgfrith East Anglia, Kent, Mercia
## 20 Ecgfrith East Anglia, Kent, Mercia
## 22 Cҩnwulf East Anglia, Kent, Mercia
## 23 Cҩnwulf and Cynehelm East Anglia, Kent, Mercia
## 24 Cҩnwulf East Anglia, Kent, Mercia
## 25 Ceolwulf East Anglia, Kent, Mercia
## 26 Beornwulf East Anglia, Mercia
## 82 Ecgbehrt and Æthelwulf Kent, Wessex
## 83 Ecgbehrt and Æthelwulf Kent, Mercia, Wessex
## 84 Ecgbehrt and Æthelwulf Kent, Wessex
## 85 Æthelwulf and Æðelstan I Kent, Wessex
## 86 Æthelwulf Kent, Wessex
## 87 Æthelwulf and Æðelberht III Kent, Wessex
## 88 Æðelberht III Kent, Wessex
## 89 Æthelred I Kent, Wessex
## 95 Oswiu Mercia, Northumbria
同样常见的是,王国的权力并非为某位统治者专有,而是由几个人分享(当一个强大的国王有好几个儿子时特别常见)。我们可以通过在name
一栏中寻找逗号或“and”来发现这些例子。这一次,因为要同时寻找两件事情,所以更简单的做法是使用一个正则表达式而非固定的字符串。在正则表达式和R中,管道字符|
的含义均为:或。
在下例中,为防止输出内容太多,我们只返回name
一列,且忽略缺失值(使用is.na
):
multiple_rulers <- str_detect(english_monarchs$name, ",|and")
english_monarchs$name[multiple_rulers & !is.na(multiple_rulers)]
## [1] Sigeberht and Ecgric
## [2] Hun, Beonna and Alberht
## [3] Offa and Ecgfrith
## [4] Cҩnwulf and Cynehelm
## [5] Sighere and Sebbi
## [6] Sigeheard and Swaefred
## [7] Eorcenberht and Eormenred
## [8] Oswine, Swæfbehrt, Swæfheard
## [9] Swæfbehrt, Swæfheard, Wihtred
## [10] Æðelberht II, Ælfric and Eadberht I
## [11] Æðelberht II and Eardwulf
## [12] Eadberht II, Eanmund and Sigered
## [13] Heaberht and Ecgbehrt II
## [14] Ecgbehrt and Æthelwulf
## [15] Ecgbehrt and Æthelwulf
## [16] Ecgbehrt and Æthelwulf
## [17] Æthelwulf and Æðelstan I
## [18] Æthelwulf and Æðelberht III
## [19] Penda and Eowa
## [20] Penda and Peada
## [21] Æthelred, Lord of the Mercians
## [22] Æthelflæd, Lady of the Mercians
## [23] Ælfwynn, Second Lady of the Mercians
## [24] Hálfdan and Eowils
## [25] Noðhelm and Watt
## [26] Noðhelm and Bryni
## [27] Noðhelm and Osric
## [28] Noðhelm and Æðelstan
## [29] Ælfwald, Oslac and Osmund
## [30] Ælfwald, Ealdwulf, Oslac and Osmund
## [31] Ælfwald, Ealdwulf, Oslac, Osmund and Oswald
## [32] Cenwalh and Seaxburh
## 211 Levels: Adda Æðelbehrt Æðelberht I ... Wulfhere
如果想把name
一列拆分,使它列出每位统治者的名字,可以使用str_split
(或用R基本包中的strsplit
,作用基本一样)。str_split
接受一个向量作为输入参数,且将返回一个列表,这是因为每个输入的字符串可以被分成长度不同的向量。如果每个输入须返回相同的分割数,则可使用str_split_fixed
,它将返回一个矩阵。以下输出显示了多位统治者中的前几个例子:
individual_rulers <- str_split(english_monarchs$name, ", | and ")
head(individual_rulers[sapply(individual_rulers, length) > 1])
## [[1]]
## [1] "Sigeberht" "Ecgric"
##
## [[2]]
## [1] "Hun" "Beonna" "Alberht"
##
## [[3]]
## [1] "Offa" "Ecgfrith"
##
## [[4]]
## [1] "Cҩnwulf" "Cynehelm"
##
## [[5]]
## [1] "Sighere" "Sebbi"
##
## [[6]]
## [1] "Sigeheard" "Swaefred"
在此期间,许多盎格鲁 - 撒克逊(Anglo-Saxon)统治者的名字中有古英语字符,像“æ”(“ash”),它代表“ae”或“ð”和“þ”(分别为“eth”和“thorn,”),它们都代表“th.”。在很多情况下,每个统治者名字的拼写并不一致,但要识别某位统治者,其名字拼写就必须固定下来。
让我们来看看有多少个th
、ð
和þ
用来组成字母“th.”。我们可以用str_count
计算出它们在每个名称中的出现次数,然后用sum
来对所有统治者求和计算出总的出现次数:
th <- c("th", "ð", "þ")
sapply( # 也可以使用plyr中的laply
th,
function(th)
{
sum(str_count(english_monarchs$name, th))
}
)
## th ð þ
## 74 26 7
在这个数据集开始看起来像常见的现代标准拉丁拼写法。如果要替换掉eth和thorn等字符串,可使用str_replace_all
。(有一个稍微不同的函数str_replace
,它仅仅替换掉第一个匹配的字符串。)把eth和thorn置于在方括号中的意思是:符合下列任一字符:
english_monarchs$new_name <- str_replace_all(english_monarchs$name, "[ðþ]", "th")
这种技巧对于清理一个类别变量的水平值非常有用。例如,性别在英语中可通过多种方式指定,但我们通常只需要其中的两个。在下例中,我们将匹配以“m”开头的(^
)、后面跟着一个可选的(?
)“ale”、且以字符串($
)为结尾:
gender <- c(
"MALE", "Male", "male", "M", "FEMALE",
"Female", "female", "f", NA
)
clean_gender <- str_replace(
gender,
ignore.case("^m(ale)?$"),
"Male"
)
(clean_gender <- str_replace(
clean_gender,
ignore.case("^f(emale)?$"), "
Female"
))
## [1] "Male" "Male" "Male" "Male" "Female" "Female" "Female" "Female"
## [9] NA