우수고객 선별하기(가장 소비를 많이 한 고객),고객 코호트 분석

학습목표

  1. 소비 우수고객 찾기
  2. 고객 retention
from datetime import datetime
import numpy as np
import pandas as pd
import seaborn as sns
from matplotlib import pyplot as plt

%matplotlib inline
  • seaborn은 matplotlib 처럼 그래프를 그리는 기능이다(matplotlib으로 그래프 그리는 꿀팁이 궁금하다면?). matplotlip으로도 대부분의 시각화는 가능하지만 아래와 같은 이유들로 seaborn을 더 선호하는 추세이다.
  1. seaborn에서만 제공되는 통계 기반 plot
  2. 특별하게 꾸미지 않아도 깔끔하게 구현되는 기본 color
  3. 더 아름답게 그래프 구현이 가능한 palette 기능
  4. pandas 데이터프레임과 높은 호환성
    hue 옵션으로 bar 구분이 가능하며, xtick, ytick, xlabel, ylabel, legend 등이 추가적인 코딩 작업없이 자동으로 세팅된다.
dtypes = {
    'UnitPrice': np.float32,
    'CustomerID': np.int32,
    'Quantity': np.int32
}
retail = pd.read_csv('./OnlineRetailClean.csv', dtype=dtypes)
retail['InvoiceDate'] = pd.to_datetime(retail['InvoiceDate'], infer_datetime_format=True) #      infer_datetime_format=True 날짜시간 포맷 추정해서 파싱하기
retail.head()
Unnamed: 0 InvoiceNo StockCode Description Quantity InvoiceDate UnitPrice CustomerID Country CheckoutPrice
0 0 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6 2010-12-01 08:26:00 2.55 17850 United Kingdom 15.30
1 1 536365 71053 WHITE METAL LANTERN 6 2010-12-01 08:26:00 3.39 17850 United Kingdom 20.34
2 2 536365 84406B CREAM CUPID HEARTS COAT HANGER 8 2010-12-01 08:26:00 2.75 17850 United Kingdom 22.00
3 3 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6 2010-12-01 08:26:00 3.39 17850 United Kingdom 20.34
4 4 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6 2010-12-01 08:26:00 3.39 17850 United Kingdom 20.34

우수 고객 확인

  • 구매 횟수 기준
  • 지불 금액 기준
retail.groupby('CustomerID').count()['Quantity'].sort_values(ascending=False)
CustomerID
17841    7847
14911    5675
14096    5111
12748    4595
14606    2700
         ... 
17846       1
13017       1
13099       1
13106       1
12346       1
Name: Quantity, Length: 4338, dtype: int64
retail.groupby('CustomerID').sum()['CheckoutPrice'].sort_values(ascending=False)
CustomerID
14646    280206.02
18102    259657.30
17450    194550.79
16446    168472.50
14911    143825.06
           ...    
16878        13.30
17956        12.75
16454         6.90
14792         6.20
16738         3.75
Name: CheckoutPrice, Length: 4338, dtype: float64

사용자 retention 분석

  • 월간 사용자 cohort를 바탕으로 월별 재구매율(retention) 분석하기
  • heatmap으로 한눈에 재구매율을 파악 가능 -goodcustomer

사용자 기준으로 최초 구매한 월(month) 연산하기

  • Month : 구매월(일(day)을 무시)
  • MonthStarted: 사용자가 최초 구매한 달
def get_month_as_datetime(date):
    return datetime(date.year, date.month, 1) #년,월,일 

retail['Month'] = retail['InvoiceDate'].apply(get_month_as_datetime)  # Month 컬럼 생성됨

retail.head()
Unnamed: 0 InvoiceNo StockCode Description Quantity InvoiceDate UnitPrice CustomerID Country CheckoutPrice Month
0 0 536365 85123A WHITE HANGING HEART T-LIGHT HOLDER 6 2010-12-01 08:26:00 2.55 17850 United Kingdom 15.30 2010-12-01
1 1 536365 71053 WHITE METAL LANTERN 6 2010-12-01 08:26:00 3.39 17850 United Kingdom 20.34 2010-12-01
2 2 536365 84406B CREAM CUPID HEARTS COAT HANGER 8 2010-12-01 08:26:00 2.75 17850 United Kingdom 22.00 2010-12-01
3 3 536365 84029G KNITTED UNION FLAG HOT WATER BOTTLE 6 2010-12-01 08:26:00 3.39 17850 United Kingdom 20.34 2010-12-01
4 4 536365 84029E RED WOOLLY HOTTIE WHITE HEART. 6 2010-12-01 08:26:00 3.39 17850 United Kingdom 20.34 2010-12-01
retail.groupby('CustomerID')['Month']
<pandas.core.groupby.generic.SeriesGroupBy object at 0x000001F01E170610>
month_group = retail.groupby('CustomerID')['Month']
retail['MonthStarted'] = month_group.transform(np.min)

retail.tail() #최초로 이용한 달 검색
Unnamed: 0 InvoiceNo StockCode Description Quantity InvoiceDate UnitPrice CustomerID Country CheckoutPrice Month MonthStarted
397879 541904 581587 22613 PACK OF 20 SPACEBOY NAPKINS 12 2011-12-09 12:50:00 0.85 12680 France 10.20 2011-12-01 2011-08-01
397880 541905 581587 22899 CHILDREN'S APRON DOLLY GIRL 6 2011-12-09 12:50:00 2.10 12680 France 12.60 2011-12-01 2011-08-01
397881 541906 581587 23254 CHILDRENS CUTLERY DOLLY GIRL 4 2011-12-09 12:50:00 4.15 12680 France 16.60 2011-12-01 2011-08-01
397882 541907 581587 23255 CHILDRENS CUTLERY CIRCUS PARADE 4 2011-12-09 12:50:00 4.15 12680 France 16.60 2011-12-01 2011-08-01
397883 541908 581587 22138 BAKING SET 9 PIECE RETROSPOT 3 2011-12-09 12:50:00 4.95 12680 France 14.85 2011-12-01 2011-08-01

기준이 되는 월과 실제 구매 월의 차이 계산하기

  • 각 구매가 최초 구매로 부터 얼마의 월이 지났는지 연산
  • MonthPassed : 최초 구매월로부터의 월 차이
retail['MonthPassed'] = (retail['Month'].dt.year - retail['MonthStarted'].dt.year) * 12 + \
    (retail['Month'].dt.month - retail['MonthStarted'].dt.month)
retail.tail()
Unnamed: 0 InvoiceNo StockCode Description Quantity InvoiceDate UnitPrice CustomerID Country CheckoutPrice Month MonthStarted MonthPassed
397879 541904 581587 22613 PACK OF 20 SPACEBOY NAPKINS 12 2011-12-09 12:50:00 0.85 12680 France 10.20 2011-12-01 2011-08-01 4
397880 541905 581587 22899 CHILDREN'S APRON DOLLY GIRL 6 2011-12-09 12:50:00 2.10 12680 France 12.60 2011-12-01 2011-08-01 4
397881 541906 581587 23254 CHILDRENS CUTLERY DOLLY GIRL 4 2011-12-09 12:50:00 4.15 12680 France 16.60 2011-12-01 2011-08-01 4
397882 541907 581587 23255 CHILDRENS CUTLERY CIRCUS PARADE 4 2011-12-09 12:50:00 4.15 12680 France 16.60 2011-12-01 2011-08-01 4
397883 541908 581587 22138 BAKING SET 9 PIECE RETROSPOT 3 2011-12-09 12:50:00 4.95 12680 France 14.85 2011-12-01 2011-08-01 4

기준 월, MonthPassed를 기준으로 고객 카운팅

  • 기준이 되는 월과 그 월로부터 지난 기간의 고객 수를 계산
def get_unique_no(x):
    return len(np.unique(x))

cohort_group = retail.groupby(['MonthStarted', 'MonthPassed'])
cohort_df = cohort_group['CustomerID'].apply(get_unique_no).reset_index() #reset_index 함수로 index 없애기
cohort_df.head()
MonthStarted MonthPassed CustomerID
0 2010-12-01 0 885
1 2010-12-01 1 324
2 2010-12-01 2 286
3 2010-12-01 3 340
4 2010-12-01 4 321

테이블 피벗

  • pivot 함수를 이용하여 index는 MonthStarted, columns을 MonthPassed로 변경하여 테이블 변경
  • 첫번째 column을 기준으로 100분위 연산
