2번째 과제 "리텐션분석"입니다.
아까 퍼널분석 때 힘 다 썼어요. 힘들어요 엉엉
목표는 "뷰티 기업인 GlowCos의 리텐션 분석 "
Attitude는 "quick quick"
할 일 정리~
1. 과제 파악
2. 데이터셋 확인
3. 데이터 분석
- 데이터불러오기 / 데이터 파악 / 데이터 정제 / 분석을 위한 데이터 가공 / 마케팅 지표 집계
4. 인사이트 도출
1. 과제 파악
화장품 온라인몰 "GlowCos"는 자체 플랫폼을 통해 스킨케어, 메이크업, 향수 등을 판매하는 브랜드
■ 과업 내용
- 문제설정: 고객 주문 리텐션(잔존율)을 분석하려고함
- 활용 데이터셋: 2019년 10월~12월(총 3개월)의 고객 이벤트 로그 데이터
■ 산출물
- 리텐션 분석
리텐션 분석하는 목적이 뭐겠어요..
리텐션 적은 덴 높이고 리텐션 높은덴 더 높이려는 거겠죠.
인사이트에 분석한 리텐션을 바탕으로 마케팅 액션을 도출할 생각하면서 분석해볼게요..
리텐션 분석을 할 때 어떤 기준으로 리텐션을 확인할 것이냐부터 생각하고 시작해야되는데요.
월별 구매자 기준으로 코호트 분석하는 것이 가장 바람직하다네요
2. 데이터셋 확인

지금보니, 세션 컬럼도 리텐션 분석에 활용할 수 있는 데이터가 아니었나 싶네요.
3. 데이터 분석
1) 데이터 불러오기
이번에는 코랩 마크다운에 사용할 함수 다 적고 시작하고 싶었는데
적다보니까 저는 처음부터 다 예상하고 코드 짤 수 있는 수준이 아니더라구요.
제미나이랑 얘기해서 갈래만 잡고 실행해봤습니다.


user session 값이 되게 신기했어요. UUID라는 고유번호라는데 궁금해서 찾아봤습니다.
■ user_session으로 분석할 수 있는 내용들
① 방문당 페이지 뷰 (Depth)
- 분석: 한 세션 안에 이벤트(event_type)가 몇 개나 찍혔는지 봅니다.
- 의미: 숫자가 높을수록 우리 앱을 여기저기 많이 구경했다는 뜻입니다(몰입도).
② 세션당 체류 시간 (Duration)
- 분석: 한 세션의 첫 이벤트 시간과 마지막 이벤트 시간의 차이를 구합니다.
- 의미: 너무 짧으면 들어오자마자 나간 거고(이탈), 너무 길면 고민을 엄청나게 하고 있다는 뜻입니다.
③ 구매 전환율 (Session-based CVR)
- 분석: (구매가 일어난 세션 수 / 전체 세션 수)
- 의미: "이 사람이 우리 앱에 한 번 들어왔을 때 살 확률이 얼마나 되지?"를 알 수 있습니다.
※ 분석한 내용 활용법
| 분석 결과 | 해석 | 마케팅 액션(활용) |
| 세션 수 多 , 구매 0 | 구경은 자주 오는데 결심을 안함 | -crm 푸시알림 발송 (장바구니 상품, 할인중) |
| 한 세션 내 체류시간이 너무 짧음 | 들어오자마자 나감 | -첫화면의 UI/UX 개선 - hero 섹션 -상품 추천 로직 변경 -로딩 딜레이 개선 |
| 특정 세션에서 cart만 하고 끝남 | 배송비나 결제과정에서 이탈했을 확률 높음 | -결제 페이지 단계 축소 -무료배송 쿠폰 증정 |
2) 데이터셋 파악
event_time이 object로 되어있네요. 날짜로 변환해줘야겠어요.

