""" User subset selection and subscription feature extraction. """ import pandas as pd import numpy as np from typing import Dict, List, Optional, Tuple, Any import gc class UserSelector: """Handle user subset selection and basic feature extraction.""" def __init__(self): self.subscription_mapping = {"active": 2, "past_due": 1, "free": 0} def select_creators( self, total_clip_df: pd.DataFrame, discord_info_df: pd.DataFrame, include_all_users: bool = True, verbose: bool = True, ) -> pd.DataFrame: """ Select users for analysis, including both creators and non-creators. Args: total_clip_df: DataFrame containing all clips discord_info_df: DataFrame containing user info include_all_users: If True, include all users (creators and non-creators) verbose: Whether to print progress messages Returns: DataFrame with selected user IDs and creator flag """ if verbose: if include_all_users: print("🎯 USER SUBSET SELECTION - ALL USERS") else: print("🎯 USER SUBSET SELECTION - CREATORS ONLY") print("=" * 50) # Get users who have created clips clip_creators = set(total_clip_df["user_id"].dropna().unique()) if include_all_users: # Start with ALL users from discord_info_df users_df = pd.DataFrame({"user_id": discord_info_df["user_id"].unique()}) users_df["user_id"] = pd.to_numeric(users_df["user_id"], errors="coerce") users_df = users_df.dropna(subset=["user_id"]).astype({"user_id": int}) users_df = users_df.drop_duplicates().reset_index(drop=True) # Add creator flag users_df["is_creator"] = users_df["user_id"].isin(clip_creators).astype(int) if verbose: n_creators = users_df["is_creator"].sum() n_non_creators = len(users_df) - n_creators print(f"📊 Total users selected: {len(users_df):,}") print( f" • Content creators: {n_creators:,} ({n_creators/len(users_df)*100:.1f}%)" ) print( f" • Non-creators: {n_non_creators:,} ({n_non_creators/len(users_df)*100:.1f}%)" ) print(f"✅ Focus: Analyzing entire user base including free users") else: # Original behavior - creators only users_df = pd.DataFrame({"user_id": list(clip_creators)}) users_df["user_id"] = pd.to_numeric(users_df["user_id"], errors="coerce") users_df = users_df.dropna(subset=["user_id"]).astype({"user_id": int}) users_df = users_df.drop_duplicates().reset_index(drop=True) # Filter to only users that exist in discord_info_df valid_users = set(discord_info_df["user_id"].unique()) users_df = users_df[users_df["user_id"].isin(valid_users)] users_df["is_creator"] = 1 # All are creators if verbose: print(f"📊 Content creators selected: {len(users_df):,} users") print( f"📊 Total unique creators in clips dataset: {len(clip_creators):,}" ) print(f"📊 Valid creators (exist in user database): {len(users_df):,}") print( f"📊 This represents {len(users_df)/len(discord_info_df)*100:.1f}% of all users in the system" ) print( f"✅ Focus: Analyzing actual content creators from interesting clips dataset" ) return users_df def extract_subscription_features( self, features_df: pd.DataFrame, discord_info_df: pd.DataFrame, verbose: bool = True, ) -> pd.DataFrame: """ Extract subscription-related features. Args: features_df: DataFrame with user IDs discord_info_df: DataFrame containing subscription info verbose: Whether to print progress messages Returns: DataFrame with subscription features added """ if verbose: print("💳 SUBSCRIPTION FEATURES") print("=" * 50) # Get subscription data for selected users subscription_data = discord_info_df[ discord_info_df["user_id"].isin(features_df["user_id"]) ] # Merge subscription status features_df = features_df.merge( subscription_data[["user_id", "subscription_status"]], on="user_id", how="left", ) # Map to numeric tiers features_df["subscription_tier"] = ( features_df["subscription_status"] .map(self.subscription_mapping) .fillna(0) .astype(int) ) # Drop the temporary column features_df.drop("subscription_status", axis=1, inplace=True) if verbose: # Summary sub_dist = features_df["subscription_tier"].value_counts() print( f"📊 Distribution: Free={sub_dist.get(0, 0):,} | " f"Past Due={sub_dist.get(1, 0):,} | Active={sub_dist.get(2, 0):,}" ) print(f"✅ Shape: {features_df.shape}") return features_df def create_initial_features( self, total_clip_df: pd.DataFrame, discord_info_df: pd.DataFrame, include_all_users: bool = True, verbose: bool = True, ) -> pd.DataFrame: """ Create initial feature dataframe with user selection and subscription features. Args: total_clip_df: DataFrame containing all clips discord_info_df: DataFrame containing user info include_all_users: If True, include all users (creators and non-creators) verbose: Whether to print progress messages Returns: Initial features DataFrame with is_creator flag """ # Select users (now includes all users by default) features_df = self.select_creators( total_clip_df, discord_info_df, include_all_users, verbose ) # Add subscription features features_df = self.extract_subscription_features( features_df, discord_info_df, verbose ) return features_df def get_user_statistics(self, features_df: pd.DataFrame) -> Dict[str, Any]: """ Get statistics about the selected users. Args: features_df: DataFrame with selected users Returns: Dictionary with user statistics """ stats = { "total_users": len(features_df), "subscription_distribution": features_df["subscription_tier"] .value_counts() .to_dict(), "active_subscribers": (features_df["subscription_tier"] == 2).sum(), "active_subscriber_pct": (features_df["subscription_tier"] == 2).mean() * 100, "paying_users": (features_df["subscription_tier"] > 0).sum(), "paying_user_pct": (features_df["subscription_tier"] > 0).mean() * 100, } return stats