今回は、データ解析の基本となるベクトルと行列について学びます。
ベクトル、行列、データフレームの違いの確認
スカラー、ベクトル、行列間の演算方法の確認(数学における演算ルールと一部異なることに注意)
ベクトルと行列を使った簡単な演習
まずはベクトルを作ってみましょう。c()関数を使って複数の数値を一つのオブジェクト(ベクトル)に代入することができます。
#simple generation of vectors
test_v <- c(1.0, 2.0, 2.5)
作ったオブジェクトのクラスを確認するにはclass()関数を使います。
class(test_v)
コンソールに返される値は"numeric"(数値)なので、陽にベクトルというクラスとして認識されているわけではないようです。
ベクトルに変換できそうな関数 as.vector() を使って、ベクトルに変換後、再びクラスを確認してみます。
test_v <- as.vector(test_v)
class(test_v)
コンソールに返される値は相変わらず"numeric"のままです。
もう一つ、ベクトルに変換できそうな関数は、as.array() です。array(配列)はベクトル、行列、テンソルなど多次元のデータを保存するためのオブジェクトクラスです。
test_v <- as.array(test_v)
class(test_v)
確かに、"array"タイプのオブジェクトになったことが確認できるはずです。ただし、以下のベクトル同士、あるいは、ベクトルと行列の演算(計算)に大きな影響は内容で、ベクトルに関してはわざわざarrayに変換する必要はありません。最後に次の計算につなげるためにもう一つベクトルを作っておきましょう。ベクトルや行列の挙動を調べるためのトライアルなので、数値には意味はありません。
test_v2 <- c(2.0, 3.0, 4.0)
次に、上で用意した二つのベクトルtest_v1, test_v2を結合して行列をつくろうと思います。二つの方法を比較しましょう。
cbind()関数~columm (列↓)方向にベクトルを結合する関数~を使うと、
test_m <- cbind(test_v, test_v2)
class(test_m)
コンソールに返される値は、"matrix" "array"となって行列タイプであることがわかります(matrixは2次元のarray(配列)なので矛盾はありません。ちなみに1次元の配列はベクトル、3次元以上の配列はテンソルといいます。)
ここでcbind.as.dataframe()関数を使うと、関数名から予想がつくように生成されるオブジェクトはデータフレームになります。
test_d <- cbind.data.frame(test_v, test_v2)
class(test_d)
データフレームと行列(一般にはarray)で何が違うかは、少し先のところで確認します。
さて、データを外部ファイルから読み込んでもオブジェクトは作れますね。read.csv()関数を使うと、
#load dataset
data <- read.csv("./data_sample2.csv", header=TRUE)
#Show the dataset
View(data)
class(data)
生成されたオブジェクトはデータフレームであることが分かります。
データフレームでは、rownames()関数とcolnames()関数を使って、行(row→)と列(column↓)の名前を確認することができます。
rownames(data)
colnames(data)
このオブジェクトをas.matrix()関数を使って行列に変換後、同じ操作をしてみると、View()関数で表示されるデータの並びは全く同じなのに、行名だけがNULLと返されるので、行名が無いようです。列名はあるのに行名は無いというのは、ちょっと一貫性がないように思いますが、これがデータフレームと行列の違いの一つです。
test_m2 <- as.matrix(data)
View(test_m2)
class(test_m2)
rownames(test_m2)
colnames(test_m2)
名前があるかどうか、よりももう少しプログラミングする上で問題となる違いは、「どうやってオブジェクト内の要素にアクセスするか?」です。
データフレームの場合は、列名も指定できるのが特徴です。以下を一つずつ確認してください。
#data access
#for dataframe
data$x
data$x[2]
data[1,]
data[,1]
data[2,3]
一方、配列(行列)では、列名が存在するにもかかわらず、列名での指定はできません。オブジェクト名test_mの後に$マークをタイプしても、データフレームの時と違って、列名の候補が出てきません。
#for matrix (array)
test_m
colnames(test_m)
test_m[1,]
test_m[,1]
test_m[2,3]
以上が、ベクトル、行列(配列)、データフレームの類似点・相違点です。
スカラー(=1つの数値)、ベクトル(=1次元の配列)、行列(=2次元の配列)間の加法・減法・乗法・除法については十分注意が必要です。なぜなら、数学での定義と同じ計算と全く異なる計算が混じっているからです。演算のルールを説明するために以下のようにスカラー、ベクトル、行列を用意しておきます。
スカラーとベクトルの演算
数学での定義と同じものとは、スカラーとベクトルの掛け算・割り算(乗法・除法)です。
数学での定義は、以下の通り。
Rで掛け算の演算子(記号)は、"*"です。数学での定義通りに計算できることを確認してください。割り算もいっしょです。
#vector with scalar
test_v
2*test_v
test_v/2
コンソールには以下のように値が返されるはずです。
> test_v
[1] 1.0 2.0 2.5
> 2*test_v
[1] 2 4 5
> test_v/2
[1] 0.50 1.00 1.25
問題は、スカラーとベクトルの足し算、引き算です。スカラーとベクトルは次元が違うので、数学では足したり引いたりできません。しかしRでは以下のようにスカラーを各要素に足し引きするルールになります(R独自の演算ルールは、数学のルールと区別するため、ここでは=を使わずに→→を使うことにします)。
実際にスクリプトで確認してみましょう。
test_v + 2
test_v - 2
ベクトルとベクトルの演算
次に二つのベクトル同士の演算を考えます。数学と同じなのは加法・減法です。次元の同じベクトル同士では、各要素ごとの加法・減法となります。数学でのルールは以下の通りです。
これは、Rでの演算でも同じです。
#vector with vector
test_v + test_v2
test_v - test_v2
数学とは違うルールなのは、ベクトル同士の掛け算、割り算です。後述しますが、数学ではベクトル同士の掛け算では内積・外積というのがありますが、通常割り算は考えません。Rではどちらも要素同士の掛け算、割り算になるので注意が必要です。
実際にスクリプトで確認してみましょう。
test_v*test_v2
test_v/test_v2
要素ごとの掛け算、割り算になっているのが分かるはずです。
次に生態学や数理モデル、データ解析でよく使う、ベクトル同士の「内積」ですが、数学では以下のように定義されています。ざっくばらんに書くと、数学で通常の掛け算は内積になります。もちろん外積という他の掛け算やRでの掛け算と同じルールの演算も定義できますが、普通は内積です。
Rでは通常の掛け算の記号(「演算子」と言います)"*"はR独自の掛け算で使ってしまっていますから、内積を計算するときは、それとは別の演算子"%*%"を使います。
test_v%*%test_v2
これの結果が、以下のように数学の定義通りに計算したものと一致することも確かめましょう。
test_v[1]*test_v2[1] + test_v[2]*test_v2[2] + test_v[3]*test_v2[3]
スカラーと行列の演算
次に行列について調べます。まずは3x3の行列を用意するための追加のスクリプトを用意します。
#matrix with scalar
test_v3 <- c(1,1,1)
test_m3 <- cbind(test_m, test_v3)
class(test_m3)
test_m3
スカラーと行列の掛け算・割り算は数学とRでは演算ルールが同一です。行列の各要素にスカラーを使って掛け算・割り算をすることになります。スカラーと行列の足し算・引き算は、数学では次元が異なるので通常定義されませんが、スカラーとベクトルの足し算・引き算と同じように要素ごとに演算がなされます。ルールの提示は省略しますが、次のスクリプトで確かめることができます。
2.0*test_m3
test_m3 + 1
1 + test_m3
次のステップの準備として、行列をもう一つ用意しておきましょう。
#matrix with matrix
test_m4 <- matrix(1:9, nrow=3, ncol=3)
test_m4
行列同士、行列とベクトルの演算
最後に一番複雑な計算を確認します。まずは、行列同士の足し算は、Rでも数学でも要素ごとの足し算になります(ルール省略)。
test_m3 + test_m4
Rでのベクトル同士の掛け算のルールが、数学でのルールとは違って要素ごとの掛け算になっているのと同じように、行列同士の掛け算も要素ごとの掛け算になります。
test_m3*test_m4
次に数学で通常行う行列同士の掛け算(内積)ですが、以下の解説サイトをちょっと読んでみてください。
https://atarimae.biz/archives/23930
ポイントは、行列同士の内積は行列になるということです。別の言い方をすると、行列を別の行列に変換する計算が、行列同士の掛け算だとも言えます。計算方法を確認したら、ベクトル通しの内積と同じようにRでは"%*%"という演算子を使って計算してみましょう。
test_m3%*%test_m4
数学での定義通りに、1行目1列目の要素の計算をしてみれば、上の演算が内積になっているのを確認できます。数学での定義も下に載せておきます。
test_m3[1,1]*test_m4[1,1] + test_m3[1,2]*test_m4[2,1] + test_m3[1,3]*test_m4[3,1]
要素ごとの掛け算との相違点として、かける順番で結果は異なるということです。意味としては、test_m3%*%test_m4は、行列test_m4を行列test_m3を使って別の行列に変換することになるのですが、それを逆にしてtest_m4%*%test_m3とするとtest_m3を別の行列に変換することになるからです。
test_m4%*%test_m3
同じように、ベクトルに行列をかけることもできます(よく使います)。ベクトルに左から行列をかけると、別のベクトルに変換されます(向きと大きさが変わるということです)。
この計算は、以下のようにできます。
test_m3%*%test_v2
ちなみに行列とベクトルの掛け算(内積)は、行列の列の数とベクトルの次元が一致していないとエラーとなります。
たとえば、ベクトルの一つ目と二つ目の要素しか使わないとうまくいきません。実際にやってみましょう。
test_v2[1:2]
test_m3%*%test_v2[1:2] #incompatibility of the dimension
演習1
直前の例で、ベクトルに左から行列をかけると別のベクトルになるという演算を見ました。さらに何回も同じ行列をかけると、ベクトルの向きと大きさが次々と変化していくことを表現できます。そこで、for loopを使って、任意の3次元ベクトルv、任意の3X3の行列M、任意の自然数nをパラメータとして、ベクトルにn回行列をかけた結果を返す関数を作ってみましょう。そして実際に、適当なベクトルと行列を代入して計算がうまくいくか試してみましょう。
数学的には以下のような計算をするということです(注意:数学の表現として行列のn乗は内積をn回繰り返すことですが、R上でM^nとしてはいけません。それでは行列の要素ごとにn乗されるだけです)。
演習2
演習1に似ていますが、任意の2次元ベクトルv, 任意の2x2の行列M、任意の自然数nを用いて、ベクトルに行列をかけるごとに更新されたベクトルをデータフレームに追加で保存していき、n回の変換の軌跡を簡単な散布図で可視化してみましょう。関数にする必要はありません。
ヒント
まずベクトル(numeric)を作って、そこから結果格納用のデータフレームを作る
vN <- c(1,1)
result_data <- data.frame(x = vN[1], y = vN[2])
ベクトルと行列の内積をとると「ベクトル」は明示的に"array"クラスになってしまうので、結果格納用のデータフレーム追加していくには、numericに変換が必要。
result_data <- rbind(result_data, as.numeric(vN))
以下のような回転成分(cos, sin)と拡大(1.2*)を含む行列に対して実行すると面白い。
L_m <- matrix(nrow = 2, ncol=2)
L_m[1,1] <- 1.2*cos(0.6)
L_m[1,2] <- -sin(0.6)
L_m[2,1] <- sin(0.6)
L_m[2,2] <- cos(0.6)
L_m
plot()関数で単純な散布図が描けるが、オプションでtype="b"を指定すると以下の図のように点と線を両方描いてくれる。