[내배캠] 데이터분석 6기/프로젝트 기록

[본캠프 26일차] 부록: [chap 2] 기초 프로젝트 준비③

물맨두 2025. 3. 25. 20:01

 

어제 오후 스크럼 때 수요일까진 발표용 PPT 자료를 만드는 것을 목표로 오늘 각자 할 일을 정했는데

250325 오후 스크럼 시간 동안 회의하면서 각자 나눈 부분

그래서 오늘은 이게 말이 되든 안 되든 고객 세그먼트를 해가야 해..

 

그나저나 어제 두통이 심해서 그냥 우선 자고 나면 괜찮아지겠지 싶어서 자고 일어났는데 피곤한 것도, 두통도 여전하구나... 주말 언제 와.

 


 

방문 횟수에 따른 고객군 탐색하기

(출처: 내 티스토리 "[본캠프 25일차] 부록: [chap 2] 기초 프로젝트 준비" 中)

어제 구한 방문 횟수 기준으로 고객 수, 총 판매액, 전체 판매액에서 차지하는 비율,객단가 평균, 객단가 중앙값을 보기로 한다.

 

# 방문횟수로 피벗 테이블 만들기

# 방문횟수별 판매액 계산한 컬럼 'total_sum' 생성
pivot_visitcnt = tra_final2.pivot_table(index=['visit_cnt'], values=['total_sum'], aggfunc='sum').reset_index()
# 방문횟수별 판매액이 2019년 전체 판매액에서 몇 퍼센트를 차지하는지 계산한 컬럼 'total_sum(%)' 생성 
total_sum_of_2019 = tra_final2['total_sum'].sum()
pivot_visitcnt['total_sum(%)'] = (pivot_visitcnt['total_sum'] / total_sum_of_2019)*100

# 방문횟수별 고객 수를 집계한 컬럼 'customer_cnt' 생성
p1 = tra_final2.pivot_table(index=['visit_cnt'], values=['customer_id'], aggfunc='nunique').reset_index()
p1.rename(columns={'customer_id':'customer_cnt'}, inplace=True)
# 방문횟수별 고객 수가 2019년 전체 방문한 고객 수에서 몇 퍼센트를 차지하는지 계산한 컬럼 'customer_cnt(%)' 생성
total_customer_cnt_of_2019 = tra_final2['customer_id'].nunique()
p1['customer_cnt(%)'] = (p1['customer_cnt'] / total_customer_cnt_of_2019)*100
pivot_visitcnt = pd.merge(pivot_visitcnt, p1, how='left', left_on='visit_cnt', right_on='visit_cnt')

# 방문횟수별 객단가를 계산한 컬럼 'AOV'를 생성 *객단가: 고객 1명의 평균 구매 금액 (total_sum을 customer_cnt로 나눔)
pivot_visitcnt['AOV'] = pivot_visitcnt['total_sum'] / pivot_visitcnt['customer_cnt']

# 방문횟수별 고객의 소비 금액을 집계해 그 중 중앙값을 확인할 수 있는 컬럼 'median of spending' 생성 (AOV 계산할 때 이상치를 제거하지 않은 상태로 평균을 구했기 때문에 참고하고자 함)
# 고객별 2019년 소비 총액을 구함
sum_per_cusid = tra_final2.pivot_table(index=['visit_cnt', 'customer_id'], values=['price3_KRW'], aggfunc='sum').reset_index()
# 방문횟수별 일인 소비 총액의 중앙값을 구함
median_per_visitcnt = sum_per_cusid.pivot_table(index=['visit_cnt'], values=['price3_KRW'], aggfunc='median').reset_index()
pivot_visitcnt = pd.merge(pivot_visitcnt, median_per_visitcnt, how='left', left_on='visit_cnt', right_on='visit_cnt')
pivot_visitcnt.rename(columns={'price3_KRW':'median of spending'}, inplace=True)

 

위의 테이블 pivot_visitcnt은 방문 횟수를 기준으로 소비액, 고객 수, 객단가 등을 구한 테이블이다. 각 컬럼들을 소개하자면,

  • visit_cnt : 방문 횟수 (customer_id별로 t_dat을 nunique로 집계함)
  • total_sum: 총 소비금액 (visit_cnt별로 각자의 소비 금액인 'total_sum'을 sum()으로 합침)
  • total_sum(%): 2019년 총 판매액에서 해당 total_sum이 차지하는 비율을 구함 ((total_sum / 2019년 총 판매액)*100)
  • customer_cnt: 고객 수 (visit_cnt별로 customer_id를 nunique로 집계함)
  • customer_cnt(%): 2019년에 방문한 전체 고객 중 해당 방문 횟수의 고객이 차지하는 비율을 구함 ((customer_cnt/2019년 전체 고객 수)*100) 
  • AOV: 방문 횟수별 고객 한 명 소비하는 금액의 평균 (total_sum / customer_cnt)
  • median of spending: 각 고객별로 2019년 소비 총액을 구해서 → 방문 횟수별로 개인 소비 총액의 중앙값을 구함 

