2017-09-06 71 views
2

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

对于这个例子让我们假设我有格式的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 

我假设的选择需要与执行行参数映射另一个函数来包装,这是怎么完成的?

+1

你可能能够用do.call做到这一点,但是你的代码并不能远程做你想做的事 - 你需要先理解do.call在你之前实际做了些什么可以使用它。 –

+0

我是R新手,所以这都是一种学习体验。我想我明白do.call做了什么,它调用一个函数,其他参数作为参数。根据我对下面答案的评论,我认为在这里逃避的是如何以自动化的方式创建一个命名列表。我不想手动输入所有的field = type参数,我把它们分成两列,我只想让R为我创建指定的列表。 – RandomString

+0

是的,你实际上在你的问题描述中发现。从你的问题来看,你似乎没有理解这一点。但是这部分问题实际上可以通过'setNames'方便地解决。另一个更大的问题是你的参数是字符串,而不是代码。因此,你首先需要对它们进行评估,尽管这是可能的(通过解析/评估),但它很混乱,可能不是一个好主意(以及你的情况)。乔兰的方法是优越的。 –

回答

0

TL;博士:有很多事情这使得这看起来更复杂。但是,这是可行的,并且一旦个别部分被理解,所得到的代码(在最后提供)并不复杂。

正如评论中所讨论的,我基本上更喜欢乔兰的方法。事实上,每当你发现自己在字符串中存储代码表达式时,这应该引起警钟:它是一种反模式,称为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列作为参数ID列作为参数。我们可以通过创建一个名称为ID,值为Type的新列表来做到这一点:setNames(Type, ID)
  2. cols不知道如何处理字符串参数。它需要列规格 - 类型为Collector的对象。

    换句话说,不管你写"col_date()"还是col_date(),这都是巨大的差异。

为了解决这个问题,我们需要做一些相当复杂的:我们的东东解析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, .) 

一块执行这个代码块,看看它做什么,并说服自己,它的正常工作。

+1

setNames /与部分正是我所需要的。我知道eval问题,但将在稍后修复它,很可能是通过类型字符串 - > S3对象的简单映射。 – RandomString

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) 
) 
+0

我喜欢这个解决方案,它的工作原理!我使用Tibble的主要原因是最终我可能有50-60列,并且在源文件中维护该列表可能很烦人,所以我希望能够通过csv读入。是否有一种简单的方法可以将我需要的两列从t take中移出并将它们变成一个列表?我是R新手,我认为用自动化方式创建一个命名列表的方法正在逃避我。 – RandomString