R ggplot2 странное поведение. Похоже, что это проходит по ссылке

#r #variables #ggplot2

Вопрос:

Я пытаюсь скопировать объект ggplot, а затем изменить некоторые свойства нового скопированного объекта, например, цветовую линию на красный.

Предположим, что этот код:

 df = data.frame(cbind(x=1:10, y=1:10))
a = ggplot(df, aes(x=x, y=y))   geom_line()
b = a
 

Затем, если я изменю цвет строки переменной a

 a$layers[[1]]$geom_params$colour = "red"
 

он также меняет цвет b

 > b$layers[[1]]$geom_params$colour 
[1] "red"    # why it is not "black"?  
 

Я хотел бы иметь два разных объекта a и b с разными характеристиками. Итак, чтобы сделать это правильно, мне нужно будет снова вызвать сюжет для b использования b = ggplot(df, aes(xy, y=z)) geom_line() . Однако в настоящее время в алгоритме нет способа узнать команду построения ggplot(df, aes(x=x, y=y)) geom_line()

Ты знаешь, что в этом плохого? Обрабатываются ли объекты ggplot по-другому?

Спасибо!

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

1. Ну, я согласен, что это немного жутковато, но если вы посмотрите str(a) , вы заметите, что слои являются прото-объектами, так что, скорее всего, это объясняет, почему вы видите такое поведение.

Ответ №1:

Проблема здесь в том, что ggplot proto библиотека используется для имитации объектов в стиле OO. proto Библиотека использует среды для сбора переменных для объектов. Среды передаются по ссылке, поэтому вы видите свое поведение (а также причину, по которой никто, вероятно, не рекомендовал бы изменять свойства слоя таким образом).

В любом случае, адаптировав пример из proto документа, мы можем попытаться сделать глубокую копию более поздних частей ggplot объекта. Это должно «отключить» их. Вот такая вспомогательная функция

 duplicate.ggplot<-function(x) {
    require(proto)
    r<-x
    r$layers <- lapply(r$layers, function(x) {
        as.proto(as.list(x), parent=x)
    })
    r
}
 

так что, если мы побежим

 df = data.frame(cbind(x=1:10, y=1:10))
a = ggplot(df, aes(x=x, y=y))   geom_line()
b = a
c = duplicate.ggplot(a)

a$layers[[1]]$geom_params$colour = "red"
 

тогда составьте план всех трех, мы получим

введите описание изображения здесь

что показывает, что мы можем изменить «с» независимо от «а»

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

1. Неплохо. Я размышлял о том, существует ли канонический способ выполнения задания (на уровне R, а не на уровне C), который гарантирует создание копии.

2. @joran Я не знаю способа принудительно создать глубокую копию среды в коде R; но это не значит, что ее не существует, я думаю.

3. Очень хорошее объяснение. Большое спасибо!

4. Это больше не работает. выдает «Ошибка при назначении(s, x[[s]], envir = envir) : недопустимый аргумент «envir»». Есть какие-нибудь идеи по исправлению?

Ответ №2:

Игнорируя специфику ggplot, есть простой трюк, чтобы сделать глубокую копию (почти) любого объекта в R:

 obj_copy <- unserialize(serialize(obj, NULL))
 

Это сериализует объект в двоичное представление, подходящее для записи на диск, а затем восстанавливает объект из этого представления. Это эквивалентно сохранению объекта в файл, а затем его повторной загрузке (т. Е. saveRDS с последующим readRDS ), только на самом деле он никогда не сохраняется в файл. Вероятно, это не самое эффективное решение, но оно должно работать практически для любого объекта, который можно сохранить в файл.

Вы можете определить deepcopy функцию, используя этот трюк:

 deepcopy <- function(p) {
    unserialize(serialize(p, NULL))
}
 

Это, по-видимому, успешно разрывает связи между связанными ggplots.

Очевидно, что это не будет работать для объектов, которые не могут быть сериализованы, таких как большие матрицы из пакета bigmemory.