3) 데이터셋 정제
정제할 내용은 다음의 3가지
- user_id 문자화
: 이건 왜 문자화해야하는지 모르겠어서 강사님께 질문했어요.. - event_time 날짜화
- purchase만 필터링
: event_type을 보면 view, cart, remove_from_cart, purchase 총 4가지 타입이 있는데 이중에서 우리는 '구매 전환율'을 분석할거니까 purchase만 있는 데이터를 만들어주는 게 우선이라는 의미입니다.

우선 df_10 데이터에 코드 적용해보고 잘 나오는지 확인 후에 나머지 데이터에도 적용해줍니다.(이래놓고 나머지 월데이터에 적용안해서 뒤에가서 재구매일 계산 오류남ㅠㅠ)
#1. user_id 문자화
df_10['user_id'] = df_10['user_id'].astype(str)
#2. event_time 날짜화 : 분까지만 표시
df_10['event_time'] = pd.to_datetime(df_10['event_time'], utc=True) #utc 세계표준시 맞춰라
df_10['event_time'] = df_10['event_time'].dt.strftime('%Y-%m-%d %H:%M')
#3. 주문리텐션 집계를 위해 purchase만 필터링
df_10_pur = df_10[df_10['event_type'] == 'purchase'].copy() #원본 건들지말기

4) 데이터 가공
여기서 해야할 일은
- 주문 겁수 집계
: 한 번 구매할 때 여러개 구매하면 여러행으로 나오기 때문에, 동일 유저가 같은 시간대 구매한 건 주문 1건으로 묶어줄거예요. 우리가 해야할 리텐션은 "재구매를 했냐, 안했냐"가 중요하지 "재구매시 상품 몇 개를 샀느냐"에 초점맞추는 게 아니니까요. - 5개월 데이터 병합
: 시간의 흐름순이니까 데이터를 아래로 붙여줄게요 → concat 사용! (컬럼기준 붙이는건 merge)
<주문 건수 집계 코드 해석>
df_10_orders = df_10_pur.groupby(['user_id', 'event_time']).size().reset_index(name='product_num_df_10')
# [1] df_10_orders = : 계산한 결과를 이 이름의 '새 변수'에 담아라
# [2] df_10_pur : 10월 구매(purchase) 데이터 서랍을 열어서
# [3] .groupby(['user_id', 'event_time']) : '유저ID'와 '결제시간'이 '동시에' 똑같은 것끼리 한 그룹으로 묶어라
# [4] .size() : 그 묶음(그룹) 안에 데이터가 몇 줄 들어있는지 개수를 세어라
# [5] .reset_index(name='product_num_df_10') : 묶여있던 데이터를 다시 '표' 모양으로 예쁘게 펴주고, 아까 센 개수 컬럼에 '상품개수'라는 이름을 붙여라


※ .size()와 .count()의 차이
왜 groupby하고나서 size()로 데이터 줄 세는지 이해가 안돼서 찾아봤는데요.
count는 특정 컬럼 데이터수 측정이라서 컬럼명 지정해줘야하니 코드가 길어지고 복잡해지고요.
size는 그룹 행 수를 측정하는 함수라 groupby도 했겠다 + 컬럼명지정도 필요없겠다 = 더 간단하고 쉬운 코드니 사용하라는 겁니다.