median of spending 컬럼을 추가적으로 구한 것은 현재 AOV를 계산할 때 이상치를 전처리하지 않은 상태에서 평균을 구한 것이기에 AOV가 과연 해당 방문 횟수에 해당하는 고객들의 소비 경향을 얼마나 잘 보여주고 있는지 참고하고자 방문 횟수별 일인 소비 총액의 중앙값도 함께 구해봤다.

[참고] "평균과 중앙값의 차이와 각각의 의미"

 

import seaborn as sns

plt.figure(figsize=(15, 7))
sns.boxplot(data=sum_per_cusid, x='visit_cnt', y='price3_KRW')
plt.title('Spending per Visit Count')
plt.xlabel('Visit Count')
plt.ylabel('Total Spending of 2019 per Customer')

방문 횟수별 회원들의 2019년 소비 총액을 상자그림으로 그려보았다. 

 

위의 그래프에서도 보이는 것처럼 같은 방문 횟수 안에서도 각자 소비하는 총액이 크게 차이남을 확인할 수 있는데,

# 방문횟수가 1~3회인 고객군의 기본 통계 파악하기 (∵해당 고객이 전체 고객의 88.7%에 해당)
sum_per_cusid[sum_per_cusid['visit_cnt'] == 1]['price3_KRW'].describe()
sum_per_cusid[sum_per_cusid['visit_cnt'] == 2]['price3_KRW'].describe()
sum_per_cusid[sum_per_cusid['visit_cnt'] == 3]['price3_KRW'].describe()

방문횟수별로 2019년 소비 총액 관련 기초 통계값 확인하기 ((좌) 1회 방문 / (중) 2회 방문 / (우) 3회 방문)

세부적으로 우선 방문 횟수가 1회, 2회, 3회인 고객들의 2019년 소비 총액의 최솟값과 최댓값을 비롯한 기초 통계 정보를 확인해봤다.

이들만 우선 확인한 것은 이들이 2019년 전체 고객의 88.69%를 차지하기 때문이고, 또한 상자그림에서 이상치가 많이 보였기 때문이다. 이 통계값을 바탕으로 상자그림에서 각 방문횟수의 최댓값(Q3+IQR*1.5)은,

  • 1회 : 62,290.34원
  • 2회 : 117,947.03원
  • 3회 : 174,666.57원

해당 최댓값을 벗어나는 고객들이 얼마나 되는지 그 수와 그들의 소비 총액를 집계하면,

# 상자그림의 최댓값을 벗어나는 이상치들이 얼마나 되는지 집계
sum_per_cusid[(sum_per_cusid['visit_cnt'] == 1) & (sum_per_cusid['price3_KRW'] > 62290.34)].count()
sum_per_cusid[(sum_per_cusid['visit_cnt'] == 2) & (sum_per_cusid['price3_KRW'] > 117947.03)].count()
sum_per_cusid[(sum_per_cusid['visit_cnt'] == 3) & (sum_per_cusid['price3_KRW'] > 174666.57)].count()

# 상자그림의 최댓값을 벗어나는 이상치들의 총액이 얼마나 되는지 집계
sum_per_cusid[(sum_per_cusid['visit_cnt'] == 1) & (sum_per_cusid['price3_KRW'] > 62290.34)]['price3_KRW'].sum()
sum_per_cusid[(sum_per_cusid['visit_cnt'] == 2) & (sum_per_cusid['price3_KRW'] > 117947.03)]['price3_KRW'].sum()
sum_per_cusid[(sum_per_cusid['visit_cnt'] == 3) & (sum_per_cusid['price3_KRW'] > 174666.57)]['price3_KRW'].sum()
  • 1회 : 13,637명 (5.18%) / 1,216,651,636.45원 (12.87%)
  • 2회 : 4,195명 (4.28%) / 651,049,177.49원 (4.72%)
  • 3회 : 1,802명 (3.96%) / 395,004,112.07원 (2.66%)

1회 방문 고객에 대한 이상치의 데이터가 2~3회보다 유독 이상치의 총액이 차지하는 비중이 큰 것은 아무래도 1회 방문 한 번에 홀로 1,313,404.06원 소비한 고객이 있어서 그런 듯하다.

 

파레토 법칙으로 고객 데이터 바라보기

파레토 법칙

[참고] 파레토 법칙

파레토 법칙(Pareto principle)이란 전체 결과의 80%가 전체 원인의 20%에서 일어나는 현상을 지칭한다. 예를 들어, 20%의 고객이 백화점 전체 매출의 80%에 해당하는 만큼 쇼핑하는 현상을 설명할 때 이 용어를 사용한다.

 

데이터에서 파레토 법칙을 확인할 수 있는가

이는 위에서 구한 방문 횟수에 대한 피봇 테이블이다.

 

고객 수에 대한 컬럼을 보면 1회 방문한 고객이 57.39%, 2회 방문한 고객이 21.37%로 1~2회 방문한 고객들이 2019년 전체 고객의 약 78%를 차지하기에 1회 방문한 고객, 2회 방문한 고객, 3회 방문한 고객으로 비교해보기로 했다.

