2016-04-17 30 views
6

私は視覚化のためにほとんどggplot2を使用します。通常は、プロット を対話的に設計します(つまり、NSEを使用する生のggplot2コード)が、最終的には のプロットを受け取る関数にそのコードをラップアップしてしまいます。そして、これはいつも悪夢の少しです。 関数内のggplot2の遅延評価

したがって、一般的な状況は次のようになります。私はいくつかのデータを持っており、私は のプロットを作成しています(この場合は、のデータセットを とした非常に簡単な例です)。

library(ggplot2) 
data(mpg) 

ggplot(data = mpg, 
     mapping = aes(x = class, y = hwy)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 


そして、私はプロットの設計が終了したら、私は通常、など 異なる変数やデータ、のためにそれを使用したいので、私は、引数として、プロットのため データと変数を受け取る関数を作成。しかしNSEのために、それは関数ヘッダのために であり、関数引数のために変数 をコピー/ペーストして置き換えるほど簡単ではありません。以下のように、それはうまくいかないでしょう。

mpg <- mpg 
plotfn <- function(data, xvar, yvar){ 
    ggplot(data = data, 
      mapping = aes(x = xvar, y = yvar)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 
} 
plotfn(mpg, class, hwy) # Can't find object 

## Don't know how to automatically pick scale for object of type function. Defaulting to continuous. 

## Warning: restarting interrupted promise evaluation 

## Error in eval(expr, envir, enclos): object 'hwy' not found 

plotfn(mpg, "class", "hwy") # 


だから私はaes_string NSEを使用していますaesのintead(この例では、それはかなり簡単ですが、より複雑なプロットの を使用して、例えば、戻って、コードを修正する必要があり、多くの変換とレイヤーで、 これは悪夢になります)。

plotfn <- function(data, xvar, yvar){ 
    ggplot(data = data, 
      mapping = aes_string(x = xvar, y = yvar)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 
} 
plotfn(mpg, "class", "hwy") # Now this works 


この事は、私は非常に便利NSEを見つけてもlazyevalということです。だから 私はこのようなことをしたい。

plotfn(NULL, rep(letters[1:4], 250), 1:100) # And even this crazyness works 


これは、多くの柔軟性を私のプロット機能を提供します

plotfn(mpg, "class", "hwy") # This still works 

mpg <- mpg 
plotfn <- function(data, xvar, yvar){ 
    data_gd <- data.frame(
     xvar = lazyeval::lazy_eval(substitute(xvar), data = data), 
     yvar = lazyeval::lazy_eval(substitute(yvar), data = data)) 

    ggplot(data = data_gd, 
      mapping = aes(x = xvar, y = yvar)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 
} 
plotfn(mpg, class, hwy) # Now this works 
。たとえば、 には、変数名(レイジー評価の悪用のようなもの)ではなく、引用または引用されていない変数名、さらには直接データの を渡すことができます。

しかし、これには大きな問題があります。関数はプログラムで を使用することはできません。

dynamically_changing_xvar <- "class" 
plotfn(mpg, dynamically_changing_xvar, hwy) 

## Error in eval(expr, envir, enclos): object 'dynamically_changing_xvar' not found 

# This does not work, because it never finds the object 
# dynamically_changing_xvar in the data, and it does not get evaluated to 
# obtain the variable name (class) 

だから、変数、またはデータの 異なる組み合わせに対して同じプロットを生成するために(例えばlapply)ループを使用することはできません。

私はさらに、怠惰な標準と標準でない標準の 評価を悪用し、これらをすべて組み合わせてみると、上記の柔軟性 とその機能をプログラムで使用することができます。 基本的には、tryCatch最初にlazy_eval 式を各変数に使用し、失敗した場合は、解析済みの 式を評価することです。

plotfn <- function(data, xvar, yvar){ 
    data_gd <- NULL 
    data_gd$xvar <- tryCatch(
     expr = lazyeval::lazy_eval(substitute(xvar), data = data), 
     error = function(e) eval(envir = data, expr = parse(text=xvar)) 
    ) 
    data_gd$yvar <- tryCatch(
     expr = lazyeval::lazy_eval(substitute(yvar), data = data), 
     error = function(e) eval(envir = data, expr = parse(text=yvar)) 
    ) 


    ggplot(data = as.data.frame(data_gd), 
      mapping = aes(x = xvar, y = yvar)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 
} 

plotfn(mpg, class, hwy) # Now this works, again 

plotfn(mpg, "class", "hwy") # This still works, again 

plotfn(NULL, rep(letters[1:4], 250), 1:100) # And this crazyness still works 

# And now, I can also pass a local variable to the function, that contains 
# the name of the variable that I want to plot 
dynamically_changing_xvar <- "class" 
plotfn(mpg, dynamically_changing_xvar, hwy) 


したがって、前述の柔軟性に加えて、 の1ライナーなどを使用して、同じプロットの多くを異なる 変数(またはデータ)で作成することができます。

lapply(c("class", "fl", "drv"), FUN = plotfn, yvar = hwy, data = mpg) 

## [[1]] 

## 
## [[2]] 

## 
## [[3]] 


、それは非常に実用的ですが、私はこれは良い習慣ではないと思います。しかし、 それはいかに悪い練習ですか?それが私の重要な質問です。何が他の選択肢 私は両方の世界の最高のを使用することができますか?

もちろん、私はこのパターンが問題を引き起こすことがあります。例えば。

# If I have a variable in the global environment that contains the variable 
# I want to plot, but whose name is in the data passed to the function, 
# then it will use the name of the variable and not its content 
drv <- "class" 
plotfn(mpg, drv, hwy) # Here xvar on the plot is drv and not class 


そして、いくつかの(多く?)他の問題。しかし、構文柔軟性の点で の利点が他の問題よりも優れているように思えます。これについての考えは?あなたが自由に文字列を混在させることができますので、

library(ggplot2) 
data(mpg) 

plotfn <- function(data, xvar, yvar){ 
    data_gd <- NULL 
    data_gd$xvar <- tryCatch(
    expr = lazyeval::lazy_eval(substitute(xvar), data = data), 
    error = function(e) eval(envir = data, expr = parse(text=xvar)) 
) 
    data_gd$yvar <- tryCatch(
    expr = lazyeval::lazy_eval(substitute(yvar), data = data), 
    error = function(e) eval(envir = data, expr = parse(text=yvar)) 
) 

    ggplot(data = as.data.frame(data_gd), 
     mapping = aes(x = xvar, y = yvar)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 
} 

このような機能は、一般的に非常に有用であり、裸の変数名:

+1

ベストプラクティスは、一対の関数を生成することです。 1つはNSEで、もう1つはSEです。これは 'vignette( 'nse')'で概説されています。これは 'aes'の代わりに' aes_'を使うことを意味します。 – Axeman

+0

ありがとう、...、ええ、私は答えになるだろうと恐れていました。私はdplyr&coの利点を見ていますが。 "一貫した命名体系:SEは最後に_が付いたNSE名です"というのは、プログラミングやインタラクティブな作業のために別の関数を使用しなければならないというバグです。 – elikesprogramming

答えて

2

わかりやすくするために、あなたの提案機能を抽出します。しかし、あなたが言うように、必ずしも安全であるとは限りません。次のような工夫した例を考えてみましょう:

あなたの関数は何を生成しますか?これらは同じですか(そうではありません)?結果がどうなるかは私には分かりません。このような関数でプログラミングすると、dataに存在し、環境内に存在する変数に応じて、予期しない結果が生じることがあります。多くの人がxxvarcountのような変数名を使用しているので(たぶんそうではないはずですが)、物事が乱れることがあります。

また、classのいずれかまたは他の解釈を強制したい場合、私はできません。

attachを使用するのと似たようなものだと思います。便利ですが、ある時点ではあなたの背中に噛み付くかもしれません。

したがって、私はNSEとSEのペアを使用したい:これらは実際にあなたの関数よりも簡単です作成

plotfn <- function(data, xvar, yvar) { 
    plotfn_(data, 
      lazyeval::lazy_eval(xvar, data = data), 
      lazyeval::lazy_eval(yvar, data = data)) 
) 
} 

plotfn_ <- function(data, xvar, yvar){ 
    ggplot(data = data, 
     mapping = aes_(x = xvar, y = yvar)) + 
    geom_boxplot() + 
    geom_jitter(alpha = 0.1, color = "blue") 
} 

を、私は思います。 lazy_dotsでもすべての引数を遅く取得することができます。

今、私たちは安全なSEのバージョンを使用した場合の結果を予測することは、より簡単に取得:

class <- "drv" 
Class <- "drv" 
plotfn_(mpg, class, 'hwy') 
plotfn_(mpg, Class, 'hwy') 

NSEのバージョンはしかし、まだ影響されます。

plotfn(mpg, class, hwy) 
plotfn(mpg, Class, hwy) 

(私はそれが穏やかに迷惑ggplot2::aes_ doesnのことを見つけます

+1

ええ、私は100%「このような関数を使ったプログラミングは、データ内に存在し、環境内に存在する変数に応じて予期せぬ結果をもたらすかもしれない」と同意します。私の背後に噛まれる危険性を上回ります。 – elikesprogramming

+0

最後の2行のコードは、実行しようとしたときに機能しませんでした。 – student

+0

TidyverseのNSEが変更されたので、これは可能です。 – Axeman