Làm cách nào để nhập nhiều tệp .csv cùng một lúc?


219

Giả sử chúng ta có một thư mục chứa nhiều tệp data.csv, mỗi tệp chứa cùng một số biến nhưng mỗi biến từ các thời điểm khác nhau. Có cách nào trong R để nhập tất cả chúng đồng thời thay vì phải nhập tất cả chúng riêng lẻ không?

Vấn đề của tôi là tôi có khoảng 2000 tệp dữ liệu để nhập và phải nhập chúng riêng lẻ chỉ bằng cách sử dụng mã:

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

không hiệu quả lắm

Câu trả lời:


259

Một cái gì đó như sau sẽ dẫn đến mỗi khung dữ liệu là một thành phần riêng biệt trong một danh sách:

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

Điều này giả định rằng bạn có các CSV đó trong một thư mục - thư mục làm việc hiện tại của bạn - và tất cả chúng đều có phần mở rộng chữ thường .csv.

Nếu sau đó bạn muốn kết hợp các khung dữ liệu đó thành một khung dữ liệu duy nhất, hãy xem các giải pháp trong các câu trả lời khác bằng cách sử dụng những thứ như do.call(rbind,...), dplyr::bind_rows()hoặc data.table::rbindlist().

Nếu bạn thực sự muốn mỗi khung dữ liệu trong một đối tượng riêng biệt, mặc dù điều đó thường không phù hợp, bạn có thể làm như sau với assign:

temp = list.files(pattern="*.csv")
for (i in 1:length(temp)) assign(temp[i], read.csv(temp[i]))

Hoặc, không có assign, và để chứng minh (1) làm thế nào để xóa tên tệp và (2) hiển thị cách sử dụng list2env, bạn có thể thử các cách sau:

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

Nhưng một lần nữa, tốt hơn là để chúng trong một danh sách.


Cảm ơn! Điều này hoạt động rất tốt ... làm thế nào tôi có thể đặt tên cho mỗi tệp tôi vừa nhập để tôi có thể dễ dàng gọi chúng lên?
Jojo Ono

nếu bạn có thể chỉ cho chúng tôi vài dòng đầu tiên của một số tệp của bạn, chúng tôi có thể có một số đề xuất - chỉnh sửa câu hỏi của bạn cho điều đó!
Spacesman

2
Đoạn mã trên hoạt động hoàn hảo để nhập chúng dưới dạng các đối tượng đơn lẻ nhưng khi tôi cố gắng gọi một cột từ tập dữ liệu thì nó không nhận ra nó vì nó chỉ là một đối tượng không phải là khung dữ liệu, tức là phiên bản mã trên của tôi là: setwd ( 'C: / Users / new / Desktop / Dives / 0904_003') temp <-list.files (mẫu = "*. Csv") ddives <- lapply (temp, read.csv) Vì vậy, bây giờ mỗi tệp được gọi là ddives [n ] nhưng làm thế nào tôi có thể viết một vòng lặp để biến chúng thành tất cả các khung dữ liệu thay vì các đối tượng đơn lẻ? Tôi có thể đạt được điều này một cách riêng lẻ bằng cách sử dụng toán tử data.frame nhưng không chắc chắn về cách lặp lại điều này. @mrdwab
Jojo Ono

@JosephOnoufriou, xem cập nhật của tôi. Nhưng nhìn chung, tôi thấy làm việc với các danh sách dễ dàng hơn nếu tôi thực hiện các phép tính tương tự trên tất cả các khung dữ liệu.
A5C1D2H2I1M1N2O1R2T1

2
Đối với bất kỳ ai đang cố gắng viết một hàm để thực hiện phiên bản cập nhật của câu trả lời này bằng cách sử dụng assign... Nếu bạn muốn các giá trị được gán cư trú trong môi trường toàn cầu, hãy đảm bảo bạn đã đặt inherits=T.
dnlbrky

127

Một tidyversegiải pháp nhanh chóng và cô đọng : (nhanh hơn gấp đôi so với Base R read.csv )

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

data.table 's fread()thậm chí có thể cắt những thời gian tải một nửa nữa. (cho 1/4 số lần R cơ sở )

