gpt4 book ai didi

r - 了解 R data.table 中非标准评估的范围

转载 作者:行者123 更新时间:2023-12-04 11:04:34 25 4
gpt4 key购买 nike

如何确保使用 data.table 的非标准评估1 正在从父框架继承所需的变量?

根据我对动态作用域的理解,我的下面代码应该可以工作,但实际上没有。我做错了什么?

详情

我有一个列表,其中包含许多我想应用于单个 data.table 的函数,它返回 bool 检查和消息(当检查为 TRUE 时)。例如,假设我正在审计一个账户表。

library(data.table)
#----- Example data -----------------------------------------------------------
n <- 100
set.seed(123)
df <- data.table( acct_id = paste0('ID',seq(n)),
acct_balance = round(pmax(rnorm(n,1000,5000),0)),
days_overdue = round(pmax(rnorm(n,20,20),0))
)
#----- Example list of rules to check (real case has more elements)------------
AuditRules <- list(
list(
msg_id = 1,
msg_cat = 'Balance',
cond_fn = function(d) d[, acct_balance > balance_limit ],
msg_txt =
function(d) d[, paste('Account',acct_id,'balance is',
acct_balance - balance_limit,
'over the limit.')]
),
list(
msg_id = 2,
msg_cat = 'Overdue',
cond_fn = function(d) d[, days_overdue > grace_period ],
msg_txt =
function(d) d[, paste('Account',acct_id,'is overdue',
days_overdue-grace_period,
'days beyond grace period.')]
)
)

我正在遍历规则列表并检查每个规则的数据集。

期望的输出

这在全局环境中运行良好。

balance_limit <- 1e4
grace_period <- 14
audit <- rbindlist(
lapply(AuditRules, function(item){
with( item,
df[ cond_fn(df),
.(msg_id,
msg_cat,
msg_txt = msg_txt(.SD) )
]
)
} )
)
print(head(audit), row.names=FALSE)
#----------------- Result --------------------------------------
# msg_id msg_cat msg_txt
# 1 Balance Account ID44 balance is 1845 over the limit.
# 1 Balance Account ID70 balance is 1250 over the limit.
# 1 Balance Account ID97 balance is 1937 over the limit.
# 2 Overdue Account ID2 is overdue 11 days beyond grace period.
# 2 Overdue Account ID3 is overdue 1 days beyond grace period.
# 2 Overdue Account ID6 is overdue 5 days beyond grace period.

什么不起作用(需要解决方案)

rm(balance_limit, grace_period) # see "aside"

auditTheData <- function(d, balance_limit = 1e4, grace_period=14){
rbindlist(
lapply(AuditRules, function(item){
with( item,
d[ cond_fn(d),
.(msg_id,
msg_cat,
msg_txt = msg_txt(.SD) )
]
)
} )
)
}
auditTheData(df)

导致错误:

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

这不是 with() 的问题,尽管我读过 (?with) 通常应该避免将它用于编程。这也行不通:

auditTheData2 <- function(d, balance_limit = 1e4, grace_period=14){
rbindlist(
lapply(AuditRules, function(item){
d[ item[['cond_fn']](d),
.(msg_id,
msg_cat,
msg_txt = item[['msg_txt']](.SD) )
]
} )
)
}
auditTheData2(df) # Same error

旁白:如果您不在“什么不起作用”函数之前执行 rm(balance_limit, grace_period) —— 即,将它们留在全局环境中-- 你得到了想要的结果。所以看起来 function(item) 得到 lapply-ed 可以“看到”全局环境而不是父环境(AuditTheData).


1我在这里使用的“非标准”是不科学意义上的“不寻常”。我知道什么算作非标准,但这是另一个(而且太宽泛了?)问题。

最佳答案

这似乎可行:

ar <- list(
list(
cat = 'Balance',
cond_expr = quote(acct_balance > balance_limit),
msg_expr = quote(sprintf('Account %s balance is %s over the limit.',
acct_id,
acct_balance - balance_limit))
),
list(
cat = 'Overdue',
cond_expr = quote(days_overdue > grace_period),
msg_expr = quote(sprintf('Account %s is overdue %s days beyond grace period.',
acct_id,
days_overdue-grace_period))
)
)

audDT = rbindlist(rapply(ar, list, "call", how = "replace"), id="msg_id")

auditem = function(d, a, balance_limit = 1e4, grace_period = 14){
a[, {
cond = cond_expr[[1]]
msg = msg_expr[[1]]
.(txt = d[eval(cond), eval(msg)])
}, by=.(msg_id, cat)]
}

例如……

> head(auditem(df, audDT))
msg_id cat txt
1: 1 Balance Account ID44 balance is 1845 over the limit.
2: 1 Balance Account ID70 balance is 1250 over the limit.
3: 1 Balance Account ID97 balance is 1937 over the limit.
4: 2 Overdue Account ID2 is overdue 11 days beyond grace period.
5: 2 Overdue Account ID3 is overdue 1 days beyond grace period.
6: 2 Overdue Account ID6 is overdue 5 days beyond grace period.

我不确定这些更改中的哪一个有所不同:

  • eval 预定义表达式,而不是在函数内的 j 中组合它们
  • 使用表格作为规则,有一些好处:
    • 由于每个条目都应具有相同的结构,因此您可以验证每个条目是否格式正确(没有缺失的组件)
    • msg_id 可以使用 rbindlist 自动编号,因此不必手动输入
    • by= 可以用来代替 lapply,因为后者有一些奇怪的评估行为

我还将 paste 切换为 sprintf 但我确信这无关紧要。

rapply 是必需的,因为 data.table 不支持调用/表达式作为列类型(显然),但支持列表列。

关于r - 了解 R data.table 中非标准评估的范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48835565/

25 4 0