[DACON] 쇼핑물 매출액 예측 경진대회
from google.colab import drive
drive.mount('/content/drive')
개인 구글 드라이브에 파일을 업로드 한뒤 코렙 계정과 연동합니다.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings("ignore")
path = '/content/drive/MyDrive/shop/'
train = pd.read_csv(path + 'train.csv')
test = pd.read_csv(path + 'test.csv')
sample_submission = pd.read_csv(path + 'sample_submission.csv')
train.head()
데이터 분석에 필요한 필수 패키지를 설치하고 데이터를 불러옵니다.
print(train.shape)
print(test.shape)
칼럼 개수는 총 12개, 트레인 데이터는 6255개, 테스트 데이터는 180개 입니다. 트레인 데이터가 어느정도 있습니다.
plt.figure(figsize=(8,6))
plt.scatter(range(train.shape[0]), np.sort(train['Weekly_Sales'].values))
plt.show()
데이터 분석시 가장 먼저 반응변수 형태를 분석해야합니다. 매출액 데이터이기 때문에 튀는 값이 다소 관찰됩니다.
sns.distplot(train['Weekly_Sales'], fit=stats.norm)
반응변수의 히스토그램과 만약 정규분포일 때 히스토그램을 비교했습니다.
정규분포와 비교했을 때 오른쪽 꼬리가 다소 길어보입니다.
sns.distplot(np.log1p(train['Weekly_Sales']), fit=stats.norm)
로그변환시 이전보다 확실히 정규분포에 가까워졌습니다.
train.info()
데이터를 전반적으로 살펴보고 결측치 여부 또한 알아봅니다. Promotion 변수 5개가 결측값이 상당하네요.
import datetime
train['Date'] = pd.to_datetime(train['Date'])
test['Date'] = pd.to_datetime(test['Date'])
train['Year'] = train['Date'].dt.year
train['Month'] = train['Date'].dt.month
test['Year'] = test['Date'].dt.year
test['Month'] = test['Date'].dt.month
train.head()
파이썬 내 datetime를 임포트하고 Date 값을 datetime으로 변환하면 손쉽게 날짜 데이터를 다룰수 있습니다.
여기서 날짜 데이터의 연과 월 값을 간편하게 뽑아냈습니다.
print(sum(train['Promotion1'] < 0))
print(sum(train['Promotion2'] < 0))
print(sum(train['Promotion3'] < 0))
print(sum(train['Promotion4'] < 0))
print(sum(train['Promotion5'] < 0))
Promotion 값이 0 미만인게 혹시 있는지 확인 했습니다. 일부 관측되는 것으로 보입니다.
train['Promotion1'].hist()
train['Promotion2'].hist()
train['Promotion3'].hist()
train['Promotion4'].hist()
train['Promotion5'].hist()
Promotion 변수의 히스토그램 5개를 확인했습니다. 대부분 작은 값을 가지고 있으나 일부 값이 극단적으로 만 단위를 넘어갑니다.
정말 가끔씩 0 이하의 값도 존재하고 만이 넘어가는 값도 있고 결측값도 상당히 많아 어떤 변수인지 추측하기 어렵습니다.
tem = train.dropna()
promotion = ['Promotion1', 'Promotion2', 'Promotion3', 'Promotion4', 'Promotion5']
plt.figure(figsize=(15,10))
ax = sns.heatmap(tem[promotion + ['Weekly_Sales']].corr(), annot=True)
plt.show()
프로모션 변수를 더 관찰하기 위해 결측값이 있는 데이터는 제외하고 상관관계를 살펴보았습니다.
프로모션1과 프로모션4가 연관이 상당히 있으며 프로모션2를 제외하곤 타겟값에 긍정적인 영향을 일부 끼친다고 볼 수 있습니다.
즉 유의미한 변수이긴 하다라고 생각해야겠네요.
print(tem['Weekly_Sales'].mean())
print(train['Weekly_Sales'].mean())
그럼 프로모션 변수가 결측값인 것은 의미가 있을까가 궁금했는데요.
프로모션 변수가 결측값이 아닌 데이터의 타겟값 평균이 전체 데이터의 타겟값 평균보다 꽤 큰 것을 알 수 있습니다.
train[train['Year'] == 2010].info()
2010년 데이터입니다. 프로모션 변수는 모두 결측값입니다.
train[train['Year'] == 2011].info()
2011년 데이터입니다. 프로모션 변수는 대부분 결측값이네요.
train[train['Year'] == 2012].info()
2012년 데이터입니다. 프로모션 변수는 대부분 결측값이 아닙니다.
2011년 특정 시점부터 프로모션 변수를 체크하기 시작됬다고 생각할 수 있습니다.
train.fillna(0, inplace = True)
test.fillna(0, inplace = True)
train['Promotion1'][train['Promotion1'] < 0] = 0
train['Promotion2'][train['Promotion2'] < 0] = 0
train['Promotion3'][train['Promotion3'] < 0] = 0
train['Promotion4'][train['Promotion4'] < 0] = 0
train['Promotion5'][train['Promotion5'] < 0] = 0
test['Promotion1'][test['Promotion1'] < 0] = 0
test['Promotion2'][test['Promotion2'] < 0] = 0
test['Promotion3'][test['Promotion3'] < 0] = 0
test['Promotion4'][test['Promotion4'] < 0] = 0
test['Promotion5'][test['Promotion5'] < 0] = 0
train['Promotion1'] = np.log1p(train['Promotion1'])
train['Promotion2'] = np.log1p(train['Promotion2'])
train['Promotion3'] = np.log1p(train['Promotion3'])
train['Promotion4'] = np.log1p(train['Promotion4'])
train['Promotion5'] = np.log1p(train['Promotion5'])
test['Promotion1'] = np.log1p(test['Promotion1'])
test['Promotion2'] = np.log1p(test['Promotion2'])
test['Promotion3'] = np.log1p(test['Promotion3'])
test['Promotion4'] = np.log1p(test['Promotion4'])
test['Promotion5'] = np.log1p(test['Promotion5'])
프로모션 변수가 결측값이 아닐때 타겟값이 커진다는 사실에 기반해 결측값인 경우 0으로 채워넣었습니다.
또 프로모션 변수는 값이 극단적으로 큰 경우가 나오기 때문에 로그변환을 했습니다.
이때 0보다 작은 값은 로그변환시 좋지 않으므로 모두 0으로 바꾼 뒤 로그변환을 했습니다.
def discrete_plot(variable):
plt.figure(figsize=(12,8))
sns.violinplot(x= train[variable], y= train['Weekly_Sales'])
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
discrete_plot('Year')
연도 변수별 매출액 그래프 입니다. 매해 비슷한 모습을 보입니다. 다만 2010, 2011년 매출액 피크지점이 다소 높아보이네요.
discrete_plot('Month')
달별 매출액 그래프 입니다. 평균값이 달마다 조금씩 차이나 보입니다. 12월달이 역시 매출이 잘 나오네요.
discrete_plot('IsHoliday')
공휴일 여부에 따른 매출액 분포입니다. 크진 않으나 약간의 차이가 보이네요.
discrete_plot('Store')
매장 별 매출액 차이입니다. 사실 매장별로 매출액의 분포가 큰 폭으로 다른 것은 상식적으로 당연합니다.
분석시 유의사항으로 매장들이 현재 숫자형 값으로 입력되어있는데 순서형 자료로 분석되서는 절때 안됩니다.
매장 종류가 45가지나 되므로 트리모델을 사용하던, 회귀모형을 사용하던 1~45 값 그대로 넣는 것은 위험합니다.
모델에 넣기 전 원-핫 인코딩을 수행하겠습니다.
continuou = ['Temperature', 'Fuel_Price', 'Unemployment', 'Promotion1', 'Promotion2', 'Promotion3', 'Promotion4', 'Promotion5']
plt.figure(figsize=(15,10))
ax = sns.heatmap(tem[continuou + ['Weekly_Sales']].corr(), annot=True)
plt.show()
연속형 변수간 상관관계와 매출액과에 상관관계를 알아보고자 합니다.
온도와 연료가격은 상관관계로 확인했을때는 별로 중요한 변수는 아닌걸로 판단됩니다.
다만 상관관계가 타겟변수를 예측하는데 절대적인 수치는 아닌 점을 고려하여 정말 필요가 없는 변수라면 모델에서 알아서 걸러지겠지라는 마인드로 그대로 사용합니다.
train = pd.get_dummies(train, columns = ['Store'])
test = pd.get_dummies(test, columns = ['Store'])
print(train.shape)
print(test.shape)
train.head()
앞서 언급한대로 매장별로 원핫인코딩을 수행합니다.
train_label = np.log1p(train['Weekly_Sales'])
train.drop(['Weekly_Sales', 'id', 'Date'], axis = 1, inplace = True)
test.drop(['id', 'Date'], axis = 1, inplace = True)
타겟변수를 로그변환하고 필요없는 변수를 제거합니다.
from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor(random_state = 0, n_estimators = 100)
rf.fit(train,train_label)
sample_submission['Weekly_Sales'] = np.expm1(rf.predict(test))
sample_submission.to_csv('Sales1.csv',index=False)
간단한 렌덤포레스트 모델을 사용했습니다. 성능은 Public 기준 58454 정도 나오네요.
더 다양한 모델을 사용하거나 하이퍼파라미터 튜닝을 한다면 점수가 다소 오를 수 있습니다.
또 시도해볼만한 것은 시계열적 요소를 고려하면 좋을 것 같네요.
읽어주셔서 감사합니다.