web-dev-qa-db-ja.com

dplyr-mutate:動的変数名を使用

dplyr'smutate()を使用して、データフレームに複数の新しい列を作成します。列名とその内容は動的に生成される必要があります。

アイリスのデータ例:

require(dplyr)
data(iris)
iris <- tbl_df(iris)

Petal.Width変数から新しい列を変更する関数を作成しました:

multipetal <- function(df, n) {
    varname <- paste("petal", n , sep=".")
    df <- mutate(df, varname = Petal.Width * n)  ## problem arises here
    df
}

次に、列を作成するループを作成します。

for(i in 2:5) {
    iris <- multipetal(df=iris, n=i)
}

ただし、mutateはvarnameをリテラル変数名と見なしているため、ループは4つ(petal.2-petal.5と呼ばれる)ではなく、1つの新しい変数(varnameと呼ばれる)のみを作成します。

mutate()を取得して、動的名を変数名として使用するにはどうすればよいですか?

116
Timm S.

変数名を文字値として劇的に構築しているため、列名に文字値を使用できる標準のdata.frameインデックスを使用して割り当てを行う方が合理的です。例えば:

multipetal <- function(df, n) {
    varname <- paste("petal", n , sep=".")
    df[[varname]] <- with(df, Petal.Width * n)
    df
}

mutate関数を使用すると、名前付きパラメーターを使用して新しい列に簡単に名前を付けることができます。ただし、コマンドを入力するときに名前がわかっていることを前提としています。列名を動的に指定する場合は、名前付き引数も作成する必要があります。

最新バージョンのdplyr(0.7)では、:=を使用してパラメーター名を動的に割り当てることでこれを行います。次のように関数を記述できます。

# --- dplyr version 0.7+---
multipetal <- function(df, n) {
    varname <- paste("petal", n , sep=".")
    mutate(df, !!varname := Petal.Width * n)
}

詳細については、vignette("programming", "dplyr")から入手可能なドキュメントを参照してください。

Dplyrの少し前のバージョン(> = 0.3 <0.7)では、多くの関数に代わる「標準評価」の使用を推奨していました。詳細については、非標準の評価ビネット(vignette("nse"))を参照してください。

したがって、ここでの答えは、mutate_()ではなくmutate()を使用し、実行することです。

# --- dplyr version 0.3-0.5---
multipetal <- function(df, n) {
    varname <- paste("petal", n , sep=".")
    varval <- lazyeval::interp(~Petal.Width * n, n=n)
    mutate_(df, .dots= setNames(list(varval), varname))
}

dplyrの古いバージョン

これは、質問が最初に提示されたときに存在していたdplyrの古いバージョンでも可能です。 quoteおよびsetNameを慎重に使用する必要があります。

# --- dplyr versions < 0.3 ---
multipetal <- function(df, n) {
    varname <- paste("petal", n , sep=".")
    pp <- c(quote(df), setNames(list(quote(Petal.Width * n)), varname))
    do.call("mutate", pp)
}
128
MrFlick

dplyr(2017年4月に待機中の0.6.0)の新しいリリースでは、割り当て(:=)を実行し、変数を列名として引用符なし(!!)で渡して評価しないようにすることもできます

 library(dplyr)
 multipetalN <- function(df, n){
      varname <- paste0("petal.", n)
      df %>%
         mutate(!!varname := Petal.Width * n)
 }

 data(iris)
 iris1 <- tbl_df(iris)
 iris2 <- tbl_df(iris)
 for(i in 2:5) {
     iris2 <- multipetalN(df=iris2, n=i)
 }   

「iris1」に適用された@MrFlickのmultipetalに基づいて出力を確認する

identical(iris1, iris2)
#[1] TRUE
45
akrun

ここに別のバージョンがありますが、それはおそらくもう少し簡単です。

multipetal <- function(df, n) {
    varname <- paste("petal", n, sep=".")
    df<-mutate_(df, .dots=setNames(paste0("Petal.Width*",n), varname))
    df
}

for(i in 2:5) {
    iris <- multipetal(df=iris, n=i)
}

