Data.table: 式の列をプログラムで置換する方法の改善

作成日 2018ĺš´03月03日  Âˇ  28コメント  Âˇ  ソース: Rdatatable/data.table

require(data.table) # devel compiled on 3rd March 2018
dt <- data.table(x=1:5, y=6:10, z=11:15)
#    x  y  z
# 1: 1  6 11
# 2: 2  7 12
# 3: 3  8 13
# 4: 4  9 14
# 5: 5 10 15

これで、次のように便利な..表記を使用して列をサブセット化できます。

cols <- "z"
dt[, ..cols]
#     z
# 1: 11
# 2: 12
# 3: 13
# 4: 14
# 5: 15

..varは非常に特別な意味を持っているので、これを許可することもできますか?

dt[, c(..cols, "x")]

現在、次のようにエラーが発生します。

# Error in eval(jsub, SDenv, parent.frame()) : object '..cols' not found

あまり考えていませんでした。迷子にならないように、出くわして問題として提出したかっただけです。

enhancement programming top request

最も参考になるコメント

良い! 私も好きです。 また、実装すると、次のような問題に対処します。

dt[x > x]

その後、次のように実行できます。

dt[x > ..x]

全てのコメント28件

もちろん、 dt[ , c(cols, 'x'), with = FALSE]まだ機能します... with = FALSEを完全に段階的に廃止しようとしているかどうかという疑問が再び発生します

それはいいですね。 Synergistによる#633の同様のアイデアは次のとおりです。

dt <- data.table(x = 1:10, y = rnorm(10), z = runif(10))
cols <- c('y', 'z')
dt[, !..cols] # should return col x

そのとおり。 1.10.2の元のニュース項目(2017年1月)

jが..で始まるシンボルの場合、呼び出しスコープで検索され、その値は列名または番号と見なされます。

myCols = c( "colA"、 "colB")
DT [、myCols、with = FALSE]
DT [、.. myCols]#同じ
..プレフィックスが表示されたら、親ディレクトリを意味するすべてのオペレーティングシステムでディレクトリ..のように1つ上のレベルを考えます。 将来的には、..プレフィックスは、DT [...]内の任意の場所に表示されるすべてのシンボルで機能するように作成される可能性があります。 これは、誤って列名を取得することからコードを保護するための便利な方法となることを目的としています。 どのようにxに似ています。 そして私。 プレフィックス(SQLテーブルエイリアスに類似)は、xとiの両方に存在する同じ列名を明確にするためにすでに使用できます。 式で安全に使用したいスコープの呼び出しに多くの変数がある場合、..()関数ではなくシンボルプレフィックスを使用すると、内部で最適化するのが簡単になり、便利になります。 この機能は2012年に最初に開発され、長い間望まれていた#633です。 実験的です。

関連する文を太字にしました。 当時はよくわからなかったので、続行する前にフィードバックを期待していました。 それなら、それはうまくいくようです。 IIRCは、まだコードを確認していませんが、呼び出しスコープで確認するためにget()および/またはおそらくeval()のすべての外観の置換がすでに行われているため、それほど難しくありません。 かつて私はeval()を「スコープの呼び出しにおける評価」を伝えるための優れたラッパーだと思っていましたが、 [...]はすでにそれを行っているのではないかと思います。 今、私は..プレフィックスを好みます。これは、よりシンプルで堅牢だからです。 ..はシンボルのプレフィックスであるため、シンボルでなければならないことがわかっています( paste(...)などに渡される可能性のあるeval()関数とは異なります)。

ここでの最近のツイートも肯定的な反応を示したようです。
https://twitter.com/MattDowle/status/967290562725359617

良い! 私も好きです。 また、実装すると、次のような問題に対処します。

dt[x > x]

その後、次のように実行できます。

dt[x > ..x]

@MichaelChirico私はに基づいてケースだと仮定https://github.com/Rdatatable/data.table/issues/2620

ある段階で、単一の.プレフィックスを使用して、「このスコープを確実に」を意味し、列名でない場合はスコープを呼び出さないという提案があったと思います(これも、単一の.と同様です)。 ..使用してスコープを呼び出す必要があり、そのプレフィックスがない場合、シンボルは列名である必要があります。 破損の可能性がある変更ですが、通常の方法で管理されます。たとえば、options(datatable.strict.scope)FALSEから始めて、数年にわたって通知と警告とともに徐々にTRUEに変更されます。 この場合、呼び出しスコープで検出されたシンボルを実行時に検出するため、どのシンボルに..をプレフィックスとして付けるかを正確に示す警告が非常に進んでいる可能性があります。 堅牢性と読みやすさへの移行であり、何年にもわたってユーザーが自分の時間で変更を加えることができるため、ユーザーはそれを望んでいると思います。

..は、呼び出しスコープに到達するために使用する必要があり、そのプレフィックスがない場合、シンボルは列名である必要があります。

https://github.com/Rdatatable/data.table/issues/697#issuecomment -46305236を見ると、 i内の単一のシンボルの例外が保持されると思います(結合として解釈されるため、 ..なくてもスコープを呼び出す場合)?

うーん、はい。 違いは、 i内の単一の記号が列名を意味する場合、意味がないことです。 ただし、その列がタイプlogical 。 個人的にDT[someCol==TRUE]とにかく

これを楽しみにしています! これは、いっぱいになってから書いたコードのほとんどを散らかすeval / parse / quote / getボートロードを置き換える可能性があるようですdata.table 。

私の1つのパイプの夢は、次のようになります。両方の式で同じ出力が得られます。 (私がこれをタイプし始めたとき、私はこれがさらに現実的であるかどうか疑問に思い始めました)

set.seed(1234)
DT <- data.table(foo = rep(LETTERS[1:2],8),
                 bar = rep(letters[17:20],4),
                 month = rep(month.name[1:4],4),
                 day = seq_len(16),
                 yyy = rnorm(16,mean = 0.5,1.5))

## Expression 1: with hard coded columns
DT[yyy > 0,.(NewCol = paste(month, day),
             bar,
             yyy), by = foo]
## Expression 2: everything passed by reference
A = "yyy"
B = 0
C = "NewCol"
D = "month"
E = "day"
G = "foo"
H = c("bar","yyy")

DT[..A > ..B , .(..C = paste(..D, ..E),
                 ..H), by = ..G]

`` `
foo NewCol bar yyy
1:B 2月2日r0.9161439
2:B 2月6日r1.2590838
3:B 2月14日r0.5966882
4:B 4月16日t0.3345718
5:3月3日2.1266618
6:1月5日q 1.1436870
7:3月15日1.9392411

However, even as I write this I'm starting to see some potential hang-ups, in particular with how the `i` part of the expression is evaluated. 

In the case below, it does seem like there is potential ambiguity in whether `..B` would be treated as a column name if a column `xyz` existed in DT. Is my interpretation anywhere close to what you're currently planning on implementing, and should I perhaps be using a single `.` notation in some places? 

```r
A = "foo" ## intended column name
B = "xyz" ## intended literal character string for comparison

DT[..A == ..B]

実装されるものが何であれ、私はそれを楽しみにしています!


いくつかの関連するスタックオーバーフローの質問(ここに寄稿者からの多くの回答があります):

さて、マスターになりました。 今のところj=で使用されている記号についてのみ。
すべて試してみてください。
NEWSアイテムとPR#2704の新しいテストを参照してください。

