複数選択データが1カラムで来たときにRで分割する方法

いわゆるマルチアンサー(以下、MA)、ようするに複数選択のデータなんですけれども、一般的にはこんな感じのCSVでやってくるはずですよね。

sampl.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"

ですが、たまにこんな時があって非常に困ります。

sampl.csv
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)"

比較的最新版です。

まず、サンプルデータ

を用意してみました。

sample
> 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"
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.