본문 바로가기
프로덕트 분석

[Pandas] RFM Segmentation 분석(feat. K-Means Clustering)

by ISLA! 2024. 1. 27.

코호트 분석과 동일한 데이터로 이어서 RFM 세그먼트를 분석해본다.

(RFM에 대한 개념과 예시는 별도 포스팅으로 게시할 예정입니다)

RFM을 구하기 위한 전처리

  • 먼저 총 구매금액 컬럼부터 생성해준다. 
  • 상품 개당 가격과 판매량을 곱해준다.
# 총 구매금액 컬럼 생성
df['TotalSum'] = df['UnitPrice'] * df['Quantity']

 

  • 송장번호 최솟값과 최댓값(가장 오래전 주문 건과 최근 주문 건의 날짜)을 출력해본다.
  • 대략적인 시간 범위를 파악한다.
print('Min Invoice Date: ', df.InvoiceDate.dt.date.min(), 'max Invoice Date: ', df.InvoiceDate.dt.date.max(), '\n')
df.head(3)

 

  • 보통 당일 또는 어제의 가장 최근 데이터를 기준으로 RFM을 측정한다.
  • 따라서 이 예제에서는 InvoiceDate 열에서 가장 최대 날짜를 찾고, 그 날짜에 하루 더하는 연산을 수행한다.
snapshot_date = df['InvoiceDate'].max() + dt.timedelta(days=1)
snapshot_date

 

 

고객별로 RFM을 구하기 

  • rfm은 고객을 분류하는 기준이므로 CustomerID를 기준으로 Groupby한다. 
  • 이때 집계함수는 컬럼마다 각각 다르게 적용한다.(아래와 같다)
    • 얼마나 최근에 구매했는지 확인하기 위해 snapshot_date 와 가장 최근 구매일(송장일 최댓값)을 빼주고, days로 일 수를 센다.
    • 얼마나 자주 구매했는지 확인하기 위해 InvoiceNo의 개수를 count
    • 얼마나 많이 구매했는지 확인하기 위해 Totalsum의 합을 구한다
rfm = df.groupby(['CustomerID']).agg({'InvoiceDate': lambda x : (snapshot_date - x.max()).days,
                               'InvoiceNo':'count',
                               'TotalSum':'sum'})

rfm.rename(columns={'InvoiceDate':'Recency', 'InvoiceNo':'Frequency', 'TotalSum':'MonetaryValue'}, inplace=True)

 

RFM Segment 

  • R, F, M 값들을 구했으면 이제 구간을 나눠야한다. qcut을 사용하고, 구간개수는 4로 한다.
  • label은 미리 지정해주는데, 이때 recency만 작을수록 중요고객이므로 유의한다.
# RFM Segment 
r_label = range(4, 0, -1) #작을수록 중요고객
f_label = range(1, 5) #클수록 중요고객
m_label = range(1, 5) #클수록 중요고객

r_quantiles = pd.qcut(rfm['Recency'], q=4, labels = r_label)
f_quantiles = pd.qcut(rfm['Frequency'], q=4, labels = f_label)
m_quantiles = pd.qcut(rfm['MonetaryValue'], q=4, labels = m_label)

 

  • 이후 rfm 테이블에 위에서 구한 값들을 추가로 지정해준다.
rfm = rfm.assign(R = r_quantiles, F = f_quantiles, M = m_quantiles)
rfm.head()

 

RFM Score 구하기

  • RFM Score는 위에서 각각 구한 R, F, M 값을 나란히 붙여서 만든다. 해당 함수를 작성한다.
  • 함수를 적용하여 RFM_Segment 컬럼을 생성한다.
  • 그리고 R, F, M 값을 모두 더하여 RFM Score를 구한다.
# RFM Score
def add_rfm(x):
    return str(x['R']) + str(x['F']) + str(x['M'])
    
rfm['RFM_Segment'] = rfm.apply(add_rfm, axis=1)
rfm['RFM_Score'] = rfm[['R', 'F', 'M']].sum(axis=1)
rfm.head()


