1. 대한민국 시군 인구증가

통계청에서 제공하는 연도별 인구데이터를 바탕으로 2005년부터 2016년까지 인구변동 데이터를 바탕으로 시군별 인구변동에 대해 이해한다.

국가통계포털, KOSIS에서 “통계표” 검색창에 “도시지역 인구현황(시군구)” 입력하게 되면 2005년부터 2016까지 시군구별 인구데이터를 받아올 수 있다. 직접 다운로드 링크

2. 인구변동이 많은 시군 통계 작성

2.1. 데이터 가져오기 및 데이터 전처리

인구변동이 많은 시군 통계 분석을 위해 필요한 팩키지를 불러 읽어온다. 통계청 KOSIS에서 다운로드 받은 파일을 data 폴더 아래 저장하고 나서, 전처리 작업을 수행한다. 서울특별시를 포함한 광역시에 포함된 군은 데이터 분석에 제외할 것이기 때문에 stringr 정규표현식 기능을 활용하여 깔끔하게 향후 데이터분석을 위한 데이터프레임으로 정리한다.

# 0. 환경설정 -----------
library(tidyverse)
library(rvest)
library(stringr)
library(purrr)
library(readxl)
library(ggpubr)
library(extrafont)
loadfonts()
library(rlang)
library(lubridate)
library(forcats)


# 1. 데이터 가져오기 ---------

kor_dat <- read_excel("data/korea-pop-zipf-law.xlsx", sheet="데이터", skip=1) 

kor_dat <- kor_dat %>% 
  filter(`소재지(시군구)별` !="전국") %>% 
  filter(`인구현황별` =="전체인구(A)")

# 2. 데이터 전처리 ---------
## 2.1. 시군 뽑아내기 -------

sigungu_v <- kor_dat %>% count(`소재지(시군구)별`) %>% 
  pull(`소재지(시군구)별`)

sigungu_v <- sigungu_v[str_detect(sigungu_v, "시$|군$")]

## 2.2. 시군 데이터 전처리 -------

kor_df <- kor_dat %>% 
  filter(`소재지(시군구)별` %in% sigungu_v) %>% 
  filter(!is.na(`2016 년`)) %>% group_by(`소재지(시군구)별`) %>% 
  summarise_if(is.numeric, mean) %>% 
  rename(시군 = `소재지(시군구)별`)

## 2.3. 시각화 데이터 변환  -------
kor_lng_df <- kor_df %>% 
  gather(연도, 인구수, -시군) %>% 
  mutate(연도 = as.numeric(str_extract(연도, "[0-9]+")))

2.2. 문제점

대한민국 시군이 166 이기 때문에 인구변동이 많은 시군을 추출하기 위해서 서울시는 천만명 근처이고 가장 작은 울릉군은 2016년 기준 10,001 명이라 편차가 매우 크다. 따라서, 이를 시각화를 하게 되면 문제점이 한눈에 파악된다.

# 3. 시각화 ----------------
## 3.1. 시군별 연도별 인구수 변화
kor_lng_df %>% 
  mutate(연도 = make_date(year=연도)) %>% 
  ggplot(aes(x=연도, y=인구수, color=시군, group=시군)) +
    geom_point() +
    geom_line() +
    theme_pubclean(base_family="NanumGothic") +
    scale_y_log10(labels=scales::comma) +
    scale_x_date(date_labels = "%Y") +
    theme(legend.position = "none") +
    labs(x="", y="인구수", title="시군 인구수 년도별 변화(2005 - 2016)")

3. 함수형 프로그래밍을 통한 문제 해법

이러한 문제점에 대해 가장 많이 활용되는 기법이 자료구조로 티블(tibble)을 도입하고, 데이터 분석을 위한 방법으로 함수형 프로그래밍을 조합하는 것이다.

3.1. 티블 자료구조

데이터프레임을 기존 폭넓은(wide) 형태를 긴(long) 형태로 변환하고 이를 nest()를 적용시키면 함수형 프로그램을 적용시킬 수 있는 자료구조가 된다. 더불어, 선형회귀모형을 각 시군별로 적용시킬 예정이라 회귀모형 함수도 생성시켜 둔다.

그리고 나서 전체 시군별로 연도별 인구변화를 회귀분석으로 수행하여 수행결과를 broom 팩키지의 tidy, glance, augment 함수를 활용하여 데이터와 모형분석결과를 결합시킨다.