library(data.table)

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

Đối stringsAsFactors = FALSEsố giữ cho yếu tố khung dữ liệu miễn phí, (và như chỉ ra, là cài đặt mặc định cho fread)

Nếu typecasting đang táo tợn, bạn có thể buộc tất cả các cột là ký tự với col_typesđối số.

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

Nếu bạn muốn nhúng vào các thư mục con để xây dựng danh sách các tệp của mình để cuối cùng liên kết, thì hãy chắc chắn bao gồm tên đường dẫn, cũng như đăng ký các tệp với tên đầy đủ của chúng trong danh sách của bạn. Điều này sẽ cho phép công việc ràng buộc đi ra ngoài thư mục hiện tại. (Suy nghĩ về các tên đường dẫn đầy đủ hoạt động như hộ chiếu để cho phép di chuyển trở lại qua thư mục 'viền'.)

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

Như Hadley mô tả ở đây (khoảng một nửa):

map_df(x, f)có hiệu quả tương tự như do.call("rbind", lapply(x, f))....

Tính năng Phần thưởng - thêm tên tệp vào bản ghi theo yêu cầu tính năng Niks trong các nhận xét bên dưới:
* Thêm bản gốc filenamevào mỗi bản ghi.

Mã giải thích: tạo một hàm để nối tên tệp vào mỗi bản ghi trong quá trình đọc các bảng ban đầu. Sau đó sử dụng chức năng đó thay vì read_csv()chức năng đơn giản .

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

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

(Các cách tiếp cận xử lý kiểu chữ và thư mục con cũng có thể được xử lý bên trong read_plus()hàm theo cách tương tự như minh họa trong các biến thể thứ hai và thứ ba được đề xuất ở trên.)

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

Trường hợp sử dụng Middling

Boxplot So sánh thời gian đã trôi qua Trường hợp sử dụng điển hình của tôi

Trường hợp sử dụng lớn hơn

Boxplot So sánh thời gian đã qua cho tải trọng lớn

Các trường hợp sử dụng

Hàng: số tập tin (1000, 100, 10)
Cột: kích thước khung dữ liệu cuối cùng (5MB, 50MB, 500MB)
(nhấp vào hình ảnh để xem kích thước ban đầu) Boxplot So sánh các biến thể kích thước thư mục

Các kết quả cơ sở R tốt hơn cho các trường hợp sử dụng nhỏ nhất trong đó chi phí đưa các thư viện C của purrr và dplyr vượt xa hiệu suất đạt được khi quan sát các tác vụ xử lý quy mô lớn hơn.

nếu bạn muốn chạy thử nghiệm của riêng mình, bạn có thể thấy tập lệnh bash này hữu ích.

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 sẽ tạo 100 bản sao tệp của bạn được đánh số liên tục (sau 8 ký tự ban đầu của tên tệp và dấu gạch dưới).

Phân bổ và đánh giá cao

Với lời cảm ơn đặc biệt đến:

  • Tyler RinkerAkrun để chứng minh microbenchmark.
  • Jake Kaupp đã giới thiệu tôi đến map_df() đây .
  • David McLaughlin cho phản hồi hữu ích về việc cải thiện trực quan hóa và thảo luận / xác nhận các nghịch đảo hiệu suất được quan sát trong tệp nhỏ, kết quả phân tích khung dữ liệu nhỏ.
  • marbel để chỉ ra hành vi mặc định cho fread(). (Tôi cần nghiên cứu về data.table.)

1
bạn giải pháp làm việc cho tôi. Trong phần này tôi muốn lưu trữ tên tệp đó để phân biệt chúng .. Có thể không?
Niks

1
@Niks - Chắc chắn! Chỉ cần viết và trao đổi trong một chức năng nhỏ không chỉ đọc các tệp mà ngay lập tức nối thêm tên tệp vào mỗi bản ghi đã đọc. Giống như vậy readAddFilename <- function(flnm) { read_csv(flnm) %>% mutate(filename = flnm) }Sau đó, chỉ cần thả nó vào map_dfthay vì chỉ đọc đơn giản read_csv()là có ngay bây giờ. Tôi có thể cập nhật mục trên để hiển thị chức năng và cách nó phù hợp với đường ống nếu bạn vẫn còn thắc mắc hoặc bạn nghĩ rằng điều đó sẽ hữu ích.
leerssej

