簡體   English   中英

如何一次導入多個.csv文件?

[英]How to import multiple .csv files at once?

假設我們有一個包含多個 data.csv 文件的文件夾,每個文件包含相同數量的變量,但每個來自不同的時間。 R 中是否有辦法同時導入它們,而不必單獨導入它們?

我的問題是我有大約 2000 個數據文件要導入,並且必須使用代碼單獨導入它們:

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

效率不是很高。

類似下面的內容應該導致每個數據框作為單個列表中的單獨元素:

temp = list.files(pattern="*.csv")
myfiles = lapply(temp, read.delim)

這假設您將這些 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]))

或者,不使用assign ,並演示 (1) 如何清理文件名和 (2) 展示如何使用list2env ,您可以嘗試以下操作:

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

但同樣,最好將它們留在一個列表中。

一個快速簡潔的tidyverse解決方案:(比Base R 的read.csv快兩倍多)

tbl <-
    list.files(pattern = "*.csv") %>% 
    map_df(~read_csv(.))

data.tablefread()甚至可以將這些加載時間再次減少一半。 (對於 1/4 Base R倍)

library(data.table)

tbl_fread <- 
    list.files(pattern = "*.csv") %>% 
    map_df(~fread(.))

stringsAsFactors = FALSE參數使數據幀因子保持自由,(正如 marbel 指出的那樣,是fread的默認設置)

如果類型轉換是厚顏無恥的,您可以使用col_types參數強制所有列作為字符。

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

如果您想深入子目錄以構建最終綁定的文件列表,請確保包含路徑名,並在列表中注冊文件及其全名。 這將允許綁定工作在當前目錄之外進行。 (將完整路徑名視為像護照一樣操作,以允許跨目錄“邊界”移動。)

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

正如哈德利在這里描述的(大約一半):

map_df(x, f)實際上與do.call("rbind", lapply(x, f)) ... 相同。

獎勵功能-在下面的評論中根據 Niks 功能請求將文件名添加到記錄中:
* 為每條記錄添加原始filename

代碼解釋:在表的初始讀取期間創建一個函數以將文件名附加到每條記錄。 然后使用該函數而不是簡單的read_csv()函數。

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

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

(類型轉換和子目錄處理方法也可以在read_plus()函數內部以與上面建議的第二個和第三個變體中說明的相同方式進行處理。)

### 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)")

中等用例

我的典型用例經過時間的箱線圖比較

更大的用例

超大負載經過時間的箱線圖比較

各種用例

行:文件計數(1000、100、10)
列:最終數據幀大小(5MB、50MB、500MB)
(點擊圖片查看原始尺寸) 目錄大小變化的箱線圖比較

對於最小的用例,基本 R 結果更好,在這些用例中,使 purrr 和 dplyr 的 C 庫承擔的開銷超過了執行更大規模處理任務時觀察到的性能增益。

如果您想運行自己的測試,您可能會發現這個 bash 腳本很有幫助。

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

bash what_you_name_this_script.sh "fileName_you_want_copied" 100將創建 100 個按順序編號的文件副本(在文件名的前 8 個字符和下划線之后)。

歸因和贊賞

特別感謝:

  • Tyler RinkerAkrun演示微基准測試。
  • Jake Kaupp 在這里向我介紹了map_df()
  • David McLaughlin 就改進可視化和討論/確認在小文件、小數據框分析結果中觀察到的性能倒置提供有用的反饋。
  • marbel 指出fread()的默認行為。 (我需要研究data.table 。)

以下是一些使用 R 基礎將 .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)))

編輯: - 使用data.tablereadr的更多選擇

fread()版本,它是data.table包的一個函數。 這是迄今為止 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()

對於許多文件和許多內核, 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 csvs 讀入單個 data.table 了。 每次是平均三輪然后四舍五入。 每個 csv 有 3 列,一個標題行,平均有 4.510 行。 Machine 是具有 96 個內核的 GCP VM。

@A5C1D2H2I1M1N2O1R2T1、@leerssej 和 @marbel 的前三個答案基本相同:將 fread(或 read.delim)應用於每個文件,然后 rbind/rbindlist 生成的 data.tables。 對於小型數據集,我通常使用rbindlist(lapply(list.files("*.csv"),fread))形式。 對於中等規模的數據集,我使用並行的 mclapply 而不是 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)
names(x) = names(header)

如果生成的串聯 csv 對於系統內存來說太大了怎么辦? (例如,/dev/shm 空間不足錯誤)

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 的速度差不多 :)

注意:對於 v1.11.6 之前的 data.table(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 函數的更復雜版本,適用於您希望結果 data.table 包含每個 csv 的輸入路徑的列的情況。 在這種情況下,還必須使用 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 都使用相同的分隔符。

除了在 R 中使用lapply或其他一些循環結構外,您還可以將 CSV 文件合並到一個文件中。

在 Unix 中,如果文件沒有標題,那么它很簡單:

cat *.csv > all.csv

或者如果有標題,你可以找到一個匹配標題的字符串並且只有標題(即假設標題行都以“Age”開頭),你會這樣做:

cat *.csv | grep -v ^Age > all.csv

我認為在 Windows 中,您可以使用 DOS 命令框中的COPYSEARCH (或FIND或其他東西)來執行此操作,但為什么不安裝cygwin並獲得 Unix 命令 shell 的強大功能呢?

這是我開發的用於將所有 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="")))
}

