用dplyr管道时获取lhs对象名称

时间:2015-05-05 15:32:19

标签: r pipe dplyr chain

我想要一个可以使用从dplyr导出的管道运算符的函数。我没有使用magrittr。

df %>% my_function

我如何获得df名称?如果我试试

my_function <- function(tbl){print(deparse(substitute(tbl)))}

它返回

[1] "."

虽然我想拥有     [1]“df”

有什么建议吗?

提前谢谢你,
尼古拉

5 个答案:

答案 0 :(得分:4)

注释中的JBGruber links to的SO答案大部分可以解决该问题。它通过在执行环境中向上移动直到找到某个变量,然后从该环境返回lhs来工作。唯一缺少的是该功能必须同时输出原始数据帧的名称和操作后的数据的要求–我从OP的注释之一中收集了后者的要求。为此,我们只需要输出包含这些内容的列表,就可以通过修改MrFlick的答案来做到这一点:

get_orig_name <- function(df){
    i <- 1
    while(!("chain_parts" %in% ls(envir=parent.frame(i))) && i < sys.nframe()) {
        i <- i+1
    }
    list(name = deparse(parent.frame(i)$lhs), output = df)
}

现在,我们可以运行get_orig_name到任何管道的末尾,以获取操作数据和列表中原始数据帧的名称。我们使用$进行访问:

mtcars %>% summarize_all(mean) %>% get_orig_name

#### OUTPUT ####

$name
[1] "mtcars"

$output
       mpg    cyl     disp       hp     drat      wt     qsec     vs      am   gear   carb
1 20.09062 6.1875 230.7219 146.6875 3.596563 3.21725 17.84875 0.4375 0.40625 3.6875 2.8125

我还应该提到,尽管我认为该策略的细节很有趣,但我也认为它不必要地复杂。听起来OP的目标是处理数据,然后将其写入与原始未经处理的数据帧同名的文件中,这可以使用更直接的方法轻松完成。例如,如果我们要处理多个数据帧,则可以执行以下操作:

df_list <- list(mtcars = mtcars, iris = iris)

for(name in names(df_list)){
    df_list[[name]] %>% 
        group_by_if(is.factor) %>%
        summarise_all(mean) %>% 
        write.csv(paste0(name, ".csv"))
}

答案 1 :(得分:0)

这是一种愚蠢的做法,我肯定会在很多边缘案件中突破:

library(data.table) # for the address function
                    # or parse .Internal(inspect if you feel masochistic

fn = function(tbl) {
  objs = ls(parent.env(environment()))
  objs[sapply(objs,
          function(x) address(get(x, env = parent.env(environment()))) == address(tbl))]
}

df = data.frame(a = 1:10)
df %>% fn
#[1] "df"

答案 2 :(得分:0)

灵感来自gersht提到的link

您可以追溯5代获得名字

df %>% {parent.frame(5)$lhs}

示例如下:

library(dplyr)

a <- 1

df1 <- data.frame(a = 1:10)

df2 <- data.frame(a = 1:10)

a %>% {parent.frame(5)$lhs}

df1 %>% {parent.frame(5)$lhs}

df2 %>% {parent.frame(5)$lhs}

答案 3 :(得分:0)

尽管这个问题是一个古老的问题,并且已经获得了赏金,但我想进一步扩展gersht的出色答案,该答案对于获得最左侧的对象名称非常有用。但是,除了在管道的最后一步中使用这种方法之外,尚未将该功能集成到dplyr工作流程中。

由于我经常使用dplyr,所以我围绕常用的dplyr动词创建了一组自定义包装函数,这些动词称为metadplyr(我仍在使用该功能,这就是为什么我没有尚未将其上传到github)。

本质上,这些函数在小标题的顶部创建了一个名为meta_tbl的新类,并将某些内容写入该对象的属性中。对于OP的问题,我提供了一个filter的简单示例,但是该过程也适用于任何其他dplyr动词。

在我的原始函数族中,我使用的名称与dplyr略有不同,但是这种方法在“覆盖”原始dplyr动词时也适用。

下面是一个新的过滤器函数,它将数据帧或小标题转换为meta_tbl,并将lhs对象的原始名称写入属性.name中。在这里,我使用的是gersht方法的简短版本。

library(dplyr)

 filter <- function(.data, ...) {

    if(!("meta_tbl" %in% class(.data))) {

      .data2 <- as_tibble(.data)

      # add new class 'meta_tbl' to data.frame  
      attr(.data2, "class") <- c(attr(.data2, "class"), "meta_tbl")

      # write lhs original name into attributes
      i <- 1
      while(!("chain_parts" %in% ls(envir=parent.frame(i)))) {
        i <- i+1
      }
      attr(.data2, ".name") <- deparse(parent.frame(i)$lhs)

    }

    dplyr::filter(.data2, ...)

}

为方便起见,最好具有一些帮助器功能,以便轻松地从属性中提取原始名称。

.name <- function(.data) {
  if("meta_tbl" %in% class(.data)) {
  attr(.data, ".name")
  } else stop("this function only work on objects of class 'meta_tbl'")

}

可以通过以下方式在工作流中使用这两种功能:

mtcars %>% 
  filter(gear == 4) %>% 
  write.csv(paste0(.name(.), ".csv"))

这可能是一个不好的例子,因为管道不会继续,但是从理论上讲,我们可以使用包含原始名称的管道并将其管道用于其他函数调用。

答案 4 :(得分:-1)

如果不向my_function添加额外的参数,我认为这是不可能的。使用dplyr链接函数时,它会自动将df转换为tbl_df对象,因此"."范围内的新名称dplyr可使管道更简单。

以下是dplyr非常hacky的方法,它只是添加了一个额外的参数来返回原始data.frame的名称

my_function <- function(tbl, orig.df){print(deparse(substitute(orig.df)))}
df %>% my_function(df)
[1] "df"

请注意,您不能仅使用原始函数传递df,因为tbl_df对象会自动传递给所有后续函数。