13
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

RAdvent Calendar 2021

Day 19

ggpatternでggplot2の色分けをパターンや網掛けに変えて白黒対応させる&ついでにセンター地理のグラフを再現(おまけで模擬問題も作る)

Last updated at Posted at 2021-12-19

こういうグラフがggplot2でも作れる!

center_graph_pattern.png

ggplot2を使えばカラフルなプロットが可能!でもグラフを白黒対応させたいときには困ることも…

ggplot2などでプロットできるカラフルなグラフは、色分けによってデータの違いがとても見やすいです。このようなグラフは、電子的に配布されてディスプレイ上で閲覧される資料などに非常に適しています。

一方で、資料がモノクロで印刷されることを想定したり、より多くの人にとって見やすいグラフを目指したりすると、色に頼らずにグラフの内容を表現する必要が出てきます。しかし、色分けをしたグラフをそのまま白黒にすると見にくいですし、濃淡での表現にも限界があります。

そこで線のタイプや点の形、パターンや網掛けによる描き分けで違いを表現できると嬉しいのですが、後者の「パターンや網掛けによる描き分け」についてはggplotのみで行うことが現時点では難しいです。

出来ればどんなグラフでもRとggplotで作れるようにしたい!
そこでここでは、ggplotによる領域の塗り分け方法を拡張してくれる**ggpattern**パッケージを利用することで後者の問題を解決し、きちんと白黒対応したグラフをggplotで作ってみたいと思います。

ggpatternパッケージとは?

logo.png

ggpatternパッケージのページにはパッケージについて以下のような説明があります。

Feature Summary

  • Custom versions of (almost) all the geoms from ggplot2 which have a region which can be filled.
  • A suite of aesthetics for controlling the pattern appearance (e.g. pattern_alpha)
  • The ability to include user-defined patterns

特色のまとめ(拙訳)

  • ggplot2由来の、塗りつぶしされる領域を持つ(ほとんど)すべてのgeomについてのカスタムバージョン
  • パターンの見た目をコントロールするためのaestheticsの一式
  • ユーザーが定義したパターンを取り入れる能力

from https://coolbutuseless.github.io/package/ggpattern/

つまりこのパッケージは**「領域を塗りつぶす」系geom_*関数の「塗りつぶし方」を拡張するパッケージ**です。
そしてこの「塗りつぶし方」はパターンからグラデーションから画像を使うものにいたるまで多岐にわたり、それらをggplot2のgeom_*関数と対応する形で整備されたgeom_*_pattern関数、およびパターンを調整するためのscale_pattern_*関数でコントロールすることができます。

※ただし、geom_sf_patternに関しては自分の環境では上手くいかなかったので、まだ地図の描画は難しいかもしれません(自分の環境のせいなのかもしれないので、もしかしたら使えるかも?)

そのggpatternパッケージの機能のごく一部を使って白黒対応の図を作ろうというのがこの記事の目論見です。

パッケージの使い方

パッケージのインストール

パッケージはGitHubからインストールできます。
※エラーが出る場合もあるらしいので注意。参考:https://github.com/coolbutuseless/ggpattern#installation

.R
remotes::install_github("coolbutuseless/ggpattern")

パッケージの読み込み

libraryで呼び出して使います。
ggplot2のgeom_*関数やscale_*関数と対応している関数を使用できるので、tidyverseと併用するのがいいと思います。

.R
library(tidyverse)
library(ggpattern)

プロット方法

それでは実際にggpatternパッケージを使って白黒対応のグラフを作りながら、プロット方法を解説していきたいと思います。
ここでは**「グラフ中の色による塗り分けをパターンでの描き分けに変える」**というのを主目的とした方法をご紹介します。
公式のクイックスタートに書いてある方法を自分なりに少しアレンジした形になります。

※注意※
場合によっては描画に多くのリソースを使う可能性があるので、ハード的な制約がある場合には注意したほうがいいかもしれません。

白黒対応させるグラフ

今回は以下のグラフの色による塗り分けをパターンでの描き分けに変更してみたいと思います。
棒グラフにおける割合の塗り分けなどは、モノクロ化する際にパターンによる描き分けが特に役立つ対象かと思います。

