utils::read.csvとdata.table::fread、readr::read_csv

# この記事について

ちょっと大きなcsvファイルを読み込むのに、`data.table::fread`がすごく速かったとつぶやいたら（[data.table::freadが速い](https://twitter.com/takeshou/status/651555217901682688)）、いくつかリツイートしてもらったので、その後の話を少し公開してみる。`data.table::fread`や`readr::read_csv`は確かに速いけど、`utils::read.csv`と同じ結果を求めるためには、ちょっと一手間いりますよという話です。

（追記1：日付忘れました）@r-de-rさまからコメントをいただき、Macでは同じようにならない旨、ご指摘いただきました。参考までにたけしょうがこの記事を書いたときの実行環境を記しておきます。

* Windows 7 SP1
* R 3.2.2
* data.table 1.9.6
* readr 0.2.2

（追記2：20151111）@yutannihilationさまがいろいろと調べて下さいました。この記事の内容はパッケージのバージョンによって結果が左右されるようです。すごく魅力的なパッケージですが、まだ開発も続いているパッケージですし、ある程度落ち着くまでは、積極的な利用は控えていた方がいいかもしれません（コードが後々使えなくなる可能性があるので）。

# 読み込みの検証

同じデータを読み込んで、同じ結果になるまでの手順を探ります。

## 読み込みデータについて

ファイルは[適当に作ったcsvファイル](http://plaza.umin.ac.jp/~takeshou/R/dat/survey.csv)（5KB程度）を利用します。

## utils::read.csv

みんな大好き`utils::read.csv`です。最初、データの読み込みには、この関数を使えと教わる人が多いことでしょう（`utils::read.table`含む）。以下の例では、`na.strings = ""`として、空白をNAとして扱う指定をしています。

```r
dat1 <- read.csv("http://plaza.umin.ac.jp/~takeshou/R/dat/survey.csv",
              na.strings = "")
str(dat1)
```

> 'data.frame':   100 obs. of  10 variables:
 $ sample   : int  1 2 3 4 5 6 7 8 9 10 ...
 $ age      : int  60 28 23 46 59 43 56 64 45 52 ...
 $ height   : num  185 155 171 163 163 ...
 $ weight   : num  86.7 57.8 66.1 62.4 60.4 ...
 $ sex      : Factor w/ 2 levels "f","m": 1 2 2 1 1 2 2 1 2 2 ...
 $ village  : Factor w/ 3 levels "A","B","C": 2 1 1 1 3 1 1 3 2 1 ...
 $ income   : num  8.72 13.61 9.2 6.9 11.18 ...
 $ disease  : int  1 0 0 0 1 0 0 0 1 0 ...
 $ treatment: Factor w/ 2 levels "X","Y": 1 NA NA NA 2 NA NA NA 2 NA ...
 $ outcome  : int  1 NA NA NA 1 NA NA NA 1 NA ...

`utils::read.csv`のdefaultでは、文字列は因子になって付置されます。医学・保健系の解析を行う場合では、ほとんどの場合、この動作で問題はないと思いますし、この動作が最も便利だと思います。[^1]

[^1]: この後、diseaseやoutcomeも因子に変更しますが、今回はそこまでは無視します。

ですので、今回は、この`dat1`と同じモノを`data.table::fread`と`readr::read_csv`を使って作成してみましょう。

## data.table::fread

後述の`readr::read_csv`もそうですが、defaultでは、文字列が文字列として読み込まれます。なので前提として、`stringsAsFactors = TRUE`というオプション指定が必要です。

```r
library("data.table")
dat2 <- fread("http://plaza.umin.ac.jp/~takeshou/R/dat/survey.csv",
              stringsAsFactors = TRUE,
              na.strings = "")
str(dat2)
```

> Classes ‘data.table’ and 'data.frame':        100 obs. of  10 variables:
 $ sample   : int  1 2 3 4 5 6 7 8 9 10 ...
 $ age      : int  60 28 23 46 59 43 56 64 45 52 ...
 $ height   : num  185 155 171 163 163 ...
 $ weight   : num  86.7 57.8 66.1 62.4 60.4 ...
 $ sex      : Factor w/ 3 levels NA,"f","m": 2 3 3 2 2 3 3 2 3 3 ...
 $ village  : Factor w/ 4 levels NA,"A","B","C": 3 2 2 2 4 2 2 4 3 2 ...
 $ income   : num  8.72 13.61 9.2 6.9 11.18 ...
 $ disease  : int  1 0 0 0 1 0 0 0 1 0 ...
 $ treatment: Factor w/ 3 levels NA,"X","Y": 2 1 1 1 3 1 1 1 3 1 ...
 $ outcome  : int  1 NA NA NA 1 NA NA NA 1 NA ...
 - attr(*, ".internal.selfref")=<externalptr> 

このままでは、`data.table`としての属性も持ちますので、`as.data.frame`を使って、`data.frame`に変換しますが、実は、`Factor`のところでNAも水準の1つとしてカウントされています。これは`factor`を使って、調整します。

```r
dat2 <- as.data.frame(dat2)
for(loop in seq(along.with = dat2)){
  if(is.factor(dat2[, loop])){
    dat2[, loop] <- factor(dat2[, loop])
  }
}
```

ここまでやって、漸く`dat1`と`dat2`で同じものになりました。

```r
identical(dat1, dat2)
```

> [1] TRUE

## readr::read_csv
`readr::read_csv`もほぼ同様ですが、今度は問題が2つになります。1つめは、文字列を因子に変換してくれないことで、2つめは、文字コードの問題です。僕は基本的にWindows環境ですので、最初、SJISを指定せずに読み込んだとき、結構悲惨な状態になりました。

```r
library(readr)
dat3 <- read_csv("http://plaza.umin.ac.jp/~takeshou/R/dat/survey.csv",
                 locale = locale(encoding = "SJIS"))
str(dat3)
```

> Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   100 obs. of  10 variables:
 $ sample   : int  1 2 3 4 5 6 7 8 9 10 ...
 $ age      : int  60 28 23 46 59 43 56 64 45 52 ...
 $ height   : num  185 155 171 163 163 ...
 $ weight   : num  86.7 57.8 66.1 62.4 60.4 ...
 $ sex      : chr  "f" "m" "m" "f" ...
 $ village  : chr  "B" "A" "A" "A" ...
 $ income   : num  8.72 13.61 9.2 6.9 11.18 ...
 $ disease  : int  1 0 0 0 1 0 0 0 1 0 ...
 $ treatment: chr  "X" NA NA NA ...
 $ outcome  : int  1 NA NA NA 1 NA NA NA 1 NA ...

最初に`data.frame`に変換し、次に`character`から`facto`rへの変換を行います。

```r
dat3 <- as.data.frame(dat3)
for(loop in seq(along.with = dat3)){
  if(is.character(dat3[, loop])){
    dat3[, loop] <- factor(dat3[, loop])
  }
}
identical(dat1, dat3)
```

> [1] TRUE

これで漸く`utils::read.csv`と同等の結果が得られました。

以上で[このつぶやき](https://twitter.com/takeshou/status/659549682889199616)の全貌の紹介を終わります。

## 読み込みスピード

今回は、小さなファイルの読み込みですので、スピードについては検証していません。同じ結果を得られるためのスピードも誰かが…

### 終わりに

初めてQiitaに書いてみました。こういう情報共有サービスというのはありがたいですね。なるべく間違いのないように記述したつもりですが、何かございましたら、なんなりとご指摘いただけますと助かります。