получить имя объекта lhs при передаче с помощью dplyr

Я хотел бы иметь функцию, которая может использовать оператор канала, экспортированный из dplyr. Я не пользуюсь магритром.

df %>% my_function

Как я могу получить имя df? Если я попытаюсь

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

он возвращается

[1] "."

в то время как я хотел бы иметь [1] "df"

Любое предложение?

Заранее спасибо,
Николай


person Nicola Sturaro Sommacal    schedule 05.05.2015    source источник
comment
Хотя я думаю, что это интересный вопрос, почему вы хотите это сделать?   -  person eddi    schedule 05.05.2015
comment
@eddi: my_func берет результат некоторых манипуляций с dplyr и выполняет некоторые дополнительные операции для форматирования данных для печати и экспорта. Я хотел бы экспортировать данные объекта в файл csv с тем же именем, что и объект.   -  person Nicola Sturaro Sommacal    schedule 06.05.2015
comment
Что ж, df %>% stuff %>% my_func не может этого сделать, поскольку к моменту, когда он доберется до my_func, это вполне может быть совершенно другой объект, и информация о крайнем левом объекте будет потеряна, поэтому я предлагаю просто сделать my_func(df)   -  person eddi    schedule 06.05.2015
comment
Этот ответ, кажется, работает: stackoverflow.com/a/42561430/5028841 или я что-то упустил?   -  person JBGruber    schedule 24.10.2019


Ответы (5)


Ответ SO, на который ссылается JBGruber в комментариях в основном решает проблему. Он работает, перемещаясь вверх по средам выполнения, пока не будет найдена определенная переменная, а затем возвращает lhs из этой среды. Единственное, чего не хватает, — это требования, чтобы функция выводила как имя исходного фрейма данных , так и обработанные данные — последнее требование я почерпнул из одного из комментариев ОП. Для этого нам просто нужно вывести список, содержащий эти вещи, что мы можем сделать, изменив ответ 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"))
}
person gersht    schedule 24.10.2019
comment
Я думал опубликовать это, но отказался от этого, так как этому вопросу уже несколько лет, и вы не можете многое улучшить в связанной функции. Две вещи, которые я бы предложил, - это отказаться от этой части && i < sys.nframe(), поскольку она, кажется, никогда не становится FALSE, и использовать message(deparse(parent.frame(i)$lhs)) для печати имени перед return(df). Я бы не стал возвращать список, так как дальнейшая передача была бы более сложной. - person JBGruber; 24.10.2019
comment
@JBGruber, да, я почти закончил отвечать, прежде чем заметил, что это было из 2015 года. Меня это немного раздражало, но я уже взял на себя обязательство, поэтому я все равно решил опубликовать. Не уверен, как работают награды, когда они от кого-то другого, кроме ОП. Я ожидаю, что get_orig_name будет использоваться только в конце канала, поэтому дальнейшее подключение не должно быть проблемой. Также достаточно просто сделать что-то вроде … %>% get_orig_name %>% `$`(output) %>% …, если вы хотите продолжить работу с данными после получения имени фрейма данных. Я думаю, что оставлю все как есть, пока этот пост не получит больше внимания. - person gersht; 24.10.2019
comment
По крайней мере, я чему-то научился, работая над ним. +1 за объяснение того, как работает функция, чего не было в исходном ответе. - person JBGruber; 24.10.2019

Вот хакерский способ сделать это, который, я уверен, сломается во множестве крайних случаев:

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"
person eddi    schedule 05.05.2015

Хотя вопрос старый, и награда уже присуждена, я хотел бы расширить отличный ответ gersht, который отлично работает для получения имени объекта с самой левой стороны. Однако интеграция этой функциональности в рабочий процесс dplyr еще не решена, за исключением использования этого подхода на самом последнем этапе пайпа.

Поскольку я часто использую dplyr, я создал группу пользовательских функций-оболочек вокруг общих глаголов dplyr, которые я называю metadplyr (я все еще играю с функциональностью, поэтому я еще не загрузил ее на github ).

По сути, эти функции создают новый класс с именем meta_tbl поверх таблицы и записывают определенные вещи в атрибутах этого объекта. Применительно к проблеме OP я привожу простой пример с filter, но процедура работает и с любым другим глаголом dplyr.

В моем исходном семействе функций я использую немного другие имена, чем dplyr, но этот подход также работает при «перезаписи» исходных глаголов dplyr.

Ниже представлена ​​новая функция фильтра, которая превращает фрейм данных или табличку в meta_tbl и записывает исходное имя объекта lhs в атрибут .name. Здесь я использую укороченную версию подхода Гершта.

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"))

Это может быть плохим примером, так как канал не продолжается, но теоретически мы могли бы использовать этот канал, включая исходное имя, и передавать его в дальнейших вызовах функций.

person TimTeaFan    schedule 24.10.2019

Вдохновленный ссылкой, упомянутой гершт

Вы можете вернуться на 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}
person Kevin Ho    schedule 24.10.2019

Я не верю, что это возможно без добавления дополнительного аргумента к вашему my_function. При объединении функций с dplyr он автоматически преобразует df в объект tbl_df, отсюда и новое имя "." в области dplyr для упрощения конвейерной обработки.

Ниже приведен очень хитрый способ с dplyr, который просто добавляет дополнительный аргумент, чтобы вернуть имя исходного data.frame.

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

Обратите внимание, что вы не можете просто передать df с исходной функцией, потому что объект tbl_df автоматически передается всем последующим функциям.

person cdeterman    schedule 05.05.2015
comment
Я бы сказал, что это хуже, чем делать my_fn=function(tbl, origin.df) print(origin.df); df %>% my_fn("df"), что явно бессмысленно. - person eddi; 05.05.2015
comment
@eddi Я полностью согласен, я только намеревался предоставить то, что запросил ОП, без использования дополнительного пакета. Все это, как вы говорите, бессмысленно. - person cdeterman; 05.05.2015