dplyrを使いこなす！Window関数編

## はじめに
dplyrの使い方にちょっと慣れてくると、「あー、これもうちょっと簡単にできないの？」みたいな事が出てきたりします。

今回は、そんな悩みをほんのちょっと解決できるかもしれない、Window関数について解説したいと思います。
SQLに詳しい人はすぐイメージできると思いますが、私の周りにもWindow関数の存在自体を知らない人が結構居たのでいい機会なので、ざっくりまとめます。

dplyrってなんぞやという方は、[基礎編の記事](http://qiita.com/matsuou1/items/e995da273e3108e2338e)を見ていただければと。

## Window関数を使うと簡単にできることの例
とは言っても、具体的に何ができるのか、分からなかったら読むのもメンドクサイので、まずは簡単にできることを紹介します。

* ランキング（タイ順位考慮あり、なし等含む）
* 前日比、前週比（前後のレコードとの比較等）
* 累積（累積和等）
* 移動平均（Windowサイズの指定、Windowの位置、重み等）

どれも自分でやれなくはないけど、結構メンドクサイですよね。。
でも、Window関数を使えるようになると、dplyrの一連の処理の流れの中に数行で組み込めるので、簡単だし、コードも見やすくなり、そして、何より、仕事が捗ってる感を得ることができます。（初回限定）

## 集約関数とWindow関数の違い
まずは、いつも使っている集約関数とWindow関数の違いについて、整理します。
#### 集約関数
集約関数は、複数の入力レコードに対し、1つのレコードを出力します。
count、sum、mean、max、min等がその代表です。

#### Window関数
対して、Window関数は、複数の入力レコードに対し、レコード毎に1つのレコードを出力します。
ランキングや累積和をイメージすると、分かりやすいかと思います。

という感じなので、集約関数の場合は、group_byして、summarise内で使う場合がほとんどだと思いますが、
Window関数は、新しい列を追加するケースが多いので、mutate内で使用することになります。
（慣れてくれば、filter内で使うことも増えてくるかもしれません。）

それでは、具体的に見ていきましょう。

## ランキング関数
最初は、比較的イメージしやすいランキング関数から。
ランキング系の関数には、以下の6つの関数があります。

### 主なランキング関数

|関数|説明|
|:---------------------------------------|:-------------------------------------------------------------------|
|row_number|昇順にランキングを付ける。**同じ値がある場合は、最初に来た方を優先**|
|min_rank|昇順にランキングを付ける。同じ値がある場合は、同じ順位を付ける。**gapあり**|
|dense_rank|昇順にランキングを付ける。同じ値がある場合は、同じ順位を付ける。**gapなし**|
|percent_rank|min_rankを0～1にリスケールしたもの|
|cume_dist|累積割合。percent_rankの累積和では**ない**。|
|ntile|n個の群に分割する|

### ランキング関数構文
```r:構文
> row_number
function (x) 

> min_rank
function (x) 

> dense_rank
function (x) 

> percent_rank
function (x) 

> cume_dist
function (x) 

> ntile
function (x, n) 
```

### 例1：ランキング関数（昇順）
```r:R
data.frame(x = c(5 , 1 , 3 , 2 , 2 , NA)) %>%
   dplyr::mutate(row_number=row_number(x)) %>%
   dplyr::mutate(min_rank=min_rank(x)) %>%
   dplyr::mutate(dense_rank=dense_rank(x)) %>%
   dplyr::mutate(percent_rank=percent_rank(x)) %>%
   dplyr::mutate(cume_dist=cume_dist(x)) %>%
   dplyr::mutate(ntile=ntile(x , 3))

   x row_number min_rank dense_rank percent_rank cume_dist ntile
1  5          5        5          4         1.00       1.0     3
2  1          1        1          1         0.00       0.2     1
3  3          4        4          3         0.75       0.8     2
4  2          2        2          2         0.25       0.6     1
5  2          3        2          2         0.25       0.6     2
6 NA         NA       NA         NA           NA        NA    NA
```
min_rank(gapあり)、denserank(gapなし)は、タイ順位があった場合にその次の順位を飛ばすか、飛ばさないかの差です。
上記例で言えば、1,2,2,4となるか、1,2,2,3となるかの違いですね。

cume_distは、上位5%とか出したいときや、全体の90%を占めるラインは何処かを求める際に使うイメージです。
percent_rankの累積ではないので、勘違いしないようにしましょう。

入力値がNAの場合は、どの関数もランキングせずに、NAを返します。

### 例2：ランキング関数（降順）
```r:R
# 降順
# スコアの高い順でランキングしたい場合等は、desc関数を使用すればよい
> data.frame(x = c(5 , 1 , 3 , 2 , 2 , NA)) %>%
   dplyr::mutate(row_number=row_number(desc(x))) %>%
   dplyr::mutate(min_rank=min_rank(desc(x))) %>%
   dplyr::mutate(dense_rank=dense_rank(desc(x))) %>%
   dplyr::mutate(percent_rank=percent_rank(desc(x))) %>%
   dplyr::mutate(cume_dist=cume_dist(desc(x))) %>%
   dplyr::mutate(ntile=ntile(desc(x) , 3))

   x row_number min_rank dense_rank percent_rank cume_dist ntile
1  5          1        1          1         0.00       0.2     1
2  1          5        5          4         1.00       1.0     3
3  3          2        2          2         0.25       0.4     1
4  2          3        3          3         0.50       0.8     2
5  2          4        3          3         0.50       0.8     2
6 NA         NA       NA         NA           NA        NA    NA

```
スコアの高い順にランキングしたい場合は、desc関数を使用します。

## Offsets(lead , lag)
lead 、lag関数を使用すると、前後のレコードの値を取得でき、前日比、前週比等の値が簡単に出せるようになります。

### lead、lagの説明
|関数|説明|
|:-----------|:------------|
|lead|xを前方にnだけずらして、後方をdefaultで埋める|
|lag|xを後方にnだけずらして、前方をdefaultで埋める|

### lead、lagの構文
```r:R
> lead
function (x, n = 1L, default = NA, order_by = NULL, ...) 

> lag
function (x, n = 1L, default = NA, order_by = NULL, ...) 
```

### 例1：標準的な使い方
```r:構文
# １つづつ前後にずらす
data.frame(x = c(1, 2, 3, 4, 5)) %>%
   dplyr::mutate(lead=lead(x)) %>%
   dplyr::mutate(lag=lag(x))

  x lead lag
1 1    2  NA
2 2    3   1
3 3    4   2
4 4    5   3
5 5   NA   4
```

### 例2：nオプションを使用して、２つづつずらす
複数ずらすことも可能です。n=7にすると前週比とか簡単に出せるようになります。

```r:オプションn
# 前後に２つづつずらす
data.frame(x = c(1, 2, 3, 4, 5)) %>%
  dplyr::mutate(lead=lead(x , n=2)) %>%
  dplyr::mutate(lag=lag(x , n=2))

  x lead lag
1 1    3  NA
2 2    4  NA
3 3    5   1
4 4   NA   2
5 5   NA   3

```

### 例3：defaultオプションを使用して、0で埋める
ずらして欠損値となる部分に埋める値を指定できます。

```r:オプションdefault
# 値がないところを、０埋め
data_frame(x = c(1:5)) %>%
  dplyr::mutate(lead=lead(x , default=0, n=2 )) %>%
  dplyr::mutate(lag=lag(x , default=0 , n=2))

  x lead lag
1 1    3   0
2 2    4   0
3 3    5   1
4 4    0   2
5 5    0   3

```

### 例4：order_byオプションを使用して、ソート列を指定する
order_byで、ソート列を指定できます。逆順にする場合は、descを使用します。

```r:オプションorder_by
# yの値でランキング
data_frame(x = c(1 , 2, 3, 4, 5), y=c(5,4,2,1,3)) %>%
   dplyr::mutate(lead=lead(x , default=0, order_by=y)) %>%
   dplyr::mutate(lag=lag(x , default=0 , order_by=y))

  x y lead lag
1 1 5    0   2
2 2 4    1   5
3 3 2    5   4
4 4 1    3   0
5 5 3    2   3
```

### 例5：group_by
group_byを使用すると、グループ内でのoffsetが可能です。

```r
data_frame(x = c(1 , 2, 3, 4, 5) , y=c("aa" ,"aa" , "aa" , "bb" , "bb")) %>%
   dplyr::group_by(y) %>%
   dplyr::mutate(lead=lead(x , default=0)) %>%
   dplyr::mutate(lag=lag(x , default=0))

  x  y lead lag
1 1 aa    2   0
2 2 aa    3   1
3 3 aa    0   2
4 4 bb    5   0
5 5 bb    0   4
```

## 累積関数
各項目の累積に対して、集約関数をかけます。
言葉で書くよりも、図で書いた方が分かりやすいので、例にイメージ図を書いてみました。

### 主な累積関数
|関数|説明|
|:-----------|:------------|
|cumsum|部分和|
|cummin|部分min|
|cummax|部分max|
|cummean|部分平均|
|cumall|部分all|
|cumany|部分any|

### 累積関数の構文
```r:構文
> cumsum
function (x)

> cummin
function (x)

> cummax
function (x)

> cummean
function (x) 

> cumall
function (x) 

> cumany
function (x) 
```

### 例1：cumsum、cummin、cummax、cummean
累積に対して、集約関数をかけるイメージ
![cumsum.png](https://qiita-image-store.s3.amazonaws.com/0/931/b7b0c9b8-0aa2-c137-c14a-654b9b55960e.png)

```r
data_frame(x = c(1,2,3,4,5)) %>%
   dplyr::mutate(cumsum=cumsum(x)) %>%
   dplyr::mutate(cummin=cummin(x)) %>%
   dplyr::mutate(cummax=cummax(x)) %>%
   dplyr::mutate(cummean=cummean(x))

  x cumsum cummin cummax cummean
1 1      1      1      1     1.0
2 2      3      1      2     1.5
3 3      6      1      3     2.0
4 4     10      1      4     2.5
5 5     15      1      5     3.0
```

### 例2：cumall、cumany
cumallは、累積のすべてがTRUEの場合にTRUE
cumanyは、累積のいずれか一つがTRUEの場合にTRUE
![cumall.png](https://qiita-image-store.s3.amazonaws.com/0/931/60d60382-e898-95a0-97a8-fa32c97bde60.png)

```r
data_frame(x = c(TRUE , TRUE ,  FALSE ,TRUE , TRUE)) %>%
   dplyr::mutate(cumall=cumall(x)) %>%
   dplyr::mutate(cumany=cumany(x))

      x cumall cumany
1  TRUE   TRUE   TRUE
2  TRUE   TRUE   TRUE
3 FALSE  FALSE   TRUE
4  TRUE  FALSE   TRUE
5  TRUE  FALSE   TRUE

```

## ローリング関数
RcppRollを用いることで、指定ウィンドウサイズ内で集約関数を使用することができます。
移動平均等も簡単に出せるので、便利です。
ローリング関数を使用するには、RcppRollパッケージのインストールが必要です。

```r:パッケージインストール＆読み込み
install.packages("RcppRoll")
library(RcppRoll)
```

ローリング関数も、文字で説明してもなかなかイメージしづらいので、イメージ図を作成しました。

![rolling_function.png](https://qiita-image-store.s3.amazonaws.com/0/931/79616bc0-d95a-03db-4ce2-7bbf115bf439.png)

### 主なローリング関数
|関数|説明|
|:-----------|:------------|
|roll_max|指定ウィンドウサイズ内の最大値を取得|
|roll_min|指定ウィンドウサイズ内の最小値を取得|
|roll_mean|指定ウィンドウサイズ内の平均値を取得|
|roll_median|指定ウィンドウサイズ内の中央値を取得|
|roll_sum|指定ウィンドウサイズ内の合計を取得|
|roll_prod|指定ウィンドウサイズ内の総積を取得|
|roll_var|指定ウィンドウサイズ内の分散を取得|
|roll_sd|指定ウィンドウサイズ内の標準偏差を取得|

### 主なローリング関数の構文

```r:構文
> roll_max
function (x, n = 1L, weights = NULL, by = 1L, fill = numeric(0), 
    partial = FALSE, align = c("center", "left", "right"), normalize = TRUE, 
    na.rm = FALSE) 

> roll_min
function (x, n = 1L, weights = NULL, by = 1L, fill = numeric(0), 
    partial = FALSE, align = c("center", "left", "right"), normalize = TRUE, 
    na.rm = FALSE) 

> roll_mean
function (x, n = 1L, weights = NULL, by = 1L, fill = numeric(0), 
    partial = FALSE, align = c("center", "left", "right"), normalize = TRUE, 
    na.rm = FALSE) 

> roll_median
function (x, n = 1L, weights = NULL, by = 1L, fill = numeric(0), 
    partial = FALSE, align = c("center", "left", "right"), normalize = TRUE, 
    na.rm = FALSE) 

> roll_sum
function (x, n = 1L, weights = NULL, by = 1L, fill = numeric(0), 
    partial = FALSE, align = c("center", "left", "right"), normalize = TRUE, 
    na.rm = FALSE) 

> roll_prod
function (x, n = 1L, weights = NULL, by = 1L, fill = numeric(0), 
    partial = FALSE, align = c("center", "left", "right"), normalize = TRUE, 
    na.rm = FALSE) 

> roll_var
function (x, n = 1L, weights = NULL, by = 1L, fill = numeric(0), 
    partial = FALSE, align = c("center", "left", "right"), normalize = TRUE, 
    na.rm = FALSE) 

> roll_sd
function (x, n = 1L, weights = NULL, by = 1L, fill = numeric(0), 
    partial = FALSE, align = c("center", "left", "right"), normalize = TRUE, 
    na.rm = FALSE) 
```

### 主なオプションの説明

|オプション|説明|
|:-----------|:------------|
|n|ウィンドウサイズ|
|weights|ウィンドサイズと同じ長さのベクトルで、各要素の重みを指定します。|
|fill|欠損値を何で埋めるかを指定します。|
|align|ウインドウの位置を指定します。"left"、"center"、"right"が指定できます。|
|normalize|重みをNormalizeするかどうかを指定します。|
|na.rm|NAを削除するかどうかを指定します。|

### 例1：alignの違い
```r:roll_max
# Windowサイズn=3で、最大のものを取得する例
# nが1か全体でなければ、fillの指定が必要
# align="center" : n=3の場合、該当行、およびその前後の1行を使用し、最大値を出す
# align="left"   : n=3の場合、該当行、およびその後ろ2行を使用し、最大値を出す
#    roll_maxl関数は、roll_max(x , align="left")と同様
# align="right"  : n=3の場合、該当行、およびその前2行を使用し、最大値を出す 
#    roll_maxr関数は、roll_max(x , align="right")と同様
data_frame(x = c(1:10) ) %>%
   dplyr::mutate(roll_max_center= roll_max(x , n=3 ,align="center" , fill=NA)) %>%
   dplyr::mutate(roll_max_left  = roll_max(x , n=3 ,align="left"   , fill=NA)) %>%
   dplyr::mutate(roll_max_right = roll_max(x , n=3 ,align="right"  , fill=NA))

    x roll_max_center roll_max_left roll_max_right
1   1              NA             3             NA
2   2               3             4             NA
3   3               4             5              3
4   4               5             6              4
5   5               6             7              5
6   6               7             8              6
7   7               8             9              7
8   8               9            10              8
9   9              10            NA              9
10 10              NA            NA             10
```

### 例2：重み付き移動平均
weightsオプションで重みを指定することができます。

```r:weightsオプション
# weightsには、ウィンドウサイズnを同じ長さのベクトルを指定します。
data_frame(x = c(1:10) ) %>%
   dplyr::mutate(roll_mean=roll_mean(x , n=3 ,fill=NA)) %>%
   dplyr::mutate(roll_mean112=roll_mean(x , n=3 , weights=c(1,1,2) ,fill=NA)) %>%
   dplyr::mutate(roll_mean211=roll_mean(x , n=3 , weights=c(2,1,1) ,fill=NA)) %>%
   dplyr::mutate(roll_mean121=roll_mean(x , n=3 , weights=c(1,2,1) ,fill=NA))

    x roll_mean roll_mean112 roll_mean211 roll_mean121
1   1        NA           NA           NA           NA
2   2         2         2.25         1.75            2
3   3         3         3.25         2.75            3
4   4         4         4.25         3.75            4
5   5         5         5.25         4.75            5
6   6         6         6.25         5.75            6
7   7         7         7.25         6.75            7
8   8         8         8.25         7.75            8
9   9         9         9.25         8.75            9
10 10        NA           NA           NA           NA
```

### 例3：fillオプション指定
欠損値が出る場合、fillで指定した値が使用されます。

```r:fill
data_frame(x = c(1:10) ) %>%
   dplyr::mutate(roll_mean1=roll_mean(x , n=3 , fill=0)) %>%
   dplyr::mutate(roll_mean2=roll_mean(x , n=3 , fill=-1)) %>%
   dplyr::mutate(roll_mean3=roll_mean(x , n=3 , fill=NA))

    x roll_mean1 roll_mean2 roll_mean3
1   1          0         -1         NA
2   2          2          2          2
3   3          3          3          3
4   4          4          4          4
5   5          5          5          5
6   6          6          6          6
7   7          7          7          7
8   8          8          8          8
9   9          9          9          9
10 10          0         -1         NA
```

## Window関数をfilterに使用する
これまでは、主に列を追加する際にWindow関数を使用していましたが、filter関数にも使用することができます。
### 例1：yでグルーピングしてmin_rankが1のもののみ抽出

```r:ranking
data_frame(x = c(1 , 2, 3, 4, 5) , y=c("aa" ,"aa" , "aa" , "bb" , "bb")) %>%
   dplyr::group_by(y) %>%
   dplyr::filter(min_rank(x) == 1)

  x  y
1 1 aa
2 4 bb

```

### 例2：累積和が5を超えるもののみ抽出

```r:累積
data_frame(x = c(1,2,3,4,5)) %>%
   dplyr::filter(cumsum(x)>5)

  x
1 3
2 4
3 5
```

## まとめ
Window関数編は以上になります。
分かりにくいところもあるので、今回は具体的なイメージ図を入れてみました。
使いこなせると、普段の集計には殆ど困ることは無くなると思いますので、是非、使いこなせるようになりましょう！

これだけ解説しても、まだまだ機能が残ってるdplyr、恐るべし・・・。
きっとあと1回か、2回ぐらい続きます。