Rのこと。

記事は引っ越し作業中。2023年中までに引っ越しを完了させてブログは削除予定

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

f:id:AZUMINO:20190728165838p:plain

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()

f:id:AZUMINO:20190728165355p:plain

下記は再帰的にサブセットされるので等価。

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