1. 주제 : 상점 신용카드 매출 예측

  • 2019.07 ~ 2019.10 에 DACON에서 주최한 상점 신용카드 매출 예측 경진대회의 데이터

  • 데이터 제공자 : 핀테크 기업 FUNDA

  • 문제 : 2019년 2월 28일까지의 카드 거래 데이터를 이용해 2019년 3월 1일 ~ 5월 31일 까지의 상점별 3개월 총 매출 예측하기

2. 데이터 소개 및 문제 정의

데이터 소개

데이터 출처 : DACON

funda_train.csv : 모델 학습용 데이터

  • store_id : 상점의 고유id

  • card_id : 사용한 카드의 고유 id

  • card_company : 비식별화된 카드 회사

  • transcated_date : 거래 날짜

  • transacted_time : 거래 시간

  • installment_term : 할부 개월 수

  • region : 상점이 위치한 지역

  • type_of_business : 상점 업종

  • amount : 거래액 (단위: 미상)

문제 정의

  • 주어진 데이터는 거래액 (amount) 인데, 예측해야 할 값은 ‘상점 별 매출액’ 이다.

  • 또한, store_id별 3월 1일 ~ 5월 31일 매출(3개월)의 총 합을 예측해야 한다.

  • 각 상점별 특정 기간 기준 매출 데이터의 특징을 기반으로 + 3개월 뒤의 매출액을 예측하는 식으로 모델이 구축되야 할 것 같다.

  • 그 특정 기간은 정답 format과 같은 3개월을 기준으로 상점별 특징을 잡는 것이 좋을 것 같다.


EDA로 살펴본 데이터들을 모델링하기 위해 전처리를 통해 별도의 feature를 만들고 불필요한 자료들을 다듬는 과정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import os

# 캔버스 사이즈 적용
plt.rcParams["figure.figsize"] = (12, 9)

# 컬럼 전체 확인 가능하도록 출력 범위 설정
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 10000)

# 불필요한 경고 표시 생략
import warnings
warnings.filterwarnings(action = 'ignore')

# pandas 결과값의 표현 범위 소수점 2자리수로 변경
pd.options.display.float_format = "{:.2f}".format

# 파일 로드위한 directory 확인 및 현재 경로로 설정
a = os.getcwd()
os.chdir(a)
1
2
df = pd.read_csv("funda_train.csv")
submission_df = pd.read_csv("submission.csv")

Data Processing - Train data

년/월 추출

1
2
3
4
5
6
7
8
# .str.split으로 년/월 추출

# year
df['transacted_year'] = df['transacted_date'].str.split('-', expand = True).iloc[:, 0].astype(int)

# month
df['transacted_month'] = df['transacted_date'].str.split('-', expand = True).iloc[:, 1].astype(int)
df.head()
store_id card_id card_company transacted_date transacted_time installment_term region type_of_business amount transacted_year transacted_month
0 0 0 b 2016-06-01 13:13 0 NaN 기타 미용업 1857.14 2016 6
1 0 1 h 2016-06-01 18:12 0 NaN 기타 미용업 857.14 2016 6
2 0 2 c 2016-06-01 18:52 0 NaN 기타 미용업 2000.00 2016 6
3 0 3 a 2016-06-01 20:22 0 NaN 기타 미용업 7857.14 2016 6
4 0 4 c 2016-06-02 11:06 0 NaN 기타 미용업 2000.00 2016 6
  • transacted_date의 시작점이 2016년 6월 1일 이므로 transacted_year에서 2016을 빼고 ‘month’를 기준으로 데이터를 분류하는 index번호처럼 생성

계산 방식은 다음과 같다.

1
2
3
standard_t = (연도-2016)*12 + '월'

  • 2016년의 경우 6월 =6, 7월 =7

  • 2017년의 경우 1월 = 12, 2월 = 24

  • 위와 같이 각 연도별 월을 구분지을 수 있게 가공된다.

기준 시점이 될 std_mth (= standard_month) 생성

1
2
3
# 데이터 병합을 위해 시간 데이터를 standard_t 로 변경하고, 불필요한 컬럼은 drop
df['std_mth'] = (df['transacted_year'] - 2016) * 12 + df['transacted_month']
df.drop(['transacted_year', 'transacted_month', 'transacted_date', 'transacted_time'], axis = 1, inplace = True)
1
df['std_mth'].unique()
array([ 6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
       23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38])

