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


220

Предположим, у нас есть папка, содержащая несколько файлов 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)

Но опять же, часто лучше оставлять их в одном списке.


Спасибо! это работает очень хорошо ... как бы я назвал каждый файл, который я только что импортировал, чтобы я мог легко вызвать их?
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, но не уверен, как это зациклить. @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))....

Бонусная функция - добавление имен файлов к записям по запросу функции Никса в комментариях ниже:
* Добавьте оригинал 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 Сравнение прошедшего времени мой типичный случай использования

Большой вариант использования

Сравнение прошедшего времени для очень большой нагрузки

Разнообразие вариантов использования

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

Базовые результаты R лучше для самых маленьких случаев использования, когда издержки, связанные с переносом библиотек C 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() здесь .
  • Дэвида Маклафлина (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. Я бы включил тест, если вы хотите сказать, что что-то быстрее. Одной из идей является создание 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

105

Вот несколько вариантов преобразования файлов .csv в один файл data.frame с использованием базы R и некоторых доступных пакетов для чтения файлов в 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
как это работает против Reduce (rbind, lapply (...))? Просто изучаю R, но мое предположение менее производительно
Аарон

4
Я добавил data.tableверсию, которая должна улучшить производительность.
Марбель

Можно ли читать только определенные файлы? ex-файлы, содержащие имя «погода»?
Заброшенный

1
нашел его здесь: stackoverflow.com/questions/10353540/… спасибо.
Заброшенный

1
+1 похоже на создание одного фрейма данных - SQL UNION из всех файлов CSV - с ним проще всего работать. Так как OP не указал, хотят ли они 1 фрейм данных или много фреймов данных, я предположил, что 1 фрейм данных является лучшим, поэтому я удивлен, что принятый ответ не выполняет ни одного из «UNION». Мне нравится этот ответ, который согласуется с этим объяснениемdo.call
The Red Pea

24

Помимо использования lapplyили какой-либо другой циклической конструкции в R, вы можете объединить ваши CSV-файлы в один файл.

В Unix, если файлы не имеют заголовков, это так же просто, как:

cat *.csv > all.csv

или если есть заголовки, и вы можете найти строку, которая соответствует заголовкам и только заголовкам (т.е. предположим, что все строки заголовка начинаются с «Age»), вы должны сделать:

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

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


или даже пойти с Git Bash, который падает с Gitустановкой?
leerssej

По моему опыту, это не самое быстрое решение, если ваши файлы начинают становиться довольно большими.
Амир

21

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

9

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

Это лучше, чем другие R-внутренние альтернативы, и хорошо для небольшого числа больших 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)

А что если у вас так много файлов, что *.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")

и это примерно с той же скоростью, что и обычный кот xargs :)

Примечание: для data.table pre-v1.11.6 (19 сентября 2018) опустите cmd=from fread(cmd=.

Приложение: использование mclapply параллельной библиотеки вместо последовательного lapply, например, rbindlist(lapply(list.files("*.csv"),fread))также намного быстрее, чем rbindlist lapply fread.

Время читать 121401 csvs в одну таблицу data.table. Каждый CSV имеет 3 столбца, одну строку заголовка и в среднем 4,510 строк. Машина представляет собой виртуальную машину GCP с 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.table, 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? Спасибо!
Tung

1
@Tung Глядя на ?ldplyпоказывает ...другие аргументы переданы .fun. Используя либо, fread, skip = 100либо function(x) fread(x, skip = 100)будет работать
manotheshark

Использование function(x) fread(x, skip = 100)не работает для меня, но предоставление дополнительных аргументов после голого имени функции добилось цели. Еще раз спасибо!
Tung

3

Опираясь на комментарий 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 / 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)
}

Вы можете использовать файл read.csv пакета 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)
}

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. Учитывая, что это крошечный пакет (и он не может зависеть от сторонних пакетов), вот что я придумал:

#' 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.