如何一次导入多个.csv文件?

Joj*_*Ono 195 csv import r r-faq

假设我们有一个包含多个data.csv文件的文件夹,每个文件包含相同数量的变量,但每个变量包含不同的时间.R中有没有办法同时导入它们而不必单独导入它们?

我的问题是我有大约2000个数据文件要导入,只需使用代码就可以单独导入它们:

read.delim(file="filename", header=TRUE, sep="\t")
Run Code Online (Sandbox Code Playgroud)

不是很有效率.

A5C*_*2T1 231

类似下面的内容应该会将每个数据框作为单个列表中的单独元素:

temp = list.files(pattern="*.csv")
myfiles = lapply(temp, read.delim)
Run Code Online (Sandbox Code Playgroud)

这假设您将这些CSV放在一个目录中 - 您当前的工作目录 - 并且所有这些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]))
Run Code Online (Sandbox Code Playgroud)

或者,没有assign,并演示(1)如何清理文件名和(2)显示如何使用list2env,您可以尝试以下方法:

temp = list.files(pattern="*.csv")
list2env(
  lapply(setNames(temp, make.names(gsub("*.csv$", "", temp))), 
         read.csv), envir = .GlobalEnv)
Run Code Online (Sandbox Code Playgroud)

但同样,将它们放在一个列表中通常会更好.

  • 上面的代码非常适合将它们作为单个对象导入,但是当我尝试从数据集中调用列时,它无法识别它,因为它只是一个对象而不是数据框,即我上面的代码版本是:setwd( 'C:/ Users / new / Desktop / Dives / 0904_003')temp <-list.files(pattern =“ *。csv”)ddives <-lapply(temp,read.csv)所以现在每个文件都称为ddives [n ],但是我将如何编写一个循环以使它们成为所有数据帧而不是单个对象?我可以使用data.frame运算符单独实现此功能,但是不确定如何循环执行此操作。@mrdwab (2认同)
  • 对于任何尝试编写函数来使用`assign`来执行此答案的更新版本的人...如果您希望指定的值驻留在全局环境中,请确保设置`inherits = T`. (2认同)

lee*_*sej 103

快速简洁的tidyverse解决方案:(比Base R的 速度快两倍read.csv)

tbl <-
    list.files(pattern = "*.csv") %>% 
    map_df(~read_csv(.))
Run Code Online (Sandbox Code Playgroud)

data.tablefread()甚至可以通过半再次下调的加载时间.(基本R次的1/4 )

library(data.table)

tbl_fread <- 
    list.files(pattern = "*.csv") %>% 
    map_df(~fread(.))
Run Code Online (Sandbox Code Playgroud)

stringsAsFactors = FALSE参数使数据帧因子保持自由.

如果类型转换是厚脸皮的,您可以强制所有列作为fread参数的字符.

tbl <-
    list.files(pattern = "*.csv") %>% 
    map_df(~read_csv(., col_types = cols(.default = "c")))
Run Code Online (Sandbox Code Playgroud)

如果您想深入子目录来构建最终要绑定的文件列表,那么请确保包含路径名,以及在列表中注册文件及其全名.这将允许绑定工作在当前目录之外进行.(将完整的路径名视为像护照一样操作,以允许在目录'边界'之间移动.)

tbl <-
    list.files(path = "./subdirectory/",
               pattern = "*.csv", 
               full.names = T) %>% 
    map_df(~read_csv(., col_types = cols(.default = "c"))) 
Run Code Online (Sandbox Code Playgroud)

正如哈德利在这里描述的那样(大约一半):

col_types实际上和map_df(x, f)......一样

奖励功能 - 在下面的评论中将文件名添加到每个Niks功能请求的记录中:
*do.call("rbind", lapply(x, f))为每条记录添加原始文件.

代码解释:创建一个函数,在初始读取表时将文件名附加到每个记录.然后使用该函数而不是简单filename函数.

read_plus <- function(flnm) {
    read_csv(flnm) %>% 
        mutate(filename = flnm)
}

tbl_with_sources <-
    list.files(pattern = "*.csv", 
               full.names = T) %>% 
    map_df(~read_plus(.))
Run Code Online (Sandbox Code Playgroud)

(类型转换和子目录处理方法也可以在read_csv()函数内部处理,其方式与上面建议的第二个和第三个变体中所示的相同.)

### Benchmark Code & Results 
library(tidyverse)
library(data.table)
library(microbenchmark)

### Base R Approaches
#### Instead of a dataframe, this approach creates a list of lists
#### removed from analysis as this alone doubled analysis time reqd
# lapply_read.delim <- function(path, pattern = "*.csv") {
#     temp = list.files(path, pattern, full.names = TRUE)
#     myfiles = lapply(temp, read.delim)
# }

