readrでファイル読み込み高速化

みんな大好きHadley氏作ったreadrが0.1.0になったようなので、早速試してみました。
今回はやったのは、使い方の確認と簡単な速度比較です。

まずは、readrの紹介から。

## readrとは
表形式のデータを高速に、そして簡単に読み込むことを目的としたRのpackage。
一応、ファイル書き込み用の関数もあります。

### 使い方
読み込み用の関数は幾つかありますが、代表的なread_delimを説明します。
read_csvやread_tsv等は、read_delimのパラメータが設定されているラッパーです。

```r:構文
read_delim(file, 
           delim, 
           quote = '\"', 
           escape_backslash = TRUE,
           escape_double = FALSE, 
           na = "NA", 
           col_names = TRUE, 
           col_types = NULL,
           skip = 0, 
           n_max = -1, 
           progress = interactive())
```

入力パラメータの説明です。

|パラメータ|デフォルト値|解説|
|:---|:---|:---|
|file||ファイルパス、コネクション、リテラルデータを指定できます。<br/> gz、bz2、xz、zipは自動解凍されます<br/> http:// 、https://、ftp://、ftps://はdownloadします <br/> d|
|delim||区切り文字|
|quote|\"|quote string|
|escape_backslash|TRUE||
|escape_double|FALSE||
|na|NA|値がない場合にいれる文字|
|col_names|TRUE|以下の3つの値を取れます。<br/>TRUE:ヘッダー行を列名とする。<br/>FALSE:順に番号振ります。<br/>列名に使用するvector|
|col_types|NULL|デフォルトの列クラスを上書きする。後で詳細に解説します。|
|skip|0|スキップする行数を指定|
|n_max|-1|読み込む最大行を指定|
|progress|interactive()|推定ロード時間が5秒以上の場合はプログレスバーを表示する。<br/>FALSEを指定すると非表示。|

