いわゆるマルチアンサー(以下、MA)、ようするに複数選択のデータなんですけれども、一般的にはこんな感じのCSVでやってくるはずですよね。
q1,q2_1,q2_2,q2_3
"1","1","0","0"
"2","1","1","0"
"1","0","1","0"
"2","1","1","1"
"1","0","1","1"
"2","0","0","1"
"1","1","0","1"
"2","1","0","0"
ですが、たまにこんな時があって非常に困ります。
q1,q2
"1","1"
"2","1,2"
"1","2"
"2","1,2,3"
"1","2,3"
"2","3"
"1","1,3"
"2","1"
べつにRubyとかPythonとかで変換しても良いのですけれども、Rの中でやっちゃいたいとき用のメモです。
Rのバージョンはこちら
> R.version.string
[1] "R version 3.4.4 (2018-03-15)"
比較的最新版です。
まず、サンプルデータ
を用意してみました。
> test <- data.frame(q1=c(1,2,1,2,1,2,1,2), q2=c("1","1,2","2","1,2,3","2,3","3","1,3","1"))
> test
q1 q2
1 1 1
2 2 1,2
3 1 2
4 2 1,2,3
5 1 2,3
6 2 3
7 1 1,3
8 2 1
> class(test$q1)
[1] "numeric"
> class(test$q2)
[1] "factor"
> test$q2
[1] 1 1,2 2 1,2,3 2,3 3 1,3 1
Levels: 1 1,2 1,2,3 1,3 2 2,3 3
いろいろ方法はあるのだと思いますが、まだまだRをはじめたばかりでして、基本的な構文しか知りません…… なので非常にベタな方法でいきたいと思います。
とりあえず、カンマ区切りを分割
したいところですが、型がfactorですので、そのままでは分割できません。
> strsplit(test$q2, ",")
Error in strsplit(test$q2, ",") : non-character argument
なので、まずは型を文字列にしてから分割します。
> strsplit(as.character(test$q2), ",")
[[1]]
[1] "1"
[[2]]
[1] "1" "2"
[[3]]
[1] "2"
[[4]]
[1] "1" "2" "3"
[[5]]
[1] "2" "3"
[[6]]
[1] "3"
[[7]]
[1] "1" "3"
[[8]]
[1] "1"
こんな感じ。ちなみにstrsplitの出力はlistです。Rは型が複雑でなかなか大変ですね。このままでは使いにくいので、これを一旦代入しておきます。
> q2A <- strsplit(as.character(test$q2), ",")
listをループでまわしてみる
listについては後半でまとめているので割愛。こんな感じでループで回せます。
> for (y in 1:length(q2A)) {
+ for (x in 1:length(q2A[[y]])) {
+ print(q2A[[y]][x])
+ }
+ }
[1] "1"
[1] "1"
[1] "2"
[1] "2"
[1] "1"
[1] "2"
[1] "3"
[1] "2"
[1] "3"
[1] "3"
[1] "1"
[1] "3"
[1] "1"
元のデータフレームに追加する
さて、データフレームですが、1行追加や1列追加はbind系のコマンドで意外と簡単です。
今回のMAは1,2,3の3選択肢で、本来でしたら1行ずつ、3つ分のデータを8回追加していきたいところですが、そういう方法は探してみたけど見つからなかったので、全8レコード分の1列を3回追加する方針で行きます。
> for (i in 1:3) {
+ tmp <- numeric(8)
+ for (y in 1:length(q2A)) {
+ for (x in 1:length(q2A[[y]])) {
+ n <- as.integer(q2A[[y]][x])
+ if (n == i) {
+ tmp[y] = 1
+ }
+ }
+ }
+ test <- cbind(test, tmp)
+ }
> test
q1 q2 tmp tmp tmp
1 1 1 1 0 0
2 2 1,2 1 1 0
3 1 2 0 1 0
4 2 1,2,3 1 1 1
5 1 2,3 0 1 1
6 2 3 0 0 1
7 1 1,3 1 0 1
8 2 1 1 0 0
やっていることは簡単なので、ここも割愛です。Rでの実装方法を把握するまでが面倒なんですよね……
名前を付ける
このままでは全て「tmp」なので、名前を付けます。これも一発です。
> names(test)[3:5] <- paste("q2", 1:3, sep="_")
> test
q1 q2 q2_1 q2_2 q2_3
1 1 1 1 0 0
2 2 1,2 1 1 0
3 1 2 0 1 0
4 2 1,2,3 1 1 1
5 1 2,3 0 1 1
6 2 3 0 0 1
7 1 1,3 1 0 1
8 2 1 1 0 0
集計してみる
> summarise_all(data.frame(test[3:5]), funs(sum))
q2_1 q2_2 q2_3
1 5 4 4
> reshape2::melt(summarise_all(data.frame(test[3:5]), funs(sum)))
No id variables; using all as measure variables
variable value
1 q2_1 5
2 q2_2 4
3 q2_3 4
> transform(reshape2::melt(summarise_all(data.frame(test[3:5]), funs(sum))), prop=value/nrow(test))
No id variables; using all as measure variables
variable value prop
1 q2_1 5 0.625
2 q2_2 4 0.500
3 q2_3 4 0.500
大丈夫そうですね。
listについてのメモ
以下、Appendix的な。
listってのが複雑でして、以下の通りとなります。
> q2A[2]
[[1]]
[1] "1" "2"
> q2A[[2]]
[1] "1" "2"
> q2A[2][2]
[[1]]
NULL
> q2A[[2]][2]
[1] "2"
まず、[]では返ってくるのがlistでして、[[]]だと中身が返ってくる感じです。以下、確認です。
> str(q2A[2])
List of 1
$ : chr [1:2] "1" "2"
> str(q2A[[2]])
chr [1:2] "1" "2"
なので、以下の2つが同じことになります。
> q2A[[2]][2]
[1] "2"
> unlist(q2A[2])[2]
[1] "2"