@msummersgillのコメントを今より詳しく見ると、2つの異なるアイデアがあるようです。

  1. 列名も使用するj式で使用するために、呼び出しスコープで変数の値をフェッチします。 例: beta=10L; DT[, sum(colB)*..beta] 。 betaという列を持つDT可能性がない限り、これはすでに(ベータ版の..プレフィックスなしで)正常に機能します。 ..はそれをより安全にします。

  2. j=式で列名を使用します。使用する列名は、呼び出しスコープの変数によって定義されます。 ..プレフィックスが実装されたので、これはget(..whichCol)で確実に実行できるようになりました。ここで、 whichColは呼び出しスコープ内にあり、そのDTこれまでに発生した場合でも機能するようになりました。 whichColという列も含まれています。 ただし、 get()を使用するのは少し面倒で、内部で最適化するのは少し難しいです(現在、 getは、すべての列を呼び出して.SDに配置します)。 さらに、呼び出しスコープのwhichCol 、実際には列名ではない名前が含まれている可能性があります。 その場合、 get()自体が呼び出しスコープを調べ、運が悪ければ、呼び出しスコープで意図しない値を取得する可能性があります。 get-that-c​​olumnを確実に取得するための構文、およびそれ以外の場合はエラーがあれば便利です。

私はこれまでケース1についてのみ考えていましたが、それが@arunsrinivasanがこの問題を提起したものであり、 @ franknarf1と@MichaelChiricoはそれに関するさらなる例に同意しました。 これが、現在マージされているPRが行うことです。

しかし、 @ msummersgillは、まったく異なる何かを提案しています(ケース2)。これまで考えたことはありませんでした。 それはとても素敵なアイデアで、私はそれが好きです。 その場合、ケース2に別のプレフィックスが必要であり、それも必要ですか?

私がこれまで正しく理解した場合、tidyevalの!!varはどのような場合になりますか?

以来DT[, ..cols] (ここで、すなわちj=唯一のケースだった、単一のシンボルであるが) ..前に働いていた、私は眺めている方法を見て..手段がしますgetが結成されました。 しかし、 ..colsがたまたまそこで使用されているだけで、 ..colsは、そのコンテキストでその列、またはより多くの場合は列のセットを選択することを意味します。

そして今、私は、 @ msummersgillが彼のコメントの最後にこれらの2つのケースをすでに詳述しているのを見るが、私はそれを以前に消化しなかった。

解析されないため、 _または__プレフィックスを付けることができないようです。 _または__接尾辞を付けることができます。 しかし、私はどういうわけか、接尾辞よりも接頭辞が本当に好きです。
それから私は、多分@whichColだと思いました。 これはget(..whichCol)を意味しますが、 whichColの値は列名である必要があり、そうでない場合はエラーになります。 しかし、それも解析しません。
つまり、古いEVAL = parse(text = paste()))を指しているので、私はまったく気にしませんが、EVALラッパーを使用している場合でも少し醜くなる可能性があります。 繰り返しますが、 @ msummersgillがすでに

だから、マットSの例をとると、どうですか:

DT[ " <strong i="17">@A</strong> > <strong i="18">@B</strong> , .(<strong i="19">@C</strong> = paste(<strong i="20">@D</strong>, @E),  @H), by = <strong i="21">@G</strong> " ]

ルールは(freadの最初の引数に少し似ています) i=が1つ以上の@を含む文字列である場合、トークン置換モードに切り替わります。 このモードでは、他のすべての引数( j= 、 by=など)が欠落している必要があります。 内部はトークンを置き換えてから再解析します。 実装はそれほど難しくないかもしれません。

その他の例:

col = "x"
DT[, ..col]               # select column "x" (even if "cols" is a column name too)
DT[, c(..col, "y")]       # select columns "x" and "y"
thresh = 6.5
DT[ x > ..thresh ]        # select rows where "x" column > 6.5
DT[ "<strong i="29">@col</strong> > y" ]          #  same as DT [ x > y ]  i.e. comparison of two columns
anyString = "..thresh"
DT[ "<strong i="30">@col</strong> > @anyString" ] # same as DT[ x > ..thresh ]

@プレフィックスが解析されないのは、視覚的には他の通常のRコードと区別できるため、実際には利点です。 制限は、トークン置換モードではS4のobj @slotを直接使用できないことです。 「 obj @ slot 」をトークンの値に移動する必要があります。 @である必要はありません。 それは何でもかまいません。 たぶん$または$$プレフィックスで、文字列の$を伝えます。

