対象者と前提知識

ggplot2とは

A layered grammar of graphics

“Grammar of graphics”の思想をレイヤー化してR上で実装したパッケージです。詳しくはHadley WickhamのAcademic portfolioにある論文を参照してください。このパッケージの根底にある原初の思想が書かれいるので,時間があるときに読むのをおすすめします。

ポイント

よく「ggplot2はきれいだ」とか言われますが,そこは本質的な部分ではないと(個人的には)思っています。大事なのは,「グラフィックスに関する記述が整然としている」点です。なのでその特徴をつかめば,(語彙的な要素を調べれば)広く応用できることになります。

ggplotオブジェクトの構造

全体構成

大雑把に書くと,以下のような感じです:

  • Defaults
    • Data
    • Mapping
  • Layers
    • Data
    • Mapping
    • Geom
    • Stat
    • Position
  • Scales
  • Coorddinates
  • Facet
  • Theme
  • Labels

ポイントはDefaultsです。ggplotオブジェクトはデフォルトとなるDataとMappingを用意します。これを実際に確認してみます:

library(ggplot2)
g_null <- ggplot()
class(g_null)
## [1] "gg"     "ggplot"

ggplot()はggplotオブジェクトを生成する関数です。引数を一切指定しない場合は中身が何もない空っぽのggplotオブジェクトを返します。ちなみにこれを出力してみるとこうなります:

g_null

まさに空っぽの出力がでてきますね.ではこの中に含まれる要素を確認します:

names(g_null)
## [1] "data"        "layers"      "scales"      "mapping"     "theme"      
## [6] "coordinates" "facet"       "plot_env"    "labels"

このうち,datamappingに格納されているのがデフォルトのものです。ではデフォルト設定を与えたものを作ってみます:

g_def_data <- ggplot(data = iris)
names(g_def_data)
## [1] "data"        "layers"      "scales"      "mapping"     "theme"      
## [6] "coordinates" "facet"       "plot_env"    "labels"

第一階層には変化がありません。ではそれぞれのdataの内容を確認してみます:

g_null$data %>% str()
##  list()
##  - attr(*, "class")= chr "waiver"
g_def_data$data %>% str()
## 'data.frame':    150 obs. of  5 variables:
##  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
##  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
##  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
##  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
##  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

このように,ggplot()内で指定したものは一番上の階層で保持されます。ggplotオブジェクトは、デフォルトで指定されたデータを保持します。ではこれをplotとして出力してみると、以下のようになります:

g_def_data

さっきと全く変化がありません。これは、データはあくまでデータであってグラフィックスとして表出する要素は一切ないためです。これは考えてみれば当たり前の話で、縦軸とか指定してないのにどうやってやればいいんだよって話ですんで。

では、今度はmappingをデフォルトに与えてみます:

g_def_mapping <- ggplot(mapping = aes(x = Sepal.Length, y = Sepal.Width))
g_null$mapping %>% str()
##  list()
g_def_mapping$mapping %>% str()
## List of 2
##  $ x: symbol Sepal.Length
##  $ y: symbol Sepal.Width

mappingは審美的要素を設定します。グラフにはいくつか必要な要素があり、例えばx軸に添える変数だったりy軸に添える変数だったり、あるいは凡例に渡すような項目であったり…mappingはデータと出力先との対応を指示・決定する要素となります。

これを描写すると「’Sepal.Length’がないから描けないよ」とエラーがでて描写されません。ということでdataとmappingの両方をデフォルトに与えます:

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

これでやっとggplotでよくみる形となりました。そしてこれを描写するとこうなります:

g_def

このように,Defaultへdataとmappingをセットすることにより,ベースとなるものを構築できるようになります。

Layerと継承

Layerとは

ではこれからデータをプロットしていきます。データをプロットするにはLayerを作成して上から重ねます。ということで,早速レイヤーを作成します:

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

ggplotのlayerは上述したように5つの構成要素を保持します:

data

そのlayerで取り扱うデータ。

mapping

データをどのようにマッピングするかを指定。

geom

geometry要素。幾何学的にどう描くかを指定。

stat

データ処理要素。データをどう処理して取り扱うかを指定。

position

配置・位置づけ要素。データ系列の相対的な配置・位置づけを指定。

これらは全てグラフィック要素として必須なものです。なのでlayerにはこの5つ全てを指定する必要があります。具体的に作られたlayerの内容は以下のとおりです:

