<aside> 📌 Task
큐에르
# exit_position_numeric 75 이상이면 완독한 것으로 간주
df['is_completed'] = df['exit_position_numeric'] >= 75
# 유입 경로별 완독률
completion_by_channel = df.groupby('entry_channel')['is_completed'].mean().sort_values(ascending=False)
# 추천 클릭 + 자발적 이탈 + 세부 이탈 사유 필터링
filtered = df[
(df['recommendation_clicked'] == 1) &
(df['dropout_reason_category'] == '자발적')
# & (df['dropout_reason_detail'].isin(['지루함', '너무 김']))
]
reason_ratio = filtered['dropout_reason_detail'].value_counts(normalize=True)
# 이탈 기준: exit_position_numeric < 75 (완독 실패)
df['is_dropout'] = df['exit_position_numeric'] < 75
# 추천 or 홈메인배너 유입자만 필터
subset = df[df['entry_channel'].isin(['추천', '홈메인배너'])]
# 미리보기 사용 여부별 이탈률 비교
dropout_by_preview = subset.groupby('quick_preview_used')['is_dropout'].mean()
석미느
# theme_mode 입력값 중 오타 정제
merged_df["theme_mode"] = merged_df["theme_mode"].replace("customized", "custom")
merged_df["theme_mode"] = merged_df["theme_mode"].replace("dark_mode", "dark")
merged_df["theme_mode"] = merged_df["theme_mode"].replace("Light", "light")
# birthday 컬럼으로 age / age_group 파생 변수 생성
merged_df["birth_year"] = pd.to_numeric(merged_df["birthday"].str[:4], errors="coerce")
current_year = pd.Timestamp.now().year
merged_df['age'] = current_year - merged_df['birth_year']
bins = [0, 19, 29, 39, 49, 200]
labels = ['10대 이하', '20대', '30대', '40대', '50대 이상']
merged_df['age_group'] = pd.cut(merged_df['age'], bins=bins, labels=labels, right=True)
재웅티비
여기에 입력하세욝!
정무느
birthday 990 non-null object
last_access_timestamp 1000 non-null object
⇒ datetime으로 변환 필요
기준일과 6개월 이전 날짜 정의 cutoff_date = pd.Timestamp("2024-01-01") - pd.DateOffset(months=6)
⇒
이녕으
</aside>
<aside> 📌 실행 및 진행 사항 정리
큐에르
# exit_position_numeric 95 이상이면 완독한 것으로 간주
df['is_completed'] = df['exit_position_numeric'] >= 95
# 유입 경로별 완독률
completion_by_channel = df.groupby('entry_channel')['is_completed'].mean().sort_values(ascending=False)
# 추천 클릭 + 자발적 이탈 + 세부 이탈 사유 필터링
filtered = df[
(df['recommendation_clicked'] == 1) &
(df['dropout_reason_category'] == '자발적')
# & (df['dropout_reason_detail'].isin(['지루함', '너무 김']))
]
reason_ratio = filtered['dropout_reason_detail'].value_counts(normalize=True)
# 이탈 기준: exit_position_numeric < 80 (완독 실패)
df['is_dropout'] = df['exit_position_numeric'] < 95
# 추천 or 홈메인배너 유입자만 필터
subset = df[df['entry_channel'].isin(['추천', '홈메인배너'])]
# 미리보기 사용 여부별 이탈률 비교
dropout_by_preview = subset.groupby('quick_preview_used')['is_dropout'].mean()
석미느
# theme_mode 입력값 중 오타 정제
merged_df["theme_mode"] = merged_df["theme_mode"].replace("customized", "custom")
merged_df["theme_mode"] = merged_df["theme_mode"].replace("dark_mode", "dark")
merged_df["theme_mode"] = merged_df["theme_mode"].replace("Light", "light")
# birthday 컬럼으로 age / age_group 파생 변수 생성
merged_df["birth_year"] = pd.to_numeric(merged_df["birthday"].str[:4], errors="coerce")
current_year = pd.Timestamp.now().year
merged_df['age'] = current_year - merged_df['birth_year']
bins = [0, 19, 29, 39, 49, 200]
labels = ['10대 이하', '20대', '30대', '40대', '50대 이상']
merged_df['age_group'] = pd.cut(merged_df['age'], bins=bins, labels=labels, right=True)
재웅티비
정무느
*데이터 정제 및 변환
# 1. last_access_timestamp datetime 변환
df['last_access_timestamp'] = pd.to_datetime(df['last_access_timestamp'])
# 2. birthday datetime 변환
df['birthday'] = pd.to_datetime(df['birthday'], errors='coerce')
#요일별평균 exit_position_numeric
#1 이상치 날짜 빼기
df = df.dropna(subset=["last_access_timestamp"])
# 2 weekday 컬럼 생성
df["weekday"] = df["last_access_timestamp"].dt.dayofweek
# 3 요일별 평균 exit_position_numeric 계산
weekday_avg_exit = (
filtered_df.groupby("weekday")["exit_position_numeric"]
.mean()
.reset_index())
# 4 요일 숫자를 이름으로 매핑
weekday_avg_exit["weekday_name"] = weekday_avg_exit["weekday"].map({
0: "월요일", 1: "화요일", 2: "수요일", 3: "목요일",
4: "금요일", 5: "토요일", 6: "일요일"})
# 5 평균 이탈 위치 기준 내림차순 정렬
weekday_avg_exit_sorted = weekday_avg_exit.sort_values(by="exit_position_numeric", ascending=False)
# 6 출력
print(weekday_avg_exit_sorted)
=> 주말완독율이 더 높지 않음, 틀린 가설
# 6개월 이상 미접속자의 이탈 사유 분석
# 1. 기준일과 6개월 이전 날짜 정의
cutoff_date = pd.Timestamp("2024-01-01") - pd.DateOffset(months=6)
# 2. 6개월 이상 접속하지 않은 유저 필터링
inactive_users = df[df["last_access_timestamp"] < cutoff_date]
# 3. dropout_reason_category 비율 계산
dropout_reason_pct = (
inactive_users["dropout_reason_category"]
.value_counts(normalize=True) * 100
).reset_index()
# 4. 컬럼명 정리
dropout_reason_pct.columns = ["dropout_reason_category", "percentage"]
# 5. 출력
print(dropout_reason_pct)
#자발적 이탈유저 상세사유
# 3. 자발적 이탈한 유저 중 상세 사유 존재하는 데이터만 필터링
voluntary_dropouts = inactive_users[
(inactive_users["dropout_reason_category"] == "자발적") &
(inactive_users["dropout_reason_detail"].notna())]
# 4. 상세 사유 비율 계산
detail_reason_pct = (
voluntary_dropouts["dropout_reason_detail"]
.value_counts(normalize=True) * 100).reset_index()
# 5. 컬럼명 정리
detail_reason_pct.columns = ["dropout_reason_detail", "percentage"]
# 6. 출력
print(detail_reason_pct)
이녕으
</aside>
<aside> 📌 결과
큐에르
user_id gender birthday device_type subscription_plan theme_mode \ 0 user_0001 male 1998-06-17 mobile monthly dark
1 user_0002 male 2010-04-15 mobile monthly dark
2 user_0003 male 1985-02-13 mobile monthly dark
3 user_0004 female 1974-11-21 eReader monthly light
4 user_0005 male 1970-11-01 mobile free_trial dark
.. ... ... ... ... ... ...
995 user_0996 male 2008-08-11 tablet monthly light
996 user_0997 male 1981-01-23 tablet monthly light
997 user_0998 male 2009-07-08 tablet monthly light
998 user_0999 male 2015-07-05 tablet monthly light
999 user_1000 male 1998-10-06 tablet monthly light
entry_channel quick_preview_used recommendation_clicked \\\\
0 추천 No True
1 추천 No True
2 추천 No True
3 추천 Yes True
4 검색 Yes False
.. ... ... ...
995 추천 Yes True
996 추천 Yes True
997 추천 Yes True
998 추천 Yes True
999 추천 Yes True
last_access_timestamp book_id genre exit_position_numeric \\\\
0 2023-05-08 14:13:00 E01 경제/시사 68
1 2023-01-13 00:54:00 E01 경제/시사 44
2 2023-07-12 09:13:03 E01 경제/시사 59
3 2023-11-20 21:24:55 N05 소설 97
4 2023-11-01 05:55:04 S02 자기계발 51
.. ... ... ... ...
995 2023-09-03 17:44:46 S01 자기계발 39
996 2023-04-06 07:03:38 S01 자기계발 71
997 2023-12-26 13:44:38 S01 자기계발 10
998 2023-06-26 22:39:14 S01 자기계발 15
999 2023-01-01 20:58:12 S01 자기계발 71
dropout_reason_category dropout_reason_detail is_completed is_dropout
0 자발적 지루함 False True
1 자발적 추천 실패 False True
2 자발적 너무 김 False True
3 UX 불편 NaN True False
4 UX 불편 NaN False True
.. ... ... ... ...
995 자발적 추천 실패 False True
996 자발적 너무 김 False True
997 UX 불편 NaN False True
998 자발적 금한일 False True
999 자발적 지루함 False True
</aside>