使用 readr 包中的 tidy-selection 指定跨多个列的列类型

use*_*435 5 file-io r readr tidyselect across

我尝试使用read_csvfrom{readr}将文件读入 R。为了演示我的真正问题,我首先CSV将参数重置为 5(默认为 1000)guess_max

\n
library(readr)\nformals(read_csv)$guess_max <- 5\n
Run Code Online (Sandbox Code Playgroud)\n

并以较小的文字数据为例:

\n
csv <- I(\n"ID, Col1, Col2, VarA, VarB, VarC\n1, NA, NA, NA, NA, NA\n2, NA, NA, NA, NA, NA\n3, NA, NA, NA, NA, NA\n4, NA, NA, NA, NA, NA\n5, 0, 1, x, y, z\n6, NA, NA, NA, NA, NA")\n\nread_csv(csv)\n\n# # A tibble: 6 \xc3\x97 6\n#      ID  Col1    Col2    VarA   VarB   VarC \n#   <dbl>  <lgl>   <lgl>   <lgl>  <lgl>  <lgl>\n# 1     1  NA      NA      NA     NA     NA   \n# 2     2  NA      NA      NA     NA     NA   \n# 3     3  NA      NA      NA     NA     NA   \n# 4     4  NA      NA      NA     NA     NA   \n# 5     5  FALSE*  TRUE*   NA*    NA*    NA*\n# 6     6  NA      NA      NA     NA     NA\n
Run Code Online (Sandbox Code Playgroud)\n

*: 出现解析问题

\n
\n

受 影响guess_max,仅前 5 行(列名称和ID1 至 4)用于猜测列类型。由于 1 到 4 中的值ID全部缺失,因此所有列都被猜测为logical并被错误解析:

\n
    \n
  • 0, 1(整数) \xe2\x86\x92 FALSE, TRUE(逻辑)
  • \n
  • \'x\', \'y\', \'z\'(字符)\xe2\x86\x92 NA(逻辑)
  • \n
\n

在这种情况下我必须col_types手动设置:

\n
read_csv(csv, col_types = cols(Col1 = col_integer(), Col2 = col_integer(),\n                               VarA = col_character(), VarB = col_character(), VarC = col_character()))\n\n# # A tibble: 6 \xc3\x97 6                                                                                                   \n#      ID  Col1  Col2 VarA  VarB  VarC \n#   <dbl> <int> <int> <chr> <chr> <chr>\n# 1     1    NA    NA NA    NA    NA   \n# 2     2    NA    NA NA    NA    NA   \n# 3     3    NA    NA NA    NA    NA   \n# 4     4    NA    NA NA    NA    NA   \n# 5     5     0     1 x     y     z    \n# 6     6    NA    NA NA    NA    NA\n
Run Code Online (Sandbox Code Playgroud)\n

当列太多时,一一提供列类型很烦人。如果我想要指定的那些列的名称具有某些模式,我希望使用类似<tidy-select>的语法来指定跨多个列的类型,例如across(){dplyr}. 伪代码如下:

\n
read_csv(csv, col_types = cols(across(starts_with("Col"), col_integer()),\n                               across(starts_with("Var"), col_character())))\n
Run Code Online (Sandbox Code Playgroud)\n

是否可以readr单独使用或使用其他附加包?

\n

提前致谢!

\n
\n

编辑

\n

我需要使用col_xxx()而不是它们的缩写(\'i\'\'c\'等)来创建更通用的列规范,例如

\n
cols(across(contains("Date"), col_date(format = "%m-%d-%Y")),\n     across(Fct1:Fct9, col_factor(levels = custom_levels)))\n
Run Code Online (Sandbox Code Playgroud)\n

Dar*_*sai 7

read_delim()系列使用 tidy-selection 来选择带有参数的列col_select。您可以利用此参数将 tidy-selection 合并到列类型的规范中。下面是一个简单的实现。关键是设置n_max = 0L为只读取列名行。

版本1

col_across <- function(.cols, .fns, file) {
  col_selected <- read_csv(file, n_max = 0L, col_select = {{.cols}}, show_col_types = FALSE)
  lapply(col_selected, function(x) .fns)
}
Run Code Online (Sandbox Code Playgroud)
df <- read_csv(csv, col_types = c(col_across(starts_with("Col"), col_integer(), csv),
                                  col_across(VarA:VarC, col_factor(c('x', 'y', 'z')), csv)))
Run Code Online (Sandbox Code Playgroud)

上面的方法很简单,但还可以,但有一些缺点

  1. 需要将相同的文件源(即对象csv)传递给每个col_across().
  2. read_delim家族包括多个变体,例如read_csvread_csv2read_tsv。调用时df <- read_xxx(...),必须确认col_across()使用了一致的read_xxx读取列名。

版本2

col_across开发了一个改进版本,可以自动检测read_xxx使用的是哪个,并从外部调用中检索文件源。

col_across <- function(.cols, .fns) {
  sc <- sys.call(1L)
  sc <- match.call(match.fun(sc[[1L]]), sc)
  read_call <- sc[c(1L, match("file", names(sc), 0L))]
  read_call$n_max <- 0L
  read_call$col_select <- substitute(.cols)
  read_call$show_col_types <- FALSE
  lapply(eval(read_call, parent.frame()), function(x) .fns)
}
Run Code Online (Sandbox Code Playgroud)
df <- read_csv(csv, col_types = c(col_across(starts_with("Col"), col_integer()),
                                  col_across(VarA:VarC, col_factor(c('x', 'y', 'z')))))
Run Code Online (Sandbox Code Playgroud)

请注意,此版本的col_across只能在read_delim()家族内部使用,就像在 中across一样。mutatedplyr


检查色谱柱规格

spec(df)

# cols(
#   ID = col_double(),
#   Col1 = col_integer(),
#   Col2 = col_integer(),
#   VarA = col_factor(levels = c("x", "y", "z"), ordered = FALSE, include_na = FALSE),
#   VarB = col_factor(levels = c("x", "y", "z"), ordered = FALSE, include_na = FALSE),
#   VarC = col_factor(levels = c("x", "y", "z"), ordered = FALSE, include_na = FALSE)
# )
Run Code Online (Sandbox Code Playgroud)