14  사례: 시군구 소멸

현시점 지방소멸 문제는 저출산, 고령화, 수도권 인구 집중 등으로 인해 지방 지자체 인구가 급속히 감소하고 있다. 이로 인해 지방 경제가 침체되고, 지역 공동체가 와해되는 등 다양한 사회적 문제가 야기되고 있다.

인구 증감률 분석, 고령화 지수 분석, 합계출산율 분석, 인구 구조 분석, 인구 이동 분석 등을 통해 인구 소멸 위험이 높은 시군구를 파악하여 지방 소멸 문제에 선제적으로 대응할 수 있는 정책을 수립하는 데 활용할 수 있다.

통계청에서 제공하는 연도별 인구데이터를 바탕으로 2005년부터 2022년까지 인구변동 데이터를 바탕으로 시군별 인구변동을 확인해 보자. 국가통계포털, KOSIS에서 “통계표” 검색창에 “도시지역 인구현황(시군구)”을 입력하게 되면 2005년부터 2016년까지 시군구별 인구데이터1를 받아올 수 있다.

csv, txt, xlsx/xls, sdmx 등 다양한 형식으로 데이터를 다운로드 받을 수 있으며, 이 중 xlsx 형식의 데이터를 다운로드 받아 사용한다. sdmx 파일 형식이 UN, OECD, ILO, WHO 등 다양한 국제기구에서 자료제공 표준으로 자리 잡고 있어 rsdmx 패키지를 통해 데이터를 불러올 수 있다. 시군구별 도시지역 인구현황을 전체기간에 걸쳐 엑셀로 다운로드 받게 되면 wide 형식 데이터와 소계, 총계 등 집계가 포함된 것이 확인된다.

그림 14.1: 국가통계포털에서 다운받은 전체기간 도시지역 인구현황(시군구) 데이터

통계청 KOSIS에서 제공되는 시군구와 시점이 확인되어 인구데이터를 확보하고 시군구별 인구변동 분석 작업을 수행한다. 통계청 KOSIS에서 확보된 시군구별 인구데이터를 전처리한다. 탐색적 데이터분석을 통해 시군구 간 인구 편차가 큰 문제를 파악하고, 이를 해결하기 위해 티블 자료구조와 함수형 프로그래밍을 도입하여 해결한다. 시군구별로 선형회귀모형을 적용하고 broom 패키지를 사용하여 데이터와 모형 결과를 결합한다. 특히, 급성장/역성장 시군구를 파악하기 위해 결정계수와 회귀계수를 기준으로 상위/하위 5개 시군구를 추출한다. 마지막으로 급성장/역성장 시군구 인구 변화를 시각화하여 결과분석을 마무리한다.

graph LR
   classDef emphasize fill:#f9d423,stroke:#333,stroke-width:2px;
   classDef grayStyle fill:#f0f0f0,stroke:#333,stroke-width:2px;
   
   subgraph start["데이터 준비"]
       A["통계청 시군구별<br>인구데이터"] --> B(데이터 전처리)
       class A grayStyle
       class B grayStyle
   end
   
   start --> C
   
   subgraph problem["문제 해결"]
       direction TB
       C["시군구<br>인구편차"] --> E["티블 +<br>함수형<br>프로그래밍"]
       E --> G["시군구별<br>선형회귀모형<br>broom 패키지"]
       class E emphasize;
       class C grayStyle
       class G grayStyle
   end
   
   subgraph 결과_분석 [결과 분석]
       G --> I{"급성장/역성장<br>시군구 파악"}
       I -->|"결정계수,<br>회귀계수"| J["상위 5개<br>시군구 추출"]
       I -->|"결정계수,<br>회귀계수"| K["하위 5개<br>시군구 추출"]
       class I grayStyle
       class J grayStyle
       class K grayStyle
   end
   
   subgraph 시각화
       J --> L["급/역 성장<br>시군구<br>인구 변화<br>시각화"]
       K --> L
       class L grayStyle
   end
   
   style start fill:#f0f0f0,stroke:#333,stroke-width:2px
   style problem fill:#f0f0f0,stroke:#333,stroke-width:2px
   style 결과_분석 fill:#f0f0f0,stroke:#333,stroke-width:2px
   style 시각화 fill:#f0f0f0,stroke:#333,stroke-width:2px
그림 14.2: 시군구 단위 인구변동 분석

14.1 시군구 인구변동

인구변동이 많은 시군구 통계 분석을 위해 필요한 팩키지를 불러온다. 통계청 KOSIS에서 다운로드 받은 파일을 data 폴더 아래 저장하고 나서, 전처리 작업을 수행한다. kor_raw 데이터프레임에서 시작하여 janitor 패키지의 clean_names() 함수를 사용하여 변수명을 정리한다. slice(2:n()) 함수를 사용하여 2행으로 구성된 칼럼을 정리하고, pivot_longer() 함수를 사용하여 연도별 인구수 자료구조를 변환한다. 열 이름은 “연도”, 값은 “인구수”로 지정한다. set_names() 함수를 사용하여 변수명을 “시도명”, “시군구”, “연도”, “인구수”로 확정하고, fill() 함수를 사용하여 시도명 결측값을 채워 넣는다. filter() 함수를 사용하여 시군구가 “소계”인 행을 제거하고, 또 다른 filter() 함수를 사용하여 시군구가 특정 값(강원도, 경기도 등)에 속하지 않는 행을 제거한다. mutate() 함수를 사용하여 연도와 인구수 변수의 자료형을 숫자로 변환한다. 마지막으로 filter() 함수를 사용하여 시군구가 “북제주군”이나 “남제주군”이 아닌 행만 선택한다. 이유는 2005년 인구 데이터만 있기 때문에 제거한다.

