stringr包处理字符串(字符串长度、组合、取子集、正则表达式模式匹配)


使用stringr处理字符串

用于字符串处理的 stringr 包。stringr 不是tidyverse 核心 R 包的一部分,我们需要使用命令来加载它。

library(tidyverse)

library(stringr)

1.2 字符串基础

可以使用单引号或双引号来创建字符串。单引号和双引号在 R 中没有区别。

string1 <- "This is a string"

string2 <- 'To put a "quote" inside a string, use single quotes'

加号,这是一个续行符:

如果想要查看字符串的初始内容,可以使用 writelines() 函数:

x <- c("\"", "\\")

x

writeLines(x)

 

多个字符串通常保存在一个字符向量中,你可以使用 c() 函数来创建字符向量:

c("one", "two", "three")

 

1.2.1 字符串长度

str_length() 函数可以返回字符串中的字符数量:

str_length(c("a", "R for data science", NA))

 

1.2.2 字符串组合

要想组合两个或更多字符串,可以使用 str_c() 函数:

str_c("x", "y")

 

str_c("x", "y", "z")

 

使用 sep 参数来控制字符串间的分隔方式:

str_c("x", "y", sep = ", ")

 

如果想要将它们输出为 "NA",可以使用 str_replace_na()

x <- c("abc", NA)

str_c("|-", x, "-|")

 

str_c("|-", str_replace_na(x), "-|")

 

str_c() 函数是向量化的,它可以自动循环短向量,使得其与最长的向量具有相同的长度:

str_c("prefix-", c("a", "b", "c"), "-suffix")

 

长度为 0 的对象会被无声无息地丢弃。这与 if 结合起来特别有用:

name <- "Hadley"

time_of_day <- "morning"

birthday <- FALSE

str_c(

 "Good ", time_of_day, " ", name,

 if (birthday) " and HAPPY BIRTHDAY",

 "."

)

 

要想将字符向量合并为字符串,可以使用 collapse() 函数:

str_c(c("x", "y", "z"), collapse = ", ")

 

1.2.3 字符串取子集

可以使用 str_sub() 函数来提取字符串的一部分str_sub() 函数中还有 start end 参数,它们给出了子串的位置(包括 start end 在内):

x <- c("Apple", "Banana", "Pear")

str_sub(x, 1, 3)

 

# 负数表示从后往前数

str_sub(x, -3, -1)

 

即使字符串过短,str_sub() 函数也不会出错,它将返回尽可能多的字符:

str_sub("a", 1, 5)

 

可以使用 str_sub() 函数的赋值形式来修改字符串:

str_sub(x, 1, 1) <- str_to_lower(str_sub(x, 1, 1))

x

 

1.2.4 区域设置

str_to_lower() 函数将文本转换为小写,str_to_upper()函数将文本转换为大写。可以通过明确区域设置来选择使用哪种规则。

# 土耳其语中有带点和不带点的两个i,它们在转换为大写时是不同的:

str_to_upper(c("i", "ı"))

 

str_to_upper(c("i", "ı"), locale = "tr")

 

R 基础包中的 order() sort() 函数使用当前区域设置对字符串进行排序。更强大的可以使用 str_sort() str_order() 函数,它们可以使用 locale 参数来进行区域设置:

x <- c("apple", "eggplant", "banana")

str_sort(x, locale = "en")  # 英语

 

str_sort(x, locale = "haw")  # 夏威夷语

 

1.3 使用正则表达式进行模式匹配

我们通过 str_view() str_view_all() 函数来学习正则表达式。这两个函数接受一个字符

向量和一个正则表达式,并显示出它们是如何匹配的

1.3.1 基础匹配

最简单的模式是精确匹配字符串

x <- c("apple", "banana", "pear")

str_view(x, "an")

 

更复杂一些的模式是使用 . ,它可以匹配任意字符(除了换行符):

str_view(x, ".a.")

 

如果 . 可以匹配任意字符,那么如何匹配字符 . 需要使用一个“转义”符号来告诉正则表达式实际上就是要匹配 . 这个字符,而不是使用 . 来匹配其他字符。而且 \ 在字符串中也用作转义字符,所以正则表达式 \. 的字符串形式应是 \\. :

# 要想建立正则表示式,我们需要使用\\

