臨床研究や予備的な検討で、手元の対象者の中から決まった人数をランダムに選びたいことがあります。たとえば 20 人の中から 10 人を無作為に取り出す、といった場面です。ここでは初心者にも過程が見える形で、R を使ってランダムに抽出する方法を紹介します。考え方は「各人にランダムな番号をふり、その順に並べて上から取る」という、くじ引きと同じやり方です。
## なぜシード(種)を設定するのか
> [!info] シードとは
> R の乱数は、本来は実行するたびに違う値になります。これでは「前回と同じ抽出をもう一度確認する」ことができません。`set.seed()` に好きな整数を 1 つ渡しておくと、そこから生成される乱数列がいつも同じになり、何度実行しても同じ抽出結果を再現できます。`set.seed` の seed は「種」という意味で、同じ種をまけば同じ結果が育つ、とイメージすると分かりやすいです。
>
> 論文やレポートには、使ったシードの値(下の例では 123)を書き残しておきます。そうすれば、ほかの人も同じ結果を再現できます。
実際に試してみます。`sample(10)` は 1 〜 10 をランダムに並べ替えて返す命令です。まず `set.seed()` を使わずに 3 回続けて実行すると、毎回違う並びになります。
```r
sample(10)
sample(10)
sample(10)
```
```
[1] 9 7 3 6 4 1 2 10 8 5
[1] 7 3 10 6 2 5 1 9 8 4
[1] 10 2 9 6 4 7 8 1 5 3
```
この 3 つは実行するたびに変わるので、お手元では別の数字になります。次に、毎回 `set.seed(123)` を実行してから `sample(10)` を行うと、何度やっても同じ並びになります。
```r
set.seed(123); sample(10)
set.seed(123); sample(10)
set.seed(123); sample(10)
```
```
[1] 3 10 2 8 6 9 1 7 5 4
[1] 3 10 2 8 6 9 1 7 5 4
[1] 3 10 2 8 6 9 1 7 5 4
```
同じ種(123)をまくたびに、まったく同じ乱数が返ってきます。これが「シードを固定すると抽出を再現できる」という意味です。この記事でも、抽出の前に `set.seed()` を実行してから進めます。
## 練習用のデータを用意する
`id`(対象者番号)、`sbp`(収縮期血圧)、`dbp`(拡張期血圧)の 20 例を `data.frame()` で作ります。
```r
df <- data.frame(
id = 1:20,
sbp = c(132,145,128,150,138,122,160,141,135,118,
155,129,148,133,126,142,137,151,120,144),
dbp = c(84,92,80,95,88,76,98,90,85,72,
96,82,93,86,79,89,87,94,75,91)
)
head(df)
```
```
id sbp dbp
1 1 132 84
2 2 145 92
3 3 128 80
4 4 150 95
5 5 138 88
6 6 122 76
```
> [!note] 迷ったら戻る
> `data.frame()` でのデータ作成は [[031 - data.frame で表を作る]]、`c()` で数値をまとめる操作は [[017 - c() で数値ベクトルを作る]]、`head()` で先頭を見る操作は [[033 - 先頭と末尾を見る]] で練習できます。
## 手順1 シードを固定して、くじ番号の列を追加する
シードを固定したうえで、各人に「くじ番号」をランダムに 1 つずつふり、`rand` という列として追加します。ここで `nrow(df)` は、データの行数(ここでは人数の 20)を自動で数える関数です。`sample(1:nrow(df))` と書くと、1 から人数までの整数をランダムな順番に並べ替えて返すので、各人に重ならない番号が 1 つずつ割り当てられます。人数を直接 `20` と書かずに `nrow(df)` を使うのがポイントで、こうしておけば 30 人でも 100 人でも、同じコードがそのまま使えます。
> [!info] `nrow(df)` とは何か
> `nrow()` は number of rows(行の数)の略で、データフレームの行数を返す関数です。今回のデータは 1 人 1 行なので、行数がそのまま人数になります。試しに打ってみると、次のように数字が返ってきます。
> ```r
> nrow(df)
> ```
> ```
> [1] 20
> ```
> いまの `df` は 20 人ぶんなので `20` と返ります。つまり `sample(1:nrow(df))` は、このデータでは `sample(1:20)` と同じ意味です。人数が違うデータに貼り替えれば、その人数に自動で合わせてくれます。
```r
set.seed(123)
df$rand <- sample(1:nrow(df))
head(df)
```
```
id sbp dbp rand
1 1 132 84 15
2 2 145 92 19
3 3 128 80 14
4 4 150 95 3
5 5 138 88 10
6 6 122 76 2
```
各人にくじ番号がふられました。たとえば 1 番の人は 15、4 番の人は 3、という具合です。番号の大小そのものに意味はなく、ただのランダムなくじだと考えてください。
> [!note] 迷ったら戻る
> 新しい列を追加する操作(`df$rand <- ...`)は [[040 - 新しい列を追加する]]、行数を数える `nrow()` は [[034 - 行数と列数を調べる]] で練習できます。`set.seed()` と `sample()` は、この記事で初めて出てくる関数です(`set.seed()` は乱数を固定する関数、`sample()` はランダムに並べ替え・抽出する関数です)。
## 手順2 くじ番号の順に並べ替えて、上から取り出す
`rand` の小さい順に全体を並べ替え、上から 10 例を取り出します。並べ替えには `order()` を使います。`order(df$rand)` は「`rand` が小さい順に並べたときの行番号」を返すので、それを使って行を並べ替えます。
```r
df_sorted <- df[order(df$rand), ]
selected <- head(df_sorted, 10)
selected
```
```
id sbp dbp rand
18 18 151 94 1
6 6 122 76 2
4 4 150 95 3
10 10 118 72 4
9 9 135 85 5
7 7 160 98 6
16 16 142 89 7
19 19 120 75 8
12 12 129 82 9
5 5 138 88 10
```
くじ番号 1 〜 10 の人が、上から順に並びました。これが、ランダムに選ばれた 10 例です。左端の数字(18, 6, 4 …)はもとの行番号で、`id` 列を見るとどの対象者が選ばれたか分かります。5 例だけ取りたいときは `head(df_sorted, 5)` とします。
> [!note] 迷ったら戻る
> 並べ替えに使う `order()` は [[048 - 値の大きい順に並べ替える]]、行や列を位置で取り出す `df[行, 列]` の書き方は [[038 - 行と列を位置で取り出す]]、条件で行を絞る考え方は [[039 - 条件に合う行を取り出す]] で練習できます。
## 抽出するときの注意点
> [!tip] この方法のよいところ
> 各人にふられた番号が `rand` 列として残るため、「なぜこの人が選ばれたのか」を後から確認でき、その表をそのまま割付の記録として保存できます。手順が目に見えるので、何が起きているか理解しやすいのも利点です。
あわせて、大切な点をまとめます。
- 抽出の前に必ず `set.seed()` を実行し、使ったシードの値を記録しておきます。
- 元データの並び順をあらかじめ固定しておきます(上の例では `id` の順)。並び順が違うと、同じシードでも結果が変わります。
- シードの値を変えれば、別のランダムな抽出になります。
## そのまま貼って使えるコード
データフレームを `df`、取り出したい人数を 10 とした、汎用のコードです。`df` を自分のデータの名前に、`10` を必要な人数に変えるだけで、列の名前や数、行数(人数)に関係なくそのまま使えます。
```r
set.seed(123) # シードを決めて記録しておく
df$rand <- sample(1:nrow(df)) # 各行にランダムなくじ番号をふる
df_sorted <- df[order(df$rand), ] # くじ番号の小さい順に並べ替える
selected <- head(df_sorted, 10) # 上から必要な人数を取り出す
selected
```
`nrow(df)` が行数を自動で数えるので、データが何人でも `20` のような数字を書き換える必要はありません。人数を変えたいときは最後の `10` だけを直します。