前回、R言語で変数型を意識した方が良い例として重回帰分析を紹介しました。その投稿を見てくれた知り合いが、自身の失敗談を教えてくれたので、今回はその話をします。
知り合いの話では、「本来数値データとして独立変数に入れるつもりが、なぜかファクター型として読み込まれてしまっていた。回帰分析の出力結果を見たときに、変数の読み込み失敗に気づいたので、ファクター型変数を数量型変数へと変換した。ところが解析結果は結局おかしなものになってしまった。」というものでした。
仮想例による再現
前回の例を参考にしながら、上で書いた状態を再現してみましょう。
まず、従属変数と独立変数として二つの数量型変数をつくります。
set.seed(1) DV <- rnorm(100) set.seed(2) IV <- rnorm(100) summary(cbind(DV,IV))
次に独立変数が誤ってファクター型変数になってしまった状態を意図的に作ります。
IVc <- factor(IV) summary(IVc)
ファクター型の変数を独立変数としたおかしな回帰分析の結果を再現してみます。
res.c <- lm(DV ~ IVc) summary(res.c)
回帰分析を実施した出力が以下のようになり、何かおかしい事に気づきました。
これは変数の読み込みがおかしいのだと考え、以下のように独立変数を数量型データへと変換します*1。
そしてもう一度回帰分析をしてみました。
IVcn <- as.numeric(IVc) res.cn <- lm(DV ~ IVcn) summary(res.cn)
結果を表示すると、一見何の問題もないように見えます。しかし、本来あるべき解析結果と比較すると結果が異なっているのです。
res <- lm(DV ~ IV) summary(res)
こうなってしまったのは、ファクター型から数量型へデータを変換するために使ったas.numeric関数が原因です。ただし、これはバグではなく、仕様です。詳しく見てみましょう。
factor型変数のLevels
factor関数は、数量型または文字列型の変数を変換する際に、水準(levels)という出力を作成します。水準には与えられた入力オブジェクトの値の異なりが保存されています。以下の例がわかりやすいと思います。ファクター型に変換すると、"a","b","c"のような文字列のオブジェクトではa,b,cが、1,2,4のような数値のオブジェクトでは1,2,3という水準が割り振られています。
alphabetObject <- c("a","b","c","a","c","b","b") numberObject <- c(1,2,4,1,4,2,2) alphabetObject.factor <- factor(alphabetObject) numberObject.factor<- factor(numberObject) print(alphabetObject.factor) print(numberObject.factor)
次に上記の変数をas.numeric関数を利用して変換してみます。
as.numeric(alphabetObject.factor) as.numeric(numberObject.factor)
出力結果は、どちらも1,2,3という数字で構成されています。文字列が割り振られているファクター型の変数は水準の低い方から1,2,3というように数字を割り振られています。また、数値オブジェクトから変換したファクター型の変数でも、水準の低い方から1,2,3と割り振られています。なので、元々の数値オブジェクトは3という値が含まれていないのに、ファクター型に変換したものを再び数値型に変換すると3という値が含まれています。このような変換の仕様によって、回帰分析の値の中身が変わってしまっていたのでした。これを確かめるため、回帰分析の独立変数について最初の変数と、変換をした後の変数のそれぞれについて最初の6個の値をみてみましょう。
#最初の変数 head(IV) #ファクター型に変換した変数 head(IVc) #再び数値型に変換した変数 head(IVcn)
このように途中まで一見して同じ値の変数になっているので、特に注意が必要です。
対処法: 一旦文字列にしてから、数量データに変換する
では、何らかの理由でファクター型に変換されてしまった変数を、値を変えることなく再び数値型の変数にするにはどうすればよいのでしょうか。実は、直接数値型に戻さずに、一旦文字列型に戻してから、数値型に変換すると値を変えずに数値型オブジェクトに変換できます。
#一旦文字列型に変換 IVcch <- as.character(IVc) #文字列型の変数を数値型変数に変換 IVcchn <- as.numeric(IVcch) #最初の変数の最初の6個の値 head(IV) #変換後の変数の最初の6個の値 head(IVcchn)
*1:今回は変数の型を変換したことが明示的に示されるように別の変数への代入という方法を取っています。このままだとメモリを解放しないまま解析が進んでしまうので、サイズの大きなデータでは解析が進むにしたがってメモリの問題が深刻になることもあります。これを防ぐには、もう使わない変数をrm関数で削除したり、同じ変数名のまま上書きしていくなどの処理が有効です。