업종 특성, 지역, 할부 평균 - 파생변수 생성

installment_term - 매장별 평균 할부 비율

  • EDA에서 본 바와 같이 대부분이 일시불이므로 일시불 / 할부로 범주를 이원화 하여 사용 (전체 할부 개월 수 로 하기엔 너무 범위가 큼)

  • 다만, 단순 할부 여부는 동일 매장에서 결제 건수가 여러건 발생 하므로 T/F로 입력할 수가 없다. -> 매장별 평균 할부개월 비율로 전처리

1
2
df['installment_term'] = (df['installment_term'] > 0).astype(int) # bool to int (True=1, False=0)
df['installment_term'].value_counts()
0    6327632
1     228981
Name: installment_term, dtype: int64
1
2
3
# 각 매장 기준으로 할부 기간의 평균 계산
installment_term_per_store = df.groupby(['store_id'])['installment_term'].mean()
installment_term_per_store.head()
store_id
0   0.04
1   0.00
2   0.08
4   0.00
5   0.08
Name: installment_term, dtype: float64
1
2
# 결측치 확인
pd.DataFrame(installment_term_per_store).isnull().sum()
installment_term    0
dtype: int64

region - group by로 데이터 셋팅

  • 범주형이지만, 종류가 너무 많아 dummy 하여 사용하기는 어려움 -> 활용을 위해 파생변수 생성 필요 (group by)

  • 결측치가 2042766 개 존재함

  • 결측치를 drop하기에는 너무 많은 데이터 -> 전처리를 통해 group by에 포함되도록 해야 함

1
2
3
# 결측치 전처리
df['region'].fillna('미분류', inplace = True)
df['region'].value_counts().head()
미분류       2042766
경기 수원시     122029
충북 청주시     116766
경남 창원시     107147
경남 김해시     100673
Name: region, dtype: int64
1
2
# 결측치 재확인
df['region'].value_counts().isnull().sum()
0

type_of_business - 데이터 셋팅

  • 범주형이지만, 종류가 너무 많아(145개) dummy 하여 사용하기는 어려움 -> 활용을 위해 파생변수 생성 필요 (group by)

  • 결측치가 3952609 개 존재함

  • 결측치를 drop하기에는 너무 많은 데이터 -> 전처리를 통해 group by에 포함되도록 해야 함

1
2
3
# groupby에 결측을 포함시키기 위해, 결측을 문자로 대체
df['type_of_business'].fillna('미분류', inplace = True)
df['type_of_business'].value_counts().head()
미분류        3952609
한식 음식점업     745905
두발 미용업      178475
의복 소매업      158234
기타 주점업      102413
Name: type_of_business, dtype: int64
1
2
# 결과 재확인
df['type_of_business'].value_counts().isnull().sum()
0

불필요한 변수 제거

  • card_id, card_company는 특징으로 사용하기에는 범주가 너무 세밀하고, 특징으로서 유의하지 않을 것이라 판단되므로 drop
1
df.drop(['card_id', 'card_company'], axis = 1, inplace = True)

중복 데이터 처리

1
2
3
# 'store_id', 'region', 'type_of_business', 'std_mth'를 기준으로 중복 제거
train_df = df.drop_duplicates(subset = ['store_id', 'region', 'type_of_business', 'std_mth'])[['store_id', 'region', 'type_of_business', 'std_mth']]
train_df.head()
store_id region type_of_business std_mth
0 0 미분류 기타 미용업 6
145 0 미분류 기타 미용업 7
323 0 미분류 기타 미용업 8
494 0 미분류 기타 미용업 9
654 0 미분류 기타 미용업 10

평균 할부율 입력 to train_df

1
train_df['평균할부율'] = train_df['store_id'].replace(installment_term_per_store.to_dict())

std_mth(기준월)의 -1, -2, -3 시점에 대한 매장별/지역별/ 업종별 매출 파생변수 생성

  • df1에서는 시점 t를, 다른 데이터에서는 시점 t+1 or t-1 을 붙여야 되는 상황

  • t가 유니크 하다면, df2를 shift()해서 공백을 만든 뒤 concat을 하면 된다. (시계열 데이터에서 많이 사용)

  • t가 유니크 하지 않은 경우는 새로운 변수( e.g: t_1)을 만들어서 merge한다.

