Advanced R: Subsetting
はじめに
Advanced R 2nd EditionのPaperBook版が届いたので、Rについてのおさらい備忘録。また、書籍を読んでいるにも関わらず誤った解釈や用語の誤用もあるかもしれませんので、参考にする際は自己責任でお願いします。
この記事のライセンスはAdvanced R 2nd Editionと同様で下記の通りです。
Subsetting
Subsettingについて。ベクトル、データフレーム、リストから部分集合を作る方法のことで、[[
、[
、$
で実行できるが、その違いについて解説している。
アトミックベクトル
アトミックベクトルに対する部分集合の作り方。[
は、ベクトルから任意の数の要素を抽出するために使用する。まず、正の整数を使う場合。正の整数が該当する部分を抽出する。
x <- 1:10 x[5] [1] 5 x[c(1,5,10)] [1] 1 5 10 x[c(1,1,1)] [1] 1 1 1
次は負の整数を使う場合。負の整数が該当する部分以外を抽出する。
x[-5] [1] 1 2 3 4 6 7 8 9 10 x[-c(1,5,10)] [1] 2 3 4 6 7 8 9 x[-c(1,1,1)] [1] 2 3 4 5 6 7 8 9 10
論理ベクトルを使う場合。論理ベクトルが該当する部分を抜き出す。
x[c(T,F,T,F,T,F,T,F,T)] [1] 1 3 5 7 9 10 # リサイクル規則 x[c(T,F)] [1] 1 3 5 7 9 x[x > 5] [1] 6 7 8 9 10
ゼロを使う場合。これはゼロベクトルを作る場合に使う。
x[0] integer(0)
文字を使う場合。これは該当する文字の部分が抽出される。
x["v5"] v5 5 x[c("v1", "v5")] v1 v5 1 5 x[c("v1", "v1", "v1")] v1 v1 v1 1 1 1 x["v100"] <NA> NA
因子を使う場合。これは非推奨。前回の記事で説明したとおり、因子は整数型のクラスが因子なので、factor("v10")
と単独で使うと、これはレベルの整数が1
ということになる。なので、1番目のベクトルが抽出する。
x[factor("v10")] v1 1
行列
ここでは2次元の行列を扱う。行列も配列も特別な属性を持つ単なるベクトルであるため、それらが1次元ベクトルであるかのように、サブセット化できる。また、指定する場合は要素の位置をmatrix[i, j]
のように指定する。
x <- matrix(1:15, ncol = 3) colnames(x) <- paste0("col", 1:3) x[1, ] col1 col2 col3 1 6 11 x[, 1] [1] 1 2 3 4 5
基本的にはアトミックベクトルと同じように指定して抜き出すことができる。
x[1:3, c(1, 3)] col1 col3 [1,] 1 11 [2,] 2 12 [3,] 3 13 x[c(T,F), c("col1", "col3")] col1 col3 [1,] 1 11 [2,] 3 13 [3,] 5 15
上三角、下三角、対角要素を抜き出す場合は関数が用意されている。upper.tri()
、lower.tri()
、diag()
。
x[upper.tri(x)] [1] 6 11 12 16 17 18 21 22 23 24 x[lower.tri(x)] [1] 2 3 4 5 8 9 10 14 15 20 diag(x) [1] 1 7 13 19 25 # 自作版 diag2 <- function(m){m[.col(dim(x)) == .row(dim(x))]} diag2(x) [1] 1 7 13 19 25
データフレーム
1つのインデックスでサブセット化する場合、リストのように動き、2つのインデックスでサブセット化すると、行列のように動く。
df <- data.frame(x = 1:10, y = 10:1, z = letters[1:10], l = letters[10:1]) df x y z l 1 1 10 a j 2 2 9 b i 3 3 8 c h 4 4 7 d g 5 5 6 e f 6 6 5 f e 7 7 4 g d 8 8 3 h c 9 9 2 i b 10 10 1 j a
df[5, ] x y z l 5 5 6 e f df[, 3] [1] a b c d e f g h i j Levels: a b c d e f g h i j df[c(1,5), ] x y z l 1 1 10 a j 5 5 6 e f df[, c(1,3)] x z 1 1 a 2 2 b 3 3 c 4 4 d 5 5 e 6 6 f 7 7 g 8 8 h 9 9 i 10 10 j df[c(1,5), c(1,3)] x z 1 1 a 5 5 e
リストのように動かす場合と、行列のように動かす場合。
# Like a list df[c("x", "z")] x z 1 1 a 2 2 b 3 3 c 4 4 d 5 5 e 6 6 f 7 7 g 8 8 h 9 9 i 10 10 j # Like a matrix df[, c("x", "z")] x z 1 1 a 2 2 b 3 3 c 4 4 d 5 5 e 6 6 f 7 7 g 8 8 h 9 9 i 10 10 j
カラムの並びをアルファベット順にしたければorder()
を使う。
iris[order(names(iris))][1:5,] Petal.Length Petal.Width Sepal.Length Sepal.Width Species 1 1.4 0.2 5.1 3.5 setosa 2 1.4 0.2 4.9 3.0 setosa 3 1.3 0.2 4.7 3.2 setosa 4 1.5 0.2 4.6 3.1 setosa 5 1.4 0.2 5.0 3.6 setosa
Rでは、1つのインデックスで指定すると、1次元に次元が落とされるので、drop = TRUE
で次元を保存することも可能。
df[5, , drop = FALSE] x y z l 5 5 6 e f df[, 3, drop = FALSE] z 1 a 2 b 3 c 4 d 5 e 6 f 7 g 8 h 9 i 10 j
$
演算子でも値を抜き出すことができる。
df$x [1] 1 2 3 4 5 6 7 8 9 10 df$z [1] a b c d e f g h i j Levels: a b c d e f g h i j
単一の要素を抽出する意味では下記はすべて等価である。
mtcars[3, 2] mtcars$cyl[[3]] mtcars[3, "cyl"] mtcars[["cyl"]][[3]] mtcars[ , "cyl"][[3]] mtcars[3, ]$cyl mtcars[3, ][ , "cyl"] mtcars[3, ][["cyl"]] mtcars[[c(2, 3)]]
論理サブセット
論理サブセット化を使うことで、複数の列からの条件を簡単に組み合わせることができるため、データフレームから行を抽出するために一般的に使用されている。
df[df$x > 5, ] x y z l 6 6 5 f e 7 7 4 g d 8 8 3 h c 9 9 2 i b 10 10 1 j a df[df$l == "a", ] x y z l 10 10 1 j a df[df$x > 5 & df$l == "a", ] x y z l 10 10 1 j a df[df$x > 5 | df$l == "a", ] x y z l 6 6 5 f e 7 7 4 g d 8 8 3 h c 9 9 2 i b 10 10 1 j a
which()
などと組み合わせると抽出の幅も広げられる。
x <- sample(10) x [1] 9 2 8 6 7 5 3 4 10 1 x[x %% 2 == 0] [1] 2 8 6 4 10 # Get Index # which(x %% 2 == 0) # [1] 2 3 4 8 9 x[which(x %% 2 == 0)] [1] 2 8 6 4 10
重複抽出やランダム抽出
同じインデックスを使うことで、重複させることができる。これを上手く使えばブートストラップも簡単に実現できる。
df[c(1,1,1,1,1,1,1,1,1,1,1), ] x y z l 1 1 10 a j 1.1 1 10 a j 1.2 1 10 a j 1.3 1 10 a j 1.4 1 10 a j 1.5 1 10 a j 1.6 1 10 a j 1.7 1 10 a j 1.8 1 10 a j 1.9 1 10 a j 1.10 1 10 a j
sample()
を組み合わせる。
df[sample(nrow(df), replace = TRUE), ] x y z l 6 6 5 f e 5 5 6 e f 3 3 8 c h 2 2 9 b i 4 4 7 d g 2.1 2 9 b i 10 10 1 j a 7 7 4 g d 1 1 10 a j 2.2 2 9 b i
out <- vector(mode = "list", length = 10) for(i in seq_along(out)){ out[[i]] <- df[sample(nrow(df), replace = TRUE), ] } out[[1]] x y z l 3 3 8 c h 4 4 7 d g 1 1 10 a j 6 6 5 f e 3.1 3 8 c h 1.1 1 10 a j 1.2 1 10 a j 5 5 6 e f 4.1 4 7 d g 9 9 2 i b out[[5]] x y z l 2 2 9 b i 5 5 6 e f 4 4 7 d g 2.1 2 9 b i 10 10 1 j a 6 6 5 f e 4.1 4 7 d g 9 9 2 i b 10.1 10 1 j a 3 3 8 c h
ランダムフォレストのように行数、列数をサンプリングする場合も簡単にできる。
num_r <- 5 num_c <- 2 iris[sample(nrow(iris), num_r), sample(ncol(iris), num_c), drop = FALSE] Sepal.Width Petal.Length 122 2.8 4.9 54 2.3 4.0 74 2.8 4.7 109 2.5 5.8 18 3.5 1.4 iris[sample(nrow(iris), num_r), sample(ncol(iris), num_c), drop = FALSE] Species Sepal.Width 68 versicolor 2.7 112 virginica 2.7 60 versicolor 2.7 5 setosa 3.6 92 versicolor 3.0 iris[sample(nrow(iris), num_r), sample(ncol(iris), num_c), drop = FALSE] Species Sepal.Length 132 virginica 7.9 71 versicolor 5.9 123 virginica 7.7 89 versicolor 5.6 104 virginica 6.3
指定した行数の数だけ、行を複製することも簡単にできる。
df <- data.frame(id = c("A","B","C"), no = c(3,2,4)) idx <- rep(1:nrow(df), df$no) df[idx,] id no 1 A 3 1.1 A 3 1.2 A 3 2 B 2 2.1 B 2 3 C 4 3.1 C 4 3.2 C 4 3.3 C 4
層化やブロック化したい場合もこんな感じできる。
block <- 10 start <- sample(nrow(iris) - block, 1) end <- start + block -1 iris[start:end, ] Sepal.Length Sepal.Width Petal.Length Petal.Width Species 84 6.0 2.7 5.1 1.6 versicolor 85 5.4 3.0 4.5 1.5 versicolor 86 6.0 3.4 4.5 1.6 versicolor 87 6.7 3.1 4.7 1.5 versicolor 88 6.3 2.3 4.4 1.3 versicolor 89 5.6 3.0 4.1 1.3 versicolor 90 5.5 2.5 4.0 1.3 versicolor 91 5.5 2.6 4.4 1.2 versicolor 92 6.1 3.0 4.6 1.4 versicolor 93 5.8 2.6 4.0 1.2 versicolor start <- sample(nrow(iris) - block, 1) end <- start + block -1 iris[start:end, ] Sepal.Length Sepal.Width Petal.Length Petal.Width Species 117 6.5 3.0 5.5 1.8 virginica 118 7.7 3.8 6.7 2.2 virginica 119 7.7 2.6 6.9 2.3 virginica 120 6.0 2.2 5.0 1.5 virginica 121 6.9 3.2 5.7 2.3 virginica 122 5.6 2.8 4.9 2.0 virginica 123 7.7 2.8 6.7 2.0 virginica 124 6.3 2.7 4.9 1.8 virginica 125 6.7 3.3 5.7 2.1 virginica 126 7.2 3.2 6.0 1.8 virginica
単一要素の抽出
サブセット化演算子[[
と$
が使える。また、[
もあるが[[
との違いについてはAdvanced R 2nd Editionのイメージ図がわかりやすい。[
は小さなリストを返すが、[[
は要素のみを返す。
x <- list(1:3, "a", 4:6) x[1] [[1]] [1] 1 2 3 x[[1]] [1] 1 2 3
x[1:2] [[1]] [1] 1 2 3 [[2]] [1] "a" x[-2] [[1]] [1] 1 2 3 [[2]] [1] 4 5 6 x[c(1,1)] [[1]] [1] 1 2 3 [[2]] [1] 1 2 3 x[0] list()
下記は再帰的にサブセットされるので等価。
x[[c(1, 2)]] [1] 2 x[[1]][[2]] [1] 2
また、リストを使って関数を作る際やループを使う際に、単一の値を抽出したい場合は必ずアトミックベクトルを使用することが推奨される。例えば、下記のよう感じ。
# 非推奨 for (i in 2:length(x)) { out[i] <- fun(x[i], out[i - 1]) } # 推奨 for (i in 2:length(x)) { out[[i]] <- fun(x[[i]], out[[i - 1]]) }
割り当て
すべてのサブセット化演算子は、代入と組み合わせることで部分代入が可能。基本形式はx[i] <- value
。
x <- c(1:5, NA) x[c(1, 2)] <- c(11, 12) x [1] 11 12 3 4 5 NA x[is.na(x)] <- 10000 x [1] 11 12 3 4 5 10000
データフレームに使うことでNA
を補完できる。つまり、TRUE
の部分に値を代入する。
df x1 x2 1 NA 1 2 2 1 3 2 NA 4 NA NA 5 NA NA 6 2 2 7 NA 2 8 1 1 9 2 NA 10 1 NA df[is.na(df)] <- 100 df x1 x2 1 100 1 2 2 1 3 2 100 4 100 100 5 100 100 6 2 2 7 100 2 8 1 1 9 2 100 10 1 100