주제

1. 데이터 마이닝 복습
2. 데이터 웨어하우스
3. 트래픽 신호 시계열 데이터 실습
4. 글로벌 인구 통계 추세 데이터 실습

 

1. 데이터 마이닝 복습

  • 데이터 마이닝이란?
    • 대용량 데이터 내에 존재하는 관계, 패턴, 규칙을 탐색. 이로부터 유용한 지식을 추출하는과정
  • 데이터 선택 → 전처리 → 데이터 변환 → 데이터 마이닝 → 해석 및 평가

1.1 데이터 마이닝 프로세스

1. 데이터 수집 및 통합

  • 목적으로 하는 문제를 풀기 위한 다양한 데이터를 수집
  • 데이터 통합 과정 :
    • 같은 종류의 데이터라면 일관된 형식으로 만드는 과정이 필요
    • 크롤링 과정으로 생성된 DOM 구조 제거
    • 이미지 데이터의 경우 크기 조절
  • 데이터 품질 관리 :
    • 데이터 검증 및 정화(오류, 중복 수정 및 제거)
    • 완결성 검사(누락 데이터 서칭 및 핸들링, 제거 혹은 가상의 값으로 대체)
    • 모니터링(품질을 지속적으로 모니터링, 업데이트로 인한 버전 관리)

2. 데이터 전처리

  • 노이즈 및 오류 제거
    • 노이즈로 인한 이상치 데이터를 확인(IQR, 이상치 알고리즘 결과 등)
    • 수집 과정에서의 이상 상태로 인한 오류 데이터 존재 가능
    • 식별된 이상치 혹은 오류 데이터는 제거 혹은 수정
  • 데이터 정규화
    • 데이터 스케일을 일치시키는 과정
    • 서로 다른 데이터 사이의 일치 뿐 아니라 같은 데이터 내에서도 통일성을 위해 정규화를 진행

3. 데이터 마이닝 기법 적용

  • 수집한 데이터에 특화된 데이터 분석 방법론을 적용
  • 유의미한 패턴과 관계, 통찰을 도출하는 방법을 사용
  • 가장 좋은 방법은 비슷한 데이터를 분석한 사례를 확인하는 것.
  • 주요 마이닝 기법 :
    • 분류, 클러스터링, 예측, 잠재적 의미 표면화 등

4. 데이터 마이닝 결과 분석

  • 데이터에서 인사이트를 얻고 이를 바탕으로 의사 결정과 같은 과정에 사용
  • 주의할 점 :
    • 모델 평가 과정이 존재한다면 모델을 평가하는 평가 수치가 의사 결정에 도움이 되는 평가인지를 판단
    • 평가한 데이터가 의미 있는 데이터인지 확인이 필요함
  • 평가 과정 없이 사람의 직관과 판단이 들어가야 한다면 원본 데이터에 특이성과 같은 편향에서 자유로운지 직관에 대한 위험성 확인 필요

 

2. 데이터 웨어하우스

  • 기업 내부에서 움직이는 데이터의 흐름을 효율적으로 컨트롤할 수 있게 하는 데이터가 모이는 창고
  • 조직이 수집한 데이터를 모두 저장함(과거 정보까지)
  • 전통적으로 정형 데이터를 저장하고 관리했지만 비정형 데이터를 처리하고 저장하는 기능으로 통합

2.1 데이터 웨어하우스 vs. 데이터 베이스

  • 데이터 베이스 :
    • 실시간 데이터 처리와 트랜잭션 관리에 중점을 둠
    • 일상적인 업무 및 응용 프로그램에 필요한 현재의 데이터를 저장 및 관리
    • 데이터의 신속한 read와 write의 목적을 갖고 있음
    • 데이터 소비처 혹은 생산처에서 만들어지고 관리되는 대상
    • 접근 사용자 : 다수의 사용들이 동시에 입력 및 수정 가능
  • 데이터 웨어하우스 :
    • 대규모 데이터를 통합, 분석, 보고하는데 사용되는 시스템
    • 과거의 데이터도 포함하고 있음
    • DB의 데이터가 주기적으로 모여 만들어짐
    • 접근 사용자 : 조직 내 특정 그룹의 사용자에게만 제한

2.2 데이터 웨어하우스의 구조

  • ETL :
    • 원천 데이터 소스에서 데이터를 추출
    • 저장할 형태에 맞춰 변형
    • 데이터 웨어하우스 중앙 데이터 저장소로 적제
  • 중앙 데이터 저장소 :
    • ETL 처리된 데이터가 쌓이는 저장소
  • 메타 데이터 :
    • 데이터가 쌓이면서 만들어지는 추가 정보
    • 원천 데이터의 장소, 중앙 데이터 저장소의 크기 및 구성 방법 등
  • 접근 :
    • 사용자의 데이터 저장소와의 상호작용 지원

