วิธีนำเข้าไฟล์. csv หลายไฟล์พร้อมกันได้อย่างไร


219

สมมติว่าเรามีโฟลเดอร์ที่มีไฟล์ data.csv หลายไฟล์แต่ละไฟล์มีจำนวนตัวแปรเท่ากัน แต่แต่ละไฟล์มีเวลาต่างกัน มีวิธีใน R ที่จะนำเข้าพวกเขาทั้งหมดพร้อมกันแทนที่จะต้องนำเข้าพวกเขาทั้งหมดที?

ปัญหาของฉันคือฉันมีไฟล์ข้อมูลประมาณ 2,000 ไฟล์ที่จะนำเข้าและต้องนำเข้าแต่ละไฟล์โดยใช้รหัส:

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

ไม่มีประสิทธิภาพมาก

คำตอบ:


259

สิ่งต่อไปนี้ควรส่งผลให้แต่ละเฟรมข้อมูลเป็นองค์ประกอบแยกต่างหากในรายการเดียว:

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

หรือหากไม่มีassignและเพื่อสาธิต (1) วิธีล้างชื่อไฟล์และ (2) แสดงวิธีใช้list2envคุณสามารถลองดังต่อไปนี้:

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

แต่อีกครั้งมันมักจะดีกว่าที่จะทิ้งไว้ในรายการเดียว


ขอบคุณ! มันใช้งานได้ดีมาก ... ฉันจะตั้งชื่อไฟล์แต่ละไฟล์ที่ฉันเพิ่งนำเข้ามาได้อย่างไร
Jojo Ono

หากคุณสามารถแสดงให้เราเห็นสองสามบรรทัดแรกของไฟล์บางไฟล์เราอาจมีคำแนะนำ - แก้ไขคำถามของคุณ!
Spacedman

2
โค้ดด้านบนทำงานได้อย่างสมบูรณ์แบบสำหรับการนำเข้าเป็นวัตถุเดียว แต่เมื่อฉันพยายามเรียกคอลัมน์จากชุดข้อมูลมันไม่รู้จักเพราะมันเป็นเพียงวัตถุเดียวที่ไม่ใช่กรอบข้อมูลนั่นคือรุ่นของรหัสด้านบนคือ: setwd ( 'C: / Users / new / Desktop / Dives / 0904_003') temp <-list.files (pattern = "*. csv") ddives <- lapply (temp, read.csv) ดังนั้นตอนนี้แต่ละไฟล์ถูกเรียกว่า ddives [n ] แต่ฉันจะเขียนการวนรอบเพื่อทำให้เฟรมข้อมูลทั้งหมดเป็นวัตถุเดียวได้อย่างไร ฉันสามารถทำได้โดยใช้ data.frame operator แต่ฉันไม่แน่ใจว่าจะวนรอบนี้อย่างไร @mrdwab
Jojo Ono

@JosephOnoufriou โปรดดูการอัปเดตของฉัน แต่โดยทั่วไปฉันพบว่าการทำงานกับรายการง่ายขึ้นถ้าฉันจะทำการคำนวณที่คล้ายกันในกรอบข้อมูลทั้งหมด
A5C1D2H2I1M1N2O1R2T1

2
สำหรับทุกคนพยายามที่จะเขียนฟังก์ชั่นที่จะทำรุ่นปรับปรุงของคำตอบนี้โดยใช้assign... inherits=Tถ้าคุณต้องการค่ามอบหมายจะอาศัยอยู่ในสภาพแวดล้อมของโลกให้แน่ใจว่าคุณตั้งค่า
dnlbrky

127

tidyverseโซลูชันที่รวดเร็วและรวบรัด: (เร็วกว่าสองเท่าของBase R read.csv )

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

และdata.table 's fread()ยังสามารถตัดเวลาในการโหลดเหล่านั้นโดยครึ่งหนึ่งอีกครั้ง (สำหรับ 1/4 ฐาน Rครั้ง)

library(data.table)

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

