Data Analysis

인구데이터 지도 시각화, folium, choropieth map,

김심슨 2025. 6. 1. 15:01

대한민국의 시군구별 인구 데이터를 folium 라이브러리를 사용하여 단계구분도로 시각화하는 예제

단계 구분도(choropieth map) : 지역별 통계를 색으로 구분한 지도 *folium 외부 라이브러리 필요 

 

목적 : 

지역별 인구수 분포를 시각적으로 표현해 공간적 특성 이해 

지역별 인구 밀도의 편차, 지역 간 비교 분석

 

<필수 라이브러리>

webbrowser, json, pandas, folium

browser_open 함수 : 생성된 지도를 html 파일로 저장하고 웹 브라우저에서 자동 열기 

 

< 필요 데이터>

대한민국 시군구별 경계좌표 데이터 geojson = 지형 데이터)

시군구별 인구데이터

서울시 동별 경계 지도 데이터

동별 외국인 인구 데이터

구 경계선 데이터

 

< 파이참에서 웹브라우져를 통해 지도 표시 >

import webbrowser
def browser_open (f_map, path) :
	html_page = f'{path}'
    f_map.save(html_page)
    webbrowser.open(html_page)

 

<전체 과정의 흐름 >

1. GeoJSON 파일 (지도 경계 좌표) 로딩 (json 라이브러리 사용)

2. 시군구 인구 데이터 (csv) 로딩 및 전처리 (pandas 라이브러리 사용)

3. 지도에 데이터 시각화 (folium)

4. 지도 웹 브라우져로 보기 (webbrowser)

# geovisulalization1.py
# 대한민국 시군구별 인구데이터 지도시각화

# pycharm에서 웹브라우져를 통해 지도 표시 함수
import webbrowser
def browser_open(f_map, path):
    html_page = f'{path}'
    f_map.save(html_page)
    webbrowser.open(html_page)

# 대한민국 시군구 경계 좌표 데이터
import json
geo = json.load(open('assets/SIG.geojson', encoding="utf-8"))
#print(geo)
#print(type(geo)) # dictionary

# 행정구역 코드
print(geo['features'][0]['properties']['SIG_CD'])

# 위도, 경도 좌표
print(geo['features'][0]['geometry']['coordinates'])

# 시군구별 인구데이터
import pandas as pd
df_pop = pd.read_csv('assets/Population_SIG.csv')
df_pop.info()

# code를 int64타입에서 문자타입으로 변환
df_pop['code'] = df_pop['code'].astype(str)

# 단계구분도 생성
import folium
# 지도 형태
map_sig = folium.Map(
    location = [35.95, 127.7], # 중심 위/경도
    zoom_start = 8, # 확대 레벨
    tiles = 'cartodbpositron' # 지도의 타일
)
# 지도 데이터
folium.Choropleth(
    geo_data = geo, # 지도데이터
    data = df_pop, # 인구데이터
    columns = ('code', 'pop'), # 행정구역코드, 인구수
    key_on = 'feature.properties.SIG_CD' # geo 행정구역코드를 키로 사용
).add_to(map_sig) # 지도에 데이터 추가

# 지도를 웹브라우져에 표시
browser_open(map_sig, 'geo1.html')

# 계급구간 정하기
bins = list(df_pop['pop'].quantile([0, 0.2, 0.4, 0.6, 0.8, 1]))
#print(bins)

folium.Choropleth(
    geo_data = geo, # 지도데이터
    data = df_pop, # 인구데이터
    columns = ('code', 'pop'), # 행정구역코드, 인구수
    key_on = 'feature.properties.SIG_CD', # geo 행정구역코드를 키로 사용
    fill_color = 'YlGnBu', # 채움 색상
    fill_opacity = 0.5, # 투명도
    bins = bins # 계급구간
).add_to(map_sig) # 지도에 데이터 추가

browser_open(map_sig, 'geo2.html')

 

< 코드 뜯어보기 >

import webbrowser

def browser_open(f_map, path):
    html_page = f'{path}'
    f_map.save(html_page)
    webbrowser.open(html_page)

- 지도를 html로 저장하고, 브라우저에서 자동으로 열어줄 때, 반복적으로 지도를 확인할 때 편리하게 쓰인다. 

import json
geo = json.load(open('assets/SIG.geojson', encoding="utf-8"))

지도 경계(시군구 단위) 데이터를 JSON 형태로 불러옴 (각 시군구는 좌표와 코드(SIG_CD) 를 가지고 있음)

 

print(geo['features'][0]['properties']['SIG_CD'])  # 행정구역 코드 확인
print(geo['features'][0]['geometry']['coordinates'])  # 경계 좌표 확인

 

+) 

geo의 구조 확인하고 이해하기 

geo 데이터의 "features" (key)의 첫 번째 인덱스를 가져오고 [0], "properties"를 가져온 뒤 SIG_CD를 뽑은 것

객체의 객체의 객체 구조이므로 확실하게 필요함. 

 

import pandas as pd
df_pop = pd.read_csv('assets/Population_SIG.csv')
df_pop.info()

데이터 처리+분석을 위한 필수 라이브러리 pandas 임포트 

