본문 바로가기

Python

Python으로 코호트 분석(Cohort Analysis)하고 Pandas 명령어 시행

반응형

What is Cohort Analysis? 코호트 분석이란? 

 코호트(Cohort)는 가입 날짜, 첫 구매 월, 생년월일, 유입 채널 등 공통점을 공유하는 사용자 그룹을 말하는 것으로 비즈니스에서 많이 사용되는 용어입니다. 코호트 분석은 이러한 그룹을 시간에 따라 추적하여 파악하는 데 도움이 되는 방법으로 반복 적인 고객 행동 (구매, 참여, 지출 금액 등)을 이해하고 고객 및 수익 유지를 모니터링하는데 많이 이용을 합니다. 

 

코호트 분석이 가치가 있는 이유?

 코호트 분석은 고객의 충성도로 비즈니스의 건전성과 "고착성(Stickiness)"을 이해하는 데 도움을 줍니다. 신규 고객을 확보하는 것보다 현재 고객을 유지하는 것이 비용적으로 효율적이기 때문에 "고착성(Stickness)"은 매우 중요합니다. StartUp에서 특히 중요한 지표로 인식하고 있으며 사용자의 유지/이탈 현황을 시각화하고 LTV(Life Time Value)를 측정하고 이해하는데 좋은 방법입니다.

 

 


데이터 세트를 다운로드 후 프로그램 시작합시다.

 

cohort_test.xlsx
0.16MB

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

pd.set_option('max_columns', 30000)
#mpl.rcParams['lines.linewidth'] = 2

df = pd.read_excel('C:\\Users\\HANA\\\Downloads\\cohort_test.xlsx')
print(df.head())

#Result
   OrderId  OrderDate  UserId  TotalCharges CommonId  PupId PickupDate
0      262 2009-01-11      47         50.67    TRQKD      2 2009-01-12
1      278 2009-01-20      47         26.60    4HH2S      3 2009-01-20
2      294 2009-02-03      47         38.71    3TRDC      2 2009-02-04
3      301 2009-02-06      47         53.38    NGAZJ      2 2009-02-09
4      302 2009-02-06      47         14.28    FFYHD      2 2009-02-09

잘 정돈된 사용자 ID와 주문정보가 들어있는 데이터 세트입니다. 

 

df['OrderPeriod'] = df.OrderDate.apply(lambda x: x.strftime('%Y-%m'))

월별 Cohort를 수행하기 위해 데이터 세트에 OrderPeriod에 YYYY-MM형식의 데이터 세트를 입력해주게 됩니다. 이 때 사용 편의성을 위해 lambda를 사용하여 코딩의 용이성을 높이게 됩니다.  날짜 형식을 변경하는 함수로 strftime을 사용하는것도 기억하면 이후 코딩에 도움을 주게 됩니다.

 

df.set_index('UserId', inplace=True)
df['CohortGroup'] = df.groupby(level=0)['OrderDate'].min().apply(lambda x: x.strftime('%Y-%m'))
df.reset_index(inplace=True)
print(df.head())

#set_index : UserId에 인덱스를 설정합니다. inplace=True 셋팅은 원본 객체를 변경할지 여부 설정
#groupby(level=0): Index를 설정한 첫번째 열을 기준으로 group by를 실행함을 의미
#reset_index: 새로운 컬럼이 추가되어 index를 새로 reset합니다.


#Result
   UserId  OrderId  OrderDate  ...  PickupDate OrderPeriod  CohortGroup
0      47      262 2009-01-11  ...  2009-01-12     2009-01      2009-01
1      47      278 2009-01-20  ...  2009-01-20     2009-01      2009-01
2      47      294 2009-02-03  ...  2009-02-04     2009-02      2009-01
3      47      301 2009-02-06  ...  2009-02-09     2009-02      2009-01
4      47      302 2009-02-06  ...  2009-02-09     2009-02      2009-01

CohortGroup의 새로운 열을 만들고, 사용자의 첫 구매가 발생한 연도-월을 구합니다. 즉 Cohort의 기준을 고객별로 산정하기 위해 실행합니다.

 

