100MB超えたらnysolが良さそう

野球データを速く集計したい (R vs nysol)
===

## はじめに

[R advent calendar](http://qiita.com/advent-calendar/2014/r-rstudio), 12月10日分です. 

nysolを使ってみた, という話です. 

[速度比較の記事](http://www.slideshare.net/SatoshiKitajima2/m1-38513054)を見ると, nysol使いたくなりますよね. 

使ってみたら案外イケました. コードも書きやすいです.

野球データ解析のコードを例に, plyr, dplyr, nysolのコードと処理速度を比較してみました. 

## メジャーリーグ: 大チャンスでの得点率

メジャーリーグで, 犠牲フライでも点が入る場面で, ちゃんと得点を挙げてくれる選手を調べます. 

2013年メジャーリーグの全打席結果データ(77MB)を利用して, 集計してみます. 

データは[retrosheet](http://www.retrosheet.org)からダウンロード出来ますし, [github](https://github.com/gghatano/nysol_baseball)にもあります. . 

集計は, 以下の3つの方法で行います. 

- data.frame + plyr

- data.table + dplyr

- nysol

集計からランキング表示まで, どの方法が一番速いでしょうか. 

## data.frame + plyrで集計

何も工夫せずに書いている感じです. 去年の私の知識で頑張ったのでしょう. 

```r
start = proc.time()
library(plyr)
library(stringr)
# データ読み込み
read_time = system.time(data2013 <- read.csv("all2013.csv"))

# 打者結果だけ
data2013 <- subset(data2013, BAT_EVENT_FL == TRUE)

# 大チャンス場面かどうかをSuperChanceに入れる
data2013$SuperChance <- with(data2013, ifelse(OUTS_CT<2 & BASE3_RUN_ID!="" , 1,0 ))

# 大チャンス場面で点が入ったかどうかをSuperChanceScoredに入れる
data2013$SuperChanceScored <- with(data2013, ifelse(OUTS_CT<2 & BASE3_RUN_ID!="" & str_detect(EVENT_TX,"3-H"), 1,0 ))

# ddplyで要約
data2013.SuperChance <- ddply(data2013, .(BAT_ID), summarize, 
                              SuperChance = sum(SuperChance),
                              SuperChanceScored = sum(SuperChanceScored),
                              SuperChanceScoredRate = SuperChanceScored/SuperChance)
# 10打席以上
data2013.SuperChance.over10chance <- subset(data2013.SuperChance, SuperChance>10)
# 得点ゲット率で並び替え 
data2013.SuperChance.over10chance.arranged <- arrange(data2013.SuperChance.over10chance, desc(SuperChance))
# ランキング
head(data2013.SuperChance.over10chance.arranged, 10)
```

```
##      BAT_ID SuperChance SuperChanceScored SuperChanceScoredRate
## 1  philb001          65                39             0.6000000
## 2  brucj001          54                30             0.5555556
## 3  pedrd001          54                33             0.6111111
## 4  vottj001          53                21             0.3962264
## 5  fielp001          50                27             0.5400000
## 6  mccua001          50                22             0.4400000
## 7  canor001          49                28             0.5714286
## 8  napom001          47                21             0.4468085
## 9  andre001          46                24             0.5217391
## 10 belta001          46                21             0.4565217
```

```r
end = proc.time()
```

### 処理時間

```r
## 全体の処理時間
end - start
```

```
##    user  system elapsed 
##  21.422   0.570  22.051
```

```r
## データ読み込み時間
read_time
```

```
##    user  system elapsed 
##  18.810   0.385  19.238
```

読み込みに19.238秒, 全体の処理時間が22.051秒. 

大半がデータ読み込み時間だ, ということでした. 

subsetとかddplyとか, 久しぶりに見ました. 2度と使わないと思います. 

## data.table + dplyrで集計

freadで読み込んでdplyrで集計しましょう. 

```r
start2 = proc.time()
library(data.table)
library(dplyr)
library(stringr)

## データ読み込み
read_time2 = system.time(data2013 <- fread("all2013.csv"))

## 大チャンスに強い打者ランキング作成
data2013 %>% 
  filter(BAT_EVENT_FL=="T") %>% 
  mutate(SUPER_CHANCE=ifelse((OUTS_CT<2 & BASE3_RUN_ID!=""), 1, 0)) %>% 
  mutate(SUPER_CHANCE_SCORED = 
           ifelse(OUTS_CT<2 & BASE3_RUN_ID!="" & str_detect(EVENT_TX,"3-H"), 1, 0)) %>% 
  group_by(BAT_ID) %>% 
  summarise(SUPER_CHANCE=sum(SUPER_CHANCE), 
            SUPER_CHANCE_SCORED = sum(SUPER_CHANCE_SCORED)) %>% 
  mutate(SUPER_CHANCE_SCORED_RATE = SUPER_CHANCE_SCORED/SUPER_CHANCE) %>%
  filter(SUPER_CHANCE>10) %>% 
  arrange(desc(SUPER_CHANCE_SCORED_RATE)) 
```

```
## Source: local data table [340 x 4]
## 
##      BAT_ID SUPER_CHANCE SUPER_CHANCE_SCORED SUPER_CHANCE_SCORED_RATE
## 1  robeb003           15                  14                0.9333333
## 2  blank002           11                  10                0.9090909
## 3  bourm001           20                  17                0.8500000
## 4  reyej001           12                  10                0.8333333
## 5  peres002           21                  17                0.8095238
## 6  suzuk001           15                  12                0.8000000
## 7  lombs002           14                  11                0.7857143
## 8  utlec001           29                  22                0.7586207
## 9  carpm001           20                  15                0.7500000
## 10 encae001           20                  15                0.7500000
## ..      ...          ...                 ...                      ...
```

```r
end2 = proc.time()
```

### 処理時間

```r
## 全体の処理時間
end2 - start2
```

```
##    user  system elapsed 
##   3.348   0.343   3.791
```

```r
## データ読み込み時間
read_time2
```

```
##    user  system elapsed 
##   1.105   0.078   1.219
```

読み込みに1.219秒, 全体の処理時間が3.791秒. 

だいぶ速くなりました. 

## nysolで集計


```bash
# 処理に使うコード
cat ./superChance.bash
```

```
## #!/bin/bash
## 
## cat ./all2013.csv | 
##   mselstr f=BAT_EVENT_FL v=T | 
##   mcal c='if(${OUTS_CT}<2 && $s{BASE3_RUN_ID}!="", 1, 0)' a="SUPER_CHANCE" | 
##   mcal c='if(${OUTS_CT}<2 && $s{BASE3_RUN_ID}!="" && regexs($s{EVENT_TX}, "3-H"),1, 0)' \
##     a="SUPER_CHANCE_SCORED" | 
##   mcut f=BAT_ID,SUPER_CHANCE,SUPER_CHANCE_SCORED | 
##   msum k=BAT_ID f=SUPER_CHANCE,SUPER_CHANCE_SCORED | 
##   mcal c='${SUPER_CHANCE_SCORED}/${SUPER_CHANCE}' a="SUPER_CHANCE_SCORED_RATE" | 
##   msel c='${SUPER_CHANCE}>10' | 
##   msortf f=SUPER_CHANCE_SCORED_RATE%nr | 
##   head -n 10
```
dplyrで書いたコードと, ほとんど同じ頭の使い方で書けます. 

処理時間を測ってみます. 

```bash
# 処理時間の計測
time ./superChance.bash
```

```
## BAT_ID,SUPER_CHANCE,SUPER_CHANCE_SCORED,SUPER_CHANCE_SCORED_RATE%0nr
## robeb003,15,14,0.9333333333
## blank002,11,10,0.9090909091
## bourm001,20,17,0.85
## reyej001,12,10,0.8333333333
## peres002,21,17,0.8095238095
## suzuk001,15,12,0.8
## lombs002,14,11,0.7857142857
## utlec001,29,22,0.7586206897
## encae001,20,15,0.75
## 
## real	0m1.950s
## user	0m2.134s
## sys	0m0.345s
```

あまり変わらないような... データが数10MBですからね. こんなもんかと. 

## ここまでのまとめ

nysolが1番速いですが, そこまで性能差を感じません. 

データの大きさに依存すると思います. 

10MBオーダーなら, data.table::freadしてdplyrでいいと思いました. 

## 追加実験: 野球データ(80年分:4.3GB)を集計したい

これまでは2013年の結果データ(77MB)を使っていました. 

データを大きくして, 同じ処理を行ってみます. 

1921年から2013年までのメジャーリーグ打席結果データを全て繋げて, 集計してみます. 4.3GBあります. ビッグデータです. 

コードは同じです. 結果だけ載せます. 手元では, こうなりました. 

- data.frame + plyr 実行不可能

- data.table + dplyr 20分

- nysol 1分16秒

なるほど. nysol. 凄い. 

## まとめ

100MBを超えたくらいから, Rへのデータ読み込みがキツくなってくると思います. 

そこでnysol. dplyrのノリでなかなか自由に書けて楽しいです.

データが大きくになるにつれて, nysolが強くなっていきます. 

nysolのmコマンドを覚えておいて, 状況に応じてRと使い分けていくといいかな, と思いました.  

以上です.