Як імпортувати декілька файлів .csv одночасно?


219

Припустимо, у нас є папка, що містить кілька файлів data.csv, кожен з яких має однакову кількість змінних, але кожен з різних часів. Чи існує спосіб у R імпортувати їх одночасно, а не імпортувати їх поодинці?

Моя проблема полягає в тому, що у мене є близько 2000 файлів даних, які потрібно імпортувати, і потрібно імпортувати їх окремо лише за допомогою коду:

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)

Але знову ж таки, часто краще залишити їх в одному списку.


Дякую! це працює дуже добре ... як би я поступив щодо іменування кожного файлу, який я щойно імпортував, щоб я міг їх легко викликати?
Джоджо Оно

якщо ви можете показати нам перші кілька рядків деяких ваших файлів, ми можемо мати кілька пропозицій - відредагуйте своє запитання для цього!
Spacedman

2
Вищевказаний код прекрасно працює для імпортування їх як окремих об'єктів, але коли я намагаюся викликати стовпчик з набору даних, він не розпізнає його, оскільки це лише один об'єкт, а не кадр даних, тобто моя версія вищевказаного коду: setwd ( 'C: / Users / new / Desktop / Dives / 0904_003') temp <-list.files (pattern = "*. Csv") ddives <- lapply (temp, read.csv) Отже, кожен файл називається ddives [n ] але як би я пішов писати цикл, щоб зробити їх усіма кадрами даних, а не окремими об'єктами? Я можу досягти цього окремо за допомогою оператора data.frame, але я не впевнений, як це зробити. @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 безкоштовно (і , як 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))….

Bonus Feature - додавання імен файлів до записів на запит щодо функції 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)")

Справа середнього використання

Boxplot Порівняння минулого часу - мій типовий випадок використання

Більш широкий випадок використання

Boxplot Порівняння минулого часу для надмірно великих навантажень

Різноманітні випадки використання

Рядки: кількість файлів (1000, 100, 10)
Стовпці: кінцевий розмір фрейму даних (5 МБ, 50 МБ, 500 МБ)
(натисніть на зображення, щоб переглянути оригінальний розмір) Boxplot Порівняння варіацій розмірів каталогу

Результати базового R краще для найменших випадків використання, коли накладні витрати, приводячи бібліотеки С purrr і dplyr, переважають над підвищеннями продуктивності, які спостерігаються при виконанні завдань з більшого масштабу обробки.

якщо ви хочете запустити власні тести, вам може бути корисний цей скрипт 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 символів імені файлу та підкреслення).

Атрибуції та вдячності

З особливою подякою:

  • Тайлер Рінкер і Акрун для демонстрації мікробензика.
  • Джейк Каупп за те, що познайомив мене map_df() тут .
  • Девід Маклафлін за корисні відгуки щодо покращення візуалізації та обговорення / підтвердження інверсій продуктивності, що спостерігаються у невеликому файлі, невеликих результатах аналізу фреймів даних.
  • marbel для вказівки поведінки за замовчуванням для fread(). (Мені потрібно вчитися data.table.)

1
ти рішення працює на мене. У цьому я хочу зберегти це ім'я файлу, щоб розмежувати їх. Чи можливо це?
Нікс

1
@Niks - Безумовно! Просто напишіть та поміняйте невелику функцію, яка не лише зчитує файли, але негайно додає ім'я файлу до кожного прочитаного запису. Так, readAddFilename <- function(flnm) { read_csv(flnm) %>% mutate(filename = flnm) }тоді просто киньте це на map_dfзамість простого читання лише того, read_csv()що є зараз. Я можу оновити запис вище, щоб показати функцію та те, як вона впишеться в трубу, якщо у вас все ще виникають питання або ви думаєте, що це буде корисно.
leerssej

Проблема на практиці полягає в тому, що read_csvце набагато повільніше, ніж fread. Я б включив орієнтир, якщо ти збираєшся сказати щось швидше. Одна ідея - створити 30 1 Гб файлів і прочитати їх, це був би випадок, коли продуктивність має значення.
марбель

@marbel: Дякую за пропозицію! На 530 МБ і більше дрібних каталогів (с до 100 файлів) Я знаходжу поліпшення на 25% в продуктивності між data.table «s fread()і dplyr » s read_csv(): 14,2 проти 19,9 сек. TBH, я порівнював лише базу R з dplyr і, як read_csv()приблизно, на 2-4 рази швидше, ніж read.csv()тестування, здавалося, не потрібно. Однак було цікаво fread()закрутитись і зробити паузу, щоб перевірити більш повні результати еталону. Знову дякую!
leerssej

1
Ще один чудовий момент. Я думаю, коли писав, що я надто обережно став захищати діяльність data.table від мутації даних на місці (що впливає на продуктивність для наступних та всіх наступних запусків даних). Звичайно, в цьому випадку немає сенсу. Дякую. :-D Чекаємо на швидке повторне запуску цифр без функцій та з більшими наборами даних з більшою машиною.
leerssej

104

Ось кілька варіантів перетворення файлів .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.tableтаreadr

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, швидше, ніж база R, але має різні функції.

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

2
як це виконується проти зменшення (rbind, lapply (...))? Тільки вивчаю R, але я здогадуюсь менш ефективно
aaron

4
Я додав data.tableверсію, яка повинна покращити продуктивність.
марбель

Чи можна читати лише певні файли? ex Файли, що містять у назві "погода"?
Занедбане

1
знайшов його тут: stackoverflow.com/questions/10353540/… дякую.
Занедбане

1
+1 здається, що створити єдиний фрейм даних - SQL UNION усіх файлів CSV - з ним найпростіше працювати. Оскільки в ОП не було вказано, хочуть вони 1 кадр даних чи багато кадрів даних, я вважав, що найкращий 1 кадр даних, тому я здивований, що прийнята відповідь не робить жодного з "Спілки". Мені подобається ця відповідь, яка відповідає цьому поясненнюdo.call
Червоний горох

24

Окрім використання lapplyчи іншої контурної конструкції в R, ви можете об'єднати свої CSV файли в один файл.

Якщо в файлах Unix не було заголовків, то це так само просто, як:

cat *.csv > all.csv

або якщо є заголовки, і ви можете знайти рядок, який відповідає заголовкам і лише заголовкам (тобто припустімо, що рядки заголовків починаються з "Age"), ви зробите:

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

Я думаю, що в Windows ви могли б зробити це за допомогою командного поля DOS COPYта SEARCH(або FINDщось таке), але чому б не встановити cygwinта отримати потужність командної оболонки Unix?


чи навіть піти з Git Bash, який вбудовується при Gitвстановленні?
leerssej

На мій досвід, це не найшвидше рішення, якщо ваші файли починають отримувати досить великі розміри.
Амір

20

Це код, який я розробив для зчитування всіх файлів 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="")))
}

8

Три найкращі відповіді від @ A5C1D2H2I1M1N2O1R2T1, @leerssej та @marbel, і всі по суті однакові: застосувати fread до кожного файлу, а потім rbind / rbindlist отриманих таблиць даних. Я зазвичай використовую rbindlist(lapply(list.files("*.csv"),fread))форму.

Це краще, ніж інші R-внутрішні альтернативи, і добре для невеликої кількості великих csv, але не найкраще для великої кількості малих csv, коли швидкість має значення. У такому випадку спочатку використовувати його можна набагато швидше cat, як пропонує @Spacedman у відповіді четвертого рейтингу. Я додам детальну інформацію про те, як це зробити зсередини R:

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

Однак що робити, якщо кожен csv має заголовок?

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

А що, якщо у вас так багато файлів, що *.csvглобальна оболонка виходить з ладу?

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

і це приблизно така ж швидкість, як і звичайна кішка Fread xargs :)

Примітка: для data.table pre-v1.11.6 (19 вересня 2018 р.) Опустіть поле cmd=з fread(cmd=.

Додаток: використання паралельної бібліотеки mclapply замість послідовного пропуску, наприклад, rbindlist(lapply(list.files("*.csv"),fread))також набагато швидше, ніж rbindlist lapply fread.

Час для читання 121401 csvs в єдиний таблицю даних. Кожен 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 приблизно в 50 разів швидше, ніж найшвидше рішення в топ-3 відповідей.


6

На мою думку, більшість інших відповідей застаріли rio::import_list, що є стислим однорядним:

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

Будь-які додаткові аргументи передаються rio::import. rioможе мати справу з майже будь-яким файловим форматом, який R може читати, і він використовує data.tables, freadде це можливо, тому він повинен бути швидким.


5

Використання plyr::ldplyтам приблизно на 50% збільшує швидкість, вмикаючи .parallelможливість, читаючи 400 csv файлів приблизно 30-40 Мб кожен. Приклад включає в себе смугу прогресу тексту.

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)не працювало для мене, але надання додаткових аргументів після голої назви функції зробило свою справу. Знову дякую!
Тунг

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 на істинне, ви отримаєте повний шлях до кожного файлу як окремий рядок символів у вашому списку файлів, наприклад, 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)
}

Ви можете використовувати фрейд пакета data.table або базовий R read.csv замість read_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])  
  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 кадр даних:

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

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.caseі invert.


Для ще більшої гнучкості використання 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

Передайте додаткові аргументи через ... або всередині функції лямбда.

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

Було запропоновано додати цю функціональність до пакета 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{/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, які добре підходять для менших наборів даних.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.