12

subset()マニュアルの状態として:

警告: これはインタラクティブな使用を意図した便利な関数です

この素晴らしい記事から、この警告の背後にある秘密だけでなく、substitute(), match.call(), eval(), quote()‍<code>call やpromise、少し複雑なその他の関連する R の主題についてもよく理解できました。

これで、上記の警告の意味がわかりました。の非常に単純な実装はsubset()次のようになります。

subset = function(x, condition) x[eval(substitute(condition), envir=x),]

を満たす行のテーブルをsubset(mtcars, cyl==4)返しますが、別の関数でのエンベロープは失敗します。mtcarscyl==4subset()

sub = function(x, condition) subset(x, condition)

sub(mtcars, cyl == 4)
# Error in eval(expr, envir, enclos) : object 'cyl' not found

の元のバージョンを使用してsubset()も、まったく同じエラー状態が発生します。substitute()-eval()これは、ペアの制限によるものです: is の間conditionは問題なく動作しますがcyl==4、 がconditionエンベロープ関数を通過するsub()と、 のcondition引数はsubset()willではなく、本体にcyl==4ネストさconditionれ、が失敗します。少し複雑です。sub()eval()

しかし、プログラミングに安全な、つまり、別の関数によって呼び出されている間にその条件を評価できる、まったく同じ引数subset()を持つの他の実装は存在しますか?

4

4 に答える 4

8

[ 関数はあなたが探しているものです。?"[". mtcars[mtcars$cyl == 4,]サブセット コマンドと同等であり、「プログラミング」セーフです。

sub = function(x, condition) {
 x[condition,]
}

sub(mtcars, mtcars$cyl==4)

with()関数呼び出しで暗黙的にせずに、あなたが求めていることを行います。詳細は複雑ですが、次のような関数です。

sub = function(x, quoted_condition) {
  x[with(x, eval(parse(text=quoted_condition))),]
}

sub(mtcars, 'cyl==4')

Sorta はあなたが探していることを行いますが、これが予期しない結果をもたらす稀なケースがあります。


data.tableおよび[サブセット関数を使用すると、with(...)探している暗黙的なものを取得できます。

library(data.table)
MT = data.table(mtcars)

MT[cyl==4]

でこのサブセット化を行うためのより優れた、より高速な方法がありますdata.tableが、これは要点をよく示しています。


を使用しdata.tableて、後で評価される式を作成することもできます

cond = expression(cyl==4)

MT[eval(cond)]

これら 2 つは関数を介して渡すことができるようになりました。

wrapper = function(DT, condition) {
  DT[eval(condition)]
}
于 2012-10-11T23:39:38.280 に答える
7

入れ子になっていても機能し続けるの代替バージョンを次に示します。subset()少なくとも、論理サブセット式 (例: cyl == 4) がトップレベルの関数呼び出しに提供されている限りは。

コール スタックを上っていき、substitute()各ステップで ing を実行して、ユーザーから渡された論理サブセット式を最終的に取得します。sub2()たとえば、以下の への呼び出しでは、forループは呼び出しスタックを からexprまでxAA最後に まで処理しcyl ==4ます。

SUBSET <- function(`_dat`, expr) {
    ff <- sys.frames()
    ex <- substitute(expr)
    ii <- rev(seq_along(ff))
    for(i in ii) {
        ex <- eval(substitute(substitute(x, env=sys.frames()[[n]]),
                              env = list(x = ex, n=i)))
    }
    `_dat`[eval(ex, envir = `_dat`),]
}

## Define test functions that nest SUBSET() more and more deeply
sub <- function(x, condition) SUBSET(x, condition)
sub2 <- function(AA, BB) sub(AA, BB)

## Show that it works, at least when the top-level function call
## contains the logical subsetting expression
a <- SUBSET(mtcars, cyl == 4)  ## Direct call to SUBSET()
b <- sub(mtcars, cyl == 4)     ## SUBSET() called one level down
c <- sub2(mtcars, cyl == 4)    ## SUBSET() called two levels down

identical(a,b)
# [1] TRUE
> identical(a,c)
# [1] TRUE
a[1:5,]
#                 mpg cyl  disp  hp drat    wt  qsec vs am gear carb
# Datsun 710     22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
# Merc 240D      24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
# Merc 230       22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
# Fiat 128       32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
# Honda Civic    30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2

**forループ内の構造の説明については、R 言語定義マニュアルのセクション 6.2のパラグラフ 6 を参照してください。

于 2012-10-12T03:57:59.160 に答える
4

心が折れそうなほど楽しい (??) という理由だけで、Hadley が私の受け入れた解決策へのコメントで指摘した問題に対処する、少し異なる解決策を次に示します。

ハドリーは、受け入れられた関数がうまくいかない状況を示す要点を投稿しました。その例 (以下にコピー) のひねりは、渡されたシンボルがSUBSET()呼び出し関数の 1 つの本体 (引数ではなく) で定義されていることです。substitute()したがって、意図したグローバル変数の代わりにキャプチャされます。紛らわしいもの、私は知っています。

f <- function() {
  cyl <- 4
  g()
}

g <- function() {
  SUBSET(mtcars, cyl == 4)$cyl
}
f()

呼び出し関数の引数リストにあるシンボルの値のみを置換する、より優れた関数を次に示します。Hadley または私がこれまでに提案したすべての状況で機能します。

SUBSET <- function(`_dat`, expr) {
   ff <- sys.frames()
   n <- length(ff)
   ex <- substitute(expr)
   ii <- seq_len(n)
   for(i in ii) {
       ## 'which' is the frame number, and 'n' is # of frames to go back.
       margs <- as.list(match.call(definition = sys.function(n - i),
                                   call = sys.call(sys.parent(i))))[-1]
       ex <- eval(substitute(substitute(x, env = ll),
                             env = list(x = ex, ll = margs)))
   }
   `_dat`[eval(ex, envir = `_dat`),]
}

## Works in Hadley's counterexample ...
f()
# [1] 4 4 4 4 4 4 4 4 4 4 4

## ... and in my original test cases.
sub <- function(x, condition) SUBSET(x, condition)
sub2 <- function(AA, BB) sub(AA, BB)

a <- SUBSET(mtcars, cyl == 4)  ## Direct call to SUBSET()
b <- sub(mtcars, cyl == 4)     ## SUBSET() called one level down
c <- sub2(mtcars, cyl == 4)
all(identical(a, b), identical(b, c))
# [1] TRUE

重要:これはまだ一般的に有用な機能ではない (また、そうすることができない) ことに注意してください。関数がコール スタックを処理するときに実行するすべての置換で使用するシンボルを関数が知る方法はまったくありません。関数本体内で割り当てられたシンボルの値を使用したい状況はたくさんありますが、この関数は常にそれらを無視します。

于 2012-10-12T23:19:58.517 に答える
1

アップデート:

以下は、2 つの問題を修正した新しいバージョンです。

a) 以前のバージョンは単純sys.frames()に逆方向にトラバースしました。このバージョンはparent.frames()に達するまで続きます.GlobalEnv。これは、たとえばのフレームを無視する場合subscrambleに重要です。scramble

b) このバージョンには、substituteレベルごとに 1 つのみがあります。これにより、2 番目のsubstitute呼び出しが、最初の呼び出しによって導入された 1 つ上のレベルのシンボルに置き換わることがなくなりsubstituteます。