> head(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species petal.2 petal.3 petal.4 petal.5
1          5.1         3.5          1.4         0.2  setosa     0.4     0.6     0.8       1
2          4.9         3.0          1.4         0.2  setosa     0.4     0.6     0.8       1
3          4.7         3.2          1.3         0.2  setosa     0.4     0.6     0.8       1
4          4.6         3.1          1.5         0.2  setosa     0.4     0.6     0.8       1
5          5.0         3.6          1.4         0.2  setosa     0.4     0.6     0.8       1
6          5.4         3.9          1.7         0.4  setosa     0.8     1.2     1.6       2
12
user2946432

多くの試行錯誤の後、パターンUQ(rlang::sym("some string here")))が文字列とdplyr動詞を操作するのに本当に役立つことがわかりました。多くの驚くべき状況で機能するようです。

mutateの例を次に示します。 2つの列を加算する関数を作成し、両方の列名を文字列として関数に渡します。これを行うには、代入演算子:=と共にこのパターンを使用できます。

## Take column `name1`, add it to column `name2`, and call the result `new_name`
mutate_values <- function(new_name, name1, name2){
  mtcars %>% 
    mutate(UQ(rlang::sym(new_name)) :=  UQ(rlang::sym(name1)) +  UQ(rlang::sym(name2)))
}
mutate_values('test', 'mpg', 'cyl')

このパターンは、他のdplyr関数でも機能します。 filterは次のとおりです。

## filter a column by a value 
filter_values <- function(name, value){
  mtcars %>% 
    filter(UQ(rlang::sym(name)) != value)
}
filter_values('gear', 4)

またはarrange

## transform a variable and then sort by it 
arrange_values <- function(name, transform){
  mtcars %>% 
    arrange(UQ(rlang::sym(name)) %>%  UQ(rlang::sym(transform)))
}
arrange_values('mpg', 'sin')

selectの場合、パターンを使用する必要はありません。代わりに、!!を使用できます。

## select a column 
select_name <- function(name){
  mtcars %>% 
    select(!!name)
}
select_name('mpg')
10
Tom Roth

また、回答を検索するときにこのエントリに来たので、これを少し増やす回答を追加していますが、これにはほとんど必要なものがありましたが、@ MrFlikの回答とR lazyevalビネット。

文字列からDateオブジェクトに変換するデータフレームと列名のベクトル(文字列として)を取得できる関数を作成したかったのです。 as.Date()が文字列である引数を取り、それを列に変換する方法がわからなかったので、以下に示すようにしました。

以下は、SE mutate(mutate_())と.dots引数を使用してこれを行った方法です。これを改善する批判を歓迎します。

library(dplyr)

dat <- data.frame(a="leave alone",
                  dt="2015-08-03 00:00:00",
                  dt2="2015-01-20 00:00:00")

# This function takes a dataframe and list of column names
# that have strings that need to be
# converted to dates in the data frame
convertSelectDates <- function(df, dtnames=character(0)) {
    for (col in dtnames) {
        varval <- sprintf("as.Date(%s)", col)
        df <- df %>% mutate_(.dots= setNames(list(varval), col))
    }
    return(df)
}

dat <- convertSelectDates(dat, c("dt", "dt2"))
dat %>% str
4
mpettis

パッケージ friendlyeval を楽しむことができます。これは、新しい/カジュアルなdplyrユーザー向けの簡素化された整頓されたeval APIとドキュメントを提供します。

mutateが列名として処理する文字列を作成しています。したがって、friendlyevalを使用すると、次のように記述できます。

multipetal <- function(df, n) {
  varname <- paste("petal", n , sep=".")
  df <- mutate(df, !!treat_string_as_col(varname) := Petal.Width * n)
  df
}

for(i in 2:5) {
  iris <- multipetal(df=iris, n=i)
}

内部でrlangをチェックするvarname関数を呼び出すのは、列名として有効です。

friendlyevalコードは、RStudioアドインを使用して、いつでも同等の単純な評価コードに変換できます。

1
MilesMcBain

私はインタラクティブな使用のためにdplyrを使用することを楽しんでいますが、lazyeval :: interp()、setNamesなどの回避策を使用するにはフープを通過する必要があるため、dplyrを使用してこれを行うのは非常に難しいと思います。

これはベースRを使用したより単純なバージョンです。少なくとも私にとっては、関数内にループを置く方が直感的で、@ MrFlicksのソリューションを拡張したものです。

multipetal <- function(df, n) {
   for (i in 1:n){
      varname <- paste("petal", i , sep=".")
      df[[varname]] <- with(df, Petal.Width * i)
   }
   df
}
multipetal(iris, 3) 
1
hackR