preprocessing.png


  • 단, std_mth는 각 매장별로 값이 존재하고, 필요한 값은 기준 월(std_mth)을 중심으로 -1, -2, -3 개월의 매출 값이다.

  • 이를 위해 for문을 사용하여 i = 1, 2, 3 일때 각각 t_{i} 값이 들어가는 변수를 만들어서 병합할 것이다.

  • 중복 방지를 위해 병합한 변수명 변경 및 drop 작업도 함께 수행한다.

각 시점별 매장의 월 매출 (파생변수)

각 store당 월별 amount 계산
  • standard_t (월): 6(2016년 6월) ~ 38 (2019년 2월)

  • 즉, store_id가 0이라는 매장의 standard_t가 6일때의 매출 , 7일때의 매출… 38일때의 매출을 각각 입력

1
2
3
# store_id와 standard_t에 따른 amount 합계 계산: total_amt_per_mth_and_store
total_amt_per_mth_and_store = df.groupby(['store_id', 'std_mth'], as_index = False)['amount'].sum()
total_amt_per_mth_and_store.head()
store_id std_mth amount
0 0 6 747000.00
1 0 7 1005000.00
2 0 8 871571.43
3 0 9 897857.14
4 0 10 835428.57
  • group by의 경우 지정한 조건에 해당하는 값이 없으면 건너뛴다.

  • 각 지점별로 ‘모든 월’ 에 대한 amount가 들어가야 하므로, 매장별 매출에서 값이 생략된 월들은 0으로 값을 넣어줘야 한다.

  • 그렇지 않을 경우 다른 데이터와 merge 할 경우 에러 발생, 혹은 잘못된 값이 입력될 수 있다.

매장별로 결측된 월이 있는지 확인

1
total_amt_per_mth_and_store.groupby(['store_id'])['std_mth'].count()
store_id
0       33
1       33
2       33
4       33
5       33
        ..
2132    31
2133    32
2134    26
2135    31
2136    22
Name: std_mth, Length: 1967, dtype: int64
  • 몇몇 store_id에서 누락된 값이 보인다.

  • group by에서 생략된 값이 실제 매출이 발생했는데 단순 누락된 값인지, 매장 운영이 되지 않은 기간이어서 0인건지 확인이 어렵다.

  • 이를 위해 pivot_table 활용


  • 누락된 값이 연속적인 경우만 있다면 매출이 발생하지 않은 것으로 가정 하여 0으로 채운다.

    • e.g) 시작점은 2016년 6월이지만, 누락된 매장들은 시작점 보다 늦게 개점하여 누락된 경우
  • 누락된 값이 불연속적이라면, 매출값이 누락된 것이라 가정하고 누락된 값의 ‘전/후’ 값으로 채워넣는다.