2.3 데이터 마트(Data Mart)

  • 데이터 마트 : 요청에 맞는 작은 데이터 집합을 제공하는 곳
  • 과거 데이터를 포함해 분석과 보고가 목적
  • 필수 사항은 아니지만 조직 내부에서 사업적 분석을 통해 인사이트를 얻고자 많이 사용함
  • 특징 :
    • 부서 중심적 & 주제 중심적 :
      • 특정 부서나 특정 주제에 맞춰서 설계됨
      • 항상 준비된 것이 아니라 주제에 맞는 부서의 요청이 있을 때 만들어짐
    • 데이터 집중도 증가
      • 관련 있는 데이터만 집중적으로 포함하기 때문에 사용자 그룹이 필요로 하는 정보를 빠르고 쉽게 확인 가능
    • 효율적 운영 및 사용자 친화성
      • 큰 데이터 웨어하우스 시스템의 일부로 존재
      • 집중도 있는 데이터의 최적화된 집합
      • 필요한 데이터에 대한 간단한 쿼리와 간단한 분석 진행 가능

 

3. 트래픽 신호 시계열 데이터 실습

  • 사용할 데이터는 케글의 미국 미네소타 트윈 시티 메트로 지역의 교통 트래픽 데이터(링크)
  • 2015년 7월 ~ 9월 사이의 임의의 시간 동안 교통 센서 데이터 포함
  • 시계열 데이터에서 중요한 성분은 시간(timestamp)
  • 이 값들은 서로 시간적 계산이 가능함
    • 시간차 계산, 시간 범위 세팅, 시간 기준으로 리샘플링 등
  • 판다스의 to_datetime함수를 활용하여 가능한 시계열 데이터 내에 있는 시간 관련 값을 datetime으로 변경
# datetime 형태로 변경
TravelTime_387['timestamp'] = pd.to_datetime(TravelTime_387['timestamp'])
TravelTime_451['timestamp'] = pd.to_datetime(TravelTime_451['timestamp'])
occupancy_6005['timestamp'] = pd.to_datetime(occupancy_6005['timestamp'])
occupancy_t4013['timestamp'] = pd.to_datetime(occupancy_t4013['timestamp'])
speed_6005['timestamp'] = pd.to_datetime(speed_6005['timestamp'])
speed_7578['timestamp'] = pd.to_datetime(speed_7578['timestamp'])
speed_t4013['timestamp'] = pd.to_datetime(speed_t4013['timestamp'])

 

plotly

  • 데이터 시각화를 위해 사용되는 패키지
  • 사용자가 시계열 데이터를 쉽게 탐색할 수 있는 기능을 제공
    • 줌 인/아웃 및 슬라이딩
    • 여러 변수 간의 관계 탐색
    • 마우스 오버로 정보 확인 등
import plotly.express as px

# Plotly를 사용하여 시각화
fig = px.line(occupancy_6005, x='timestamp', y='value', title='Time vs Occupancy 6005')
fig.update_xaxes(rangeslider_visible=True)
fig.show()

슬라이더를 통해 시계열 데이터의 시각화가 가능해짐

 

 

시계열 데이터와 주기

  • 시계열 데이터는 보통 적지 않은 경우로 주기성을 갖는 경우가 있음
  • 주기를 잘 이용하면 시계열 분석에서 의미 있는 통찰을 확인할 수 있음
    • 매 일을 기준으로 : 출/퇴근 교통량 분석, 일일 웹사이트 트래픽 등
    • 매 주를 기준으로 : 주간 판매량, TV 프로그램 시청 현황 등
    • 매 달을 기준으로 : 월 판매량, 신용카드 소비 패턴
    • 계절을 기준으로 : 농작물 가격, 패션 트렌드, 관광 패턴 등
  • 교통량 데이터이므로 하루를 기준으로 반복될 것이라 예상
  • 원본 데이터의 시간 정보를 바탕으로 시간(Hour) 정보를 추출
  • 추출된 시간 정보를 활용해 동일한 시간 정보가 같은 데이터끼리 통합
  • 0 ~ 24시간 데이터끼리 평균을 취해 최종 주기 데이터를 생성
# 시간대별 그룹화
occupancy_6005['hour'] = occupancy_6005['timestamp'].dt.hour
occupancy_6005.head()

