Post

9.22 Chapter 03-04

9.22 Chapter 03-04

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

전제조건

  • 30개의 테크닉을 이용해서 스포츠 센터의 데이터를 다룹니다.

  • 회원 종류
    1. 종일 회원
    2. 주간 회원
    3. 야간 회원
  • 입회비 행사
    1. 입회비 반액 할인
    2. 입회비 무료 행사
    3. 입회비 전액
  • 탈퇴하려면 월말까지 신청하고 그 다음 달에 탈퇴됨

No.파일 이름개요
1use_log.csv센터의 이용 이력 데이터. 기간은 2018년 4월 ~ 2019년 3월
2customer_master.csv2019년 3월 말 시점의 회원 데이터
3class_master.csv회원 구분 데이터 (종일, 주간, 야간)
4campaign_master.csv행사 구분 데이터 (입회비 유무 등)

데이터를 읽어 들이고 확인하자

1
2
3
4
import pandas as pd
uselog = pd.read_csv('data/chap03/use_log.csv')
print(len(uselog))
uselog.head()

use_log.csv


  • use_log
    • 고객 ID, 이용일을 포함한 간단한 데이터
    • 어떤 고객이 언제 센터를 이용했는지를 알 수 있는 데이터입니다.
  • customer
    • 고객 ID, 이름, 회원 클래스, 성별, 등록일 정보
    • is_deleted 열은 2019년 3월 시점에 탈퇴한 유저를 시스템에서 빨리 찾기 위한 칼럼
  • class_master, campaign_master

  • 분석을 위해 데이터를 가공할 때, 기준이 되는 데이터를 결정해야 합니다.
  • 가능한 것은 고객 데이터인 customer와 이용 이력 데이터인 uselog 입니다.

고객 데이터를 가공하자

  • merge: 중심이 되는 데이터를 바탕으로 가로로 결합
1
2
3
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()

customer_join.csv


  • 테크닉 21에서 불러온 customer에 회원 구분 class_master와 캠페인 구분 campaign_master를 결합해서 customer_join을 새로 생성합니다.
1
customer_join.isnull().sum()

결측치 확인

  • join이후 결측치를 확인합니다.

고객 데이터를 집계하자

1
2
3
4
5
6
7
8
9
10
11
# 회원 클래스는 종일반이 거의 절반을 차지하고 야간 다음, 주간 순입니다.
customer_join.groupby('class_name').count()['customer_id']

# 캠페인은 일반 입회가 많고, 입회 캠페인에 의한 가입이 약 20%입니다.
customer_join.groupby('campaign_name').count()['customer_id']

# 남녀 비율은 남자쪽이 약간 많다는 것을 알 수 있습니다.
customer_join.groupby('gender').count()['customer_id']

# 마지막으로 2019년 3월 현재 가입된 회원은 2842면이고 탈퇴한 유저는 1350명인 것을 알 수 있습니다.
customer_join.groupby('is_deleted').count()['customer_id']
1
2
3
4
customer_join['start_date'] = pd.to_datetime(customer_join['start_date'])
customer_start = customer_join.loc[customer_join['start_date'] > pd.to_datetime('20180401')]
print(len(customer_start)) # 1361
print(len(customer_join)) # 4192
  • start_date를 datetime형으로 변환한 후, customer_start 변수에 해당 유저의 데이터를 저장하고 개수를 세어봅니다.

최신 고객 데이터를 집계하자

  • 현재 고객 데이터에는 이미 탈퇴한 고객도 포함되어 있기 때문에 월별 집계와는 차이가 있습니다. 여기서는 가장 최근 월의 고객 데이터를 집계해서 현재 고객의 전체 모습을 파악합니다.
1
2
3
4
5
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())]
print(len(customer_newer)) # end-date가 2019년 03월 31일 이후인 회원들의 수
customer_newer['end_date'].unique() # 2953
  • 종료일이 2019년 3월 31일 이후인 고객들을 추출하고, 추출한 데이터가 제대로 추출됐는지 확인하기 위해서 end_date의 유니크 개수를 확인합니다.
