데이터 마이닝 (Data Mining)
: 대용량의 다양한 유형의 데이터 내에 존재하는 관계, 패턴, 규칙을 탐색하고, 이로부터 유용한 지식을 추출하는 과정
데이터 마이닝의 중요성
- 의사결정 강화
- 효율성 증대
- 고객 이해
- 시장 동향 예측
데이터 마이닝 프로세스
1단계. 데이터 수집 및 통합
: 목표하는 문제를 풀기 위한 다양한 데이터를 수집하는 과정
데이터 통합
- 같은 종류의 데이터라면 일관된 형식으로 만드는 과정 필요
- ex. 크롤링 과정으로 생성된 DOM 구조 제거
- ex. 이미지 데이터 크기 조절
데이터 품질 관리
- 데이터 검증 및 정화 (오류, 중복 수정/제거)
- 완결성 검사 (누락 데이터 서치/핸들링/제거/대체)
- 모니터링 (지속적으로 품질 모니터링, 업데이트로 버전관리)
2단계. 데이터 전처리
: 모델 및 분석 방법에 맞도록 데이터를 가공하는 초기 과정
노이즈 및 오류 제거
- 노이즈로 인한 이상치 데이터를 확인 (ex. IQR, 이상치 알고리즘 결과)
- 수집 과정에서의 이상 상태로 인한 오류 데이터 존재 가능
- 식별된 이상치 혹은 오류 데이터는 제거 혹은 수정
데이터 정규화
- 데이터의 스케일을 일치시키는 과정
- 서로 다른 데이터 사이의 일치를 위해 진행
- 같은 데이터 내에서도 통일성을 위해 진행
3단계. 데이터 마이닝 기법 적용
: 유의미한 패턴, 관계, 통찰을 도출할 수 있도록 수집한 데이터에 맞는 데이터 분석 방법론을 적용하는 과정
- 비슷한 데이터를 분석한 선례를 확인
- 주로 활용되는 기법 : 분류, 클러스터링, 예측, 잠재적 의미 표면화
4단계. 데이터 마이닝 결과 분석
: 데이터에서 인사이트를 얻고 이를 바탕으로 의사결정과 같은 과정에 사용
- 모델 평가 과정이 존재한다면,
- 평가 수치가 의사결정에 도움이 되는 평가인지를 경계하면서 판단해야함
- 평가한 데이터가 의미있는 데이터인지 확인해야함
- 평과 과정 없이 사람의 직관/판단이 들어가야 한다면,
- 원본 데이터에 특이성과 같은 편향에서 자유로운지 확인해야함
- 그 직관에 위험성은 없는지 확인해야함
[문제 상황] 대기업의 데이터 관리자입장에서 생각해보자.
- 다양한 부서가 존재하고 특정 부서는 데이터가 생성됨
- 임의의 부서는 다른 부서들의 데이터에 접근함
- 이때마다 데이터 호출 인터페이스를 각각 따로 만든다면 → 상당한 비용 소모, 거미집 현상(Spider Web) 발생
↓
↓
↓
[해결방법] 데이터를 생성하는 부서와 소비하는 부서 사이에 '데이터 창고'를 두고 데이터의 흐름을 컨트롤하면
기업 내부에서 움직이는 데이터의 흐름을 효율적으로 컨트롤할 수 있음
↓
↓
↓
데이터 웨어하우스 (Data Warehouse)
: 데이터가 데이터가 모이는 창고(warehouse)
- 조직이 수집한 데이터를 과거의 정보까지 모두 저장
- 기술의 발전 & 비즈니스 요구의 변화로 정형 데이터 + 비정형 데이터 모두 처리하고 저장하는 기능으로 통합
데이터 웨어하우스 (DW) vs. 데이터 베이스 (DB)
데이터 웨어하우스 (DW) | 데이터 베이스 (DB) | |
목적 | - 대규모 데이터를 통합/분석/보고하는데 사용 - 과거 데이터까지 모두 포함 |
- 실시간 데이터 처리, 트랜잭션 관리 - 일상적인 업무 및 응용 프로그램에 필요한 현재의 데이터를 저장/관리 - 데이터의 신속한 read & write가 목적 |
생성/관리 | - DB의 데이터가 주기적으로 모여서 만들어짐 | - 데이터 소비처 or 생산처에서 생성/관리되는 대상 |
접근 사용자 | - 조직 내 특정 그룹의 사용자에게만 제한 | - 다수의 사용들이 동시에 입력/수정 가능 |
데이터 웨어하우스의 구조
1. ETL
- E : 원천 데이터 소스에서 데이터를 추출(Extract)하고
- T : 저장할 형태에 맞게 변형(Transform)하고
- L : 데이터 웨어하우스 중앙 데이터 저장소로 적제(Load) 하는 과정
2. 중앙 데이터 저장소
- ETL 처리된 데이터가 쌓이는 저장소
3. 메타 데이터
- 데이터가 쌓이면서 만들어지는 추가 정보
- 원천 데이터의 장소, 중앙 데이터 저장소의 크기/구성방법 등
4. 접근
- 사용자의 데이터 저장소와의 상호작용을 지원
데이터 마트 (Data Mart)
: 데이터 웨어하우스에 있는 과거의 데이터를 보면서 주기적으로 통찰을 얻고자 하는 사람들을 위해, 데이터 웨어하우스에서 요청에 맞게 제공해주는 작은 데이터 집합
- 소비자를 위해 창고에서 물건을 마트에 가져다 두는 것과 유사한 개념
- 해당 부서에서 사용하는 DB와는 달리, 과거 데이터를 포함해 분석/보고를 목적으로 함
- write는 안하고 read
- 필수는 아니지만 조직 내부에서 사업적 분석을 통해 인사이트를 얻고자 사용됨
- 부서 중심적 & 주제 중심적
- 데이터 마트는 특정 부서/주제에 맞게 설계됨
- 항상 준비된게 X → 주제에 맞는 부서 요청이 있을때만 만들어짐
- 데이터 집중도가 매우 큼
- 관련된 데이터, 사용자 그룹이 필요로 하는 데이터만 집중적으로 포함
- 효율적 운영 & 사용자 친화성
- 필요한 데이터에 대한 간단한 쿼리 작성/분석 가능
데이터 마이닝 실습
사용 데이터
글로벌인구 통계 추세 데이터 (WPP2022 Demographic Indicators)
🔗 실습 링크 : https://www.kaggle.com/datasets/abmsayem/wpp2022-demographic-indicators
총 67개 컬럼
1단계. 데이터 로드
import pandas as pd
file_path = 'WPP2022_Demographic_Indicators.csv'
data = pd.read_csv(file_path)
data.head()
# 연도는 1950년부터,
# 2020년 이후 데이터는 사용하지 않을 예정
data['Time']
2단계. 타겟 데이터 선정
# 여기서 TPopulation1Jan 를 기준으로 분석할 예정
data = data[data['TPopulation1Jan'].notnull()]
# 지역 종류 확인
set(data['LocTypeName'].tolist())
all_countries = set(data[data['LocTypeName']=='Country/Area']['Location'].tolist())
# 나라 정보만 가져오기
country_data = data[data['LocTypeName']=='Country/Area'].copy()
3단계. 위도, 경도 정보 추가
[방법 1] API를 통해 호출하는 방법
- 너무 많이 불러오면 IP ban(제한)에 걸림
- 시간이 너무 오래 걸림
# # ###########################################################
# # 방법 1) API를 통해 호출하는 경우 시간이 오래걸리고, 제한이 걸릴 수 있음! #
# # ###########################################################
from geopy.geocoders import Nominatim
import time
# # Geopy의 Nominatim 사용을 위한 Geolocator 객체 생성
geolocator = Nominatim(user_agent="geoapiExercises")
# # 국가별 위도와 경도를 저장할 딕셔너리 생성
location_coordinates = {}
# # 중복된 위치 요청을 방지하기 위해 이미 조회된 위치를 저장할 집합
queried_locations = set()
# # 각 국가에 대해 위도와 경도 조회
for location in all_countries:
# 이미 조회된 위치는 건너뜀
if location in queried_locations:
continue
try:
# 위치에 대한 위도, 경도 정보 얻기
loc = geolocator.geocode(location)
if loc:
location_coordinates[location] = (loc.latitude, loc.longitude)
queried_locations.add(location)
except Exception as e:
print(f"Error occurred for location: {location}, error: {e}")
# 요청 사이에 일정 시간 지연을 두어 서비스 제한을 피함
time.sleep(1)
location_coordinates
[방법 2] 이미 만들어둔 위도&경도 데이터 불러오기
# ########################################
# 방법 2) 이미 만들어둔 위도 & 경도 데이터 불러오기 #
# ########################################
# 다운로드 받은 데이터 업로드 필요!
import json
with open('location_coordinates.json', 'r') as f:
location_coordinates = json.load(f)
# 데이터 프레임에 위도 및 경도 정보 추가
country_data['latitude'] = country_data['Location'].apply(lambda x: location_coordinates[x][0]
if x in location_coordinates else None)
country_data['longitude'] = country_data['Location'].apply(lambda x: location_coordinates[x][1]
if x in location_coordinates else None)
4단계. 지도에 인구수 통계값 표시 (원 활용)
1) 위도, 경도 추가 + 지도 생성
country_data_2020 = country_data[(country_data['Time'] == 2020) &
country_data['TPopulation1Jan'].notnull() &
## 뒤쪽에 열 추가
country_data['latitude'].notnull() & ## 위도
country_data['longitude'].notnull()] ## 경도
`folium.Map` : 인터렉션이 가능한 세계제도를 보여준다.
# 기본적인 비어있는 지도 생성!
import folium
m = folium.Map(location=[20, 0], zoom_start=2) # location : 초기 지도의 중심 위치 (위도, 경도)
# zoom_start : 지도의 초기 확대 레벨
m
2) 원으로 지도에 표시
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
3) 제곱근으로 원의 크기를 적절하게 조절
import numpy as np
m = folium.Map(location=[20, 0], zoom_start=2)
# 지도에 인구 데이터를 원으로 표시 (제곱근 스케일 사용)
for idx, row in country_data_2020.iterrows():
# 제곱근 스케일로 원의 크기 조정
radius = np.sqrt(row['TPopulation1July']) * 1000
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
5단계. 지도에 인구수 통계값 표시 (히트맵 활용)
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
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()]
# 히트맵 색상 조절을 위한 gradient 생성
gradient = {0.1: 'blue',
0.5: 'green',
1.0: 'red'}
HeatMap(heat_data,
min_opacity=0.5,
radius=30,
blur=25,
gradient=gradient).add_to(m)
m
6단계. 시간에 따른 인구수 통계값 확인 (IPyWidgets 활용)
- 사용자 인터랙션의 결과로 특정 값을 반환할 수 있는 위젯 사용 가능
- 슬라이더로 사용자가 움직여가면서 해당 연도의 데이터를 불러와서 지도에 그림을 그림
import ipywidgets as widgets
from IPython.display import display, clear_output
# 연도 선택을 위한 정수 기반 슬라이더(IntSlider) 위젯 생성
year_slider = widgets.IntSlider(
value=2020, # 초기값 설정
min=1950, # 최솟값
max=2022, # 최댓값
step=1,
description='Year:', # 연도별로 볼거니까
# 값 변화가 있는 그 순간에 변화가 업데이트 되지 않도록 함. 변화가 멈춰야 값을 업데이트
# 마우스를 클릭하고 놨을때 값이 업데이트됨
# True로 설정하면 클릭하고 있을때도 계속 값이 업데이트됨
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)
# on_year_chage : 지도를 다시 그리는 update_map 함수 호출
# 여기에는 변경 사항 중 새로운 값(new)을 입력으로 넣어줌
def on_year_change(change):
update_map(change['new'])
# year_slider의 변화를 감지(observe)하고, (특히 value라는 속성이 변경되면)
# 변화가 있다면 on_year_change 함수를 실행
year_slider.observe(on_year_change, names='value')
# 초기 지도 및 슬라이더 위젯 표시
display(year_slider)
update_map(2020)
display(map_output)
7단계. 연도 변경에 따른 인구 변화 확인 (Bar Plot 활용)
- 수의 양적 차이를 직관적으로 보여주는 그래프
- Bar Plot을 위젯 내부에 그려줌
- `seaborn` 패키지 활용 : `Matplotlib`의 기본 기능에 추가 개선된 기능 제공, API 쉽게 사용, 많은 스타일 및 테마
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'])
# 바 차트 출력을 위한 Output 위젯
output = widgets.Output()
# 바 차트를 그리는 함수
def plot_top10_population(year):
with output:
clear_output(wait=True)
# 해당 연도 데이터 필터링
data_year = data_clean[data_clean['Time'] == year]
# 상위 10개 국가의 인구 데이터 추출
top10_data = data_year.nlargest(10, 'TPopulation1July')
# 바 차트 그리기
plt.figure(figsize=(10, 6))
sns.barplot(x='TPopulation1July', y='Location', data=top10_data)
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)
나라마다 고유한 색상을 지정해서 변화를 직관적으로 살펴보자.
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)
트래픽 신호 시계열 데이터 실습
사용 데이터
미국 미네소타 트윈 시티 메트로 지역의 교통 트래픽을 수집한 데이터
🔗 실습 링크 : https://www.kaggle.com/datasets/boltzmannbrain/nab
여기서 realTraffic 폴더 내 데이터만 사용할 예정
1단계. 데이터 로드
`display()` : 하나의 셀에서 여러 개의 데이터를 데이터프레임 형태로 보여준다.
import pandas as pd
from IPython.display import display
# 데이터 로드
TravelTime_387 = pd.read_csv("TravelTime_387.csv")
TravelTime_451 = pd.read_csv("TravelTime_451.csv")
occupancy_6005 = pd.read_csv("occupancy_6005.csv")
occupancy_t4013 = pd.read_csv("occupancy_t4013.csv")
speed_6005 = pd.read_csv("speed_6005.csv")
speed_7578 = pd.read_csv("speed_7578.csv")
speed_t4013 = pd.read_csv("speed_t4013.csv")
print('TravelTime_387')
display(TravelTime_387.head())
print('\nTravelTime_451')
display(TravelTime_451.head())
print('\noccupancy_6005')
display(occupancy_6005.head())
print('\noccupancy_t4013')
display(occupancy_t4013.head())
print('\nspeed_6005')
display(speed_6005.head())
print('\nspeed_7578')
display(speed_7578.head())
print('\nspeed_t4013')
display(speed_t4013.head())
► 같은 지역에 있는 센서도 측정한 timestamp의 시작점이 다를 수 있다.
원활한 시간 연산을 위해 timestamp 컬럼을 `datetime` 타입으로 변환해보자.
# timestamp 열을 datetime 타입으로 변환
print('변환 전: ' ,occupancy_6005['timestamp'].dtype) # object : str으로 보면 됨
display(occupancy_6005.head())
occupancy_6005['timestamp'] = pd.to_datetime(occupancy_6005['timestamp'])
print('\n변환 후: ' ,occupancy_6005['timestamp'].dtype) # datetime64[ns] : datetime 타입
display(occupancy_6005.head())
2단계. 시계열 데이터 시각화 (matplotlib 활용)
우선, 나머지 컬럼들도 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'])
- `subplot` : 서로 다르 데이터를 한번에 여러개 시각화 가능함
- `matplotlib`의 한계점 : 시간적으로 길이가 길다면 데이터으 특징을 눈으로 보기 어려움
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 12))
plt.subplot(2, 2, 1)
plt.plot(occupancy_6005['timestamp'], occupancy_6005['value'], label='Occupancy 6005', color='blue')
plt.title('Time vs Occupancy (6005)')
plt.xlabel('Timestamp')
plt.ylabel('Occupancy')
plt.xticks(rotation=45)
plt.legend()
plt.subplot(2, 2, 2)
plt.plot(occupancy_t4013['timestamp'], occupancy_t4013['value'], label='Occupancy t4013', color='orange')
plt.title('Time vs Occupancy (t4013)')
plt.xlabel('Timestamp')
plt.ylabel('Occupancy')
plt.xticks(rotation=45)
plt.legend()
plt.subplot(2, 2, 3)
plt.plot(speed_6005['timestamp'], speed_6005['value'], label='Speed 6005', color='green')
plt.title('Time vs Speed (6005)')
plt.xlabel('Timestamp')
plt.ylabel('Speed')
plt.xticks(rotation=45)
plt.legend()
plt.subplot(2, 2, 4)
plt.plot(speed_t4013['timestamp'], speed_t4013['value'], label='Speed t4013', color='magenta')
plt.title('Time vs Speed (t4013)')
plt.xlabel('Timestamp')
plt.ylabel('Speed')
plt.xticks(rotation=45)
plt.legend()
plt.tight_layout()
plt.show()
3단계. 시계열 데이터 시각화 (plotly 활용)
`plotly` : 파이썬, R, JS 등에서 사용할 수 있는 그래프 생성 라이브러리
- 사용자가 시계열 데이터를 쉽게 탐색할 수 있는 기능 제공
- 줌 인/아웃 및 슬라이딩
- 여러 변수 간의 관계 탐색
- 마우스 오버로 정보 확인
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) # 하단에 보고싶은 timestamp를 슬라이드할 수 있는 바 설정
fig.show()
`graph_object` 클래스
`add_trace()` : 트래킹하고 싶은 요소를 추가할 수 있는 함수
- `yaxis`: 서로 다른 y축 스케일 지정
- `y1` : 그래프를 왼쪽 y축에 맞춰서 그리도록 지정
- `y2` : 그래프를 오른쪽 y축에 맞춰서 그리도록 지정
import plotly.graph_objects as go
fig = go.Figure() # 그래픽적 요소를 살펴볼 수 있음
# Occupancy 6005 데이터 추가
# add_trace : 트래킹하고 싶은 요소 추가
fig.add_trace(go.Scatter(x=occupancy_6005['timestamp'], y=occupancy_6005['value'],
mode='lines', name='occupancy_6005',
yaxis='y1')) # 그래프를 왼쪽 y축에 맞춰 그리도록 설정 (y1 : 왼쪽)
# Speed 6005 데이터 추가
fig.add_trace(go.Scatter(x=speed_6005['timestamp'], y=speed_6005['value'],
mode='lines', name='speed_6005',
yaxis='y2')) # 그래프를 오른쪽 y축에 맞춰 그리도록 설정 (y2 : 오른쪽)
# 그래프 레이아웃 설정
fig.update_layout(title='Occupancy and Speed Data in 6005 sensor',
xaxis_title='Timestamp',
legend_title='Data Type',
yaxis=dict(
title='Occupancy',
),
yaxis2=dict(
title='Speed',
anchor="x", # y1의 x축과 서로 연동
overlaying="y", # y1 그래프 위에 겹쳐 그림
side="right", # y2 정보를 오른쪽에 표시
),
xaxis_rangeslider_visible=True)
fig.show()
서로 다른 데이터를 병합해보자.
# plotly 패키지를 활용해 이종의 데이터를 같은 시간 도메인에서 확인
# 두 데이터셋 결합
# 두 데이터셋에 존재하는 timestamp 시간의 값만 취해서 생성
combined_data = pd.merge(occupancy_6005, speed_6005,
on='timestamp', suffixes=('_occupancy', '_speed'))
# 필터링
filtered_speed_6005 = speed_6005[
(speed_6005['timestamp'] >= '2015-09-01 13:40:00') &
(speed_6005['timestamp'] < '2015-09-01 14:00:00')
]
print('occupancy_6005')
display(occupancy_6005.head())
print('\nspeed_6005')
display(speed_6005.head())
print('\nfiltered_speed_6005')
display(filtered_speed_6005.head())
print('\ncombined_data')
display(combined_data.head())
두 개의 데이터가 공존하는 시간만 취한다.
4단계. 주기성을 활용한 통계 시각화 (Bar Plot 활용)
본 데이터의 경우 교통의 흐름이므로 매일(day)을 기준으로 반복되는 주기를 갖고 있다.
- 원본 데이터의 시간 정보를 바탕으로 시간(Hour) 정보를 추출
- 추출된 시간(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()
# Plotly를 이용한 시각화
fig = px.bar(average_occupancy_by_hour, x='hour', y='value',
title='Average Occupancy by Hour of Day (6005)')
fig.update_layout(xaxis_title='Hour of Day', yaxis_title='Average Occupancy')
fig.show()
import plotly.graph_objects as go
# 서로 다른 데이터 간의 주기성 데이터 확인
# plotly 패키지의 bar plot 활용
# 시간대별 그룹화 및 평균 계산
occupancy_6005['hour'] = occupancy_6005['timestamp'].dt.hour
speed_6005['hour'] = speed_6005['timestamp'].dt.hour
average_occupancy_by_hour = occupancy_6005.groupby('hour')['value'].mean().reset_index()
average_speed_by_hour = speed_6005.groupby('hour')['value'].mean().reset_index()
# Plotly를 이용한 대화형 시각화
fig = go.Figure()
# 교통량 그래프 추가
fig.add_trace(go.Bar(x=average_occupancy_by_hour['hour'],
y=average_occupancy_by_hour['value'],
name='Average Occupancy'))
# 속도 그래프 추가
fig.add_trace(go.Bar(x=average_speed_by_hour['hour'],
y=average_speed_by_hour['value'],
name='Average Speed'))
# 레이아웃 설정
fig.update_layout(title='Average Occupancy and Speed by Hour of Day (6005)',
xaxis_title='Hour of Day',
yaxis_title='Average Value',
barmode='group')
fig.show()
5단계. 주기성을 활용한 통계 시각화 (Heatmap 활용)
하루 주기성(시간 별)과 주간 주기성(요일 별)을 기준으로 확인해보자.
# occupancy_6005 데이터를 사용하여 필요한 2 종류의 주기 데이터 확보
occupancy_6005['day_of_week'] = occupancy_6005['timestamp'].dt.dayofweek # 0 : 월요일 ~ 6 : 일요일
occupancy_6005['hour'] = occupancy_6005['timestamp'].dt.hour
# 요일과 시간별 평균 occupancy 값 생성
occupancy_heatmap_data = occupancy_6005.groupby(['day_of_week', 'hour'])['value'].mean().unstack()
occupancy_heatmap_data
# day_of_week 값과 hour 값을 활용해 히트맵 생성
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()
# Speed_6005 데이터도 히트맵 생성
# 교통량과 속도 데이터 비교 확인
# Speed 데이터를 요일과 시간별로 정리
speed_6005['day_of_week'] = speed_6005['timestamp'].dt.dayofweek
speed_6005['hour'] = speed_6005['timestamp'].dt.hour
# 요일과 시간별 평균 Speed 계산
speed_heatmap_data = speed_6005.groupby(['day_of_week', 'hour'])['value'].mean().unstack()
# 히트맵 생성
plt.figure(figsize=(15, 6))
plt.subplot(1, 2, 1)
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.subplot(1, 2, 2)
sns.heatmap(speed_heatmap_data, cmap="YlGnBu", annot=False)
plt.title('Heatmap of speed_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.tight_layout()
plt.show()
6단계. 계절성 분석
계절성 주기
: 주, 달, 분기, 계절 년 등의 시간적 주기
시계열 데이터의 구성 성분
1. Trend (경향)
: 데이터 전반에 걸쳐 보여지는 일반적인 경향성
- 시간적 구간(window)을 잡아 해당 구간의 평균값 활용
- 윈도잉(windowing) 방식으로 시간의 축 방향으로 연속적으로 계산
2. Seasonal (계절성)
: 데이터에 존재하는 주기적인 성분
- 어떠한 주기 때문에 발생되는 데이터 변동
- 사용자가 제공한 주기(period) 단위로 평균값 활용
3. Residual (잔차)
: Trend와 Seasonal로 예측되지 못하는 추가 변동분
- Residual = 원본 데이터 - (Trend + Seasonal)
- 값이 너무 클 경우 :
- 사용자가 제공한 주기(period)가 터무니 없을 수 있음
- 계절성 분석으로 분석하기에 데이터가 너무 복잡할 수 있음
- 노이즈, 이상치에 심한 영향을 받은 데이터일 수 있음
► 계절성 분석의 additive 방식
원래 데이터= Trend + Seasonal + Residual
※ 계절성 분석을 위해서는 datetime 형태가 인덱스(index)로 들어가 있어야 함
# 데이터 전처리
# 사용하는 패키지는 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')
print('Test data')
display(df)
print('\nfill NaN')
display(fill_df)
📌 window : 특정 구간
`period` : window를 설정할 수 있음
# 필요한 라이브러리 재로드
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()
► Residual의 분포를 보고 작아지는 구간을 찾았다면 그게 바로 해당 데이터를 표현하는 '주기'가 된다.
# 시간 단위로 재표본화하여 결측치 처리
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()
7단계. 계절성 분석 결과 시각화 (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()
# 필요한 라이브러리 임포트
from plotly.subplots import make_subplots
# Plotly를 이용한 대화형 시각화
fig = go.Figure()
# 원본 데이터 추가
fig.add_trace(go.Scatter(x=result_hourly.observed.index,
y=result_hourly.observed,
mode='lines', name='Original'))
# 추세 컴포넌트 추가
fig.add_trace(go.Scatter(x=result_hourly.trend.index,
y=result_hourly.trend,
mode='lines', name='Trend'))
# 계절성 컴포넌트 추가
fig.add_trace(go.Scatter(x=result_hourly.seasonal.index,
y=result_hourly.seasonal,
mode='lines', name='Seasonality'))
# 잔차 컴포넌트 추가
fig.add_trace(go.Scatter(x=result_hourly.resid.index,
y=result_hourly.resid,
mode='lines', name='Residual'))
# 레이아웃 업데이트
fig.update_layout(height=600,
width=800,
title_text="Seasonal Decompose using Plotly in one palette",
xaxis_rangeslider_visible=True)
# 그래프 표시
fig.show()