R: geom_smooth + geom_hline увеличивает использование памяти - PullRequest
1 голос
/ 03 августа 2020

Я работаю с набором данных> 1 ГБ и у меня возникают ошибки нехватки памяти ("Невозможно выделить ...") при построении графиков ggplot2. Пытаясь исследовать, куда уходит вся моя память (с помощью таких источников, как this и this и this , я обнаружил, что следующий код с фиктивные данные вызывают значительное использование памяти, которое, кажется, невостребовано диспетчером задач Windows даже после повторных вызовов gc().

print(begMemSize <- memory.size())

library(ggplot2)
numRows <- 1e6
df <- data.frame( x1 = runif(numRows), x2 = runif(numRows), xGroup = factor(trunc(runif(numRows, 1, 6))) )
df$y = df$x1 + df$x2

gc()
print(mid1MemSize <- memory.size())

# This is fine
ggplot( data = df, mapping = aes( x = x1)) +
  geom_smooth( mapping = aes( y = y))

gc()
print(mid2MemSize <- memory.size())

# This makes memory.size() explode
ggplot( data = df, mapping = aes( x = x1)) +
  geom_smooth( mapping = aes( y = y)) +
  geom_hline( mapping = aes( yintercept = 0.25))

gc()
print(endMemSize <- memory.size())

Выражение c( begMemSize, mid1MemSize, mid2MemSize, endMemSize) возвращает:

[1]   50.62  102.30  199.22 1208.39

Обратите внимание на огромный скачок последнего числа. Это последнее число соответствует показаниям в Windows Диспетчере задач (очень близко к «Память (активный рабочий набор)» и лишь немного меньше, чем «Размер фиксации» на вкладке «Подробности»). Иногда , с повторными вызовами gc() я могу получить memory.size() до go в R, но не показания в диспетчере задач Windows. Я беспокоюсь, что мои ошибки нехватки памяти связаны с этим, но мои Непосредственные вопросы:

  1. Почему это происходит?
  2. Есть ли способ снизить показания памяти диспетчера задач Windows до go в этой ситуации (без, очевидно, , закрывая R и потеря всей обработки данных в памяти)?

sessionInfo() вывод (с использованием RStudio 1.3.1056):

R version 4.0.2 (2020-06-22)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19041)

Matrix products: default

Random number generation:
 RNG:     Mersenne-Twister 
 Normal:  Inversion 
 Sample:  Rounding 
 
locale:
[1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252    LC_MONETARY=English_United States.1252 LC_NUMERIC=C                           LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] ggplot2_3.3.2

loaded via a namespace (and not attached):
 [1] rstudioapi_0.11  magrittr_1.5     splines_4.0.2    tidyselect_1.1.0 munsell_0.5.0    colorspace_1.4-1 lattice_0.20-41  R6_2.4.1         rlang_0.4.6      dplyr_1.0.0      tools_4.0.2      grid_4.0.2      
[13] gtable_0.3.0     nlme_3.1-148     mgcv_1.8-31      withr_2.2.0      ellipsis_0.3.1   digest_0.6.25    tibble_3.0.1     lifecycle_0.2.0  crayon_1.3.4     Matrix_1.2-18    farver_2.0.3     purrr_0.3.4     
[25] vctrs_0.3.1      glue_1.4.1       labeling_0.3     compiler_4.0.2   pillar_1.4.4     generics_0.0.2   scales_1.1.1     pkgconfig_2.0.3 

1 Ответ

3 голосов
/ 10 августа 2020

Это всего лишь частичный ответ относительно одного аспекта проблемы.

Когда вы помещаете yintercept в функцию aes(), вы даете команду ggplot2 сопоставить yintercept aestheti c в каждую строку аргумента data. Следовательно, слой geom_hline() преобразует свои данные в большой data.frame, содержащий много строк. Если вы не поместите его в функцию aes(), а используете в качестве обычного аргумента слоя, данные слоя останутся небольшими. См. Пример ниже.

library(ggplot2)
numRows <- 1e6
df <- data.frame( x1 = runif(numRows), x2 = runif(numRows), xGroup = factor(trunc(runif(numRows, 1, 6))) )
df$y = df$x1 + df$x2

p <- ggplot( data = df, mapping = aes( x = x1)) +
  geom_smooth( mapping = aes( y = y))

p_mapped <- p + geom_hline(mapping = aes(yintercept = 0.25))
p_unmapped <- p + geom_hline(yintercept = 0.25)

layer_mapped <- layer_data(p_mapped, 2)
#> `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
layer_unmapped <- layer_data(p_unmapped, 2)
#> `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'

(format(object.size(layer_mapped), units = "Mb"))
#> [1] "42 Mb"
(format(object.size(layer_unmapped), units = "Kb"))
#> [1] "2.2 Kb"

Обратите внимание, что хотя размер данных слоя не учитывает большую часть используемой памяти, нужно иметь в виду, что многие вещи вычисляются на основе данных, начиная с вычисления по осям, чтобы применить альфа-канал к цветам.

Кроме того, запустив ваш пример и изменив место, где yintercept было определено из аргумента сопоставления на обычный аргумент слоя, endMemSize было около ~ 200Мб для меня.

Наконец, ggplot2 хранит копию последнего графика в своем пространстве имен, которая не видна пользователям. Однако вы можете использовать set_last_plot(NULL), чтобы освободить дополнительную память.

...