Самый быстрый способ в R для чтения и сравнения CSV-файлов

#r #csv #data.table

Вопрос:

Я знаю, что есть и другие вопросы о переполнении стека о самом быстром способе чтения csv — файлов в R-и на них были даны ответы; data.table, похоже, это правильный путь. Но у меня есть дополнительные требования.

Мне нужно придумать сценарий, который устанавливает операцию различия между двумя группами векторов (чтобы найти количество значений, совпадающих в обоих векторах). Обе группы векторов должны быть извлечены из csv-файлов в двух разных каталогах, dirA и dirB. Каждый вектор в dirA будет сравниваться со всеми векторами в dirB, и будет записано количество совпадений. dirA содержит около 50 файлов, а dirB-3000 файлов различного размера (от 1 до 60 Мбайт).

Ниже приведена моя попытка сделать это с помощью R. Это не так быстро, как я ожидал бы (по сравнению с аналогичным решением, реализованным в Pandas, этот код на 30% медленнее). Один раз чтение 3000 файлов занимает более 120 секунд. Есть ли что — то, чего мне не хватает, или это лучшее, что я могу получить в R-my be, разумно используя векторизацию и многократные сравнения за один раз? Мы будем признательны за любую помощь. Спасибо.

  • Я использую data.table версии 1.13.6.
  • Я хочу читать все как строку (есть начальные нули и некоторые другие аномалии).

Код:

 path_dirA <- "data/processed_data_dirA"
path_dirB <- "data/processed_data_dirB"

fn_dirA <- list.files(here(path_dirA), pattern="csv")
fn_dirB <- list.files(here(path_dirB), pattern="csv")
v_count_matched <- integer()

for (fn1 in fn_dirA) {
  f1 <- data.table::fread(here(fn_dirA, fn1), colClasses = 'character')
 
  for (fn2 in fn_dirB) {
    f2 <- data.table::fread(here(fn_dirB, fn2), colClasses = 'character')
    v_count_matched <- c(v_count_matched, length( fintersect(f1[,1],f2[,1]) ) )

    }
  }
}
 

Комментарии:

1. В некоторых случаях vroom это быстрее, чем data.table для приема файлов, так как он индексирует их для ленивой загрузки. Возможно, стоит попробовать. github.com/r-lib/vroom

2. Вместо того, чтобы объединяться length( fintersect(f1[,1],f2[,1]) ) для v_count_matched каждой итерации цикла, создайте вектор v_count_matched , который имеет длину fn_dirA , и заполните его как v_count_matched[fn1] = length( fintersect(f1[,1],f2[,1]) ) .

3. Проверьте круг 2 в R Inferno . Проблема может быть в постоянном добавлении, а не в чтении файла.

4. Если вы сравниваете только первый столбец, вы можете прочитать только этот столбец (вместе fread с select параметром).

5. Попробуйте обновить data.table , есть некоторые недавние улучшения производительности, которые могут повлиять на ваш случай.

Ответ №1:

В данном конкретном случае большая часть времени уходит на чтение CSV-файлов. Если бы вы могли кэшировать на диске эти CSV-файлы в другом формате с более быстрым временем чтения, вы получили бы максимальную экономию.

Например, если вам нужно ежедневно повторять сравнения, но изменился только один CSV.

Вы могли бы сохранить эти CSV-файлы (кэшированные на диске) в fst формате. https://www.fstpackage.org/

Комментарии:

1. vroom пакет в этом случае бесполезен, потому что его преимуществом является медленная загрузка. Но вам действительно нужно прочитать все содержимое файлов.

2. Набор файлов в папке B меняется не так часто. Преобразование их в fst очень помогает и фактически экономит больше всего времени; принятие этого ответа. Другие ответы также помогли мне лучше понять. Так Что Спасибо Вам ВСЕМ.

Ответ №2:

Одним из возможных ускорений было бы использование индексов для добавления данных, а не для объединения:

 fn_dirA <- list.files(here(path_dirA), pattern="csv")
fn_dirB <- list.files(here(path_dirB), pattern="csv")
v_count_matched <- vector(NA, length(fn_dirA)*length(n_dirA))


counter = 0
for (fn1 in fn_dirA) {
  f1 <- data.table::fread(here(fn_dirA, fn1), colClasses = 'character')
 
  for (fn2 in fn_dirB) {
    counter = counter   1
    f2 <- data.table::fread(here(fn_dirB, fn2), colClasses = 'character')
    v_count_matched[counter] <- length( fintersect(f1[,1],f2[,1]))

    }
  }
}
 

Ответ №3:

Я принял ответ, основанный на том, что работало ранее. Тем не менее, я смог значительно сократить время выполнения, просто добавив setkey. Теперь все это занимает 6 часов вместо нескольких дней!