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

时间:2016-12-30 20:35:02

标签: r lazy-evaluation

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

enter image description here

####我希望能够动态传递两个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语句被传递为一个声明。让我们测试一下:

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),]),解析(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)):     二元运算符的非数字参数

那我们怎么做呢?

3 个答案:

答案 0 :(得分:2)

感谢这个stackoverflow问题,感谢@ amit-kohli的错误报告,我发现condformat包中有一个错误。

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

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

安装condformat:

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 = identityidentity只是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")

答案 1 :(得分:1)

注意:此答案为旧版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的标准评估对应物。 1}}。这个例子甚至在?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

把它放在一起

将NSE版本与Reduce一起使用很简单:

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!

当然,这可以包含一个自定义函数,它将数据框和列名的字符串向量作为输入。

答案 2 :(得分:0)

@Gregor的回答很完美。有点hacky,但工作得很好。

在我的用例中,我需要更多的复杂功能,我会在这里发布,以防其他人有用。

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

public class RaycastReflection : MonoBehaviour
{
    public int gunDamage = 1;
    public float hitForce = 1f;
    private Transform goTransform;
    private LineRenderer lineRenderer;
    private Ray ray;
    private RaycastHit hit;
    private Vector3 inDirection;
    public int nReflections = 2;
    private int nPoints;

    void Awake()
    {

    }

    void Update()
    {      
        nReflections = Mathf.Clamp(nReflections, 1, nReflections);
        ray = new Ray(goTransform.position, goTransform.forward);
        nPoints = nReflections;

        lineRenderer.SetVertexCount(nPoints);
        lineRenderer.SetPosition(0, goTransform.position);

        for (int i = 0; i <= nReflections; i++)
        {
            //If the ray hasn't reflected yet  
            if (i == 0)
            {
                //cast the ray 100 units at the specified direction  
                if (Physics.Raycast(ray.origin, ray.direction, out hit, 100))
                {  
                    inDirection = Vector3.Reflect(ray.direction, hit.normal);
                    ray = new Ray(hit.point, inDirection);

                    if (nReflections == 1)
                    {
                        lineRenderer.SetVertexCount(++nPoints);
                    }

                    lineRenderer.SetPosition(i + 1, hit.point);
                }
            }
            else 
            {
                if (Physics.Raycast(ray.origin, ray.direction, out hit, 100))
                {
                    Target health = hit.collider.GetComponent<Target>()
                    if (health != null)

                    health.Damage(gunDamage);

                    if (hit.rigidbody != null)
                        hit.rigidbody.AddForce(-hit.normal * hitForce);

                    inDirection = Vector3.Reflect(inDirection, hit.normal);
                    ray = new Ray(hit.point, inDirection);

                    lineRenderer.SetVertexCount(++nPoints);
                    lineRenderer.SetPosition(i + 1, hit.point);
                }
            }
        }
    }
}