Understanding How cbind/rbind works in R

Rを使うようになってきた最大の動機はR自身で柔軟にデータを作成できることにあります。例えば、パッケージユーザーのための機械学習(1):決定木 - 銀座で働くデータサイエンティストのブログでは以下のようなXORパターンのデータを用いて決定木の解説を行っています。

「少しランダムが入った市松模様」とでもいうのでしょうか。上述のものは「シンプルなXORパターン」と称されていました。少し点群の数を増やすと市松模様を基調としている様子がはっきりしてきます。

自分も是非、こういうデータを短時間でゴリゴリ作れるようになりたいと思いました。先のエントリでは、ありがたいことにデータを作成した際のRの実装を掲載してくださっていました。

# シンプルなXORパターン
> p11<-cbind(rnorm(n=25,mean=1,sd=0.5),rnorm(n=25,mean=1,sd=0.5))
> p12<-cbind(rnorm(n=25,mean=-1,sd=0.5),rnorm(n=25,mean=1,sd=0.5))
> p13<-cbind(rnorm(n=25,mean=-1,sd=0.5),rnorm(n=25,mean=-1,sd=0.5))
> p14<-cbind(rnorm(n=25,mean=1,sd=0.5),rnorm(n=25,mean=-1,sd=0.5))
> t<-as.factor(c(rep(0,50),rep(1,50)))
> d1<-as.data.frame(cbind(rbind(p11,p13,p12,p14),t))
> names(d1)<-c("x","y","label")

...これで出来るのか、という印象。初見では何をやっているか全く検討もつきませんでした。

部分的に実行してコードリーディングを行った結果、cbindとrbindという関数が何回も登場することに気付きました。

> help(cbind)
Combine R Objects by Rows or Columns

Description:

     Take a sequence of vector, matrix or data frames arguments and
     combine by _c_olumns or _r_ows, respectively.  These are generic
     functions with methods for other R classes.

Usage:

     cbind(..., deparse.level = 1)
     rbind(..., deparse.level = 1)

                                   :

なるほど、cbindもrbindもベクトルや行列を結合する関数のようです。cとrはそれぞれ、ColumnとRowを表すとのこと。要素数の少ないベクトルや行列で実験を行った結果、なかなか一貫性のある設計であることに気付きました。


まずベクトルのcbindとrbindから考えてみることにします。ベクトルaとbを以下のように置きます。

Rでもベクトルを定義しましょう。

> a = c(1, 2, 3)
> b = c(4, 5, 6)
> a
[1] 1 2 3
> b
[1] 4 5 6

これらのベクトルをcbindしたものは行列となります。これをMcとします。Mcは以下のような、2つのベクトルを結合した行列となります。


> cbind(a, b)
     a b
[1,] 1 4
[2,] 2 5
[3,] 3 6

同様に、ベクトルをrbindしたものをMrとします。Mrの結果は以下のようになりました。


> rbind(a, b)
  [,1] [,2] [,3]
a    1    2    3
b    4    5    6

次に行列のcbind/rbindを考えてみましょう。以下のような行列SとTを考えます。

Rでも行列を定義しましょう。

> S = matrix(c(1,  4, 2,  5, 3,  6), 2, 3)
> T = matrix(c(7, 10, 8, 11, 9, 12), 2, 3)
> S
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
> T
     [,1] [,2] [,3]
[1,]    7    8    9
[2,]   10   11   12

ベクトルの場合と同様に、行列をcbindしたものをMcとします。Mcは以下のような行列となります。


> cbind(S, T)
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    2    3    7    8    9
[2,]    4    5    6   10   11   12

行列をrbindしたものをMrとします。Mrは以下のような行列となります。


> rbind(S, T)
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6
[3,]    7    8    9
[4,]   10   11   12

さて、どうでしょう? とても一貫していますね。引数がベクトルである場合でも行列である場合でもcbind(a, b)は以下のような行列を作成するのです。

同様にrbind(a, b)は以下のような行列を作成します。

ベクトルのcbind/rbindには少し注意が必要です - ベクトルが列ベクトルとして扱われるか行ベクトルとして扱われるかは関数によって異なります。また、cbindで以下のように合成することは出来ません(このような結合を行うにはc(a, b)とします)。


さて、ここまでのcbind/rbindの知識を元に、今一度XORパターンの実装を読んでみましょう(再掲)。

# シンプルなXORパターン
> p11<-cbind(rnorm(n=25,mean=1,sd=0.5),rnorm(n=25,mean=1,sd=0.5))
> p12<-cbind(rnorm(n=25,mean=-1,sd=0.5),rnorm(n=25,mean=1,sd=0.5))
> p13<-cbind(rnorm(n=25,mean=-1,sd=0.5),rnorm(n=25,mean=-1,sd=0.5))
> p14<-cbind(rnorm(n=25,mean=1,sd=0.5),rnorm(n=25,mean=-1,sd=0.5))
> t<-as.factor(c(rep(0,50),rep(1,50)))
> d1<-as.data.frame(cbind(rbind(p11,p13,p12,p14),t))
> names(d1)<-c("x","y","label")

さて、もう読めるようになりましたね...と簡単に行けばよいのですが、やはりまだ難解ですね。

この実装では結合した行列から最終的にデータフレームを生成していますが、行列を作成することを目標としてみましょう。as.data.frame(...)の中身を作成するところまで、と考えてください。

少し分解しましょう。まず、p11〜p14までのデータを考えてみます。これらはX座標、Y座標共に標準偏差0.5の正規分布に従ったランダムな点群です。X座標、Y座標のそれぞれのベクトルとして用意し、cbindを用いて合成をしています。以下のようなイメージです。

meanを変えることで分布の中心を変えながら、p12、p13、p14を同様に作成します。二つ目の添字が象限を表しています。例えば、p13は第三象限に分布した点群です。そのため、ほとんどの値は負の値となります。

さて、ベクトルを合成した結果は行列となるのでした。これをrbindで合成します。市松模様とするために少し工夫を施し、p11、p13、p12、p14の順番で合成しましょう。

これとは別に0と1のラベルからなるベクトルを作成します。先ほど少し触れましたが、ベクトルとベクトルを直列に合成するにはc()を用います。

最後にcbindを用いて行列とベクトルを合成します。

これで目的の行列を作成することができました。データフレームに変換しなくても行列の段階でプロットすることも可能です。

plot(M[,1:2], col=M[,3], pch=16, xlab='x', ylab='y')

まとめ


ベクトルや行列等を合成して新たな行列を作成する場合はcbind/rbindを使うと便利です。Rにはまだ慣れていないためどんな場合でもデータフレームにまとめようとする傾向にありますが、今後はcbind/rbindをどんどん活用していきたいと考えています。

もちろん好みによるとは思いますが、個人的にはmatrixを使うよりもrbindを使ったほうが直感的に行列が記述出来るよう感じています。

> S = rbind(c(1, 2, 3), c(4, 5, 6))
> S
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    4    5    6

以下にmatrixを使った行列の作成を再掲します。是非比較してみてください。

> S = matrix(c(1, 4, 2, 5, 3, 6), 2, 3)