# 0. 환경설정 -----------
library(tidyverse)
library(readxl)

# 1. 데이터 가져오기 ---------
kor_raw <- read_excel("data/도시지역_인구현황_시군구__20240331111602.xlsx", sheet="데이터", skip=0) 

# 2. 데이터 전처리 ---------
kor_tbl <- kor_raw %>% 
  # 변수명 정리
  janitor::clean_names(ascii = FALSE) |> 
  # 2 행으로 구성된 칼럼 정리
  slice(2:n()) |> 
  # 연도별 인구수 자료 구조 변환
  pivot_longer(cols = starts_with("x"), names_to = "연도", values_to = "인구수") |> 
  # 변수명 확정
  set_names(c("시도명", "시군구", "연도", "인구수")) |> 
  # 결측값 채워넣기
  fill(시도명) |>
  # 시군구 중복, 소계 제거
  filter(시군구 != "소계") |> 
  filter(!시군구 %in% c("강원도", "경기도", "경상남도", "경상북도", "광주광역시", 
                     "대구광역시", "대전광역시", "부산광역시", "서울특별시", 
                     "세종특별자치시", "울산광역시", "인천광역시", "전라남도",
                     "전라북도", "제주특별자치도", "충청남도", "충청북도")) |> 
  # 자료형 변환
  mutate(연도 = parse_number(연도),
         인구수 = parse_number(인구수)) |> 
  # 2005년 인구 데이터만 있음 -- 제거 
  filter(!시군구 %in% c("북제주군", "남제주군"))

kor_tbl
#> # A tibble: 4,248 × 4
#>   시도명     시군구  연도 인구수
#>   <chr>      <chr>  <dbl>  <dbl>
#> 1 서울특별시 종로구  2005 169315
#> 2 서울특별시 종로구  2006 166793
#> 3 서울특별시 종로구  2007 165846
#> 4 서울특별시 종로구  2008 170705
#> 5 서울특별시 종로구  2009 168603
#> 6 서울특별시 종로구  2010 170578
#> # ℹ 4,242 more rows

14.2 문제 파악

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

kor_tbl %>% 
  ggplot(aes(x=연도, y=인구수, color=시군구, group=시군구)) +
    geom_point() +
    geom_line() +
    theme_minimal() +
    scale_y_log10(labels=scales::comma) +
    theme(legend.position = "none") +
    labs(x="", y="인구수", title="시군 인구수 년도별 변화(2005 - 2022)")
그림 14.3: 전체 시군구 인구수 변화

14.3 문제해결

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

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

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

kor_sigun_tbl <- kor_tbl %>% 
  group_by(시도명, 시군구) %>% 
  nest()

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

## 데이터 + 모형 결합
kor_sigun_mod <- kor_sigun_tbl %>% 
  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),
    회귀계수    = map(tidy, ~ .x %>% filter(term == "연도") %>% select(estimate)) 
  ) |> 
  mutate(회귀계수 = map_dbl(회귀계수, ~ .x %>% pull(estimate))) |> 
  ungroup()

kor_sigun_mod |> 
  select(시도명, 시군구, 결정계수, 회귀계수) |> 
  arrange(desc(결정계수))
#> # A tibble: 236 × 4
#>   시도명     시군구 결정계수 회귀계수
#>   <chr>      <chr>     <dbl>    <dbl>
#> 1 부산광역시 영도구    0.999   -3223.
#> 2 강원도     원주시    0.994    4163.
#> 3 경상북도   경주시    0.993   -1401.
#> 4 전라남도   보성군    0.991    -807.
#> 5 경기도     양평군    0.989    2359.
#> 6 충청북도   옥천군    0.987    -326.
#> # ℹ 230 more rows

14.4 급/역 성장 시군

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

인구가 꾸준히 증가하고 있는 시군구는 경기도 광주시, 남양주시, 양평군과 강원도 원주시, 충청남도 천안시다. 수도권 인근 지역이 많이 포함되어 있다. 반면, 인구가 꾸준히 감소하고 있는 시군구는 부산 영도구, 경상북도 경주시, 전라남도 보성군, 화순군, 충청북도 옥천군으로 확인되며 부산 영도구가 가장 뚜렷하게 인구가 감소하는 것으로 확인된다.

## 급성장, 역성장 시군구 추출
positive_growth_sigun_tbl <- kor_sigun_mod %>% 
  filter(회귀계수 > 0) |> 
  top_n(5, 결정계수) %>% 
  unnest(data) %>% 
  mutate(구분="급성장")

