앎을 경계하기

Machine Learning/Statistics

파이썬 데이터 분석 실무 테크닉 100 - 3장

양갱맨 2020. 12. 17. 13:18

 </p

 

3장 고객의 전체 모습을 파악하는 테크닉 10

데이터 분석의 묘미는 미래를 예측하는 것이다.

현재 데이터로 미래를 예측하여 최적의 정책을 실시할 수 있게 하는 것이다.

미래를 예측하는 방법은 다양하다.

데이터를 시각화하는 것만으로도 많은 정보를 얻을 수 있어서 분석 엔지니어는 적절한 데이터 가공 기술을 구사한다.

이 장에서는 머신러닝을 위한 데이터 가공 기술을 배운다.

VoC, 전제조건

VoC : 운영하는 스포츠 센터는 트레이닝 붐 덕에 고객 수가 늘었다. 그러나 최근 1년간 고객 수가 늘지 않았다. 자주 이용하는 고객은 있으나 가끔 오는 고객은 어느 새 오지 않는 경우가 생기는 것 같다. 제대로 데이터 분석을 하면 원인을 알 수 있을까?

전제조건 :

  • 스포츠 센터에는 언제는 사용 가능한 종일 회원, 낮에만 사용 가능한 주간 회원, 밤에만 사용 가능한 야간회원으로 3종류의 회원 구분이 있다.
  • 보통 입회비가 있고, 비정기적으로 입회비 반액 할인이나 무료 행사를 진행하여 신규회원을 늘렸다. 탈퇴는 월말까지 신청하면 다음 달 말에 탈퇴 된다.
  • 취급 데이터는 4종류이고 테이블로 나타난다.
    • use_log.csv : 센터 이용 이력, 2018년 4월부터 2019년 3월까지
    • customer_master.csv : 2019년 3월 말 시점의 회원 데이터, 이전 탈퇴회원 포함
    • class_master.csv, campaign_master.csv : 회원구분, 가입 시 행사 종류 데이터

데이터 읽기

In [63]:
import pandas as pd
In [64]:
uselog = pd.read_csv('/content/drive/MyDrive/파이썬데이터분석실무테크닉100/pyda100/3장/use_log.csv')
uselog.head()
Out[64]:
  log_id customer_id usedate
0 L00000049012330 AS009373 2018-04-01
1 L00000049012331 AS015315 2018-04-01
2 L00000049012332 AS040841 2018-04-01
3 L00000049012333 AS046594 2018-04-01
4 L00000049012334 AS073285 2018-04-01
In [65]:
customer = pd.read_csv('/content/drive/MyDrive/파이썬데이터분석실무테크닉100/pyda100/3장/customer_master.csv')
customer.head()
Out[65]:
  customer_id name class gender start_date end_date campaign_id is_deleted
0 OA832399 XXXX C01 F 2015-05-01 00:00:00 NaN CA1 0
1 PL270116 XXXXX C01 M 2015-05-01 00:00:00 NaN CA1 0
2 OA974876 XXXXX C01 M 2015-05-01 00:00:00 NaN CA1 0
3 HD024127 XXXXX C01 F 2015-05-01 00:00:00 NaN CA1 0
4 HD661448 XXXXX C03 F 2015-05-01 00:00:00 NaN CA1 0
In [66]:
class_master = pd.read_csv('/content/drive/MyDrive/파이썬데이터분석실무테크닉100/pyda100/3장/class_master.csv')
class_master.head()
Out[66]:
  class class_name price
0 C01 0_종일 10500
1 C02 1_주간 7500
2 C03 2_야간 6000
In [67]:
campaign_master = pd.read_csv('/content/drive/MyDrive/파이썬데이터분석실무테크닉100/pyda100/3장/campaign_master.csv')
campaign_master.head()
Out[67]:
  campaign_id campaign_name
0 CA1 2_일반
1 CA2 0_입회비반액할인
2 CA3 1_입회비무료
 

분석을 위한 데이터 가공 전, 기준 데이터를 정해야한다. uselog와 customer 자료를 사용하여 가공 가능하다.

