【问题标题】:Convert Tibble to Parameter List将小标题转换为参数列表
【发布时间】:2017-09-06 16:13:43
【问题描述】:

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

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

> (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

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

【问题讨论】:

  • 可能可以使用do.call 执行此操作,但您的代码并不能远程执行您想要的操作——您需要先了解@ 987654325@实际上是在你可以使用它之前。
  • 我是 R 新手,所以这都是一次学习经历。我想我理解 do.call 的作用,它以其他参数作为参数调用一个函数。根据我对下面答案的评论,我认为在这里逃避的是如何以自动方式创建命名列表。我不想手动输入所有 field=type 参数,我将它们放在两列中,我只想让 R 为我创建命名列表。
  • 是的,您实际上在问题描述中很准确。从您的问题来看,您似乎没有理解这一点。但是这部分问题实际上可以使用setNames 轻松解决。另一个更大的问题是您的参数是字符串,而不是代码。因此,您首先需要评估它们,虽然这是可能的(通过 parse/eval),但它很混乱,并且可能不是一个好主意(嗯;在您的情况下可能是这样)。 Joran 的方法更胜一筹。

标签: r parameter-passing do.call tibble


【解决方案1】:

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

正如 cmets 中所讨论的,我从根本上更喜欢 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 列作为参数,并使用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, .)

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

【讨论】:

  • setNames / with 部分正是我所需要的。我知道 eval 问题,但打算在以后修复它,很可能通过类型字符串的简单映射 -> S3 对象。
【解决方案2】:

我可能会以不同的方式处理这个问题,并将文件规范存储在一个简单的列表中:

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

【讨论】:

  • 我喜欢这个解决方案,而且它有效!我使用 tibble 的主要原因是因为最后我可能有 50-60 列并且在源文件中维护该列表可能很烦人,所以我希望通过 csv 读入来做到这一点。有没有一种简单的方法可以将我需要的两列从小标题中取出并将它们变成一个列表?我是 R 新手,我认为以自动方式创建命名列表的方法正在逃避我。
猜你喜欢
  • 2020-02-11
  • 2018-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-01-17
  • 1970-01-01
相关资源
最近更新 更多