split-lapply - объединить большой фрейм данных, чтобы избежать проблем с памятью? - PullRequest
0 голосов
/ 10 декабря 2018

У меня есть ситуация, когда использование split-apply-comb может привести к проблемам с памятью во время выполнения.Задача состоит в том, чтобы идентифицировать общие элементы во всех симуляциях.

numListFull <- replicate(1000, sample(1:55000, sample(54900:55000), 
                                  replace = FALSE))
format(object.size(numListFull), units = "auto", standard = "SI")
# [1] "66 MB"

# Create list of nums shared by all simulations
numListAll <- numListFull[[1]]
numList <- lapply(numListFull[2:length(numListFull)],
                    function(x){intersect(x, numListAll)})
format(object.size(numList), units = "auto", standard = "SI")
# [1] "65.7 MB"

numListAll <- Reduce(intersect, numList)
format(object.size(numListAll), units = "auto", standard = "SI")
# [1] "166.4 kB"

Когда репликации увеличены с 300 до 1000, размеры составляют 219.9 MB, 219.9 MB и 87.5 kB,

Иногда репликации пересекают даже 10000, т. Е. В 10 раз больше последней ситуации.Знаете ли вы о каком-нибудь лучшем способе сделать это, чтобы избежать проблем с памятью на компьютере?

Что-то вроде sensible ?

numList <- lapply(split(2:length(numListFull), rep_len(1:100,length(numListFull))), 
                  function(ind){
                    lapply(numListFull[ind], 
                           function(x){
                             intersect(x, numListAll)})})
format(object.size(numList), units = "auto", standard = "SI")  
# [1] "87.5 MB"

Обновление: Конечно, цикл работает как шарм без проблем с памятью, но за счет распараллеливания!

1 Ответ

0 голосов
/ 11 декабря 2018

Проблема в том, что вы сравниваете яблоки с яблоками; -)

Если я правильно понял, конечным результатом должен быть список или вектор, содержащий только те элементы, которые присутствуют в all семпла, верно?
Потому что сейчас вы сравниваете все свои семплы с первым, что слишком много работы.Давайте рассмотрим небольшой пример и то, что вы на самом деле храните.
Мы сделаем то же самое в вашем коде, за исключением
numListFull <- replicate(10, sample(1:1, sample(8:10, size=1)))
(примечание: предоставление size=1 будеттакже немного ускорить ваш код, хотя и ненамного)
В любом случае, после запуска вашего кода, numList теперь содержит все значения, которых нет в первом примере , но в нем все еще многозначения дублируются, а значения могут быть удалены.В вашей второй попытке это не отличается, вы все равно сравниваете весь семпл с первым, хотя я не совсем уверен, что вы там пытались сделать.

Честно говоря, я думаю, что цикл for можетне будь худшим здесь.Как только вы узнаете, что некоторые значения находятся не в первом или во втором примере, сравнивать эти значения с теми, что присутствуют в третьем примере, бесполезно.Так что если

firststep <- intersect(numListFull[[1]], numListFull[[2]])

, то лучшим следующим шагом будет сравнение numListFull[[3]] с firststep вместо того, чтобы снова смотреть на numListFull[[1]].Это означает, что ваш результат будет постоянно обновляться, и цикл for будет лучшим.
Просто сохраняйте все значения, присутствующие во всех выборках до этой точки, вместо сохранения всех видов промежуточных результатов.

Или параллельный

Можно сделать это распараллеленным, но тогда вам придется рассчитывать вещи индивидуально.Так что сравнивай sample1 с sample2, а между тем сравнивай 3 с 4;5 с 6 и т. Д. Я думаю, что это больше в духе обработки больших данных: рассчитать на небольшой кусок, чтобы сократить до результата, который можно передать.Но визуализируйте его как бинарное дерево: на каждом шаге передается результат, который не превышает половины.Я думаю, это то, что вы пытались сделать во втором примере.По крайней мере, я получаю это:

numListFull <- lapply(split(numListFull, 
   rep(1:ceiling(length(numListFull)/2), each=2)[seq_len(length(numListFull))]), function(dfs) {
     if(length(dfs)==2) {
       return(intersect(dfs[[1]], dfs[[2]]))
     } else {
       # Can be just one
       return(dfs[[1]])
     }
   })

Конечно, это всего лишь один шаг, но если вы поместите его в цикл while, он продолжится до длины 1. Для памяти numListFull перезаписывается в каждом циклеТаким образом, контроль мусора всегда может уменьшить использование памяти до уровня, меньшего, чем тот, с которого мы начали.

С другой стороны, отладка может быть сложнее, чем в цикле for, и я не уверен, какбольшая параллелизация уже происходит в intersect.В любом случае, вы должны заменить lapply на parallel::mclapply, чтобы получить эти преимущества, и если вы работаете на обычном настольном компьютере или ноутбуке, вы также сталкиваетесь с другой проблемой с памятью, то есть с кэшированием.
В цикле for процессор может хранить соответствующие данные в кэше, возможно, даже заглянуть в будущее, чтобы получить данные, которые ему понадобятся через некоторое время.Однако, если вы настаиваете на параллельности, это означает, что вы работаете с различными частями памяти, то есть все данные должны быть записаны и восстановлены снова.Что вполне может сбалансировать любые выгоды, которые вы получите, используя несколько ядер.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...