1
2
3
# pivot_table로 nan값의 분포 형태 확인
check_nan_amount = pd.pivot_table(df, values = 'amount', index = 'store_id', columns = 'std_mth', aggfunc = 'sum')
check_nan_amount
std_mth 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
store_id
0 747000.00 1005000.00 871571.43 897857.14 835428.57 697000.00 761857.14 585642.86 794000.00 720257.14 685285.71 744428.57 682000.00 728285.71 749000.00 840857.14 600571.43 630857.14 812714.29 643142.86 685285.71 848428.57 636142.86 686428.57 707285.71 758714.29 679857.14 651857.14 739000.00 676000.00 874571.43 682857.14 515285.71
1 137214.29 163000.00 118142.86 90428.57 118071.43 111857.14 115571.43 129642.86 160214.29 168428.57 152571.43 107500.00 110357.14 132571.43 107642.86 131357.14 80142.86 110142.86 100714.29 109571.43 94214.29 108357.14 108857.14 80500.00 78285.71 100785.71 92142.86 63571.43 95000.00 80785.71 85285.71 148285.71 77428.57
2 260714.29 82857.14 131428.57 142857.14 109714.29 198571.43 160000.00 180714.29 154285.71 43571.43 201428.57 186428.57 120571.43 207142.86 190000.00 232857.14 266714.29 252857.14 238571.43 299714.29 312857.14 189714.29 283571.43 472857.14 354285.71 689285.71 457857.14 480714.29 510000.00 185428.57 340714.29 407857.14 496857.14
4 733428.57 768928.57 698428.57 936428.57 762714.29 859571.43 1069857.14 689142.86 1050142.86 970285.71 1085171.43 1035857.14 894142.86 1027285.71 1186857.14 972571.43 1060571.43 1189142.86 1010142.86 831571.43 651000.00 908000.00 792214.29 775428.57 881285.71 1050928.57 849285.71 698142.86 828428.57 883000.00 923857.14 944857.14 882285.71
5 342500.00 432714.29 263500.00 232142.86 211571.43 182085.71 147571.43 120957.14 186428.57 169000.00 312857.14 235342.86 475857.14 410914.29 297714.29 291428.57 396157.14 399285.71 441557.14 388428.57 316785.71 370142.86 297857.14 443857.14 563714.29 607071.43 482885.71 195000.00 324928.57 383300.00 399571.43 323000.00 215514.29
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2132 NaN NaN 281142.86 783428.57 822000.00 941142.86 731142.86 684571.43 701000.00 742428.57 624614.29 566857.14 742857.14 865285.71 898428.57 787857.14 531857.14 1095000.00 1014071.43 580785.71 703857.14 909142.86 938428.57 670571.43 757857.14 753428.57 524142.86 637714.29 938571.43 729857.14 474285.71 571142.86 630428.57
2133 85000.00 367857.14 743571.43 494714.29 178571.43 124285.71 36285.71 31857.14 145114.29 313128.57 300685.71 384057.14 524342.86 425342.86 438257.14 493757.14 421214.29 646628.57 601171.43 433414.29 277342.86 308485.71 484071.43 626071.43 395700.00 421614.29 548942.86 310971.43 192700.00 84714.29 NaN 84000.00 116285.71
2134 NaN NaN NaN NaN NaN 393000.00 678214.29 459071.43 463428.57 446285.71 363571.43 580285.71 486857.14 503642.86 467785.71 431642.86 248714.29 355714.29 374142.86 313785.71 171857.14 245571.43 72357.14 216142.86 209785.71 140714.29 NaN NaN 84428.57 60785.71 4285.71 209428.57 166000.00
2135 NaN NaN 357428.57 786000.00 760285.71 751428.57 682857.14 544000.00 656000.00 631000.00 698571.43 611714.29 763857.14 675857.14 575714.29 575857.14 473714.29 505285.71 521857.14 563714.29 443928.57 468285.71 445142.86 577571.43 714428.57 438428.57 566142.86 509857.14 850428.57 589428.57 541857.14 462285.71 404285.71
2136 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 469214.29 2713814.29 2967357.14 3364357.14 3190642.86 3364285.71 2706714.29 2869571.43 1877857.14 2023642.86 2352071.43 2197428.57 2194428.57 2333000.00 1601785.71 1222300.00 2775714.29 2012214.29 2135428.57 2427428.57 1873642.86 2227428.57

1967 rows × 33 columns

  • pivot table을 통해 보았을때, ‘월’ 의 중간 중간 nan값이 발생하는 것으로 보아, 실제 매출은 발생하였으나 누락된 데이터 일 것이라 가정

  • nan값을 바로 앞, 뒤 standard_t의 매출로 채워 넣는다.

1
2
3
4
5
6
# 발생한 nan값을 바로 앞/뒤의 값으로 채워넣는다.
# method = 'ffill', axis = 1 #전
# method = 'bfill', axis = 1 #후

