시계열분석 1차시 EDA

JiHyeon Lee·2024년 7월 29일
post-thumbnail

시계열 분석이란?

시계열 분석은 시간에 따라 발생한 데이터의 패턴, 트렌드, 주기성 등을 이해하고 예측하는 통계적 기법이다.
이는 일정한 시간 간격으로 측정된 데이터를 대상으로 하며, 주로 시계열 데이터로 알려진 데이터 유형을 다룬다.

시계열 데이터는 일반적으로 시간에 따라 변화하는 데이터를 포함한다. 예를 들어 일별 주가, 매월 판매량, 연도별 기온 등이 시계열 데이터의 예시이다.
이러한 데이터는 특정 시점에서의 값뿐만 아니라 시간에 따른 패턴과 트렌드를 갖고 있으며, 이를 통해 과거의 동향을 분석하고 미래를 예측할 수 있다.


이번에 제가 시계열 분석을 하게 될 프로젝트의 주제는"제주 특산물 가격 예측"입니다.
데이콘에서 개최한 경진대회의 데이터셋과 코드를 참고하였습니다.
링크텍스트

대회 개최 배경과 데이터셋에 대한 설명입니다.

[배경]

제주도에는 다양한 특산물이 존재하며, 그 중 양배추, 무(월동무), 당근, 브로콜리, 감귤은 제주도의 대표적인 특산물들 중 일부입니다.

이런 특산물들의 안정적이고 효율적인 수급을 위해서는 해당 특산물들의 가격에 대한 정확한 예측이 필요합니다.

따라서 제주테크노파크에서는 특산물 가격 예측에 대한 효율적인 인공지능 알고리즘과 인사이트 발굴을 목표로 본 대회를 개최합니다.

[주제]

제주도 특산물의 가격을 예측하는 AI 모델 개발 및 인사이트 발굴

Dataset Info.

  1. train.csv

train 데이터 : 2019년 01월 01일부터 2023년 03월 03일까지의 유통된 품목의 가격 데이터

item: 품목 코드

TG : 감귤

BC : 브로콜리

RD : 무

CR : 당근

CB : 양배추

corporation : 유통 법인 코드

법인 A부터 F 존재

location : 지역 코드

J : 제주도 제주시

S : 제주도 서귀포시

supply(kg) : 유통된 물량, kg 단위

price(원/kg) : 유통된 품목들의 kg 마다의 가격, 원 단위

  1. international_trade.csv

관련 품목 수출입 정보

중량 단위 kg

금액 단위 천 달러

  1. test.csv

test 데이터 : 2023년 03월 04일부터 2023년 03월 31일까지의 데이터

  1. sample_submission.csv

제출을 위한 양식

2023년 03월 04일부터 2023년 03월 31일까지의 price(원/kg)을 예측

ID는 품목, 유통 법인, 지역 코드로 구성된 식별자

해당 ID에 맞춰 price(원/kg) 예측값을 answer 컬럼에 기입해야 함

이 중 2번 데이터셋은 참고용, 4번 데이터셋은 제출용으로 모델 훈련과 예측에는 1,3번 데이터셋을 사용하였습니다. 목표는 과거 품목 코드, 유통 법인 코드, 지역 코드, 물량 데이터를 이용하여 미래 특산품의 가격을 예측하는 것이므로 target은 price입니다.


시계열 분석의 첫 단계로 EDA를 진행해보았습니다.

EDA란

EDA(탐색적 데이터 분석, Exploratory Data Analysis)는 데이터 분석 과정에서 데이터를 이해하고, 주요 특성을 파악하며, 처음으로 인사이트를 얻기 위해 사용하는 다양한 기법과 도구를 의미합니다. EDA는 데이터 과학과 분석의 중요한 단계로, 데이터에 숨겨진 패턴을 발견하고 데이터의 구조와 특성을 이해하는 데 초점을 맞춥니다.

EDA의 주요 목표

데이터의 분포 이해:
데이터가 어떻게 분포되어 있는지 파악하고, 이를 통해 이상치(outlier)나 특이값을 식별합니다.
히스토그램, 박스플롯(Boxplot), 밀도 플롯(Density Plot) 등을 사용합니다.

데이터의 관계 탐색:
여러 변수 간의 관계를 이해하여 상관관계나 인과관계를 탐색합니다.
산점도(Scatter Plot), 상관 행렬(Correlation Matrix), 쌍플롯(Pair Plot) 등을 사용합니다.

데이터의 요약 통계량 확인:
평균, 중위수, 표준편차 등의 기본적인 통계량을 계산하여 데이터의 중심 경향과 변동성을 파악합니다.

데이터 전처리 필요성 평가:
결측값(Missing Value), 중복값(Duplicate Value) 등의 문제를 식별하고, 데이터 정제를 위한 전처리 과정을 계획합니다.