1
2
3
4
5
6
# 종일: 1444, 주간: 696, 야간: 813
customer_newer.groupby('class_name').count()['customer_id']
# 입회비반액할인: 311, 입회비무료: 242, 일반: 2400
customer_newer.groupby('campaign_name').count()['customer_id']
# F: 1400, M: 1553
customer_newer.groupby('gender').count()['customer_id']

이용 이력 데이터를 집계하자

  • 먼저, 고객마다 월 이용 횟수를 집계한 데이터를 작성합니다.
1
2
3
4
5
6
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()

고객 월 이용 횟수


  • 고객별로 평균값, 중앙값, 최댓값, 최솟값을 집계합니다.
1
2
3
uselog_customer = uselog_months.groupby('customer_id').agg({'count': ['mean', 'median', 'max', 'min']})
uselog_customer = uselog_customer.reset_index(drop=False)
uselog_customer.head()

고객 월 이용 횟수

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

  • 여기서는 고객마다 월/요일별로 집계하고, 최댓값이 4 이상인 요일이 하나라도 있는 회원은 플래그를 1로 처리합니다.

  • 먼저, 고객마다 월/요일별로 집계합니다.

1
2
3
4
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()

고객 월/요일별 이용 횟수

1
2
3
4
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()

고객 최대 이용 횟수

  • 위 1행에서는 특정 월, 특정 요일에 가장 많이 이용한 횟수를 계산합니다.
  • 즉, 이 횟수가 4 또는 5인 사람은 적어도 어떤 달에 매주 특정 요일에 정기적으로 방문한 고객입니다.

고객 데이터와 이용 이력 데이터를 결합하자

  • 우선 uselog_customer, uselog_weekdaycustomer_join과 결합합니다.
1
2
3
4
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으로 결합과정

1
customer_join.isnull().sum() # join 이후 결측치 확인
  • 결측치를 확인하여 결합에 문제가 없음을 확인합니다.

회원 기간을 계산하자

  • 회원기간: end_date - start_date
  • end_date에 결측치가 들어 있을 경우, 탈퇴하지 않은 회원은 2019년 4월 30일로 채워서 회원 기간을 계산합니다.
1
2
3
4
5
6
7
8
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()

고객 행동의 각종 통계향을 파악하자

1
2
3
customer_join[['mean', 'median', 'max', 'min']].describe()

customer_join.groupby('routine_flg').count()['customer_id']

customer_join의 각종 통계량 파악

  • 칼럼 이름 mean은 고객의 매월 평균 이용 횟수
  • 행에 있는 mean은 고객의 매월 평균 이용 횟수의 평균
  • 평균값, 중앙값에는 큰 차이가 없으며, 고객 1명당 이용 횟수는 약 5번 정도인 것을 알 수 있습니다.
1
2
3
import matplotlib.pyplot as plt
%matplotlib inline
plt.hist(customer_join['membership_period']) # 회원 기간의 분포

회원 기간 분포 히스토그램

탈퇴 회원과 지속 회원의 차이를 파악하자

1
2
3
# 탈퇴 회원
customer_end = customer_join.loc[customer_join['is_deleted'] == 1]
customer_end.describe()

회원 기간 분포 히스토그램

1
2
3
# 지속 회원
customer_stay = customer_join.loc[customer_join['is_deleted']==0]
customer_stay.describe()

회원 기간 분포 히스토그램

  • 탈퇴 회원의 매월 이용 횟수의 평균값, 중앙값, 최댓값, 최솟값은 모두 지속 회원보다 작습니다.
  • 특히, 평균값과 중앙값은 1.5배 정도 차이가 나는 것을 알 수 있습니다.
  • 반면에 매월 최대 이용 횟수의 평균값은 지속 회원이 높기는 하지만, 탈퇴 회원도 6.4 정도입니다.
  • routine_flg의 평균값은 차이가 크게 나서 지속 회원은 0.98로 많은 회원이 정기적으로 이용하고 있습니다. 하지만 탈퇴 회원은 0.45로 거의 절반은 랜덤하게 이용하고 있다고 생각할 수 있습니다.

