3

我正在尝试将 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 和 Type 转换为 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

我假设选择需要用另一个执行行到参数映射的函数包装,这是如何完成的?

4

2 回答 2

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)
)
于 2017-09-06T16:43:32.323 回答
1

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

正如评论中所讨论的,我从根本上更喜欢 Joran 的方法。事实上,每当您发现自己将代码表达式存储在字符串中时,都应该敲响警钟:它是一种被称为字符串类型代码的反模式(强类型代码的一种重复,与强类型代码完全相反)。不幸的是,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列用作参数,并将ID列用作参数名称。我们可以通过创建一个具有ID名称和Type值的新列表来做到这一点:setNames(Type, ID).

  2. cols不知道如何处理字符串参数。它需要列规范——类型的对象Collector

    换句话说,无论你是写"col_date()"还是写col_date().

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

这只是/复杂且通常被避免的原因之一其他原因包括:如果代码中出现解析错误会发生什么(事实上,您的代码确实包含缺少右括号……)?parseeval

但我们走了。现在我们已经将所有部分组合在一起,我们可以相对轻松地将它们连接起来:

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, .)

逐段执行这段代码,看看它做了什么,并说服自己它工作正常。

于 2017-09-07T12:09:21.023 回答