@を置き換えた1つの文字列のアイデアは好きではありません。 ユーザーはbquoteを使用して式の変数を簡単に置き換えることができ、それはより基本的なRの方法にとどまります。 新しいカスタム設計および保守されたAPIはありません。
プログラムの観点から、ユーザーは1つの長い文字列を作成し、それをDT[i=渡す必要があります。 それほど堅牢ではありません。 リストを受け入れると、プログラム的にはるかに使いやすくなり、最終的にはhttps://github.com/Rdatatable/data.table/issues/1579#issuecomment-194323194のようになり

Janが文字列よりも式を好むことに同意します。 もう1つのアイデア: get / mgetような関数を追加しますが、これはdata.tableの列の間でのみ表示されます..?

DT = data.table(x = 1, y = 2)

cols = c("x", "y")
DT[, ..cols]                        # as now
DT[, g(..cols)]                     # same
DT[, g(cols)]                       # same
DT[, g(..cols), strict=TRUE]        # same
DT[, g(cols), strict=TRUE]          # error: symbol cols is column name in DT
                                    #        nor an external variable highlighted with a .. prefix

# behaving like mget but checking for col names
cols = c("x", "y")
DT[, lapply(g(..cols), max)]        # same as current behavior of DT[, lapply(mget(cols), max)]
v = "z"
DT[, {z = 4; lapply(g(..v), max)]   # error: selected column "z" not found
                                    # in contrast with DT[, {z = 4; lapply(mget(v), max)}]

# simplifying for a single col by default
vv = "x"
DT[, g(..vv)]                      # same as DT[, x] with simplification from list to vector
DT[, g(..vv, simplify = FALSE)]    # same as DT[, .(x)]

実現可能性については何も知りませんが、このようなものは理解しやすく、扱いやすいと思います。

https://github.com/Rdatatable/data.table/issues/1712#issuecomment-220402452およびhttps://github.com/Rdatatable/data.table/issues/633に関連

明確にするために、ここにマットの例の翻訳があります:

col = "x"
DT[, ..col]                     # DT[, .(x)]
DT[, c(..col, "y")]             # DT[, .(x, y)]

DT[ g(col) > y ]                # same as DT [ x > y ]
DT[ g(col) > y, strict=TRUE ]   # error: symbol col is not a column name nor highlighted with ..
DT[ g(..col) > y, strict=TRUE ] # same as DT [ x > y ]

thresh = 6.5
DT[ x > ..thresh ]              # same as DT[ x > 6.5 ]
DT[ x > thresh, strict=TRUE]    # error: symbol thresh is not a column name nor highlighted with ..
DT[ g(col) > ..thresh ]         # same as DT[ x > 6.5 ]
anyString = quote(..thresh)
my_expr = bquote(g(col) > .(anyString))
DT[ eval(my_expr) ]             # same as DT[ x > 6.5 ]
DT[ eval(my_expr), strict=TRUE] # error: symbol col is not a column name nor highlighted with ..

名前を識別するために@を使用してiに単一の文字列を含めるというアイデアは非常に柔軟ですが、 (主観的に)通常のdata.table構文とは少し異質に感じます。 他のいくつかの小さな欠点は、Rをサポートするコードエディタで作業しているときに、列名のオートコンプリートと複数行の式の自動インデントが失われることです。

これらの欠点は確かにトレードオフの価値があるかもしれませんが、現時点で私が見ているもう1つの客観的なスピードバンプは、単一のシンボル@使用すると、ターゲット間のあいまいさの同じ機会が残る可能性があることです。外部オブジェクトまたはdata.table列。

ここでの根本的な課題の1つは、data.tableの列名を、まったく異なる定義で外部環境で使用できることです。 ..プレフィックスは、既存の「列名のリスト」機能にあいまいさを残しませんが、概念を拡張して、本格的で明確な関数型プログラミングを可能にするには、少なくとも1つの追加のプレフィックスが必要になる場合があります。

@mattdowle以下に概説する考えられる各ケースを説明するいくつかの使用例を追加することにより、以下の質問に対処しようとしました。 DT["<strong i="14">@A</strong> = ..."]の道を進むことにした場合、おそらく?を使用して、列名として評価されるはずのものとそうでないものを明確に区別できるようにすることは、私の個人的な希望リストの上位にあると思います。

単一のテーブルのみでの操作について考えると(結合により、名前にまったく新しい潜在的な重複のセットが導入されます。このスコープでそれを考慮する必要があるかどうかはわかりません) 、私(およびおそらく他のユーザー)が考えられるいくつかの方法のリストを次に示します。あいまいさを避けるために、さまざまな潜在的なターゲットを明示的に定義できるようにしたい。

私には、 ..の使用法を単一の列(ケース3)に拡張することは直感的に理解でき、同じプレフィックスを持つケース4、6、および7を処理できるように思われます。 以下の例では、プレフィックスとして..ext.を使用します。これは、名前が外部環境の値として評価され、 DT内の列として評価されないことを表します。

|ケース| 現在のプレフィックス| 新しいプレフィックス| で使用| ターゲット| ターゲットの説明
| --- | -------- | -------- | ---------- | -------- | ------- -|
| 1 | なし| なし| I、J、K | コラム| 文字列名|
| 2 | なし| なし| 私、J | 外部| リテラル値|
| 3 | | .. | I、J、K | コラム| 外部で定義された列名|
| 4 | | ..ext. | 私、J | 外部| 外部で定義された値(またはベクトル)|
| 5 | .. | .. | J、K | 列| 外部で定義された列名のリスト|
| 6 | | ..ext. | J、K | 外部| DTで作成され、外部で定義された新しい列の名前|
| 7 | | ..ext. | J | 外部| 外部で定義された、DTで作成された複数の新しい列の名前のリスト|

例:

# Case --------------------------------
#   1      2
DT[foo == "A"] # Cases 1 & 2: Literal column name, and a literal value


col = "foo" # Case 3: Column Name defined externally
val = "A"   # Case 4: Value defined externally

# Case --------------------------------
#     3         4
DT[..col == ..ext.val] # DT[foo == "A"]


cols = c("foo","bar") # Case 5: List of column names defined externally
# Case --------------------------------
#       5
DT[, ..cols] # DT[, .(foo,bar)]


newcol = "nextday" # Case 6:Name of new column created in DT, defined externally 
# Case --------------------------------
#       6             1    2
DT[, ..ext.newcol := day + 1] # DT[, nextday := day + 1]


newcols = c("nextday", "nextday2") # Case 7:  List of names for multiple new columns created in DT, defined externally
# Case --------------------------------
#       7                1    2   1    2
DT[, ..ext.newcols := .(day + 1, day + 2)] # DT[, c("nextday", "nextday2") := .(day + 1, day + 2)]


## Putting it all together (in a totally nonsensical way for illustration only)
col = "foo"
col2 = "bar"
col3 = "day"
newgroup = "baz"
val = "A"
val2 = 1
newcols = c("nextday", "fooday")
printcols = c("baz","nextday","fooday")
# Case -------------------------------------------------------------------------------------------------------------
#    3         4            7                3          4                3      3                  6              3           5 
DT[..col == ..ext.val, ..ext.newcols := .(..col3 + ..ext.val2, paste0(..col, ..col3)), by = .(..ext.newgroup = ..col2)][, ..printcols]
#DT[foo == "A", c("nextday", "fooday") := .(day + 1, paste0(foo, day)), by = .(baz = bar)][,.(baz, nextday, fooday)]

私が理解していることから、プレフィックス記号はベースRで有効なオブジェクト名として解析されるものに制限されていますか? もしそうなら、ケース4、6、7をカバーするいくつかのオプションは._col 、 .._col 、 ..ext.colかもしれませんが、それらのどれも..ように指先から流れ出ません...は理論的には機能するかもしれませんが、 ..と...を区別しようとすると、他の何よりも片頭痛が増えるようです。

他の人がほのめかしているように、外部環境で定義されたときに列名(ケース3と6)が引用された場合、ケース3〜7のすべての単一のプレフィックスが機能する可能性があるようです。 フレームワークに精通していないので、同じ設計上の決定のいくつかがここで潜在的に役立つかどうかを確認するために、もう少し読んでみます。 更新: rlangとtidyeval読んだのですが、今は混乱しています。

ある種のstrict = TRUEオプションを使用するというアイデアは、少数の上級ユーザーがdata.tableオブジェクトを操作する関数を、初心者に過度の負担をかけずに、より防御的に記述できるようにするのに役立つ可能性があるようです。新しい構文とプレフィックスの束。

コメントを上から順番に見ていきましょう:

[1月] @の代わりに1つの文字列を使用するというアイデアは好きではありません。 ユーザーはbquoteを使用して式の変数を簡単に置き換えることができ、それはより基本的なRの方法にとどまります。 新しいカスタム設計および保守されたAPIはありません。

マットSの例でbquoteを使用するのがいかに簡単であるかの例を示していただけますか?

[1月]プログラムの観点から、ユーザーは1つの長い文字列を作成し、それをDT[i=.渡す必要があります。

実際、変数が必要になるため、文字列を渡すことはできません。 iの変数は、何か違うことを意味します。 文字列は[...]内に表示される必要があります; つまり、文字通りDT ["..."]。 この場合、 substitute(i)タイプはcharacterであるため、これはRで区別できます。

[1月]それほど堅牢ではありません。

脆弱性の一例を教えていただければ、理解できます。

[1月]リストを受け入れれば、プログラム的にはるかに使いやすくなり、最終的には#1579(コメント)に似たものになります。

私は過去にdata.tableのユーザーであったときに、このようなプログラム的なことをしました。 しかし、正直なところ、私はそれが本当に好きではありませんでした。 quote()またはbquote() (どちらか)は事前に設定する必要があり、パラメータ全体をそれらのいずれかで作成する必要がありますか、それともquote()で作成できますか?またはbquote() j=式の一部で使用しますか? 少なくとも私の目には優しいものではありません。 そのマクロのアイデアは、説明するのが簡単でも簡単でもありませんでした。 個人的には、私が提案したもの(単純なトークンの置き換え)を使用します。

[1月]優先度が高くない場合は、とりあえずそのままにしておきましょう。

..プレフィックスを拡張して、 j=内のすべてのシンボルで機能するようにしたためです。 この..が間違いである場合は、それを解放してロックする前に、今すぐそれを確立する必要があります。 後で元に戻すのは難しくなります。 別のプレフィックスが必要な場合は、今すぐ決定して、満足のいく完全な計画を立てる必要があります。結局、 ..決定していなかったと考えられるからです。

[率直]私は、文字列よりも式を好むJanに同意します。

Janが上記の点に回答した後、もう一度見てみましょう。

[フランク] get / mgetのような関数を追加しますが、これはdata.tableの列の間でのみ表示されます

問題は、それを内部でどのように処理するかです。 j=がグループごとに実行されており、グループがたくさんあるとします。 g()を無駄に何度も実行したくはありません。毎回同じ列を取得するだけです(クエリが開始される前に1回定義されています)。 したがって、 g()がグループに応じて異なる列を返す可能性があるかどうかを内部で確認する必要があります。そうでない場合は、それを前もって取り出して最適化し、定数になるようにします。 これには、 g()呼び出しを定数に置き換えることが含まれます。 それはすべて面倒でエラーが発生しやすいです。 mgetがすべての列を呼び出して.SD存在する理由の一部は、このようなものによるものです。 それは実行可能ですが、難しいです。

トークンの置換が好きなのは、それが次のように言うことができる明示的な構文だからです...わかりました、このクエリはプログラムですが、事前にプログラムで一度定義されています。 その後、通常のクエリとまったく同じように実行されます。 私の見解では、それは理にかなっていることは明らかです。 ロジックは明確に分離されています。i)実行するクエリを定義する最初のステップ、次にii)クエリを実行します。 g()、eval()、bquote()などがある場合、それらは内部全体で実行され、困難になります。 EVAL = parse(text = paste(...))アプローチが好きで、推奨した理由を一度書いたことを覚えています。これは、通常の単純なクエリを最初に入力した場合と同じように、クエリをきれいに最適化できるためです。

[マットS]名前を識別するために@を使用してiに単一の文字列を含めるというアイデアは非常に柔軟ですが、(主観的に)通常のdata.table構文とは少し異質に感じます。

NS。 数回寝た後も、私はまだそれが好きです。 この場合の「通常」はbquoteやevalなどを使用しているため、「通常」のdata.table構文にはこの機能がないため、比較する必要があります。

[マットS]他のいくつかの小さな欠点は、Rをサポートするコードエディターで作業しているときに、列名のオートコンプリートと複数行の式の自動インデントが失われることです。

メタクエリを作成するときは、これらのことはとにかく機能しません(使用される列名は柔軟に定義されるため)。したがって、これは文字列リテラルを使用しているため、エディターで色が異なるため、目立つようになっているとよいでしょう。 例を示す私のコメントで上記のGitHubの色を見ても、すでに私の目にはかなりよく見えます。 メタクエリは青色で目立ち、引用符で囲まれていますが、呼び出しスコープの実際の変数はオレンジ色で強調表示されています。 私のエディターでもローカルで同じように見えます。

私はあなたのコメントの残りの多くをフォローしていません。 実際のコード例を見る必要があります。 たとえば、「外部」の意味が100%明確ではないため、そのテーブルをコードに変換できません。

[Matt S]ある種のstrict = TRUEオプションを使用するというアイデアは、少数の上級ユーザーがdata.tableオブジェクトをより防御的に操作する関数を、初心者に理解の必要性を過度に負担させることなく記述できるようにするのに役立つ可能性があるようです。たくさんの新しい構文とプレフィックス。

はい。 そのアイデアは過渡的なものであり、うまくいけば数年後、strict = TRUEが常に当てはまり、オプションではありません。 ただし、上級ユーザーだけのものではありません。 実際、その逆です。 上級者以外のユーザーによる事故を防ぐためです。 初心者は、呼び出しスコープに到達するために..プレフィックスを使用する必要があります。 彼らは、新しい関数を含むものよりも、メタクエリを把握しやすい(結局のところ、文字列の置換にすぎない)と思うでしょう。 私もそうですが、彼らはquote / bquote / enquoteすべてが非常に混乱していると感じています。 エラーメッセージは、初心者を念頭に置いて非常に役立ちます(例: "'coef' is not a column name, but it is in calling scope. Please add the prefix '..' to coef to make it clear so it won't ever break in future if you ever add a column called coef."

それでは、マットSの例でbquoteを使用するのがいかに簡単であるかの例を示していただけますか?

実際には引用ではなく、以下の言語ソリューションです。

脆弱性の一例を教えていただければ、理解できます。

このソリューションには脆弱性はありませんが、文字式によるDTのクエリは、eval-parseによって適切に処理されます。

言語と文字列のソリューション。どれも実際にはユーザーフレンドリーではありませんが、強力でベースRであり、安定していて、明確に定義され、テストされたAPIです。
私は、ユーザーの便宜のためにそれを書くためのより速い方法を持つことが理にかなっていることに同意します。 それでも、本番コードの場合、構文の健全性チェックが追加されるため、文字列を操作するのではなく、呼び出しを作成します。

library(data.table)
set.seed(1234)
DT <- data.table(foo = rep(LETTERS[1:2],8),
                 bar = rep(letters[17:20],4),
                 month = rep(month.name[1:4],4),
                 day = seq_len(16),
                 yyy = rnorm(16,mean = 0.5,1.5))

## Expression 1: with hard coded columns
DT[yyy > 0,.(NewCol = paste(month, day),
             bar,
             yyy), by = foo] -> ans
## Expression 2: everything passed by reference
A = "yyy"
B = 0
C = "NewCol"
D = "month"
E = "day"
G = "foo"
H = c("bar","yyy")

#DT[..A > ..B , .(..C = paste(..D, ..E),
#                 ..H), by = ..G]
#   foo      NewCol bar       yyy

"# ---- language ----"

qc = as.call(list(
    as.name("["),
    x = quote(DT),
    i = call(">", as.name(A), B),
    j = as.call(c(list(as.name(".")), setNames(list(call("paste", as.name(D), as.name(E))), C), lapply(H, as.name))),
    by = as.name(G)
))
eval(qc) -> ans2

"# ---- string ----"

ci = paste(A, B, sep=" > ")
cj = paste0(".(", C, " = paste(", D, ", ", E, "), ", paste(H,collapse=", "),")")
cby = paste0("list(", G, ")") # this could be simplified
DT[eval(parse(text=ci)), eval(parse(text=cj)), by = eval(parse(text=cby))] -> ans3

# another
cdt = paste0("DT[", paste(c(ci, cj, cby), collapse=","), "]")
eval(parse(text=cdt)) -> ans4

identical(ans, ans2) # T
identical(ans, ans3) # T
identical(ans, ans4) # T

実際に私にとってより共鳴しているように見えるのは、 @vars置き換えることによる文字列構築のヘルパー関数です。 [呼び出しから独立しています。 次に、「新しいapi」を最小限に制限し、構築された文字列をeval-parseするだけにします。 したがって、これを作成することは、この例では文字列の代わりに言語オブジェクトを使用するマクロ提案に対応する機能です。

query = helper("<strong i="19">@A</strong> > <strong i="20">@B</strong>, .(<strong i="21">@C</strong> = paste(<strong i="22">@D</strong>, @E), @H), @G")
DT[text = query]
# or alternative api
DT[~query]

どちらの場合、それは明らかではないので注意が必要かもしれません@Hでなければなりませんunlist 1つのリストにマージする編で@Cとして、あるいは滞在c("bar","yyy") / .(bar, yyy) 。
iでさらに多くの種類の呼び出しを処理することは避けたいと思います。見た目は素晴らしくシンプルですが、ドキュメントに新しいifが追加されます。

j =のすべてのシンボルで機能するように..プレフィックスを拡張したばかりだからです。 これが間違いである場合は、それを解放してロックする前に、今すぐそれを確立する必要があります。 後で元に戻すのは難しくなります。 別のプレフィックスが必要な場合は、今すぐ決定して、満足のいく完全な計画を立てる必要があります。結局、決定していなかったと考えられるからです。

同意

[Matt Dowle:]したがって、 g()がグループに応じて異なる列を返す可能性があるかどうかを内部で確認する必要があります。そうでない場合は、それを前もって取り出して最適化することで、定数になります。 これには、 g()呼び出しを定数に置き換えることが含まれます。 それはすべて面倒でエラーが発生しやすいです。 mgetがすべての列を呼び出して.SD存在する理由の一部は、このようなものによるものです。 それは実行可能ですが、難しいです。
[...]
ロジックは明確に分離されています。i)実行するクエリを定義する最初のステップ、次にii)クエリを実行します。 g()、eval()、bquote()などがある場合、それらは内部全体で実行され、困難になります。

返信ありがとうございます。 それは理にかなっている。 私は、 g()が、適切な関数ではなく、 listになる.と同様に、ステップ(i)で置換をトリガーするマークアップであると想像していました。 そして、唯一の引数としてのような文字列1つのレベルまでのプレフィックス変数ポインティング、許可g(..v)になると思われる、 xまたは.(x = x, y = y)かで同様のj 、置換を示す詳細メッセージ付き。

@プレフィックスに対する1つの異議は、単一変数のユースケースをカバーするためだけに設計されているように見えることです。 .SDcolsサブセットではない列のベクトルを渡したい場合はどうなりますか? (サブセットの場合、少なくとも.SD[, ..v]を実行できます。)

私の他の異議は、Janが述べたことです。「それでも、本番コードの場合、構文の健全性チェックが追加されるため、文字列を操作するのではなく、呼び出しを作成します。」 文字列内にコードを書くのではなく、コードをコードとして書くことに伴うRの構文チェックが好きです。 そして、本番コードの場合、文字列に書き込んだコードは本当に信用できないと思います。

同じアイデアの別のバリエーション: .SDcols (または他の引数)を拡張して、置換する列選択式のリストをサポートします。

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

z = "newcol"
DT[, .(..z = sum(..x + ..y), lapply(.SD, max)), by=am, 
  .SDcols = list(disp:drat, x = "wt", y = "qsec")]
# substitutes j before evaluation to become...
# list(newcol = sum(wt + qsec), disp = max(disp), hp = max(hp), drat = max(drat))

したがって、この他の引数で..xと..yを最初にDT[...]に検索し(実際に1レベル上を検索する前に)、関連する列に置き換えます。

Janはもうすぐそこにいるようで、ヘルパーとしてではありますが、@トークンの置換について同意しています。 (これらのquote()、call()、およびas.call()は、ユーザーはもちろんのこと、すべて私にとって大変な作業です。)@トークンは、呼び出しスコープ、型文字、および長さ1の変数であるかどうかがチェックされます(そうでない場合はエラー) )。 ドキュメントへのもう1つの「if」はかなり良いと思います。 それはメタクエリが何であるかを説明するでしょう、そしてそれは簡単です。 混乱やバグレポートが確立されている代替案を説明するよりもはるかに簡単です。 メタクエリを含む変数を渡さない理由は、それがすぐそこにあるメタクエリであることを明確にするためです。 それがメタクエリであるかどうかは、コードの上位で定義されているか、渡される変数に依存する必要はありません。メタクエリのように見えるはずです。

