부트캠프

멋쟁이사자처럼 부트캠프 그로스마케팅 4기 30일차_260421

Yuuma 2026. 4. 21. 18:02
Day 14 | 전자상거래 퍼널 분석
DAY 14  ·  그로스마케팅 부트캠프

데이터 수집 및 전처리
전자상거래 퍼널 분석

실제 이커머스 데이터로 배우는 퍼널 분석의 모든 것.
"왜 고객이 이탈하는가?"에 대한 답을 데이터로 찾아가는 여정.

📅 2026.04.21 🗃️ Retailrocket Dataset 🐍 Python · pandas · matplotlib 📊 275만+ 이벤트 분석

1전자상거래 이벤트 데이터의 특성

일반 이벤트 vs 전자상거래 이벤트

이전 수업에서 다룬 일반 이벤트 데이터는 "누가, 무엇을, 언제"라는 기본 구조였습니다. 전자상거래 이벤트 데이터는 여기에 "구매 여정의 단계"라는 개념이 추가됩니다. 각 이벤트는 독립적으로 존재하는 것이 아니라, 구매라는 목표를 향한 흐름 위에 존재합니다.

일반 이벤트 vs 전자상거래 이벤트
EXAMPLE
# 일반 이벤트: 각 이벤트가 독립적으로 존재
user_id | event     | timestamp
--------|-----------|----------
101     | page_view | 09:00
101     | scroll    | 09:01
101     | click     | 09:02

# 전자상거래 이벤트: 구매 흐름 위에 존재
user_id | event       | timestamp | item_id | 퍼널 단계
--------|-------------|-----------|---------|----------
101     | view        | 09:00     | A001    | 1단계: 탐색
101     | addtocart   | 09:03     | A001    | 2단계: 의향
101     | transaction | 09:08     | A001    | 3단계: 전환

GA4 표준 퍼널 계층 구조

전자상거래 이벤트는 단순히 나열되는 것이 아니라 계층 구조를 갖습니다. 각 단계에서 다음 단계로 넘어가지 못한 사용자의 이탈률을 드롭오프율(Drop-off Rate)이라고 합니다.

GA4 표준 퍼널
STRUCTURE
session_start    ← 방문 시작
      |
      v
view_item        ← 상품 상세 조회
      |
      v
add_to_cart      ← 장바구니 담기
      |
      v
begin_checkout   ← 결제 시작
      |
      v
purchase         ← 구매 완료

실제 데이터에서 마주치는 현실적인 문제 4가지

문제내용예시
비순차적 이벤트사용자가 반드시 순서대로 이동하지 않음view → addtocart → view → view → transaction
반복 구매같은 사용자가 퍼널을 여러 번 완주세션 단위로 나눠서 분석 필요
다중 상품한 거래에 여러 상품 포함 가능이벤트 1건 ≠ 상품 1개
시간 기반 세션 분리GA4의 30분 비활성 기준을 직접 구현세션 분리 로직 직접 코딩 필요

2퍼널 분석이란 무엇인가

퍼널(Funnel)은 깔때기를 의미합니다. 많은 사람이 입구로 들어오지만, 최종 목표(구매)에 도달하는 사람은 점점 줄어드는 모습이 깔때기와 닮아있어요. 마케터의 역할은 단순히 각 단계의 숫자를 보는 것이 아니라, 어느 구간에서 가장 많이 이탈하는지를 찾아내는 것입니다.

퍼널 분석 인포그래픽

퍼널 분석 핵심 지표 3가지

지표계산식의미
단계별 전환율다음 단계 수 ÷ 현재 단계 수각 단계를 통과하는 비율
드롭오프율1 − 단계별 전환율각 단계에서 이탈하는 비율
전체 전환율최종 단계 수 ÷ 첫 단계 수퍼널 전체의 효율
🎯

마케터의 핵심 업무: 이탈 구간을 찾으면 다음 질문으로 이어집니다.

· 장바구니 → 결제 시작 50% 이탈 → 왜 결제를 시작하지 않았나? (배송비 노출? 로그인 강요?)
· 결제 시작 → 구매 완료 40% 이탈 → 왜 결제를 완료하지 않았나? (UI 문제? 결제 수단 부족?)

이 가설을 데이터로 검증하고 개선하는 것이 그로스 마케터의 핵심 업무입니다.

3실습 데이터셋 : Retailrocket