cohort_df = cohort_df.pivot(index='MonthStarted', columns='MonthPassed')
cohort_df.head()
CustomerID
MonthPassed 0 1 2 3 4 5 6 7 8 9 10 11 12
MonthStarted
2010-12-01 885.0 324.0 286.0 340.0 321.0 352.0 321.0 309.0 313.0 350.0 331.0 445.0 235.0
2011-01-01 417.0 92.0 111.0 96.0 134.0 120.0 103.0 101.0 125.0 136.0 152.0 49.0 NaN
2011-02-01 380.0 71.0 71.0 108.0 103.0 94.0 96.0 106.0 94.0 116.0 26.0 NaN NaN
2011-03-01 452.0 68.0 114.0 90.0 101.0 76.0 121.0 104.0 126.0 39.0 NaN NaN NaN
2011-04-01 300.0 64.0 61.0 63.0 59.0 68.0 65.0 78.0 22.0 NaN NaN NaN NaN
customer_cohort = cohort_df.div(cohort_df.iloc[:, 0], axis=0) * 100
#df.iloc[ ]는 row와 column의 이름을 그대로 쓰는 것이 아니라 각 row와 column의 인덱스 값으로 인덱싱하는 방법이다.
customer_cohort.head()
CustomerID
MonthPassed 0 1 2 3 4 5 6 7 8 9 10 11 12
MonthStarted
2010-12-01 100.0 36.610169 32.316384 38.418079 36.271186 39.774011 36.271186 34.915254 35.367232 39.548023 37.401130 50.282486 26.553672
2011-01-01 100.0 22.062350 26.618705 23.021583 32.134293 28.776978 24.700240 24.220624 29.976019 32.613909 36.450839 11.750600 NaN
2011-02-01 100.0 18.684211 18.684211 28.421053 27.105263 24.736842 25.263158 27.894737 24.736842 30.526316 6.842105 NaN NaN
2011-03-01 100.0 15.044248 25.221239 19.911504 22.345133 16.814159 26.769912 23.008850 27.876106 8.628319 NaN NaN NaN
2011-04-01 100.0 21.333333 20.333333 21.000000 19.666667 22.666667 21.666667 26.000000 7.333333 NaN NaN NaN NaN
customer_cohort = customer_cohort.round(decimals=2)

customer_cohort
CustomerID
MonthPassed 0 1 2 3 4 5 6 7 8 9 10 11 12
MonthStarted
2010-12-01 100.0 36.61 32.32 38.42 36.27 39.77 36.27 34.92 35.37 39.55 37.40 50.28 26.55
2011-01-01 100.0 22.06 26.62 23.02 32.13 28.78 24.70 24.22 29.98 32.61 36.45 11.75 NaN
2011-02-01 100.0 18.68 18.68 28.42 27.11 24.74 25.26 27.89 24.74 30.53 6.84 NaN NaN
2011-03-01 100.0 15.04 25.22 19.91 22.35 16.81 26.77 23.01 27.88 8.63 NaN NaN NaN
2011-04-01 100.0 21.33 20.33 21.00 19.67 22.67 21.67 26.00 7.33 NaN NaN NaN NaN
2011-05-01 100.0 19.01 17.25 17.25 20.77 23.24 26.41 9.51 NaN NaN NaN NaN NaN
2011-06-01 100.0 17.36 15.70 26.45 23.14 33.47 9.50 NaN NaN NaN NaN NaN NaN
2011-07-01 100.0 18.09 20.74 22.34 27.13 11.17 NaN NaN NaN NaN NaN NaN NaN
2011-08-01 100.0 20.71 24.85 24.26 12.43 NaN NaN NaN NaN NaN NaN NaN NaN
2011-09-01 100.0 23.41 30.10 11.37 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2011-10-01 100.0 24.02 11.45 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2011-11-01 100.0 11.15 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2011-12-01 100.0 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN

heatmap 출력하기

  • seaborn의 heatmap 함수로 visualization!
xticks = np.arange(0, 13)
yticks = ['2010/12', '2011/01', '2011/02', '2011/03', '2011/04', '2011/05', '2011/06', '2011/07', '2011/08', '2011/09', '2011/10', '2011/11', '2011/12']

plt.figure(figsize = (15, 8))
sns.heatmap(customer_cohort, 
            annot=True, 
            xticklabels=xticks,
            yticklabels=yticks, 
            fmt='.1f')

<AxesSubplot:xlabel='None-MonthPassed', ylabel='MonthStarted'>

output_22_1