묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
Retention 쿼리를 작성하면서.. 궁금한점이 있습니다.
Weekly Retention은 diff_of_week 을 활용하여, 시간의 경과에 따른 리텐션 변화를 구합니다. 따라서, 제가 생각했을 때, Weekly Retention을 구하는 쿼리에서는 다음과 같은 가정을 하고 진행한 것이 아닐까? 라는 생각이 들었습니다.주차별로(시간에 따라) 활동 중인 사용자 수는 달라질 것이다.user_type에 따라 활동 중인 사용자 수는 차이가 있을 것이다.2.의 경우는 만약의 신규/복귀... 유저를 구분한다면, 해당 가설을 기반으로, Weekly Retention을 구하는 행위를 한 것이 아닐까? 라는 생각이 들었습니다.(추가 궁금증)Retention에 영향을 주는 인자를 분석하는 경우도 있을까요? (실무에서) 저는 Retention을 분석하기 전에, Retention과 관련이 높은 것이 무엇일지, 가설을 세우고 검정을 해보았습니다. 가설: 방문일수는 Retention에 높은 상관관계를 가진다.데이터 범위: 2022-08-01 ~ 2022-11-01D7_retention : (bool) 사용자의 첫번째 이벤트 시점 ~ 7일 이후에도 활동을 하면, 1 아니면 0D30_retention : (bool) 사용자의 첫번째 이벤트 시점 ~ 30일 이후에도 활동을 하면, 1아니면 0import os from google.cloud import bigquery from google.oauth2 import service_account import pandas as pd import statsmodels.api as sm from scipy.stats import pointbiserialr import numpy as npos.environ['GOOGLE_APPLICATION_CREDENTIALS'] = './service_account.json' key_path = './service_account.json' credentials = service_account.Credentials.from_service_account_file( key_path, scopes = ["https://www.googleapis.com/auth/cloud-platform"], ) client = bigquery.Client(credentials=credentials, project=credentials.project_id, location="US")query = """ WITH user_visits AS ( SELECT user_pseudo_id, COUNT(DISTINCT event_date) AS visit_days FROM advanced.app_logs WHERE event_date BETWEEN '2022-08-01' AND '2022-11-01' GROUP BY user_pseudo_id ), retention_data AS ( SELECT user_pseudo_id, MIN(event_date) AS first_event_date, MAX(event_date) AS last_event_date, CASE WHEN MAX(event_date) >= DATE_ADD(MIN(event_date), INTERVAL 7 DAY) THEN 1 ELSE 0 END AS D7_retention, CASE WHEN MAX(event_date) >= DATE_ADD(MIN(event_date), INTERVAL 30 DAY) THEN 1 ELSE 0 END AS D30_retention FROM advanced.app_logs WHERE event_date BETWEEN '2022-08-01' AND '2022-11-01' GROUP BY user_pseudo_id ), combined_data AS ( SELECT v.user_pseudo_id, v.visit_days, r.D7_retention, r.D30_retention FROM user_visits v JOIN retention_data r ON v.user_pseudo_id = r.user_pseudo_id ) SELECT * FROM combined_data; """df = client.query(query).to_dataframe() df['visit_days'] = pd.to_numeric(df['visit_days'], errors='coerce').astype(np.float64) df['D7_retention'] = pd.to_numeric(df['D7_retention'], errors='coerce').astype(np.float64) df['D30_retention'] = pd.to_numeric(df['D30_retention'], errors='coerce').astype(np.float64) # 결측치가 있는지 확인하고 제거 df = df.dropna(subset=['visit_days', 'D7_retention', 'D30_retention']) # 상수항 추가 X = sm.add_constant(df[['visit_days']]) # D7_retention에 대한 로지스틱 회귀 모델 적합 y_D7 = df['D7_retention'] logit_model_D7 = sm.Logit(y_D7, X).fit() print(logit_model_D7.summary()) # D30_retention에 대한 로지스틱 회귀 모델 적합 y_D30 = df['D30_retention'] logit_model_D30 = sm.Logit(y_D30, X).fit() print(logit_model_D30.summary())visit_days_range = np.linspace(df['visit_days'].min(), df['visit_days'].max(), 100) prob_D7 = logit_model_D7.predict(sm.add_constant(visit_days_range)) prob_D30 = logit_model_D30.predict(sm.add_constant(visit_days_range)) plt.plot(visit_days_range, prob_D7, label='D7 Retention Probability') plt.plot(visit_days_range, prob_D30, label='D30 Retention Probability', linestyle='--') plt.xlabel('Visit Days') plt.ylabel('Retention Probability') plt.title('Retention Probability vs Visit Days') plt.legend() plt.show()따라서, 방문일수는 Retention과 상관성을 보인다. 가설2. 방문일 수는 user_type에 따라 각기 다른 상관성을 보일 것이다.결론: user_type은 new_user, current_user는 통계적으로 유의하며, 높은 상관성을 가지나, 휴면 유저, 복귀 유저는 통계적으로 유의미하지 않으며, 낮은 상관성을 띈다. 이렇게 결론을 내놓는 방식이, 적합한 방식인지 궁금합니다.
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
리텐션 과제 연습문제 4번(core_event)
최근에, 면접이 있어서 공부를 제대로 못했네요. 오늘부터 매일 매일 못했던 공부를 다시 해보려고 합니다.문제: core_event를 'click_payment'라고 설정하고, Weekly Retention을 구하여라.저는 click_payment 까지의 사용자 여정 별로 세그먼트를 나누려는 시도를 해보았습니다. 문제에 대한 고민사항은 길어지니, 최종적으로 나누었던 세그먼트를 먼저 소개해드리겠습니다.click_payment 까지의 여정은 크게 다음과 같이 구성할 수 있습니다.click_search(검색) -> click_paymentclick_banner(배너 클릭) -> click_paymentclick_food_category(음식 카테고리 클릭) -> click_paymentclick_restaurant_nearby( 내 위치 기반 주변 레스토랑) -> click_paymentclick_recommend_food(추천) -> click_payment크게 다음과 같이 5개의 세그먼트로 나누어보고, 각 세그먼트 별로, Count를 해보았습니다. 이 때, click_login -> click_search -> .... -> click_payment -> click_search .... 이런 경우를 대비해서, 각 이벤트마다 제일 처음 이벤트가 발생한 시간 을 기준으로 구분하도록 하였습니다. 이제 각 퍼널별로 리텐션을 구해보겠습니다.WITH user_events AS ( SELECT user_pseudo_id, event_name, event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, ROW_NUMBER() OVER (PARTITION BY user_pseudo_id, event_name ORDER BY event_timestamp) AS r_num FROM advanced.app_logs WHERE event_name IN ('click_search', 'click_banner', 'click_food_category', 'click_restaurant_nearby', 'click_recommend_food', 'click_payment') AND event_name NOT IN ('screen_view', 'click_login') ), FIRST_EVENTS AS ( SELECT *, DATE_DIFF(event_week, first_week, WEEK) AS diff_of_week FROM ( SELECT user_pseudo_id, event_date, 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, MIN(CASE WHEN event_name = 'click_search' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_search_time, MIN(CASE WHEN event_name = 'click_payment' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_payment_time, MIN(CASE WHEN event_name = 'click_banner' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_banner_time, MIN(CASE WHEN event_name = 'click_food_category' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_food_category_time, MIN(CASE WHEN event_name = 'click_restaurant_nearby' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_restaurant_nearby_time, MIN(CASE WHEN event_name = 'click_recommend_food' THEN event_timestamp END) OVER (PARTITION BY user_pseudo_id, event_date) AS first_recommend_food_time FROM user_events ) ), FUNNEL_CLASSIFICATION AS ( SELECT DISTINCT *, CASE WHEN first_search_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_search_time < first_payment_time THEN 'click_search -> click_payment' WHEN first_banner_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_banner_time < first_payment_time THEN 'click_banner -> click_payment' WHEN first_food_category_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_food_category_time < first_payment_time THEN 'click_food_category -> click_payment' WHEN first_restaurant_nearby_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_restaurant_nearby_time < first_payment_time THEN 'click_restaurant_nearby -> click_payment' WHEN first_recommend_food_time IS NOT NULL AND first_payment_time IS NOT NULL AND first_recommend_food_time < first_payment_time THEN 'click_recommend_food -> click_payment' ELSE 'Other' END AS funnel FROM FIRST_EVENTS WHERE first_payment_time IS NOT NULL ), PIVOTED_ANALYSIS AS ( SELECT diff_of_week, COUNT(DISTINCT CASE WHEN funnel = 'click_search -> click_payment' THEN user_pseudo_id END) AS user_cnt_search_payment, COUNT(DISTINCT CASE WHEN funnel = 'click_banner -> click_payment' THEN user_pseudo_id END) AS user_cnt_banner_payment, COUNT(DISTINCT CASE WHEN funnel = 'click_food_category -> click_payment' THEN user_pseudo_id END) AS user_cnt_food_category_payment, COUNT(DISTINCT CASE WHEN funnel = 'click_restaurant_nearby -> click_payment' THEN user_pseudo_id END) AS user_cnt_restaurant_nearby_payment, COUNT(DISTINCT CASE WHEN funnel = 'click_recommend_food -> click_payment' THEN user_pseudo_id END) AS user_cnt_recommend_food_payment FROM FUNNEL_CLASSIFICATION GROUP BY diff_of_week ), INITIAL_USERS AS ( SELECT FIRST_VALUE(user_cnt_search_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_search_payment, FIRST_VALUE(user_cnt_banner_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_banner_payment, FIRST_VALUE(user_cnt_food_category_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_food_category_payment, FIRST_VALUE(user_cnt_restaurant_nearby_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_restaurant_nearby_payment, FIRST_VALUE(user_cnt_recommend_food_payment) OVER (ORDER BY diff_of_week) AS first_user_cnt_recommend_food_payment FROM PIVOTED_ANALYSIS LIMIT 1 ) SELECT pa.diff_of_week, pa.user_cnt_search_payment AS search_active_user, pa.user_cnt_banner_payment AS banner_active_user, pa.user_cnt_food_category_payment AS category_active_user, pa.user_cnt_restaurant_nearby_payment AS nearby_active_user, pa.user_cnt_recommend_food_payment AS recommend_active_user, iu.first_user_cnt_search_payment AS search_cohort_user, iu.first_user_cnt_banner_payment AS banner_cohort_user, iu.first_user_cnt_food_category_payment AS category_cohort_user, iu.first_user_cnt_restaurant_nearby_payment AS nearby_cohort_user, iu.first_user_cnt_recommend_food_payment AS recommend_cohort_user, ROUND(SAFE_DIVIDE(pa.user_cnt_search_payment, iu.first_user_cnt_search_payment), 3) AS retention_week_rate_search_payment, ROUND(SAFE_DIVIDE(pa.user_cnt_banner_payment, iu.first_user_cnt_banner_payment), 3) AS retention_week_rate_banner_payment, ROUND(SAFE_DIVIDE(pa.user_cnt_food_category_payment, iu.first_user_cnt_food_category_payment), 3) AS retention_week_rate_food_category_payment, ROUND(SAFE_DIVIDE(pa.user_cnt_restaurant_nearby_payment, iu.first_user_cnt_restaurant_nearby_payment), 3) AS retention_week_rate_restaurant_nearby_payment, ROUND(SAFE_DIVIDE(pa.user_cnt_recommend_food_payment, iu.first_user_cnt_recommend_food_payment), 3) AS retention_week_rate_recommend_food_payment FROM PIVOTED_ANALYSIS pa, INITIAL_USERS iu ORDER BY pa.diff_of_week; 1000자 이내로 작성해야 게시글 하나를 쓸 수 있음..screen_view -> screen_view -> click_login의 경우, 사용자가 로그인을 하지 않고, 앱이 잠시 백그라운드 상에 동작중인 상태에서, 다시 앱을 켰을 때, screen_view 로그가 찍히는 것을 확인했습니다.현재 집중해야 할 부분은 사용자가 상품을 들여다보는 시간이나, item을 찾을 때, UI/UX 적으로 개선할 부분이 있는지 찾기보다, click_payment를 하기까지의 주요 이벤트 여정을 세그먼트로 분류하는 작업을 하고 있기 때문에, screen_view 이벤트는 제외해야겠다는 생각을 했습니다.또한, screen_view -> click_login -> screen_view -> screen_view -> click_login 처럼, 이전 이벤트와 현재 이벤트가 같은 경우를 제외한 다른 경우만을 필터링해서, 굵직한 이벤트만을 필터링해보자! 라는 생각을 가졌었습니다.--- 1000자 이내로 작성해야 글이 올ㅠ 퍼널의 수가 열 몇개로 나오지 않을까? 하는 예상과 다르게 총 433개의 단계가 나왔습니다. click_login -> click_food_category -> click_restaurant -> click_food -> click_cart -> view_recommend_extra_food -> click_payment와 같이 click_login ----- > click_payment까지의 여정의 가짓수가 너무 많아, 단계를 단순화할 필요성이 있어보였습니다. 그래서, 위의 결과와 같이 총 5개로 퍼널을 나누었습니다.
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
리텐션 과제_연습문제 2번
안녕하세요. 연습문제 2번을 다음과 같이 풀어보았습니다. Q: 리텐션 연습문제: Retain User를 New, Current, Resurrected, Dormant User로 나누는 쿼리를 작성하여라. 주어진 데이터에서 어떤 사람들이 리텐션이 그나마 높은지, 찾아보아라.주차별, 전체 유저에 대한 코호트 분석 그래프를 보면, 다음과 같습니다. 문제 정의2022년 12월 19일 이후로 리텐션이 떨어지고 있는 문제점이 발생했다. 해당 원인이 무엇일까?가설 설정1) 고객의 유입량이 감소하고 있다.-> 2022년 12월 19일 이전과 이후의 주차별 신규 가입자 수를 비교한다.2) 리텐션의 변화가 특정 유저 그룹에 의해 영향을 받는가?-> 신규유저는 어떤 시점을 기준으로 분류할 것인가?주차별 신규 가입자 수 시각화2022년 12월 19일을 월요일로 하는 주차부터 신규 가입자 수가 감소하는 추세이다. 전반적으로 신규가입자 수는 증가하는 추세였으나, 12월 19일을 기점으로 꺾이는 현상을 보인다. 따라서, 신규 가입자 수가 리텐션에 영향을 준다는 것을 확인할 수 있다.기존의 추세와 반하게, 떨어지는 추세를 보이기 때문에 다음과 같이 기존 유저와 신규 유저를 정의하겠다.1) 기존 유저=> 2022년 10월 03일 ~ 2022년 12월 19일 이전2) 신규 유저=> 2022년 12월 19일 이후복귀유저와 이탈 유저를 판단하는 기준이 필요하다. 이를 위해, 첫 로그인 이후, 두번째로 로그인을 하기까지의 걸리는 시간을 4분위 수로 검증해서, 중위값을 기준으로 기존유저와 복귀유저를 구분해보기로 했다.로그인 판단 기준 = click_login 이라는 이벤트가 발생했을 경우1. 자동 로그인 여부 판단SELECT COUNT(*) FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep WHERE ep.key = "firebase_screen" AND ep.value.string_value = 'welcome' AND user_id IS NOT NULL자동 로그인이 된다면, click_login 이벤트가 발생하지 않을 것이다. 하지만, COUNT(*)가 0이기 때문에, 해당 앱은 자동 로그인이 되지 않음을 알 수 있다.즉, 사용자가 앱에 접속하면, 가입을 했더라도, 무조건 로그인을 해야한다. 따라서, 사용자가 앱에 접속 시, 로그인 이벤트가 무조건 발생한다.2.첫번째 로그인 후, 다음 로그인하기까지 걸린 시간을 4분위 수로 계산WITH base AS (SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, user_pseudo_id, event_name, ROW_NUMBER() OVER (PARTITION BY user_pseudo_id ORDER BY event_date) AS login_rn FROM advanced.app_logs WHERE event_name = 'click_login' AND event_date >= '2022-10-03' QUALIFY login_rn < 3) , second_event_data AS (SELECT event_date, event_timestamp, user_pseudo_id, event_name, login_rn, IF(second_event_date IS NULL, '2023-01-20', second_event_date) AS second_event_date FROM (SELECT *, LEAD(event_date) OVER (PARTITION BY user_pseudo_id ORDER BY event_date) AS second_event_date FROM base)) SELECT APPROX_QUANTILES(date_diff_day, 100)[OFFSET(25)] AS percentile_25, APPROX_QUANTILES(date_diff_day, 100)[OFFSET(50)] AS percentile_50, APPROX_QUANTILES(date_diff_day, 100)[OFFSET(75)] AS percentile_75, MAX(date_diff_day) AS percentile_100, AVG(date_diff_day) AS average FROM (SELECT DATE_DIFF(second_event_date, event_date, DAY) AS date_diff_day FROM second_event_data)유저별로 로그인을 한 시점을 ROW_NUMBER()로 카운트했다. 또한, second_event_date(다음 로그인 시점)이 NULL인 경우는 이탈 유저이다. 그런데, 4분위 수를 계산하려면, 해당 값을 채워야했다. 2099-12-31로 하려고 했으나, 너무 큰 값을 채워버리면, 4분위 수 검증이 정확하지 않게 되기 때문에 (mid와 avg값이 오른쪽으로 치우쳐버리는 현상이 발생할 수 있다.) second_event_date의 max값을 구해서, 2023-01-20을 채워주었다.Approx_quantiles 함수는 백분위로 나눌 경우, 각 근사치의 최대값을 나타내는 함수이다. 중위값에 해당하는 값들 중 가장 큰 값은 39일이다. 이번에는, 해당 범위에 속하는 값들이 몇개가 있는지 확인해보겠다.SELECT COUNTIF(date_diff_day <= 19) AS count_up_to_25, COUNTIF(date_diff_day > 19 AND date_diff_day <= 39) AS count_25_to_50, COUNTIF(date_diff_day > 39 AND date_diff_day <= 66) AS count_50_to_75, COUNTIF(date_diff_day > 66 AND date_diff_day <= 109) AS count_75_to_100 FROM temp데이터가 1분위, 2분위에 몰려있다. 따라서, 40일 을 기준점으로 삼아야겠다.2022년 12월 19일 이후 ~ (신규유저)2022년 10월 3일 이후 ~ 2022년 12월 19일 이전, 10월 3일 이후부터 다음 접속 시간이 40일 이전인 경우 (기존유저)2022년 10월 3일 이후로부터 40일 이후로 접속한 유저(복귀유저)2022년 10월 3일 이후로부터 40일 이후로도 접속 이력이 없는 경우 (이탈 유저)3. 다음 접속일까지의 걸린 일 수 기반, 유저 분류-- 유저별, 첫번째 로그인 후, 다음 로그인하기 걸린 시간을 4분위 수로 계산 -- 25% | 50% | 75% | 100% -- 로그인_이벤트: click_login -- event_date, event_timestamp, event_name, -- 중간테이블 -- 유저별 로그인을 한 시점을 ROW_NUMBER()로 카운트하기 -- 유저별 첫번째 로그인을 한 시점 구하기 MIN(event_date) OVER() -- second_event_date가 NULL인 경우는 이탈 유저임. -- 그런데, 4분위 수를 계산하려면, MAX(second_event_date) 값을 구하고, NULL을 잠시 해당 값으로 채워놔야함. -- 그 이유는 너무 큰 값으로 채워버리면, 4분위 수 검증이 정확하지 않기 때문임 (mid값과 avg값이 오른쪽으로 치워쳐버리는 현상이 발생할 수 있음) max_event_date: 2023-01-20 WITH base AS (SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, user_pseudo_id, event_name, ROW_NUMBER() OVER (PARTITION BY user_pseudo_id ORDER BY event_date) AS login_rn FROM advanced.app_logs WHERE event_name = 'click_login' AND event_date > '2022-10-03' QUALIFY login_rn < 3) , second_event_data AS (SELECT DISTINCT event_date, event_timestamp, user_pseudo_id, event_name, login_rn, IF(second_event_date IS NULL, '2099-01-20', second_event_date) AS second_event_date FROM (SELECT *, LEAD(event_date) OVER (PARTITION BY user_pseudo_id ORDER BY event_date) AS second_event_date FROM base)) -- 접속 데이터 기반, 신규, 기존, 복귀, 이탈 구분 , user_type_data AS (SELECT event_date, second_event_date, user_pseudo_id, DATE_DIFF(second_event_date, event_date, DAY) AS comeback_day, CASE WHEN event_date >= DATE('2022-12-19') THEN '신규 유저' WHEN event_date >= DATE('2022-10-03') AND second_event_date <= DATE_ADD(event_date, INTERVAL 40 DAY) THEN '기존 유저' WHEN event_date >= DATE('2022-10-03') AND second_event_date > DATE_ADD(event_date, INTERVAL 40 DAY) AND second_event_date != DATE('2099-01-20') THEN '복귀 유저' WHEN event_date >= DATE('2022-10-03') AND second_event_date = DATE('2099-01-20') THEN '이탈 유저' ELSE NULL -- 예외 처리 END AS user_type FROM second_event_data) 4. 각 유저 타입별 리텐션 계산기존 유저-- 일자별 리텐션 계산 (기존 유저) , analysis_current AS (SELECT diff_of_day, COUNT(DISTINCT user_pseudo_id) AS user_cnt FROM (SELECT user_pseudo_id, MIN(event_date) OVER (PARTITION BY user_pseudo_id) AS first_day, event_date, comeback_day as diff_of_day FROM user_type_data WHERE user_type = '기존 유저' ORDER BY event_date, second_event_date) GROUP BY 1) SELECT diff_of_day, user_cnt, first_user_cnt, ROUND(SAFE_DIVIDE(user_cnt, first_user_cnt), 3) AS retention_day_rate FROM (SELECT diff_of_day, user_cnt, FIRST_VALUE(user_cnt) OVER (ORDER BY diff_of_day) AS first_user_cnt FROM analysis_current)복귀 유저신규 유저이탈 유저리텐션이 평평하게 안나오네요... 뭔가 단단히 잘못된것 같습니다..
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
3-7 리텐션 SQL 쿼리 작성하기 연습 (Weekly, Monthly)
리텐션 SQL 쿼리 작성하기, Weekly와 Monthly 쿼리를 작성한 부분을 게시판에 올립니다.-- Weekly 리텐션 작성하기. 마지막 부분 , analysis AS (SELECT diff_of_week, user_cnt, FIRST_VALUE(user_cnt) OVER (ORDER BY diff_of_week) AS first_user_cnt FROM (SELECT diff_of_week, COUNT(DISTINCT user_pseudo_id) AS user_cnt FROM first_date_and_diff GROUP BY 1)) SELECT diff_of_week, user_cnt, ROUND(SAFE_DIVIDE(user_cnt, first_user_cnt), 3) AS retention_week_rate FROM analysis ORDER BY 1;-- 월별 리텐션 계산 WITH base AS ( SELECT DISTINCT user_pseudo_id, event_name, DATE(DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul')) AS event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_datetime, FROM advanced.app_logs WHERE event_date BETWEEN '2022-08-01' AND '2022-11-03' ), first_date_and_diff AS ( SELECT *, DATE_DIFf(event_month, first_month, MONTH) AS diff_of_month FROM ( SELECT DISTINCT user_pseudo_id, DATE_TRUNC(MIN(event_date) OVER(PARTITION BY user_pseudo_id), MONTH) AS first_month, DATE_TRUNC(event_date, MONTH) AS event_month FROM base ) ), analysis AS ( SELECT diff_of_month, user_cnt, FIRST_VALUE(user_cnt) OVER(ORDER BY diff_of_month) AS first_user_cnt FROM ( SELECT diff_of_month, COUNT(DISTINCT user_pseudo_id) AS user_cnt FROM first_date_and_diff GROUP BY 1 ) ) SELECT diff_of_month, user_cnt, ROUND(SAFE_DIVIDE(user_cnt, first_user_cnt), 3) AS retention_month_rate FROM analysis
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
강의 자료 파일을 열 수 없습니다.
윈도우 사용자입니다. 첨부파일을 다운로드 받았는데, 압축파일 풀기를 하면 아래와 같이 오류 메시지가 나오는데요. 어떻게 해결하면 좋을까요?
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
퍼널별 유저들의 새로운 세션 시작 횟수의 총합 _ 윈도우 함수 마지막 문제 응용
안녕하세요. 저는 이번에 마지막 문제를 응용해서, 퍼널별로 유저들의 새로운 세션 시작 횟수의 총합을 계산해보았습니다.[결과 그래프]새로운 세션 시작 기준: 이전 세션 시간 대비 20초 이상이 지나면, 새로운 세션을 활성화했다고 가정 (강의 내용과 동일)첫번째 강의에서 배운, UNNEST 문법과 PIVOT 을 하는 방법을 활용해서, event_params에 있는 firebase_screen의 string_value 값을 새로운 열인 firebase_screen 이라고 만듭니다. 전체 데이터를 조회하면, 쿼리 비용이 많이 들기 때문에, where 조건 절로 값을 작게 명시해줍니다.WITH base AS ( SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, event_name, user_pseudo_id, MAX(IF(ep.key = 'firebase_screen', ep.value.string_value, NULL)) AS firebase_screen FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep WHERE event_date = '2022-08-18' AND user_pseudo_id = '5464646449.4088767327' GROUP BY ALL )LAG() 함수를 사용하여, 이전 날짜 값을 가져옵니다. 이 때, 퍼널별로 구해야하기 때문에, PARTITION BY에 firebase_screen 을 써줍니다. 그 후, DATETIMEDIFF 함수를 사용해서, 현재값과 이전값의 차이를 계산합니다.WITH base AS ( SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, event_name, user_pseudo_id, MAX(IF(ep.key = 'firebase_screen', ep.value.string_value, NULL)) AS firebase_screen FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep WHERE event_date = '2022-08-18' AND user_pseudo_id = '5464646449.4088767327' GROUP BY ALL ), diff_funnel_time AS ( SELECT *, DATETIME_DIFF(event_timestamp, prev_event_timestamp, SECOND) AS second_diff FROM ( SELECT *, LAG(event_timestamp, 1) OVER(PARTITION BY firebase_screen ORDER BY event_timestamp) AS prev_event_timestamp FROM base ) ) SELECT * FROM diff_funnel_time3. ROW_NUMBER() 함수를 사용해서, 퍼널별로 순서를 매깁니다. 그 후, second_diff 의 값이 NULL인 경우는 해당 퍼널의 첫 시작 위치를 가리키므로 1을 넣어주고, rn > 1이고 second_diff >= 20인 경우는 1을 넣어줍니다. 이 때 funnel_per_session_start 컬럼은 퍼널별로 새로운 세션이 시작했는지 여부를 가리킵니다. , funnel_start AS ( SELECT *, CASE WHEN second_diff IS NULL THEN 1 WHEN rn > 1 AND second_diff >= 20 THEN 1 ELSE 0 END AS funnel_per_session_start FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY firebase_screen ORDER BY event_timestamp) AS rn FROM diff_funnel_time ) )이제, 퍼널별로 세션 시작 여부를 SUM()하고, GROUP BY를 해줍니다.SELECT firebase_screen, SUM(funnel_per_session_start) AS funnel_per_session_start_cnt FROM funnel_start GROUP BY ALL ORDER BY 2 DESC;WHERE 조건절을 주석처리하고, 전체 데이터에 대해서, 집계해봅니다. WITH base AS ( SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, event_name, user_pseudo_id, MAX(IF(ep.key = 'firebase_screen', ep.value.string_value, NULL)) AS firebase_screen FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep -- WHERE -- event_date = '2022-08-18' -- AND user_pseudo_id = '5464646449.4088767327' GROUP BY ALL ), diff_funnel_time AS ( SELECT *, DATETIME_DIFF(event_timestamp, prev_event_timestamp, SECOND) AS second_diff FROM ( SELECT *, LAG(event_timestamp, 1) OVER(PARTITION BY firebase_screen ORDER BY event_timestamp) AS prev_event_timestamp FROM base ) ) , funnel_start AS ( SELECT *, CASE WHEN second_diff IS NULL THEN 1 WHEN rn > 1 AND second_diff >= 20 THEN 1 ELSE 0 END AS funnel_per_session_start FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY firebase_screen ORDER BY event_timestamp) AS rn FROM diff_funnel_time ) ) SELECT firebase_screen, SUM(funnel_per_session_start) AS funnel_per_session_start_cnt FROM funnel_start GROUP BY ALL ORDER BY 2 DESC;
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
windows function default 값에 대하여 range between과 rows between의 차이
안녕하세요. 강의를 잘 듣고 있습니다.다름이 아니라, 연습문제를 모두 풀고 나서 윈도우함수 강의를 듣는 과정에서, default에 대해 궁금한 점이 생겼습니다. 정확히, 계산을 해야할 경우, 중복된 데이터가 있는 경우를 고려해서, ROWS BETWEEN UNBOUNDED PRECEDINIG AND CURRENT ROW 라고 프레임의 범위를 정확하게 명시를 하는 것이 좋을 것 같다. 라는 생각이 들었습니다.그 이유는 다음과 같습니다.강의에서 말씀해주신대로, 구글 빅쿼리는 default 값이 RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 라고 되어있는데요,-- 4) -- 누적 쿼리: 과거의 시간(UNBOUNDED PRECEDING)부터 current row까지 SELECT *, SUM(query_cnt) OVER(PARTITION BY user ORDER BY query_date) AS cumulative_sum SUM(query_cnt) OVER(PARTITION BY user ORDER BY query_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_sum2 FROM ( SELECT query_date, team, user, COUNT(user) AS query_cnt FROM advanced.query_logs GROUP BY ALL ) -- QUALIFY cumulative_sum != cumulative_sum2 ORDER BY user, query_date문득, default값이 RANGE BETWEEN UNBOUND PRECEDING AND CURRENT ROW 라면, 왜 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 와 같은 값을 가질까? 다른 값을 가지는 경우도 있지 않을까? 하는 의문이 들었습니다.예제에서는, GROUP BY ALL을 했기 때문에, 중복 데이터가 있어도, query_cnt로 집계가 된 상태에서, 윈도우 함수를 실행하다보니, QUALIFY로 조건절 검증을 수행해도, != 를 만족하는 데이터가 없었던 것 같았습니다.그래서, 1번 예제와 4번 예제를 결합해서, 다음과 같이 검증을 수행해보았습니다.-- INSERT INTO my_sess.query_logs (user, team, query_date) -- VALUES ('샘', 'Data Science', '2024-04-24'); SELECT *, -- 시간의 흐름에 따라, 일자별 유저가 실행한 누적 총 쿼리 수 SUM(total_query_cnt) OVER(PARTITION BY user ORDER BY query_date) AS cumulative_total_sum, SUM(total_query_cnt) OVER(PARTITION BY user ORDER BY query_date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_total_sum2 FROM ( SELECT *, -- 사용자별 시간의 흐름에 따라 쿼리를 실행한 총 횟수: total_query_cnt COUNT(query_date) OVER(PARTITION BY user) AS total_query_cnt FROM my_sess.query_logs ) -- QUALIFY cumulative_total_sum != cumulative_total_sum2 ORDER BY user, query_date결과는 다음과 같습니다.중복데이터가 있을 경우, RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW는 다 합쳐서 하나로 집계해서 결과값을 내놓는 것으로 보입니다.QUALIFY 함수를 적용했을 때cumulative_total_sum = cumulative_total_sum2 인 경우데이터 중복이 없을 경우에는, 같은 값이 나옵니다.
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
소리는 잘 들리는데 화면이 검은 화면으로 나와요
맥북 크롬 환경에서 강의 수강하고 있는데 소리만 들리고 강의 화면이 검정 화면으로만 나옵니다. 혹시 저만 그런걸까요..??
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
Bigquery 활용편(PDF)파일 P.229 페이지 코드 오류
안녕하세요. 이어서, 윈도우 함수에 대해서 잘 듣고 있습니다.Bigquery PDF파일의 P.229페이지를 보면, LAST_VALUE 에 대해서, 쿼리 값이 있는데요, LAST_VALUE가 각 파티션을 나눈 user_id의 마지막 값(1004의 경우, 8, 2112의 경우 7, 3912의 경우 4)를 가져오려면, UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 값이 들어가야 합니다.그 이유가, 해당 FRAME이 따로 정의되어있지 않으면, 다음과 같은 값이 나옵니다.섹션 2-4 윈도우 함수 탐색 함수 연습 문제에서, FIRST_VALUE, LAST_VALUE로도 한번 쿼리 작성을 해보라는 연습을 해보라고 하셔서, 연습하는 도중에 발견하게 되었습니다.이번에는 UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING 을 추가해보겠습니다.
-
해결됨BigQuery(SQL) 활용편(퍼널 분석, 리텐션 분석)
파티셔닝 시, 데이터 크기가 0B가 됩니다!
안녕하세요. 빅쿼리를 좀 더 공부하고 싶어, 강의를 결제하게 되었습니다.다름이 아니라, app_logs_temp 데이터를 강의에서 알려준대로, upload 하고 테이블을 만들었을 떄는 정상적으로, 잘 표시가 되는데요, 문제는 event_date 를 기준으로 (날짜 기반 파티셔닝)을 했을 때, 데이터가 0B가 되는 문제가 발생합니다!Array, Struct 문제에서 4번 문제를 풀때, "데이터를 표시할 수 없다" 라는 말이 있어서, 왜 이럴까.. 하다가, 발견하게 되었어요..저는 이미 예전에 빅쿼리를 사용해본 적이 있어서, billing account 계정이 모두 연결된 상태이구요. chatgpt 말로는 이 방법도 안된다면, 빈 파티셔닝 테이블 만들어 놓고 INSERT INTO로 하나하나 데이터 넣으라는데... 예전에 프로젝트 할 때, 이 방법으로 했다가 돈이 꽤 나와서요.. 어떻게 해야할까요?
-
미해결SQL과 구글시트로 시작하는 데이터분석
설치가 제대로 안됩니다.
설치가 제대로 안되네요 어떻게해야될까요?
-
해결됨업무자동화를 위한 chatGPT와 구글스프레드시트 활용 입문
api key 등록
openai playground 에 billing 정보 등록하고스프레드에 확장프로그램 설치를 했는데도 set api key 버튼이 나오지 않는데 어디서 잘못되었을까요 ?
-
미해결Google Apps Script로 시작하는 업무 자동화
완성된 코드를 공유해 주실 수 있으신가요?
안녕하세요, 강의 잘 듣고 있습니다. 혼자 하려고 하니, 코드 오류가 일어나거나 이메일 본문과 첨부파일이 분리되어 오는데,완성된 코드를 공유해 주실 수 있으신가요? 제가 작성한 코드와 비교해 보고 싶습니다.
-
미해결스마트요원에게 배우는 구글 스프레드시트
문자합치기 부분에서 질문있습니다
함수에서 C6을 왜 &로 두번 감싸신건지 궁금합니다
-
해결됨[기초]코딩 무경험자도 가능! 90분에 뽀개는 구글 시트 업무 자동화
주석 처리하는 단축키는 무엇인가요?
강의 유익하게 잘 보고 있습니다. 중간 중간 주석처리 하실 때, 드래그로 선택하신 뒤 무언가 단축키를 누르시는 것 같은데요. 어떻게 하시는 건가요?
-
해결됨[기초]코딩 무경험자도 가능! 90분에 뽀개는 구글 시트 업무 자동화
중급 강의에서 배우고 싶어요!
중급 강의에서는 월말 결산 자동화를 배우고 싶네요. 진짜 월말 결산하다보면 시간 뚝딱 흐르거든요. 초급에서 배운걸로 응용해서 해보려다가 예시 영상처럼 하려면 뭔가 .. 여러 변수를 지정해야할 거 같은데 제가 생각한 범위로는 vlookup 과 어떻게 다를지 예상이 안되어서 포기했어요. 얼른 중급 강의로 돌아와주세요! 배우고 싶습니다!
-
미해결구글 스프레드시트로 배우는 업무 자동화(직장인 코딩)
강의자료 공유 부탁드립니다. potamia49@gmail.com
강의자료 공유 부탁드립니다. potamia49@gmail.com
-
미해결구글 스프레드시트로 배우는 업무 자동화(직장인 코딩)
강의자료 공유 부탁드립니다.
안녕하세요 :)수업 잘 듣고 있습니다.쿼리문 이후 진행을 위해 강의자료 potermanya@gmail.com 으로 공유해주시면 감사하겠습니다!
-
미해결구글 스프레드시트로 배우는 업무 자동화(직장인 코딩)
API강의문서 질문
API강의하는 word문서는 따로 제공하지않는걸까요?