[SSUDA] 판매량 예측 데이터 분석
!pip install kaggle
from google.colab import files
files.upload()
!mkdir -p ~/.kaggle
!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
!kaggle competitions download -c competitive-data-science-predict-future-sales
!unzip items.csv.zip
!unzip sales_train.csv.zip
!unzip sample_submission.csv.zip
!unzip test.csv.zip
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from matplotlib import pylab as plt
import matplotlib.dates as mdates
plt.rcParams['figure.figsize'] = (15.0, 8.0)
import seaborn as sns
train = pd.read_csv('./sales_train.csv')
print ('number of shops: ', train['shop_id'].max())
print ('number of items: ', train['item_id'].max())
num_month = train['date_block_num'].max()
print ('number of month: ', num_month)
print ('size of train: ', train.shape)
train.head()
변수 설명
date : 날짜 변수, date_block_num : 달 변수(2013년 1월 => 0, 2015년 10월 => 33)
shop_id, item_id : 상점/제품의 고유번호 변수
item_price : 제품의 가격 변수, item_cnt_dat : 그 날 제품이 팔린 개수
(여기서 item_cnt_dat 변수가 음수인 것은 물건이 반품된 것을 의미하는 것 같습니다.)
test = pd.read_csv('./test.csv')
test.head()
sub = pd.read_csv('./sample_submission.csv')
sub.head()
2015년 11월 데이터를 예측하는 캐글 대회입니다.
date_block_num 변수는 34가 되겠죠.
items = pd.read_csv('./items.csv')
print ('number of categories: ', items['item_category_id'].max()) # the maximun number of category id
items.head()
train_clean = train.drop(labels = ['date', 'item_price'], axis = 1)
train_clean.head()
날짜는 대체하는 date_block_num 변수가 있기 때문에 빼줍니다.
또 제품 가격 변수 또한 빼줍니다.
train_clean = train_clean.groupby(["item_id","shop_id","date_block_num"]).sum().reset_index()
train_clean = train_clean.rename(index=str, columns = {"item_cnt_day":"item_cnt_month"})
train_clean = train_clean[["item_id","shop_id","date_block_num","item_cnt_month"]]
train_clean
같은 달별로(= date_block_num 변수가 같은 값으로) 묶어줍니다.
테스트 데이터에서 예측하고자 하는 값의 범위가 달 단위이기 때문입니다.
변수 이름 또한 그에 맞게 item_cnt_month로 바꿨습니다.
check = train_clean[["shop_id","item_id","date_block_num","item_cnt_month"]]
check = check.loc[check['shop_id'] == 5]
check = check.loc[check['item_id'] == 5037]
check
특정 shop_id와 item_id 값을 가지는 값만 모았습니다.
시계열 분석을 처음하기 때문에 1차로 소량의 데이터를 다루었습니다.
이렇게 데이터 분석을 공부하면 보다 직관적으로 LSTM 모델을 학습할 수 있을 것 같습니다.
plt.figure(figsize=(10,4))
plt.title('Check - Sales of Item 5037 at Shop 5')
plt.xlabel('Month')
plt.ylabel('Sales of Item 5037 at Shop 5')
plt.plot(check["date_block_num"],check["item_cnt_month"]);
단순히 Y값에 대해 그림을 그려보았습니다.
month_list=[i for i in range(num_month+1)] # num_month = train['date_block_num'].max(), 최고값
shop = []
for i in range(num_month+1):
shop.append(5)
item = []
for i in range(num_month+1):
item.append(5037)
months_full = pd.DataFrame({'shop_id':shop, 'item_id':item,'date_block_num':month_list})
months_full.head(10)
빈 데이터를 없애기 위해 처음부터 데이터프레임을 세팅하는 모습입니다.
shop = [] for i in range(num_month+1): shop.append(5)
다만 이 코드 보다는 [5]*(num_month+1) 식으로 리스트를 구성하는게 더 깔끔한 것 같습니다.
sales_33month = pd.merge(check, months_full, how='right', on=['shop_id','item_id','date_block_num'])
sales_33month = sales_33month.sort_values(by=['date_block_num'])
sales_33month.fillna(0.00,inplace=True)
plt.figure(figsize=(10,4))
plt.title('Check - Sales of Item 5037 at Shop 5 for whole period')
plt.xlabel('Month')
plt.ylabel('Sales of Item 5037 at Shop 5')
plt.plot(sales_33month["date_block_num"],sales_33month["item_cnt_month"]);
물품 구매가 없는 데이터까지 0 값을 넣어서 그림을 그렸습니다.
for i in range(1,6):
sales_33month["T_" + str(i)] = sales_33month.item_cnt_month.shift(i)
sales_33month.fillna(0.0, inplace=True)
df = sales_33month[['shop_id','item_id','date_block_num','T_1','T_2','T_3','T_4','T_5', 'item_cnt_month']].reset_index()
df = df.drop(labels = ['index'], axis = 1)
df
시계열 분석을 기초부터 뜯어본 것 같습니다.
T1 ~ T5에 의미는 최근 5달간 이전 Y값의 기록입니다. 예를 들면 T1은 한달 전 Y값을 나타냅니다.
시간의 흐름에 따라 예측값이 영향을 받기 때문에 이러한 방식이 지금 이 데이터에서 적절합니다.
train_df = df[:-3]
val_df = df[-3:]
x_train,y_train = train_df.drop(["item_cnt_month"],axis=1),train_df.item_cnt_month
x_val,y_val = val_df.drop(["item_cnt_month"],axis=1),val_df.item_cnt_month
맨 마지막 3개 데이터를 test 데이터로 사용합니다.
from keras.models import Sequential
from keras.layers import Dense
from keras.layers import LSTM
model_lstm = Sequential()
model_lstm.add(LSTM(15, input_shape=(1,8)))
model_lstm.add(Dense(1))
model_lstm.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy'])
from sklearn.preprocessing import StandardScaler,MinMaxScaler
scaler = StandardScaler()
scaler = MinMaxScaler(feature_range=(-1, 1))
x_train_scaled = scaler.fit_transform(x_train)
x_valid_scaled = scaler.fit_transform(x_val)
x_train_reshaped = x_train_scaled.reshape((x_train_scaled.shape[0], 1, x_train_scaled.shape[1]))
x_val_resaped = x_valid_scaled.reshape((x_valid_scaled.shape[0], 1, x_valid_scaled.shape[1]))
history = model_lstm.fit(x_train_reshaped, y_train, validation_data=(x_val_resaped, y_val),epochs=70, batch_size=12, verbose=2, shuffle=False)
y_pre = model_lstm.predict(x_val_resaped)
fig, ax = plt.subplots()
ax.plot(x_val['date_block_num'], y_val, label='Actual')
ax.plot(x_val['date_block_num'], y_pre, label='Predicted')
plt.title('LSTM Prediction vs Actual Sales for last 3 months')
plt.xlabel('Month')
plt.xticks(x_val['date_block_num'])
plt.ylabel('Sales of Item 5037 at Shop 5')
ax.legend()
plt.show()
LSTM 모델을 적용시킨 모습입니다.
잘 맞췄다면 잘 맞췄다고도 말 할수 있고 아쉽다면 아쉽다고 할 수 있는 결과인 것 같습니다.
sales_data = pd.read_csv('./sales_train.csv')
item_cat = pd.read_csv('./item_categories.csv')
items = pd.read_csv('./items.csv')
shops = pd.read_csv('./shops.csv')
sample_submission = pd.read_csv('./sample_submission.csv')
test_data = pd.read_csv('./test.csv')
def basic_eda(df):
print("----------TOP 5 RECORDS--------")
print(df.head(5))
print("----------INFO-----------------")
print(df.info())
print("----------Describe-------------")
print(df.describe())
print("----------Columns--------------")
print(df.columns)
print("----------Data Types-----------")
print(df.dtypes)
print("-------Missing Values----------")
print(df.isnull().sum())
print("-------NULL values-------------")
print(df.isna().sum())
print("-----Shape Of Data-------------")
print(df.shape)
print("=============================Sales Data=============================")
basic_eda(sales_data)
print("=============================Test data=============================")
basic_eda(test_data)
print("=============================Item Categories=============================")
basic_eda(item_cat)
print("=============================Items=============================")
basic_eda(items)
print("=============================Shops=============================")
basic_eda(shops)
print("=============================Sample Submission=============================")
basic_eda(sample_submission)
앞 코드와 다른 사람 코드입니다.
여기서 train 데이터 프레임을 이 사람은 sales_data 이름으로 했네요.
사실 데이터 탐색하는 함수를 잘 만들어 놓은것 같아서 향후 다른 데이터 분석시 복사를 위해 가져왔습니다.
sales_data['date'] = pd.to_datetime(sales_data['date'],format = '%d.%m.%Y')
dataset = sales_data.pivot_table(index = ['shop_id','item_id'],
values = ['item_cnt_day'],columns = ['date_block_num'],fill_value = 0,aggfunc='sum')
dataset.reset_index(inplace = True)
dataset.head()
판다스 내 피벗 테이블을 사용하는 모습입니다. group_by 함수를 확장한 것으로 생각할 수 있습니다.
피벗 테이블은 우선 index로 데이터를 구분 짓습니다. 여기서 shop_id, item_id가 모두 같은 값을 가진 행끼리 그룹을 짓습니다.
다음으로 columns로 한번 더 데이터를 구분 짓습니다. 같은 상점, 같은 제품을 달별로 나누었습니다.
values는 실제 적용되는 값을 의미합니다. 여기서는 item_cnt_day 변수를 사용했습니다.
상점, 제품, 달이 같은 데이터 별로 구분했을때 여러개의 item_cnt_day 값을 더해주는 함수(aggfunc='sum')를 사용합니다.
빈 값도 충분히 존재할 가능성이 있는데, 그 경우 거래 기록이 존재하지 않았다는 의미이므로 0값을 채웁니다.(fill_value = 0)
dataset = pd.merge(test_data,dataset,on = ['item_id','shop_id'],how = 'left')
dataset.fillna(0,inplace = True)
dataset.head()
피벗 테이블을 사용해 같은 상점, 제품을 달 별로 거래기록이 몇건 있었는가를 나타내는 데이터 프레임입니다.
이를 활용해 test 데이터 프레임과 병합한다면 테스트 데이터에 있는 상점, 제품의 이전 달별 거래기록을 전부 알 수 있습니다.
이때 만약 병합이 안된 데이터가 있다면(이전 거래기록이 없는 데이터이겠죠?) 0으로 값을 넣어줍니다.
dataset.drop(['shop_id','item_id','ID'],inplace = True, axis = 1)
dataset.head()
X_train = np.expand_dims(dataset.values[:,:-1],axis = 2)
y_train = dataset.values[:,-1:]
X_test = np.expand_dims(dataset.values[:,1:],axis = 2)
print(X_train.shape,y_train.shape,X_test.shape)
데이터를 모델링 하기 위해 상점, 제품 데이터를 지우고, train과 test 데이터 셋을 만들었습니다.
X_train : 0번째 달부터 32번째 달까지 거래 기록 데이터
y_train : 33번째 달 거래 기록 데이터
X_test : 1번째 달부터 33번째 달까지 거래 기록 데이터(train과 test간 데이터 형식을 맞추기 위해)
우리가 예측해야할 y_test는 34번째 달 거래 기록 데이터, 즉 2015년 10월 거래 기록 데이터 입니다.
from keras.models import Sequential
from keras.layers import LSTM,Dense,Dropout
my_model = Sequential()
my_model.add(LSTM(units = 64,input_shape = (33,1)))
my_model.add(Dropout(0.4))
my_model.add(Dense(1))
my_model.compile(loss = 'mse',optimizer = 'adam', metrics = ['mean_squared_error'])
my_model.summary()
my_model.fit(X_train,y_train,batch_size = 4096,epochs = 10)
모델을 LSTM(시계열 분석) 방법을 사용해서 분석합니다. 사실 LSTM 모델을 처음 사용했는데요.
이번주에 다소 시간이 부족해 LSTM 모델의 사용방법이나 원리 등은 아직 파악하지 못했네요. (다른 사람 발표를 경청하겠습니다.)
submission_pfs = my_model.predict(X_test)
submission_pfs = submission_pfs.clip(0,20)
submission = pd.DataFrame({'ID':test_data['ID'],'item_cnt_month':submission_pfs.ravel()})
submission.to_csv('./submission.csv',index = False)
submission
데이터를 모델에 적용시켜 예측값을 찾은 뒤, 제출 형식에 맞게 데이터 프레임 형식을 조정했습니다.
이때 clip 함수는 이상치 조정 함수입니다.
clip(최솟값, 최댓값) 구조로 범위를 벗어나면 범위 내로 값을 조정시켜줍니다.
</div> </div> </div>data = {'col_0': [9, -3, 0, -1, 5], 'col_1': [-2, -7, 6, 8, -5]}
df = pd.DataFrame(data)
df
df.clip(-4, 6)
예시를 보면 보다 직관적으로 이해가 가능할 것 같습니다.
이 함수는 범용성이 넓으니 다른 데이터 분석에 자주 쓰일 수 있어 따로 정리했네요.
!kaggle competitions submit -c competitive-data-science-predict-future-sales -f submission.csv -m "Message"
캐글에 파일을 자동 제출하는 코드입니다.
스코어는 약 1.02로 만 2천명 중 6천등 정도를 기록합니다.
우선 공부하기 좋은 데이터를 찾아 줘서 고맙습니다.
시계열 자료가 현실에서 상당히 많아 꼭 공부해보고 싶은 분야였는데, 이번 기회에 분석하게 되서 너무 좋습니다.
개인적으로 공부하고 싶은 분야가 이미지 분류같은 것 보다는 자연어 처리, 시계열 분석 등 현실 세계를 설명할 수 있는 것 입니다.
이번엔 시간이 다소 부족해서 자주쓰는 시계열 모델인 LSTM 모델의 탐구가 부족했습니다.
다른 사람 발표 경청하고, 시간이 있을때 LSTM 모델을 열심히 공부해보고 싶네요.
감사합니다.