root3のメモ帳

データサイエンスな知識を残していくブログ。

【R言語】欠損値補完パッケージ「simputation」が使いやすそう、という話

「Rで欠損値埋めるのにいいパッケージないかな〜」と思って色々探してみたら、「simputation」という良さそうなパッケージを見つけました。

参考文献

紹介スライド

そもそもこのパッケージの事を知ったのは、下記のスライドがきっかけです。「こんな感じでコードがかけたら便利だなあ」みたいなものが的確に実装されている事がわかりました。

www.slideshare.net

使い方

当然英語ではあるものの製作者(?)から提供されている具体例も非常にシンプルでわかりやすく、基本的な機能の使い方はこれで身につけられると思います。かく言う僕もまだこれを読んだ程度の理解です。

https://cran.r-project.org/web/packages/simputation/vignettes/intro.html

Rのhelp

最後に、いつものドキュメント。

https://cran.r-project.org/web/packages/simputation/simputation.pdf

{simputation}の何がいいのか

基本的な使い方は参考文献に譲るとして、ここではどのあたりが使いやすいのかを僕視点で解説します。

なお、この記事では例を紹介する際に、irisデータを以下の操作で加工したものを利用します。上記使い方でも利用しているものです。

dat <- iris
dat[1:3,1] <- dat[3:7,2] <- dat[8:10,5] <- NA
head(dat,10)

中身はこんな感じです:

##    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1            NA         3.5          1.4         0.2  setosa
## 2            NA         3.0          1.4         0.2  setosa
## 3            NA          NA          1.3         0.2  setosa
## 4           4.6          NA          1.5         0.2  setosa
## 5           5.0          NA          1.4         0.2  setosa
## 6           5.4          NA          1.7         0.4  setosa
## 7           4.6          NA          1.4         0.3  setosa
## 8           5.0         3.4          1.5         0.2    <NA>
## 9           4.4         2.9          1.4         0.2    <NA>
## 10          4.9         3.1          1.5         0.1    <NA>

書き方がシンプル

「simputation=simple + imputation」と言う意味らしいですが、その名前通り、非常にシンプルに記述する事ができます。
例えば、「Sepal.LengthをSepal.WidthとPetal.Lengthで回帰して補完する」と言うコードをRの組み込み関数のみで表現する場合、下記のようになるでしょう。

mod <- lm(Sepal.Length ~ Sepal.Width + Petal.Length, data = dat)
pred <- predict(mod, newdata = dat)
da1 <- dat
da1$Sepal.Length <- ifelse(is.na(da1$Sepal.Length), pred, da1$Sepal.Length)

内容としては、

  1. Sepal.WidthとPetal.LengthでSepal.Lengthを回帰
  2. 予測値を算出
  3. NAが含まれるレコードだけ予測値に置き換え

といった感じです。

これを、{simputation}を使って書くと、一行で済みます。

da1 <- impute_lm(dat, Sepal.Length ~ Sepal.Width + Petal.Length)

「一行で書ければいいってものじゃない」と言うのはその通りですが、この一行をみて「Sepal.LengthをSepal.WidthとPetal.Lengthで回帰して補完する」という意図は即読み取れるのではないでしょうか。

{dplyr}と相性がいい

2点目は、dplyrとの相性の良さです。impute_lmに代表されるような{simputation}のimpute用関数は、全て下記の形を取っています。

impute_hoge(dat, formula, ...)
# dat : データ
# formula : 補完式

重要なのは引数の順番です。datが一番最初にあります。つまり、パイプ演算子を使って連鎖的に書く事が可能です。

da4 <- dat %>% 
  impute_lm(Sepal.Length ~ Sepal.Width + Species) %>%
  impute_median(Sepal.Length ~ Species) %>%
  impute_cart(Species ~ .)

impute関数が非常に便利

impute_lmの他にも、{simputation}には様々な補完メソッドを実装されています。しかし、いくら豊富な補完メソッドがあるとはいえ、全てはカバーしきれないものです。 しかし、そんな時でも使える関数を{simputation}は用意してくれています。それがimpute関数です。例えば、最初の例である「Sepal.LengthをSepal.WidthとPetal.Lengthで回帰して補完する」を、impute関数を使う場合は以下のように表現します。

mod <- lm(Sepal.Length ~ Sepal.Width + Petal.Length, data = dat)
da1 <- dat %>%
    impute(Sepal.Length ~ mod)

このコードの意味は、「Sepal.Lengthをmodを使ってimputeする」です。modのところに持ってくるmodelオブジェクトを変えれば、任意の方法で補完を行う事が可能です。

また、この関数は訓練データと検証データを区別している際にも役に立ちます。欠損値補完用のモデルを構築する際には当然訓練データを使いますが、検証用データにも同じモデルを適用する必要があります。その場合は下記のように書けば良いことになります。

mod <- lm(Sepal.Length ~ Sepal.Width + Petal.Length, data = X_train)
X_test <- X_test %>% 
    impute(Sepal.Length ~ mod)

モデルを保持しておく必要がある、という意味では手間は組み込み関数と同じですが、見た目非常にスッキリしていると思います。

まとめ

ここでは挙げていないですが、{simputation}にはまだまだ良いところがあります。詳細は使い方などを見てください。

  • グループごとの欠損値補完
  • 複数欠損値の同時補完
  • {VIM}パッケージを使って高速化(?)

3つ目に関しては、「実際にサンプルコードをループさせたらむしろ遅かった」とか「tibbleに対して適用したら何故かエラーが出た(他のサンプルコードでは出ていない)」等、よくわからないところは多いのですが、それ以外に関しては非常に使いやすいと思っています。現状欠損値補完するならこれ一択ではないでしょうか。

というわけで、あまりにも使いやすかった{simputation}の紹介でした。