LoginSignup
154

More than 5 years have passed since last update.

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

Posted at

はじめに

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

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

dplyrってなんぞやという方は、基礎編の記事を見ていただければと。

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個の群に分割する

ランキング関数構文

構文
> 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
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
# 降順
# スコアの高い順でランキングしたい場合等は、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
> lead
function (x, n = 1L, default = NA, order_by = NULL, ...) 

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

例1:標準的な使い方

構文
# 1つづつ前後にずらす
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オプションを使用して、2つづつずらす

複数ずらすことも可能です。n=7にすると前週比とか簡単に出せるようになります。

オプションn
# 前後に2つづつずらす
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で埋める

ずらして欠損値となる部分に埋める値を指定できます。

オプションdefault
# 値がないところを、0埋め
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を使用します。

オプション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が可能です。

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

累積関数の構文

構文
> 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

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

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パッケージのインストールが必要です。

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

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

rolling_function.png

主なローリング関数

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

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

構文
> 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の違い

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オプションで重みを指定することができます。

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で指定した値が使用されます。

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のもののみ抽出

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を超えるもののみ抽出

累積
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回ぐらい続きます。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
154