精华内容
下载资源
问答
  • 这篇文章将为你介绍如何使用R语言来处理字符串。你学会字符串基本操作和如何创建字符串,但这章重点会在正则表达式上。由于字符串通常包含未经处理、格式混乱的数据,所以正则表达式会在此发挥很大作用。正则...

    原文地址:http://r4ds.had.co.nz/strings.html
    作者:Hadley Wickham

    1 介绍

    这篇文章将为你介绍如何使用R语言来处理字符串。你学会字符串的基本操作和如何创建字符串,但这章的重点会在正则表达式上。由于字符串通常包含未经处理、格式混乱的数据,所以正则表达式会在此发挥很大的作用。正则表达式是种简明的语言,用于描述字符串中的组织模式。当你第一次看到正则表达式时,他们就像躺在你键盘上的猫一样无从下手,但当你逐步理解时,你就明白了其意义。

    1.1 前提条件

    这篇文章将以stringr包作为字符串处理的重心。由于我们通常不总会处理到文本数据,所以stringr包不是tidyverse包的核心组成部分,所以我们需要额外导入这个包。

    library(tidyverse)
    library(stringr)

    2 字符串基本操作

    你可以通过单引号或双引号来创建字符串。不同于其他语言,这两种方法并不会造成不同的结果。但我推荐使用双引号"",除非你想要创建包含有多个双引号的字符串。

    string1 <- "This is a string"
    string2 <- 'If I want to include a "quote" inside a string, I use single quotes'

    如果你忘记加上第二个双引号(或单引号)就回车,在第二行行首会出现+,在这里我们叫它续行符:

    > "This is a string without a closing quote
    + 
    + 
    + HELP I'M STUCK

    如果发生了这样的情况,可以按Esc来结束这次输入,也可以在结尾输入第二个双引号(或单引号)来完成输入。

    要想在字符串输入单引号或双引号而不引起错误的话,你可以使用\来为其“转义”:

    double_quote <- "\"" # or '"'
    single_quote <- '\'' # or "'"

    这意味着,若是你想要输入反斜线符号”\“,则需要输入两次:”\”。

    但要当心,字符串的打印表示和其自身并不一样,因为打印表示会显示出转义符。如果想要看到其真正的样子,要用writeLines():

    x <- c("\"", "\\")
    x
    #> [1] "\"" "\\"
    writeLines(x)
    #> "
    #> \

    这类特殊字符并不算多。最常用的便是”\n”,用来表示换行符(newline),还有”\t”,表示制表符(tab),你也可以通过输入?'"'?"'"来查看完整的转义字符列表。有时你也会看到类似”\u00b5”的字符,这是种输入非英文字符的方法,适用于所有平台:

    x <- "\u00b5"
    x
    #> [1] "µ"

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

    c("one", "two", "three")
    #> [1] "one"   "two"   "three"

    2.1 字符串长度

    R语言中本身自带有多个函数来处理字符串,但我们在此不使用它们,因为它们用起来不方便且难记。在此我们会使用stringr包提供的函数。这些函数的名字相较前者更为直观,而且都以str_开头。例如,str_length()表示字符串中的字符数量:

    str_length(c("a", "R for data science", NA))
    #> [1]  1 18 NA

    如果你使用RStudio的话,str_前缀会更为好用,因为当你输入str_时会触发其自动完成功能,让你能看到所有stringr包中的函数:

    auto

    2.2 合并字符串

    要想合并两个及以上的字符串,就要用str_c()

    str_c("x", "y")
    #> [1] "xy"
    str_c("x", "y", "z")
    #> [1] "xyz"

    使用seq参数来控制其合并后字符串之间的字符:

    str_c("x", "y", sep = ", ")
    #> [1] "x, y"

    就如R语言中的其他函数一样,缺失值处理起来很麻烦。如果你想让缺失值输出为”NA”,可以使用str_replace_na()

    x <- c("abc", NA)
    str_c("|-", x, "-|")
    #> [1] "|-abc-|" NA
    str_c("|-", str_replace_na(x), "-|")
    #> [1] "|-abc-|" "|-NA-|"

    如上所示,str_c()的输出结果也是向量,若是参数中含有固定字符串和向量,则会将固定字符串分别和各个向量结合:

    str_c("prefix-", c("a", "b", "c"), "-suffix")
    #> [1] "prefix-a-suffix" "prefix-b-suffix" "prefix-c-suffix"

    而字符大小为0的对象则会被丢弃。这个特性在和if一同使用时会尤其有用:

    name <- "Hadley"
    time_of_day <- "morning"
    birthday <- FALSE
    
    str_c(
      "Good ", time_of_day, " ", name,
      if (birthday) " and HAPPY BIRTHDAY",
      "."
    )
    #> [1] "Good morning Hadley."

    要是想要将一个字符串向量结合成单个字符串,使用collapse参数:

    str_c(c("x", "y", "z"), collapse = ", ")
    #> [1] "x, y, z"

    2.3 分割字符串

    你可以通过使用str_sub()来分割字符串。就如字符串一样,str_sub()也通过接受起点和终点的参数来确定切片的位置:

    x <- c("Apple", "Banana", "Pear")
    str_sub(x, 1, 3)
    #> [1] "App" "Ban" "Pea"
    # negative numbers count backwards from end
    str_sub(x, -3, -1)
    #> [1] "ple" "ana" "ear"

    需要注意的是,str_sub()在字符串比切片始末点短时也不会出错,因为它会返回尽可能多的字符:

    str_sub("a", 1, 5)
    #> [1] "a"

    你也可以使用赋值形式来修改字符串:

    str_sub(x, 1, 1) <- str_to_lower(str_sub(x, 1, 1))
    x
    #> [1] "apple"  "banana" "pear"

    2.4 字符串本地化处理

    之前我曾说过使用str_to_lower()来让字母变为小写。你也可以用str_to_upper()str_to_title()来做类似的处理。然而,改变大小写事实上却比想象的复杂,因为不同语言有不同的大小写规则和形式。你可以通过定义其国别来应用相关规则:

    # 土耳其语中有两种i: 有点的和没有点的
    # 他们的大小写也有不一样的规则:
    str_to_upper(c("i", "ı"))
    #> [1] "I" "I"
    str_to_upper(c("i", "ı"), locale = "tr")
    #> [1] "İ" "I"

    本地化国别参数由ISO 639语言编码标准决定,其参数通常是两个或三个字母的缩写。如果你不知道你自己语言的参数代码,参阅维基上的列表。如果你为该参数留空,则默认使用当前机子的语言本地化。

    另一个受本地化影响较大的操作是分类。R语言自带的order()sort()函数基于当前本地化来分类字符串。如果你想要让分类在不同电脑间有更好的表现,就使用str_sort()str_order()吧,这两个函数也带有locale参数:

    x <- c("apple", "eggplant", "banana")
    
    str_sort(x, locale = "en")  # English
    #> [1] "apple"    "banana"   "eggplant"
    
    str_sort(x, locale = "haw") # Hawaiian
    #> [1] "apple"    "eggplant" "banana"

    2.5 小练习

    1. 有些不使用stringr包的代码中,通常会有paste()paste0()。它们之间有什么不同?它们相当于stringr包中的什么函数呢?这些函数在处理缺失值时又有什么不同呢?
    2. 用自己的话来阐述str_c()sepcollapse参数之间的的不同。
    3. 使用str_length()str_sub()来从一个字符串中提取中间的字符。当字符串中的字符数量是复数时你怎么办?
    4. str_wrap()有什么用?你什么时候会用到它?
    5. str_trim()有什么用?它是不是str_trim()的反面函数?
    6. 写出一个函数,让其完成如下类似操作:将向量c("a", "b", "c")转变为字符串”a, b, and c”。思考若是其向量长度为0,1,或2时它会怎样。

    3 用正则表达式匹配字符串

    正则表达式是个十分简明的语言,其让你能描述字符串中的组成模式。你需要一些时间来理解正则表达式,但一旦你能看懂它们,你会发现它们有用极了。

    在这里,我们将使用str_view()str_view_all()来学习正则表达式。这两个函数输入一个字符向量和一个正则表达式,输出给你匹配结果。我们会从最简单的正则表达式开始,慢慢过渡到更复杂的表达式。一旦你学会模式匹配方法,你会学会如何应用这些点子到大量stringr函数中。

    3.1 基本匹配操作

    最简单的模式匹配是匹配对应一模一样的字符串:

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

    <截图1>

    接下来学习.的用法,它能匹配任何字符(除了换行符):

    str_view(x, ".a.")

    <截图2>

    但若是”.“能匹配任何字符,它怎么才能匹配自己,即”.“字符呢?你只需要输入转义字符来告诉正则表达式你想匹配它就好了。就如字符串一样,正则表达式也使用反斜线\来转义特殊字符。所以要想匹配”.”,你只需要使用\.就好了。然而这也会造成麻烦。我们使用字符串来表现正则表达式,而\在字符串里也是个转义符号。所以若是要创建正则表达式的.,我们需要输入字符串”\.”。

    # To create the regular expression, we need \\
    dot <- "\\."
    
    # But the expression itself only contains one:
    writeLines(dot)
    #> \.
    
    # And this tells R to look for an explicit .
    str_view(c("abc", "a.c", "bef"), "a\\.c")

    <截图3>

    如果\在正则表达式里用作转义字符,那要匹配反斜线\要怎么做呢?你还是需要让其转义,创建正则表达式\。要想使用正则表达式,你需要个字符串,这里还需要转义\。所以,你想要匹配反斜线\的话,你要输入”\\”————你需要四个反斜线才能匹配到一个真正的反斜线!

    x <- "a\\b"
    writeLines(x)
    #> a\b
    
    str_view(x, "\\\\")

    <截图3>

    3.1.1 小练习

    1. 解释为什么这些字符串不能匹配到一个\:”\”, “\”, “\\”。
    2. 如何匹配"'\
    3. \..\..\..这个正则表达式会匹配到什么?如何用一个字符串表现它?

    3.2 锚点

    默认情况下,正则表达式会匹配任何位置的字符串。使用锚点对正则表达式十分有用,那样正则表达式就能确定要匹配字符串首端或是末端的特定字符串。
    你可以使用:
    * ^来匹配字符串的首端
    * $来匹配字符串的末端

    x <- c("apple", "banana", "pear")
    str_view(x, "^a")

    <截图4>

    str_view(x, "a$")

    <截图5>

    要想记住这两个符号,试试我从Evan Misshula学到的记忆法:
    如果你一开始就拥有了力量(^),那你到最后会收获金钱($)。

    要想让正则表达式只匹配完整的一个字符串,使用^和$来匹配:

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

    <截图6>

    str_view(x, "^apple$")

    <截图7>

    你也可以使用\b来匹配两边有边界的单词。在R里我不经常用到它,但我在使用RStudio时会用来查找某个函数是否是其他函数的组成部分。例如,我会搜索\bsum\b来避免匹配到summarise, summary, rowsum这些函数。

    3.2.1 小练习

    1. 如何匹配"$^$"这个字符串?
    2. 根据string::words中的语料库,创建正则表达式来分别查找符合下列条件的单词:
        1. 以”y”开头。
        1. 以”x”结束。
        1. 正好是三个字母组成。(拒绝使用str_length()作弊!)
        1. 带有七个以上的字母。
          由于这个列表比较长,你或许想要使用str_view()中的match参数来只显示匹配到的或是未匹配到的单词。

    3.3 字符类型和多选符

    正则表达式中还有很多特殊符号能够匹配多个字符。比如前面已经出现的.,可以匹配除了换行符的任何字符。以下是四个这类符号:
    * \d: 匹配任何整数.
    * \s:匹配任何空白符(例如空格符,制表符,和换行符).
    * [abc]: 能匹配a, b, 或c.
    * [^abc]: 匹配除了a, b, 或c以外的任何字符.

    记住,要想创建包含\d\s的正则表达式,你要多写一个\,所以应该输入"\\d""\\s"

    你也可以使用多选符来在一个或多个多选模式中选取字符。例如,abc|d..f将匹配”abc”或”deaf”。注意,|的优先权较低,所以abc|xyz将匹配abcxyz而不是abcyz或者abxyz。就如一些数学符号一样,若是优先权问题困扰着你,你可以使用括号来让其意义更为清楚:

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

    3.3.1 小练习

    1. 使用正则表达式匹配以下条件的单词:
        1. 由元音开头。
        1. 只包含辅音字母的(提示:想想如何匹配”非”元音字母)。
        1. ed结尾,但不以eed结尾。
        1. ingise结尾
    2. 验证一下构词规则”i通常在e之前,除非在前面有c”
    3. “q”后面是否总跟着”u”?
    4. 写出个正则表达式,匹配一个由英式英语而不是美式英语写出的单词。
    5. 写出个正则表达式,匹配常见的电话号码。

    3.4 重复匹配

    这一部分我们将学习控制匹配的次数:

    • ?: 0次或1次
    • +: 1次及以上
    • *: 0次及以上
    x <- "1888 is the longest year in Roman numerals: MDCCCLXXXVIII"
    str_view(x, "CC?")

    <截图8>

    str_view(x, "CC+")

    <截图9>

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

    注意,这种操作符的优先度较高,所以你可以用colou?r来匹配其英式形式和美式形式。这意味着,其多数情况下要输入括号来注明,例如bana(na)+

    你也可以标明需要匹配的次数:

    • {n}: 正好n次
    • {n,}: n次及以上
    • {,m}: 最多m次
    • {n,m}: n次和m次之间
    str_view(x, "C{2}")

    <截图10>

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

    <截图11>

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

    默认情况下,这些匹配是”贪婪匹配”:他们将匹配尽可能长的字符串。当然你也能设置”懒惰匹配”,匹配出尽可能短的字符,只需在后面加上?即可。这是正则表达式的高级特性,而且十分有用:

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

    <截图12>

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

    <截图13>

    3.4.1 小练习

    1. {m,n}来表示?,+,*这三种匹配方式。
    2. 用自己的话说一下这些正则表达式的作用(仔细阅读,判断我是用一个正则表达式还是一个字符串来定义正则表达式):
        1. ^.*$
        1. "\\{.+\\}"
        1. \d{4}-\d{2}-\d{2}
        1. "\\\\{4}"
    3. 写出个正则表达式,找出符合以下条件的单词:
        1. 以三个辅音字母开头。
        1. 带有连续三个原因。
        1. 带有连续两个及以上元音-辅音字母对。
    4. 去完成链接中的正则表达式练习

    3.5 分组和反向引用

    之前的部分,你学会如何使用括号来给表达式消除歧义。括号也能用于定义”分组”,这些分组能让你在之后使用\1\2这样的符号进行反向引用。例如,以下正则表达式会找到所有包含重复字母组合的水果单词:

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

    <截图14>

    3.5.1 小练习

    1. 说出以下表达式会匹配到什么:
        1. (.)\1\1
        1. "(.)(.)\\2\\1"
        1. (..)\1
        1. "(.).\\1.\\1"
        1. "(.)(.)(.).*\\3\\2\\1"
    2. 写出符合以下匹配条件的正则表达式:
        1. 以相同字符开始和结束。
        1. 包含重复的字母对(例如,”church”包含两个”ch”)
        1. 包含一个重复了至少三次的字母(例如,”eleven”有三个”e”)

    4 相关工具

    到这里你已经学会了正则表达式的基本用法,是时候将它们用于解决实际问题了。这部分你将学会大量stringr包里的函数,它们能让你:

    • 确定哪些字符串能够被匹配
    • 找到匹配结果的位置
    • 提取匹配结果的内容
    • 替换匹配结果为其他内容
    • 基于匹配结果切分字符串

    这里要注意,因为正则表达式十分强大,想要用一个正则表达式来解决所有难题也很简单。
    就如Jamie Zawinski所说:

    有些人面对一个难题时,会想“我知道,我会用正则表达式来解决”。于是,他们现在遇到了两个难题。

    作为个警示的例子,看看下面这个用来检查email地址是否有效的正则表达式:

    (?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]
    )+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:
    \r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(
    ?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ 
    \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\0
    31]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\
    ](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+
    (?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:
    (?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z
    |(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)
    ?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\
    r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[
     \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)
    ?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t]
    )*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[
     \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*
    )(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t]
    )+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)
    *:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+
    |\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r
    \n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:
    \r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t
    ]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031
    ]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](
    ?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?
    :(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?
    :\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)|(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?
    :(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?
    [ \t]))*"(?:(?:\r\n)?[ \t])*)*:(?:(?:\r\n)?[ \t])*(?:(?:(?:[^()<>@,;:\\".\[\] 
    \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|
    \\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>
    @,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"
    (?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t]
    )*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
    ".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?
    :[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[
    \]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:[^()<>@,;:\\".\[\] \000-
    \031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(
    ?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)?[ \t])*(?:@(?:[^()<>@,;
    :\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([
    ^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\"
    .\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\
    ]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\
    [\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\
    r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] 
    \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]
    |\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?(?:[^()<>@,;:\\".\[\] \0
    00-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\
    .|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,
    ;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|"(?
    :[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*))*@(?:(?:\r\n)?[ \t])*
    (?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".
    \[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t])*(?:[
    ^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\]
    ]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(?:\r\n)?[ \t])*)(?:,\s*(
    ?:(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
    ".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(
    ?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[
    \["()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t
    ])*))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t
    ])+|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?
    :\.(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|
    \Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*|(?:
    [^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".\[\
    ]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)*\<(?:(?:\r\n)
    ?[ \t])*(?:@(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["
    ()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)
    ?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>
    @,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*(?:,@(?:(?:\r\n)?[
     \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,
    ;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\.(?:(?:\r\n)?[ \t]
    )*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\
    ".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*)*:(?:(?:\r\n)?[ \t])*)?
    (?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\["()<>@,;:\\".
    \[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])*)(?:\.(?:(?:
    \r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z|(?=[\[
    "()<>@,;:\\".\[\]]))|"(?:[^\"\r\\]|\\.|(?:(?:\r\n)?[ \t]))*"(?:(?:\r\n)?[ \t])
    *))*@(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])
    +|\Z|(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*)(?:\
    .(?:(?:\r\n)?[ \t])*(?:[^()<>@,;:\\".\[\] \000-\031]+(?:(?:(?:\r\n)?[ \t])+|\Z
    |(?=[\["()<>@,;:\\".\[\]]))|\[([^\[\]\r\\]|\\.)*\](?:(?:\r\n)?[ \t])*))*\>(?:(
    ?:\r\n)?[ \t])*))*)?;\s*)

    这是个相当典型的例子(因为email地址通常惊人地复杂),但在实际代码里经常被用到。你可以查看该StackOverflow链接以获得更多细节。

    别忘了你在使用编程语言,你任意使用其他工具。通常情况下,创建一系列简单的正则表达式会比创建一整个复杂的正则表达式要简单。如果你创建正则表达式过程中遇到困难,你可以停下想想,看是否能将问题简化成简单点的问题,在遇到下个问题前解决所有问题。

    4.1 检测匹配结果

    要想知道一个字符向量是否符合一个模式,使用str_detect()函数。它会返回输入向量一样长度的逻辑向量:

    x <- c("apple", "banana", "pear")
    str_detect(x, "e")
    #> [1]  TRUE FALSE  TRUE

    记住,当你在数值型环境中使用逻辑向量时,FALSE的值为0,而TRUE的值为1。所以要是你想知道关于匹配结果的一些情况,sum()mean()在此会很有用:

    # 有多少单词以t开头?
    sum(str_detect(words, "^t"))
    #> [1] 65
    
    # 由元音结尾的单词占多大比例?
    mean(str_detect(words, "[aeiou]$"))
    #> [1] 0.277

    当你遇到复杂的逻辑匹配条件时(例如,要匹配a或b,但没有d的话不匹配c),相较于使用正则表达式,使用多个str_detect()来调用逻辑运算符会处理起来更简单。例如,下面是两个匹配没有元音字母单词的方法:

    # 找到所有至少包含一个元音字母的单词,将其除外
    no_vowels_1 <- !str_detect(words, "[aeiou]")
    
    # 找到所有只包含辅音字母的单词
    no_vowels_2 <- str_detect(words, "^[^aeiou]+$")
    
    identical(no_vowels_1, no_vowels_2)
    #> [1] TRUE

    两种方法的结果是一样的,但我觉得第一种方法明显更简单明了。当你的正则表达式变得相当复杂时,试着将其分成一个个小部分,再给它们分别取名字,最后再用逻辑运算符来将它们结合起来。

    通常我们使用str_detect()来选择符合某种模式的元素。你可以将其运用于逻辑切片,或是较为方便的str_subset()分装器:

    words[str_detect(words, "x$")]
    #> [1] "box" "sex" "six" "tax"
    str_subset(words, "x$")
    #> [1] "box" "sex" "six" "tax"

    通常情况下,你的字符串属于某个数据框中的一列,我们会使用filter()处理它:

    df <- tibble(
      word = words, 
      i = seq_along(word)
    )
    df %>% 
      filter(str_detect(words, "x$"))
    #> # A tibble: 4 × 2
    #>    word     i
    #>   <chr> <int>
    #> 1   box   108
    #> 2   sex   747
    #> 3   six   772
    #> 4   tax   841

    str_detect()的一个变体则是str_count(),这个函数不是简单返回对或错,而是告诉你在这一个字符串中有多少个符合条件的匹配结果:

    x <- c("apple", "banana", "pear")
    str_count(x, "a")
    #> [1] 1 3 1
    
    # On average, how many vowels per word?
    mean(str_count(words, "[aeiou]"))
    #> [1] 1.99

    mutate()一起使用str_count()效果也很好:

    df %>% 
      mutate(
        vowels = str_count(word, "[aeiou]"),
        consonants = str_count(word, "[^aeiou]")
      )
    #> # A tibble: 980 × 4
    #>       word     i vowels consonants
    #>      <chr> <int>  <int>      <int>
    #> 1        a     1      1          0
    #> 2     able     2      2          2
    #> 3    about     3      3          2
    #> 4 absolute     4      4          4
    #> 5   accept     5      2          4
    #> 6  account     6      3          4
    #> # ... with 974 more rows

    值得注意的是,这些匹配结果不会重叠。例如,在abababa这个字符串中,你觉得aba会被匹配到多少次呢?正则表达式告诉你是两次而不是三次:

    str_count("abababa", "aba")
    #> [1] 2
    str_view_all("abababa", "aba")

    <截图15>

    注意str_view_all()的用法。就如你刚知道的,很多stringr函数都是成对出现的:一个函数用于匹配单个结果,另一个匹配所有结果。而那后者则通常带有后缀_all

    4.2 小练习

    1. 对于以下练习,试着使用单个正则表达式,和多个str_detect()调用来分别完成。

        1. 找到所有以x开头或结尾的单词。
        1. 找到所有以元音字母开头和以辅音字母结束的单词。
        1. 有没有包含所有元音字母的单词。
    2. 什么单词的元音字母数量最多?什么单词中元音字母所占比例最大?(提示:这个问题中的分母是什么呢?)

    4.3 提取匹配结果

    要想提取文本里匹配结果,可以使用str_extract()。这次,我将用个更为复杂的例子。我将用到Harvard sentences,这是用于检测VOIP系统的一套文本,但在练习正则表达式时也一样有用。它们内置于string::sentences里:

    length(sentences)
    #> [1] 720
    head(sentences)
    #> [1] "The birch canoe slid on the smooth planks." 
    #> [2] "Glue the sheet to the dark blue background."
    #> [3] "It's easy to tell the depth of a well."     
    #> [4] "These days a chicken leg is a rare dish."   
    #> [5] "Rice is often served in round bowls."       
    #> [6] "The juice of lemons makes fine punch."

    如果我们想找到所有包含颜色的句子,我们首先创建一个以颜色名字构成的向量,然后将其转变为正则表达式:

    colours <- c("red", "orange", "yellow", "green", "blue", "purple")
    colour_match <- str_c(colours, collapse = "|")
    colour_match
    #> [1] "red|orange|yellow|green|blue|purple"

    现在我们就能选出那些包含颜色词的句子了,然后提取出来看看它们都是什么:

    has_colour <- str_subset(sentences, colour_match)
    matches <- str_extract(has_colour, colour_match)
    head(matches)
    #> [1] "blue" "blue" "red"  "red"  "red"  "blue"

    注意,str_extract()只会提取其首次匹配的结果。我们可以直接选取带有一个匹配结果以上的所有句子:

    more <- sentences[str_count(sentences, colour_match) > 1]
    str_view_all(more, colour_match)

    <截图16>

    str_extract(more, colour_match)
    #> [1] "blue"   "green"  "orange"

    这是stringr函数中的常见模式,因为只处理一次匹配能让你使用更为简单的数据结构。要想得到所有匹配结果,使用str_extract_all()。它将返回一个列表:

    str_extract_all(more, colour_match)
    #> [[1]]
    #> [1] "blue" "red" 
    #> 
    #> [[2]]
    #> [1] "green" "red"  
    #> 
    #> [[3]]
    #> [1] "orange" "red"

    你将在lists部分和iteration部分了解到更多关于列表的信息。

    如果你使用simplify = TRUE参数,str_extract_all()会返回一个矩阵:

    str_extract_all(more, colour_match, simplify = TRUE)
    #>      [,1]     [,2] 
    #> [1,] "blue"   "red"
    #> [2,] "green"  "red"
    #> [3,] "orange" "red"
    
    x <- c("a", "a b", "a b c")
    str_extract_all(x, "[a-z]", simplify = TRUE)
    #>      [,1] [,2] [,3]
    #> [1,] "a"  ""   ""  
    #> [2,] "a"  "b"  ""  
    #> [3,] "a"  "b"  "c"

    4.3.1 小练习

    1. 在之前的例子中,你会发现那个正则表达式会匹配到”flickered”,但它不是颜色词。修改该正则表达式以修复这个问题。

    2. 从 Harvard sentences 数据中,提取:

        1. 每个句子的第一个单词。
        1. 所有以ing结尾的单词。
        1. 所有复数词。

    4.4 分组匹配

    这篇文章前面些内容里,我们谈到了使用括号来明确优先级和匹配时的反向引用。你也可以使用括号来提取整个匹配结果的某些部分。例如,我们要想提取句子里的名词。我们可以直接找出那些在”a”或”the”后面的单词。想要在正则表达式中定义一个“单词”的方式比较特别,所以在此我将使用个方法来匹配尽量符合条件的单词:一串至少有一个非空格字符的字符串。

    noun <- "(a|the) ([^ ]+)"
    
    has_noun <- sentences %>%
      str_subset(noun) %>%
      head(10)
    has_noun %>% 
      str_extract(noun)
    #>  [1] "the smooth" "the sheet"  "the depth"  "a chicken"  "the parked"
    #>  [6] "the sun"    "the huge"   "the ball"   "the woman"  "a helps"

    str_extract()会返回完整的匹配结果;而str_match()会返回每个独立的组成部分,返回形式将是个矩阵而不是字符向量:

    has_noun %>% 
      str_match(noun)
    #>       [,1]         [,2]  [,3]     
    #>  [1,] "the smooth" "the" "smooth" 
    #>  [2,] "the sheet"  "the" "sheet"  
    #>  [3,] "the depth"  "the" "depth"  
    #>  [4,] "a chicken"  "a"   "chicken"
    #>  [5,] "the parked" "the" "parked" 
    #>  [6,] "the sun"    "the" "sun"    
    #>  [7,] "the huge"   "the" "huge"   
    #>  [8,] "the ball"   "the" "ball"   
    #>  [9,] "the woman"  "the" "woman"  
    #> [10,] "a helps"    "a"   "helps"

    (很明显,我们的方法仍有很大欠缺,它不仅匹配了名词还匹配了smooth和parked这类形容词。)

    如果你的数据是tibble类型,通常使用tidyr::extract()来处理更简单。它用起来和str_match()差不多,但要你为匹配结果命名:

    tibble(sentence = sentences) %>% 
      tidyr::extract(
        sentence, c("article", "noun"), "(a|the) ([^ ]+)", 
        remove = FALSE
      )
    #> # A tibble: 720 × 3
    #>                                      sentence article    noun
    #> *                                       <chr>   <chr>   <chr>
    #> 1  The birch canoe slid on the smooth planks.     the  smooth
    #> 2 Glue the sheet to the dark blue background.     the   sheet
    #> 3      It's easy to tell the depth of a well.     the   depth
    #> 4    These days a chicken leg is a rare dish.       a chicken
    #> 5        Rice is often served in round bowls.    <NA>    <NA>
    #> 6       The juice of lemons makes fine punch.    <NA>    <NA>
    #> # ... with 714 more rows

    str_extract()一样,如果你想要得到每个字符串的所有匹配结果,你要用str_match_all()来获取。

    4.4.1 小练习

    1. 找到所有紧跟着数字的单词,例如在”one”或”two”之后的单词。将这些数字连同单词一起提取出来。

    2. 找到所有的缩略词。将其以单引号为分界切片。

    4.5 替换匹配结果

    str_replace()str_replace_all()可以让你以其他字符串替换匹配结果。最简单的用法是将匹配结果替换为某个确定的字符串:

    x <- c("apple", "pear", "banana")
    str_replace(x, "[aeiou]", "-")
    #> [1] "-pple"  "p-ar"   "b-nana"
    str_replace_all(x, "[aeiou]", "-")
    #> [1] "-ppl-"  "p--r"   "b-n-n-"

    使用str_replace_all()还可以进行多重替换:

    x <- c("1 house", "2 cars", "3 people")
    str_replace_all(x, c("1" = "one", "2" = "two", "3" = "three"))
    #> [1] "one house"    "two cars"     "three people"
    

    不只是用写好的字符串来替换,你还可以用反向引用的结果来作为替换值。下列代码中我改变了匹配到的第二和第三个单词的位置:

    sentences %>% 
      str_replace("([^ ]+) ([^ ]+) ([^ ]+)", "\\1 \\3 \\2") %>% 
      head(5)
    #> [1] "The canoe birch slid on the smooth planks." 
    #> [2] "Glue sheet the to the dark blue background."
    #> [3] "It's to easy tell the depth of a well."     
    #> [4] "These a days chicken leg is a rare dish."   
    #> [5] "Rice often is served in round bowls."

    4.5.1 小练习

    1. 替换所有正斜线为反斜线。

    2. replace_all()来实现简单的str_to_lower()的功能。

    3. words中的所有第一和最后一个字母。哪些被替换后的字符串仍是正确的单词?

    4.6 切分

    str_split()来将字符串切分为更小的部分。例如,我们可以将句子切分成单词:

    sentences %>%
      head(5) %>% 
      str_split(" ")
    #> [[1]]
    #> [1] "The"     "birch"   "canoe"   "slid"    "on"      "the"     "smooth" 
    #> [8] "planks."
    #> 
    #> [[2]]
    #> [1] "Glue"        "the"         "sheet"       "to"          "the"        
    #> [6] "dark"        "blue"        "background."
    #> 
    #> [[3]]
    #> [1] "It's"  "easy"  "to"    "tell"  "the"   "depth" "of"    "a"     "well."
    #> 
    #> [[4]]
    #> [1] "These"   "days"    "a"       "chicken" "leg"     "is"      "a"      
    #> [8] "rare"    "dish."  
    #> 
    #> [[5]]
    #> [1] "Rice"   "is"     "often"  "served" "in"     "round"  "bowls."

    因为每个部分的单词字母都不一样,这将返回一个列表。如果你在处理一个单字符长度的向量,最简单的方法是提取列表中的第一个元素:

    "a|b|c|d" %>% 
      str_split("\\|") %>% 
      .[[1]]
    #> [1] "a" "b" "c" "d"

    否则,你可以使用simplify = TRUE返回矩阵:

    sentences %>%
      head(5) %>% 
      str_split(" ", simplify = TRUE)
    #>      [,1]    [,2]    [,3]    [,4]      [,5]  [,6]    [,7]    
    #> [1,] "The"   "birch" "canoe" "slid"    "on"  "the"   "smooth"
    #> [2,] "Glue"  "the"   "sheet" "to"      "the" "dark"  "blue"  
    #> [3,] "It's"  "easy"  "to"    "tell"    "the" "depth" "of"    
    #> [4,] "These" "days"  "a"     "chicken" "leg" "is"    "a"     
    #> [5,] "Rice"  "is"    "often" "served"  "in"  "round" "bowls."
    #>      [,8]          [,9]   
    #> [1,] "planks."     ""     
    #> [2,] "background." ""     
    #> [3,] "a"           "well."
    #> [4,] "rare"        "dish."
    #> [5,] ""            ""

    你也可以控制列数的数量:

    fields <- c("Name: Hadley", "Country: NZ", "Age: 35")
    fields %>% str_split(": ", n = 2, simplify = TRUE)
    #>      [,1]      [,2]    
    #> [1,] "Name"    "Hadley"
    #> [2,] "Country" "NZ"    
    #> [3,] "Age"     "35"

    除了以模式来切分,你也可以以字符、行数、句子和单词来切分:

    x <- "This is a sentence.  This is another sentence."
    str_view_all(x, boundary("word"))

    <截图17>

    
    str_split(x, " ")[[1]]
    #> [1] "This"      "is"        "a"         "sentence." ""          "This"     
    #> [7] "is"        "another"   "sentence."
    str_split(x, boundary("word"))[[1]]
    #> [1] "This"     "is"       "a"        "sentence" "This"     "is"      
    #> [7] "another"  "sentence"

    4.6.1 小练习

    1. 切分类似"apples, pears, and bananas"这样的字符串为一个个单词。

    2. 为什么用boundary(word)会比用" "来切分更好?

    3. 以空字符("")切分有什么用?试一下再阅读文档。

    4.7 寻找匹配位置

    str_locate()str_locate_all()可以给你提供每次匹配的始末点。你可以使用str_locate()来找到匹配模式,用str_sub()来提取和/或修改他们。

    5 其他模式类型

    当使用的模式是字符串时,它将会自动包装成一次调用给regex()

    # The regular call:
    str_view(fruit, "nana")
    # Is shorthand for
    str_view(fruit, regex("nana"))

    可以使用regex()的其他参数来控制匹配的细节:

    • ignore_case = TRUE可以无视大小写形式来匹配。其大小写会根据当前本地化设置而定:
    bananas <- c("banana", "Banana", "BANANA")
    str_view(bananas, "banana")

    <截图18>

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

    <截图19>

    • multiline = TRUE可以让^$匹配每一行的始末位置,而不是整个字符串的石墨位置。
    x <- "Line 1\nLine 2\nLine 3"
    str_extract_all(x, "^Line")[[1]]
    #> [1] "Line"
    str_extract_all(x, regex("^Line", multiline = TRUE))[[1]]
    #> [1] "Line" "Line" "Line"
    • comments = TRUE可以让你给复杂的正则表达式写注释。在这里空格会被无视,#后的也不会计入运行代码的部分。要想匹配一个空格,就要转义它:"\\ "
    phone <- regex("
      \\(?     # optional opening parens
      (\\d{3}) # area code
      [)- ]?   # optional closing parens, dash, or space
      (\\d{3}) # another three numbers
      [ -]?    # optional space or dash
      (\\d{3}) # three more numbers
      ", comments = TRUE)
    
    str_match("514-791-8141", phone)
    #>      [,1]          [,2]  [,3]  [,4] 
    #> [1,] "514-791-814" "514" "791" "814"
    • dotall = TRUE将让.匹配所有字符,包括\n

    以下是可以代替部分regex()功能的其他函数:

    • fixed():能够匹配准确的字节序列。它会无视所有特别的正则表达式并在很低的水平上操作。这意味着你可以避免复杂的转义操作并可以比正则表达式匹配得更快:
    microbenchmark::microbenchmark(
      fixed = str_detect(sentences, fixed("the")),
      regex = str_detect(sentences, "the"),
      times = 20
    )
    #> Unit: microseconds
    #>   expr min  lq mean median  uq  max neval
    #>  fixed 157 164  228    170 272  603    20
    #>  regex 588 611  664    635 672 1103    20

    fixed()处理非英文数据时要小心。因为通常会有很多种方法显示相同的字符,所以处理起来会很麻烦。例如,定义“á”有两种方法:用单独的该字符表示或是用”a”加上一个音符符号来表示:

    a1 <- "\u00e1"
    a2 <- "a\u0301"
    c(a1, a2)
    #> [1] "á" "á"
    a1 == a2
    #> [1] FALSE

    这样渲染出来的样子是一致的,但是定义过程却完全不同,所以fixed()在此无法得到匹配结果。然而使用coll()则可以按照人类对字符的对比规则来匹配:

    str_detect(a1, fixed(a2))
    #> [1] FALSE
    str_detect(a1, coll(a2))
    #> [1] TRUE
    • coll():使用标准排序规则来对比字符串。这在进行不分大小写的匹配中十分有用。但要注意,coll()在比较字符时使用了locale参数来控制所使用的规则。
    # 这意味着你还需要注意其规则的不同
    # 在做部分大小写的匹配时:
    i <- c("I", "İ", "i", "ı")
    i
    #> [1] "I" "İ" "i" "ı"
    
    str_subset(i, coll("i", ignore_case = TRUE))
    #> [1] "I" "i"
    str_subset(i, coll("i", ignore_case = TRUE, locale = "tr"))
    #> [1] "İ" "i"

    fixed()regex()都接收ignore_case参数,但它们不允许你选择本地化设置:它们都总会使用默认的本地化设置。你可以用以下代码查看当前的默认设置:

    stringi::stri_locale_info()
    #> $Language
    #> [1] "en"
    #> 
    #> $Country
    #> [1] "US"
    #> 
    #> $Variant
    #> [1] ""
    #> 
    #> $Name
    #> [1] "en_US"

    然而coll()的不足之处就是速度。因为其识别字符是否一致的规则十分复杂,coll()相对于regex()fixed()运行速度会慢很多。

    • 如你所知,你可以用str_split()中的boundary()来匹配边界。你也可以在其他函数使用这个参数:
    x <- "This is a sentence."
    str_view_all(x, boundary("word"))

    <截图20>

    str_extract_all(x, boundary("word"))
    #> [[1]]
    #> [1] "This"     "is"       "a"        "sentence"

    5.1 小练习

    1. regex()fixed()分别找到所有带有\的字符串。

    2. sentences数据中哪五个词最常见?

    6 正则表达式的其他用途

    在R语言基本函数中有两个十分有用的函数也用到了正则表达式:

    • apropos()会从全局环境中搜索所有可用对象。这在你无法记住函数名时特别有用。
    apropos("replace")
    #> [1] "%+replace%"      "replace"         "replace_na"      "str_replace"    
    #> [5] "str_replace_all" "str_replace_na"  "theme_replace"
    • dir()将列出该目录下的所有文件。而pattern参数则接收一个正则表达式,返回符合模式的文件名。例如,你可以这样从当前目录找到所有R Markdown文件:
    head(dir(pattern = "\\.Rmd$"))
    #> [1] "communicate-plots.Rmd" "communicate.Rmd"       "datetimes.Rmd"        
    #> [4] "EDA.Rmd"               "explore.Rmd"           "factors.Rmd"

    7 stringi

    stringr是在stringi的基础上构建的。stringr在你学习R语言时会很有用,因为这个包只用了一小部分函数,却解决了大多数字符串处理会遇到的问题。而stringi则是以功能的全面为目的。stringi包含了任何你需要的函数:stringi有232个函数,而stringr有43个函数。

    如果你发现在用stringr包无法处理一些问题的话,你可以试试stringi中的函数。这两个包的用法很相似,所以你可以很自然地从stringr过渡到stringi。二者最主要的区别在于前缀:str_stri_

    7.1 小练习

    1. 找到stringi包中符合下列功能的函数:

        1. 用来统计单词数量的函数。
        1. 用来找到重复字符串的函数。
        1. 生成随机文本的函数。
    2. 在使用stri_sort()函数时,如何控制分类的语言?

    注:本文由 vvvict0r 翻译自 Hadley Wickham. R for Data Science - string

    展开全文
  • 作者 | Nick Heath译者 | 弯月,责编 | 屠敏出品 | CSDN(ID:CSDNnews)以下为译文:最近,R语言又受到了沉重打击:它在TIOBE最流行语言排行榜上掉出了前20名。这是近三年来R语言首次掉出前20名...

    55c714fee9996a08cea5c96a1a6e7aa6.gif

    两三年前,凭借着强大可视化功能的 R 语言在统计领域可谓是风光无限,不过随着更简单易上手的 Python 崛起,R 语言的市场似乎正逐步被 Python 吞噬。

    8dbd13db5faccd19b35850f2191efbdb.png

    作者 | Nick Heath

    译者 | 弯月,责编 | 屠敏

    出品 | CSDN(ID:CSDNnews)

    以下为译文:

    最近,R语言又受到了沉重打击:它在TIOBE最流行的语言排行榜上掉出了前20名。

    这是近三年来R语言首次掉出前20名。TIOBE认为,R语言衰退的原因是Python逐渐统治了数据科学和机器学习这两个R语言的主要应用领域。

    TIOBE的一名分析师表示:“看来统计编程市场上出现了合并的趋势。”

    “Python成了最大的赢家。原因可能是,如今统计编程从大学走向了业界,而在业界人们更容易接受Python语言。”

    众所周知,Python一直在实用性和用户友好程度上保持了很好的平衡性。上周流媒体巨头Netflix也透露,他们使用了大量Python语言,其中也包括大量统计分析和机器学习方面的应用。

    TIOBE指数会根据主流搜索引擎的结果来评估编程语言的流行程度。有人批评TIOBE指数有时过于马后炮,而且很容易受到语言流行度之外的各种因素的影响。但在这次的排行榜中,TIOBE指数根据一系列的调查结果,得出了R语言正在下滑的结论。

    今年在另一个排行榜——RedMonk编程语言排行中,R语言也下滑了一位,Cloud Academy提供的人才需求分析也表明,只有18%的招聘要求R语言,而66%的招聘要求应聘者掌握Python。与此相似,Slashdata的全美开发者状态报告也表明,69%的机器学习开发者和数据科学家使用Python,而使用R的只有24%。由于缺乏活力、没有社区参与,R语言在Codementor “最不应该学习的语言”排行榜中上升了12位。

    相比之下,Python则占据了统治地位。Kaggle的一份调查发现,Python是数据科学家在工作中最常使用的语言。同时,Python在对机器学习感兴趣的人群中也是最流行的语言。另外,Python还是今年的Stack Overflow开发者调查问卷中增长速度最快的语言。

    话虽如此,现在断言R语言的衰退还为时过早。很多迹象表明,R语言依然在数据科学和统计分析方面占有一席之地。最近的一份调查(尽管反馈人数较低)表明,几乎半数的数据科学家依然经常使用R语言。

    RedMonk的分析师还提醒说,不要将其调查结果过度解读为“R语言的失败”,因为R语言依然是“统计和数据科学的重要基础”。

    原文:https://www.techrepublic.com/google-amp/article/is-python-squeezing-the-r-programming-language-out-of-data-science/

    本文为 CSDN 翻译,转载请注明来源出处。

    【END】

    3d0cd0a0d8f158deac567e1560cd7ef9.png

    作为码一代,想教码二代却无从下手:

    听说少儿编程很火,可它有哪些好处呢?

    孩子多大开始学习比较好呢?又该如何学习呢?

    最新的编程教育政策又有哪些呢?

    下面给大家介绍CSDN新成员:极客宝宝(ID:geek_baby)

    戳他了解更多↓↓↓

    885c9ba9f3163cea158502200d6f2e48.png

     热 文 推 荐 

    焦虑的 BAT、不安的编程语言,揭秘程序员技术圈生存现状!

    华为三星和解;联想全球首发折叠电脑;苹果回应美高院裁决 | 极客头条

    阿里火力全开 IoT!

    天才少年,大学创业,29 岁创立 Coinbase!| 人物志

    没上过大学,曾拒绝盖茨的 Offer,四代码农靠他吃饭 | 人物志

    狂赚320亿! 小伙建立第一个区块链国家, 国土面积7km², 自由之城诞生记

    云在物联网中的惊人优势 | 技术头条

    太嚣张!程序员别再闷头学机器学习了

    补偿100万?Oracle裁900+程序员,新方案已出!

    5ffbf45e7b14ace61a23619060ab913f.gif

    f91c084129ec978b7c131abccc720b48.png你点的每个“在看”,我都认真当成了喜欢
    展开全文
  • R语言中的管道操作 这是R数据科学的读书笔记之一,《R数据科学》是一本教你如何用R语言进行数据分析的书。即便我使用R语言快2年多了,但是读这本书还是受益颇多。 这一篇学习笔记对应第13章:使用magrittr进行管道...

    R语言中的管道操作

    这是R数据科学的读书笔记之一,《R数据科学》是一本教你如何用R语言进行数据分析的书。即便我使用R语言快2年多了,但是读这本书还是受益颇多。

    这一篇学习笔记对应第13章:使用magrittr进行管道操作。关于管道这个概念,我最早在Linux系统中接触,它是Unix系统设计哲学的体现,“组合小功能完成大任务”,比如说BWA比对后排序用管道的写法就是

    bwa mem ref 1.fq 2.fq | samtools sort > align.bam
    

    在R语言接触管道符号"%>%"是在学习dplyr包时候,那个时候我以为这个符号是 Hadley Wickham 创造出来的,其实是来源于Stefan Milton Bache开发的magrittr中。

    基础部分

    在没有管道符号之前,如果我要对一个变量做一系列的分析的话,那么写法是下面这个样子

    # 先创建100个随机数
    nums <- rnorm(100) 
    # 分成两列
    nums_matrix <- matrix(nums, ncol = 2)
    # 分别求两列的均值
    nums_mean <- Matrix::colMeans(nums_matrix)
    

    这里面我写了很多中间变量,要多敲很多字,而且如果我要修改输入的话的100个随机数的话,我需要修改两处。当然可以进行函数嵌套.

    Matrix::colMeans(matrix(rnorm(100), ncol=2))
    

    但是这种写法不利于人的阅读,当我读到这个函数的时候,我需要先连续往大脑里塞进去两个函数后,才能抵达核心,然后再从里往外解析。

    但是有了管道符号之后一切就不一样了,写法就是

    rnorm(100) %>% matrix(ncol=2) %>% Matrix::colMeans()
    

    你会发现从左往右阅读,代码读起来非常的流畅。

    虽然管道看起来很美好,但是在如下的场景中就不太适合了,

    • 操作步骤特别的多,比如说10个,那么你就需要用一些有意义的中间变量来存放中间结果,方便调试
    • 多输入多输出。比如说A和B输入,输出C和D
    • 操作步骤构成了一张复杂关系的有向图,比如说D结果依赖于B和C,而B和C依赖于A。

    简单点说,就是类似于A > B > C > D 这种场景用管道比较好。

    除了%>%这个好用的符号外,magrittr还提供了其他三个比较好用的符号,%$%%<>%%T>%

    高级部分

    上面都是常规操作,作为有一定基础的R语言使用者,更希望探索点这个符号的本质。

    首先明确一点,在R语言中一切符号本质上都是函数,比如说"+"也是一个函数,常规用法都是1 + 2, 但是我们可以用函数的方式来写哦

    `+`(4,5)
    # 9
    

    因此rnorm(100) %>% matrix(ncol=2)其实应该理解成

    `%>%`(rnorm(100), matrix(ncol=2))
    

    那么我们就可以看看管道符号的源代码了

    ?magrittr::`%>%`
    function (lhs, rhs) 
    {
        parent <- parent.frame()
        env <- new.env(parent = parent)
        chain_parts <- split_chain(match.call(), env = env)
        pipes <- chain_parts[["pipes"]]
        rhss <- chain_parts[["rhss"]]
        lhs <- chain_parts[["lhs"]]
        env[["_function_list"]] <- lapply(1:length(rhss), function(i) wrap_function(rhss[[i]], 
            pipes[[i]], parent))
        env[["_fseq"]] <- `class<-`(eval(quote(function(value) freduce(value, 
            `_function_list`)), env, env), c("fseq", "function"))
        env[["freduce"]] <- freduce
        if (is_placeholder(lhs)) {
            env[["_fseq"]]
        }
        else {
            env[["_lhs"]] <- eval(lhs, parent, parent)
            result <- withVisible(eval(quote(`_fseq`(`_lhs`)), env, 
                env))
            if (is_compound_pipe(pipes[[1L]])) {
                eval(call("<-", lhs, result[["value"]]), parent, 
                    parent)
            }
            else {
                if (result[["visible"]]) 
                    result[["value"]]
                else invisible(result[["value"]])
            }
        }
    }
    

    这个代码的核心在于如下两行

    env[["_function_list"]] <- lapply(1:length(rhss), function(i) wrap_function(rhss[[i]], 
            pipes[[i]], parent))
    env[["_fseq"]] <- `class<-`(eval(quote(function(value) freduce(value, 
            `_function_list`)), env, env), c("fseq", "function"))
    

    这两行干的活其实是进行词法转换,也就是把我们之前的管道串联起来的部分转换成

    my_pipe <- function(.){
        . <- rnorm(.) 
        . <- matrix(., ncol = 2)
        . <- Matrix::colMeans(.)
    }
    my_pipe(100)
    

    多说两句

    考虑到在管道里面用"+"."-"这些函数时用到`+`或许会有点诡异,于是magrittr给这些符号命名了对应的别名,如下

    extract `[`
    extract2    `[[`
    inset   `[<-`
    inset2  `[[<-`
    use_series  `$`
    add `+`
    subtract    `-`
    multiply_by `*`
    raise_to_power  `^`
    multiply_by_matrix  `%*%`
    divide_by   `/`
    divide_by_int   `%/%`
    mod `%%`
    is_in   `%in%`
    and `&`
    or  `|`
    equals  `==`
    is_greater_than `>`
    is_weakly_greater_than  `>=`
    is_less_than    `<`
    is_weakly_less_than `<=`
    not (`n'est pas`)   `!`
    set_colnames    `colnames<-`
    set_rownames    `rownames<-`
    set_names   `names<-`
    
    展开全文
  • 本节书摘来自华章出版社《数据科学R语言实现》一 书中的第1章,第1.1节,作者:R for Data Science Cookbook 丘祐玮(David Chiu),更多章节内容可以访问云栖社区“华章计算机”公众号查看。 第1章 R中的函数1.1...

    本节书摘来自华章出版社《数据科学:R语言实现》一 书中的第1章,第1.1节,作者:R for Data Science Cookbook 丘祐玮(David Chiu),更多章节内容可以访问云栖社区“华章计算机”公众号查看。

    第1章

    R中的函数
    1.1 引言
    R语言是数据科学家的主流编程语言。基于著名的数据分析网站KDnuggets的民意测验,3项(2012年到2014年)的调查显示,R语言在数据分析、数据挖掘和数据科学领域中是最受欢迎的语言。对许多数据科学家来说,R语言不仅是一门编程语言,而且相关软件还提供了交互式的开发环境,支持运行各种数据分析任务。
    R语言在数据操作和分析方面有许多优势。下面是3个最显著的优势。
    开源并且免费:用户使用SAS或者SPSS需要购买使用许可。而用户使用R语言是免费的,并且可以方便地学习如何实现每一个函数源代码中的统计算法。
    强大的数据分析函数:R语言在数据科学领域很出名。许多生物学家、统计学家和编程人员在使用CRAN(Comprehensive R Archive Network)全球发布之前,就把他们的模型封装在R程序包里了。这种机制允许任何用户通过CRAN包下载和安装,开展分析项目。
    易于使用:由于R语言是一种自解释的高级语言,使用R语言编程非常简单。R语言用户只需要知道如何使用R函数,并借助强大的文档即可知道每一个变量如何工作,而不需要了解其背后复杂的数学知识,就可以轻松地执行高级数据分析任务。
    这些优势使得复杂的数据分析变得更加简单易行。对此,R语言用户都深信不疑。而且,R语言尤其适合基础用户或者开发人员。对于一名R语言用户,我们只需知道函数如何工作,而不需要知道函数实现的具体知识。类似于SPSS,我们可以通过R语言的交互式shell,运行各种类型的数据分析任务。另外,作为一名R语言开发人员,我们可以编写函数来创建新的模型,甚至可以把实现的函数封装在包中。
    本书并不会讲解如何从零开始编写R程序。相反,本书的目标是要讨论如何成为一名R语言开发人员。本章的主要目的是向用户展示如何定义函数,从而加速分析过程。我们首先介绍如何创建函数,然后介绍R环境,接着讲解如何创建匹配参数。 本章的内容还会涵盖如何执行R语言函数式编程,如何创建高级函数,例如中缀操作符和替代,以及如何处理错误和调试函数。

    展开全文
  • 从事数据科学Python和R语言学哪个好?答案肯定是学Python更好,当然也不是就完全否定了学习R语言的作用和意义。只是和R语言比较起来,Python优势...选择学习Python一个重要理由就是,在从事数据科学的工作,...
  • 本节书摘来自华章计算机《数据科学R语言实现》一书中的第1章,第1.1节,作者 丘祐玮(David Chiu),更多章节内容可以访问云栖社区“华章计算机”公众号查看。 第1章 R中的函数 1.1 引言 R语言数据科学家的主流...
  • 两三年前,凭借着强大可视化功能 R 语言在统计领域可谓是风光无限,不过随着更简单易上手 Python 崛起,R 语言...最近,R语言又受到了沉重打击:它在TIOBE最流行语言排行榜上掉出了前20名。 这是近三年来...
  • 本文来源:数据分析师R 语言是...为此,我想解释为什么我对 R 语言的长期前景非常乐观,以及为什么我认为这也许是今天可以学习最好的数据科学语言R 语言始终是最好的语言之一我想让你们明白一件事情是:目前 R
  • 本节书摘来自华章计算机《数据科学R语言实现》一书中的第1章,第1.10节,作者 丘祐玮(David Chiu),更多章节内容可以访问云栖社区“华章计算机”公众号查看。 1.10 处理函数中的错误 如果你对现代编程语言比较...
  • 之前我们在kdnuggets上做了这样一个问卷调查,2016、2017两年,在分析、数据科学和机器学习工作,你用R语言,还是Python,或两者都用,或选择其他语言?通过分析954个回答,我们得出了这样结论:虽然Python...
  • 前言9102年是互联网大环境不太好一年,这一年更需要苦练基本功,数据科学领域基本功无非就是数据处理,而 DataFrame 是其中核心。那么,都9102年了,如何在 R 语言中优雅地使用 DataFrame 呢?是否优雅是矿工...
  • 本节书摘来自华章出版社《数据科学R语言实现》一 书中的第2章,第2.6节,作者:R for Data Science Cookbook 丘祐玮(David Chiu),更多章节内容可以访问云栖社区“华章计算机”公众号查看。 2.6 从数据库中读取...
  • 近日,kdnuggets做了一个关于数据科学、机器学习语言使用情况问卷调查,他们分析了954个回答,得出结论——Python已经打败R语言,成为分析、数据科学和机器学习平台使用频率最高语言。有关此次问卷更具体...
  • 现代化数据科学中的 DataFrame 概念源起R语言,而 Python Pandas 和 Spark DateFrame 都是参考R设计的。不过在实际的网络数据通讯中,类似DateFrame这样的格式却并不是主流,真正主流的方式其实是JSON(JavaScript ...
  • 1. 我认为 R,Python 和 Julia 是机器学习和数据科学中三个最重要的语言。任何人如果想在这个领域有所发展,长远来说这三种语言都需要掌握。2. 我自己学习数据科学主要语言当然是 Python,不光是因为用 Python ...
  • 近日,kdnuggets做了一个关于数据科学、机器学习语言使用情况问卷调查,他们分析了954个回答,得出结论——Python已经打败R语言,成为分析、数据科学和机器学习平台使用频率最高语言。有关此次问卷更具体...
  • 数据科学和机器学习使用最多20个包都在这里。
  • 数据科学中的R编程:高容量数据 中文字幕R Programming in Data Science: High Volume Data 数据填满了所有可用空间,现在存储空间便宜,数据量已经爆炸 但是,如果没有分析和背景,所有这些信息都是无用的 R编程...
  • 本节书摘来自异步社区《数据科学R语言实战》一书中的第2章,第2.1节,作者 【美】Dan Toomey(丹·图米),更多章节内容可以访问云栖社区“异步社区”公众号查看 第2章 序列的数据挖掘 数据科学R语言实战数据...
  • Matt Asay (From MongoDB)2013年11月25号对于数据科学家来说,R语言无疑是他们选择,但是Python正在抢夺R语言的地盘。关于这个改变有很多 原因,或许最大原因是相对于R语言的难以掌握复杂编程环境来说,...
  • 本节书摘来自华章出版社《数据科学R语言实现》一 书中的第3章,第3.8节,作者:R for Data Science Cookbook 丘祐玮(David Chiu),更多章节内容可以访问云栖社区“华章计算机”公众号查看。 3.8 合并数据 数据...
  • 本节书摘来自华章计算机《数据科学R语言实现》一书中的第3章,第3.6节,作者 丘祐玮(David Chiu),更多章节内容可以访问云栖社区“华章计算机”公众号查看。 3.6 过滤数据 数据过滤对于希望分析部分数据而不是...
  • John-Hopkins-University-Mastering-Software-Development-in-R:此存储库涵盖用于构建数据科学工具的R软件开发。 随着数据科学领域的发展,很明显,软件开发技能对于产生有用的数据科学结果和产品至关重要。 您将...
  • 本节书摘来自异步社区《数据科学R语言实战》一书中的第1章,第1.1节,作者 【美】Dan Toomey(丹·图米),更多章节内容可以访问云栖社区“异步社区”公众号查看 第1章 模式的数据挖掘 数据科学R语言实战数据...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 901
精华内容 360
关键字:

数据科学中的r语言