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개월을 기준으로 상점별 특징을 잡는 것이 좋을 것 같다.


필요 모듈 로드

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. 데이터 로드 및 살펴보기

1
2
# 데이터 로드
df = pd.read_csv("funda_train.csv")

train data살펴보기

1
2
3
4
# train data 살펴보기
print(df.info())
print(df.describe())
df.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6556613 entries, 0 to 6556612
Data columns (total 9 columns):
 #   Column            Dtype  
---  ------            -----  
 0   store_id          int64  
 1   card_id           int64  
 2   card_company      object 
 3   transacted_date   object 
 4   transacted_time   object 
 5   installment_term  int64  
 6   region            object 
 7   type_of_business  object 
 8   amount            float64
dtypes: float64(1), int64(3), object(5)
memory usage: 450.2+ MB
None
        store_id    card_id  installment_term      amount
count 6556613.00 6556613.00        6556613.00  6556613.00
mean     1084.93 2268127.02              0.14    10435.11
std       615.22 1351057.85              1.19    31040.31
min         0.00       0.00              0.00 -2771428.57
25%       586.00 1088828.00              0.00     2142.86
50%      1074.00 2239304.00              0.00     4285.71
75%      1615.00 3438488.00              0.00     8571.43
max      2136.00 4663856.00             93.00  5571428.57
store_id card_id card_company transacted_date transacted_time installment_term region type_of_business amount
0 0 0 b 2016-06-01 13:13 0 NaN 기타 미용업 1857.14
1 0 1 h 2016-06-01 18:12 0 NaN 기타 미용업 857.14
2 0 2 c 2016-06-01 18:52 0 NaN 기타 미용업 2000.00
3 0 3 a 2016-06-01 20:22 0 NaN 기타 미용업 7857.14
4 0 4 c 2016-06-02 11:06 0 NaN 기타 미용업 2000.00
1
df.isnull().sum()
store_id                  0
card_id                   0
card_company              0
transacted_date           0
transacted_time           0
installment_term          0
region              2042766
type_of_business    3952609
amount                    0
dtype: int64

submission 데이터 살펴보기

