はじめに
文字列処理・テキスト処理とは、プログラミングを行うなかで、文字列・テキストに対する色々な操作のことを指します。それら処理をうまく使いこなすことで、文字列を自由に処理できるようになります。文字列処理の活用事例は、キーワード抽出、テキスト分類、テキストマイニングの前処理など、多岐に渡ります。 今回の「Rでの文字列処理」シリーズで扱う、文字列処理のライブラリ・関数群やプログラムコードは、R環境上で無料で提供されている、オープン・ソフトウェアを用います。
この記事では、R言語のプログラミングでのテキストマイニング、文字列の検出・検索、テキストの検出・検索について、いろいろと試してみました。 主に、base、stringrのパッケージを扱っています。
テキスト処理の関連記事
下準備について
まずは、関連パッケージとテキストデータを準備します。
######################## #シリーズ共通 ######################## #必要なパッケージの読み込み require(readr) require(stringr) require(stringdist) #テストファイルのダウンロード utils::download.file(url="https://raw.githubusercontent.com/kumeS/Blog/master/TXT_proc/test.txt", destfile="test.txt")
【1】完全一致で、その文字列を含むかどうかのの判定
比較演算子 ==
!=
完全一致の基本は、比較演算子 ==
や !=
を用いて、文字列の一致有無を判定する方法です。
#入力データフレーム c <- data.frame(readr::read_tsv(file="./test.txt", col_names=F)) c #完全一致で、文字列を含むかどうかの判定 #比較演算子を使う場合、その文字列(右辺)を含むかの判定 c == "abc" # X1 #[1,] FALSE #[2,] FALSE #[3,] FALSE #[4,] FALSE #[5,] FALSE #[6,] FALSE c == "abc ABC abc" # X1 #[1,] TRUE #[2,] FALSE #[3,] FALSE #[4,] FALSE #[5,] FALSE #[6,] FALSE #その文字列(右辺)を含まないか? c != "abc ABC abc" # X1 #[1,] FALSE #[2,] TRUE #[3,] TRUE #[4,] TRUE #[5,] TRUE #[6,] TRUE
base::match、stringr::str_match、演算子 %in%
次に、base::match、stringr::str_match、演算子 %in%
を使って、完全一致の判定を行います。
#base::matchを使った場合 base::match(c$X1, table="abc") #[1] NA NA NA NA NA NA base::match(c$X1, table="abc ABC abc") #[1] 1 NA NA NA NA NA #stringr::str_matchを使った場合 stringr::str_match(c$X1, pattern="abc ABC abc") # %in% を使った場合 c$X1 %in% "abc" #[1] FALSE FALSE FALSE FALSE FALSE FALSE c$X1 %in% "abc ABC abc" #[1] TRUE FALSE FALSE FALSE FALSE FALSE
【2】部分一致で、その文字列を含むかどうかの判定
charmatch関数、pmatch関数
まずは、charmatch関数、pmatch関数を使った、部分文字列の一致判定の実行例を示します。どうも文字列の先頭との部分一致を判定しているみたいです。
実行の挙動は、直観的に使いにくいかもしれません・・・
#基本的な実行例(1) charmatch("m", c("mean", "median", "mode")) #[1] 0 # => 複数ヒットの場合、「0」を返す base::charmatch("mo", table=c("mean", "median", "mode")) #[1] 3 ## => modeの「3」を返ってくる base::charmatch("ode", table=c("mean", "median", "mode")) #[1] NA # => 先頭の文字が含まれないと、「NA」を返す #基本的な実行例(2) base::pmatch("m", c("mean", "median", "mode")) #[1] NA base::pmatch("mo", c("mean", "median", "mode")) #[1] 3 #デフォルトは、1対1で対応づける base::pmatch(c("", "ab", "ab"), c("abc", "ab")) #[1] NA 2 1 #「duplicates.ok = T」で、1対多での対応づけをOKにする base::pmatch(c("", "ab", "ab"), c("abc", "ab"), duplicates.ok = T) #[1] NA 2 2
grep関数、grepl関数、stringr::str_detect関数
次に、grep関数、grepl関数、stringr::str_detect関数を使った事例を紹介する。こっちの方が使い勝手が良いと思われます。
c$X1 #[1] "abc ABC abc" "ABC abc ABC" "def DEF def" #[4] "DEF def DEF" "abc.ABC.abc" "ABC.abc.ABC" base::grep(pattern="abc", c$X1) #[1] 1 2 5 6 base::grepl(pattern="abc", c$X1) #[1] TRUE TRUE FALSE FALSE TRUE TRUE c$X1[base::grepl(pattern="abc", c$X1)] #[1] "abc ABC abc" "ABC abc ABC" "abc.ABC.abc" #[4] "ABC.abc.ABC" stringr::str_detect(c$X1, pattern="abc") #[1] TRUE TRUE FALSE FALSE TRUE TRUE #上記の逆論理値を返す stringr::str_detect(c$X1, pattern="abc", negate = T) #[1] FALSE FALSE TRUE TRUE FALSE FALSE
【3】文字列部分一致の曖昧判定
base::grepl関数
拡張正規表現を使った、文字列部分一致の曖昧判定について、いろいろな事例を扱ってみる。一通り覚えると、結構使えます。
まずは、base::grepl / stringr::str_detect + 拡張正規表現
を使います。
#「A〜C」の3文字からなる文字列を含むかどうか base::grepl(pattern="[A-C][A-C][A-C]", c$X1) #[1] TRUE TRUE FALSE FALSE TRUE TRUE #「A〜C」、任意文字、「A〜C」の3文字からなる文字列を含むかどうか base::grepl(pattern="[A-C].[A-C]", c$X1) #[1] TRUE TRUE FALSE FALSE TRUE TRUE #任意文字、任意文字、任意文字の3文字からなる文字列を含むかどうか base::grepl(pattern="...", c$X1) #[1] TRUE TRUE TRUE TRUE TRUE TRUE
(メモ) 正規表現の違い | |
---|---|
任意文字 | . |
.(ピリオド)を文字指定 | [.] |
続いて、新たな文字列サンプルで試してみます。
#新たな文字列 test0 <- c("123", "12345", "Q1", "Q12", "QQ123") test0 #[1] "123" "12345" "Q1" "Q12" "QQ123" #「0〜9」からなる文字列を3連続で含むかどうか base::grepl(pattern="[0-9][0-9][0-9]", test0) #[1] TRUE TRUE FALSE FALSE TRUE #先頭「Q」から始まって、数字2文字からなる文字列を含むかどうか base::grepl(pattern="^Q[1-9][0-9]", test0) #[1] FALSE FALSE FALSE TRUE FALSE #語尾が「0-9」「3-5」で終わる、数字2文字からなる文字列を含むかどうか base::grepl(pattern="[0-9][3-5]$", test0) #[1] TRUE TRUE FALSE FALSE TRUE
(メモ) よく使う正規表現 | |
---|---|
先頭文字指定 | 先頭に ^ マーク |
末尾文字指定 | 最後に $ マーク |
次に、少し複雑な内容をやってみます。
TRUE / FALSEだと長ったらしいので、該当文字列のみを抽出しています。
#少し応用編 test1 <- c("あ", "ア", "M", "m", "1", " ", " ", "㍍", "〒", "*", "+", "漢") #平仮名 [あ-ん] を抽出する test1[base::grepl(pattern="[あ-ん]", test1)] #[1] "あ" #片仮名 [ア-ン] を抽出する test1[base::grepl(pattern="[ア-ン]", test1)] #[1] "ア" #大文字・小文字アルファベットを抽出する test1[base::grepl(pattern="[a-z]|[A-Z]", test1)] #[1] "M" "m" #OR test1[base::grepl(pattern="[[:alpha:]]", test1)] #[1] "M" "m" #小文字アルファベットを抽出する test1[base::grepl(pattern="[a-z]", test1)] #[1] "m" #OR test1[base::grepl(pattern="[[:lower:]]", test1)] #[1] "m" #数値を抽出する test1[base::grepl(pattern="[0-9]", test1)] #[1] "1" #OR test1[base::grepl(pattern="[[:digit:]]", test1)] #[1] "1" #アルファベット OR 数値を抽出する test1[base::grepl(pattern="[a-z]|[A-Z]|[0-9]", test1)] #[1] "M" "m" "1" #OR test1[base::grepl(pattern="\\w", test1)] #[1] "M" "m" "1" test1[base::grepl(pattern="[[:alnum:]]", test1)] #[1] "M" "m" "1" #空白文字、タブをを抽出する: やや分かりづらい test1[base::grepl(pattern=" | ", test1)] #[1] " " " " #OR test1[base::grepl(pattern="[[:blank:]]", test1)] #[1] " " " " #印字可能な文字列を抽出する test1[base::grepl(pattern="[[:print:]]", test1)] #[1] "あ" "ア" "M" "m" "1" " " " " "㍍" "〒" "*" "+" "漢" #グラフィカル文字列を抽出する test1[base::grepl(pattern="[[:graph:]]", test1)] #[1] "あ" "ア" "M" "m" "1" "㍍" "〒" "*" "+" "漢" #パンクチュエーション文字列を抽出する test1[base::grepl(pattern="[[:punct:]]", test1)] #[1] "㍍" "〒" "*" "+"
stringr::str_detect関数
次に、stringr::str_detect関数での実行例を示します。
#平仮名 [あ-ん] を抽出する test1[stringr::str_detect(test1, pattern="\\p{Hiragana}")] #[1] "あ" #OR test1[stringr::str_detect(test1, pattern="[あ-ん]")] #[1] "あ" #片仮名 [ア-ン] を抽出する test1[stringr::str_detect(test1, pattern="\\p{Katakana}")] #[1] "ア" "㍍" #OR test1[stringr::str_detect(test1, pattern="[ア-ン]")] #[1] "ア" #漢字 を抽出する test1[stringr::str_detect(test1, pattern="\\p{Han}")] #[1] "漢"
stringr::str_detect関数では、\\p{Katakana}
で、㍍
が抽出できるのが素敵です。
まとめ
文字列の検出は、実際テキスト処理とかでよく使います。
個人的には、grepl関数でT/F結果で返すのが「テッパン」だと思います。
補足
データフレーム全体に対して、文字列の置換を行う場合のTips
data.frame関数、lapply関数、gsub関数を組み合わせて行う。
以下には、データフレームのDatオブジェクトの各列の%
を``に置換する場合の実施例を示す。
Dat <- data.frame(lapply(Dat, function(x){gsub(pattern="%", replacement = "", x)}), stringsAsFactors = FALSE)
TRUE/FALSEをカウントする
lengthをよく使ってたけど、sumでもT/Fのカウントができるみたい。
文字列サンプル test0 <- c("123", "12345", "Q1", "Q12", "QQ123") #(例)「0〜9」からなる文字列を3連続で含むかどうか count <- base::grepl(pattern="[0-9][0-9][0-9]", test0) count #[1] TRUE TRUE FALSE FALSE TRUE #lengthで、TRUEのカウント length(count[count]) #[1] 3 #lengthで、FALSEのカウント length(count[!count]) #[1] 2 #sumで、TRUEのカウント sum(count) #[1] 3 #sumで、FALSEのカウント sum(!count) #[1] 2
【応用編】文字列の検出: character(0)、numeric(0)を検出する
character(0)
とnumeric(0)
の処理は結構悩ましいので、メモしておく。
#前準備 a <- character(0) b <- numeric(0) #character(0)検出の方法 identical(a, character(0)) #[1] TRUE #失敗例 character(0) == 0 #logical(0) #numeric(0)検出の方法 identical(b, numeric(0)) #[1] TRUE #失敗例 numeric(0) == 0 #logical(0) #character(0)あるいはnumeric(0)の条件で検出する Hantei <- function(x){ ch <- identical(x, character(0)) nu <- identical(x, numeric(0)) return(any(c(ch, nu))) } #判定結果 Hantei(a) #[1] TRUE Hantei(b) #[1] TRUE Hantei("Baka") #[1] FALSE