如何检测裸变量或字符串

How to detect if bare variable or string

我正在尝试编写一个绘图函数,您可以在其中将裸列名称传递给 select 绘制哪些列。我也希望能够指定一个字符串作为颜色。

我发现如果我想将字符串传递给 aes_string,我需要使用 shQuote。现在我的问题是弄清楚是否传递了裸名或字符串。 我该怎么做?

dat <- data.frame(
    time = factor(c("Lunch","Dinner"), levels=c("Lunch","Dinner")),
    total_bill = c(14.89, 17.23)
)



plot_it <- function(dat, x,y, fill){
    require(rlang)
    require(ggplot2)

    x <- enquo(x)
    y <- enquo(y)
    fill <- enquo(fill)

    xN <- quo_name(x)
    yN <- quo_name(y)
    fillN <- quo_name(fill)

ggplot(data=dat, aes_string(x=xN, y=yN, fill=fillN)) +
    geom_bar(stat="identity")

}

这个有效:

plot_it(dat, time, total_bill, time)

这不是:

plot_it(dat, time, total_bill, "grey")

请注意,这需要最新版本的 rlang 和 ggplot2。

根据@akrun 关于如何检测我们遇到的情况(已删除)的建议,我发现了一些符合我要求的东西:

plot_it <- function(dat, x, y, fill) {

    lst <- as.list(match.call())

    if(is.character(lst$fill)){
        fillN <- shQuote(fill)
    } else{
        fillN <- quo_name(enquo(fill))
    }

    x <- enquo(x)
    y <- enquo(y)


    xN <- quo_name(x)
    yN <- quo_name(y)


    p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill=fillN)) +
        geom_bar(stat="identity")

    return(p)
}

事实证明,这实际上并没有按照我的想法进行,因为它将引用的值指定为分配颜色的因素。不是实际颜色。

我想到了这个似乎可行但不是很优雅的方法:

plot_it <- function(dat, x, y, fill) {

    lst <- as.list(match.call())

    if(!(type_of(lst$fill)=="symbol" | (type_of(lst$fill)=="string" & length(lst$fill)==1))) stop("Fill must either be a bare name or a vector of length 1.")

    x <- enquo(x)
    y <- enquo(y)

    xN <- quo_name(x)
    yN <- quo_name(y)


    if(is.character(lst$fill)){
        dat[,"fillN"] <- fill
        fillN <- fill

        p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill = shQuote(fillN))) +
             scale_fill_manual(name="fill", values=setNames(fillN,fillN))
    } else{
        fillN <- quo_name(enquo(fill))

        p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill = fillN))
    }



       p <- p + geom_bar(stat="identity")

    return(p)
}

有什么办法让这个更优雅一点吗?

所以我的做法是使用 do.call 和参数列表来选择性地将参数传递给 geom_bar 函数。

plot_it <- function(dat, x, y, fill) {

  lst <- as.list(match.call())

  xN <- quo_name(enquo(x))
  yN <- quo_name(enquo(y))
  fillN <- quo_name(enquo(fill))

  # Build the geom_bar call using do.call and a list of parameters
  # If the fill parameter is a character then the parameter list contains
  # both stat = "identity" and colour = ...; this colour will override the 
  # colour aesthetic
  p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill=fillN)) +
    do.call(geom_bar, c(list(stat = "identity"), list(fill = lst$fill)[is.character(lst$fill)]))

  return(p)
}

plot_it(dat, time, total_bill, time)
plot_it(dat, time, total_bill, "blue")
plot_it(dat, time, total_bill, 5)

为了清楚起见,我使用了 "blue",因为 ggplot 无论如何都会默认为灰色,但据我所知,它确实适用于任何颜色文字。我认为这比使用条件更优雅。

有趣的是,geom_col 在这种情况下可能比 geom_bar 更合适。

and 开始工作,这是一个版本:

  • 解决了您原来的问题
  • 防止出现不必要的图例,就像 Eumenedies 所做的那样
  • 提供可以无误添加的 ggplot 对象
  • 根据 Eumenedies 的建议,使用更合适的 geom_col

主要技巧是,如果 fill 不是情节中的一个因素,您希望它在 aes / aes_string 块之外。

plot_it <- function(dat, x, y, fill) {

  lst <- as.list(match.call())

  xN <- quo_name(enquo(x))
  yN <- quo_name(enquo(y))

  if(is.character(lst$fill)) {
    p <- ggplot(data=dat, aes_string(x=xN, y=yN)) +
      geom_col(fill = fill)
  } else {
    p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill = quo_name(enquo(fill)))) +
      geom_col()
  }

  return(p)
}

plot_it(dat, time, total_bill, time)
plot_it(dat, time, total_bill, "blue")
plot_it(dat, time, total_bill, "blue") + geom_point()

您可以通过将第二种情况下的 fill 美学移动到 geom_col 调用来缩短 if 块,但是如果您添加更多,那将以不同的方式扩展几何学。

此外,一旦 ggplot 更新为支持 rlang,避免 aes_stringquo_name 组合并仅使用 !!fill 会更清晰。

请注意,假设 fill 因素存在,如果它总是与 x 因素相同,那么使用 [=12] 的版本可能更有意义=] 是可选参数。如果包含该参数,您只会覆盖默认的每因子颜色。

您要求一个参数代表两种类型的参数:一种命名列,另一种命名颜色。最简单的解决方案是将其拆分为两个参数并添加一些检查以确保只提供一个。

plot_it <- function(dat, x, y, fill_column, fill_color = NULL){
  require(rlang)
  require(ggplot2)

  x <- enquo(x)
  y <- enquo(y)

  xN <- quo_name(x)
  yN <- quo_name(y)

  if (!missing(fill_column) && !is.null(fill_color)) {
    stop("Specify either fill_column or fill_color, not both")
  }
  if (missing(fill_column) && is.null(fill_color)) {
    stop("Specify one of fill_column or fill_color")
  }
  plot_geom <- if (!is.null(fill_color)) {
    geom_bar(stat = "identity", fill = fill_color)
  } else {
    fill <- enquo(fill_column)
    fillN <- quo_name(fill)
    geom_bar(stat = "identity", aes_string(fill = fillN))
  }

  ggplot(data = dat, aes_string(x = xN, y = yN)) +
    plot_geom
}


plot_it(dat, time, total_bill, fill_column = time)
plot_it(dat, time, total_bill, fill_color  = "grey")