メモ:tidyr::extract()の正規表現でマッチしたりしなかったりするグループがあるときは、文字列全体にマッチさせつつnon-greedyに
タイトルは何言ってるかわかりませんが…
こういうデータがあるときに、
name_with_note |
---|
name1 |
name2(note1) |
name2(note2) |
(...)
の部分とそれ以外で分けて、こういう結果がほしい、というときの話。
name | note |
---|---|
name1 | NA |
name2 | (note1) |
name2 | (note2) |
簡単に見えてちょっとてこずったのでメモ。1つ目の行には(...)
の部分がないのが問題です。
データはこんな感じ。
d <- data.frame( name_with_note = c( "name1", "name2(note1)", "name2(note2)" ), stringsAsFactors = FALSE )
いろいろ正規表現を変えて試すので、そのための関数を定義しておきます。
library(magrittr) extract_by_regex <- function(regex) { tidyr::extract(d, col = name_with_note, into = c("name", "note"), regex = regex) %>% knitr::kable() }
まずは単純に、(...)
で囲まれている部分とそれ以外の部分を別々の正規表現にするとこんな感じでしょう。
extract_by_regex("(.*)(\\(.*\\))")
name | note |
---|---|
NA | NA |
name2 | (note1) |
name2 | (note2) |
これだと、1行目が引っかかりません。(...)
の部分がないのでまあ当然ですよね。
では次、(...)
の部分は存在したりしなかったりするので?
を付けます。
extract_by_regex("(.*)(\\(.*\\))?")
name | note |
---|---|
name1 | NA |
name2(note1) | NA |
name2(note2) | NA |
今度は、すべての行がヒットしましたが、note
がNA
になってしまいました。これは、.*
の部分がgreedyにマッチしてしまっているからです。
では、non-greedyにするために.*?
にしてみましょう。
extract_by_regex("(.*?)(\\(.*\\))?")
name | note |
---|---|
NA | |
NA | |
NA |
今度はname
が空になってしまいました。.*?
の最小マッチは何にもマッチしないことなので、まあそうなりますよね(これが理解できずに30分ほど悩んだ…)。
正解は、^
と$
を付けて文字列全体にマッチさせることです。
extract_by_regex("^(.*?)(\\(.*\\))?$")
name | note |
---|---|
name1 | NA |
name2 | (note1) |
name2 | (note2) |
正規表現むずかしい。もっと簡単にできるよ、というのがあれば教えてください。