niszetの日記

細かい情報を載せていくブログ

(R) 社内勉強会でggplotのオブジェクトを見ながら説明した時の話

あるいはViewでデータ構造を視る話。応用編。

最近、社内でRを布教しています。

Rの強みは色々あると思いますが、個人的には探索的な使い方1に向いていてさらにそれが再現可能である(コードに落とせる)点が大きいかなと思っています。まさに環境ですね2。 特に可視化に関連する部分はRStudio IDEが充実していることもあって、興味を持ってもらいやすい特長の1つだと思います(まぁ、今更言うことでもないですけどね…)

ふんわりした理解で使っている人にはぜひ読んでほしい

さて、そんなわけで社内勉強会でRを布教して、基本的な文法からggplot2パッケージの使い方などを色々やってきたのですが、「geomってのがよくわからない」「+で足していくのか良く分からない」「レイヤー?レイヤーって何?」みたいな意見をいただいたので、kazutanさんの下記の資料、

ggplotのオブジェクトから眺めてみる

を使って説明をしました。このとき、RStudio IDE上でView()を使って作成されたggオブジェクトの構造を見せることで理解が深まったのではないかなと思いますので、ざっくりとご紹介します。

なお、基本的な説明の流れは🐘さんの資料の通りですので、View()で表示するとどうなるのか?を中心に書いていきます。

見ていく。

さて、順に見ていくです。

library(ggplot2)
g_null <- ggplot()
View(g_null)

f:id:niszet:20180301225644p:plain

こんな感じ。元記事にもあるように、何も設定していないすっからかんの状態では、data, layers, mapping, などなど中身がすっからかんなことが Type列から読み取れますね。

続いで、dataにみんな大好きirisデータを入れてみる例。

g_def_data <- ggplot(data = iris)
View(g_def_data)

f:id:niszet:20180301230940p:plain

こんな感じ。dataのところ、data.frameとしてirisのデータがまるまる格納されている事がよみとれますね。また、📜のアイコンをクリックすればこのdata.frameを取り出して(つまりirisデータを)Viewで視ることが出来ます。コンソール上には

View(g_def_data[["data"]])

と表示されます(そのうえで実行される=表示される)

次。mappingも指定した例

g_def <- ggplot(iris, aes(Sepal.Length, Sepal.Width))
View(g_def)

はこのように、xとyに指定した列名が(symbolとして)入っていますね。

f:id:niszet:20180301230917p:plain

レイヤー

レイヤーも当然?View()で視れます。

lay_iris_point <- layer(data = iris, mapping = aes(x = Sepal.Length, y = Sepal.Width),
                      geom = "point", stat = "identity", position = "identity")
View(lay_iris_point)

f:id:niszet:20180301231550p:plain

これはggplot()から返ってくるオブジェクトではなくlayerであることが1行目のTypeからわかりますね。そして、geomやstat、positionがどうやら設定した項目に関係するS3クラスが入っていることが確認できます。

どんどんいきましょうか。

g_null_lay_iris_point <- g_null + lay_iris_point
View(g_null_lay_iris_point)

f:id:niszet:20180301231933p:plain

空のggplot()に先に定義したlayerを+で足すと、layersの下にlayerの情報が全て格納されることが見て取れます。top階層には入らないんですね~。あくまでも「layerとして定義したものは必要な情報を完備しているのでそれ自身で完結し、layersの中収まる」ということなんですねぇ

さて、あと2つ。よく見るgeomで指定するタイプですね。 2つの違いがわかると腹にストンと落ちる感じがします。

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)

f:id:niszet:20180301232908p:plain

こちらはgeomの方に全部指定、g_nullggplot()なので一番上の階層はデフォルト(dataなどは空の状態)のまま、geomで指定したものが全てlayersの中に入っていることがわかりますね。

geom_point_null <- geom_point()
g_def_gpoint_null <- g_def + geom_point_null
View(g_def_gpoint_null)

f:id:niszet:20180301232933p:plain

で、こちらはg_defなのでggplot()にdataとmappingを指定しています。実際にその通りになっていますね。つまり、それぞれの関数に指定した引数はそれぞれの階層に収まるということなわけですね。

この場合、layers1の中にはdataが入っていないので、上の階層にあるdataを参照する、これがkazutanさんの記事にある「継承」の意味ということです。
layersにあるように、これにgeom_line()などを追加していくとここが1, 2, ...と増えていきます。それぞれどうなっていくのかを見ていくのも良いと思います。それぞれに引数を指定したら?しなかったら?で見ていくと面白いかもしれませんね。

あと、geomほげほげはlayerを返します。実際に中を見てみると

geom_point
#>function (mapping = NULL, data = NULL, stat = "identity", position = #>"identity", 
#>    ..., na.rm = FALSE, show.legend = NA, inherit.aes = TRUE) 
#>{
#>    layer(data = data, mapping = mapping, stat = stat, geom = GeomPoint, 
#>        position = position, show.legend = show.legend, inherit.aes = #>inherit.aes, 
#>        params = list(na.rm = na.rm, ...))
#>}
#><environment: namespace:ggplot2>

なので、layersの中に納まるんですね。なぜgeomを使うかと言えば、上のコードを見るとわかるようにそれぞれのgeomにはよく使う設定(statなら"identity")がデフォルト引数として指定されているので、記述を省略することが出来るのですね(layerは5要素が全部そろっていないといけないというルールはgeomではデフォルト引数を与えることで満たしている、ということ)

半年前の自分へ。

そんなわけで、ただView()で視るだけだし、元ネタkazutanさんの記事が良すぎて乗っかっただけなのですが、半年前の自分にはそれでもちょっと敷居が高かった気がする。

その理由の一つがstrで、コンソールにぶわっと階層構造が表示されるのでどれが重要でどれが重要じゃないかがちょっとわからなかったのですよね…。まぁ理解してなくても動くし…まだ早いんだよ…くらいに思っていましたが、わからなかったら聞いたほうが良いぞ!半年前の自分!

さて、しかしView()を使うと階層構造が折りたたんだ状態で表示できるのでかなり見やすくなると思います。TypeやValueに必要な情報が入っているので、構造と何が収まっているかの確認がとてもしやすい。と、思います。

元の記事が出た後にRStudio v1.1、そしてこのViewの機能が出たので、今ならこれを使って必要な情報と差分を見比べていく方法で初心者に見せていくのが良いのかなって思っています(実際にやったんだけど)

この、ggplotのオブジェクトの構造の理解は手を動かして見てみるのが一番です。まだふんわり理解しているなぁ…という方、是非元のkazutanさんの記事にあるコード、上から全部実行して、VIewで確認しながら進めて言ってほしいです。

そんな感じ。

最後ただのポエムですなぁ…。まぁいいか…

Enjoy!!


  1. インタラクティブに、と言っても良いかもしれないですね。バッチでやるためにRで書く必要はないですが、結果としてそういうコードに落としていくのはアリですよね

  2. 先日のこちらのツイート見て改めて思いましたです。