クボセンセーとのやりとりで出てきたstringiパッケージのサワリです。


はじめに

stringiとはバックエンドにICUを採用してゴニョゴニョできるようになりつつ、 処理本体をCでゴニョゴニョすることで高速な文字列処理を可能とする ハドレー教の一宗派である。

だそうです。 ハドレー教だけあって、APIはstringrのそれを採用してます。 str_hoge_hogestri_hoge_hogeにすればだいたいのことはできます。

ちなみにここで挙げてる以外にも高速便利関数がたくさんあります。 あ、%>%をオーバーロードしてるので、dplyrと一緒に使っちゃダメです。 この問題、いい加減なんとかした方がいいと思うんだけど・・・。


セッションインフォ

sessionInfo()
## R version 3.1.1 (2014-07-10)
## Platform: x86_64-apple-darwin13.1.0 (64-bit)
## 
## locale:
## [1] ja_JP.UTF-8/ja_JP.UTF-8/ja_JP.UTF-8/C/ja_JP.UTF-8/ja_JP.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## loaded via a namespace (and not attached):
## [1] digest_0.6.4    evaluate_0.5.5  formatR_1.0     htmltools_0.2.6
## [5] knitr_1.6.22    rmarkdown_0.3.8 stringr_0.6.2   tools_3.1.1    
## [9] yaml_2.1.13

準備

ベンチマークについては一部、公式 http://www.rexamine.com/2014/05/stringi-0-2-3-released/ あたりから借りてますぅ

set.seed(42)
library(knitr)
opts_chunk$set(cache=TRUE, cache.extra = 1)
options(width = 1000)
library(microbenchmark)
library(stringi)
library(stringr)


# 10文字のランダム文字列が1e+05個だよー
str = stri_rand_strings(1e+05, 10)
head(str)
## [1] "uwHpdWj8eh" "SivFSwy7TY" "u8zw5VOuRp" "joOg0p0Cub" "NR2yQxtdyc" "KLOm2kfAGV"
# 日本語もやってみるよー
strj = stri_rand_strings(1e+05, 10, pattern = "[あ-ん]")
head(strj)
## [1] "でっよくぬっねってけ" "はへぼのくゅえぎあぜ" "ささゐがすぽまきをご" "まふあねさのりそふに" "へべおゑふぱつへうけ" "うのめづづらうゐすね"

ベンチマーク

検索

stringi速い。stringrが微妙に遅いわー

固定文字列

microbenchmark(stri_detect_fixed(str, "abba"),
               str_detect(str, fixed("abba")),
               grepl("abba", str, fixed=TRUE))
## Unit: milliseconds
##                              expr      min       lq      mean   median        uq      max neval
##    stri_detect_fixed(str, "abba") 4.035864 4.185468  5.601303 4.327253  4.939369 62.68688   100
##    str_detect(str, fixed("abba")) 7.661898 7.884671 11.043578 8.209151 14.902786 69.43982   100
##  grepl("abba", str, fixed = TRUE) 5.516206 5.639213  7.124968 5.743263  6.063265 23.74515   100

正規表現

microbenchmark(stri_detect_regex(str, "a..a"),
               str_detect(str, "a..a"),
               grepl("a..a", str))
## Unit: milliseconds
##                            expr      min       lq     mean   median       uq      max neval
##  stri_detect_regex(str, "a..a") 15.75550 16.14140 17.93948 16.64575 18.22624 35.71516   100
##         str_detect(str, "a..a") 36.25395 36.89241 40.41848 37.57298 44.42513 90.86483   100
##              grepl("a..a", str) 33.86655 34.54136 36.49452 35.01991 37.74537 48.05728   100

日本語検索

圧倒的だな

固定文字列

microbenchmark(stri_detect_fixed(strj, "なめこ"),
               str_detect(strj, fixed("なめこ")),
               grepl("なめこ", strj, fixed=TRUE),
               times = 10)
## Unit: milliseconds
##                                 expr      min       lq     mean   median       uq      max neval
##    stri_detect_fixed(strj, "なめこ") 16.10392 16.47598 16.88976 16.52027 16.89350 19.67792    10
##    str_detect(strj, fixed("なめこ")) 33.16463 33.70853 37.24028 34.96871 41.07442 44.50666    10
##  grepl("なめこ", strj, fixed = TRUE) 31.39783 31.59830 32.57676 32.27361 33.56419 34.31391    10

正規表現

microbenchmark(stri_detect_regex(strj, "な.こ"),
               str_detect(strj, "な.こ"),
               grepl("な.こ", strj),
               times = 10)
## Unit: milliseconds
##                              expr      min       lq     mean   median       uq       max neval
##  stri_detect_regex(strj, "な.こ") 28.38939 28.97387 37.43506 30.35704 42.53637  61.67056    10
##         str_detect(strj, "な.こ") 76.02259 76.24321 89.42131 83.96264 93.42407 127.20450    10
##              grepl("な.こ", strj) 66.49910 73.82659 78.99823 79.45426 82.14890  88.46347    10

ところで「なめこ」ってあるんですかね。

(n <- sum(stri_detect_fixed(strj, "なめこ")))
## [1] 3

おー、3個ありました。


置換

そこそこ速い。

microbenchmark(stri_replace_all_regex(strj, "な.こ", "なめこ"),
               str_replace_all(strj, "な.こ", "なめこ"),
               gsub("な.こ", "なめこ", strj),
               times = 10)
## Unit: milliseconds
##                                             expr       min       lq      mean   median        uq       max neval
##  stri_replace_all_regex(strj, "な.こ", "なめこ")  84.46038  85.6175  87.76002  87.3060  88.68601  93.48167    10
##         str_replace_all(strj, "な.こ", "なめこ") 106.60033 109.4666 115.89717 115.8674 121.70633 123.02140    10
##                    gsub("な.こ", "なめこ", strj) 100.89139 110.6775 112.63626 112.8713 117.52663 120.71247    10

なめこ何個になりましたかね。

(n <- sum(stri_detect_fixed(stri_replace_all_regex(strj, "な.こ", "なめこ"), "なめこ")))
## [1] 138

138個 ^^/


半角 → 全角

han <- "ダイダ、オレ"
(ret <- stri_trans_nfkc(han))
## [1] "ダイダ、オレ"
ret == "ダイダ、オレ"
## [1] TRUE

ちなみにnfkdだとちょっと違います。

han <- "ダイダ、オレ"
(ret <- stri_trans_nfkd(han))
## [1] "ダイダ、オレ"
ret == "ダイダ、オレ"
## [1] FALSE
iconv(ret, "utf-8-mac", "utf-8", ret) == "ダイダ、オレ"
## [1] TRUE

マジデヤミ


最後に

「Rで前処理が、、、文字列処理ができない・・・何故だ?」

「坊やだからさ」

PythonにできてRにできないことはオフサイドルールによるブロック表現だけですからっ!

Enjoy!!