이제 월별로 주문 중복값도 없으니
리텐션을 구하기 위해 기준일을 만들고 리텐션 구하는 식을 만들어볼게요.
리텐션 구하는 함수 방법이 생각보다 너무 많았어요.
제미나이랑 대화할수록 저는 더 바보가 됩니다.
1. 기준일 지정 = 첫구매일 구하기(=first_order)
first_order_date = total_orders.groupby('user_id')['event_time'].min().reset_index()
# [1] total_orders.groupby('user_id') : 전체 데이터를 '유저 아이디'별로 묶을 건데
# [2] ['event_time'] : event_time 컬럼에서
# [3] .min() : 가장 작은 값(즉, 가장 과거인 첫 구매 시간)을 딱 하나씩만 골라라
# [4] .reset_index() : 묶여있던 데이터를 다시 우리가 보기 편한 '표' 모양으로 예쁘게 펴줘
# [5] first_order_date = : 이 결과물을 'first_order_date'라는 새 이름표를 붙여 저장해
first_order_date.columns = ['user_id', 'first_order']
# [1] first_order_date.columns : 'first_order_date' 표의 맨 윗줄 이름표들을
# [2] = ['user_id', 'first_order'] : 순서대로 'user_id'랑 'first_order'로 변경해
2. 첫구매일 병합
df_final = pd.merge(total_orders, first_order_date, on='user_id')
# [1] df_final = : 두 표를 합친 결과를 'df_final'이라는 최종 표(변수)에 담아라
# [2] pd.merge( : 판다스야, 지금부터 두 개의 표를 옆으로 합쳐줘)
# [3] total_orders, : 첫 번째 재료는 모든 구매 기록이 있는 'total_orders' 표고,
# [4] first_order_date, : 두 번째 붙일 건 아까 구한 '유저별 첫 구매일' 명단이야
# [5] on='user_id' : 두 표에서 'user_id'가 똑같은 것끼리 맞춰서 붙여줘
이렇게 하면,
이렇게 total_orders에서 order_date와 first_order 컬럼이 생겼어요
order_date는 event_time(purchase)의 일자고
first_order는 첫구매일이죠

※ 시행착오
for 반복문 쓰면 되겠지~ 하면서 코드만들어보니 이렇게 만들면 직전달과 현재달만 비교가능하니 전반적인 흐름은 못보는 데이터가 되더라구요. 그래서 정리하면 직전달 비교방식으로는 코호트 분석이 어려우니 그로스마케팅에 적합하지 않고 그닥 유의미한 데이터분석도 아닌거죠.
그냥 가장 효율적인 방법을 외우고 시작하는게 낫겠다 생각했습니다
# [날짜 형식 변환] 일자 기준 집계 위해서 datetime써서 order_date 만들기
total_orders['order_date'] = pd.to_datetime(total_orders['event_time']).dt.strftime('%Y-%m-%d')
# [파생변수 생성] for반복문 활용해서 유저 재구매 여부 확인하기
month_dfs = [df_10_orders, df_11_orders, df_12_orders, df_01_orders, df_02_orders]
month_names = ['10월', '11월', '12월', '1월', '2월']
for r in range(len(month_dfs)):
#이번달 데이터 + 다음달 데이터
current_df = month_dfs[r]
next_df = month_dfs[r+1]
#다음달 구매자 명단 추출
next_user_list = next_df['user_id'].unique()
#리텐션 여부 컬럼 추가(1 또는 0) , == 사용하면 오류 잦다고 함
#current_df의 user_id가 next_user_list와 일치하면 1이고 retained 변수에 담을 것
current_df['retained'] = current_df['user_id'].isin(next_user_list).astype(int)

