Python/코딩 실습

시계열은 처음이라 (제주도 특산물 가격 예측 AI 경진 대회)

관형이 2023. 11. 21. 15:58

이번 보아즈 미니 플젝2에 우리는 '제주도 특산물 가격 예측 AI 경진 대회'에 나갔다.

 

 상위 10프로 가능했을거 같긴 한데 문턱에서 끊겼다. 그래도 새로운 것을 많이 알게 되어서 이 정도면 만족이다.

졌잘싸...

 

 

 

 

성능을 업그레이드 하기 위해 우리가 집중한 방식은 3가지에만 집중했다.

 

3가지

 

  • 전처리
  • 피처 엔지니어링
  • 좋은 모델 사용

 

이 3가지에 집중했다. 그 중 많은 내용을 다 다룰 수 없으니, 핵심 idea인 피처 엔지니어링 ,전처리에 대해 요약해 볼 예정이다.

 


구성

 

1. 문제 정의

  • EDA -> 어떤 문제가 있는지?

 

2. 문제 해결 

  • 피처 엔지니어링
  • 전처리

 

3. 추가로 무엇을 하려고 했는지?

  • 모델 앙상블 (품목 별 학습 (xgb), 회사 별 학습(xgb), 전체 학습 앙상블. (autogluon timeseires))
  • Meta data 활용 (무역 데이터 <-> Supply)
  • price = 0이 반복되는 구간 심층 분석

 

데이터 탐색

 

train data

item: 품목 코드

-TG : 감귤

-BC : 브로콜리

-RD : 무

-CR : 당근

-CB : 양배추

corporation : 유통 법인 코드

-법인 A부터 F 존재

location : 지역 코드

-J : 제주도 제주시

-S : 제주도 서귀포시

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


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

 

 

위의 대회는 제주도 특산물 2019 ~ 2023년 2월 데이터를 갖고

2023년 3월 데이터 가격을 예측하는 것이다.

 

 

분석 전 데이터만 봤을 때?,

 

  • 범주형 데이터가 많음 -> 선형 회귀보단 트리 계열 모델 사용
  • 특산물 -> 싯가 반영이 중요
  • 계절성을 학습 할 수 있는 모델 선정이 중요 (시계열 model?)

 

하다는 결론을 먼저 내릴 수 있다.

 

문제 정의 (EDA)

 

 

1. 문제정의(EDA): Target 분포의 문제점

 

Train data: 2019.01.01 ~ 2023.03.03

 

Test data: 2023.03.04 ~ 2023.03.17 추정

 

target값의 분포

 

target 사분위수

 

 

target의 문제 요약:

 

1. price = 0

2. 첨도가 심함 -> log 변환 (정규 분포)?

3. price = 0 적절 보간 -> 스플라인 보간 (결측치로 판단하고?)

 

 

사분위수와 plot을 찍어보면, 50프로 이상이 target값이 0인 것을 알 수 있다.

 

먼저 피처 엔지니어링 하기 전, 왜 price = 0 인 값들이 많은지 알아내는 것이 중요했다.

또한 supply가 0이면, price도 0인 것을 확인 할 수 있었다. (온라인 판매와 같은 건 없는 듯)

 


 

 

2. 문제 정의 (EDA) : supply (kg) (연속형 변수)

 

 

 

 

 

문제 요약:

1. test data에는 존재 x

2. supply = 0 -> price = 0 : 온라인 예약 판매 x

3. 0에 가까울 수록 특정 높은 가격 형성 : 시장의 원리 ?

 

 

 

supply :0 → price: 0 이지만,

supply가 0에 가까울 수록, 높은 price의 경향이 나타남

(전반적으로 봤을 땐 price와 supply가 큰 영향은 없는듯. 특정 피크 월마다 수요량 대비 공급량이 적어서 그러지 않을까?)

이것들 어떻게 처리해야 할까?

또한 test 데이터에는 supply열이 없는데, train에만 있는 supply열을 어떻게 잘 이용할까?

 


 

 

3.  문제 정의 (EDA) : 범주형 변수 (item, corporation, location)

 

item

 

item 변수 문제 요약:

TG (감귤) 의 평균 가격이 특히 큰 것을 알 수 있음. -> 품목 별 분리해서 학습 ? or 이상치 처리시 품목 별 처리 ? 

 

location <-> price

 

 

제주도의 특산물일 수록 극단적인 가격 차이 나타남, but 평균 가격 형성은 서귀포가 더 높음. 

-> 별로 의미 없을 듯? 이용하는 것은.

 

 

corporation과 location 별 price

 

같은 회사 (corporation) 이어도 제주시와 서귀포시 두 곳에 존재한다는 것을 알 수 있음.

 

 


 

4. 주기성 분석

 

- location별 가격 변동 주기

 

 

- item별 가격 변동 주기

 

 

- corporation별 가격 변동 주기

 

 