フランク-わかりました。g()についてのあなたの考えがわかりました。 ただし、g()を置換する場合は、トークン置換を使用することもできます。 このようなコードの読者は、g()とは何か、そしてそれが何をしたかを知る必要があります。 メタクエリモードをより明確に示すには、ルックアンドフィールをより劇的に切り替える必要があると思います。

@プレフィックスに対する1つの異議は、単一変数のユースケースをカバーするためだけに設計されているように見えることです。

それは何でもカバーするように設計されています。 完全に柔軟。 data.tableクエリを作成するための単純なトークン置換。 詳細モードでは、作成されたクエリが出力されます。 そのクエリが解析されない場合でも、Rはそれをキャッチします。 しかし、実行時に。 とにかく必要なのはこれです。 これは動的クエリです。

.SDcolsのサブセットではない列のベクトルを渡したい場合はどうなりますか? (サブセットの場合、少なくとも.SD [、.. v]を実行できます。)

完全な例は何ですか?それを調べてみましょう。

私の他の異議は、Janが述べたことです。「それでも、本番コードの場合、構文の健全性チェックが追加されるため、文字列を操作するのではなく、呼び出しを作成します。」 文字列内にコードを書くのではなく、コードをコードとして書くことに伴うRの構文チェックが好きです。 そして、本番コードの場合、文字列に書き込んだコードは本当に信用できないと思います。

