ifelse()関数とfactor型が合わさると恐ろしい事が起こりうる
最近論文を書いていて大ダメージを受けたので、共有しておく(reviseの際に気づいたから良いものの、ギリギリで危なかった。) エラーが出ないミスは本当に怖い。
R使っている人はコピペで全部確認できます。
<目的>
スポーツ習慣とマラソンでゴールできるかどうかの関係を調べる為にdata_setというデータベースを作ったとする。これを分類していく。
data_setの作成
sex <- c("F","F","M","M","M") height <- c(158,162,177,173,166) weight <- c(51,55,72,57,64) sports <- c("1","2","0",NA,"2") goal <- c("1","1","0","0","0") data_set <- data.frame(SEX=sex, HEIGHT=height, WEIGHT=weight,SPORTS=sports,GOAL=goal)#ODBCと同じ動き data_set ## SEX HEIGHT WEIGHT SPORTS GOAL ## 1 F 158 51 1 1 ## 2 F 162 55 2 1 ## 3 M 177 72 0 0 ## 4 M 173 57 <NA> 0 ## 5 M 166 64 2 0
str(data_set) ## 'data.frame': 5 obs. of 5 variables: ## $ SEX : Factor w/ 2 levels "F","M": 1 1 2 2 2 ## $ HEIGHT: num 158 162 177 173 166 ## $ WEIGHT: num 51 55 72 57 64 ## $ SPORTS: Factor w/ 3 levels "0","1","2": 2 3 1 NA 3 ## $ GOAL : Factor w/ 2 levels "0","1": 2 2 1 1 1
変数説明としてのイメージは、
- SPORTS=2:毎週かなり運動している,1:少しは運動している,0:運動習慣がない,NA:無回答
- GOAL=1:goalした,0:goalできなかった
今から このSPORTSという変数をいじっていきます
問題形式で一つ一つ説明
Question.1 factorを整数にするとどうなる?
as.integer(data_set$SPORTS) ## [1] 2 3 1 NA 3
as.numeric(data_set$SPORTS) ## [1] 2 3 1 NA 3
上記SPORTS: Factor w/ 3 levels "0","1","2": 2 3 1 NA 3
の変数の中身
"0","1","2"
と2 3 1 NA 3
の違いがここで出現する。(level,label問題)
Question.2 factorを整数にしてからもう一度factorに変えるとどうなる?
as.factor(as.integer(data_set$SPORTS)) ## [1] 2 3 1 <NA> 3 ## Levels: 1 2 3
as.character(as.integer(data_set$SPORTS)) ## [1] "2" "3" "1" NA "3"
結果として、(sports <- c("1","2","0",NA,"2")
)と比べると、全部+1されます。
-> これはよくやるので、知っている人も多いかと。
Question.3 ifelseで「スポーツ習慣がある人」をまとめて二値にするとどうなる?
#元のsports <- c("1","2","0",NA,"2") data_set$SPO_answer <- c("1","1","0",NA,"1")#こうなって欲しい data_set$SPO <- as.factor(ifelse(data_set$SPORTS=="2","1",data_set$SPORTS)) data_set[,c("SPORTS","SPO","SPO_answer")] ## SPORTS SPO SPO_answer ## 1 1 2 1 ## 2 2 1 1 ## 3 0 1 0 ## 4 <NA> <NA> <NA> ## 5 2 1 1
比較してみると、全然違う結果に(なんと、0->1,1->2,2->1に変わっている。)
こうしてもだめだが、こうすれば大丈夫というものも記載。
data_set$SPO2 <- as.factor(ifelse(data_set$SPORTS=="2","1",as.factor(data_set$SPORTS)))#こうしてもだめ data_set$SPO3 <- as.factor(ifelse(data_set$SPORTS=="2","1",as.character(data_set$SPORTS)))#ここからは大丈夫 data_set$SPO4 <- as.factor(ifelse(data_set$SPORTS=="2"|data_set$SPORTS=="1","1","0")) data_set$SPO5 <- as.factor(dplyr::if_else(data_set$SPORTS=="2","1",as.character(data_set$SPORTS)))#,missing=で指定もできる。 data_set$SPO6 <- as.factor(dplyr::case_when(data_set$SPORTS=="2"|data_set$SPORTS=="1" ~ "1",data_set$SPORTS=="0" ~ "0")) data_set[,c("SPORTS","SPO","SPO2","SPO3","SPO4","SPO5","SPO6","SPO_answer")] ## SPORTS SPO SPO2 SPO3 SPO4 SPO5 SPO6 SPO_answer ## 1 1 2 2 1 1 1 1 1 ## 2 2 1 1 1 1 1 1 1 ## 3 0 1 1 0 0 0 0 0 ## 4 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> ## 5 2 1 1 1 1 1 1 1
改めて注意ですが、SPO,SPO2はだめな例です
if_else()を使えばまだ安心
if_else() で、as.character()を返し値に入れないと、以下のエラーが出てくれます。(だから、if_else()なら何かおかしいなと感じます。)
Error: false must be type character, not integer
他に頂いた意見としては、
ifelse(as.character(data_set$SPORTS)=="2","1",as.character(data_set$SPORTS)) ## [1] "1" "1" "0" NA "1" ifelse(as.character(data_set$SPORTS)=="0","0","1") ## [1] "1" "1" "0" NA "1"
ifelse()は使うなという詳細は下記サイト参照
- https://notchained.hatenablog.com/entry/2016/11/23/112729
- https://suryu.me/post/dplyr_recode/
- https://notchained.hatenablog.com/entry/2014/06/19/214502
- https://notchained.hatenablog.com/entry/2015/09/24/215646
factor型を使わずに全てcharacter型にすべきという意見も多い。
でもfactor型にしないと動かない関数もあるからなあ。。
結論
一番良いのは、一つ一つ変数を確認する事だけど、
とかあるので、やはりこう言う事が起こり得るという認識が必要な気がする。
参考にもっと単純なfactorと論理演算子で実験する
x <- factor(c(0,1,2), labels=c(5,6,7)) str(x) as.factor(x) str(as.factor(x)) x==7 x==3 x==5 x=="5" x x <- ifelse(x=="5", "2", x) x x==7 x==3 str(x) as.factor(x) str(as.factor(x))