lay_iris_point
## mapping: x = Sepal.Length, y = Sepal.Width 
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity
lay_iris_point %>% str()
## Classes 'LayerInstance', 'Layer', 'ggproto' <ggproto object: Class LayerInstance, Layer>
##     aes_params: list
##     compute_aesthetics: function
##     compute_geom_1: function
##     compute_geom_2: function
##     compute_position: function
##     compute_statistic: function
##     data: data.frame
##     draw_geom: function
##     finish_statistics: function
##     geom: <ggproto object: Class GeomPoint, Geom>
##         aesthetics: function
##         default_aes: uneval
##         draw_group: function
##         draw_key: function
##         draw_layer: function
##         draw_panel: function
##         extra_params: na.rm
##         handle_na: function
##         non_missing_aes: size shape colour
##         optional_aes: 
##         parameters: function
##         required_aes: x y
##         setup_data: function
##         use_defaults: function
##         super:  <ggproto object: Class Geom>
##     geom_params: list
##     inherit.aes: TRUE
##     layer_data: function
##     map_statistic: function
##     mapping: uneval
##     position: <ggproto object: Class PositionIdentity, Position>
##         compute_layer: function
##         compute_panel: function
##         required_aes: 
##         setup_data: function
##         setup_params: function
##         super:  <ggproto object: Class Position>
##     print: function
##     show.legend: NA
##     stat: <ggproto object: Class StatIdentity, Stat>
##         aesthetics: function
##         compute_group: function
##         compute_layer: function
##         compute_panel: function
##         default_aes: uneval
##         extra_params: na.rm
##         finish_layer: function
##         non_missing_aes: 
##         parameters: function
##         required_aes: 
##         retransform: TRUE
##         setup_data: function
##         setup_params: function
##         super:  <ggproto object: Class Stat>
##     stat_params: list
##     subset: NULL
##     super:  <ggproto object: Class Layer>

ではこれをggplotオブジェクトに重ねていきます:

g_null_lay_iris_point <- g_null + lay_iris_point
g_null_lay_iris_point

これでggplotで描写することができました。これはg_nullに重ねています。つまりlayerには描画するための情報・構成要素が存在するということです。それではこのオブジェクトの内部を確認します:

names(g_null_lay_iris_point)
## [1] "data"        "layers"      "scales"      "mapping"     "theme"      
## [6] "coordinates" "facet"       "plot_env"    "labels"

第一階層のラインナップには変更ありません。dataとmappingを確認してみます:

g_null_lay_iris_point$data %>% str()
##  list()
##  - attr(*, "class")= chr "waiver"
g_null_lay_iris_point$mapping %>% str()
##  list()

入ってきていません。つまりDefaultsはデフォルトなのでそのまま維持されています。ではどこに入っているかというとlayersです:

g_null_lay_iris_point$layers
## [[1]]
## mapping: x = Sepal.Length, y = Sepal.Width 
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity
g_null_lay_iris_point$layers %>% str()
## List of 1
##  $ :Classes 'LayerInstance', 'Layer', 'ggproto' <ggproto object: Class LayerInstance, Layer>
##     aes_params: list
##     compute_aesthetics: function
##     compute_geom_1: function
##     compute_geom_2: function
##     compute_position: function
##     compute_statistic: function
##     data: data.frame
##     draw_geom: function
##     finish_statistics: function
##     geom: <ggproto object: Class GeomPoint, Geom>
##         aesthetics: function
##         default_aes: uneval
##         draw_group: function
##         draw_key: function
##         draw_layer: function
##         draw_panel: function
##         extra_params: na.rm
##         handle_na: function
##         non_missing_aes: size shape colour
##         optional_aes: 
##         parameters: function
##         required_aes: x y
##         setup_data: function
##         use_defaults: function
##         super:  <ggproto object: Class Geom>
##     geom_params: list
##     inherit.aes: TRUE
##     layer_data: function
##     map_statistic: function
##     mapping: uneval
##     position: <ggproto object: Class PositionIdentity, Position>
##         compute_layer: function
##         compute_panel: function
##         required_aes: 
##         setup_data: function
##         setup_params: function
##         super:  <ggproto object: Class Position>
##     print: function
##     show.legend: NA
##     stat: <ggproto object: Class StatIdentity, Stat>
##         aesthetics: function
##         compute_group: function
##         compute_layer: function
##         compute_panel: function
##         default_aes: uneval
##         extra_params: na.rm
##         finish_layer: function
##         non_missing_aes: 
##         parameters: function
##         required_aes: 
##         retransform: TRUE
##         setup_data: function
##         setup_params: function
##         super:  <ggproto object: Class Stat>
##     stat_params: list
##     subset: NULL
##     super:  <ggproto object: Class Layer>

このように,ggplotオブジェクトに+でlayerオブジェクトを重ねると,それがまるっとlayersにリストとして格納されるようになります。ではさらにもう一枚layerを作って重ねてみましょう。まずはlayerを作成します:

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

これを空っぽのggplotに重ねてみましょう:

g_null_lay_iris_line <- g_null + lay_iris_line
g_null_lay_iris_line

では2つのlayerを重ねます:

g_null_lay_iris_point_line <- g_null + lay_iris_point + lay_iris_line
g_null_lay_iris_point_line

