行列屋さんの作業ログ

行列まわりで色々やってたエンジニアの作業メモ&国内外旅行記ブログ

dplyrでデータの集計を行う

大量(1億件くらい)のデータを集計する必要が生じた。
ということで今回は、Rであるラベル(複数)のデータがいくつ存在しているのか高速に集計するお話。
アンケートを取って、下のような結果が得られたとする。

adress <- sample(c('千葉','滋賀','佐賀'),size = 1000,replace = TRUE)
sex <- sample(c('男','女'),size = 1000,replace = TRUE)
work <- sample(c('学生','社会人'),size = 1000,replace = TRUE)

data <- data.frame(adress=adress,sex=sex,work=work)

> head(data)
  adress sex   work

1   千葉  女 社会人

2   佐賀  女   学生

3   千葉  女 社会人

4   滋賀  男   学生

5   佐賀  女 社会人

6   佐賀  女 社会人

各県の居住者が何人居るのかは、table関数を使えば判る。

> table(data$adress)
佐賀 千葉 滋賀
 337  357  306

じゃあ、居住地が◯で、性別が☓で、職業が△な人が何人居るのかカウントするにはどうすれば良いのだろう。
それも出来れば高速で。1億件近いデータを処理せねばならんのだ。

こうして悩んでいた所、先輩からdplyrパッケージというのを教わる。
このパッケージの使い方は、このサイトに非常によく纏まっていた。

plyr — データ分割-関数適用-再結合を効率的に — Watallica metallicus

前半はplyrという別のパッケージの話。こっちは処理速度遅め。
(内部でapply系関数を回してるという噂)

CRANからいつものようにインストール。
libraryで読み込んだら、group_by関数とsummarise関数を使う。

> g <- group_by(data,adress,sex,work)> summarise(g,n())
Source: local data frame [12 x 4]

Groups: adress, sex



   adress sex   work n()

1    佐賀  女   学生  84

2    佐賀  女 社会人  79

3    佐賀  男   学生  77

4    佐賀  男 社会人  92

5    千葉  女   学生  91

6    千葉  女 社会人  84

7    千葉  男   学生  86

8    千葉  男 社会人  82

9    滋賀  女   学生  89

10   滋賀  女 社会人  75

11   滋賀  男   学生  81

12   滋賀  男 社会人  80

group_byではdataにつづいて、集計対象の変数名(列名)を記述する。
そしてグルーピングの結果をsummarise関数に渡し、グループ毎にカウントをん()でやってあげる。

このパッケージの良い所は、物凄い高速に処理を行えるところ。
実際に1億件のデータを計算機上で処理させてみたけれど、数秒で終わってしまった。
このパッケージつかえば、既存のスクリプトもかなり高速化出来そうである、

Rで並列計算 パラメータを変えながら計算を回す

行列分解の一種、CUR分解をパラメータを色々変えながら実行してみようと思った。

パラメータの組み合わせが500通り位あるので、せっかくだから並列化してみる。

ということでサンプルコードを書く。

2つの入力パラメータの積を返す、myprodという関数を定義した。

myprod <- function(a,b){
 return(a*b)
 }

この関数を、並列に回すのが今回の目標。もちろん、それぞれ別のパラメータを持たせながら。

使うのはsnowパッケージ(と、パラメータの組み合わせを作るためのreshapeパッケージ)

library(snow)
library(reshape)

パラメータの組み合わせを作る。

param1 <- 1:10
param2 <- 11:20
param <- expand.grid(param1,param2)

こんなかんじの組み合わせ100つ

> param
    Var1 Var2
1      1   11
2      2   11
3      3   11
4      4   11
(以下略)

普通に計算するなら下のようにする。結果はリストでまとまってくる。

lapply(1:dim(param)[1],function(i){
        myprod(param[i,1],param[i,2])
})

並列計算するなら、下のようにする。
まずは並列計算用のクラスター作成。今回は4コア。

hosts <- rep('localhost',4)
scl <- makeCluster(hosts, "SOCK")

次に、並列計算に使用するオブジェクトをエクスポートしてやる。今回はmyprodとparamをエクスポート。

clusterExport(scl,c('myprod','param'))

以上で準備完了。
parLapply(lapply関数の並列版)を使って計算する。

result_parallel <- parLapply(cl = scl,x = 1:dim(param)[1],fun = function(i){
        myprod(param[i,1],param[i,2])
})

計算自体はこれで完了。
計算が終わったら、使用したクラスタを止めてあげる。

stopCluster(scl)

並列計算の結果は、シングルコアで処理した時のともちろん同じ。

> identical(result_single,result_parallel)
[1] TRUE

さて、気になる計算時間(proc.timeを基に計算)は、
シングルが0.005s
並列時は、クラスタ生成から停止までで0.989s…うん遅い。
parLapplyにかかった時間は0.005s。この程度じゃ差が生まれなかった。

じゃあ、1000*1000の10000通りなら?
シングルなら80.051s
並列なら、クラスタ生成から停止まで30.489s
parLapplyだけなら25.305s

無事早くなってくれた。

並列化する方法はsnowパッケージで出来るものだけでも沢山あるし、他のパッケージとしてdoMCやffもある。
今回は、既存コードをからの変更点が一番少なさそうだったからこれを使ったけれど、1から作るなら別に良い方法があるかも。