matrixもdata.frameも何となく同じように行、列に数字を並べたもののようで、あまり明確に違いを意識してこなかったのですが、ここを曖昧にしてしまうと計算ミスを招きかねないということに気づいたので整理しています。わかっている人には至極当たり前のことかと思いますが、この記事を読んで僕と同じようにつまずく人が少しでも減ってくれれば幸いです。
『matrixとdata.frameは別物』
『matrixは単純な数字の並び、data.frameは意味のある数字の列のまとまり』
列のまとまり
matrixとdata.frameで同じような数字の並びのデータを考えます。
c1 <- c(160, 161, 162, 163)
c2 <- c(55, 60, 65, 70)
m <- matrix(c(c1, c2), ncol = 2)
colnames(m) <- c("c1", "c2")
df <- data.frame(c1, c2)
このデータに対して、m,dfを出力すると以下のように、ほぼ同じような出力が得られます。
> print(m)
c1 c2
[1,] 160 55
[2,] 161 60
[3,] 162 65
[4,] 163 70
> print(df)
c1 c2
1 160 55
2 161 60
3 162 65
4 163 70
>
この段階で、気がつくこと。
「matrixでは、列の名前をcolnames(m)で指定したから、その通り表示されるのに対し、data.frameは、名前を特に指定しなくても、data.frame作成に使用したベクトルがそのまま名前になっている。
これはmatrixは数字(ベクトル)を単に並べたもの(8個のデータを4×2の形に表示したもの)であるのに対し、data.frameは2つの列のまとまり(要素c1,c2)をもつlistであるということから来るものと考えられます。
dimensionの変換
matrixの場合、今、このコードで書かれているmatrixは4×2で表されていますが、以下のように、1×8の行列や8×1の行列に書き換えることもできます。
> # 1×8の行列に変換
> dim(m) <-c(1,8)
> print(m)
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
[1,] 160 161 162 163 55 60 65 70
>
> # 8×1の行列に変換
> dim(m) <-c(8,1)
> print(m)
[,1]
[1,] 160
[2,] 161
[3,] 162
[4,] 163
[5,] 55
[6,] 60
[7,] 65
[8,] 70
これに対し、data.frameは列そのものに意味があるので(例えば、c1 = 身長、c2 = 体重)、これを1行8列のデータにすること自体、意味がありません。そのため、matrixのようにdimensionを変えようとしても、以下のようにエラーになります。
> # 1×8の行列に変換
> dim(df) <-c(1,8)
dim(df) <- c(1, 8) でエラー:
dims [product 8] はオブジェクト [2] の長さに整合しません
>
> # 8×1の行列に変換
> dim(df) <-c(8,1)
dim(df) <- c(8, 1) でエラー:
dims [product 8] はオブジェクト [2] の長さに整合しません
matrix, data.frameに対する演算(その1)
matrix, data.frameに対して演算(平均)を行ってみます。
> mean(m)
[1] 112
> mean(df)
[1] NA
警告メッセージ:
mean.default(df) で: 引数は数値でも論理値でもありません。NA 値を返します
matrixに対してmean()を行うと、全データの平均が返ってくるのに対し、data.frameに対してmean()を適用しても正しく計算されません(NAになります)。
これもmatrixは「単なる数字の並び」なので、mean()を計算すれば、全部のデータから平均を計算することができるけれど、data.frameは列毎に意味があるので、隣の列と合わせた平均(身長と体重の平均)に意味はないので計算できないと考えると理解できる挙動かと思います。
martrix, data.frameに対する演算(その2)
「data.frameに対して、平均を計算する」ということは、「data.frameの各列毎(各要素)に対して平均を計算する」ということだと思いますので、以下のようなコードを書くことで、c1, c2(身長, 体重)のそれぞれに対して平均計算することができます。
> sapply(df,mean)
c1 c2
161.5 62.5
>
ちなみに行列mに対して、sapply(m,mean)とすると以下のように、平均も何もしてない、そのままの値が返ってきます。
sapply(m,mean)
[1] 160 161 162 163 55 60 65 70
これは多分、mをlist化(as.list())すると以下のように各要素がそれぞれ1つのリストになり、そのリストの要素ごとの平均をとった計算結果を返すためなのかと思います(推定です。もし違っていたらご指摘ください)。
> as.list(m)
[[1]]
[1] 160
[[2]]
[1] 161
[[3]]
[1] 162
[[4]]
[1] 163
[[5]]
[1] 55
[[6]]
[1] 60
[[7]]
[1] 65
[[8]]
[1] 70
注意しなければいけないのは、matrix,data.frameに対して、以下の計算をした場合、エラーが出るわけではないけれど、両者、返ってくる結果は違うということ。
このあたりの計算の意味をきちんと理解していないと痛い目にあってしまうので要注意ということが書きたかったことです(この場合であれば、matrixとdata.frameの違いをきちんと理解しておかないとまずいなぁ、ということです)
sapply(m,mean)
sapply(df,mean)
matrix型とdata.frame型に対してlapply()を適用した場合について、こちらでもう少し深掘りしてます(lapplyとsapplyは戻り値の型が異なるだけなので基本的に同じような挙動をします)。
まとめ
今回計算に使っているm, dfのタイプを調べるとそれぞれ、double, listという結果が返ってきます。matrixは単なる数値(double)であるのに対し、data.frameは同じ内容の数字が入っていても、数値の型ではなく、listという入れ物の型が表示されます。matrixとdata.frameの違いは「単なる数字の並び」か「意味のある要素の『列』の並びという型を持ったデータ」か、というところにあり、ここが両者の本質的な違いなのかと思います。
> typeof(m)
[1] "double"
> typeof(df)
[1] "list"
matrixとdata.frameは異なるものなので、データをどちらの形で保持するかによって、計算できたりできなかったりするし、計算できても返ってくる結果が異なったりもします。両者の違いをきちんと理解してコードを書く必要があるということかと思います。