연도별 한국 인구 분포의 동적 시각화 - matplotlib 그래프에 애니메이션 효과 넣기

소환인·2023년 11월 17일
0

스터디노트

목록 보기
25/48

오늘의 목표는 간단합니다. 아래와 같은 그래프를 그리는 것입니다.

한국의 2015년부터 2022년까지의 연령별, 성별 인구 분포를 담은 이 애니메이션은, 데이터가 단순히 숫자의 나열이 아닌, 시간의 흐름 속에서 변화하는 모습을 보여줍니다.

시각화 과정은 유튭 영상을 보고 참고하였습니다. 영상에서는 미국의 연도별 인구 변화를 다루었지만, 저는 한국의 세대별 인구 데이터로 시각화 작업을 진행했습니다.

데이터 전처리

시각화에 앞서 데이터를 전처리하는 과정은 다음과 같습니다.

  1. 데이터 로드: 먼저 Excel 파일에서 인구 데이터를 로드합니다. 이 데이터에는 연령별 및 성별 인구 정보가 포함되어 있습니다. 통계사이트에서 받은 엑셀 파일을 불러오면 흔히 볼 수 있는 형태입니다. 엑셀에서의 셀 병합으로 인해 컬럼제목이 제대로 설정되어 있지 않습니다.
df = pd.read_excel('../data/연령_및_성별_인구_2015~2022.xlsx', skipfooter=2)

  1. 불필요한 열 제거: 행정구역별 열을 제거하여 필요한 데이터만 남깁니다.

    del df['행정구역별(읍면동)']
  2. 연도별 데이터 분리: 각 연도에 해당하는 열을 분리하여 연도별 데이터프레임을 생성합니다. 이 과정에서 separate_by_year라는 사용자 정의 함수를 사용합니다.

    def seperate_by_year(df, year):
        str_year = str(year)
        df_y = df.loc[:, str_year:str_year+'.7']
        df_age = df[['연령별']]
        ndf = pd.concat([df_age, df_y], axis=1)
        ndf.columns = ndf.iloc[0]
        ndf = ndf[2:].reset_index(drop=True)
        ndf['기준년도'] = year
        return ndf
    
    df_list = []
    for y in range(2015, 2023):
        temp = seperate_by_year(df, y)
        df_list.append(temp)
        
    result = pd.concat(df_list, ignore_index=True)
  3. 데이터 타입 조정: 데이터프레임의 각 열에 적절한 데이터 타입을 할당합니다. 이를 통해 데이터 처리의 정확성을 높입니다.

    int_cols = ['총인구(명)', '총인구_남자(명)', '총인구_여자(명)', '내국인(명)', '내국인_남자(명)', '내국인_여자(명)']
    float_cols = ['총인구_성비', '내국인_성비']
    
    for col in float_cols:
        result[col] = result[col].astype('float')
        
    for col in int_cols:
        result[col] = result[col].astype('int')
  4. 데이터 필터링: 특정 연령대에 해당하는 데이터만 선택하여 분석에 활용합니다.

    condition = (result['연령별'].isin(['15세미만', '15~64세', '65세이상', '85세이상']) == False)
    result = result[condition]
    result.reset_index(drop=True, inplace=True)

이러한 전처리 과정은 데이터를 분석하고 시각화하는 데 필수적인 단계입니다. 이 과정을 통해 데이터가 분석 및 시각화에 적합한 형태로 가공되며, 이를 바탕으로 효과적인 시각화를 수행할 수 있습니다.

막대그래프 반전 기법

우리가 일반적으로 보는 막대그래프는 양의 값을 통해 높이나 길이를 나타냅니다. 남성과 여성의 인구 데이터를 서로 다른 방향으로 함께 나타내기 위해선 작은 트릭이 필요합니다. 여성의 인구에 -1을 곱해주면 그래프가 여성 인구 그래프가 반대 방향으로 표현됩니다. 이렇게 반전된 막대그래프는 성별에 따른 인구 분포의 차이를 더욱 명확하게 보여줍니다.