# 시간대별 평균 계산
average_occupancy_by_hour = occupancy_6005.groupby('hour')['value'].mean().reset_index()
average_occupancy_by_hour

6005 센서의 시간별 데이터 시각화

주기 데이터 시각화(히트맵 활용)

  • 하루 주기성과 주간 주기성을 기준으로 확인
  • 필요한 주기가 2종류이므로 그룹 및 평균 계산 과정이 필요함
# occupancy_6005 데이터를 사용하여 필요한 2 종류의 주기 데이터 확보
occupancy_6005['day_of_week'] = occupancy_6005['timestamp'].dt.dayofweek # 0 : 월요일 ~ 6 : 일요일
occupancy_6005['hour'] = occupancy_6005['timestamp'].dt.hour

# day_of_week 값과 hour 값을 활용해 히트맵 생성

# 요일과 시간별 평균 occupancy 값 생성
occupancy_heatmap_data = occupancy_6005.groupby(['day_of_week', 'hour'])['value'].mean().unstack()
occupancy_heatmap_data

import numpy as np
import seaborn as sns

# 히트맵 그리기
plt.figure(figsize=(12, 6))
sns.heatmap(occupancy_heatmap_data, cmap="YlGnBu", annot=False)
plt.title('Heatmap of occupancy_6005')
plt.xlabel('Hour of Day')
plt.ylabel('Day of Week')
plt.yticks(np.arange(7), ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], rotation=0)
plt.show()

히트맵

계절성 분석

  • 특정 주기를 갖는 데이터는 계절성 분석을 진행할 수 있음
    • 주, 달, 분기, 계절, 년 등의 어떠한 시간적 주기를 계절성 주기라고 함
  • 시계열 데이터의 성분 :
    • Trend
      • 시간에 따른 데이터 흐름
      • 전반적인 데이터 경향성
    • Seasonal
      • 어떠한 주기 때문에 발생되는 데이터 변동
    • Residual
      • Trend와 Seasonal로 예측되지 못하는 추가 변동분
      • 노이즈 혹은 비 주기적 정보로 인한 부분
    • 원래 데이터 = Trend + Seasonal + Residual
  • 사용자가 갖고 있는 도메인 지식적 주기 이외의 새로운 주기 혹은 인사이트를 찾아낼 수 있음

Trend, Seasonal, Residual

  • Trend
    • 데이터 전반에 걸쳐 보여지는 일반적인 경향성
    • 시간적 구간(window)을 잡아 해당 구간의 평균값 활용
    • 윈도윙 방식으로 시간의 축 방향으로 연속적으로 계산
  • Seasonal
    • 데이터에 존재하는 주기적인 성분을 측정
    • 원본 데이터에서 Trend 파트를 제거하고 사용자가 제공한 주기 단위로 값의 평균을 활용
  • Residual
    • Trend와 Seasonal 값으로 설명되지 않는 값
    • 원본 데이터 - Trend - Seasonal 과정으로 도출
    • 이 값이 너무 크면 :
      • 사용자가 제공한 주기가 터무니 없을 수 있음
      • 계절성 분석으로 분석하기에 데이터가 너무 복잡하거나, 노이즈와 이상치에 심한 영향을 받은 데이터일 수 있음
  • 데이터 전처리
# 데이터 전처리
# 사용하는 패키지는 timestamp를 index로 받아야 함
print('원래 데이터')
display(occupancy_6005.head())

occupancy_6005.set_index('timestamp', inplace=True)
print('index 대체 데이터 ')
display(occupancy_6005.head())

# 일별 데이터 평균으로 재표본화하고 결측치 처리
# 일별 데이터를 모아서 평균을 사용
# 만약 데이터가 없어 NaN이 존재한다면 ffill 방식으로 데이터를 대체
# ffill : forward fill. 바로 앞의 유효한 데이터로 NaN 데이터를 대체

occupancy_6005_resample = occupancy_6005['value'].resample('D').mean().fillna(method='ffill')

df = pd.DataFrame({'A': [1, 2, None, 4, 5, None, 7]})

# 결측치를 앞 방향으로 채우기
fill_df = df.fillna(method='ffill')

 

  • 시계열 분해 후 시각화
# 필요한 라이브러리 재로드
from statsmodels.tsa.seasonal import seasonal_decompose

# 시계열 분해
result = seasonal_decompose(occupancy_6005_resample, model='additive', period=4) # period : 사용자가 지정하는 주기, 4일을 기준

# 분해 결과 시각화
fig_decompose = result.plot()
fig_decompose.set_size_inches(14, 10)
plt.show()

시계열 분해 결과 시각화

  • 시간 단위로 재표본하여 결측치 처리 후 분해