실제 이커머스 웹사이트에서 4.5개월간 수집된 실제 사용자 행동 데이터입니다. 개인정보 보호를 위해 모든 값이 해시 처리되어 있으며, view / addtocart / transaction 세 가지 이벤트만 존재해 퍼널 분석에 최적화되어 있습니다.

Retailrocket 데이터셋 구조

파일별 컬럼 상세

파일컬럼명타입설명
events.csvtimestampint이벤트 발생시각 (Unix timestamp, 밀리초)
visitoridint방문자 고유 ID
eventstringview / addtocart / transaction
itemidint상품 고유 ID
transactionidfloat거래 ID (transaction 이벤트에만 존재, 나머지는 NaN)
item_propertiestimestampint속성 기록 시각
itemidint상품 고유 ID
propertystring속성 이름 (categoryid, price, available 등)
valuestring속성 값
category_treecategoryidint카테고리 고유 ID
parentidfloat상위 카테고리 ID (최상위는 NaN)

4전처리 실습 : 전자상거래 이벤트 처리

전처리 로드맵

기본 설정 및 파일 불러오기

준비 라이브러리 임포트 & 3개 파일 불러오기
setup.py
PYTHON
import pandas as pd
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')

pd.set_option('display.max_columns', None)
pd.set_option('display.float_format', '{:.2f}'.format)

# 이벤트 파일
events = pd.read_csv('events.csv')

# 상품 속성 파일 2개 세로로 이어붙이기
props1 = pd.read_csv('item_properties_part1.csv')
props2 = pd.read_csv('item_properties_part2.csv')
item_props = pd.concat([props1, props2], ignore_index=True)

# 카테고리 트리 파일
category_tree = pd.read_csv('category_tree.csv')
⇒ 실습 데이터 규모
events       :  2,756,101행  × 5열
item_props   : 20,275,902행  × 4열
category_tree:    1,669행   × 2열
이벤트 타입 분포 확인
PYTHON
print("건수")
print(events['event'].value_counts())

print("비율(%)")
print(events['event'].value_counts(normalize=True) * 100)
⇒ 실행 결과
              건수       비율(%)
view      2,664,312    96.67
addtocart    69,332     2.52
transaction  22,457     0.81
여기서 이미 퍼널의 전체 윤곽이 보입니다. 상품을 조회한 사람 중 약 2.5%만 장바구니에 담고, 0.8%만 실제로 구매합니다. 데이터를 불러오자마자 이탈 규모가 눈에 들어오는 것이 전자상거래 데이터의 특징입니다.
실습 01 timestamp 변환 : Unix 밀리초 → datetime
목적 : 숫자 타임스탬프를 datetime으로 변환하고 날짜 파생 컬럼 생성
lab01_timestamp.py
PYTHON
print("변환 전:", events['timestamp'].iloc[0])
# 출력: 1433221332117  ← 숫자라 날짜 연산 불가!

# Unix 밀리초 → datetime 변환  (unit='ms' : 밀리초 단위임을 명시)
events['timestamp'] = pd.to_datetime(events['timestamp'], unit='ms')

# 날짜 파생 컬럼 생성
events['date']        = events['timestamp'].dt.date
events['hour']        = events['timestamp'].dt.hour
events['day_of_week'] = events['timestamp'].dt.day_name()
events['week']        = events['timestamp'].dt.isocalendar().week.astype(int)

print(f"수집 기간: {(events['timestamp'].max() - events['timestamp'].min()).days}일")
⇒ 실행 결과
변환 전: 1433221332117
수집 기간: 137일
📌 함수 설명
pd.to_datetime(unit='ms')Unix 타임스탬프를 날짜/시간 형식으로 변환. unit='ms'는 밀리초 단위를 알려주는 옵션. 없으면 1,000배 커져서 3000년대 날짜가 나옴!
.dt.date / .dt.hourdatetime 컬럼에서 날짜·시간·요일·주차를 꺼내는 접근자. 엑셀의 YEAR(), MONTH() 함수와 비슷한 역할
.dt.isocalendar().weekISO 표준 주차 번호 추출. 주차별 전환율 추이 분석에 사용
실습 02 transactionid 결측치 구조 파악
목적 : 결측치가 의도된 것인지 오류인지 판별하는 방법 이해
lab02_null_check.py
PYTHON
# 이벤트 종류별로 그룹을 나눠서 결측치 비율 확인
for event_name, group in events.groupby('event'):
    null_count  = group['transactionid'].isnull().sum()
    total_count = len(group)
    null_ratio  = null_count / total_count * 100
    print(f" {event_name}: {null_count}건 ({null_ratio}%)")