quote 、 bquote 、 as.call 、 callなどの関数(Janが彼のコメントは、私が本番環境に適さないと考えています。)これらは、書くのも、デバッグするのも、維持するのも難しいです。 私の見解では、トークン置換方法はより単純であり、したがって実際には本番目的でより堅牢です。 たとえば、本番環境で実行された実際のクエリはログファイルに書き込まれるため(eval()、bquote()、call()などが含まれないため、誰でも読み取ることができます)、本番環境にログインする方が堅牢です。 。 私は生産システムに関して多くの経験を持っています。 私は20代前半のときに、リーマンブラザーズの取引システムを半日停止する責任がありました。 私は、コントロールが緩いために死んだ飼料が見過ごされてしまうという経験をたくさん持っています。 生産の最優先の目標は単純さです。 歯車が少ないほど良いです。 依存関係が少ないほど良いです。 単純なコードは、常により複雑なコードよりも優先されます。 call()、as.call()、bquote()などのすべての呼び出しが行っているのは、とにかく動的クエリを作成することです。 はい、Rはそれらを作成するときに、それらの構造が有効なRであることを確認していますが、動的クエリでそれらが組み合わされて一緒に使用されるのは実行時です(そして、それが壊れるかもしれません)。動的クエリが本番環境で壊れた場合、次に、 quote 、 call 、 bquote混乱をデバッグするのではなく、実行されたクエリを通知するメタクエリを処理したいと思います。呼び出します。

[私:]。SDcolsのサブセットではない列のベクトルを渡したい場合はどうなりますか? (サブセットの場合、少なくとも.SD [、.. v]を実行できます。)

[Matt Dowle:]完全な例を教えてください。それを調べてみましょう。

たとえば、変数の複数のグループを要約し、プログラムで入力列を選択し、出力列に名前を付けることを考えています。

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

