cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

名前空間の衝突をconflictedパッケージで防ぐ

要約

  • パッケージを複数利用すると関数名等の衝突が発生し、意図しない振る舞いを取ることがある
  • conflictedパッケージは、こうした衝突を防ぐための機能を提供する
  • 多少の手間を惜しんでも、衝突の恐れのある関数については名前空間を指定することを勧める

f:id:u_ribo:20180929102909p:plain

続きを読む

ggplot2ベースの図から凡例 (legend) のみ得る方法あれこれ

タイトルに書いたように「凡例だけが描画したい」ことが時々あるかと思います(止むを得ずに凡例の位置が図から分断されてしまうときとか)。そんな時には、ここで紹介する方法を使うと良いです。やり方はいくつかあります。ここではgtableパッケージを使う場合の2例とlemonパッケージを使う方法の3通りを紹介します。

⚠️ ggplot2の図の描画に使われているgridシステムやこの記事で扱うgtableとの関係について知りたい人は、毎度おなじみ id:yutannihilation さんの下記の記事を読みましょう。

notchained.hatenablog.com

まずは凡例を含む図を作りましょう🎨

library(ggplot2)

p <- 
  ggplot(mtcars, aes(wt, mpg)) +
  geom_point(aes(color = factor(cyl), shape = factor(cyl))) +
  guides(color = guide_legend(title = "Number of cylinders"),
         shape = guide_legend(title = "Number of cylinders")) +
  theme(legend.position = "bottom")
  
p

f:id:u_ribo:20180913224433p:plain

📈gtableパッケージを使う場合

最初の方法では、gtableパッケージを使います。このパッケージではggplot2ベースの図を構成する要素を表形式に整理したgtableオブジェクトに対して処理を適用することが可能です。

まず先ほどggplot2で作ったオブジェクトをggplotGrob()に渡し、gtableオブジェクトを得ます。

p_gtable <- 
  ggplotGrob(p)
class(p_gtable)
## [1] "gtable" "gTree"  "grob"   "gDesc"

オブジェクトを表示してみると、グラフを構成する情報がつらつらと出力されます。ここでname列はggplot2の図のパーツというか、描画に使われている部位を指しており、マッピングが行われる部位だけでなく、背景やタイトルについて確認できます。

p_gtable
## TableGrob (14 x 9) "layout": 19 grobs
## z         cells       name                                           grob
## 1   0 ( 1-14, 1- 9) background               rect[plot.background..rect.3427]
## 2   5 ( 6- 6, 4- 4)     spacer                                 zeroGrob[NULL]
## 3   7 ( 7- 7, 4- 4)     axis-l           absoluteGrob[GRID.absoluteGrob.3388]
## 4   3 ( 8- 8, 4- 4)     spacer                                 zeroGrob[NULL]
## 5   6 ( 6- 6, 5- 5)     axis-t                                 zeroGrob[NULL]
## 6   1 ( 7- 7, 5- 5)      panel                      gTree[panel-1.gTree.3374]
## 7   9 ( 8- 8, 5- 5)     axis-b           absoluteGrob[GRID.absoluteGrob.3381]
## 8   4 ( 6- 6, 6- 6)     spacer                                 zeroGrob[NULL]
## 9   8 ( 7- 7, 6- 6)     axis-r                                 zeroGrob[NULL]
## 10  2 ( 8- 8, 6- 6)     spacer                                 zeroGrob[NULL]
## 11 10 ( 5- 5, 5- 5)     xlab-t                                 zeroGrob[NULL]
## 12 11 ( 9- 9, 5- 5)     xlab-b titleGrob[axis.title.x.bottom..titleGrob.3391]
## 13 12 ( 7- 7, 3- 3)     ylab-l   titleGrob[axis.title.y.left..titleGrob.3394]
## 14 13 ( 7- 7, 7- 7)     ylab-r                                 zeroGrob[NULL]
## 15 14 (11-11, 5- 5)  guide-box                              gtable[guide-box]
## 16 15 ( 4- 4, 5- 5)   subtitle         zeroGrob[plot.subtitle..zeroGrob.3423]
## 17 16 ( 3- 3, 5- 5)      title            zeroGrob[plot.title..zeroGrob.3422]
## 18 17 (12-12, 5- 5)    caption          zeroGrob[plot.caption..zeroGrob.3425]
## 19 18 ( 2- 2, 2- 2)        tag              zeroGrob[plot.tag..zeroGrob.3424]