#### `read.csv()`
do.call_rbind_read.csv <- function(path, pattern = "*.csv") {
    files = list.files(path, pattern, full.names = TRUE)
    do.call(rbind, lapply(files, function(x) read.csv(x, stringsAsFactors = FALSE)))
}

map_df_read.csv <- function(path, pattern = "*.csv") {
    list.files(path, pattern, full.names = TRUE) %>% 
    map_df(~read.csv(., stringsAsFactors = FALSE))
}


### *dplyr()*
#### `read_csv()`
lapply_read_csv_bind_rows <- function(path, pattern = "*.csv") {
    files = list.files(path, pattern, full.names = TRUE)
    lapply(files, read_csv) %>% bind_rows()
}

map_df_read_csv <- function(path, pattern = "*.csv") {
    list.files(path, pattern, full.names = TRUE) %>% 
    map_df(~read_csv(., col_types = cols(.default = "c")))
}

### *data.table* / *purrr* hybrid
map_df_fread <- function(path, pattern = "*.csv") {
    list.files(path, pattern, full.names = TRUE) %>% 
    map_df(~fread(.))
}

### *data.table*
rbindlist_fread <- function(path, pattern = "*.csv") {
    files = list.files(path, pattern, full.names = TRUE)
    rbindlist(lapply(files, function(x) fread(x)))
}

do.call_rbind_fread <- function(path, pattern = "*.csv") {
    files = list.files(path, pattern, full.names = TRUE)
    do.call(rbind, lapply(files, function(x) fread(x, stringsAsFactors = FALSE)))
}


read_results <- function(dir_size){
    microbenchmark(
        # lapply_read.delim = lapply_read.delim(dir_size), # too slow to include in benchmarks
        do.call_rbind_read.csv = do.call_rbind_read.csv(dir_size),
        map_df_read.csv = map_df_read.csv(dir_size),
        lapply_read_csv_bind_rows = lapply_read_csv_bind_rows(dir_size),
        map_df_read_csv = map_df_read_csv(dir_size),
        rbindlist_fread = rbindlist_fread(dir_size),
        do.call_rbind_fread = do.call_rbind_fread(dir_size),
        map_df_fread = map_df_fread(dir_size),
        times = 10L) 
}

read_results_lrg_mid_mid <- read_results('./testFolder/500MB_12.5MB_40files')
print(read_results_lrg_mid_mid, digits = 3)

read_results_sml_mic_mny <- read_results('./testFolder/5MB_5KB_1000files/')
read_results_sml_tny_mod <- read_results('./testFolder/5MB_50KB_100files/')
read_results_sml_sml_few <- read_results('./testFolder/5MB_500KB_10files/')

read_results_med_sml_mny <- read_results('./testFolder/50MB_5OKB_1000files')
read_results_med_sml_mod <- read_results('./testFolder/50MB_5OOKB_100files')
read_results_med_med_few <- read_results('./testFolder/50MB_5MB_10files')

read_results_lrg_sml_mny <- read_results('./testFolder/500MB_500KB_1000files')
read_results_lrg_med_mod <- read_results('./testFolder/500MB_5MB_100files')
read_results_lrg_lrg_few <- read_results('./testFolder/500MB_50MB_10files')

read_results_xlg_lrg_mod <- read_results('./testFolder/5000MB_50MB_100files')


print(read_results_sml_mic_mny, digits = 3)
print(read_results_sml_tny_mod, digits = 3)
print(read_results_sml_sml_few, digits = 3)

print(read_results_med_sml_mny, digits = 3)
print(read_results_med_sml_mod, digits = 3)
print(read_results_med_med_few, digits = 3)

print(read_results_lrg_sml_mny, digits = 3)
print(read_results_lrg_med_mod, digits = 3)
print(read_results_lrg_lrg_few, digits = 3)

print(read_results_xlg_lrg_mod, digits = 3)

# display boxplot of my typical use case results & basic machine max load
par(oma = c(0,0,0,0)) # remove overall margins if present
par(mfcol = c(1,1)) # remove grid if present
par(mar = c(12,5,1,1) + 0.1) # to display just a single boxplot with its complete labels
boxplot(read_results_lrg_mid_mid, las = 2, xlab = "", ylab = "Duration (seconds)", main = "40 files @ 12.5MB (500MB)")
boxplot(read_results_xlg_lrg_mod, las = 2, xlab = "", ylab = "Duration (seconds)", main = "100 files @ 50MB (5GB)")