RFM Segment 살펴보기

  • 바로 비즈니스 액션에 적용하거나 타겟 마케팅 등을 실행하기 전에 각 segment의 사이즈부터 확인해보는 것이 좋다.
rfm.groupby(['RFM_Segment']).size().sort_values(ascending = False)[:5]

  • 특정 segment도 따로 필터링해서 살펴본다.
# segment 필터링
rfm[rfm['RFM_Segment']=='1.01.01.0'].head()

 

  • RFM Score 별로 요약 통계도 살펴본다.
# RFM Score 별로 요약통계
rfm.groupby(['RFM_Score']).agg({'Recency':'mean', 'Frequency':'mean', 'MonetaryValue':['mean', 'count']}).round(1)

 

 

  • 이제 RFM Score 별로 고객 군을 나누어 이름을 붙여준다.
  • 데이터프레임을 매개변수로 넣었을 때, 점수에 따라 골드/실버/브론즈 등급을 매기는 함수를 작성한다.
def segments(df):
    if df['RFM_Score'] > 9 :
        return 'Gold'
    elif (df['RFM_Score'] > 5) & (df['RFM_Score'] <= 9):
        return 'Silver'
    else:
        return 'Bronze'

# 함수 적용
rfm['General_Segment'] = rfm.apply(segments, axis=1)

# 골드/실버/브론즈 별로 확인
rfm.groupby(['General_Segment']).agg({'Recency':'mean', 'Frequency':'mean', 'MonetaryValue':['mean', 'count']}).round(1)


🎖️부록. K-Means Clustering으로 고객 군집화하기

참고로, K-Means Clustering을 활용해 고객 군집을 생성할 수도 있다.

K-Means Clustering으로 군집화 시 다음 순서로 진행한다.

  1. 데이터 전처리 : 아래 예시 참고
  2. 군집의 개수 선택 : 엘보우(시각적 방법), 실루엣 계수(수학적 방법)
  3. 전처리된 데이터에서 k-평균 군집화 실행
  4. 각 군집의 평균 RFM 값 분석

 

▶︎ 데이터 전처리

  • k-means의 성능과 정확도를 높이기 위해 다음과 같은 주요 가정을 확인해야한다.
    • 변수의 대칭적 분포(왜도 없음) : 데이터가 한쪽으로 치우치지 않아야 함 👉 심하면 로그변환
    • 변수들의 평균값이 유사해야함 : 군집화 알고리즘이 각 변수의 중요성을 동등하게 고려해야 하므로 👉 표준화
    • 변수들의 분산이 유사해야함 : 분산이 크게 다르면, 군집화 알고리즘이 분산이 큰 변수에 민감하게 반응하므로 👉 스케일링
  • 따라서 describe()로 평균과 분산을 확인하고
  • distplot을 그려 왜도도 확인해본다.
rfm_rfm = rfm[['Recency','Frequency','MonetaryValue']]
rfm_rfm.describe()

f, ax = plt.subplots(figsize = (10, 12))
plt.subplot(3, 1, 1);sns.distplot(rfm.Recency, label = 'Recency')
plt.subplot(3, 1, 2);sns.distplot(rfm.Frequency, label = 'Frequency')
plt.subplot(3, 1, 3);sns.distplot(rfm.MonetaryValue, label = 'MonetaryValue')

plt.tight_layout()
plt.show(

 

 

  • 이렇게 왜도가 심하면 로그변환을 취해줘야한다.
# 로그 변환으로 왜도 낮추기
rfm_log = rfm[['Recency','Frequency','MonetaryValue']].apply(np.log, axis=1).round(3)

# 로그변환된 RFM값 분포 확인
f, ax = plt.subplots(figsize=(10, 12))
plt.subplot(3, 1, 1);sns.distplot(rfm_log.Recency, label='Recency')
plt.subplot(3, 1, 2);sns.distplot(rfm_log.Frequency, label = 'Frequency')
plt.subplot(3, 1, 3);sns.distplot(rfm_log.MonetaryValue, label = 'MonetaryValue')

plt.style.use('fivethirtyeight')
plt.tight_layout()
plt.show()

728x90