matplotlib 라이브러리의 barh 함수를 사용하여 수평 막대그래프를 그릴 수 있습니다. 여기서 남성 데이터는 표준 방향으로, 여성 데이터는 -1을 곱해 반대 방향으로 표시했습니다. 이를 통해, 동일한 축을 기준으로 양쪽 방향으로 데이터를 나타내며 대조를 이루는 효과를 줄 수 있습니다.

# 남성 데이터는 표준 방향, 여성 데이터는 반전하여 표시
males = plt.barh(y=filtered['연령별'], width=filtered['총인구_남자(명)'], color='#00ADB5')
females = plt.barh(y=filtered['연령별'], width=-filtered['총인구_여자(명)'], color='#AAD8D3')

애니메이션 효과로 시간의 흐름 담기

정적인 그래프만으로는 시간에 따른 변화를 포착하기 어렵습니다. FuncAnimation을 이용해 각 연도별 변화를 애니메이션으로 표현할 수 있습니다. 이 기법을 사용하면, 연도가 변함에 따라 그래프의 바가 움직이는 것을 볼 수 있어, 시간에 따른 인구 변화의 흐름을 한눈에 파악할 수 있습니다.

animation = FuncAnimation(fig, animate, frames=range(result['기준년도'].min(), result['기준년도'].max()+1))
animation.save('../data/population.gif', dpi=300, writer=PillowWriter(fps=2))

단순한 눈요기가 아니라 필요한 경우에 애니메이션은 데이터로 이야기를 전달할 수 있게 해줍니다. 특히 사회적 이슈나 변화의 추이를 보여줄 때, 이러한 동적인 시각화 방법은 더욱 유용하다고 생각합니다.

전체코드는 이렇습니다.

# 주피터 노트북에서 사용한 matplotlib과 필요한 라이브러리를 불러옵니다.
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib import rcParams

# 폰트 설정을 위한 코드입니다.
rc('font', family='Malgun Gothic')
rcParams['axes.unicode_minus'] = False
%matplotlib inline

# 시각화를 위한 그래프의 기본 설정을 합니다.
fig, ax = plt.subplots(figsize=(15, 8))

# 각 연도별로 데이터를 필터링하고 그래프를 그리는 함수입니다.
def animate(year):
    ax.clear()  # 이전 그래프를 지웁니다.
    filtered = result[result['기준년도']==year]  # 연도별 데이터 필터링
    
    # 남성 데이터는 표준 방향, 여성 데이터는 반전하여 막대그래프를 그립니다.
    males = plt.barh(y=filtered['연령별'], width=filtered['총인구_남자(명)'], color='#00ADB5')
    females = plt.barh(y=filtered['연령별'], width=-filtered['총인구_여자(명)'], color='#AAD8D3')

    # x축의 범위를 설정합니다.
    ax.set_xlim(-3_000_000, 3_000_000)

    # 막대 위에 인구 수를 표시합니다.
    ax.bar_label(males, padding=3, labels=[f'{round(value, -3):,}' for value in filtered['총인구_남자(명)']])
    ax.bar_label(females, padding=3, labels=[f'{round(value, -3):,}' for value in filtered['총인구_여자(명)']])

    # 그래프의 외곽선을 제거하고 y축 눈금을 숨깁니다.
    for edge in ['top', 'right', 'left', 'bottom']:
        ax.spines[edge].set_visible(False)
    ax.tick_params(left=False)
    ax.get_xaxis().set_visible(False)

    # 범례와 타이틀을 추가합니다.
    ax.legend([males, females], ['남자', '여자'])
    ax.set_title(f'{year}년도 연령별/성별 인구', size=18, weight='bold')

# FuncAnimation을 사용하여 각 연도별 변화를 애니메이션으로 표현합니다.
animation = FuncAnimation(fig, animate, frames=range(result['기준년도'].min(), result['기준년도'].max()+1))
animation.save('../data/population.gif', dpi=300, writer=PillowWriter(fps=2))  # 애니메이션을 GIF 파일로 저장합니다.
plt.show()  # 그래프를 보여줍니다.
profile
돌고돌아

0개의 댓글