# 방문횟수가 4회 이상은 데이터들은 전부 합치기
viscnt_over_3 = pivot_visitcnt[['visit_cnt', 'total_sum', 'customer_cnt']].loc[3:]
viscnt_over_3_sum = viscnt_over_3['total_sum'].sum() # 74487766521.20349 원
viscnt_over_3_cus = viscnt_over_3['customer_cnt'].sum() # 51825 명

# 결국 일일이 가져옴..
viscnt = ['1', '2', 'over 2']
pie_cus = [262995, 97910, 97330] # 방문횟수 별 고객 수(1회, 2회, 3회 이상)
pie_sum = [9455805328.87, 13790755112.08, 89337790179.7835]

plt.pie(pie_cus, labels= viscnt, autopct='%.1f')
plt.legend(viscnt)
plt.title('Customer per Visit Count')

plt.pie(pie_sum, labels= viscnt, autopct='%.1f')
plt.legend(viscnt)
plt.title('Amount per Visit Count')

위의 원 그래프에서 확인 가능한 것처럼 21.2%에 해당하는 고객들이 2019년 총 판매액의 79.4%를 차지하고 있다는 것에서 이들에게 집중해 마케팅을 펼칠 필요가 있다는 것을 알 수 있다.

 

단, 단순히 방문 횟수로만 고객 세그먼트를 진행할 순 없고 소비 금액 역시 적용할 수 있는 부분을 찾아봤다.

 

qcut() 함수를 이용해 고객 등급 구해보기

# 첫 번째 시도: 전체 고객 데이터를 가지고 고객 등급 산정하기
# qcut() 활용해 상대구간 나누기
spending_grage = ['VIP', 'Gold', 'Silver', 'Bronze']

# sum_per_cusid : 고객id별 총 소비 금액
sum_per_cusid['spending_grage'] = pd.qcut(sum_per_cusid['price3_KRW'], 4, labels=spending_grage)
pd.qcut(sum_per_cusid['price3_KRW'], q=4)

테이블 sum_per_cusid는 고객ID별로 해당 고객의 2019년 총 소비 금액을 구해놓은 테이블이다.

 

우선 처음에는 전체 고객들의 총 소비 금액을 대상으로 4개의 등급으로 구했을 때, 커트라인은 다음과 같다.

  • Bronze: 0 ~ 19,321원
  • Silver: 19,321 ~ 35,345원
  • Gold: 35,345원 ~ 65,940원
  • VIP: 65,940 ~ 1,740,874원

이건... 아니다 싶어서 따질 것도 없이 바로 폐기했다.

 

# 두 번째 시도 : 방문 횟수가 3회 이상인 고객들을 대상으로 지출 금액 등급 산정 기준 구해보기
pd.qcut(sum_per_cusid[sum_per_cusid['visit_cnt']>2]['price3_KRW'], q=4)

앞서서 방문 2회 이상인 고객층이 전체 판매액의 80%를 차지하고 있었으니 이들만 대상으로 하면 구간이 달라질까 싶어서 시도해봤다.

  • Bronze: 0 ~ 70,807.8원
  • Silver: 70,807.8 ~ 104,027.36원
  • Gold: 104,027.36 ~ 158,533.24원
  • VIP: 158,533.24 ~ 1,740,874.997원
pd.qcut(sum_per_cusid[sum_per_cusid['visit_cnt']>2]['price3_KRW'], [0, 0.5, 0.8, 0.96, 1])
  • Bronze: 5849.34 0 ~ 104,027.36원
  • Silver: 104,027.36 ~ 176,201.473원
  • Gold: 176,201.473 ~ 328,662.54원
  • VIP: 328,662.54원 ~ 1740874.997

[참고] 멤버십 도입 가이드: 등급 기준과 효과 분석 방법

이번에는 각 구간별 고객 수를 동등하게 나누는 것이 아닌 50%, 30%, 16%, 4% 구간으로 4가지 등급을 나눈 지출 금액 기준이다.

 

sum_per_cusid['spending_grade'] = pd.qcut(sum_per_cusid[sum_per_cusid['visit_cnt']>2]['price3_KRW'], [0, 0.5, 0.8, 0.96, 1], labels=spending_grade)
sum_per_cusid['spending_grade'].value_counts()

현재 단순히 고객 비중으로만 고객 등급을 구분해 놓았을 때의 고객 수다. 그런데 현재는 이처럼 고객 비중을 기준이 소비 금액 구간을 나눠놓은 터라 구간 금액이 애매해서 깔끔하게 떨어지는 값으로 다음과 같이 손을 보았다.

sum_per_cusid['spending_grade'] = pd.cut(sum_per_cusid['price3_KRW'], [0, 100000, 180000, 350000, sum_per_cusid['price3_KRW'].max()], labels=spending_grade)
  • Bronze: 5849.34 0 ~ 100,000원
  • Silver: 100,000 ~ 180,000원
  • Gold: 180,000 ~ 350,000원
  • VIP: 350,000원 ~ 1740874.997