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

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

물맨두 2025. 3. 27. 14:58

(코드 정리. 공유를 위한 ipynb 파일의 내용을 혹시 몰라 티스토리에도 남겨 놓는다)

 


라이브러리 및 데이터 불러오기

# 라이브러리
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

# 데이터 (불러올 때 파일 경로는 각자의 것으로 변경해주세요)
cus = pd.read_csv("/Users/.../customer_hm.csv")
tra = pd.read_csv("/Users/.../transactions_hm.csv")
art = pd.read_csv("/Users/.../articles_hm.csv")

거래 내역(transactions) 데이터로 고객 분석하기

🔍 transactions 테이블 살펴보기

# 테이블에 어떤 컬럼들이 있고, 그 컬럼별 결측치의 개수 및 데이터 타입을 확인하기
tra.info()

# t_dat : 구매일 (데이터 타입이 datetime이 아님!!)
# customer_id : 고객 id ( <- FK (cus의 PK) )
# article_id : 품목 id ( <- FK (art의 PK) )
# price : 금액
# sales_channel_id : 판매 채널 (1: 오프라인 / 2: 온라인)

 

# 데이터에 대한 기초통계값 확인하기 (데이터 타입이 object인 컬럼은 제외하고 계산함)
tra.describe()

 

# 데이터의 첫 5행를 조회에 값들이 어떻게 들어가있는지 확인하기
tra.head()

📝 데이터에서 발견한 점

* 컬럼 't_dat'의 데이터 타입이 datetime이 아니므로 이를 활용하기 위해선 데이터 형식을 변경해주어야 함
* 컬럼 'price' H&M에서 정보 보호를 위해 암호화된 가격으로 이 값을 해석할 필요가 있음
* .describe()로 살펴봤을 때 큰 값의 경우 숫자가 공학용 지수 표기법으로 나타나는 부분을 보기 좋게 표시되게끔 처리해 줄 필요가 있음

 

🔍 transactions 테이블 전처리 ➀

# ['t_dat']의 데이터 타입 변환하기
tra['t_dat'] = pd.to_datetime(tra['t_dat'])

# ['price']의 암호화된 가격 해석하기 (feat. 튜터님의 은혜)
tra['price2_USD'] = tra['price']*590 # 암호화를 풀어서 USD로 환산하기
tra['price3_KRW'] = tra['price2_USD']*1466 # USD를 KRW로 환산하기

# 숫자 포맷팅 처리하기
pd.set_option('float_format', '{:.2f}'.format)
tra['price3_KRW'] = tra['price3_KRW'].round(2)

 

# 다시 테이블 정보 및 기초 통계값을 확인하면 위에 처리한 내용들이 반영됐음을 확인할 수 있음
tra.info()
tra.describe()


🔍 2019년에 거래한 고객 살펴보기

✔️ 전체 고객 중 2019년에 구매한 고객 비율 구하기

#구매 고객 수 세기
구매_회원_수 = tra['customer_id'].nunique()
전체_회원_수 = cus['customer_id'].nunique()
print(f'전체 회원 수: {전체_회원_수}, 구매 회원 수: {구매_회원_수} 구매 회원이 차지하는 비율은 {(구매_회원_수/전체_회원_수)*100}%')

 

✔️ 방문 횟수에 따른 소비 금액에 대한 데이터 계산하기

# 방문 횟수 집계 (방문 횟수: 해당 고객이 2019년 동안 자사에서 구매한 일자를 중복값 없이 집계)
trapt_visitcnt = tra.pivot_table(index='customer_id', values='t_dat', aggfunc='nunique')
trapt_visitcnt.rename(columns={'t_dat':'visit_cnt'}, inplace=True)
# 고객ID별 방문횟수를 구한 테이블
trapt_visitcnt

# 고객ID별 2019년 총 소비 금액 구하기
amount_id = pd.DataFrame(tra.groupby('customer_id')['price3_KRW'].sum())
amount_id = amount_id.reset_index()
# 고객ID별 2019년 총 소비 금액을 구한 테이블
amount_id


# 기존의 transactions 테이블에 고객의 방문 횟수에 대한 컬럼 ['visit_cnt'], ['id_amount'] 추가
tra1 = pd.merge(tra, trapt_visitcnt, how='left', left_on='customer_id', right_on='customer_id')
tra1 = pd.merge(tra1, amount_id, how='left', left_on='customer_id', right_on='customer_id')
tra1.rename(columns={'price3_KRW_x':'price3_KRW', 'price3_KRW_y':'id_amount'}, inplace=True)
tra1

 

# tra1 테이블로 '방문 횟수'별 총 판매액, 고객 수, 객단가 등 집계하기

# 1. 방문횟수별 총 판매액(['total_sum']) 구하기
pivot_visitcnt = tra1.pivot_table(index=['visit_cnt'], values=['price3_KRW'], aggfunc='sum').reset_index()
pivot_visitcnt.rename(columns={'price3_KRW':'total_sum'}, inplace=True)
# 방문횟수별 판매액이 2019년 전체 판매액에서 몇 퍼센트를 차지하는지 계산한 컬럼 'total_sum(%)' 생성
total_sum_of_2019 = tra1['price3_KRW'].sum()
pivot_visitcnt['total_sum(%)'] = (pivot_visitcnt['total_sum'] / total_sum_of_2019)*100

