【发布时间】:2021-02-18 11:05:33
【问题描述】:
假设我想创建一个函数,该函数可以使用用户传递的任何函数来改变列。我需要知道如何在该函数到达 dbplyr 解析器之前 引用和取消引用它。我们来看一个例子,假设我有一个这样的函数:
testFun <- function(data, fun, colName, colOut = "myAwesomeColumn") {
dplyr::mutate(.data = data, !!colOut := fun(.data[[colName]]))
}
sc <- sparklyr::spark_connection(master = "local")
mtcars_spark <- dplyr::copy_to(sc, mtcars, "mtcars")
testFun(mtcars_spark, mean, "mpg")
所以在上面的示例中,我想将mean() 函数应用于"mpg" 列并将其存储在一个名为"myAwesomeColumn" 的新列中。
在使用 Spark,特别是 sparklyr 时,dbplyr 会尝试将此代码转换为 SQL 并将其发送到 Spark。我的理解是 dbplyr 应用以下规则:
- 如果它可以找到 Spark SQL 等效项,它将使用它(例如
mean()->AVG) - 否则它将按原样传递函数以查找 Scala 扩展或 UDF
第二个选项是这里发生的情况,因为它找不到函数fun,因此它返回一个 Spark 错误
Error: org.apache.spark.sql.AnalysisException: Undefined function: 'fun'.
This function is neither a registered temporary function nor a permanent function
registered in the database 'default'.; line 1 pos 85
...
所以我们需要另一种方法。问题是让 rlang 在 dbplyr 解释之前将fun 转换为mean。我知道如果我将函数名作为字符串传递并使用rlang::parse_expr(),我可以做到这一点,例如:
testFun <- function(data, fun, colName, colOut = "myAwesomeColumn") {
dplyr::mutate(data, !!colOut := rlang::parse_expr(paste0(fun, "(", colName, ")"))
}
testFun(mtcars_spark, "mean", "mpg")
# # Source: spark<?> [?? x 12]
# mpg cyl disp hp drat wt qsec vs am gear carb myAwesomeColumn
# <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
# 1 21 6 160 110 3.9 2.62 16.5 0 1 4 4 20.1
# 2 21 6 160 110 3.9 2.88 17.0 0 1 4 4 20.1
# # ... with more rows
【问题讨论】:
-
解决方案应该解决什么问题?您想避免使用
rlang::parse_expr解析字符串吗?输入应该是一个裸名(例如mean)而不是字符串(“mean”)吗? -
是的,就是这样。我想通过
mean而不是"mean"。 -
我没有 Spark 和 sparklyr,所以无法测试。我会尝试
rlang::call2并使用!!取消引用,因为bang bang 运算符会更早地评估其对象。但我不知道这是否可行。testFun <- function(data, fun, colName, colOut = "myAwesomeColumn") { .fun <- enquo(fun) dplyr::mutate(.data = data, !!colOut := eval(call2(!! .fun, .data[[colName]]))) } -
这几乎奏效了,我做了些微调整,现在它确实奏效了。我会发布我的答案,谢谢。
-
很高兴看到我正在使用 {rlang} 的
call2和fun的双引号和取消引号在正确的树上吠叫。我有点惊讶我们不需要evalcall2的输出。仍然想知道这是在哪里发生的。我也很惊讶这适用于tbl_spark,但不适用于data.frame(这里出现错误)。