<머신러닝 • 딥러닝 문제해결 전략> 9장의 내용을 실습했습니다.
경진대회 이해하기
본 경진대회의 목표는 과거 판매 데이터를 보고 향후 판매량을 예측하는 것입니다.
타깃값이 판매량이므로 범주형 데이터가 아닙니다. 따라서 본 경진대회는 회귀문제에 속한다는 것을 알 수 있습니다.
훈련 데이터는 2013/01~2015/10 까지의 일별 판매내역이며, 상점, 상품, 상품분류에 관한 추가 데이터도 있습니다.
저희는 이 데이터를 토대로 2015년 11월 각 상점의 상품별 월간 판매량을 예측해야합니다.
지금까지 실습해왔던 경진대회에서는 훈련, 테스트, 샘플 제출 데이터 이렇게 데이터 파일을 3개씩 제공하였습니다.
그러나 이번 경진대회에서는 훈련 데이터와 관련된 추가 데이터들을 포함하여 총 6개를 제공합니다.
각 데이터들의 의미를 알아봅시다.
- sales_train : 2013/01 ~ 2015/10 까지의 일별 판매내역 (기본적인 훈련 데이터)
- shop : 상점에 관한 추가 정보
- items : 상품에 관한 추가 정보
- item_categories : 상품분류에 관한 추가 정보
- test : 테스트 데이터 (2015년 11월 각 상점의 상품별 월간 판매량을 예측해야 함)
- sample_submission : 샘플 제출 파일
그리고 유의해야할 점이 있습니다. 각 상점의 상품별 월간 판매량(타깃값)은 0에서 20개 사이여야 한다는 점입니다.
다시 말해 월간 판매량이 20개보다 많을 경우 20개로 간주한다는 뜻입니다. 음수 또한 허용되지 않습니다.
그렇기에 타깃값 뿐만아니라 판매량과 관련된 모든 피처는 모두 0에서 20 사이의 값으로 제한해주어야 한다는 것을 알 수 있습니다.
데이터 둘러보기
sales_train, shops, items, item_categories 각 데이터가 어떻게 구성되었는지 살펴본 뒤 이 데이터들을 병합하고, 병합한 데이터를 활용해 피처요약표를 만들어 보겠습니다.
먼저 데이터를 불러옵니다.
import pandas as pd
# 데이터 경로
data_path = '/kaggle/input/competitive-data-science-predict-future-sales/'
sales_train = pd.read_csv(data_path + 'sales_train.csv')
shops = pd.read_csv(data_path + 'shops.csv')
items = pd.read_csv(data_path + 'items.csv')
item_categories = pd.read_csv(data_path + 'item_categories.csv')
test = pd.read_csv(data_path + 'test.csv')
submission = pd.read_csv(data_path + 'sample_submission.csv')
그 후 sales_train 데이터를 살펴보겠습니다.
sales_train.head()
date 피처는 날짜를 나타내고 date_block_num 피처는 날짜(월) 구분자입니다. 본 경진대회 타깃값은 '월별' 판매량이므로
date_block_num 피처만 있으면 되니 필요없는 date 피처는 제거하도록 하겠습니다.
shop_id와 item_id는 각각 상점ID, 상품ID를 나타내고 item_price는 상품 판매가입니다. (러시아 데이터여서 화폐단위는 루블이고, 이 가격이 고정된 것이 아닌 시간이 지나면 바뀔 수 있다는 점을 참고해주십시오)
item_cnt_day는 당일 판매량을 나타냅니다. 그런데 해당 데이터 피처는 타깃값인 '월간' 판매량이 아닙니다. 그래서 이 피처로 월간 판매량을 구할 수 있도록 응용해야합니다. date_num_blcok 피처를 기준으로 그룹화하여 item_cnt_day 값을 합하면 타깃값이 될 수 있습니다.
info()함수를 활용해 세부사항을 살펴보겠습니다.
sales_train.info(show_counts=True)
모든 피처의 Non-Null 개수가 전체 데이터 수인 2,935,849와 같습니다. 즉 모든 피처에 결측값이 하나도 없다는 것을 알 수 있습니다. 데이터 타입은 object ,int64, float64로 다양하고, 데이터가 300만개 가까이 돼서 메모리 사용량 또한 134Mb 정도 됩니다. 작업 속도를 빠르게 만들기 위해 뒤에서 메모리 사용량을 줄이는 방법을 실시해주도록 하겠습니다.
본 경진대회 데이터는 시계열 데이터로 시간 흐름이 중요합니다. 2013/01 ~ 2015/09까지의 판매 내역을 훈련 데이터로 사용하고 2015/10의 데이터를 검증 데이터로 사용해보겠습니다.
shops 데이터를 살펴보겠습니다.
shops.head()
상점에 대한 추가 정보가 담긴 데이터로 상점명과 상점ID 피처가 있습니다. 상점명이 러시아어로 되어있습니다. 상점명의 첫 단어는 상점이 위치한 도시를 나타내는데 이를 활용해 shop_name에서 첫 단어를 추출해 도시 피처를 새롭게 만들어보겠습니다.
shops 데이터의 shop_id는 sales_train에도 있는 피처이기 때문에 shop_id를 기준으로 sales_train과 shops를 병합해 줄 수 있습니다.
shops.info()
상점은 60개, 결측값이 없고, 데이터 개수가 적어 메모리 용량도 적다는 것을 알 수 있습니다.
items 데이터 살펴보기
items.head()
상품명, 상품 ID, 상품분류 ID로 구성되어있습니다. 상품명 피처의 경우 러시아어로 되어있고, 유용한 정보 또한 얻기 힘들어 모델링할 땐 제거해주는 것이 좋겠습니다.
item_id는 sales_train에도 존재하는 피처로, item_id 피처를 기준으로 sales_train과 items를 병합할 수 있습니다.
items.info()
상품은 총 22,170개이고, 결측값은 없습니다.
item_categories 데이터 살펴보기
item_categories.head()
상품 분류명과 상품 분류ID로 구성되어 있고 item_category_id는 sales_train에도 해당 피처가 존재합니다.
즉 item_category_id를 기준으로 sales_train과 item_categories를 병합할 수 있습니다.
그리고 상품분류명 또한 러시아어임을 확인할 수 있습니다. 해당 데이터의 상품분류명 첫단어는 대분류를 뜻합니다.
이를 활용해 추후 피처 엔지니어링 시 대분류 피처로 만들어보겠습니다.
item_categories.info()
84개의 상품분류가 있으며 역시나 결측값은 없습니다.
테스트 데이터 살펴보기
test.head()
테스트 데이터 식별자인 ID, 상점ID, 상품ID로 구성되어있습니다. 여기서 각 상점의 월간 판매량을 예측해야합니다.
데이터 병합
앞의 과정에서 sales_train, shops, items, item_categories 데이터는 특정 피처를 기준으로 병합할 수 있음을 확인했습니다. 이를 활용해 데이터들을 서로 병합해보도록 하겠습니다.
train = sales_train.merge(shops, on='shop_id', how='left')
train = train.merge(items, on='item_id', how='left')
train = train.merge(item_categories, on='item_category_id', how='left')
train.head()
sales_train, shops, items, item_categories를 모두 병합해 그 결과를 train에 할당해보겠습니다.
피처 요약표 만들기
앞서 병합한 train을 활용해 피처요약표를 만들어보겠습니다. 피처 요약표에는 데이터 타입, 결측값 개수, 고윳값 개수, 첫번째 값, 두번째 값을 담아보겠습니다.
def resumetable(df):
print(f'데이터 세트 형상: {df.shape}')
summary = pd.DataFrame(df.dtypes, columns=['데이터 타입'])
summary = summary.reset_index()
summary = summary.rename(columns={'index': '피처'})
summary['결측값 개수'] = df.isnull().sum().values
summary['고윳값 개수'] = df.nunique().values
summary['첫 번째 값'] = df.loc[0].values
summary['두 번째 값'] = df.loc[1].values
return summary
resumetable(train)
shop_id와 shop_name, item_id와 item_name, item_category_id와 item_category_came 피처의 고유값 개수가 서로 같습니다. 이는 id와 name이 일대일로 매칭된다는 뜻입니다. 이러한 이유로 id와 name 둘 중 한 가지만 있으면 된다는 것을 확인하게 되었습니다. (추후에 name 피처를 활용해 모델링에 도움되는 파생피처를 만들 수 있습니다.)
데이터 시각화
일별 판매량
train에서 식별자나 문자 데이터를 제외하면 item_cnt_day 피처와 item_price 피처만 남습니다.
수치형 데이터인 두 피처를 박스플롯으로 시각화해보겠습니다. 먼저 일별판매량인 item_cnt_day 피처를 시각화해보겠습니다.
# 일별 판매량 item_cnt_day
import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
sns.boxplot(y='item_cnt_day', data=train);
이상치가 많아 박스플롯 모양이 이상한 것을 확인할 수 있습니다. 과한 이상치인 1000이상인 데이터를 추후에 제거해야한다는 것을 확인할 수 있었습니다.
판매가 (상품가격)
# 판매가(상품가격) item_price
sns.boxplot(y='item_price', data=train)
이번 박스플롯도 납작한 모양으로 50,000루블 이상인 이상치를 제거해야한다는 것을 확인할 수 있습니다.
그룹화
특정 피처를 기준으로 그룹화 해 원하는 집계값을 구하려면 groupby() 함수를 사용합니다. 회귀 문제에서 자주 쓰이는 함수로, 지금처럼 주로 집곗값을 구할 때 사용합니다.
# 그룹화 groupby()
group = train.groupby('date_block_num').agg({'item_cnt_day': 'sum'})
group.reset_index() # 인덱스 재설정
월별판매량
groupby 합 연산 결과를 막대그래프로 시각화해보겠습니다. 월별 판매량을 의미합니다.
# 월별 월간 판매량. groupby 합연산 결과
mpl.rc('font', size=13)
figure, ax = plt.subplots()
figure.set_size_inches(11, 5)
# 월별 총 상품 판매량
group_month_sum = train.groupby('date_block_num').agg({'item_cnt_day' : 'sum'})
group_month_sum = group_month_sum.reset_index()
# 월별 총 상품 판매량 막대그래프
sns.barplot(x='date_block_num', y='item_cnt_day', data=group_month_sum)
# 그래프 제목, x축 라벨, y축 라벨명 설정
ax.set(title='Distribution of monthly item counts by date block number',
xlabel='Date block number',
ylabel='Monthly item counts');
11일 때와 23일 때 판매량이 가장 많은데, 각각 2013년 12월과 2014년 12월입니다. 즉 연말이라 판매량이 급증한 것으로 보입니다.
상품분류별 판매량
상품분류는 총 84개로, 피처 고윳값개수를 알려주는 nunique()으로 구할 수 있습니다.
# 상품 분류별 판매량. 피처고유값 개수 알려주는 nunique()로 구할 수 있다
train['item_category_id'].nunique()
# 상품 분류별 월간 판매량
figure, ax = plt.subplots()
figure.set_size_inches(11, 5)
# 상품 분류별 총 상품 판매량
group_cat_sum = train.groupby('item_category_id').agg({'item_cnt_day' : 'sum'})
group_cat_sum = group_cat_sum.reset_index()
# 총 판매량이 10,000개를 초과하는 상품분류만 추출
group_cat_sum = group_cat_sum[group_cat_sum['item_cnt_day']> 10000]
# 상품 분류별 총 상품 판매량 막대그래프
sns.barplot(x='item_category_id', y='item_cnt_day', data=group_cat_sum)
ax.set(title='Distribution of total item counts by item category id',
xlabel='Item category ID',
ylabel='Total item counts')
ax.tick_params(axis='x', labelrotation=90) # x축 라벨 회전
ID 40인 상품분류가 가장 많이 팔렸습니다. 그 뒤로는 ID30과 55가 뒤를 잇습니다.
상점별 판매량
상점 개수는 60개입니다. 그래프 하나로 나타내기에 많아서 판매량이 10,000개를 초과하는 상점만 추렸습니다.
# 상점별 판매량 (상점별 월간 판매량)
figrue, ax = plt.subplots()
figrue.set_size_inches(11, 5)
# 상점별 총 상품 판매량
group_shop_sum = train.groupby('shop_id').agg({'item_cnt_day' : 'sum'})
group_shop_sum = group_shop_sum.reset_index()
group_shop_sum =group_shop_sum[group_shop_sum['item_cnt_day'] > 10000]
# 상점별 총 상푼 판매량 막대그래프
sns.barplot(x='shop_id', y='item_cnt_day', data=group_shop_sum)
ax.set(title='Distribution of total item counts by shop id',
xlabel='Shop ID',
ylabel='Total item counts')
ax.tick_params(axis='x', labelrotation=90)
ID가 31인 상점이 가장 많은 상품을 판매했고, ID가 25인 상점이 뒤를 잇습니다.
7~8개 상점이 다른 상점보다 많이 판매하는 양상을 보입니다.
'Data > MLDL' 카테고리의 다른 글
병든 잎사귀 식별 경진대회 (2) (0) | 2023.02.06 |
---|---|
병든 잎사귀 식별 경진대회 (0) | 2023.02.06 |
안전 운전자 예측 경진대회: 성능개선2(XGBoost 모델) (0) | 2022.11.14 |
안전 운전자 예측 경진대회: 성능개선1(lightGBM 모델) (0) | 2022.11.14 |
안전 운전자 예측 경진대회: 베이스라인 모델 (0) | 2022.11.14 |