1
2
3
submission_df = pd.read_csv("submission.csv")
print(submission_df.info())
submission_df.head()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1967 entries, 0 to 1966
Data columns (total 2 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   store_id  1967 non-null   int64
 1   amount    1967 non-null   int64
dtypes: int64(2)
memory usage: 30.9 KB
None
store_id amount
0 0 0
1 1 0
2 2 0
3 4 0
4 5 0
1
submission_df.isnull().sum()
store_id    0
amount      0
dtype: int64
1
submission_df['amount'].unique()
array([0], dtype=int64)

제출 파일인 submission_df의 amount가 모두 0인 것으로 보아, 예측한 결과값을 amount에 추가해야 할 것으로 보인다.

2. EDA

store_id

1
2
print(df['store_id'].unique())
print(submission_df['store_id'].unique())
[   0    1    2 ... 2134 2135 2136]
[   0    1    2 ... 2134 2135 2136]
1
2
print(len(df['store_id']))
print(len(submission_df['store_id']))
6556613
1967
  • train의 store_id는 transcate_date 별 매출 (동일 매장에서 중복 매출 발생) 하여 수가 더 많음

  • 두 데이터 set의 unique() 값은 일치 -> 최종적으로 train 데이터를 통해 ‘매장 별 3개월간 총 매출’ 형식으로 prediction 을 만들어야 함

card_id

1
df['card_id'].unique()
array([      0,       1,       2, ..., 4663854, 4663855, 4663856],
      dtype=int64)
1
2
sns.displot(x = df['card_id'], kde=True)
plt.show()

1
2
3
4
5
6
7
# 데이터 범주가 너무 넓어서 상위 20개 분포만 재확인
card_id = df['card_id'].value_counts()[:20].index
counts = df['card_id'].value_counts()[:20]

bar_plot = sns.barplot(x=card_id, y=counts, alpha=0.9, palette='rocket')
plt.xticks(rotation=90)
plt.show()

  • 전체 6556613 개의 데이터 중 겹치지 않는 자료가 4663856개

  • card_id는 한 고객이 여러개의 카드를 가질 가능성도 있고, 특정 카드를 사용할때 매출과 직접적으로 연관을 지을 수 있는 별도의 feature가 없음

  • 만약, 카드별 할인율이나 , 프모로션 제도 등에 대한 feature가 있다면 활용이 가능할 수 도 있음.

  • 현재로선 예측 결과에 직접적인 영향을 주긴 어려운 feature일 것이라 판단됨.

  • drop 하는 것이 바람직해 보인다.

card_company

1
df['card_company'].unique()
array(['b', 'h', 'c', 'a', 'f', 'e', 'g', 'd'], dtype=object)
1
2
3
x = df['card_company'].value_counts().index
y = df['card_company'].value_counts()
sns.barplot(x=y, y=x, alpha=0.9, palette='mako');

  • 비식별화된 카드회사 정보인데, 알파벳 순 = value_counts()가 일치

    • 너무 짜여진 데이터 같음 => 모델이 잘못된 방식으로 데이터 인지할 가능성 우려
  • 추가적으로, card_id 와 동일한 이유로 활용하기도 어려움

  • drop 하는 것이 바람직해 보인다.

transcated_date

1
2
print(df['transacted_date'])
pd.DataFrame(df['transacted_date']).isnull().sum() #결측치 확인
0          2016-06-01
1          2016-06-01
2          2016-06-01
3          2016-06-01
4          2016-06-02
              ...    
6556608    2019-02-28
6556609    2019-02-28
6556610    2019-02-28
6556611    2019-02-28
6556612    2019-02-28
Name: transacted_date, Length: 6556613, dtype: object
transacted_date    0
dtype: int64
  • df의 시간 범위: 2016-06-01 ~ 2019-02-28

  • 예측에 활용할 주요 feature

  • 예측은 매장 별 3개월 총 매출이므로 ‘일 단위’‘월 단위’ 데이터로 통합해야 함

installment_term

1
df['installment_term'].value_counts(sort=True)
0     6327632
3      134709
2       42101
5       23751
6       10792
10       6241
4        4816
12       2699
60       1290
7         553
8         413
24        404
9         349
18        332
15        130
20        116
80         83
11         47
30         43
36         36
16         23
14         12
63          8
83          6
65          6
19          4
72          4
13          3
93          2
23          2
35          2
82          2
22          1
17          1
Name: installment_term, dtype: int64
1
2
#결측치 확인
pd.DataFrame(df['installment_term'].value_counts()).isnull().sum()
installment_term    0
dtype: int64
  • 전체 6556613 개의 데이터 중 일시불인 경우가 6327632 로 대부분이다.

  • 대부분이 일시불 값이므로 할부 T/F로 이진화 하여 feature로 사용하는 것이 더 유용할 것이다.

1
2
3
4
# 시각화 위해 데이터 이진화
df['installment_term2'] = (df['installment_term'] > 0).astype(int) # bool to int (True=1, False=0)
installment = df['installment_term2'].value_counts() 
installment = list(installment)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# canvas 크기 설정
plt.figure(figsize=(15,5))


# 그래프1
plt.subplot(121)

# 데이터 추출
count_installment_terms = df['installment_term'].value_counts(sort=True)
count_installment_terms = count_installment_terms[:5,]

# 그래프 1 시각화
sns.barplot(count_installment_terms.index, count_installment_terms.values, alpha=0.8)
plt.title('Top5 Installment term', fontsize = 15)
plt.ylabel('Counts', fontsize=12)
plt.xlabel('installment term', fontsize=12)


# 그래프2
plt.subplot(122)
x = ['Intallment_true', 'Installment_false']
y = installment

# 그래프2 시각화
sns.barplot(x, y, alpha=0.8, palette='crest')
plt.ylabel('Counts', fontsize=12)
plt.title('Installment Payment Status (T/F)', fontsize=15)

plt.show()

type_of_business

1
2
print((df['type_of_business'].value_counts())) #데이터 분포 확인
pd.DataFrame(df['type_of_business'].value_counts()).isnull().sum() #결측치 확인
한식 음식점업                     745905
두발 미용업                      178475
의복 소매업                      158234
기타 주점업                      102413
치킨 전문점                       89277
                             ...  
곡물 및 기타 식량작물 재배업               569
주방용품 및 가정용 유리, 요업 제품 소매업       551
배전반 및 전기 자동제어반 제조업             533
그 외 기타 생활용품 도매업                519
신선식품 및 단순 가공식품 도매업             231
Name: type_of_business, Length: 145, dtype: int64
type_of_business    0
dtype: int64
1
2
3
4
# 종류가 너무 많아 상위 20개만 확인
x = df['type_of_business'].value_counts()[:20].index
y = df['type_of_business'].value_counts()[:20]
sns.barplot(x=y, y=x, alpha=0.9, palette='YlOrRd');

1
2
3
4
- 범주형 변수
- 업종 종류가 너무 다양해서 type of business raw 데이터로는 예측을 위한 특징으로 사용하기는 어려울 것으로 판단된다.
- 그렇다고 dummy화 하기에는 수가 너무 많음
- store_id와는 명확하게 매칭이 되는 데이터 -> transacted_date 기준으로 분류하면 사용이 가능  .

region

1
df['region'].value_counts().head()
경기 수원시    122029
충북 청주시    116766
경남 창원시    107147
경남 김해시    100673
경기 평택시     82138
Name: region, dtype: int64
1
pd.DataFrame(df['region']).describe()
region
count 4513847
unique 180
top 경기 수원시
freq 122029
1
2
# 결측치 확인
pd.DataFrame(df['region']).isnull().sum()
region    2042766
dtype: int64
1
2
3
4
# 지역이 너무 많아 상위 20개만 확인
x = df['region'].value_counts()[:20].index
y = df['region'].value_counts()[:20]
sns.barplot(x=y, y=x, alpha=0.9, palette='viridis');

  • 범주형 변수

  • type of business 와 마찬가지로 지역의 unique value가 너무 많음 -> raw 데이터로는 예측을 위한 특징 으로 사용하기에 부적합

  • region별 매출 특성을 파악 하는 것에는 사용이 가능할 듯.

EDA 결과 정리

  • store_id : 각 상점을 구분지을 중요 feature. 변형없이 사용

  • card_id, card_company : 매장 별 매출액 분석에 유의미 하지 않으므로 drop (도메인지식 기반 판단)

  • intallment_term : 일시불(0)인 데이터가 압도적으로 많음 -> 일시불 / 할부 로 데이터 이원화 하는 것이 유용

  • type_of_business , region : 범주형 데이터, 다만 range가 너무 커서 dummy할 수는 없음. 시간 & store 기준으로 데이터를 병합하면 매출 특성을 구분하는데 사용할 수 있을 것으로 예상됨

  • transcade_time : 예측해야할 값은 ‘월 단위’. 특정 시간대의 매출 경향성이 전체 월 매출에 영향을 끼치지 않을 것이라 판단

    • 추가로 transcade_date 와 중복되는 경향이 있어 배제

  • transcade_date : ‘일’ 단위 데이터 -> ‘월 단위’ 로 변경 필요 : group by의 기준이 되는 데이터가 되어야 함 (분석의 key value)

    • standard_t(기준 월) 을 기준으로 매출 합계를 예측 해야 함

    • standard_t-2, standard_t-1, standard_t 3개의 데이터 기반 -> standard_t+1, standard_t+2, standard_t+3 을 예측 하는 방식

    • e.g) 2016년 1월 , 2016년 2월, 2016년 3월 매출 데이터 기반 -> 2016년 4월 매출, 2016년 5월 매출, 2016년 6월 매출을 예측하는 식으로 반복 학습 방식으로 3년치 데이터 기반으로 최종적으로 2019년 3월 ~ 5월 매출 예측 (중첩된 예측과 검증으로 예측력 상승을 기대)

    • 연도 & 월 로 하는 경우 데이터 구분이 명확하지 않을 가능성 있음. 특정 시점을 기준으로 Index 식으로 구분하는 것이 더 모델이 인지를 잘 할 것으로 예상

댓글남기기