인프런 커뮤니티 질문&답변

꿀떡님의 프로필 이미지
꿀떡

작성한 질문수

BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)

3-13 4번 문제 풀어보았습니다

해결된 질문

작성

·

91

0

4. Core Event를 "click_payment"라고 설정하고 Weekly Retention을 구해주세요.

 

WITH base AS(
  SELECT
    event_date,
    event_timestamp,
    user_pseudo_id,
    event_name,
    MAX(IF(ep.key = 'firebase_screen', ep.value.string_value, NULL)) AS firebase_screen,
    MAX(IF(ep.key = 'food_id', ep.value.int_value, NULL)) AS food_id,
    MAX(IF(ep.key = 'session_od', ep.value.string_value, NULL)) AS session_od
  FROM advanced.app_logs
  CROSS JOIN UNNEST(event_params) AS ep
  GROUP BY ALL
), payment AS(
  SELECT 
    user_pseudo_id,
    event_timestamp,
    event_date,
    event_name,
    --firebase_screen,
  FROM base 
  WHERE event_name = 'click_payment'
  ORDER BY event_date
), payment_first_week AS(
  SELECT
    *,
    DATE_TRUNC(MIN(event_date) OVER(PARTITION BY user_pseudo_id), WEEK(MONDAY)) AS first_week,
    DATE_TRUNC(event_date, WEEK(MONDAY)) AS event_week
  FROM(
    SELECT 
      * EXCEPT(event_timestamp, event_date),
      DATE(DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul')) AS event_date
    FROM payment
    )
), payment_user_cnt AS(
  SELECT
    diff_of_week,
    COUNT(DISTINCT user_pseudo_id) AS user_cnt 
  FROM(
    SELECT 
      *,
      DATE_DIFF(event_week, first_week, WEEK) AS diff_of_week
    FROM payment_first_week
  )
  GROUP BY diff_of_week
  ORDER BY diff_of_week
)

############################### 리텐션 비율 구하기 ###############################

SELECT 
*,
SAFE_DIVIDE(user_cnt, first_week_user_cnt) AS retention_rate
FROM(
 SELECT 
   *,
   FIRST_VALUE(user_cnt) OVER(ORDER BY diff_of_week) AS first_week_user_cnt
 FROM payment_user_cnt
)

여기까지는 payment 테이블 WHERE절에 event_name으로 "click_payment" 조건을 걸어준 것 빼곤 강의의 코드를 거의 그대로 사용했습니다.

image (5).png

결과는 이렇게 나왔고, 저는 diff_of_week가 4일 때 갑자기 user_cnt가 상승했다가 5일 때 다시 이전 수치로 돌아간 것에 주목했습니다(약 18% 상승).

SELECT
event_week,
COUNT(DISTINCT user_pseudo_id) AS user_cnt 
FROM(
SELECT 
  *,
  DATE_DIFF(event_week, first_week, WEEK) AS diff_of_week
FROM payment_first_week
)
WHERE diff_of_week = 4
GROUP BY event_week
ORDER BY user_cnt DESC

그래서 diff_of_week가 4일 때의 "click_payment" 이벤트를 발생시킨 유저의 수를 추출해보았습니다.

결과를 보았을 때, 2022-10-31 ~ 2023-01-09의 유저 수가 많음을 확인하였고 이는, 해당 기간이 첫 주문 후 4주가 지난 사람들의 재주문 건수가 많았을 것이라는 생각이 들었습니다.

##################### 해당 날짜, 이전, 이후의 데이터 추출 ####################

# target_date 테이블: 해당 날짜를 만족하는 행만 추출
, target_date AS(
  SELECT 
    *
  FROM base
  WHERE user_pseudo_id IN(
    SELECT user_pseudo_id
    FROM payment_first_week
    WHERE first_week BETWEEN '2022-10-03' AND '2022-12-12'
    ) 
  ORDER BY user_pseudo_id, event_timestamp 

# before_target_date 테이블: 해당 날짜 이전
), before_target_date AS(
  SELECT 
    *
  FROM base
  WHERE user_pseudo_id IN(
    SELECT user_pseudo_id
    FROM payment_first_week
    WHERE first_week BETWEEN '2022-08-01' AND '2022-10-02'
    ) 
  ORDER BY user_pseudo_id, event_timestamp 

# after_target_date 테이블: 해당 날짜 이후
), after_target_date AS(
  SELECT 
    *
  FROM base
  WHERE user_pseudo_id IN(
    SELECT user_pseudo_id
    FROM payment_first_week
    WHERE first_week BETWEEN '2022-12-13' AND '2023-12-31'
    ) 
  ORDER BY user_pseudo_id, event_timestamp 
)

################ click_payment를 발생시킨 유저와 그 때의 날짜 추출 ################

# payment_users 테이블: 해당 날짜에서 event_name칼럼에 "click_payment"를 가지고 있는 user_pseudo_id와 그 때의 event_date를 추출
, payment_users_target_date AS(
  SELECT 
    event_date,
    user_pseudo_id
  FROM(
  SELECT 
    *,
    CASE WHEN event_name = 'click_payment' THEN 1 
    ELSE 0 END AS payment_user
  FROM target_date
  )
  WHERE payment_user = 1
)

# payment_users_before 테이블: 해당 날짜 이전의 payment user의 정보
,payment_users_before AS(
  SELECT 
    event_date,
    user_pseudo_id
  FROM(
  SELECT 
    *,
    CASE WHEN event_name = 'click_payment' THEN 1 
    ELSE 0 END AS payment_user
  FROM before_target_date
  )
  WHERE payment_user = 1
)

# payment_users_after 테이블: 해당 날짜 이후의 payment user의 정보
,payment_users_after AS(
  SELECT 
    event_date,
    user_pseudo_id
  FROM(
  SELECT 
    *,
    CASE WHEN event_name = 'click_payment' THEN 1 
    ELSE 0 END AS payment_user
  FROM after_target_date
  )
  WHERE payment_user = 1
)

그래서 전체 날짜를 위의 해당 날짜와 그 전, 후 3개로 나누어 해당 기간의 정보만을 담는 테이블을 생성하고, 각 기간에 "click_payment" 이벤트를 가지고 있는 유저의 user_pseudo_id와 그 때의 event_date를 추출하였습니다.

################ 3개의 기간동안 사람들이 주문한 음식의 food_id 추출 ################


# 해당 기간동안 사람들이 주문한 food_id
, food_cnt_target_date AS(
  SELECT 
    food_id,
    COUNT(*) AS food_cnt
  FROM target_date td
  JOIN payment_users_target_date pu
  ON td.user_pseudo_id = pu.user_pseudo_id 
  AND td.event_date = pu.event_date
  WHERE event_name = "click_cart"
  GROUP BY food_id
  ORDER BY food_cnt DESC
  --ORDER BY td.user_pseudo_id, event_timestamp

), # 해당 기간 이전 사람들이 주문한 food_id
  food_cnt_before_target AS(
  SELECT 
    food_id,
    COUNT(*) AS food_cnt
  FROM before_target_date td
  JOIN payment_users_before pu
  ON td.user_pseudo_id = pu.user_pseudo_id 
  AND td.event_date = pu.event_date
  WHERE event_name = "click_cart"
  GROUP BY food_id
  ORDER BY food_cnt DESC

), # 해당 기간 이후 사람들이 주문한 food_id 
  food_cnt_after_target AS(
  SELECT 
    food_id,
    COUNT(*) AS food_cnt
  FROM after_target_date td
  JOIN payment_users_after pu
  ON td.user_pseudo_id = pu.user_pseudo_id 
  AND td.event_date = pu.event_date
  WHERE event_name = "click_cart"
  GROUP BY food_id
  ORDER BY food_cnt DESC
)

위에서 구한 id와 date를 이용해 세 기간동안 사람들이 주문한 음식과 음식 별 건수를 추출하였습니다. 전체 데이터를 살펴본 결과, "click_payment"는 항상 주문 마지막에 발생하는 이벤트이며, 이 row에는 food_id는 없고, "click_cart" 이벤트를 발생시켰을 때 해당 주문건에 대한 food_id가 row에 포함되어 있어 event_name을 "click_cart"로 지정하였습니다.

 

################ 각 food_id 별 해당 기간, 이전, 이후의 주문 건수 카운트 ################

, total_cnt AS(
  SELECT 
    td.food_id,
    btd.food_cnt AS food_cnt_before_target_date,
    td.food_cnt AS food_cnt_target_date,
    atd.food_cnt AS food_cnt_after_target_date
  FROM food_cnt_target_date td
  JOIN food_cnt_before_target btd
  ON td.food_id = btd.food_id
  JOIN food_cnt_after_target atd
  ON td.food_id = atd.food_id
)

################ 각 food_id 별 해당 기간, 이전, 이후의 주문 건수 비율 ################

SELECT 
 food_id,
 food_cnt_before_target_date,
 ROUND(SAFE_DIVIDE(food_cnt_before_target_date, SUM(food_cnt_before_target_date) OVER()), 4) AS rate_before,

 food_cnt_target_date,
 ROUND(SAFE_DIVIDE(food_cnt_target_date, SUM(food_cnt_target_date) OVER()), 4) AS rate_target,

 food_cnt_after_target_date,
 ROUND(SAFE_DIVIDE(food_cnt_after_target_date, SUM(food_cnt_after_target_date) OVER()), 4) AS rate_after
FROM total_cnt
ORDER BY rate_target DESC

세 기간동안 사람들이 주문한 food_id와 그 횟수, 비율을 구해보았습니다. 각 음식의 비율은 매우 적지만 target_date를 기준으로 정렬해 보았을 때, 확실히 해당 기간동안 주문량이 높고 나머지 기간에는 주문량이 낮은 음식들이 존재했습니다(ex. 1438, 1516 등).

 

하지만, 이것이 4주전 주문자들의 실제 재주문 결과인지, 단지 해당기간 동안 신규 유저가 늘었기 때문인지 확실하지 않다고 생각문자와 그 외 기간동안의 신규 주문자를 비교해 보았습니다.

SELECT
  new_user_target_date,
  COUNT(*) AS new_user_cnt
FROM(
SELECT
 CASE WHEN first_week BETWEEN '2022-10-31' AND '2023-01-09' THEN 1 
 ELSE 0 END AS new_user_target_date
FROM payment_first_week
)
GROUP BY new_user_target_date

해당 기간의 신규 유저수는(new_user_target_date = 1) 6261명, 그 외 기간의 신규 유저수는 6898명으로, 전체 기간 대비 해당 기간의 비율을 고려해보았을 때, 신규 유저가 크게 늘었다고 보기 어려웠습니다.

 

따라서, 2022~10-03 ~ 2022-12-12일이 포함된 주차에 첫 주문을 한 고객들의 만족도가 높았거나, 그 당시에 시행했던 첫 주문 관련 마케팅, 광고 효과가 좋았을 것이라고 판단할 수 있었습니다.

 

** 앱 로그 데이터를 처음 접했기 때문에 코드 오류나 논리적인 비약이 있을 것이라고 생각하지만, 단지 Weekly Retention에 WHERE 조건을 추가하는 것에 그치기 아쉬워서(?) 의식의 흐름대로 분석해보았습니다.

답변 1

0

카일스쿨님의 프로필 이미지
카일스쿨
지식공유자

꿀떡님 안녕하세요!

오 click_payment로 가정하고 하나씩 가설을 만들고, 논리적인 근거를 찾기 위해 시도하신 점 너무 칭찬드리고 싶어요. 이런 자신만의 논리를 연습하기 위한 목적을 드리기 위해 이 문제를 출시했답니다.

 

지금 데이터는 앱 로그 데이터지만, 회사 상황보다 정보가 적은 상황이죠. 언제 프로모션을 했는지, 회사의 전략 방향이 어떤지 등은 나오지 않았습니다. 회사에서는 이 부분을 고려해서 분석 방향을 계속 조정하게 될거에요.

 

분석에서 생각하는 과정과 쿼리 짜는 부분은 잘 작성해주셨고, 해주신 방법에서 몇가지 피드백을 드려볼게요

 

  1. Weekly 리텐션을 구해주신 부분에서 diff_of_week이 4일 때 user_cnt가 상승했다는 점을 주목해주셨는데, 이 차이를 인지하신 부분 잘하셨어요. 갑자기 상승하거나 갑자기 하락하는 부분에서 가설을 만드는 부분 좋아요.
    추가적으로 해보면 좋은 것은 이 데이터를 해석하는 것이에요. 전반적인 해석을 하는 것이지요. diff_of_week이 0일 때 11467명이 있는데, 1주차 뒤에 99%가 빠지게 되는 구조입니다. 그 후에도 계속 빠지곤 하죠. 그래서 일단 들어온 사람들 대비 꽤 많은 사람들이 이탈하고 있다는 현상을 발견하고, 이게 월별로 할 때는 어떻게 될지 월별 리텐션을 뽑아서 확인해볼 것 같네요(주차의 패턴을 월별에서도 반영될테니 떨어지는 비율은 높을거에요)
    이런 경우에 절대값 숫자를 보고, 이정도면 해볼만하다라고 생각할 수도 있고 이 부분은 적은데?라고 생각할 수도 있어요. 이건 상황에 따라 다르지만 유저 100명이 우리에게 어떤 의미를 주는지 생각해보셔도 좋습니다. 절대적인 조건을 적용하는 것보다 우리 앱의 현재 주차별 앱 접속자(WAU)가 얼마나 되는지 확인해보고 그 중에 몇%인지 볼 것 같아요. 그러면 100명이 WAU 대비 얼마인지 알 수 있고, 그 사람들을 어떻게 전환할지 고민하게 되겠지요.

    2. 어떤 Action Item을 낼지 고민해보기.


    리텐션 분석을 해서 어떤 Action을 내야할지에 대해 고민을 해봐야 합니다. 다만 데이터만 너무 볼 때 Action을 찾기 어려운 경우를 많이 경험했습니다. 이럴 때는 앱의 본질적인 부분으로 돌아가서, 사람들이 제품을 탐색하다가 결제까지 많이 이탈을 한 것인지 혹은 애초에 적은 비율로 결제를 하는지를 고민해보면 좋습니다. 퍼널 분석과 리텐션 분석이 딱 나뉘는 것이 아니고 두개를 같이 진행하면서 고민해보는 과정이지요.
    그래서 어떻게 해야 click_payment를 늘릴 수 있을까?를 생각해보는 것이 핵심입니다. 이럴 때 가설을 잡고 어떤 것을 한 사람이 click_payment가 많을 것이다라고 생각하고 데이터로 확인해보고 맞을지 아닐지 생각하고 다시 또 가설 만들고 이런 흐름으로 생각해보셔요. 지금의 분석 결과는 "그 당시에 시행했던 첫 주문 마케팅, 광고 효과가 좋았을 것"이라는 내용이라 Action Item이 아니고 회사였으면 이걸 보면 "당연한 내용을 말하시네요"라고 할 수 있습니다. 마케팅/광고하면 당연히 좋아야 하는거 아니야?라는 생각이 있거든요. 이런 경우라면 차라리 광고 성과 분석을 해보는 것이 좋을 수도 있어요. 각 광고들의 효과가 얼마나 되었고 평균적으로 이정도 광고면 우리에게 매출이 얼마나 들어온다. 이 부분은 저희 앱 데이터에선 없지만 이런 방식으로 생각해보시면 좋을 것 같아 남겨보아요

     

     


    3. 리텐션 분석으로 시작했지만, 전체적인 앱 데이터의 흐름을 아시면 더 좋을 수 있어요. 하루 앱 사용자는 얼마나 되고, 퍼널은 어떤 것이 있고 얼마나 넘어가고 등. 이런 전체적인 Overview를 파악하신 후에 데이터를 접근하시면 더 도움이 될 것 같아요.

 

 

데이터 기반 프로젝트 진행 프로세스를 알고 가셔도 좋을 것 같아서 PM을 위한 데이터 리터러시 자료 공유드려요. 정리본이지만 이미지만 보셔도 어떤 느낌인지 감이 오실거에요

https://zzsza.github.io/data-for-pm/project/process.html

 

 

문제 푸느라 너무 고생하셨습니다! 제 피드백은 회사에서 이런 방식으로 일이 생길수도 있으니 이런 부분을 생각해보면 좋겠다라는 관점으로 드린 것이고, 지금 상황에서 꿀떡님이 이정도 하신 것도 잘하신거라 생각해요. 계속 학습하시면서 궁금한 것은 언제든 말씀해주셔요.

 

 

 

 

꿀떡님의 프로필 이미지
꿀떡
질문자

상세한 피드백 감사드립니다...데이터에 너무 집중하느라 인사이트나 action item은 미처 생각하지 못 한 것 같네요 ㅠㅠ 나중에 꼭 다시 한 번 풀어보겠습니다

꿀떡님의 프로필 이미지
꿀떡

작성한 질문수

질문하기