total_amt_per_mth_and_store = check_nan_amount.fillna(method = 'ffill', axis = 1).fillna(method = 'bfill', axis = 1)
total_amt_per_mth_and_store.head(10)
std_mth 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
store_id
0 747000.00 1005000.00 871571.43 897857.14 835428.57 697000.00 761857.14 585642.86 794000.00 720257.14 685285.71 744428.57 682000.00 728285.71 749000.00 840857.14 600571.43 630857.14 812714.29 643142.86 685285.71 848428.57 636142.86 686428.57 707285.71 758714.29 679857.14 651857.14 739000.00 676000.00 874571.43 682857.14 515285.71
1 137214.29 163000.00 118142.86 90428.57 118071.43 111857.14 115571.43 129642.86 160214.29 168428.57 152571.43 107500.00 110357.14 132571.43 107642.86 131357.14 80142.86 110142.86 100714.29 109571.43 94214.29 108357.14 108857.14 80500.00 78285.71 100785.71 92142.86 63571.43 95000.00 80785.71 85285.71 148285.71 77428.57
2 260714.29 82857.14 131428.57 142857.14 109714.29 198571.43 160000.00 180714.29 154285.71 43571.43 201428.57 186428.57 120571.43 207142.86 190000.00 232857.14 266714.29 252857.14 238571.43 299714.29 312857.14 189714.29 283571.43 472857.14 354285.71 689285.71 457857.14 480714.29 510000.00 185428.57 340714.29 407857.14 496857.14
4 733428.57 768928.57 698428.57 936428.57 762714.29 859571.43 1069857.14 689142.86 1050142.86 970285.71 1085171.43 1035857.14 894142.86 1027285.71 1186857.14 972571.43 1060571.43 1189142.86 1010142.86 831571.43 651000.00 908000.00 792214.29 775428.57 881285.71 1050928.57 849285.71 698142.86 828428.57 883000.00 923857.14 944857.14 882285.71
5 342500.00 432714.29 263500.00 232142.86 211571.43 182085.71 147571.43 120957.14 186428.57 169000.00 312857.14 235342.86 475857.14 410914.29 297714.29 291428.57 396157.14 399285.71 441557.14 388428.57 316785.71 370142.86 297857.14 443857.14 563714.29 607071.43 482885.71 195000.00 324928.57 383300.00 399571.43 323000.00 215514.29
6 568857.14 568857.14 568857.14 1440142.86 1238857.14 1055428.57 926857.14 885642.86 800357.14 930714.29 855071.43 1029785.71 1071571.43 1037214.29 1054857.14 937857.14 1216285.71 1833571.43 2429500.00 2147714.29 2113357.14 2348714.29 1876857.14 1808357.14 1752285.71 1583785.71 1628785.71 2074071.43 1907642.86 2389142.86 2230285.71 2015500.00 2463857.14
7 107857.14 107857.14 107857.14 375642.86 323642.86 345000.00 291428.57 231614.29 271357.14 249857.14 131500.00 118642.86 53285.71 372285.71 183000.00 527857.14 218214.29 817714.29 750645.71 761571.43 636571.43 339857.14 1039357.14 265714.29 419542.86 462842.86 423128.57 320328.57 420028.57 314385.71 302414.29 136471.43 57971.43
8 192571.43 192571.43 192571.43 192571.43 192571.43 192571.43 735500.00 467857.14 475642.86 603500.00 1074642.86 1144571.43 1030928.57 1375571.43 1078928.57 984500.00 896785.71 514500.00 552214.29 618785.71 461714.29 744500.00 867071.43 1837428.57 1359857.14 1213542.86 1086000.00 1369557.14 1272071.43 1260557.14 1157257.14 1134671.43 1298328.57
9 107142.86 107142.86 107142.86 107142.86 107142.86 637142.86 603571.43 225428.57 287142.86 344428.57 352857.14 208571.43 178571.43 761714.29 535000.00 715714.29 672142.86 634285.71 333714.29 295428.57 628285.71 318571.43 1016857.14 638571.43 276571.43 340000.00 254285.71 926571.43 871428.57 692857.14 662857.14 370000.00 405714.29
10 496714.29 496714.29 496714.29 496714.29 496714.29 496714.29 496714.29 496714.29 496714.29 496714.29 496714.29 492571.43 341857.14 442428.57 438714.29 438571.43 470428.57 144428.57 444714.29 466714.29 722857.14 338571.43 475000.00 290285.71 607857.14 444571.43 641428.57 795571.43 499285.71 590142.86 518428.57 525142.86 654857.14
  • train_df와 병합을 위해 pivot_table 형태인 total_amt_per_mth_and_store 테이블을 stack() 함수를 사용해 동일한 형태의 DataFrame으로 변경

  • stack() : column 과 row를 바꾸는 함수

  • 위의 pivot_table 기준으로보면 store_id, std_mth, amount가 column이 되고, 기존 columns 별로 입력되있던 값이 row가 된다.

