ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [프로젝트] RFM 모형을 활용한 고객 세그먼테이션과 세그먼트별 마케팅
    데이터분석 2023. 4. 19. 11:42

    0. 배경

    많은 기업에서 신규 고객을 유입시키고 기존 고객을 유지하기 위해 CRM 마케팅 기법을 사용합니다. CRM 마케팅은 Customer Relationship Management의 약자로, 말 그대로 고객 관계 관리를 말합니다. 기존 고객과의 관계를 유지하고, 신규 고객의 원활한 정착을 돕기 위해 사용한다고 할 수 있습니다. 그렇기 때문에 모든 고객에게 똑같은 마케팅을 적용하면 안 되고, 고객별 특징에 따라 세그먼테이션을 진행하고, 세그먼트별로 마케팅하는 것이 중요합니다.

     

    1. 주제 선정 이유 및 데이터 소개

    위와 같은 이유로 CRM 마케팅을 경험해보고 싶었고, 데이터로 CRM의 대표적인 기법인 RFM 고객 세그먼테이션을 직접 해보며 세그먼트별 특징을 살펴 보면 좋은 경험이 될 것이라고 생각했습니다.

     

    분석에 사용한 데이터는 kaggle의 'Brazilian E-Commerce Public Dataset by Olist'를 사용했습니다. 브라질에서 실제로 운영 중인 Olist라는 이커머스 기업에서 제공한 실 데이터인데, 2016년에서 2018년 사이의 다양한 데이터를 담고 있습니다.

    사용 데이터의 데이터 스키마

    이 데이터는 위 이미지와 같이 orders 관련 데이터, customer 관련 데이터, product 관련 데이터 등 이커머스와 관련된 여러 데이터가 모두 포함된 관계형 데이터입니다. 아래 링크를 통해 데이터에 대한 설명과 각 컬럼에 대한 설명을 볼 수 있습니다.

    https://www.kaggle.com/datasets/olistbr/brazilian-ecommerce?select=olist_products_dataset.csv 

     

    Brazilian E-Commerce Public Dataset by Olist

    100,000 Orders with product, customer and reviews info

    www.kaggle.com

     

    2. 가설 설정

    분석에 들어가기 전에 설정한 가설은 다음과 같습니다.

    고객 세그먼트별 뚜렷한 특징이 있을 것이다.

    이 가설이 채택되게 된다면 세그먼트별 뚜렷한 특징이 있기 때문에 그 특징을 이용해서 세그먼트별로 다르게 타겟 마케팅을 하면 성과가 더 좋을 것을 의미하게 됩니다. 이 가설을 증명하기 위해 RFM 모형으로 고객을 분류하고 각 세그먼트별 특징을 살펴봤습니다.

     

    3. EDA

    먼저 사용할 라이브러리와 데이터를 불러왔습니다.

    # 라이브러리 불러오기
    import pandas as pd
    import numpy as np
    from datetime import datetime, timedelta, time
    import matplotlib.pyplot as plt
    from matplotlib import rc, font_manager
    import seaborn as sns
    import plotly.express as px
    import plotly.graph_objs as go
    from plotly.subplots import make_subplots
    import warnings
    warnings.filterwarnings("ignore")
    
    # 데이터 불러오기
    orders = pd.read_csv('data/olist_orders_dataset.csv')
    payments = pd.read_csv('data/olist_order_payments_dataset.csv')
    items = pd.read_csv('data/olist_order_items_dataset.csv')
    customers = pd.read_csv('data/olist_customers_dataset.csv')
    products = pd.read_csv('data/olist_products_dataset.csv')
    sellers = pd.read_csv('data/olist_sellers_dataset.csv')
    review = pd.read_csv('data/olist_order_reviews_dataset.csv')
    products_eng = pd.read_csv('data/product_category_name_translation.csv')

    다음으로 불러온 데이터들을 하나하나 간단히 탐색했습니다. products 테이블의 product_category_name 컬럼 값들이 포루투갈어로 들어가 있다는 것을 알 수 있었습니다.

    원활한 분석을 위해 영어로 바꾸는 과정이 필요했고, 데이터 중 products_eng 테이블에 포루투갈 카테고리명을 영어로 바꿔둔 데이터가 있다는 것을 발견했습니다.

    for문을 사용해 products 테이블의 product_category_name을 products_eng 테이블의 product_category_name_english로 변경했습니다.

    product = products['product_category_name'].tolist()
    name = products_eng['product_category_name'].tolist()
    eng_name = products_eng['product_category_name_english'].tolist()
    
    for i in product:
        if i in name:
            product[product.index(i)] = eng_name[name.index(i)]

    확인해본 결과 제대로 바뀐 것을 확인할 수 있었습니다.

    바뀐 데이터로 기존 데이터를 덮어쓴 뒤, 불러온 데이터들을 모두 하나의 데이터 프레임으로 만드는 작업을 진행했습니다. merge 함수로 공통된  컬럼을 기준으로 합쳐줬고, 사용한 코드는 다음과 같습니다.

    df = orders.merge(customers, on = 'customer_id', how = 'outer')
    df = df.merge(items, on = 'order_id', how = 'outer')
    df = df.merge(payments, on = 'order_id', how = 'outer')
    df = df.merge(review, on = 'order_id', how = 'outer')
    df = df.merge(products, on = 'product_id', how = 'outer')

    이렇게 합친 df의 정보를 확인해봤을 때 날짜와 관련된 데이터가 모두 object 타입으로 되어 있다는 것을 발견했습니다. 추후 연산이 쉽도록 데이터 타입을 모두 datetime으로 변환했습니다.

     

    orders 테이블에서 null 값을 확인했을 때 order_purchase_timestamp 값은 있지만 order_delivered_carrier_date나 order_delivered_customer_date는 비어 있는 경우가 있었습니다. 이런 경우, 구매는 했지만 중간에 취소하여 고객에게 도착하지 않거나 배송사에 도착하지 않은 것이라고 판단해 필요한 null 값이라고 생각했습니다. 또한 review 테이블에서도 평점에 null 값이 있었지만, 이 경우 0점으로 처리하는 것이 더 낫겠다고 판단해 null 값은 모두 0으로 채웠습니다.

     

    이렇게 필요한 null 값을 제외한 모든 null 값은 dropna로 삭제했고, 제대로 삭제되었다는 것을 확인할 수 있었습니다.

     

    4. 데이터 분석

    1. RFM 고객 세그먼테이션

    RFM 고객 세그먼테이션이란 무엇일까요?

    고객별로 얼마나 최근에(Recency), 얼마나 자주(Frequency), 얼마나 많은 금액(Monetary)을 지출했는지에 따라 고객의 가치를 분석하고 고객 등급을 나누는 분석 기법을 말합니다.

     

    앞서 만든 df 데이터에 RFM을 적용해 고객 세그먼테이션을 진행하기 위해 먼저 R, F, M에 해당하는 점수를 각각 부여했습니다.

     

    1. R (Recency)

    R은 '고객이 최근에 구매했는가?'를 의미합니다. 고객의 마지막 활동이 언제인지를 나타내는 변수로, 최근에 구매한 고객일수록 높은 점수가 부여됩니다.

     

    먼저 고객별로 가장 최근에 구매한 날짜가 언제인지를 구하기로 했습니다.

    recency = rfm.groupby('customer_unique_id')['order_purchase_timestamp'].max().reset_index()

    groupby를 사용해 고객의 고유한 ID값인 customer_unique_id를 기준으로 구매 timestamp의 최댓값을 묶어서 가장 최근에 구매한 날짜를 구했습니다.

     

    다음으로 전체 데이터 중 고객이 가장 최근에 구매한 날짜를 max_date라는 변수로 설정하고, 그 값에서 고객별 가장 최근 구매 날짜를 빼서 마지막 구매로부터 며칠이 지났는지를 구했습니다.

    max_date = recency['recency'].max()
    recency['diff_date'] = (max_date - recency['recency']).dt.days # dt.days : 시간 없이 날짜만 남기기

    이제 diff_date에 따라 점수를 부여하면 R 점수를 구할 수 있습니다. 구간을 나눌 때 사용할 수 있는 함수는 같은 길이로 구간을 나누는 pd.cut() 또는 같은 개수로 구간을 나누는 pd.qcut()이 있습니다. 저희는 pd.qcut() 함수로 diff_date를 5등분 해 가장 낮은 구간에 5점에서 가장 높은 구간에 1점까지 점수를 부여했습니다.

    2. F (Frequency)

    F는 '고객이 얼마나 자주 구매했는가?'를 나타냅니다.

    고객이 얼마나 자주 구매했는지를 나타내는 변수로, 자주 구매한 고객일수록 높은 점수가 부여됩니다.

     

    사용할 데이터에서 customer_unique_id와 order_purchase_timestamp만 뽑아봤습니다. 그 결과 같은 customer_unique_id에 똑같은 order_purchase_timestamp가 여러 개 있는 고객이 있다는 것을 확인할 수 있었습니다.

    저희는 이 현상을 한 번 구매할 때 하나의 order_purchase_timestamp가 생기는 게 아니라, 구매한 개수대로 order_purchase_timestamp가 수집된다고 해석했습니다.

     

    따라서 F를 구하기 위해서는 customer_unique_id를 기준으로 order_purchase_timestamp를 nunique() 함수로 집계해 구매 빈도수를 구했습니다.

    frequency = rfm.groupby('customer_unique_id')['order_purchase_timestamp'].nunique().reset_index()

    frequency 점수의 분포를 살펴봤을 때, 1회 또는 2회만 구매한 고객이 너무 많아서 pd.qcut() 함수가 적용이 불가능하다는 것을 알게 되었습니다. 그렇기 때문에 F 점수는 나머지와 다르게 1회 구매했으면 1점, 2회 구매했으면 2점, 3회 구매했으면 3점, 4회 구매했으면 4점, 그 이상 구매했으면 5점을 부여하는 함수를 만들어 적용했습니다.

    def parse_values(x):
        switch = {
            1: 1,
            2: 2,
            3: 3,
            4: 4
        }
        return switch.get(x, 5)
        
    frequency['f_score'] = frequency['frequency'].apply(parse_values)

    3. M (Monetary)

    M은 '고객이 얼마나 구매했나?'를 측정하는 지표입니다.

    고객이 구매한 총 금액을 의미하는 변수로, 구매 금액이 높은 고객일수록 높은 점수가 부여됩니다.

     

    customer_unique_id별로 지금까지 구매한 금액의 총합을 묶어 고객별로 얼마나 구매했는지 구했습니다.

    monetary = rfm.groupby('customer_unique_id')['payment_value'].sum().reset_index()

    R 점수를 부여했던 것과 마찬가지로 M 점수 또한 qcut 함수를 사용했습니다.

    m_bins = pd.qcut(monetary['monetary'], 5, labels = [1, 2, 3, 4, 5])
    monetary['m_score'] = m_bins

    4. RFM 적용

    다음으로 앞서 구한 R, F, M 점수를 합치는 작업을 진행했습니다.

    각각 만들었던 recency, frequency, monetary 데이터프레임을 하나로 합친 뒤 r_score, f_score, m_score 값들을 모두 string 형태로 변환했습니다. 그 다음 rfm_score이라는 컬럼을 만들어 R, F, M 점수를 하나로 이은 RFM 점수를 고객별로 부여했습니다.

    # recency, frequency, monetary 데이터프레임 하나로 합치기
    rfm_score = recency.merge(frequency, on = 'customer_unique_id')
    rfm_score = rfm_score.merge(monetary, on = 'customer_unique_id')
    
    # rfm_score을 합치기 위해 string으로 형 변환
    rfm_score['r_score'] = rfm_score['r_score'].astype(str)
    rfm_score['f_score'] = rfm_score['f_score'].astype(str)
    rfm_score['m_score'] = rfm_score['m_score'].astype(str)
    
    # rfm_score 합치기
    rfm_score['rfm_score'] = rfm_score['r_score'] + rfm_score['f_score'] + rfm_score['m_score']

    이렇게 구한 RFM 점수를 바탕으로 고객 세그먼트를 나누면 되는데, 저희는 아래 링크를 참고해 고객 세그먼테이션을 진행했습니다.

    https://documentation.bloomreach.com/engagement/docs/rfm-segmentation

     

    RFM Segmentation

    RFM segmentation categorizes your customers into different segments, according to their interactions with your website, which will allow you to subsequently approach these groups in the most effective way. In this article, we will show you how to make an R

    documentation.bloomreach.com

    segments = {
        r'111|112|121|131|141|151' : '이탈 고객',
        r'332|322|233|232|223|222|132|123|122|212|211' : '동면 고객', 
        r'155|154|144|214|215|115|114|113' : '놓치면 안 될 고객',
        r'255|254|245|244|253|252|243|242|235|234|225|224|153|152|145|143|142|135|134|133|125|124' : '이탈 우려 고객',
        r'331|321|312|221|213|231|241|251' : '휴면 예정 고객',
        r'535|534|443|434|343|334|325|324' : '관심 필요 고객',
        r'525|524|523|515|514|513|425|424|413|414|415|315|314|313' : '잠재 고객',
        r'522|521|512|511|422|421|412|411|311' : '신규 고객',
        r'553|551|552|541|542|533|532|531|452|451|442|441|431|453|433|432|423|353|352|351|342|341|333|323' : '잠재 충성 고객',
        r'543|444|435|355|354|345|344|335' : '충성 고객',
        r'555|554|544|545|454|455|445' : 'VIP 고객'
    }
    
    rfm_score['segment'] = rfm_score['rfm_score'].replace(segments, regex=True)

     

    이렇게 RFM을 적용한 데이터프레임에 기존에 합쳐뒀던 데이터를 merge해 최종 데이터 프레임을 만들었습니다.

     

    5. 고객 등급별 특징 분석

    원활한 특징 분석을 위해 11개로 나눈 고객 세그먼트를 총 4개의 그룹으로 등급을 부여하여 특징 분석을 진행했습니다.

    VIP 고객, 충성 고객, 잠재 충성 고객은 다이아몬드 등급으로, 신규 고객, 잠재 고객, 관심 고객은 골드 등급으로, 휴면 예정 고객, 이탈 우려 고객, 놓치면 안 될 고객은 실버 등급으로, 동면 고객, 이탈 고객은 브론즈 등급으로 나눴습니다.

     

    사용할 데이터프레임 data에 고객등급 column을 추가해 등급을 추가해줬습니다.

    conditions = [
        data["segment"] == "VIP 고객",
        data["segment"] == "충성 고객",
        data["segment"] == "잠재 충성 고객",
        data["segment"] == "신규 고객",
        data["segment"] == "잠재 고객",
        data["segment"] == "관심 필요 고객",
        data["segment"] == "휴면 예정 고객",
        data["segment"] == "이탈 우려 고객",
        data["segment"] == "놓치면 안 될 고객",
        data["segment"] == "동면 고객",
        data["segment"] == "이탈 고객",
    ]
    values = ["다이아몬드", "다이아몬드", "다이아몬드", "골드", "골드", "골드", "실버", "실버", "실버", "브론즈", "브론즈"]
    
    data["고객등급"] = np.select(conditions, values, default="")

     

    고객 등급별 특징을 분석하기 위해 plotly 라이브러리를 사용해 시각화를 진행했습니다. plotly는 matplotlib이나 seaborn 라이브러리보다 더 세련되고 다양한 기능을 지원하는 시각화 라이브러리로, interactive하다는 것이 가장 큰 특징입니다. 이 특징을 살려 등급별 특징을 상세하게 보고 싶어서 plotly를 사용하게 되었습니다.

     

    1. 고객 세그먼트별 고객 수

    먼저 고객 세그먼트별로 분포가 어떻게 되어 있는지 보고자 barplot을 그려봤습니다. 잠재 고객과 신규 고객이 가장 많았고, 충성 고객과 VIP 고객이 가장 적었습니다. 이 분포대로라면 골드 등급의 고객 수가 가장 많고, 다이아몬드 등급의 고객 수가 가장 적다는 것을 나타내겠죠? 한번 살펴보겠습니다.

    2. 고객 등급별 고객 수

    이렇게 등급별 고객 수도 시각화를 해봤는데, 예상한 대로 골드가 가장 많고, 다이아몬드가 가장 적다는 것을 볼 수 있습니다. 위 그래프를 보면 다이아몬드 등급이 너무 적어서 그래프가 잘 나오지 않는데, plotly의 확대 기능을 사용해보겠습니다.

    확대하고 싶은 부분을 드래그로 손쉽게 확대할 수 있고, 각 그래프 위에 마우스오버를 하면 세부 정보까지 확인할 수 있습니다. 다시 전체 화면을 볼 때는 빈 곳을 더블클릭하면 전체 그래프를 볼 수 있습니다.

     

    이렇게 고객 등급별 고객 수를 살펴봤을 때 알 수 있던 것은 RFM 점수로 고객 등급을 나눴을 때는 다이아몬드 등급이 우리의 핵심 고객이 되어야 하지만, 고객 수로 보면 골드 등급이 월등히 많기 때문에 골드 등급이 우리의 메인 타겟이 되어야 한다는 것을 알 수 있었습니다.

     

    3. 고객 등급별 고객 거주지 Top 10

    고객 등급별 고객 거주지가 어떤 차이가 있을지 보기 위해 각 도시별 고객 수를 계산한 뒤 상위 10개의 도시를 뽑았습니다.

    subplot으로 각 등급별로 거주지 Top 10을 시각화해봤는데, 모든 등급에서 sao paulo, rio de janeiro, belo horizonte가 공통적으로 Top 3에 위치한다는 것을 볼 수 있습니다. 그 외 지역들도 많이 겹친다는 것을 볼 수 있기 때문에 고객 등급별로 거주 지역에 대해서는 뚜렷한 특징이 없다는 것을 발견했습니다.

    4. 고객 등급별 많이 구매한 카테고리 Top 5

    그렇다면 등급별로 가장 많이 구매한 카테고리에는 차이가 있을지 살펴보고자 했습니다. 카테고리가 굉장히 많기 때문에 상위 5개만 시각화를 해봤습니다.

    이 그래프를 통해 알 수 있던 것은 다이아몬드 등급은 다른 등급과 달리 watches_gifts를 많이 구매 했다는 것을 알 수 있었습니다. 골드 등급과 실버 등급은 Top 5 카테고리는 동일했지만 그 양의 차이가 달랐습니다. 마지막으로 브론즈 등급은 월등히 많이 구매한 카테고리는 없었고, 단가가 높지 않은 제품들을 주로 구매했음을 볼 수 있었습니다.

    5. 고객 등급별 review score

    다음으로 고객 등급별로 리뷰 평점의 평균이 어떻게 되는지 살펴봤습니다.

    흔히 생각했을 때 다이아몬드 등급 고객의 평점이 가장 높고, 브론즈 등급 고객의 평점이 가장 낮을 거라고 생각할 수 있지만 실제로 평균 평점을 살펴봤을 땐 그렇지 않은 결과가 나왔습니다. 이를 통해 브론즈 등급의 고객들이 이탈하는 이유가 만족도는 아니라는 것을 알 수 있었습니다. 또한 골드 등급과 실버 등급의 고객 수가 전체 고객 수의 약 80%를 차지하기 때문에 골드 등급과 실버 등급의 평균 평점이 비교적 낮은 결과가 나온 것으로 해석했습니다. 혹은 평점을 높게 남기면 쿠폰을 제공하는 등 어떤 이벤트로 인해 고객들이 평점을 솔직하게 남기지 않아 평점이 신뢰도가 높지 않다고 해석할 수도 있다고 생각했습니다.

    6. 고객 등급별 결제 후 배송받지 못한 주문건 비율 (주문했다가 취소한 비율)

    마지막으로 고객 등급별로 결제는 했지만 배송 날짜 데이터가 null 값인, 즉 주문했다가 취소한 비율이 얼마나 되는 지 살펴봤습니다.

    이 특징이 가장 등급별로 뚜렷하게 나왔다고 생각하는데, 다이아몬드 등급에서는 취소한 비율이 가장 낮고, 등급이 낮을수록 취소 비율이 높다는 것을 확인할 수 있었습니다.

     

    6. 결론

    이렇게 고객 등급별 특징을 살펴봤는데, 등급별로 나타나는 뚜렷한 차이는 많이 없다는 것을 알 수 있었기 때문에 처음 세웠던 가설인 '고객 세그먼트별 뚜렷한 특징이 있을 것이다'는 기각되었습니다. 하지만 최근 구매 여부, 구매 빈도, 구매 금액에 따라 세그먼트를 나눴기 때문에 RFM 정도에서의 차이가 있고, 고객 등급이 높을수록 결제 후 취소하는 비율이 적다는 유의미한 결과를 얻었습니다. 또한 각 등급별 선호 카테고리에 맞춘 프로모션을 진행하거나 상품을 추천하는 것이 유용할 것이라는 인사이트를 얻었습니다. 따라서 고객 세그먼트별로 다른 마케팅 방안을 적용하는 게 좋겠다는 결론을 내릴 수 있었습니다.

     

    다음 이미지는 고객 세그먼트별 어떤 특징이 있고, 어떤 마케팅 방안을 적용하는 게 좋을지 정리해본 표입니다.

    VIP 고객충성 고객은 이미 서비스를 활발히 이용하고 있고 기업에 크게 기여하고 있는 고객이기 때문에 멤버십 제도를 통해 프리미엄 서비스를 제공한다면 지속적인 서비스 이용을 할 것으로 추측했습니다.


    잠재 충성 고객의 경우에는 지속적으로 서비스에 접근하고 있고, 구매 횟수도 많기 때문에 서비스에 관심은 이미 충분하다고 생각했습니다. 하지만, 현재의 소비 패턴을 유지할 것으로 판단되어 크게 매출 상승을 기대하기는 어렵다고 보여서 해당 세그먼트의 고객에게는 따로 마케팅을 하지 않는 디마케팅 전략을 생각했습니다. 


    신규 고객과 잠재 고객에게는 추가 구매를 유도할 수 있는 크로스셀링 전략을 중심으로 쿠폰과 푸쉬 메세지로 지속적인 관심을 유도할 수 있는 전략이 효과적이라고 생각했습니다.


    이탈 우려 고객과 놓치면 안 될 고객의 경우 최근 방문이 뜸한 고객들이기 때문에 재로그인을 유도할 수 있도록 재로그인시 컴백 쿠폰이 제공됨을 알리는 방안이 필요하다고 생각했습니다. 


    마지막으로 동면 고객과 이탈 고객은 이미 서비스를 이용하지 않고 있거나 앞으로 이탈할 확률이 높은 고객이기 때문에 이 세그먼트 고객들에게는 특별한 마케팅을 진행하기보단 이벤트 발생 시 앱 푸쉬 메세지를 보내는 방법으로 충분하다고 생각했습니다.

     

    7. 한계점

    데이터를 분석하고 특징을 파악해 마케팅 방안을 도출해내는 과정에서 여러 한계점들이 있었습니다.

     

    첫째, 국내 데이터가 아니고 브라질의 한 이커머스 기업의 데이터이기 때문에 현지 상황을 정확하게 알 수 없어 막연한 마케팅 방안 도출 이상으로 나아가지 못했다는 점입니다. 데이터만으로 알 수 없는 배경 지식에 대한 정보가 없었다는 점이 아쉬움으로 남았습니다.

     

    둘째, RFM 모형을 적용할 때 최근성, 빈도, 구매 금액에 가중치를 동일하게 적용해 정확도가 떨어질 수 있다는 점입니다. 각 기준에 가중치를 다르게 적용했다면 더 정확한 세그먼테이션이 되었을 것 같지만 시간 관계 상 하지 못해 아쉬움이 있었습니다.

     

    셋째, R/F/M 각각의 점수를 1~5점으로 나눴는데, 세그먼트로 나눌 때 수동으로 나눴기 때문에 명확하게 나누기 힘들었던 점수들이 있었습니다. 수동으로 세그먼테이션을 진행했을 때의 한계점을 느낄 수 있었습니다.


    약 일주일 간 진행한 프로젝트는 여기까지 입니다.

    학생들끼리 진행했기 때문에 여러 오류나 잘못된 해석이 있을 수 있습니다.

    댓글로 알려주시면 많은 공부가 될 것 같습니다.

    감사합니다 😊

    댓글

Designed by Tistory.