본문 바로가기
코딩코딩/Kaggle 분석

[Dacon] 대출 상점 총 매출 예측 경진대회 1등 솔루션 분석

by g0n1 2021. 3. 25.
728x90

1등 솔루션 (Yongjip님)

1등 수상자이신 Yongjip님께서는 경제학을 전공하고 빅데이터 관련 프로젝트들을 하다가 미국 버지니아에 있는 테크회사에서 data analyst로 일하시다가 귀국해 쿠팡의 business analyst로 근무를 하고 있으시다고 한다. 아무래도 경제학이라는 백그라운드 덕분에 시계열 예측 문제에서 지식을 발휘하여 1등을 할 수 있던 것 같다. (그렇다고 내 전공이 호텔머시기라고 쉴드치는 것 아님)

목표

이번 포스팅에서는 1등 솔루션을 분석하면서 시계열 문제에서 자주 쓰이는 ARIMA모델, 다운샘플링에 대해 가볍게나마 이해해보자.

 

EDA, 전처리

Negative transaction elimination

매출량인 amount 칼럼에 가끔 음수가 있어, 하루 매출 자체가 음수가 되는 경우가 있다고 한다. 이런 경우에는 분산이 커지면서 prediction interval도 함께 커진다. (prediction interval: 회귀분석의 예측과 같이 쓰이는 신뢰구간의 일종이라고 한다) 따라서 이 음수들을 제거하는데 card_id 칼럼을 사용했다. 방법은 다음과 같다.

negative transaction(refund case)와 card_id가 같으면서 이전 시간에 결제한 case를 찾아서

1. 절댓값이 같은 positive transaction 제거 (zero canceling)

2. 절댓값이 더 큰 positive transaction에서 negative transaction을 차감

그런데 여기서 이전 transaction 중에 negative transaction의 card_id가 존재하지 않거나 transaction이 존재하는 경우에는 positive transaction이 제공받은 데이터보다 과거의 데이터이므로 데이터에서 제외한다.

def reduce_noise_by_removing_neg_vals(df_copy):
    df_pos = df_copy[df_copy.amount > 0]
    df_neg = df_copy[df_copy.amount < 0]

    start = datetime.now()

    for nega_i in df_neg.to_records()[:]:
        store_i = nega_i[1]
        date_i = nega_i[2]
        card_i = nega_i[4]
        amt_i = nega_i[5]
        row_i = df_pos[df_pos.store_id == store_i]
        row_i = row_i[row_i.card_id == card_i]
        row_i = row_i[row_i.amount >= abs(amt_i)]
        row_i = row_i[row_i.date <= date_i]
        if len(row_i[row_i.amount == abs(amt_i)]) > 0:
            row_i = row_i[row_i.amount == abs(amt_i)]
            matched_row = row_i[row_i.date == max(row_i.date)]
            # df_pos.loc[matched_row.index, 'amount'] = 0
            df_pos = df_pos.loc[~df_pos.index.isin(matched_row.index), :]
        elif len(row_i[row_i.amount > abs(amt_i)]) > 0:
            matched_row = row_i[row_i.date == max(row_i.date)]
            df_pos.loc[matched_row.index, 'amount'] = matched_row.amount + amt_i
        # else:
        #     pass
            # no_match.append(nega_i)
    end = datetime.now()
    time_took = (end - start).seconds / 60

    print(round(time_took, 2))
    return df_pos

 

business day

store_id마다 시간을 조정하여 하루 단위로 맞춰주려고 했으나, 각 가게마다 시간이 상이하고 downsampling하는 기간이 길어지면 이 부분이 어느정도 해결되므로 business day 관련 전처리는 하지 않음.

amount

 

Yongjip님께서는 QQ plot으로 로그변환한 amount의 정규분포를 확인하였고, outlier들의 영향을 줄이기 위해 로그를 씌운 판매금액을 종속변수로 사용한다고 하였음.

 

train['log_amount'] = np.log(train['amount'])
train['log_amount'] = train['log_amount'].replace([np.inf, -np.inf], np.nan)
fig, ax = plt.subplots(figsize=(20,8),nrows=1, ncols=2)
sns.distplot(train['amount'],ax=ax[0])
sns.distplot(train['log_amount'],ax=ax[1])
plt.show()

(좌: amount, 우: 로그변환 amount) 

import scipy.stats as stats

fig, ax = plt.subplots(figsize=(20,8),nrows=1, ncols=2)
stats.probplot(train['amount'], plot=ax[0])
ax[0].set_title('amount')
stats.probplot(train['log_amount'], plot=ax[1])
ax[1].set_title('log_amount')

두번째 그림은 뭔가 이상하다...사실qqplot이 뭔지도 잘모르겠으니 일단 넘어가자.

로그변환 후에 null이 되거나 무한이 되면 그 값은 데이터셋에서 일단 제거하고, 1 -  (null + inf)/ total # 한 확률을 구해서 최종 prediction에 곱해줌으로써 discount해주기로 했다. (null + inf)/ total 은 probability fo no sales를 의미한다.

Downsampling

store마다 매우 상이한 데이터 기간을 가지고 있다.

따라서 각 store가 가지고 있는 정보를 최대한 살리는 리샘플링 기간을 주기로 함. 

요일로 인한 seasonality: 최소 resampling 주기를 1주일로 두어서 해소한다.

매년 있는 seasonality: 365.25를 잘 떨어지게 나누는 숫자로 downsampling 주기를 잡음(28,14,7)

최소 샘플 숫자를 넘지 못하는 경우에는 점점 적은 기간으로 downsampling

 

 

PCAF (편자기상관함수)

AR 모형의 차수(order)를 추정하기 위한 방법의 하나이다. ARIMA(p,q,r)에서 최적의 파라미터를 찾아내고자 할 때 , PCAF(partial autocorrelation function), ACF(autocorrelation function)를 사용한다고 한다. 

ARIMA를 비롯한 통계지식이 너무 부족하다. 공부가 더 필요하다.

 

 

 

 

http://bit.ly/2DnkoFD 인터벌

http://bit.ly/37A5l95 downsampling

yamalab.tistory.com/112

 

 

728x90

'코딩코딩 > Kaggle 분석' 카테고리의 다른 글

복붙창고  (0) 2021.04.04
[Dacon] 병원 개/폐업문제 1등 솔루션 분석  (0) 2021.03.12
Sejong Kaggle Challengers 2기 시작  (0) 2021.03.03
교내 캐글 경진 대회  (0) 2020.10.12

댓글