### 標準のread.delimで生成されるデータフレームとの違い
* 文字列をfactorに自動変換しない。（stringsAsFactors = FALSEが常に指定されている感じ）
* 有効な列名かどうかチェックしてない。`で列を囲めば使える。
* classが"tbl_df"、"tbl"、"data.frame"が設定され、dplyrで扱う場合にちょっと便利。
* 行名がセットされない

### Column types
col_typesを指定しない場合は、最初の100行を読んで、列の型を推測します。
推測される型は以下の通り。

|列型|省略形|説明|
|:---|:---|:---|
|col_logical()|l|contains only T, F, TRUE or FALSE.|
|col_integer()|i|integers.|
|col_double()|d|doubles.|
|col_euro_double()|e|“Euro” doubles that use , as the decimal separator.|
|col_date()|D|Y-m-d dates.|
|col_datetime()|T|ISO8601 date times|
|col_character()|c|everything else.|

推定では、意図した型にならない場合は、列毎に型を指定することができます。

|列型|省略形|説明|
|:---|:---|:---|
|col_skip()|_|don’t import this column.|
|col_date(format) and col_datetime(format, tz)||dates or date times parsed with given format string. Dates and times are rather complex, so they’re described in more detail in the next section.|
|col_numeric()|n|a sloppy numeric parser that ignores everything apart from 0-9, - and . (this is useful for parsing currency data).|
|col_factor(levels, ordered)||parse a fixed set of known values into a (optionally ordered) factor.|

#### col_types の引数
col_typesは、文字列とvectorの2種類の引数を取ることができます。
一つはコンパクトな文字列で、"dc \_\_d"のように、1列目がdouble型、2列目がcharacter型、3,4列目をskip、5列目がdouble型のように上記表の省略形の文字列を繋いだ形で指定できます。
もう一つは、リストで各列毎にcol_typeを指定する方法。指定を省略した場合は、skipされるのではなく、列の型が推定されます。


### problems
データを読み込んだ際に、問題が発生した行と列をレポートしてくれます。
壊れたデータが紛れていた場合等に便利ですね。

## 速度比較
使い方がだいたいわかったところで、速度比較してみます。
比較するのは、`標準のread.delim`、`readr::read_delim``data.table::fread`の3パターンです。

ドキュメントによると、標準のread_delimより10倍以上速いが、data.table::freadよりは1.2～2倍ぐらい遅いらしいですが、実測したいと思います。

計測方法は、以下に記載している方法で、各10回の試行の平均を採用しています。
読み込む対象のファイルは、約554万行、9列でファイルサイズが231MBのTSVファイルです。

それでは、まずは標準のread.delimから。

```r:標準のread.delim
system.time(
  df <- read.delim("C://tmp/all_user_list-2015-04-10.tsv" , sep="\t")
)
```
23.892秒。それなりに時間がかかる。
続いて、readr::read_delim

```r:readrのread_delim
system.time(
  df2 <- readr::read_delim("C://tmp/all_user_list-2015-04-10.tsv" , delim="\t")
)
```
9.130秒。は、早い・・！？
最後にdata.table::fread

```r:data.tableのfread
system.time(
  df3 <- data.table::fread("C://tmp/all_user_list-2015-04-10.tsv" , sep="\t" , header=T )
)
```

2.477秒。爆速。

### 比較結果
![readr_3.png](https://qiita-image-store.s3.amazonaws.com/0/931/f7b55f62-ad78-e67a-c019-78ea31632deb.png)

Hadley氏も言っている通り、data.table::freadがその爆速っぷりを見せつける結果となりました。
readrは標準のread_delimより2.5倍早いですが、ちょっと物足りない数値です。data.table::freadともちょっと差が開いています。


折角調べたので、速度に関係ありそうなオプションを変更して計測してみます。

まずは、型を明示的に指定した場合に早くなるか。

```r:readrのread_delim(型指定)
system.time(
  df4 <- readr::read_tsv("C://tmp/all_user_list-2015-04-10.tsv" , col_types = "iDiDiiDii")
)
```
結果：9.004秒
結果は、誤差の範囲内でほぼ変わらず。
先頭100行読んで9列の型を推定するぐらいだと、速度にはほぼ影響しないです。

よくあるDate型が遅いんじゃないか説を検証するために、Date型をcharacter型で読み込む。

```r:readrのread_delim(Dateをcharacterとして読み込む)
## col_character
system.time(
  df5 <- readr::read_tsv("C://tmp/all_user_list-2015-04-10.tsv" , col_types = "iciciicii")
)
```
結果：5.27秒
早くなった！4秒弱ぐらい早くなったので、結構前進。

最後は、不要な列の指定ができるので、最低限の列だけに絞って読み込む。

```r:readrのread_delim(不要な列をskip)
system.time(
  df6 <- readr::read_tsv("C://tmp/all_user_list-2015-04-10.tsv" , col_types = "ic_____i_")
)
```
結果：3.656秒

さらに早くなった！（当たり前だけど。。）
ざっと試せる感じだと、このあたりが限界のような気がします。

### 最終比較結果
![readr_5.png](https://qiita-image-store.s3.amazonaws.com/0/931/897bf24f-fb7f-62a8-1b8d-456a9fcb0008.png)

色々オプションを指定することで、data.table::freadよりはちょっと遅いですが、標準のread.delimよりは十分に早い速度を出すことができました。

## まとめ
速度的な面は、data.table::freadの方が早い。
速度が命の場面では、data.table::freadを使用し、それ以外の用途ではreadrを使うみたいな使い分けができそうですね。
日付や時間の扱いやすさや、視覚的な読み込み時間のわかりやすさ、そして現時点でも十分早いので、今後使う頻度が増えそうです。

今回は、試せたパターンが少なかったので、安定性はまだ評価できてないですが、今後に期待できそうです！



### おまけ
↓こんな感じの書き方もできるので、ちょっとした集計するのに便利。
今回は、計測した実行時間の平均を取るのに利用しました。地味に便利！

```:r
readr::read_tsv(
  "elapsed_time
3.75
3.56
3.71
3.64
3.61
3.70
3.66 
3.66
3.72
3.66
3.67
3.54
"
) %>% summarise(avg=mean(as.numeric(elapsed_time)))

       avg
1 3.656667
```