apply系関数でデータフレームを返したいときは plyr パッケージが便利

この話の続き。
「Rを使ってYahoo!乗換案内から運賃や所要時間,乗換回数を取得するコード書いた」をscrapeRで書いてみたよ - XXXannex
前のエントリーに追記してもよかったのですが、内容的に独立したエントリーに分けた方がよさそう。

listから変換

applyから返ってきた結果をいい感じにデータフレームにしたい、という話について、コメントでアドバイスをいただきましたので試してみました。

実際にスクレイピングする必要はないのでコードは簡略化してます。

#出発駅と到着駅ベクトル
station1 <- c("渋谷","表参道", "外苑前")
station2 <- c("品川","大崎", "五反田")

transit.search <- function(from, to){
    Cost.v     <- 1
    Transfer.v <- 2
    Time.in    <- 3
    Time.out   <- 4
    list(Origin=from, Destination=to, Cost=Cost.v, Timein=Time.in, Timeout=Time.out, Transfer=Transfer.v)
}
d <- data.frame(t(mapply(transit.search, station1, station2)))
rownames(d) <- seq(nrow(d))

listで返して、t()で縦横を入れ替えて、データフレームにします。

> d
  Origin Destination Cost Timein Timeout Transfer
1   渋谷        品川    1      3       4        2
2 表参道        大崎    1      3       4        2
3 外苑前      五反田    1      3       4        2
>

うまくできた!文字列をread.tableするとかヘンなことをしなくてもできるんですね。

plyr パッケージを使う

で、本題なのですが。うまく行ったとはいえ、もう少し直感的にできたらいいなーと思って色々調べてみると、どうやらplyrパッケージの中にそういうモノがあるようです。

http://had.co.nz/plyr/plyr-intro-090510.pdf

今回の目的には、mdply()がピッタリのようです。mdply()を使って書き直すとこんな感じ。

library(plyr)

#出発駅と到着駅ベクトル
Origin      <- c("渋谷","表参道", "外苑前")
Destination <- c("品川","大崎", "五反田")

transit.search <- function(Origin, Destination){
    Cost     <- "1"
    Transfer <- "2"
    Timein   <- "3"
    Timeout  <- "4"
    data.frame(Origin, Destination, Cost, Timein, Timeout, Transfer)
}

mdply(data.frame(Origin, Destination), transit.search)

実行結果。

  Origin Destination Cost Timein Timeout Transfer
1   渋谷        品川    1      3       4        2
2 表参道        大崎    1      3       4        2
3 外苑前      五反田    1      3       4        2

完璧じゃないですか・・・。全く無駄のないコード。


しかしこの関数、使い勝手が独特で、どうやって使うのか小一時間悩んでしまいました。サンプル見てもよく分からないし・・・。

まず引数。

> args(mdply)
function (.data, .fun = NULL, ..., .progress = "none") 

"The m*ply functions are the plyr version of mapply" などとマニュアルには書いてあるくせに、入力するデータが1つしかない!なんだこれ!

仕方ないのでマニュアルとコードを見ながら調べていった結果、ざっくりまとめると「第2引数で指定した関数の引数に、第1引数のデータフレームをwithした名前を指定する」ということらしい。

例えばこういうデータに何かしたい時は、"Origin"、"Destination" という名前の引数を持つ関数を定義してやる。

> data.frame(Origin, Destination)
  Origin Destination
1   渋谷        品川
2 表参道        大崎
3 外苑前      五反田

うーん、まあ、言われてみれば確かに直感的なんだけど。直感的すぎて不気味というか、何となく黒魔術っぽいなーと思ってしまう。変数の名前とデータの値の区別が曖昧なところがそう感じさせるのかなあ。

なんにしても、やっぱRすごい。ここまで出来るんだったら、他のLLじゃなくてRでスクレイピングするメリットがかなり出てくる気がする。