In [68]:
len(uselog)
Out[68]:
197428
In [69]:
len(customer)
Out[69]:
4192
In [70]:
customer_join = pd.merge(customer, class_master,on='class', how='left')
customer_join = pd.merge(customer_join, campaign_master,on='campaign_id', how='left')
customer_join.head()
Out[70]:
  customer_id name class gender start_date end_date campaign_id is_deleted class_name price campaign_name
0 OA832399 XXXX C01 F 2015-05-01 00:00:00 NaN CA1 0 0_종일 10500 2_일반
1 PL270116 XXXXX C01 M 2015-05-01 00:00:00 NaN CA1 0 0_종일 10500 2_일반
2 OA974876 XXXXX C01 M 2015-05-01 00:00:00 NaN CA1 0 0_종일 10500 2_일반
3 HD024127 XXXXX C01 F 2015-05-01 00:00:00 NaN CA1 0 0_종일 10500 2_일반
4 HD661448 XXXXX C03 F 2015-05-01 00:00:00 NaN CA1 0 2_야간 6000 2_일반
In [71]:
len(customer_join)
Out[71]:
4192
In [72]:
customer_join.isnull().sum() # 결측치 확인
Out[72]:
customer_id         0
name                0
class               0
gender              0
start_date          0
end_date         2842
campaign_id         0
is_deleted          0
class_name          0
price               0
campaign_name       0
dtype: int64
 

데이터 집계

집계 항목

  • 회원 유형
  • 캠페인 유형
  • 입회/탈퇴가 많은 기간
  • 성별 비율
  • 탈퇴까지의 남은 기간 등

먼저, 캠페인 구분, 성별, 탈퇴자인지 확인

In [73]:
customer_join.groupby("class_name").count()['customer_id']
Out[73]:
class_name
0_종일    2045
1_주간    1019
2_야간    1128
Name: customer_id, dtype: int64
In [74]:
customer_join.groupby('campaign_name').count()['customer_id']
Out[74]:
campaign_name
0_입회비반액할인     650
1_입회비무료       492
2_일반         3050
Name: customer_id, dtype: int64
In [75]:
customer_join.groupby('gender').count()['customer_id']
Out[75]:
gender
F    1983
M    2209
Name: customer_id, dtype: int64
In [76]:
customer_join.groupby('is_deleted').count()['customer_id']
Out[76]:
is_deleted
0    2842
1    1350
Name: customer_id, dtype: int64
 

2018/04/01 이후부터 2019/03/31일까지 가입인원 집계

In [77]:
customer_join['start_date'] = pd.to_datetime(customer_join['start_date'])
customer_start = customer_join.loc[customer_join['start_date']>pd.to_datetime('20180401')]
len(customer_start)
Out[77]:
1361
 

최신 고객 집계

가장 최근(2019년 3월) 고객 데이터 파악하기

In [78]:
customer_join['end_date'] = pd.to_datetime(customer_join['end_date'])
customer_newer = customer_join.loc[(customer_join["end_date"]>=pd.to_datetime('20190331'))|(customer_join['end_date'].isna())]
len(customer_newer)
Out[78]:
2953
In [79]:
customer_newer['end_date'].unique() #검산
Out[79]:
array([                          'NaT', '2019-03-31T00:00:00.000000000'],
      dtype='datetime64[ns]')
In [79]:
 
In [80]:
customer_newer.groupby('campaign_name').count()['customer_id']
Out[80]:
campaign_name
0_입회비반액할인     311
1_입회비무료       242
2_일반         2400
Name: customer_id, dtype: int64
In [81]:
customer_newer.groupby('gender').count()['customer_id']
Out[81]:
gender
F    1400
M    1553
Name: customer_id, dtype: int64
In [82]:
half = (customer_join.groupby('campaign_name').count()['customer_id'][0]/customer_join['customer_id'].count())*100
In [83]:
free = (customer_join.groupby('campaign_name').count()['customer_id'][1]/customer_join['customer_id'].count())*100
In [84]:
common = (customer_join.groupby('campaign_name').count()['customer_id'][2]/customer_join['customer_id'].count())*100
In [85]:
print(half+free+common)
print(half, free, common)
 
100.0
15.505725190839694 11.736641221374047 72.75763358778626
 

이용 이력 데이터 집계

월 이용 횟수 평균, 중앙, 최대, 최소 등을 집계

