假设我们有一个包含多个data.csv文件的文件夹,每个文件包含相同数量的变量,但每个变量来自不同的时间。 在R中是否有一种方法可以同时导入它们而不是逐个导入?

我的问题是我有大约2000个数据文件要导入,并且只能通过使用代码单独导入它们:

read.delim(file="filename", header=TRUE, sep="\t")

效率不高。


当前回答

我喜欢使用list.files(), lapply()和list2env()(或fs::dir_ls(), purrr::map()和list2env())的方法。这看起来既简单又灵活。

或者,您可以尝试小包{tor} (to-R):默认情况下,它将文件从工作目录导入到列表(list_*()变量)或全局环境(load_*()变量)。

例如,这里我使用tor::list_csv()将我工作目录中的所有.csv文件读入一个列表:

library(tor)

dir()
#>  [1] "_pkgdown.yml"     "cran-comments.md" "csv1.csv"        
#>  [4] "csv2.csv"         "datasets"         "DESCRIPTION"     
#>  [7] "docs"             "inst"             "LICENSE.md"      
#> [10] "man"              "NAMESPACE"        "NEWS.md"         
#> [13] "R"                "README.md"        "README.Rmd"      
#> [16] "tests"            "tmp.R"            "tor.Rproj"

list_csv()
#> $csv1
#>   x
#> 1 1
#> 2 2
#> 
#> $csv2
#>   y
#> 1 a
#> 2 b

现在我用tor::load_csv()将这些文件加载到我的全局环境中:

# The working directory contains .csv files
dir()
#>  [1] "_pkgdown.yml"     "cran-comments.md" "CRAN-RELEASE"    
#>  [4] "csv1.csv"         "csv2.csv"         "datasets"        
#>  [7] "DESCRIPTION"      "docs"             "inst"            
#> [10] "LICENSE.md"       "man"              "NAMESPACE"       
#> [13] "NEWS.md"          "R"                "README.md"       
#> [16] "README.Rmd"       "tests"            "tmp.R"           
#> [19] "tor.Rproj"

load_csv()

# Each file is now available as a dataframe in the global environment
csv1
#>   x
#> 1 1
#> 2 2
csv2
#>   y
#> 1 a
#> 2 b

如果您需要读取特定的文件,您可以使用regexp, ignore匹配它们的文件路径。大小写颠倒。


为了获得更大的灵活性,请使用list_any()。它允许您通过参数.f提供reader函数。

(path_csv <- tor_example("csv"))
#> [1] "C:/Users/LeporeM/Documents/R/R-3.5.2/library/tor/extdata/csv"
dir(path_csv)
#> [1] "file1.csv" "file2.csv"

list_any(path_csv, read.csv)
#> $file1
#>   x
#> 1 1
#> 2 2
#> 
#> $file2
#>   y
#> 1 a
#> 2 b

通过…传递附加参数或者在函数内部。

path_csv %>% 
  list_any(readr::read_csv, skip = 1)
#> Parsed with column specification:
#> cols(
#>   `1` = col_double()
#> )
#> Parsed with column specification:
#> cols(
#>   a = col_character()
#> )
#> $file1
#> # A tibble: 1 x 1
#>     `1`
#>   <dbl>
#> 1     2
#> 
#> $file2
#> # A tibble: 1 x 1
#>   a    
#>   <chr>
#> 1 b

path_csv %>% 
  list_any(~read.csv(., stringsAsFactors = FALSE)) %>% 
  map(as_tibble)
#> $file1
#> # A tibble: 2 x 1
#>       x
#>   <int>
#> 1     1
#> 2     2
#> 
#> $file2
#> # A tibble: 2 x 1
#>   y    
#>   <chr>
#> 1 a    
#> 2 b

其他回答

下面是一些使用R base将.csv文件转换为data.frame的选项,以及一些可用来读取R文件的包。

这比下面的选项要慢。

# Get the files names
files = list.files(pattern="*.csv")
# First apply read.csv, then rbind
myfiles = do.call(rbind, lapply(files, function(x) read.csv(x, stringsAsFactors = FALSE)))

编辑:-更多的额外选择使用数据。表和读取器

一个fread()版本,它是数据的函数。表方案。这是目前为止R中最快的选项。

