我有一个大型数据集(36 亿行,20 列,我正在处理 100 万行的可管理块)。我的单元格可以包含类似以下字符串的条目:
"sometext "some other text"... ; , "a" some more text \t . ;"
这是一个单元格。 fread() 以某种方式设法理解下一列的开始位置,这真是太神奇了。我想将我的块保存为 txt 文件,以便任何未来的程序(实际上是箭头包)在处理此类情况时不如 fread() 更好,它将理解列的结束和开始位置,但我不想更改外部“...”内的文本。我使用不同的命令遇到很多错误,我认为这是因为所有常用的分隔符都作为字符包含在我的字符串中,但也包含“...”。我想我可以设置一个无意义的分隔符,例如“;-.-;”或者类似的东西永远不会出现在我的 3.6bnx20 细胞中。但显然它需要是一个角色。然而,每个可以想象到的字符都会在某个时刻出现在我的 36 亿行中的某个位置。如何在这样的数据集中设置分隔符?
p.s.:我猜一个恰当的例子是 stackoverflows 着色方案无法识别上面的单元格应该是一个字符串;)
我不会认为仅仅因为列位于正确的位置,读取和写入 csv 就不会破坏文本。让我们创建一些包含两列的示例数据。第一个是您添加了一些随机 Unicode 字符和表情符号的字符串,第二个是对这些字符进行随机采样:
library(data.table)
str <- '"sometext \U002 \U003 😊 🙄 "some other text"... ; , "a" some more text \t . ;"'
set.seed(2024); N_ROWS <- 100
dat <- data.frame(
col1 = rep(str, N_ROWS),
col2 = sapply(seq(N_ROWS), \(., x = str) paste0(sample(strsplit(x, "")[[1]], nchar(x)), collapse = ""))
)
如果我们将其写入文件并将其读回,看起来可能没问题:
fwrite(dat, "./tmp.csv")
dat_dt <- fread("./tmp.csv", data.table = FALSE)
names(dat) # [1] "col1" "col2"
names(dat_dt) # [1] "col1" "col2"
但是,虽然列数是正确的,但使用默认的
fread()
和 fwrite()
选项,引号现在加倍:
print(dat$col1[1], quote = FALSE)
# [1] "sometext \001 \002 \003 😊 🙄 "some other text"... ; , "a" some more text \t . ;"
print(dat_dt$col1[1], quote = FALSE)
# [1] ""sometext \001 \002 \003 😊 🙄 ""some other text""... ; , ""a"" some more text \t . ;""
identical(dat_dt, dat) # FALSE
解决此问题的最佳方法是使用 csv 以外的格式,例如 Parquet。但是,如果您必须使用纯文本格式,那么您需要一个分隔符,该分隔符要么不在文本中,要么可以在文本中替换。
\U001
。这是标题开始字符,它是不可打印且已过时,因此不太可能出现在您的数据中,如果存在,则删除它应该没有问题。万一您需要此字符,Unicode 有大约一百万其他字符您可以选择用于相同目的。
这里有一个函数,可以从数据中删除
"\U001"
,然后使用它作为分隔符保存文件:
save_clean <- function(df, outfile = "./tmp.csv", sep = "\U001", repl = "") {
char_cols <- names(df)[sapply(df, is.character)]
df[char_cols] <- lapply(df[char_cols], \(x) gsub(sep, repl, x))
fwrite(df, outfile, sep = sep, quote = FALSE)
}
这将允许您保存和读取文件:
save_clean(dat)
dat_dt <- fread("./tmp.csv", sep = "\U001", data.table = FALSE, quote = "", strip.white = FALSE)
identical(dat, dat_dt) # TRUE
这也适用于基础 R 和
readr
:
dat_base <- read.csv("./tmp.csv", sep = "\U001", quote = "")
dat_readr <- readr::read_delim("./tmp.csv", delim = "\U001", quote = "") |>
data.frame()
identical(dat, dat_base) # [1] TRUE
identical(dat, dat_readr) # [1] TRUE
如果您的文本可能以您想要保留的空格开头,那么这样做很重要
quote = ""
和strip.white=FALSE
。
我认为这可能效率低下,因为当我怀疑数据中出现
gsub()
时,它会浪费时间与"\U001"
。我建议你提前流一下文件,看看是否需要替换角色。在 Linux/Mac 上:
grep -P -l '\x01' *.csv
或者在 Windows PowerShell 上(可能有更优雅的方式):
Get-ChildItem -Path . -Filter *.csv -Recurse | ForEach-Object {
if (Select-String -Path $_.FullName -Pattern ([char]0x01) -Quiet) {
$_.FullName
}
}
这些命令将返回包含该字符的 csv 文件列表。如果没有,您可以使用
fwrite(df, "./tmp.csv", sep = "\U0001", quote = FALSE)
正常保存并跳过可能昂贵的替换。