고객의 행동을 예측하는 테크닉 10

  • 이 장에서는 앞 장에서 사전 분석한 스포츠 센터 회원의 행동 정보를 이용해서 머신러닝으로 예측을 진행합니다.

전제조건

  • 여기서는 5개의 데이터 중에서 use_log.csv와 customer_join.csv만 사용합니다.

|No.|파일 이름|개요| |:—:|:—:|:—:| |1|use_log.csv|센터의 이용 이력 데이터. 기간은 2018년 4월 ~ 2019년 3월| |2|customer_master.csv|2019년 3월 말 시점의 회원 데이터| |3|class_master.csv|회원 구분 데이터 (종일, 주간, 야간)| |4|campaign_master.csv|행사 구분 데이터 (입회비 유무 등)| |5|customer_join.csv|3장에서 작성한 이용 이력을 포함한 고객 데이터| —

데이터를 읽어 들이고 확인하자

  • use_log.csv와 customer_join.csv만을 읽어 들입니다.
  • 또한 만약의 상황을 대비해 결측치도 확인합니다.
1
2
3
import pandas as pd
uselog = pd.read_csv('data/chap04/use_log.csv')
uselog.isnull().sum() # 결측치 확인 결과: 0
1
2
customer = pd.read_csv('data/chap04/customer_join_new.csv')
customer.isnull().sum() # 결측치 확인 결과: 0

클러스터링으로 회원을 그룹화하자

  • customer 데이터를 사용해서 회원 그룹화를 진행합니다.
  • 클러스터링에 이용하는 변수는 고객의 한 달 이용 이력 데이터인 mean, median, max, min, membership_period로 하겠습니다.
1
2
3
# 필요한 변수를 추출
customer_clustering = customer[['mean', 'median', 'max', 'min', 'membership_period']]
customer_clustering.head()

클러스터링을 위한 변수 추출

  • 이번에 사용할 클러스터링 방법
    • 가장 전통적인 클러스터링
    • 변수 간의 거리를 기반으로 그룹화를 진행하는 K-means clustering
    • K = 4로 4개의 그룹을 설정
    • mean, median, max, min은 1~8 사이의 값이지만, membership_period는 1~47 사이의 값이기에 표준화를 진행합니다.
1
2
3
4
5
6
7
8
9
10
from sklearn.cluster import KMeans # kmeans import
from sklearn.preprocessing import StandardScaler # 표준화 import
sc = StandardScaler()
customer_clustering_sc = sc.fit_transform(customer_clustering)

kmeans = KMeans(n_clusters=4, random_state=0)
clusters = kmeans.fit(customer_clustering_sc)
customer_clustering['cluster'] = clusters.labels_
print(customer_clustering['cluster'].unique()) # [2 1 0 3]
customer_clustering.head()

클러스터링 결과

  • 0~3까지 4개의 그룹이 작성됐고, 처음 5행의 결과처럼 각 고객 데이터에 그룹이 할당된 것을 확인할 수 있습니다.

클러스터링 결과를 분석하자

1
2
# 칼럼 이름 변경
customer_clustering.columns = ['월평균값', '월중앙값', '월최댓값', '월최솟값', '회원기간', 'cluster']
1
customer_clustering.groupby('cluster').count()

클러스터링 결과

  • 그룹 0이 가장 많아 1336명의 고객이 존재하고, 다음으로 그룹3, 그룹2, 그룹1의 순서입니다.
1
customer_clustering.groupby('cluster').mean()

클러스터링 결과

  • 그룹 2는 회원 기간은 짧지만, 이용률이 높은 회원입니다.
  • 그룹 1은 회원 기간이 짧고 가장 이용률이 낮은 회원입니다.
  • 그룹 3과 그룹 0은 그룹 1, 그룹 2보타 회원 기간이 깁니다.
  • 그룹 3과 그룹 0을 비교하면 그룹 3쪽이 회원 기간은 길지만, 이용률이 약간 낮습니다.

