Р: Как предотвратить переполнение памяти при использовании mgsub в векторном режиме?

У меня есть длинный вектор символов (например, «Hello World» и т. д.), 1,7 млн ​​строк, и мне нужно заменить в них слова, используя карту между двумя векторами, и сохранить результат в том же векторе. Вот простой пример:

library(qdap)
line = c("one", "two one", "four phones")
e = c("one", "two")
r = c("ONE", "TWO")
line = mgsub(e,r,line)

Результат:

[1] "ONE"  "TWO ONE" "four phONEs"

Как видите, каждый экземпляр e[j] в строке заменяется на r[j] и только на r[j]. Он отлично работает с относительно небольшой «строкой» и длиной словаря e->r, но когда я запускаю length(line) = 1700000 и length(e) = 750, я достигаю общей выделенной памяти:

Reached total allocation of 7851Mb: see help(memory.size)

Есть идеи, как этого избежать?


person Alexey Ferapontov    schedule 08.12.2014    source источник
comment
Возможно, по одному: for(i in seq_along(e)) line <- gsub(e[i], f[i], line, fixed = TRUE) . Это должно работать, если элементы e не являются подстроками f.   -  person G. Grothendieck    schedule 09.12.2014
comment
Спасибо за предложение! Я пробовал это раньше. Фактически, это был мой основной метод до того, как я установил qdap и начал использовать mgsub. Недостатком этого является то, что он очень медленный, так как подразумевает явный цикл из 750 элементов в словаре x 1,7 млн ​​строк в строке.   -  person Alexey Ferapontov    schedule 09.12.2014
comment
Вы использовали fixed=TRUE. Это может немного ускорить.   -  person G. Grothendieck    schedule 09.12.2014
comment
Ага; циклы for, как правило, будут медленными. Вы можете получить /небольшое/ увеличение скорости из семейства функций apply, но оно очень незначительное и не применимо напрямую в данном случае. Одна хорошая среда будет делать это кусками; измените его так, чтобы вместо одного вектора размера N вы читали вектор размера N/A, применяя mgsub, записывая, очищая память и повторяя - продолжайте корректировать A вниз, пока не достигнете предела памяти. Это не элегантно, но, вероятно, быстрее с точки зрения времени выполнения.   -  person Oliver Keyes    schedule 09.12.2014
comment
Еще одна вещь, которую можно попробовать, - это использовать sub вместо gsub при условии, что может быть не более одного вхождения любого компонента e в любой компонент line. Еще одна возможность состоит в том, чтобы обрабатывать более k компонентов line в то время, когда правильно выбрано целое число k: n <- length(line); g <- gl(n, k, n); for(lv in levels(g)) { ok <- lv == seq_along(line); line[ok] <- mgsub(e, f, line[ok]) }.   -  person G. Grothendieck    schedule 09.12.2014
comment
Да, в методе цикла for я использовал fixed = TRUE. Все еще очень медленный (750x1.7e6). gsub не подходит, так как строки могут содержать более одного экземпляра e. Что вы думаете об использовании ddply? Преобразование списка в DF, разделение, запуск несколько раз, очистка после каждого раза, слияние. Я очень новичок в ddply, поэтому я не уверен, что он вообще будет работать. Другие варианты? Вектор будет занимать довольно много места в памяти, но выходной результирующий вектор по-прежнему будет состоять из 1,7 млн ​​строк.   -  person Alexey Ferapontov    schedule 09.12.2014
comment
Быстрое обновление: я только что понял, что я не могу использовать fixed = TRUE в gsub, так как замена может быть частью большего слова, что недопустимо (например, e = "caps", line = "capsule" - заглавные буквы в строке будут заменены каким-либо символом из r) - я должен использовать \\be\\b. Есть ли способ обойти это, то есть использовать fixed=T и сопоставлять только целые слова?   -  person Alexey Ferapontov    schedule 09.12.2014
comment
@AlexeyFerapontov Т}ваш пост вдохновил меня обновить код для mgsub, чтобы использовать внутренний цикл for. Это сделает его более быстрым и менее проблематичным с проблемами памяти. github.com/trinker/qdap/issues/201   -  person Tyler Rinker    schedule 18.12.2014
comment
Привет, Тайлер, спасибо! Будут ли эти изменения глобальными, то есть во всем сообществе R, или это ваш личный код? Все еще кажется, что для моего приложения gsub с fixed=T будет работать немного быстрее, но теперь у нас есть альтернатива, когда скорость менее важна, а важнее компактный векторизованный код   -  person Alexey Ferapontov    schedule 18.12.2014


Ответы (3)


Я считаю, что вы можете использовать fixed = TRUE.

Похоже, вас беспокоят пробелы... так что просто добавьте пробелы к концам всех трех векторов, с которыми вы работаете. Чтобы запустить всю эту последовательность от ## Start до ## Finish (примерно размер ваших данных), требуется Time difference of 2.906395 secs на 1,7 миллиона строк. Большая часть времени находится в конце с удалением лишних пробелов.

## Recreate data
line <- c("one", "two one", "four phones", "and a capsule", "But here's a caps key")
e <- c("one", "two", "caps")
r <- c("ONE", "TWO", "CAPS")

line <- rep(line, 1700000/length(line))

## Start    
line2 <- paste0(" ", line, " ")
e2 <-  paste0(" ", e, " ")
r2 <- paste0(" ", r, " ")


for (i in seq_along(e2)) {
    line2 <- gsub(e2[i], r2[i], line2, fixed=TRUE)
}

gsub("^\\s|\\s$", "", line2, perl=TRUE)
## Finish

