지도상에 표현되는 거품 차트(bubble chart)
데이터를 분석하는 분석가나 분석가가 되기를 원하는 사람들에게 필독서인 ‘팩트풀니스’(한스 로슬링, 김영사, 2019)에서는 데이터를 분석하는데 가장 유용하게 사용하는 시각화로 거품형 차트를 제시하고 있다. 거품형 차트는 X, Y축에 매핑되는 데카르트 좌표계에 원의 크기로 데이터의 크기를 표현하는 차트로 최소 3개의 변수를 표현할 수 있는 방법이다. 이에 원의 색을 추가하면 4개의 변수를 하나의 시각화에 표현한다는 점에서 매우 활용성이 높은 시각화 방법이다.
사실 이 거품 차트는 산점도와 종이 한 장 차이라고 볼 수 있다. 산점도를 ggplot2
패키지로 만드려면 geom_point()
하나의 레이어로 간단히 만들 수 있는데 X, Y 매핑에 size를 거품의 크기로 나타내고자 하는 변수에 매핑해주면 간단히 생성된다. 여기에 color
를 변수에 매핑시켜 주면 4개의 변수가 표현되는 거품 차트가 만들어 진다.
그런데 이 거품 차트를 X, Y 축의 데카르트 2차원 좌표계가 아닌 지도상에 위치시키려면 어떻게 해아할까?
이 포스트에서는 각 시도별 정규 교원 1인당 학생수를 거품 차트로 지도위에 표현해보도록 하겠다.
데이터 import
지도위에 표현되는 거품 차트를 생성하기 위해 앞서 연도별 시도별 비정규 교원 1인당 학생수 in R - rank()에서 사용했던 ‘df_provinfo_21’ 데이터프레임을 사용한다.
지도를 표현하기 위해서는 Shape 데이터와 geojson 데이터를 사용한 지도의 시각화에서 shape 파일에서 읽어들인 ‘spdf_shp’ 데이터를 사용한다.
교원 1인당 학생수 처리
거품 차트를 그리기 위해서는 먼저 원 크기로 표현할 데이터를 생성해야 한다. 여기서는 각 시도별 교원 1인당 학생수를 원의 크기로 표현하는데 이를 위해 먼저 ‘df_provinfo_21’ 데이터프레임의 구조를 살펴본다.
library(tidyverse)
glimpse(df_provinfo_21)
## Rows: 239
## Columns: 9
## $ 연도 <chr> "2021", "2021", "2021", "2021", "2021", "2021", "2021~
## $ 시도 <chr> "전국", "전국", "전국", "전국", "전국", "전국", "전국~
## $ 학제 <chr> "유치원", "초등학교", "중학교", "고등학교", "(일반고)~
## $ 학급수 <dbl> 33381, 124047, 53053, 56245, 40063, 2865, 10352, 2965,~
## $ 학생수 <dbl> 582572, 2672340, 1350770, 1299965, 961275, 63181, 1986~
## $ 전체교원수 <dbl> 53457, 191224, 113238, 131120, 91448, 8001, 24816, 6855,~
## $ 기간제교원수 <dbl> 4652, 9566, 20089, 24929, 16879, 1181, 5734, 1135, 2410, ~
## $ 시간강사수 <dbl> 2, 1120, 2325, 1868, 1106, 291, 229, 242, 9, 0, 21, 211,~
## $ 비정규교원당학생수 <dbl> 125.17662, 250.07861, 60.26457, 48.51159, 53.44871, 42.92188~
위의 결과를 보면 행의 수는 239개, 열은 ‘연도’, ‘시도’ 등 총 9개로 구성되어 있다. 이 중 교원 1인당 학생수를 구하기 위해서는 각 지역의 학생수를 교원수로 나누어주어야 한다. 이미 비정규 교원당 학생수는 산출되었으므로 이번에는 전체 교원당 학생수와 정규 교원당 학생수를 산출하겠다.
|>
df_provinfo_21 mutate(`전체교원당학생수` = 학생수 / 전체교원수,
`정규교원당학생수` = 학생수 / (전체교원수 - 기간제교원수 - 시간강사수))
## # A tibble: 239 x 11
## 연도 시도 학제 학급수 학생수 전체교원수 기간제교원수 시간강사수
## <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 2021 전국 유치원 33381 582572 53457 4652 2
## 2 2021 전국 초등학교 124047 2672340 191224 9566 1120
## 3 2021 전국 중학교 53053 1350770 113238 20089 2325
## 4 2021 전국 고등학교 56245 1299965 131120 24929 1868
## 5 2021 전국 (일반고) 40063 961275 91448 16879 1106
## 6 2021 전국 (특목고) 2865 63181 8001 1181 291
## 7 2021 전국 (특성화고) 10352 198663 24816 5734 229
## 8 2021 전국 (자율고) 2965 76846 6855 1135 242
## 9 2021 전국 특수학교 5105 26967 10269 2410 9
## 10 2021 전국 고등공민학교 5 43 5 0 0
## # ... with 229 more rows, and 3 more variables: 비정규교원당학생수 <dbl>,
## # 전체교원당학생수 <dbl>, 정규교원당학생수 <dbl>
이 중 시도가 ’전체’인 데이터를 제외하여야 하기 떄문에 최종 데이터는 다음과 같이 산출한다.
<- df_provinfo_21 |>
df_stu_per_tech mutate(`전체교원당학생수` = 학생수 / 전체교원수,
`정규교원당학생수` = 학생수 / (전체교원수 - 기간제교원수 - 시간강사수)) |>
filter(시도 != '전국')
교원 1인당 학생와 지리 데이터의 조인
기초 데이터가 완성되었으니 이제 지리 데이터와 조인을 해야한다. 우선 지리 데이터에서 사용하는 시도 코드를 행정안전부의 행정표준코드를 사용하여 생성해준다.
<- df_stu_per_tech |>
df_stu_per_tech mutate(CTPRVN_CD = case_when(
== '강원' ~ '42',
시도 == '경기' ~ '41',
시도 == '경남' ~ '48',
시도 == '경북' ~ '47',
시도 == '광주' ~ '29',
시도 == '대구' ~ '27',
시도 == '대전' ~ '30',
시도 == '부산' ~ '26',
시도 == '서울' ~ '11',
시도 == '세종' ~ '36',
시도 == '울산' ~ '31',
시도 == '인천' ~ '28',
시도 == '전남' ~ '46',
시도 == '전북' ~ '45',
시도 == '제주' ~ '50',
시도 == '충남' ~ '44',
시도 == '충북' ~ '43'
시도 ))
이제 shape 파일에서 생성한 sf 객체인 ’spdf_shp’와 ’df_stu_per_tech’를 ’CTPRVN_CD’을 사용하여 다음과 같이 조인한다.
<- left_join(spdf_shp, df_stu_per_tech, by = 'CTPRVN_CD') df_joined
지형 데이터의 시각화
이제 조인된 데이터를 사용하여 지형 데이터를 시각화한다.
library(ggspatial)
|>
df_joined ggplot() +
geom_sf(color = 'gray80') +
labs(x = '위도', y = '경도') +
annotation_scale(location = "br") +
annotation_north_arrow(location = "br", pad_y = unit(0.05, 'npc'),
style = north_arrow_nautical) +
theme_bw()
이 데이터를 사용하여 앞서 살펴본 단계 구분도를 그리면 다음과 같다. 전체 학교급 중 초등학교에 대해 정규교원 1인당 학생수에 대한 단계구분도는 다음과 같이 그릴 수 있다.
|>
df_joined filter(학제 == '초등학교') |>
ggplot() +
geom_sf(aes(fill = 정규교원당학생수), color = 'gray80') +
labs(x = '위도', y = '경도') +
annotation_scale(location = "br") +
annotation_north_arrow(location = "br", pad_y = unit(0.05, 'npc'),
style = north_arrow_nautical) +
theme_bw()
지형 데이터의 거품 차트 시각화
이번에는 이 단계구분도를 거품 차트로 변경한다. 우선 거품 차트에 사용해야 할 원의 위치를 각 시도별로 설정해야 한다. 이를 위해서 st_centroid()
를 사용하여 각 지역별 중심점을 계산한다. 이와 관련해서는 지도에 지역 이름 넣기 in R를 참조하라.
<- as.data.frame(st_centroid(spdf_shp))
centroid_shp
|> head() centroid_shp
## CTPRVN_CD CTP_ENG_NM CTP_KOR_NM geometry
## 1 42 Gangwon-do 강원도 POINT (1070808 1969047)
## 2 41 Gyeonggi-do 경기도 POINT (971834.4 1948256)
## 3 48 Gyeongsangnam-do 경상남도 POINT (1069218 1703449)
## 4 47 Gyeongsangbuk-do 경상북도 POINT (1112027 1817405)
## 5 29 Gwangju 광주광역시 POINT (939472 1684701)
## 6 27 Daegu 대구광역시 POINT (1096215 1759774)
이 데이터를 거품의 중심점으로 사용해야하기 떄문에 이 중심점 데이터도 ’CTPRVN_CD’를 사용하여 ’df_stu_per_tech’와 조인한다.
<- left_join(df_stu_per_tech, centroid_shp, by = 'CTPRVN_CD')
df_joined_centroid
glimpse(df_joined_centroid)
## Rows: 224
## Columns: 15
## $ 연도 <chr> "2021", "2021", "2021", "2021", "2021", "2021", "2021~
## $ 시도 <chr> "서울", "서울", "서울", "서울", "서울", "서울", "서울~
## $ 학제 <chr> "유치원", "초등학교", "중학교", "고등학교", "(일반고)~
## $ 학급수 <dbl> 3704, 18396, 8563, 9194, 6303, 484, 1758, 649, 858, 3,~
## $ 학생수 <dbl> 69958, 399435, 209749, 216319, 149174, 12169, 35349, 1~
## $ 전체교원수 <dbl> 6391, 28219, 17234, 21039, 14099, 1246, 4264, 1430, 1685~
## $ 기간제교원수 <dbl> 186, 716, 3166, 4839, 3044, 229, 1184, 382, 365, 0, 125, ~
## $ 시간강사수 <dbl> 0, 307, 1057, 613, 418, 71, 45, 79, 3, 0, 78, 0, 0, 2058~
## $ 비정규교원당학생수 <dbl> 376.118280, 390.454545, 49.668245, 39.676999, 43.088966, 40.~
## $ 전체교원당학생수 <dbl> 10.9463308, 14.1548248, 12.1706510, 10.2818100, 10.5804667,~
## $ 정규교원당학생수 <dbl> 11.274456, 14.687270, 16.120898, 13.878168, 14.024067, 12.8~
## $ CTPRVN_CD <chr> "11", "11", "11", "11", "11", "11", "11", "11", "11~
## $ CTP_ENG_NM <chr> "Seoul", "Seoul", "Seoul", "Seoul", "Seoul", "Seoul~
## $ CTP_KOR_NM <chr> "서울특별시", "서울특별시", "서울특별시", "서울특별~
## $ geometry <POINT [m]> POINT (955111.6 1950406), POINT (955111.6 195~
조인된 데이터를 확인하면 중심점을 표현하는 열이 geometry로 POINT 타입의 데이터로 조인되어 있다. 이 데이터를 바로 geom_point()
의 X, Y 매핑으로 사용할수 없으니 이를 X, Y 형태의 데이터프레임으로 바꾸어야 한다. 이를 위한 함수가 st_coordinates()
이다. 이 결과로 생성된 데이터는 matrix 형태이므로 이를 데이터프레임으로 변경하고 열로 붙여주기 위해 cbind()
를 사용하였다.
<- cbind(df_joined_centroid, data.frame(st_coordinates(df_joined_centroid$geometry))) df_joined_centroid
이렇게 생성된 데이터는 다음과 같다.
|>
df_joined_centroid filter(학제 == '초등학교') |>
select(시도, 정규교원당학생수, X, Y)
시도 | 정규교원당학생수 | X | Y | |
---|---|---|---|---|
2 | 서울 | 14.68727 | 955111.6 | 1950406 |
16 | 부산 | 15.62650 | 1142039.9 | 1690714 |
30 | 대구 | 14.05329 | 1096215.2 | 1759774 |
43 | 인천 | 16.30998 | 900984.4 | 1954430 |
57 | 광주 | 14.36748 | 939472.0 | 1684701 |
71 | 대전 | 13.26362 | 990489.4 | 1815820 |
84 | 울산 | 16.41378 | 1157493.1 | 1730077 |
97 | 세종 | 14.33784 | 978414.3 | 1840349 |
107 | 경기 | 17.57210 | 971834.4 | 1948256 |
121 | 강원 | 11.14116 | 1070808.5 | 1969047 |
134 | 충북 | 12.72663 | 1029502.4 | 1860032 |
147 | 충남 | 13.28535 | 941837.4 | 1836960 |
161 | 전북 | 11.41730 | 967659.5 | 1746665 |
174 | 전남 | 11.38512 | 945193.5 | 1653619 |
187 | 경북 | 13.03628 | 1112027.0 | 1817405 |
200 | 경남 | 14.43981 | 1069217.9 | 1703449 |
215 | 제주 | 15.09423 | 911983.8 | 1488782 |
이제 조인된 두개의 데이터를 사용하여 정규교원 1인당 학생수에 대한 지형 거품 차트를 그려보겠다. 우선 ’df_joined’를 사용하여 지도를 그려주고 중심점이 계산된 ’df_joined_centroid’의 X, Y 열과 정규교원당학생수 열을 geom_point()
에 사용하여 거품 차트를 그려준다.
|>
df_joined filter(학제 == '초등학교') |>
ggplot() +
geom_sf(color = 'gray80') +
geom_point(data = df_joined_centroid |> filter(학제 == '초등학교'), aes(x = X, y = Y, size = `정규교원당학생수`), color = 'steelblue', alpha = 0.5) +
# scale_size(range = c(.1, 10)) +
labs(x = '위도', y = '경도') +
annotation_scale(location = "br") +
annotation_north_arrow(location = "br", pad_y = unit(0.05, 'npc'),
style = north_arrow_nautical) +
theme_bw()
만약 고등학교의 세부 분류인 일반고, 특목고, 특성화고, 자율고를 비교하고자 한다면 다음과 같이 facet_wrap()
을 사용할 수 있다.
|>
df_joined filter(학제 %in% c('(일반고)', '(특목고)', '(특성화고)', '(자율고)')) |>
ggplot() +
geom_sf(color = 'gray80') +
geom_point(data = df_joined_centroid |> filter(학제 %in% c('(일반고)', '(특목고)', '(특성화고)', '(자율고)')), aes(x = X, y = Y, size = `정규교원당학생수`), color = 'steelblue', alpha = 0.5) +
# scale_size(range = c(.1, 10)) +
labs(x = '위도', y = '경도') +
facet_wrap(~학제) +
annotation_scale(location = "br") +
annotation_north_arrow(location = "br", pad_y = unit(0.05, 'npc'),
style = north_arrow_nautical) +
theme_bw()
'지도 시각화' 카테고리의 다른 글
전국 사설 학원수 범주형 단계 구분도 in R - 연속형 변수를 범주형 변수로 변환 (6) | 2022.07.16 |
---|---|
전국 사설 학원 수 단계 구분도 그리기 in R (3) | 2022.07.11 |
지도에 지역 이름 넣기 in R (0) | 2022.07.08 |
지도에 나침반과 축척 넣기 in R (3) | 2022.07.08 |
Shape 데이터와 geojson 데이터를 사용한 지도의 시각화 (10) | 2022.07.08 |
댓글