1
total_amt_per_mth_and_store = total_amt_per_mth_and_store.stack().reset_index() # index 번호 새로 부여
1
total_amt_per_mth_and_store
store_id std_mth 0
0 0 6 747000.00
1 0 7 1005000.00
2 0 8 871571.43
3 0 9 897857.14
4 0 10 835428.57
... ... ... ...
64906 2136 34 2012214.29
64907 2136 35 2135428.57
64908 2136 36 2427428.57
64909 2136 37 1873642.86
64910 2136 38 2227428.57

64911 rows × 3 columns

1
2
3
# amount로 컬럼명 변경
total_amt_per_mth_and_store.rename({0:"amount"}, axis = 1, inplace = True)
total_amt_per_mth_and_store.head(3)
store_id std_mth amount
0 0 6 747000.00
1 0 7 1005000.00
2 0 8 871571.43
매장별 월 매출 + train_df
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# std_mth + i (i = 1, 2, 3) 시점의 부착
# train_df의 std_mth는 total_amt_per_mth_and_store의 std_mth-i와 부착되어야 하므로, total_amt_per_mth_and_store의 std_mth에 i를 더함

for i in range(1, 4): 
    # i값에 따른 새로운 변수 생성
    total_amt_per_mth_and_store['std_mth_{}'.format(i)] = total_amt_per_mth_and_store['std_mth'] + i
    
    # 두 테이블 데이터 결합
    # 두 table에 모두 std_mth가 존재하므로 drop
    train_df = pd.merge(train_df, total_amt_per_mth_and_store.drop('std_mth', axis = 1), left_on = ['store_id', 'std_mth'], right_on = ['store_id', 'std_mth_{}'.format(i)])
    
    # 변수명 변경: 다음 loop에서 merge할때 중복 column 때문에 _x, _y가 생기는 것 방지
    train_df.rename({"amount":"{}_before_amount".format(i)}, axis = 1, inplace = True)
    
    #필요없어진 변수 drop
    train_df.drop(['std_mth_{}'.format(i)], axis = 1, inplace = True)
    total_amt_per_mth_and_store.drop(['std_mth_{}'.format(i)], axis = 1, inplace = True)
    
train_df.head()
store_id region type_of_business std_mth 평균할부율 1_before_amount 2_before_amount 3_before_amount
0 0 미분류 기타 미용업 9 0.04 871571.43 1005000.00 747000.00
1 0 미분류 기타 미용업 10 0.04 897857.14 871571.43 1005000.00
2 0 미분류 기타 미용업 11 0.04 835428.57 897857.14 871571.43
3 0 미분류 기타 미용업 12 0.04 697000.00 835428.57 897857.14
4 0 미분류 기타 미용업 13 0.04 761857.14 697000.00 835428.57
  • std_mth의 시작점이 6 -> 9로 바뀐다. (for문이 적용되고 merge되면서 사라진 것)

  • 6월의 amount는 3_before_amount에 입력되어 있다.

  • 일괄적으로 +3이 된 것이기 때문에 ok

  • std_mth 9(=16년 9월)를 기준으로 보았을때

    • 1_before_amount는 16년 8월, 2_before_amount는 16년 7월, 3_before_amount는 16년 6월을 의미한다.
  • 전체적으로 본다면 2016년 6월에 대한 특징 데이터는 소실된다. 하지만 반대로 다른 시점에 특성을 구분할 수 있는 데이터가 늘어남


  • i값을 3으로 한 이유 :

    1) 예측해야 하는 값이 3개월 평균 이기 때문

    2) i가 클수록 유실되는 데이터량이 많아져서 최소화 하기 위해( 1)의 기간도 감안 )

각 시점별 지역의 월 매출 (파생변수)

region(지역) 별 평균 매출 계산
1
2
3
4
5
6
7
8
9
#지역별 매장을 분류(dict) & 중복제거
store_to_region = df[['store_id', 'region']].drop_duplicates().set_index(['store_id'])['region'].to_dict() 

# store_id를 region으로 대체 -> 위에서 사용한 코드 동일하게 사용 가능
total_amt_per_mth_and_store['region'] = total_amt_per_mth_and_store['store_id'].replace(store_to_region)

