randomForestではCharacterは使わないようにしよう
RのrandomForest
を使っていてはまったのでメモしておきます。
①目的変数がcharacterだと分類として扱ってくれない
最初にはまったのがこちらでした。目的変数がcharacter
だとカテゴリ変数として扱ってもらえないため、分類ではなく回帰としてプログラムが進んでしまい、エラーが返ります。
まずは以下のようにデータを読み込みます。ちなみにこのデータはTelco Customer Churn
で、
KaggleからDLしてきました。
library(tidyverse) library(randomForest) d <- read_csv("./Data/WA_Fn-UseC_-Telco-Customer-Churn.csv")
このデータを使って下記のようにrandomForest
を実行します:
> randomForest(Churn ~ gender, d) y - ymean でエラー: 二項演算子の引数が数値ではありません
「何このエラー??」と思いながらrandomForest
の中身を見てみると、以下のような記述が見つかります。
addclass <- is.null(y) classRF <- addclass || is.factor(y)
classRF
というのはカテゴリ変数であるかを判定するbooleanなのですが、is.factor
で判断しているんですね。ここでFALSE
が返ると、後の工程で分類のためのプログラム(Cで書かれたもの、多分コレのL38 ~ L540)ではなく、回帰用のプログラム(多分コレのL22 ~ L340)が呼ばれてしまうようです。
ちなみにrandomForest
はformulaでもmatrixでも対応できるような総称関数になっているので、
コンソールでrandomForest
と叩いても中身を見ることはできません。そのような場合、まずはmethods
でどのような関数が含まれているかを確認しましょう。
> methods(randomForest) [1] randomForest.default* randomForest.formula* see '?methods' for accessing help and source code
randomForest
という関数はrandomForest.default
とrandomForest.formula
という関数を総称しているようです。前者がメインのようなので以下のコマンドを実行します。
getS3method("randomForest", "default")
そうすると先程の関数定義を確認することができます。さらにちなみに、lookup
というパッケージを用いることでそういった総称関数やCで書かれた関数などをRStudio上でシンタックスハイライトさせながら表示することができます。大変便利ですので、是非こちらの記事を参考にして使ってみてください。
②説明変数がcharacterだとダミー化してくれない
次にはまったのがこちらでした。さきほどエラーが返ってきたrandomForest
を、目的変数をfactorに直して実行してみましょう:
d2 <- d %>% mutate(Churn = as.factor(Churn))
> randomForest(Churn ~ gender, d2) 強制変換により NA が生成されました randomForest.default(m, y, ...) でエラー: 外部関数の呼び出し (引数 1) 中に NA/NaN/Inf があります
NA
がありますというエラーなのですが、このデータではNA
はTotalCharges
列にしかありませんので、どうやら途中で生成されているようです。ちなみにRStudioでRMarkdownを使っているとき、チャンク内で実行すると上記のエラーが表示されるのですが、コンソールで実行すると以下のようにもう少し情報が追加されます:
> randomForest(Churn ~ gender, d2) randomForest.default(m, y, ...) でエラー: 外部関数の呼び出し (引数 1) 中に NA/NaN/Inf があります 追加情報: 警告メッセージ: data.matrix(x) で: 強制変換により NA が生成されました
data.matrix
が悪さしているようですね。私は普段チャンク内で実行させることが多いため、
この表示に気付かなくて時間を無駄にしました。
randomForest.default
を見ると以下の記述があります。
if (is.data.frame(x)) { xlevels <- lapply(x, mylevels) ncat <- sapply(xlevels, length) ncat <- ifelse(sapply(x, is.ordered), 1, ncat) x <- data.matrix(x)
ここでxをdata.matrix(x)
でダミー化しようとしたものの、character
であったために失敗しているようですね。
> d2 %>% select(Churn, gender) %>% str() Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 7043 obs. of 2 variables: $ Churn : Factor w/ 2 levels "No","Yes": 1 1 2 1 2 2 1 1 2 1 ... $ gender: chr "Female" "Male" "Male" "Male" ...
> d2 %>% select(Churn, gender) %>% data.matrix(.) %>% head() 強制変換により NA が生成されました Churn gender [1,] 1 NA [2,] 1 NA [3,] 2 NA [4,] 1 NA [5,] 2 NA [6,] 2 NA
それではfactor
に直して実行してみましょう。
d3 <- d2 %>% select(Churn, gender) %>% mutate(gender = as.factor(.$gender))
> str(d3) Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 7043 obs. of 2 variables: $ Churn : Factor w/ 2 levels "No","Yes": 1 1 2 1 2 2 1 1 2 1 ... $ gender: Factor w/ 2 levels "Female","Male": 1 2 2 2 1 1 2 1 1 2 ...
> randomForest(Churn ~ gender, d3) Call: randomForest(formula = Churn ~ gender, data = d3) Type of random forest: classification Number of trees: 500 No. of variables tried at each split: 1 OOB estimate of error rate: 26.54% Confusion matrix: No Yes class.error No 5174 0 0 Yes 1869 0 1
ようやく動くようになりました。
このようなエラーにはまらないためには、例えばread_csv
の代わりに(StringsAsFactors
をFALSE
にしないで)read.csv
を使うか、randomForest
の代わりにranger
を使うという手があります。
> ranger::ranger(Churn ~ gender, d) Ranger result Call: ranger::ranger(Churn ~ gender, d) Type: Classification Number of trees: 500 Sample size: 7043 Number of independent variables: 1 Mtry: 1 Target node size: 1 Variable importance mode: none Splitrule: gini OOB prediction error: 26.54 %
もとのデータでも動いていますね!
終わりに
今回のエラーはモデリングに入る前にダミー化を行っていれば防げたものでしたが、Rだとカテゴリ変数をそのまま渡しても動くものが多いのでついサボってしまいました。普段からダミー化をする習慣が身についている人や、多分Pythonでははまらないんでしょうね。
ただ今回はエラーを追いかけながら総称関数のソースの便利な確認方法を知ることができたので良かったです。