niszetの日記

アナログCMOS系雑用エンジニアが頑張る備忘録系日記

(R) ggplot2の gg, ggplot オブジェクトに格納されているlayersは名前を持っていてほしい

昨日のTokyoRの一次会でちょっと話したやつ

View()で見ていくとき、layersに入っているレイヤーオブジェクトには名前がなく、[[1]]とか[[2]]とかそっけない。そして複数のレイヤーを重ねた場合にはどれがどれかわからん…!となりそう。
じゃあレイヤーに名前つけてみるかぁという気持ちをRで書くとこんな感じかな。

# データの準備。これは例のkazutanさんの記事からの引用。
library(ggplot2)
g_null <- ggplot()
geom_point_iris <- geom_point(data = iris, mapping = aes(x = Sepal.Length, y = Sepal.Width),
                              stat = "identity", position = "identity")
g_gpoint_iris <- g_null + geom_point_iris
# 準備はここまで

# 名前を付けるのはここ。レイヤー枚数に応じて代入する文字を適当に追加してあげてください。
names(g_gpoint_iris$layers)<-c("Scatter_Plot")
View(g_gpoint_iris)

f:id:niszet:20180304201804p:plain

これでgeom_point()で追加されたlayerは"Scatter_Plot"1という名前を持ちます。コンソール上にg_gpoint_irisと書けばちゃんとプロットもされますね。
f:id:niszet:20180304202005p:plain

おまけ

まぁこれだけで終わるのもなんなので、zeallotを使ってlayer取り出そうと思ってやってみたコードがこちらです。

library(ggplot2)
library(zeallot)
library(magrittr)

# %->% の list 対応
destructure.list <- function(x){x}

# 以下はggplot側のデータの作成(さっきのと同じ)
g_null <- ggplot()
geom_point_iris <- geom_point(data = iris, mapping = aes(x = Sepal.Length, y = Sepal.Width), stat = "identity", position = "identity")
g_gpoint_iris <- g_null + geom_point_iris
# View(g_gpoint_iris)

# classをいったん消してlistにするので復元するためにclassの値を保存しておく
class_name <- class(g_gpoint_iris)
# 同様に、要素の名前も保存しておく
top_hier_names <- names(g_gpoint_iris)

# これを実行すると.GlobalEnvに9つの変数が出てくる…
g_gpoint_iris %>% unclass %->% c(data, layers, scales, mapping, theme, coordinates, facet, plot_env, labels)


# layerに名前を付ける。今回は1つだけ
names(layers)<-c("Scatter_Plot")

# listを再構成
g_gpoint_iris_ln<-list(data, layers, scales, mapping, theme, coordinates, facet, plot_env, labels)

# classを復元
class(g_gpoint_iris_ln) <- class_name

# nameも復元
names(g_gpoint_iris_ln) <- top_hier_names

# みてみる
View(g_gpoint_iris_ln)

f:id:niszet:20180304202516p:plain

と、いう感じ。ちゃんとg_gpoint_iris_lnとコンソールに書いてEnterでplotされますな。

補足

道を踏み外した感じがありますが、ちょっと色々確認したかったので…。

destructure.list <- function(x){x} はコメントにあるように %->% の list 対応。 zeallot%->%を使う場合にはclassに応じた関数を準備しておく必要がある。data.frameは内部でlistにしているだけなので、listなら引数をそのまま返せば良いだろう。
しかし、なぜ素の状態でlistの対応がないのだろう。不思議

実はdestructure.ggだとなんだかうまくいかないのですよね…(理由はよくわかっていない)。そのため、ggのオブジェクトはそのまま渡せず、今回はunclass()でclass属性を消して単なるlistとなっています。で、これをzeallotで分解して各要素に分けています。

その後layersに名前を与え、分解した要素をlistにして再構成して、class()でもともと持っていた"gg"と"ggplot"を与え、listsの要素には元のggクラスだった時の名前を与えなおしています。ggplotのオブジェクトは要素名が上記のようになっていないとプロットしてくれないらしく。namesを与える部分を省略して実行してみてください。View()で中身を見ても良いかも。

これはRStudio上の挙動なのかもしれませんが…。素のRで確認とかはしていないです。
そもそもなぜggのオブジェクトをコンソール上で叩くとプロットされるのかと言えば、実際にはggplot2:::print.ggplot(g_gpoint_iris)が呼び出されているからで2…しかしこれはREPL上の動きなので実際は… yutannihilationさんの記事をご参照ください notchained.hatenablog.com

zeallotを積極的に使っていきたみだけでやってみた感じの記事ですね。

Enjoy!!


  1. ラクダなのか蛇なのかはっきりしろよって感じの名前にしてしまった…。しかしやり直すのが面倒くさいのでこのままにしてしまうのであった。みなさんはちゃんと命名規則を守ろうね!

  2. 記事に書かれているように、REPL上の動きはprintで直接呼んだ時とは挙動が違うらしいんですが、今の私には説明できないです。