在我看來,大多數其他答案都被rio::import_list淘汰了,這是一個簡潔的單行:

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

任何額外的參數都會傳遞給rio::import rio幾乎可以處理 R 可以讀取的任何文件格式,並且它盡可能使用data.tablefread ,所以它也應該很快。

使用plyr::ldply通過啟用.parallel選項可以提高大約 50% 的速度,同時讀取 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)

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

如果沒有set_names().id=將使用整數指示符,而不是實際的文件名。

如果您只想要沒有完整路徑的短文件名:

merged <- merged %>% mutate(filename=basename(filename))

基於 dnlbrk 的評論,對於大文件,assign 比 list2env 快得多。

library(readr)
library(stringr)

List_of_file_paths <- list.files(path ="C:/Users/Anon/Documents/Folder_with_csv_files/", pattern = ".csv", all.files = TRUE, full.names = TRUE)

通過將 full.names 參數設置為 true,您將獲得每個文件的完整路徑作為文件列表中的單獨字符串,例如,List_of_file_paths[1] 將類似於“C:/Users/Anon/Documents/文件夾_with_csv_files/file1.csv"

for(f in 1:length(List_of_filepaths)) {
  file_name <- str_sub(string = List_of_filepaths[f], start = 46, end = -5)
  file_df <- read_csv(List_of_filepaths[f])  
  assign( x = file_name, value = file_df, envir = .GlobalEnv)
}

您可以使用 data.table 包的 fread 或 base R read.csv 而不是 read_csv。 file_name 步驟允許您整理名稱,以便每個數據框不會保留文件的完整路徑作為其名稱。 在將數據表傳輸到全局環境之前,您可以擴展循環以對數據表執行更多操作,例如:

for(f in 1:length(List_of_filepaths)) {
  file_name <- str_sub(string = List_of_filepaths[f], start = 46, end = -5)
  file_df <- read_csv(List_of_filepaths[f])  
  file_df <- file_df[,1:3] #if you only need the first three columns
  assign( x = file_name, value = file_df, envir = .GlobalEnv)
}

這是我讀取多個文件並將它們組合成 1 個數據框的具體示例:

path<- file.path("C:/folder/subfolder")
files <- list.files(path=path, pattern="/*.csv",full.names = T)
library(data.table)
data = do.call(rbind, lapply(files, function(x) read.csv(x, stringsAsFactors = FALSE)))

從 readr 2.0.0 開始,您可以一次讀取多個文件,只需提供file參數的路徑列表即可。 這是一個使用readr::read_csv()顯示此內容的示例。

packageVersion("readr")
#> [1] '2.0.1'
library(readr)
library(fs)

# create files to read in
write_csv(read_csv("1, 2 \n 3, 4", col_names = c("x", "y")), file = "file1.csv")
write_csv(read_csv("5, 6 \n 7, 8", col_names = c("x", "y")), file = "file2.csv")

# get a list of files
files <- dir_ls(".", glob = "file*csv")
files
#> file1.csv file2.csv

# read them in at once
# record paths in a column called filename
read_csv(files, id = "filename")
#> # A tibble: 4 × 3
#>   filename      x     y
#>   <chr>     <dbl> <dbl>
#> 1 file1.csv     1     2
#> 2 file1.csv     3     4
#> 3 file2.csv     5     6
#> 4 file2.csv     7     8

reprex 包於 2021-09-16 創建 (v2.0.1)

只要您的計算機上有許多內核,以下代碼應該可以為您提供最快的大數據速度:

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)

更新於 2020/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

我喜歡使用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

如果您需要讀取特定文件,可以將它們的文件路徑與regexpignore.caseinvert匹配。


要獲得更大的靈活性,請使用list_any() 它允許您通過參數.f提供閱讀器功能。

(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

通過 ... 或在 lambda 函數內部傳遞其他參數。

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

有人要求我將此功能添加到 stackoverflow R 包中。 鑒於它是一個 tinyverse 包(並且不能依賴第三方包),這就是我想出的:

#' Bulk import data files 
#' 
#' Read in each file at a path and then unnest them. Defaults to csv format.
#' 
#' @param path        a character vector of full path names
#' @param pattern     an optional \link[=regex]{regular expression}. Only file names which match the regular expression will be returned.
#' @param reader      a function that can read data from a file name.
#' @param ...         optional arguments to pass to the reader function (eg \code{stringsAsFactors}).
#' @param reducer     a function to unnest the individual data files. Use I to retain the nested structure. 
#' @param recursive     logical. Should the listing recurse into directories?
#'  
#' @author Neal Fultz
#' @references \url{https://stackoverflow.com/questions/11433432/how-to-import-multiple-csv-files-at-once}
#' 
#' @importFrom utils read.csv
#' @export
read.directory <- function(path='.', pattern=NULL, reader=read.csv, ..., 
                           reducer=function(dfs) do.call(rbind.data.frame, dfs), recursive=FALSE) {
  files <- list.files(path, pattern, full.names = TRUE, recursive = recursive)

  reducer(lapply(files, reader, ...))
}

通過參數化 reader 和 reducer 函數,人們可以選擇使用 data.table 或 dplyr,或者只使用適用於較小數據集的基本 R 函數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM