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

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

【R・ビッグデータ解析の処方箋】Rで、10万ノードを超える大きなネットワーク図を描画するTips 〜 igraph::plot.igraphは使い物にならない件 〜

はじめに

Rでのネットワーク図の作成では、igraph packageがよく使われる。

ただ、igraphによるネットワーク図の描写は、1万ノードを超えたあたりから、結構な時間がかかる。

そのため、10万ノードを超えるような、大規模なネットワーク図の描画には、ちょっとしたコツがある。

今回、そんな大規模なネットワークの作成方法を取り上げ、実際の実行時間を検証してみた。

結論的には、igraph::plot.igraphを使うよりも、graphics::plot.defaultで描画する方が、10倍以上早くなる。

graphics::plot.defaultによるネットワーク図の作成例

まずは、Barabasi-Albertモデルで、サンプルのグラフ構造を作成する。

#パッケージ準備
library(igraph)

#サンプル・グラフの作成
#sample_pa: Barabasi-Albertモデルによるスケールフリー・グラフの生成関数
#BAモデルは、グラフを構築するためのシンプルな確率的アルゴリズムである。
pa <- igraph::sample_pa(n=10, power=1, m=1, directed=F)

pa
#IGRAPH fe31e44 U--- 10 9 -- Barabasi graph
#+ attr: name (g/c), power (g/n), m (g/n), zero.appeal (g/n),
#| algorithm (g/c)
#+ edges from fe31e44:
#[1] 1-- 2 2-- 3 2-- 4 2-- 5 1-- 6 3-- 7 1-- 8 5-- 9 7--10

#graphics::plot.defaultによる作図
system.time(graphics::plot.default(layout_with_fr(pa), pch=20, cex=2.5,
                       axes = F,  type = "p", xlab = NA, ylab = NA))
#quartz.save(file = paste0("./Graph_net_00.png"), type = "png", dpi = 150); dev.off()

うーん、この図だと、ネットワークのエッジがない。

そこで、線分を追加するsegments関数で、エッジの描画を行う。

関数として組むと、こんな感じである。

gist.github.com

このNetwork_plot関数で、pa を描画してみる。

#パッケージの準備 & 簡単な実行例
library(data.table)
library(magrittr)
source("https://gist.githubusercontent.com/kumeS/2c8204b1c8a78a16d00ec2eaed5a7ca5/raw/e67a5760d7fcfc3a9c8f62f729e407b2722f5bf2/Network_plot.R")

pa <- igraph::sample_pa(n=10, power=1, m=1, directed=F)
Network_plot(pa, Cex=2.5)
#quartz.save(file = paste0("./Graph_net_01.png"), type = "png", dpi = 150); dev.off()

見慣れた、ネットワーク図が得られる。

グラフのレイアウトは、layout_with_fr *1 を使用している。

グラフ・ノード数による描画時間の比較

それでは、次に、グラフのノード数を増やしていって、igraph::plot.igraphとgraphics::plot.defaultを使う場合とでの実行時間を比較してみる。

検証したスクリプトは、以下の通りである。

#####################
#10 nodes
#####################
#plot.igraphでの実行
pa <- sample_pa(n=10, power=1, m=1, directed=F)
system.time(plot.igraph(pa, vertex.size=10, vertex.label=NA))
#   ユーザ   システム       経過  
#     0.014      0.002      0.019 
#quartz.save(file = paste0("./Graph_01.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=2.5))
#   ユーザ   システム       経過  
#     0.016      0.002      0.018 
#quartz.save(file = paste0("./Graph_02.png"), type = "png", dpi = 150); dev.off()

10 nodes by plot.igraph

10 nodes by Network_plot

#####################
#100 nodes
#####################
#plot.igraphでの実行
pa <- sample_pa(n=100, power=1, m=1, directed=F)
system.time(plot.igraph(pa, vertex.size=4, vertex.label=NA))
#   ユーザ   システム       経過  
#     0.023      0.003      0.026
#quartz.save(file = paste0("./Graph_03.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=1.5))
#   ユーザ   システム       経過  
#     0.029      0.003      0.031
#quartz.save(file = paste0("./Graph_04.png"), type = "png", dpi = 150); dev.off()