# 고유 거래 수 확인 (transaction 이벤트에서)
unique_tx = (
    events[events['event'] == 'transaction']
    ['transactionid'].nunique()
)
print(f"고유 거래 수: {unique_tx:,}건")
⇒ 실행 결과
addtocart   : 69,332건 (100.0%)
transaction :      0건 (0.0%)    ← 이것만 값 있음
view        : 2,664,312건 (100.0%)

고유 거래 수: 17,672건
transaction 이벤트에만 거래 ID가 있고 나머지는 100% 결측치 → 이건 오류가 아니라 의도된 구조! 거래 ID는 실제 구매가 발생했을 때만 생성됩니다. 고유 거래 수(17,672)가 transaction 이벤트 수(22,457)보다 적은 이유? 한 번의 거래에서 여러 상품을 구매했기 때문입니다.
📌 함수 설명
.groupby('event')이벤트 종류별로 데이터를 나누는 함수. for문과 함께 쓰면 각 그룹을 하나씩 꺼내 분석 가능
.isnull().sum()결측치(빈 칸) 개수를 세는 함수. isnull()은 빈 칸이면 True, .sum()으로 True의 개수를 합산
.nunique()중복 제거 후 고유한 값의 개수를 세는 함수. 같은 거래 ID가 여러 줄이어도 1개로 카운트
실습 03 중복 이벤트 탐지 및 처리
목적 : 진짜 중복(오류)과 정상적인 반복 이벤트를 구분하여 처리
lab03_duplicates.py
PYTHON
print(f"처리 전 행 수: {len(events):,}")

# 완전히 동일한 행 확인 (timestamp까지 같아야 진짜 중복)
exact_duplicates = events.duplicated().sum()
print(f"완전 중복 행 수: {exact_duplicates:,}")

events = events.drop_duplicates()
print(f"처리 후 행 수: {len(events):,}")

# 정상적인 반복 이벤트 확인 (같은 사용자가 같은 상품을 여러 번 조회)
repeat_view = (
    events[events['event'] == 'view']
    .groupby(['visitorid', 'itemid'])
    .size()
    .reset_index(name='view_count')
)
print(f"동일 상품을 2번 이상 조회한 사용자-상품 쌍: {(repeat_view['view_count'] >= 2).sum():,}건")
⇒ 실행 결과
처리 전 행 수: 2,756,101
완전 중복 행 수: 460
처리 후 행 수: 2,755,641
동일 상품을 2번 이상 조회한 사용자-상품 쌍: 306,548건

핵심 개념: 같은 사용자가 같은 상품을 여러 번 조회하는 것은 오류가 아닙니다. 오히려 구매 의향이 높다는 신호일 수 있어요. timestamp까지 완전히 동일한 행만 진짜 중복으로 처리합니다.

실습 04 상품 속성 파일 피벗 : Long 형식 → Wide 형식
목적 : Long 형식 상품 속성 데이터를 Wide 형식으로 변환하여 이벤트 데이터와 결합 가능하게 만들기
lab04_pivot.py
PYTHON
# 분석에 필요한 주요 속성만 추출
key_properties = ['categoryid', 'available']
item_filtered = item_props[item_props['property'].isin(key_properties)].copy()

# 동일 상품+속성에서 가장 최신 값만 유지
item_props_latest = (
    item_filtered
    .sort_values('timestamp', ascending=False)
    .drop_duplicates(subset=['itemid', 'property'])
)

# Long → Wide 변환 (pivot)
item_wide = item_props_latest.pivot(
    index='itemid',
    columns='property',
    values='value'
).reset_index()
📌 함수 설명
.isin(리스트)컬럼 값이 리스트 안에 있는 행만 필터링. 엑셀에서 특정 값만 골라내는 VLOOKUP과 비슷한 느낌
.sort_values(ascending=False)내림차순 정렬. 가장 최신 timestamp가 위로 올라옴
.pivot(index, columns, values)세로(Long) → 가로(Wide) 변환의 핵심 함수. index는 행, columns는 새 컬럼, values는 채울 값
실습 05 이벤트 데이터와 상품 속성 결합
목적 : events와 item_wide를 itemid 기준으로 LEFT JOIN
lab05_merge.py
PYTHON
# LEFT JOIN: 이벤트는 모두 유지, 상품 속성은 있으면 붙이고 없으면 NaN
events_enriched = events.merge(item_wide, on='itemid', how='left')