# 지역별 평균 매출 계산
# total_amt_per_mth_and_store에 region이란 컬럼 추가 후 amt_mean_per_std_mth_and_region 테이블로 변경
amt_mean_per_std_mth_and_region = total_amt_per_mth_and_store.groupby(['region', 'std_mth'], as_index = False)['amount'].mean()
1
amt_mean_per_std_mth_and_region.head(3)
region std_mth amount
0 강원 강릉시 6 623271.75
1 강원 강릉시 7 501311.43
2 강원 강릉시 8 508130.79
  • 위에서 store_id의 누락치를 처리하는 과정에서 nan값을 모두 처리함

  • 즉, store_id를 region으로 대체한 것이기 때문에, pivot_table & stack 등을 통한 결측치 제거는 하지 않아도 됨.

train_df + amt_mean_per_std_mth_and_region
1
2
3
4
5
6
7
8
9
10
11
12
# std_mth + i (i = 1, 2, 3)

for i in range(1, 4):
    amt_mean_per_std_mth_and_region['std_mth_{}'.format(i)] = amt_mean_per_std_mth_and_region['std_mth'] + i
    train_df = pd.merge(train_df, amt_mean_per_std_mth_and_region.drop('std_mth', axis = 1), left_on = ['region', 'std_mth'], right_on = ['region', 'std_mth_{}'.format(i)])
    train_df.rename({"amount":"{}_before_amount_of_region".format(i)}, axis = 1, inplace = True)
    
    # 계산에 사용되고 필요없어진 데이터 drop
    train_df.drop(['std_mth_{}'.format(i)], axis = 1, inplace = True)
    amt_mean_per_std_mth_and_region.drop(['std_mth_{}'.format(i)], axis = 1, inplace = True)    
    
train_df.head()
store_id region type_of_business std_mth 평균할부율 1_before_amount 2_before_amount 3_before_amount 1_before_amount_of_region 2_before_amount_of_region 3_before_amount_of_region
0 0 미분류 기타 미용업 9 0.04 871571.43 1005000.00 747000.00 761987.42 756108.67 739654.07
1 1 미분류 미분류 9 0.00 118142.86 163000.00 137214.29 761987.42 756108.67 739654.07
2 2 미분류 미분류 9 0.08 131428.57 82857.14 260714.29 761987.42 756108.67 739654.07
3 5 미분류 의복 액세서리 및 모조 장신구 도매업 9 0.08 263500.00 432714.29 342500.00 761987.42 756108.67 739654.07
4 7 미분류 미분류 9 0.01 107857.14 107857.14 107857.14 761987.42 756108.67 739654.07

각 시점별 업종의 월 매출 (파생변수)

type_of_business별 평균 매출 계산
1
2
3
4
5
6
7
8
##업종별 매장을 분류(dict) & 중복제거
store_to_type_of_business = df[['store_id', 'type_of_business']].drop_duplicates().set_index(['store_id'])['type_of_business'].to_dict()

# store_id를 type_of_business으로 대체 -> 위에서 사용한 코드 동일하게 사용 가능
total_amt_per_mth_and_store['type_of_business'] = total_amt_per_mth_and_store['store_id'].replace(store_to_type_of_business)

# 지역별 평균 매출 계산
amount_mean_per_mth_and_type_of_business = total_amt_per_mth_and_store.groupby(['type_of_business', 'std_mth'], as_index = False)['amount'].mean()
amount_mean_per_mth_and_type_of_business + train_df
1
2
3
4
5
6
7
8
9
10
11
12
13
# std_mth + i (i = 1, 2, 3)
# train_df의 std_mth는 total_amt_per_mth_and_store의 std_mth-i와 merge 해야하므로, total_amt_per_mth_and_store의 std_mth에 i를 더한다.

for i in range(1, 4):
    amount_mean_per_mth_and_type_of_business['std_mth_{}'.format(i)] = amount_mean_per_mth_and_type_of_business['std_mth'] + i
    train_df = pd.merge(train_df, amount_mean_per_mth_and_type_of_business.drop('std_mth', axis = 1), left_on = ['type_of_business', 'std_mth'], right_on = ['type_of_business', 'std_mth_{}'.format(i)])
    train_df.rename({"amount":"{}_before_amount_of_type_of_business".format(i)}, axis = 1, inplace = True)
    
    # 계산에 사용되고 필요없어진 데이터 drop
    train_df.drop(['std_mth_{}'.format(i)], axis = 1, inplace = True)
    amount_mean_per_mth_and_type_of_business.drop(['std_mth_{}'.format(i)], axis = 1, inplace = True)       
    