# 시간 단위로 재표본화하여 결측치 처리
occupancy_6005_resample_hourly = occupancy_6005['value'].resample('H').mean().fillna(method='ffill')

# 시계열 분해 (시간 단위 주기로 가정)
result_hourly = seasonal_decompose(occupancy_6005_resample_hourly, model='additive', period=24) # 24 시간을 기준으로 주기 설정

# 분해 결과 시각화
fig_decompose_hourly = result_hourly.plot()
fig_decompose_hourly.set_size_inches(14, 10)
plt.show()

 

  • plotly로 계절성 분석 결과 확인
# 필요한 라이브러리 임포트
from plotly.subplots import make_subplots

# Plotly를 이용한 대화형 시각화
fig = go.Figure()
fig = make_subplots(rows=4, cols=1, subplot_titles=('Original', 'Trend', 'Seasonality', 'Residual'))

# 원본 데이터 서브플롯 추가
fig.add_trace(go.Scatter(x=result_hourly.observed.index,
                         y=result_hourly.observed,
                         mode='lines', name='Original'), row=1, col=1)

# 추세 컴포넌트 서브플롯 추가
fig.add_trace(go.Scatter(x=result_hourly.trend.index,
                         y=result_hourly.trend,
                         mode='lines', name='Trend'), row=2, col=1)

# 계절성 컴포넌트 서브플롯 추가
fig.add_trace(go.Scatter(x=result_hourly.seasonal.index,
                         y=result_hourly.seasonal,
                         mode='lines', name='Seasonality'), row=3, col=1)

# 잔차 컴포넌트 서브플롯 추가
fig.add_trace(go.Scatter(x=result_hourly.resid.index,
                         y=result_hourly.resid,
                         mode='lines', name='Residual'), row=4, col=1)

# 레이아웃 업데이트
fig.update_layout(height=600,
                  width=800,
                  title_text="Seasonal Decompose using Plotly one by one")

# 그래프 표시
fig.show()

 

  • 합친 결과

 

 

 

4. 글로벌 인구 통계 추세 데이터 실습

  • 사용할 데이터는 케글의 글로벌 인구 통계 추세 데이터 (링크)
  • 1950년부터 2020년까지 세계적인 인구 통계적 추세 정보를 담고 있음
  • 총 54개의 통계적 항목이 존재함
  • 타겟 데이터는 특정 년도 1월의 전체 인구수를 나타내는 항목인 TPopulation1Jan을 선택
  • 지도를 그리기 위해 나라별 위도와 경도 정보가 필요 → 실습을 위해 따로 제공

특정 해의 인구 관련 지도 그래프 그리기

  • 2020년의 인구 수치를 기반으로 지리적 데이터를 지도에 시각화
  • folium이라는 파이썬 패키지 사용
  • 지도 위에 원을 이용해 인구수를 시각적으로 표시
    • 원 클릭시 정보를 제공하기 위해 popup argument 전달
# 기본적인 비어있는 지도 생성!
import folium
m = folium.Map(location=[20, 0], zoom_start=2) # location : 초기 지도의 중심 위치 (위도, 경도)
                                               # zoom_start : 지도의 초기 확대 레벨
m
m = folium.Map(location=[20, 0], zoom_start=2)

for idx, row in country_data_2020.iterrows():
    # 원의 크기는 인구에 비례하도록 설정
    radius = row['TPopulation1July'] # 1단위 : 천 명
    # 원을 선택 시, 뜨는 정보 표시 (나라 & 인구수)
    popup_message = f"{row['Location']}: {row['TPopulation1July']}"

    # 원을 지도에 추가
    folium.Circle(
        location=[row['latitude'], row['longitude']],
        radius=radius,
        color='blue',
        fill=True,
        fill_color='blue',
        popup=popup_message  # 팝업으로 정보 표시
    ).add_to(m)

# 지도 표시
m

인터랙티브한 지도가 생성됨

  • 히트맵으로 인구수 표시
    • min_opacity : 투명도 설정
    • radius : 데이터 포인트의 영향 반경
    • blur : 블러 효과, 값이 크면 부드럽게 표현
    • gradient : 히트맵의 색상을 사용자가 정의
import folium
from folium.plugins import HeatMap

# 기본 지도 생성
m = folium.Map(location=[20, 0], zoom_start=2)

# 히트맵 데이터 준비 (위도, 경도, 인구수를 가중치로 사용)
heat_data = [[row['latitude'], row['longitude'], row['TPopulation1July']] for idx, row in country_data_2020.iterrows()]