주기성 변수 문제 요약:

 

1. 주기성 (범주형 변수 별 특정 주기)

2. 특정 이상치의 원인 ?

 

-> 모두 계절성이 돋보임 (계절성을 학습 할 수 있도록 피처 엔지니어링 방식, 전처리)

 


 

문제정의 종합 요약: 

위의 말한 문제점들을 하나씩 요약하면 3가지 결론을 낼 수 있다.

 

위의 총 요약된 3가지를 해결하는 방식으로 진행 했다.

 

밑에 문제 해결 부분을 볼 때도 순번 위주로 집중하여 보면 된다.

 

 

문제 해결

 

 

1. 첨도가 심한 target ->log 변환

 

기존 첨도가 높아 log 변환을 수행했다.

정규 분포로 만들어 보편 적인 예측 결과를 만들어 낼 수 있기 때문이다.

 

또한 price = 0인 값의 비율이 50프로가 넘어 이것을, 단순 결측치로 보고 시계열 전처리 방식인 스플라인 보간을 해봤다.

하지만, 결과적으로는 평가 지표인 RMSE의 성능 저하로 이어졌다.

 

price = 0인 값들의 이유가 있기 때문에, 이것들을 단순 결측치로 봐서는 안될거 같다.

 

 


 

2. 계절성 피처 엔지니어링 -> PolynomialFeatures

 

 

 

앞에 train data만 봐도 알 수 있듯, 학습에 사용되는 피처는 대부분이 범주형 변수이며 심지어 피처의 수도 매우 작다.

 

당시 나는 계절성 피처 엔지니어링 방식을 몰랐기 때문에, 가능한 조합의 피처들을 만들어주는 polynomialFeatures를 사용했다. 

-> 오히려 성능 저하

 

 

 

 

2.  계절성 피처 엔지니어링 -> 공휴일 변수 추가

 

prcie = 0 인 부분이 너무 많아 공휴일 변수를 추가해줬다. (패키지 이용)

보통 공휴일에 영업을 안하니, (시간 데이터가 공휴일이면, 영업을 안하는 날 price =  0 -> 성능향상 기대)

 

Train data: 2019.01.01 ~ 2023.03.03

 

Test data: 2023.03.04 ~ 2023.03.17 추정

 

데이터 내부를 살펴보면 2023년의 전체 시간 데이터는 없다.

함수로 만들어 2023년 일부까지라도 적용하려고 했지만, 오류가 발생.

 

따라서 2023년의 공휴일 적용은 커스텀 처리했다.

 

! pip install holidays
!pip install pytimekr

 

from pytimekr import pytimekr

import holidays
import pandas as pd
import datetime

#2019 ~ 2022 공휴일 데이터 처리
def add_weekday(df):
    df['date'] = pd.to_datetime(df['timestamp'])
    df['weekcode'] = df['date'].dt.weekday

    year_min = df['date'].dt.date.unique().min().year
    year_max = df['date'].dt.date.unique().max().year
    kor_holidays = list(holidays.KOR(years=range(year_min, year_max)).keys())  # 2019~2022년 공휴일을 포함하도록 수정
    idx_kor_holidays = pd.to_datetime(kor_holidays) #2023년 공휴일은 수기 수정

    # 인덱스에 공휴일 날짜를 추가
    df_temp = df.set_index('date')
    df_temp['is_holiday'] = 0  # 새로운 열을 만들고 초기값을 0으로 설정
    df_temp.loc[idx_kor_holidays, 'is_holiday'] = 1  # 공휴일인 날은 1로 설정
    df_temp.reset_index(inplace=True)

    df['weekcode'] = df_temp['is_holiday']  # 'weekcode' 열을 'is_holiday' 열로 대체

    return df

 

test 2023년의 데이터를 예측하는 것이니, 예측의 정확도를 올리기 위해 대체 공휴일등 빠진 공휴일까지 함께 커스텀 처리 했다.

#2023 수기 처리
kr_holidays_2023 = pytimekr.holidays(year=2023)

 

#대체 공휴일, 빠진 공휴일 커스텀 처리 2023
kr_holidays_2023.append(datetime.date(2023, 5, 29))
kr_holidays_2023.append(datetime.date(2023, 1, 24))

 

 #2022년까지의 공휴일 정보 load
train_df = add_weekday(train_df)
test_df = add_weekday(test_df)

 

#2023년 수기처리 함
def add_weekday_2023(df, kr_holidays_2023):
    # kr_holidays_2023에 포함된 날짜의 'weekcode'를 1로 변경
    df.loc[df['date'].isin(kr_holidays_2023), 'weekcode'] = 1

    return df
train_df = add_weekday_2023(train_df, kr_holidays_2023)
test_df = add_weekday_2023(test_df, kr_holidays_2023)

 

 

 

weekcode라는 열에 공휴일이면 1 아니면 0이 들어간 것을 확인 할 수 있음.