min_em = c("disp", "hp")
min_em_prefix = "min"
mean_em = c("drat", "wt")
mean_em_prefix = "mean"
max_it = "qsec"
max_it_name = "mymax"
by_em = "am"

# goal query

DT[, .(
  min.disp = min(disp), min.hp = min(hp), 
  mean.drat = mean(drat), mean.wt = mean(wt), 
  mymax = max(qsec)
), by=am ]

# current syntax
# with four warnings since ..min_em and ..mean_em already up one level relative to .SD[...], i guess

DT[, c(
  {z <- lapply(.SD[, ..min_em], min); setNames(z, sprintf("%s.%s", ..min_em_prefix, names(z)))}, 
  {z <- lapply(.SD[, ..mean_em], mean); setNames(z, sprintf("%s.%s", ..mean_em_prefix, names(z)))}, 
  setNames(max(.SD[[..max_it]]), ..max_it_name)
), by=by_em]

# proposed syntax?

DT[, "c(
  ..min_em_prefix = lapply(<strong i="10">@min_em</strong>, min), 
  ..mean_em_prefix = lapply(<strong i="11">@mean_em</strong>, mean),
  ..max_it_name = max(@max_it)
)", by="..by_em"]

したがって、ここでの「@」は、2つの異なるタイプの置換を行っています。

  • @min_em場合、列の名前付きリストを指定します
  • @max_it 、1つの列のみを指定します

それはあなたが考えていたものですか? これまでは単一列の例にしか気づかず、このユースケースもカバーするかどうかわからなかったので、質問します。

上記の「現在の構文」の私のバージョンでは、.SDをサブセット化する代わりにget / mgetを記述できますが、それらを回避しようとする傾向があります。 また、現在の構文では、この例でGForceが引き続き機能することを確認する方法があると思いますが、面倒だと思います。

他の懸念を打ち負かす単純さに関する良い点。 説明してくれてありがとう。 私はこれを使用できる/使用するだろうと確信しています。

素晴らしい例です。 だから、与えられた:

min_em = c("disp", "hp")
min_em_prefix = "min"
mean_em = c("drat", "wt")
mean_em_prefix = "mean"
max_it = "qsec"
max_it_name = "mymax"
by_em = "am"

# goal query

DT[, .(
  min.disp = min(disp), min.hp = min(hp), 
  mean.drat = mean(drat), mean.wt = mean(wt), 
  mymax = max(qsec)
), by=am ]

これは難しいです! これらすべてを行うことは、おそらく私が考えていたものを伸ばすことです。 しかし、それをやってみましょう。 最初に考えたのは、要件を多くの変数名ではなく1つの定義に体系化することです。 それ自体がdata.tableとして、楽しみのために。

