在R中使用arrow读入多个带有open_dataset的CSV时,如何将int转换为double?

nnvyjq4y  于 9个月前  发布在  其他
关注(0)|答案(2)|浏览(87)

我正在尝试将一个包含CSV文件的大文件夹转换为parquet。数据集太大,内存无法容纳,所以我使用箭头。
对于某些列,有些文件只有整数,而其他文件有decimal/float/double。我希望它在发生这种情况时将int 64转换为double。但它却抛出了一个关于int和double“不兼容”的错误。即使我指定unify_schemas = TRUE,我仍然得到错误。
我相信arrow只是查看每个文件的前N行来猜测文件类型。我的真实的数据集有一个前500,000行为整数的列,然后出现一个浮点数。所以arrow只能看到整数。是否有一个选项可以强制arrow在推断类型之前读取 * 所有 * 行?
目前我使用R,但我会接受Python的解决方案。

复制步骤

data/01.csv

a,b
1,1
2,2

字符串
data/02.csv

a,b
11,1.1
22,2.2


main.R

library(tidyverse)
library(arrow)

csvs <- open_dataset(
  sources = "data/", 
  format = "csv",
  unify_schemas = TRUE,
)

csvs |> glimpse()

预期行为

两个文件成功读入一个嵌套框架。列b被视为double。文件01.csv中列b的值被隐式转换为double

实际行为

open_dataset()中的错误:!无效:无法合并:字段b具有不兼容的类型:double vs int 64

我所尝试的

我尝试编写一个python脚本来将CSV文件合并为一个,然后只读入一个文件。(open_dataset还不能处理一个文件,只能处理一个文件的文件夹。)我仍然得到一个错误。
我有数百个数据集要这样做。所以我更喜欢一个解决方案,其中我不必显式枚举所有应该像这样转换的列。特别是,open_dataset()schema参数需要所有列或没有列。我似乎不能只传递一个。(或者有办法做到这一点吗?)
我的下一步是写一个脚本来读取整个数据集作为文本,并搜索每个单元格值的小数。然后重新读取整个数据集,并将.0附加到某些值上。然后我可以(希望)用open_dataset()读取它。我想知道是否有更简单的方法。

kyxcudwk

kyxcudwk1#

虽然schema只接受完整模式或不接受模式,但col_types允许部分模式:

csvs <- open_dataset(
  sources = "data/", 
  format = "csv",
  unify_schemas = TRUE,
  col_types = schema(b = double()))
)

字符串
通过查看文档,我不得不说,在查看open_dataset时,这并不明显,因为它只在更专门的 Package 器函数中有记录(即使在那里,for partial schema也不清楚):https://arrow.apache.org/docs/r/reference/open_delim_dataset.html
我已经打开了一个issue来改进文档。

vwkv1x7d

vwkv1x7d2#

不幸的是,我认为唯一的出路是指定schema=,因为它总是推断数据在指定时没有标题行,所以我们必须指定所有列。
但是,我们可以尝试动态地创建schema=对象。

types <- list.files("~/StackOverflow/5443120/77557377/data", full.names = TRUE) |>
  lapply(open_dataset, format = "csv") |>
  lapply(function(z) strsplit(z$schema$ToString(), "\n")[[1]])
types <- lapply(1:max(lengths(types)), function(i) sapply(types, `[[`, i))
names(types) <- sapply(types, function(z) sub(":.*", "", z[1]))
types <- lapply(types, function(z) unique(sub(".*: ", "", z)))
types
# $a
# [1] "int64"
# $b
# [1] "int64"  "double"
  
some_types <- c(
  # bits?
  "null", "bool",
  # ints
  "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64",
  # floats
  "float16", "float32", "double", "float64",
  # dates/times
  "date32", "date64", "time32", "time64",
  # strings
  "utf8")

types <- lapply(types, function(z) {
  typ <- some_types[max(match(z, some_types))]
  if (typ == "double") typ <- "float64"
  getFunction(typ, where = asNamespace("arrow"))()
})

types
# $a
# Int64
# int64
# $b
# Float64
# double

字符串
我们可以直接使用:

csvs <- open_dataset(
  sources = "~/StackOverflow/5443120/77557377/data", 
  format = "csv",
  unify_schemas = TRUE,
  schema = do.call(schema, types),
  skip = 1
)
collect(csvs)
# # A tibble: 4 × 2
#       a     b
#   <int> <dbl>
# 1     1   1  
# 2     2   2  
# 3    11   1.1
# 4    22   2.2


注意,我必须添加skip=1,因为通过提供schemas=,arrow包假定没有标题行。
当然,some_types中的类比我提供的要多,而且我还没有详尽地测试我的层次结构是否真的能处理所有不同的类型。如果你想安全起见,也许只需要手动调整“length > 1”字段。

相关问题