【问题标题】:How can I pass character strings as independent parameters after a `+`?如何在 `+` 之后将字符串作为独立参数传递?
【发布时间】:2016-12-30 20:35:02
【问题描述】:

在标记为 dup 之前,我知道 Use character string as function argument,但我的用例略有不同。我不需要在函数内部传递参数,我想在+ 之后传递动态数量的参数(想想ggplot2)。

(注意:请不要格式化并删除多余的####,我已将它们保留在其中,以便人们可以将代码复制粘贴到 R 中为简单起见)。

这是我的过程:

#### 所以让我们重现这个例子:

library(condformat)
condformat(iris[c(1:5,70:75, 120:125),]) +
   rule_fill_discrete(Species) +
   rule_fill_discrete(Petal.Width) 

#### 我希望能够动态传递两个rule_fill_discrete() 函数(在我的实际用例中,我有可变数量的可能输入,并且不可能将它们硬编码)。

#### 首先,创建一个泛化函数:

PlotSeries <- function(x){
   b=NULL
   for (i in 1:length(x)){
     a <- paste('rule_fill_discrete(',x[i],')',sep="")
     b <- paste(paste(b,a,sep="+")) 
     }
   b <- gsub("^\\+","",b)
   eval(parse(text = b))
 }

#### 与一个参数一起工作

condformat(iris[c(1:5,70:75, 120:125),]) +
   PlotSeries("Species")

#### 但如果我们传递两个参数则不会:

condformat(iris[c(1:5,70:75, 120:125),]) +
   PlotSeries(c("Species","Petal.Width"))

rule_fill_discrete(Species) + rule_fill_discrete(Petal.Width) 中的错误: 二元运算符的非数字参数

#### 如果我们单独调用每个,它将起作用

condformat(iris[c(1:5,70:75, 120:125),]) +
   PlotSeries("Species") +
   PlotSeries("Petal.Width")

#### 这给了我们一个关于问题所在的指示......事实上它不喜欢 rule_fill_discrete 语句作为 one 语句传入。让我们测试一下:

condformat(iris[c(1:5,70:75, 120:125),]) +
   eval(rule_fill_discrete(Species) +
          rule_fill_discrete(Petal.Width) )

rule_fill_discrete(Species) + rule_fill_discrete(Petal.Width) 中的错误: 二元运算符的非数字参数

#### 失败。但是:

condformat(iris[c(1:5,70:75, 120:125),]) +
   eval(rule_fill_discrete(Species)) +
   eval(rule_fill_discrete(Petal.Width) )

#### 这行得通。但是我们需要能够传入一组语句(这就是重点)。因此,让我们尝试获取 eval 语句:

Nasty <- "eval(rule_fill_discrete(Species)) eval(rule_fill_discrete(Petal.Width))"

 condformat(iris[c(1:5,70:75, 120:125),]) + Nasty                   #### FAIL

+.default(condformat(iris[c(1:5, 70:75, 120:125), ]) 中的错误,讨厌): 二元运算符的非数字参数

condformat(iris[c(1:5,70:75, 120:125),]) + eval(Nasty)             #### FAIL

+.default(condformat(iris[c(1:5, 70:75, 120:125), ]), eval(Nasty)) 中的错误: 二元运算符的非数字参数

condformat(iris[c(1:5,70:75, 120:125),]) + parse(text=Nasty)       #### FAIL

+.default(condformat(iris[c(1:5, 70:75, 120:125), ]), parse(text = Nasty)) 中的错误: 二元运算符的非数字参数

condformat(iris[c(1:5,70:75, 120:125),]) + eval(parse(text=Nasty)) #### FAIL

eval(rule_fill_discrete(Species)) + eval(rule_fill_discrete(Petal.Width)) 中的错误: 二元运算符的非数字参数

那么我们该怎么做呢?

【问题讨论】:

  • 我发现使用“动态”一词的问题通常非常不清楚。这个词有很多不同的可能含义。最好是做一个简单的例子,并说出输入和输出应该是什么。此时我们有了输入,但是所需的输出要么不存在,要么与各种错误消息混在一起,以至于我至少找不到所需输出的正确描述。尝试将宏样式处理与引用的 "eval" 混合起来似乎太复杂了。
  • 对不起 42,让我试着澄清一下。我从所需的输出开始...我只想创建一个函数,允许我将任意数量的条件格式语句传递给 condformat
  • 我想我知道你想要做什么,但是在查看了 condformat 包使用的约定之后,我认为标准和非标准评估的混乱混合对我来说太过分了。我不相信标记为 ggplot2 真的是“诚实的”。我不认为+ 运算符具有相同的评估机制。
  • 不确定,但purrr 包的地图功能可能会有所帮助。
  • 是的,bungo,谢谢。请参阅我对 Gregor 出色答案的详细阐述......它使用 purrr 函数来应用稍微复杂的用例

标签: r lazy-evaluation


【解决方案1】:

感谢这个 stackoverflow 问题和来自 @amit-kohli 的错误报告,我知道 condformat 包中存在错误。

更新:更新答案以反映 condformat 0.7 中引入的新 condformat API。

这里我展示了如何(使用 condformat 0.7.0)。请注意,我在标准评估函数中使用的语法源自 rlang 包。

安装条件格式:

install.packages("condformat)"

一个简单的例子,在问题中问:

# Reproduce the example
library(condformat)
condformat(iris[c(1:5,70:75, 120:125),]) %>%
   rule_fill_discrete(Species) %>%
   rule_fill_discrete(Petal.Width) 

# With variables:
col1 <- rlang::quo(Species)
col2 <- rlang::quo(Petal.Width)
condformat(iris[c(1:5,70:75, 120:125),]) %>%
  rule_fill_discrete(!! col1) %>%
  rule_fill_discrete(!! col2)

# Or even with character strings to give the column names:
col1 <- "Species"
col2 <- "Petal.Width"

condformat(iris[c(1:5,70:75, 120:125),]) %>%
  rule_fill_discrete(!! col1) %>%
  rule_fill_discrete(!! col2) 

# Do it programmatically (In a function)
#' @importFrom magrittr %>%
some_color <- function(data, col1, col2) {
  condformat::condformat(data) %>%
    condformat::rule_fill_discrete(!! col1) %>%
    condformat::rule_fill_discrete(!! col2)
}
some_color(iris[c(1:5,70:75, 120:125),], "Species", "Petal.Width")

更一般的例子,使用表达式:

# General example, using an expression:
condformat(iris[c(1:5,70:75, 120:125),]) %>% 
  rule_fill_gradient(Species, expression = Sepal.Width - Sepal.Length)

# General example, using a column given as character and an
# expression given as character as well:
expr <- rlang::parse_expr("Sepal.Width - Sepal.Length")
condformat(iris[c(1:5,70:75, 120:125),]) %>%
  rule_fill_gradient("Species", expression = !! expr)


# General example, in a function, everything given as a character:
two_column_difference <- function(data, col_to_colour, col1, col2)  {
  expr1 <- rlang::parse_expr(col1)
  expr2 <- rlang::parse_expr(col2)
  condformat::condformat(data) %>%
    condformat::rule_fill_gradient(
      !! col_to_colour,
      expression = (!!expr1) - (!!expr2))
}
two_column_difference(iris[c(1:5,70:75, 120:125),],
                      col_to_colour = "Species",
                      col1 = "Sepal.Width",
                      col2 = "Sepal.Length")

连续值的自定义离散尺度

可以使用将连续列预处理为离散刻度的函数来指定自定义离散颜色值:

discretize <- function(column) {
  sapply(column,
    FUN = function(value) {
      if (value < 4.7) {
        return("low")
      } else if (value < 5.0) {
        return("mid")
      } else {
        return("high")
      }
    })
}

我们可以使用colours = 为每个等级指定颜色:

condformat(head(iris)) %>%
  rule_fill_discrete(
    "Sepal.Length",
    expression = discretize(Sepal.Length),
    colours = c("low" = "red", "mid" = "yellow", "high" = "green"))

如果我们愿意,discretize 函数可以返回颜色:

discretize_colours <- function(column) {
  sapply(column,
    FUN = function(value) {
      if (value < 4.7) {
        return("red")
      } else if (value < 5.0) {
        return("yellow")
      } else {
        return("green")
      }
    })
}

使用它的代码:

condformat(head(iris)) %>%
  rule_fill_discrete(
    "Sepal.Length",
    expression = discretize_colours(Sepal.Length),
    colours = identity)

请注意,expression 返回我们使用colours = identity 的颜色。 identity 就是 function(x) x

最后,使用一些rlangtidy evaluation我们可以创建一个函数:

colour_based_function <- function(data, col1) {
  col <- rlang::parse_expr(col1)
  condformat::condformat(data) %>%
    condformat::rule_fill_discrete(
      columns = !! col1,
      expression = discretize_colours(!! col),
      colours = identity)
}
colour_based_function(head(iris), "Sepal.Length")

【讨论】:

    【解决方案2】:

    注意:此答案为旧版本 condformat 中的错误提供了解决方法。该错误已被修复,修复此错误后,请参阅@zeehio 对当前版本的回答。


    我认为您有两个基本独立的问题。这些都在你的帖子中混合在一起。我将尝试逐一重申和回答,然后将它们放在一起——这在这一点上并不完全奏效,但已经接近了。

    首先,让我们通过定义几个变量来节省一些输入:

    ir = iris[c(1:5,70:75, 120:125), ]
    cf = condformat(ir) 
    

    Q1:如何在向量或输入列表上使用+

    这是一个简单的问题。 base 的答案是 Reduce。以下都是等价的:

    10 + 1 + 2 + 5 
    "+"("+"("+"(10, 1), 2), 5)
    Reduce("+", c(1, 2, 5), init = 10))
    

    与您的情况更相关,我们可以这样做以复制您想要的输出:

    fills = list(rule_fill_discrete(Species), rule_fill_discrete(Petal.Width))
    res = Reduce(f = "+", x = fills, init = cf)
    res
    

    Q2:如何在rule_fill_discrete 中使用字符串输入?

    这是我第一次使用condformat,但它看起来是用lazyeval 范式编写的,rule_fill_discrete_ 作为非标准评估rule_fill_discrete 的标准评估对应物。这个例子甚至在?rule_fill_discrete 中给出,但是它没有按预期工作

    cf + rule_fill_discrete_(columns = "Species")
    # bad: Species column colored entirely red, not colored by species
    # possibly a bug? At the very least misleading documentation...
    
    cf + rule_fill_discrete_(columns = "Species", expression = expression(Species))
    # bad: works as expected, but still uses an unquoted Species
    
    # other failed attempts
    cf + rule_fill_discrete_(columns = "Species", expression = expression("Species"))
    cf + rule_fill_discrete_(columns = "Species", expression = "Species")
    # bad: single color still single color column
    

    SE 函数中还有一个 env 环境参数,但我也没有运气。也许有更多lazyeval/表达经验的人可以指出我忽略或做错的事情。

    解决方法:我们可以做的是直接传递该列。这是有效的,因为我们没有对列做任何花哨的功能,只是直接使用它的值来确定颜色:

    cf + rule_fill_discrete_(columns = c("Species"), expression = ir[["Species"]])
    # hacky, but it works
    

    把它放在一起

    使用带有Reduce 的 NSE 版本很容易:

    fills = list(rule_fill_discrete(Species), rule_fill_discrete(Petal.Width))
    res = Reduce(f = "+", x = fills, init = cf)
    res
    # works!
    

    使用带有输入字符串的 SE,我们可以使用 hacky 解决方法。

    input = c("Species", "Petal.Width")
    fills_ = lapply(input, function(x) rule_fill_discrete_(x, expression = ir[[x]]))
    res_ = Reduce(f = "+", x = fills_, init = cf)
    res_
    # works!
    

    当然,您可以封装成一个自定义函数,该函数将数据框和列名的字符串向量作为输入。

    【讨论】:

    • 查看我的答案,详细说明这适用于我稍微复杂的用例。
    • condformat 如何处理标准评估似乎存在错误。我会尽快调查。 rule_fill_discrete_("Species") 应该可以工作
    【解决方案3】:

    @Gregor 的回答非常完美。有点hacky,但效果很好。

    在我的用例中,我需要更复杂一些,我会在此处发布,以防对其他人有用。

    在我的用例中,我需要能够根据一列的值为多列着色。 condformat 已经允许我们这样做,但我们又遇到了参数化问题。根据 Gregor 的回复,这是我的解决方案:

    CondFormatForInput <- function(Table,VectorToColor,VectorFromColor) {
            cf <- condformat(Table)
            input = data.frame(Val=VectorToColor,
                               Comp=VectorFromColor)
            fills2_ = map2(input$Val,.y = input$Comp,.f = function(x,y) rule_fill_discrete_(x, expression = 
                                                                                              iris[[y]]))
            res_ = Reduce(f = "+", x = fills2_, init = cf)
            res_
          }
    
          CondFormatForInput(iris,
                            c("Sepal.Length","Sepal.Width","Petal.Length","Petal.Width"),
                            c("Sepal.Width","Sepal.Width","Petal.Width","Petal.Width"))
    

    【讨论】:

      猜你喜欢
      • 2014-05-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多