“Grammar of graphics”の思想をレイヤー化してR上で実装したパッケージです。詳しくはHadley WickhamのAcademic portfolioにある論文を参照してください。このパッケージの根底にある原初の思想が書かれいるので,時間があるときに読むのをおすすめします。
よく「ggplot2はきれいだ」とか言われますが,そこは本質的な部分ではないと(個人的には)思っています。大事なのは,「グラフィックスに関する記述が整然としている」点です。なのでその特徴をつかめば,(語彙的な要素を調べれば)広く応用できることになります。
大雑把に書くと,以下のような感じです:
ポイントは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"
このうち,data
とmapping
に格納されているのがデフォルトのものです。ではデフォルト設定を与えたものを作ってみます:
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を作成して上から重ねます。ということで,早速レイヤーを作成します:
lay_iris_point <- layer(data = iris, mapping = aes(x = Sepal.Length, y = Sepal.Width),
geom = "point", stat = "identity", position = "identity")
ggplotのlayerは上述したように5つの構成要素を保持します:
そのlayerで取り扱うデータ。
データをどのようにマッピングするかを指定。
geometry要素。幾何学的にどう描くかを指定。
データ処理要素。データをどう処理して取り扱うかを指定。
配置・位置づけ要素。データ系列の相対的な配置・位置づけを指定。
これらは全てグラフィック要素として必須なものです。なので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として機能するか」は,
ためです。なお具体的にはggplot2:::GeomPoint
から持ってきています。
なおstat_*
は,この話でgeomとstatが入れ替わっただけとなります。
省略します。でもここまでの内容が理解できればそう難しくない話です。