備忘ログ

チラシの裏的備忘録&メモ

Rのggplot2で地図を書いた時にいい感じに見えるようにするときのメモ

タイトルそのまま、Rのggplot2で地図を書いた時にいい感じに見えるようにするときの個人的なメモ。

例として東京都の市区町村レベルの地図を人口データで色分けしてみる。

データは東京都の国土地理院の行政区域のシェープファイルN03-20210101_13_GML.zip)と総務省の市町村別の人口及び世帯数データ000033846.xls)をダウンロードしておく。

このデータを適当にdplyrleft_join()で結合させると名前の付け方の都合で素直にくっつかないので(国土地理院の市区町村名では奥多摩町となっているところ、総務省のデータだと団体名が西多摩郡奥多摩町となってて、西多摩郡がうまくくっつかない)、国土地理院のデータで行政区域コードがtranslateKSJData()でいい塩梅に処理されているのでそこから東京都を切り取った値を作って、それと総務省の団体名をキーにしてjoinさせるとできる。

# 使うライブラリを読み込んでおく
library(dplyr)
## 
##  次のパッケージを付け加えます: 'dplyr'

##  以下のオブジェクトは 'package:stats' からマスクされています: 
## 
##      filter, lag

##  以下のオブジェクトは 'package:base' からマスクされています: 
## 
##      intersect, setdiff, setequal, union
library(ggplot2)
library(stringr)
library(readxl)
library(kokudosuuchi)
library(rmapshaper)
## Registered S3 method overwritten by 'geojsonlint':
##   method         from 
##   print.location dplyr
# 総務省の人口データを読み込む
# 1行目は読み込みたくないのでskipする
tokyo.pop <- read_xls("000033846.xls", skip = 1)
# 国土地理院のデータを読み込み、ラベル等を処理しておく
tokyo <- readKSJData("N03-20210101_13_GML.zip") %>% 
  translateKSJData()
tokyo <- tokyo[[1]]

# 国土地理院のデータは離れている地域ごと一つの行になっているので集計しておく
# 更に地図データが実用上以上に精緻なのでデータを1/100にしておく
# 更に島嶼部を含むと地図が大変に見にくくなってしまうので島嶼部を除く処理をする
tokyo <- tokyo %>% 
  ms_dissolve(field = "行政区域コード_code", copy_fields = c("都道府県名", "市区町村名", "行政区域コード")) %>% 
  ms_simplify(keep = 0.01, keep_shapes = TRUE) %>% 
  ms_filter_islands(min_area = 100000000)

tokyo <- tokyo %>% 
  mutate(行政区域コード = str_remove(行政区域コード, pattern = 都道府県名))
# 国土地理院のデータと総務省のデータを表記を調整した行政区域コードと団体名をキーにして結合させる
tokyo <- left_join(tokyo, tokyo.pop, by = c("行政区域コード" = "団体名"))

これをggplot2で地図を書いて人口でfillすると次のようになる。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =))

f:id:indenkun:20210925223108p:plain

このままだと凡例のタイトルが「計」となるので、labs()fillの凡例名を変えてやる。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  labs(fill = "人口")

f:id:indenkun:20210925223120p:plain

ところでこの地図はfillで色塗り指定した値がNAの場合、その箇所が濃いめのグレーになる(この地図だと洋上の所属未定地など)。もともとデフォルトの背景が薄めのグレーなので濃いめのグレーでもそんなに違和感がないが、例えば次のように背景を白くした時にちょっと気になる。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  labs(fill = "人口") +
  theme_void()

f:id:indenkun:20210925223130p:plain

でこのNAの箇所を薄いグレーにするにはscale_fill_continuous(na.value = "gray")fillで塗り分けたときに値がNAのときの制御ができる。ついでに、labsで制御していた凡例名をscale_fill_continuous()内でnameをつかって制御する。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  scale_fill_continuous(na.value = "gray", 
                        name = "人口") +
  theme_void()

f:id:indenkun:20210925223144p:plain

さらに凡例が科学的表記法になっているが、ちょっと見慣れないなとなったらこれもscale_fill_continuous()内で制御できる。

試しに{sacales}パッケージ({ggplot2}の依存関係でインストールされているはず)の{label_number}を使って、全部表記を通常に使われる数字の表記の形にしてみる。

tokyo%>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  geom_sf(data = ms_innerlines(tokyo), color = "gray") +
  scale_fill_continuous(labels = scales::label_number(big.mark = ""), 
                        na.value = "gray", 
                        name = "人口") +
  theme_void()

f:id:indenkun:20210925223154p:plain

ところで、市区町村の境界線も県境の境界線と同じく黒なのでちょとと変えたい、例えばNAの場合にグレーに塗るようにしているので、市町村境界線は白色にしたいと思うことがある。

ぱっと思いつく方法としては、この地図に市区町村の境界線だけを白にしたものを上に重ねるという方法と、もう一つはこの地図の境界線を白にしてその上に都道府県のみの境界線のポリゴンを黒で重ねて色を透明fill = "transparent"にするかalpha1にして完全に透かしてやるというのがある。

とりあえず前者を実施する。

市区町村の境界線、つまりポリゴンに囲まれた線だけを取り出すには{rmapshapar}パッケージのms_innerlines()を使う。

具体的には次のようにするとかける。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  geom_sf(data = ms_innerlines(tokyo), color = "white") +
  scale_fill_continuous(labels = scales::label_number(big.mark = ""), 
                        na.value = "gray", 
                        name = "人口") +
  theme_void()

f:id:indenkun:20210925223205p:plain

すべてをグレーにしたものを書いた上でその上に黒い県境を重ねる方法は次のようになる。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =), color = "white") +
  geom_sf(data = ms_dissolve(tokyo), fill = "transparent") +
  scale_fill_continuous(labels = scales::label_number(big.mark = ""), 
                        na.value = "gray", 
                        name = "人口") +
  theme_void()

f:id:indenkun:20210925223215p:plain

あとこの地図で気になるところとしては、人口が多いほうが色が薄くなる塗り分けがされているが、多くの場合は濃いほうが多いという風に思われることが多い。

これはscale_fill_continuous()lowで少ない側の色、highで大き側の色を指定するとそういう色配置でグラデーションしてくれる。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  geom_sf(data = ms_innerlines(tokyo), color = "white") +
  scale_fill_continuous(labels = scales::label_number(big.mark = ""), 
                        na.value = "gray",
                        name = "人口",  
                        low = "#56B1F7", 
                        high = "#132B43") +
  theme_void()

f:id:indenkun:20210925223226p:plain

とすると少ないほうが色が薄くなり、濃いほうが色が濃くなるようにできる。色はお好みの色で良いと思う。

ところで同じように神奈川県につて考えてみると次のようになる。

library(stringr)
Kanagawa <- readKSJData("N03-20210101_14_GML.zip") %>% 
  translateKSJData()
Kanagawa <- Kanagawa[[1]]
Kanagawa <- Kanagawa %>% 
  ms_dissolve(field = "行政区域コード_code", copy_fields = c("都道府県名", "市区町村名", "行政区域コード")) %>% 
  ms_simplify(keep = 0.01, keep_shapes = TRUE) %>% 
  ms_filter_islands(min_area = 100000000)

Kanagawa.pop <- read_xls("000033847.xls", skip = 1)
Kanagawa <- Kanagawa %>% 
  mutate(行政区域コード = str_remove(行政区域コード, pattern = 都道府県名))

Kanagawa <- left_join(Kanagawa, Kanagawa.pop, by = c("行政区域コード" = "団体名"))

Kanagawa %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  geom_sf(data = ms_innerlines(Kanagawa), color = "white") +
  scale_fill_continuous(labels = scales::label_number(big.mark = ""), 
                        na.value = "gray", 
                        low = "#56B1F7", 
                        high = "#132B43") +
  labs(fill = "人口") +
  theme_void()

f:id:indenkun:20210925223237p:plain

相模原市NAになっているのは総務省データでは相模原市で一つになっているが、国土地理院のデータでは行政区域が相模原市緑区相模原市中央区相模原市南区でわかれているので割り当てられなかったということに起因している。

こうすると凡例が東京都と異なり色から与えられる情報差異があり、東京都の地図のあとに表示されるとちょっと気になる(濃い青は神奈川県内的には人口が多い地域を示しているが、東京都くらべて……は よくわかりにくい)。scale_fill_continuous()limitsで凡例の範囲、breaksで凡例に表示される値を制御できるので

Kanagawa %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  geom_sf(data = ms_innerlines(Kanagawa), color = "white") +
  scale_fill_continuous(labels = scales::label_number(big.mark = ""), 
                        na.value = "gray", 
                        low = "#56B1F7", 
                        high = "#132B43", 
                        name = "人口", 
                        limits = c(2000, 1000000), 
                        breaks = seq(100000, 1000000, by = 250000)) +
  theme_void()

f:id:indenkun:20210925223247p:plain

で凡例の幅や表示する範囲を強制的に規定できるので、東京都も同じように設定すると同じ凡例で色の塗り分けができるので、比べてみたときに色どうしで比較しやすい。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  geom_sf(data = ms_innerlines(tokyo), color = "white") +
  scale_fill_continuous(labels = scales::label_number(big.mark = ""), 
                        na.value = "gray",
                        name = "人口",  
                        low = "#56B1F7", 
                        high = "#132B43", 
                        limits = c(2000, 1000000),
                        breaks = seq(100000, 1000000, by = 250000)) +
  theme_void()

f:id:indenkun:20210925223256p:plain

ただし、今回の人口の場合は東京都の多い方の人口に合わせているので範囲が東京都のものすごく多い方に合わせられるため、神奈川県だと市区町村間での差異がみにくくなってしまっている。状況により使い分けが重要かと思う。

ちなみに地図用の{ggplot2}用のテーマが{ggthemes}パッケージにある。適用すると凡例が左下に移動し小さくなる。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  geom_sf(data = ms_innerlines(tokyo), color = "white") +
  scale_fill_continuous(labels = scales::label_number(big.mark = ""), 
                        na.value = "gray",
                        name = "人口",
                        low = "#56B1F7",
                        high = "#132B43", 
                        limits = c(2000, 1000000), 
                        breaks = seq(100000, 1000000, by = 250000)) +
  ggthemes::theme_map()

f:id:indenkun:20210925223305p:plain

ところで大きい数字は読みにくい(個人的見解)ので一般的にはコンマをつけるとよいと思うが、日本人的には{zipangu}label_kansuji_suffix()を使うと桁漢字表記になって数字表記の量が減って見やすく万以上の値だとパット見てわかる(気がする)。図なのでパット見てわかるというのは個人的には重要だと思う。

tokyo %>% 
  ggplot() +
  geom_sf(aes(fill =)) +
  geom_sf(data = ms_innerlines(tokyo), color = "white") +
  scale_fill_continuous(labels = zipangu::label_kansuji_suffix(),
                        na.value = "gray",
                        name = "人口", 
                        low = "#56B1F7",
                        high = "#132B43",
                        limits = c(2000, 1000000),
                        breaks = seq(100000, 1000000, by = 250000)) +
  theme_void()

f:id:indenkun:20210925223316p:plain