5) 마케팅 지표 집계
기준점인 첫구매일도 설정했겠다.
재구매가 생겼다면 재구매일이 얼마나 되는지 + 그 퍼센티지는 어떻게 되는지 구할 수 있습니다.
그럼 재구매일 계산하고 월별로 리텐션율을 구해볼게요
월별 첫구매자 기준으로 리텐션율을 확인해보기로 했으니까,
월별 첫구매자가 왼쪽 y열(2019년 10월 첫구매자, 2019년 11월 첫구매자...)에 들어가고
시계열값(10월, 11월, ...)이 상단 x열에 들어갈 것 같아요.
1. 재구매일 계산
간단하게 생각해보면, "재구매가 일어난 날 - 첫구매일" 하면 되겠죠.
문제는 단위인데.. 전부 월별 데이터고, x열 y열 전부 월 기준이니까 재구매일도 월기준으로 출력해야될 것 같아요.
하지만 날짜는 연-월로 되어있으니까 월로 단위를 맞춰야 합니다.
diff 써줄게요 (=주어진 데이터프레임이나 시리즈의 각 요소와 그 이전 요소의 차이를 계산)
# 연도 차이를 달 수로 바꾸기
year_diff = (df_final['event_time'].dt.year - df_final['first_order'].dt.year) * 12
# [1] df_final['event_time'].dt.year : 이번에 구매한 날짜에서 '연도'만 뽑고 (예: 2020)
# [2] - df_final['first_order'].dt.year : 처음 구매한 날짜의 '연도'를 빼서 (예: 2019)
# [3] * 12 : 그 결과(1년 차이)에 12를 곱해라 (즉, 12개월 차이로 변환)
# 월 차이 구하기
month_diff = (df_final['event_time'].dt.month - df_final['first_order'].dt.month)
# [1] df_final['event_time'].dt.month : 이번에 구매한 날짜의 '월'에서 (예: 1월)
# [2] - df_final['first_order'].dt.month : 처음 구매한 날짜의 '월'을 빼라 (예: 10월)
# [3] month_diff = : 그 결과를 'month_diff'에 담아라 (예: 1 - 10 = -9)
# 최종 '몇 달 차이'인지 합치기
df_final['month_diff'] = year_diff + month_diff
# [1] year_diff + month_diff : 위에서 구한 연도차이(12)와 월차이(-9)를 더해라 (12 + (-9) = 3)
# [2] df_final['month_diff'] = : 이 차이값을 새 컬럼에 저장해
# 기준이 되는 '첫구매월' 생성→ 다음에 구매월과 첫구매월 차이를 기준으로 묶어줄거니까 첫구매월 데이터도 필요
df_final['first_month'] = df_final['first_order'].dt.month
# [1] df_final['first_order'].dt.month : 이 유저가 처음 산 날짜에서 '월'만 쏙 뽑아서
# [2] df_final['first_month'] = : '첫 구매월'이라는 이름의 새 컬럼에 담아라

2. 리텐션율 계산
- 리텐션유저수 집계
- 리텐션율 집계
# 리텐션 유저수 세기
retention_report = df_final.groupby(['first_month', 'month_diff']).agg( 리텐션유저수=('user_id', 'nunique') ).reset_index()
# [1] df_final.groupby(['first_month', 'month_diff']) : '첫구매월'과 '몇 달 뒤인지'를 기준으로 묶고
# [2] .agg(리텐션유저수=('user_id', 'nunique')) : 그 속의 유저들 중 중복을 빼고 "진짜 몇 명(nunique)"인지 세어서 '리텐션유저수'라고 이름 붙여라
# [3] .reset_index() : 꼬여있는 인덱스를 풀어서 보기 좋은 평범한 표로 만들어라
#총유저수 구하기
retention_report['총유저수'] = retention_report.groupby('first_month')['리텐션유저수'].transform('first')
# [1] retention_report.groupby('first_month') : 다시 '첫구매월‘끼리 묶고
# [2] ['리텐션유저수'].transform('first') : '가장 첫 번째 값(0개월 차 인원)'을 복사해서 모든 줄에 '총유저수'라는 이름으로 설정해
*첫구매월이 그 월의 총 유저수가 되겠죠
#리텐션율(%) 구하기
retention_report['리텐션율(%)'] = ((retention_report['리텐션유저수'] / retention_report['총유저수']) * 100 ).round(1)
# [1] (리텐션유저수 / 총유저수) : 전체 중 몇 명이 잔존했는지 나누고
# [2] * 100 : 우리가 아는 퍼센트(%) 단위로 바꿔라
# [3] .round(1) : 소수점 첫째 자리까지만 표시해

이제 보기 쉽게 matrix 표로 봐봅시다
#리텐션유저수 확인
# [1] final_matrix = : 결과물을 'final_matrix'라는 이름의 표에 저장해
# [2] retention_report.pivot_table( : 아까 만든 요약표를 가지고 피벗 테이블 생성해
# [3] index='first_month', : 세로줄(행)에는 '첫 구매월'이 나오게 하고
# [4] columns='month_diff', : 가로줄(열)에는 '몇 달 뒤인지'가 나오게 해
# [5] values='리텐션유저수' : 표 안에는 '리텐션유저수'로 채워 넣어라
# [6] final_matrix : 완성된 인원수 표를 화면에 띄워라
#리텐션율 확인은 values 값만 바꿔주면 완료