このように2つのレイヤーが重なって描写されました。では内部を見てみましょう:

g_null_lay_iris_point_line$layers
## [[1]]
## mapping: x = Sepal.Length, y = Sepal.Width 
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity 
## 
## [[2]]
## mapping: x = Sepal.Length, y = Sepal.Width 
## geom_line: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity

このように,listとしてそれぞれが格納されています。これがよく言われているggplotのlayerです。

geom_*stat_*,そして継承

しかしながら,ggplot2を使って描写する際にはlayer()を使うことはまずなく,geom_*あるいはstat_*を利用しているかと思います。これらは何なのでしょう。例としてgeom_point()を検証します。

geom_point()のUsageは以下のとおりです:

geom_point(mapping = NULL, data = NULL, stat = "identity",
  position = "identity", ..., na.rm = FALSE, show.legend = NA,
  inherit.aes = TRUE)

これを見ると,layerの5要素のうち4つがあります。実際に何も指定せずに関数を実行してみて内部を確認します:

geom_point_null <- geom_point()
geom_point_null
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity
geom_point_null %>% str()
## Classes 'LayerInstance', 'Layer', 'ggproto' <ggproto object: Class LayerInstance, Layer>
##     aes_params: list
##     compute_aesthetics: function
##     compute_geom_1: function
##     compute_geom_2: function
##     compute_position: function
##     compute_statistic: function
##     data: waiver
##     draw_geom: function
##     finish_statistics: function
##     geom: <ggproto object: Class GeomPoint, Geom>
##         aesthetics: function
##         default_aes: uneval
##         draw_group: function
##         draw_key: function
##         draw_layer: function
##         draw_panel: function
##         extra_params: na.rm
##         handle_na: function
##         non_missing_aes: size shape colour
##         optional_aes: 
##         parameters: function
##         required_aes: x y
##         setup_data: function
##         use_defaults: function
##         super:  <ggproto object: Class Geom>
##     geom_params: list
##     inherit.aes: TRUE
##     layer_data: function
##     map_statistic: function
##     mapping: NULL
##     position: <ggproto object: Class PositionIdentity, Position>
##         compute_layer: function
##         compute_panel: function
##         required_aes: 
##         setup_data: function
##         setup_params: function
##         super:  <ggproto object: Class Position>
##     print: function
##     show.legend: NA
##     stat: <ggproto object: Class StatIdentity, Stat>
##         aesthetics: function
##         compute_group: function
##         compute_layer: function
##         compute_panel: function
##         default_aes: uneval
##         extra_params: na.rm
##         finish_layer: function
##         non_missing_aes: 
##         parameters: function
##         required_aes: 
##         retransform: TRUE
##         setup_data: function
##         setup_params: function
##         super:  <ggproto object: Class Stat>
##     stat_params: list
##     subset: NULL
##     super:  <ggproto object: Class Layer>

このように,layerオブジェクトが出来上がっています。すわなちgeom_*stat_*はレイヤーを作成する関数です。geom_*は各幾何学的なタイプごとにいい感じのインスタンスを引き出してくれるのです。では,geom_point()を使って描画します:

g_gpoint_null <- g_null + geom_point_null
g_gpoint_null

なんと何も表示されません。でもこれは当然で,今回は空っぽのggplotに空っぽのgeom_point()を与えているからです。では,dataなどをちゃんと与えたgeom_pointを準備して描画してみます:

geom_point_iris <- geom_point(data = iris, mapping = aes(x = Sepal.Length, y = Sepal.Width),
                              stat = "identity", position = "identity")
geom_point_iris
## mapping: x = Sepal.Length, y = Sepal.Width 
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity
g_gpoint_iris <- g_null + geom_point_iris
g_gpoint_iris

これで,一般的に使われるggplot2での描画となりますね。では,以下のコードと出力を見てください:

g_def_gpoint_null <- g_def + geom_point_null
g_def_gpoint_null

さて,ここで思い出してほしいのは,重ねているlayergeom_point_nullは中身空っぽなのです。でもこのようにちゃんとlayerとして機能しています。ではこのggplotオブジェクトの中身を確認してみましょう:

names(g_def_gpoint_null)
## [1] "data"        "layers"      "scales"      "mapping"     "theme"      
## [6] "coordinates" "facet"       "plot_env"    "labels"
g_def_gpoint_null$layers
## [[1]]
## geom_point: na.rm = FALSE
## stat_identity: na.rm = FALSE
## position_identity

やはりlayerオブジェクト内は空っぽです。ではどこからdataやmappingがきているかと言うと,Defaultsです。ggplotオブジェクトは,layerに必要なdataやmappingが存在しない場合,Defaultsから継承(inherit)してきます

よって,「なぜ空っぽのgeom_point()がlayerとし