stringsAsFactors = FALSEอาร์กิวเมนต์ช่วยให้ฟรีปัจจัย dataframe (และเป็นจุดเบญจมบพิตรออกเป็นค่าเริ่มต้นสำหรับfread)

หาก typecasting หน้าด้านคุณสามารถบังคับคอลัมน์ทั้งหมดให้เป็นตัวละครที่มี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"))) 

ดังที่ Hadley อธิบายไว้ที่นี่ (ประมาณครึ่งทาง):

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

(แนวทางการจัดการ typecasting และไดเรกทอรีย่อยยังสามารถจัดการภายใน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)")

Middling กรณีการใช้งาน

เปรียบเทียบ Boxplot ของเวลาที่ผ่านไปกรณีการใช้งานทั่วไปของฉัน

กรณีใช้งานที่ใหญ่ขึ้น

เปรียบเทียบ Boxplot ของเวลาที่ผ่านไปสำหรับการโหลดขนาดใหญ่พิเศษ

กรณีการใช้ที่หลากหลาย

แถว: นับจำนวนไฟล์ (1,000, 100, 10)
คอลัมน์: ขนาดดาต้าเฟรมสุดท้าย (5MB, 50MB, 500MB)
(คลิกที่ภาพเพื่อดูขนาดดั้งเดิม) เปรียบเทียบ Boxplot ของการเปลี่ยนแปลงขนาดของไดเรกทอรี

ผลลัพธ์ฐาน R นั้นดีกว่าสำหรับกรณีการใช้งานที่น้อยที่สุดซึ่งค่าใช้จ่ายในการนำไลบรารี C ของ purrr และ dplyr เพื่อแบกรับผลการดำเนินงานที่เกินดุลที่สังเกตได้เมื่อดำเนินการประมวลผลขนาดใหญ่

หากคุณต้องการทำการทดสอบของคุณเองคุณอาจพบว่าสคริปต์ทุบตีนี้มีประโยชน์

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 RinkerและAkrunสำหรับแสดงให้เห็นถึง microbenchmark
  • เจค Kaupp สำหรับการแนะนำฉันไปที่นี่map_df()
  • David McLaughlin สำหรับข้อเสนอแนะที่เป็นประโยชน์ในการปรับปรุงการสร้างภาพข้อมูลและการอภิปราย / ยืนยันการรุกรานประสิทธิภาพที่พบในไฟล์ขนาดเล็กผลการวิเคราะห์ดาต้าเฟรมขนาดเล็ก
  • fread()เบญจมบพิตรสำหรับการชี้ให้เห็นพฤติกรรมเริ่มต้นสำหรับ (ฉันต้องศึกษาต่อdata.table)

1
คุณแก้ปัญหาได้ผลสำหรับฉัน ในนี้ฉันต้องการเก็บชื่อไฟล์นั้นเพื่อแยกความแตกต่าง .. เป็นไปได้หรือไม่
Niks

1
@ Niks - แน่นอน! เพียงเขียนและสลับในฟังก์ชั่นเล็ก ๆ ที่ไม่เพียง แต่อ่านไฟล์ แต่ต่อท้ายชื่อไฟล์ต่อท้ายแต่ละเรคคอร์ด เช่นนี้readAddFilename <- function(flnm) { read_csv(flnm) %>% mutate(filename = flnm) }แล้วเพียงแค่วางลงในmap_dfแทนที่จะอ่านง่ายเท่านั้นread_csv()ที่มีอยู่ในขณะนี้ ฉันสามารถอัปเดตรายการด้านบนเพื่อแสดงฟังก์ชั่นและวิธีการที่จะพอดีกับไปป์ถ้าคุณยังมีคำถามหรือคุณคิดว่ามันจะเป็นประโยชน์
leerssej

ปัญหาในทางปฏิบัติคือread_csvช้ากว่าfreadมาก ฉันจะรวมเกณฑ์มาตรฐานหากคุณกำลังจะพูดบางอย่างเร็วขึ้น แนวคิดหนึ่งคือสร้างไฟล์ขนาด 30GB และอ่านไฟล์ซึ่งเป็นกรณีที่ประสิทธิภาพมีความสำคัญ
marbel

@marbel: ขอบคุณสำหรับคำแนะนำ! ใน 530 MB และไดเรกทอรีที่เล็กกว่า (ที่มีมากถึง 100 ไฟล์) ฉันพบว่าการปรับปรุงประสิทธิภาพ 25% ระหว่างdata.table 's fread()และdplyr ' s read_csv(): 14.2 vs 19.9 วินาที TBH ฉันเพิ่งเปรียบเทียบฐาน R กับ dplyr เท่านั้นและread_csv()เร็วกว่า2-4 เท่าการread.csv()เปรียบเทียบก็ไม่จำเป็น อย่างไรก็ตามมันเป็นเรื่องที่น่าสนใจที่จะให้fread()คำตอบสั้น ๆ และหยุดชั่วคราวเพื่อตรวจสอบผลการวัดประสิทธิภาพที่สมบูรณ์ยิ่งขึ้น ขอบคุณอีกครั้ง!
leerssej

1
อีกจุดที่ดี ฉันคิดว่าเมื่อฉันเขียนว่าฉันระมัดระวังเกินไปเกี่ยวกับการปกป้องกิจกรรม data.table จากการกลายพันธุ์ข้อมูลในสถานที่ (ซึ่งมีผลต่อประสิทธิภาพการทำงานสำหรับการทำงานครั้งต่อไป แน่นอนว่ามันไม่สมเหตุสมผลในกรณีนี้ ขอบคุณ. :-D รอคอยที่จะเรียกใช้ตัวเลขอีกครั้งในไม่ช้าโดยไม่มีฟังก์ชั่นและชุดข้อมูลขนาดใหญ่ที่มีเครื่องที่ใหญ่กว่า
leerssej

104

ต่อไปนี้เป็นตัวเลือกในการแปลงไฟล์. csv เป็น data.frame เดียวโดยใช้ R base และแพ็คเกจที่มีอยู่บางส่วนสำหรับการอ่านไฟล์ใน 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.tableและreadr

fread()รุ่นซึ่งเป็นฟังก์ชั่นของการdata.tableแพคเกจ นี่คือไกลโดยตัวเลือกที่เร็วที่สุดในการวิจัย

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

การใช้readrซึ่งเป็นแพ็คเกจอื่นสำหรับการอ่านไฟล์ csv มันช้ากว่าfreadเร็วกว่าฐาน R แต่มีฟังก์ชันการทำงานที่แตกต่างกัน

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

2
สิ่งนี้มีประสิทธิภาพอย่างไรกับการลด (rbind, lapply (... )) เพียงแค่เรียนรู้ R แต่การเดาของฉันมีประสิทธิภาพน้อยกว่า
รอน

4
ฉันได้เพิ่มdata.tableเวอร์ชันเพื่อปรับปรุงประสิทธิภาพ
marbel

เป็นไปได้หรือไม่ที่จะอ่านเฉพาะไฟล์บางไฟล์? ตัวอย่างไฟล์ที่มี 'สภาพอากาศ' ในชื่อหรือไม่
Derelict

1
พบได้ที่นี่: stackoverflow.com/questions/10353540/…ขอบคุณ
Derelict

1
+1 ดูเหมือนว่าจะสร้างเฟรมข้อมูลเดียว - SQL UNION ของไฟล์ CSV ทั้งหมด - เป็นวิธีที่ง่ายที่สุดในการทำงานกับ เนื่องจาก OP ไม่ได้ระบุว่าพวกเขาต้องการเฟรมข้อมูล 1 เฟรมหรือหลายเฟรมข้อมูลฉันจึงคิดว่าเฟรมข้อมูล 1 เฟรมนั้นดีที่สุดดังนั้นฉันจึงประหลาดใจที่คำตอบที่ยอมรับไม่ได้ทำ "ยูเนี่ยน" ใด ๆ ฉันชอบคำตอบนี้ซึ่งสอดคล้องกับคำอธิบายของdo.call
ถั่วแดง

24

เช่นเดียวกับการใช้lapplyหรือโครงสร้างวนลูปอื่น ๆ ใน R คุณสามารถรวมไฟล์ CSV ของคุณเป็นไฟล์เดียว

ใน Unix หากไฟล์ไม่มีส่วนหัวก็ง่ายเหมือน:

cat *.csv > all.csv

หรือหากมีส่วนหัวและคุณสามารถค้นหาสตริงที่ตรงกับส่วนหัวและเฉพาะส่วนหัว (เช่นสมมติว่าส่วนหัวบรรทัดทั้งหมดเริ่มต้นด้วย "อายุ") คุณจะทำ:

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

ฉันคิดว่าใน Windows คุณสามารถทำได้ด้วยCOPYและSEARCH(หรือFINDบางอย่าง) จากกล่องคำสั่ง DOS แต่ทำไมไม่ติดตั้งcygwinและรับพลังของเชลล์คำสั่ง Unix?


หรือแม้แต่ไปกับGit Bashที่Gitติดตั้งในตัว?
leerssej

จากประสบการณ์ของฉันนี่ไม่ใช่วิธีแก้ปัญหาที่เร็วที่สุดหากไฟล์ของคุณเริ่มมีขนาดค่อนข้างใหญ่
อาเมียร์

20

นี่คือรหัสที่ฉันพัฒนาขึ้นเพื่ออ่านไฟล์ csv ทั้งหมดลงใน R มันจะสร้างดาต้าเฟรมสำหรับไฟล์ csv แต่ละไฟล์แยกกันและชื่อที่ 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="")))
}

8

คำตอบสามอันดับแรกโดย @ A5C1D2H2I1M1N2O1R2T1, @leerssej และ @marbel และเป็นหลักเดียวกัน: ใช้ fread กับแต่ละไฟล์จากนั้น rbind / rbindlist data.tables ผลลัพธ์ ฉันมักจะใช้rbindlist(lapply(list.files("*.csv"),fread))แบบฟอร์ม

สิ่งนี้ดีกว่าทางเลือก R-internal อื่น ๆ และใช้ได้ดีสำหรับ csv ขนาดใหญ่จำนวนเล็กน้อย แต่ไม่ดีที่สุดสำหรับ csv ขนาดเล็กจำนวนมากเมื่อความเร็วมีความสำคัญ ในกรณีนี้มันสามารถใช้งานได้เร็วกว่ามากในcatขณะที่ @Spacedman แนะนำในคำตอบอันดับที่ 4 ฉันจะเพิ่มรายละเอียดเกี่ยวกับวิธีการทำจากภายใน R:

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

อย่างไรก็ตามถ้าแต่ละ csv มีส่วนหัวล่ะ

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

และถ้าคุณมีไฟล์มากมายที่*.csvshell 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 ที่ต่อกันเป็นผลลัพธ์นั้นใหญ่เกินไปสำหรับหน่วยความจำระบบ

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

และนี่เป็นความเร็วเดียวกับ xads cat fread ธรรมดา :)

หมายเหตุ: สำหรับ data.table ก่อน v1.11.6 (19 กันยายน 2018) ละเว้นจากcmd=fread(cmd=

ภาคผนวก: การใช้ mclapply ของไลบรารีขนานแทน lapply อนุกรมเช่นrbindlist(lapply(list.files("*.csv"),fread))จะเร็วกว่า rbindlist lapply fread มาก

ใช้เวลาในการอ่าน 12,1401 csvs ลงใน data.table เดียว แต่ละ csv มี 3 คอลัมน์หนึ่งแถวส่วนหัวและโดยเฉลี่ย 4.510 แถว เครื่องเป็น GCP VM ที่มี 96 แกน:

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

เพื่อสรุปหากคุณสนใจความเร็วและมีไฟล์จำนวนมากและหลายคอร์ fread xargs cat นั้นเร็วกว่าโซลูชันที่เร็วที่สุดในคำตอบ 3 อันดับแรกประมาณ 50x


6

ในมุมมองของฉันคำตอบอื่น ๆ ส่วนใหญ่ล้าสมัยไปแล้วrio::import_listซึ่งเป็นคำย่อขนาดหนึ่งที่สั้น:

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

ข้อโต้แย้งใด ๆ rio::importเพิ่มเติมจะส่งผ่านไป rioสามารถจัดการกับเกือบทุกรูปแบบไฟล์ R สามารถอ่านและจะใช้data.tableเป็นfreadที่เป็นไปได้ดังนั้นจึงควรจะเร็วเกินไป


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)