피봇테이블을 보면 2019년 10월 첫구매자가 전환율이 항상 높고, 특히 보통 구매전환율은 우하향하기 마련인데
2020년 1월에는 상승하기 까지 했네요.
뭔가 일이 있었던 것 같기두 2019년 10월 첫구매자에게만 좋은 캠페인이 있었는지 2019년 11월 첫구매자의 전환율은 별로인데 말이에요.
아니면 10월 첫구매자가 가입했을 때 뭔가 좋았던 경험이 있거나 혜택이 있거나 상품라인업이 좋다거나 캠페인이 효과적이었다거나 등등
2019년 10월에 진행한 캠페인, 온보딩서비스, 가입자 세그먼트 분석과 2020년 1월에 진행한 캠페인, 온보딩서비스을 살펴볼 것 같아요.
4. 인사이트 도출
💡 월 별로 첫구매자를 분류했을 때, 2019년 10월 첫구매자들의 리텐션율이 두자리수를 유지하면서 다른 그룹보다 월등히 높습니다. 심지어 타 그룹들은 리텐션율이 우하향하지만 2019년 10월 첫구매자들은 2020년 1월에 재구매율이 직전달보다 상승한것으로 나타났습니다.
이에 두 가지 인사이트를 도출할 수 있습니다.
1. 2019년 10월 첫구매자 타겟으로 가입시 진행된 캠페인과 온보딩 서비스를 분석해서 다음달 신규 구매자 타겟으로 적용하면 고객 잔존율이 높아질 것입니다.
2. 2019년 10월 첫구매자들의 퀄리티가 좋으므로 2020년 1월에 진행된 캠페인과 2019년 10월 첫구매자 타겟의 상관관계를 분석하고 이 분석 내용을 기반으로 재구매 혜택 프로모션을 진행하면 재구매 전환율이 상승할 것입니다.
과제 1 퍼널분석보다 더 방황..🪦
리텐션 분석을 위해도 세분화하고 들어가야 하는 게, 코호트 분석( 10월에 처음 온 사람들이 1개월 뒤, 2개월 뒤, 3개월 뒤에 각각 몇 %나 남아있는지)을 하려면, 전체 유저 활동 표(Binary Matrix)를 구해야 유의미합니다.
for 반복문 사용하면 직전 달 비교 방식(Rolling Retention)는 단순 유지율만 확인 가능하니까..
근데 어제 퍼널분석보다 시간은 덜 걸렸어요. 왜냐하면 이해못해도 gemini랑 타협하면서 해서 ㅎㅎ
💡과제 인사이트 : 우선 가장 효율적인 코드를 암기하자 ! 수학 문제 처럼 공식을 외우고나서 응용하자 🥹

'Growth Marketing > Job Preparation' 카테고리의 다른 글
| 마케터 지망생의 데이터 분석 정복기 ①|Pandas로 이커머스 기업 퍼널분석 하기! 🚢 (0) | 2026.04.18 |
|---|---|
| 마케터 지망생의 Figma Site 정복기|LPO전략 설계부터 랜딩페이지 제작까지! (0) | 2026.03.24 |
| 마케터 지망생의 META Pixel 정복기|팝업 이미지 내 버튼 클릭 이벤트 설치하기 ! (0) | 2026.03.24 |
| 마케터 지망생의 GTM 정복기|팝업 이미지 띄우고 링크 연결까지! (0) | 2026.03.23 |
| 마케터의 종류와 특징 비교 - 퍼포먼스 · 브랜드 · 콘텐츠 · CRM · 그로스 (1) | 2026.03.20 |