環境、というかggplot2のレイヤーをコピーすることを考えます。
まず、1つレイヤーをつくります。
library(ggplot2) d <- data.frame( x = 1:4, y = 1:4 ) l1 <- geom_point(d = d, aes(x, y))
このレイヤーの中身をいろいろ表示してみましょう。
l1 #> mapping: x = ~x, y = ~y #> geom_point: na.rm = FALSE #> stat_identity: na.rm = FALSE #> position_identity names(l1) #> [1] "mapping" "geom_params" "show.legend" "stat_params" "stat" #> [6] "inherit.aes" "geom" "position" "super" "data" #> [11] "aes_params" l1$data #> x y #> 1 1 1 #> 2 2 2 #> 3 3 3 #> 4 4 4
data
という項目に指定したデータが入っているのがわかりました。
これはggplot()
と足し合わせれば当然グラフを描けます。
ggplot() + l1
次に、このレイヤーをコピーして、ちょっとずらした位置に赤い点をかぶせることを考えてみましょう。
l2
という変数にl1
を代入し、このdata
のx
列に2をかけてみます。
l2 <- l1 l2$data #> x y #> 1 1 1 #> 2 2 2 #> 3 3 3 #> 4 4 4 l2$data$x <- l2$data$x * 2 # 詳しい説明は省きますが、こうすると赤くなります l2$aes_params$colour <- "red"
では、これを足し合わせてみましょう。
ggplot() + l1 + l2
あれ? なぜか赤い点しかなくなってしまっています。
l1
の黒い点はどこに消えたのでしょう。
実は、l1
のデータはl2
のデータとまったく同じになっているので、赤い点の後ろに隠れてしまっているのです。
l1$data #> x y #> 1 2 1 #> 2 4 2 #> 3 6 3 #> 4 8 4
変更を加えたのはl2
のdata
に対してなのに、なぜl1
のdata
まで変更されてしまっているのでしょうか。
これは、ggplot2のレイヤーが環境だからです。
print.default()
でむりやり表示してみるとわかりますが、l1
とl2
は同じ環境を指しています。
なので、環境の中のオブジェクトに変更を加えるとどちらにも影響するのです。
print.default(l1) #> <environment: 0x000000001f924720> #> attr(,"class") #> [1] "LayerInstance" "Layer" "ggproto" "gg" print.default(l2) #> <environment: 0x000000001f924720> #> attr(,"class") #> [1] "LayerInstance" "Layer" "ggproto" "gg"
ではどうすればいいかというと、古典的には、as.list()
で環境をリストにし、list2env()
でそのリストから新しい環境をつくる、という手があるみたいです。
rlang::env_clone()
が、そのアイディアのC実装版みたいです(コード)。
これを使ってもう一度やってみると、次のようになります。
l3 <- geom_point(d = d, aes(x, y)) l4 <- rlang::env_clone(l3) l4$data$x <- l4$data$x * 2 l4$aes_params$colour <- "red" # このままだとただの環境なので、クラスを合わせる必要がある l4 #> <environment: 0x000000001e1b7910> # クラスをあわせる class(l4) <- class(l3) ggplot() + l3 + l4
今度はうまくいきました。
ちなみに、ggprotoではなくR6にはちゃんとclone()
というメソッドが実装されています。
コードを見るとまあまあ複雑そうでした。
まあディープコピーをどうする、であるとかを考えるといろいろありそうです。ちゃんとやる必要があるなら、もうちょっとちゃんと考えましょう、ということで。