'Population_SIG.csv"를 읽어 pandas dataframe(df_pop)으로 저장 => 각 시군구별 행정구역 코드와 인구수가 있음 

info() : 데이터 프레임의 정보(컬럼명, 타입, 결측값 등) 확인 

 

df_pop['code'] = df_pop['code'].astype(str)

code 컬럼(행정구역 코드)을 숫자형 -> 문자형 으로 변환 

GeoJSON과 연결할 때 문자형으로 통일하는 게 중요 

 

import folium
map_sig = folium.Map(
    location=[35.95, 127.7],
    zoom_start=8,
    tiles='cartodbpositron'
)

지도 시각화에 자주 사용되는 folium 라이브러리 불러오기 

map_sig : 지도 객체 생성 

중심 좌표 : 대한민국 중심부 

초기 줌 레벨 : 8 -> 전국 시군구 범위 표현에 적합 

타일 유형 : cartodbpositron : 깔끔하고 가독성 높은 스타일

 

folium.Choropleth(
    geo_data=geo,
    data=df_pop,
    columns=('code', 'pop'),
    key_on='feature.properties.SIG_CD'
).add_to(map_sig)

단계구분도 지도 추가 

시군구 경계를 기반으로 인구수 데이터를 지도 위에 색상으로 표현 

geo_data = geo : 지리적 경계 정보를 담은 GeoJSON 데이터 

data = df_pop : 분석할 실제 데이터 (인구수 데이터)

columns = ('code', 'pop') : 데이터 프레임에서 사용할 컬럼 (지역코드, 인구수)

key_on='feature.properties.SIG_CD'. : GeoJSON 데이터와 연결할 키로 사용할 행정쿠역 코드 설정 

.add_to(map_sig) : 지도 (map_sig)에 이 데이터 추가 

 

browser_open(map_sig, 'geo1.html')

지금까지 생성한 지도를 'geo1.html' 파일로 저장하고, 웹브라우저에서 표시하는 함수 호출 

 

bins = list(df_pop['pop'].quantile([0, 0.2, 0.4, 0.6, 0.8, 1]))

folium.Choropleth(
    geo_data=geo,
    data=df_pop,
    columns=('code', 'pop'),
    key_on='feature.properties.SIG_CD',
    fill_color='YlGnBu',
    fill_opacity=0.5,
    bins=bins
).add_to(map_sig)

인구 데이터에서 0% ~ 100% 백분위수 값을 계급으로 설정해 리스트로 만듦 => 데이터 분포를 계급으로 나눠 표현해 보다 직관적인 인사이트 도출 가능 

bins(계급 구간)을 활용한 단계 구분도 재 생성 

추가된 옵션 :

fill_color = "YIGnBu" : 색상 팔레트 : 노랑 -> 초록 -> 파랑 

fill_opacity=0.5 : 지도 색상의 투명도 조정 (지도 가시성 높임)

bins = bins =: 미리 정한 계급 구간을 적용 -> 데이터의 시각적 명확성 향상 

browser_open(map_sig, 'geo2.html')

계급 구간이 적용된 최종 지도를 html 파일로 저장하고 웹 브라우저에서 바로 표시 


< 인사이트 > 

인구 밀집 지역 파악 : 인구가 집중된 지역과 인구가 적은 지역을 시각적으로 즉각 식별 가능 

지역간 인구 불균형 정도 평가 : 특정 계급 간 급격한 색상 변화로 인구수 격차를 직관적으로 확인 

추가 분석 연결 가능 : 지역별 인구를 기반으로 한 인구수 격차를 직관적으로 확인 가능 

 

< 추가 >

1. GeoJSON과 데이터 연결 키 (SIG_CD) 처리 : 데이터 타입을 문자형으로 일치 시키기 

df_pop['code'] = df_pop['code'].astype(str)

 

2. 지도 스타일링 및 UX 개선 

* 지도 색상 팔레트 (fill_color)는 데이터 특성에 따라 변경 

- 인구수처럼 연속적인 변수 -> Sequential 색상 팔레트 (YIFnBu, PuBu 등)

- 양극성 데이터 -> Diverging 색상 팔레트 활용 (RdBu 등)

* 계급(bins)을 설정할 때 데이터 분포에 따라 수정 

- 균등 분할 외에도 Jenks Natural Breaks 등 통계적 접근도 활용.

 

3. 인터랙티브 지도 정보 추가 

Folium의 GeoJsonTooltip 활용해서 지역 이름과 인구스를 마우스 오버로 제공하면 편의성+활용성 up

folium.GeoJson(
    geo,
    tooltip=folium.GeoJsonTooltip(fields=['SIG_KOR_NM', 'SIG_CD'])
).add_to(map_sig)

 

< 향후 개선 방향 및 확장성 > 

1. 시계열 데이터 적용 : 특정 연도별 변화 추이를 비교 시각화 (시간적 인사이트 제공)

2. 다양한 데이터 병합 활용 : 경제 지표, 연령층 분포, 인프라 수준 등을 병합하여 복합적 분석 수행 가능 

3. 웹 기반 서비스로 확장 : 대시보드 (Ploty Dash 등)로 확장하여 실시간으로 관리하고 전달 가능