かっこが1or2か所に含まれる文字列からの抽出.R

やりたいこと

hoge (fuga);
hoge (piyo) (fuga)

 特定の列に上記2パターンで記述された値が混在しているので、それを次のような形に分割して別の列に格納したい

hoge, fuga
hoge (piyo), fuga

背景

 例としては、歴代内閣総理大臣の一覧に「菅義偉 (第99代) (令和2年9月16日~令和3年10月4日)」と「安倍晋三 (平成29年11月1日~令和2年9月16日, 平成26年12月24日~平成29年11月1日, 平成24年12月26日~平成26年12月24日, 平成18年9月26日~平成19年9月26日)」が並んでいて、そこから在職期間の記載部分を引っこ抜きたい(何代目かには情報としての価値がない)。この場合、hoge = 総理大臣の名前、fuga = 在職期間、 piyo = n代目に相当する。
 便宜上csv的に書くと、求める結果は下のようになる。

菅義偉 (第99代), (令和2年9月16日~令和3年10月4日)
安倍晋三, (平成29年11月1日~令和2年9月16日, 平成26年12月24日~平成29年11月1日, 平成24年12月26日~平成26年12月24日, 平成18年9月26日~平成19年9月26日)

 最終的には、fugaの中にさらに複数の値が格納されているので、これをばらしてhoge(もしくは hoge (piyo))と対応付けをとるようにしたい。上の例なら下記のようになる。こうしておけば、それぞれの在職期間に対して(さらにあれこれして)在職日数を計算することができるようになる。

安倍晋三, 平成29年11月1日~令和2年9月16日
安倍晋三, 平成26年12月24日~平成29年11月1日
安倍晋三, 平成24年12月26日~平成26年12月24日
安倍晋三, 平成18年9月26日~平成19年9月26日

セパレータの正規表現は?

 最初は"(fuga)"をstringr::str_extractで抽出しようかと思ったのだけれど、抽出されて残ったhoge / hoge (piyo)も別の列に格納したいので、思い切ってdplyr::separateでやってみた。つまり、sep = "\\([平成|令和]"としてfuga部分を切り出すということになるのだが、当然セパレータに指定した文字列は消えてなくなってしまう。はじめかっこは消えてしまってもよいので"\\("だけをセパレータに指定しようとすると、piyoの前でも分割できてしまう。
 "\\("は分割結果として抽出したくない、でもそのあとの"[平成|令和]"は抽出したい。さあどうしたものか。

抽出する場合は?

 一方、stringr::str_extractを使って正規表現で抽出する場合はおそらく下記のようになる。そのときはもう、かっこは抽出しないようにして中身だけ抽出することになる。
   "(?<=\\()[平成|令和]+.*?(?=\\))"
 "(?<=)"でくくられた中身は、認識はするけれど実際には抽出しないものとして扱われる。従って、認識させたい範囲の始端であるはじめかっこを認識しつつ、はじめかっこは抽出せずにその後ろにある文字列からを抽出するのが上の表現だ。"(?=)"も同様だが、反対に直前の文字列までを抽出する。
 これで抽出は可能だけれども、分割するためには残りの部分も別に抽出してあげる必要がある。

セパレータの話に戻る

 これを踏まえて先ほどのセパレータの話を考え直してみる。
 "[平成|昭和]"は認識したいけど、セパレータとしては削除されてほしくない。つまりセパレータとして抽出されたくない。認識されるものの、その直前まではセパレータとして抽出されたい。
 となれば、"(?<=[平成|令和])"としておけばたぶんOKだろう。
 あとは、はじめかっこをどうするか…。認識してほしい、抽出してほしくない。消えてほしい。まあ、単純に"\\("でよいので、最終的にこうなる。
   sep = "\\((?<=[平成|令和])"
 別の言い方をすると、「直後に[平成|昭和]が来るはじめかっこ」をセパレータに設定していることになる。さらに別の言い方をすれば、[平成|令和]の直前、はじめかっことの間に仮想の区切りが挿入されているともいえる。
 実際これで抽出すると、一番最後のとじかっこは残るもののおおむね満足する形で分割することができた。

菅義偉 (第99代), 令和2年9月16日~令和3年10月4日)
安倍晋三, 平成29年11月1日~令和2年9月16日, 平成26年12月24日~平成29年11月1日, 平成24年12月26日~平成26年12月24日, 平成18年9月26日~平成19年9月26日)

 こうなれば後処理は簡単で、str_removeでとじかっこを除去しつつ、カンマをセパレータとしてtidyr::separate_rowsすれば先ほどの再集計が得られることになる。

かっこは残したまま分割するときはどうなるか

 これは比較的簡単で、はじめかっこは認識しつつ、その直前に仮想の区切りが挿入されている状態であればよい。つまりこうなる。
   sep = "(?=\\([平成|令和]+)"
 直後に平成or令和があるはじめかっこを認識しつつも、その直前に仮想の区切りが挿入されて、その仮想区切りをセパレータとして指定するようなイメージだ。

以上。

いいなと思ったら応援しよう!