이 부분이 결과 차이의 80% 이상을 차지할 거예요.
subscription 전체 데이터에서 조건을 만족하는 user_id를 추출한 뒤, unique()로 리스트를 만듭니다. 그 후 apply를 통해 **"이 유저가 한 번이라도 유지한 적이 있는가?"**를 묻습니다.
first_sub라는 특정 데이터프레임의 **각 행(Row)**에 대해 즉시 판단합니다. 만약 first_sub가 유저당 하나의 레코드만 있는 게 아니라면, 같은 유저라도 어떤 행은 retain, 어떤 행은 churn으로 남을 수 있습니다.💡 핵심 요약: 1번은 '유저 중심(Global)', 2번은 '데이터 행 중심(Local)'으로 판단하고 있습니다.
파이썬에서 시간 데이터를 다룰 때 흔히 발생하는 '경계값' 문제입니다.
.dt.days를 사용했습니다. 이는 시간 차이를 계산한 뒤 소수점을 버리고 '일(day)' 단위 정수만 남깁니다.
7일 10시간이라면 dt.days는 7이 됩니다. 7 <= 7은 True이므로 'Retain'이 됩니다.pd.Timedelta(days=7)를 사용하여 타임스탬프 자체를 비교합니다.
expired_at + 7일 시점보다 10시간이라도 늦으면 False가 되어 'Churn'이 됩니다.time_diff <= 7 연산 시 NaN은 자동으로 False가 됩니다. new_retain_users에 포함되지 않으므로 else 'Churn'에 의해 'Churn'으로 분류됩니다. (논리적으로 안전함)isna()를 체크하여 'is_churned'를 True로 만들고 있습니다. 하지만 is_retained와 is_churned가 동시에 False가 될 가능성은 없는지, 그리고 first_sub['user_group'] = 'etc'라는 초기값이 나중에 어떻게 덮어씌워지는지에 따라 미세한 누수가 발생할 수 있습니다.2번 코드는 .loc를 여러 번 사용하여 값을 덮어쓰는 방식인데, 이는 데이터 양이 많아질 때 실수할 여지가 있고 가독성이 떨어집니다. 반면 1번 코드는 apply를 쓰긴 했지만 unique() 리스트를 만드는 과정에서 메모리 효율성이 떨어질 수 있죠.