備忘ログ

チラシの裏的備忘録&メモ

Rの文字列で正規表現での意外な挙動

この間関数を作っているときに、文字列をstringrでいじっているときに、正規表現で文字列をマッチングしたときに自分が想定していたのと異なる挙動をしたのでメモしておく。

indenkun.hatenablog.com

問題となる処理

{stringr}str_detctしたときに問題の処理があった。文字列に数字だけの文字列があるのかマッチングしたいときに、(格好つけて)名前付き文字クラスが予め定義されている正規表現のパターンをつかってマッチングしたかった。

どそこで、数値の定義されている名前付き文字クラスの[:digit:]でマッチングしようと思った。

x <- c("昭和", "64", "年")
stringr::str_detect(x, pattern = "[:digit:]")
## [1] FALSE  TRUE FALSE

となって、数字のところだけTRUEで返ってきて嬉しい。

いろいろ処理しているときに、

x <- c("30", "30", 30)
stringr::str_detect(x, pattern = "[:digit:]")
## [1] TRUE TRUE TRUE

となることに気づいた。いや、全角の「30」って数値なの!?と。全角って文字じゃないのか!?と。

この処理は個人的には全角と半角の判別を兼ねて使いたかったので意外な挙動だった。

調べてみた

これは、{stringr}だけでなく、Rで文字列処理していると起こる問題?だということがわかった。

たとえば、{base}の文字列マッチでstr_detctと同じように理論型で結果を返すgrepl

x <- c("30", "30", 30)
grepl(pattern = "[[:digit:]]", x)
## [1] TRUE TRUE TRUE

と、同じく全角の30も数値として結果を返す。

調べてみたら、 [http://www.okadajp.org/RWiki/?R+%E3%81%AB%E3%81%8A%E3%81%91%E3%82%8B%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE#content_1_2:title] によると名前付き文字クラスで予め定義されているものはロケール依存だということがわかった。

つまり、日本語環境下では30も数字だし、30も数字なので間違った挙動はしていなかったということである。

しかし、R上での数値と言ったら半角数字だけで、全角数字は文字として扱われると思っていただけに意外だった。

回避策

全角半角を区別して半角数字のみを拾いたかったら

x <- c("30", "30", 30)
stringr::str_detect(x, pattern = "[0123456789]")
## [1]  TRUE FALSE  TRUE

と格好つけずに素直に描くと、全角はマッチングしなくなる。

greplのような{base}系なら

x <- c("30", "30", 30)
grepl(pattern = "[0123456789]", x)
## [1]  TRUE FALSE  TRUE

でもいいし、格好つけて[:digit:]を使いたかったら

x <- c("30", "30", 30)
grepl(pattern = "[[:digit:]]", x, perl = TRUE)
## [1]  TRUE FALSE  TRUE

とパール風正規表現を使うことでもいける。

残念ながらパール風正規表現を使うオプションがstringr系にはないので、なにか方法があるのかもしれないが、今のところは諦めるしかない。

参考

R における正規表現 - RjpWiki