Rで140万行のリストから10万行をルックアップするのに30分かかる

今回の話は、Rで140万行のデータフレームを扱ってみたけど、処理が遅くて困ってる、というか処理が遅いのかもよくわからない、という話をしているだけで問題を解決していません。進展があったら追記します。

JVDataにはレコード種別H1というレース毎の単勝、複勝などの投票数を記録したレコードがあります。私のデータベースソフトウェアJVData To SQLiteではこのH1レコードをもとに票数単勝テーブル、票数複勝テーブルなど投票種類毎のテーブルに分けてSQLiteデータベースにデータを追加する仕組みになっています。票数単勝テーブルは次のような感じです。

票数単勝テーブルの一部

テーブルの列は、レースの固有番号であるレースID(レースの開催年月日、馬場コードなどの文字列)、馬番、票数があります。

このテーブルを参照すれば、1986年から現在までにJRAで行われたレースの投票数がわかります。

試しにレース毎に票数を合計してみようとしました。実はJVDataにはレース毎の合計票数のデータもあるのですが、こういうデータ処理はよく使うはずなので試しにやってみようと思ったのです。

まず、必要なデータセットを得るにはRで次のようなコードを作りました。

# 必要ならinstall.packages("RSQLite")
library(RSQLite)
connection <- dbConnect(SQLite(), "C:\\home\\JVDataToSQLite\\setup.sqlite3", synchronous="off")
DataSource <- dbGetQuery(connection, "SELECT レース.レースID AS RaceID, レース.発走日時 AS RaceTime, 票数単勝.馬番 AS HorseNumber, 票数単勝.票数 AS VotesCount FROM レース JOIN 票数単勝 ON レース.レースID = 票数単勝.レースID WHERE レース.データ区分 == '7' AND レース.開催年 >= 1986;")

読み込んだデータフレームをRStudioで見ると次のようなものです。

単勝票数のデータセット

テーブルの列はレースID、レースの発送日時、馬番、単勝票数です。行数は約143万。レースは全部でいくつあるかというと、

s <- unique(DataSource$RaceID)
length(s)
## 108481

10万8000あります。

このデータフレームでレースID毎に単勝票数を合計すれば、レース毎の合計単勝票数が得られます。Rのプログラムは次のようにしました。

SumVotesCount <- function(RaceID) {
  sum(DataSource$VotesCount[DataSource$RaceID == RaceID])
}
sapply(s, SumVotesCount))

レースIDを引数にデータフレーム内の一致する行を合計して返す関数SumVotesCount()に、sapplyでレースIDの集合sを適用しています。

プログラムの実行結果はそれらしいので良いのですが、プログラムの実行に30分かかりました(Intel Core i7 / 16GB RAM)。時間かかりすぎですよね??Rで140万行のデータフレームを扱ったことないのでよくわからないのですが。

sum(DataSource$RaceID)とデータフレームの1列丸々を合計するのは一瞬で終わるので、このプログラムのホットスポットは[item == value]しか考えられません。

もしかしなくとも[item == value]をコールする度にデータフレームのソートが行われているのかもと思い、データフレームのインデックス化ができないか調べていたら、データフレームの高機能版であるデータテーブル(data.tableパッケージ)を見つけて、それを試してみました。

# 必要ならinstall.packages("data.table")
library(data.table)
dt <- as.data.table(DataSource)
setkey(dt, RaceID)

data.tableをロードして、setkey()でキーをレースIDにセットしました。これでdata.tableパッケージはレースIDでソートしたデータセットに対して検索を行うようです。

でも、この後、さっきと同様のプログラムを実行すると実行時間は20分になり、10分短縮されたのですが、まだ遅いような感じです。

R fast single item lookup from list vs data.table vs hash - Stack OverflowにハッシュテーブルやEnvironmentオブジェクトを使うとルックアップ(データ参照)が2桁速くなると書いてあります。ただ、このケースは、1行に1キーを設定していて、キーにマッチする唯一の行ベクトルを抽出するのが速いということです。

100万行のテーブルの1行1行に固有の参照キーを与えるなら、参照が速いのは当たり前な感じなんですけど、なんで、ただのリスト、データテーブル、ハッシュ、Environmentオブジェクトで実行時間に大きな差がでるのかよくわかりません。

そもそも、今回のように140万行のテーブルの中に11万個のレース(11万個のキー)があって、レース毎の行ベクトルを抽出する処理を数秒で終わらすことができるのかわかりません。list2env()でデータフレームをEnvironmentオブジェクトに変換してみたのですが、使い方がまだよくわりません。

Kosuke Maeda / まえだこうすけ

「機械学習で競馬予想して勝てるのか?」をテーマに活動中! QiitaにはR、VBAなどのTipsを投稿しています。