In [86]:
uselog['usedate'] = pd.to_datetime(uselog['usedate'])
uselog['연월'] = uselog['usedate'].dt.strftime('%Y%m')
uselog_months = uselog.groupby(['연월', 'customer_id'], as_index=False).count()
uselog_months.rename(columns={'log_id':'count'}, inplace=True)
del uselog_months['usedate']
uselog_months.head()
Out[86]:
  연월 customer_id count
0 201804 AS002855 4
1 201804 AS009013 2
2 201804 AS009373 3
3 201804 AS015315 6
4 201804 AS015739 7
In [87]:
uselog_customer = uselog_months.groupby('customer_id').agg(['mean', 'median', 'max', 'min'])['count']
uselog_customer = uselog_customer.reset_index(drop=False)
uselog_customer.head()
Out[87]:
  customer_id mean median max min
0 AS002855 4.500000 5.0 7 2
1 AS008805 4.000000 4.0 8 1
2 AS009013 2.000000 2.0 2 2
3 AS009373 5.083333 5.0 7 3
4 AS015233 7.545455 7.0 11 4
 

이용 이력 데이터로부터 정기 이용 플래그 작성

매주 같은 요일에 오는 고객들을 정기적이라고 판단.

최댓값이 4이상인 요일이 있으면 플래그 1로 처리

먼저, 고객마다 월/요일별로 집계

In [88]:
uselog['weekday'] = uselog['usedate'].dt.weekday
uselog_weekday = uselog.groupby(['customer_id', '연월', 'weekday'], as_index=False).count()[['customer_id', '연월', 'weekday', 'log_id']]
uselog_weekday.rename(columns={'log_id':'count'}, inplace=True)
uselog_weekday.head()
Out[88]:
  customer_id 연월 weekday count
0 AS002855 201804 5 4
1 AS002855 201805 2 1
2 AS002855 201805 5 4
3 AS002855 201806 5 5
4 AS002855 201807 1 1
In [89]:
uselog_weekday = uselog_weekday.groupby('customer_id', as_index=False).max()[['customer_id', 'count']]
uselog_weekday['routine_flg'] = 0
uselog_weekday['routine_flg'] = uselog_weekday['routine_flg'].where(uselog_weekday['count']<4, 1)
uselog_weekday.head()
Out[89]:
  customer_id count routine_flg
0 AS002855 5 1
1 AS008805 4 1
2 AS009013 2 0
3 AS009373 5 1
4 AS015233 5 1
 

고객 데이터와 이용 이력 데이터 결합

uselog_customer, uselog_weekday, customer_join 결합

In [90]:
customer_join = pd.merge(customer_join, uselog_customer, on='customer_id', how='left')
customer_join = pd.merge(customer_join, uselog_weekday[['customer_id', 'routine_flg']], on='customer_id', how='left')
customer_join.head()
customer_join.isnull().sum()
Out[90]:
customer_id         0
name                0
class               0
gender              0
start_date          0
end_date         2842
campaign_id         0
is_deleted          0
class_name          0
price               0
campaign_name       0
mean                0
median              0
max                 0
min                 0
routine_flg         0
dtype: int64
 

회원 기간 계산

기간은 start_date-end_date이다. 그러나 탈퇴하지 않은 회원은 end_date가 결측값이기 때문에 값 수정이 필요하다.

In [91]:
from dateutil.relativedelta import relativedelta
customer_join['calc_date'] = customer_join['end_date']
customer_join['calc_date'] = customer_join['calc_date'].fillna(pd.to_datetime('20190430'))
customer_join['membership_period'] = 0
for i in range(len(customer_join)):
    delta = relativedelta(customer_join['calc_date'].iloc[i], customer_join['start_date'].iloc[i])
    customer_join['membership_period'].iloc[i] = delta.years*12+delta.months
customer_join.head()
 
/usr/local/lib/python3.6/dist-packages/pandas/core/indexing.py:670: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  iloc._setitem_with_indexer(indexer, value)
Out[91]:
  customer_id name class gender start_date end_date campaign_id is_deleted class_name price campaign_name mean median max min routine_flg calc_date membership_period