Vấn đề trong thực tế là read_csvchậm hơn nhiều fread. Tôi sẽ bao gồm một điểm chuẩn nếu bạn sẽ nói một cái gì đó nhanh hơn. Một ý tưởng là tạo 30 tệp 1GB và đọc chúng, đó sẽ là trường hợp hiệu suất quan trọng.
Marbel

@marbel: Cảm ơn bạn đã gợi ý! Trên 530 MB và thư mục nhỏ hơn (lên đến 100 file) Tôi đang tìm kiếm một sự cải thiện 25% về hiệu suất giữa data.table 's fread()dplyr ' s read_csv(): 14,2 vs 19,9 giây. TBH, tôi chỉ so sánh cơ sở R với dplyr và read_csv()nhanh hơn khoảng 2-4 lần so với read.csv(), điểm chuẩn dường như không cần thiết. Tuy nhiên, thật thú vị khi đưa ra fread()một vòng xoáy và tạm dừng để kiểm tra kết quả điểm chuẩn hoàn chỉnh hơn. Cảm ơn một lần nữa!
leerssej

1
Một điểm tuyệt vời khác. Tôi nghĩ rằng khi tôi viết rằng tôi đã quá cẩn thận trong việc bảo vệ các hoạt động của dữ liệu. Không thể thay đổi dữ liệu tại chỗ (điều này ảnh hưởng đến hiệu suất cho lần chạy tiếp theo và tất cả các lần chạy tiếp theo trên dữ liệu). Điều đó tất nhiên không có ý nghĩa trong trường hợp này. Cảm ơn bạn. :-D Mong sớm được chạy lại các số mà không có chức năng và với các bộ dữ liệu lớn hơn với một máy lớn hơn.
leerssej

104

Dưới đây là một số tùy chọn để chuyển đổi các tệp .csv thành một data.frame bằng cơ sở R và một số gói có sẵn để đọc tệp trong R.

Điều này chậm hơn các tùy chọn bên dưới.

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

Chỉnh sửa: - Một vài lựa chọn khác sử dụng data.tablereadr

Một fread()phiên bản, là một chức năng của data.tablegói. Đây là đến nay là lựa chọn nhanh nhất trong R .

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

Sử dụng readr , một gói khác để đọc tệp csv. Nó chậm hơn fread, nhanh hơn cơ sở R nhưng có các chức năng khác nhau.

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

2
Làm thế nào điều này thực hiện so với Giảm (rbind, lapply (...))? Chỉ học R nhưng tôi đoán là ít biểu diễn hơn
aaron

4
Tôi đã thêm một data.tablephiên bản, điều đó sẽ cải thiện hiệu suất.
marbel

Có thể chỉ đọc các tập tin cụ thể? Tập tin cũ có chứa 'thời tiết' trong tên?
Vô chủ

1
tìm thấy nó ở đây: stackoverflow.com/questions/10353540/ cảm ơn.
Vô chủ

1
+1 có vẻ như tạo ra một khung dữ liệu duy nhất - UNION SQL của tất cả các tệp CSV - là cách dễ nhất để làm việc. Vì OP không chỉ định liệu họ muốn 1 khung dữ liệu hay nhiều khung dữ liệu, tôi giả sử 1 khung dữ liệu là tốt nhất, vì vậy tôi rất ngạc nhiên khi câu trả lời được chấp nhận không thực hiện bất kỳ "UNION" nào. Tôi thích câu trả lời này, phù hợp với lời giải thích này củado.call
The Red Pea

24

Cũng như sử dụng lapplyhoặc một số cấu trúc lặp khác trong R, bạn có thể hợp nhất các tệp CSV của mình thành một tệp.

Trong Unix, nếu các tệp không có tiêu đề, thì nó dễ như:

cat *.csv > all.csv

hoặc nếu có các tiêu đề và bạn có thể tìm thấy một chuỗi khớp với các tiêu đề và chỉ các tiêu đề (ví dụ: tất cả các dòng tiêu đề bắt đầu bằng "Tuổi"), bạn sẽ làm:

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

Tôi nghĩ trong Windows bạn có thể làm điều này với COPYSEARCH(hoặc FINDmột cái gì đó) từ hộp lệnh DOS, nhưng tại sao không cài đặt cygwinvà lấy sức mạnh của shell lệnh Unix?


hoặc thậm chí đi với Git BashGitcài đặt?
leerssej

Theo kinh nghiệm của tôi, đây không phải là giải pháp nhanh nhất nếu các tệp của bạn bắt đầu khá lớn.
Amir

20

Đây là mã tôi đã phát triển để đọc tất cả các tệp csv vào R. Nó sẽ tạo một khung dữ liệu cho từng tệp csv riêng lẻ và tiêu đề mà khung dữ liệu đó là tên gốc của tệp (loại bỏ khoảng trắng và .csv) Tôi hy vọng bạn thấy nó hữu ích!

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="")))
}

8

Ba câu trả lời hàng đầu của @ A5C1D2H2I1M1N2O1R2T1, @leerssej và @marbel và tất cả đều giống nhau: áp dụng fread cho mỗi tệp, sau đó rbind / rbindlist dữ liệu kết quả. Tôi thường sử dụng các rbindlist(lapply(list.files("*.csv"),fread))hình thức.

Điều này tốt hơn so với các lựa chọn thay thế nội bộ R khác, và tốt cho một số lượng nhỏ các csv lớn, nhưng không phải là tốt nhất cho một số lượng lớn các csv nhỏ khi tốc độ xảy ra. Trong trường hợp đó, lần đầu tiên sử dụng có thể nhanh hơn nhiều cat, như @Spacesman gợi ý trong câu trả lời xếp hạng 4. Tôi sẽ thêm một số chi tiết về cách thực hiện việc này từ bên trong R:

x = fread(cmd='cat *.csv', header=F)

Tuy nhiên, nếu mỗi csv có một tiêu đề thì sao?

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

Và nếu bạn có quá nhiều tệp mà *.csvshell shell bị lỗi thì sao?

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

Và nếu tất cả các tệp có tiêu đề VÀ có quá nhiều tệp thì sao?

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)

Và nếu csv kết nối quá lớn cho bộ nhớ hệ thống thì sao?

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

Với tiêu đề?

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)

Cuối cùng, nếu bạn không muốn tất cả .csv trong một thư mục, mà là một tập hợp các tệp cụ thể thì sao? (Ngoài ra, tất cả chúng đều có tiêu đề.) (Đây là trường hợp sử dụng của tôi.)

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

và đây là về tốc độ tương tự như mèo xargs fread đồng bằng :)

Lưu ý: đối với data.table trước v1.11.6 (ngày 19 tháng 9 năm 2018), hãy bỏ qua cmd=từ fread(cmd=.

Phụ lục: sử dụng mclapply của thư viện song song thay cho serial lapply, ví dụ, rbindlist(lapply(list.files("*.csv"),fread))cũng nhanh hơn nhiều so với rbindlist lapply fread.

Thời gian để đọc 121401 csvs vào một dữ liệu duy nhất. Mỗi csv có 3 cột, một hàng tiêu đề và trung bình 4.510 hàng. Máy là máy ảo GCP với 96 lõi:

rbindlist lapply fread   234.172s 247.513s 256.349s
rbindlist mclapply fread  15.223s   9.558s   9.292s
fread xargs cat            4.761s   4.259s   5.095s

Tóm lại, nếu bạn quan tâm đến tốc độ và có nhiều tệp và nhiều lõi, mèo xeads nhanh hơn khoảng 50 lần so với giải pháp nhanh nhất trong 3 câu trả lời hàng đầu.


6

Theo quan điểm của tôi, hầu hết các câu trả lời khác đều bị lỗi thời rio::import_list, đó là một câu trả lời ngắn gọn:

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

Bất kỳ đối số bổ sung được chuyển đến rio::import. riocó thể đối phó với hầu như bất kỳ tập tin định dạng R có thể đọc, và nó sử dụng data.tablefreadnếu có thể, vì vậy nó cần được nhanh chóng quá.


5

Sử dụng plyr::ldplycó tốc độ tăng khoảng 50% bằng cách bật .paralleltùy chọn trong khi đọc 400 tệp csv khoảng 30-40 MB mỗi tệp. Ví dụ bao gồm một thanh tiến trình văn bản.

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)

