인터랙티브 지도(Mapview)

인터랙티브 지도를 제작해보자.

저자
소속
소스코드

1 패키지

코드
if (!require("tidyverse")) install.packages("tidyverse")
if (!require("BFS")) install.packages("BFS")
if (!require("giscoR")) install.packages("giscoR")
if (!require("mapview")) install.packages("mapview")
if (!require("sf")) install.packages("sf")
if (!require("leafsync")) install.packages("leafsync")
if (!require("leaflet.extras2")) install.packages("leaflet.extras2")

library(tidyverse) # data wrangling and viz ecosystem
library(BFS) # Search and Download Data from the Swiss Statistical Office
library(giscoR) # Access Eurostat Mapping API
library(mapview) # Create Interactive Maps easily
library(sf) # mapping with Simple Feature
library(leaflet) # interactive maps
library(leafsync) # plugin for leaflet

crs_LONGLAT <- "+proj=longlat +datum=WGS84 +no_defs"

2 데이터

2.1 지도

코드
# switzerland_sf <- gisco_get_nuts(
  # country = "Switzerland", 
  # nuts_level = 3, 
  # resolution = "01",
  # cache = TRUE,
  # update_cache = TRUE)

switzerland_sf <- sf::st_read("data/gisco-services.ec.europa.eu_distribution_v2_nuts_geojson_NUTS_RG_01M_2016_4326_LEVL_3.geojson")
#> Reading layer `gisco-services.ec.europa.eu_distribution_v2_nuts_geojson_NUTS_RG_01M_2016_4326_LEVL_3' from data source `D:\tcs\map_challenge\data\gisco-services.ec.europa.eu_distribution_v2_nuts_geojson_NUTS_RG_01M_2016_4326_LEVL_3.geojson' 
#>   using driver `GeoJSON'
#> Simple feature collection with 1522 features and 10 fields
#> Geometry type: MULTIPOLYGON
#> Dimension:     XY
#> Bounding box:  xmin: -63.15119 ymin: -21.38885 xmax: 55.83578 ymax: 71.18416
#> Geodetic CRS:  WGS 84

switzerland_sf
#> Simple feature collection with 1522 features and 10 fields
#> Geometry type: MULTIPOLYGON
#> Dimension:     XY
#> Bounding box:  xmin: -63.15119 ymin: -21.38885 xmax: 55.83578 ymax: 71.18416
#> Geodetic CRS:  WGS 84
#> First 10 features:
#>       id LEVL_CODE NUTS_ID CNTR_CODE                           NAME_LATN
#> 1  HR043         3   HR043        HR         Krapinsko-zagorska županija
#> 2  AT314         3   AT314        AT                     Steyr-Kirchdorf
#> 3  AT315         3   AT315        AT                        Traunviertel
#> 4  DE600         3   DE600        DE                             Hamburg
#> 5  DE711         3   DE711        DE         Darmstadt, Kreisfreie Stadt
#> 6  DE712         3   DE712        DE Frankfurt am Main, Kreisfreie Stadt
#> 7  DE713         3   DE713        DE Offenbach am Main, Kreisfreie Stadt
#> 8  DE714         3   DE714        DE         Wiesbaden, Kreisfreie Stadt
#> 9  DE715         3   DE715        DE                         Bergstraße
#> 10 BE353         3   BE353        BE                  Arr. Philippeville
#>                              NUTS_NAME MOUNT_TYPE URBN_TYPE COAST_TYPE   FID
#> 1          Krapinsko-zagorska županija          4         3          3 HR043
#> 2                      Steyr-Kirchdorf          3         3          3 AT314
#> 3                         Traunviertel          3         3          3 AT315
#> 4                              Hamburg          4         1          1 DE600
#> 5          Darmstadt, Kreisfreie Stadt          4         1          3 DE711
#> 6  Frankfurt am Main, Kreisfreie Stadt          4         1          3 DE712
#> 7  Offenbach am Main, Kreisfreie Stadt          4         1          3 DE713
#> 8          Wiesbaden, Kreisfreie Stadt          4         1          3 DE714
#> 9                          Bergstraße          4         1          3 DE715
#> 10                  Arr. Philippeville          4         3          3 BE353
#>                          geometry
#> 1  MULTIPOLYGON (((16.25128 46...
#> 2  MULTIPOLYGON (((14.48275 48...
#> 3  MULTIPOLYGON (((13.75402 48...
#> 4  MULTIPOLYGON (((9.945376 53...
#> 5  MULTIPOLYGON (((8.725708 49...
#> 6  MULTIPOLYGON (((8.590239 50...
#> 7  MULTIPOLYGON (((8.717834 50...
#> 8  MULTIPOLYGON (((8.327649 50...
#> 9  MULTIPOLYGON (((8.93188 49....
#> 10 MULTIPOLYGON (((4.588663 50...

2.2 스위스 데이터셋

