行列とは次元を持つベクトル
Rの行列は値の長方形——行と列——で、すべてのセルが同じ型、たいていは数値を保持します。内部的には、文字どおり dim 属性を貼り付けたベクトルであり、その挙動のほとんどはここから説明できます。全体で1つの型、どこでもベクトル化された演算、というわけです。
作るには matrix() でベクトルを整形します。
値が6つで行が2つなので、Rは列が3つ必要だと判断します。dim() は両方の次元を 2 3 として一度に報告し、nrow() と ncol() は個別に返します。
表示された行列をよく見てください。値は最初の列を 1 2 と下り、次の列を 3 4 と下っています。Rは既定で行列を列ごとに埋めます。 データが行ごとに読めるかたち——人間が普通に書くかたち——なら、byrow = TRUE でそう伝えます。
これで最初の行は 1 2 3 になります。byrow = TRUE を忘れてもエラーにはならず、同じ数値が転置されたような配置で静かに返ってくるだけです。生の値から行列を作るときは、表示されたレイアウトを確認する習慣をつけましょう。
添字アクセス:m[行, 列]
行列の添字アクセスは、1組の角かっこの中に2つの位置を取ります。カンマの前が行、後が列で、どちらも1から数えます。
位置を空にすると「すべて」を意味します。m[1, ] は最初の行全体、m[, 2] は2番目の列全体です。どちらも素のベクトルとして返ってくることに注意してください。Rは大きさ1につぶれた次元を落とすのです。対話的には便利ですが、コードの中では罠になります。行列を期待する関数がそのベクトルでつまずくからです。形を保つように頼むには drop = FALSE を使います。
dim() は 1 3 と報告し、依然として行列のままです。関数の中で1行や1列を切り出すときは、必ず drop = FALSE と書きましょう。これが防いでくれるバグ(幅の広いデータでは動くのに、1列のデータで壊れるコード)は、追跡するのが実に厄介です。
論理マスクもここで使えます。m[m > 3] は3より大きいすべてのセルをベクトルとして返します。
cbind() と rbind() による組み立て
1本の長いベクトルを整形する代わりに、部品から行列を組み立てることもできます。cbind() はベクトルを列として、rbind() は行として結合します。同じ関数で既存の行列を拡張することもできます。
名前はベクトルと一緒に運ばれます。cbind() は heights と weights を自動的に列名として使い、表示された行列を読みやすく保ちます。どちらの関数も長さが揃っていることを要求します(長さ1の値はリサイクルされます)。4行の行列に長さ3のベクトルを結合しようとすると警告が出ます。
列名と行名は colnames(m) <- ... や rownames(m) <- ... で直接設定でき、その後は名前で添字アクセスできます(people[, "weights"])。
要素ごとの * と本物の行列積 %*%
この記事全体で最も重要な区別がこれです。Rには行列に対する掛け算の演算子が2つあり、まったく違うものを計算します。
a * aは要素ごとです。各セルが対応するセルと掛けられます。1 2 3 4は1 4 9 16になり、同じ形に並びます。これはただのベクトル化された算術で、ベクトルに使うのと同じ*です。a %*% aは線形代数の行列積です。結果の各セルは、1つ目の行列の行と2つ目の行列の列を掛けて合計したものです。同じ入力から7 10 15 22が得られます。まったく別の数値です。
コードを実行して2つの出力を並べて見比べてみてください。同じ入力に対して結果が違うのを目にすることが、この区別を定着させます。数学的に %*% が必要な場所で * と書いても、Rは警告してくれません。正方行列ならどちらでも形は整合するからです。ただ誤った数値が出るだけです。統計のコード(共分散行列、線形モデルの代数)では、これが典型的な静かなバグのひとつです。
t() は転置——行と列を入れ替える——を行い、行列積は内側の次元が一致する必要があるため、%*% の隣に絶えず登場します。
補足として、solve(m) は行列の逆行列を求め、%*% にベクトルを渡すとそれを1列の行列として扱います。ほとんどのデータ処理で必要になるのはこの程度までです。
行と列の要約
行や列にわたる合計や平均はあまりに頻出するので、Rは専用の高速な関数を用意しています。
rowSums() は各行を1つの数値につぶし(ここでは 6 15)、colSums() は各列をつぶし(5 7 9)、Means 版は代わりに平均を取ります。手書きのループはもちろん、apply(m, 1, sum) よりもこれらを選びましょう。明快で、しかも高速です。この4つでカバーできない要約(列ごとの最大値など)には、汎用の道具である apply系を使います(apply(m, 2, max))。
行列とデータフレーム、どちらを使う?
どちらも長方形ですが、どう選べばよいでしょうか。
- 行列:すべてのセルが同じ型で、計算が重要な場合。数値計算、線形代数、距離の計算、画像のようなグリッド。行列が軽量で演算が速いのは、まさにこの「1つの型」という保証があるからです。
- データフレーム:異なる型の列が並ぶ場合。名前の隣に年齢、その隣に論理フラグ、といった具合です。これが現実の表形式データで、ほぼすべてのデータ分析関数が期待するものでもあります。
よい目安があります。名前付きで型の混ざった列を持ち、表計算ソフトで開くのが自然なものならデータフレーム。代数を行うつもりの数値のグリッドなら行列です。相互変換は簡単ですが(as.matrix()、as.data.frame())、テキストの列を1つでも含むデータフレームに as.matrix() を使うとすべてが文字列に強制されるので、数値の列だけを変換しましょう。
この記事のまとめ
- 行列とは次元を持つベクトルです。全体で1つの型、
matrix(data, nrow, ncol)で作成し、byrow = TRUEを渡さない限り列ごとに埋まります。 - 添字アクセスは
m[row, col]。位置を空にすると「すべて」。コードの中で1行や1列を切り出すときはdrop = FALSEを加えましょう。 cbind()とrbind()はベクトルから行列を組み立て、既存の行列を拡張します。*は要素ごと、%*%は本物の行列積です。同じ入力、違う答え、警告なし。rowSums()/colSums()/rowMeans()/colMeans()が日常的な要約を担当します。
次は因子(factor)です。Rがカテゴリカルデータをどう表現するか、そしてそれに伴う落とし穴を扱います。
よくある質問
Rで行列はどう作りますか?
matrix(1:6, nrow = 2) はベクトルを2行3列に整形し、列ごとに埋めていきます。行ごとに埋めたいときは byrow = TRUE を加えます。ベクトルから組み立てることもでき、cbind() は列として、rbind() は行としてつなぎ合わせます。
Rの * と %*% の違いは何ですか?
* は要素ごとの掛け算で、各セルと対応するセルを掛けます。形が揃っている必要があります。%*% は線形代数の本物の行列積で(行×列なので内側の次元が一致する必要があります)、同じ行列に対してまったく異なる結果を返します。%*% のつもりで * を使うのは、典型的な静かなバグです。
Rで行列の1行や1列を取り出すには?
もう一方の位置を空にします。m[1, ] が最初の行、m[, 2] が2番目の列です。既定ではどちらも素のベクトルとして返ってきます。m[1, , drop = FALSE] のように drop = FALSE を加えると、1行または1列の行列のまま保てます。後続のコードが2次元を期待している場合に重要です。