100 nodes by plot.igraph

100 nodes by Network_plot

#####################
#1000 nodes
#####################
pa <- sample_pa(n=1000, power=1, m=1, directed=F)
system.time(plot(pa, vertex.size=2, vertex.label=NA))
#   ユーザ   システム       経過  
#     1.033      0.017      1.053 
#quartz.save(file = paste0("./Graph_05.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=0.5))
#   ユーザ   システム       経過  
#     0.771      0.006      0.781 
#quartz.save(file = paste0("./Graph_06.png"), type = "png", dpi = 150); dev.off()

1000 nodes by plot.igraph

1000 nodes by Network_plot

#####################
#10000 nodes: 1万ノード
#####################
pa <- sample_pa(n=10000, power=1, m=1, directed=F)
system.time(plot(pa, vertex.size=1, vertex.label=NA))
#   ユーザ   システム       経過  
#    11.014      0.035     11.061
#quartz.save(file = paste0("./Graph_07.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=0.2))
#   ユーザ   システム       経過  
#     0.873      0.005      0.881
#quartz.save(file = paste0("./Graph_08.png"), type = "png", dpi = 150); dev.off()

10000 nodes by plot.igraph

10000 nodes by Network_plot

#####################
#100000 nodes: 10万ノード
#####################
pa <- sample_pa(n=100000, power=1, m=1, directed=F)
system.time(plot(pa, vertex.size=0.5, vertex.label=NA))
#   ユーザ   システム       経過  
#   140.286      0.545    141.231
#quartz.save(file = paste0("./Graph_09.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=0.03))
#   ユーザ   システム       経過  
#     9.381      0.021      9.423
#quartz.save(file = paste0("./Graph_10.png"), type = "png", dpi = 150); dev.off()

100000 nodes by plot.igraph

100000 nodes by Network_plot

#####################
#200000 nodes: 20万ノード
#####################
pa <- sample_pa(n=200000, power=1, m=1, directed=F)
system.time(plot(pa, vertex.size=0.5, vertex.label=NA))
#   ユーザ   システム       経過  
#   278.272      0.539    279.129
#quartz.save(file = paste0("./Graph_11.png"), type = "png", dpi = 150); dev.off()

#Network_plotでの実行
system.time(Network_plot(pa, Cex=0.01))
#   ユーザ   システム       経過  
#    20.613      0.037     20.700 
#quartz.save(file = paste0("./Graph_12.png"), type = "png", dpi = 150); dev.off()

200000 nodes by plot.igraph

200000 nodes by Network_plot

実行時間の可視化

実行時間を表で比較すると、1000や10000あたりから、すでに差が出てくる。

ノード数 igraph::plot.igraphでの実行時間 Network_plotでの実行時間
10 0.019 0.018
100 0.026 0.031
1000 1.053 0.781
10000 11.061 0.881
100000 141.231 9.423
200000 279.129 20.700

図としてプロットすると、こんな感じ。

tm <- data.frame(a=c(10, 100, 1000, 10000, 100000, 200000),
                 b=c(0.019,0.026,1.053,11.061,141.231,279.129),
                 c=c(0.018,0.031,0.781,0.881,9.423,20.700))
par(family="HiraKakuProN-W3", mgp=c(2.5, 1, 0), mai=c(0.75, 0.75, 0.5, 0.25))
plot(tm[,1], tm[,2], type="n", log="x", xlab="nodes", ylab="Time")
points(tm[,1], tm[,2], type="b", pch=16)
points(tm[,1], tm[,3], type="b", pch=21)
legend("topleft", legend=c("igraph::plot.igraphでの実行時間","Network_plotでの実行時間"), pch=c(16, 21), cex=1)
#quartz.save(file = paste0("./Graph_13.png"), type = "png", dpi = 150); dev.off()

まとめ

描画速度が歴然と違う、、、igraphオブジェクトは1万ノードくらいを扱うのが良いところかな。

大規模なネットワークを扱いたい人には、graphics::plot.defaultが基本型だろう。

参考資料

stackoverflow.com

*1:Fruchterman and ReingoldによるForce-Directed Layoutアルゴリズムを用いて、平面上に頂点を配置するレイアウト。

【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" ""