# 4. 인구변화 심한 시군 추출 ----------------
## 4.1. 데이터 
kor_sigun_tb <- kor_lng_df %>% group_by(시군) %>% 
  nest()

## 4.2. 모형 - 선형회귀모형
sigun_model <- function(df) {
  lm(인구수 ~ 연도, data=df)
}

## 4.3. 데이터 + 모형 결합
kor_sigun_tb <- kor_sigun_tb %>% 
  mutate(model = map(data, sigun_model)) %>% 
  mutate(
    tidy    = map(model, broom::tidy),
    glance  = map(model, broom::glance),
    결정계수 = glance %>% map_dbl("r.squared"),
    augment = map(model, broom::augment)
  )

DT::datatable(kor_sigun_tb)

3.2. 인구 급성장 시군 - 결정계수(\(R^2\))

회귀분석 결정계수(\(R^2\))기준 인구가 연도별로 높은 상관관계를 갖는 시군과 그렇지 않는 상위 하위 5개 시군을 뽑아 시각화 해보자.

## 4.4. 고성장 및 정체 시군 추출

kor_high_growth_sigun_tb <- kor_sigun_tb %>% 
  top_n(5, 결정계수) %>% 
  unnest(data) %>% 
  mutate(구분="고성장")

kor_low_growth_sigun_tb <- kor_sigun_tb %>% 
  top_n(-5, 결정계수) %>% 
  unnest(data) %>% 
  mutate(구분="저성장")

kor_growth_sigun_tb <- bind_rows(kor_high_growth_sigun_tb, kor_low_growth_sigun_tb)

## 4.5. 고성장 및 정체 시군 시각화

kor_growth_sigun_tb %>% 
  mutate(구분 = factor(구분),
         시군 = fct_reorder(시군, 결정계수)) %>% 
  mutate(연도 = make_date(year=연도)) %>% 
  ggplot(aes(x=연도, y=인구수, color=구분, group=시군)) +
  geom_point() +
  geom_line() +
  theme_pubclean(base_family="NanumGothic") +
  scale_y_log10(labels=scales::comma) +
  theme(legend.position = "none") +
  facet_wrap(~시군, nrow=2, scale="free") +
  labs(x="", y="인구수", title="시군 인구수 년도별 변화(2005 - 2016)") +
  scale_x_date(date_labels = "%Y")

3.3. 인구 급성장 시군 - 회귀계수

회귀분석 결정계수(\(R^2\))기준이 아닌 회귀계수(\(\beta_1\))를 기준으로 상위 5개, 하위 5개 시군을 뽑아 연도별 인구변화를 시각화해보자.

# 5. 인구 증가 및 인구감소 ----------------
kor_sigun_reg_df <- kor_sigun_tb %>% 
  mutate(증감 = map(tidy, "estimate")) %>% 
  unnest(증감) %>% 
  filter(row_number() %% 2 == 0)

top10_plus_sigun <- kor_sigun_tb %>% 
  mutate(증감 = map(tidy, "estimate")) %>% 
  unnest(증감) %>% 
  filter(row_number() %% 2 == 0) %>% 
  top_n(5, 증감) %>% 
  pull(시군)

top10_minus_sigun <-kor_sigun_tb %>% 
  mutate(증감 = map(tidy, "estimate")) %>% 
  unnest(증감) %>% 
  filter(row_number() %% 2 == 0) %>% 
  top_n(-5, 증감) %>% 
  pull(시군)

kor_lng_df %>% 
  left_join(kor_sigun_reg_df, by="시군") %>% 
  filter(시군 %in% c(top10_plus_sigun, top10_minus_sigun)) %>%
  mutate(구분 = ifelse(시군 %in% top10_plus_sigun, "성장", "역성장"),
         시군 = fct_reorder(시군, -증감)) %>% 
  mutate(연도 = make_date(year=연도)) %>% 
  ggplot(aes(x=연도, y=인구수, color=구분, group=구분)) +
  geom_point() +
  geom_line() +
  theme_pubclean(base_family="NanumGothic") +
  scale_y_continuous(labels=scales::comma) +
  scale_x_date(date_labels = "%Y") +
  theme(legend.position = "none") +
  labs(x="", y="인구수", title="시군 인구수 년도별 변화(2005 - 2016)") +
  facet_wrap(~시군, scale="free", nrow=2)