2014年7月29日火曜日

Rのifelse文が行う先行評価のせいで警告メッセージが出るときの対処法

放射能の測定結果の表し方にはお約束があって、検出下限値未満だったときは、検出下限値の数値の左側に不等号(小なり記号)をつけて表現します。

つまり

  8.6

と書いてあれば検出値が8.6だったということですが、

  <8.6

と書いてあれば、検出下限値が8.6という環境(測定器機の精度、試料の量、計測時間 etc.)で測定したが、値が小さいせいで検出されなかった、ということになります。

これを知らない人がやらかしてしまったのが、↓こちらの誤報だったりします。

新潟県:「女性セブン」誌の放射能関連記事に誤った内容が掲載されたため、訂正と謝罪を求める抗議文を送付しました。

「出典は厚生労働省のホームページであり、不等号(<)が付されている値が検出下限値であることを理解せず転記した」 by小学館

とのこと。記者一人が勘違いしたとしても、チェック機構はなかったんでしょうかね。不等号を見て、「これ何だろう?」って思わなかったんでしょうか。

で、話を戻しまして・・・

このような表記法となっておりますので、データはこんな感じになっていたりします。

> data
  食品 結果
1    A <7.8
2    B  8.9
3    C <6.7
4    D  9.0


下限値未満をどう扱うかは分析の方針次第ですが、仮に「下限値未満はゼロとして扱う」としたかったとしましょう。その場合、不等号付きの結果を加工してやる必要があります。

また、data$結果はcharacter型かfactor型(因子型)になっているので、このままでは平均を出したり、ヒストグラムを書いたりはできません。

「結果」をnumeric型に変換した「結果2」という列を追加してみましょう。

for ( i in 1:nrow(data) ) {
  if ( substr(data$結果[i], 1, 1) == "<" ) {
    # 不等号付きのときは、ゼロにする
    data$結果2[i] <- 0
  } else {
    # 不等号付きでないときは、そのまま数値にする
    data$結果2[i] <- as.numeric(data$結果[i])
  }
}

>data

  食品 結果 結果2
1    A <7.8   0.0
2    B  8.9   8.9
3    C <6.7   0.0
4    D  9.0   9.0


結果2はnumeric型になっていますので、mean(data$結果2) とか、hist(data$結果2)とかすることも可能です。

が、このようにfor文とif文で処理する人はあまりいないと思います。Rにはifelseという便利な記法があり、しかもifelseは「ベクトル化された演算」(詳しくは「アート・オブ・Rプログラミング」参照)であるため、for文で記述するよりも高速に実行できるというメリットもあります。

先ほどのfor文によるループをifelseで書き直すと以下のようになります。

data$結果2 <- ifelse( substr(data$結果, 1, 1) == "<", 0, as.numeric(data$結果) )

ifelseは第1引数を評価して、真ならば第2引数を、偽ならば第3引数を返すという動きをします。引数としてベクトルを渡せば、すべての要素について処理を繰り返してくれます。

実行結果として生成される、データフレームの列は、先ほどのfor文の場合と同じになります。

が、しかし、上記の書き方だと、↓こんなwarningが出てしまうんですよね。

 警告メッセージ:
In ifelse(substr(data$結果, 1, 1) == "<", 0, as.numeric(data$結果)) :
   強制変換により NA が生成されました


結果の中にNAなどないし、強制変換しなきゃいけないような処理もしていないはずだし・・・、なぜでしょうか?

どうやらこれは、ifelseが「第1引数の真偽にかかわらず、第2引数・第3引数を評価する」という動きをしているのことに起因しているようです。分岐はせずにすべて評価しておいて、不要な結果は捨てるという動きをしているのでしょう。

つまり「<7.8」に対しても、as.numericしてしまい結果がNAとなる、でも第1引数の条件式は偽になるので、その結果は使われない。なので、最終結果としては問題なしです。

でも、警告が出るのが気持ち悪い。なんとかならないものか・・・

で、結論としては、下記のように書けばOKということが分かりました。(前置きが長くなりました・・・)

data$結果2 <- as.numeric( ifelse( substr(data$結果, 1, 1) == "<", 0, data$結果 ) )

最後にas.numericすればいいだけですね。0に対してもas.numericすることになってしまいますが、これについては警告は出ないので、こちらの方がベターかなと思います。

【重要な補足】
「結果」の列がcharacter型ではなく、factor型になっている場合があります。いやむしろデフォルトでCSVファイルを読み込むとそうなるので、そちらのケースが多いかもしれません。

その場合は・・・

data$結果2 <- as.numeric( ifelse( substr(data$結果, 1, 1) == "<", 0, as.character(data$結果) ) )

としてやる必要があります。

ファクタをそのまま as.numericすると文字列の表す数字ではなく、水準の値が返ってきてしまうんですよね。

水準というのは、"男"、"女"、"その他" というファクタがあったときに、内部的にはそれぞれに 1、2、3が割り当たっていたりするんですが、その数字のことです。

ファクタの水準値を使われてしまうと、数値が明らかにおかしくなるので、変換後のデータフレームの内容をちゃんとチェックしてから、次の処理を行いましょう。




0 件のコメント:

コメントを投稿