`dplyr`で動的変数名を使用する


168

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

アイリスからのデータ例:

library(dplyr)
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()動的名を変数名として使用するにはどうすればよいですか?


1
突然変異を主張するのではなく、それが可能かどうか尋ねています。多分それは私が知らないちょっとしたトリックだけです。別の方法がある場合は、それを聞いてみましょう。
Timm S.

私はそこだと信じて見て宇宙lazyevalパッケージは
バティスト


16
ビネットではについても触れられておらずmutate_、他の関数からはその使用方法は本当に明らかではありません。
nacnudus

回答:


191

変数名を文字値として動的に構築しているので、列名に文字値を許可する標準のdata.frameインデックスを使用して代入を行う方が理にかなっています。例えば:

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

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


dplyrバージョン> = 0.7

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)

少し前のバージョン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 <0.3

これはdplyr、質問が最初に提起されたときに存在していた古いバージョンでも可能です。それは慎重に使用する必要がありますquotesetName

# --- 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)
}

24
ありがとう、それは役に立ちます。ところで、私はいつも本当に劇的な変数を作成します。
Timm S.

27
へへ。それはおそらく私がしばらくの間作った私のお気に入りのタイプミスの1つです。残しておくと思います。
MrFlick 2014

1
do.call()おそらく、rpubs.com / hadley / do-call2のように、思ったように動作しませ。dplyrのdevバージョンのnseビネットも参照してください。
ハドリー2014

4
したがって、@ hadleyのあなたのポイントを理解できれば、do.call上記を使用してリストでdo.call("mutate")引用するように更新しましたdf。それはあなたが提案していたことですか?のlazyevalバージョンがdplyrリリースされたバージョンであるmutate_(df, .dots= setNames(list(~Petal.Width * n), varname))場合、より優れたソリューションになるでしょうか?
MrFlick 2014

1
割り当ての左側だけでなく右側にも可変列ヘッダーが必要な場合はどうなりますか?たとえば、mutate(df, !!newVar := (!!var1 + !!var2) / 2)機能しません:(
Mario Reutter

55

の新しいリリースではdplyr0.6.02017年4月まで)、割り当て:=を解除(!!)し、評価を行わないように引用符を外す()ことで変数を列名として渡すこともできます

 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)
 }   

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

identical(iris1, iris2)
#[1] TRUE

26

多くの試行錯誤の末、パターン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')

ヒントは非常にうまく機能しますが、少し問題があります。最初の列myColを(たとえば)URL に変更しmyColInitialValue、データフレームの最後にある古い列をdf新しい名前でコピーします。しかしwhich(colnames(df)=='myCol')、col#ofを送り返しmyColInitialValueます。reprexが見つからなかったので、まだ問題を書きませんでした。私の目標はのescapeパラメータですDT::datatable()。それescape=FALSEを待つのに使っています。定数を使用しても機能しませんが、DTパッケージは不良な#列を取得するようです。:)
phili_b


動的変数が原因ではないようです。(btw reprexが追加されました)
phili_b

この回答をありがとう!ここで私はそれを使用する方法の超簡単な例である:varname = sym("Petal.Width"); ggplot(iris, aes(x=!!varname)) + geom_histogram()
bdemarest

これは、!! varnameが機能しなかった式の中でうまくいきました。
daknowles

12

ここに別のバージョンがあり、それは間違いなく少し簡単です。

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

8

これrlang 0.4.0には、カーリーカーリー演算子({{}})があり、これは非常に簡単です。

library(dplyr)
library(rlang)

iris1 <- tbl_df(iris)

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

multipetal(iris1, 4)

# A tibble: 150 x 6
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species petal.4
#          <dbl>       <dbl>        <dbl>       <dbl> <fct>     <dbl>
# 1          5.1         3.5          1.4         0.2 setosa      0.8
# 2          4.9         3            1.4         0.2 setosa      0.8
# 3          4.7         3.2          1.3         0.2 setosa      0.8
# 4          4.6         3.1          1.5         0.2 setosa      0.8
# 5          5           3.6          1.4         0.2 setosa      0.8
# 6          5.4         3.9          1.7         0.4 setosa      1.6
# 7          4.6         3.4          1.4         0.3 setosa      1.2
# 8          5           3.4          1.5         0.2 setosa      0.8
# 9          4.4         2.9          1.4         0.2 setosa      0.8
#10          4.9         3.1          1.5         0.1 setosa      0.4
# … with 140 more rows

引用符付き/引用符なしの変数名を渡して、列名として割り当てることもできます。

multipetal <- function(df, name, n) {
   mutate(df, {{name}} := Petal.Width * n)
}

multipetal(iris1, temp, 3)

# A tibble: 150 x 6
#   Sepal.Length Sepal.Width Petal.Length Petal.Width Species  temp
#          <dbl>       <dbl>        <dbl>       <dbl> <fct>   <dbl>
# 1          5.1         3.5          1.4         0.2 setosa  0.6  
# 2          4.9         3            1.4         0.2 setosa  0.6  
# 3          4.7         3.2          1.3         0.2 setosa  0.6  
# 4          4.6         3.1          1.5         0.2 setosa  0.6  
# 5          5           3.6          1.4         0.2 setosa  0.6  
# 6          5.4         3.9          1.7         0.4 setosa  1.2  
# 7          4.6         3.4          1.4         0.3 setosa  0.900
# 8          5           3.4          1.5         0.2 setosa  0.6  
# 9          4.4         2.9          1.4         0.2 setosa  0.6  
#10          4.9         3.1          1.5         0.1 setosa  0.3  
# … with 140 more rows

それは同じように動作します

multipetal(iris1, "temp", 3)

4

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

文字列から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

3

私は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) 

2
+1、私はまだdplyr非インタラクティブな設定で多くを使用していますが、関数内でバリアベル入力と一緒に使用すると、非常に不格好な構文が使用されます。
Paul Hiemstra 2017

3

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アドインを使用して、いつでも同等の整然としたevalコードに変換できます。


0

別の代替方法:{}引用符内で使用すると、動的な名前を簡単に作成できます。これは他のソリューションと似ていますが、まったく同じではありません。

library(dplyr)
library(tibble)

iris <- as_tibble(iris)

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

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

私はこれが原因だと思いますdplyr 1.0.0が、確かではありません(rlang 4.7.0重要な場合もそうです)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.