데이터의 구조 이해:
데이터의 차원(행과 열의 수), 변수의 유형(범주형, 연속형) 등을 파악합니다.

다시말해, 모델을 선정하고 훈련시키기 전에 우선 데이터의 형태를 파악하는 단계라고 생각하시면 될 것 같습니다.

데이터가 어떻게 생겼는지 알아야 적합한 모델을 고를 수 있겠죠? 이상치도 처리해야 하고요.

저는 1차시 EDA 단계에서 산점도, box plot, 상관계수행렬을 시각화하여 데이터의 형태를 알아보고, 이상치 제거까지 해볼 것입니다.


링크텍스트
SEo0s2님이 공유해주신 코드를 기반으로(약간 수정함) EDA를 진행해보았습니다.


데이터 로드 및 기본 탐색

...
print("\n고유값 빈도 출력-----------\n", data['item'].value_counts())
print("\n결측값 출력-----------\n", data.isna().sum())

출력 결과는 다음과 같습니다.

고유값 빈도 출력-----------
 item
TG    15230
BC    13707
RD    12184
CR    10661
CB     7615
Name: count, dtype: int64

결측값 출력-----------
 ID             0
timestamp      0
item           0
corporation    0
location       0
supply(kg)     0
price(원/kg)    0
dtype: int64 

item 중 가방 많은 것은 감귤(TG), 가장 적은 것은 양배추(CB)입니다. 각각 15230, 7615개로 더 분석을 해봐야 알겠지만 그냥 봤을 때 유의미한 차이가 발생할 정도로 개수가 편향되지는 않은 것 같습니다. 결측값은 모든 칼럼에서 존재하지 않는 것을 알 수 있습니다. 오예~


데이터 타입 변환

type(data['timestamp'][0])
import datetime
data['timestamp'] = pd.to_datetime(data['timestamp'])
print("데이터 타입", type(data['timestamp'][0]))

timestamp 컬럼의 값을 문자열에서 pandas에서 제공하는 Timestamp 객체로 변환한 것입니다. (Timestamp 객체는 연도, 월, 일, 시간, 분, 초 등의 시간 구성 요소를 포함)

마지막 줄에서 제대로 데이터 타입이 변경되었는지 확인하기 위해 가장 첫 번째 행의 timestamp 칼럼의 type를 출력합니다.

데이터 타입 <class 'pandas._libs.tslibs.timestamps.Timestamp'>

출력 결과를 보면 Timestamp로 잘 변경된 것을 알 수 있습니다.


boxplot, 산점도

import seaborn as sns
import matplotlib.pyplot as plt

sns.boxplot(x='item',y='price(원/kg)',data=data)
sns.boxplot(x='corporation',y='price(원/kg)',data=data)
sns.boxplot(x='location',y='price(원/kg)',data=data)

plt.figure(figsize=(10,8))
sns.scatterplot(data=data,x ='supply(kg)',y='price(원/kg)')

plt.figure(figsize=(10,8))
sns.scatterplot(data=data,x ='supply(kg)',y='price(원/kg)',hue='item')

plt.figure(figsize=(10,8))
sns.scatterplot(data=data,x ='supply(kg)',y='price(원/kg)',hue='corporation')

plt.figure(figsize=(10,8))
sns.scatterplot(data=data,x ='supply(kg)',y='price(원/kg)',hue='location')


box plot을 그려본 결과입니다. whisker 밖의 값이 상당히 많은 것을 볼 수 있습니다. 흠.. 어떻게 처리할지 벌써 막막합니다.


다음은 공급량-가격 산점도입니다. 네 산점도는 모두 같은 그림인데, 칼럼을 기준으로 값에 따라 색깔을 다르게 라벨링한 것입니다.

일반적인 상식과 같이 공급이 증가하면 가격이 감소하는 형태를 띄고 있습니다. 다만, 공급이 0일 때 값이 모여있는 것을 확인할 수 있습니다.

공급이 없는데 가격이 생성될 수가 있을까요? 어떻게 된 일인지 알아봐야될 거 같습니다..

count_nonzero_price = data[(data['supply(kg)'] == 0) & (data['price(원/kg)'] != 0)].shape[0]
print("Supply가 0이고 Price가 0이 아닌 행의 개수:", count_nonzero_price)
Supply가 0이고 Price가 0이 아닌 행의 개수: 0

실행 결과는 0이라고 뜹니다. 즉, 공급이 없을 때 가격이 0이 아닌 논리적으로 오류가 있는 데이터는 없다는 것입니다. 산점도의 scale이 작아 가로축에서 0에 가까운 값들이 0과 근사하게 보였던 것입니다. 실제로 csv 파일에서 supply 칼럼을 기준으로 오름차순 정렬해보았습니다.