클러스터링 결과를 가시화하자

  • 차원 축소란 비지도학습의 일종으로, 정보를 되도록 잃지 않게 하면서 새로운 축을 만드는 것입니다.
  • 여기서, 차원 축소의 대표적인 방법으로 주성분 분석을 사용합니다.
  • 이를 통해 5개의 변수를 2차원 평면상에 가시화합니다.
1
2
3
4
5
6
7
from sklearn.decomposition import PCA
X = customer_clustering_sc
pca = PCA(n_components=2)
pca.fit(X)
x_pca = pca.transform(X)
pca_df = pd.DataFrame(x_pca)
pca_df['cluster'] = customer_clustering['cluster']
  • 위 코드에서 주성분 분석을 진행합니다.
1
2
3
4
5
import matplotlib.pyplot as plt
%matplotlib inline
for i in customer_clustering['cluster'].unique():
    tmp = pca_df.loc[pca_df['cluster']==i]
    plt.scatter(tmp[0], tmp[1])

클러스터링 결과

  • 주성분 분석을 통해 2차원으로 축소한 데이터가 저장된 pca_dfmatplotlib을 이용해서 가시화를 진행합니다.

클러스터링 결과를 바탕으로 탈퇴 회원의 경향을 파악하자

  • 클러스터링으로 분할한 4개의 그룹 속에서, 지속 회원과 탈퇴 회원을 집계합니다.
  • 탈퇴 회원을 특정하기 위해서 is_deleted 열을 customer_clustering에 추가해서 clusteris_deleted 별로 집계합니다.
1
2
customer_clustering = pd.concat([customer_clustering, customer], axis=1)
customer_clustering.groupby(['cluster', 'is_deleted'], as_index=False).count()[['cluster', 'is_deleted', 'customer_id']]

클러스터링 결과

  • 그룹 2와 그룹 3은 지속 회원이 많습니다.
  • 그룹 1은 탈퇴 회원만 있습니다.
  • 그룹 0은 골고루 포함되어 있습니다.

  • 그룹 2는 회원 기간이 짧지만 초기에 의욕적이어서 전체적으로 이용률이 높습니다.
  • 그룹 3은 회원 기간이 길고 이용률이 그룹 2보다 낮지만 지속 회원이 많은 것을 생각하면 이용이 안정적이라고 생각할 수 있습니다.
1
customer_clustering.groupby(['cluster', 'routine_flg'], as_index=False).count()[['cluster', 'routine_flg', 'customer_id']]

클러스터링 결과

  • groupby를 이용해서 cluster, routine_flg 별로 customer_id의 건수를 집계합니다.
  • 지속 회원이 많은 그룹 0, 그룹 3에는 정기적으로 이용하는 회원이 많다는 것을 알 수 있습니다.

다음 달의 이용 횟수 예측을 위해 데이터를 준비하자

  • 지도학습은 미리 정답을 알고 있는 숫자 데이터를 이용해서 예측합니다.
  • 고객의 과거 행동 데이터로부터 다음 달의 이용 횟수를 예측하는 경우에는 지도학습의 회귀 분석을 이용합니다.
  • 여기에서는 과거 6개월의 이용 데이터를 사용해 다음 달의 이용 횟수를 예측해 봅니다.

  • 이번 달: 2018.10
No.데이터 종류기간개월 수
1x_data2018.05 ~ 2018.106개월
2y_data2018.111개월
  • 먼저 uselog 데이터를 이용해 연월, 회원마다 집계합니다.
1
2
3
4
5
6
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()

연월 회원 데이터

1
2
3
4
5
6
7
8
9
10
11
12
year_months = list(uselog['연월'].unique())
predict_data = pd.DataFrame()
for i in range(6, len(year_months)):
    tmp = uselog_months.loc[uselog_months['연월'] == year_months[i]]
    tmp.rename(columns={'count': 'count_pred'}, inplace=True)
    for j in range(1,7):
        tmp_before = uselog_months.loc[uselog_months['연월'] == year_months[i-j]]
        del tmp_before['연월']
        tmp_before.rename(columns={'count': 'count_{}'.format(j-1)}, inplace=True)
        tmp = pd.merge(tmp, tmp_before, on='customer_id', how='left')
    predict_data = pd.concat([predict_data, tmp], ignore_index=True)
