Posted at

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

More than 3 years have passed since last update.


はじめに

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