Rでデータのバリデーションチェックをやってみる


はじめに

業務でデータのバリデーションチェックを行う機会がありました.
本番では別のソフトを使いましたが,Rでも実行できるし,
使いようによってはもっと簡単に実行できるのでは?と思い
Rでバリデーションチェックを行ってみたので方法を記事にしました.

バリデーションチェックとは

データマネジメントの現場では,しばしばデータの「品質チェック」
(バリデーションチェック)が行われます.
大量のデータをすべて目で見るのは大変で,間違いも起こってしまいます.
そこで,今あるデータの「品質」を簡単に評価するために,データの範囲や
極端な値をチェックし異常をあらかじめ浮き彫りにするという操作です.

Rでバリデーションチェックを行う

Rには,統計解析に用いる関数群はもちろんですが,データ操作のための関数や
パッケージも充実しています.
ファイルの出力も容易なので,再現性のある結果を共有することもできます.

実行環境

今回の実行環境です.
Windows10 64bit 言語:日本語

R.Version()$version.string
Console
[1] "R version 3.6.2 (2019-12-12)"

データの読み込みには以下のパッケージを使います

 library(readr)  #csvファイルの読み込み
 library(readxl) #xlsxファイルの読み込み

バリデーションチェックやデータクレンジングにおいては
どのような処理を行ったか記録を残しておくことは重要です.
なので,データを読み込む前に,R上での欠損値と空白(スペース)の扱いを抑えておきます.

スペースの扱い

次のようなデータを用意します.

スペースの位置 半角スペース 全角スペース
" ab"  ab
後ろ "ab " ab 
前後 " ab "  ab 
文字中 "a b" a b

※半角スペースの方は,みやすいように""でかこっています

各行は文字前後の空白の位置を表しています.
それぞれ文字の前,後ろ,前後,文字中に
半角と全角のスペースが挿入されています.

このデータがsampleという名称で,保存されているとします.

csvの場合

データがcsv形式で保存されている場合
readrパッケージのread_csv関数で読み込みます.

Data<-read_csv("sample.csv")

データを確認します.

Data
Console
# A tibble: 4 x 3
  スペースの位置 半角スペース 全角スペース
  <chr>          <chr>        <chr>       
1              ab            ab        
2 後ろ           ab           ab         
3 前後           ac            ac       
4 文字中         a b          a b   

ここでデータを見てみると,半角スペースの方は
文字前後のスペースが削除されていることがわかります.

実はread_csv関数は,引数trim_wsでデータ前後の空白を
データ読み込み時に自動で削除するか選択することができます.

このオプションはデフォルトではTRUEになっているため
普段意識する必要がありませんが,
データクレンジングを行う際は,暗黙のうちにクレンジング処理が
行われていることを認識しておく必要があります.

また,trim_ws=FALSEとして読み込めば
データを「そのまま」の状態で読み込みことができます.

Data<-read_csv("sample.csv",trim_ws = F)
Data
Console
# A tibble: 4 x 3
  スペースの位置 半角スペース 全角スペース
  <chr>          <chr>        <chr>       
1              " ab"         ab        
2 後ろ           "ab "        ab         
3 前後           " ac "        ac       
4 文字中         "a b"        a b   

xlsxの場合

データがxlsxで保存されている場合も,使用する関数は異なりますが処理は同様です.

Data<-read_xlsx("sample.xlsx")
Data
Console
# A tibble: 4 x 3
  スペースの位置 半角スペース 全角スペース
  <chr>          <chr>        <chr>       
1              ab            ab        
2 後ろ           ab           ab         
3 前後           ac            ac       
4 文字中         a b          a b   
Data<-read_xlsx("sample.xlsx",trim_ws = F)
Data
Console
# A tibble: 4 x 3
  スペースの位置 半角スペース 全角スペース
  <chr>          <chr>        <chr>       
1              " ab"         ab        
2 後ろ           "ab "        ab         
3 前後           " ac "        ac       
4 文字中         "a b"        a b  

欠損値の扱い

Rではデータが欠損している場合,NA(Not Available)として読み込まれます.

以下のようなデータを用意します.

スペースの数 半角スペース 全角スペース
1 " " " "
2 " " "  "
3 " " "   " 

※今回も,みやすいように""でかこっています

sampleという名前で保存し,データを読み込んでみます.

Data<-read_csv("sample.csv")
Data
Console
# A tibble: 3 x 3
  スペースの数 半角スペース 全角スペース
         <dbl> <lgl>        <chr>       
1            1 NA                      
2            2 NA                     
3            3 NA                

半角スペースはその数に関わらず,欠損値として扱われ
全角スペースはそのまま文字列として扱われていることがわかります.

xlsx形式でも同様です.

Data<-read_xlsx("sample.xlsx")
Data
Console
# A tibble: 3 x 3
  スペースの数 半角スペース 全角スペース
         <dbl> <lgl>        <chr>       
1            1 NA                      
2            2 NA                     
3            3 NA                

これらのことからデータの読み込み時には

  • 全角スペースは文字と同様の扱いがなされる
  • 半角スペースのみは欠損,文字前後の半角スペースは自動でトリミングの対象となる

と認識しておけば問題なさそうです.

バリデーションチェックをやってみる

実際にバリデーションチェックを行います.
バリデーションチェックでは次のような項目をチェックします.

  • データ型
  • データの長さ
  • ユニーク数
  • 欠損値の数
  • NULL値の数
  • 空白スペースの有無

