'최대한 친절하게 쓴 R로 지도에 점 찍고, 선 긋고, 색칠하기' 포스트를 쓰면서 인터랙티브 지도를 그릴 때는 R보다 태블로(tablebau) 같은 소프트웨어를 쓰는 게 더 나을 수 있다고 말씀드렸습니다.
개인적으로는 지금도 그 생각에는 큰 변함이 없습니다.
그러다가 어른들 사정으로 R를 가지고 인터랙티브 지도를 어떻게 그리는지 다른 분들께 말씀드려야 할 일이 있었습니다. 그래서 어차피 자료 및 코드를 정리해 놓은 김에 블로그에도 내용을 남겨 놓습니다.
'최대한 친절하게 쓴 R로 지도에 점 찍고, 선 긋고, 색칠하기' 포스트에서 말씀드린 것처럼 R에서 인터랙티브 지도를 그릴 수 있도록 도와주는 제일 유명한 패키지는 leaflet입니다.
일단 패키지를 (설치하고) 불러오는 것부터 시작해야겠죠? 아, 물론 R를 시작할 때 제일 먼저 해야 하는 건 tidyverse를 불러오기입니다.
#install.packages('tidyverse')
library('tidyverse')
## Registered S3 methods overwritten by 'ggplot2': ## method from ## [.quosures rlang ## c.quosures rlang ## print.quosures rlang
## -- Attaching packages ---------------------------------------------------------------- tidyverse 1.2.1 --
## √ ggplot2 3.1.1 √ purrr 0.3.2 ## √ tibble 2.1.3 √ dplyr 0.8.1 ## √ tidyr 0.8.3 √ stringr 1.4.0 ## √ readr 1.3.1 √ forcats 0.3.0
## -- Conflicts ------------------------------------------------------------------- tidyverse_conflicts() -- ## x dplyr::filter() masks stats::filter() ## x dplyr::lag() masks stats::lag()
#install.packages('leaflet')
library('leaflet')
그다음 아래처럼 코드를 입력하면 서울 지역 지도가 뜹니다. 아래 코드는 서울이 자리잡은 동경 126.9784, 북위 37.566를 중심으로 줌(zoom)을 11로 해서 지도를 그려달라는 뜻입니다.
leaflet() %>%
setView(lng=126.9784, lat=37.566, zoom=11) %>%
addTiles()
보시는 것처럼 잘 나왔습니다. 이제 점을 찍을 차례입니다.
이번 포스트에서는 '별 다방' 각 지점 위치를 찍어보려고 합니다. 아래 있는 파일을 내려 받으신 다음에 불러오시면 됩니다.
원래 tidyverse에는 CSV 파일을 읽을 때 쓰라고 read_csv() 함수가 들어 있습니다. 그런데 이 함수를 쓰면 한글 인코딩 문제가 생기는 일이 많습니다. 그래서 저는 보통 아래 같은 방식으로 CSV 파일을 읽습니다.
sb <- read.csv('starbucks.csv') %>% as_tibble
sb
## # A tibble: 1,362 x 7 ## address sido sigungu code lat long SIG_CD ## <fct> <fct> <fct> <fct> <dbl> <dbl> <int> ## 1 "강원도 강릉시 경강로 2096 (임당동)033-645-~ 강원 강릉 강릉 37.8 129. 42150 ## 2 "강원도 강릉시 경강로 2400 (송정동)033-652-~ 강원 강릉 강릉 37.8 129. 42150 ## 3 "강원도 강릉시 교동광장로 114 (교동)033-642-~ 강원 강릉 강릉 37.8 129. 42150 ## 4 "강원도 강릉시 창해로14번길 40 (견소동)033-65~ 강원 강릉 강릉 37.8 129. 42150 ## 5 "강원도 고성군 토성면 미시령옛길 1153033-636-~ 강원 고성 강원고성~ 38.2 128. 42820 ## 6 "강원도 동해시 중앙로 219 (천곡동)033-532-3~ 강원 동해 동해 37.5 129. 42170 ## 7 "강원도 속초시 동해대로 4114 (조양동)033-633~ 강원 속초 속초 38.2 129. 42210 ## 8 "강원도 속초시 미시령로2983번길 111 (장사동)03~ 강원 속초 속초 38.2 129. 42210 ## 9 "강원도 속초시 중앙로 123 (금호동)033-637-8~ 강원 속초 속초 38.2 129. 42210 ## 10 "강원도 원주시 남원로 588 (명륜동)033-764-2~ 강원 원주 원주 37.3 128. 42130 ## # ... with 1,352 more rows
leaflet에서 점을 찍을 때는 addCircles()라는 함수를 쓰시면 됩니다. 보시는 것처럼 이 자료에는 각 지점 경도(long) 위도(lat)가 들어 있기 때문에 이 자료를 가져가다 점을 찍으라고 명령을 내리면 됩니다.
아, leaflet으로 지도를 그릴 때는 addProviderTiles() 함수로 지도 스타일을 바꿀 수 있습니다. 어떤 스타일을 쓸 수 있는지는 이 링크에서 확인하시면 됩니다.
이번에 우리 'CartoDB.Positron'을 쓰겠습니다. 점 색깔도 별 다방 컬러로 바꾸겠습니다.
leaflet(sb) %>%
setView(lng=126.9784, lat=37.566, zoom=11) %>%
addProviderTiles('CartoDB.Positron') %>%
addCircles(lng=~long, lat=~lat, color='#006633')
그러면 점 색깔을 특정 기준에 따라 달리하고 싶을 때는 어떻게 하면 될까요? ggplot 문법에서는 ase() 안에 변수를 지정하면 곧바로 색을 바꿀 수 있습니다. 애석하게도 leaflet은 그렇지 않습니다. 아래처럼 color*() 함수로 따로 팔레트를 만들어야 합니다.
우리는 시군구마다 다른 색을 찍을 거니까 colorFactor() 함수를 씁니다. 만약 숫자 크기에 따라 색을 달리하고 싶을 때는 colorNumeric()을 쓰시면 됩니다.
pal <- colorFactor("viridis", sb$code)
이렇게 팔레트를 만들도 나면 pal() 안에 원하는 변수 이름을 쓰면 각 값에 맞는 색으로 점을 찍게 됩니다. 아래처럼 말입니다.
leaflet(sb) %>%
setView(lng=126.9784, lat=37.566, zoom=11) %>%
addProviderTiles('CartoDB.Positron') %>%
addCircles(lng=~long, lat=~lat, color=~pal(code))
지도에는 보통 점보다 마커라고 부르는 표시를 남기는 일이 더 흔합니다. leaflet에서는 addMarkers() 함수를 쓰면 마커를 표시할 수 있습니다.
아래 코드는 동경 127.7669, 북위 35.90776를 중심으로 줌 6을 준 지도를 그리고 그 위에 마커를 표시하면서 address 열에 있는 데이터를 레이블로 삼으라는 뜻입니다.
leaflet(sb) %>%
setView(lng=127.7669, lat=35.90776, zoom=6) %>%
addProviderTiles('CartoDB.Positron') %>%
addMarkers(lng=~long, lat=~lat, label=~address)
마커 위에 마우스를 올려보시면 각 지점 주소+전화번호가 뜨는 걸 확인할 수 있습니다.
이어서 선을 그어 보겠습니다.
'최대한 친절하게 쓴 R로 지도에 점 찍고, 선 긋고, 색칠하기'에서 설명드린 것과 마찬가지로 선을 그으려면 점과 점을 그룹으로 묶어주는 과정이 필요합니다.
여기서는 가장 서쪽에 있는(=lat가 제일 작은) 별 다방 지점과 가장 동쪽에 있는 지점을 선으로 연결해 보겠습니다.
dplyr 패키지에 있는 filter() 함수를 써서 먼저 두 지점만 선택합니다. 이어서 두 지점을 1이라는 group으로 묶습니다.
이렇게 정리를 하고 나면 addPolylines() 함수로 선을 그리라고 명령하면 그만입니다.
sb %>%
filter(lat==min(lat) | lat==max(lat)) %>%
mutate(group=1) %>%
leaflet() %>%
setView(lng=127.7669,lat=35.90776, zoom=6) %>%
addProviderTiles('CartoDB.Positron') %>%
addCircles(lng=~long, lat=~lat) %>%
addPolylines(lng=~long, lat=~lat, group=~group, weight=.5)
이제 마지막으로 색을 칠할 차례.
역시 '최대한 친절하게 쓴 R로 지도에 점 찍고, 선 긋고, 색칠하기'에서 말씀드렸던 것처럼 색을 칠하려면 셰이프파일이라는 '미농지'가 필요합니다. 우리가 쓸 미농지는 아래 첨부 파일에 들어 있습니다.
※티스토리에 한 번에 올릴 수 있는 파일 크기가 10메가바이트라 둘로 나눴을 뿐 다른 이유는 없습니다.
압축을 풀고 나면 파일 네 개가 들어 있습니다. 그 가운데 우리에게 필요한 건 'TL_SCCO_SIG.shp'입니다.
이 파일을 R 안으로 불러들이려면 raster 패키지가 필요합니다.
#install.packages('raster')
library('raster')
## Loading required package: sp
## ## Attaching package: 'raster'
## The following object is masked from 'package:dplyr': ## ## select
## The following object is masked from 'package:tidyr': ## ## extract
셰이프파일은 shapefile()이라는 함수로 불러옵니다. 그냥 아래처럼 쓰시면 됩니다.
korea <- shapefile('TL_SCCO_SIG.shp')
셰이프파일은 기본적으로 '덩치'가 있습니다. 인터랙티브 지도를 그릴 때 이 덩치 그대로 데이터를 불러오면 속도에 영향을 주는 게 당연한 일.
그래서 셰이프파일을 날 것 그대로 쓰지 않고 무게를 줄여주는 작업을 진행하는 일이 흔합니다.
R에서는 rmapshaper라는 패키지를 통해 이 작업을 진행할 수 있습니다.
#install.packages('rmapshaper')
library('rmapshaper')
## Registered S3 method overwritten by 'geojsonlint': ## method from ## print.location dplyr
현재는 korea라는 변수에 셰이프파일이 들어 있는데 ms_simplify() 함수로 이 파일 덩치를 줄여서 korea2 변수에 넣겠습니다.
korea2 <- ms_simplify(korea)
셰이프파일 안에는 행정구역 이름과 코드 등을 담은 데이터 프레임이 들어 있습니다. 변수 이름 다음에 '@data'라고 입력하면 그 내용을 확인할 수 있습니다.
korea2@data %>% head
## SIG_CD SIG_ENG_NM SIG_KOR_NM ## 1 11110 Jongno-gu <U+FFFD><U+FFFD><U+FFFD>α<U+FFFD> ## 2 11140 Jung-gu <U+FFFD><U+07F1><U+FFFD> ## 3 11170 Yongsan-gu <U+FFFD><U+FFFD>걸 ## 4 11200 Seongdong-gu <U+FFFD><U+FFFD><U+FFFD><U+FFFD><U+FFFD><U+FFFD> ## 5 11215 Gwangjin-gu <U+FFFD><U+FFFD><U+FFFD><U+FFFD><U+FFFD><U+FFFD> ## 6 11230 Dongdaemun-gu <U+FFFD><U+FFFD><U+FFFD>빮<U+FFFD><U+FFFD>
계속 별 다방 자료를 가지고 놀고 있으니 각 시군구별로 이 다방이 몇 개 있는지 합친 다음 그 숫자로 지도에 색을 칠하도록 하겠습니다.
이미 별 다방 자료에도 이 셰이프파일과 마찬가지로 시군구 코드가 들어 있습니다. 이 코드별로 묶고 개수를 세어 보겠습니다. 그리고 그 결과를 sb2라는 변수에 넣겠습니다.
sb %>% group_by(SIG_CD) %>%
summarise(count=n()) -> sb2
sb2
## # A tibble: 166 x 2 ## SIG_CD count ## <int> <int> ## 1 11110 36 ## 2 11140 52 ## 3 11170 18 ## 4 11200 10 ## 5 11215 15 ## 6 11230 8 ## 7 11260 6 ## 8 11290 13 ## 9 11305 5 ## 10 11320 2 ## # ... with 156 more rows
잘 들어왔습니다. 이제 이 결과를 셰이프파일과 합쳐야 합니다. 이때는 sp 패키지에 있는 merge() 함수를 쓰는 게 제일 간단합니다. 이렇게 병합을 거쳐서 korea3 변수를 만들겠습니다.
sp::merge(korea2, sb2) -> korea3
이번에도 잘 들어왔는지 확인해 봐야겠죠?
korea3@data %>% head
## SIG_CD SIG_ENG_NM SIG_KOR_NM ## 1 11110 Jongno-gu <U+FFFD><U+FFFD><U+FFFD>α<U+FFFD> ## 2 11140 Jung-gu <U+FFFD><U+07F1><U+FFFD> ## 3 11170 Yongsan-gu <U+FFFD><U+FFFD>걸 ## 4 11200 Seongdong-gu <U+FFFD><U+FFFD><U+FFFD><U+FFFD><U+FFFD><U+FFFD> ## 5 11215 Gwangjin-gu <U+FFFD><U+FFFD><U+FFFD><U+FFFD><U+FFFD><U+FFFD> ## 6 11230 Dongdaemun-gu <U+FFFD><U+FFFD><U+FFFD>빮<U+FFFD><U+FFFD> ## count ## 1 36 ## 2 52 ## 3 18 ## 4 10 ## 5 15 ## 6 8
네, 잘 들어왔습니다. 이제 또 팔레트를 만들어야 합니다. 이번에는 숫자가 크고 적은 걸 구분해야 하니까 colorNumeric() 함수를 씁니다. 'reverse=TRUE'는 scale_viridis()에서 'direction=-1'을 준 것과 같은 효과입니다.
pal2 <- colorNumeric("viridis", korea3@data$count, reverse=TRUE)
이제 지도를 그리는 일만 남았습니다. 셰이프파일을 얹을 때는 addPolygons() 함수를 쓰시면 됩니다. 그리고 fillColor 속성에 위에서 만든 팔레트를 지정하면 변수값에 따라 단계를 구분해 색깔을 칠합니다.
leaflet(korea3) %>%
setView(lng=127.7669,lat=35.90776, zoom=6) %>%
addProviderTiles('CartoDB.Positron') %>%
addPolygons(color='#444', weight=1, fillColor=~pal2(count))
아주 잘 나왔습니다. 사실 확대를 해보시면 셰이프파일이 지도와 완전히 일치하지는 않습니다. 무게를 줄이면서 정확도가 줄어들었기 때문. 그래도 못 쓸 정도는 아닙니다.
R를 가지고 인터랙티브 지도를 그려보고 싶으신 분들께 도움이 되었으면 좋겠습니다.
그럼 Happy Charting!
댓글,