【问题标题】:Assigning labels to a data frame from bank of possible labels从可能的标签库中为数据框分配标签
【发布时间】:2020-08-28 21:59:22
【问题描述】:

我想创建一个从不同环境更新数据框的函数。具体来说,我想使用Hmisc::label() 函数更新数据框的标签。

assign_label <- function(df, col) {
  col <- rlang::as_name(rlang::ensym(col))
  Hmisc::label(df[,col]) <- fetch_label(col)
}

fetch_label <- function(col) {
  val <- c("mpg" = "MPG",
           "hp" = "HP") 
  unname(val[col])
}

以下代码执行没有问题:assign_label(mtcars, hp)

但是,它实际上并没有改变调用环境中的数据框。我只是想不通如何让它达到我的想象。

理想情况下,我希望能够像这样将数据帧通过管道传递给该函数:

mtcars %&gt;% assign_label(mpg)

【问题讨论】:

  • 在前面,我不知道如何......但是你在这里打了一场艰苦的战斗,因为大多数 R 在参数是按值传递的范式下运行,而不是通过引用传递。 data.table 按参考做事,也许你可以看看那里。如果您本身不需要帧并且可以处理environments,它们(总是)通过引用传递,因此根据您的用例,也许您可​​以朝那个方向转变。
  • 我强烈建议您不要这样做。拥有在其作用域之外更新变量的函数并不是很实用,现在 R 中的大多数函数是如何工作的。最好返回具有更新值的对象,如果要保存它,只需重新分配给原始值即可。
  • 如果您担心您的工具适用于较大的数据集,而 R 经常复制的优势是一个问题,那么...data.table

标签: r non-standard-evaluation


【解决方案1】:

1) 返回修改后的对象 在 R 中不鼓励就地修改对象。通常的方法是返回数据框,然后将其分配给新名称或返回原始名称破坏或遮蔽它。

assign_label <- function(df, col) {
  col <- deparse(substitute(col))
  Hmisc::label(df[[col]]) <- fetch_label(col)
  df
}

mtcars_labelled <- mtcars %>% assign_label(mpg)

2) magrittr 尽管我们在上面说过,在 R 和一些 R 包中仍有一些可以修改的功能。 magrittr 包提供了覆盖或隐藏输入的语法。使用(1)中的定义,我们可以这样写:

library(mtcars)
mtcars %<>% assign_label(mpg)

如果 mtcars 在全局环境中,它将用新值覆盖它,但在这种情况下,mtcars 在数据集中,因此将新的 mtcars 写入调用者,而数据集中的原始值保持不变。

3) 替换函数 虽然没有被广泛使用,但 R 确实提供了替换函数,这些函数是这样定义和使用的。这确实会覆盖或隐藏输入。

`assign_label<-` <- function(df, value) {
  Hmisc::label(df[[value]]) <- fetch_label(value)
  df
}

assign_label(mtcars) <- "mpg"

注意

顺便说一句,如果目标是与 tidyverse 一致的接口,则使用 tidyselect 检索列名,以便以下示例起作用:

assign_labels <- function(df, col) {
  nms <- names(select(df, {{col}}))
  for(nm in nms) Hmisc::label(df[[nm]]) <- fetch_label(nm)
  df
}

mtcars_labelled <- mtcars %>% assign_labels(starts_with("mp"))
str(mtcars_labelled)

mtcars_labelled <- mtcars %>% assign_labels(mpg|hp)
str(mtcars_labelled)

【讨论】:

  • 有什么理由使用deparse(subsitute(col)) 而不是rlang::as_name(rlang::ensym(col))
  • 当可以在基础 R 中用更少的代码完成时,添加一个包似乎毫无意义。
  • 好吧,除了我在脚本的其他地方使用了该包这一事实之外,这是有道理的。我不知道其中一个是否“更安全”或更强大。
  • @DylanRussell:substitute() 可以有undesirable behavior in nested functions。否则两者是等价的。
  • 这表明 NSE 本来就不是一个好主意。在 (1) 中,如果我们设计函数来传递 col 的字符串,那么所有这些复杂性都会被消除,函数会更短(可以消除主体的第一行)并且我们可以传递计算的表达式名称与传递名称一样容易,例如 assign_label(mtcars, names(mtcars)[1]) 传递第一列。
【解决方案2】:

关于不修改函数范围之外的 cmets,我创建了两个函数,它们为新的数据帧分配标签。

fetch_label <- function(col) {
  val <- c("mpg" = "MPG",
           "hp" = "HP") 
  unname(val[col])
}
 
assign_label <- function(df, col) {
  col <- rlang::as_name(rlang::ensym(col))
  Hmisc::label(df[[col]]) <- fetch_label(col)
  return(df)
}

assign_labels <- function(df) {
  purrr::iwalk(df, function(.x, .y) {
    lab <- fetch_label(.y)
    Hmisc::label(df[[col]]) <<- lab
  })
  return(df)
}

mtcars <- mtcars %>% assign_label(hp)
mtcars <- mtcars %>% assign_labels()

【讨论】:

  • 第一个函数中的return(df)是多余的(第二个函数中return也不需要,df就足够了)。
猜你喜欢
  • 2012-09-08
  • 2015-02-05
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-31
相关资源
最近更新 更多