凡例部位も独立しており、次の処理により抽出可能です。

p_gtable[["grobs"]][[grep("guide-box", p_gtable$layout$name)]]
## TableGrob (5 x 5) "guide-box": 2 grobs
##                                     z     cells                  name
## 99_ba71de4077ba9ca80dafe2aa749ab62d 1 (3-3,3-3)                guides
##                                     0 (2-4,2-4) legend.box.background
##                                               grob
## 99_ba71de4077ba9ca80dafe2aa749ab62d gtable[layout]
##                                     zeroGrob[NULL]

gtableにはこれらの部位を指定して抽出する関数が用意されていて、上記の処理は次のように書き換えられます。また、抽出したgtableオブジェクト(凡例のみ)は、gridパッケージの関数でグラフとして出力できます。

p_legend <- 
  gtable::gtable_filter(p_gtable, pattern = "guide-box")

grid::grid.draw(p_legend)

f:id:u_ribo:20180913224748p:plain

一方でファイルへの保存にはgtableの状態で行います。

ggsave("legend.png", p_legend)

📉lemonパッケージを使う場合🍋

いちいちggplotGrob()にして、凡例のgrobを指定して〜、という手続きが煩わしい場合、lemonパッケージのg_legend()でggplot2ベースの図から凡例部分を取り出すことができます。私はこっちの方法が好みです。

p_legend <- 
  lemon::g_legend(p)

このパッケージを使った場合も描画と保存の処理はgtableの時と同じです。

grid::grid.newpage() # 先ほど描画したオブジェクトを消します
grid::grid.draw(p_legend)

f:id:u_ribo:20180913224924p:plain

あとは煮るなり焼くなりお好みでどうぞ。 Enjoy!

市区町村ポリゴンの中から区だけを結合する

前回の記事で、市区町村に分割されているポリゴンを都道府県単位に結合する処理を紹介しました。

uribo.hatenablog.com

今日はその続編として、今度は区ごとにポリゴンが分かれている「政令指定都市」を一つの市として扱えるようにしてみようと思います。これは、例えば市町村単位のデータと市区町村のポリゴンを紐づけようとする際、つまりデータ間で政令指定都市の扱いが異なる時に役立つtipsです。 なお、ポリゴンの方が細かい、すなわち政令指定都市が「区」に分かれていて紐づけるデータの方が「市」であることを想定しています。

20180913追記✍️ ポリゴンに不備がない場合には、group_by(city) %>% summarise() のようにするだけで良いです。 id:yutannihilation さんからのツッコミ。いつもありがとう🙏

岡山県を例に区の結合処理を扱います。このデータは、city_code列に市区町村コード、city列に市区町村名を含んでいます。問題の政令指定都市には「hoge市piyo区」の形式が与えられています。

📔 この記事の中で利用したポリゴンデータは、国土地理院長の承認を得て、同院発行の数値地図(国土基本情報)電子国土基本図(地図情報)を使用し、瓜生真也が加工・編集したものです (承認番号 平28情使、第603号)

library(tidyverse) # dplyr, stringr, tidyr, purrrパッケージの関数を利用します
library(sf)
library(mapview)
library(jpndistrict)
## This package provide map data is based on the Digital Map 25000
## (Map Image) published by Geospatial Information Authority of Japan
## (Approval No.603FY2017 information usage <http://www.gsi.go.jp>)
sf_pref33 <- 
  jpn_pref(33, district = TRUE)

sf_pref33
## Simple feature collection with 30 features and 4 fields
## geometry type:  GEOMETRY
## dimension:      XY
## bbox:           xmin: 133.2667 ymin: 34.29839 xmax: 134.4132 ymax: 35.35286
## epsg (SRID):    4326
## proj4string:    +proj=longlat +datum=WGS84 +no_defs
## # A tibble: 30 x 5
##    pref_code prefecture city_code city                            geometry
##    <chr>     <chr>      <chr>     <chr>                     <GEOMETRY [°]>
##  1 33        岡山県     33101     岡山市 北区… POLYGON ((133.9098 34.948, 133.…
##  2 33        岡山県     33102     岡山市 中区… MULTIPOLYGON (((133.9747 34.601…
##  3 33        岡山県     33103     岡山市 東区… MULTIPOLYGON (((133.9958 34.613…
##  4 33        岡山県     33104     岡山市 南区… MULTIPOLYGON (((133.9109 34.643…
##  5 33        岡山県     33202     倉敷市  MULTIPOLYGON (((133.6414 34.504…
##  6 33        岡山県     33203     津山市  POLYGON ((134.0871 35.30721, 13…
##  7 33        岡山県     33204     玉野市  MULTIPOLYGON (((133.8985 34.456…
##  8 33        岡山県     33205     笠岡市  MULTIPOLYGON (((133.5929 34.373…
##  9 33        岡山県     33207     井原市  POLYGON ((133.3705 34.74212, 13…
## 10 33        岡山県     33208     総社市  POLYGON ((133.6613 34.78109, 13…
## # ... with 20 more rows

岡山県の県庁所在地である岡山市は2009年に政令指定都市へ移行し、北区、中区、東区、南区の4つの区からなります。

sf_pref33 %>% 
  filter(str_detect(city, "^岡山市")) %>% 
  mapview()

f:id:u_ribo:20180905230624p:plain

4区を一つのポリゴンに結合するには前回書いた方法を使えばできます。ただし今回は、政令指定都市でない他の市町村に関してはその形状を保持するという制約がつきます。

そんなん簡単やろー、と思いましたが結構悩みました。以下に解決策を示しますが、あまりスマートな方法とは思えないのでお助け募集です。また、市区町村コードに関しては政令指定都市の場合には原則として100番台の値から始まりますが、例外もあり、市で表記した時の扱いもまちまちだったので以降の処理では除外しています。

まずは市区町村の形状を保ったままcityの値を変えます。下記の正規表現による文字列置換を適用しました。

sf_pref33 <- 
  sf_pref33 %>% 
  mutate(city = str_remove(city, "[[:space:]].+区$"))

この処理により、「hoge市piyo区」が「hoge市」になります。その後、cityの値ごとにグループ化し、tidyr::nest()により、市町村ごとのポリゴンを格納した入れ子データフレームを作成します。

sf_pref33 <- 
  sf_pref33 %>% 
  group_by(city) %>% 
  nest()

入れ子の中には、岡山市のようにもともと複数のポリゴンを含む場合には複数行のデータフレームが格納されています。その他の市町村については一つです。この入れ子のポリゴンデータに対して、複数ポリゴンを隙間なく結合する処理を関数化したものを適用します。

city_union <- function(x) {
  x %>% 
    lwgeom::st_make_valid() %>% 
    sf::st_union(by_feature = FALSE) %>% 
    sf::st_transform(crs = 4326) %>% 
    sf::st_cast("POLYGON") %>% 
    purrr::map(
      ~ .x[1]
    ) %>% 
    sf::st_multipolygon() %>% 
    sf::st_sfc(crs = 4326)
}
sf_pref33 <- 
  sf_pref33 %>% 
  transmute(city,
            geometry = pmap(., ~ city_union(..2)) %>% 
              reduce(c)) %>% 
  st_sf()

では確認します。

sf_pref33 %>% 
  mapview()

f:id:u_ribo:20180905230727p:plain

できました!岡山市の各区の境界がなくなり、一つのポリゴンとして扱えるようになっています。また、ポリゴンの空隙もありません。綺麗なポリゴンになっています。

というわけでEnjoy!