ほとんどの dplyr の動詞は、何らかの形で Tidy評価 を使用します。 Tidy評価は、tidyverse 全体で使用される非標準的な評価の特別なタイプです。 dplyr には2つの基本的な形式があります。
arrange()
, count()
, filter()
, group_by()
, mutate()
, and summarise()
use data masking so that you can use data variables as if they were variables in the environment (i.e. you write my_variable
not df$myvariable
).
across()
, relocate()
, rename()
, select()
, and pull()
use tidy selection so you can easily choose variables based on their position, name, or type (e.g. starts_with("x")
or is.numeric
).
関数の引数がデータマスキングやTidy選択を使用しているかどうかを判断するには、ドキュメントを見てください。 引数リストには、<data-masking>
または <tidy-select>
と表示されています。
データマスキングやティディセレクションは、インタラクティブなデータ探索を高速かつスムーズに行うことができますが、for ループや関数の中など、間接的に使用しようとすると、新たな課題が生じます。 この vignette では、これらの課題を克服する方法を紹介します。 まず、データマスキングと Tidy 選択の基本を説明し、それらを間接的に使用する方法について説明し、よくある問題を解決するためのいくつかのレシピを紹介します。
この vignette では、Tidy 評価を使った効果的なプログラマーになるための最低限の知識を得ることができます。 基礎となる理論や、非標準的な評価との違いを正確に知りたい場合は、Advanced R の Metaprogramming の章を読むことをお勧めします。
library(dplyr)
データマスキングを行うと、入力が少なくて済むので、データ操作が速くなります。 ほとんどの(すべてではありませんが1)R の基本関数では,変数を $
で参照する必要があり,データフレームの名前を何度も繰り返すコードになります。
$homeworld == "Naboo" & starwars$species == "Human", ,] starwars[starwars
このコードの dplyr 版は、データマスキングにより starwars
と1回入力するだけで済むため、より簡潔なコードになっています。
%>% filter(homeworld == "Naboo", species == "Human") starwars
データマスキングの重要な考え方は、「変数」という言葉の2つの異なる意味の境界線を曖昧にすることです。:
env-variables は、環境に常駐する「プログラミング」変数です。 これらは通常、<-
で作成されます。
data-variables は、データフレーム内に存在する「統計的」な変数です。 通常、データファイル(.csv
、.xls
など)から取得したり、既存の変数を操作して作成したりします。
これらの定義をもう少し具体的に説明するために、次のようなコードを考えてみましょう。:
<- data.frame(x = runif(3), y = runif(3))
df $x
df#> [1] 0.08075014 0.83433304 0.60076089
x
と y
という2つのデータ変数を含む環境変数dfを作成し、$
を使って環境変数 df
からデータ変数 x
を抽出します。
このように「変数」の意味を曖昧にすることで、データ変数を接頭辞なしでそのまま参照することができるので、インタラクティブなデータ分析にはとても良い機能だと思います。 多くの新しいRユーザーは diamonds[x == 0 | y == 0, ]
と書こうとするので、これはかなり直感的なことだと思います。
残念ながら、このメリットは無料ではありません。このツールを使ってプログラミングを始めると、その区別に悩まされることになります。 今まで考えたことがなかったので、頭が新しい概念やカテゴリーを覚えるのに時間がかかります。 しかし、「変数」という概念を data-variable と env-variable に分けて考えてみると、かなりわかりやすく使えるようになると思います。
データマスキングを使用した関数を使ったプログラミングの主な課題は、データ変数名を直接入力するのではなく、env-variable からデータ変数を取得したいというような、間接的な操作を導入する場合に発生します。 これには主に2つのケースがあります。
When you have the data-variable in a function argument (i.e. an env-variable that holds a promise2), you need to embrace the argument by surrounding it in doubled braces, like filter(df, {{ var }})
.
The following function uses embracing to create a wrapper around summarise()
that computes the minimum and maximum values of a variable, as well as the number of observations that were summarised:
<- function(data, var) {
var_summary %>%
data summarise(n = n(), min = min({{ var }}), max = max({{ var }}))
}%>%
mtcars group_by(cyl) %>%
var_summary(mpg)
When you have an env-variable that is a character vector, you need to index into the .data
pronoun with [[
, like summarise(df, mean = mean(.data[[var]]))
.
The following example uses .data
to count the number of unique values in each variable of mtcars
:
for (var in names(mtcars)) {
%>% count(.data[[var]]) %>% print()
mtcars }
Note that .data
is not a data frame; it’s a special construct, a pronoun, that allows you to access the current variables either directly, with .data$x
or indirectly with .data[[var]]
. Don’t expect other functions to work with it.
データマスキングは、データセット内の値の計算を容易にします。 Tidy select は、データセットの列を簡単に扱うことができる補完的なツールです。
Tidyselect を使用するすべての関数の根底には tidyselect パッケージがあります。 これは、名前、位置、またはタイプで列を選択することを容易にする小型のドメイン固有言語を提供します。 たとえば
select(df, 1)
selects the first column; select(df, last_col())
selects the last column.
select(df, c(a, b, c))
selects columns a
, b
, and c
.
select(df, starts_with("a"))
selects all columns whose name starts with “a”; select(df, ends_with("z"))
selects all columns whose name ends with “z”.
select(df, where(is.numeric))
selects all numeric columns.
詳細は、?dplyr_tidy_select
で確認できます。
データマスキングと同様に、Tidy Selectは一般的なタスクを容易にしますが、その代償として一般的でないタスクを困難にします。 列の仕様を中間変数に格納してTidy Selectを間接的に使用したい場合は、いくつかの新しいツールを学ぶ必要があります。 繰り返しになりますが、インダイレクトには2つの形式があります。
When you have the data-variable in an env-variable that is a function argument, you use the same technique as data masking: you embrace the argument by surrounding it in doubled braces.
The following function summarises a data frame by computing the mean of all variables selected by the user:
<- function(data, vars) {
summarise_mean %>% summarise(n = n(), across({{ vars }}, mean))
data
}%>%
mtcars group_by(cyl) %>%
summarise_mean(where(is.numeric))
When you have an env-variable that is a character vector, you need to use all_of()
or any_of()
depending on whether you want the function to error if a variable is not found.
The following code uses all_of()
to select all of the variables found in a character vector; then !
plus all_of()
to select all of the variables not found in a character vector:
<- c("mpg", "vs")
vars %>% select(all_of(vars))
mtcars %>% select(!all_of(vars)) mtcars
以下の例は、よくある問題を解決するためのものです。 基本的なアイデアを理解していただくために、最小限のコードを示しています。 実際の問題では、より多くのコードや複数の技術を組み合わせる必要があります。
ドキュメントを確認すると、.data
はデータマスキングや Tidy Select を使用していないことがわかります。 つまり、関数内で特別なことをする必要はありません。
<- function(data) {
mutate_y mutate(data, y = a + x)
}
R CMD check
NOTE
を削除以下のようにパッケージを書いていて、データ変数を使う関数があったとします。
<- function(data) {
my_summary_function %>%
data filter(x > 0) %>%
group_by(grp) %>%
summarise(y = mean(y), n = n())
}
以下のように、R CMD CHECK
NOTE
を得ることができます。
N checking R code for possible problems
my_summary_function: no visible binding for global variable ‘x’, ‘grp’, ‘y’
Undefined global functions or variables:
x grp y
.data$var
を使い、.data
を rlang パッケージ(Tidy評価を実装する基本パッケージ)のソースからインポートすることで、これを解消することができます。
#' @importFrom rlang .data
<- function(data) {
my_summary_function %>%
data filter(.data$x > 0) %>%
group_by(.data$grp) %>%
summarise(y = mean(.data$y), n = n())
}
データマスキングや tidy Select を使用する引数に渡される式をユーザーに提供したい場合は、その引数を受け入れてください。
<- function(data, group_var) {
my_summarise %>%
data group_by({{ group_var }}) %>%
summarise(mean = mean(mass))
}
これは、ユーザーが提供する1つの表現を複数の場所で使用したい場合に、わかりやすく一般化します。
<- function(data, expr) {
my_summarise2 %>% summarise(
data mean = mean({{ expr }}),
sum = sum({{ expr }}),
n = n()
) }
ユーザーに複数の表現を提供してもらいたい場合は、それぞれの表現を受け入れます。
<- function(data, mean_var, sd_var) {
my_summarise3 %>%
data summarise(mean = mean({{ mean_var }}), sd = sd({{ sd_var }}))
}
変数名を出力に使用したい場合は、グルー構文を :=
と組み合わせて使用します。
<- function(data, expr) {
my_summarise4 %>% summarise(
data "mean_{{expr}}" := mean({{ expr }}),
"sum_{{expr}}" := sum({{ expr }}),
"n_{{expr}}" := n()
)
}<- function(data, mean_var, sd_var) {
my_summarise5 %>%
data summarise(
"mean_{{mean_var}}" := mean({{ mean_var }}),
"sd_{{sd_var}}" := sd({{ sd_var }})
) }
ユーザーが提供する任意の数の式を取りたい場合は、...
を使用します。 この機能は、group_by()
や mutate()
のように、パイプラインの一部分をユーザーが完全に制御できるようにする場合によく使用されます。
<- function(.data, ...) {
my_summarise %>%
.data group_by(...) %>%
summarise(mass = mean(mass, na.rm = TRUE), height = mean(height, na.rm = TRUE))
}
%>% my_summarise(homeworld)
starwars #> # A tibble: 49 x 3
#> homeworld mass height
#> <chr> <dbl> <dbl>
#> 1 Alderaan 64 176.
#> 2 Aleen Minor 15 79
#> 3 Bespin 79 175
#> 4 Bestine IV 110 180
#> # … with 45 more rows
%>% my_summarise(sex, gender)
starwars #> `summarise()` has grouped output by 'sex'. You can override using the `.groups` argument.
#> # A tibble: 6 x 4
#> # Groups: sex [5]
#> sex gender mass height
#> <chr> <chr> <dbl> <dbl>
#> 1 female feminine 54.7 169.
#> 2 hermaphroditic masculine 1358 175
#> 3 male masculine 81.0 179.
#> 4 none feminine NaN 96
#> # … with 2 more rows
このように ...
を使用する場合は、引数が衝突する可能性を減らすために、他の引数が .
で始まることを確認してください。 詳細はhttps://design.tidyverse.org/dots-prefix.htmlを参照してください。
ユーザーが提供したデータ変数を変換したい場合は、 across()
を使用します。
<- function(data, summary_vars) {
my_summarise %>%
data summarise(across({{ summary_vars }}, ~ mean(., na.rm = TRUE)))
}%>%
starwars group_by(species) %>%
my_summarise(c(mass, height))
#> # A tibble: 38 x 3
#> species mass height
#> <chr> <dbl> <dbl>
#> 1 Aleena 15 79
#> 2 Besalisk 102 198
#> 3 Cerean 82 198
#> 4 Chagrian NaN 196
#> # … with 34 more rows
これと同じ考え方で、以下のように複数の入力データ・変数のセットにも使用できます。
<- function(data, group_var, summarise_var) {
my_summarise %>%
data group_by(across({{ group_var }})) %>%
summarise(across({{ summarise_var }}, mean))
}
across()
の .names
引数を使って、出力の名前を制御します。
<- function(data, group_var, summarise_var) {
my_summarise %>%
data group_by(across({{ group_var }})) %>%
summarise(across({{ summarise_var }}, mean, .names = "mean_{.col}"))
}
変数名の文字ベクトルがあり、それらを for ループで操作したい場合は、特別な代名詞 .data
にインデックスを付けます。
for (var in names(mtcars)) {
%>% count(.data[[var]]) %>% print()
mtcars }
この手法は、base R apply()
ファミリーや purrr map()
ファミリーのような for ループの代替でも同じように使えます。
%>%
mtcars names() %>%
::map(~ count(mtcars, .data[[.x]])) purrr
Shiny の入力コントロールは文字のベクターを返すことが多いので、上記と同じ方法である .data[[input$var]]
を使うことができます。
library(shiny)
<- fluidPage(
ui selectInput("var", "Variable", choices = names(diamonds)),
tableOutput("output")
)<- function(input, output, session) {
server <- reactive(filter(diamonds, .data[[input$var]] > 0))
data $output <- renderTable(head(data()))
output }
詳細や事例については https://mastering-shiny.org/action-tidy.html をご覧ください。
dplyr の filter()
は、base R の subset()
にヒントを得ています。 subset()
はデータのマスキングを行いますが、整然とした評価は行わないため、この章で説明するテクニックは適用されません。↩︎
Rでは、引数は遅延的に評価されます。つまり、使用しようとするまで、引数は値を保持せず、値を計算する方法を記述した__promise__を保持するだけです。 詳しくは、以下ををご覧ください。 https://adv-r.hadley.nz/functions.html#lazy-evaluation↩︎