dot <- "\\."

# 实际上表达式本身只包含一个\

writeLines(dot)

 

# 这个表达式告诉R搜索一个.

str_view(c("abc", "a.c", "bef"), "a\\.c")

 

如果 \ 在正则表达式中用作转义字符,建立形式为 \\ 的正则表达式。要想建立这样的正则表达式,我们需要使用一个字符串,其中还需要对 \ 进行转义。这意味着要想匹配字符 \,我们需要输入 "\\\\" ——你需要 4 个反斜杠来匹配 1 个反斜杠

x <- "a\\b"

writeLines(x)

 

str_view(x, "\\\\")

 

1.3.3 锚点

有时我们需要在正则表达式中设置锚点,以便 R 从字符串的开头或末尾进行匹配。我们可以设置两种锚点。

^ 从字符串开头进行匹配。

$ 从字符串末尾进行匹配。

x <- c("apple", "banana", "pear")

str_view(x, "^a")

 

str_view(x, "a$")

 

如果想要强制正则表达式匹配一个完整字符串,那么可以同时设置 ^ $ 这两个锚点:

x <- c("apple pie", "apple", "apple cake")

str_view(x, "apple")

 

str_view(x, "^apple$")

 

1.3.5 字符类与字符选项

.  它可以匹配除换行符外的任意字符。还有其他 4 种常用的字符类

•  \d 可以匹配任意数字。

•  \s 可以匹配任意空白字符(如空格、制表符和换行符)

•  [abc] 可以匹配 ab c

•  [^abc] 可以匹配除 abc 外的任意字符。

要想创建包含 \d \s 的正则表达式,你需要在字符串中对 \ 进行转义,因此需要输入 "\\d" "\\s"abc|xyz 匹配的是 abc xyz