Câu trả lời tốt đẹp! Làm thế nào để bạn vượt qua các đối số bổ sung cho freadhoặc user-defined functions? Cảm ơn!
Tung

1
@Tung Nhìn vào ?ldplycho thấy ...các đối số khác được truyền vào .fun. Sử dụng một trong hai fread, skip = 100hoặc function(x) fread(x, skip = 100)sẽ hoạt động
manotheshark

sử dụng function(x) fread(x, skip = 100)không làm việc cho tôi nhưng cung cấp thêm đối số sau khi tên hàm trần đã thực hiện thủ thuật. Cảm ơn một lần nữa!
Tung

3

Dựa trên nhận xét của dnlbrk, việc gán có thể nhanh hơn đáng kể so với list2env cho các tệp lớn.

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)

Bằng cách đặt đối số full.names thành true, bạn sẽ nhận được đường dẫn đầy đủ đến từng tệp dưới dạng một chuỗi ký tự riêng trong danh sách tệp của mình, ví dụ: List_of_file_paths [1] sẽ giống như "C: / Users / Anon / Documents / Thư mục_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)
}

Bạn có thể sử dụng gói dữ liệu fread hoặc cơ sở R read.csv thay vì read_csv. Bước file_name cho phép bạn sắp xếp tên sao cho mỗi khung dữ liệu không còn với đường dẫn đầy đủ đến tệp như tên của nó. Bạn có thể mở rộng vòng lặp của mình để làm những việc khác vào bảng dữ liệu trước khi chuyển nó sang môi trường toàn cầu, ví dụ:

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)
}

3

Đây là ví dụ cụ thể của tôi để đọc nhiều tệp và kết hợp chúng thành 1 khung dữ liệu:

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

1
Bạn có thể sử dụng rbindlist()từdata.table
jogo

3

Các mã sau sẽ cung cấp cho bạn tốc độ nhanh nhất cho dữ liệu lớn miễn là bạn có nhiều lõi trên máy tính của mình:

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)

Cập nhật vào 2020/04/16: Khi tôi tìm thấy một gói mới có sẵn để tính toán song song, một giải pháp thay thế được cung cấp bằng cách sử dụng các mã sau.

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

1

Tôi thích cách tiếp cận bằng cách sử dụng list.files(), lapply()list2env()(hoặc fs::dir_ls(), purrr::map()list2env()). Điều đó có vẻ đơn giản và linh hoạt.

Ngoài ra, bạn có thể thử gói nhỏ { tor } ( to-R ): Theo mặc định, nó nhập các tệp từ thư mục làm việc vào danh sách ( list_*()các biến thể) hoặc vào môi trường toàn cầu ( load_*()các biến thể).

Ví dụ, ở đây tôi đọc tất cả các tệp .csv từ thư mục làm việc của tôi vào một danh sách bằng cách sử dụng tor::list_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

Và bây giờ tôi tải các tệp đó vào môi trường toàn cầu của mình với 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

Nếu bạn cần đọc các tập tin cụ thể, bạn có thể kết hợp tập tin-con đường của mình với regexp, ignore.caseinvert.


Để sử dụng linh hoạt hơn nữa list_any(). Nó cho phép bạn cung cấp chức năng đọc thông qua đối số .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

Truyền các đối số bổ sung thông qua ... hoặc bên trong hàm 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

1

Tôi đã yêu cầu tôi thêm chức năng này vào gói R stackoverflow. Cho rằng đó là gói nhỏ (và không thể phụ thuộc vào gói của bên thứ ba), đây là những gì tôi nghĩ ra:

#' 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{/programming/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, ...))
}

Bằng cách tham số hóa chức năng đọc và giảm tốc, mọi người có thể sử dụng data.table hoặc dplyr nếu họ chọn hoặc chỉ sử dụng các hàm R cơ bản phù hợp với các tập dữ liệu nhỏ hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.