Подтвердить что ты не робот

Чтение нескольких файлов 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
}
4b9b3361

Ответ 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. Однако вам придется сворачивать ваши собственные утилиты для запроса через границы.

Несмотря на то, что это не лучший вариант, вы можете сделать что-то, что можно сделать в крайнем случае - и это позволит вам легко удалять/устаревать старые записи и не корректировать связанные индексы. он также позволяет загружать предварительно обработанные входящие данные "разделом", если это необходимо.