猫も杓子も構造化

発達障害、特別支援などについて書いています。最近は心理学関係の内容が多めです。

Rでの大規模データ処理:ffパッケージを試してみる

Rは大変便利な言語なのだけれど、大規模なデータの扱いは苦手と言われている。その理由は、データをメモリに全部載せて作業するので、メモリの量によって扱える操作が制限されるとういもの。最近やや大規模なデータを扱う必要があってその処理の仕方を勉強しているのでその覚書。

ffパッケージ

大規模データを扱うためのパッケージにffというものがある。このスライドThe ff packageとかこのスライド Managing large datasets in R – ff examples and conceptsを参考に試してみる。日本語で書かれているものでff packageの使い方も大変参考になった。

ffはメモリに全部保存するかわりに、データそのものはストレージにおいておき必要なときに必要な分だけ出せるようにする仕組みらしい。

ちなみに、Rは整数だと2^{31}-1までのデータしか表示できない。次のように打つと華麗にエラーを吐き出す。

> as.integer(2^31-1)
[1] 2147483647
> as.integer(2^31)
[1] NA
 警告メッセージ:
NAs introduced by coercion to integer range

ffはこの問題も解決してくれるらしい。

ffの仕組み

ffのパッケージは、データの入れものとして機能する新しいオブジェクトの型が導入されるようである。ffC++で書かれる層とRで書かれる層の2層からなっているそうだ。

Rの層では普段のベクトルやデータフレームと同じ要領で操作ができるようなので色々と操作をしてみる。ff() という関数にlengthを指定してやればベクトルが生成できるらしい。いくつか作ってみる。

# ベクトルの生成
library(ff)
a <- ff(0, length=1)  # 何も指定しないとデータの最初をみて型を判断
b <- ff(0, length=1, vmode="integer") # vmodeで型の明示も可能
c  <- ff(2^31, length=1)  #大きいデータも扱える
d <- ff(1:1000, length=1000) # ベクトルだってOK
e <- ff(1:1e+8, length=1e+8) # 長さ一億のベクトルだってOK

f:id:nekomosyakushimo:20180708151329p:plain

ベクトルへの数値の代入

数値の代入は普通のRと同じようにできる。例えば、先ほどのaというオブジェクトに数字を代入したければ次のようにすれば良い。

a[1] <- 9999

基本的にobject名[]を使って中の要素にアクセスできるのだけれど不用意に大規模データの中身にアクセスしようとする大量のベクトルデータが返ってきて酷い目に合う。

サイズの違い

ffのオブジェクトは、中に大きなデータが入っていてもRの上では必要な分だけデータをとってくるので、オブジェクトのサイズが大きく違う。簡単な数値で実験してみると違いが分かる。

# Rで一様分布
g <- double(10000000)
g[] <- runif(10000000)

# ffで一様分布
h<- ff(vmode="double", length=10000000)
for (i in chunk(h)) h[i] <- runif(sum(i))

print(object.size(g),units="auto")  # 76.3 Mb
print(object.size(h),units="auto")  # 3.1Kb

試しに一様乱数で1,000,000のデータを発生させてみたのだが、普通のRのオブジェクトだと76.3 Mbなのに対して、なんとffオブジェクトは3.1Kbである。ffで生成されたオブジェクトは基本的に必要なときに必要なデータを持ってくるので、実質的に入っているデータがこの10倍になろうがR上では変わらない。

 ファイルの場所

Rのオンメモリのファイルサイズが小さいといってもデータは存在している訳で、デフォルトだと/tmp/Rtmp******とテンポラリーフォルダ内に.ffという拡張子のファイルが生成されている。

オブジェクトの本物のファイルがどこに保存されているかをを確認したいときには、getOption("fftempdir")を用いるとファイルが生成されるフォルダを返す。ためしいにいくつかffオブジェクトを作成すると、その中に.ffの拡張子のファイルが生成されるのが確認できる。

フォルダ内のファイルを確認するとファイルの中身によって大きさが違っており、たとえばさきほど作った長さ1のベクトルが8KBなのに対して、長さ1億のベクトルは400MBのファイルサイズであった。

rm()を用いてr上でffオブジェクトを消してもこの.ffの本体のファイルが消える訳ではないらしい。本体のファルを消したいときにはdelete.ff()と言う関数を用いると関数が開けるかどうかを真偽値で返して、TRUEなのであればそのファイルを削除してくれる。ちなみに、生成した.ffのファイル本体をFinder上で消してからdelete.ff()を実行すると次のようなエラーを返すため、ファイルが開ける場合にのみ削除が実行されるようである。

f:id:nekomosyakushimo:20180708151410p:plain

ファイルの保存場所を指定したいときはoptions(fftempdir="")で指定する。たとえば、options(fftempdir=paste(getwd(),"/fftemp", sep=""))あたりで指定すると、現在の作業ディレクトリの下のフォルダにできたりもする。

行列で実験

ベクトルだけでなく行列も作ってみる。基本的にdimを指定してやると行列になるっぽい。

# 行列の作成
i <- ff(1:12, dim=c(3,4))
t(i) # 転置もできるぞ
j <- ff(0, dim=c(10000000,100)) # 大きい行列だってOK

データフレームの作成

データフレームはffdf()という関数でffのベクトルをくっつけるとできるようだ。

# データフレームの作成
m <- ff(0, vmode="integer",length=100)
n <- ff(0, vmode="double", length=100)
o <- ffdf(m,n)
colnames(o) <- c("A", "B")  # 列名をつけるのもOK

f:id:nekomosyakushimo:20180708151429p:plain

データの読み込みと書き出し

ffdfオブジェクトでデータフレームの読み書きにはwrite.csv.ffdf()read.csv.ffdf()という関数を使うと良いようだ(csvで扱う場合)。

# データフレームの読み書き
write.csv.ffdf(o, file="ff_sample.csv")
p <- read.csv.ffdf(file="ff_sample.csv", fileEncoding="utf-8", header=TRUE)

ここら辺もRで普段データフレームを扱っているのと同じ要領で処理できる。

とりあえずここら辺までで、また色々と調べたら書き足したい。