将Tibble转换为参数列表

时间:2017-09-06 16:13:43

标签: r parameter-passing do.call tibble

我正在尝试将Tibble转换为函数调用的参数列表。我这样做的原因是因为我想创建一个简单的文件规范Tibble,用于读取具有不同列的多个固定宽度文件。这样我只需要使用pull和select指定文件中的列,然后我就可以自动加载和解析文件。但是,我遇到了使用cols对象指定列格式的问题。

对于这个例子,假设我有一个格式的Tibble:

> (filespec <- tibble(ID = c("Title", "Date", "ATTR"), Length = c(23, 8, 6), Type = c("col_character()", "col_date()", "col_factor(levels=c(123456,654321)")))
# A tibble: 3 x 3
     ID Length                               Type
  <chr>  <dbl>                              <chr>
1 Title     23                    col_character()
2  Date      8                         col_date()
3  ATTR      6 col_factor(levels=c(123456,654321)

我希望得到格式为的cols对象:

> (cols(Title = col_character(), Date = col_date(), ATTR=col_factor(levels=c(123456,654321))))
cols(
  Title = col_character(),
  Date = col_date(format = ""),
  ATTR = col_factor(levels = c(123456, 654321), ordered = FALSE)
)

从我读过的其他问题我知道可以用do.call完成。但我无法弄清楚如何以自动方式将列ID和类型转换为cols对象。这是我尝试过的一个例子......

> do.call(cols, select(filespec,ID, Type))
Error in switch(x, `_` = , `-` = col_skip(), `?` = col_guess(), c = col_character(),  : 
  EXPR must be a length 1 vector

我假设select需要用另一个执行行到参数映射的函数包装,这是怎么做的?

2 个答案:

答案 0 :(得分:1)

我可能会稍微改变一下,并将文件规范存储在一个简单的列表中:

library(purrr)
library(readr)
filespec <- list(Title = list(Length = 23,
                              Type = col_character()),
                 Date = list(Length = 8,
                             Type = col_date()),
                 ATTR = list(Length = 6,
                             Type = col_factor(levels = 123456,654321)))

a <- at_depth(.x = filespec,.depth = 1,.f = "Type")
> invoke(.f = cols,.x = a)

cols(
  Title = col_character(),
  Date = col_date(format = ""),
  ATTR = col_factor(levels = 123456, ordered = 654321, include_na = FALSE)
)

,或者

> invoke(.f = cols,.x = a[c('Title','ATTR')])
cols(
  Title = col_character(),
  ATTR = col_factor(levels = 123456, ordered = 654321, include_na = FALSE)
)

答案 1 :(得分:0)

  

tl; dr :有很多东西使它比看起来更复杂。但这是可行的,一旦理解了各个部分,最终的代码(在最后提供)并不复杂。

正如评论中所讨论的,我从根本上更喜欢Joran的方法。事实上,每当你发现自己在字符串中存储代码表达式时,这应该引发警钟:它是一种反模式,称为stringly typed code(一种即兴的,与strongly typed code完全相反) 。不幸的是,R充满了字符串类型的代码。

那就是说,你的用例(基于文件的配置)本身就是一个好主意。我会考虑以不同于R代码片段的格式存储信息。但是,它确实有效。因此,让我们探讨为什么您的代码不起作用。

第一个问题是:你将一个tibble传递给do.call。 Tibbles是列的列表,因此do.call允许这样做。但是,在内部,您的调用将转换为等效于:

的内容
cols(
    ID = c("Title", "Date", "ATTR"),
    Type = c("col_character()", "col_date()", "col_factor(levels=c(123456,654321))")
)

- 但这不是我们想要的代码!

我们需要在这里解决两件事:

  1. 我们需要使用Type列作为参数 values ,将ID列作为参数名称。我们可以通过创建一个名称为ID且值为Type的新列表来完成此操作:setNames(Type, ID)
  2. cols不知道如何处理字符串参数。它需要列规范 - Collector类型的对象。

    换句话说,无论您是写"col_date()"还是col_date(),都会有很大的不同。

  3. 要解决这个问题,我们需要做一些相当复杂的事情:我们需要将Type列解析为R代码,我们需要评估生成的解析表达式。 R提供了两个方便的函数(分别为parseeval)来完成此任务。但是不要让两个简单函数的存在欺骗你:这是一个非常复杂的操作。 R本质上需要在代码片段上运行完整的解析器和解释器。如果代码不符合您的预期,它会变得更加毛茸茸。例如,文本可能包含代码unlink('/', recursive = TRUE)而不是col_date()。然后R会很高兴地擦掉你的硬盘。

    这只是一个 parse / eval复杂且通常避免的原因。其他原因包括:如果代码中存在解析错误会发生什么(实际上,您的代码确实包含缺少的右括号...)?

    但是我们走了。现在我们将所有部分组合在一起,我们可以相对容易地加入它们:

    filespec %>%
        mutate(Parsed = lapply(Type, function (x) parse(text = x, encoding = 'UTF-8'))) %>%
        mutate(ColSpec = lapply(Parsed, eval)) %>%
        with(setNames(ColSpec, ID)) %>%
        do.call(cols, .)
    

    逐段执行此代码以查看其功能,并说服自己它正常工作。