ウィンドウ関数 は、集約関数のバリエーションの一つです。 sum() や mean() のような集約関数がn個の入力を受けて1つの値を返すのに対して、窓関数はn個の値を返します。 窓関数の出力はそのすべての入力値に依存するので、窓関数には +
や round()
のような要素ごとに動作する関数は含まれません。 窓関数には、cumsum()
や cummean()
のような集約関数のバリエーションや、rank()
のような順位付けを行う関数、lead()
や lag()
のようなオフセットを取る関数などがあります。
この vignette では、Lahman batting datasetの小さなサンプルを使って、賞を獲得した選手を含めてみます。
library(Lahman)
batting <- Lahman::Batting %>%
as_tibble() %>%
select(playerID, yearID, teamID, G, AB:H) %>%
arrange(playerID, yearID, teamID) %>%
semi_join(Lahman::AwardsPlayers, by = "playerID")
players <- batting %>% group_by(playerID)
ウィンドウ関数は,mutate()
や filter()
と組み合わせて,さまざまな問題を解決します。 ここではその一部を紹介します。
# For each player, find the two years with most hits
filter(players, min_rank(desc(H)) <= 2 & H > 0)
# Within each player, rank each year by the number of games played
mutate(players, G_rank = min_rank(G))
# For each player, find every year that was better than the previous year
filter(players, G > lag(G))
# For each player, compute avg change in games played per year
mutate(players, G_change = (G - lag(G)) / (yearID - lag(yearID)))
# For each player, find all years where they played more games than they did on average
filter(players, G > mean(G))
# For each, player compute a z score based on number of games played
mutate(players, G_z = (G - mean(G)) / sd(G))
この vignette を読む前に、mutate()
とfilter()
に慣れておく必要があります。
ウィンドウ関数には大きく分けて5つの種類があります。 2つのファミリーは集約関数とは無関係である。
順位付け関数と順序付け関数。 dense_rank()
, cume_dist()
, percent_rank()
, ntile()
である。これらの これらの関数はすべて、順序付けのためのベクトルを受け取り、さまざまなタイプのランクを返します。
オフセット lead()
と lag()
によって,ベクトルの前の値と次の値にアクセスすることができます。 ベクトルの前後の値にアクセスすることができ,差や傾向を簡単に計算することができます.
他の3つのファミリーは,おなじみの集計関数のバリエーションです.
累積集計。累積集約: cumsum()
, cummin()
, cummax()
(base Rより), 累積集約:cumum()
, cummin()
, cummax()
(base Rより), および cumall()
, cumany()
, cummean()
(dplyr より).
ローリングアグリゲートは、固定幅のウィンドウで動作します。base R や dplyr では見られません。 base R や dplyr にはありませんが、他のパッケージには多くの実装があります。 他のパッケージには多くの実装があります. RcppRoll。
リサイクルされた集合体:入力の長さに合わせて集合体が繰り返されます。 繰り返されます.これらはRでは必要ありません。なぜなら、ベクトルのリサイクル は必要なところで自動的に集約をリサイクルしますので,Rでは必要ありません.これらはSQLでは重要です。 なぜなら、集約関数が存在すると、通常はデータベースに対して グループごとに1つの行だけを返すように指示します。
各ファミリーについては、一般的な目的と dplyr での使用方法に焦点を当てて、以下で詳しく説明します。詳細については、各関数のドキュメントを参照してください。
ランキング関数は同じテーマのバリエーションで、tie の扱い方が異なります。
x <- c(1, 1, 2, 2, 2)
row_number(x)
#> [1] 1 2 3 4 5
min_rank(x)
#> [1] 1 1 3 3 3
dense_rank(x)
#> [1] 1 1 2 2 2
Rに精通している方であれば,row_number()
や min_rank()
が基本の rank()
関数と ties.method
引数の様々な値を使って計算できることをご存知かもしれません。 これらの関数は,タイプミスを少しでも減らし,RとSQLの変換を容易にするために提供されています.
他の2つのランキング関数は0と1の間の数値を返します。 percent_rank()
はランクのパーセンテージを,cume_dist()
は現在の値以下の値の割合を表します。
これらは、各グループ内の上位10%のレコードを(例えば)選択したい場合に便利です。例えば:
filter(players, cume_dist(desc(G)) < 0.1)
#> # A tibble: 1,047 x 7
#> # Groups: playerID [954]
#> playerID yearID teamID G AB R H
#> <chr> <int> <fct> <int> <int> <int> <int>
#> 1 aaronha01 1963 ML1 161 631 121 201
#> 2 aaronha01 1968 ATL 160 606 84 174
#> 3 abbotji01 1991 CAL 34 0 0 0
#> 4 abernte02 1965 CHN 84 18 1 3
#> # … with 1,043 more rows
最後に ntile()
はデータを n
個の均等なサイズのバケットに分割します。 これは粗いランキングで、mutate()
と一緒に使うことで、データをバケットに分割してさらに要約することができます。 例えば、ntile()
を使って、チーム内の選手を4つのランクのグループに分け、それぞれのグループの平均試合数を計算することができます。
by_team_player <- group_by(batting, teamID, playerID)
by_team <- summarise(by_team_player, G = sum(G))
#> `summarise()` regrouping output by 'teamID' (override with `.groups` argument)
by_team_quartile <- group_by(by_team, quartile = ntile(G, 4))
summarise(by_team_quartile, mean(G))
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # A tibble: 4 x 2
#> quartile `mean(G)`
#> <int> <dbl>
#> 1 1 26.7
#> 2 2 97.0
#> 3 3 270.
#> 4 4 975.
すべてのランキング関数は、小さな入力値が小さなランクを得るように、最小から最大へとランク付けします。 最高から最低へのランク付けには desc()
を使います。
lead()
と lag()
は入力ベクトルのオフセットを生成し、元のベクトルに先行するか後続するかを決定する。
以下のような用途に使用できます。
差分や変化率を計算する。
入力が n
に対して diff()
は n - 1
の出力を返すので,lag()
を使う方が diff()
よりも便利です. diff()は n - 1
個の出力を返すからです。
値が変化したことを知ることができます。
lead()
と lag()
には,オプションの引数として order_by
があります。 これがセットされていると、どの値が他の値より前に来るかを決定するために行の順序を使用する代わりに、別の変数を使用します。 これは、データをまだソートしていない場合や、ある方法でソートして別の方法で遅延させたい場合に重要です。
ここでは、必要なときに order_by
を指定しなかった場合の簡単な例を紹介します。
df <- data.frame(year = 2000:2005, value = (0:5) ^ 2)
scrambled <- df[sample(nrow(df)), ]
wrong <- mutate(scrambled, prev_value = lag(value))
arrange(wrong, year)
#> year value prev_value
#> 1 2000 0 4
#> 2 2001 1 0
#> 3 2002 4 9
#> 4 2003 9 16
#> 5 2004 16 NA
#> 6 2005 25 1
right <- mutate(scrambled, prev_value = lag(value, order_by = year))
arrange(right, year)
#> year value prev_value
#> 1 2000 0 NA
#> 2 2001 1 0
#> 3 2002 4 1
#> 4 2003 9 4
#> 5 2004 16 9
#> 6 2005 25 16
base Rは累積和(cumsum()
)、累積最小(cummin()
)、累積最大(cummax()
)を提供しています。 (また、cumprod()
も提供されていますが、これはほとんど役に立ちません)。 他の一般的な累積関数としては,cumany()
と cumall()
,||
と &&
の累積版、cummean()
、累積平均があります。 これらは base R には含まれていませんが、効率的なバージョンは dplyr
で提供されています。
cumany()
と cumall()
は,ある条件が最初に(あるいは最後に)真となったときに,それまでのすべての行、あるいはそれ以降のすべての行を選択するのに便利です。 例えば,cumany()
を使って、ある選手が150試合出場した年以降のすべての記録を探すことができます。
lead と lag のように、蓄積される順序を制御したい場合があります。 内蔵の関数には order_by
という引数がありませんので、dplyr
はヘルパーを提供します: order_by()
です。 order_by()
には,順序付けしたい変数と,ウィンドウ関数への呼び出しを与えます.
代わりに、よりシンプルで簡潔な with_order()
を使用してください。
Rのベクトルリサイクルは、要約よりも高い値や低い値を簡単に選択することができます。 これをリサイクル集約と呼んでいるのは、集約の値が元のベクトルと同じ長さにリサイクルされるからです。 リサイクル集計は、平均値よりも大きい、または中央値よりも小さいすべてのレコードを見つけたい場合に便利です。
ほとんどのSQLデータベースでは,median()
や quantile()
に相当するものはありませんが,フィルタリングを行う場合には ntile()
で同じ効果を得ることができます。 例えば,x > median(x)
は ntile(x, 2) == 2
と同等であり、x > quantile(x, 75)
は ntile(x, 100) > 75
や ntile(x, 4) > 3
と同等です。
このアイデアを使って、あるフィールドの値が最も高い(x == max(x)
)、 または最も低い(x == min(x)
)レコードを選択することもできますが、ランキング関数の方が同値をよりコントロールでき、 任意の数のレコードを選択することができます。
リサイクル集計は,mutate()
と組み合わせて使うこともできます。 例えば、バッティングのデータを使って、選手がリーグに入ってからプレーした年数である「career year」を計算することができます。
mutate(players, career_year = yearID - min(yearID) + 1)
#> # A tibble: 19,978 x 8
#> # Groups: playerID [1,359]
#> playerID yearID teamID G AB R H career_year
#> <chr> <int> <fct> <int> <int> <int> <int> <dbl>
#> 1 aaronha01 1954 ML1 122 468 58 131 1
#> 2 aaronha01 1955 ML1 153 602 105 189 2
#> 3 aaronha01 1956 ML1 153 609 106 200 3
#> 4 aaronha01 1957 ML1 151 615 118 198 4
#> # … with 19,974 more rows
また、入門編の例のように、z-scoreを計算することもできます。
mutate(players, G_z = (G - mean(G)) / sd(G))
#> # A tibble: 19,978 x 8
#> # Groups: playerID [1,359]
#> playerID yearID teamID G AB R H G_z
#> <chr> <int> <fct> <int> <int> <int> <int> <dbl>
#> 1 aaronha01 1954 ML1 122 468 58 131 -1.16
#> 2 aaronha01 1955 ML1 153 602 105 189 0.519
#> 3 aaronha01 1956 ML1 153 609 106 200 0.519
#> 4 aaronha01 1957 ML1 151 615 118 198 0.411
#> # … with 19,974 more rows