subset <- function(x, condition) {

    call <- substitute(condition)
    frames <- sys.frames()
    parents <- sys.parents()

    # starting one frame up, keep climbing until we get to .GlobalEnv 
    i <- tail(parents, 1)
    while(i != 0) {

        f <- sys.frames()[[i]]

        # copy x into f, except for variable with conflicting names.
        xnames <- setdiff(ls(x), ls(f))
        for (n in xnames) assign(n, x[[n]], envir=f)

        call <- eval(substitute(substitute(expr, f), list(expr=call)))

        # leave f the way we found it
        rm(list=xnames, envir=f)

        i <- parents[i]
    }

    r <- eval(call, x, .GlobalEnv)

    x[r, ]
}

このバージョンは、コメントからの @hadley のテストに合格しています。

mtcars $ condition <- 4; subscramble(mtcars, cyl == 4)

残念ながら、次の 2 つの例は異なる動作をするようになりました。

cyl <- 6; subset(mtcars, cyl==4)
local({cyl <- 6; subset(mtcars, cyl==4)})

これは Josh の最初の関数を少し変更したものです。スタック内の各フレームで、フレームから置換するx前に置換します。これは、データ フレーム内のシンボルがすべてのステップで優先されることを意味します。ループ内で のフレームを_datスキップすることで、疑似 gensyms を回避できます。subsetfor

subset <- function(x, condition) {

    call <- substitute(condition)
    frames <- rev(sys.frames())[-1]

    for(f in frames) {

        call <- eval(substitute(substitute(expr, x), list(expr=call)))
        call <- eval(substitute(substitute(expr, f), list(expr=call)))
    }

    r <- eval(call, x, .GlobalEnv)

    x[r, ]
}

このバージョンは単純なケースで機能します (回帰が発生していないことを確認する価値があります)。

subset(mtcars, cyl == 4)
#                 mpg cyl  disp  hp drat    wt  qsec vs am gear carb
# Datsun 710     22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
# Merc 240D      24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
# Merc 230       22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
# Fiat 128       32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
# Honda Civic    30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
# Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
# Toyota Corona  21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
# Fiat X1-9      27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
# Porsche 914-2  26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
# Lotus Europa   30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
# Volvo 142E     21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2

また、 と で動作しsubscrambleますf:

scramble <- function(x) x[sample(nrow(x)), ]
subscramble <- function(x, condition) scramble(subset(x, condition))

subscramble(mtcars, cyl == 4) $ cyl
# [1] 4 4 4 4 4 4 4 4 4 4 4

f <- function() {cyl <- 4; g()}
g <- function() subset(mtcars, cyl == 4) $ cyl

g()
# [1] 4 4 4 4 4 4 4 4 4 4 4

さらに、いくつかのトリッキーな状況でも機能します。

gear5 <- function(z, condition) {

    x <- 5
    subset(z, condition & (gear == x))
}

x <- 4
gear5(mtcars, cyl == x)
#                mpg cyl  disp  hp drat    wt qsec vs am gear carb
# Porsche 914-2 26.0   4 120.3  91 4.43 2.140 16.7  0  1    5    2
# Lotus Europa  30.4   4  95.1 113 3.77 1.513 16.9  1  1    5    2

forループ内の行については、説明が必要な場合があります。次callのように割り当てられているとします。

call <- quote(y == x)
str(call)
# language y == x

inの値4を代入します。しかし、シンボル ではなく の内容が必要なため、単純な方法は機能しません。xcallcallcall

substitute(call, list(x=4))
# call

substituteそのため、別の呼び出しを使用して、必要な式を作成します。

substitute(substitute(expr, list(x=4)), list(expr=call))
# substitute(y == x, list(x = 4))

これで、やりたいことを説明する言語オブジェクトができました。実際にそれを行うために残っているのは次のとおりです。

eval(substitute(substitute(expr, list(x=4)), list(expr=call)))
# y == 4
于 2012-10-16T11:30:38.730 に答える