0 OA832399 XXXX C01 F 2015-05-01 NaT CA1 0 0_종일 10500 2_일반 4.833333 5.0 8 2 1 2019-04-30 47
1 PL270116 XXXXX C01 M 2015-05-01 NaT CA1 0 0_종일 10500 2_일반 5.083333 5.0 7 3 1 2019-04-30 47
2 OA974876 XXXXX C01 M 2015-05-01 NaT CA1 0 0_종일 10500 2_일반 4.583333 5.0 6 3 1 2019-04-30 47
3 HD024127 XXXXX C01 F 2015-05-01 NaT CA1 0 0_종일 10500 2_일반 4.833333 4.5 7 2 1 2019-04-30 47
4 HD661448 XXXXX C03 F 2015-05-01 NaT CA1 0 2_야간 6000 2_일반 3.916667 4.0 6 1 1 2019-04-30 47
 

고객 행동의 각종 통계량 파악

In [92]:
customer_join[['mean','median','max','min']].describe()
Out[92]:
  mean median max min
count 4192.000000 4192.000000 4192.000000 4192.000000
mean 5.333127 5.250596 7.823950 3.041269
std 1.777533 1.874874 2.168959 1.951565
min 1.000000 1.000000 1.000000 1.000000
25% 4.250000 4.000000 7.000000 2.000000
50% 5.000000 5.000000 8.000000 3.000000
75% 6.416667 6.500000 9.000000 4.000000
max 12.000000 12.000000 14.000000 12.000000
In [93]:
customer_join.groupby('routine_flg').count()['customer_id']
Out[93]:
routine_flg
0     779
1    3413
Name: customer_id, dtype: int64
In [94]:
import matplotlib.pyplot as plt
%matplotlib inline
plt.hist(customer_join['membership_period'])
Out[94]:
(array([857., 774., 395., 368., 311., 331., 323., 237., 288., 308.]),
 array([ 1. ,  5.6, 10.2, 14.8, 19.4, 24. , 28.6, 33.2, 37.8, 42.4, 47. ]),
 <a list of 10 Patch objects>)
 
 

탈퇴 회원과 지속 회원의 차이

In [95]:
customer_end = customer_join.loc[customer_join['is_deleted']==1]
customer_end.describe()
Out[95]:
  is_deleted price mean median max min routine_flg membership_period
count 1350.0 1350.000000 1350.000000 1350.000000 1350.000000 1350.000000 1350.000000 1350.000000
mean 1.0 8595.555556 3.865474 3.621852 6.461481 1.821481 0.456296 8.026667
std 0.0 1949.163652 1.246385 1.270847 2.584021 0.976361 0.498271 5.033692
min 1.0 6000.000000 1.000000 1.000000 1.000000 1.000000 0.000000 1.000000
25% 1.0 6000.000000 3.000000 3.000000 4.000000 1.000000 0.000000 4.000000
50% 1.0 7500.000000 4.000000 4.000000 7.000000 2.000000 0.000000 7.000000
75% 1.0 10500.000000 4.666667 4.500000 8.000000 2.000000 1.000000 11.000000
max 1.0 10500.000000 9.000000 9.000000 13.000000 8.000000 1.000000 23.000000
In [96]:
customer_end = customer_join.loc[customer_join['is_deleted']==0]
customer_end.describe()
Out[96]:
  is_deleted price mean median max min routine_flg membership_period
count 2842.0 2842.000000 2842.000000 2842.000000 2842.000000 2842.000000 2842.000000 2842.000000
mean 0.0 8542.927516 6.030288 6.024279 8.471147 3.620690 0.984166 23.970443
std 0.0 1977.189779 1.553587 1.599765 1.571048 2.030488 0.124855 13.746761
min 0.0 6000.000000 3.166667 3.000000 5.000000 1.000000 0.000000 1.000000
25% 0.0 6000.000000 4.833333 5.000000 7.000000 2.000000 1.000000 12.000000
50% 0.0 7500.000000 5.583333 5.500000 8.000000 3.000000 1.000000 24.000000
75% 0.0 10500.000000 7.178030 7.000000 10.000000 5.000000 1.000000 35.000000
max 0.0 10500.000000 12.000000 12.000000 14.000000 12.000000 1.000000 47.000000
In [ ]: