{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.17","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"tpu1vmV38","dataSources":[{"sourceId":104854,"databundleVersionId":12637092,"sourceType":"competition"}],"dockerImageVersionId":31042,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom multiprocessing import Pool, cpu_count\nfrom tqdm import tqdm\n\nfrom datetime import datetime, timedelta\nimport warnings\nwarnings.filterwarnings('ignore')\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom scipy.stats import chi2_contingency, f_oneway\n\ntrain = pd.read_parquet('/kaggle/input/alpha-summer-challenge/train.pa')\ntransactions = pd.read_parquet('/kaggle/input/alpha-summer-challenge/df_transaction.pa')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T05:22:58.626603Z","iopub.execute_input":"2025-07-04T05:22:58.627106Z","iopub.status.idle":"2025-07-04T05:23:11.859172Z","shell.execute_reply.started":"2025-07-04T05:22:58.627079Z","shell.execute_reply":"2025-07-04T05:23:11.854765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Анализ частот\nmcc_freq = transactions['mcc_code'].value_counts()\nprint(\"Топ-20 MCC по частоте:\")\nprint(mcc_freq.head(20))\n\n# 2. Анализ средних сумм  \nmcc_amounts = transactions.groupby('mcc_code')['amount'].agg(['mean', 'median', 'count']).sort_values('mean', ascending=False)\nprint(\"\\nТоп-20 MCC по средней сумме:\")\nprint(mcc_amounts.head(20))\n\n# 3. Найти \"дорогие\" vs \"дешевые\" категории\nexpensive_threshold = transactions['amount'].quantile(0.8)\ncheap_threshold = transactions['amount'].quantile(0.2)\n\nexpensive_mccs = transactions[transactions['amount'] > expensive_threshold]['mcc_code'].value_counts().head(10).index.tolist()\ncheap_mccs = transactions[transactions['amount'] < cheap_threshold]['mcc_code'].value_counts().head(10).index.tolist()\n\nprint(f\"\\nВероятно 'luxury' MCC (дорогие покупки): {expensive_mccs}\")\nprint(f\"Вероятно 'essential' MCC (дешевые/частые): {cheap_mccs}\")\n\n# 4. Data-driven категории\nessential_categories = mcc_freq.head(10).index.tolist()  # Топ по частоте\nluxury_categories = expensive_mccs[:5]  # Топ по сумме\n\nprint(f\"\\nData-driven essential: {essential_categories}\")\nprint(f\"Data-driven luxury: {luxury_categories}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:14:54.067861Z","iopub.execute_input":"2025-07-04T07:14:54.068205Z","iopub.status.idle":"2025-07-04T07:14:58.113100Z","shell.execute_reply.started":"2025-07-04T07:14:54.068177Z","shell.execute_reply":"2025-07-04T07:14:58.106281Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom multiprocessing import Pool, cpu_count\nfrom tqdm import tqdm\nfrom datetime import datetime, timedelta\nimport warnings\nwarnings.filterwarnings('ignore')\n\ndef create_advanced_features(transactions, train_clients):\n    \"\"\"\n    Создание продвинутых признаков для банковских транзакций\n    \"\"\"\n    features = []\n    \n    # Подготавливаем базовые временные фичи, если их нет\n    if 'hour' not in transactions.columns:\n        transactions['hour'] = pd.to_datetime(transactions['date_time']).dt.hour\n    if 'weekday' not in transactions.columns:\n        transactions['weekday'] = pd.to_datetime(transactions['date_time']).dt.weekday\n    if 'date' not in transactions.columns:\n        transactions['date'] = pd.to_datetime(transactions['date_time']).dt.date\n    \n    # === 1. VELOCITY & MOMENTUM FEATURES ===\n    # Анализ скорости изменения поведения клиента\n    print(\"🚀 Создаем velocity/momentum фичи...\")\n    \n    # Разделим данные на периоды (первые 2 месяца vs последний месяц)\n    transactions['date'] = pd.to_datetime(transactions['date_time']).dt.date\n    max_date = transactions['date'].max()\n    split_date = max_date - timedelta(days=30)\n    \n    # Статистики по периодам\n    early_period = transactions[transactions['date'] <= split_date]\n    late_period = transactions[transactions['date'] > split_date]\n    \n    # Сравнение активности между периодами\n    early_stats = early_period.groupby('client_num').agg({\n        'amount': ['sum', 'mean', 'count'],\n        'mcc_code': 'nunique'\n    }).reset_index()\n    early_stats.columns = ['client_num', 'early_sum', 'early_mean', 'early_count', 'early_mcc']\n    \n    late_stats = late_period.groupby('client_num').agg({\n        'amount': ['sum', 'mean', 'count'],\n        'mcc_code': 'nunique'\n    }).reset_index()\n    late_stats.columns = ['client_num', 'late_sum', 'late_mean', 'late_count', 'late_mcc']\n    \n    # Momentum фичи\n    momentum_df = pd.merge(early_stats, late_stats, on='client_num', how='outer').fillna(0)\n    momentum_df['momentum_sum'] = (momentum_df['late_sum'] - momentum_df['early_sum']) / (momentum_df['early_sum'] + 1)\n    momentum_df['momentum_mean'] = (momentum_df['late_mean'] - momentum_df['early_mean']) / (momentum_df['early_mean'] + 1)\n    momentum_df['momentum_count'] = (momentum_df['late_count'] - momentum_df['early_count']) / (momentum_df['early_count'] + 1)\n    momentum_df['momentum_mcc'] = (momentum_df['late_mcc'] - momentum_df['early_mcc']) / (momentum_df['early_mcc'] + 1)\n    \n    # Стабильность поведения\n    momentum_df['behavior_stability'] = 1 / (1 + np.abs(momentum_df['momentum_sum']) + \n                                           np.abs(momentum_df['momentum_mean']) + \n                                           np.abs(momentum_df['momentum_count']))\n    \n    features.append(momentum_df[['client_num', 'momentum_sum', 'momentum_mean', 'momentum_count', \n                                'momentum_mcc', 'behavior_stability']])\n    \n    # === 2. CYCLICAL & SEASONAL FEATURES ===\n    print(\"🔄 Создаем циклические/сезонные фичи...\")\n    \n    transactions['month'] = pd.to_datetime(transactions['date_time']).dt.month\n    transactions['day_of_month'] = pd.to_datetime(transactions['date_time']).dt.day\n    transactions['is_weekend'] = pd.to_datetime(transactions['date_time']).dt.weekday >= 5\n    transactions['is_month_start'] = transactions['day_of_month'] <= 7\n    transactions['is_month_end'] = transactions['day_of_month'] >= 25\n    \n    # Циклические энкодинги\n    transactions['hour_sin'] = np.sin(2 * np.pi * transactions['hour'] / 24)\n    transactions['hour_cos'] = np.cos(2 * np.pi * transactions['hour'] / 24)\n    transactions['weekday_sin'] = np.sin(2 * np.pi * transactions['weekday'] / 7)\n    transactions['weekday_cos'] = np.cos(2 * np.pi * transactions['weekday'] / 7)\n    \n    # Сезонные паттерны\n    seasonal_features = transactions.groupby('client_num').agg({\n        'is_weekend': ['sum', 'mean'],\n        'is_month_start': ['sum', 'mean'],\n        'is_month_end': ['sum', 'mean'],\n        'hour_sin': 'mean',\n        'hour_cos': 'mean',\n        'weekday_sin': 'mean',\n        'weekday_cos': 'mean'\n    }).reset_index()\n    seasonal_features.columns = ['client_num', 'weekend_txns', 'weekend_ratio', \n                               'month_start_txns', 'month_start_ratio',\n                               'month_end_txns', 'month_end_ratio',\n                               'avg_hour_sin', 'avg_hour_cos',\n                               'avg_weekday_sin', 'avg_weekday_cos']\n    \n    features.append(seasonal_features)\n    \n    # === 3. TRANSACTION GRAPH/NETWORK FEATURES ===\n    print(\"🕸️ Создаем сетевые фичи...\")\n    \n    # Связи через мерчантов\n    merchant_clients = transactions.groupby('merchant_name')['client_num'].nunique().reset_index()\n    merchant_clients.columns = ['merchant_name', 'merchant_popularity']\n    \n    # Джойним обратно к транзакциям\n    transactions_with_merchant = transactions.merge(merchant_clients, on='merchant_name', how='left')\n    \n    # Сетевые метрики клиентов\n    network_features = transactions_with_merchant.groupby('client_num').agg({\n        'merchant_popularity': ['mean', 'max', 'min', 'std'],\n        'merchant_name': 'nunique'\n    }).reset_index()\n    network_features.columns = ['client_num', 'avg_merchant_popularity', 'max_merchant_popularity',\n                              'min_merchant_popularity', 'std_merchant_popularity', 'unique_merchants']\n    \n    # Эксклюзивность клиента (использует ли редкие мерчанты)\n    network_features['exclusivity_score'] = 1 / (network_features['avg_merchant_popularity'] + 1)\n    \n    features.append(network_features)\n    \n    # === 4. SPENDING PATTERN COMPLEXITY ===\n    print(\"📊 Создаем фичи сложности паттернов...\")\n    \n    # Энтропия трат по категориям\n    def calculate_entropy(series):\n        probs = series.value_counts(normalize=True)\n        return -np.sum(probs * np.log2(probs + 1e-10))\n    \n    # Энтропия по MCC кодам\n    mcc_entropy = transactions.groupby('client_num')['mcc_code'].apply(calculate_entropy).reset_index()\n    mcc_entropy.columns = ['client_num', 'mcc_entropy']\n    \n    # Энтропия по времени (часы)\n    time_entropy = transactions.groupby('client_num')['hour'].apply(calculate_entropy).reset_index()\n    time_entropy.columns = ['client_num', 'time_entropy']\n    \n    # Энтропия по суммам (дискретизированным)\n    transactions['amount_bucket'] = pd.cut(transactions['amount'], bins=20, labels=False)\n    amount_entropy = transactions.groupby('client_num')['amount_bucket'].apply(calculate_entropy).reset_index()\n    amount_entropy.columns = ['client_num', 'amount_entropy']\n    \n    # Объединяем энтропии\n    entropy_features = mcc_entropy.merge(time_entropy, on='client_num', how='outer')\n    entropy_features = entropy_features.merge(amount_entropy, on='client_num', how='outer')\n    \n    # Общая сложность поведения\n    entropy_features['total_complexity'] = (entropy_features['mcc_entropy'] + \n                                          entropy_features['time_entropy'] + \n                                          entropy_features['amount_entropy'])\n    \n    features.append(entropy_features)\n    \n    # === 5. FINANCIAL HEALTH INDICATORS ===\n    print(\"💰 Создаем индикаторы финансового здоровья...\")\n    \n    # Анализ крупных трат\n    transactions['is_large_transaction'] = transactions['amount'] > transactions.groupby('client_num')['amount'].transform('quantile', 0.9)\n    \n    # Паттерны крупных трат\n    large_txn_features = transactions.groupby('client_num').agg({\n        'is_large_transaction': ['sum', 'mean']\n    }).reset_index()\n    large_txn_features.columns = ['client_num', 'large_txn_count', 'large_txn_ratio']\n    \n    # Регулярность трат\n    daily_spending = transactions.groupby(['client_num', 'date'])['amount'].sum().reset_index()\n    spending_regularity = daily_spending.groupby('client_num')['amount'].agg(['std', 'mean']).reset_index()\n    spending_regularity['spending_cv'] = spending_regularity['std'] / (spending_regularity['mean'] + 1)\n    spending_regularity = spending_regularity[['client_num', 'spending_cv']]\n    \n    # Тренд трат (линейная регрессия по времени)\n    def calculate_spending_trend(group):\n        if len(group) < 3:\n            return 0\n        x = np.arange(len(group))\n        try:\n            slope = np.polyfit(x, group['amount'], 1)[0]\n            return slope\n        except:\n            return 0\n    \n    spending_trend = daily_spending.groupby('client_num').apply(calculate_spending_trend).reset_index()\n    spending_trend.columns = ['client_num', 'spending_trend']\n    \n    # Объединяем финансовые индикаторы\n    financial_features = large_txn_features.merge(spending_regularity, on='client_num', how='outer')\n    financial_features = financial_features.merge(spending_trend, on='client_num', how='outer')\n    \n    features.append(financial_features)\n    \n    # === 6. BEHAVIORAL ANOMALIES ===\n    print(\"🚨 Создаем фичи аномалий поведения...\")\n    \n    # Z-scores для основных метрик\n    client_stats = transactions.groupby('client_num').agg({\n        'amount': ['mean', 'std', 'count'],\n        'date': 'nunique'\n    }).reset_index()\n    client_stats.columns = ['client_num', 'avg_amount', 'std_amount', 'txn_count', 'active_days']\n    \n    # Глобальные z-scores\n    client_stats['z_avg_amount'] = (client_stats['avg_amount'] - client_stats['avg_amount'].mean()) / client_stats['avg_amount'].std()\n    client_stats['z_txn_count'] = (client_stats['txn_count'] - client_stats['txn_count'].mean()) / client_stats['txn_count'].std()\n    client_stats['z_active_days'] = (client_stats['active_days'] - client_stats['active_days'].mean()) / client_stats['active_days'].std()\n    \n    # Аномальность клиента\n    client_stats['anomaly_score'] = np.sqrt(client_stats['z_avg_amount']**2 + \n                                          client_stats['z_txn_count']**2 + \n                                          client_stats['z_active_days']**2)\n    \n    features.append(client_stats[['client_num', 'z_avg_amount', 'z_txn_count', \n                                'z_active_days', 'anomaly_score']])\n    \n    # === 7. ADVANCED ROLLING FEATURES ===\n    print(\"📈 Создаем продвинутые rolling фичи...\")\n    \n    # Экспоненциальное скользящее среднее\n    def calculate_ema_features(group, alpha=0.3):\n        amounts = group.sort_values('date_time')['amount']\n        ema = amounts.ewm(alpha=alpha).mean()\n        return pd.Series({\n            'ema_amount': ema.iloc[-1] if len(ema) > 0 else 0,\n            'ema_volatility': amounts.ewm(alpha=alpha).std().iloc[-1] if len(amounts) > 1 else 0\n        })\n    \n    ema_features = transactions.groupby('client_num').apply(calculate_ema_features).reset_index()\n    \n    # Автокорреляция в тратах\n    def calculate_autocorr(group):\n        if len(group) < 10:\n            return 0\n        daily_amounts = group.groupby('date')['amount'].sum()\n        try:\n            return daily_amounts.autocorr(lag=1)\n        except:\n            return 0\n    \n    autocorr_features = transactions.groupby('client_num').apply(calculate_autocorr).reset_index()\n    autocorr_features.columns = ['client_num', 'spending_autocorr']\n    \n    # Объединяем advanced rolling фичи\n    advanced_rolling = ema_features.merge(autocorr_features, on='client_num', how='outer')\n    features.append(advanced_rolling)\n    \n    # === 8. CATEGORY-SPECIFIC INSIGHTS ===\n    print(\"🏪 Создаем фичи по категориям...\")\n    \n    # DATA-DRIVEN категории на основе ваших данных\n    # Высокочастотные (ежедневные нужды)\n    frequent_categories = [5411, 5499, 5814, 4131, 3990]  # Топ-5 по частоте\n    \n    # Дорогие операции (по средней сумме, исключая outliers)\n    expensive_categories = [6011, 5511, 3011, 4722, 5712]  # Высокие средние суммы\n    \n    # Средний ценовой сегмент (частые + умеренные суммы)\n    moderate_categories = [5541, 5912, 5921, 5812, 6536]\n    \n    # Редкие категории (низкая частота)\n    rare_categories = [7999, 5993, 5462]  # Из топ-20 но с низкой частотой\n    \n    transactions['is_frequent'] = transactions['mcc_code'].isin(frequent_categories)\n    transactions['is_expensive'] = transactions['mcc_code'].isin(expensive_categories)\n    transactions['is_moderate'] = transactions['mcc_code'].isin(moderate_categories)\n    transactions['is_rare'] = transactions['mcc_code'].isin(rare_categories)\n    \n    category_features = transactions.groupby('client_num').agg({\n        'is_frequent': ['sum', 'mean'],\n        'is_expensive': ['sum', 'mean'],\n        'is_moderate': ['sum', 'mean'],\n        'is_rare': ['sum', 'mean']\n    }).reset_index()\n    category_features.columns = ['client_num', 'frequent_txns', 'frequent_ratio', \n                               'expensive_txns', 'expensive_ratio',\n                               'moderate_txns', 'moderate_ratio',\n                               'rare_txns', 'rare_ratio']\n    \n    # Паттерны поведения по категориям\n    category_features['frequent_expensive_ratio'] = category_features['frequent_txns'] / (category_features['expensive_txns'] + 1)\n    category_features['category_diversity'] = (category_features['frequent_ratio'] + \n                                             category_features['expensive_ratio'] + \n                                             category_features['moderate_ratio'] + \n                                             category_features['rare_ratio'])\n    \n    # Индекс \"practical spending\" - много частых, мало редких\n    category_features['practical_spending_index'] = (category_features['frequent_ratio'] - \n                                                   category_features['rare_ratio'] + 1) / 2\n    \n    features.append(category_features)\n    \n    # === ОБЪЕДИНЕНИЕ ВСЕХ ФИЧЕЙ ===\n    print(\"🔗 Объединяем все фичи...\")\n    \n    # Начинаем с первого набора фичей\n    final_features = features[0]\n    \n    # Последовательно джойним все остальные\n    for i, feature_set in enumerate(features[1:], 1):\n        print(f\"   Джойним набор {i+1}/{len(features)}: {feature_set.shape}\")\n        final_features = final_features.merge(feature_set, on='client_num', how='outer')\n    \n    # Заполняем пропуски\n    final_features = final_features.fillna(0)\n    \n    print(f\"✅ Создано {final_features.shape[1]-1} новых фичей для {final_features.shape[0]} клиентов\")\n    \n    return final_features","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-04T05:23:11.873301Z","iopub.execute_input":"2025-07-04T05:23:11.873661Z","iopub.status.idle":"2025-07-04T05:23:11.917189Z","shell.execute_reply.started":"2025-07-04T05:23:11.873640Z","shell.execute_reply":"2025-07-04T05:23:11.911860Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_balance_specific_features(transactions, train_clients):\n    \"\"\"\n    Создание фичей специально для предсказания баланса счета\n    \"\"\"\n    features = []\n    \n    # Подготавливаем базовые временные фичи, если их нет\n    if 'hour' not in transactions.columns:\n        transactions['hour'] = pd.to_datetime(transactions['date_time']).dt.hour\n    if 'weekday' not in transactions.columns:\n        transactions['weekday'] = pd.to_datetime(transactions['date_time']).dt.weekday\n    if 'date' not in transactions.columns:\n        transactions['date'] = pd.to_datetime(transactions['date_time']).dt.date\n    \n    # === 1. CASH FLOW PATTERNS ===\n    print(\"💸 Создаем фичи денежных потоков...\")\n    \n    # Имитация входящих/исходящих потоков\n    # Предположим, что крупные положительные суммы - это поступления\n    # а обычные суммы - это траты\n    \n    # Анализ распределения сумм для определения пороговых значений\n    amount_stats = transactions.groupby('client_num')['amount'].agg(['mean', 'std', 'quantile']).reset_index()\n    \n    # Определяем \"крупные\" транзакции для каждого клиента\n    def classify_transactions(group):\n        q75 = group['amount'].quantile(0.75)\n        q25 = group['amount'].quantile(0.25)\n        \n        group['is_large_inflow'] = (group['amount'] > q75 * 2)  # Возможные поступления\n        group['is_regular_spend'] = (group['amount'] >= q25) & (group['amount'] <= q75)\n        group['is_small_spend'] = group['amount'] < q25\n        \n        return group[['client_num', 'amount', 'is_large_inflow', 'is_regular_spend', 'is_small_spend']]\n    \n    classified_txns = transactions.groupby('client_num').apply(classify_transactions).reset_index(drop=True)\n    \n    # Паттерны денежных потоков\n    cashflow_features = classified_txns.groupby('client_num').agg({\n        'is_large_inflow': ['sum', 'mean'],\n        'is_regular_spend': ['sum', 'mean'],\n        'is_small_spend': ['sum', 'mean']\n    }).reset_index()\n    cashflow_features.columns = ['client_num', 'large_inflow_count', 'large_inflow_ratio',\n                               'regular_spend_count', 'regular_spend_ratio',\n                               'small_spend_count', 'small_spend_ratio']\n    \n    # Баланс между поступлениями и тратами\n    cashflow_features['inflow_spend_balance'] = cashflow_features['large_inflow_ratio'] - cashflow_features['regular_spend_ratio']\n    \n    features.append(cashflow_features)\n    \n    # === 2. SPENDING VELOCITY & BURN RATE ===\n    print(\"🔥 Создаем фичи скорости трат...\")\n    \n    # Сортируем транзакции по времени\n    transactions_sorted = transactions.sort_values(['client_num', 'date_time'])\n    \n    # Кумулятивные суммы и скорость трат\n    def calculate_burn_rate(group):\n        if len(group) < 2:\n            return pd.Series({'burn_rate': 0, 'acceleration': 0, 'days_to_spend_median': 0})\n        \n        # Кумулятивная сумма\n        group['cumsum'] = group['amount'].cumsum()\n        \n        # Время между транзакциями в днях\n        group['time_diff_days'] = group['date_time'].diff().dt.total_seconds() / (24 * 3600)\n        \n        # Скорость трат (сумма/день)\n        daily_spend = group['amount'].sum() / ((group['date_time'].max() - group['date_time'].min()).days + 1)\n        \n        # Ускорение трат (изменение скорости)\n        if len(group) >= 4:\n            half_point = len(group) // 2\n            early_rate = group.iloc[:half_point]['amount'].sum() / (half_point + 1)\n            late_rate = group.iloc[half_point:]['amount'].sum() / (len(group) - half_point + 1)\n            acceleration = (late_rate - early_rate) / (early_rate + 1)\n        else:\n            acceleration = 0\n        \n        # Дни на потратить медианную сумму\n        median_amount = group['amount'].median()\n        days_to_spend_median = median_amount / (daily_spend + 1)\n        \n        return pd.Series({\n            'burn_rate': daily_spend,\n            'acceleration': acceleration,\n            'days_to_spend_median': days_to_spend_median\n        })\n    \n    burn_features = transactions_sorted.groupby('client_num').apply(calculate_burn_rate).reset_index()\n    features.append(burn_features)\n    \n    # === 3. FINANCIAL DISCIPLINE INDICATORS ===\n    print(\"🎯 Создаем индикаторы финансовой дисциплины...\")\n    \n    # Регулярность трат\n    daily_spending = transactions.groupby(['client_num', 'date'])['amount'].sum().reset_index()\n    \n    def calculate_discipline_metrics(group):\n        amounts = group['amount']\n        \n        # Коэффициент вариации\n        cv = amounts.std() / (amounts.mean() + 1)\n        \n        # Количество дней без трат\n        total_days = (group['date'].max() - group['date'].min()).days + 1\n        active_days = len(group)\n        inactive_days = total_days - active_days\n        \n        # Максимальная пауза в тратах\n        dates_sorted = sorted(group['date'])\n        max_gap = max([(dates_sorted[i+1] - dates_sorted[i]).days for i in range(len(dates_sorted)-1)] + [0])\n        \n        # Стабильность недельных трат\n        group['week'] = pd.to_datetime(group['date']).dt.isocalendar().week\n        weekly_spending = group.groupby('week')['amount'].sum()\n        weekly_stability = 1 / (weekly_spending.std() / (weekly_spending.mean() + 1) + 1)\n        \n        return pd.Series({\n            'spending_cv': cv,\n            'inactive_days_ratio': inactive_days / total_days,\n            'max_spending_gap': max_gap,\n            'weekly_stability': weekly_stability\n        })\n    \n    discipline_features = daily_spending.groupby('client_num').apply(calculate_discipline_metrics).reset_index()\n    features.append(discipline_features)\n    \n    # === 4. ECONOMIC CYCLE SENSITIVITY ===\n    print(\"📊 Создаем фичи чувствительности к экономическим циклам...\")\n    \n    # Анализ трат в начале/середине/конце месяца\n    transactions['day_of_month'] = pd.to_datetime(transactions['date_time']).dt.day\n    transactions['month_period'] = pd.cut(transactions['day_of_month'], \n                                        bins=[0, 10, 20, 31], \n                                        labels=['month_start', 'month_mid', 'month_end'])\n    \n    # Паттерны трат по периодам месяца\n    monthly_patterns = transactions.groupby(['client_num', 'month_period'])['amount'].sum().unstack(fill_value=0)\n    monthly_patterns.columns = [f'spending_{col}' for col in monthly_patterns.columns]\n    monthly_patterns = monthly_patterns.reset_index()\n    \n    # Коэффициент неравномерности трат по месяцу\n    monthly_patterns['monthly_unevenness'] = monthly_patterns.iloc[:, 1:].std(axis=1) / (monthly_patterns.iloc[:, 1:].mean(axis=1) + 1)\n    \n    features.append(monthly_patterns)\n    \n    # === 5. LOYALTY & HABIT PATTERNS ===\n    print(\"🔄 Создаем фичи лояльности и привычек...\")\n    \n    # Анализ повторных покупок у одних мерчантов\n    merchant_loyalty = transactions.groupby(['client_num', 'merchant_name']).agg({\n        'amount': ['count', 'sum', 'mean'],\n        'date_time': ['min', 'max']\n    }).reset_index()\n    \n    merchant_loyalty.columns = ['client_num', 'merchant_name', 'visits', 'total_spent', 'avg_spent', 'first_visit', 'last_visit']\n    \n    # Метрики лояльности\n    loyalty_metrics = merchant_loyalty.groupby('client_num').agg({\n        'visits': ['max', 'mean', 'std'],\n        'total_spent': ['max', 'mean'],\n        'merchant_name': 'nunique'\n    }).reset_index()\n    loyalty_metrics.columns = ['client_num', 'max_visits_merchant', 'avg_visits_merchant', 'std_visits_merchant',\n                             'max_spent_merchant', 'avg_spent_merchant', 'unique_merchants']\n    \n    # Индекс лояльности\n    loyalty_metrics['loyalty_index'] = loyalty_metrics['max_visits_merchant'] / (loyalty_metrics['unique_merchants'] + 1)\n    \n    # Привычки по дням недели\n    weekday_habits = transactions.groupby(['client_num', 'weekday'])['amount'].sum().unstack(fill_value=0)\n    weekday_habits.columns = [f'weekday_{col}_spending' for col in weekday_habits.columns]\n    weekday_habits = weekday_habits.reset_index()\n    \n    # Стабильность привычек\n    weekday_habits['weekday_habit_stability'] = 1 / (weekday_habits.iloc[:, 1:].std(axis=1) / (weekday_habits.iloc[:, 1:].mean(axis=1) + 1) + 1)\n    \n    features.append(loyalty_metrics)\n    features.append(weekday_habits)\n    \n    # === 6. TRANSACTION TIMING INTELLIGENCE ===\n    print(\"⏰ Создаем фичи интеллектуального анализа времени...\")\n    \n    # Анализ времени между транзакциями\n    def calculate_timing_features(group):\n        if len(group) < 3:\n            return pd.Series({'timing_regularity': 0, 'peak_activity_hour': 12, 'activity_concentration': 0})\n        \n        # Сортируем по времени\n        sorted_group = group.sort_values('date_time')\n        \n        # Интервалы между транзакциями\n        intervals = sorted_group['date_time'].diff().dt.total_seconds() / 3600  # в часах\n        intervals = intervals.dropna()\n        \n        # Регулярность по интервалам\n        if len(intervals) > 1:\n            timing_regularity = 1 / (intervals.std() / (intervals.mean() + 1) + 1)\n        else:\n            timing_regularity = 0\n        \n        # Пиковый час активности\n        hour_counts = sorted_group['hour'].value_counts()\n        peak_activity_hour = hour_counts.index[0] if len(hour_counts) > 0 else 12\n        \n        # Концентрация активности (энтропия по часам)\n        hour_probs = hour_counts / len(sorted_group)\n        activity_concentration = -np.sum(hour_probs * np.log2(hour_probs + 1e-10))\n        \n        return pd.Series({\n            'timing_regularity': timing_regularity,\n            'peak_activity_hour': peak_activity_hour,\n            'activity_concentration': activity_concentration\n        })\n    \n    timing_features = transactions.groupby('client_num').apply(calculate_timing_features).reset_index()\n    features.append(timing_features)\n    \n    # === 7. RISK INDICATORS ===\n    print(\"⚠️ Создаем индикаторы финансового риска...\")\n    \n    # Анализ экстремальных трат\n    def calculate_risk_indicators(group):\n        amounts = group['amount']\n        \n        # Процент экстремальных трат\n        q95 = amounts.quantile(0.95)\n        extreme_ratio = (amounts > q95).mean()\n        \n        # Максимальная трата относительно медианы\n        max_vs_median = amounts.max() / (amounts.median() + 1)\n        \n        # Волатильность трат\n        volatility = amounts.std() / (amounts.mean() + 1)\n        \n        # Склонность к крупным тратам в конце периода\n        sorted_amounts = group.sort_values('date_time')['amount']\n        if len(sorted_amounts) >= 4:\n            last_quarter = sorted_amounts.iloc[-len(sorted_amounts)//4:]\n            first_quarter = sorted_amounts.iloc[:len(sorted_amounts)//4]\n            end_period_bias = last_quarter.mean() / (first_quarter.mean() + 1)\n        else:\n            end_period_bias = 1\n        \n        return pd.Series({\n            'extreme_spending_ratio': extreme_ratio,\n            'max_vs_median_ratio': max_vs_median,\n            'spending_volatility': volatility,\n            'end_period_spending_bias': end_period_bias\n        })\n    \n    risk_features = transactions.groupby('client_num').apply(calculate_risk_indicators).reset_index()\n    features.append(risk_features)\n    \n    # === 8. CATEGORY DIVERSIFICATION ===\n    print(\"🎯 Создаем фичи диверсификации трат...\")\n    \n    # Индекс диверсификации Херфиндаля для MCC кодов\n    def calculate_herfindahl_index(group):\n        mcc_counts = group['mcc_code'].value_counts(normalize=True)\n        hhi = (mcc_counts ** 2).sum()\n        return 1 - hhi  # Инвертируем для интерпретации как диверсификации\n    \n    # Диверсификация по категориям\n    diversification_mcc = transactions.groupby('client_num').apply(calculate_herfindahl_index).reset_index()\n    diversification_mcc.columns = ['client_num', 'mcc_diversification']\n    \n    # Диверсификация по мерчантам\n    def calculate_merchant_diversification(group):\n        merchant_counts = group['merchant_name'].value_counts(normalize=True)\n        hhi = (merchant_counts ** 2).sum()\n        return 1 - hhi\n    \n    diversification_merchant = transactions.groupby('client_num').apply(calculate_merchant_diversification).reset_index()\n    diversification_merchant.columns = ['client_num', 'merchant_diversification']\n    \n    # Объединяем диверсификации\n    diversification_features = diversification_mcc.merge(diversification_merchant, on='client_num', how='outer')\n    \n    # Общий индекс диверсификации\n    diversification_features['total_diversification'] = (diversification_features['mcc_diversification'] + \n                                                       diversification_features['merchant_diversification']) / 2\n    \n    features.append(diversification_features)\n    \n    # === 9. PREDICTIVE LAG FEATURES ===\n    print(\"📈 Создаем предиктивные лаговые фичи...\")\n    \n    # Создаем дневные агрегаты\n    daily_aggs = transactions.groupby(['client_num', 'date']).agg({\n        'amount': ['sum', 'count', 'mean'],\n        'mcc_code': 'nunique'\n    }).reset_index()\n    daily_aggs.columns = ['client_num', 'date', 'daily_amount', 'daily_txns', 'daily_avg_amount', 'daily_unique_mcc']\n    \n    # Лаговые фичи последних дней\n    def create_lag_features(group, lags=[1, 3, 7, 14]):\n        group = group.sort_values('date')\n        lag_features = {}\n        \n        for lag in lags:\n            lag_features[f'amount_lag_{lag}'] = group['daily_amount'].shift(lag).fillna(0).iloc[-1] if len(group) > lag else 0\n            lag_features[f'txns_lag_{lag}'] = group['daily_txns'].shift(lag).fillna(0).iloc[-1] if len(group) > lag else 0\n            lag_features[f'avg_amount_lag_{lag}'] = group['daily_avg_amount'].shift(lag).fillna(0).iloc[-1] if len(group) > lag else 0\n        \n        return pd.Series(lag_features)\n    \n    lag_features = daily_aggs.groupby('client_num').apply(create_lag_features).reset_index()\n    features.append(lag_features)\n    \n    # === 10. MCC-SPECIFIC BEHAVIORAL PATTERNS ===\n    print(\"🎯 Создаем MCC-специфичные паттерны...\")\n    \n    # Основываемся на ВАШИХ данных\n    top_frequent_mccs = [5411, 5499, 5814, 4131, 3990]  # Топ-5 по частоте\n    top_expensive_mccs = [6011, 5511, 3011, 4722, 5712]  # Топ-5 по средней сумме\n    \n    # Паттерны по топ MCC\n    for mcc in top_frequent_mccs:\n        mcc_data = transactions[transactions['mcc_code'] == mcc]\n        if len(mcc_data) > 0:\n            mcc_stats = mcc_data.groupby('client_num').agg({\n                'amount': ['sum', 'mean', 'count']\n            }).reset_index()\n            mcc_stats.columns = ['client_num', f'mcc_{mcc}_total', f'mcc_{mcc}_avg', f'mcc_{mcc}_count']\n            features.append(mcc_stats)\n    \n    # Концентрация трат в топ категориях\n    top_mcc_concentration = transactions[transactions['mcc_code'].isin(top_frequent_mccs)].groupby('client_num').agg({\n        'amount': 'sum'\n    }).reset_index()\n    \n    total_spending = transactions.groupby('client_num')['amount'].sum().reset_index()\n    total_spending.columns = ['client_num', 'total_amount']\n    \n    concentration_features = top_mcc_concentration.merge(total_spending, on='client_num', how='right').fillna(0)\n    concentration_features['top_mcc_concentration'] = concentration_features['amount'] / (concentration_features['total_amount'] + 1)\n    concentration_features = concentration_features[['client_num', 'top_mcc_concentration']]\n    features.append(concentration_features)\n    \n    # === 10. WEEKEND/WEEKDAY BEHAVIOR ANALYSIS ===\n    print(\"📅 Создаем анализ поведения выходные/будни...\")\n    \n    transactions['is_weekend'] = transactions['weekday'] >= 5\n    \n    # Сравнение поведения в выходные и будни - ИСПРАВЛЕННАЯ ВЕРСИЯ\n    weekend_stats = transactions.groupby(['client_num', 'is_weekend']).agg({\n        'amount': ['sum', 'mean', 'count'],\n        'mcc_code': 'nunique'\n    }).reset_index()\n    \n    # Убираем мультииндекс колонок\n    weekend_stats.columns = ['client_num', 'is_weekend', 'amount_sum', 'amount_mean', 'amount_count', 'unique_mcc']\n    \n    # Создаем отдельные фичи для выходных и будней\n    weekend_data = weekend_stats[weekend_stats['is_weekend'] == True][['client_num', 'amount_sum', 'amount_mean', 'amount_count', 'unique_mcc']]\n    weekend_data.columns = ['client_num', 'weekend_amount_sum', 'weekend_amount_mean', 'weekend_amount_count', 'weekend_unique_mcc']\n    \n    weekday_data = weekend_stats[weekend_stats['is_weekend'] == False][['client_num', 'amount_sum', 'amount_mean', 'amount_count', 'unique_mcc']]\n    weekday_data.columns = ['client_num', 'weekday_amount_sum', 'weekday_amount_mean', 'weekday_amount_count', 'weekday_unique_mcc']\n    \n    # Объединяем и создаем соотношения\n    weekend_combined = weekend_data.merge(weekday_data, on='client_num', how='outer').fillna(0)\n    \n    # Соотношения выходные/будни\n    weekend_combined['weekend_weekday_amount_ratio'] = weekend_combined['weekend_amount_sum'] / (weekend_combined['weekday_amount_sum'] + 1)\n    weekend_combined['weekend_weekday_count_ratio'] = weekend_combined['weekend_amount_count'] / (weekend_combined['weekday_amount_count'] + 1)\n    weekend_combined['weekend_activity_preference'] = (weekend_combined['weekend_amount_count'] / \n                                                     (weekend_combined['weekend_amount_count'] + weekend_combined['weekday_amount_count'] + 1))\n    \n    features.append(weekend_combined)\n    \n    # === ОБЪЕДИНЕНИЕ ВСЕХ ФИЧЕЙ ===\n    print(\"🔗 Объединяем все balance-specific фичи...\")\n    \n    # Начинаем с первого набора\n    final_features = features[0]\n    \n    # Последовательно джойним все остальные\n    for i, feature_set in enumerate(features[1:], 1):\n        print(f\"   Джойним набор {i+1}/{len(features)}: {feature_set.shape}\")\n        final_features = final_features.merge(feature_set, on='client_num', how='outer')\n    \n    # Заполняем пропуски\n    final_features = final_features.fillna(0)\n    \n    # Создаем композитные фичи\n    print(\"🔄 Создаем композитные фичи...\")\n    \n    # Финансовая стабильность (композитная метрика)\n    stability_cols = ['weekly_stability', 'timing_regularity', 'spending_cv', 'monthly_unevenness']\n    available_stability_cols = [col for col in stability_cols if col in final_features.columns]\n    if available_stability_cols:\n        final_features['financial_stability_score'] = final_features[available_stability_cols].mean(axis=1)\n    \n    # Риск банкротства (композитная метрика)\n    risk_cols = ['spending_volatility', 'extreme_spending_ratio', 'burn_rate', 'acceleration']\n    available_risk_cols = [col for col in risk_cols if col in final_features.columns]\n    if available_risk_cols:\n        final_features['bankruptcy_risk_score'] = final_features[available_risk_cols].mean(axis=1)\n    \n    # Индекс финансового здоровья\n    if 'financial_stability_score' in final_features.columns and 'bankruptcy_risk_score' in final_features.columns:\n        final_features['financial_health_index'] = final_features['financial_stability_score'] - final_features['bankruptcy_risk_score']\n    \n    print(f\"✅ Создано {final_features.shape[1]-1} balance-specific фичей для {final_features.shape[0]} клиентов\")\n    \n    return final_features\n\n\n# === INTEGRATION FUNCTION ===\ndef integrate_all_features(original_features, transactions, train_clients):\n    \"\"\"\n    Интегрирует оригинальные фичи с новыми продвинутыми фичами\n    \"\"\"\n    print(\"🚀 Начинаем создание всех новых фичей...\")\n    \n    # Создаем продвинутые фичи\n    advanced_features = create_advanced_features(transactions, train_clients)\n    \n    # Создаем balance-specific фичи\n    balance_features = create_balance_specific_features(transactions, train_clients)\n    \n    # Объединяем все фичи\n    print(\"🔗 Интегрируем с оригинальными фичами...\")\n    \n    # Джойним с оригинальными фичами\n    enhanced_features = original_features.merge(advanced_features, on='client_num', how='left')\n    enhanced_features = enhanced_features.merge(balance_features, on='client_num', how='left')\n    \n    # Заполняем пропуски\n    enhanced_features = enhanced_features.fillna(0)\n    \n    print(f\"🎉 Итого создано {enhanced_features.shape[1]-1} фичей!\")\n    print(f\"   Оригинальных: {original_features.shape[1]-1}\")\n    print(f\"   Новых продвинутых: {advanced_features.shape[1]-1}\")\n    print(f\"   Новых balance-specific: {balance_features.shape[1]-1}\")\n    \n    # Выводим новые фичи\n    original_cols = set(original_features.columns)\n    new_cols = [col for col in enhanced_features.columns if col not in original_cols]\n    print(f\"\\n📋 Список новых фичей ({len(new_cols)}):\")\n    for i, col in enumerate(new_cols[:20]):  # Показываем первые 20\n        print(f\"   {i+1:2d}. {col}\")\n    if len(new_cols) > 20:\n        print(f\"   ... и еще {len(new_cols)-20} фичей\")\n    \n    return enhanced_features","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T05:23:11.919709Z","iopub.execute_input":"2025-07-04T05:23:11.919954Z","iopub.status.idle":"2025-07-04T05:23:11.976065Z","shell.execute_reply.started":"2025-07-04T05:23:11.919925Z","shell.execute_reply":"2025-07-04T05:23:11.970990Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\ndef create_data_driven_features(transactions, train_clients):\n    \"\"\"\n    ЧИСТЫЕ data-driven фичи без багов - на основе анализа ваших MCC\n    \"\"\"\n    print(\"🎯 Создаем ЧИСТЫЕ data-driven фичи...\")\n    \n    # Подготавливаем базовые поля\n    if 'hour' not in transactions.columns:\n        transactions['hour'] = pd.to_datetime(transactions['date_time']).dt.hour\n    if 'weekday' not in transactions.columns:\n        transactions['weekday'] = pd.to_datetime(transactions['date_time']).dt.weekday\n    if 'date' not in transactions.columns:\n        transactions['date'] = pd.to_datetime(transactions['date_time']).dt.date\n    \n    # Базовый список всех клиентов\n    all_clients = pd.DataFrame({'client_num': transactions['client_num'].unique()})\n    features_list = []\n    \n    # === 1. ФИЧИ ДЛЯ ДОМИНИРУЮЩЕГО MCC 5411 ===\n    print(\"   📊 MCC 5411 фичи...\")\n    \n    mcc_5411_data = transactions[transactions['mcc_code'] == 5411]\n    if len(mcc_5411_data) > 0:\n        mcc_5411_features = mcc_5411_data.groupby('client_num').agg({\n            'amount': ['sum', 'mean', 'count', 'std'],\n            'hour': 'mean',\n            'weekday': 'mean'\n        })\n        \n        # Простые названия колонок\n        mcc_5411_features.columns = [\n            'mcc5411_total', 'mcc5411_avg', 'mcc5411_count', 'mcc5411_std',\n            'mcc5411_avg_hour', 'mcc5411_avg_weekday'\n        ]\n        mcc_5411_features = mcc_5411_features.reset_index()\n        \n        # Джойним ко всем клиентам и заполняем нули\n        mcc_5411_final = all_clients.merge(mcc_5411_features, on='client_num', how='left').fillna(0)\n        features_list.append(mcc_5411_final)\n    \n    # === 2. ФИЧИ ДЛЯ MCC 5499 ===\n    print(\"   📊 MCC 5499 фичи...\")\n    \n    mcc_5499_data = transactions[transactions['mcc_code'] == 5499]\n    if len(mcc_5499_data) > 0:\n        mcc_5499_features = mcc_5499_data.groupby('client_num').agg({\n            'amount': ['sum', 'mean', 'count']\n        })\n        \n        mcc_5499_features.columns = ['mcc5499_total', 'mcc5499_avg', 'mcc5499_count']\n        mcc_5499_features = mcc_5499_features.reset_index()\n        \n        mcc_5499_final = all_clients.merge(mcc_5499_features, on='client_num', how='left').fillna(0)\n        features_list.append(mcc_5499_final)\n    \n    # === 3. ФИЧИ ДЛЯ ДОРОГОГО MCC 6011 ===\n    print(\"   💰 MCC 6011 фичи...\")\n    \n    mcc_6011_data = transactions[transactions['mcc_code'] == 6011]\n    if len(mcc_6011_data) > 0:\n        mcc_6011_features = mcc_6011_data.groupby('client_num').agg({\n            'amount': ['sum', 'mean', 'count', 'max']\n        })\n        \n        mcc_6011_features.columns = ['mcc6011_total', 'mcc6011_avg', 'mcc6011_count', 'mcc6011_max']\n        mcc_6011_features = mcc_6011_features.reset_index()\n        \n        mcc_6011_final = all_clients.merge(mcc_6011_features, on='client_num', how='left').fillna(0)\n        features_list.append(mcc_6011_final)\n    \n    # === 4. КОНЦЕНТРАЦИЯ НА ТОП-3 MCC ===\n    print(\"   🎯 Концентрация фичи...\")\n    \n    top3_mccs = [5411, 5499, 5814]\n    \n    # Траты в топ-3\n    top3_spending = transactions[transactions['mcc_code'].isin(top3_mccs)].groupby('client_num')['amount'].sum().reset_index()\n    top3_spending.columns = ['client_num', 'top3_spending']\n    \n    # Общие траты\n    total_spending = transactions.groupby('client_num')['amount'].sum().reset_index()\n    total_spending.columns = ['client_num', 'total_spending']\n    \n    # Концентрация\n    concentration = total_spending.merge(top3_spending, on='client_num', how='left').fillna(0)\n    concentration['top3_concentration'] = concentration['top3_spending'] / (concentration['total_spending'] + 1)\n    concentration_final = concentration[['client_num', 'top3_concentration']]\n    \n    features_list.append(concentration_final)\n    \n    # === 5. ВЫСОКИЕ ТРАНЗАКЦИИ ===\n    print(\"   💎 Высокие транзакции...\")\n    \n    high_threshold = transactions['amount'].quantile(0.8)\n    high_txns = transactions[transactions['amount'] >= high_threshold]\n    \n    if len(high_txns) > 0:\n        high_features = high_txns.groupby('client_num').agg({\n            'amount': ['sum', 'count', 'mean'],\n            'mcc_code': 'nunique'\n        })\n        \n        high_features.columns = ['high_total', 'high_count', 'high_avg', 'high_unique_mcc']\n        high_features = high_features.reset_index()\n        \n        # Добавляем долю высоких транзакций\n        high_with_total = high_features.merge(total_spending, on='client_num', how='right').fillna(0)\n        high_with_total['high_ratio'] = high_with_total['high_total'] / (high_with_total['total_spending'] + 1)\n        \n        high_final = high_with_total[['client_num', 'high_total', 'high_count', 'high_avg', 'high_unique_mcc', 'high_ratio']]\n        features_list.append(high_final)\n    \n    # === 6. ЧАСТЫЕ VS РЕДКИЕ КАТЕГОРИИ ===\n    print(\"   📈 Частые vs редкие...\")\n    \n    # Частые категории (топ-5)\n    frequent_mccs = [5411, 5499, 5814, 4131, 3990]\n    frequent_spending = transactions[transactions['mcc_code'].isin(frequent_mccs)].groupby('client_num')['amount'].sum().reset_index()\n    frequent_spending.columns = ['client_num', 'frequent_spending']\n    \n    # Редкие категории (последние 50)\n    mcc_counts = transactions['mcc_code'].value_counts()\n    rare_mccs = mcc_counts.tail(50).index.tolist()\n    rare_spending = transactions[transactions['mcc_code'].isin(rare_mccs)].groupby('client_num')['amount'].sum().reset_index()\n    rare_spending.columns = ['client_num', 'rare_spending']\n    \n    # Объединяем\n    freq_rare = all_clients.merge(frequent_spending, on='client_num', how='left').fillna(0)\n    freq_rare = freq_rare.merge(rare_spending, on='client_num', how='left').fillna(0)\n    \n    # Соотношения\n    freq_rare['frequent_rare_ratio'] = freq_rare['frequent_spending'] / (freq_rare['rare_spending'] + 1)\n    freq_rare['rare_tendency'] = freq_rare['rare_spending'] / (freq_rare['frequent_spending'] + freq_rare['rare_spending'] + 1)\n    \n    features_list.append(freq_rare)\n    \n    # === 7. ПЕРЕКЛЮЧЕНИЯ МЕЖДУ КАТЕГОРИЯМИ ===\n    print(\"   🔄 Переключения категорий...\")\n    \n    def calculate_switches(group):\n        if len(group) < 2:\n            return pd.Series({'category_switches': 0, 'switch_rate': 0})\n        \n        sorted_group = group.sort_values('date_time')\n        mcc_sequence = sorted_group['mcc_code'].tolist()\n        \n        switches = sum(1 for i in range(len(mcc_sequence)-1) \n                      if mcc_sequence[i] != mcc_sequence[i+1])\n        switch_rate = switches / len(mcc_sequence) if len(mcc_sequence) > 1 else 0\n        \n        return pd.Series({\n            'category_switches': switches,\n            'switch_rate': switch_rate\n        })\n    \n    switch_features = transactions.groupby('client_num').apply(calculate_switches)\n    switch_features = switch_features.reset_index()\n    \n    features_list.append(switch_features)\n    \n    # === 8. СТАБИЛЬНОСТЬ ПОВЕДЕНИЯ ===\n    print(\"   📊 Стабильность поведения...\")\n    \n    # Количество уникальных дней с транзакциями\n    active_days = transactions.groupby('client_num')['date'].nunique().reset_index()\n    active_days.columns = ['client_num', 'unique_active_days']\n    \n    # Средняя сумма транзакции в разные дни недели\n    weekday_std = transactions.groupby(['client_num', 'weekday'])['amount'].mean().groupby('client_num').std().reset_index()\n    weekday_std.columns = ['client_num', 'weekday_amount_stability']\n    weekday_std['weekday_amount_stability'] = weekday_std['weekday_amount_stability'].fillna(0)\n    \n    # Объединяем стабильность\n    stability = active_days.merge(weekday_std, on='client_num', how='left').fillna(0)\n    features_list.append(stability)\n    \n    # === 9. ВРЕМЕННЫЕ ПАТТЕРНЫ ===\n    print(\"   ⏰ Временные паттерны...\")\n    \n    # Любимое время для трат\n    time_patterns = transactions.groupby('client_num').agg({\n        'hour': ['mean', 'std'],\n        'weekday': ['mean', 'std']\n    })\n    \n    time_patterns.columns = ['avg_hour', 'std_hour', 'avg_weekday', 'std_weekday']\n    time_patterns = time_patterns.reset_index()\n    time_patterns = time_patterns.fillna(0)\n    \n    features_list.append(time_patterns)\n    \n    # === 10. ОБЪЕДИНЕНИЕ ВСЕХ ФИЧЕЙ ===\n    print(\"   🔗 Объединяем все фичи...\")\n    \n    # Начинаем с all_clients\n    final_features = all_clients.copy()\n    \n    # Последовательно джойним все фичи\n    for i, feature_df in enumerate(features_list):\n        print(f\"      Добавляем набор {i+1}/{len(features_list)}: {feature_df.shape}\")\n        final_features = final_features.merge(feature_df, on='client_num', how='left')\n    \n    # Финальное заполнение пропусков\n    final_features = final_features.fillna(0)\n    \n    print(f\"✅ Создано {final_features.shape[1]-1} чистых data-driven фичей для {final_features.shape[0]} клиентов\")\n    \n    return final_features\n\n# # === ТЕСТИРОВАНИЕ ФУНКЦИИ ===\n# if __name__ == \"__main__\":\n#     # Простой тест\n#     print(\"🧪 Тестируем функцию...\")\n    \n#     # Создаем тестовые данные\n#     test_data = pd.DataFrame({\n#         'client_num': [1, 1, 2, 2, 3],\n#         'date_time': pd.to_datetime(['2024-01-01 10:00', '2024-01-02 15:00', \n#                                    '2024-01-01 12:00', '2024-01-03 18:00', \n#                                    '2024-01-01 09:00']),\n#         'mcc_code': [5411, 5499, 5411, 6011, 5814],\n#         'amount': [1000, 500, 1500, 50000, 800]\n#     })\n    \n#     test_clients = {1, 2}\n    \n#     try:\n#         result = create_clean_data_driven_features(test_data, test_clients)\n#         print(f\"✅ Тест прошел! Создано {result.shape[1]-1} фичей для {result.shape[0]} клиентов\")\n#         print(\"Колонки:\", result.columns.tolist())\n#     except Exception as e:\n#         print(f\"❌ Тест провален: {e}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T05:23:11.978184Z","iopub.execute_input":"2025-07-04T05:23:11.978389Z","iopub.status.idle":"2025-07-04T05:23:12.009291Z","shell.execute_reply.started":"2025-07-04T05:23:11.978369Z","shell.execute_reply":"2025-07-04T05:23:12.003802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Метки\ntrain_clients = set(train['client_num'])\ntransactions['date_time'] = pd.to_datetime(transactions['date_time'])\ntransactions['date'] = transactions['date_time'].dt.date\ntransactions['hour'] = transactions['date_time'].dt.hour\ntransactions['weekday'] = transactions['date_time'].dt.weekday\ntransactions['part_of_day'] = pd.cut(\n    transactions['hour'], bins=[-1, 5, 11, 17, 23],\n    labels=['night', 'morning', 'day', 'evening']\n)\n\n# === СНАЧАЛА СОЗДАЕМ НОВЫЕ ФИЧИ ===\nprint(\"🚀 Создаем новые фичи...\")\nadvanced_features = create_advanced_features(transactions, train_clients)\nbalance_features = create_balance_specific_features(transactions, train_clients)\ndata_driven_features = create_data_driven_features(transactions, train_clients)\n\n# === ПОТОМ ОРИГИНАЛЬНЫЕ ФИЧИ ===\nprint(\"🔧 Создаем оригинальные фичи...\")\nfeatures = []\nlags = [3, 7, 10, 14, 30, 60, 90, 180, 360]\n\n# === 1. Базовые агрегаты ===\nagg_main = transactions.groupby('client_num').agg(\n    total_amount=('amount', 'sum'),\n    mean_amount=('amount', 'mean'),\n    median_amount=('amount', 'median'),\n    std_amount=('amount', 'std'),\n    max_amount=('amount', 'max'),\n    min_amount=('amount', 'min'),\n    skew_amount=('amount', pd.Series.skew),\n    kurtosis_amount=('amount', pd.Series.kurtosis),\n    transaction_count=('amount', 'count'),\n    unique_mcc=('mcc_code', 'nunique'),\n).reset_index()\nfor col in ['total_amount', 'mean_amount', 'median_amount', 'std_amount']:\n    agg_main['log_' + col] = np.log1p(agg_main[col])\nfeatures.append(agg_main)\n\n# === 2. Rolling фичи с multiprocessing ===\ndaily = transactions.groupby(['client_num', 'date']).agg(\n    daily_amount=('amount', 'sum'),\n    daily_count=('amount', 'count')\n).reset_index()\ndaily['date'] = pd.to_datetime(daily['date'])\n\ndef compute_rolling_features(lag):\n    df = daily.copy()\n    df = df.sort_values(['client_num', 'date'])\n    df[f'rolling_mean_amt_{lag}'] = df.groupby('client_num')['daily_amount'].transform(lambda x: x.rolling(lag, min_periods=1).mean())\n    df[f'rolling_median_amt_{lag}'] = df.groupby('client_num')['daily_amount'].transform(lambda x: x.rolling(lag, min_periods=1).median())\n    df[f'rolling_std_amt_{lag}'] = df.groupby('client_num')['daily_amount'].transform(lambda x: x.rolling(lag, min_periods=1).std())\n    df[f'rolling_mean_cnt_{lag}'] = df.groupby('client_num')['daily_count'].transform(lambda x: x.rolling(lag, min_periods=1).mean())\n    df_last = df.groupby('client_num').tail(1).drop(columns=['date', 'daily_amount', 'daily_count'])\n    return df_last\n\nwith Pool(cpu_count()) as pool:\n    rolling_results = list(tqdm(pool.imap(compute_rolling_features, lags), total=len(lags), desc='Rolling lags'))\n\nrolling_df = pd.concat(rolling_results).groupby('client_num').mean().reset_index()\nfeatures.append(rolling_df)\n\n# === 3. Temporal features ===\npartofday = transactions.groupby(['client_num', 'part_of_day'])['amount'].mean().unstack().add_prefix('avg_amt_').reset_index()\nfeatures.append(partofday)\n\nweekday_stats = transactions.groupby(['client_num', 'weekday'])['amount'].mean().unstack().add_prefix('weekday_avg_').reset_index()\nfeatures.append(weekday_stats)\n\ntransactions = transactions.sort_values(['client_num', 'date_time'])\ntransactions['time_diff'] = transactions.groupby('client_num')['date_time'].diff().dt.total_seconds() / 3600\nintervals = transactions.groupby('client_num')['time_diff'].agg(['mean', 'std', 'min', 'max']).add_prefix('interval_').reset_index()\nfeatures.append(intervals)\n\nlife = transactions.groupby('client_num').agg(\n    first_txn=('date_time', 'min'),\n    last_txn=('date_time', 'max')\n).reset_index()\nlife['active_days'] = (life['last_txn'] - life['first_txn']).dt.days\nfeatures.append(life[['client_num', 'active_days']])\n\n# === 4. MCC features ===\ntop_mcc_global = transactions['mcc_code'].value_counts().nlargest(10).index.tolist()\ntransactions['top_mcc'] = transactions['mcc_code'].apply(lambda x: x if x in top_mcc_global else 'other')\ntop_mcc_stats = transactions.groupby(['client_num', 'top_mcc']).size().unstack(fill_value=0).add_prefix('mcc_').reset_index()\nfeatures.append(top_mcc_stats)\n\ndef get_top_mccs(group):\n    top = group['mcc_code'].value_counts().nlargest(3).index.tolist()\n    return pd.Series(top + [np.nan] * (3 - len(top)), index=['mcc1', 'mcc2', 'mcc3'])\n\ntop_mcc_client = transactions.groupby('client_num').apply(get_top_mccs).reset_index()\nfeatures.append(top_mcc_client)\n\n# === 5. RFM ===\nnow = transactions['date_time'].max()\nrfm = transactions.groupby('client_num').agg(\n    rfm_recency=('date_time', lambda x: (now - x.max()).days),\n    rfm_frequency=('amount', 'count'),\n    rfm_monetary=('amount', 'sum')\n).reset_index()\nfeatures.append(rfm)\n\n# === 6. Объединение ОРИГИНАЛЬНЫХ фичей ===\noriginal_features = features[0]\nfor f in features[1:]:\n    original_features = original_features.merge(f, on='client_num', how='left')\n\nprint(f\"Оригинальных фичей: {original_features.shape[1]-1}\")\n\n# === 7. ОБЪЕДИНЕНИЕ С НОВЫМИ ФИЧАМИ ===\nprint(\"🔗 Объединяем с новыми фичами...\")\nenhanced_features = original_features.copy()\nenhanced_features = enhanced_features.merge(advanced_features, on='client_num', how='left')\nenhanced_features = enhanced_features.merge(balance_features, on='client_num', how='left')\nenhanced_features = enhanced_features.merge(data_driven_features, on='client_num', how='left')\nenhanced_features = enhanced_features.fillna(0)\n\nprint(f\"Итого фичей: {enhanced_features.shape[1]-1}\")\n\n# === 8. Разделение train/test ===\ntrain_full = train.merge(enhanced_features, on='client_num', how='left')\ntrain_data = train_full.drop(columns=['client_num'])\n\ntest_clients = list(set(transactions['client_num']) - set(train['client_num']))\ntest_data = enhanced_features[enhanced_features['client_num'].isin(test_clients)].reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T05:23:12.011456Z","iopub.execute_input":"2025-07-04T05:23:12.011670Z","iopub.status.idle":"2025-07-04T06:06:30.683973Z","shell.execute_reply.started":"2025-07-04T05:23:12.011648Z","shell.execute_reply":"2025-07-04T06:06:30.678096Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"🔧 Исправляем типы данных...\")\n\n# Исправляем колонки с MCC кодами\nmcc_columns = [col for col in train_data.columns if col.startswith('mcc') and train_data[col].dtype == 'object']\nfor col in mcc_columns:\n    train_data[col] = pd.to_numeric(train_data[col], errors='coerce').fillna(0).astype('int64')\n\nmcc_columns_test = [col for col in test_data.columns if col.startswith('mcc') and test_data[col].dtype == 'object']\nfor col in mcc_columns_test:\n    test_data[col] = pd.to_numeric(test_data[col], errors='coerce').fillna(0).astype('int64')\n\n# Теперь сохраняем\ntrain_data.to_parquet('SET_1_train_data.parquet', index=False)\ntest_data.to_parquet('SET_1_test_data.parquet', index=False)\n\nprint(\"✅ Готово! Enhanced фичи сохранены.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:25:26.665688Z","iopub.execute_input":"2025-07-04T07:25:26.666026Z","iopub.status.idle":"2025-07-04T07:25:27.722527Z","shell.execute_reply.started":"2025-07-04T07:25:26.665992Z","shell.execute_reply":"2025-07-04T07:25:27.718545Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T06:29:34.306627Z","iopub.execute_input":"2025-07-04T06:29:34.306945Z","iopub.status.idle":"2025-07-04T06:29:34.356841Z","shell.execute_reply.started":"2025-07-04T06:29:34.306917Z","shell.execute_reply":"2025-07-04T06:29:34.351784Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.columns.to_list()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T06:29:44.960531Z","iopub.execute_input":"2025-07-04T06:29:44.960839Z","iopub.status.idle":"2025-07-04T06:29:44.979273Z","shell.execute_reply.started":"2025-07-04T06:29:44.960812Z","shell.execute_reply":"2025-07-04T06:29:44.973081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.set_option('display.max_columns', None)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T06:30:27.597556Z","iopub.execute_input":"2025-07-04T06:30:27.597862Z","iopub.status.idle":"2025-07-04T06:30:27.607229Z","shell.execute_reply.started":"2025-07-04T06:30:27.597834Z","shell.execute_reply":"2025-07-04T06:30:27.602822Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T06:32:43.468111Z","iopub.execute_input":"2025-07-04T06:32:43.468459Z","iopub.status.idle":"2025-07-04T06:32:44.412342Z","shell.execute_reply.started":"2025-07-04T06:32:43.468427Z","shell.execute_reply":"2025-07-04T06:32:44.406752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def analyze_target_correlations(train_data):\n    \"\"\"\n    Глубокий анализ корреляций признаков с таргетом\n    \"\"\"\n    print(\"🎯 Анализируем корреляции с таргетом...\")\n    \n    # Разделяем фичи и таргет\n    target = train_data['target']\n    features = train_data.drop('target', axis=1)\n    \n    print(f\"📊 Распределение таргета:\")\n    target_dist = target.value_counts().sort_index()\n    for cls, count in target_dist.items():\n        pct = count / len(target) * 100\n        print(f\"   Класс {cls}: {count:,} ({pct:.1f}%)\")\n    \n    # === 1. ОБЩИЕ КОРРЕЛЯЦИИ ===\n    print(\"\\n🔍 Общие корреляции с таргетом (топ-20):\")\n    \n    # Только числовые колонки\n    numeric_features = features.select_dtypes(include=[np.number])\n    correlations = numeric_features.corrwith(target).abs().sort_values(ascending=False)\n    \n    print(correlations.head(20))\n    \n    # === 2. АНАЛИЗ ПО КЛАССАМ ===\n    print(\"\\n🎯 Анализ различий между классами...\")\n    \n    # Создаем бинарные индикаторы для каждого класса\n    class_analysis = {}\n    \n    for target_class in sorted(target.unique()):\n        print(f\"\\n{'='*50}\")\n        print(f\"📊 КЛАСС {target_class} vs остальные\")\n        print(f\"{'='*50}\")\n        \n        # Создаем бинарный таргет (класс vs не класс)\n        binary_target = (target == target_class).astype(int)\n        \n        # Корреляции для этого класса\n        class_corr = numeric_features.corrwith(binary_target).abs().sort_values(ascending=False)\n        \n        print(f\"Топ-15 предикторов для класса {target_class}:\")\n        for i, (feature, corr) in enumerate(class_corr.head(15).items(), 1):\n            direction = \"↑\" if numeric_features[feature].corr(binary_target) > 0 else \"↓\"\n            print(f\"   {i:2d}. {feature:<30} {corr:.4f} {direction}\")\n        \n        class_analysis[target_class] = class_corr.head(20).to_dict()\n    \n    # === 3. СТАТИСТИЧЕСКАЯ ЗНАЧИМОСТЬ (ANOVA) ===\n    print(f\"\\n{'='*60}\")\n    print(\"📈 СТАТИСТИЧЕСКАЯ ЗНАЧИМОСТЬ (ANOVA F-статистика)\")\n    print(f\"{'='*60}\")\n    \n    f_scores = []\n    for feature in numeric_features.columns:\n        try:\n            # Группируем по классам таргета\n            groups = [numeric_features[feature][target == cls].dropna() for cls in sorted(target.unique())]\n            \n            # Удаляем пустые группы\n            groups = [g for g in groups if len(g) > 0]\n            \n            if len(groups) >= 2:\n                f_stat, p_value = f_oneway(*groups)\n                f_scores.append({\n                    'feature': feature,\n                    'f_statistic': f_stat,\n                    'p_value': p_value,\n                    'significant': p_value < 0.001\n                })\n        except:\n            continue\n    \n    f_scores_df = pd.DataFrame(f_scores).sort_values('f_statistic', ascending=False)\n    \n    print(\"Топ-20 признаков по F-статистике (различия между классами):\")\n    for i, row in f_scores_df.head(20).iterrows():\n        sig_mark = \"***\" if row['significant'] else \"\"\n        print(f\"   {row.name+1:2d}. {row['feature']:<30} F={row['f_statistic']:8.2f} p={row['p_value']:.2e} {sig_mark}\")\n    \n    # === 4. СРЕДНИЕ ЗНАЧЕНИЯ ПО КЛАССАМ ===\n    print(f\"\\n{'='*60}\")\n    print(\"📊 СРЕДНИЕ ЗНАЧЕНИЯ ТОП-ФИЧЕЙ ПО КЛАССАМ\")\n    print(f\"{'='*60}\")\n    \n    # Берем топ-10 фичей по F-статистике\n    top_features = f_scores_df.head(10)['feature'].tolist()\n    \n    for feature in top_features:\n        print(f\"\\n🔍 {feature}:\")\n        means_by_class = train_data.groupby('target')[feature].agg(['mean', 'std', 'count'])\n        \n        for cls in sorted(target.unique()):\n            mean_val = means_by_class.loc[cls, 'mean']\n            std_val = means_by_class.loc[cls, 'std']\n            count_val = means_by_class.loc[cls, 'count']\n            print(f\"   Класс {cls}: {mean_val:10.2f} ± {std_val:8.2f} (n={count_val:,})\")\n    \n    # === 5. СПЕЦИАЛЬНЫЙ АНАЛИЗ ДЛЯ КЛАССА 0 ===\n    print(f\"\\n{'='*60}\")\n    print(\"🚨 СПЕЦИАЛЬНЫЙ АНАЛИЗ ДЛЯ КЛАССА 0 (самый важный для WMAE)\")\n    print(f\"{'='*60}\")\n    \n    class_0_indicator = (target == 0).astype(int)\n    \n    # Топ предикторы для класса 0\n    class_0_corr = numeric_features.corrwith(class_0_indicator).sort_values(key=abs, ascending=False)\n    \n    print(\"Топ-15 предикторов обнуления баланса (класс 0):\")\n    for i, (feature, corr) in enumerate(class_0_corr.head(15).items(), 1):\n        direction = \"способствует обнулению\" if corr > 0 else \"защищает от обнуления\"\n        print(f\"   {i:2d}. {feature:<30} {corr:7.4f} ({direction})\")\n    \n    # Анализ защитных факторов (отрицательная корреляция с классом 0)\n    protective_factors = class_0_corr[class_0_corr < -0.05].sort_values()\n    print(f\"\\n🛡️  Топ-10 защитных факторов от обнуления баланса:\")\n    for i, (feature, corr) in enumerate(protective_factors.head(10).items(), 1):\n        print(f\"   {i:2d}. {feature:<30} {corr:7.4f}\")\n    \n    # Факторы риска (положительная корреляция с классом 0)\n    risk_factors = class_0_corr[class_0_corr > 0.05].sort_values(ascending=False)\n    print(f\"\\n⚠️  Топ-10 факторов риска обнуления баланса:\")\n    for i, (feature, corr) in enumerate(risk_factors.head(10).items(), 1):\n        print(f\"   {i:2d}. {feature:<30} {corr:7.4f}\")\n    \n    # === 6. КОМБИНИРОВАННЫЙ АНАЛИЗ ===\n    print(f\"\\n{'='*60}\")\n    print(\"🎯 КОМБИНИРОВАННЫЙ СКОР (корреляция + F-статистика)\")\n    print(f\"{'='*60}\")\n    \n    # Создаем комбинированный скор\n    combined_scores = []\n    \n    for feature in numeric_features.columns:\n        # Корреляция с классом 0\n        class_0_corr_val = abs(class_0_corr.get(feature, 0))\n        \n        # F-статистика\n        f_row = f_scores_df[f_scores_df['feature'] == feature]\n        f_stat_norm = f_row['f_statistic'].iloc[0] / f_scores_df['f_statistic'].max() if len(f_row) > 0 else 0\n        \n        # Комбинированный скор\n        combined_score = class_0_corr_val * 0.6 + f_stat_norm * 0.4\n        \n        combined_scores.append({\n            'feature': feature,\n            'class_0_corr': class_0_corr_val,\n            'f_stat_norm': f_stat_norm,\n            'combined_score': combined_score\n        })\n    \n    combined_df = pd.DataFrame(combined_scores).sort_values('combined_score', ascending=False)\n    \n    print(\"Топ-20 самых важных признаков (комбинированный скор):\")\n    for i, row in combined_df.head(20).iterrows():\n        print(f\"   {i+1:2d}. {row['feature']:<30} Score={row['combined_score']:.4f} \"\n              f\"(Corr={row['class_0_corr']:.3f}, F-norm={row['f_stat_norm']:.3f})\")\n    \n    # === 7. ПРАКТИЧЕСКИЕ РЕКОМЕНДАЦИИ ===\n    print(f\"\\n{'='*60}\")\n    print(\"💡 ПРАКТИЧЕСКИЕ РЕКОМЕНДАЦИИ\")\n    print(f\"{'='*60}\")\n    \n    print(\"🎯 Для улучшения предсказания класса 0 (обнуление баланса):\")\n    \n    top_5_features = combined_df.head(5)['feature'].tolist()\n    print(f\"\\n   Фокусируйтесь на топ-5 признаках:\")\n    for i, feature in enumerate(top_5_features, 1):\n        corr_val = class_0_corr.get(feature, 0)\n        trend = \"↑ увеличивает риск\" if corr_val > 0 else \"↓ снижает риск\"\n        print(f\"   {i}. {feature} ({trend})\")\n    \n    # Анализ новых vs старых фичей\n    new_feature_keywords = ['mcc5411', 'mcc5499', 'mcc6011', 'momentum', 'burn_rate', 'stability', \n                           'concentration', 'frequent_rare', 'high_value', 'consistency']\n    \n    new_features_in_top = [f for f in top_5_features if any(kw in f for kw in new_feature_keywords)]\n    \n    if new_features_in_top:\n        print(f\"\\n🚀 Новые фичи в топ-5: {len(new_features_in_top)}\")\n        for feat in new_features_in_top:\n            print(f\"   ✅ {feat}\")\n    else:\n        print(f\"\\n⚠️  Новые фичи не попали в топ-5, возможно нужна доработка\")\n    \n    return {\n        'correlations': correlations,\n        'class_analysis': class_analysis,\n        'f_scores': f_scores_df,\n        'class_0_analysis': class_0_corr,\n        'combined_scores': combined_df,\n        'top_features': top_5_features\n    }\n\n# === ВИЗУАЛИЗАЦИЯ ===\ndef plot_correlation_analysis(train_data, analysis_results):\n    \"\"\"\n    Создает визуализации корреляционного анализа\n    \"\"\"\n    print(\"📊 Создаем визуализации...\")\n    \n    fig, axes = plt.subplots(2, 2, figsize=(20, 16))\n    \n    # 1. Топ корреляции с таргетом\n    top_corr = analysis_results['correlations'].head(15)\n    axes[0,0].barh(range(len(top_corr)), top_corr.values)\n    axes[0,0].set_yticks(range(len(top_corr)))\n    axes[0,0].set_yticklabels(top_corr.index, fontsize=10)\n    axes[0,0].set_title('Топ-15 корреляций с таргетом', fontsize=14)\n    axes[0,0].set_xlabel('Абсолютная корреляция')\n    \n    # 2. F-статистики\n    top_f = analysis_results['f_scores'].head(15)\n    axes[0,1].barh(range(len(top_f)), top_f['f_statistic'])\n    axes[0,1].set_yticks(range(len(top_f)))\n    axes[0,1].set_yticklabels(top_f['feature'], fontsize=10)\n    axes[0,1].set_title('Топ-15 F-статистик (различия между классами)', fontsize=14)\n    axes[0,1].set_xlabel('F-статистика')\n    \n    # 3. Корреляции с классом 0\n    class_0_corr = analysis_results['class_0_analysis'].head(15)\n    colors = ['red' if x > 0 else 'green' for x in class_0_corr.values]\n    axes[1,0].barh(range(len(class_0_corr)), class_0_corr.values, color=colors, alpha=0.7)\n    axes[1,0].set_yticks(range(len(class_0_corr)))\n    axes[1,0].set_yticklabels(class_0_corr.index, fontsize=10)\n    axes[1,0].set_title('Топ-15 корреляций с классом 0\\n(красный=риск, зеленый=защита)', fontsize=14)\n    axes[1,0].set_xlabel('Корреляция с классом 0')\n    \n    # 4. Комбинированные скоры\n    top_combined = analysis_results['combined_scores'].head(15)\n    axes[1,1].barh(range(len(top_combined)), top_combined['combined_score'])\n    axes[1,1].set_yticks(range(len(top_combined)))\n    axes[1,1].set_yticklabels(top_combined['feature'], fontsize=10)\n    axes[1,1].set_title('Топ-15 комбинированных скоров', fontsize=14)\n    axes[1,1].set_xlabel('Комбинированный скор')\n    \n    plt.tight_layout()\n    plt.show()\n    \n    # Heatmap средних значений по классам для топ фичей\n    top_features = analysis_results['top_features']\n    class_means = train_data.groupby('target')[top_features].mean()\n    \n    plt.figure(figsize=(12, 8))\n    sns.heatmap(class_means.T, annot=True, fmt='.2f', cmap='RdYlBu_r', center=0)\n    plt.title('Средние значения топ-5 фичей по классам таргета')\n    plt.xlabel('Класс таргета')\n    plt.ylabel('Признаки')\n    plt.tight_layout()\n    plt.show()\n\n# Использование:\nanalysis_results = analyze_target_correlations(train_data)\nplot_correlation_analysis(train_data, analysis_results)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T06:36:38.462094Z","iopub.execute_input":"2025-07-04T06:36:38.462414Z","iopub.status.idle":"2025-07-04T06:36:49.670289Z","shell.execute_reply.started":"2025-07-04T06:36:38.462386Z","shell.execute_reply":"2025-07-04T06:36:49.665739Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_activity_recency_features(transactions, train_clients):\n    \"\"\"\n    Создает фичи активности и recency на основе корреляционного анализа\n    Фокус на самых сильных предикторах класса 0\n    \"\"\"\n    print(\"🎯 Создаем ACTIVITY/RECENCY фичи на основе анализа корреляций...\")\n    \n    # Подготавливаем временные поля\n    if 'date' not in transactions.columns:\n        transactions['date'] = pd.to_datetime(transactions['date_time']).dt.date\n    \n    # Базовые временные метрики\n    now = transactions['date_time'].max()\n    max_date = transactions['date'].max()\n    min_date = transactions['date'].min()\n    \n    print(f\"   Период данных: {min_date} - {max_date}\")\n    \n    features_list = []\n    all_clients = pd.DataFrame({'client_num': transactions['client_num'].unique()})\n    \n    # === 1. ENHANCED ACTIVITY FEATURES ===\n    print(\"   🏃 Enhanced activity фичи...\")\n    \n    # Плотность активности (транзакции/день)\n    def calculate_activity_density(group):\n        total_days = (group['date'].max() - group['date'].min()).days + 1\n        return len(group) / total_days if total_days > 0 else 0\n    \n    activity_density = transactions.groupby('client_num').apply(calculate_activity_density).reset_index()\n    activity_density.columns = ['client_num', 'activity_density']\n    \n    # Стабильность активности по неделям\n    transactions['week'] = pd.to_datetime(transactions['date']).dt.isocalendar().week\n    weekly_activity = transactions.groupby(['client_num', 'week']).size().reset_index()\n    weekly_activity.columns = ['client_num', 'week', 'weekly_txns']\n    \n    weekly_stats = weekly_activity.groupby('client_num')['weekly_txns'].agg(['mean', 'std', 'count']).reset_index()\n    weekly_stats.columns = ['client_num', 'avg_weekly_txns', 'std_weekly_txns', 'active_weeks']\n    weekly_stats['weekly_consistency'] = 1 / (weekly_stats['std_weekly_txns'] / (weekly_stats['avg_weekly_txns'] + 1) + 1)\n    weekly_stats = weekly_stats.fillna(0)\n    \n    # Объединяем activity фичи\n    activity_features = activity_density.merge(weekly_stats, on='client_num', how='outer').fillna(0)\n    features_list.append(activity_features)\n    \n    # === 2. ADVANCED RECENCY FEATURES ===\n    print(\"   ⏰ Advanced recency фичи...\")\n    \n    # Детализированный recency анализ\n    def calculate_advanced_recency(group):\n        sorted_dates = sorted(group['date_time'])\n        \n        if len(sorted_dates) == 0:\n            return pd.Series({\n                'days_since_last_txn': 999,\n                'days_since_penultimate': 999,\n                'recency_acceleration': 0,\n                'recency_trend': 0\n            })\n        \n        # Дни с последней транзакции\n        days_since_last = (now - sorted_dates[-1]).days\n        \n        # Дни с предпоследней транзакции\n        days_since_penultimate = (now - sorted_dates[-2]).days if len(sorted_dates) >= 2 else days_since_last\n        \n        # Ускорение пассивности (растет ли gap между транзакциями?)\n        if len(sorted_dates) >= 3:\n            recent_gap = (sorted_dates[-1] - sorted_dates[-2]).days\n            prev_gap = (sorted_dates[-2] - sorted_dates[-3]).days\n            recency_acceleration = recent_gap / (prev_gap + 1)\n        else:\n            recency_acceleration = 1\n        \n        # Тренд recency (становится ли хуже со временем?)\n        if len(sorted_dates) >= 4:\n            gaps = [(sorted_dates[i] - sorted_dates[i-1]).days for i in range(1, len(sorted_dates))]\n            recency_trend = np.polyfit(range(len(gaps)), gaps, 1)[0] if len(gaps) > 1 else 0\n        else:\n            recency_trend = 0\n        \n        return pd.Series({\n            'days_since_last_txn': days_since_last,\n            'days_since_penultimate': days_since_penultimate,\n            'recency_acceleration': recency_acceleration,\n            'recency_trend': recency_trend\n        })\n    \n    recency_features = transactions.groupby('client_num').apply(calculate_advanced_recency).reset_index()\n    features_list.append(recency_features)\n    \n    # === 3. RECENT ACTIVITY ANALYSIS ===\n    print(\"   📈 Recent activity анализ...\")\n    \n    # Активность в последние периоды\n    periods = [7, 14, 30]  # дни\n    \n    recent_activity_features = []\n    \n    for period in periods:\n        cutoff_date = max_date - timedelta(days=period)\n        recent_data = transactions[transactions['date'] >= cutoff_date]\n        \n        if len(recent_data) > 0:\n            recent_stats = recent_data.groupby('client_num').agg({\n                'amount': ['sum', 'count', 'mean'],\n                'mcc_code': 'nunique',\n                'date': 'nunique'\n            })\n            \n            recent_stats.columns = [f'recent_{period}d_{col[1]}_{col[0]}' if col[1] else f'recent_{period}d_{col[0]}' \n                                   for col in recent_stats.columns]\n            recent_stats = recent_stats.reset_index()\n            \n            # Добавляем интенсивность активности\n            recent_stats[f'recent_{period}d_intensity'] = recent_stats[f'recent_{period}d_count_amount'] / period\n            \n        else:\n            # Если нет данных за период, создаем пустой DataFrame\n            recent_stats = pd.DataFrame({'client_num': []})\n        \n        recent_activity_features.append(recent_stats)\n    \n    # Объединяем recent activity\n    recent_combined = all_clients.copy()\n    for recent_df in recent_activity_features:\n        if len(recent_df) > 0:\n            recent_combined = recent_combined.merge(recent_df, on='client_num', how='left')\n    \n    recent_combined = recent_combined.fillna(0)\n    features_list.append(recent_combined)\n    \n    # === 4. MOMENTUM INDICATORS ===\n    print(\"   🚀 Momentum индикаторы...\")\n    \n    # Сравнение первой и второй половины периода\n    total_days = (max_date - min_date).days\n    mid_date = min_date + timedelta(days=total_days//2)\n    \n    first_half = transactions[transactions['date'] <= mid_date]\n    second_half = transactions[transactions['date'] > mid_date]\n    \n    # Статистики по половинам\n    first_half_stats = first_half.groupby('client_num').agg({\n        'amount': ['sum', 'count'],\n        'date': 'nunique'\n    })\n    first_half_stats.columns = ['first_half_amount', 'first_half_count', 'first_half_days']\n    first_half_stats = first_half_stats.reset_index()\n    \n    second_half_stats = second_half.groupby('client_num').agg({\n        'amount': ['sum', 'count'],\n        'date': 'nunique'\n    })\n    second_half_stats.columns = ['second_half_amount', 'second_half_count', 'second_half_days']\n    second_half_stats = second_half_stats.reset_index()\n    \n    # Momentum фичи\n    momentum_features = all_clients.merge(first_half_stats, on='client_num', how='left').fillna(0)\n    momentum_features = momentum_features.merge(second_half_stats, on='client_num', how='left').fillna(0)\n    \n    # Расчет momentum\n    momentum_features['activity_momentum'] = (\n        momentum_features['second_half_count'] - momentum_features['first_half_count']\n    ) / (momentum_features['first_half_count'] + 1)\n    \n    momentum_features['spending_momentum'] = (\n        momentum_features['second_half_amount'] - momentum_features['first_half_amount']\n    ) / (momentum_features['first_half_amount'] + 1)\n    \n    momentum_features['days_momentum'] = (\n        momentum_features['second_half_days'] - momentum_features['first_half_days']\n    ) / (momentum_features['first_half_days'] + 1)\n    \n    # Общий momentum score\n    momentum_features['overall_momentum'] = (\n        momentum_features['activity_momentum'] + \n        momentum_features['spending_momentum'] + \n        momentum_features['days_momentum']\n    ) / 3\n    \n    features_list.append(momentum_features)\n    \n    # === 5. BANKRUPTCY RISK SCORE ===\n    print(\"   🚨 Bankruptcy risk score...\")\n    \n    # Создаем промежуточный датасет для risk score\n    temp_features = all_clients.copy()\n    for feat_df in features_list:\n        temp_features = temp_features.merge(feat_df, on='client_num', how='left')\n    temp_features = temp_features.fillna(0)\n    \n    # Композитный риск-скор на основе найденных корреляций\n    risk_components = []\n    \n    # Компонент 1: Recency risk (чем больше дней, тем хуже)\n    if 'days_since_last_txn' in temp_features.columns:\n        recency_risk = temp_features['days_since_last_txn'] / temp_features['days_since_last_txn'].max()\n        risk_components.append(recency_risk * 0.3)\n    \n    # Компонент 2: Low activity risk\n    if 'activity_density' in temp_features.columns:\n        activity_risk = 1 / (temp_features['activity_density'] + 0.01)  # Инвертируем\n        activity_risk = activity_risk / activity_risk.max()  # Нормализуем\n        risk_components.append(activity_risk * 0.25)\n    \n    # Компонент 3: Negative momentum\n    if 'overall_momentum' in temp_features.columns:\n        momentum_risk = np.clip(-temp_features['overall_momentum'], 0, None)  # Только негативный momentum\n        if momentum_risk.max() > 0:\n            momentum_risk = momentum_risk / momentum_risk.max()\n        risk_components.append(momentum_risk * 0.25)\n    \n    # Компонент 4: Inconsistency\n    if 'weekly_consistency' in temp_features.columns:\n        consistency_risk = 1 - temp_features['weekly_consistency']  # Инвертируем\n        risk_components.append(consistency_risk * 0.2)\n    \n    # Итоговый risk score\n    if risk_components:\n        bankruptcy_risk_score = sum(risk_components)\n        bankruptcy_risk_df = pd.DataFrame({\n            'client_num': temp_features['client_num'],\n            'bankruptcy_risk_score': bankruptcy_risk_score,\n            'risk_category': pd.cut(bankruptcy_risk_score, bins=5, labels=['Very Low', 'Low', 'Medium', 'High', 'Very High'])\n        })\n        \n        features_list.append(bankruptcy_risk_df)\n    \n    # === ОБЪЕДИНЕНИЕ ВСЕХ ФИЧЕЙ ===\n    print(\"   🔗 Объединяем activity/recency фичи...\")\n    \n    final_features = all_clients.copy()\n    \n    for i, feature_df in enumerate(features_list):\n        print(f\"      Добавляем набор {i+1}/{len(features_list)}: {feature_df.shape}\")\n        final_features = final_features.merge(feature_df, on='client_num', how='left')\n    \n    # Финальная обработка - ИСПРАВЛЕННАЯ ВЕРСИЯ\n    # Заполняем только числовые колонки\n    numeric_cols = final_features.select_dtypes(include=[np.number]).columns\n    final_features[numeric_cols] = final_features[numeric_cols].fillna(0)\n    \n    # Убираем категориальные колонки для корректного сохранения\n    categorical_cols = final_features.select_dtypes(include=['category', 'object']).columns\n    categorical_cols = [col for col in categorical_cols if col != 'client_num']\n    \n    if len(categorical_cols) > 0:\n        print(f\"      Удаляем категориальные колонки: {categorical_cols}\")\n        final_features = final_features.drop(columns=categorical_cols)\n    \n    print(f\"✅ Создано {final_features.shape[1]-1} activity/recency фичей для {final_features.shape[0]} клиентов\")\n    \n    # Показываем созданные фичи\n    new_features = [col for col in final_features.columns if col != 'client_num']\n    print(f\"\\n📋 Созданные фичи:\")\n    for i, feat in enumerate(new_features, 1):\n        print(f\"   {i:2d}. {feat}\")\n    \n    return final_features\n\n# === БЫСТРОЕ ТЕСТИРОВАНИЕ ===\nif __name__ == \"__main__\":\n    print(\"🧪 Тестируем activity/recency функцию...\")\n    \n    # Тестовые данные\n    test_data = pd.DataFrame({\n        'client_num': [1, 1, 1, 2, 2, 3, 3, 3, 3],\n        'date_time': pd.to_datetime([\n            '2024-01-01 10:00', '2024-01-15 15:00', '2024-01-30 12:00',  # Client 1\n            '2024-01-01 11:00', '2024-01-02 16:00',                      # Client 2 (recent)\n            '2024-01-01 09:00', '2024-01-10 14:00', '2024-01-20 13:00', '2024-01-29 18:00'  # Client 3\n        ]),\n        'mcc_code': [5411, 5499, 5814, 5411, 6011, 5411, 5499, 5814, 6011],\n        'amount': [1000, 500, 1500, 2000, 50000, 800, 600, 1200, 30000]\n    })\n    \n    test_clients = {1, 2, 3}\n    \n    try:\n        result = create_activity_recency_features(test_data, test_clients)\n        print(f\"✅ Тест прошел! Создано {result.shape[1]-1} фичей для {result.shape[0]} клиентов\")\n    except Exception as e:\n        print(f\"❌ Тест провален: {e}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T06:46:25.480199Z","iopub.execute_input":"2025-07-04T06:46:25.480542Z","iopub.status.idle":"2025-07-04T06:46:25.602806Z","shell.execute_reply.started":"2025-07-04T06:46:25.480511Z","shell.execute_reply":"2025-07-04T06:46:25.598180Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Создаем новые activity/recency фичи\nactivity_recency_features = create_activity_recency_features(transactions, train_clients)\n\nprint(\"🔗 Добавляем к существующим фичам...\")\n\n# Загружаем enhanced данные\nenhanced_train = pd.read_parquet('/kaggle/working/SET_1_train_data.parquet')\nenhanced_test = pd.read_parquet('/kaggle/working/SET_1_test_data.parquet')\n\n# Загружаем train для получения client_num и target\ntrain = pd.read_parquet('/kaggle/input/alpha-summer-challenge/train.pa')\n\n# ЧИСТОЕ объединение без дубликатов\n# Для train\ntrain_with_features = train[['client_num', 'target']].copy()\ntrain_with_features = train_with_features.reset_index(drop=True)\nenhanced_train = enhanced_train.reset_index(drop=True)\n\n# Объединяем по индексу (они должны совпадать)\ntrain_combined = pd.concat([train_with_features, enhanced_train], axis=1)\n\n# Добавляем новые activity фичи\nfinal_train = train_combined.merge(activity_recency_features, on='client_num', how='left').fillna(0)\n\n# Для test - просто добавляем новые фичи\nfinal_test = enhanced_test.merge(activity_recency_features, on='client_num', how='left').fillna(0)\n\n# Убираем дубликаты колонок если есть\nfinal_train = final_train.loc[:, ~final_train.columns.duplicated()]\nfinal_test = final_test.loc[:, ~final_test.columns.duplicated()]\n\nprint(f\"Train: {final_train.shape[1]-2} фичей\")  # -2 для client_num и target\nprint(f\"Test: {final_test.shape[1]-1} фичей\")   # -1 для client_num\n\n# Сохраняем\nfinal_train.to_parquet('/kaggle/working/SET_2_train_data.parquet', index=False)\nfinal_test.to_parquet('/kaggle/working/SET_2_test_data.parquet', index=False)\n\nprint(\"✅ ГОТОВО! Финальные датасеты сохранены!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:05:16.706904Z","iopub.execute_input":"2025-07-04T07:05:16.707275Z","iopub.status.idle":"2025-07-04T07:07:35.883945Z","shell.execute_reply.started":"2025-07-04T07:05:16.707245Z","shell.execute_reply":"2025-07-04T07:07:35.878635Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"final_train","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:08:46.147658Z","iopub.execute_input":"2025-07-04T07:08:46.148000Z","iopub.status.idle":"2025-07-04T07:08:46.339048Z","shell.execute_reply.started":"2025-07-04T07:08:46.147971Z","shell.execute_reply":"2025-07-04T07:08:46.331210Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"📊 Анализируем {final_train.shape[1]-2} фичей на {final_train.shape[0]} клиентах\")\n\nanalysis_results_v2 = analyze_target_correlations(final_train)\nplot_correlation_analysis(final_train, analysis_results_v2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-04T07:11:47.549188Z","iopub.execute_input":"2025-07-04T07:11:47.549551Z","iopub.status.idle":"2025-07-04T07:11:53.937312Z","shell.execute_reply.started":"2025-07-04T07:11:47.549522Z","shell.execute_reply":"2025-07-04T07:11:53.930829Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---------","metadata":{}},{"cell_type":"code","source":"features_guide = \"\"\"\n# Справочник признаков\n\n## 1. SIMPLE FEATURES\n\n### 1.1 Базовые агрегаты\n| Признак | Описание |\n|---------|----------|\n| `total_amount` | Общая сумма всех транзакций клиента |\n| `mean_amount` | Средняя сумма транзакции |\n| `median_amount` | Медианная сумма транзакции |\n| `std_amount` | Стандартное отклонение сумм |\n| `max_amount` | Максимальная сумма транзакции |\n| `min_amount` | Минимальная сумма транзакции |\n| `skew_amount` | Асимметрия распределения сумм |\n| `kurtosis_amount` | Эксцесс распределения сумм |\n| `transaction_count` | Общее количество транзакций |\n| `unique_mcc` | Количество уникальных MCC кодов |\n| `log_total_amount` | Логарифм общей суммы |\n| `log_mean_amount` | Логарифм средней суммы |\n| `log_median_amount` | Логарифм медианной суммы |\n| `log_std_amount` | Логарифм стандартного отклонения |\n\n### 1.2 Rolling признаки\n| Признак | Описание |\n|---------|----------|\n| `rolling_mean_amt_{lag}` | Скользящее среднее суммы за {lag} дней |\n| `rolling_median_amt_{lag}` | Скользящая медиана суммы за {lag} дней |\n| `rolling_std_amt_{lag}` | Скользящее стандартное отклонение за {lag} дней |\n| `rolling_mean_cnt_{lag}` | Скользящее среднее количества транзакций за {lag} дней |\n\n*Лаги: 3, 7, 10, 14, 30, 60, 90, 180, 360 дней*\n\n### 1.3 Временные признаки\n| Признак | Описание |\n|---------|----------|\n| `avg_amt_night` | Средняя сумма ночных транзакций (0-5 часов) |\n| `avg_amt_morning` | Средняя сумма утренних транзакций (6-11 часов) |\n| `avg_amt_day` | Средняя сумма дневных транзакций (12-17 часов) |\n| `avg_amt_evening` | Средняя сумма вечерних транзакций (18-23 часа) |\n| `weekday_avg_{0-6}` | Средняя сумма по дням недели (0=понедельник) |\n| `interval_mean` | Среднее время между транзакциями (часы) |\n| `interval_std` | Стандартное отклонение времени между транзакциями |\n| `interval_min` | Минимальное время между транзакциями |\n| `interval_max` | Максимальное время между транзакциями |\n| `active_days` | Количество дней от первой до последней транзакции |\n\n### 1.4 MCC признаки\n| Признак | Описание |\n|---------|----------|\n| `mcc_{код}` | Количество транзакций по конкретному MCC коду |\n| `mcc_other` | Количество транзакций редких MCC кодов |\n| `mcc1`, `mcc2`, `mcc3` | Топ-3 MCC кода клиента по частоте |\n\n### 1.5 RFM признаки\n| Признак | Описание |\n|---------|----------|\n| `rfm_recency` | Дни с последней транзакции |\n| `rfm_frequency` | Частота транзакций (общее количество) |\n| `rfm_monetary` | Денежная ценность (общая сумма) |\n\n---\n\n## 2. ADVANCED FEATURES\n\n### 2.1 Momentum & Velocity\n| Признак | Описание |\n|---------|----------|\n| `momentum_sum` | Изменение суммы трат (вторая половина vs первая) |\n| `momentum_mean` | Изменение средней суммы между периодами |\n| `momentum_count` | Изменение количества транзакций |\n| `momentum_mcc` | Изменение разнообразия MCC кодов |\n| `behavior_stability` | Стабильность поведения (обратная к изменчивости) |\n\n### 2.2 Циклические и сезонные\n| Признак | Описание |\n|---------|----------|\n| `weekend_txns` | Количество транзакций в выходные |\n| `weekend_ratio` | Доля транзакций в выходные |\n| `month_start_txns` | Транзакции в начале месяца (1-7 число) |\n| `month_start_ratio` | Доля транзакций в начале месяца |\n| `month_end_txns` | Транзакции в конце месяца (25-31 число) |\n| `month_end_ratio` | Доля транзакций в конце месяца |\n| `avg_hour_sin` | Циклическое кодирование среднего часа (sin) |\n| `avg_hour_cos` | Циклическое кодирование среднего часа (cos) |\n| `avg_weekday_sin` | Циклическое кодирование дня недели (sin) |\n| `avg_weekday_cos` | Циклическое кодирование дня недели (cos) |\n\n### 2.3 Сетевые признаки\n| Признак | Описание |\n|---------|----------|\n| `avg_merchant_popularity` | Средняя популярность используемых мерчантов |\n| `max_merchant_popularity` | Максимальная популярность мерчанта |\n| `min_merchant_popularity` | Минимальная популярность мерчанта |\n| `std_merchant_popularity` | Разброс популярности мерчантов |\n| `unique_merchants_x` | Количество уникальных мерчантов |\n| `exclusivity_score` | Склонность к редким мерчантам |\n\n### 2.4 Энтропия и сложность\n| Признак | Описание |\n|---------|----------|\n| `mcc_entropy` | Энтропия распределения MCC кодов |\n| `time_entropy` | Энтропия временных паттернов |\n| `amount_entropy` | Энтропия распределения сумм |\n| `total_complexity` | Общая сложность поведения |\n\n### 2.5 Аномалии поведения\n| Признак | Описание |\n|---------|----------|\n| `z_avg_amount` | Z-score средней суммы |\n| `z_txn_count` | Z-score количества транзакций |\n| `z_active_days` | Z-score активных дней |\n| `anomaly_score` | Общий скор аномальности |\n\n### 2.6 Продвинутые rolling\n| Признак | Описание |\n|---------|----------|\n| `ema_amount` | Экспоненциальное скользящее среднее сумм |\n| `ema_volatility` | EMA волатильности |\n| `spending_autocorr` | Автокорреляция дневных трат |\n\n### 2.7 Категории по поведению\n| Признак | Описание |\n|---------|----------|\n| `frequent_txns` | Количество транзакций в частых категориях |\n| `frequent_ratio` | Доля частых категорий |\n| `expensive_txns` | Количество дорогих транзакций |\n| `expensive_ratio` | Доля дорогих транзакций |\n| `moderate_txns` | Умеренные транзакции |\n| `rare_txns` | Редкие транзакции |\n| `frequent_expensive_ratio` | Соотношение частых к дорогим |\n| `category_diversity` | Разнообразие категорий |\n| `practical_spending_index` | Индекс практичности трат |\n\n---\n\n## 3. BALANCE-SPECIFIC FEATURES\n\n### 3.1 Денежные потоки\n| Признак | Описание |\n|---------|----------|\n| `large_inflow_count` | Количество крупных поступлений |\n| `large_inflow_ratio` | Доля крупных поступлений |\n| `regular_spend_count` | Количество регулярных трат |\n| `regular_spend_ratio` | Доля регулярных трат |\n| `small_spend_count` | Количество мелких трат |\n| `small_spend_ratio` | Доля мелких трат |\n| `inflow_spend_balance` | Баланс поступлений и трат |\n\n### 3.2 Скорость трат\n| Признак | Описание |\n|---------|----------|\n| `burn_rate` | Скорость сжигания денег (сумма/день) |\n| `acceleration` | Ускорение трат |\n| `days_to_spend_median` | Дней на трату медианной суммы |\n\n### 3.3 Финансовая дисциплина\n| Признак | Описание |\n|---------|----------|\n| `spending_cv` | Коэффициент вариации трат |\n| `inactive_days_ratio` | Доля неактивных дней |\n| `max_spending_gap` | Максимальная пауза в тратах |\n| `weekly_stability` | Стабильность недельных трат |\n\n### 3.4 Экономические циклы\n| Признак | Описание |\n|---------|----------|\n| `spending_month_start` | Траты в начале месяца |\n| `spending_month_mid` | Траты в середине месяца |\n| `spending_month_end` | Траты в конце месяца |\n| `monthly_unevenness` | Неравномерность трат по месяцу |\n\n### 3.5 Лояльность и привычки\n| Признак | Описание |\n|---------|----------|\n| `max_visits_merchant` | Максимум посещений одного мерчанта |\n| `avg_visits_merchant` | Среднее посещений мерчантов |\n| `std_visits_merchant` | Разброс посещений |\n| `max_spent_merchant` | Максимум потрачено у одного мерчанта |\n| `avg_spent_merchant` | Среднее потрачено у мерчантов |\n| `unique_merchants_y` | Уникальные мерчанты |\n| `loyalty_index` | Индекс лояльности |\n| `weekday_{0-6}_spending` | Траты по дням недели |\n| `weekday_habit_stability` | Стабильность привычек |\n\n### 3.6 Временной анализ\n| Признак | Описание |\n|---------|----------|\n| `timing_regularity` | Регулярность по времени |\n| `peak_activity_hour` | Пиковый час активности |\n| `activity_concentration` | Концентрация активности |\n\n### 3.7 Индикаторы риска\n| Признак | Описание |\n|---------|----------|\n| `extreme_spending_ratio` | Доля экстремальных трат |\n| `max_vs_median_ratio` | Отношение максимума к медиане |\n| `spending_volatility` | Волатильность трат |\n| `end_period_spending_bias` | Смещение к концу периода |\n\n### 3.8 Диверсификация\n| Признак | Описание |\n|---------|----------|\n| `mcc_diversification` | Диверсификация по MCC |\n| `merchant_diversification` | Диверсификация по мерчантам |\n| `total_diversification` | Общая диверсификация |\n\n### 3.9 Лаговые признаки\n| Признак | Описание |\n|---------|----------|\n| `amount_lag_{1,3,7,14}` | Сумма {N} дней назад |\n| `txns_lag_{1,3,7,14}` | Транзакции {N} дней назад |\n| `avg_amount_lag_{1,3,7,14}` | Средняя сумма {N} дней назад |\n\n### 3.10 Выходные vs будни\n| Признак | Описание |\n|---------|----------|\n| `weekend_amount_sum` | Общая сумма в выходные |\n| `weekend_amount_mean` | Средняя сумма в выходные |\n| `weekend_amount_count` | Количество транзакций в выходные |\n| `weekend_unique_mcc` | Уникальные MCC в выходные |\n| `weekday_amount_sum` | Общая сумма в будни |\n| `weekday_amount_mean` | Средняя сумма в будни |\n| `weekday_amount_count` | Количество транзакций в будни |\n| `weekday_unique_mcc` | Уникальные MCC в будни |\n| `weekend_weekday_amount_ratio` | Соотношение сумм выходные/будни |\n| `weekend_weekday_count_ratio` | Соотношение количества выходные/будни |\n| `weekend_activity_preference` | Предпочтение активности в выходные |\n\n### 3.11 Композитные индексы\n| Признак | Описание |\n|---------|----------|\n| `financial_stability_score` | Скор финансовой стабильности |\n| `bankruptcy_risk_score_x` | Риск банкротства (версия 1) |\n| `financial_health_index` | Индекс финансового здоровья |\n\n---\n\n## 4. DATA-DRIVEN FEATURES\n\n### 4.1 MCC-специфичные признаки\n| Признак | Описание |\n|---------|----------|\n| `mcc5411_total` | Общая сумма по MCC 5411 (продукты) |\n| `mcc5411_avg` | Средняя сумма по MCC 5411 |\n| `mcc5411_count` | Количество транзакций MCC 5411 |\n| `mcc5411_std` | Стандартное отклонение MCC 5411 |\n| `mcc5411_avg_hour` | Средний час для MCC 5411 |\n| `mcc5411_avg_weekday` | Средний день недели для MCC 5411 |\n| `mcc5499_total` | Общая сумма по MCC 5499 |\n| `mcc5499_avg` | Средняя сумма по MCC 5499 |\n| `mcc5499_count` | Количество транзакций MCC 5499 |\n| `mcc6011_total` | Общая сумма по MCC 6011 (дорогие операции) |\n| `mcc6011_avg` | Средняя сумма по MCC 6011 |\n| `mcc6011_count` | Количество транзакций MCC 6011 |\n| `mcc6011_max` | Максимальная сумма по MCC 6011 |\n\n### 4.2 Концентрация трат\n| Признак | Описание |\n|---------|----------|\n| `top3_concentration` | Концентрация на топ-3 MCC |\n\n### 4.3 Высокие транзакции\n| Признак | Описание |\n|---------|----------|\n| `high_total` | Общая сумма высоких транзакций |\n| `high_count` | Количество высоких транзакций |\n| `high_avg` | Средняя высокая транзакция |\n| `high_unique_mcc` | Уникальные MCC в высоких транзакциях |\n| `high_ratio` | Доля высоких транзакций |\n\n### 4.4 Частые vs редкие\n| Признак | Описание |\n|---------|----------|\n| `frequent_spending` | Траты в частых категориях |\n| `rare_spending` | Траты в редких категориях |\n| `frequent_rare_ratio` | Соотношение частых к редким |\n| `rare_tendency` | Склонность к редким категориям |\n\n### 4.5 Переключения категорий\n| Признак | Описание |\n|---------|----------|\n| `category_switches` | Количество переключений между MCC |\n| `switch_rate` | Частота переключений |\n\n### 4.6 Стабильность поведения\n| Признак | Описание |\n|---------|----------|\n| `unique_active_days` | Количество уникальных активных дней |\n| `weekday_amount_stability` | Стабильность трат по дням недели |\n\n### 4.7 Временные паттерны\n| Признак | Описание |\n|---------|----------|\n| `avg_hour` | Средний час транзакций |\n| `std_hour` | Стандартное отклонение часов |\n| `avg_weekday` | Средний день недели |\n| `std_weekday` | Стандартное отклонение дней недели |\n\n---\n\n## 5. ACTIVITY/RECENCY FEATURES\n\n### 5.1 Enhanced Activity\n| Признак | Описание |\n|---------|----------|\n| `activity_density` | Плотность активности (транзакции/день) |\n| `avg_weekly_txns` | Среднее транзакций в неделю |\n| `std_weekly_txns` | Стандартное отклонение недельных транзакций |\n| `active_weeks` | Количество активных недель |\n| `weekly_consistency` | Согласованность недельной активности |\n\n### 5.2 Advanced Recency\n| Признак | Описание |\n|---------|----------|\n| `days_since_last_txn` | Дни с последней транзакции |\n| `days_since_penultimate` | Дни с предпоследней транзакции |\n| `recency_acceleration` | Ускорение пассивности |\n| `recency_trend` | Тренд увеличения пауз |\n\n### 5.3 Recent Activity Analysis\n| Признак | Описание |\n|---------|----------|\n| `recent_7d_sum_amount` | Сумма за последние 7 дней |\n| `recent_7d_count_amount` | Количество транзакций за 7 дней |\n| `recent_7d_mean_amount` | Средняя сумма за 7 дней |\n| `recent_7d_nunique_mcc_code` | Уникальные MCC за 7 дней |\n| `recent_7d_nunique_date` | Уникальные дни с транзакциями за 7 дней |\n| `recent_7d_intensity` | Интенсивность активности за 7 дней |\n| `recent_14d_*` | Аналогичные метрики за 14 дней |\n| `recent_30d_*` | Аналогичные метрики за 30 дней |\n\n### 5.4 Momentum Indicators\n| Признак | Описание |\n|---------|----------|\n| `first_half_amount` | Сумма в первой половине периода |\n| `first_half_count` | Количество в первой половине |\n| `first_half_days` | Активные дни в первой половине |\n| `second_half_amount` | Сумма во второй половине периода |\n| `second_half_count` | Количество во второй половине |\n| `second_half_days` | Активные дни во второй половине |\n| `activity_momentum` | Изменение активности между половинами |\n| `spending_momentum` | Изменение трат между половинами |\n| `days_momentum` | Изменение активных дней |\n| `overall_momentum` | Общий momentum активности |\n\n### 5.5 Bankruptcy Risk Score\n| Признак | Описание |\n|---------|----------|\n| `bankruptcy_risk_score_y` | Композитный скор риска обнуления баланса |\n\"\"\"\n\n# Сохраняем в файл\nwith open('/kaggle/working/features_guide.md', 'w', encoding='utf-8') as f:\n    f.write(features_guide)\n\nprint(\"Справочник сохранен как features_guide.md\")","metadata":{"execution":{"iopub.status.busy":"2025-07-04T09:15:27.375266Z","iopub.execute_input":"2025-07-04T09:15:27.375624Z","iopub.status.idle":"2025-07-04T09:15:27.398648Z","shell.execute_reply.started":"2025-07-04T09:15:27.375597Z","shell.execute_reply":"2025-07-04T09:15:27.392556Z"},"trusted":true,"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Справочник признаков\n\n## 1. SIMPLE FEATURES\n\n### 1.1 Базовые агрегаты\n| Признак | Описание |\n|---------|----------|\n| `total_amount` | Общая сумма всех транзакций клиента |\n| `mean_amount` | Средняя сумма транзакции |\n| `median_amount` | Медианная сумма транзакции |\n| `std_amount` | Стандартное отклонение сумм |\n| `max_amount` | Максимальная сумма транзакции |\n| `min_amount` | Минимальная сумма транзакции |\n| `skew_amount` | Асимметрия распределения сумм |\n| `kurtosis_amount` | Эксцесс распределения сумм |\n| `transaction_count` | Общее количество транзакций |\n| `unique_mcc` | Количество уникальных MCC кодов |\n| `log_total_amount` | Логарифм общей суммы |\n| `log_mean_amount` | Логарифм средней суммы |\n| `log_median_amount` | Логарифм медианной суммы |\n| `log_std_amount` | Логарифм стандартного отклонения |\n\n### 1.2 Rolling признаки\n| Признак | Описание |\n|---------|----------|\n| `rolling_mean_amt_{lag}` | Скользящее среднее суммы за {lag} дней |\n| `rolling_median_amt_{lag}` | Скользящая медиана суммы за {lag} дней |\n| `rolling_std_amt_{lag}` | Скользящее стандартное отклонение за {lag} дней |\n| `rolling_mean_cnt_{lag}` | Скользящее среднее количества транзакций за {lag} дней |\n\n*Лаги: 3, 7, 10, 14, 30, 60, 90, 180, 360 дней*\n\n### 1.3 Временные признаки\n| Признак | Описание |\n|---------|----------|\n| `avg_amt_night` | Средняя сумма ночных транзакций (0-5 часов) |\n| `avg_amt_morning` | Средняя сумма утренних транзакций (6-11 часов) |\n| `avg_amt_day` | Средняя сумма дневных транзакций (12-17 часов) |\n| `avg_amt_evening` | Средняя сумма вечерних транзакций (18-23 часа) |\n| `weekday_avg_{0-6}` | Средняя сумма по дням недели (0=понедельник) |\n| `interval_mean` | Среднее время между транзакциями (часы) |\n| `interval_std` | Стандартное отклонение времени между транзакциями |\n| `interval_min` | Минимальное время между транзакциями |\n| `interval_max` | Максимальное время между транзакциями |\n| `active_days` | Количество дней от первой до последней транзакции |\n\n### 1.4 MCC признаки\n| Признак | Описание |\n|---------|----------|\n| `mcc_{код}` | Количество транзакций по конкретному MCC коду |\n| `mcc_other` | Количество транзакций редких MCC кодов |\n| `mcc1`, `mcc2`, `mcc3` | Топ-3 MCC кода клиента по частоте |\n\n### 1.5 RFM признаки\n| Признак | Описание |\n|---------|----------|\n| `rfm_recency` | Дни с последней транзакции |\n| `rfm_frequency` | Частота транзакций (общее количество) |\n| `rfm_monetary` | Денежная ценность (общая сумма) |\n\n---\n\n## 2. ADVANCED FEATURES\n\n### 2.1 Momentum & Velocity\n| Признак | Описание |\n|---------|----------|\n| `momentum_sum` | Изменение суммы трат (вторая половина vs первая) |\n| `momentum_mean` | Изменение средней суммы между периодами |\n| `momentum_count` | Изменение количества транзакций |\n| `momentum_mcc` | Изменение разнообразия MCC кодов |\n| `behavior_stability` | Стабильность поведения (обратная к изменчивости) |\n\n### 2.2 Циклические и сезонные\n| Признак | Описание |\n|---------|----------|\n| `weekend_txns` | Количество транзакций в выходные |\n| `weekend_ratio` | Доля транзакций в выходные |\n| `month_start_txns` | Транзакции в начале месяца (1-7 число) |\n| `month_start_ratio` | Доля транзакций в начале месяца |\n| `month_end_txns` | Транзакции в конце месяца (25-31 число) |\n| `month_end_ratio` | Доля транзакций в конце месяца |\n| `avg_hour_sin` | Циклическое кодирование среднего часа (sin) |\n| `avg_hour_cos` | Циклическое кодирование среднего часа (cos) |\n| `avg_weekday_sin` | Циклическое кодирование дня недели (sin) |\n| `avg_weekday_cos` | Циклическое кодирование дня недели (cos) |\n\n### 2.3 Сетевые признаки\n| Признак | Описание |\n|---------|----------|\n| `avg_merchant_popularity` | Средняя популярность используемых мерчантов |\n| `max_merchant_popularity` | Максимальная популярность мерчанта |\n| `min_merchant_popularity` | Минимальная популярность мерчанта |\n| `std_merchant_popularity` | Разброс популярности мерчантов |\n| `unique_merchants_x` | Количество уникальных мерчантов |\n| `exclusivity_score` | Склонность к редким мерчантам |\n\n### 2.4 Энтропия и сложность\n| Признак | Описание |\n|---------|----------|\n| `mcc_entropy` | Энтропия распределения MCC кодов |\n| `time_entropy` | Энтропия временных паттернов |\n| `amount_entropy` | Энтропия распределения сумм |\n| `total_complexity` | Общая сложность поведения |\n\n### 2.5 Аномалии поведения\n| Признак | Описание |\n|---------|----------|\n| `z_avg_amount` | Z-score средней суммы |\n| `z_txn_count` | Z-score количества транзакций |\n| `z_active_days` | Z-score активных дней |\n| `anomaly_score` | Общий скор аномальности |\n\n### 2.6 Продвинутые rolling\n| Признак | Описание |\n|---------|----------|\n| `ema_amount` | Экспоненциальное скользящее среднее сумм |\n| `ema_volatility` | EMA волатильности |\n| `spending_autocorr` | Автокорреляция дневных трат |\n\n### 2.7 Категории по поведению\n| Признак | Описание |\n|---------|----------|\n| `frequent_txns` | Количество транзакций в частых категориях |\n| `frequent_ratio` | Доля частых категорий |\n| `expensive_txns` | Количество дорогих транзакций |\n| `expensive_ratio` | Доля дорогих транзакций |\n| `moderate_txns` | Умеренные транзакции |\n| `rare_txns` | Редкие транзакции |\n| `frequent_expensive_ratio` | Соотношение частых к дорогим |\n| `category_diversity` | Разнообразие категорий |\n| `practical_spending_index` | Индекс практичности трат |\n\n---\n\n## 3. BALANCE-SPECIFIC FEATURES\n\n### 3.1 Денежные потоки\n| Признак | Описание |\n|---------|----------|\n| `large_inflow_count` | Количество крупных поступлений |\n| `large_inflow_ratio` | Доля крупных поступлений |\n| `regular_spend_count` | Количество регулярных трат |\n| `regular_spend_ratio` | Доля регулярных трат |\n| `small_spend_count` | Количество мелких трат |\n| `small_spend_ratio` | Доля мелких трат |\n| `inflow_spend_balance` | Баланс поступлений и трат |\n\n### 3.2 Скорость трат\n| Признак | Описание |\n|---------|----------|\n| `burn_rate` | Скорость сжигания денег (сумма/день) |\n| `acceleration` | Ускорение трат |\n| `days_to_spend_median` | Дней на трату медианной суммы |\n\n### 3.3 Финансовая дисциплина\n| Признак | Описание |\n|---------|----------|\n| `spending_cv` | Коэффициент вариации трат |\n| `inactive_days_ratio` | Доля неактивных дней |\n| `max_spending_gap` | Максимальная пауза в тратах |\n| `weekly_stability` | Стабильность недельных трат |\n\n### 3.4 Экономические циклы\n| Признак | Описание |\n|---------|----------|\n| `spending_month_start` | Траты в начале месяца |\n| `spending_month_mid` | Траты в середине месяца |\n| `spending_month_end` | Траты в конце месяца |\n| `monthly_unevenness` | Неравномерность трат по месяцу |\n\n### 3.5 Лояльность и привычки\n| Признак | Описание |\n|---------|----------|\n| `max_visits_merchant` | Максимум посещений одного мерчанта |\n| `avg_visits_merchant` | Среднее посещений мерчантов |\n| `std_visits_merchant` | Разброс посещений |\n| `max_spent_merchant` | Максимум потрачено у одного мерчанта |\n| `avg_spent_merchant` | Среднее потрачено у мерчантов |\n| `unique_merchants_y` | Уникальные мерчанты |\n| `loyalty_index` | Индекс лояльности |\n| `weekday_{0-6}_spending` | Траты по дням недели |\n| `weekday_habit_stability` | Стабильность привычек |\n\n### 3.6 Временной анализ\n| Признак | Описание |\n|---------|----------|\n| `timing_regularity` | Регулярность по времени |\n| `peak_activity_hour` | Пиковый час активности |\n| `activity_concentration` | Концентрация активности |\n\n### 3.7 Индикаторы риска\n| Признак | Описание |\n|---------|----------|\n| `extreme_spending_ratio` | Доля экстремальных трат |\n| `max_vs_median_ratio` | Отношение максимума к медиане |\n| `spending_volatility` | Волатильность трат |\n| `end_period_spending_bias` | Смещение к концу периода |\n\n### 3.8 Диверсификация\n| Признак | Описание |\n|---------|----------|\n| `mcc_diversification` | Диверсификация по MCC |\n| `merchant_diversification` | Диверсификация по мерчантам |\n| `total_diversification` | Общая диверсификация |\n\n### 3.9 Лаговые признаки\n| Признак | Описание |\n|---------|----------|\n| `amount_lag_{1,3,7,14}` | Сумма {N} дней назад |\n| `txns_lag_{1,3,7,14}` | Транзакции {N} дней назад |\n| `avg_amount_lag_{1,3,7,14}` | Средняя сумма {N} дней назад |\n\n### 3.10 Выходные vs будни\n| Признак | Описание |\n|---------|----------|\n| `weekend_amount_sum` | Общая сумма в выходные |\n| `weekend_amount_mean` | Средняя сумма в выходные |\n| `weekend_amount_count` | Количество транзакций в выходные |\n| `weekend_unique_mcc` | Уникальные MCC в выходные |\n| `weekday_amount_sum` | Общая сумма в будни |\n| `weekday_amount_mean` | Средняя сумма в будни |\n| `weekday_amount_count` | Количество транзакций в будни |\n| `weekday_unique_mcc` | Уникальные MCC в будни |\n| `weekend_weekday_amount_ratio` | Соотношение сумм выходные/будни |\n| `weekend_weekday_count_ratio` | Соотношение количества выходные/будни |\n| `weekend_activity_preference` | Предпочтение активности в выходные |\n\n### 3.11 Композитные индексы\n| Признак | Описание |\n|---------|----------|\n| `financial_stability_score` | Скор финансовой стабильности |\n| `bankruptcy_risk_score_x` | Риск банкротства (версия 1) |\n| `financial_health_index` | Индекс финансового здоровья |\n\n---\n\n## 4. DATA-DRIVEN FEATURES\n\n### 4.1 MCC-специфичные признаки\n| Признак | Описание |\n|---------|----------|\n| `mcc5411_total` | Общая сумма по MCC 5411 (продукты) |\n| `mcc5411_avg` | Средняя сумма по MCC 5411 |\n| `mcc5411_count` | Количество транзакций MCC 5411 |\n| `mcc5411_std` | Стандартное отклонение MCC 5411 |\n| `mcc5411_avg_hour` | Средний час для MCC 5411 |\n| `mcc5411_avg_weekday` | Средний день недели для MCC 5411 |\n| `mcc5499_total` | Общая сумма по MCC 5499 |\n| `mcc5499_avg` | Средняя сумма по MCC 5499 |\n| `mcc5499_count` | Количество транзакций MCC 5499 |\n| `mcc6011_total` | Общая сумма по MCC 6011 (дорогие операции) |\n| `mcc6011_avg` | Средняя сумма по MCC 6011 |\n| `mcc6011_count` | Количество транзакций MCC 6011 |\n| `mcc6011_max` | Максимальная сумма по MCC 6011 |\n\n### 4.2 Концентрация трат\n| Признак | Описание |\n|---------|----------|\n| `top3_concentration` | Концентрация на топ-3 MCC |\n\n### 4.3 Высокие транзакции\n| Признак | Описание |\n|---------|----------|\n| `high_total` | Общая сумма высоких транзакций |\n| `high_count` | Количество высоких транзакций |\n| `high_avg` | Средняя высокая транзакция |\n| `high_unique_mcc` | Уникальные MCC в высоких транзакциях |\n| `high_ratio` | Доля высоких транзакций |\n\n### 4.4 Частые vs редкие\n| Признак | Описание |\n|---------|----------|\n| `frequent_spending` | Траты в частых категориях |\n| `rare_spending` | Траты в редких категориях |\n| `frequent_rare_ratio` | Соотношение частых к редким |\n| `rare_tendency` | Склонность к редким категориям |\n\n### 4.5 Переключения категорий\n| Признак | Описание |\n|---------|----------|\n| `category_switches` | Количество переключений между MCC |\n| `switch_rate` | Частота переключений |\n\n### 4.6 Стабильность поведения\n| Признак | Описание |\n|---------|----------|\n| `unique_active_days` | Количество уникальных активных дней |\n| `weekday_amount_stability` | Стабильность трат по дням недели |\n\n### 4.7 Временные паттерны\n| Признак | Описание |\n|---------|----------|\n| `avg_hour` | Средний час транзакций |\n| `std_hour` | Стандартное отклонение часов |\n| `avg_weekday` | Средний день недели |\n| `std_weekday` | Стандартное отклонение дней недели |\n\n---\n\n## 5. ACTIVITY/RECENCY FEATURES\n\n### 5.1 Enhanced Activity\n| Признак | Описание |\n|---------|----------|\n| `activity_density` | Плотность активности (транзакции/день) |\n| `avg_weekly_txns` | Среднее транзакций в неделю |\n| `std_weekly_txns` | Стандартное отклонение недельных транзакций |\n| `active_weeks` | Количество активных недель |\n| `weekly_consistency` | Согласованность недельной активности |\n\n### 5.2 Advanced Recency\n| Признак | Описание |\n|---------|----------|\n| `days_since_last_txn` | Дни с последней транзакции |\n| `days_since_penultimate` | Дни с предпоследней транзакции |\n| `recency_acceleration` | Ускорение пассивности |\n| `recency_trend` | Тренд увеличения пауз |\n\n### 5.3 Recent Activity Analysis\n| Признак | Описание |\n|---------|----------|\n| `recent_7d_sum_amount` | Сумма за последние 7 дней |\n| `recent_7d_count_amount` | Количество транзакций за 7 дней |\n| `recent_7d_mean_amount` | Средняя сумма за 7 дней |\n| `recent_7d_nunique_mcc_code` | Уникальные MCC за 7 дней |\n| `recent_7d_nunique_date` | Уникальные дни с транзакциями за 7 дней |\n| `recent_7d_intensity` | Интенсивность активности за 7 дней |\n| `recent_14d_*` | Аналогичные метрики за 14 дней |\n| `recent_30d_*` | Аналогичные метрики за 30 дней |\n\n### 5.4 Momentum Indicators\n| Признак | Описание |\n|---------|----------|\n| `first_half_amount` | Сумма в первой половине периода |\n| `first_half_count` | Количество в первой половине |\n| `first_half_days` | Активные дни в первой половине |\n| `second_half_amount` | Сумма во второй половине периода |\n| `second_half_count` | Количество во второй половине |\n| `second_half_days` | Активные дни во второй половине |\n| `activity_momentum` | Изменение активности между половинами |\n| `spending_momentum` | Изменение трат между половинами |\n| `days_momentum` | Изменение активных дней |\n| `overall_momentum` | Общий momentum активности |\n\n### 5.5 Bankruptcy Risk Score\n| Признак | Описание |\n|---------|----------|\n| `bankruptcy_risk_score_y` | Композитный скор риска обнуления баланса |","metadata":{"execution":{"iopub.status.busy":"2025-07-04T09:16:20.885685Z","iopub.execute_input":"2025-07-04T09:16:20.885974Z","iopub.status.idle":"2025-07-04T09:16:20.938416Z","shell.execute_reply.started":"2025-07-04T09:16:20.885948Z","shell.execute_reply":"2025-07-04T09:16:20.932789Z"}}},{"cell_type":"markdown","source":"---------\nThat's all folks! Have fun... 🙏\n\n[Samvel Kocharyan](https://www.linkedin.com/in/samvelkoch/), 2025","metadata":{}}]}