我正在尝试将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需要用另一个执行行到参数映射的函数包装,这是怎么做的?
答案 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))")
)
- 但这不是我们想要的代码!
我们需要在这里解决两件事:
Type
列作为参数 values ,将ID
列作为参数名称。我们可以通过创建一个名称为ID
且值为Type
的新列表来完成此操作:setNames(Type, ID)
。 cols
不知道如何处理字符串参数。它需要列规范 - Collector
类型的对象。
换句话说,无论您是写"col_date()"
还是col_date()
,都会有很大的不同。
要解决这个问题,我们需要做一些相当复杂的事情:我们需要将Type
列解析为R代码,我们需要评估生成的解析表达式。 R提供了两个方便的函数(分别为parse
和eval
)来完成此任务。但是不要让两个简单函数的存在欺骗你:这是一个非常复杂的操作。 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, .)
逐段执行此代码以查看其功能,并说服自己它正常工作。