Чтение нескольких файлов csv быстрее в data.table R
У меня есть 900000 csv файлов, которые я хочу объединить в один большой data.table
. Для этого случая я создал for loop
, который читает каждый файл один за другим и добавляет их в data.table
. Проблема состоит в том, что он выполняет замедление, и количество используемого времени расширяется экспоненциально. Было бы здорово, если бы кто-то помог мне сделать код быстрее. Каждый из файлов csv имеет 300 строк и 15 столбцов.
Код, который я использую до сих пор:
library(data.table)
setwd("~/My/Folder")
WD="~/My/Folder"
data<-data.table(read.csv(text="X,Field1,PostId,ThreadId,UserId,Timestamp,Upvotes,Downvotes,Flagged,Approved,Deleted,Replies,ReplyTo,Content,Sentiment"))
csv.list<- list.files(WD)
k=1
for (i in csv.list){
temp.data<-read.csv(i)
data<-data.table(rbind(data,temp.data))
if (k %% 100 == 0)
print(k/length(csv.list))
k<-k+1
}
Ответы
Ответ 1
Предполагая, что ваши файлы являются обычными csv, я бы использовал data.table::fread
, поскольку он быстрее. Если вы используете Linux-подобную ОС, я бы использовал тот факт, что он позволяет командам оболочки. Предполагая, что ваши входные файлы являются единственными файлами csv в папке, которую я сделал бы:
dt <- fread("tail -n-1 -q ~/My/Folder/*.csv")
После этого вам нужно будет указывать имена столбцов вручную.
Если вы хотите сохранить вещи в R, я бы использовал lapply
и rbindlist
:
lst <- lapply(csv.list, fread)
dt <- rbindlist(lst)
Вы также можете использовать plyr::ldply
:
dt <- setDT(ldply(csv.list, fread))
Это имеет то преимущество, что вы можете использовать .progress = "text"
для получения информации о прогрессе в чтении.
Все вышесказанное предполагает, что все файлы имеют одинаковый формат и имеют строку заголовка.
Ответ 2
Как было предложено @Repmat, используйте rbind.fill. Как предложил @Christian Borck, используйте fread для более быстрого чтения.
require(data.table)
require(plyr)
files <- list.files("dir/name")
df <- rbind.fill(lapply(files, fread, header=TRUE))
В качестве альтернативы вы можете использовать do.call, но rbind.fill быстрее (http://www.r-bloggers.com/the-rbinding-race-for-vs-do-call-vs-rbind-fill/)
df <- do.call(rbind, lapply(files, fread, header=TRUE))
Или вы можете использовать пакет data.table, см. это
Ответ 3
Вы расширяете свою таблицу данных в цикле for - вот почему она берет навсегда. Если вы хотите сохранить цикл for как есть, сначала создайте пустой кадр данных (перед циклом), который имеет необходимые размеры (строки x столбцов) и поместите его в ОЗУ.
Затем пишите в этот пустой кадр на каждой итерации.
В противном случае используйте rbind.fill из пакета plyr - и избегайте петли altogehter.
Чтобы использовать rbind.fill:
require(plyr)
data <- rbind.fill(df1, df2, df3, ... , dfN)
Чтобы передать имена df, вы могли/должны использовать функцию apply.
Ответ 4
Основываясь на Ответ Ник Кеннеди, используя plyr::ldply
, увеличение скорости примерно на 50% обеспечивается включением опции .parallel
при чтении 400 файлов csv примерно 30-40 MB каждый.
Оригинальный ответ с индикатором выполнения
dt <- setDT(ldply(csv.list, fread, .progress="text")
Включение .parallel
также с помощью строки выполнения текста
library(plyr)
library(data.table)
library(doSNOW)
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)
Ответ 5
Я иду с @Repmat, так как ваше текущее решение с помощью rbind()
копирует всю таблицу данных в памяти каждый раз, когда она вызывается (поэтому время растет экспоненциально). Хотя другим способом было бы создать пустой файл csv только с заголовками, а затем просто добавить данные всех ваших файлов в этот CSV файл.
write.table(fread(i), file = "your_final_csv_file", sep = ";",
col.names = FALSE, row.names=FALSE, append=TRUE, quote=FALSE)
Таким образом, вам не нужно беспокоиться о том, чтобы данные попадали в нужные индексы в вашей таблице данных. Также как подсказка: fread()
- это считыватель файлов данных. Это намного быстрее, чем read.csv.
В обобщенном R не будет моим первым выбором для этих задач перебора данных.
Ответ 6
Одно из предложений заключалось бы в том, чтобы объединить их сначала в группах по 10 или около того, а затем объединить эти группы и т.д. Это имеет то преимущество, что если отдельные слияния терпят неудачу, вы не теряете всю работу. То, как вы делаете это сейчас, не только приводит к экспоненциальному замедлению выполнения, но и дает вам возможность начинать с самого начала каждый раз, когда вы терпите неудачу.
Этот способ также уменьшит средний размер кадров данных, участвующих в вызовах rbind
, так как большинство из них будут добавляться к небольшим кадрам данных и только несколько больших в конце. Это должно устранить большую часть времени выполнения, которое растет экспоненциально.
Я думаю, что независимо от того, что вы делаете, это будет большая работа.
Ответ 7
Некоторые вещи, которые следует учитывать в предположении, вы можете доверять всем входным данным и что каждая запись обязательно будет уникальной:
-
Рассмотрите возможность создания импортируемой таблицы без индексов. По мере того, как индексы становятся огромными, время, затрачиваемое на их управление во время импорта, растет - похоже, это может произойти. Если это ваша проблема, потребуется еще много времени для создания индексов.
-
В качестве альтернативы, с объемом данных, которые вы обсуждаете, вы можете захотеть рассмотреть способ разделения данных (часто выполняемый с помощью диапазонов дат). В зависимости от вашей базы данных вы можете затем индивидуально проиндексировать разделы - ослабляя усилия индекса.
-
Если ваш демонстрационный код не разрешает работу с программой импорта файлов базы данных, используйте эту утилиту.
-
Возможно, стоит загружать файлы в большие массивы данных до их импорта. Вы можете поэкспериментировать с этим, объединив 100 файлов в один больший файл перед загрузкой, например, и время сравнения.
Если вы не можете использовать разделы (в зависимости от среды и опыта персонала базы данных), вы можете использовать домашний варочный метод разделения данных на различные таблицы. Например, data201401 - data201412. Однако вам придется сворачивать ваши собственные утилиты для запроса через границы.
Несмотря на то, что это не лучший вариант, вы можете сделать что-то, что можно сделать в крайнем случае - и это позволит вам легко удалять/устаревать старые записи и не корректировать связанные индексы. он также позволяет загружать предварительно обработанные входящие данные "разделом", если это необходимо.