train_df.head()
store_id region type_of_business std_mth 평균할부율 1_before_amount 2_before_amount 3_before_amount 1_before_amount_of_region 2_before_amount_of_region 3_before_amount_of_region 1_before_amount_of_type_of_business 2_before_amount_of_type_of_business 3_before_amount_of_type_of_business
0 0 미분류 기타 미용업 9 0.04 871571.43 1005000.00 747000.00 761987.42 756108.67 739654.07 761025.00 804979.76 679950.00
1 792 미분류 기타 미용업 9 0.22 681142.86 880857.14 733714.29 761987.42 756108.67 739654.07 761025.00 804979.76 679950.00
2 23 경기 안양시 기타 미용업 9 0.05 879242.86 730857.14 845285.71 828831.71 588733.00 955973.29 761025.00 804979.76 679950.00
3 192 경기 화성시 기타 미용업 9 0.10 579000.00 523428.57 551142.86 1234460.01 1227920.91 1180455.31 761025.00 804979.76 679950.00
4 536 서울 광진구 기타 미용업 9 0.01 96285.71 79857.14 99857.14 3786819.95 3397972.56 3524074.62 761025.00 804979.76 679950.00

label 부착하기 ( std_mth +1, std_mth+2, std_mth+3)

  • label 은 기준월 (std_mth) 에서 각각 +1, +2, +3 인 시점에 대한 amount는 예측 모델을 통해 파악해야 하는 값이다.

  • 이후 에는 model 학습을 위한 데이터 전처리가 들어가기 때문에, raw data인 현상황에서 +1, +2, +3 인 시점의 amount를 입력해 두는 것이 추후 작업에서 유용하다.

1
2
3
4
5
6
7
8
9
10
11
12
# 불필요한 컬럼 drop (중복 컬럼 방지)
total_amt_per_mth_and_store.drop(['region', 'type_of_business'], axis = 1, inplace = True)

# std_mth - i (i = 1, 2, 3)
for i in range(1, 4):
    total_amt_per_mth_and_store['std_mth_{}'.format(i)] = total_amt_per_mth_and_store['std_mth'] - i   
    train_df = pd.merge(train_df, total_amt_per_mth_and_store.drop('std_mth', axis = 1), left_on = ['store_id', 'std_mth'], right_on = ['store_id', 'std_mth_{}'.format(i)])
    train_df.rename({"amount": "Y_{}".format(i)}, axis = 1, inplace = True)
    
    # 계산에 사용되고 필요없어진 데이터 drop
    train_df.drop(['std_mth_{}'.format(i)], axis = 1, inplace = True)
    total_amt_per_mth_and_store.drop(['std_mth_{}'.format(i)], axis = 1, inplace = True)      
1
train_df[['std_mth','Y_1','Y_2','Y_3']]
std_mth Y_1 Y_2 Y_3
0 9 835428.57 697000.00 761857.14
1 9 725142.86 653428.57 730071.43
2 9 741714.29 608857.14 844285.71
3 9 529000.00 545142.86 449714.29
4 9 87142.86 159928.57 124142.86
... ... ... ... ...
50226 31 13623585.71 10826500.00 12053585.71
50227 32 10826500.00 12053585.71 12916214.29
50228 33 12053585.71 12916214.29 13290000.00
50229 34 12916214.29 13290000.00 15355300.00
50230 35 13290000.00 15355300.00 11063685.71

50231 rows × 4 columns

  • for loop가 수행되면서 각각 i=1 일때, total_amt_per_mth_and_store의 std_mth 기준으로 +1 인 시점의 amount 가 Y_1이 된다.

  • i=2 일때는 std_mth 기준 +2 인 시점의 amount 가 Y_2이 되고, i=3 일때, std_mth 기준 +3 인 시점의 amount 가 Y_3이 된다.

1
2
# Y변수 (미래 3개월의 매출) 생성
train_df['Y'] = train_df['Y_1'] + train_df['Y_2'] + train_df['Y_3']
1
train_df.to_csv('train_df.csv')

댓글남기기