tidyrでダミー変数を作成する(赤ペン先生希望)

とりあえず必要なパッケージを読み込んでおく。

library(tidyr)
library(dplyr)
library(pipeR)

例えば、以下のようなデータを考える。

> df <- data.frame(id=1:10, feature=c(1,NA,2,2,2,NA,NA,3,3,1))
> df
   id feature
1   1       1
2   2      NA
3   3       2
4   4       2
5   5       2
6   6      NA
7   7      NA
8   8       3
9   9       3
10 10       1

このデータのfeature列の値を回帰分析なりにつっこみたいので、feature列の値毎にダミー変数(列)を作る必要がある。model.matrix関数なんかでイケるのかなと思ったけど、どうもそうではないっぽいので自分でやってみようということです。

とりあえず何も考えずにtidyrパッケージのspreadをすれば良さそうだぞと。NAはうざいのでとりあえず0埋めで。

> df %>>% spread(feature, feature, fill=0)
   id 1 2 3 NA
1   1 1 0 0  0
2   2 0 0 0  0
3   3 0 2 0  0
4   4 0 2 0  0
5   5 0 2 0  0
6   6 0 0 0  0
7   7 0 0 0  0
8   8 0 0 3  0
9   9 0 0 3  0
10 10 1 0 0  0

さらに新しく出来たNA列がうざいので、こいつを削除したい。したいんだが・・・

> df %>>% 
+   spread(feature, feature, fill=0) %>>%
+   select(-contains("NA"))
data frame with 0 columns and 10 rows
> df %>>% 
+   spread(feature, feature, fill=0) %>>%
+   select(-NA)
   id 1 2 3 NA
1   1 1 0 0  0
2   2 0 0 0  0
3   3 0 2 0  0
4   4 0 2 0  0
5   5 0 2 0  0
6   6 0 0 0  0
7   7 0 0 0  0
8   8 0 0 3  0
9   9 0 0 3  0
10 10 1 0 0  0

・・・のように、うまくいかなかった。

追記
教えてもらったのだが、いったんdata.frameをかましてNA列をNA.列にするか、あるいはNAをバッククオート``で囲めばOKとのこと。

> df %>% 
+   spread(feature, feature, fill=0) %>%
+   data.frame %>%
+   select(-NA.)
   id X1 X2 X3
1   1  1  0  0
2   2  0  0  0
3   3  0  2  0
4   4  0  2  0
5   5  0  2  0
6   6  0  0  0
7   7  0  0  0
8   8  0  0  3
9   9  0  0  3
10 10  1  0  0
> 
> df %>% 
+   spread(feature, feature, fill=0) %>%
+   select(-`NA`)
   id 1 2 3
1   1 1 0 0
2   2 0 0 0
3   3 0 2 0
4   4 0 2 0
5   5 0 2 0
6   6 0 0 0
7   7 0 0 0
8   8 0 0 3
9   9 0 0 3
10 10 1 0 0

〜〜

しょうがなく列名を変更してから削除

> df %>>% 
+   spread(feature, feature, fill=0) %>>% 
+   setNames(c("id", paste0("X", colnames(.)[-1]))) %>>%
+   select(-XNA)
   id X1 X2 X3
1   1  1  0  0
2   2  0  0  0
3   3  0  2  0
4   4  0  2  0
5   5  0  2  0
6   6  0  0  0
7   7  0  0  0
8   8  0  0  3
9   9  0  0  3
10 10  1  0  0

それから、各列の値を全部1に持っていきたいので、以下にifelseで書く&その処理はid列には非適用。

> df %>>% 
+   spread(feature, feature, fill=0) %>>% 
+   setNames(c("id", paste0("X", colnames(.)[-1]))) %>>%
+   select(-XNA) %>>%
+   mutate_each(funs(ifelse(.>=1, 1, 0)), -id)
   id X1 X2 X3
1   1  1  0  0
2   2  0  0  0
3   3  0  1  0
4   4  0  1  0
5   5  0  1  0
6   6  0  0  0
7   7  0  0  0
8   8  0  0  1
9   9  0  0  1
10 10  1  0  0

・・・で、御望みの形になった。なったはいいけど、もっといいやり方があるはずなので、赤ペン先生希望します。

追記

コメント欄含めアドバイス多数頂戴した、ありがたいことです。


NA非対応だけどこんなかんじ。はじめから0埋めしておけばいいのか。
> model.matrix(~feature-1, df %>% mutate(feature = factor(feature)))
   feature1 feature2 feature3
1         1        0        0
3         0        1        0
4         0        1        0
5         0        1        0
8         0        0        1
9         0        0        1
10        1        0        0


若干遠回りしてる感あるけど、これでもいいのか。
> library(caret)
> feat <- df %>% mutate(feature = factor(feature))
> obj <- dummyVars(~feature, feat)
> predict(obj, feat)
   feature.1 feature.2 feature.3
1          1         0         0
2         NA        NA        NA
3          0         1         0
4          0         1         0
5          0         1         0
6         NA        NA        NA
7         NA        NA        NA
8          0         0         1
9          0         0         1
10         1         0         0

こちらのスライドの中で紹介されているdummiesパッケージを使うのもよさげ。dummy, dummy.data.frame関数でサクッといく。
> library(dummies)
> dummy(df$feature)
      feature1 feature2 feature3 featureNA
 [1,]        1        0        0         0
 [2,]        0        0        0         1
 [3,]        0        1        0         0
 [4,]        0        1        0         0
 [5,]        0        1        0         0
 [6,]        0        0        0         1
 [7,]        0        0        0         1
 [8,]        0        0        1         0
 [9,]        0        0        1         0
[10,]        1        0        0         0
> dummy.data.frame(df %>% mutate(feature=factor(feature)))
   id feature1 feature2 feature3 featureNA
1   1        1        0        0         0
2   2        0        0        0         1
3   3        0        1        0         0
4   4        0        1        0         0
5   5        0        1        0         0
6   6        0        0        0         1
7   7        0        0        0         1
8   8        0        0        1         0
9   9        0        0        1         0
10 10        1        0        0         0

qdapToolsパッケージというのもあるらしい。こいつのmtabulate関数でもイケる。

> qdapTools::mtabulate(df$feature)
   1 2 3
1  1 0 0
2  0 0 0
3  0 1 0
4  0 1 0
5  0 1 0
6  0 0 0
7  0 0 0
8  0 0 1
9  0 0 1
10 1 0 0

tidyrパッケージの前身であるreshape2でも出来る。出来るっちゃ出来るが抱える問題もtidyrとほぼ同じか。

> reshape2::dcast(df, id~feature, value.var = "feature", length)
   id 1 2 3 NA
1   1 1 0 0  0
2   2 0 0 0  1
3   3 0 1 0  0
4   4 0 1 0  0
5   5 0 1 0  0
6   6 0 0 0  1
7   7 0 0 0  1
8   8 0 0 1  0
9   9 0 0 1  0
10 10 1 0 0  0