data.frameにapply()関数を適用する場合の注意点をまとめています。apply()関数の使い方をネットで調べても、data.frameに対する使用法はたくさん見つかりますが、注意点が全然出てきません。エラーに悩まされたり、間違った計算をしないように、押さえておくべきところをちゃんと押さえておきましょう。
ネットでapply()関数の使い方を調べると
ネットでapply()関数の使い方を調べると、
- apply関数は,data.frame形式の行方向や列方向に対して処理を行う関数
- apply関数で、data.frameの行ごとの計算をする
- data.frameの各行/各列に対して、同種の演算を一括に行うときにapply を利用する
というような説明がなされていることが多いです。けれど、この説明だとちょっと危険。apply()関数のことをきちんと理解していないと、意味不明のエラーメッセージに悩まされることになりかねません。
apply()関数をヘルプで調べてみる
RStudioのヘルプでapply()関数を調べてみると、適用できるデータ型は配列(array)、行列(matrix)であると書かれています(以下の文章の赤線部分)。data.frame型は書かれていません。
Description
Returns a vector or array or list of values obtained by applying a function to margins of an array or matrix.Usage
apply(X, MARGIN, FUN, …)Arguments
X
an array, including a matrix.MARGIN
a vector giving the subscripts which the function will be applied over. E.g., for a matrix 1 indicates rows, 2 indicates columns, c(1, 2) indicates rows and columns. Where X has named dimnames, it can be a character vector selecting dimension names.FUN
the function to be applied: see ‘Details’. In the case of functions like +, %*%, etc., the function name must be backquoted or quoted.…
optional arguments to FUN.
じゃぁどうして、data.frameでも機能するの?
じゃぁ、apply()関数はdata.frameに対して使えないかというと、そういうわけでもありません。
apply関数の中身を確認してみると、初めの段階で変数Xがオブジェクトかどうかチェックされ、オブジェクトの場合、Xはmatrix型かarray型に変換されてから指定した関数が適用されます(下のコードの赤線部分)。つまり、data.frame型に対して、apply()関数を適用すると、一旦、matrix型に変換されてから、指定した関数が適用されているということ。
function (X, MARGIN, FUN, ...) { FUN<-match.fun(FUN) dl<-length(dim(X)) if (!dl) stop("dim(X) must have a positive length") if (is.object(X)) X<-if (dl==2L) as.matrix(X) else as.array(X) d<-dim(X) dn<-dimnames(X) ds<-seq_len(dl) 以下省略
data.frameは列が異なっていればデータ型は異なっても構わないのに対し、matrix型やarray型は全ての要素(行、列とも)が同じデータ型であることが必要です。このため、異なる型が混ざっているdata.frameにapply関数を適用すると、指定した関数に渡される変数の型がdata.frameの型と異なる可能性が出てきます。これをきちんと理解していないと、意味不明のエラーメッセージに悩まされたり、最悪の場合は間違った計算結果を出してしまう可能性があります。
以下のようなdata.frameを考えます(df1)。
df1 <- data.frame(x = 1:5, y = 11:15, z = 21:25) df1
> df1 x y z 1 1 11 21 2 2 12 22 3 3 13 23 4 4 14 24 5 5 15 25
これに対して、apply()関数を使って、以下の関数を適用してみます。
my_func1 <- function(x){ paste0("val1 = ", x[1] ," val2 + val3 = ", x[2] + x[3]) } apply(df1, MARGIN = 1, FUN = my_func1) apply(df1, MARGIN = 2, FUN = my_func1)
> apply(df1, MARGIN = 1, FUN = my_func1) [1] "val1 = 1 val2 + val3 = 32" [2] "val1 = 2 val2 + val3 = 34" [3] "val1 = 3 val2 + val3 = 36" [4] "val1 = 4 val2 + val3 = 38" [5] "val1 = 5 val2 + val3 = 40"
> apply(df1, MARGIN = 2, FUN = my_func1) x "val1 = 1 val2 + val3 = 5" y "val1 = 11 val2 + val3 = 25" z "val1 = 21 val2 + val3 = 45"
これは想像どおりの結果になったのではないかと思います。
次に、以下のようなdata.frame型の変数df2を考えます。df2はdf1のxを数字ではなく、文字に変えただけのdata.frameです。
df2 <- df1 df2$x <- as.character(df2$x) # xのみデータ型をcharacter型に変える df2
> df2 x y z 1 1 11 21 2 2 12 22 3 3 13 23 4 4 14 24
単純に表示しただけでは、df1と見た目は同じです。でもstr()関数で構造を確認してみると、以下のようにdf1とdf2は違っていることがわかります(そういう風に作ったので当たり前ですが)。
str(df1) str(df2)
> str(df1)
'data.frame': 5 obs. of 3 variables:
$ x: int 1 2 3 4 5
$ y: int 11 12 13 14 15
$ z: int 21 22 23 24 25
> str(df2)
'data.frame': 5 obs. of 3 variables:
$ x: chr "1" "2" "3" "4" ...
$ y: int 11 12 13 14 15
$ z: int 21 22 23 24 25
df2に対して、my_func1を適用するとエラーになります。
apply(df2, MARGIN = 1, FUN = my_func1)
> apply(df2, MARGIN = 1, FUN = my_func1)
x[2] + x[3] でエラー: 二項演算子の引数が数値ではありません
Called from: paste0("val1 = ", x[1], " val2 + val3 = ", x[2] + x[3])
data.frameに対して、apply()関数を適用するとデータ型が変わる可能性があることを理解していないと、
「x[2] + x[3] でエラー: 二項演算子の引数が数値ではありません」ってどういうこと???
2列目と3列目は数値のはずなのに…
って、なっちゃいますよね。知っていてもなってしまうかも…。でもエラーが出てくるなら、まだまし。もしエラーが出ずに処理できてしまう関数だったりしたら…、怖っ!
data.frameに対してapply()関数を適用する場合、こういうリスクがあるということを認識して、事前の確認を丁寧に行いながらコードを書いていくしかないのかなと考えています。
データ型が変わっていることの確認
先程の例に対して、引数の型を返す関数を適用してみると、渡されるデータは型変換されてしまっているのが確認できます。
my_func2 <- function(x){ paste0("val1 = ", class(x[1]), " val2 = ", class(x[2]), " val3 = ", class(x[3])) } apply(df1, MARGIN = 1, FUN = my_func2) apply(df2, MARGIN = 1, FUN = my_func2)
> apply(df1, MARGIN = 1, FUN = my_func2)
[1] "val1 = integer val2 = integer val3 = integer"
[2] "val1 = integer val2 = integer val3 = integer"
[3] "val1 = integer val2 = integer val3 = integer"
[4] "val1 = integer val2 = integer val3 = integer"
[5] "val1 = integer val2 = integer val3 = integer"
> apply(df2, MARGIN = 1, FUN = my_func2)
[1] "val1 = character val2 = character val3 = character"
[2] "val1 = character val2 = character val3 = character"
[3] "val1 = character val2 = character val3 = character"
[4] "val1 = character val2 = character val3 = character"
[5] "val1 = character val2 = character val3 = character"
まとめ
apply()関数はmatrix, array型に対して使うのが基本。data.frame型に対して使う場合は、処理結果を丁寧に確認して使ってください。
apply系の関数の使い方はこちら。
それにしても、Rって、ちょっと癖がありますね。とても面白い言語なんだけど。