dplyr および一般的なRは、列に対する操作を行うのに適しており、行に対する操作を行うのは非常に困難です。 この vignette では、rowwise()
で作成される行単位のデータフレームを中心とした dplyr のアプローチを学びます。
この vignette では、3つの一般的な使用例を紹介します。
この種の問題は for ループで簡単に解決できることが多いのですが、パイプラインに自然に収まる解決策があるといいですね。
Of course, someone has to write loops. It doesn’t have to be you. — Jenny Bryan
行単位の操作では、各グループが1つの行で構成される特殊なグループ化が必要です。これを作成するには rowwise()
を使用します。:
df <- tibble(x = 1:2, y = 3:4, z = 5:6)
df %>% rowwise()
#> # A tibble: 2 x 3
#> # Rowwise:
#> x y z
#> <int> <int> <int>
#> 1 1 3 5
#> 2 2 4 6
group_by()
と同様に、rowwise()
もそれ自体は何もしません。 ただ、他の動詞の動作を変えるだけです。 例えば,以下のコードで mutate()
の結果を比較してみてください。:
df %>% mutate(m = mean(c(x, y, z)))
#> # A tibble: 2 x 4
#> x y z m
#> <int> <int> <int> <dbl>
#> 1 1 3 5 3.5
#> 2 2 4 6 3.5
df %>% rowwise() %>% mutate(m = mean(c(x, y, z)))
#> # A tibble: 2 x 4
#> # Rowwise:
#> x y z m
#> <int> <int> <int> <dbl>
#> 1 1 3 5 3
#> 2 2 4 6 4
通常のデータフレームで mutate()
を使うと、すべての行の x
, y
, z
の平均を計算します。 これを行単位のデータフレームに適用した場合,各行の平均を計算します。
rowwise()
を呼び出す際には,オプションで “identifier” 変数を指定することができます。 これらの変数は,summarise()
を呼び出したときに保存されるので,group_by()
に渡されたグループ化変数と多少似たような動作をします。:
df <- tibble(name = c("Mara", "Hadley"), x = 1:2, y = 3:4, z = 5:6)
df %>%
rowwise() %>%
summarise(m = mean(c(x, y, z)))
#> `summarise()` has ungrouped output. You can override using the `.groups` argument.
#> # A tibble: 2 x 1
#> m
#> <dbl>
#> 1 3
#> 2 4
df %>%
rowwise(name) %>%
summarise(m = mean(c(x, y, z)))
#> `summarise()` has grouped output by 'name'. You can override using the `.groups` argument.
#> # A tibble: 2 x 2
#> # Groups: name [2]
#> name m
#> <chr> <dbl>
#> 1 Mara 3
#> 2 Hadley 4
rowwise()
はグループ化の特殊な形式なので、データフレームからグループ化を削除したい場合は、ungroup()
を呼び出してください。
dplyr::summarise()
を使うと、1列の中の行全体の値を非常に簡単に要約することができます。また、rowwise()
と組み合わせることで、1つの行の中の複数の列の値を簡単に要約することができます。 その方法を見るために、まずは小さなデータセットを作ってみましょう。:
df <- tibble(id = 1:6, w = 10:15, x = 20:25, y = 30:35, z = 40:45)
df
#> # A tibble: 6 x 5
#> id w x y z
#> <int> <int> <int> <int> <int>
#> 1 1 10 20 30 40
#> 2 2 11 21 31 41
#> 3 3 12 22 32 42
#> 4 4 13 23 33 43
#> # ... with 2 more rows
例えば、各行の w
, x
, y
, z
の和を計算したいとしましょう。まず、行単位のデータフレームを作成します。:
その後、各行に新しい列を追加するには mutate()
を、1つの要約を返すには summarise()
を使用します。:
rf %>% mutate(total = sum(c(w, x, y, z)))
#> # A tibble: 6 x 6
#> # Rowwise: id
#> id w x y z total
#> <int> <int> <int> <int> <int> <int>
#> 1 1 10 20 30 40 100
#> 2 2 11 21 31 41 104
#> 3 3 12 22 32 42 108
#> 4 4 13 23 33 43 112
#> # ... with 2 more rows
rf %>% summarise(total = sum(c(w, x, y, z)))
#> `summarise()` has grouped output by 'id'. You can override using the `.groups` argument.
#> # A tibble: 6 x 2
#> # Groups: id [6]
#> id total
#> <int> <int>
#> 1 1 100
#> 2 2 104
#> 3 3 108
#> 4 4 112
#> # ... with 2 more rows
もちろん、たくさんの変数がある場合には、すべての変数名を入力するのは面倒でしょう。 その代わりに、c_across()
を使えば、整然とした選択構文を使って、多くの変数を簡潔に選択することができます。:
rf %>% mutate(total = sum(c_across(w:z)))
#> # A tibble: 6 x 6
#> # Rowwise: id
#> id w x y z total
#> <int> <int> <int> <int> <int> <int>
#> 1 1 10 20 30 40 100
#> 2 2 11 21 31 41 104
#> 3 3 12 22 32 42 108
#> 4 4 13 23 33 43 112
#> # ... with 2 more rows
rf %>% mutate(total = sum(c_across(where(is.numeric))))
#> # A tibble: 6 x 6
#> # Rowwise: id
#> id w x y z total
#> <int> <int> <int> <int> <int> <int>
#> 1 1 10 20 30 40 100
#> 2 2 11 21 31 41 104
#> 3 3 12 22 32 42 108
#> 4 4 13 23 33 43 112
#> # ... with 2 more rows
これを列ごとの演算(詳しくは vignette("colwise")
を参照)と組み合わせることで、各列の合計に対する割合を計算することができます。:
rf %>%
mutate(total = sum(c_across(w:z))) %>%
ungroup() %>%
mutate(across(w:z, ~ . / total))
#> # A tibble: 6 x 6
#> id w x y z total
#> <int> <dbl> <dbl> <dbl> <dbl> <int>
#> 1 1 0.1 0.2 0.3 0.4 100
#> 2 2 0.106 0.202 0.298 0.394 104
#> 3 3 0.111 0.204 0.296 0.389 108
#> 4 4 0.116 0.205 0.295 0.384 112
#> # ... with 2 more rows
どのような要約関数でも rowwise()
のアプローチは有効です。 しかし、より高速な処理が必要であれば、組み込みの行単位の要約関数を探す価値があります。 このような関数は、データフレームを行ごとに分割して要約を計算し、その結果を再び結合するのではなく、データフレーム全体を操作するので、より効率的です。:
df %>% mutate(total = rowSums(across(where(is.numeric))))
#> # A tibble: 6 x 6
#> id w x y z total
#> <int> <int> <int> <int> <int> <dbl>
#> 1 1 10 20 30 40 101
#> 2 2 11 21 31 41 106
#> 3 3 12 22 32 42 111
#> 4 4 13 23 33 43 116
#> # ... with 2 more rows
df %>% mutate(mean = rowMeans(across(where(is.numeric))))
#> # A tibble: 6 x 6
#> id w x y z mean
#> <int> <int> <int> <int> <int> <dbl>
#> 1 1 10 20 30 40 20.2
#> 2 2 11 21 31 41 21.2
#> 3 3 12 22 32 42 22.2
#> 4 4 13 23 33 43 23.2
#> # ... with 2 more rows
注: ここでは、rf
ではなくdf
を、c_across()
ではなく across()
を使用しています。 なぜなら、rowMeans
とrowSum
は複数行のデータフレームを入力とするからです。:
rowwise()
操作は、リスト列がある場合に自然に組み合わされます。 これにより、明示的なループや apply()
や purrr::map()
系の関数を回避することができます。
このようなデータフレームがあり、各要素の長さを数えたいとします。:
length()
を呼び出してみましょう。;
df %>% mutate(l = length(x))
#> # A tibble: 3 x 2
#> x l
#> <list> <int>
#> 1 <dbl [1]> 3
#> 2 <int [2]> 3
#> 3 <int [3]> 3
しかし、これは列の長さを返すのであって、個々の値の長さを返すのではありません。 Rのドキュメントが好きな方なら、この目的のための base R 関数がすでにあることをご存知かもしれません。:
df %>% mutate(l = lengths(x))
#> # A tibble: 3 x 2
#> x l
#> <list> <int>
#> 1 <dbl [1]> 1
#> 2 <int [2]> 2
#> 3 <int [3]> 3
また、経験豊富なRプログラマーであれば、sapply()
や vapply()
、あるいは purrr map()
関数の一つを使って、リストの各要素に関数を適用する方法を知っているかもしれません。:
df %>% mutate(l = sapply(x, length))
#> # A tibble: 3 x 2
#> x l
#> <list> <int>
#> 1 <dbl [1]> 1
#> 2 <int [2]> 2
#> 3 <int [3]> 3
df %>% mutate(l = purrr::map_int(x, length))
#> # A tibble: 3 x 2
#> x l
#> <list> <int>
#> 1 <dbl [1]> 1
#> 2 <int [2]> 2
#> 3 <int [3]> 3
しかし、length(x)
と書けば、dplyr が x
の中の要素の長さを計算したいことを理解してくれたらいいと思いませんか? このページをご覧になっているあなたは、すでにその答えを想像しているかもしれません。:
先に進む前に、この機能を実現するためのマジックについて簡単に説明しておきたいと思います。 一般的にはあまり気にする必要はありませんが、何かあったときのために知っておくと便利です。
各グループがたまたま1行であるグループ化されたデータフレームと、各グループが常に1行である行単位のデータフレームには、重要な違いがあります。 次の2つのデータフレームを見てみましょう。:
y
のいくつかの特性を計算してみると、結果が違ってくることに気づくでしょう。:
gf %>% mutate(type = typeof(y), length = length(y))
#> # A tibble: 2 x 4
#> # Groups: g [2]
#> g y type length
#> <int> <list> <chr> <int>
#> 1 1 <int [3]> list 1
#> 2 2 <chr [1]> list 1
rf %>% mutate(type = typeof(y), length = length(y))
#> # A tibble: 2 x 4
#> # Rowwise: g
#> g y type length
#> <int> <list> <chr> <int>
#> 1 1 <int [3]> integer 3
#> 2 2 <chr [1]> character 1
これらの重要な違いは、mutate()
が列をスライスして length(y)
に渡すときに、グループ化されたミューテートは[
を使用し、行単位のミューテートは [[
を使用することです。 次のコードは、for ループを使った場合の違いを示しています。:
# grouped
out1 <- integer(2)
for (i in 1:2) {
out1[[i]] <- length(df$y[i])
}
out1
#> [1] 1 1
# rowwise
out2 <- integer(2)
for (i in 1:2) {
out2[[i]] <- length(df$y[[i]])
}
out2
#> [1] 3 1
なお、このマジックは既存の列を参照する場合にのみ適用され、新しい行を作成する場合には適用されません。 これは混乱を招く可能性がありますが、エラーメッセージのヒントを考えると、これが最も悪い解決策であると確信しています。:
gf %>% mutate(y2 = y)
#> # A tibble: 2 x 3
#> # Groups: g [2]
#> g y y2
#> <int> <list> <list>
#> 1 1 <int [3]> <int [3]>
#> 2 2 <chr [1]> <chr [1]>
rf %>% mutate(y2 = y)
#> Error: Problem with `mutate()` input `y2`.
#> x Input `y2` can't be recycled to size 1.
#> i Input `y2` is `y`.
#> i Input `y2` must be size 1, not 3.
#> i Did you mean: `y2 = list(y)` ?
#> i The error occurred in row 1.
rf %>% mutate(y2 = list(y))
#> # A tibble: 2 x 3
#> # Rowwise: g
#> g y y2
#> <int> <list> <list>
#> 1 1 <int [3]> <int [3]>
#> 2 2 <chr [1]> <chr [1]>
rowwise()
データフレームを使うと、様々なモデリングの問題を非常にエレガントな方法で解決することができます。 まず、ネストしたデータフレームを作ることから始めます。:
by_cyl <- mtcars %>% nest_by(cyl)
by_cyl
#> # A tibble: 3 x 2
#> # Rowwise: cyl
#> cyl data
#> <dbl> <list>
#> 1 4 <tibble [11 x 10]>
#> 2 6 <tibble [7 x 10]>
#> 3 8 <tibble [14 x 10]>
これは通常の group_by()
の出力とは少し違います。データの構造を明らかに変えています。 3つの行(各グループに1つずつ)があり、そのグループのデータを格納するリストコル data
があります。 また、出力が rowwise()
であることにも注目してください。 これは、データフレームのリストをより簡単に扱うために重要です。
1行に1つのデータフレームがあれば、1行に1つのモデルを作るのは簡単です。:
mods <- by_cyl %>% mutate(mod = list(lm(mpg ~ wt, data = data)))
mods
#> # A tibble: 3 x 3
#> # Rowwise: cyl
#> cyl data mod
#> <dbl> <list> <list>
#> 1 4 <tibble [11 x 10]> <lm>
#> 2 6 <tibble [7 x 10]> <lm>
#> 3 8 <tibble [14 x 10]> <lm>
そして、それを補うために、1列に1セットの予測をする。:
mods <- mods %>% mutate(pred = list(predict(mod, data)))
mods
#> # A tibble: 3 x 4
#> # Rowwise: cyl
#> cyl data mod pred
#> <dbl> <list> <list> <list>
#> 1 4 <tibble [11 x 10]> <lm> <dbl [11]>
#> 2 6 <tibble [7 x 10]> <lm> <dbl [7]>
#> 3 8 <tibble [14 x 10]> <lm> <dbl [14]>
そして、そのモデルを様々な方法でまとめることができます。:
mods %>% summarise(rmse = sqrt(mean((pred - data$mpg) ^ 2)))
#> `summarise()` has grouped output by 'cyl'. You can override using the `.groups` argument.
#> # A tibble: 3 x 2
#> # Groups: cyl [3]
#> cyl rmse
#> <dbl> <dbl>
#> 1 4 3.01
#> 2 6 0.985
#> 3 8 1.87
mods %>% summarise(rsq = summary(mod)$r.squared)
#> `summarise()` has grouped output by 'cyl'. You can override using the `.groups` argument.
#> # A tibble: 3 x 2
#> # Groups: cyl [3]
#> cyl rsq
#> <dbl> <dbl>
#> 1 4 0.509
#> 2 6 0.465
#> 3 8 0.423
mods %>% summarise(broom::glance(mod))
#> `summarise()` has grouped output by 'cyl'. You can override using the `.groups` argument.
#> # A tibble: 3 x 13
#> # Groups: cyl [3]
#> cyl r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 4 0.509 0.454 3.33 9.32 0.0137 1 -27.7 61.5 62.7
#> 2 6 0.465 0.357 1.17 4.34 0.0918 1 -9.83 25.7 25.5
#> 3 8 0.423 0.375 2.02 8.80 0.0118 1 -28.7 63.3 65.2
#> # ... with 3 more variables: deviance <dbl>, df.residual <int>, nobs <int>
また、各モデルのパラメータにも簡単にアクセスできます。:
mods %>% summarise(broom::tidy(mod))
#> `summarise()` has grouped output by 'cyl'. You can override using the `.groups` argument.
#> # A tibble: 6 x 6
#> # Groups: cyl [3]
#> cyl term estimate std.error statistic p.value
#> <dbl> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 4 (Intercept) 39.6 4.35 9.10 0.00000777
#> 2 4 wt -5.65 1.85 -3.05 0.0137
#> 3 6 (Intercept) 28.4 4.18 6.79 0.00105
#> 4 6 wt -2.78 1.33 -2.08 0.0918
#> # ... with 2 more rows
rowwise()
は、長さ1のベクトルを返す関数(別名、要約関数)だけでなく、結果がリストであればどんな関数でも動作します。 つまり、rowwise()
や mutate()
は、引数を変えて何度も関数を呼び出し、その出力を入力と一緒に保存するエレガントな方法を提供します。
この方法は、シミュレーションを行う際に、シミュレーション値を生成したパラメータと一緒に保存することができるので、特にエレガントな方法だと思います。 例えば、一様分布からの3つのサンプルの特性を表す次のようなデータフレームがあるとします。
これらのパラメータを runif()
に与えるには,rowwise()
や mutate()
を用いるとよい。
df %>%
rowwise() %>%
mutate(data = list(runif(n, min, max)))
#> # A tibble: 3 x 4
#> # Rowwise:
#> n min max data
#> <dbl> <dbl> <dbl> <list>
#> 1 1 0 1 <dbl [1]>
#> 2 2 10 100 <dbl [2]>
#> 3 3 100 1000 <dbl [3]>
runif()
は複数の値を返し、mutate()
は長さ1の値を返さなければなりません。 list()
は、各行が複数の値を含むリストであるリスト列を得ることを意味します。 もし、list()
を使い忘れた場合は、dplyr がヒントを与えてくれます。
df %>%
rowwise() %>%
mutate(data = runif(n, min, max))
#> Error: Problem with `mutate()` input `data`.
#> x Input `data` can't be recycled to size 1.
#> i Input `data` is `runif(n, min, max)`.
#> i Input `data` must be size 1, not 2.
#> i Did you mean: `data = list(runif(n, min, max))` ?
#> i The error occurred in row 2.
入力の組み合わせごとに関数を呼び出したい場合はどうすればよいでしょうか。 expand.grid()
(または tidyr::expand_grid()
) を使ってデータフレームを生成し、上記と同じパターンを繰り返すことができます。
df <- expand.grid(mean = c(-1, 0, 1), sd = c(1, 10, 100))
df %>%
rowwise() %>%
mutate(data = list(rnorm(10, mean, sd)))
#> # A tibble: 9 x 3
#> # Rowwise:
#> mean sd data
#> <dbl> <dbl> <list>
#> 1 -1 1 <dbl [10]>
#> 2 0 1 <dbl [10]>
#> 3 1 1 <dbl [10]>
#> 4 -1 10 <dbl [10]>
#> # ... with 5 more rows
より複雑な問題では、呼び出される関数を変化させることも考えられます。 この方法では、入力テーブルの列の規則性が低くなるため、少し不便になる傾向があります。 しかし、それでも可能であり、do.call()
を使うのは自然なことです。
df <- tribble(
~rng, ~params,
"runif", list(n = 10),
"rnorm", list(n = 20),
"rpois", list(n = 10, lambda = 5),
) %>%
rowwise()
df %>%
mutate(data = list(do.call(rng, params)))
#> # A tibble: 3 x 3
#> # Rowwise:
#> rng params data
#> <chr> <list> <list>
#> 1 runif <named list [1]> <dbl [10]>
#> 2 rnorm <named list [1]> <dbl [20]>
#> 3 rpois <named list [2]> <int [10]>
rowwise()
rowwise()
も長い間疑問視されていましたが、これは各行の複数の変数の合計を計算するネイティブな機能を必要としている人がどれだけいるのかを理解していなかったことも理由の一つです。代替案として、purrr の map()
関数を使って行単位の操作を行うことを推奨しました。 しかし、これは、変化する引数の数と結果のタイプに基づいてマップ関数を選ぶ必要があり、purrr 関数に関するかなりの知識が必要であったため、困難でした。
また、rowwise()
に抵抗があったのは、list()
の結果を自動的に do()
にするのと同じように、[
と [[
を自動的に切り替えるのは魔法のようすぎると感じていたからです。 今では、行単位のマジックは良いマジックだと自分を納得させています。 その理由の一つは、多くの人が [
と [[
の区別が不可解だと感じており、rowwise()
はそのことを考える必要がないからです。
rowwise()
が明らかに有用であるため、もはや疑問視されておらず、長期的に存在することが期待されています。
do()
というのも、他のdplyrの動詞とは似ても似つかないからです。 これには2つの主な動作モードがありました。:
引数名なし:データフレームを入出力する関数を呼び出すことができます。 データフレームを入出力する関数を呼び出す際には、「現在の」グループを参照するために .
を使用します。例えば,次のコードは 次のコードは、各グループの最初の行を取得します。:
mtcars %>%
group_by(cyl) %>%
do(head(., 1))
#> # A tibble: 3 x 11
#> # Groups: cyl [3]
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 22.8 4 108 93 3.85 2.32 18.6 1 1 4 1
#> 2 21 6 160 110 3.9 2.62 16.5 0 1 4 4
#> 3 18.7 8 360 175 3.15 3.44 17.0 0 0 3 2
これは cur_data()
に取って代わられ、より寛容な summarise()
では複数の列と複数の行を作成できるようになっています。
mtcars %>%
group_by(cyl) %>%
summarise(head(cur_data(), 1))
#> # A tibble: 3 x 11
#> cyl mpg disp hp drat wt qsec vs am gear carb
#> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 4 22.8 108 93 3.85 2.32 18.6 1 1 4 1
#> 2 6 21 160 110 3.9 2.62 16.5 0 1 4 4
#> 3 8 18.7 360 175 3.15 3.44 17.0 0 0 3 2
引数付きの場合は、mutate()
のように動作しますが、リストのすべての要素を自動的にラップします。:
mtcars %>%
group_by(cyl) %>%
do(nrows = nrow(.))
#> # A tibble: 3 x 2
#> # Rowwise:
#> cyl nrows
#> <dbl> <list>
#> 1 4 <int [1]>
#> 2 6 <int [1]>
#> 3 8 <int [1]>
今では、この動作はあまりにも魔法的であり、あまり有用ではないと考えています。 そして、summarise()
や cur_data()
で置き換えることができます。
mtcars %>%
group_by(cyl) %>%
summarise(nrows = nrow(cur_data()))
#> # A tibble: 3 x 2
#> cyl nrows
#> <dbl> <int>
#> 1 4 11
#> 2 6 7
#> 3 8 14
必要であれば(ここでは違いますが)、結果を自分でリストにまとめることもできます。
cur_data()
/across()
が追加され、summarise()
のスコープが大きくなったことで、do()
が不要になったので、これを廃止します。