| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- 파이썬시각화
- 코드잇스프린트
- 태블로
- 데이터전처리
- 로그설계
- 데이터분석공부
- 퍼널분석
- 스프린트미션
- 부트캠프
- 데이터분석가공부
- seaborn
- 탐색적데이터분석
- 데이터분석가
- 파이썬
- 지표
- 결측값
- 파이썬라이브러리
- 프로덕트데이터
- amplitude
- 데이터분석프로젝트
- aarrr
- 데이터분석가부트캠프
- SQL
- 지표설계
- 로그
- 코드잇
- 프로덕트분석
- Tableau
- retention
- 데이터분석
- Today
- Total
StuDyata.zip
비즈니스 분석 프레임워크 | 퍼널 분석, 코호트 분석, RFM 분석 실습까지 전체 정리 본문
이 글은 코드잇 스프린트 데이터 분석가 과정 학습 기록입니다.
수업 내용과 느낀 점을 매일 정리하며 데이터 분석 공부 과정을 기록하고 있습니다.
🧭 비즈니스 분석 프레임워크 정리
이번 이론 데이에서는 비즈니스 분석 프레임워크와 데이터 기반 프로덕트 개선 프로세스를 배웠다. 데이터 분석을 할 때는 단순히 데이터를 불러오고 그래프를 그리는 것에서 끝나는 것이 아니라, 현재 비즈니스 상황에 맞는 분석 방법을 선택하고, 그 분석 결과를 실제 서비스 개선 방향으로 연결하는 것이 중요하다. 이번 시간에는 데이터 분석 및 인사이트 도출 단계에서 자주 사용되는 대표적인 분석 프레임워크인 퍼널 분석, 코호트 분석, RFM 분석을 중심으로 학습하였다.
세 가지 분석은 모두 고객 행동을 이해하기 위한 방법이지만, 각각 바라보는 관점이 다르다. 퍼널 분석은 사용자가 목표 행동에 도달하기까지의 단계별 흐름을 분석한다. 즉, 사용자가 어느 단계에서 많이 이탈하는지를 확인하는 데 적합하다. 코호트 분석은 특정 기준으로 묶인 사용자 집단이 시간이 지나면서 어떻게 행동하는지를 분석한다. 즉, 시간이 흐른 뒤에도 사용자가 계속 남아 있는지, 이탈하는지를 확인하는 데 적합하다. RFM 분석은 고객의 최근 구매 시점, 구매 빈도, 구매 금액을 기준으로 고객 가치를 평가하고 세분화하는 분석이다. 즉, 어떤 고객이 더 가치 있는 고객인지 구분하고, 고객군별 마케팅 전략을 세우는 데 적합하다. 이번 글에서는 이 세 가지 분석 방법을 각각 정리하고, 실습 코드 흐름까지 함께 기록해보려고 한다.
🛒 퍼널 분석(Funnel Analysis)
📍퍼널 분석이란?
퍼널 분석이란 사용자가 특정 목표를 달성하기까지의 경로를 단계별로 나누어 분석하는 방법이다. 서비스 안에서 사용자는 보통 하나의 행동만 하고 끝나는 것이 아니라, 여러 단계를 거쳐 최종 목표 행동에 도달한다. 예를 들어 커머스 서비스에서 구매까지의 흐름을 생각해보면 다음과 같다.
상품 조회 → 장바구니 담기 → 구매
이때 모든 사용자가 상품을 조회한 뒤 장바구니에 담고, 다시 구매까지 이어지는 것은 아니다. 어떤 사용자는 상품만 보고 나가고, 어떤 사용자는 장바구니까지 담았지만 구매하지 않고 이탈할 수 있다.
퍼널 분석은 바로 이 지점을 보기 위한 분석이다. 즉, 사용자가 목표 행동까지 가는 과정에서 어느 단계에서 가장 많이 줄어드는지, 어느 단계의 전환율이 낮은지, 어느 단계의 개선이 최종 전환율 향상에 가장 큰 영향을 줄 수 있는지를 확인한다. 퍼널 분석의 목표는 최종 전환율을 높이는 것이다. 이를 위해서는 크게 두 가지 방향을 생각할 수 있다.
첫 번째는 단계별 전환율을 높이는 것이다. 예를 들어 상품 조회에서 장바구니 담기로 넘어가는 비율이 낮다면 상품 상세 페이지의 정보, 가격, 배송 혜택, 리뷰, 할인 메시지 등을 개선할 수 있다.
두 번째는 퍼널 단계 자체를 줄이는 것이다. 사용자가 구매까지 가는 과정이 너무 길거나 복잡하다면, 불필요한 단계를 줄여 이탈 가능성을 낮출 수 있다.
아래 실습에서는 화장품 커머스 데이터를 사용하여 사용자의 구매 흐름을 다음과 같은 퍼널로 정의하였다.
view → cart → purchase
각 이벤트의 의미는 다음과 같다.
- view: 사용자가 상품을 조회한 단계
- cart: 사용자가 상품을 장바구니에 담은 단계
- purchase: 사용자가 상품을 구매한 단계
이번 퍼널 분석의 핵심 목표는 고객의 구매 행동을 촉진하기 위해 어느 단계에서 이탈이 많이 발생하는지 확인하는 것이다.
🚶♀️ 퍼널 분석 실습
📍분석에 사용할 라이브러리 불러오기
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
📍데이터 불러오기
url = "https://bakey-api.codeit.kr/api/files/resource?root=static&seqId=10713&version=1&directory=/cosmetic.csv&name=cosmetic.csv"
cosmetic_df = pd.read_csv(url)
이 코드는 온라인에 저장된 cosmetic.csv 데이터를 불러오는 코드이다. 첫 번째 줄에서는 CSV 파일 주소를 url 변수에 저장한다. 두 번째 줄에서는 pd.read_csv(url)을 사용해서 해당 주소의 CSV 파일을 읽어오고, 이를 cosmetic_df라는 데이터프레임에 저장한다.
cosmetic_df.head()
데이터의 앞부분 5행을 확인하는 코드이다. 데이터를 처음 불러온 뒤에는 컬럼이 어떻게 구성되어 있는지, 값이 어떤 식으로 들어가 있는지 먼저 확인해야 한다.
cosmetic_df.info()
데이터프레임의 전체적인 정보를 확인하는 코드이다. 전체 행 개수, 컬럼 개수, 컬럼명, 결측치가 아닌 값의 개수, 데이터 타입 등을 확인할 수 있다. 퍼널 분석에서는 사용자의 행동 순서가 중요하기 때문에 event_time 컬럼이 날짜/시간 타입인지 확인하는 것이 중요하다.
cosmetic_df['event_time'] = pd.to_datetime(cosmetic_df['event_time'])
event_time 컬럼을 날짜/시간 타입으로 변환하는 코드이다. 날짜 데이터가 문자열 형태로 되어 있으면 시간 순서 정렬이나 날짜 계산을 정확하게 하기 어렵기 때문에 pd.to_datetime()을 사용해 datetime 타입으로 변환한다.
cosmetic_df['event_time'].min(), cosmetic_df['event_time'].max()
데이터에서 가장 이른 이벤트 시간과 가장 늦은 이벤트 시간을 확인하는 코드이다. 이를 통해 데이터가 어느 기간 동안 수집되었는지 확인할 수 있다.
cosmetic_df['event_type'].unique()
event_type 컬럼에 어떤 이벤트 종류가 있는지 확인하는 코드이다. 퍼널 분석에서는 행동 단계를 정의해야 하므로, 어떤 이벤트가 존재하는지 먼저 확인해야 한다.
cosmetic_df['event_type'].value_counts()
이벤트 종류별로 데이터가 몇 개씩 있는지 확인하는 코드이다. 데이터에는 대략 다음과 같은 이벤트가 존재한다.
view
cart
remove_from_cart
purchase
분석 흐름상 이벤트는 다음과 같이 볼 수 있다.
view → cart → remove_from_cart → purchase
하지만 이번 퍼널 분석에서는 핵심 구매 흐름을 보기 위해 다음 단계만 사용한다.
view → cart → purchase
remove_from_cart는 장바구니에서 상품을 제거하는 행동이므로, 기본 구매 전환 퍼널에서는 제외한다.
📍퍼널 분석용 데이터 준비
퍼널 분석에서는 행동의 순서가 중요하다. 따라서 먼저 데이터를 사용자, 상품, 시간 순서대로 정렬한다.
cosmetic_df = cosmetic_df.sort_values(by=['user_id', 'product_id', 'event_time'])
정렬 기준은 다음과 같다.
- user_id
- product_id
- event_time
즉, 같은 사용자가 같은 상품에 대해 어떤 행동을 어떤 시간 순서로 했는지 확인할 수 있도록 정렬하는 것이다. 예를 들어 같은 사용자가 같은 상품에 대해 다음과 같은 순서로 행동했다면 자연스러운 구매 흐름으로 볼 수 있다.
view → cart → purchase
반대로 어떤 데이터가 다음처럼 시작한다면, 이번 분석의 기준인 view → cart → purchase 흐름으로 보기 어렵다.
cart → purchase
따라서 사용자-상품 조합별로 첫 이벤트가 무엇인지 확인하고, 첫 이벤트가 view인 경우만 남긴다.
temp = cosmetic_df.groupby(['user_id', 'product_id']).first()
같은 사용자와 같은 상품 조합별로 가장 첫 번째 이벤트를 가져오는 코드이다. groupby(['user_id', 'product_id'])는 한 사용자가 특정 상품에 대해 남긴 행동 기록 단위로 데이터를 묶는다. 앞에서 이미 event_time 기준으로 정렬했기 때문에 first()를 사용하면 해당 사용자-상품 조합에서 가장 먼저 발생한 이벤트를 가져올 수 있다.
target_index = temp.query("event_type == 'view'").index
사용자-상품 조합 중에서 첫 이벤트가 view인 경우만 찾는 코드이다.
이번 퍼널은 view → cart → purchase 흐름을 기준으로 하기 때문에, 첫 행동이 상품 조회인 경우만 분석 대상으로 남긴다.
cosmetic_df = cosmetic_df.set_index(['user_id', 'product_id']).loc[target_index].reset_index()
전체 데이터에서 첫 이벤트가 view인 사용자-상품 조합만 남기는 코드이다. 조금 나누어 보면 다음과 같다. set_index(['user_id', 'product_id'])는 기존 데이터프레임의 인덱스를 사용자 ID와 상품 ID로 변경한다. .loc[target_index]는 앞에서 구한 첫 이벤트가 view인 사용자-상품 조합만 선택한다. .reset_index()는 인덱스로 설정했던 user_id, product_id를 다시 일반 컬럼으로 되돌린다. 이 과정을 통해 상품 조회에서 시작한 행동 흐름만 남기고, 더 자연스러운 퍼널 분석을 진행할 수 있다.
📍단계별 사용자 수 집계
이제 본격적으로 퍼널 단계별 사용자 수를 계산한다.
user_counts = cosmetic_df.groupby(['event_type', 'product_id'])['user_id'].nunique()
이벤트 타입과 상품별로 고유 사용자 수를 계산하는 코드이다. groupby(['event_type', 'product_id'])는 이벤트 타입과 상품 ID를 기준으로 데이터를 그룹화한다. 예를 들면 다음과 같은 단위로 묶인다.
view 이벤트 - 상품 A
cart 이벤트 - 상품 A
purchase 이벤트 - 상품 A
view 이벤트 - 상품 B
cart 이벤트 - 상품 B
purchase 이벤트 - 상품 B
그중에서 user_id 컬럼을 선택하고, nunique()를 사용해 중복을 제외한 고유 사용자 수를 계산한다. 같은 사용자가 같은 상품을 여러 번 조회했더라도 1명으로 계산하기 때문에, 단순 이벤트 횟수가 아니라 각 단계에 도달한 실제 사용자 수를 볼 수 있다.
user_counts = user_counts.groupby('event_type').sum().reset_index()
앞에서 상품별로 나뉘어 있던 사용자 수를 이벤트 타입별로 다시 합산하는 코드이다.
즉, view, cart, purchase 단계별 전체 사용자 수를 확인할 수 있게 된다.
user_counts = user_counts.rename(columns={'user_id': 'users'})
앞에서 user_id 컬럼을 기준으로 고유 사용자 수를 계산했기 때문에, 결과 컬럼에는 사용자 수가 들어 있다. 컬럼명이 그대로 user_id이면 의미가 헷갈릴 수 있으므로 users로 변경한다.
user_counts = user_counts.query("event_type != 'remove_from_cart'")
이번 퍼널 분석에서는 핵심 흐름이 view → cart → purchase이므로, 장바구니에서 상품을 제거하는 행동인 remove_from_cart는 제외한다.
user_counts = user_counts.sort_values(by='users', ascending=False)
퍼널에서는 일반적으로 첫 단계에서 사용자 수가 가장 많고 이후 단계로 갈수록 사용자 수가 줄어든다. 따라서 사용자 수가 많은 순서대로 정렬하면 보통 view → cart → purchase 순서가 된다.
user_counts
최종적으로 퍼널 단계별 사용자 수가 어떻게 집계되었는지 확인한다.
📍퍼널 차트 시각화
px.funnel(user_counts, x='users', y='event_type')
Plotly를 사용해서 퍼널 차트를 그리는 코드이다. px.funnel()은 퍼널 차트를 만드는 함수이다.
- user_counts: 시각화에 사용할 데이터프레임
- x='users': x축에는 각 단계별 사용자 수를 넣는다.
- y='event_type': y축에는 이벤트 타입을 넣는다.
퍼널 차트를 보면 view, cart, purchase 단계로 갈수록 사용자 수가 얼마나 줄어드는지 직관적으로 확인할 수 있다.
📍이전 단계 대비 전환율과 이탈률 계산
퍼널 분석에서는 단순히 단계별 사용자 수만 보는 것이 아니라, 이전 단계에서 다음 단계로 얼마나 넘어갔는지를 확인해야 한다.
user_counts['conversion_rate(%)'] = user_counts['users'] / user_counts['users'].shift(1) * 100
이전 단계 대비 현재 단계의 전환율을 계산하는 코드이다. user_counts['users']는 현재 단계의 사용자 수이다. user_counts['users'].shift(1)은 이전 행의 사용자 수를 가져온다. 예를 들어 데이터가 다음과 같다고 하면,
view 1000
cart 125
purchase 46
shift(1)을 적용하면 다음과 같이 한 칸씩 아래로 밀린다.
view NaN
cart 1000
purchase 125
따라서 cart 단계에서는 cart 사용자 수 / view 사용자 수를 계산할 수 있고, purchase 단계에서는 purchase 사용자 수 / cart 사용자 수를 계산할 수 있다. 즉, 이 코드는 다음 두 전환율을 계산한다.
view → cart 전환율
cart → purchase 전환율
첫 번째 단계인 view는 이전 단계가 없기 때문에 전환율이 NaN으로 나온다.
user_counts['churn_rate(%)'] = 100 - user_counts['conversion_rate(%)']
단계별 이탈률을 계산하는 코드이다. 전환율은 이전 단계에서 다음 단계로 넘어간 비율이다. 이탈률은 반대로 다음 단계로 넘어가지 않은 비율이다.
이탈률 = 100 - 전환율
user_counts
최종 결과를 확인하면 단계별 사용자 수, 전환율, 이탈률을 함께 볼 수 있다. 이 결과를 통해 상품 조회에서 장바구니로 넘어가는 비율, 장바구니에서 구매로 넘어가는 비율, 각 단계에서 얼마나 많은 사용자가 이탈하는지 확인할 수 있다.
📍view 100% 기준 전환율 분석
앞에서 계산한 전환율은 바로 이전 단계 대비 전환율이다. 이번에는 view를 100%로 두고, 각 단계가 전체 상품 조회 대비 어느 정도까지 도달했는지 확인한다. 즉, 다음을 계산한다.
view 기준 cart 도달률
view 기준 purchase 도달률
user_counts.iloc[0, 1]
user_counts 데이터프레임에서 첫 번째 행, 두 번째 열의 값을 가져오는 코드이다. 앞에서 사용자 수가 많은 순서대로 정렬했기 때문에 첫 번째 행은 보통 view 단계이고, 두 번째 열은 users 컬럼이다. 즉, 이 코드는 view 단계의 사용자 수를 가져오는 코드이다.
user_counts['conversion_rate_total'] = user_counts['users'] / user_counts.iloc[0, 1]
view 단계 사용자 수를 기준으로 각 단계의 도달률을 계산한다.
- view: 1, 즉 100%
- cart: view 대비 장바구니 도달률
- purchase: view 대비 구매 도달률
fig = px.funnel(user_counts, x='conversion_rate_total', y='event_type')
fig.update_traces(texttemplate="%{value:.2%}")
view 기준 전환율을 퍼널 차트로 시각화하는 코드이다. fig.update_traces(texttemplate="%{value:.2%}")는 차트 위에 표시되는 숫자를 퍼센트 형식으로 바꿔준다. 예를 들어 0.125는 12.50%로 표시된다. 분석 결과, 상품 조회 후 장바구니까지 이어지는 비율은 약 12.5%, 상품 조회 후 구매까지 이어지는 비율은 약 4.6%로 해석할 수 있다. 즉, 상품을 본 사용자 중 대부분은 장바구니 단계로 넘어가지 않고 이탈하고 있었다.
📍가격대별 퍼널 분석
전체 퍼널 분석을 통해 view → cart 단계에서 이탈이 크게 발생한다는 것을 확인했다. 이번에는 상품 가격대에 따라 전환율에 차이가 있는지 확인한다. 가격이 낮은 상품과 높은 상품은 사용자의 구매 의사결정 방식이 다를 수 있다. 가격이 낮은 상품은 부담 없이 장바구니에 담거나 구매할 수 있지만, 가격이 높은 상품은 구매 전 고민이 더 길어질 수 있다. 먼저 상품별 가격 정보를 정리한다.
temp = cosmetic_df[['product_id', 'price']].drop_duplicates()
상품 ID와 가격 컬럼만 선택한 뒤 중복을 제거한다. 같은 상품이 여러 이벤트로 등장할 수 있기 때문에 상품별 가격 정보를 확인할 때는 중복을 제거해야 한다.
temp['price'].quantile([0.25, 0.5, 0.75])
상품 가격의 25%, 50%, 75% 지점을 확인하는 코드이다. 분위수를 참고하여 가격대를 나눌 수 있다. 이번 실습에서는 가격대를 다음과 같이 나누었다.
~3
3~5
5~8
8~
가격대 분류 함수를 만든다.
def group_price(x):
if x <= 3:
return '~3'
elif x <= 5:
return '3~5'
elif x <= 8:
return '5~8'
else:
return '8~'
이 함수는 가격 값 x를 입력받아 가격대 그룹으로 분류한다.
- 가격이 3 이하이면 ~3
- 가격이 3보다 크고 5 이하이면 3~5
- 가격이 5보다 크고 8 이하이면 5~8
- 가격이 8보다 크면 8~
cosmetic_df['price_range'] = cosmetic_df['price'].apply(group_price)
각 상품의 가격을 가격대 그룹으로 변환해서 price_range 컬럼을 새로 만드는 코드이다. 이제 가격대별 퍼널 분석을 진행한다.
price_funnel = cosmetic_df.groupby(['price_range', 'event_type', 'product_id'])['user_id'].nunique()
가격대, 이벤트 타입, 상품 ID별로 고유 사용자 수를 계산하는 코드이다. 예를 들어 다음과 같은 단위로 그룹화된다.
~3 가격대 - view - 상품 A
~3 가격대 - cart - 상품 A
3~5 가격대 - view - 상품 B
5~8 가격대 - purchase - 상품 C
price_funnel = price_funnel.groupby(['price_range', 'event_type']).sum().reset_index()
상품별로 나뉘어 있던 사용자 수를 가격대와 이벤트 타입 기준으로 합산하는 코드이다.
price_funnel = price_funnel.rename(columns={'user_id': 'users'})
고유 사용자 수를 계산한 결과이므로 컬럼명을 users로 변경한다.
price_funnel = price_funnel.query("event_type != 'remove_from_cart'")
이번 가격대별 퍼널 분석에서도 핵심 흐름은 view → cart → purchase이므로 remove_from_cart는 제외한다.
price_funnel = price_funnel.groupby('price_range').apply(
lambda x: x.sort_values(by='users', ascending=False)
).reset_index(drop=True)
각 가격대 안에서 사용자 수가 많은 이벤트 순서대로 정렬하는 코드이다. 가격대별 전환율을 계산하려면 각 가격대 안에서 첫 번째 단계가 view여야 한다. 따라서 사용자 수가 많은 순서대로 정렬하여 보통 view → cart → purchase 순서가 되도록 한다.
price_funnel['conversion_rate'] = price_funnel['users'] / price_funnel.groupby('price_range')['users'].transform('first')
각 가격대 안에서 view를 기준으로 전환율을 계산한다. price_funnel.groupby('price_range')['users'].transform('first')는 각 가격대 그룹에서 첫 번째 사용자 수를 가져온다. 앞에서 사용자 수가 많은 순서대로 정렬했기 때문에 첫 번째 값은 보통 view 단계의 사용자 수이다. 따라서 이 코드는 가격대별로 다음을 계산한다.
각 단계 사용자 수 / 해당 가격대의 view 사용자 수
즉, 가격대별로 view를 100%로 두고 cart와 purchase까지 얼마나 전환되는지 계산한다. 가격대별 퍼널 차트를 그린다.
fig = px.funnel(
price_funnel,
x='conversion_rate',
y='event_type',
facet_col='price_range',
color='price_range',
category_orders={'price_range': ['~3', '3~5', '5~8', '8~']}
)
fig.update_traces(texttemplate="%{value:.2%}")
가격대별 퍼널 차트를 그리는 코드이다.
- x='conversion_rate': x축에는 가격대별 view 기준 전환율을 넣는다.
- y='event_type': y축에는 이벤트 타입을 넣는다.
- facet_col='price_range': 가격대별로 차트를 나누어 보여준다.
- color='price_range': 가격대별로 색상을 다르게 표시한다.
- category_orders: 가격대가 원하는 순서대로 나오도록 지정한다.
- fig.update_traces(texttemplate="%{value:.2%}"): 차트에 표시되는 값을 퍼센트 형식으로 바꾼다.
이 시각화를 통해 가격대가 높아질수록 구매까지 이어지는 전환율이 낮아지는지 확인할 수 있다.
📍가격대별 단계 전환율 자세히 보기
앞에서는 각 가격대별로 view 기준 도달률을 확인했다. 이번에는 가격대별로 다음 두 가지 전환율을 따로 계산한다.
view → cart 전환율
cart → purchase 전환율
먼저 가격대별 이벤트 사용자 수를 피벗 테이블로 만든다.
pivot_temp = price_funnel.pivot(index='price_range', columns='event_type', values='users')
pivot()은 데이터를 행과 열 구조로 재배치한다.
- index='price_range': 행에는 가격대를 넣는다.
- columns='event_type': 열에는 이벤트 타입을 넣는다.
- values='users': 각 셀에는 사용자 수를 넣는다.
결과적으로 다음과 비슷한 형태의 표가 만들어진다.
event_type cart purchase view
price_range
~3
3~5
5~8
8~
이렇게 바꾸면 view, cart, purchase 사용자 수를 이용해 단계별 전환율을 쉽게 계산할 수 있다.
pivot_temp['view_to_cart'] = pivot_temp['cart'] / pivot_temp['view'] * 100
pivot_temp['cart_to_purchase'] = pivot_temp['purchase'] / pivot_temp['cart'] * 100
첫 번째 줄은 가격대별 view → cart 전환율을 계산한다.
view_to_cart = cart 사용자 수 / view 사용자 수 × 100
즉, 상품을 본 사용자 중 몇 퍼센트가 장바구니에 담았는지 계산한다. 두 번째 줄은 가격대별 cart → purchase 전환율을 계산한다.
cart_to_purchase = purchase 사용자 수 / cart 사용자 수 × 100
즉, 장바구니에 담은 사용자 중 몇 퍼센트가 실제 구매까지 이어졌는지 계산한다.
pivot_temp.reindex(index=['~3', '3~5', '5~8', '8~'])
가격대 순서를 원하는 순서로 다시 정렬하는 코드이다. 낮은 가격대부터 높은 가격대까지 자연스럽게 비교하기 위해 ~3 → 3~5 → 5~8 → 8~ 순서로 정렬한다.
📍퍼널 분석 결과 해석
이번 퍼널 분석에서는 고객의 구매 흐름을 다음과 같이 정의하였다.
view → cart → purchase
분석 결과, 상품을 조회한 사용자가 장바구니에 담는 비율은 약 12.5% 수준으로 나타났다. 즉, 상품을 본 사용자 중 대부분은 장바구니 단계로 넘어가지 않고 이탈하고 있었다. 또한 상품을 조회한 사용자 중 실제 구매까지 이어지는 비율은 약 4.6% 수준으로 나타났다. 이를 통해 구매 전환 과정에서 가장 큰 개선 포인트는 view → cart 단계에 있음을 확인할 수 있다. 가격대별로 살펴보면 가격대가 높은 상품일수록 구매까지 이어지는 전환율이 낮게 나타났다. 특히 가격이 높아질수록 view → cart 전환율이 낮아지는 경향이 확인되었다.
반면 cart → purchase 전환율은 3 이하 가격대와 3 초과 가격대 사이에는 차이가 있었지만, 3 초과 가격대끼리는 큰 차이가 나타나지 않았다. 즉, 가격대가 높은 상품의 경우 사용자가 상품을 보고도 장바구니에 담기 전 단계에서 고민하거나 이탈하는 비중이 크다고 해석할 수 있다.
📍퍼널 분석 개선 방향
가격대가 높은 상품은 사용자가 상품을 본 뒤 장바구니에 담도록 유도하는 전략이 필요하다. 예를 들어 상품 상세 페이지에서 다음 요소를 더 명확하게 보여줄 수 있다.
- 할인 혜택
- 무료 배송
- 쿠폰
- 적립금
- 리뷰
- 상세 설명
- 인기 상품 문구
가격대가 낮은 상품은 cart → purchase 전환율이 상대적으로 높게 나타났기 때문에, 장바구니에 담긴 상품에 대해 알림이나 추천 메시지를 제공하면 구매 전환을 더 끌어올릴 수 있다. 즉, 가격대가 높은 상품은 장바구니 진입 유도가 중요하고, 가격대가 낮은 상품은 장바구니 이후 구매 유도가 중요하다고 볼 수 있다.
📆 코호트 분석(Cohort Analysis)
📍코호트 분석이란?
코호트 분석이란 특정 시점이나 이벤트를 기준으로 그룹화된 사용자 집단인 코호트를 추적하여, 시간 경과에 따른 행동 변화를 분석하는 방법이다. 여기서 코호트란 동질 집단을 의미한다. 즉, 특정 기간 동안 유사한 특성을 공유한 사용자 집단이라고 볼 수 있다. 예를 들어 다음과 같은 사용자들을 하나의 코호트로 묶을 수 있다.
2024년 1월 가입 사용자
2024년 2월 첫 구매 사용자
특정 상품을 처음 구매한 사용자
특정 캠페인을 통해 유입된 사용자
같은 연령대와 성별을 가진 사용자
코호트 분석은 특정 기간 동안 특정 경험을 공유한 집단 간의 행동 패턴을 비교하고 분석하는 방법이다. 예를 들어 올해 신규로 가입한 사용자의 시간별 행동 패턴을 분석하면, 서비스가 고객에게 지속적으로 사용되고 있는지 확인할 수 있다. 코호트 분석을 하는 이유는 데이터를 쪼개면 유의미한 패턴을 발견하기 쉽기 때문이다.
전체 데이터를 한 번에 보면 보이지 않는 특성이, 사용자 집단을 나누어 보면 드러날 수 있다. 강의에서는 이와 관련해 심슨의 역설(Simpson’s Paradox)도 언급되었는데 심슨의 역설은 집단 통계가 개별 그룹의 통계와 모순될 수 있음을 보여주는 현상이다. 전체 데이터를 보면 좋아 보이는 결과도, 그룹별로 나누어 보면 전혀 다른 결과가 나타날 수 있다. 그래서 분석에서는 데이터를 통으로만 보는 것이 아니라, 적절한 기준으로 나누어 보는 것이 중요하다. 분석에서의 “분”은 나눌 분, “석”은 쪼갤 석이라고 한다. 즉, 분석이란 데이터를 통으로 보는 것이 아니라 적절한 기준으로 나누고 쪼개서 보는 과정이라고 이해할 수 있다.
📍코호트 분석 관련 용어
코호트 기간(Cohort Period)
코호트 기간은 코호트를 추적하는 시간 단위이다.
시간 단위는 일, 주, 월 등으로 설정할 수 있다. 예를 들어 월 단위 코호트 분석이라면 1개월 후, 2개월 후, 3개월 후의 행동 변화를 확인할 수 있다.
유지율(Retention Rate)
유지율은 코호트 내에서 일정 기간 후에도 계속해서 서비스를 사용하는 고객의 비율이다.
예를 들어 1월 가입자 100명 중 2월에도 40명이 서비스를 사용했다면, 1개월 후 유지율은 40%라고 볼 수 있다.
이탈률(Churn Rate)
이탈률은 일정 기간 후에 서비스를 더 이상 사용하지 않게 된 고객의 비율이다.
이탈률은 유지율의 반대 개념이다.
이탈률 = 100% - 유지율
리텐션 플래토(Retention Plateau)
리텐션 플래토는 유지율 그래프에서 그래프의 기울기가 평평해지는 구간을 의미한다.
하나의 코호트 안에서 기간에 따른 유지율 추이를 보면서 유지율이 안정화되는 지점을 찾을 수 있다.
리텐션 플래토가 존재하는 경우, 프로덕트가 어느 정도 성공한 것으로 판단하기도 한다. 시간이 지나도 일정 비율의 사용자가 계속 남아 있다는 의미이기 때문이다.
📍코호트 분석의 활용 예시
코호트 분석은 여러 상황에서 활용될 수 있다.
첫 번째는 고객의 행동 패턴 파악이다. 시간에 따라 고객의 행동 패턴이 어떻게 변하는지를 파악하면 시장 수요에 더 잘 반응할 수 있다. 두 번째는 유지율 개선 및 이탈률 감소이다. 코호트별로 고객 유지에 기여하는 요소를 강화하고, 이탈을 초래하는 요소에 대응할 수 있다. 세 번째는 고객 맞춤형 마케팅 전략 수립이다. 코호트 간 행동 패턴이 다르다면, 각 코호트에 맞는 타겟 마케팅 전략을 세울 수 있다. 이를 통해 고객 만족도를 높이고, 마케팅 효율도 개선할 수 있다.
📍코호트 분석 프로세스
코호트 분석 프로세스는 다음과 같다.
1. 목표 설정
2. 데이터 수집 및 정제
3. 코호트 그룹 정의하기
4. 코호트 그룹별 분석
5. 결과 해석 및 전략 수립
6. 전략 실행 및 반응 피드백
먼저 분석 목표를 설정한다. 예를 들어 고객 유지율 향상, 고객 이탈률 감소, 재구매율 개선 등이 목표가 될 수 있다. 그다음 데이터를 수집하고 정제한다. 코호트 분석에서는 일반적으로 로그 데이터를 사용하며, 사용자 ID, 가입 날짜, 구매 날짜, 주문 금액 등의 데이터가 포함될 수 있다. 이후 코호트 그룹을 정의한다. 코호트 그룹은 시간 기반, 행동 기반, 인구 통계학적 특성 기반 등으로 나눌 수 있다. 그다음 코호트별 유지율, 이탈률, 행동 패턴, 매출액 등을 분석한다. 마지막으로 결과를 해석하고 전략을 수립한 뒤, 실제 전략을 실행하고 반응을 피드백한다.
📍코호트 분석 결과 해석 방법
코호트 분석 결과는 보통 행과 열로 구성된 표나 히트맵으로 표현된다. 해석 방식은 다음과 같다.
- 가로축: 시간의 경과
- 세로축: 각기 다른 코호트 그룹
- 셀의 값: 해당 코호트 그룹이 특정 시간 경과 후 보여주는 지표 값
예를 들어 첫 구매월 기준 코호트 분석이라면, 세로축에는 첫 구매월이 들어가고, 가로축에는 첫 구매 후 경과 개월이 들어간다. 셀의 값에는 해당 코호트의 유지율이 들어갈 수 있다.
📍리텐션 측정 방식
리텐션은 측정 방식에 따라 다르게 계산될 수 있다.
클래식 리텐션
클래식 리텐션은 가장 일반적인 유지율 계산 방식이다.
유지의 기준으로 삼은 행동이 발생한 첫날을 기준으로, N일 후의 특정한 날에 다시 행동하는 사용자의 비율을 계산한다.
예를 들어 가입일로부터 정확히 7일 후에 다시 방문한 사용자의 비율을 계산할 수 있다.
범위 리텐션
범위 리텐션은 특정 기간 범위 내에서 단 한 번이라도 행동할 경우, 그 사용자는 해당 기간 동안 유지되었다고 보는 방식이다.
예를 들어 가입 후 7일 이내에 한 번이라도 방문하면 유지된 사용자로 보는 식이다.
롤링 리텐션
롤링 리텐션은 사용자가 마지막 행동을 한 날을 기준으로, 그 이전의 모든 날을 행동한 것으로 간주하는 방식이다.
예를 들어 어떤 사용자가 30일차에 마지막으로 행동했다면, 1일차부터 30일차까지는 유지된 것으로 보는 방식이다.
📈 코호트 분석 실습
코호트 분석 실습은 크게 두 부분으로 나뉜다. 첫 번째는 코호트 기준 예시이다.
시간 기반 코호트
행동 기반 코호트
인구 통계학적 코호트
두 번째는 실제 코호트 분석이다.
첫 구매월 기준 코호트 정의
첫 구매 후 경과 개월 계산
코호트별 고객 수 집계
피벗 테이블 생성
유지율 계산
히트맵 시각화
여기서 핵심은 코호트란 공통된 기준을 가진 사용자 집단이라는 점이다.
📍시간 기반 코호트 예시
시간 기반 코호트는 가입일, 첫 방문일, 첫 구매일처럼 특정 시점을 기준으로 고객을 묶는 방식이다. 예를 들어 가입 날짜를 기준으로 하면 다음과 같은 코호트를 만들 수 있다.
2023년 1월 가입 고객
2023년 2월 가입 고객
2023년 3월 가입 고객
먼저 라이브러리를 불러온다.
import numpy as np
import pandas as pd
import random
from datetime import datetime, timedelta
시간 기반 코호트 예시를 위한 가상 데이터를 만든다.
np.random.seed(42)
data = {
'customer_id': range(1, 501),
'signup_date': random.sample(
pd.date_range(start='2023-01-01', periods=500, freq='D').tolist(),
500
),
'total_payment': np.random.randint(100, 500, 500)
}
df = pd.DataFrame(data)
df.head()
이 데이터에는 다음 컬럼이 들어간다.
- customer_id: 고객 ID
- signup_date: 가입 날짜
- total_payment: 총 결제 금액
np.random.seed(42)는 랜덤 결과를 고정하는 코드이다. 같은 코드를 여러 번 실행해도 같은 랜덤 결과가 나오도록 하기 위해 사용한다. pd.date_range(start='2023-01-01', periods=500, freq='D')는 2023년 1월 1일부터 하루 단위로 날짜 500개를 생성한다.
random.sample(..., 500)은 생성된 날짜 목록에서 500개를 랜덤하게 뽑는다. 날짜가 총 500개이고 그중 500개를 모두 뽑기 때문에, 날짜의 순서가 랜덤하게 섞인다고 볼 수 있다. np.random.randint(100, 500, 500)은 100 이상 500 미만의 정수 500개를 생성한다.
df.info()
데이터프레임 정보를 확인한다. 여기서는 signup_date가 날짜 타입인지 확인하는 것이 중요하다. 가입 날짜에서 년월을 추출하여 코호트 컬럼을 만든다.
df['signup_based_cohort'] = df['signup_date'].dt.to_period('M')
dt는 날짜/시간 타입의 컬럼에서 날짜 관련 기능을 사용할 수 있게 해주는 접근자이다. to_period('M')은 날짜를 월 단위 기간으로 변환한다. 예를 들어 다음과 같이 변환된다.
2023-01-15 → 2023-01
2023-02-27 → 2023-02
2023-05-03 → 2023-05
즉, 가입 날짜를 월 단위로 묶어 시간 기반 코호트를 만드는 코드이다.
df.head()
signup_based_cohort 컬럼이 잘 생성되었는지 확인한다.
📍행동 기반 코호트 예시
행동 기반 코호트는 사용자가 한 특정 행동을 기준으로 고객을 묶는 방식이다. 예를 들어 다음과 같은 기준으로 코호트를 만들 수 있다.
첫 구매 상품
첫 구매 여부
쿠폰 사용 여부
특정 상품 구매 여부
장바구니 담기 여부
이번 실습에서는 첫 구매 상품을 기준으로 행동 기반 코호트를 만든다. 먼저 가상 주문 데이터를 생성한다.
np.random.seed(42)
data = {
'customer_id': np.random.randint(1, 101, 500),
'order_date': random.sample(
pd.date_range(start='2023-01-01', periods=500, freq='D').tolist(),
500
),
'product_id': [np.random.choice([101, 102, 103, 104, 105]) for i in range(500)],
'total_payment': np.random.randint(100, 500, 500)
}
df = pd.DataFrame(data)
df.head()
이 데이터에는 다음 컬럼이 들어간다.
- customer_id: 고객 ID
- order_date: 주문 날짜
- product_id: 상품 ID
- total_payment: 결제 금액
customer_id는 1부터 100 사이의 값으로 생성된다. 주문 데이터에서는 한 고객이 여러 번 주문할 수 있기 때문에 같은 고객 ID가 여러 번 등장할 수 있다. product_id는 101, 102, 103, 104, 105 중 하나를 랜덤하게 선택한다.
고객별 첫 구매 날짜를 구한다.
first_purchase = df.groupby('customer_id')['order_date'].min().reset_index()
first_purchase = first_purchase.rename(columns={'order_date': 'first_purchase_date'})
first_purchase.head()
groupby('customer_id')는 고객별로 데이터를 묶는다. ['order_date'].min()은 각 고객의 가장 이른 주문 날짜를 구한다. 주문 날짜 중 가장 빠른 날짜가 첫 구매 날짜이다. reset_index()는 그룹화 결과의 인덱스를 다시 일반 컬럼으로 되돌린다. rename()을 사용해 order_date 컬럼명을 first_purchase_date로 변경한다. 이제 첫 구매 날짜에 해당하는 상품 정보를 원본 주문 데이터에서 가져온다.
first_product = pd.merge(
first_purchase,
df,
left_on=['customer_id', 'first_purchase_date'],
right_on=['customer_id', 'order_date'],
how='left'
)
pd.merge()는 두 데이터프레임을 합치는 함수이다. 여기서는 고객 ID와 첫 구매 날짜를 기준으로 원본 주문 데이터와 연결하여, 고객이 첫 구매 시점에 어떤 상품을 구매했는지 가져온다. 첫 구매 상품만 남기고 컬럼명을 바꾼다.
first_product = first_product[['customer_id', 'product_id']]
first_product = first_product.rename(columns={'product_id': 'first_product_cohort'})
first_product.head()
이제 각 고객은 처음 구매한 상품 ID를 기준으로 코호트가 정해진다. 원본 데이터에 첫 구매 상품 코호트 정보를 붙인다.
df = pd.merge(df, first_product, on='customer_id', how='left')
df.head()
이렇게 하면 주문 데이터의 각 행에 해당 고객의 첫 구매 상품 코호트가 추가된다.
📍인구 통계학적 코호트 예시
인구 통계학적 코호트는 나이, 성별, 지역 등 사용자 특성을 기준으로 고객을 묶는 방식이다. 이번 실습에서는 연령대와 성별을 결합하여 코호트를 만든다.
np.random.seed(42)
data = {
'customer_id': range(1, 501),
'age': np.random.randint(10, 70, 500),
'gender': [np.random.choice(['M', 'F']) for i in range(500)],
'total_payment': np.random.randint(100, 500, 500)
}
df = pd.DataFrame(data)
df.head()
이 데이터에는 고객 ID, 나이, 성별, 총 결제 금액이 들어간다. 나이를 연령대 구간으로 나눈다.
df['age_group'] = pd.cut(
df['age'],
bins=[0, 20, 30, 40, 50, 60, 70],
labels=['10s', '20s', '30s', '40s', '50s', '60s']
)
pd.cut()은 연속형 숫자 데이터를 구간으로 나누는 함수이다. 여기서는 나이를 10대, 20대, 30대, 40대, 50대, 60대로 나눈다. 연령대와 성별을 합쳐 코호트 컬럼을 만든다.
df['cohort'] = df.apply(lambda x: f"{x['age_group']}_{x['gender']}", axis=1)
apply()를 사용해 각 행마다 age_group과 gender를 합친 값을 만든다. 예를 들어 20대 여성은 20s_F, 40대 남성은 40s_M과 같은 코호트로 표현될 수 있다.
df.head()
age_group과 cohort 컬럼이 잘 추가되었는지 확인한다.
📍실제 코호트 분석: 첫 구매월 기준 유지율 분석
앞에서는 코호트를 어떤 기준으로 나눌 수 있는지 예시를 보았다. 이제 실제 코호트 분석을 수행한다. 이번 분석에서는 첫 구매월을 기준으로 코호트를 정의하고, 이후 몇 개월 뒤에도 다시 구매했는지 확인한다. 즉, 다음과 같은 질문에 답하는 분석이다.
2024년 1월에 처음 구매한 고객은 1개월 후, 2개월 후, 3개월 후에도 얼마나 남아 있을까?
2024년 2월에 처음 구매한 고객은 이후 기간 동안 얼마나 재구매할까?
먼저 가상 구매 데이터를 생성한다.
np.random.seed(0)
n_customers = 500
start_date = datetime(2024, 1, 1)
end_date = datetime(2024, 12, 31)
date_range = (end_date - start_date).days
data = {
'customer_id': np.random.choice(range(1, n_customers + 1), size=1000),
'purchase_date': [
start_date + timedelta(days=np.random.randint(date_range))
for _ in range(1000)
],
'purchase_amount': np.random.uniform(100, 1000, size=1000)
}
df = pd.DataFrame(data)
df.head()
이 데이터에는 다음 컬럼이 들어간다.
- customer_id: 고객 ID
- purchase_date: 구매 날짜
- purchase_amount: 구매 금액
n_customers = 500은 전체 고객 수를 500명으로 설정한다. start_date는 구매 데이터의 시작 날짜를 2024년 1월 1일로 설정한다. end_date는 구매 데이터의 종료 날짜를 2024년 12월 31일로 설정한다. date_range = (end_date - start_date).days는 시작일과 종료일 사이의 일수를 계산한다. np.random.choice(range(1, n_customers + 1), size=1000)은 1번부터 500번까지 고객 중 1000개의 고객 ID를 랜덤하게 선택한다. 한 고객이 여러 번 구매할 수 있으므로 같은 고객 ID가 여러 번 등장할 수 있다. purchase_date는 시작일에 랜덤한 일수를 더해 구매 날짜를 만든다. purchase_amount는 100 이상 1000 미만의 구매 금액을 랜덤하게 생성한다.
구매월 컬럼 생성
df['purchase_month'] = df['purchase_date'].apply(lambda x: x.replace(day=1))
구매 날짜를 월 단위로 맞추는 코드이다. 예를 들어 2024-03-17은 2024-03-01로 바뀐다. 월 단위 코호트 분석에서는 정확한 일자보다 어느 월에 구매했는지가 중요하기 때문에 모든 날짜를 해당 월의 1일로 맞춘다.
고객별 첫 구매월 계산
df['first_purchase_month'] = df.groupby('customer_id')['purchase_date'].transform('min').apply(
lambda x: x.replace(day=1)
)
고객별 첫 구매 날짜를 구한 뒤, 그 날짜를 월 단위로 변환하는 코드이다. groupby('customer_id')['purchase_date'].transform('min')은 고객별 가장 이른 구매 날짜를 구한다. transform()을 사용했기 때문에 결과가 원래 데이터프레임의 행 개수에 맞게 들어간다. 이후 apply(lambda x: x.replace(day=1))을 사용해 첫 구매 날짜도 월 단위로 맞춘다.
첫 구매월 이후 경과 개월 계산
df['months_since_first_purchase'] = (
(df['purchase_month'].dt.year - df['first_purchase_month'].dt.year) * 12
+ (df['purchase_month'].dt.month - df['first_purchase_month'].dt.month)
)
첫 구매월 이후 몇 개월이 지났는지 계산한다. 연도 차이에 12를 곱하고, 월 차이를 더한다. 예를 들어 첫 구매월이 2024년 1월이고 현재 구매월이 2024년 4월이면 경과 개월은 3개월이다.
(2024 - 2024) × 12 + (4 - 1) = 3
즉, 이 컬럼은 각 구매가 첫 구매 이후 몇 개월 뒤에 발생했는지를 나타낸다.
코호트별 고객 수와 구매 금액 집계
cohort_data = (
df.groupby(['first_purchase_month', 'months_since_first_purchase'])
.agg(
n_customers=('customer_id', 'nunique'),
total_purchase=('purchase_amount', 'sum')
)
.reset_index()
)
첫 구매월과 경과 개월을 기준으로 고객 수와 총 구매 금액을 집계한다.
- first_purchase_month: 코호트 기준
- months_since_first_purchase: 첫 구매 후 경과 개월
- n_customers: 해당 시점에 구매한 고유 고객 수
- total_purchase: 해당 시점의 총 구매 금액
nunique()를 사용했기 때문에 같은 고객이 같은 기간에 여러 번 구매했더라도 한 명으로 계산된다.
코호트 피벗 테이블 생성
cohort_pivot = cohort_data.pivot_table(
index='first_purchase_month',
columns='months_since_first_purchase',
values='n_customers'
)
피벗 테이블은 코호트 분석 결과를 보기 좋은 형태로 바꾸는 과정이다.
- 행: 첫 구매월
- 열: 첫 구매 후 경과 개월
- 값: 고객 수
이렇게 하면 각 코호트가 시간이 지나면서 얼마나 유지되는지 한눈에 볼 수 있다. 예를 들어 2024년 1월 첫 구매 고객이 0개월 차에 몇 명이었고, 1개월 후에는 몇 명이 다시 구매했으며, 2개월 후에는 몇 명이 다시 구매했는지를 표 형태로 확인할 수 있다.
유지율 계산
cohort_size = cohort_pivot.iloc[:, 0]
각 코호트의 초기 고객 수를 구한다. iloc[:, 0]은 첫 번째 열을 의미한다. 코호트 분석에서 첫 번째 열은 보통 경과 개월 0개월 차이며, 해당 코호트의 초기 고객 수를 의미한다.
retention_matrix = np.round(cohort_pivot.divide(cohort_size, axis=0) * 100, 2)
코호트별 유지율을 계산한다. cohort_pivot.divide(cohort_size, axis=0)은 각 코호트의 고객 수를 해당 코호트의 초기 고객 수로 나눈다. 그다음 * 100을 해서 퍼센트로 바꾸고, np.round(..., 2)를 사용해 소수점 둘째 자리까지 반올림한다. 즉, 유지율 계산식은 다음과 같다.
유지율 = 특정 경과 개월의 고객 수 / 코호트 초기 고객 수 × 100
유지율 히트맵 시각화
plt.figure(figsize=(12, 6))
sns.heatmap(retention_matrix, annot=True, fmt='.2f', cmap='Blues')
plt.title('Cohort Retention Rate')
plt.xlabel('Months Since First Purchase')
plt.ylabel('First Purchase Month')
plt.show()
코호트 유지율을 히트맵으로 시각화하는 코드이다. plt.figure(figsize=(12, 6))은 그래프 크기를 설정한다. sns.heatmap()은 히트맵을 그리는 함수이다.
- retention_matrix: 시각화할 유지율 데이터
- annot=True: 각 셀에 숫자를 표시한다.
- fmt='.2f': 숫자를 소수점 둘째 자리까지 표시한다.
- cmap='Blues': 파란색 계열 색상으로 표시한다.
히트맵을 사용하면 코호트별 유지율을 직관적으로 확인할 수 있다. 보통 색이 진할수록 유지율이 높은 것으로 해석할 수 있다.
📍코호트 분석 결과 해석
코호트 분석에서는 각 첫 구매월 코호트가 시간이 지나면서 얼마나 유지되는지를 확인할 수 있다. 일반적으로 시간이 지날수록 유지율은 낮아지는 경향이 있다. 첫 구매 직후에는 많은 고객이 존재하지만, 시간이 지나면서 재구매하지 않는 고객이 늘어나기 때문이다. 하지만 모든 코호트가 같은 패턴을 보이는 것은 아니다. 어떤 코호트는 다른 코호트보다 유지율이 높게 나타날 수 있다. 이 경우 해당 시기에 유입된 고객의 특성, 당시 진행한 프로모션, 상품 구성, 서비스 경험 등을 함께 살펴볼 수 있다. 예를 들어 3월 첫 구매 고객의 유지율이 유독 높다면, 3월에 진행한 프로모션이나 상품 구성이 좋은 고객을 유입시켰는지 확인해볼 수 있다. 반대로 특정 월 코호트의 유지율이 낮다면, 해당 시기에 유입된 고객의 품질이 낮았거나, 첫 구매 이후 재구매를 유도하는 장치가 부족했을 가능성을 생각해볼 수 있다. 코호트 분석의 핵심은 전체 고객을 한 번에 보는 것이 아니라, 고객을 일정 기준으로 쪼개고 시간에 따른 변화를 확인하는 것이다.
🛍️ RFM 분석
📍RFM 분석이란?
RFM 분석은 고객 가치를 평가하는 대표적인 분석 방법이다. RFM은 다음 세 가지 요소를 의미한다.
R = Recency
F = Frequency
M = Monetary
각각의 의미는 다음과 같다. Recency는 고객이 얼마나 최근에 구매했는지를 의미한다. 최근에 구매한 고객일수록 다시 구매할 가능성이 높다고 볼 수 있다. Frequency는 고객이 얼마나 자주 구매했는지를 의미한다. 구매 빈도가 높은 고객일수록 서비스에 대한 충성도가 높거나 반복 구매 가능성이 높다고 볼 수 있다. Monetary는 고객이 얼마나 많은 금액을 구매했는지를 의미한다. 구매 금액이 큰 고객일수록 매출 기여도가 높다고 볼 수 있다.
즉, RFM 분석은 고객이 최근에, 자주, 많이 구매했는지를 기준으로 고객을 평가하고 세분화하는 방법이다. 예를 들어 최근에 구매했고, 자주 구매하며, 구매 금액도 큰 고객은 가치가 높은 고객으로 볼 수 있다. 반대로 오래전에 한 번만 구매했고, 구매 금액도 낮은 고객은 상대적으로 낮은 가치의 고객으로 분류될 수 있다.
📍RFM 분석의 기본 아이디어
RFM 분석의 기본 아이디어는 파레토 법칙과 연결된다. 파레토 법칙은 상위 20%의 고객이 전체 수익의 80%를 만든다는 관점이다.
물론 모든 서비스에서 정확히 20대 80으로 나타나는 것은 아니지만, 많은 비즈니스에서 일부 핵심 고객이 전체 매출의 큰 비중을 차지하는 경우가 많다. 따라서 RFM 분석은 전체 고객을 똑같이 보는 것이 아니라, 구매 데이터에 기반하여 우수 고객을 찾아내고, 해당 고객군에 맞는 전략을 세우는 데 활용된다.
📍RFM 점수 부여 방식
RFM 점수는 여러 방식으로 만들 수 있다.
합계 방식
합계 방식은 각 요소별 점수를 더해서 총점을 만드는 방식이다. 예를 들어 다음과 같이 계산할 수 있다.
고객 A: R 5점 + F 2점 + M 5점 = 12점
고객 B: R 4점 + F 4점 + M 2점 = 10점
이 방식은 계산이 단순하다는 장점이 있다. 하지만 R, F, M 각각의 조합이 가진 의미가 뭉개질 수 있다는 한계가 있다. 예를 들어 총점이 같더라도 어떤 고객은 최근 구매 점수가 높고, 어떤 고객은 구매 금액 점수가 높을 수 있다. 이 경우 단순 총점만 보면 고객의 특성을 세밀하게 구분하기 어렵다.
Cell 방식
Cell 방식은 R, F, M 점수를 각각 따로 조합하여 보는 방식이다. 예를 들어 5, 2, 5와 같이 표현할 수 있다. 이 방식은 고객의 특성을 더 세밀하게 볼 수 있다. Recency는 높지만 Frequency가 낮은 고객, Frequency와 Monetary는 높지만 Recency가 낮은 고객 등을 구분할 수 있기 때문이다.
📍RFM 분석 프로세스
RFM 분석 프로세스는 다음과 같다.
1. RFM 요소별 기준 정의 및 데이터 수집
2. 분포 시각화 및 등급 개수 결정
3. RFM 점수 부여
4. RFM 모델 및 가중치 설정
5. 고객 세그먼트 분류
6. 세그먼트별 특성 분석
7. 전략 수립
먼저 RFM 요소별 기준을 정의하고 데이터를 수집한다. 그다음 Recency, Frequency, Monetary의 분포를 확인하고, 몇 개 등급으로 나눌지 결정한다. 특정 범위에 데이터가 몰려 있다면 등급 개수를 조정하거나 구간 기준을 다시 잡아야 할 수 있다. 이후 RFM 점수를 부여하고, 분석 목적에 따라 가중치를 설정한다. 마지막으로 고객 세그먼트를 분류하고, 세그먼트별 특성을 분석한 뒤 전략을 수립한다.
📍RFM 모델과 가중치
RFM의 각 요소별 중요도는 도메인에 따라 다르다. 따라서 RFM 모델을 만들 때는 가중치를 반영할 수 있다.
RFM 모델 = a × R + b × F + c × M
여기서 각 가중치의 의미는 다음과 같다.
- a: Recency 가중치
- b: Frequency 가중치
- c: Monetary 가중치
예를 들어 유통업, 보험, 자동차처럼 오프라인 또는 장기 재구매가 중요한 산업에서는 Recency가 중요할 수 있다. 온라인 SNS, 통신업, 서비스업처럼 지속적인 사용 빈도가 중요한 산업에서는 Frequency가 중요할 수 있다. 금융업처럼 고객이 사용하는 금액 규모가 중요한 산업에서는 Monetary가 중요할 수 있다. 즉, RFM 분석에서 가중치는 정답이 정해져 있는 것이 아니라, 서비스의 특성과 분석 목적에 맞게 설정해야 한다.
📍RFM 집계 기간
RFM 분석에서 집계 기간은 보통 6개월에서 1년 정도를 사용하지만, 서비스마다 사용 주기가 다르기 때문에 무조건 정해진 기간이 있는 것은 아니다. 동일한 서비스 안에서도 유저 집단마다 사용 주기가 다를 수 있다. 예를 들어 쿠팡에서는 식료품 구매 유저와 일반 상품 구매 유저의 구매 주기가 다를 수 있다. 식료품은 자주 구매할 수 있지만, 일반 상품은 구매 주기가 상대적으로 길 수 있다. 또 무신사처럼 시즌성이 있는 서비스에서는 SS/FW 시즌에 따라 판매 상품군과 가격대가 달라질 수 있다. 따라서 RFM 분석에서는 도메인과 고객 특성을 고려하여 적절한 집계 기간을 설정해야 한다.
📍RFM 분석의 한계
RFM 분석에는 한계가 있다.
고객군의 한계
RFM 분석은 구매 데이터를 기반으로 한다. 따라서 구매하지 않은 대다수의 고객은 분석 대상에서 제외될 수 있다. 예를 들어 방문은 했지만 구매하지 않은 사용자, 장바구니에 담았지만 구매하지 않은 사용자 등은 RFM 분석만으로 충분히 설명하기 어렵다.
신뢰성의 한계
도메인에 따라 RFM 요소별 중요도가 다르고, R, F, M 요소 간 상호 독립성이 보장되지 않는다. 예를 들어 구매 빈도(F)가 높은 고객은 보통 총 구매 금액(M)도 높을 가능성이 크다. 이 경우 Frequency와 Monetary가 완전히 독립적인 요소라고 보기 어려울 수 있다.
예측의 한계
RFM 분석은 과거 데이터를 바탕으로 고객을 분류하는 방법이다. 따라서 새롭게 등장할 고객에 대한 예측에는 한계가 있다. 즉, RFM 분석은 현재 고객을 이해하고 세분화하는 데 유용하지만, 미래 행동을 정교하게 예측하기 위해서는 추가적인 모델링이나 다른 분석 방법이 필요할 수 있다.
💰 RFM 분석 실습
RFM 분석 실습의 흐름은 다음과 같다.
데이터 불러오기
→ Monetary 컬럼 생성
→ Frequency 컬럼 생성
→ Recency / Frequency / Monetary 등급화
→ RFM 점수 계산
→ 고객 세그먼트 분류
→ 가중치 조정
→ 세그먼트별 특성 분석
전처리 자체보다는 RFM 분석에 필요한 핵심 변수 생성과 점수화, 세그먼트 분석 흐름을 중심으로 정리한다.
📍라이브러리 불러오기
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
RFM 분석에 필요한 라이브러리를 불러온다.
pd.set_option('display.max_columns', None)
pandas 출력 옵션을 설정하는 코드이다. 데이터 컬럼이 많을 때 기본 설정으로는 중간 컬럼들이 생략될 수 있다. RFM 분석에서는 여러 컬럼을 확인해야 하므로 모든 컬럼이 보이도록 설정한다.
customer_df = pd.read_csv('/content/customer_data.csv', sep='\t')
customer_df.head()
고객 데이터를 불러온다. sep='\t'는 데이터가 쉼표가 아니라 탭으로 구분되어 있다는 의미이다.
📍Monetary 컬럼 생성
RFM에서 Monetary는 고객이 얼마나 많은 금액을 썼는지를 의미한다. 실습에서는 여러 품목별 구매 금액을 모두 더해서 총 구매 금액인 amount_total을 만든다.
data_amount_total = (
customer_df['amount_alcohol']
+ customer_df['amount_fruit']
+ customer_df['amount_meat']
+ customer_df['amount_fish']
+ customer_df['amount_snack']
+ customer_df['amount_general']
)
각 컬럼의 의미는 다음과 같다.
- amount_alcohol: 주류 구매 금액
- amount_fruit: 과일 구매 금액
- amount_meat: 육류 구매 금액
- amount_fish: 수산물 구매 금액
- amount_snack: 과자 구매 금액
- amount_general: 잡화 구매 금액
이 금액들을 모두 더하면 고객별 총 구매 금액을 구할 수 있다.
index_amount_general = customer_df.columns.get_loc('amount_general')
index_amount_general
amount_general 컬럼이 데이터프레임에서 몇 번째 위치에 있는지 확인하는 코드이다. get_loc()은 특정 컬럼의 위치를 반환한다.
customer_df.insert(
loc=index_amount_general + 1,
column='amount_total',
value=data_amount_total
)
customer_df.head()
amount_general 컬럼 바로 뒤에 amount_total 컬럼을 추가하는 코드이다. insert()는 원하는 위치에 새 컬럼을 추가할 때 사용한다.
- loc: 컬럼을 추가할 위치
- column: 새 컬럼명
- value: 새 컬럼에 들어갈 값
이렇게 해서 RFM 분석에서 Monetary로 사용할 고객별 총 구매 금액 컬럼이 만들어진다.
📍Frequency 컬럼 생성
RFM에서 Frequency는 고객이 얼마나 자주 구매했는지를 의미한다. 실습에서는 웹 구매 횟수, 오프라인 매장 구매 횟수, 할인 구매 횟수를 모두 더해서 총 구매 횟수인 num_purchase_total을 만든다.
num_purchase_total = (
customer_df['num_purchase_web']
+ customer_df['num_purchase_store']
+ customer_df['num_purchase_discount']
)
각 컬럼의 의미는 다음과 같다.
- num_purchase_web: 웹 구매 횟수
- num_purchase_store: 매장 구매 횟수
- num_purchase_discount: 할인 구매 횟수
이 세 구매 횟수를 모두 더하면 고객별 총 구매 횟수를 구할 수 있다.
index_num_purchase_discount = customer_df.columns.get_loc('num_purchase_discount')
index_num_purchase_discount
num_purchase_discount 컬럼의 위치를 확인하는 코드이다.
customer_df.insert(
loc=index_num_purchase_discount + 1,
column='num_purchase_total',
value=num_purchase_total
)
customer_df.head()
num_purchase_discount 컬럼 바로 뒤에 num_purchase_total 컬럼을 추가하는 코드이다. 이제 RFM 분석에서 사용할 Monetary와 Frequency 기준 컬럼이 만들어졌다.
📍RFM 등급화
RFM 분석에서는 각 요소를 등급으로 나누어 점수화한다. 이번 실습에서는 각 요소를 3개 등급으로 나누었다.
num_grades = 3
등급 개수를 3개로 설정하는 코드이다.
grade_labels = list(range(1, num_grades + 1))
grade_labels
range(1, num_grades + 1)은 1부터 num_grades까지의 숫자를 만든다. num_grades가 3이므로 결과는 다음과 같다.
[1, 2, 3]
RFM에서 점수가 높을수록 좋은 고객으로 볼 수 있다. 다만 Recency는 주의해야 한다. Recency는 최근 구매일로부터 얼마나 지났는지를 의미한다. 따라서 값이 작을수록 최근에 구매한 고객이다. 즉, Recency 값은 작을수록 좋다. 그래서 Recency 등급을 만들 때는 라벨을 역순으로 사용한다.
grade_labels[::-1]
[1, 2, 3]을 뒤집어 [3, 2, 1]로 만든다.
📍Recency 등급화
customer_df['recency_grade'] = pd.qcut(
x=customer_df['recency'],
q=num_grades,
labels=grade_labels[::-1]
)
recency 값을 3개 구간으로 나누고 등급을 부여하는 코드이다. pd.qcut()은 데이터를 분위수 기준으로 나누는 함수이다.
- x=customer_df['recency']: 등급화할 데이터
- q=num_grades: 3개 구간으로 나눈다.
- labels=grade_labels[::-1]: Recency는 값이 작을수록 좋으므로 높은 점수부터 부여한다.
즉, 최근에 구매한 고객일수록 높은 Recency 등급을 받는다.
customer_df['recency_grade'].value_counts()
Recency 등급별 고객 수를 확인한다.
📍Frequency 등급화
customer_df['frequency_grade'] = pd.qcut(
x=customer_df['num_purchase_total'],
q=num_grades,
labels=grade_labels
)
num_purchase_total 값을 기준으로 Frequency 등급을 부여하는 코드이다. Frequency는 구매 횟수가 많을수록 좋은 고객이다. 따라서 값이 클수록 높은 등급을 부여한다.
customer_df['frequency_grade'].value_counts()
Frequency 등급별 고객 수를 확인한다.
📍Monetary 등급화
customer_df['monetary_grade'] = pd.qcut(
x=customer_df['amount_total'],
q=num_grades,
labels=grade_labels
)
amount_total 값을 기준으로 Monetary 등급을 부여하는 코드이다. Monetary는 총 구매 금액이 클수록 좋은 고객이다. 따라서 값이 클수록 높은 등급을 부여한다.
customer_df['monetary_grade'].value_counts()
Monetary 등급별 고객 수를 확인한다. Monetary 등급별 매출 기여도를 확인한다.
groupby_monetary_grade = customer_df.groupby('monetary_grade').sum(numeric_only=True).reset_index()
groupby_monetary_grade
Monetary 등급별로 수치형 컬럼의 합계를 계산한다.
groupby_monetary_grade['amount_total'].plot(
kind='pie',
autopct='%.1f%%',
labels=[f"{x}등급" for x in grade_labels],
title='Monetary 등급별 매출 기여도',
ylabel=''
)
plt.show()
Monetary 등급별 매출 기여도를 파이차트로 시각화한다. amount_total은 총 구매 금액이므로, 높은 Monetary 등급이 전체 매출에서 얼마나 큰 비중을 차지하는지 확인할 수 있다.
📍RFM 모델 1: 동일한 가중치 적용
먼저 Recency, Frequency, Monetary를 모두 같은 중요도로 보고 RFM 점수를 계산한다.
weight = {}
weight['recency'] = 1 / 3
weight['frequency'] = 1 / 3
weight['monetary'] = 1 / 3
weight
세 가중치의 합은 1이다. 즉, Recency, Frequency, Monetary를 모두 동일한 비중으로 반영한다.
customer_df.info()
RFM 점수를 계산하기 전에 데이터 타입을 확인한다. pd.qcut()으로 만든 등급 컬럼은 category 타입일 수 있다. 계산을 하려면 숫자형으로 변환해야 하므로 데이터 타입 확인이 필요하다.
customer_df['rfm_score'] = (
weight['recency'] * customer_df['recency_grade'].astype('int')
+ weight['frequency'] * customer_df['frequency_grade'].astype('int')
+ weight['monetary'] * customer_df['monetary_grade'].astype('int')
)
RFM 점수를 계산하는 코드이다. 계산식은 다음과 같다.
RFM 점수
= Recency 등급 × Recency 가중치
+ Frequency 등급 × Frequency 가중치
+ Monetary 등급 × Monetary 가중치
가중치가 모두 1/3이므로 세 등급의 평균값처럼 계산된다. astype('int')는 등급 컬럼을 숫자형으로 변환하는 코드이다. 등급 컬럼이 category 타입이면 바로 계산하기 어려울 수 있기 때문에 정수형으로 변환한다.
customer_df.head()
customer_df['rfm_score'].describe()
rfm_score 컬럼이 잘 추가되었는지 확인하고, RFM 점수의 기초 통계량을 확인한다.
📍RFM 세그먼트 분류
RFM 점수는 1에서 3 사이의 값을 가진다. 각 등급이 1, 2, 3 중 하나이고, 가중치의 합이 1이기 때문이다. 따라서 1부터 3까지의 범위를 세 구간으로 나누어 고객을 1등급, 2등급, 3등급 세그먼트로 분류한다.
def rfm_segment_bins(x):
if x < 5 / 3:
return 1
elif x < 7 / 3:
return 2
else:
return 3
이 함수는 RFM 점수를 기준으로 고객을 1, 2, 3등급 세그먼트로 나눈다.
- RFM 점수가 5/3보다 작으면 1등급 세그먼트
- RFM 점수가 7/3보다 작으면 2등급 세그먼트
- 그 외에는 3등급 세그먼트
customer_df['rfm_segment'] = customer_df['rfm_score'].apply(rfm_segment_bins)
customer_df.head()
apply()를 사용해 각 고객의 rfm_score에 세그먼트 분류 함수를 적용한다.
customer_df['rfm_segment'].value_counts()
세그먼트별 고객 수를 확인한다. 세그먼트별 매출 기여도를 확인한다.
groupby_rfm_segment = customer_df.groupby('rfm_segment').sum(numeric_only=True).reset_index()
groupby_rfm_segment
RFM 세그먼트별로 수치형 컬럼의 합계를 계산한다.
groupby_rfm_segment['amount_total'].plot(
kind='pie',
autopct='%.1f%%',
labels=[f"{x}등급" for x in grade_labels],
title='RFM 세그먼트별 매출 기여도',
ylabel=''
)
plt.show()
RFM 세그먼트별 매출 기여도를 파이차트로 시각화한다. 이를 통해 어떤 RFM 세그먼트가 전체 매출에 가장 많이 기여하는지 확인할 수 있다.
📍RFM 모델 2: 가중치 조정
처음에는 R, F, M을 모두 같은 중요도로 보았다. 하지만 도메인에 따라 더 중요하게 봐야 하는 요소가 달라질 수 있다. 실습에서는 Frequency와 Monetary를 더 중요하게 보기 위해 가중치를 다음과 같이 조정하였다.
weight = {}
weight['recency'] = 0.2
weight['frequency'] = 0.4
weight['monetary'] = 0.4
weight
이 경우 최근 구매 여부보다 구매 빈도와 구매 금액을 더 중요하게 반영한다. 조정된 가중치로 RFM 점수를 다시 계산한다.
customer_df['rfm_score'] = (
weight['recency'] * customer_df['recency_grade'].astype('int')
+ weight['frequency'] * customer_df['frequency_grade'].astype('int')
+ weight['monetary'] * customer_df['monetary_grade'].astype('int')
)
세그먼트도 다시 분류한다.
customer_df['rfm_segment'] = customer_df['rfm_score'].apply(rfm_segment_bins)
customer_df.head()
가중치를 조정하면 같은 고객이라도 세그먼트가 달라질 수 있다. 예를 들어 최근 구매는 했지만 구매 빈도와 구매 금액이 낮은 고객은 동일 가중치에서는 높은 점수를 받을 수 있지만, Frequency와 Monetary를 더 중요하게 보는 모델에서는 낮은 점수를 받을 수 있다. 따라서 RFM 분석에서는 도메인과 분석 목적에 맞게 가중치를 정하는 것이 중요하다.
📍세그먼트별 연령대 고객 비중 분석
RFM 세그먼트를 나눈 뒤에는 세그먼트별 특성을 분석해야 한다. 먼저 세그먼트별로 어떤 연령대 고객이 많은지 확인한다.
customer_df['age_group'] = pd.cut(
customer_df['age'],
bins=[0, 20, 30, 40, 50, 60, 70, 100],
labels=['10대 이하', '20대', '30대', '40대', '50대', '60대', '70대 이상']
)
나이를 연령대 구간으로 나누는 코드이다. pd.cut()은 연속형 숫자 데이터를 구간형 범주로 변환할 때 사용한다. 세그먼트와 연령대별 고객 수를 집계한다.
groupby_rfm_segment_age_group = (
customer_df.groupby(['rfm_segment', 'age_group'])
.agg(num_customers=('age', 'count'))
.reset_index()
)
groupby_rfm_segment_age_group
RFM 세그먼트와 연령대를 기준으로 고객 수를 계산한다.
- groupby(['rfm_segment', 'age_group']): 세그먼트와 연령대를 기준으로 그룹화한다.
- agg(num_customers=('age', 'count')): 각 그룹의 고객 수를 계산한다.
- reset_index(): 결과를 데이터프레임 형태로 정리한다.
세그먼트별로 연령대 고객 비중을 파이차트로 시각화한다.
for i_segment in range(1, num_grades + 1):
age_group_dist = groupby_rfm_segment_age_group[
groupby_rfm_segment_age_group['rfm_segment'] == i_segment
]
age_group_dist['num_customers'].plot(
kind='pie',
autopct='%.1f%%',
labels=age_group_dist['age_group'].unique(),
title=f'{i_segment}등급 세그먼트 연령대별 고객 비중',
ylabel=''
)
plt.show()
1등급부터 3등급까지 반복하면서, 각 세그먼트 안에서 연령대별 고객 비중을 파이차트로 보여준다. 분석 결과, 매출 기여도가 높은 3등급 세그먼트에 집중하여 마케팅을 전개한다면 젊은 세대보다는 중장년층을 타겟으로 하는 것이 효과가 더 클 가능성이 있다고 해석할 수 있다.
📍세그먼트별 품목별 매출 기여도 분석
이번에는 RFM 세그먼트별로 어떤 품목에서 매출이 많이 발생하는지 확인한다.
groupby_rfm_segment = customer_df.groupby('rfm_segment').sum(numeric_only=True).reset_index()
groupby_rfm_segment
세그먼트별로 수치형 컬럼의 합계를 계산한다. 분석에 사용할 품목별 금액 컬럼을 선택한다.
selected_columns = [
'rfm_segment',
'amount_alcohol',
'amount_fruit',
'amount_meat',
'amount_fish',
'amount_snack',
'amount_general'
]
세그먼트별 품목별 매출 데이터를 따로 추출한다.
segment_amount = groupby_rfm_segment[selected_columns]
segment_amount = segment_amount.set_index('rfm_segment')
segment_amount
set_index('rfm_segment')를 사용해 RFM 세그먼트를 인덱스로 설정한다. 이렇게 하면 그래프를 그릴 때 세그먼트별 비교가 더 자연스럽다.
segment_amount.plot(kind='bar', stacked=True, figsize=(10, 6))
plt.title('RFM 세그먼트별 품목별 매출 기여도')
plt.xlabel('RFM 세그먼트')
plt.ylabel('구매 금액')
plt.show()
RFM 세그먼트별 품목별 매출 기여도를 누적 막대그래프로 시각화한다. 이를 통해 각 세그먼트가 어떤 품목에서 매출을 많이 발생시키는지 확인할 수 있다. 예를 들어 3등급 세그먼트에서 육류나 주류 구매 금액이 높게 나타난다면, 해당 세그먼트를 대상으로 관련 상품 추천이나 묶음 상품 프로모션을 기획할 수 있다.
📍세그먼트별 프로모션 참여율 분석
마지막으로 RFM 세그먼트별로 프로모션 참여율이 어떻게 다른지 확인한다.
groupby_rfm_segment = customer_df.groupby('rfm_segment').mean(numeric_only=True).reset_index()
groupby_rfm_segment
세그먼트별로 수치형 컬럼의 평균을 계산한다. 프로모션 참여 여부 컬럼이 0과 1로 되어 있다면, 평균은 참여율로 해석할 수 있다. 예를 들어 promotion_1의 평균이 0.25라면, 해당 세그먼트 고객 중 25%가 promotion_1에 참여했다는 의미이다.
groupby_rfm_segment.columns
프로모션 컬럼명을 확인하는 코드이다. 프로모션 참여율 분석에 사용할 컬럼 목록을 만든다.
selected_columns = [
'rfm_segment',
'promotion_1',
'promotion_2',
'promotion_3',
'promotion_4',
'promotion_5',
'promotion_6'
]
세그먼트별 프로모션 참여율 데이터만 따로 추출한다.
avg_promotion = groupby_rfm_segment[selected_columns]
avg_promotion = avg_promotion.set_index('rfm_segment')
avg_promotion
프로모션 참여율을 막대그래프로 시각화한다.
avg_promotion.plot(kind='bar', figsize=(10, 6))
plt.title('RFM 세그먼트별 프로모션 참여율')
plt.xlabel('RFM 세그먼트')
plt.ylabel('프로모션 참여율')
plt.show()
이 그래프를 보면 어떤 세그먼트가 어떤 프로모션에 더 잘 반응하는지 확인할 수 있다. 이를 바탕으로 세그먼트별 맞춤 프로모션 전략을 세울 수 있다.
📍RFM 분석 결과 해석
이번 RFM 분석에서는 고객 데이터를 기반으로 다음 과정을 수행하였다.
1. 고객별 총 구매 금액을 계산하여 Monetary 컬럼을 만들었다.
2. 고객별 총 구매 횟수를 계산하여 Frequency 컬럼을 만들었다.
3. Recency, Frequency, Monetary를 각각 3개 등급으로 나누었다.
4. 동일 가중치로 RFM 점수를 계산하였다.
5. RFM 점수 기준으로 고객 세그먼트를 분류하였다.
6. Frequency와 Monetary를 더 중요하게 보는 가중치로 RFM 점수를 다시 계산하였다.
7. 세그먼트별 연령대, 품목별 매출 기여도, 프로모션 참여율을 분석하였다.
RFM 분석은 고객을 구매 데이터 기반으로 세분화할 수 있다는 점에서 유용하다. 단순히 전체 고객의 평균 구매 금액을 보는 것보다, 어떤 고객군이 더 가치가 높은지, 어떤 고객군에 마케팅을 집중해야 하는지 판단하는 데 도움이 된다. 특히 매출 기여도가 높은 세그먼트를 확인한 뒤, 해당 세그먼트의 연령대, 주 구매 품목, 프로모션 반응을 함께 보면 더 구체적인 마케팅 전략을 세울 수 있다. 예를 들어 3등급 세그먼트가 매출 기여도가 높고, 중장년층 비중이 높으며, 특정 품목 구매 금액이 높게 나타난다면 해당 고객군을 대상으로 맞춤형 상품 추천이나 프로모션을 설계할 수 있다.
👥 퍼널 분석, 코호트 분석, RFM 분석 비교
이번에 배운 세 가지 분석은 모두 고객 행동을 이해하기 위한 분석 방법이다. 하지만 각각의 분석 목적은 다르다.
📍퍼널 분석
퍼널 분석은 사용자가 목표 행동에 도달하기까지의 단계별 흐름을 분석한다. 사용하면 좋은 상황은 다음과 같다.
회원가입 전환율이 낮을 때
장바구니에서 구매로 이어지지 않을 때
결제 단계에서 이탈이 많을 때
서비스 내 특정 목표 행동까지의 경로를 개선하고 싶을 때
핵심 질문은 다음과 같다.
사용자는 어느 단계에서 가장 많이 이탈하는가?
즉, 퍼널 분석은 사용자의 행동 흐름 중 막히는 지점을 찾는 데 적합하다.
📍코호트 분석
코호트 분석은 특정 기준으로 묶인 사용자 집단이 시간이 지나면서 어떻게 변화하는지 분석한다. 사용하면 좋은 상황은 다음과 같다.
신규 가입자의 재방문율을 보고 싶을 때
첫 구매 고객의 재구매율을 보고 싶을 때
특정 기간에 유입된 고객의 유지율을 비교하고 싶을 때
서비스 개선 이후 유입된 고객이 더 오래 남는지 보고 싶을 때
핵심 질문은 다음과 같다.
특정 시점이나 행동을 공유한 고객들은 시간이 지나도 유지되는가?
즉, 코호트 분석은 시간에 따른 고객 행동 변화를 확인하는 데 적합하다.
📍RFM 분석
RFM 분석은 구매 고객의 가치를 평가하고 세분화하는 분석이다. 사용하면 좋은 상황은 다음과 같다.
우수 고객을 찾고 싶을 때
고객 등급을 나누고 싶을 때
고객군별 마케팅 전략을 세우고 싶을 때
구매 데이터 기반으로 고객 가치를 평가하고 싶을 때
핵심 질문은 다음과 같다.
최근에, 자주, 많이 구매한 고객은 누구인가?
즉, RFM 분석은 고객을 가치 기준으로 나누고 세그먼트별 전략을 세우는 데 적합하다.
👏마무리
이번 이론을 배우면서 가장 크게 느낀 점은 분석 방법을 단순히 외우는 것보다 상황에 맞는 분석 방법을 선택하는 것이 훨씬 중요하다는 점이었다.
퍼널 분석은 사용자의 흐름에서 어디가 막히는지 찾는 데 적합하다. 코호트 분석은 시간이 지나면서 고객이 얼마나 남는지 확인하는 데 적합하다. RFM 분석은 구매 고객을 가치 기준으로 나누고, 세그먼트별 전략을 세우는 데 적합하다. 즉, 세 분석은 서로 대체 관계라기보다는 문제 상황에 따라 다르게 활용되는 도구라고 볼 수 있다. 예를 들어 신규 가입자는 늘고 있는데 전체 매출이 정체되어 있다면, 먼저 퍼널 분석으로 어느 단계에서 구매 전환이 막히는지 확인할 수 있다. 그다음 특정 시기에 유입된 고객들이 시간이 지나면서 얼마나 유지되는지 보고 싶다면 코호트 분석을 사용할 수 있다. 그리고 실제 구매 고객 중 어떤 고객군에 집중해야 할지 판단하고 싶다면 RFM 분석을 사용할 수 있다.
앞으로 실제 프로젝트를 할 때도 단순히 그래프를 많이 만드는 것보다, 먼저 문제 상황을 정의하고 그 상황에 맞는 분석 프레임워크를 선택하는 연습이 필요하다고 느꼈다. 결국 데이터 분석이 비즈니스 개선으로 이어지려면 '어떤 분석을 왜 하는지'를 설명할 수 있어야 한다. 이번 이론은 퍼널, 코호트, RFM이라는 각각의 분석 개념도 중요했지만, 결국 데이터 분석이 실제 의사결정으로 이어지기 위해서는 분석 목적과 방법론이 잘 연결되어야 한다는 점을 다시 생각하게 해준 수업이었다.
'Codeit Sprint > 공부 기록' 카테고리의 다른 글
| 사용자 행동 로그 설계 | 문제 정의부터 Event, Attribute, Trigger, Tracking Plan까지 전체 정리 (2) | 2026.05.07 |
|---|---|
| 데이터 기반 프로덕트 개선 프로세스 | 지표 진단부터 A/B 테스트까지 (0) | 2026.05.07 |
| 지표 이해하기 | 프로덕트, AARRR 프레임워크, 비즈니스 모델별 주요 지표까지 전체 정리 (1) | 2026.04.28 |
| SQL로 하는 데이터 분석 | SQL 기초부터 함수, JOIN, 서브쿼리, CTE까지 전체 정리 (1) | 2026.04.28 |
| Tableau로 대시보드 제작하기 | BI 개념부터 다양한 차트, 분석 기능, 대시보드 제작까지 전체 정리 (1) | 2026.03.30 |