str_view(c("grey", "gray"), "gr(e|a)y"

 

1.3.7 重复

正则表达式的另一项强大功能是,其可以控制一个模式能够匹配多少次。

•  ?0 次或 1 次。

•  +1 次或多次。

•  *0 次或多次。

x <- "1888 is the longest year in Roman numerals: MDCCCLXXXVIII"

str_view(x, "CC?")

 

str_view(x, "CC+")

 

str_view(x, 'C[LX]+')

 

可以精确设置匹配的次数

•  {n}:匹配 n 次。

•  {n,}:匹配 n 次或更多次。

•  {,m}:最多匹配 m 次。

•  {n, m}:匹配 n m 次。

str_view(x, "C{2}")   #匹配2次

 

str_view(x, "C{2,}")

 

str_view(x, "C{2,3}")

 

默认的匹配方式是贪婪的:正则表达式会匹配尽量长的字符串。通过在正则表达式后面添加一个 ?,你可以将匹配方式更改为懒惰的,即匹配尽量短的字符串。

str_view(x, 'C{2,3}?')

 

str_view(x, 'C[LX]+?')

 

1.3.9 分组与回溯引用

括号还可以定义分组,你可以通过回溯引用(如 \1\2 等)来引用这些分组。

str_view(fruit, "(..)\\1", match = TRUE)

 

1.4 工具

1.4.1 匹配检测

要想确定一个字符向量能否匹配一种模式,可以使用 str_detect() 函数

x <- c("apple", "banana", "pear")

str_detect(x, "e")

 

sum(str_detect(words, "^t")) # 有多少个以t开头的常用单词?

 

mean(str_detect(words, "[aeiou]$")) # 以元音字母结尾的常用单词的比例是多少?

 

找出不包含元音字母的所有单词:

# 找出至少包含一个元音字母的所有单词,然后取反

no_vowels_1 <- !str_detect(words, "[aeiou]")

# 找出仅包含辅音字母(非元音字母)的所有单词

no_vowels_2 <- str_detect(words, "^[^aeiou]+$")

identical(no_vowels_1, no_vowels_2)

 

str_detect() 函数的一种常见用法是选取出匹配某种模式的元素。你可以通过逻辑取子集

方式来完成这种操作,也可以使用便捷的 str_subset() 包装器函数:

words[str_detect(words, "x$")]   #匹配最后一个字符x

 

str_subset(words, "x$")

 

字符串通常会是数据框的一列,此时我们可以使用 filter 操作:

df <- tibble(

 word = words,

 i = seq_along(word)   # 从参数word的长度中提取长度

)

df %>%

 filter(str_detect(words, "x$"))

 

str_detect() 函数的一种变体是 str_count(),返回字符串中匹配的数量:

x <- c("apple", "banana", "pear")

str_count(x, "a")

 

# 平均来看,每个单词中有多少个元音字母?

mean(str_count(words, "[aeiou]"))

 

str_count() 可以同 mutate() 函数一同使用:

df %>%

 mutate(   # 增加列

 vowels = str_count(word, "[aeiou]"),   #统计单词中元音字符的数量

 consonants = str_count(word, "[^aeiou]")   #统计单词中不是元音字符的数量

 )

 

匹配从来不会重叠。例如,在 "abababa" 中,模式 "aba" 会匹配多少次?正则表达式会告诉你是 2 次,而不是 3 次:

str_count("abababa", "aba")

 

str_view_all("abababa", "aba")

 

1.4.3 提取匹配内容

stringr::sentences

length(sentences)

 

head(sentences)

 

假设我们想要找出包含一种颜色的所有句子,首先,我们需要创建一个颜色名称向量,然后将其转换成一个正则表达式:

colors <- c(   #颜色向量

 "red", "orange", "yellow", "green", "blue", "purple"

)

color_match <- str_c(colors, collapse = "|")  

color_match

 

以选取出包含一种颜色的句子,再从中提取出颜色

has_color <- str_subset(sentences, color_match)   #筛选出含有颜色的句子

matches <- str_extract(has_color, color_match)  #提取颜色

head(matches)   #展示

 

str_extract() 只提取第一个匹配。我们可以先选取出具有多于一种匹配的所有句子

more <- sentences[str_count(sentences, color_match) > 1]   #筛选出不止一个颜色的句子

str_view_all(more, color_match)   #提取句子中所有的颜色

 

str_extract(more, color_match)

 

要想得到所有匹配,可以使用 str_extract_all() 函数,它会返回一个列表:

str_extract_all(more, color_match)  

 

1.4.5 分组匹配

先进行一种启发式实验,找出跟在 a the 后面的所有单词。

noun <- "(a|the) ([^ ]+)"

has_noun <- sentences %>%

 str_subset(noun) %>%

 head(10)

has_noun %>%

 str_extract(noun)

 

str_extract() 函数可以给出完整匹配;str_match() 函数则可以给出每个独立分组。str_match() 返回的是一个矩阵,其中一列是完整匹配,后面的列是每个分组的匹配:

has_noun %>%

 str_match(noun)

 

如果数据是保存在 tibble 中的,那么使用 tidyr::extract() 会更容易,要求为每个分组提供一个名称,以作为新列放在 tibble 中:

tibble(sentence = sentences) %>%

 tidyr::extract(

 sentence, c("article", "noun"), "(a|the) ([^ ]+)",

 remove = FALSE

 )

1.4.7 替换匹配内容

str_replace() str_replace_all() 函数可以使用新字符串替换匹配内容。最简单的应用是使用固定字符串替换匹配内容:

x <- c("apple", "pear", "banana")

str_replace(x, "[aeiou]", "-")   # 使用符号 - 替换元音字符 aeiou

 

str_replace_all(x, "[aeiou]", "-")

 

通过提供一个命名向量,使用 str_replace_all() 函数可以同时执行多个替换:

x <- c("1 house", "2 cars", "3 people")

str_replace_all(x, c("1" = "one", "2" = "two", "3" = "three"))

 

可以使用回溯引用来插入匹配中的分组(没看懂)

sentences %>%

 str_replace("([^ ]+) ([^ ]+) ([^ ]+)", "\\1 \\3 \\2") %>%

 head(5)

 

1.4.9 拆分

str_split() 函数可以将字符串拆分为多个片段。例如,我们可以将句子拆分成单词:

sentences %>%

 head(5) %>%

 str_split(" ")

 

因为字符向量的每个分量会包含不同数量的片段,所以 str_split() 会返回一个列表。如果你拆分的是长度为 1 的向量,那么只要简单地提取列表的第一个元素即可:

"a|b|c|d" %>%

 str_split("\\|") %>%

 .[[1]]

 

可以通过设置 simplify = TRUE 返回一个矩阵:

sentences %>%

 head(5) %>%

 str_split(" ", simplify = TRUE)

 

可以设定拆分片段的最大数量:

fields <- c("Name: Hadley", "Country: NZ", "Age: 35")

fields %>% str_split(": ", n = 2, simplify = TRUE)

 

可以通过字母、行、句子和单词边界(boundary() 函数)来拆分字符串

x <- "This is a sentence. This is another sentence."

str_view_all(x, boundary("word"))

 

str_split(x, " ")[[1]]   #显示第1个列表内容[[1]]

 

str_split(x, boundary("word"))[[1]]

 

1.4.11 定位匹配内容

str_locate() str_locate_all() 函数可以给出每个匹配的开始位置和结束位置。以使用 str_locate() 函数找出匹配的模式,然后使用 str_sub() 函数来提取或修改匹配的内容。

1.5 其他类型的模式

当使用一个字符串作为模式时,R 会自动调用 regex() 函数对其进行包装:

# 正常调用:

str_view(fruit, "nana")

# 上面形式是以下形式的简写

str_view(fruit, regex("nana"))

 

可以使用 regex() 函数的其他参数来控制具体的匹配方式

1ignore_case = TRUE 既可以匹配大写字母,也可以匹配小写字母,它总是使用当前的区

域设置:

bananas <- c("banana", "Banana", "BANANA")

str_view(bananas, "banana")

 

str_view(bananas, regex("banana", ignore_case = TRUE))

 

2multiline = TRUE 可以使得 ^ $ 从每行的开头和末尾开始匹配,而不是从完整字符串

的开头和末尾开始匹配

x <- "Line 1\nLine 2\nLine 3"

str_extract_all(x, "^Line")[[1]]

 

str_extract_all(x, regex("^Line", multiline = TRUE))[[1]]

 

3comments = TRUE 可以让你在复杂的正则表达式中加入注释和空白字符,以便更易理解。匹配时会忽略空格和 # 后面的内容。如果想要匹配一个空格,你需要对其进行转义:"\\ "

 

phone <- regex("

 \\(? # 可选的开括号

 (\\d{3}) # 地区编码

 [)- ]? # 可选的闭括号、短划线或空格

 (\\d{3}) # 另外3个数字

 [ -]? # 可选的空格或短划线

 (\\d{3}) # 另外3个数字

 ", comments = TRUE)

str_match("514-791-8141", phone)

 

4dotall = TRUE 可以使得 . 匹配包括 \n 在内的所有字符。

fixed() 函数可以按照字符串的字节形式进行精确匹配,它会忽略正则表达式中的所有特殊字符,并在非常低的层次上进行操作。

library(microbenchmark)

microbenchmark::microbenchmark(

 fixed = str_detect(sentences, fixed("the")),

 regex = str_detect(sentences, "the"),

 times = 20)

coll() 函数使用标准排序规则来比较字符串,这在进行不区分大小写的匹配时是非常有效的。缺点是速度比较慢。 # 这意味着在进行不区分大小写的匹配时,还是需要知道不同规则之间的区别:

i <- c("I", "İ", "i", "ı")

i

str_subset(i, coll("i", ignore_case = TRUE))

 

str_subset(

 i,

 coll("i", ignore_case = TRUE, locale = "tr")   # locale 参数,以确定使用哪种规则来比较字符

)

 

fixed() regex() 函数中都有 ignore_case 参数,但都无法选择区域设置,它们总是使用默认的区域设置。查看默认区域设置

stringi::stri_locale_info()

 

1.6 正则表达式的其他应用

apropos() 函数可以在全局环境空间中搜索所有可用对象。当不能确切想起函数名称时,这个函数特别有用:

apropos("replace")

 

dir() 函数可以列出一个目录下的所有文件。dir() 函数的 patten 参数可以是一个正则

表达式,此时它只返回与这个模式相匹配的文件名。例如返回当前目录中的所有 R Markdown 文件:

head(dir(pattern = "\\.Rmd$"))

 

1.7 stringi

stringr 建立于 stringi 的基础之上,与 stringr 不同,stringi 的设计思想是尽量全面,几乎包含了我们可以用到的所有函数:stringi 中有 234 个函数,而stringr 中只有 42 个。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM