R) ggplot2 - 서울 통계자료 매핑

R) ggplot2 - 서울 통계자료 매핑

전국 시군구 행정경계지도를 기반으로 서울시 통계자료를 매핑하는 방법을 알아본다.

개요

행정경계지도만 시각화 해서는 별다른 효용이 없다. 각 행정구역에 해당하는 각종 숫자를 같이 엮어주어야 지역별 비교라던지 연도별 추이를 잘 볼 수 있을 것이다. 본 포스팅에서는 행정경계지도에 서울 열린데이터 광장의 통계자료를 매핑하는 과정을 소개하고자 한다.

데이터 준비

행정경계지도

여기에서 사용하는 데이터는 시군구 기준의 행정경계구역이 표기된 자료를 활용하며 한국 행정경계지도 시각화 포스팅에서 사용하는 것과 같으니 참고하도록 하자.

다음의 코드로 행정경계지도를 불러와서 확인한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
map = readOGR("TL_SCCO_SIG.shp")
df_map = fortify(map)
df_map_info = map@data
head(df_map_info)
## SIG_CD SIG_ENG_NM SIG_KOR_NM
## 0 42110 Chuncheon-si 춘천시
## 1 42130 Wonju-si 원주시
## 2 42150 Gangneung-si 강릉시
## 3 42170 Donghae-si 동해시
## 4 42190 Taebaek-si 태백시
## 5 42210 Sokcho-si 속초시

df_map_info[, "id"] = (1:nrow(df_map_info)) - 1
df_map_info[, "SIDO"] = as.numeric(substr(df_map_info$SIG_CD,
start = 1, stop = 2))
head(df_map_info)
## SIG_CD SIG_ENG_NM SIG_KOR_NM id SIDO
## 0 42110 Chuncheon-si 춘천시 0 42
## 1 42130 Wonju-si 원주시 1 42
## 2 42150 Gangneung-si 강릉시 2 42
## 3 42170 Donghae-si 동해시 3 42
## 4 42190 Taebaek-si 태백시 4 42
## 5 42210 Sokcho-si 속초시 5 42

데이터를 불러와서 id를 지정해주고 법정동 코드 첫 두자리를 추출하여 “SIDO” 변수에 할당한다. 해당 변수를 기반으로 서울지역의 법정동 코드의 첫 두 자리인 “11”로 필터링하여 서울 지역 행정구역 데이터를 확보하도록 하자.

1
2
3
4
5
6
df_map_info_seoul = df_map_info[df_map_info$SIDO == 11, ]
df_map_seoul = df_map[df_map$id %in% df_map_info_seoul$id, ]
head(df_map_seoul, 2)
## long lat order hole piece id group
## 608458 956615.5 1953567 1 FALSE 1 140 140.1
## 608459 956621.6 1953565 2 FALSE 1 140 140.1

통계 자료

서울 열린데이터 광장의 데이터 중 서울시 신고·등록 체육시설 통계를 사용한다.
통계자료 배너

2019년 통계를 내려받아 “stat_seoul_culture_sport_2019.txt”로 저장하였는데 해당 자료를 바로 사용하고자 한다면 아래 링크를 클릭하여 내려받아 실습을 해보도록 하자.
예제 파일: stat_seoul_culture_sport_2019.txt

“stat_seoul_culture_sport_2019.txt” 파일은 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
df_stat = read.csv("stat_seoul_culture_sport_2019.txt", sep = "\t",
fileEncoding = "UTF-8")
head(df_stat, 4)
## 기간 자치구 합계 합계.1 신고체육시설 신고체육시설.1
## 1 기간 자치구 합계 합계 요트장 요트장
## 2 기간 자치구 시설수 면적 시설수 면적
## 3 2019 서울시 10,491 4,073,512 1 27,620
## 4 2019 종로구 203 72,664 - -
## 신고체육시설.2 신고체육시설.3 신고체육시설.4 신고체육시설.5
## 1 빙상장 빙상장 종합체육시설 종합체육시설
## 2 시설수 면적 시설수 면적
## 3 14 21,219 99 317,965
## 4 1 248 7 19,054
## 신고체육시설.6 신고체육시설.7 신고체육시설.8 신고체육시설.9
## 1 수영장 수영장 체육도장 체육도장
## 2 시설수 면적 시설수 면적
## 3 131 163,416 2,198 321,619
## 4 1 1,268 22 4,164
## 신고체육시설.10 신고체육시설.11 신고체육시설.12 신고체육시설.13
## 1 골프연습장 골프연습장 체력단련장 체력단련장
## 2 시설수 면적 시설수 면적
## 3 1,916 779,610 2,598 834,839
## 4 25 13,548 43 14,288
## 신고체육시설.14 신고체육시설.15 신고체육시설.16 신고체육시설.17
## 1 당구장 당구장 썰매장 썰매장
## 2 시설수 면적 시설수 면적
## 3 3,443 534,582 3 58,345
## 4 100 19,773 - -
## 신고체육시설.18 신고체육시설.19 신고체육시설.20 신고체육시설.21
## 1 무도장 무도장 무도학원 무도학원
## 2 시설수 면적 시설수 면적
## 3 10 6,449 77 9,722
## 4 - - 4 321
## 등록체육시설 등록체육시설.1
## 1 골프장 골프장
## 2 시설수 면적
## 3 1 998,126

엑셀의 셀 병합 문제 때문에 txt확장자로 다운로드 받아도 변수명에 문제가 많은 것을 알 수 있다. 본 데이터를 깔끔하게 처리하는 방법은 별도의 포스팅에서 다룰 예정이며 우선 전체 시설 면적을 기반으로 시각화를 할 예정이다. 그리고 첫 번째 변수는 “기간”으로 되어있는데 해당 변수를 살펴보는 코드는 다음과 같다.

1
2
unique(df_stat$기간)
## [1] "기간" "2019"

“기간”과 “2019” 밖에 없기 때문에 불필요한 변수로 판단하고 다음과 같이 코드를 작성한다.

1
2
3
4
5
6
7
8
9
df_stat_sub = df_stat[-(1:3), 2:4]
head(df_stat_sub)
## 자치구 합계 합계.1
## 4 종로구 203 72,664
## 5 중구 374 116,711
## 6 용산구 168 122,422
## 7 성동구 271 64,392
## 8 광진구 359 153,562
## 9 동대문구 286 85,200

행정구역명, 시설 수, 시설 면적만 남았다. 물론 시설 수 또한 없애도 되지만 유지하였다.

한글 변수명의 경우 인코딩 문제 중 좋지 않은 문제가 발생할 수 있기 때문에 이를 영어로 바꿔주었다.

1
2
3
4
5
colnames(df_stat_sub) = c("region", "cnt", "area")
head(df_stat_sub, 2)
## region cnt area
## 4 종로구 203 72,664
## 5 중구 374 116,711

숫자에 쉼표나 단위가 섞여있으면 이를 문자로 인식하기 때문에 텍스트 처리 함수 중 하나인 gsub() 함수와 정규표현식(regular expression)을 활용하여 숫자 및 쉼표를 제외한 나머지 텍스트를 다 제거하는 코드는 다음과 같다.

1
2
3
4
5
6
df_stat_sub[, "cnt" ] = as.numeric(gsub("[^0-9.]", "", df_stat_sub$cnt))
df_stat_sub[, "area"] = as.numeric(gsub("[^0-9.]", "", df_stat_sub$area))
head(df_stat_sub, 2)
## region cnt area
## 4 종로구 203 72664
## 5 중구 374 116711

데이터 병합

지도를 그리려면 id 기준으로 통계량을 붙여줘야되는데 아쉽게도 기존 통계 데이터에는 “id” 변수가 없어 바로 매핑이 불가하다. 다음의 객체간 관계도를 참고하도록 하자.
데이터 세트 관계도

서울 지역의 행정구역 데이터와 서울 통계를 병합하는 코드는 다음과 같다.

1
2
3
4
5
6
7
library("dplyr")
df_map_info_seoul = left_join(x = df_map_info_seoul, y = df_stat_sub,
by = c("SIG_KOR_NM" = "region"))
head(df_map_info_seoul, 2)
## SIG_CD SIG_ENG_NM SIG_KOR_NM id SIDO cnt area
## 1 11110 Jongno-gu 종로구 140 11 203 72664
## 2 11140 Jung-gu 중구 141 11 374 116711

지도 데이터와 엮기 이전에 “id” 변수의 속성을 확인하면 숫자가 아니라는 것을 알 수 있다. 다음 코드를 보기 전에 join 연산을 시도했다면 에러를 마주했을 것이다.

1
2
3
4
5
6
head(df_map_seoul, 2)
## long lat order hole piece id group
## 608458 956615.5 1953567 1 FALSE 1 140 140.1
## 608459 956621.6 1953565 2 FALSE 1 140 140.1
class(df_map_seoul$id)
## [1] "character"

숫자로 변환 후 병합결과는 다음과 같다.

1
2
3
4
5
6
7
8
df_map_seoul[, "id"] = as.numeric(df_map_seoul$id)
df_map_seoul_join = left_join(x = df_map_seoul,
y = df_map_info_seoul[, c("id", "area")],
by = c("id" = "id"))
head(df_map_seoul_join, 2)
## long lat order hole piece id group area
## 1 956615.5 1953567 1 FALSE 1 140 140.1 72664
## 2 956621.6 1953565 2 FALSE 1 140 140.1 72664

시각화

기본

앞의 절차 진행 후 확보한 “df_map_seoul” 객체를 활용하여 그려본 ggplot 기반 시각화는 다음과 같다.

1
2
3
4
5
ggplot(data = df_map_seoul,
aes(x = long, y = lat,
group = group, color = id)) +
geom_polygon(fill = "#FFFFFF", size = 1.5) +
theme(legend.position = "none")

행정경계지도 - 시군구(서울)

통계값 매핑

지역별 통계값을 기반으로 색을 채운 결과는 다음과 같다.

1
2
3
4
5
6
ggplot(data = df_map_seoul_join,
aes(x = long, y = lat,
group = group, color = id,
fill = area)) +
geom_polygon() +
theme(legend.position = "none")

서울 통계 매핑 1

추가 조정

통계값에 따라 원하는 색상을 지정하기 원하는 경우 다음과 같이 scale_fill_gradient() 관련 함수를 사용하면 되고, 추가로 불필요한 위경도 정보를 theme_void() 함수로 테마설정을 하여 시각화한 코드와 결과는 다음과 같다.

1
2
3
4
5
6
7
8
ggplot(data = df_map_seoul_join,
aes(x = long, y = lat,
group = group, color = id,
fill = area)) +
geom_polygon() +
scale_fill_gradient(low = "#FFFFFF", high = "#0000FF") +
theme_void() +
theme(legend.position = "none")

서울 통계 매핑 2

기타

참고로 여기서 사용하는 지리 좌표계는 범용적으로 사용하는 WGS84 좌표계가 아니다. 좌표계 변환과 관련해서는 R) 전처리 - 지리 좌표계(CRS) 변환 포스팅을 참고하도록 하자.

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×