# generate 3x3 grid boxplots
par(oma = c(12,1,1,1)) # margins for the whole 3 x 3 grid plot
par(mfcol = c(3,3)) # create grid (filling down each column)
par(mar = c(1,4,2,1)) # margins for the individual plots in 3 x 3 grid
boxplot(read_results_sml_mic_mny, las = 2, xlab = "", ylab = "Duration (seconds)", main = "1000 files @ 5KB (5MB)", xaxt = 'n')
boxplot(read_results_sml_tny_mod, las = 2, xlab = "", ylab = "Duration (milliseconds)", main = "100 files @ 50KB (5MB)", xaxt = 'n')
boxplot(read_results_sml_sml_few, las = 2, xlab = "", ylab = "Duration (milliseconds)", main = "10 files @ 500KB (5MB)",)

boxplot(read_results_med_sml_mny, las = 2, xlab = "", ylab = "Duration (microseconds)        ", main = "1000 files @ 50KB (50MB)", xaxt = 'n')
boxplot(read_results_med_sml_mod, las = 2, xlab = "", ylab = "Duration (microseconds)", main = "100 files @ 500KB (50MB)", xaxt = 'n')
boxplot(read_results_med_med_few, las = 2, xlab = "", ylab = "Duration (seconds)", main = "10 files @ 5MB (50MB)")

boxplot(read_results_lrg_sml_mny, las = 2, xlab = "", ylab = "Duration (seconds)", main = "1000 files @ 500KB (500MB)", xaxt = 'n')
boxplot(read_results_lrg_med_mod, las = 2, xlab = "", ylab = "Duration (seconds)", main = "100 files @ 5MB (500MB)", xaxt = 'n')
boxplot(read_results_lrg_lrg_few, las = 2, xlab = "", ylab = "Duration (seconds)", main = "10 files @ 50MB (500MB)")
Run Code Online (Sandbox Code Playgroud)

中等用例

Boxplot比较经过时间我的典型用例

更大的用例

超大载荷经过时间的箱形图比较

各种用例

行:文件计数(
1000,100,10 )列:最终数据帧大小(5MB,50MB,500MB)
(单击图像查看原始大小) Boxplot目录大小变化的比较

对于最小的使用情况,基本R结果更好,其中使purrr和dplyr的C库承担的开销超过执行大规模处理任务时观察到的性能增益.

如果你想运行自己的测试,你会发现这个bash脚本很有帮助.

for ((i=1; i<=$2; i++)); do 
  cp "$1" "${1:0:8}_${i}.csv";
done
Run Code Online (Sandbox Code Playgroud)

read_plus() 将按顺序编号创建100个文件副本(在文件名的前8个字符和下划线之后).

归因和赞赏

特别感谢:

  • Tyler RinkerAkrun展示微型基准.
  • Jake Kaupp介绍我到bash what_you_name_this_script.sh "fileName_you_want_copied" 100 这里.
  • David McLaughlin提供了有关改进可视化和讨论/确认小文件中观察到的性能反转的有用反馈,小数据帧分析结果.

  • 你的解决方案对我有用。在此我想存储该文件名以区分它们.. 有可能吗? (2认同)
  • @Niks - 当然!只需编写和交换一个小函数,该函数不仅可以读取文件,还可以立即将文件名附加到每个读取的记录中。像这样`readAddFilename &lt;- function(flnm) { read_csv(flnm) %&gt;% mutate(filename = flnm) }` 然后把它放到`map_df`而不是那里的简单只读`read_csv()`现在。我可以更新上面的条目以显示该函数以及它如何适合管道,如果您仍有疑问或您认为这会有所帮助。 (2认同)

mar*_*bel 92

以下是将.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)))
Run Code Online (Sandbox Code Playgroud)

编辑: - 使用data.table和更多的额外选择readr

一个fread()版本,它是data.table包的一个功能.这应该是最快的选择.

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

使用readr,这是一个用于读取csv文件的新的hadley包.比fread慢一点但功能不同.