# 히트맵 추가
HeatMap(heat_data,
        min_opacity=0.5,
        radius=30,
        blur=25).add_to(m)

m

히트맵을 이용한 인구수 통계 시각화

시간에 따른 인구수 통계값 확인

  • 파이썬 패키지 IPyWidgets를 활용하면 사용자의 인터랙션의 결과로 특정 값을 반환 받을 수 있는 위젯을 사용할 수 있음
  • 필요한 위젯을 설정
    • 연도를 컨트롤하는 위젯
    • 지도를 그리는 위젯
  • 2020년만이 아닌 전체 데이터를 호출
    • continuous_update=False : 마우스가 슬라이더를 변화시킨 후에 클릭이 끝나야만 업데이트를 함
import ipywidgets as widgets
from IPython.display import display, clear_output

# 연도 선택을 위한 슬라이더 위젯 생성
year_slider = widgets.IntSlider(
    value=2020,
    min=1950,
    max=2022,
    step=1,
    description='Year:',
    continuous_update=False # 값 변화가 있는 그 순간에 변화가 업데이트 되지 않도록 함. 변화가 멈춰야 값을 업데이트
)

# 지도 출력을 위한 Output 위젯
map_output = widgets.Output()

data_clean = country_data.dropna(subset=['latitude', 'longitude'])

def update_map(year):
    with map_output:
        # 출력 영역을 클리어하고 새 지도를 표시
        clear_output(wait=True)
        # 지도 생성
        m = folium.Map(location=[20, 0], zoom_start=2)
        # 해당 연도의 데이터 필터링
        data_year = data_clean[data_clean['Time'] == year]

        # 지도에 데이터 추가
        for idx, row in data_year.iterrows():
            radius = np.sqrt(row['TPopulation1July']) * 1000
            folium.Circle(
                location=[row['latitude'], row['longitude']],
                radius=radius,
                color='blue',
                fill=True,
                fill_color='blue',
                popup=f"{row['Location']}: {row['TPopulation1July']}"
            ).add_to(m)

        # 지도 표시
        display(m)

def on_year_change(change):
    update_map(change['new'])

year_slider.observe(on_year_change, names='value')

# 초기 지도 및 슬라이더 위젯 표시
display(year_slider)
update_map(2020)
display(map_output)

화면 상단에 슬라이더가 생긴 모습

 

Bar plot 활용

  • seaborn을 이용하여 바 플롯을 그려 수의 양적 차이를 직관적으로 시각화
  • 나라별 고유의 색을 지정하여 보여줄 수 있음
import seaborn as sns
import ipywidgets as widgets
import matplotlib.pyplot as plt
from IPython.display import display, clear_output

year_slider = widgets.IntSlider(
    value=2020,
    min=1950,
    max=2022,
    step=1,
    description='Year:',
    continuous_update=False
)

data_clean = country_data.dropna(subset=['latitude', 'longitude'])

# 전체 데이터셋에서 고유한 나라들 추출
unique_countries = data_clean['Location'].unique()

# 고유한 나라들에 대한 색상 매핑 생성
colors = sns.color_palette("tab20", len(unique_countries))
country_colors = dict(zip(unique_countries, colors))

output = widgets.Output()

def plot_top10_population(year):
    with output:
        clear_output(wait=True)
        data_year = data_clean[data_clean['Time'] == year]
        top10_data = data_year.nlargest(10, 'TPopulation1July')

        # 상위 10개 국가에 대한 색상 할당
        top10_colors = {country: country_colors[country] for country in top10_data['Location']}

        # 바 차트 그리기
        plt.figure(figsize=(10, 6))
        sns.barplot(x='TPopulation1July', y='Location', data=top10_data, hue='Location', legend=False,
                    palette=[top10_colors[country] for country in top10_data['Location']])
        plt.title(f'Top 10 Countries by Population in {year}')
        plt.xlabel('Population (in thousands)')
        plt.ylabel('Country')
        plt.show()


def on_year_change(change):
    plot_top10_population(change['new'])

year_slider.observe(on_year_change, names='value')

display(year_slider)
plot_top10_population(2020)
display(output)

슬라이더로 연도별 인구수 확인 가능

 

 


느낀점

전에 멘토님이 시계열 데이터는 다루기가 까다롭다고 하셨던 기억이 있었는데 실습을 해보니까 확실히 복잡하고 어려운 것 같다. plotly로 시계열 데이터를 시각화해보니까 훨씬 직관적이고 인터랙션이 가능하다는 점이 큰 장점인 것 같다. 시계열 데이터는 많이 접해보고 실습해보면서 익숙해져야할 것 같다.

+ Recent posts