현재까지 결과
가설 1 : Device 문제 → 기각
가설 2 : 신규 유저 첫 구매 장벽 → 채택
가설 3 : Google 유입 품질 차이 → 부분 채택
가설
가격/객단가 부담
- 가격 부담으로 인해 결제 직전 Purchase 이탈이 발생할 것이다
- 사용자는 구매 의도는 있지만, 결제 금액이 높아질수록 최종 결제를 포기하는가?
1. 가격 관련 컬럼 구조 확인
- items 배열 안에 price와 quantity가 존재하는가?
- purchase_revenue가 purchase 이벤트에서 정상 존재하는가?
- checkout 단계 가격 추정이 가능한가?
-- STEP 1: 가격 관련 컬럼 구조 확인
-- 목적:
-- items.price, quantity, purchase_revenue 사용 가능 여부 확인
SELECT
event_name,
-- 구매 매출
ecommerce.purchase_revenue AS purchase_revenue,
-- 상품 배열 구조 확인
items
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name IN ('add_to_cart', 'begin_checkout', 'purchase')
LIMIT 50;
| 행 | event_name | purchase_revenue | items. item_id | items. item_name | ... |
| 1 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 2 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 3 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 4 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 5 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 6 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 7 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 8 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 9 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
| 10 | begin_checkout | null | 9188233 | Google Phone Stand Bamboo | ... |
2. Checkout 세션별 예상 결제 금액 계산
- 세션별 checkout_cart_value가 정상 계산되는가?
- 극단적으로 높은 이상치가 존재하는가?
- 가격 분포 분석이 가능한 구조인가?
-- STEP 2: Checkout 세션별 예상 결제 금액 계산
-- 목적:
-- begin_checkout 시점의 장바구니 총액(예상 구매 금액)을 계산한다.
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
-- 세션별 장바구니 총액
SUM(items.price * items.quantity) AS checkout_cart_value
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`,
UNNEST(items) AS items
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name = 'begin_checkout'
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
LIMIT 100;
| 행 | session_id | checkout_cart_value |
| 1 | 7296845.4997447007-7961433211 | null |
| 2 | 79725886.5249234769-6011535687 | 180 |
| 3 | 2497858756.0719748889-8157079617 | null |
| 4 | 5476414.5180022673-8978435139 | 468 |
| 5 | 34409419.8726389670-83372344 | 48 |
| 6 | 40569893.9835575904-5568359449 | 210 |
| 7 | 80435974.9462183494-263104117 | 966 |
| 8 | 35618258.9089360337-2510140352 | 708 |
| 9 | 1591325.0235466226-604072130 | 672 |
| 10 | 4568620.3650859083-9889362551 | null |
3. Purchase 여부 결합
- checkout_cart_value와 purchased가 정상 연결되는가?
- 구매 완료/미완료 세션 모두 존재하는가?
- 가격 기준 비교가 가능한가?
-- STEP 3: Checkout 금액 + Purchase 여부 결합
-- 목적:
-- checkout 금액 기준으로 실제 purchase 완료 여부를 확인한다.
WITH checkout_value AS (
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
SUM(items.price * items.quantity) AS checkout_cart_value
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`,
UNNEST(items) AS items
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name = 'begin_checkout'
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
),
purchase_flag AS (
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
MAX(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name IN ('begin_checkout', 'purchase')
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
)
SELECT
c.session_id,
c.checkout_cart_value,
p.purchased
FROM checkout_value c
JOIN purchase_flag p
ON c.session_id = p.session_id
LIMIT 100;
| 행 | session_id | checkout_cart_value | purchased |
| 1 | 1924595.6444212574-7479243854 | 48 | 0 |
| 2 | 1924595.6444212574-5587378648 | 48 | 0 |
| 3 | 5492501.6902517886-7449986104 | 69 | 0 |
| 4 | 5613969.9742050087-9214722322 | 225 | 0 |
| 5 | 6381676.9878308533-4738398471 | null | 1 |
| 6 | 7409046.9406066453-3938627093 | null | 0 |
| 7 | 8269107.5312843213-5328099107 | null | 1 |
| 8 | 8306051.0147360081-647283356 | null | 1 |
| 9 | 10702974.0923572591-7806135330 | null | 1 |
| 10 | 13514937.5999371679-2202912166 | 51 | 1 |
4. 가격대 구간화
- Low / Mid / High 가격대 표본이 충분한가?
- 고가 구간에서 purchase 비율이 낮은가?
- 가격대별 구조 차이가 존재하는가?
-- STEP 4: 가격대 구간별 세션 분류
-- 목적:
-- 가격 구간별 Purchase 전환율 비교를 위해 bucket 생성
WITH final_base AS (
SELECT
c.session_id,
c.checkout_cart_value,
p.purchased
FROM (
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
SUM(items.price * items.quantity) AS checkout_cart_value
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`,
UNNEST(items) AS items
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name = 'begin_checkout'
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
) c
JOIN (
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
MAX(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name IN ('begin_checkout', 'purchase')
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
) p
ON c.session_id = p.session_id
)
SELECT
CASE
WHEN checkout_cart_value < 50 THEN 'Low'
WHEN checkout_cart_value < 150 THEN 'Mid'
ELSE 'High'
END AS price_bucket,
COUNT(*) AS checkout_sessions,
SUM(purchased) AS purchase_sessions
FROM final_base
GROUP BY
price_bucket
ORDER BY
price_bucket;
| 행 | price_bucket | checkout_sessions | purchase_sessions |
| 1 | High | 1345 | 820 |
| 2 | Low | 305 | 65 |
| 3 | Mid | 509 | 230 |
5. 가격대별 Checkout → Purchase 전환율
- High 가격대의 checkout_to_purchase_rate가 Low / Mid보다 낮은가?
- 가격대가 높아질수록 checkout_dropoff_rate가 증가하는가?
- 가격 부담 가설을 채택할 만큼 전환율 차이가 뚜렷한가?
- 특정 가격대의 표본 수가 너무 적어 해석에 주의가 필요한가?
- 고가 상품군에 별도 혜택이나 신뢰 장치가 필요한가?
-- STEP 5: 가격대별 Checkout → Purchase 전환율
-- 목적:
-- 가격 부담 가설 검증
WITH final_base AS (
SELECT
c.session_id,
c.checkout_cart_value,
p.purchased
FROM (
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
SUM(items.price * items.quantity) AS checkout_cart_value
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`,
UNNEST(items) AS items
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name = 'begin_checkout'
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
) c
JOIN (
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
MAX(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name IN ('begin_checkout', 'purchase')
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
) p
ON c.session_id = p.session_id
)
SELECT
CASE
WHEN checkout_cart_value < 50 THEN 'Low'
WHEN checkout_cart_value < 150 THEN 'Mid'
ELSE 'High'
END AS price_bucket,
COUNT(*) AS checkout_sessions,
SUM(purchased) AS purchase_sessions,
ROUND(
SAFE_DIVIDE(SUM(purchased), COUNT(*)) * 100,
2
) AS checkout_to_purchase_rate,
ROUND(
(1 - SAFE_DIVIDE(SUM(purchased), COUNT(*))) * 100,
2
) AS checkout_dropoff_rate
FROM final_base
GROUP BY
price_bucket
ORDER BY
checkout_to_purchase_rate ASC;
| 행 | price_bucket | checkout_sessions | purchase_sessions | checkout_to_purchase_rate | checkout_dropoff_rate |
| 1 | Low | 305 | 65 | 21.31 | 78.69 |
| 2 | Mid | 509 | 230 | 45.19 | 54.81 |
| 3 | High | 1345 | 820 | 60.97 | 39.03 |
6. 구매 완료/미완료 세션의 평균 금액 비교
- Not Purchased 세션의 평균 checkout 금액이 Purchased 세션보다 높은가?
- 중앙값 기준으로도 미구매 세션의 금액 부담이 더 큰가?
- 평균과 중앙값 차이가 커서 이상치 영향을 받고 있지는 않은가?
- 구매 완료 여부에 따라 결제 금액 차이가 실제로 존재하는가?
-- STEP 6: 구매 완료 여부별 평균 Checkout 금액 비교
-- 목적:
-- 구매 완료 세션과 미완료 세션의 평균 checkout 금액을 비교하여
-- 결제 금액이 이탈에 영향을 주었는지 확인한다.
WITH checkout_value AS (
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
SUM(item.price * item.quantity) AS checkout_cart_value
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`,
UNNEST(items) AS item
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name = 'begin_checkout'
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
),
purchase_flag AS (
SELECT
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
MAX(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
AND event_name IN ('begin_checkout', 'purchase')
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
),
final_base AS (
SELECT
c.session_id,
c.checkout_cart_value,
p.purchased
FROM checkout_value c
JOIN purchase_flag p
ON c.session_id = p.session_id
)
SELECT
CASE
WHEN purchased = 1 THEN 'Purchased'
ELSE 'Not Purchased'
END AS purchase_status,
COUNT(*) AS checkout_sessions,
ROUND(AVG(checkout_cart_value), 2) AS avg_checkout_cart_value,
ROUND(APPROX_QUANTILES(checkout_cart_value, 4)[OFFSET(2)], 2) AS median_checkout_cart_value,
ROUND(MIN(checkout_cart_value), 2) AS min_checkout_cart_value,
ROUND(MAX(checkout_cart_value), 2) AS max_checkout_cart_value
FROM final_base
GROUP BY
purchase_status
ORDER BY
purchase_status;
| 행 | purchase_status | checkout_sessions | avg_checkout_cart_value | median_checkout_cart_value | min_checkout_cart_value | max_checkout_cart_value |
| 1 | Not Purchased | 1044 | 853.77 | 111 | 3 | 531156 |
| 2 | Purchased | 1115 | 368.53 | 228 | 9 | 5022 |
전체 코드
-- 4일차 가설 4 최종 코드
-- 가설:
-- Checkout 금액이 높을수록 가격 부담으로 인해 Purchase 전환율이 낮아질 수 있다.
--
-- 검증 방법:
-- begin_checkout 이벤트의 items.price * items.quantity를 기준으로
-- 세션별 예상 결제 금액을 계산하고,
-- 가격대별 Checkout → Purchase 전환율을 비교한다.
--
-- 해석 기준:
-- High 가격대의 전환율이 낮으면 가격 부담 가설 채택
-- 가격대별 전환율 차이가 크지 않으면 가격보다 다른 요인 가능성
WITH checkout_value AS (
SELECT
-- 사용자 ID와 세션 ID를 결합하여 고유 세션 생성
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
-- begin_checkout 시점의 예상 결제 금액
SUM(item.price * item.quantity) AS checkout_cart_value
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`,
UNNEST(items) AS item
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
-- 결제 시작 이벤트 기준
AND event_name = 'begin_checkout'
-- 세션 ID가 없는 이벤트 제외
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
),
purchase_flag AS (
SELECT
-- 사용자 ID와 세션 ID를 결합하여 고유 세션 생성
CONCAT(
user_pseudo_id,
'-',
CAST((
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) AS STRING)
) AS session_id,
-- 해당 세션에서 구매 완료 여부
MAX(CASE WHEN event_name = 'purchase' THEN 1 ELSE 0 END) AS purchased
FROM
`bigquery-public-data.ga4_obfuscated_sample_ecommerce.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20210101' AND '20210131'
-- 결제 시작과 구매 완료 이벤트만 사용
AND event_name IN ('begin_checkout', 'purchase')
-- 세션 ID가 없는 이벤트 제외
AND (
SELECT value.int_value
FROM UNNEST(event_params)
WHERE key = 'ga_session_id'
) IS NOT NULL
GROUP BY
session_id
),
final_base AS (
SELECT
c.session_id,
c.checkout_cart_value,
p.purchased,
-- 가격대 구간 생성
CASE
WHEN c.checkout_cart_value < 50 THEN 'Low'
WHEN c.checkout_cart_value < 150 THEN 'Mid'
ELSE 'High'
END AS price_bucket
FROM checkout_value c
JOIN purchase_flag p
ON c.session_id = p.session_id
)
SELECT
price_bucket,
COUNT(*) AS checkout_sessions,
SUM(purchased) AS purchase_sessions,
ROUND(
SAFE_DIVIDE(SUM(purchased), COUNT(*)) * 100,
2
) AS checkout_to_purchase_rate,
ROUND(
(1 - SAFE_DIVIDE(SUM(purchased), COUNT(*))) * 100,
2
) AS checkout_dropoff_rate,
ROUND(AVG(checkout_cart_value), 2) AS avg_checkout_cart_value
FROM final_base
GROUP BY
price_bucket
ORDER BY
CASE price_bucket
WHEN 'Low' THEN 1
WHEN 'Mid' THEN 2
WHEN 'High' THEN 3
END;
결과 해석
평균만 보면 오해할 수 있음
- Not Purchased 평균 금액: 853.77
- Purchased 평균 금액: 368.53
미구매 세션이 훨씬 비싸서 가격 부담이 원인으로 착각할 수 있음
핵심 해석
이상치 영향이 매우 큼
- Not Purchased 최대값: 531,156
- Purchased 최대값: 5,022
일부 극단적으로 높은 금액 세션이 평균을 크게 왜곡했을 가능성이 큼
따라서 더 신뢰할 지표 : 중앙값
중앙값 비교
- Not Purchased: 111
- Purchased: 228
핵심 발견
오히려 구매 완료 세션의 중앙값이 더 높음
일반적인 사용자 기준에서는 비싸서 사지 않는 것 보다 낮은 금액/가벼운 탐색 세션이 더 쉽게 이탈한다는 가능성이 보임
가설 판단
가격이 높아서 구매를 포기한다는 가설은 채택하기 어렵다
가격 부담 단독 원인 → 기각 또는 약한 부분 채택
- 평균은 이상치 왜곡
- 중앙값 기준 오히려 구매자가 더 높은 금액
- 고가보다 저관여 탐색/비교 세션 이탈 가능성
가능성
Not Purchased 세션
- 단순 탐색
- 여러 상품 테스트
- 장바구니 저장
- 가격 비교
- 비정상적 대량 카트
- 내부 테스트/이상 데이터
Purchased 세션
- 구매 의도 명확
- 필요한 상품 확정
- 중간 수준 금액대에서 구매
비즈니스 관점 핵심
문제는 가격이 비싸서 사지 못하는 것 보다 구매 확신이 부족한 세션이 Checkout까지 오고 있을 가능성이 큼
Checkout 진입 품질 문제
- 신규 유저
- Google 유입
- 탐색형 세션
현재까지 가장 강력한 구조
핵심 원인: Google / 신규 유저 / 저확신 Checkout 세션 → 결제 직전 이탈
액션 아이디어
가격 할인보다 더 중요한 것 : 구매 확신 강화
- 첫 구매 혜택
- 리뷰
- 배송/환불 정책
- 결제 신뢰성
- 상품 상세 정보 강화
추가 분석
추천 : 이상치 제거 후 재분석
이유 : 비정상 세션 제거 후 실제 가격 민감도 재확인
Checkout 금액 기준 구매/미구매 세션을 비교한 결과, 평균 금액은 미구매 세션에서 높았으나 이는 일부 극단적 이상치의 영향이 큰 것으로 보인다.
중앙값 기준으로는 구매 완료 세션의 결제 금액이 더 높게 나타나 단순 가격 부담이 주요 이탈 원인이라고 보기 어렵다.
따라서 결제 이탈의 핵심은 가격 자체보다 구매 의도와 신뢰 수준의 차이일 가능성이 높다고 본다.
결제 이탈의 핵심은 비싼 가격보다 ‘구매 확신 부족’이며, 할인보다 신뢰 강화가 더 중요할 가능성이 높음
현재까지 가설 총 정리
가설 1 : Device UX → 기각
가설 2 : 신규 유저 첫 구매 장벽 → 강력 채택
가설 3 : Google 유입 품질 문제 → 부분 채택
가설 4 : 가격 부담 → 단순 가격 가설 기각 / 구매 확신 문제로 재해석
가장 강력한 병목: Google 기반 신규 유저가 Checkout까지 진입하지만, 신뢰/확신 부족으로 Purchase 단계에서 이탈

'프로젝트 > GA4 분석' 카테고리의 다른 글
| [GA4 퍼널 분석] Day5-2 결제 전환율 가설 검증 (Day4 결과 통합) (0) | 2026.05.12 |
|---|---|
| [GA4 퍼널 분석] Day5-1 장바구니 전환율 가설 검증 (Day3 결과 통합) (0) | 2026.05.11 |
| [GA4 퍼널 분석] Day4-3 (begin_checkout → purchase | Source Quality 분석) (0) | 2026.05.09 |
| [GA4 퍼널 분석] Day4-2 (begin_checkout → purchase | New vs Returning 분석) (0) | 2026.05.09 |
| [GA4 퍼널 분석] Day4-1 (begin_checkout → purchase | Mobile UX 분석) (0) | 2026.05.09 |