もし,対象データにデータ定義書があればそれに基づいて
値の長さや範囲の逸脱がないかチェックを行います.(ない場合もしばしば...)

今回は簡単にサンプルとして次のようなデータが100件あるとします.
(データは自作です.必要であればここからダウンロードできます.)

データの読み込み
Df<-read_csv("sample_data.csv")

カラムの定義

ID CATALOG_ID NUMBER
CHAR(4) CHAR(3) INT

データの概観
上位5列

head(Df,n=5)
Console
# A tibble: 5 x 3
  ID    CATALOG_ID NUMBER
  <chr> <chr>      <chr> 
1 A284  BJF        548   
2 A476  RGP        701   
3 A582  EAU        344   
4 A591  MJO        780   
5 111   KIA        943  

データの定義からは,データの型と文字数しかわからないので
ここからデータを見て,推測していくことになります.

ID列は,英文字+数字3桁の文字列
CATALOG_ID列はアルファベット3文字からなる文字列
NUMBER列には文字通り整数が入っていそうです.

しかし,すでにID列の5列目にあるようにデータの定義からもれた
値が存在しています.

データの数が数万を超えてくると,当然一つ一つチェックするのは難しいので
これらの異常をバリデーションチェックにより明らかにしましょう.

チェック用の関数を定義

先ほどの項目を調べるための関数をいくつか定義します.
それぞれデータの最小/最大値,最長/最短値,欠損値の数を取り出します.

min_val<-function(x)min(x,na.rm = T)
max_val<-function(x)max(x,na.rm = T)
max_len<-function(x)max(nchar(x),na.rm = T)
min_len<-function(x)min(nchar(x),na.rm = T)
num_unique<-function(x)length(unique(x))
num_na<-function(x)sum(is.na(x))
varidation_check.R
 validation_check<-function(Df){
#結果を格納するオブジェクトを用意
 res_validation<-
   data.frame(matrix(NA,ncol(Df),6,
                     dimnames = list(colnames(Df),
                                     c("val_min","val_max","len_min","len_max",
                                       "unique_num","na_num"))))
#関数を適用
 res_validation$val_min<-apply(Df,2,min_val)
 res_validation$val_max<-apply(Df,2,max_val)
 res_validation$len_min<-apply(Df,2,min_len)
 res_validation$len_max<-apply(Df,2,max_len)
 res_validation$unique_num<-apply(Df,2,num_unique)
 res_validation$na_num<-apply(Df,2,num_na)
 res_validation
 }

バリデーションチェック

validation_check(Df)

しかし,ここで何やらエラーメッセージが.

Console
Error in nchar(x) : invalid multibyte string, element 25

データの25列目が悪さをしているようです.
中身を見てみます.

Df[25,]
Console
# A tibble: 1 x 3
  ID            CATALOG_ID NUMBER
  <chr>         <chr>      <chr> 
1 "\x88\x9f345" VTY        479   

元データも確認します.

ID CATALOG_ID NUMBER
亜345 VTY 479

文字化けが起きているようです.
Windows環境の場合,Shift-JISCP932でエンコード
していることが原因です.

元データを変更するわけにはいかないので,Rで対処します.
データ読み込み時に,エンコードを明示的に指定することで回避できます.

Df<-read_csv("sample_data.csv",locale = locale(encoding = "CP932"))
Df[25,]
Console
# A tibble: 1 x 3
  ID    CATALOG_ID NUMBER
  <chr> <chr>      <chr> 
1 亜345 VTY        479   

文字化けは回避できました.
再度バリデーションチェックを行います.

validation_check(Df)
Console
           val_min val_max len_min len_max unique_num na_num
ID                  亜345       1       4        100      1
CATALOG_ID     ARO     YVI       3       3         99      0
NUMBER         100     a12       3       3         95      0

チェックができました.
ここでデータの定義を再確認します.

カラムの定義

ID CATALOG_ID NUMBER
CHAR(4) CHAR(3) INT

結果からは以下の不整合がみられます.

  • ID列

    • 長さが4以外の値が存在する
    • 入力ミスと思われる値が存在する(亜345)
    • 全角スペースが存在する
  • CATALOG_ID列

    • ユニーク数が99となっているが,これはすべてユニークでなくても問題ないのか.
  • NUMBER列

    • 数値以外の値が存在する

簡単なチェックでも,これらの不整合が確認できます.
不明点や気になる点はデータのお客様に確認を行います.

まとめ

Rでデータのバリデーションチェックを行いました.
データを読み込み時の注意点とバリデーションチェックの確認観点を整理します.

注意点

Rでデータを読み込む際は,暗黙のうちに半角スペース削除のクレンジング処理が
行われているということを認識しておく必要があります.

また,Windows環境の場合,データにうまく扱えない文字が存在する恐れがあるので
エンコードには注意を払う必要があります.

バリデーションチェック

  • 基本的にはデータ定義書の範囲からの逸脱がないか調べる
  • 極端な値の確認(最大/最小,最長/最短,桁数)
  • 主キーの候補を考えてみる
  • 数値の最大値(最小値)も疑ってみる

不明点はデータの所有者に確認し,解決するようにしましょう.

おわりに

ご意見や誤りなどあればコメントをお願いします.

補足

今回のバリデーションチェックでは,全角スペースも文字列として扱っています.
全角スペースも欠損値として扱うことも多いと思います.その場合

  • 全角スペースもNAに置き換える
  • スペースを除いた文字の最小/最大のチェックを行う

などもう少し丁寧な処理が必要となります.

参考文献