คำตอบที่ดี! คุณจะส่งข้อโต้แย้งเพิ่มเติมไปที่freadหรือuser-defined functionsอย่างไร ขอบคุณ!
ตุง

1
@Tung มองหาที่?ldplyแสดงให้เห็นว่า...ข้อโต้แย้งอื่น ๆ .funที่ส่งผ่านไปยัง ใช้อย่างใดอย่างหนึ่งfread, skip = 100หรือfunction(x) fread(x, skip = 100)จะทำงาน
manotheshark

การใช้function(x) fread(x, skip = 100)ไม่ได้ผลสำหรับฉัน แต่การเพิ่ม args เพิ่มเติมหลังจากชื่อฟังก์ชั่นที่ไม่ได้ทำเคล็ดลับ ขอบคุณอีกครั้ง!
ตุง

3

จากความคิดเห็นของ dnlbrk การมอบหมายสามารถทำได้เร็วกว่า 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 / Folder_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)
}

คุณสามารถใช้แพ็คเกจ fread หรือ base read.csv ของ data.table แทน 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)
}

3

นี่เป็นตัวอย่างเฉพาะของฉันในการอ่านหลายไฟล์และรวมเป็น 1 data frame:

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
คุณสามารถใช้rbindlist()จากdata.table
jogo

3

รหัสต่อไปนี้ควรให้ความเร็วที่เร็วที่สุดสำหรับข้อมูลขนาดใหญ่ตราบเท่าที่คุณมีหลายคอร์ในคอมพิวเตอร์ของคุณ:

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/4/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

1

ผมชอบวิธีการที่ใช้list.files(), lapply()และlist2env()(หรือfs::dir_ls(), purrr::map()และlist2env()) ดูเหมือนง่ายและยืดหยุ่น

หรือคุณอาจลองใช้แพคเกจขนาดเล็ก { tor } ( to-R ): โดยค่าเริ่มต้นจะนำเข้าไฟล์จากไดเรกทอรีทำงานลงในรายการ (ชุดlist_*()รูปแบบ) หรือในสภาพแวดล้อมแบบโกลบอล (load_*()รูปแบบ)

ตัวอย่างเช่นที่นี่ฉันอ่านไฟล์. csv ทั้งหมดจากไดเรกทอรีการทำงานของฉันลงในรายการโดยใช้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

และตอนนี้ฉันโหลดไฟล์เหล่านั้นลงในสภาพแวดล้อมทั่วโลกด้วย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.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

1

มีการร้องขอให้ฉันเพิ่มฟังก์ชันการทำงานนี้ลงในแพ็คเกจ R ของ stackoverflow ระบุว่าเป็นแพคเกจจิ๋ว (และไม่สามารถขึ้นอยู่กับแพ็คเกจของบุคคลที่สาม) นี่คือสิ่งที่ฉันได้รับ:

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

โดยการกำหนดพารามิเตอร์ของฟังก์ชันตัวอ่านและตัวลดผู้ใช้สามารถใช้ data.table หรือ dplyr ถ้าพวกเขาเลือกหรือเพียงแค่ใช้ฟังก์ชั่นฐาน R ที่ดีสำหรับชุดข้อมูลขนาดเล็ก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.