print(f"결합 후 행 수: {len(events_enriched):,}")
print(f"컬럼: {list(events_enriched.columns)}")
📌 함수 설명
.merge(how='left')두 데이터프레임을 합치는 함수. how='left'는 LEFT JOIN으로, 왼쪽 테이블(events)의 모든 행을 유지하고 오른쪽(item_wide)은 매칭되면 붙임. SQL의 LEFT JOIN과 동일
실습 06 방문자별 이벤트 시퀀스 구성
목적 : 방문자별 이벤트 흐름을 시간순으로 정렬하고 순서 번호 부여
lab06_sequence.py
PYTHON
# 방문자별, 시간순으로 정렬
events_enriched = events_enriched.sort_values(
    ['visitorid', 'timestamp']
).reset_index(drop=True)

# 방문자 내에서 이벤트 순서 번호 부여
events_enriched['event_rank'] = (
    events_enriched.groupby('visitorid').cumcount() + 1
)

# 이벤트 약어 매핑: view→V, addtocart→A, transaction→T
event_abbr = {'view': 'V', 'addtocart': 'A', 'transaction': 'T'}
events_enriched['event_abbr'] = events_enriched['event'].map(event_abbr)

# 방문자별 이벤트 시퀀스 문자열 생성
visitor_sequences = (
    events_enriched.groupby('visitorid')['event_abbr']
    .apply(' → '.join)
    .reset_index()
    .rename(columns={'event_abbr': 'event_sequence'})
)
⇒ 가장 빈번한 이벤트 시퀀스 패턴 TOP 5
V                998,984   ← 조회 1번 후 이탈이 가장 많음
V → V            199,117
V → V → V         73,287
V → V → V → V     34,259
V → A              5,457   ← 장바구니까지 간 패턴
대부분의 사용자가 view 단계에서 이탈하고 있으며 addtocart로의 전환은 매우 낮습니다. 반복 조회 패턴이 다수 존재하는 점을 고려할 때, 고의도 사용자에 대한 리타겟팅 전략이 핵심 개선 포인트입니다.
📌 함수 설명
.cumcount()그룹 내에서 행 번호를 0부터 부여. +1 하면 1부터 시작. "이 방문자의 첫 번째 이벤트, 두 번째 이벤트..." 순서를 매기는 역할
.map(딕셔너리)딕셔너리를 참조해서 값을 변환. 'view'→'V', 'addtocart'→'A' 처럼 일괄 치환
.apply(' → '.join)그룹 내 값들을 ' → '로 이어붙여 하나의 문자열로 만듦. V, A, T를 'V → A → T'로 변환

5전처리 실습 : 퍼널 분석을 위한 데이터 정제

실습 08 방문자별 퍼널 도달 단계 분류
목적 : 방문자를 퍼널 단계별로 그룹화하여 각 단계의 사용자 수 계산
lab08_funnel_classify.py
PYTHON
# 방문자별 발생한 이벤트를 집합(set)으로 묶기
visitor_events = (
    events_enriched.groupby('visitorid')['event']
    .apply(set)     # ['view','view','addtocart'] → {'view','addtocart'}
    .reset_index()
    .rename(columns={'event': 'event_set'})
)

# 퍼널 단계 분류 함수 정의
def classify_funnel(event_set):
    if 'transaction' in event_set:   return '3_transaction'
    elif 'addtocart' in event_set: return '2_addtocart'
    elif 'view' in event_set:       return '1_view'
    else:                              return '0_other'

visitor_events['funnel_stage'] = visitor_events['event_set'].apply(classify_funnel)
funnel_counts = visitor_events['funnel_stage'].value_counts().sort_index()
📌 함수 설명
.apply(set)각 방문자의 이벤트 목록을 집합(set)으로 변환. view가 100번이어도 {'view'} 하나로 표현됨. 중복 제거 효과
def classify_funnel()퍼널 단계를 분류하는 커스텀 함수. 숫자 접두어(3_, 2_)를 붙여 sort_index() 시 자동 정렬
.apply(함수명)각 행에 함수를 적용. for문 없이 열 전체에 한 번에 적용하는 pandas의 핵심 패턴
실습 10 장바구니 이탈 사용자 추출 : 리타겟팅 세그먼트
목적 : 장바구니에 담았지만 구매하지 않은 방문자를 추출하여 리타겟팅 세그먼트로 활용
lab10_retargeting.py
PYTHON
# addtocart는 있지만 transaction이 없는 방문자 필터링
check_list = []
for event_set in visitor_events['event_set']:
    if 'addtocart' in event_set and 'transaction' not in event_set:
        check_list.append(True)
    else:
        check_list.append(False)

