5

I'm trying to copy a ggplot object and then change some properties of the new copied object as, for instance, the colour line to red.

Assume this code:

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

Then, if I change the colour of line of variable a

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

it also changes the colour of b

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

I wish I could have two different objects a and b with different characteristics. So, in order to do this in the correct way, I would need to call the plot again for b using b = ggplot(df, aes(xy, y=z)) + geom_line(). However, at this time in the algorithm, there is no way to know the plot command ggplot(df, aes(x=x, y=y)) + geom_line()

Do you know what's wrong with this? Is ggplot objects treated in a different manner?

Thanks!

Daniel Bonetti
  • 2,306
  • 2
  • 24
  • 33
  • Well, I agree that's a little spooky, but if you look at `str(a)` you'll notice that the layers are proto objects, so that most likely explains _why_ you're seeing this behavior. – joran Sep 08 '14 at 20:28

2 Answers2

7

The issue here is that ggplot uses the proto library to mimic OO-style objects. The proto library relies on environments to collect variables for objects. Environments are passed by reference which is why you are seeing the behavior you are (and also a reason no one would probably recommend changing the properties of a layer that way).

Anyway, adapting an example from the proto documentaiton, we can try to make a deep copy of the laters of the ggplot object. This should "disconnect" them. Here's such a helper function

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

so if we run

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"

then plot all three, we get

enter image description here

which shows we can change "c" independently from "a"

MrFlick
  • 195,160
  • 17
  • 277
  • 295
  • Nice. I was pondering whether there is a canonical way to do an assignment (at the R level, not at the C level) that guarantees a copy is made. – joran Sep 08 '14 at 20:33
  • @joran I know of no way to force a deep copy of an environment in R code; but that doesn't mean it doesn't exist I guess. – MrFlick Sep 08 '14 at 20:34
  • This doesn't work anymore. gives " Error in assign(s, x[[s]], envir = envir) : invalid 'envir' argument ". Any ideas for a fix? – Jan Stanstrup Feb 28 '16 at 19:38
4

Ignoring the specifics of ggplot, there's a simple trick to make a deep copy of (almost) any object in R:

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

This serializes the object to a binary representation suitable for writing to disk and then reconstructs the object from that representation. It's equivalent to saving the object to a file and then loading it again (i.e. saveRDS followed by readRDS), only it never actually saves to a file. It's probably not the most efficient solution, but it should work for just about any object that can be saved to a file.

You can define a deepcopy function using this trick:

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

This seems to successfully break the links between related ggplots.

Obviously, this will not work for objects that cannot be serialized, such as big matrices from the bigmemory package.

Ryan C. Thompson
  • 40,856
  • 28
  • 97
  • 159