library(readr)
library(dplyr)
tbl = lapply(files, read_csv) %>% bind_rows()
Run Code Online (Sandbox Code Playgroud)

  • 我添加了一个`data.table`版本,可以提高性能. (4认同)
  • 这与Reduce(rbind,lapply(...))的表现如何?只是学习R,但我的猜测是不太高效 (2认同)
  • 只需将其添加到`pattern ='wheather' (2认同)

Spa*_*man 24

除了lapply在R中使用或其他一些循环结构外,您还可以将CSV文件合并到一个文件中.

在Unix中,如果文件没有标题,那么它就像以下一样简单:

cat *.csv > all.csv
Run Code Online (Sandbox Code Playgroud)

或者如果有标题,你可以找到一个匹配标题和只有标题的字符串(即假设标题行都以"Age"开头),你会这样做:

cat *.csv | grep -v ^Age > all.csv
Run Code Online (Sandbox Code Playgroud)

我认为在Windows中你可以用DOS命令框COPYSEARCH(或者FIND什么)来做这个,但为什么不安装cygwin并获得Unix命令shell的强大功能呢?


web*_*ebb 19

有许多文件和许多内核,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
Run Code Online (Sandbox Code Playgroud)

是时候将 121401 个 csvs 读入单个 data.table。每次平均运行三轮,然后四舍五入。每个 csv 有 3 列,一个标题行,平均有 4.510 行。机器是具有 96 个内核的 GCP VM。

@A5C1D2H2I1M1N2O1R2T1、@leerssej 和 @marbel 的前三个答案基本上都相同:将 fread(或 read.delim)应用于每个文件,然后 rbind/rbindlist 生成的 data.tables。对于小数据集,我通常使用rbindlist(lapply(list.files("*.csv"),fread))表格。对于中等规模的数据集,我使用 parallel 的 mclapply 而不是 lapply,如果你有很多核心,它会快得多。

这比其他 R 内部替代方案更好,但对于速度很重要的大量小型 csvs 来说并不是最好的。在这种情况下,cat如@Spacedman 在排名第 4 的答案中所建议的那样,首次使用会快得多。当使用例如 1 个内核而不是 96 个内核时,这甚至比 mclapply 还要快。我将添加一些有关如何从 R 中执行此操作的详细信息:

x = fread(cmd='cat *.csv', header=F)
Run Code Online (Sandbox Code Playgroud)

但是,如果每个 csv 都有一个标题呢?

x = fread(cmd="awk 'NR==1||FNR!=1' *.csv", header=T)
Run Code Online (Sandbox Code Playgroud)

如果你有太多的文件导致*.csvshell glob 失败怎么办?

x = fread(cmd='find . -name "*.csv" | xargs cat', header=F)
Run Code Online (Sandbox Code Playgroud)

如果所有文件都有一个标题并且文件太多怎么办?

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)
names(x) = names(header)
Run Code Online (Sandbox Code Playgroud)

如果生成的串联 csv 对系统内存来说太大怎么办?(例如,/dev/shm 空间不足错误)

system('find . -name "*.csv" | xargs cat > combined.csv')
x = fread('combined.csv', header=F)
Run Code Online (Sandbox Code Playgroud)

带标题?

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)
Run Code Online (Sandbox Code Playgroud)

最后,如果您不想要目录中的所有 .csv,而是一组特定的文件,该怎么办?(此外,它们都有标题。)(这是我的用例。)

fread(text=paste0(system("xargs cat|awk 'NR==1||$1!=\"<column one name>\"'",input=paths,intern=T),collapse="\n"),header=T,sep="\t")
Run Code Online (Sandbox Code Playgroud)

这与普通 fread xargs cat 的速度大致相同 :)

注意:对于 v1.11.6 之前的 data.table(2018 年 9 月 19 日),请省略cmd=from fread(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
}
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很棒的解决方案,值得更多的赞扬。 (2认同)

小智 14

这是我开发的将所有csv文件读入R的代码.它将为每个csv文件单独创建一个数据框,并为数据框创建文件的原始名称(删除空格和.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="")))
}
Run Code Online (Sandbox Code Playgroud)


Pau*_*aul 6

使用purrr包含文件 ID作为列:

library(tidyverse)


p <- "my/directory"
files <- list.files(p, pattern="csv", full.names=TRUE) %>%
    set_names()
merged <- files %>% map_dfr(read_csv, .id="filename")
Run Code Online (Sandbox Code Playgroud)

如果没有set_names().id=将使用整数指示符,而不是实际的文件名。

如果您只需要短文件名而不需要完整路径:

merged <- merged %>% mutate(filename=basename(filename))
Run Code Online (Sandbox Code Playgroud)

  • 嗯,我的“set_names”函数被“magrittr”屏蔽了。现在可以了!感谢您回来查看我的情况。 (2认同)

man*_*ark 5

使用plyr::ldply有大致通过使速度提高50%.parallel选择在阅读400个CSV文件大致各为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)
Run Code Online (Sandbox Code Playgroud)


小智 5

我认为,大多数其他答案已被淘汰rio::import_list,这是一种简洁的单行代码:

library(rio)
my_data <- import_list(dir("path_to_directory", pattern = ".csv", rbind = TRUE))
Run Code Online (Sandbox Code Playgroud)

任何其他参数都将传递给rio::importrio可以应对几乎任何文件格式R可以阅读,它使用data.tablefread如果可能的话,那么它应该是快。