如何在 `+` 之后将字符串作为独立参数传递?

How can I pass character strings as independent parameters after a `+`?

在你标记为 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"))

Error in rule_fill_discrete(Species) + rule_fill_discrete(Petal.Width) : non-numeric argument to binary operator

#### 如果我们单独调用每个就可以了

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

#### 这给了我们关于问题是什么的指示......当 rule_fill_discrete 语句作为 one[ 传递时它不喜欢的事实=66=]声明。让我们测试一下:

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

Error in rule_fill_discrete(Species) + rule_fill_discrete(Petal.Width) : non-numeric argument to binary operator

#### 失败。但是:

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

Error in +.default(condformat(iris[c(1:5, 70:75, 120:125), ]), Nasty) : non-numeric argument to binary operator

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

Error in +.default(condformat(iris[c(1:5, 70:75, 120:125), ]), eval(Nasty)) : non-numeric argument to binary operator

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

Error in +.default(condformat(iris[c(1:5, 70:75, 120:125), ]), parse(text = Nasty)) : non-numeric argument to binary operator

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

Error in eval(rule_fill_discrete(Species)) + eval(rule_fill_discrete(Petal.Width)) : non-numeric argument to binary operator

那么我们该怎么做呢?

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


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

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

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

问题 2:如何在 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!

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

@Gregor 的回答很完美。有点老套,但效果很好。

在我的用例中,我需要更复杂一些,我会post在这里,以防它对其他人有用。

在我的用例中,我需要能够根据一列的值为多列着色。 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"))

多亏了这个 Whosebug 问题和@amit-kohli 的错误报告,我才知道 condformat 包中有一个错误。

更新:已更新答案以反映条件格式 0.7 中引入的新条件格式 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 函数可以 return 颜色:

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 return 我们使用 colours = identity 的颜色。 identity 只是 function(x) x.

最后,使用一些 rlang tidy 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")