cart_no_purchase = visitor_events[check_list].copy()

# 이 방문자들이 담은 상품 목록 추출 → 리타겟팅용 CSV 저장
cart_items = events_enriched[
    (events_enriched['event'] == 'addtocart') &
    (events_enriched['visitorid'].isin(cart_no_purchase['visitorid']))
][['visitorid', 'itemid', 'categoryid', 'timestamp']].copy()

cart_items.to_csv('retargeting_segment_cart_abandon.csv', index=False)
📢

실무 활용: 이 사용자 목록을 메타·구글 광고 플랫폼에 업로드하거나, CRM 시스템에 연동하여 "장바구니 리마케팅 이메일"을 발송하는 데 활용합니다. 데이터에서 바로 광고 대상을 추출하는 것이 그로스 마케팅의 핵심 실무입니다!

실습 11 주차별 퍼널 전환율 추이 분석
목적 : 주차별로 퍼널 전환율 변화를 추적하여 시계열 트렌드 파악
lab11_weekly_trend.py
PYTHON
# 주차별 이벤트 타입 집계 → 전환율 계산
weekly_funnel = (
    events_enriched.groupby(['week', 'event'])
    .size().unstack(fill_value=0).reset_index()
)

weekly_funnel['view_to_cart_rate'] = (
    weekly_funnel['addtocart'] / weekly_funnel['view'] * 100
).round(2)

weekly_funnel['cart_to_transaction_rate'] = (
    weekly_funnel['transaction'] / weekly_funnel['addtocart'] * 100
).round(2)

# 시각화
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
axes[0].plot(weekly_funnel['week'], weekly_funnel['view_to_cart_rate'],
             marker='o', color='#7B6CF6', linewidth=2)
axes[0].set_title('주차별 view → addtocart 전환율')

axes[1].plot(weekly_funnel['week'], weekly_funnel['cart_to_transaction_rate'],
             marker='o', color='#FB923C', linewidth=2)
axes[1].set_title('주차별 addtocart → transaction 전환율')
plt.tight_layout()
plt.show()
전환율이 특정 주에 급격히 떨어진다면 프로모션 종료, 사이트 장애, 경쟁사 이슈 등을 확인해야 합니다. 반대로 특정 주에 급격히 올랐다면 그 주에 시행한 마케팅이 효과가 있었던 것입니다. 이것이 시계열 분석의 힘입니다.

6전처리 작업 전체 요약

실습 01
timestamp 변환
pd.to_datetime(unit='ms')
실습 02
결측치 구조 파악
.groupby().apply()
실습 03
중복 이벤트 처리
.drop_duplicates()
실습 04
Long → Wide 피벗
.pivot()
실습 05
이벤트 + 속성 결합
.merge(how='left')
실습 06
이벤트 시퀀스 구성
.cumcount() / .apply()
실습 07
카테고리별 분포
.groupby().unstack()
실습 08
퍼널 단계 분류
apply(set) + 커스텀 함수
실습 09
퍼널 시각화
matplotlib 막대 차트
실습 10
장바구니 이탈 추출
조건 필터링 + CSV 저장
실습 11
주차별 추이 분석
.groupby(week).unstack()

이번 강의의 핵심 개념 3가지

🔍

전자상거래 데이터의 특수성
이벤트 데이터는 각각 독립적으로 보지 않고 구매 여정의 흐름으로 봐야 합니다. 어떤 이벤트가 결측치인지 오류인지는 비즈니스 맥락을 이해해야 판단할 수 있습니다.

🔄

Long 형식과 Wide 형식
실무 데이터는 장기 보관에 유리한 Long 형식으로 저장됩니다. 분석을 위해 Wide 형식으로 변환하는 것은 필수적인 전처리 과정입니다.

🎯

퍼널 분석의 실무 출력물
전환율 수치 자체보다 "어느 구간에서 왜 이탈하는가"에 대한 가설과, 그 가설을 검증하기 위한 세그먼트(예: 장바구니 이탈 사용자)를 추출하는 것이 진짜 업무입니다.