grouped = df.groupby(['CohortGroup', 'OrderPeriod'])
cohorts = grouped.agg({'UserId': pd.Series.nunique, 'OrderId': pd.Series.nunique, 'TotalCharges': np.sum})
cohorts.rename(columns={'UserId': 'TotalUsers', 'OrderId': 'TotalOrders'}, inplace=True)
cohorts.head()


#Result
                         TotalUsers  TotalOrders  TotalCharges
CohortGroup OrderPeriod                                       
2009-01     2009-01              22           30      1850.255
            2009-02               8           25      1351.065
            2009-03              10           26      1357.360
            2009-04               9           28      1604.500
            2009-05              10           26      1575.625

상기에서 새롭게 생성한 OrderPeriod와 CohortGroup을 Group화 하고, UserId와 OrderId, TotalCharses의 값을 agg함수로 Count 하고 Sum을 진행합니다.  마지막으로 각 칼럼의 이름을 변경을 하기 위해 rename을 수행합니다.

 

def cohort_period(df):
    df['CohortPeriod'] = np.arange(len(df)) + 1
    return df


cohorts = cohorts.groupby(level=0).apply(cohort_period)
cohorts.head()
print(cohorts.head())

#Result
                         TotalUsers  TotalOrders  TotalCharges  CohortPeriod
CohortGroup OrderPeriod                                                     
2009-01     2009-01              22           30     1850.2550             1
            2009-02               8           25     1351.0650             2
            2009-03              10           26     1357.3600             3
            2009-04               9           28     1604.5000             4
            2009-05              10           26     1575.6250             5
...                             ...          ...           ...           ...
2010-01     2010-02              50          101     8453.1039             2
            2010-03              26           31     2238.6461             3
2010-02     2010-02             100          139     7374.7108             1
            2010-03              19           19      945.9633             2
2010-03     2010-03              24           26     1099.5471             1

CohortGroup를 기준으로 Group화를 한 후 CohortPeriod 컬럼을 생성한 후 OrderPeriod와의 순서를 정합니다. 하기 그림의 위치를 정하기 위함입니다.  np.arrange()의 사용법을 알면 도움이 됩니다.

 

예) np.arrange()는 일정한 값들을 array형태로 반황해 주는 함수입니다.

import numpy as np
np.arange(3) 

>>> array([0, 1, 2])

 

x = df[(df.CohortGroup == '2009-01') & (df.OrderPeriod == '2009-01')]
y = cohorts.loc[('2009-01', '2009-01')]

assert(x['UserId'].nunique() == y['TotalUsers'])
assert(x['TotalCharges'].sum().round(2) == y['TotalCharges'].round(2))
assert(x['OrderId'].nunique() == y['TotalOrders'])

x = df[(df.CohortGroup == '2009-01') & (df.OrderPeriod == '2009-09')]
y = cohorts.loc[('2009-01', '2009-09')]

assert(x['UserId'].nunique() == y['TotalUsers'])
assert(x['TotalCharges'].sum().round(2) == y['TotalCharges'].round(2))
assert(x['OrderId'].nunique() == y['TotalOrders'])

x = df[(df.CohortGroup == '2009-05') & (df.OrderPeriod == '2009-09')]
y = cohorts.loc[('2009-05', '2009-09')]

assert(x['UserId'].nunique() == y['TotalUsers'])
assert(x['TotalCharges'].sum().round(2) == y['TotalCharges'].round(2))
assert(x['OrderId'].nunique() == y['TotalOrders'])

변환된 데이터가 잘 되었는지 확인하기 위한 검증 합니다. 처음 설정한 df dataframe와 새롭게 생성한 cohort의 dataframe 간 차이가 있는지 확인하기 위해 assert를 사용합니다. 

 

#assert?

assert는 뒤의 조건이 True가 아니면 AssertError를 발생합니다.

 예를들어 어떤 함수는 성능을 높이기 위해 반드시 정수만을 입력받아 처리하도록 만들 수 있습니다. 이런 함수를 만들기 위해서는 반드시 함수에 정수만 들어오는지 확인할 필요가 있습니다. 이를 위해 if문을 사용할 수도 있고 '예외 처리'를 사용할 수도 있지만 '가정 설정문'을 사용하는 방법도 있습니다.

