京橋のバイオインフォマティシャンの日常

南国のビーチパラソルの下で、Rプログラムを打ってる日常を求めて、、Daily Life of Bioinformatician in Kyobashi of Osaka

【Rでの文字列処理シリーズ(その2)】文字列ベクトルの連結と文字列長のカウント

はじめに

文字列処理・テキスト処理とは、プログラミングを行うなかで、文字列・テキストに対する色々な操作のことを指します。それら処理をうまく使いこなすことで、文字列を自由に処理できるようになります。文字列処理の活用事例は、キーワード抽出、テキスト分類、テキストマイニングの前処理など、多岐に渡ります。 今回の「Rでの文字列処理」シリーズで扱う、文字列処理のライブラリ・関数群やプログラムコードは、R環境上で無料で提供されている、オープン・ソフトウェアを用います。

この記事では、Rでの文字列ベクトルの連結とカウントについて、いろいろと試してまとめてみました。 主に、baseやstringrとかのパッケージ内の関数群を扱います。 文字列の連結・結合は普段もよく使うので簡単と思ってたけど、奥が深い一面も垣間見れて、良かったです。

テキスト処理の関連記事

skume.net

skume.net

skume.net

skume.net

skume.net

skume.net

下準備について

########################
#シリーズ共通
########################
#必要なパッケージの読み込み
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】文字列ベクトルの連結

base::paste0 関数・ stringr::str_c 関数

base::paste0、stringr::str_c についての実行例を示す。

paste0関数は、paste関数の引数 sep = ""が標準出力の関数である。

#入力ベクトル
abc <- c("A", "B", "C", "D", "E")

# 使用する引数
# sep: 入力ベクトル間に挿入する文字列
# collapse: 入力ベクトルを1つの文字列に連結するために使用される文字列

#base::paste0での実行例
#paste0での標準出力
base::paste0(abc)
#[1] "A" "B" "C" "D" "E"

#ベクトルの各要素の後ろに「,」を付与する
base::paste0(abc, sep=",")
#[1] "A," "B," "C," "D," "E,"

#ただ、base::pasteでは同じ出力とはならない
base::paste(abc, sep=",")
#[1] "A" "B" "C" "D" "E"  => この場合、処理効果なし

#文字列ベクトルを1つの文字列として連結する
base::paste0(abc, collapse="")
#[1] "ABCDE"

#ベクトルの各要素の間に「,」を付与して、文字列ベクトルを1つの文字列として連結する
base::paste0(abc, collapse=",")
#[1] "A,B,C,D,E"

次に、ベクトルではなく、文字列を並べたときの出力結果を見ていく。

#"A", "B", "C", "D", "E"と、文字列として並べたときのデフォルト出力
base::paste0("A", "B", "C", "D", "E")
#[1] "ABCDE"

#sep=","を設定すると、最後にコンマが付く
base::paste0("A", "B", "C", "D", "E", sep=",")
#[1] "ABCDE,"

#ただ、base::pasteでは同じ出力とはならず、間にコンマが入る
base::paste("A", "B", "C", "D", "E", sep=",")
#[1] "A,B,C,D,E"

#collapse=","を設定すると
base::paste0("A", "B", "C", "D", "E", collapse=",")
#[1] "ABCDE"  => この場合、処理効果なし

stringr::str_c 関数

続いて、stringr::str_c関数での実行例を見ていく。

#デフォルト出力
stringr::str_c(abc)
#[1] "A" "B" "C" "D" "E"

#sep=","の設定にすると
stringr::str_c(abc, sep=",")
#[1] "A" "B" "C" "D" "E"  => この場合、処理効果なし

#collapse=","の設定にすると、間にコンマが入る
stringr::str_c(abc, collapse=",")
#[1] "A,B,C,D,E"

#ベクトルではなく、文字列を並べたときの出力
stringr::str_c("A", "B", "C", "D", "E")
#[1] "ABCDE"

#sep=","の設定にすると、間にコンマが入る
stringr::str_c("A", "B", "C", "D", "E", sep=",")
#[1] "A,B,C,D,E"

#collapse=","の設定にすると、
stringr::str_c("A", "B", "C", "D", "E", collapse=",")
#[1] "ABCDE"  => この場合、処理効果なし