> def = data.table( fun = c("min", "mean", "max"), 
                    prefix = c("min", "mean", "max"),
                    cols=list (c("disp","hp"), c("drat", "wt"), c("qsec") )
> def
      fun prefix    cols
   <char> <char>  <list>
1:    min    min disp,hp
2:   mean   mean drat,wt
3:    max    max    qsec

次に、定義を展開します。 (このステップを実行するためのより良い方法があるはずです。)

> expanded = def[, paste0(prefix,".",cols[[1]],"=",fun,"(",cols[[1]],")"), by=1:3]
> expanded
       :                   V1
   <int>               <char>
1:     1   min.disp=min(disp)
2:     1       min.hp=min(hp)
3:     2 mean.drat=mean(drat)
4:     2     mean.wt=mean(wt)
5:     3   max.qsec=max(qsec)
>  J = paste(expanded$V1,collapse=", ")
> J
[1] "min.disp=min(disp), min.hp=min(hp), mean.drat=mean(drat), mean.wt=mean(wt), max.qsec=max(qsec)"

そしてそれを使用するだけです:

> DT[ " , .(@J), by=<strong i="15">@by_em</strong> " ]
...
Meta-query expanded to: DT[, .(min.disp=min(disp), min.hp=min(hp), mean.drat=mean(drat), mean.wt=mean(wt), max.qsec=max(qsec)), by=am]
...

ここでの難しさの一部は、要件が「楽しみを接頭辞として使用するこれらすべての楽しみによるこれらの列」ではないということです。 そうでなければ、おそらくそれは簡単でしょう。 mymax名前要件も満たしていないことはわかっていますが、拡張ヘルパー関数に組み込むことができます。

内部最適化を作成した人の1人として、この場合の拡張メタクエリは効率的で最適化されると確信しており、このようなクエリをより適切に処理できることを嬉しく思います。 たくさんのlapply 、 .SD 、 setnames()など、すべてグループごとに実行されているバージョンを最適化することに、私はまったく満足していません。 特にget / mgetが存在する場合、ユーザーは.SDすべての列を受け取らないように、 .SDcols適切に検討して使用する必要があります。 拡張メタクエリに.SDがない場合、その問題は解消されます。

後でこの行を単独で見ると、おそらく他の誰かによって書かれています:

DT[ " , .(@J), by=<strong i="30">@by_em</strong> " ]

はい、それがメタクエリであることを知る必要があります。 しかし、それを知っている限り、 @Jが構築されており、 by=もプログラマティックであることがわかります。 本質的に高度な動的クエリに関する限り、それは悪いことではありません。 lapply 、 .SD 、 .SDcols 、 quote 、 bquote 、 as.callなどはありません。初心者は、通常のdata.table構文に到達すれば、そこからメタクエリに非常に簡単に移行できると思います。これは、リテラル文字列の置換であり、(そして私たちも)比較的簡単にトレースおよびデバッグできるためです。

ドキュメントへのもう1つの「if」はかなり良いと思います。 それはメタクエリが何であるかを説明するでしょう、そしてそれは簡単です。 混乱やバグレポートが確立されている代替案を説明するよりもはるかに簡単です。

確かに、私が示した代替案は、初心者ではなく、経験豊富なRプログラマーのためのものです。

@プレフィックスに対する1つの異議は、単一変数のユースケースをカバーするためだけに設計されているように見えることです。

それは何でもカバーするように設計されています。 完全に柔軟。

ほぼ完全に柔軟です。 完全に柔軟なのは、言語オブジェクトの評価解析または操作です。 上で使用したクエリの例はそれほど明白ではありません。

DT["<strong i="14">@A</strong> > <strong i="15">@B</strong>, .(<strong i="16">@C</strong> = paste(<strong i="17">@D</strong>, @E), @H), @G"]

その直接翻訳は次のようにマップする必要があります。

DT[i = yyy > 0, j = .(NewCol = paste(month, day), list(bar, yyy)), by = foo]

しないでください:

DT[i = yyy > 0, j = .(NewCol = paste(month, day), bar, yyy), by = foo]

もちろん、この場合は2番目のものが想定されますが、 @Hと@Cを1つのリストにマージするには、バックグラウンドでc / unlistロジックが必要です。 そして、それは常に望まれるとは限りません。

引用、bquote、as.call、callなどの関数の混乱よりも、本番用のトークン置換方法を信頼します(Janがコメントで示したように、本番には不適切だと思います)。デバッグと保守が難しい。 私の見解では、トークン置換方法はより単純であり、したがって実際には本番目的でより堅牢です。 たとえば、本番環境で実行された実際のクエリはログファイルに書き込まれるため(eval()、bquote()、call()などが含まれないため、誰でも読み取ることができます)、本番環境にログインする方が堅牢です。 。

それは混乱ではなく、強力で明らかに複雑なRメタプログラミング機能です。 はい、Rメタプログラミングを定期的に使用しない限り、書くのは難しいです。 文字列をログに記録するのと同じように、文字列よりもデバッグが難しくありません。引用符で囲まれた呼び出しをprintまたはdeparseログに記録します。

言語オブジェクトを使用しているため、問題は発生しませんでした。 私はすでに何年もの間Rbitcoinで

私はメタプログラミングのための新しいよりユーザーフレンドリーな方法を作ることに完全に同意しますが、それを可能な限り最小限に抑え、ベースの評価解析または言語オブジェクトを可能な限り再利用します。

言語R機能でのコンピューティングを使用した「素晴らしい例」。

library(data.table)
DT = as.data.table(mtcars)

build.j = function(min_em, min_em_prefix, mean_em, mean_em_prefix, max_it, max_it_name)
  as.call(c(
    list(as.name(".")),
    lapply(setNames(min_em, paste(min_em_prefix, min_em, sep=".")), function(col) call("min", as.name(col))),
    lapply(setNames(mean_em, paste(mean_em_prefix, mean_em, sep=".")), function(col) call("mean", as.name(col))),
    setNames(list(call("max", as.name(max_it))), max_it_name)
  ))

qj = build.j(
  min_em = c("disp", "hp"),
  min_em_prefix = "min",
  mean_em = c("drat", "wt"),
  mean_em_prefix = "mean",
  max_it = "qsec",
  max_it_name = "mymax"
)
by_em = "am"

print(qj)
#.(min.disp = min(disp), min.hp = min(hp), mean.drat = mean(drat), mean.wt = mean(wt), mymax = max(qsec))
DT[, eval(qj), by_em] -> ans1

# as single call
qcall = as.call(list(as.name("["), quote(DT), substitute(), qj, as.name(by_em)))
print(qcall)
#DT[, .(min.disp = min(disp), min.hp = min(hp), mean.drat = mean(drat), mean.wt = mean(wt), mymax = max(qsec)), am]
eval(qcall) -> ans2

もう1つは、ユーザー提供の入力に対してeval-parseを実行するときのセキュリティです。 この場合、言語での計算ははるかに安全です。

library(data.table)
DT = as.data.table(mtcars)
col = "am"

# eval-parse way
eval(parse(text=paste0("DT[1L,.(",col,")]")))
#   am
#1:  1

# language way
eval(as.call(list(as.name("["), quote(DT), quote(1L), call(".", as.name(col)))))
#   am
#1:  1

# eval-parse abuse
writeLines("cat('harmful code here\n')", "somescript.R")
col = "{source(\"somescript.R\"); am}"
eval(parse(text=paste0("DT[1L,.(",col,")]")))
#harmful code here
#   V1
#1:  1

# try language abuse
eval(as.call(list(as.name("["), quote(DT), quote(1L), call(".", as.name(col)))))
#Error in eval(jsub, SDenv, parent.frame()) : 
#  object '{source("somescript.R"); am}' not found

これは、data.tableユーザーにとってはそれほど問題ではなく、アプリ内でdata.tableを利用してサードパーティにUIを提供したい開発者にとっては問題です。

[マット・ダウレ]私はあなたのコメントの残りの多くをフォローしていません。 実際のコード例を確認する必要があります。たとえば、「外部」の意味が100%明確ではないため、そのテーブルをコードに変換できません。

以前の投稿を編集して、いくつかの例と説明を含めました。 それでも不明な場合はお知らせください。

["@ ..."]のトピックについて

[...] (親環境の列名とオブジェクトのあいまいさなしに)完全な非標準評価に向けた継続的な手順は、依然として非常に価値があると思います。 しかし、昨日初めてdata.table.Rソースを読んだ中級ユーザーとして、それを可能にするための課題を理解せずに、ここでポニーを求めている可能性があることを理解しています。

今日これを実装した場合、私はおそらくそれをいくつかのケースで使用するでしょう。

[1月]もう1つは、ユーザー提供の入力に対してeval-parseを実行するときのセキュリティです。

私は自分が作成するすべての光沢のあるアプリケーションでdata.tableを使用していますが、私は間違いなくこれについて危機に瀕しており、他の人の言うことを聞きたいと思っています。 現在、多くのユーザーがeval(parse(text = "..."))しかないので、これは「セキュリティ」の低下を表しているのでしょうか、それとも横に進んでいるだけでしょうか。

現在の開発ブランチのテスト

[マット・ダウル]わかりました、マスターになりました。 今のところj =で使用されている記号についてのみ。 すべて試してみてください。

ベクトルとdata.tableの結果

これは妥当な設計上の決定かもしれませんが、 ..varが単一の列を参照したときに私が期待した動作ではありません。

DT = data.table(x=1:3, y=2:4)
var = "x"


DT[, x]     ## Returns a vector
DT[, ..var] ## Returns a one column data.table with column x

予期しない評価動作

DT = data.table(x=1:3, y=2:4)
var = "x"

DT[, paste(..var)] # ..var evaluates as column x
#    x
# 1: 1
# 2: 2
# 3: 3

DT[, paste(..var, x)] # ..var evaluates as string "x"
# [1] "x 1" "x 2" "x 3"


DT[, paste("blah",..var)] # ..var is concatenated by paste
# Error in `[.data.table`(DT, , paste("blah", ..var)) : 
#   column(s) not found: blah x

x = "foo"
var = "x"
dt[, paste("blah",get(..var))] # get(..var) "redirected" to global variable x
# Error in `[.data.table`(dt, , paste("blah", get(..var))) : 
#  column(s) not found: blah foo


dt[, .(paste("blah",..var))] # When wrapped in .() behavior is as expected
#        V1
# 1: blah x

dt[, .(paste("blah",get(..var)))] # When wrapped in .() behavior with get() is also as expected
#        V1
# 1: blah 1
# 2: blah 2
# 3: blah 3

まだget()を使用する必要があります

[Matt Dowle]フランク-わかりました。g()についてのあなたの考えがわかりました。 ただし、g()を置換する場合は、トークン置換を使用することもできます。 このようなコードの読者は、g()とは何か、そしてそれが何をしたかを知る必要があります。 メタクエリモードをより明確に示すには、ルックアンドフィールをより劇的に切り替える必要があると思います。

完璧な世界では、この場合の新しいプレフィックスによるトークンの置換が、私にとって理想的と思われる単一の文字列にすべてを入れずに実行できる場合。 最適化の課題について十分に理解していないので、それがどれほど現実的であるかどうかを理解できません。

いくつかの良い点

ここでお風呂の水で赤ちゃんを捨てたくないのですが、 ..が終わりにならなくても、まだ何らかの価値があるかもしれません-すべてdata.tableのNSEの解決策です。

現状では、スタックオーバーフローの質問に対する回答を提供します

また、この例は私が期待したとおりに機能します。

DT = data.table(foo = LETTERS[1:5],
                 bar = seq_len(5),
                 baz = seq_len(5)/10,
                 zero = 0L)
x = "foo"
y = "bar"
z = "baz"
string1 = "blah"
num1 = 10

DT[, .(Paste = paste(..string1, get(..x), get(..y),sep = "-"),
       Mult = get(..y)*..num1,
       Add = get(..y) + get(..z),
       foo_literal = foo,
       foo_ref = get(..x))]

@msummersgill個別のシンボルの利点の1つは、列を確実に取得できることです。

library(data.table)
DT = data.table(foo = LETTERS[1:5],
                 bar = seq_len(5),
                 baz = seq_len(5)/10,
                 zero = 0L)

# current behavior
x = "foo"
DT[, {foo = "yeehaw"; get(..x)}]
# [1] "yeehaw"

# whereas i guess the plan is
DT[', {foo = "yeehaw"; @x}']
# [1] "A" "B" "C" "D" "E"

同様に、セキュリティに関しては、@ select列のみが安全であるように思われるので...

DT1 = data.table(a = 1, b = 2)
f1 = function(x, d = DT) d[", code_vulnerable_if_x_is_bad(@x)"]
# should error for any input that isn't a column selection

# and even if the column name contains dangerous code
DT2 = data.table(`dangerous code here` = 1)
f2 = function(x, d = DT2) DT[", f(@x)"]
# should select the named column, not evaluate its name

また、開発者は、ユーザーが任意のコードを文字列として挿入できるf (char_expr, d = DT) DT[char_expr]ような関数を記述しないでください。 とにかく、それは提案の私の理解です。

次に、定義を展開します。 (このステップを実行するためのより良い方法があるはずです。)

ここで異議を唱えようとはしていませんが、明確にするために、より良い方法はcallとas.callです...しかし、この拡張はエンドユーザーではなくdata.tableによって行われますよね? したがって、これらの言語プログラミング機能に精通している必要があるわけではありません。 Janのコードが例に対してあまりにも特注に見える場合は、次のようになります...

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

# note: changing "prefix" to name to also cover mymax .. not a big extension
def = data.table(fun = c("min", "mean", "max"), 
                    name = c("min", "mean", "mymax"),
                    cols=list (c("disp","hp"), c("drat", "wt"), c("qsec") ))

def[, ncols := lengths(cols)]
longdef = def[, c(.SD[rep(.I, ncols)], .(col = unlist(cols))), .SDcols=!"cols"]
longdef[, out_name := sprintf("%s.%s", fun, col)][ncols == 1, out_name := name]

expr = with(longdef,
  as.call(c(as.name("list"), 
    setNames(Map(call, fun, lapply(col, as.name)), out_name)
  ))
)

DT[, eval(expr), by=am]

@mattdowleでは、 call / as.callを使用して、このような拡張を行うことに異議がありますか? もしそうなら、それはエンドユーザーインターフェースがどのように見えるかとは別の質問のように思えます(@と文字列入力の質問)。 そして、Janが考えていた方法(#852)で[.data.tableよりモジュール化することは「ノー」のように思えますか?

私はこのプロジェクトに貢献したことがありませんので、それは安価な話だが、私が想像#852は私のような潜在的な貢献者への障壁を下げるだろうと。 しかし、私はその問題やあなたがここで言っていることを誤解しているかもしれません。

これが以前に対処されているかどうかはわかりませんが、新しい..プレフィックスが代入演算子:=でも機能することをどのように確認しますか?

次の例を考えてみましょう。
dt <- data.table(x=rnorm(10), y=runif(10)); dt[, x := x + 1]
それは完全に受け入れられます。 プログラムでxを見つける必要がある場合は、前にこれを行います
a <- "x"; dt[, eval(as.name(a)) := eval(as.name(a)) + 1] 。
data.tableパッケージをv1.10.4に更新するまでは、完全に正常に機能していました。

今はできません
dt[, z:=..a]
また
dt[, ..a := ..a + 1]
あるいは
dt[, eval(as.name(a)) := eval(as.name(a)) + 1]もう。 接頭辞を使用して代入操作を実行できない場合、接頭辞のポイントは何ですか? 古いeval(as.name(VAR))が壊れた原因を知っている人はいますか? ありがとう。

@jjyyyinコメントを新しい号にコピーしました。そこでディスカッションをフォローアップしてください。 https://github.com/Rdatatable/data.table/issues/2816

ディスカッション全体が単純な列の選択( ..プレフィックス)から大きく逸脱したため、タイトルを変更しました。


別の代替手段は、 substituteとそのenv引数に類似したインターフェースを提供することです。

DT[, .(out_col = fun(in_col)), by=by_col,
   env = .(out_col="mpg_max", fun="max", in_col="mpg", by_col="cyl")]

envリストのすべての要素はシンボルに変換され、 i 、 j 、 by 、..に置き換えられます。

ベースRを使用した実例。LHS名を除いて、これは注意が必要です。

x = list()
class(x) = "cl"
"[.cl" = function(x, i, j, env) {
  if (!missing(env) && length(env)) {
    stopifnot(is.list(env), sapply(env, is.character))
    env = lapply(env, as.symbol)
    isub = eval(substitute(substitute(i, env)))
    jsub = eval(substitute(substitute(j, env)))
  } else {
    isub = substitute(i)
    jsub = substitute(j)
  }
  list(isub=isub, jsub=jsub)
}

x[var1==5L & var2%in%c("a","b"),
  .(fvar1=fun(var1, na.rm=TRUE), charhead=head(var2, 1L))]
#$isub
#var1 == 5L & var2 %in% c("a", "b")
#$jsub
#.(fvar1 = fun(var1, na.rm = TRUE), charhead = head(var2, 1L))
x[var1==5L & var2%in%c("a","b"),
  .(fvar1=fun(var1, na.rm=TRUE), charhead=head(var2, 1L)),
  env = list(var1="myIntCol", var2="myCharCol", fun="sum")]
#$isub
#myIntCol == 5L & myCharCol %in% c("a", "b")
#$jsub
#.(fvar1 = sum(myIntCol, na.rm = TRUE), charhead = head(myCharCol, 1L))

LHS名の処理方法を支援してくれた@ggrothendieckに感謝します。

x = list()
class(x) = "cl"
"[.cl" = function(x, i, j, env) {
  if (!missing(env) && length(env)) {
    stopifnot(is.list(env), sapply(env, is.character), sapply(env, nzchar))
    env = lapply(env, as.symbol)
    isub = if (!missing(i)) eval(substitute(substitute(i, env)))
    jsub = if (!missing(j)) eval(substitute(substitute(j, env)))
    # substitute names?
    if (any(w <- names(jsub) %in% names(env))) {
      names(jsub)[w] = unlist(lapply(env[names(jsub)[w]], as.character))
    }
  } else {
    isub = substitute(i)
    jsub = substitute(j)
  }
  list(isub=isub, jsub=jsub)
}
x[, .(fvar1=list(fvar2 = val1)),
  env = list(fvar1="outer", fvar2="inner", val1="val_column")]
#$isub
#NULL
#$jsub
#.(outer = list(fvar2 = val_column))

残念ながら、LHSのネストされた置換を探すには、式を再帰的にトラバースする必要があります。 RHSはすでにsubstituteによって適切に処理されています。

ネストされた呼び出しで名前を置き換えるための再帰的トラバースは、PR#4304で対処されています。 substitute2関数は、変数/関数名(Rのsubstituteによって処理され、引数名を呼び出す(新しい内部substitute_call_arg_namesR関数によって処理される))の両方の値を置き換えるために導入されました。

このページは役に立ちましたか?
0 / 5 - 0 評価