R 将TIBLE转换为参数列表

R 将TIBLE转换为参数列表,r,parameter-passing,do.call,tibble,R,Parameter Passing,Do.call,Tibble,我正在尝试将TIBLE转换为函数调用的参数列表。我这样做的原因是,我想创建一个简单的文件规范Tibble,用于读取具有不同列的多个固定宽度文件。这样,我只需要使用pull和select指定文件中的列,然后就可以自动加载和解析文件。但是,我在使用cols对象指定列格式时遇到了问题 在本例中,假设我有一个如下格式的TIBLE: > (filespec <- tibble(ID = c("Title", "Date", "ATTR"), Length = c(23, 8, 6), Type

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

在本例中,假设我有一个如下格式的TIBLE:

> (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(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需要使用另一个执行行到参数映射的函数进行包装,这是如何实现的?

我可能会采用稍微不同的方法,并将文件规格存储在一个简单的列表中:

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)
)
tl;医生:有很多事情使这比看起来更复杂。但这是可行的,并且一旦理解了各个部分,生成的代码(最后提供)就不复杂了

正如评论中所讨论的,我基本上更喜欢乔兰的方法。事实上,当您发现自己将代码表达式存储在字符串中时,这应该会引起警钟:这是一种反模式,称为(riff-on,与之相反)。不幸的是,R中充满了严格类型的代码

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

第一个问题是:将TIBLE传递给
do.call
。TIBLES是列的列表,因此
do.call
允许这样做。但是,在内部,您的呼叫被转换为等同于:

cols(
    ID = c("Title", "Date", "ATTR"),
    Type = c("col_character()", "col_date()", "col_factor(levels=c(123456,654321))")
)
-但这根本不是我们想要的代码

我们需要解决两个问题:

  • 我们需要使用
    Type
    列作为参数值,使用
    ID
    列作为参数名称。我们可以通过创建一个新列表来实现这一点,该列表将
    ID
    作为名称,将
    Type
    作为值:
    setNames(Type,ID)

  • cols
    不知道如何处理字符串参数。它需要列规范-类型为
    收集器的对象

    换言之,无论您是编写
    “col\u date()”
    还是
    col\u date()
    ,这都是一个巨大的差异

  • 为了解决这个问题,我们需要做一些相当复杂的事情:我们需要将
    Type
    列解析为R代码,并且我们需要评估解析后的表达式。R提供了两个方便的函数(分别是
    parse
    eval
    )来完成这一任务。但不要让两个简单函数的存在欺骗了你:这是一个极其复杂的操作。R基本上需要对代码片段运行完整的解析器和解释器。如果代码不是你所期望的,它会变得更加复杂。例如,文本可能包含代码
    unlink('/',recursive=TRUE)
    ,而不是
    col\u 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, .)
    

    逐段执行此代码,查看它的功能,并让自己相信它工作正常。

    您可以使用
    do.调用
    来执行此操作,但您的代码,就其现状而言,不远程做你想做的事-你需要首先了解
    做什么。在你可以使用它之前,call
    实际上做什么。我是R的新手,所以这都是一次学习经验。我想我理解了do.call的作用,它调用一个函数,并使用其他参数作为参数。根据我对下面答案的评论,我想我在这里逃避的是如何以自动方式创建命名列表。我不想手动键入所有的field=type参数,我将它们分为两列,我只想让R为我创建命名列表。是的,您在问题描述中的位置是正确的。从你的问题来看,你似乎不明白这一点。但这部分问题实际上可以通过使用
    setNames
    轻松解决。另一个更大的问题是,参数是字符串,而不是代码。因此,您首先需要对它们进行评估,虽然这是可能的(通过parse/eval),但这很混乱,可能不是一个好主意。Joran的方法很好。我喜欢这个解决方案,它很有效!我使用TIBLE的主要原因是因为最终我可能会有50-60列,而在源文件中维护该列表可能会很烦人,所以我希望通过csv以读入的方式完成。有没有一种简单的方法可以将我需要的两列从tibble中取出,并将它们转换成一个列表?我是R新手,我认为以自动方式创建命名列表的方法让我不知所措。setNames/with部分正是我所需要的。我知道eval问题,但打算稍后再解决,最有可能的方法是从类型strings->S3 objects进行简单映射。
    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, .)