def test(t):
    assert type(t) is int, 'Oh! No. there is no Int Type in this list'

lists = [1, 3, 6, 3, 8, 7, 13, 23, 13, 2, 3.14, 2, 3]
for i in lists:
    test(i)
    
 
- Result
Traceback (most recent call last):
  File "C:/Users/HANA/PycharmProjects/HANATOUR/Pandas/TEST_PD#2.py", line 89, in <module>
    test(i)
  File "C:/Users/HANA/PycharmProjects/HANATOUR/Pandas/TEST_PD#2.py", line 85, in test
    assert type(t) is int, 'Oh! No. there is no Int Type in this list'
AssertionError: Oh! No. there is no Int Type in this list

 

 

cohorts.reset_index(inplace=True)
cohorts.set_index(['CohortGroup', 'CohortPeriod'], inplace=True)

cohort_group_size = cohorts['TotalUsers'].groupby(level=0).first()
cohort_group_size.head()

#CohortGroup
2009-01     22
2009-02     15
2009-03     13
2009-04     39
2009-05     50
2009-06     32
2009-07     50
2009-08     31
2009-09     37
2009-10     54
2009-11    130
2009-12     65
2010-01     95
2010-02    100
2010-03     24
Name: TotalUsers, dtype: int64

각 Cohort그룹별 Base를 가져오기 위해 CohortGroup별로 전체 고객 수의 첫 번째 값을 가져오기 위해 first()를 지정한다. 

 

print('-----Head()----------')
print(cohorts.head())

print('-----unstack()-------')
print(cohorts['TotalUsers'].unstack(0).head())

print('------divide()---------')
print(cohorts['TotalUsers'].unstack(0).divide(cohort_group_size, axis=1).head())


#Result
-----Head()----------
                         OrderPeriod  TotalUsers  TotalOrders  TotalCharges
CohortGroup CohortPeriod                                                   
2009-01     1                2009-01          22           30      1850.255
            2                2009-02           8           25      1351.065
            3                2009-03          10           26      1357.360
            4                2009-04           9           28      1604.500
            5                2009-05          10           26      1575.625
 
-----unstack()-------
CohortGroup   2009-01  2009-02  2009-03  ...  2010-01  2010-02  2010-03
CohortPeriod                             ...                           
1                22.0     15.0     13.0  ...     95.0    100.0     24.0
2                 8.0      3.0      4.0  ...     50.0     19.0      NaN
3                10.0      5.0      5.0  ...     26.0      NaN      NaN
4                 9.0      1.0      4.0  ...      NaN      NaN      NaN
5                10.0      4.0      1.0  ...      NaN      NaN      NaN

[5 rows x 15 columns]
 
------divide()---------
CohortGroup    2009-01   2009-02   2009-03  ...   2010-01  2010-02  2010-03
CohortPeriod                                ...                            
1             1.000000  1.000000  1.000000  ...  1.000000     1.00      1.0
2             0.363636  0.200000  0.307692  ...  0.526316     0.19      NaN
3             0.454545  0.333333  0.384615  ...  0.273684      NaN      NaN
4             0.409091  0.066667  0.307692  ...       NaN      NaN      NaN
5             0.454545  0.266667  0.076923  ...       NaN      NaN      NaN

[5 rows x 15 columns]

Map구조로 만들기 위한 데이터 구조화 unstack()를 이용하여 CohortGroup기준으로  데이터를 정리합니다. 이후 divide를 이용하여 X축을 기준으로 나누어 주게 됩니다. 상기 결과를 보고 이해가 필요하네요.

 

 

결과를 그래프로 그리기 위한 출력을 시행합니다.

user_retention[['2009-06', '2009-07', '2009-08']].plot(figsize=(10,5))
plt.title('Cohorts: User Retention')
plt.xticks(np.arange(1, 12.1, 1))
plt.xlim(1, 12)
plt.ylabel('% of Cohort Purchasing');
plt.show()

import seaborn as sns
sns.set(style='white')

plt.figure(figsize=(12, 8))
plt.title('Cohorts: User Retention')
sns.heatmap(user_retention.T, mask=user_retention.T.isnull(), annot=True, fmt='.0%');
plt.show()

반응형