関数によって、sep、collapseの引数としての影響が若干違ってくるみたい。

【2】文字列長のカウント

文字列長をカウントする関数として、 base::nchar関数、stringr::str_length関数、stringr::str_count関数を取り上げる。

base::nchar 関数

#文字列長のカウント: ベクトルでの実行
#入力ベクトル
abc <- c("A", "B", "C", "D", "E")

#文字列長のカウント: 文字列での実行
base::nchar(abc)
#[1] 1 1 1 1 1

#文字列長のカウント: データフレームでの実行
#入力データフレーム
c <- data.frame(readr::read_tsv(file="./test.txt", col_names=F))
c
#           X1
#1 abc ABC abc
#2 ABC abc ABC
#3 def DEF def
#4 DEF def DEF
#5 abc.ABC.abc
#6 ABC.abc.ABC

#データフレームに含まれる全文字数のカウント
base::nchar(c)
#X1 
#91

#要素ごとでの文字数のカウント
base::nchar(c$X1)
#[1] 11 11 11 11 11 11

少し横道にそれるが、apply関数とbase::nchar関数とを組み合わせる場合もやってみる。

#apply関数を組み合わせた文字数のカウント
#MARGIN = 1 (rows)でapply実行する
apply(c, MARGIN=1, base::nchar)
#[1] 11 11 11 11 11 11

#MARGIN = 2 (columns)でapply実行する
apply(c, MARGIN=2, base::nchar)
#     X1
#[1,] 11
#[2,] 11
#[3,] 11
#[4,] 11
#[5,] 11
#[6,] 11

MARGIN=1の場合に、横に出力されて、 MARGIN=2の場合に、縦に出力されるのがやや違和感あるけど。。

次に、データフレームを分割して、3x6のデータフレームで、文字数のカウントをやってみる。

行列で入力するか、データフレームで入力するかで、どうも出力結果が変わるみたい。

#分割した行列での実行
c2 <- stringr::str_split_fixed(c$X1, pattern=c(" ", " ", " ", " ", "[.]", "[.]"), n=3)
c2
#     [,1]  [,2]  [,3] 
#[1,] "abc" "ABC" "abc"
#[2,] "ABC" "abc" "ABC"
#[3,] "def" "DEF" "def"
#[4,] "DEF" "def" "DEF"
#[5,] "abc" "ABC" "abc"
#[6,] "ABC" "abc" "ABC"

#要素ごとの文字列長のカウント
base::nchar(c2)
#     [,1] [,2] [,3]
#[1,]    3    3    3
#[2,]    3    3    3
#[3,]    3    3    3
#[4,]    3    3    3
#[5,]    3    3    3
#[6,]    3    3    3
#セルごとでカウントされる

#分割したデータフレームでの実行
c3 <- data.frame(stringr::str_split_fixed(c$X1, pattern=c(" ", " ", " ", " ", "[.]", "[.]"), n=3))
c3
#   X1  X2  X3
#1 abc ABC abc
#2 ABC abc ABC
#3 def DEF def
#4 DEF def DEF
#5 abc ABC abc
#6 ABC abc ABC

base::nchar(c3)
#X1 X2 X3 
#43 43 43 
#列ごとでカウントされる。

stringr::str_length 関数 ・ stringr::str_count 関数

次に、stringr::str_length関数やstringr::str_count関数でカウントしてみた。どちらの関数も、行列を入力としたところ、ベクトルで出力された。

#stringr::str_lengthでのカウント実行: 文字列の長さをベクトルで出力
stringr::str_length(c2)
#[1] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3

#文字列マッチでのカウント: 大文字あるいは小文字のアルファベット数でカウント
stringr::str_count(c2, pattern = "[a-z]|[A-Z]")
#[1] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3

補足

formatC関数を使って、出力文字列の桁数を揃える

d <- 1:10
d
#[1]  1  2  3  4  5  6  7  8  9 10

#「0」を加えて、桁数3桁で揃える。
formatC(d, width = 3, flag = "0")
#[1] "001" "002" "003" "004" "005" "006" "007"
#[8] "008" "009" "010"

連番数値を作成する

d1 <- seq_len(10)
d1
#[1]  1  2  3  4  5  6  7  8  9 10

大文字 <=> 小文字の変換

小文字 => 大文字への変換では、toupper関数、str_to_upper関数(ベクトル化される)を使用する。また、大文字 => 小文字への変換では、tolower関数、str_to_lower関数(ベクトル化される)を使用する。

#大文字・小文字が混在する行列を使用する
c2
#     [,1]  [,2]  [,3] 
#[1,] "abc" "ABC" "abc"
#[2,] "ABC" "abc" "ABC"
#[3,] "def" "DEF" "def"
#[4,] "DEF" "def" "DEF"
#[5,] "abc" "ABC" "abc"
#[6,] "ABC" "abc" "ABC"

#大文字への変換
toupper(c2)
#     [,1]  [,2]  [,3] 
#[1,] "ABC" "ABC" "ABC"
#[2,] "ABC" "ABC" "ABC"
#[3,] "DEF" "DEF" "DEF"
#[4,] "DEF" "DEF" "DEF"
#[5,] "ABC" "ABC" "ABC"
#[6,] "ABC" "ABC" "ABC"

#小文字への変換
tolower(c2)
#     [,1]  [,2]  [,3] 
#[1,] "abc" "abc" "abc"
#[2,] "abc" "abc" "abc"
#[3,] "def" "def" "def"
#[4,] "def" "def" "def"
#[5,] "abc" "abc" "abc"
#[6,] "abc" "abc" "abc"

#大文字ベクトルへの変換
stringr::str_to_upper(c2)
# [1] "ABC" "ABC" "DEF" "DEF" "ABC" "ABC" "ABC"
# [8] "ABC" "DEF" "DEF" "ABC" "ABC" "ABC" "ABC"
#[15] "DEF" "DEF" "ABC" "ABC"

#小文字ベクトルへの変換
stringr::str_to_lower(c2)
# [1] "abc" "abc" "def" "def" "abc" "abc" "abc"
# [8] "abc" "def" "def" "abc" "abc" "abc" "abc"
#[15] "def" "def" "abc" "abc"

参考資料

skume.net

skume.net

https://cran.r-project.org/web/packages/readr/readr.pdf

https://cran.r-project.org/web/packages/stringr/stringr.pdf

cran.r-project.org

github.com

【Rでの文字列処理シリーズ(その1)】テキストファイルの読み込み・文字列の分割

はじめに

文字列処理・テキスト処理とは、プログラミングを行うなかで、文字列・テキストに対する色々な操作のことを指します。それら処理をうまく使いこなすことで、文字列を自由に処理できるようになります。文字列処理の活用事例は、キーワード抽出、テキスト分類、テキストマイニングの前処理など、多岐に渡ります。 今回の「Rでの文字列処理」シリーズで扱う、文字列処理のライブラリ・関数群やプログラムコードは、R環境上で無料で提供されている、オープン・ソフトウェアを用います。

『その1: テキストファイルの読み込み・文字列の分割』では、Rでのテキストファイルの読み込み、および文字列の分割について、いろいろと試してまとめてみました。主に、baseやstringrとかのパッケージ内の関数群を扱います。

結論としては、ファイル読み込み関数は用途によって使い分けが必要である。また、文字列の分割については、stringr::str_split関数を使うのが全般的に良い印象である。

テキスト処理の関連記事

skume.net

skume.net

skume.net

skume.net

skume.net

skume.net

下準備について

########################
#シリーズ共通
########################
#必要なパッケージの読み込み
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】テキストファイルを1行ごと読み込んで、ベクトルにする。

まずは、file関数、readLines関数を使って試してみる。

file_obj <- file("./test.txt")
a <- readLines(file_obj, n=10); close(file_obj)
a
#[1] "abc ABC abc" "ABC abc ABC" "def DEF def" "DEF def DEF" "abc.ABC.abc"
#[6] "ABC.abc.ABC"

【2】テキストファイルを文字列で読み込んで、ベクトルにする。

read_file関数を使った、ファイル読み込むを試してみる。

b <- readr::read_file(file="./test.txt")
b
#[1] "abc ABC abc\nABC abc ABC\ndef DEF def\nDEF def DEF\nabc.ABC.abc\nABC.abc.ABC\n"

このままでは何とも使いづらいので、\nで文字列の分割を行う。

b1 <- base::strsplit(b, split="\n")[[1]]
#引数
#split: 分割起点となる文字列ベクトル

b1
#[1] "abc ABC abc" "ABC abc ABC" "def DEF def" "DEF def DEF" "abc.ABC.abc"
#[6] "ABC.abc.ABC"

【3】テキストファイルを読み込んで、データフレームにする。

read_tsv関数を使ってファイルを読み込むで、その後、データフレームに変換する。

c <- data.frame(readr::read_tsv(file="./test.txt", col_names=F))
#─ Column specification ─────────────────────────
#cols(
#  X1 = col_character()
#)

c
#           X1
#1 abc ABC abc
#2 ABC abc ABC
#3 def DEF def
#4 DEF def DEF
#5 abc.ABC.abc
#6 ABC.abc.ABC

文字列の分割 について

【1】スペースごとで分割して、各文字列をカラムごとに分ける

ベクトル c 内の各要素(特に、c$X1[1:4])について、 スペース区切りで文字列を分割して、別カラムに分ける場合を考えてみる。

まずは、base::strsplit関数での実行例を示す。

ベクトル全体に対する文字列の分割では、list形式で出力されるので、 一度、unlist関数でベクトルに変換して、その後にデータ型を調整する。

c1 <- data.frame(X1=c$X1[1:4])
c1

#base::strsplitでの実行例
#文字列の分割
base::strsplit(c1$X1[1], split=" ")
#[[1]]
#[1] "abc" "ABC" "abc"

#ベクトル全体に対する文字列の分割
base::strsplit(c1$X1, split=" ")
#[[1]]
#[1] "abc" "ABC" "abc"
#[[2]]
#[1] "ABC" "abc" "ABC"
#[[3]]
#[1] "def" "DEF" "def"
#[[4]]
#[1] "DEF" "def" "DEF"

#ベクトル全体に対する文字列の分割を行い、データフレームあるいは行列で返す
data.frame(Y=matrix(unlist(base::strsplit(c1$X1, split=" ")), ncol=3, byrow=T))
#  Y.1 Y.2 Y.3
#1 abc ABC abc
#2 ABC abc ABC
#3 def DEF def
#4 DEF def DEF

#行列で返す場合
matrix(unlist(base::strsplit(c1$X1, split=" ")), ncol=3, byrow=T)
#     [,1]  [,2]  [,3] 
#[1,] "abc" "ABC" "abc"
#[2,] "ABC" "abc" "ABC"
#[3,] "def" "DEF" "def"
#[4,] "DEF" "def" "DEF"

次に、stringr::str_split関数での実行例を示す。

stringr::str_split関数は、デフォルト出力ではベクトルでの出力だが、引数をsimplify = Tにすることで、 文字列分割後の出力形式を「行列」で返してくれる。

c1 <- data.frame(X1=c$X1[1:4])
c1

#stringr::str_splitでの実行例
#文字列の分割
stringr::str_split(c1$X1[1], pattern = " ")
#[[1]]
#[1] "abc" "ABC" "abc"

#ベクトル全体に対する文字列の分割
stringr::str_split(c1$X1, pattern = " ",)
#[[1]]
#[1] "abc" "ABC" "abc"
#[[2]]
#[1] "ABC" "abc" "ABC"
#[[3]]
#[1] "def" "DEF" "def"
#[[4]]
#[1] "DEF" "def" "DEF"

#ベクトル全体に対する文字列の分割を行い、データフレームで返す場合
data.frame(stringr::str_split(c1$X1, pattern = " ", simplify = T))
#   X1  X2  X3
#1 abc ABC abc
#2 ABC abc ABC
#3 def DEF def
#4 DEF def DEF

#そのまま、行列で返す場合
stringr::str_split(c1$X1, pattern = " ", simplify = T)
#     [,1]  [,2]  [,3] 
#[1,] "abc" "ABC" "abc"
#[2,] "ABC" "abc" "ABC"
#[3,] "def" "DEF" "def"
#[4,] "DEF" "def" "DEF"

【2】ピリオド(.)で分割して、文字列をカラムごとに分ける

ベクトル内の各要素(特に、c$X1[5:6])について、 ピリオド区切りで文字列を分割して、別カラムに分けたい場合を考えてみる。

この場合、少しだけ工夫が必要で、パターン文字列を"."ではなく、"[.]"あるいは"\\."とする。

c2 <- data.frame(X1=c$X1[5:6])
c2

#base::strsplitについては省略

#stringr::str_splitでの実行例
#文字列の分割
#失敗例
stringr::str_split(c2$X1[1], pattern = ".")
#[[1]]
# [1] "" "" "" "" "" "" "" "" "" "" "" ""

#成功例(1): [ ]括弧をつける
stringr::str_split(c2$X1[1], pattern = "[.]")
#[[1]]
#[1] "abc" "ABC" "abc"

#成功例(2): バックスラッシュ(\)x2
stringr::str_split(c2$X1[1], pattern = "\\.")
#[[1]]
#[1] "abc" "ABC" "abc"

#ベクトル全体に対する文字列の分割を行い、データフレームで返す
data.frame(stringr::str_split(c2$X1, pattern = "[.]", simplify = T))
#   X1  X2  X3
#1 abc ABC abc
#2 ABC abc ABC

【3】(応用編)区切るパターンが異なる、文字列ベクトルでの処理

次に、スペースとピリオドでの文字列の分割を同時に行う事例を取りあげる。

それぞれの文字列パターンをベクトルで与える、あるいは、| (パイプ)を使ってOR処理をする場合がある。

#スペース区切りでの出力例: 5, 6行目は分割されない
stringr::str_split(c$X1, pattern = " ", simplify = T)
#     [,1]          [,2]  [,3] 
#[1,] "abc"         "ABC" "abc"
#[2,] "ABC"         "abc" "ABC"
#[3,] "def"         "DEF" "def"
#[4,] "DEF"         "def" "DEF"
#[5,] "abc.ABC.abc" ""    ""   
#[6,] "ABC.abc.ABC" ""    "" 

#ピリオド区切りでの出力例: 1-4行目は分割されない
stringr::str_split(c$X1, pattern = "[.]", simplify = T)
#     [,1]          [,2]  [,3] 
#[1,] "abc ABC abc" ""    ""   
#[2,] "ABC abc ABC" ""    ""   
#[3,] "def DEF def" ""    ""   
#[4,] "DEF def DEF" ""    ""   
#[5,] "abc"         "ABC" "abc"
#[6,] "ABC"         "abc" "ABC"

#複数の区切りパターンを設定する場合
#行ごとに、区切りパターン(スペースあるいはピリオド)を変えて、文字列を分割する
stringr::str_split(c$X1, pattern = c(" ", " ", " ", " ", "[.]", "[.]"), simplify = T)
#     [,1]  [,2]  [,3] 
#[1,] "abc" "ABC" "abc"
#[2,] "ABC" "abc" "ABC"
#[3,] "def" "DEF" "def"
#[4,] "DEF" "def" "DEF"
#[5,] "abc" "ABC" "abc"
#[6,] "ABC" "abc" "ABC"

#スペースあるいはピリオドの区切りパターンで、文字列を分割する
stringr::str_split(c$X1, pattern = " |[.]", simplify = T)
#     [,1]  [,2]  [,3] 
#[1,] "abc" "ABC" "abc"
#[2,] "ABC" "abc" "ABC"
#[3,] "def" "DEF" "def"
#[4,] "DEF" "def" "DEF"
#[5,] "abc" "ABC" "abc"
#[6,] "ABC" "abc" "ABC"

また、出力するカラム数を指定したい場合には、stringr::str_split_fixed関数を用いる。

#スペースあるいはピリオドの区切りパターンで、文字列を分割して、2列で返す
stringr::str_split_fixed(c$X1, pattern=" |[.]", n=2)
#     [,1]  [,2]     
#[1,] "abc" "ABC abc"
#[2,] "ABC" "abc ABC"
#[3,] "def" "DEF def"
#[4,] "DEF" "def DEF"
#[5,] "abc" "ABC.abc"
#[6,] "ABC" "abc.ABC"

#スペースあるいはピリオドの区切りパターンで、文字列を分割して、3列で返す
stringr::str_split_fixed(c$X1, pattern=" |[.]", n=3)
#     [,1]  [,2]  [,3] 
#[1,] "abc" "ABC" "abc"
#[2,] "ABC" "abc" "ABC"
#[3,] "def" "DEF" "def"
#[4,] "DEF" "def" "DEF"
#[5,] "abc" "ABC" "abc"
#[6,] "ABC" "abc" "ABC"

#スペースあるいはピリオドの区切りパターンで、文字列を分割して、4列で返す
#4列目は、空列で出力される。
stringr::str_split_fixed(c$X1, pattern=" |[.]", n=4)
#     [,1]  [,2]  [,3]  [,4]
#[1,] "abc" "ABC" "abc" ""  
#[2,] "ABC" "abc" "ABC" ""  
#[3,] "def" "DEF" "def" ""  
#[4,] "DEF" "def" "DEF" ""  
#[5,] "abc" "ABC" "abc" ""  
#[6,] "ABC" "abc" "ABC" "" 

補足

(とか、 )とか、の括弧で、文字列を分割する場合

[(][)]を組み合わせることで、括弧を指定できる。

# ( ) を区切りパターンとして、文字列を分割する

Ex <- "AAA(BBB)"

stringr::str_split(Ex, pattern = "[(]|[)]", simplify = T)
#     [,1]  [,2]  [,3]
#[1,] "AAA" "BBB" "" 

stringr::str_split_fixed(Ex, pattern="[(]|[)]", n=3)
#     [,1]  [,2]  [,3]
#[1,] "AAA" "BBB" "" 

MacOSXでやってはいけないコマンド実行、やったら初期化は逃れないかも 〜.DS_Storeファイルが邪魔なのですべて消してみた件〜

序章

この記事の内容を実行してしまったばっかりに、 Mac PCのクリーンアップ(初期化)を余儀なくされても、 一切保証できるものではありませんので、あしからず。

先日、.DS_Storeファイルが無性に邪魔に感じて、 Mac内にある同ファイルすべてを消してみたのだが、 コマンド実行後から、OSの不具合続出で、 保存データの参照関係もメチャメチャとなり、 あえなく、PCを初期化した。。。

そもそも、.DS_Storeのファイルは何なのか?色々と調べると、MacOSで作成されるメタデータを保存する隠しファイルであるらしい。 初期設定では、全てのフォルダ内に、.DS_Storeが隠しファイルで作成される。 どのフォルダにあるかによって役割が違うのだろうが、どうも、MacOS内でのデータ参照とかの大変重要な役割を担っているらしい。

当時を思えば、 「天下のAppleさんのOSで、そんな大事な機能を、ローカルのファイルに完全に任せてることはなく、 そんなファイルが無くても、システム的に勝手に補完されるんじゃない、、、」かと 分かった気になってたのが良くなかったのかもしれない。

Macでやってはいけないコマンド実行

まずは、ターミナル起動を起動して、「DS_Store」がどれくらい存在するかを見てみる*1

# DS_Storeのファイルパスを  text.txt を保存
du -a | grep "DS_Store" > text.txt
#結構、時間かかる!

#行数をカウント
wc -l text.txt
#自分の環境では、「数千個くらい」あった!?

実は、これだけファイル数が多いと、一度に削除することはできない。

そのため、小分けにして、ファイル削除を実行する必要がある。

そこで、行き着いたのが、以下のコマンド実行である。 これだと1回の実行で、1000個ずつ、DS_Storeが消えていく*2

du -a | grep "DS_Store" | head -n 1000 | xargs rm -rf

ここでのポイントは、rm -rf に引数を渡す際にheadからの出力を直接受け取れないので、 xargsを使用して、headの出力を引数として受け取れるようにする。

ただ、このコマンドは実行しないことを強くお勧めします。

良い子は真似しないで。

*1:すでに、MacOSの聖域を見る行為であり、やってはいけない

*2:通常、消せないものを消そうとしないこと