Lesson 5-2: 正規表現

2018/12/03

前のステップ

Lesson 5-1: R の文字列 では次のことを勉強しました。

このセッションの目標

R で文字列を扱う方法を学びます。特に,

の基本を学びます。

準備

ここでも stringr を使います。

library(tidyverse)
## ── Attaching packages ───────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.1.0     ✔ purrr   0.2.5
## ✔ tibble  1.4.2     ✔ dplyr   0.7.8
## ✔ tidyr   0.8.2     ✔ stringr 1.3.1
## ✔ readr   1.1.1     ✔ forcats 0.3.0
## ── Conflicts ──────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()

Cheat Sheet は前回と同様,こちら https://www.rstudio.com/resources/cheatsheets/#stringr です。

正規表現

テキスト処理では,与えられた文字列の中に特定の文字列や文字列の類型(パターン)が含まれるかどうかを調べて,マッチする部分を数えたり,別の文字で置き換えたりするということがよくあります。

例えば,次のようなテキストファイルがあったとしましょう。

Alice <alice@example.com>
Bob <bob@example.org>
Charlie <charl@example.net>

このテキストファイルを次のような表形式データに変換したいとします(要するに tibble にしたいのです)。3行くらいなら手作業の方が早いかもしれませんが,数千行になったらどうしましょう。(学生アルバイトを雇うよりも早く確実に処理する方法があります)

name user domain
Alice alice example.com
Bob bob example.org
Charlie charl example.net

このような目的で使える強力なツールがあります。正規表現(regular expressions, regex)と呼ばれる,「文字のパターン」に対して処理を行うための規則です。上の例では,行ごとに内容は違うものの,次のような明確なパターンがありました。

name <user@domain>

このように一定のパターンに従う文字の並びを探し出して,処理を実行するのが正規表現の役割です。

実験

正規表現は R とは独立して定義されているものなので,正規表現の習得に R言語は必要ではありません。

RStudio で新しい「Text File」を作成し,

Alice <alice@example.com>
Bob <bob@example.org>
Charlie <charl@example.net>

をコピーしてください。

テキストエディタ

テキストエディタ

次に,Ctrl+F (Mac なら Comman+F) を押すと検索窓が現れますので,

  • “Regex” にチェックを入れる
  • 左の入力フォーム(検索欄)には「(.*) <(.*)@(.*)>」を入力
  • 右の入力フォーム(置換欄)には「\1,\2,\3」を入力

して,「All」をクリックしてください。コンマ区切りのデータに置き換わったはずです。これが正規表現の効能です。

正規表現の初歩

次のことを覚えるだけでテキスト処理がかなり上達します。

文字を表す正規表現

探しもの regex R
特定の文字列 abc abc
任意の文字 . .
数字(その1) [0-1] [0-1]
数字(その2) \d \\d
記号を除く文字 \w \\w
記号 \W \\W
ピリオド \. \\.

**例 5.2.1

\d+-\d+-\d+ という正規表現は 090-xxxx-yyyy の形式の電話番号を探す単純な正規表現です。電話番号が記録されているデータがあったときに携帯電話の割合などを調べるのに使えそうですね。

グループ

グループ regex
どれか1つ [abc] など
どれでもない [^abc] など
この並びで現れる (abc) など
いずれか (ab|cd) など

量を表す正規表現

regex
0個以上 *
1個以上 +
0 or 1 ?
n個 {n}
n個以上 {n,}
n個以下 {,n}
m個以上,n個以下 {m,n}

位置を表す正規表現

位置 regex R
行頭 ^ ^
行末 $ $
単語境界 \b \\b

後方参照

丸括弧(( ))はグループ化するために使えることを上の表で紹介しました。例えば (ab)+ababab などにマッチする。丸括弧は,他にも「後で使えるようにしたりするため」にも使われます。最初に現れた ( ) に 該当する部分を \1 で呼び出すことができます。

さきほどの例 (.*) <(.*)@(.*)>\1,\2,\3 に置換するというのは,

  • (.*) は「任意の文字列」をグループ化して後で使う
  • <, @, > はその位置に正にその文字があるということなので,
  • 「文字列1 文字列2@文字列3」 を探し
  • これを「文字列1,文字列2,文字列3」に置き換える

ということをやっていました。

実際にはドメインに使える文字・記号が決まっているのでそのようなルールを課す方が安全(例えば,スペースやコンマは入らないとか)なのですが,正規表現を極める方向に進むのは得策ではないかもしれません。(とても難しいので)

とりあえずのところは「正規表現という強力なツールが存在している」ということさえ認識していただければ OK です。正規表現が必要になるタスクに出会うたびに正規表現のルールを確認していきましょう。

R の正規表現

R で正規表現を使うときに 1つだけ注意をしてほしい点があります。それは「正規表現で使われるバックスラッシュ \ は R では \\ になる」ということです。例えば,\w は通常の文字(a, b, c など)にマッチする正規表現ですが,R は \w を R のエスケープ文字だと推定してしまうので(そのようなエスケープ文字は存在しないので)エラーになります。\ のエスケープ文字\\を使ってこの問題を回避する必要があるのです。

それでは例題のデータを tibble に変換してみましょう。

dta <- "Alice <alice@example.com>
Bob <bob@example.org>
Charlie <charl@example.net>"
writeLines(dta)
## Alice <alice@example.com>
## Bob <bob@example.org>
## Charlie <charl@example.net>
address <- 
  dta %>% 
  str_split("\n") %>% 
  unlist()
address
## [1] "Alice <alice@example.com>"   "Bob <bob@example.org>"      
## [3] "Charlie <charl@example.net>"
match_matrix <- address %>% 
  str_match("(.*) <(.*)@(.*)>") 
match_matrix
##      [,1]                          [,2]      [,3]    [,4]         
## [1,] "Alice <alice@example.com>"   "Alice"   "alice" "example.com"
## [2,] "Bob <bob@example.org>"       "Bob"     "bob"   "example.org"
## [3,] "Charlie <charl@example.net>" "Charlie" "charl" "example.net"
users <- as_tibble(match_matrix[, 2:4])
names(users) <- c("name", "user", "domain")
users
## # A tibble: 3 x 3
##   name    user  domain     
##   <chr>   <chr> <chr>      
## 1 Alice   alice example.com
## 2 Bob     bob   example.org
## 3 Charlie charl example.net