假设我们有一个包含多个data.csv文件的文件夹,每个文件包含相同数量的变量,但每个变量来自不同的时间。 在R中是否有一种方法可以同时导入它们而不是逐个导入?
我的问题是我有大约2000个数据文件要导入,并且只能通过使用代码单独导入它们:
read.delim(file="filename", header=TRUE, sep="\t")
效率不高。
假设我们有一个包含多个data.csv文件的文件夹,每个文件包含相同数量的变量,但每个变量来自不同的时间。 在R中是否有一种方法可以同时导入它们而不是逐个导入?
我的问题是我有大约2000个数据文件要导入,并且只能通过使用代码单独导入它们:
read.delim(file="filename", header=TRUE, sep="\t")
效率不高。
当前回答
使用plyr::ldply,在读取400个csv文件时,通过启用.parallel选项,大约可以提高50%的速度,每个文件大约30-40 MB。示例包括一个文本进度条。
library(plyr)
library(data.table)
library(doSNOW)
csv.list <- list.files(path="t:/data", pattern=".csv$", full.names=TRUE)
cl <- makeCluster(4)
registerDoSNOW(cl)
pb <- txtProgressBar(max=length(csv.list), style=3)
pbu <- function(i) setTxtProgressBar(pb, i)
dt <- setDT(ldply(csv.list, fread, .parallel=TRUE, .paropts=list(.options.snow=list(progress=pbu))))
stopCluster(cl)
其他回答
如下所示,每个数据帧都应该作为单个列表中的单独元素:
temp = list.files(pattern="*.csv")
myfiles = lapply(temp, read.delim)
这里假设您将这些csv文件放在一个目录(您当前的工作目录)中,并且它们都具有小写扩展名.csv。
如果你想把这些数据帧组合成一个单一的数据帧,请参考其他答案中的解决方案,如do.call(rbind,…),dplyr::bind_rows()或data.table::rbindlist()。
如果你真的想要每个数据帧在一个单独的对象中,即使这通常是不可取的,你可以使用assign执行以下操作:
temp = list.files(pattern="*.csv")
for (i in 1:length(temp)) assign(temp[i], read.csv(temp[i]))
或者,不带赋值,并演示(1)如何清理文件名以及(2)如何使用list2env,您可以尝试以下方法:
temp = list.files(pattern="*.csv")
list2env(
lapply(setNames(temp, make.names(gsub("*.csv$", "", temp))),
read.csv), envir = .GlobalEnv)
但是,最好还是把它们放在一个列表中。
使用许多文件和许多核心,fread xargs cat(如下所述)比前3个答案中最快的解决方案快大约50倍。
rbindlist lapply read.delim 500s <- 1st place & accepted answer
rbindlist lapply fread 250s <- 2nd & 3rd place answers
rbindlist mclapply fread 10s
fread xargs cat 5s
是时候把121401个csv读入一个data.table了。每次平均跑三次,然后四舍五入。每个csv有3列,一个标题行,平均有4.510行。Machine是一个96核的GCP VM。
@A5C1D2H2I1M1N2O1R2T1、@ leersej和@marbel给出的前三个答案本质上都是相同的:对每个文件应用fread(或read.delim),然后rbind/rbindlist结果data.tables。对于小型数据集,我通常使用rbindlist(lapply(list.files("*.csv"),fread))表单。对于中等规模的数据集,我使用并行的mclapply而不是lapply,如果有多个核心,那么lapply速度要快得多。
这比其他r内部的替代方案更好,但对于大量的小型csv来说,在速度问题上不是最好的。在这种情况下,首先使用cat将所有csv连接到一个csv中会更快,就像@Spacedman的答案一样。我将在R中添加一些关于如何做到这一点的细节:
x = fread(cmd='cat *.csv', header=F)
但是,如果每个csv都有一个头呢?
x = fread(cmd="awk 'NR==1||FNR!=1' *.csv", header=T)
如果您有太多文件,以至于*.csv shell glob失败了怎么办?
x = fread(cmd='find . -name "*.csv" | xargs cat', header=F)
如果所有文件都有头文件,而且文件太多怎么办?
header = fread(cmd='find . -name "*.csv" | head -n1 | xargs head -n1', header=T)
x = fread(cmd='find . -name "*.csv" | xargs tail -q -n+2', header=F)
setnames(x,header)
如果结果连接的csv对于系统内存来说太大怎么办?(例如,/dev/shm out of space错误)
system('find . -name "*.csv" | xargs cat > combined.csv')
x = fread('combined.csv', header=F)
头吗?
system('find . -name "*.csv" | head -n1 | xargs head -n1 > combined.csv')
system('find . -name "*.csv" | xargs tail -q -n+2 >> combined.csv')
x = fread('combined.csv', header=T)
最后,如果您不希望将所有.csv文件放在一个目录中,而希望将其放在一组特定的文件中,该怎么办?(而且,它们都有头文件。)(这是我的用例。)
fread(text=paste0(system("xargs cat|awk 'NR==1||$1!=\"<column one name>\"'",input=paths,intern=T),collapse="\n"),header=T,sep="\t")
这和普通的fread xargs cat的速度差不多:)
注:用于数据。表pre-v1.11.6(2018年9月19日),从fread中省略cmd= (cmd=。
总之,如果您对速度感兴趣,并且有很多文件和内核,那么fread xargs cat比前3个答案中最快的解决方案快大约50倍。
更新:这里是我写的一个函数,可以轻松地应用最快的解决方案。我在生产环境中使用了它,但是在信任它之前,您应该用自己的数据彻底测试它。
fread_many = function(files,header=T,...){
if(length(files)==0) return()
if(typeof(files)!='character') return()
files = files[file.exists(files)]
if(length(files)==0) return()
tmp = tempfile(fileext = ".csv")
# note 1: requires awk, not cat or tail because some files have no final newline
# note 2: parallel --xargs is 40% slower
# note 3: reading to var is 15% slower and crashes R if the string is too long
# note 4: shorter paths -> more paths per awk -> fewer awks -> measurably faster
# so best cd to the csv dir and use relative paths
if(header==T){
system(paste0('head -n1 ',files[1],' > ',tmp))
system(paste0("xargs awk 'FNR>1' >> ",tmp),input=files)
} else {
system(paste0("xargs awk '1' > ",tmp),input=files)
}
DT = fread(file=tmp,header=header,...)
file.remove(tmp)
DT
}
更新2:这里是fread_many函数的一个更复杂的版本,用于需要结果数据的情况。表中包含每个csv的inpath的列。在这种情况下,还必须使用sep参数显式地指定csv分隔符。
fread_many = function(files,header=T,keep_inpath=F,sep="auto",...){
if(length(files)==0) return()
if(typeof(files)!='character') return()
files = files[file.exists(files)]
if(length(files)==0) return()
tmp = tempfile(fileext = ".csv")
if(keep_inpath==T){
stopifnot(sep!="auto")
if(header==T){
system(paste0('/usr/bin/echo -ne inpath"',sep,'" > ',tmp))
system(paste0('head -n1 ',files[1],' >> ',tmp))
system(paste0("xargs awk -vsep='",sep,"' 'BEGIN{OFS=sep}{if(FNR>1)print FILENAME,$0}' >> ",tmp),input=files)
} else {
system(paste0("xargs awk -vsep='",sep,"' 'BEGIN{OFS=sep}{print FILENAME,$0}' > ",tmp),input=files)
}
} else {
if(header==T){
system(paste0('head -n1 ',files[1],' > ',tmp))
system(paste0("xargs awk 'FNR>1' >> ",tmp),input=files)
} else {
system(paste0("xargs awk '1' > ",tmp),input=files)
}
}
DT = fread(file=tmp,header=header,sep=sep,...)
file.remove(tmp)
DT
}
注意:在读取csv之前,我的所有解决方案都假设它们都有相同的分隔符。如果不是所有的csv都使用相同的分隔符,可以分批使用rbindlist lapply fread、rbindlist mclapply fread或fread xargs cat,其中批处理中的所有csv都使用相同的分隔符。
除了使用lapply或R中的其他循环构造,您还可以将CSV文件合并到一个文件中。
在Unix中,如果文件没有头文件,那么很简单:
cat *.csv > all.csv
或者如果有标题,你可以找到一个字符串匹配标题,只有标题(即假设标题行都以“年龄”开头),你会这样做:
cat *.csv | grep -v ^Age > all.csv
我认为在Windows中,你可以通过DOS命令框中的COPY和SEARCH(或FIND或其他什么)来做到这一点,但为什么不安装cygwin并获得Unix命令shell的强大功能呢?
只要你的电脑有多个核,下面的代码就能让你以最快的速度处理大数据:
if (!require("pacman")) install.packages("pacman")
pacman::p_load(doParallel, data.table, stringr)
# get the file name
dir() %>% str_subset("\\.csv$") -> fn
# use parallel setting
(cl <- detectCores() %>%
makeCluster()) %>%
registerDoParallel()
# read and bind all files together
system.time({
big_df <- foreach(
i = fn,
.packages = "data.table"
) %dopar%
{
fread(i, colClasses = "character")
} %>%
rbindlist(fill = TRUE)
})
# end of parallel work
stopImplicitCluster(cl)
更新于20/04/16: 当我发现一个可用于并行计算的新包时,使用以下代码提供了一个替代解决方案。
if (!require("pacman")) install.packages("pacman")
pacman::p_load(future.apply, data.table, stringr)
# get the file name
dir() %>% str_subset("\\.csv$") -> fn
plan(multiprocess)
future_lapply(fn,fread,colClasses = "character") %>%
rbindlist(fill = TRUE) -> res
# res is the merged data.table
这是我开发的代码,读取所有csv文件到R.它将为每个csv文件单独创建一个dataframe,并标题dataframe文件的原始名称(删除空格和.csv),我希望你发现它有用!
path <- "C:/Users/cfees/My Box Files/Fitness/"
files <- list.files(path=path, pattern="*.csv")
for(file in files)
{
perpos <- which(strsplit(file, "")[[1]]==".")
assign(
gsub(" ","",substr(file, 1, perpos-1)),
read.csv(paste(path,file,sep="")))
}