医療データ奮闘記

公衆衛生大学院に入った内科系専門医が医師として培った現場感と大学院で培った統計の知識を交えながら、医療や疫学や統計に関する素朴な疑問や本音をつらつら書いています。

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()は使うなという詳細は下記サイト参照

factor型を使わずに全てcharacter型にすべきという意見も多い。
でもfactor型にしないと動かない関数もあるからなあ。。

結論

一番良いのは、一つ一つ変数を確認する事だけど、

  • 一つうまく行ったら、他の変数は確認せずにコピペしちゃったり
  • まとめて確認はしたけど見た限りで異常なしとかなっちゃったり
  • その時は大丈夫だったけど、後で上の方のスクリプト(SQLとか)いじってしまった

とかあるので、やはりこう言う事が起こり得るという認識が必要な気がする。


参考にもっと単純な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))