.R
mtcars2 <- mtcars %>% mutate(cyl2 = as.character(cyl), gear2 = as.character(gear))

test_plot_color <- mtcars2 %>% 
  ggplot() + 
  geom_bar(aes(x = gear2, fill = cyl2), stat = "count", color = "black") + 
  scale_fill_discrete(name = "Number of \ncylinders") + 
  xlab("Number of forward gears") + ylab("Count") + 
  theme_classic() + 
  theme(axis.title = element_text(size = 20), axis.text = element_text(size = 20), legend.title = element_text(size = 20), legend.text = element_text(size = 20))

ggsave(test_plot_color, file = "test_plot_color.png", height = 7, width = 7)

test_plot_color.png

それでは使い方を解説していきます

1. geom_*_pattern関数でグラフを記述する

まず目的のパターン設定を適用する前に、geom_*関数をgeom_*_pattern関数に変えます。
基本的にはgeom_*の書き方と同じでいいのですが、fillで色分けしていた部分を以下のようにpattern_*オプションに変更することでパターンによる描き分けができるようになります。
細かい部分はまだ調整していませんが、これでおおまかな様子は確認できます。

.R
test_plot_pattern1 <- mtcars2 %>% 
  ggplot() + 
  geom_bar_pattern(aes(x = gear2, 
                       # cyl2ごとにpatternを変える
                       pattern = cyl2, 
                       # cyl2ごとにspacingを変える
                       pattern_spacing = cyl2, 
                       # cyl2ごとにdensityを変える
                       pattern_density = cyl2), stat = "count", 
                   fill = "white", color = "black",
                   # patternのfillとcolorの設定をする
                   pattern_fill = "gray40", pattern_color = NA) + 
  xlab("Number of forward gears") + ylab("Count") + 
  theme_classic() + 
  theme(axis.title = element_text(size = 20), axis.text = element_text(size = 20), legend.title = element_text(size = 20), legend.text = element_text(size = 20))

ggsave(test_plot_pattern1, file = "test_plot_pattern1.png", height = 7, width = 7)

するとこのように、色による塗り分けがパターンによる描き分けに変更されます(凡例名は一時的に変数名になっています)。
test_plot_pattern1.png

2. 使いたいパターンの設定を見つける

geom_*_pattern関数内では、
pattern:描画に使うパターン("stripe", "crosshatch", "circle")
pattern_density:パターンの描画密度(基本は[0, 1]の値ですがそれ以上の値も可能。デフォルトは0.2)
pattern_spacing:パターン間の間隔(デフォルトは0.05)
pattern_fill:パターンの塗りつぶし色
pattern_color:パターンの枠線色
etc...
を設定することができ、aes内でこれらの引数に変数を指定すればデータに応じてパターンを変化させることができます。
しかしこれだけだと実際のパターンの見当がつきません。また描画しながら調整するのも面倒なので、事前に便宜的な見本を作って、それを見て求めるパターンに対応する設定を把握しておこうと思います。
(以下のやり方は公式ページの例を参考にしました)

まず見本を作るために参照用のデータフレーム(コード中ではtibble)を作ります。

.R
pattern_preview_tile <- tibble(
  x = rep(1:6, each = 4), 
  y = rep(1:4, times = 6), 
  pattern = rep(c("stripe", "stripe", "crosshatch", "crosshatch", "circle", "circle"), each = 4), 
  density = rep((1:4) * 0.1, times = 6), 
  spacing = rep(c(0.05, 0.01, 0.05, 0.01, 0.05, 0.01), each = 4)
)

そして以下のコードで見本をプロットします。

.R
pattern_catalog1 <- ggplot(data = pattern_preview_tile) + 
  geom_tile_pattern(aes(x = x, 
                        y = y, 
                        pattern = pattern, 
                        pattern_density = density, 
                        pattern_spacing = spacing), 
                    fill = "white", color = "black", 
                    pattern_fill = "gray40", pattern_color = NA) + 
  geom_label(aes(x = x, y = y, label = paste0("pattern = '", pattern, "'\ndensity = ", density, "\nspace = ", spacing))) + 
  scale_pattern_identity() + 
  scale_pattern_density_identity() + 
  scale_pattern_spacing_identity() + 
  annotate("label", x = 1.8, y = 4.4, label = "fill = 'white', color = 'black', pattern_fill = 'gray40', pattern_color = NA") + 
  theme_void()

ggsave(pattern_catalog1, file = "pattern_catalog1.png", height = 7, width = 14)

そうするとこんな感じで各設定に対応するパターンを確認することができます。
pattern_catalog1.png

fillpattern_fillの設定によっても見た目が変わるので、そこの設定を変えてもう一つ作ってみます。

.R
pattern_catalog2 <- ggplot(data = pattern_preview_tile) + 
  geom_tile_pattern(aes(x = x, 
                        y = y, 
                        pattern = pattern, 
                        pattern_density = density, 
                        pattern_spacing = spacing), 
                    fill = "gray40", color = "black", 
                    pattern_fill = "white", pattern_color = NA) + 
  geom_label(aes(x = x, y = y, label = paste0("pattern = '", pattern, "'\ndensity = ", density, "\nspace = ", spacing))) + 
  scale_pattern_identity() + 
  scale_pattern_density_identity() + 
  scale_pattern_spacing_identity() + 
  annotate("label", x = 1.8, y = 4.4, label = "fill = 'gray40', color = 'black', pattern_fill = 'white', pattern_color = NA") + 
  theme_void()

ggsave(pattern_catalog2, file = "pattern_catalog2.png", height = 7, width = 14)

するとこんな感じ
pattern_catalog2.png

facet_*を使った場合、ggsaveの設定を変えた場合などには、全体のサイズが変わって出力時の見た目も変わる可能性があるので、適宜数値を調整して確認するといいと思います(おそらくpattern_densityの値が絶対値で、pattern_spacingの値がプロット全体のサイズに対する相対値で与えられているため)。
また部分的にコードを変えれば別の色合いのパターンだったり斜線の角度を変えたパターンだったりを作ることができます。

3. scale_pattern_*関数でパターン設定を与える

最後に、1.で作成したグラフに対して2.の見本で確認した設定を適用することで、目的のパターンによる描き分けを行うことができます。
設定を適用するには1.で見たパターン設定の数値をscale_pattern_*関数に与えるというのが一つの方法です。ここではscale_pattern_*_manual関数のvaluesに設定を与えることで最終的に望むパターンが出るようにしています。
scale_pattern_*関数内のnameはすべて同じものに設定しておかないと凡例が分離してしまうので注意してください。

良さそうな設定を選んで適用してみると以下のようになります。

.R
test_plot_pattern2 <- mtcars2 %>% 
  ggplot() + 
  geom_bar_pattern(aes(x = gear2, 
                       # cyl2ごとにpatternを変える
                       pattern = cyl2, 
                       # cyl2ごとにdensityを変える
                       pattern_density = cyl2, 
                       # cyl2ごとにspacingを変える
                       pattern_spacing = cyl2), stat = "count", 
                   fill = "white", color = "black",
                   # patternのfillとcolorの設定をする
                   pattern_fill = "gray40", pattern_color = NA) + 
  xlab("Number of forward gears") + ylab("Count") + 
  # それぞれのcyl2の値に対してpatternを設定する
  scale_pattern_manual(name = "Number of \ncylinders", values = c("stripe", "crosshatch", "circle")) + 
  # それぞれのcyl2の値に対してdensityを設定する
  scale_pattern_density_manual(name = "Number of \ncylinders", values = c(0.4, 0.1, 0.4)) + 
  # それぞれのcyl2の値に対してspacingを設定する
  scale_pattern_spacing_manual(name = "Number of \ncylinders", values = c(0.01, 0.01, 0.05)) + 
  theme_classic() + 
  theme(axis.title = element_text(size = 20), axis.text = element_text(size = 20), legend.title = element_text(size = 20), legend.text = element_text(size = 20))

ggsave(test_plot_pattern2, file = "test_plot_pattern2.png", height = 7, width = 7)

test_plot_pattern2.png

いい感じにパターンによって描き分けされた白黒対応の図を作ることができました!
もし必要であればpattern_fillpattern_colorなども同様に変更することでいろいろなパターンで描き分けを行うことができます。

ちなみに箱ひげ図で試してみると以下のようになります。

.R
iris2 <- iris %>% mutate(Sepal.Width_hl = case_when(Sepal.Width > 3.0 ~ "3.0 <", TRUE ~ "<= 3.0"))

test_plot_pattern3 <- iris2 %>% ggplot() + 
  geom_boxplot_pattern(aes(x = Sepal.Width_hl, 
                           y = Sepal.Length, 
                       pattern = Species, 
                       pattern_density = Species, 
                       pattern_spacing = Species), 
                       fill = "white", color = "black", 
                       pattern_fill = "gray40", pattern_color = NA) + 
  xlab("Sepal Width category") + ylab("Sepal Length") + 
  scale_pattern_manual(name = "Species", values = c("stripe", "crosshatch", "circle")) + 
  scale_pattern_density_manual(name = "Species", values = c(0.4, 0.1, 0.4)) + 
  scale_pattern_spacing_manual(name = "Species", values = c(0.01, 0.01, 0.05)) + 
  theme_classic() + 
  theme(axis.title = element_text(size = 20), axis.text = element_text(size = 20), legend.title = element_text(size = 20), legend.text = element_text(size = 20))

ggsave(test_plot_pattern3, file = "test_plot_pattern3.png", height = 7, width = 7)

test_plot_pattern3.png

応用:センター地理に出てきたグラフを再現してみる

上記のようにggplotで白黒対応のグラフを作れるようになったので、実際に近い例でプロットを試してみたいと思います。
白黒で印刷されたグラフの例を考えたとき、自分が最初に思いついたのは試験問題などに出てくるグラフでした。たいていの場合では試験問題はカラー印刷されないため、問題中のグラフはパターンや網掛けでデータの描き分けがなされています。
「今回の方法を使えば試験問題に出てくるグラフをggplotで再現できるのでは?」と思ったので、上記の方法を使って問題の中に出てくるグラフを再現してみました。

再現する問題

なんとなく見つけたセンター試験(当時)の「平成31年度本試験 地理B」第2問問1に出てきた世界の食糧生産量に関するグラフを再現してみたいと思います。
1990年と2016年における、各農作物の世界全体の生産量に占める各地域の生産量の割合の比率から、それぞれのグラフが表している農作物を当てる問題です。
※グラフはリンクから見ることができます。

使っているデータは何か

グラフは1990年と2016年におけるオリーブ、オレンジ類、コーヒー、トウモロコシの地域別生産割合を表しており、問題の下には「FAOSTATより作成」の一文があります。
FAOSTATはFAO(国際連合食糧農業機関)が公開している世界の食料と農業に関するデータベースです。
この中の「Crops and livestock products」(諸作物と家畜の生産)というデータがそれっぽそうだったのでそれを使うことにしました。
項目は以下のようなものを選択しています(全データをダウンロードして加工した方がいいかも?)。
FAOSTAT_setting.png

データの準備

CSVをダウンロードしたら以下のコードで整形しておきます。
農作物名(Item)は日本語の選択肢と一致しそうなものをそれぞれ一つずつ選択しています。

.R
# パッケージのロード
library(tidyverse)
library(ggpattern)
library(extrafont) # 見た目調整のため


# データの読み込み(適宜自分の環境で変更)
faostat1 <- read_csv("hogehoge.csv")


# 日本語名との対応
name_change <- tibble(
  ENG = c("Asia", "Africa", "Northern America", "South America", "Europe", "Oceania"), 
  JP = c("アジア", "アフリカ", "北アメリカ", "南アメリカ", "ヨーロッパ", "オセアニア")
)
  
# データ整形
faostat1 %>% 
  # 「アメリカ全体」を除外
  filter(Area != "Americas") %>% 
  # (問題文に沿って)中央アメリカとカリブ海を北アメリカに統合
  mutate(Area2 = case_when(Area %in% c("Central America", "Caribbean") ~ "Northern America", TRUE ~ Area)) %>% 
  # 日本語名をjoin
  left_join(name_change, by = c("Area2" = "ENG")) %>% 
  # Area2の順番を再設定、年ラベルを作成
  mutate(Area2 = factor(JP, name_change$JP), year_label = paste0(as.character(Year), "年")) %>% 
  # 対象作物を選択
  filter(Item %in% c("Coffee, green", "Maize", "Olives", "Oranges")) %>% 
  # Area、Item、year_labelごとに合計量を集計
  group_by(Area2, Item, year_label) %>% summarise(sum_value = sum(Value, na.rm = TRUE)) -> faostat2

色分けでの描画

パターンでの描画を行う前に、通常の色分けによるプロットでグラフを作って大まかな見た目の設定を調整しておきます。

.R
theme_new <- theme(
  axis.title = element_blank(), 
  axis.text.x = element_text(size = 15, family = "Yu Mincho Demibold"), 
  axis.text.y = element_text(size = 15, family = "Yu Mincho Demibold"), 
  axis.ticks.y = element_blank(), 
  legend.key = element_rect(color = "black"), 
  legend.key.width = unit(2, "cm"), 
  legend.position = "bottom", 
  panel.background = element_rect(color = "black", fill = NA), 
  panel.border = element_rect(color = "black", fill = NA), 
  strip.background = element_blank(), 
  strip.text = element_text(size = 15, family = "Yu Mincho Demibold")
)


center_graph_color <- ggplot(faostat2) + 
  geom_bar(aes(x = year_label, 
               y = sum_value, 
               fill = Area2), 
           color = "black", 
           stat = "identity", position = "fill") + 
  scale_x_discrete(limits = rev) +
  scale_y_reverse(breaks = seq(0, 1.0, by = 0.2), labels = c("100 %", rev(seq(0, 80, by = 20)))) + 
  scale_fill_discrete(name = "", guide = guide_legend(byrow = TRUE)) + 
  coord_flip() +  
  facet_wrap(~ Item, ncol = 1) + 
  labs(caption = "Data from FAOSTAT(https://www.fao.org/faostat/en/#home); Image created by @ocean_f") + 
  theme_new

ggsave(center_graph_color, file = "center_graph_color.png", height = 7, width = 7)

center_graph_color.png

パターンでの描画

大まかな調整が終わったらgeom_bargeom_bar_patternに変更してパターンの設定を調整していきます。
パターン設定については、facetで少しサイズが変わってしまうようなので、先ほどの見本とは少し変えた値を使っています。

.R
center_graph_pattern <- ggplot(faostat2) + 
  geom_bar_pattern(aes(x = year_label, 
                       y = sum_value, 
                       fill = Area2, 
                       pattern = Area2, 
                       pattern_spacing = Area2, 
                       pattern_density = Area2), 
                   color = "black", 
                   pattern_fill = "gray45", 
                   pattern_color = "gray45", 
                   stat = "identity", position = "fill") +  
  scale_x_discrete(limits = rev) +
  scale_y_reverse(breaks = seq(0, 1.0, by = 0.2), labels = c("100 %", rev(seq(0, 80, by = 20)))) + 
  scale_fill_manual(name = "", values = c("gray45", "white", "gray75", "white", "white", "white"), guide = guide_legend(byrow = TRUE)) + 
  scale_pattern_manual(name = "", values = c("none", "stripe", "none", "circle", "crosshatch", "none"), guide = guide_legend(byrow = TRUE)) + 
  scale_pattern_spacing_manual(name = "", values = c(NA, 0.05, NA, 0.05, 0.05, NA), guide = guide_legend(byrow = TRUE)) + 
  scale_pattern_density_manual(name = "", values = c(NA, 0.1, NA, 0.04, 0.1, NA), guide = guide_legend(byrow = TRUE)) + 
  coord_flip() +  
  facet_wrap(~ Item, ncol = 1) + 
  labs(caption = "Data from FAOSTAT(https://www.fao.org/faostat/en/#home); Image created by @ocean_f") + 
  theme_new

ggsave(center_graph_pattern, file = "center_graph_pattern.png", height = 7, width = 7)

center_graph_pattern.png

出来ました!かなりいい感じに再現できているのではと思います。
問題がちゃんとデータから再現できるのはとてもいいですね!

おまけ:模擬問題を作る

ちなみに今回のコードを応用すると、以下のように模擬問題を作ることもできます。
それぞれが**"Bananas""Cocoa, beans""Lemons and limes""Rice, paddy""Sesame seed"**のどれかのグラフになっています。

コードは以下(クリックで展開)
.R
faostat1 %>% 
  filter(Area != "Americas") %>% 
  mutate(Area2 = case_when(Area %in% c("Central America", "Caribbean") ~ "Northern America", TRUE ~ Area)) %>% 
  left_join(name_change, by = c("Area2" = "ENG")) %>% 
  mutate(Area2 = factor(JP, name_change$JP), year_label = paste0(as.character(Year), "年")) %>% 
  filter(Item %in% c("Sesame seed", "Cocoa, beans", "Rice, paddy", "Bananas", "Lemons and limes")) %>% 
  mutate(Item = factor(Item, levels = c("Sesame seed", "Cocoa, beans", "Rice, paddy", "Bananas", "Lemons and limes"))) %>% 
  group_by(Area2, Item, year_label) %>% summarise(sum_value = sum(Value, na.rm = TRUE)) -> faostat_quiz


center_graph_pattern_quiz <- ggplot(faostat_quiz) + 
  geom_bar_pattern(aes(x = year_label, 
                       y = sum_value, 
                       fill = Area2, 
                       pattern = Area2, 
                       pattern_spacing = Area2, 
                       pattern_density = Area2), 
                   color = "black", 
                   pattern_fill = "gray45", 
                   pattern_color = "gray45", 
                   stat = "identity", position = "fill") +  
  scale_x_discrete(limits = rev) +
  scale_y_reverse(breaks = seq(0, 1.0, by = 0.2), labels = c("100 %", rev(seq(0, 80, by = 20)))) + 
  scale_fill_manual(name = "", values = c("gray45", "white", "gray75", "white", "white", "white"), guide = guide_legend(byrow = TRUE)) + 
  scale_pattern_manual(name = "", values = c("none", "stripe", "none", "circle", "crosshatch", "none"), guide = guide_legend(byrow = TRUE)) + 
  scale_pattern_spacing_manual(name = "", values = c(NA, 0.05, NA, 0.05, 0.05, NA), guide = guide_legend(byrow = TRUE)) + 
  scale_pattern_density_manual(name = "", values = c(NA, 0.1, NA, 0.04, 0.1, NA), guide = guide_legend(byrow = TRUE)) + 
  coord_flip() +  
  facet_wrap(~ Item, ncol = 1) + 
  labs(caption = "Data from FAOSTAT(https://www.fao.org/faostat/en/#home); Image created by @ocean_f") + 
  theme_new + 
  # theme設定を一部上書き変更
  theme(strip.text = element_text(color = "white", size = 15, family = "Yu Mincho Demibold"))


ggsave(center_graph_pattern_quiz, file = "center_graph_pattern_quiz.png", height = 7, width = 7)
![center_graph_pattern_quiz.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535720/7072fbaf-64ba-2abd-2b7e-ad4aeb8ceabb.png)

答えはコードを見るとわかるかも…?
これからの時代、公開されているデータが元になっているような問題については、こんなふうに自分で模擬問題を作れるようになるかもしれません。選択肢の設定をランダムにすれば、自分でも使える問題が自動で生成できるかも?

ggplot2でも白黒対応の図を作れる!

ここまで見てきたようにggpatternパッケージを使えば、今まで色分けに頼っていた部分もパターンでの描き分けに変更し、白黒対応でプロットすることができるようになります。また今回はモノクロな色合いになるような設定を用いましたが、カラフルな色分けをサポートするような使い方ももちろんできます。

いままでRが対応していなかった部分にも対応していくことで、さらに多くの状況で再現性が高く、明示的なグラフ作成が可能になると思います!

13
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?