predict_data.head()

연월 회원 데이터

  • count_pred 칼럼은 예측하고 싶은 달의 데이터입니다.
  • count_0이 1개월 전의 데이터입니다.
  • 이 처럼 과거 6개월의 데이터를 나열합니다.
1
2
3
predict_data = predict_data.dropna()
predict_data = predict_data.reset_index(drop=True)
predict_data.head()
  • 아직 가입 기간이 짧아서 데이터가 존재하지 않는 경우도 있으므로, 결측치를 처리하는 코드도 작성합니다.

특징이 되는 변수를 추가하자

1
2
predict_data = pd.merge(predict_data, customer[['customer_id', 'start_date']], on='customer_id', how='left')
predict_data.head()
  • 회원 기간은 시계열 변화를 볼 수 있기 때문에 이번 데이터처럼 기본 데이터가 시계열 데이터인 경우 유효할 가능성이 있습니다.
  • 고객 데이터인 customerstart_date 칼럼을 앞에서 작성한 predict_data에 결합합니다.
1
2
3
4
5
6
7
8
predict_data['now_date'] = pd.to_datetime(predict_data['연월'], format='%Y%m')
predict_data['start_date'] = pd.to_datetime(predict_data['start_date'])
from dateutil.relativedelta import relativedelta
predict_data['period'] = None
for i in range(len(predict_data)):
    delta = relativedelta(predict_data['now_date'][i], predict_data['start_date'][i])
    predict_data['period'][i] = delta.years*12 + delta.months
predict_data.head()

period 칼럼 추가

다음 달 이용 횟수를 예측하는 모델을 구축하자

  • 2018년 4월 이후에 새로 가입한 회원만 이용해서 모델을 작성합니다.
  • 데이터를 학습용 데이터와 평가용 데이터로 나눠서 학습을 진행합니다.
1
2
3
4
5
6
7
8
predict_data = predict_data.loc[predict_data['start_date']>=pd.to_datetime('20180401')]
from sklearn import linear_model
from sklearn.model_selection import train_test_split
model = linear_model.LinearRegression()
X = predict_data[['count_0', 'count_1', 'count_2', 'count_3', 'count_4', 'count_5', 'period']]
y = predict_data['count_pred']
X_train, X_test, y_train, y_test = train_test_split(X, y)
model.fit(X_train, y_train)
  • X: 예측에 사용할 변수(설명 변수), y: 예측하고 싶은 변수(목적 변수)
  • 학습용 데이터와 평가용 데이터를 나눠 학습을 진행함으로써, 모델의 과적합을 방지합니다.
1
2
print(model.score(X_train, y_train)) # 0.5996778634722448
print(model.score(X_test, y_test)) # 0.628938039638544
  • 그 후 score함수를 사용해, 모델의 정확도를 평가하면 위와 같습니다.

모델에 기여하는 변수를 확인하자

1
2
coef = pd.DataFrame({'feature_names': X.columns, 'coefficient': model.coef_})
coef

모델에 기여하는 변수

  • 설명 변수마다 기여하는 계수를 확인합니다.
  • count_0가 가장 크고, 과거로 거슬러 올라갈수록 기여도가 작아지는 경향이 있다는 것을 알 수 있습니다.

다음 달의 이용 횟수를 예측하자

1
2
3
4
5
6
x1 = [3,4,4,6,8,7,8]
x2 = [2,2,3,3,4,6,8]
x_pred = [x1, x2]

model.predict(x_pred)
# array([3.79341431, 1.99642155])
  • 임의로 두 가상의 회원 데이터를 작성하고, 방문 횟수를 예측합니다.
  • 첫 번째는 3.8회, 두 번째는 1.9회로 예측했습니다.
This post is licensed under CC BY 4.0 by the author.