코드
# Swiss dataset: https://www.bfs.admin.ch/asset/de/px-x-1502000000_101
swiss_students <- BFS::bfs_get_data(number_bfs = "px-x-1502000000_101", language = "de", clean_names = TRUE)
#>   Downloading large query (in 8 batches):
#> 
  |                                                                            
  |                                                                      |   0%
  |                                                                            
  |=========                                                             |  12%
  |                                                                            
  |==================                                                    |  25%
  |                                                                            
  |==========================                                            |  38%
  |                                                                            
  |===================================                                   |  50%
  |                                                                            
  |============================================                          |  62%
  |                                                                            
  |====================================================                  |  75%
  |                                                                            
  |=============================================================         |  88%
  |                                                                            
  |======================================================================| 100%

swiss_students_gender <- swiss_students %>%
  pivot_wider(names_from = geschlecht, values_from = lernende) %>%
  mutate(share_woman = round(Frau/`Geschlecht - Total`*100, 1))

swiss_students_gender
#> # A tibble: 12,420 × 8
#>    bildungsstufe   schulkanton staatsangehorigkeit_…¹ jahr  `Geschlecht - Total`
#>    <chr>           <chr>       <chr>                  <chr>                <dbl>
#>  1 Bildungsstufe … Schweiz     Staatsangehörigkeit -… 1999…              1309950
#>  2 Bildungsstufe … Schweiz     Staatsangehörigkeit -… 2000…              1311661
#>  3 Bildungsstufe … Schweiz     Staatsangehörigkeit -… 2001…              1312533
#>  4 Bildungsstufe … Schweiz     Schweiz                1999…              1025029
#>  5 Bildungsstufe … Schweiz     Schweiz                2000…              1026294
#>  6 Bildungsstufe … Schweiz     Schweiz                2001…              1023831
#>  7 Bildungsstufe … Schweiz     Ausland                1999…               256356
#>  8 Bildungsstufe … Schweiz     Ausland                2000…               256191
#>  9 Bildungsstufe … Schweiz     Ausland                2001…               258098
#> 10 Bildungsstufe … Schweiz     Unbekannt              1999…                28565
#> # ℹ 12,410 more rows
#> # ℹ abbreviated name: ¹​staatsangehorigkeit_kategorie
#> # ℹ 3 more variables: Mann <dbl>, Frau <dbl>, share_woman <dbl>

2.3 지도와 데이터 결합

코드
# Preferably using NUTS-3 code if possible
swiss_student_map <- swiss_students_gender %>%
  filter(schulkanton != "Schweiz") %>%
  mutate(schulkanton = str_remove(schulkanton, ".*/"),
         schulkanton = str_trim(schulkanton),
         schulkanton = recode(schulkanton, "Berne" = "Bern", "Grischun" = "Graubünden", "Wallis" = "Valais")) %>%
  left_join(switzerland_sf, by = c("schulkanton" = "NUTS_NAME"))

swiss_student_map
#> # A tibble: 12,420 × 18
#>    bildungsstufe   schulkanton staatsangehorigkeit_…¹ jahr  `Geschlecht - Total`
#>    <chr>           <chr>       <chr>                  <chr>                <dbl>
#>  1 Bildungsstufe … Zürich      Staatsangehörigkeit -… 1999…               209028
#>  2 Bildungsstufe … Zürich      Staatsangehörigkeit -… 2000…               210844
#>  3 Bildungsstufe … Zürich      Staatsangehörigkeit -… 2001…               211811
#>  4 Bildungsstufe … Zürich      Schweiz                1999…               161170
#>  5 Bildungsstufe … Zürich      Schweiz                2000…               161670
#>  6 Bildungsstufe … Zürich      Schweiz                2001…               162710
#>  7 Bildungsstufe … Zürich      Ausland                1999…                47856
#>  8 Bildungsstufe … Zürich      Ausland                2000…                49167
#>  9 Bildungsstufe … Zürich      Ausland                2001…                49099
#> 10 Bildungsstufe … Zürich      Unbekannt              1999…                    2
#> # ℹ 12,410 more rows
#> # ℹ abbreviated name: ¹​staatsangehorigkeit_kategorie
#> # ℹ 13 more variables: Mann <dbl>, Frau <dbl>, share_woman <dbl>, id <chr>,
#> #   LEVL_CODE <int>, NUTS_ID <chr>, CNTR_CODE <chr>, NAME_LATN <chr>,
#> #   MOUNT_TYPE <int>, URBN_TYPE <int>, COAST_TYPE <int>, FID <chr>,
#> #   geometry <MULTIPOLYGON [°]>

3 시각화

3.1 교육수준

코드
swiss_student_map_bildungsstufe <- swiss_student_map %>%
  filter(jahr == "2001/02",
         schulkanton != "Schweiz",
         staatsangehorigkeit_kategorie == "Schweiz") %>%
  select(schulkanton, jahr, bildungsstufe, share_woman, geometry) %>%
  pivot_wider(names_from = "bildungsstufe", values_from = "share_woman") %>%
  sf::st_as_sf()

swiss_student_map_bildungsstufe %>%
  mapview(zcol = "Bildungsstufe - Total", layer.name = "Total education level, % Woman")

3.2 동기화

코드
# Synchronize multiple maps -----------------------------------------------

leafsync::sync(
  swiss_student_map_bildungsstufe %>%
    mapview(zcol = "Tertiärstufe", layer.name = "Tertiary level, % Woman"),
  swiss_student_map_bildungsstufe %>%
    mapview(zcol = "Sekundarstufe II", layer.name = "Secondary level II, % Woman"),
  swiss_student_map_bildungsstufe %>%
    mapview(zcol = "Obligatorische Schule", layer.name = "Mandatory school, % Woman"),
  swiss_student_map_bildungsstufe %>%
    mapview(zcol = "Nicht auf Stufen aufteilbare Ausbildungen", layer.name = "Training that cannot be divided into levels, % Woman")
)

3.3 슬라이더

코드
# Comparing maps with a slider --------------------------------------------

swiss_student_map_bildungsstufe_1999 <- swiss_student_map %>%
  filter(jahr == "1999/00",
         schulkanton != "Schweiz",
         staatsangehorigkeit_kategorie == "Schweiz") %>%
  select(schulkanton, jahr, bildungsstufe, share_woman, geometry) %>%
  pivot_wider(names_from = "bildungsstufe", values_from = "share_woman") %>%
  sf::st_as_sf()

map1 <- mapview(swiss_student_map_bildungsstufe, zcol = "Bildungsstufe - Total")

map2 <- mapview(swiss_student_map_bildungsstufe_1999, zcol = "Bildungsstufe - Total")

map1 | map2

4 한국 데이터

4.1 지도

코드
# korea_sf <- giscoR::gisco_get_nuts(
#     resolution = "1",
#     epsg = "4326",
#     nuts_level = 1, 
#     cache = TRUE,
#     update_cache = TRUE,
#     country = "KOR") |>
#     sf::st_transform(crsLONGLAT)

library(sf)
library(tidyverse)

korea_sf_raw <- sf::read_sf("data/gadm41_KOR_1.json")

korea_sf <- korea_sf_raw %>% 
  separate(NL_NAME_1, into = c("시도명", "한자"), sep="(\\||\\()") %>% 
  select(시도명, geometry) 

plot(korea_sf)

4.2 데이터

코드
library(krvote)

votes_tbl <- krvote::election_20220309$득표율 %>% 
  group_by(시도명) %>% 
  summarise(이재명 = sum(이재명),
            윤석열 = sum(윤석열))

votes_tbl
#> # A tibble: 17 × 3
#>    시도명          이재명  윤석열
#>    <chr>            <dbl>   <dbl>
#>  1 강원도          419644  544980
#>  2 경기도         4428151 3965341
#>  3 경상남도        794130 1237346
#>  4 경상북도        418371 1278922
#>  5 광주광역시      830058  124511
#>  6 대구광역시      345045 1199888
#>  7 대전광역시      434950  464060
#>  8 부산광역시      831896 1270072
#>  9 서울특별시     2944981 3255747
#> 10 세종특별자치시  119349  101491
#> 11 울산광역시      297134  396321
#> 12 인천광역시      913320  878560
#> 13 전라남도       1094872  145549
#> 14 전라북도       1016863  176809
#> 15 제주특별자치도  213130  173014
#> 16 충청남도        589991  670283
#> 17 충청북도        455853  511921

4.3 결합

코드

vote_sf <- korea_sf %>% 
  left_join(votes_tbl) %>% 
  mutate(득표마진 = 이재명 - 윤석열)

5 시각화

5.1 대선(이재명)

코드
library(mapview)

palfunc <- function (n, alpha = 1, begin = 0, end = 1, direction = 1) {
  colors <- RColorBrewer::brewer.pal(11, "RdBu")
  if (direction < 0) colors <- rev(colors)
  colorRampPalette(colors, alpha = alpha)(n)
}

vote_sf %>%
  mapview(zcol = "득표마진", layer.name = "이재명 - 윤석열", col.regions = palfunc)

5.2 이재명 vs 윤석열

코드
library(mapview)

leafsync::sync(
  vote_sf %>%
    mapview(zcol = "이재명", layer.name = "이재명 득표수"),
  vote_sf %>%
    mapview(zcol = "윤석열", layer.name = "윤석열 득표수")  
)

5.3 대선 비교

코드
vote_2012 <- krvote::election_20121219$득표율 %>% 
  group_by(시도명) %>% 
  summarise(박근혜 = sum(박근혜),
            문재인 = sum(문재인))

vote_2012_sf <- korea_sf %>% 
  left_join(vote_2012) %>% 
  mutate(득표마진 = 문재인 - 박근혜)

map_2022 <- mapview(vote_2012_sf, zcol = "득표마진", layer.name = "이재명 - 윤석열", col.regions = palfunc)

map_2012 <- mapview(vote_sf, zcol = "득표마진", layer.name = "문재인 - 박근혜", col.regions = palfunc)

map_2012 | map_2022