library(data.table)
DT = do.call(rbind, lapply(files, fread))
# The same using `rbindlist`
DT = rbindlist(lapply(files, fread))

使用readr,这是另一个读取csv文件的包。它比fread慢,比base R快,但功能不同。

library(readr)
library(dplyr)
tbl = lapply(files, read_csv) %>% bind_rows()

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

在我看来,大多数其他答案都被里约热内卢::import_list淘汰了,这是一个简洁的一行程序:

library(rio)
my_data <- import_list(dir("path_to_directory", pattern = ".csv"), rbind = TRUE)

任何额外的参数都传递给里约热内卢::import。里约热内卢几乎可以处理R可以读取的任何文件格式,而且它使用数据。桌子的fread在可能的地方,所以它也应该快。

我喜欢使用list.files(), lapply()和list2env()(或fs::dir_ls(), purrr::map()和list2env())的方法。这看起来既简单又灵活。

或者,您可以尝试小包{tor} (to-R):默认情况下,它将文件从工作目录导入到列表(list_*()变量)或全局环境(load_*()变量)。

例如,这里我使用tor::list_csv()将我工作目录中的所有.csv文件读入一个列表:

library(tor)

dir()
#>  [1] "_pkgdown.yml"     "cran-comments.md" "csv1.csv"        
#>  [4] "csv2.csv"         "datasets"         "DESCRIPTION"     
#>  [7] "docs"             "inst"             "LICENSE.md"      
#> [10] "man"              "NAMESPACE"        "NEWS.md"         
#> [13] "R"                "README.md"        "README.Rmd"      
#> [16] "tests"            "tmp.R"            "tor.Rproj"

list_csv()
#> $csv1
#>   x
#> 1 1
#> 2 2
#> 
#> $csv2
#>   y
#> 1 a
#> 2 b

现在我用tor::load_csv()将这些文件加载到我的全局环境中:

# The working directory contains .csv files
dir()
#>  [1] "_pkgdown.yml"     "cran-comments.md" "CRAN-RELEASE"    
#>  [4] "csv1.csv"         "csv2.csv"         "datasets"        
#>  [7] "DESCRIPTION"      "docs"             "inst"            
#> [10] "LICENSE.md"       "man"              "NAMESPACE"       
#> [13] "NEWS.md"          "R"                "README.md"       
#> [16] "README.Rmd"       "tests"            "tmp.R"           
#> [19] "tor.Rproj"

load_csv()

# Each file is now available as a dataframe in the global environment
csv1
#>   x
#> 1 1
#> 2 2
csv2
#>   y
#> 1 a
#> 2 b

如果您需要读取特定的文件,您可以使用regexp, ignore匹配它们的文件路径。大小写颠倒。


为了获得更大的灵活性,请使用list_any()。它允许您通过参数.f提供reader函数。

(path_csv <- tor_example("csv"))
#> [1] "C:/Users/LeporeM/Documents/R/R-3.5.2/library/tor/extdata/csv"
dir(path_csv)
#> [1] "file1.csv" "file2.csv"

list_any(path_csv, read.csv)
#> $file1
#>   x
#> 1 1
#> 2 2
#> 
#> $file2
#>   y
#> 1 a
#> 2 b

通过…传递附加参数或者在函数内部。

path_csv %>% 
  list_any(readr::read_csv, skip = 1)
#> Parsed with column specification:
#> cols(
#>   `1` = col_double()
#> )
#> Parsed with column specification:
#> cols(
#>   a = col_character()
#> )
#> $file1
#> # A tibble: 1 x 1
#>     `1`
#>   <dbl>
#> 1     2
#> 
#> $file2
#> # A tibble: 1 x 1
#>   a    
#>   <chr>
#> 1 b

path_csv %>% 
  list_any(~read.csv(., stringsAsFactors = FALSE)) %>% 
  map(as_tibble)
#> $file1
#> # A tibble: 2 x 1
#>       x
#>   <int>
#> 1     1
#> 2     2
#> 
#> $file2
#> # A tibble: 2 x 1
#>   y    
#>   <chr>
#> 1 a    
#> 2 b

使用许多文件和许多核心,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都使用相同的分隔符。