negative_growth_sigun_tbl <- kor_sigun_mod %>% 
  filter(회귀계수 < 0) |> 
  top_n(5, 결정계수) %>% 
  unnest(data) %>% 
  mutate(구분="역성장")

growth_sigun_tbl <- bind_rows(positive_growth_sigun_tbl, negative_growth_sigun_tbl)

negative_growth_sigun_tbl |> tail()
#> # A tibble: 6 × 11
#>   시도명   시군구  연도 인구수 model  tidy     glance   결정계수 augment 
#>   <chr>    <chr>  <dbl>  <dbl> <list> <list>   <list>      <dbl> <list>  
#> 1 경상북도 경주시  2017 257903 <lm>   <tibble> <tibble>    0.993 <tibble>
#> 2 경상북도 경주시  2018 256864 <lm>   <tibble> <tibble>    0.993 <tibble>
#> 3 경상북도 경주시  2019 255402 <lm>   <tibble> <tibble>    0.993 <tibble>
#> 4 경상북도 경주시  2020 253502 <lm>   <tibble> <tibble>    0.993 <tibble>
#> 5 경상북도 경주시  2021 251889 <lm>   <tibble> <tibble>    0.993 <tibble>
#> 6 경상북도 경주시  2022 249607 <lm>   <tibble> <tibble>    0.993 <tibble>
#> # ℹ 2 more variables: 회귀계수 <dbl>, 구분 <chr>

## 급성장, 역성장 시군구 인구수 변화 시각화
growth_sigun_tbl %>% 
  mutate(구분 = factor(구분)) %>% 
  mutate(연도 = make_date(year=연도)) %>% 
  ggplot(aes(x=연도, y=인구수, color=구분, group=시군구)) +
    geom_point() +
    geom_line() +
    theme_minimal() +
    scale_y_log10(labels=scales::comma) +
    scale_x_date(date_labels = "%y") +
    theme(legend.position = "none") +
    facet_wrap(구분~시군구, nrow=2, scale="fixed") +
    labs(x=NULL, 
         y="인구수", 
         title="시군구 인구수 년도별 변화(2005 - 2022)") +
    scale_color_manual(values=c("역성장"="red", "급성장"="blue"))
그림 14.4: 급성장, 역성장 시군구 인구수 변화 시각화

반대로 회귀분석 결정계수(\(R^2\))가 낮은 시군구는 장기적인 인구변화를 예측하기 어렵다는 것을 의미한다. 따라서, 이러한 시군구는 인구변화에 대한 정책을 수립할 때 주의가 필요하다.

low_rsquare_sgg <- kor_sigun_mod %>% 
  top_n(5, -결정계수) %>% 
  unnest(data)

## 결정계수 낮은 시군구 인구수 변화 시각화
low_rsquare_sgg %>% 
  mutate(연도 = make_date(year=연도)) %>% 
  ggplot(aes(x=연도, y=인구수, color = 시군구, group=시군구)) +
    geom_point() +
    geom_line() +
    theme_minimal() +
    scale_y_log10(labels=scales::comma) +
    scale_x_date(date_labels = "%y") +
    theme(legend.position = "none") +
    facet_wrap(~시군구, nrow=1, scale="fixed") +
    labs(x=NULL, 
         y="인구수", 
         title="결정계수 낮은 시군구 인구수 년도별 변화(2005 - 2022)")
그림 14.5: 결정계수가 낮은 시군구 인구수 변화 시각화

14.5 요약

통계청 KOSIS에서 제공하는 시군구별 인구데이터를 활용하여 지방소멸 문제를 분석하는 과정을 다루고 있다. 저출산, 고령화, 수도권 인구 집중 등으로 인해 지방 지자체 인구가 급속히 감소하면서 다양한 사회적 문제가 발생하고 있는 상황에서, 인구 소멸 위험이 높은 시군구를 파악하여 선제적으로 대응하는 것이 중요하다.

통계청 KOSIS에서 2005년부터 2022년까지의 시군구별 인구데이터를 확보한 후, 데이터 전처리 과정을 거쳐 분석에 활용한다. 탐색적 데이터분석을 통해 시군구 간 인구 편차가 큰 문제를 파악하고, 이를 해결하기 위해 티블 자료구조와 함수형 프로그래밍을 도입한다. 시군구별로 선형회귀모형을 적용하고 broom 패키지를 사용하여 데이터와 모형 결과를 결합한 후, 결정계수와 회귀계수를 기준으로 급성장/역성장 시군구를 파악한다.

분석 결과, 경기도 광주시, 남양주시, 양평군과 강원도 원주시, 충청남도 천안시 등 수도권 인근 지역이 인구 급성장 지역으로 나타난 반면, 부산 영도구, 경상북도 경주시, 전라남도 보성군, 화순군, 충청북도 옥천군 등이 인구 감소가 뚜렷한 지역으로 확인되었다. 한편, 회귀분석 결정계수가 낮은 시군구는 장기적인 인구변화 예측이 어려운 만큼 정책 수립 시 주의가 필요함을 시사하고 있다.


  1. 도시지역 인구현황(시군구) 다운로드 링크↩︎