Здесь mgsub qdap бесполезен. Пакет был разработан для гораздо меньших данных. Кроме того, fixed = TRUE является разумным значением по умолчанию, потому что оно намного быстрее. Смысл добавления пакетов заключается в улучшении рабочего процесса (иногда в зависимости от поля/задачи) за счет перенастройки доступных инструментов. Функция mgsub также имеет некоторую обработку ошибок и другие тонкости, которые полезны при анализе расшифровок, которые заставляют функцию поглощать память. Часто приходится идти на компромисс между безопасностью и синтаксическим сахаром против скоростью.

Обратите внимание, что только то, что две функции названы одинаково, не должно ничего подразумевать, особенно если они находятся в дополнительных пакетах. Даже функции в базе R имеют разные имена и поведение по умолчанию (посмотрите на семейство функций apply; эта проблема далеко не идеальна, но является частью исторической эволюции R). Вы как пользователь обязаны читать документацию, а не делать предположения.

person Tyler Rinker    schedule 09.12.2014
comment
Благодарю вас! Мне нравится ваше предложение о добавлении пробелов. В этом случае я действительно могу использовать fixed=TRUE (необходимо тщательно перепроверить). Хорошо известно о mgsub и его применимости, а также о различных значениях по умолчанию в функциях. - person Alexey Ferapontov; 10.12.2014
comment
Тайлер, а что, если у меня есть Но вот заглавные буквы, тильда и клавиша с цифровым замком? caps, не будет изменено на cap,, так как после запятой будут добавлены пробелы, и априори я не знаю, что будет следовать за этим полным словом - запятая, точка и т. д. Есть ли способ удалить знаки препинания, а затем сшить обратно? - person Alexey Ferapontov; 06.02.2015
comment
Могу ли я предложить новый вопрос с образцом кода. Легче тестировать и анализировать ваши данные и желаемый результат. У людей могут быть дополнительные идеи, поскольку вы спрашивали об этом раньше. - person Tyler Rinker; 06.02.2015
comment
Не будет ли это считаться дубликатом? - person Alexey Ferapontov; 06.02.2015
comment
Нет, у вас новый параметр. Объясните это там. Раньше знаков препинания не было, а теперь есть. - person Tyler Rinker; 06.02.2015
comment
Спасибо! stackoverflow.com/questions/28354573/ - person Alexey Ferapontov; 06.02.2015

Пакет stringi предоставляет быстрые согласованные инструменты для большого количества операций со строками:

stri_replace_all_regex(line, paste0("\\b", e, "\\b"), r, vectorize_all = FALSE)

Почти так же быстро (отличаются доли секунды), как и другой метод, и более прямолинейный.

person Tyler Rinker    schedule 01.03.2015
comment
Спасибо, Тайлер. Я проверю это завтра и сравню с моим текущим методом - FixedFalse с Perl. Fixed True — это та же скорость, что и при использовании False с Perl в моих данных. Но Fixed False обеспечивает большую гибкость. Если stri_replace сделает это еще быстрее, я буду очень-очень рад. В моем приложении я выполняю 3,1 миллиона строк x операций с шаблонами в секунду без переполнения памяти (как это было с mgsub) - person Alexey Ferapontov; 02.03.2015
comment
stri_replace_all_regex оказался в 3,5 раза медленнее, чем gsub с fixed=FALSE, perl=TRUE - person Alexey Ferapontov; 02.03.2015
comment
Да, я пробовал на примере выше, и это было сопоставимо. Имейте в виду stringi, хотя это отличная работа. - person Tyler Rinker; 02.03.2015

Дополнение к проблеме (Админам: если она не заслуживает отдельного ответа - пожалуйста, объедините ее с исходной). Причина, по которой mgsub работала так быстро по сравнению с простым циклом for, заключалась в том, что в mgsub параметр fixed = TRUE по умолчанию, а в gsub по умолчанию FALSE! Я только что обнаружил это. Хочу еще раз уточнить, что fixed=TRUE мне не подходит, так как я не хочу заменять caps в capsule, а только все слово caps. т.е. Я вынужден вставить \\bs в шаблон. Вот три фрагмента моего кода (я протестировал fixed=TRUE в gsub просто чтобы увидеть разницу во времени, не собираюсь его использовать).

#This is with mgsub. Now with fixed = FALSE!!
i = mgsub(paste("\\b",orig,"\\b",sep=""),change,i,fixed=FALSE)

#This is with a for loop. fixed=TRUE in one of lines is for test purposes only. Do not use
for(k in seq_along(orig)) {
  i = gsub(paste("\\b",orig[k],"\\b",sep=""),change[k],i)
  #i = gsub(orig[k],change[k],i,fixed=TRUE)
}

Вот время и использование памяти для всех трех случаев с разным количеством входных данных:

N     | mgsub, fixed=F   | gsub, fixed=F    | gsub, fixed=T
--------------------------------------------------------------
100k  | 41sec, M > 2.3GB | 37sec, M > 0.9GB | 9sec, M > 0.8GB
200k  | 99sec, M > 4GB   | 74sec, M > 1.1GB | 18sec, M > 1.3GB
300k  | 132sec, M > 5.6GB| 112sec, M > 2.6GB| 28sec, M > 1.6GB 
        + disk involved

Таким образом, я заключаю, что для моего приложения, когда fixed должно быть FALSE, нет никаких преимуществ использования mgsub. На самом деле цикл for работает быстрее и не вызывает переполнения памяти!

Спасибо всем причастным. Я хотел бы отдать должное комментаторам, но я не знаю, как это сделать в «Комментариях».

person Alexey Ferapontov    schedule 09.12.2014
comment
Операция вставки внутри цикла for сжигает время. Вы можете добавить \\b до того, как начнете orig, но ниже я предоставил способ, которым вы можете использовать fixed = TRUE. - person Tyler Rinker; 10.12.2014