データクレンジングとは IT用語辞典 によると「データベースに保存されているデータの中から、重複や誤記、表記の揺れなどを探し出し、削除や修正、正規化などを行い、データの品質を高めること。」と説明されています。よく見かけるクレンジング対象になるデータ例としては
- 欠損値
- 全角文字と半角文字の混在
- 空白の有無
- 区切り文字の有無や揺れ
などがあります。特にデータ入力がフリフォーマットの場合はクレンジングを行わないと使い物にならないデータが多くなる傾向があるようにみえます。
Packages and Datasets
本ページではR version 3.4.4 (2018-03-15)の標準パッケージ以外に以下の追加パッケージを用いています。
Package | Version | Description |
---|---|---|
tidyverse | 1.2.1 | Easily Install and Load the ‘Tidyverse’ |
imputeMissings | 0.0.3 | Impute Missing Values in a Predictive Context |
mice | 3.3.0 | Multivariate Imputation by Chained Equations |
VIM | 4.7.0 | Visualization and Imputation of Missing Values |
また、本ページでは以下のデータセットを用いています。
Dataset | Package | Version | Description |
---|---|---|---|
airquality | datasets | 3.4.4 | New York Air Quality Measurements |
欠損値処理
データには必ずと言っていいほどレコードの一部のデータが取得できていないものが含まれます。取得できていないデータを欠損値と呼びます。Rで欠損値を含んだデータを読み込んだ場合、デフォルト処理では欠損値はNA
という特殊なデータとして処理されます。
欠損値の扱いはデータや分析目的などにより変わりますが、扱い方は大きく以下のように三つに分類されます。
- 欠損値をそのまま扱う
- 欠損値を削除する
- 欠損値を補完する
欠損値を削除する
欠損値を含むレコードは不完全なものとして分析対象外として扱う方法です。欠損値を削除する代表的な方法には下表のような方法があります。
方法 | 詳細 |
---|---|
リストワイズ法 | 欠損値を持つレコード全てを削除します |
ペアワイズ法 | 統計量の算出に関わる二変数のいずれかに欠損値があるレコードを削除します |
Base Rにはna.omit
関数が用意されていますが、na.omit
関数はNA
を含む全てのレコードを削除します。特定の変数にNAが含まれている場合にのみNA
を削除したい場合にはtidyr::drop_na
関数を用います。
リストワイズ法
リストワイズ法によりtidyr::drop_na
関数はNA
を含む全てのレコードを削除する場合は以下のように行います。
airquality %>%
tidyr::drop_na() %>%
head()
## # A tibble: 6 x 6
## Ozone Solar.R Wind Temp Month Day
## * <int> <int> <dbl> <int> <int> <int>
## 1 41 190 7.4 67 5 1
## 2 36 118 8 72 5 2
## 3 12 149 12.6 74 5 3
## 4 18 313 11.5 62 5 4
## 5 23 299 8.6 65 5 7
## 6 19 99 13.8 59 5 8
ペアワイズ法
例えばSolar.R
変数とWind
変数を用いて統計量を計算するために両変数ともNA
であるレコードのみを削除するペアワイズ法の場合には
- 統計量計算時のみ当該レコードを削除する
- データセットからレコードを削除する
の二つの方法が考えられますが基本的には全社の統計量計算時のみ欠損値のあるレコードを削除するアプローチを用いるようです(確認中)。例えば相関係数を計算するにはuse
オプションで"pairwise.complete.obs"
を指定します。
cor(airquality$Solar.R, airquality$Wind, use = "pairwise.complete.obs")
## [1] -0.05679167
同様に分散(var
)や共分散(cov
)でも同様のオプション指定が可能です。
データセットから任意の二変数が共にNAの場合のレコードを削除するには以下のような方法があります。
airquality %>%
dplyr::mutate(flag = Solar.R | Ozone) %>%
tidyr::drop_na(flag) %>%
dplyr::select(-flag) %>%
head()
## # A tibble: 6 x 6
## Ozone Solar.R Wind Temp Month Day
## * <int> <int> <dbl> <int> <int> <int>
## 1 41 190 7.4 67 5 1
## 2 36 118 8 72 5 2
## 3 12 149 12.6 74 5 3
## 4 18 313 11.5 62 5 4
## 5 28 NA 14.9 66 5 6
## 6 23 299 8.6 65 5 7
欠損値を補完する
データ収集対象の性格などにより欠損値の発生が避けられない場合や取得したデータをなるべく活かしたい場合に欠損値を推定して補完する方法があります。
方法 | 詳細 |
---|---|
平均値代入法 | 平均値を用いて欠損値を補完します |
回帰代入法 | 欠損値を削除したデータから回帰分析を行いその推定値を用いて欠損値を補完します |
確率的回帰代入法 | 回帰代入法で得られた推定値にランダム誤差を加えた値で欠損値を補完します |
完全情報最尤推定法 | 欠損パターンに応じた尤度関数で最尤推定を行い推定値を求めます |
多重代入法 | 欠損値を代入した複数のデータセットを作成してそこから欠損値を推測します |
その他の方法 | その他機械学習を用いた補完方法など |
上記以外にも変数の性格から欠損値に特定値(例えばゼロ)を割り当てる方法もよく使われる方法です。
欠損値の補完や削除方法をairqularity
データセットを用いて説明します。airquality
データセットは6つの変数それぞれに「37, 7, 0, 0, 0, 0」個のNA
を含むデータセットです。このデータセットを用いてNA
の代表的な処理例を示します。
airquality %>%
head()
## # A tibble: 6 x 6
## Ozone Solar.R Wind Temp Month Day
## * <int> <int> <dbl> <int> <int> <int>
## 1 41 190 7.4 67 5 1
## 2 36 118 8 72 5 2
## 3 12 149 12.6 74 5 3
## 4 18 313 11.5 62 5 4
## 5 NA NA 14.3 56 5 5
## 6 28 NA 14.9 66 5 6
単一代入法
平均値や回帰値を用いて欠損値を補完する方法を後述の多重代入法と対比させて単一代入法と呼びます。単一代入法のように欠損値を特定の値に変換する場合は、まず変数の値がNA
であるか否かを判断する必要があります。これにはis.na
関数を用います。例えばOzone
変数のNA
をOzone
変数の平均値に変換する場合はifelse
関数と組み合わせて処理を行います。
ozone <- airquality %>%
dplyr::summarise(ozone_mean = round(mean(Ozone, na.rm = TRUE), 0))
airquality %>%
dplyr::mutate(ozone = ifelse(is.na(Ozone), ozone$ozone_mean, Ozone)) %>%
dplyr::select(Ozone, ozone, Solar.R, Wind, Temp, Month, Day) %>%
head()
## # A tibble: 6 x 7
## Ozone ozone Solar.R Wind Temp Month Day
## * <int> <dbl> <int> <dbl> <int> <int> <int>
## 1 41 41 190 7.4 67 5 1
## 2 36 36 118 8 72 5 2
## 3 12 12 149 12.6 74 5 3
## 4 18 18 313 11.5 62 5 4
## 5 NA 42 NA 14.3 56 5 5
## 6 28 28 NA 14.9 66 5 6
平均値代入法の結果を可視化すると下図のようになります。赤線の部分が欠損値を平均値で補完したものです。このような時系列データには不向きな手法に見えます。
airquality %>%
dplyr::mutate(Ozone = ifelse(is.na(Ozone), ozone$ozone_mean, Ozone)) %>%
dplyr::bind_cols(airquality) %>%
dplyr::select(Ozone, Ozone1) %>%
tibble::rowid_to_column("days") %>%
ggplot2::ggplot(ggplot2::aes(x = days)) +
ggplot2::geom_line(ggplot2::aes(y = Ozone), colour = "red") +
ggplot2::geom_line(ggplot2::aes(y = Ozone1), colour = "blue")
多重代入法
多重代入法は確率的代入の考えに基づき欠損値を疑似的に補完したデータ(疑似完全データという)を複数作成し、全ての擬似完全データを分析、それらの分析結果を統合して結論(推定値)を出すという代入、解析、統合の三段階で値を導き出す方法です。一口に多重代入法といっても様々なアルゴリズムが提案されていますが、ここで扱う多重代入法はMICE(連鎖方程式による多変量補定)によるものです。
と書くと何やら難しそうですが、前出のmice
パッケージを利用することで計算自体は簡単に行えます。例えば、airquality
データセットではOnone
変数とSolar.R
変数にある欠損値を多重代入法で補完するには以下を実行するだけで済みます。
(aq_mice <- airquality %>%
mice::mice(printFlag = FALSE, seed = 0) %>%
mice::complete()) %>%
head()
## # A tibble: 6 x 6
## Ozone Solar.R Wind Temp Month Day
## * <int> <int> <dbl> <int> <int> <int>
## 1 41 190 7.4 67 5 1
## 2 36 118 8 72 5 2
## 3 12 149 12.6 74 5 3
## 4 18 313 11.5 62 5 4
## 5 37 101 14.3 56 5 5
## 6 28 220 14.9 66 5 6
補完結果がどのようになっているか確認してみます。下図において赤線の部分が欠損値を補完したものです。平均値代入法(単一代入法)にくらべるとかなり“らしい”感じになっていると思います。
aq_mice %>%
dplyr::bind_cols(airquality) %>%
dplyr::select(Ozone, Ozone1) %>%
tibble::rowid_to_column("days") %>%
ggplot2::ggplot(ggplot2::aes(x = days)) +
ggplot2::geom_line(ggplot2::aes(y = Ozone), colour = "red") +
ggplot2::geom_line(ggplot2::aes(y = Ozone1), colour = "blue")
注意事項
多重代入法は乱数を用いますので、本ページではmice::mice
関数にseed
オプションを指定して発生する乱数を固定することで再現性を確保しています。また、疑似完全データの数はデフォルトのm = 5
を使用しています。疑似完全データの数は歴史的にはデフォルト値であるm = 5
程度と言われてきたようですが、最近では計算機の処理速度も上がってきているためm = 20
くらいが目安と言われているようです。 参考までにmice::mice
関数が計算した疑似完全データセットの密度分布(赤線)を下図に示します。疑似完全データ数が多い方が青線の元データ(欠損値を含むデータの密度分布)から大きく外れた値を計算していないように見えます。
airquality %>%
mice::mice(printFlag = FALSE, seed = 0) %>%
densityplot()
airquality %>%
mice::mice(m = 20, printFlag = FALSE, seed = 0) %>%
densityplot()
機械学習による補完
Rでは機械学習(ランダムフォレスト)による欠損値の補完をimputeMissings
パッケージを用いて行うことができます。imputeMissings::compute
関数を用いて学習データを作成しimputeMissings::impute
関数で補完する値を求めますが、学習データの用意ができない場合でも以下のように対象データを元に演算を行ってくれます。
airquality %>%
imputeMissings::impute(method = "randomForest") %>%
dplyr::bind_cols(airquality) %>%
dplyr::select(Ozone, Ozone1) %>%
tibble::rowid_to_column("days") %>%
ggplot2::ggplot(ggplot2::aes(x = days)) +
ggplot2::geom_line(ggplot2::aes(y = Ozone), colour = "red") +
ggplot2::geom_line(ggplot2::aes(y = Ozone1), colour = "blue")