# 2. 방문횟수별 고객 수['customer_cnt']를 집계하기
p1 = tra1.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 = tra1['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')

# 3. 방문횟수별 객단가['AOV'] 구하기 (*객단가: 고객 1명의 평균 구매 금액 (total_sum을 customer_cnt로 나눔))
pivot_visitcnt['AOV'] = pivot_visitcnt['total_sum'] / pivot_visitcnt['customer_cnt']

# 4. 방문횟수별 각 고객의 소비 총액에서 중앙값['median of spending'] 구하기 (AOV 계산할 때 이상치를 제거하지 않은 상태로 평균을 구했기 때문에 참고하고자 함)
# 고객별 2019년 소비 총액을 구함
sum_per_cusid = tra1.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

 

# 방문횟수에 따른 2019년 일인 총 소비 금액에 대한 데이터 분포를 box plot으로 확인하기
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')

 

✔️ 파레토 법칙을 주어진 데이터에 적용해보기

pivot_visitcnt.head()

  • 파레토 법칙: 전체 결과의 80%가 전체 원인의 20%에 의해 좌우됨
  • 해당 법칙이 가진 데이터에서도 확인 가능하다면 그 20%의 고객을 핵심 고객층으로 판단해 이들의 소비 경향을 바탕으로 고객 등급 산정하기로 함
  • 방문 횟수별로 보았을 때 1~2회 방문한 고객이 전체 고객의 약 78%이기에 기준점 다음과 같이 잡음
    • 방문 횟수 2회 이하, 방문 횟수 3회 이상의 고객군들 비교
    • 방문 횟수가 3회 이상인 고객군이 2019년 총 판매액에서 차지하는 비중을 확인하고자 함

 

# 원 그래프를 통해 확인하기

# 우선 방문 횟수가 3회 이상인 데이터들을 모두 합쳐주기
viscnt_over_2 = pivot_visitcnt[['visit_cnt', 'total_sum', 'customer_cnt']].loc[2:]
viscnt_over_2_sum = viscnt_over_2['total_sum'].sum() # 12666700950.2131 원
viscnt_over_2_cus = viscnt_over_2['customer_cnt'].sum() # 97330 명

# 일일히 가져오면서 이게 맞나 의심하지만 급하니까 우선 진행
viscnt = ['1', '2', 'over 2']
pie_cus = [262995, 97910, 97330] # 방문횟수 별 고객 수(1회, 2회, 3회 이상)
pie_sum = [7128514676.83, 5399210892.27 , 12666700950.2131]

# 원 그래프 1: 고객 수 비중
plt.pie(pie_cus, labels= viscnt, autopct='%.1f')
plt.legend(viscnt)
plt.title('Customer per Visit Count')
# 원 그래프 2: 총 판매액 비중
plt.pie(pie_sum, labels= viscnt, autopct='%.1f')
plt.legend(viscnt)
plt.title('Amount per Visit Count')

  • 2019년 총 판매액 비중에서 다음과 같은 점을 발견
    • 3회 이상 방문한 고객이 총 판매액 비중에서 80%가 아닌 절반(50.3%)을 차지함
    • 1~2회 방문한 고객이 총 판매액에서 예상보다 더 비중 있음
  • 그래서 다음과 같이 전략을 정함
    • 1~2회 방문하는 고객군을 대상으로 하는 방향, 3회 이상 방문하는 고객군을 대상으로 하는 방향을 모두 고려해야 할 필요성을 발견

 

✔️ 고객 등급 산정 시 필요한 총 구매액 구간 정의하기

# 고객 등급별 이름
spending_grade = ['Bronze', 'Silver', 'Gold', 'VIP']

# 하위 50%, 상위 30%, 상위 16%, 상위 4%의 피라미드 구조로
# 해당 비율은 일반적으로 사용한다는 기준을 적용함
pd.qcut(sum_per_cusid[sum_per_cusid['visit_cnt']>2]['price3_KRW'], [0, 0.5, 0.8, 0.96, 1])

  • 2019년 총 구매액 기준 하위 50%, 상위 30%, 상위 16%, 상위 4%의 비율로 고객들을 나눠봄
    • (5849.339, 104027.36]
    • (104027.36, 176201.472]
    • (176201.472, 328662.54]
    • (328662.54, 1740875.0]
  • 위의 금액 구간을 참고하여 다음과 같은 기준을 정함
    • 10만 원 이하
    • 10만 원 초과, 18만 원 이하
    • 18만 원 초과, 35만 원 이하
    • 35만 원 초과
# 해당 금액대를 적용해 구간별 고객 비중을 파악해 봄
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)
plt.pie(sum_per_cusid.pivot_table(index=['spending_grade'], values=['customer_id'], aggfunc='nunique')['customer_id'], labels=spending_grade, autopct='%.1f')

 


 

(나 이거 하는 데 분명히 시간 많이 들었는데, 왜 모아보니까 별 내용 없어 보이지..)