R 3.6.0 では行列や配列を MARGIN に応じたリストに分割する asplit 関数が追加された.既に purrr パッケージが同様の機能として array_tree や array_branch を実装していたので,挙動とベンチマーク結果を比較してみる.
これらの使い道は行ごとなどに lapply する時だろうか.
apply が返り値をベクトル・行列・配列・リストに勝手に変換してしまうのを嫌う場合に有用かも知れない.
m <- matrix(1:100, nrow = 50)
# 返り値はリスト
lapply(asplit(m, 2), head)
#> [[1]]
#> [1] 1 2 3 4 5 6
#>
#> [[2]]
#> [1] 51 52 53 54 55 56
# 返り値は行列
apply(m, 2, head)
#> [,1] [,2]
#> [1,] 1 51
#> [2,] 2 52
#> [3,] 3 53
#> [4,] 4 54
#> [5,] 5 55
#> [6,] 6 56base
asplit
asplit 関数は第一引数 x に行列ないし配列を,第二引数 MARGIN に数値のベクトルを取る.
MARGIN に応じて x を分割するが, MARGIN の長さは x の次元数未満でなければならない.例えば x が3次元配列の場合は MARGIN = 1:2, 2:3, c(1, 3) は可能であるが, 1:3 はエラーを返す.
asplit(行列)
asplit は行列を行ごと (MARGIN = 1) ないし列ごと (MARGIN = 2) のリストに変換する.
(M <- matrix(c(11, 21, 12, 22, 13, 23), 2, 3))
#> [,1] [,2] [,3]
#> [1,] 11 12 13
#> [2,] 21 22 23
# 行ごとのリストに分割
(x <- asplit(M, 1))
#> [[1]]
#> [1] 11 12 13
#>
#> [[2]]
#> [1] 21 22 23
# 行と列で分割しようとするとエラー
asplit(M, 1:2)
#> Error in array(newx[, i], d.call, dn.call): 'dims' cannot be of length 0asplit(配列)
配列の場合は 3 以上の MARGIN を与えることができる.また, MARGIN = 1:2 といった具合に複数の次元で切ることもできる.
# 配列の容易
(A <- array(M, c(2, 2, 2)))
#> , , 1
#>
#> [,1] [,2]
#> [1,] 11 12
#> [2,] 21 22
#>
#> , , 2
#>
#> [,1] [,2]
#> [1,] 13 11
#> [2,] 23 21
# 3次元目で分割
asplit(A, 3)
#> [[1]]
#> [,1] [,2]
#> [1,] 11 12
#> [2,] 21 22
#>
#> [[2]]
#> [,1] [,2]
#> [1,] 13 11
#> [2,] 23 21
# 1:2 次元目で分割
(y <- asplit(A, 1:2))
#> [,1] [,2]
#> [1,] Numeric,2 Numeric,2
#> [2,] Numeric,2 Numeric,2猶, y の出力が一風変わっていることに注目されたい.実は,asplit はリストの配列を返り値に持つ.
y の各要素は Numeric, 2 と表示されているので一見ベクトルであるが,実体は配列である.ある変数が配列であるかどうかは is.array が TRUE を返すことや dim が整数ベクトルを返すことで確認できる.
# y は配列なので,dimが数値を返す
dim(y)
#> [1] 2 2
# y の各要素も配列
lapply(y, dim)
#> [[1]]
#> [1] 2
#>
#> [[2]]
#> [1] 2
#>
#> [[3]]
#> [1] 2
#>
#> [[4]]
#> [1] 2先の行列の例においても,返り値は数値ベクトルのリストに見えるが,数値の一次元配列を内包したリストの一次元配列である.
# x は配列
dim(x)
#> [1] 2
# x の各要素も配列
lapply(x, dim)
#> [[1]]
#> [1] 3
#>
#> [[2]]
#> [1] 3purrr
array_tree や array_branch は
margin 引数が 長さ1の数値である場合に asplit とよく似た挙動を示す.
asplit では大文字な MARGIN 引数が purrr では小文字であることに注意されたい.
この margin 引数に長さ2以上の数値を与えた場合,配列を階層的に切る.このためarray_tree はリストのリストを作る.
array_branch は array_tree のフラットリスト版に相当する.
また margin 引数には,入力した配列の次元数と同じ長さのベクトルを取ることができる.何なら,1次元のベクトルを与えるとリスト化して返してくれる.
更に margin 引数は省略すると seq(dim(x)) 相当の値が与えられる.ただし x がベクトルの場合は 1L.
array_tree
array_tree(行列)
行列を行ごとに切ると,ベクトルを束ねたリストを返す.
M
#> [,1] [,2] [,3]
#> [1,] 11 12 13
#> [2,] 21 22 23
str(array_tree(M, 1))
#> List of 2
#> $ : num [1:3] 11 12 13
#> $ : num [1:3] 21 22 23
# asplit は配列のリストを返すため,
# 要素の長さに (1d) と記載される
str(asplit(M, 1))
#> List of 2
#> $ : num [1:3(1d)] 11 12 13
#> $ : num [1:3(1d)] 21 22 23
#> - attr(*, "dim")= int 22行3列の行列を行→列の順で切ると長さ3のリストを2つ束ねたリストを返す.
また逆順で列→行の順で切ると長さ2のリストを3つ束ねたリストを返す.
str(array_tree(M, 1:2))
#> List of 2
#> $ :List of 3
#> ..$ : num 11
#> ..$ : num 12
#> ..$ : num 13
#> $ :List of 3
#> ..$ : num 21
#> ..$ : num 22
#> ..$ : num 23
str(array_tree(M, 2:1))
#> List of 3
#> $ :List of 2
#> ..$ : num 11
#> ..$ : num 21
#> $ :List of 2
#> ..$ : num 12
#> ..$ : num 22
#> $ :List of 2
#> ..$ : num 13
#> ..$ : num 23
# asplit では次元数と同じ長さの MARGIN に対してエラーを返す
asplit(M, 1:2)
#> Error in array(newx[, i], d.call, dn.call): 'dims' cannot be of length 0margin 引数を省略すると, margin = 1:2 として扱う.
identical(array_tree(M), array_tree(M, 1:2))
#> [1] TRUEarray_branch(配列)
行列での挙動をそのまま配列に拡張しただけなので,特筆することはない,
A
#> , , 1
#>
#> [,1] [,2]
#> [1,] 11 12
#> [2,] 21 22
#>
#> , , 2
#>
#> [,1] [,2]
#> [1,] 13 11
#> [2,] 23 21
# margin の長さが1の時
str(array_tree(A, 3))
#> List of 2
#> $ : num [1:2, 1:2] 11 21 12 22
#> $ : num [1:2, 1:2] 13 23 11 21
# margin の長さが2以上の時
str(array_tree(A, 1:2))
#> List of 2
#> $ :List of 2
#> ..$ : num [1:2] 11 13
#> ..$ : num [1:2] 12 11
#> $ :List of 2
#> ..$ : num [1:2] 21 23
#> ..$ : num [1:2] 22 21
# margin を省略した時
str(array_tree(A))
#> List of 2
#> $ :List of 2
#> ..$ :List of 2
#> .. ..$ : num 11
#> .. ..$ : num 13
#> ..$ :List of 2
#> .. ..$ : num 12
#> .. ..$ : num 11
#> $ :List of 2
#> ..$ :List of 2
#> .. ..$ : num 21
#> .. ..$ : num 23
#> ..$ :List of 2
#> .. ..$ : num 22
#> .. ..$ : num 21array_tree(ベクトル)
ベクトルに対しては as.list に似た挙動を示す.
str(array_tree(1:3))
#> List of 3
#> $ : int 1
#> $ : int 2
#> $ : int 3array_branch
array_branch はざっくり言うと,array_tree の返り値をフラットなリストにしたもの,
A
#> , , 1
#>
#> [,1] [,2]
#> [1,] 11 12
#> [2,] 21 22
#>
#> , , 2
#>
#> [,1] [,2]
#> [1,] 13 11
#> [2,] 23 21
# array_branch は フラットなリストを返す
str(array_branch(A, 1:2))
#> List of 4
#> $ : num [1:2] 11 13
#> $ : num [1:2] 21 23
#> $ : num [1:2] 12 11
#> $ : num [1:2] 22 21
str(array_tree(A, 1:2))
#> List of 2
#> $ :List of 2
#> ..$ : num [1:2] 11 13
#> ..$ : num [1:2] 12 11
#> $ :List of 2
#> ..$ : num [1:2] 21 23
#> ..$ : num [1:2] 22 21
# `margin` の長さが1の時は `array_tree` と同じ値を返す.
identical(array_branch(A, 3), array_tree(A, 3))
#> [1] TRUEベンチマーク
asplit, array_tree, array_branch は margin の長さが2以上の場合は異なる返り値を返す.どれを使うかは用途次第といったところ.一方で margin の長さが1の場合は似た返り値を持つので asplit と array_tree の性能を比較してみよう.
library(bench)
library(ggplot2)
set.seed(1)
n <- 1e3
x <- matrix(rnorm(n * n), n, n)
result <- mark(
asplit = asplit(x, 2L),
array_tree = array_tree(x, 2L),
check = FALSE,
min_iterations = 100L
)
autoplot(result, 'beeswarm')ガベージコレクション次第ではあるが,概ね array_tree が強いようだ.
Atusy's blog