[小ネタ] R でコロプレス図 (色分け地図) をなるべく簡単に描く
概要
R-wakalang に投稿された質問を元にした小ネタ. タイトルの通り sf
と ggplot2
パッケージを使ったコロプレス図 (色分け地図) の作成方法を紹介する*1. あまり R に習熟していない人向けにごく簡単な例だけを紹介する.
関連する話を取り上げているページはいくつかあるが, おまけが多すぎたりしてエッセンシャルな部分が分かりにくと感じた. 自分も昔似たような話題でいくつか描いたが, 見返すとやはり冗長だったり, 方法が古くなりすぎたりしている.
[R] [OpenStreatMap] 東京の道路データをグラフに要約する - ill-identified diary
[R] Rで学ぶ都知事選のデータ可視化【地理データ編】 - ill-identified diary
2020/1/13追記: 完全に失念していたが, 人によっては Shiny なんかを使ってインタラクティブなWebアプリケーション上でコロプレス図を作りたいかもしれない. それは leafletパッケージを併用するなどして実現できるが, 今回紹介するのは画像ファイルを作成する方法のみである.
本題
まずは, おそらく殆どの環境でコピペするだけで実行できるコードを紹介する. もちろん最初の1行はインストール済みなら実行しなくて良い.
install.packages(c("tidyverse", "sf", "NipponMap")) require(tidyverse) require(sf) map <- read_sf(system.file("shapes/jpn.shp", package = "NipponMap")[1], crs = "+proj=longlat +datum=WGS84") ggplot(map, aes(fill = population)) + geom_sf() + labs(title = "都道府県別の人口")
コロプレス図を作成する最初のハードルは地図データを入手することになる. 国土数値情報には多くの地図データが用意されているが, どれをダウンロードすればいいのか分からない, とかなる. そこで今回は NipponMap
パッケージに含まれている都道府県境の地図データを利用させてもらう. これは海岸線などをいくらかデフォルメした地図であるが, コロプレス図は地図としての正確さは要求されないので問題ないだろう. この地図ファイルが NipponMap
のインストールディレクトリに入っているので, system.file()
でファイルパスを特定し . reaf_sf()
で読み込んでいる. crs = ...
というのは地図データの測地系を指定している. これも地図の正確さの問題なのでそこまで重要ではないが, 緯度経度の線がずれていると少し見栄えが悪いだろう. 一応設定しておく. ここで, map
の中身を確認する.
> map Simple feature collection with 47 features and 5 fields geometry type: POLYGON dimension: XY bbox: xmin: 127.6461 ymin: 26.0709 xmax: 148.8678 ymax: 45.5331 CRS: +proj=longlat +datum=WGS84 # A tibble: 47 x 6 SP_ID jiscode name population region geometry * <chr> <chr> <chr> <dbl> <chr> <POLYGON [°]> 1 1 01 Hokkaido 5506419 Hokkaido ((139.7707 42.3018, 139.8711 42.6623, 140.1895 42.8243, 140.3062 42.7741, 140.5259 42.988... 2 2 02 Aomori 1373339 Tohoku ((140.8727 40.48187, 140.6595 40.4018, 140.3903 40.4843, 140.0229 40.4172, 139.8692 40.58... 3 3 03 Iwate 1330147 Tohoku ((140.7862 39.85982, 140.8199 39.86421, 140.8813 39.87221, 140.8819 39.87228, 140.8999 40... 4 4 04 Miyagi 2348165 Tohoku ((140.2802 38.01415, 140.2802 38.01417, 140.2805 38.02494, 140.2804 38.02509, 140.2806 38... 5 5 05 Akita 1085997 Tohoku ((140.7895 39.86026, 140.8253 39.6481, 140.6565 39.3879, 140.8112 39.1752, 140.7777 38.95... 6 6 06 Yamagata 1168924 Tohoku ((140.2802 38.01415, 140.2799 37.97209, 140.2801 37.97209, 140.2795 37.7786, 140.2785 37.... 7 7 07 Fukushima 2029064 Tohoku ((140.2799 37.9721, 140.2799 37.97209, 140.2802 37.9721, 140.4107 37.9707, 140.6912 37.88... 8 8 08 Ibaraki 2969770 Kanto ((139.7322 36.08471, 139.7058 36.13276, 139.6663 36.20532, 139.6811 36.19859, 139.9763 36... 9 9 09 Tochigi 2007683 Kanto ((139.4263 36.33584, 139.3703 36.3661, 139.4858 36.575, 139.3311 36.6303, 139.3593 36.874... 10 10 10 Gunma 2008068 Kanto ((139.3836 36.35887, 139.6541 36.213, 139.6508 36.21342, 139.6665 36.20488, 139.6667 36.2... # … with 37 more rows
sf
オブジェクトはデータフレームのように扱うことができる. これには SP_ID
, jiscode
, name
, population
, region
, geometry
, という変数が含まれている. 最後の geometry
が点や線や多角形といった地図上の図形を表しており, 他の変数はそれに対応する属性と見ることができる. よって, 例えば都道府県別の統計データをよそから持ってきてコロプレス図にしたいなら dplyr
を使って都道府県名や自治体コードで結合すればいい*2. あるいは地図のほうをクロップしたりもできる (が, 今回は紹介しない)
そしてようやくグラフを描く. ggplot2
の基本的な使い方を既に知っているのなら難しい話ではない. sf
データの場合, 座標の指定は必要ない. 色分けなどの属性のみ aes()
で指定する. 今回は 「都道府県別の」「塗りつぶし」がしたい. map
には人口 population
があるため, この変数で色分けする. fill = population
とした. これでどういう軸で視覚化するかを指定したので, あとは geom_sf()
で地図を表示する. それが 図 1.

しかし, ggplot2
のデフォルトのスタイルはあまり地図の描画に向いていない気がする. 輪郭線が太すぎるとか, グラデーションが見づらいとか, 緯度経度はなくても良いとか. テーマを細かく調整するとどんどんコードが増えていくので, いくつかのテーマやカラーパターンのプリセットを提供してくれる ggthemes
パッケージを使って見た目を少し変えてみた (図 2). 細かい解説は各自調べて欲しい.
require(ggthemes) ggplot(map, aes(fill = population)) + geom_sf(size = .1) + labs(title = "都道府県別の人口") + scale_fill_continuous_tableau( name = "人口", labels = scales::number_format(suffix = " 万人", scale = 1/10000, big.mark = ",")) + theme_pander()

もちろん, fill = name
とすれば都道府県別に色分けすることができる. しかしこれは凡例が47種類表示され邪魔に感じるだろう. 凡例は theme(legend.position = none)
または guides(F)
で隠すことができる (図 3).
ggplot(map, aes(fill = name)) + geom_sf() + guides(F)

ただし, 都道府県を色分けすることがグラフに新しい情報をもたらすことはない. それは輪郭線だけで十分である*3. ggplot2
は塗り絵ではなくあくまでデータを視覚化するためのツールである (実際には私も明らかに前者の用途で使うこともあるが). よって, データの視覚化をしようとして長大な凡例ができてしまったならば, それは「見づらい」視覚化であり, 視覚化に失敗している可能性がある. 隠すよりそのような使い方が適切なのかをよく考え直したほうが良いだろう.
土地は投票しない, 人が投票する
2020/1/13 追記: そして私はおせっかいな性格なので, 大前提である色分け地図で表現する行為が適切かどうか, ということをも確認させてもらう. ここで公開されているアニメーションを見てもらおう.
Don't trust choropleth maps. Evidence from Switzerland! 😉 #RStats
— David Zumbach (@DavidZumbach) December 31, 2020
Shout-outs to @thomasp85 for {transformr}/{particles}, @hadleywickham et al. for {ggplot2}/{dplyr}, @edzerpebesma for {sf}, @politan_ch for {swissdd} and @hrbrmstr for {hrbrthemes}.
➡️ https://t.co/xQchB1XRPb pic.twitter.com/pzalnCUqQW
このアニメーションは, スイスの国民投票データをもとに, コロプレス図が誤った見方を生み出すことを警告している. アニメーションの始まりでは青色 (賛成過半数) の面積が大きく, 全体として賛成票が大きくリードしているように見える. しかしこれは都市ごとの投票率を加工したもので, バブルチャートにおきかえると全く見え方が変わってしまう. 実際, 賛成率は全体で 48.1 % であり, 僅差で反対票が多い. 今回の問題は投票率を行政区域で色分けしたことにある. 行政区域内の人口や面積は同じではないし, 広い行政区域の大半が人の住めない山岳や森林地帯ということは日本でもスイスでもよくあることだろう.
地図データのソース
頻繁に使ってるわけじゃないので全部を把握してないが, OSM のようなオープンソースデータベースや Google Map といったサービスから地図情報を取得するパッケージも多数ある. なんであれ sf
オブジェクトに変換できるものであればここで紹介したように ggplot(...) + geom_sf()
という書き方で地図を描画できる. 国土数値情報からダウンロードできるシェープファイル (.shp
) も, read_sf()
によって読み込むことができる.
また, jpndistrict
パッケージ*4は国土地理院の地図データに基づいた, 市区町村レベルの行政区域の地図データの sf
オブジェクトを取得することができる. sf
オブジェクトであるため結合するのは簡単である (ただし都道府県ごとにしか取得できない). しかし, これで取得できる地図は高精度であるがそのぶん読み込みが遅い. 都道府県別の色分け地図程度ではこのレベルの精度は要求されない事が多いだろうと思う. 島しょ部などは省略されずに残っているため, それくらい細かい地域の地図をプロットしたい場合に有用になるだろう.
*1:標準グラフィックスの方法はもうあるので書いていない. また, shiny や plotly を使って描くこともできるが, これらはベクタ画像で保存できないため私はほとんど使うことがない. 地図データを使ったグラフ作成の全般的な用例には, https://tsukubar.github.io/r-spatial-guide/spatial-data-mapping.html などを見ると良いだろう.
*2:なお, 国土数値情報の行政区域データは自治体コードとは異なるので使いづらい...
*3:四色定理は有名だが, ggplot2 は色が隣接しないように塗り分けたりはしないので, 正確には色分けには都道府県を識別する効果もないことになる
*4: lwgeom パッケージのインストールや, Linux では protobuf-compiler のインストールが必要だったりする.