사진의 두 번째 행 부터가 supply가 0을 넘는 데이터인데, supply 최댓값이 1222800인걸 생각하면 0에 가까워 보일 만 했네요. 어쨌든data[(data['supply(kg)'] == 0) & (data['price(원/kg)'] != 0)인 값은 없었으니 supply가 0인 행은 전부 삭제하고 분석을 진행해도 될 것 같습니다.

plt.figure(figsize=(10,8))
sns.lmplot(data=data,x ='supply(kg)',y='price(원/kg)')


산점도와 회귀선을 함께 그려보았습니다. 회귀선이 데이터를 잘 반영하지 못하는 것을 알 수 있습니다.


공급 0인 행 삭제하고 다시 plot

원 코드와는 순서를 조금 바꿔서, 위에서 확인했듯이 supply==0인 행을 모두 제거할 수 있으므로 먼저 데이터를 제거한 후 bar plot을 그려보았습니다.

sup_notzero = data[data['supply(kg)']!=0]
sup_notzero

위의 코드로 간단히 supply가 0이 아닌값만을 가지는 데이터셋을 만들었습니다.


실행해보면 23945행의 데이터가 살아남은 것을 볼 수 있습니다.

fig, axes = plt.subplots(1, 2, figsize=(18, 6))
sns.barplot(data=sup_notzero, x='year', y='price(원/kg)', ax=axes[0], palette='husl')
sns.barplot(data=sup_notzero, x='month', y='price(원/kg)', ax=axes[1])

items = sup_notzero['item'].value_counts().index.to_list()


sup_notzero 데이터프레임에서 'year'와 'month' 칼럼을 이용하여 가격의 월별 및 연도별 분포를 막대 그래프로 시각화한 결과입니다.

fig, axes = plt.subplots(5, 1, figsize=(10, 15))
for i in range(len(items)):
    r = i % 5
    sns.scatterplot(data=sup_notzero[sup_notzero['item']==items[i]], x='supply(kg)', y='price(원/kg)', ax=axes[r], label=items[i])

corp = sup_notzero['corporation'].value_counts().index.to_list()
fig, axes = plt.subplots(2, 3, figsize=(10, 10))
for i in range(len(corp)):
    n = int(i / 3)
    r = i % 3
    sns.scatterplot(data=sup_notzero[sup_notzero['corporation']==corp[i]], x='supply(kg)', y='price(원/kg)', ax=axes[n][r], label=corp[i])

loc = sup_notzero['location'].value_counts().index.to_list()

fig, axes = plt.subplots(1, 2, figsize=(18, 6))
sns.scatterplot(data=sup_notzero[sup_notzero['location']==loc[0]], x='supply(kg)', y='price(원/kg)', label=loc[0], ax=axes[0])
sns.scatterplot(data=sup_notzero[sup_notzero['location']==loc[1]], x='supply(kg)', y='price(원/kg)', label=loc[1], ax=axes[1])

item, corporation, location별로 산점도를 그리는 코드입니다. 실행 결과는 너무 많아서 생략하겠습니다. 눈으로 봤을때 "공급이 적으면 가격이 높고, 공급이 많으면 가격이 낮다"라는 상식에 어긋나는 데이터는 없었습니다.


월별 추세 파악

여기서 원 코드에는 supply가 0인 값을 제거하고 plot을 그렸는데, 저는 다르게 해석하였습니다.
특산물 채소라는 것은 수확시기가 있기 마련이고, 수확시기에는 공급량이 증가한다는 것을 전제로 추세분석을 하는 것이기 때문에 supply가 0인 것을 삭제하면 안된다고 판단하였습니다.
그냥 결측값이 아니라 이유가 있어서(ex. 수확시기가 아님 등...) 공급이 0이었던거면 삭제를 해서는 안되겠죠. 그래서 저는 본래의 데이터인 data 로 plot을 해보았습니다.

(코드는 편의상 생략하고 결과만)

감귤은 매년 5-6월까지 증가했다가 이후로 감소하는 패턴.


브로콜리는 10-11월 급격히 증가했다가 2월쯤 한번 감소하고, 다시 4월까지 증가했다가 5월에 급격히 감소하는 패턴.


무는 11월부터 증가하기 시작해서 12월-1월 이후로 감소.

그런데 1-3월 사이가 패턴이라기엔 애매하다.

특히 2022년 무에 무슨 일이?


양배추는 10월부터 증가하다가 3월에 피크를 찍고 감소하는 패턴

이것도 패턴이 좀 애매. 19,21, 22년도는 1월에서 2월 하락세인데 20, 23년도는 상승

20년도, 23년도가 좀 튐


당근은 11월부터 증가하다가 1월쯤 최대, 이후로 완만히 감소하다가 4-5월 사이 급감.

5-11월은 공급량 0

2022년만 특이하게 5-11월 공급량이 존재함

결론: 감귤, 브로콜리는 패턴이 매년 잘 맞는다

무, 당근 2022년값이 패턴에서 벗어난다


profile
Data Analysis

0개의 댓글