{"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":105399,"databundleVersionId":12733338,"sourceType":"competition"}],"dockerImageVersionId":31042,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#!/usr/bin/env python3\n\"\"\"\nBusiness Flight Recommendation Model - Fast Optimized Version\nKey optimizations:\n1. Vectorized operations instead of apply()\n2. Simplified model with fewer estimators\n3. Efficient feature engineering\n4. Reduced cross-validation folds\n\"\"\"\n\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.ensemble import RandomForestClassifier\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Configuration\nRANDOM_STATE = 42\nN_FOLDS = 2  # Reduced for speed\nN_ESTIMATORS = 50  # Reduced for speed\n\ndef parse_duration_vectorized(duration_series):\n    \"\"\"Vectorized duration parsing - much faster than apply()\"\"\"\n    # Convert to string type for consistent handling\n    duration_str = duration_series.astype(str)\n    \n    # Create output array\n    result = np.full(len(duration_series), np.nan)\n    \n    # Handle numeric values that were converted to string\n    numeric_mask = duration_str.str.match(r'^\\d+\\.?\\d*$')\n    result[numeric_mask] = pd.to_numeric(duration_str[numeric_mask])\n    \n    # Handle HH:MM:SS format\n    time_mask = duration_str.str.contains(':', na=False)\n    if time_mask.any():\n        time_parts = duration_str[time_mask].str.split(':', expand=True)\n        if time_parts.shape[1] >= 2:\n            hours = pd.to_numeric(time_parts[0], errors='coerce')\n            minutes = pd.to_numeric(time_parts[1], errors='coerce')\n            result[time_mask] = hours * 60 + minutes\n    \n    return result\n\ndef load_and_prepare_data():\n    \"\"\"Load data with optimized duration parsing\"\"\"\n    print(\"Loading data...\")\n    train = pd.read_parquet('/kaggle/input/aeroclub-recsys-2025/train.parquet')\n    test = pd.read_parquet('/kaggle/input/aeroclub-recsys-2025/test.parquet')\n    \n    print(f\"Train shape: {train.shape}\")\n    print(f\"Test shape: {test.shape}\")\n    \n    # Fast duration parsing\n    for df in [train, test]:\n        if 'legs0_duration' in df.columns:\n            print(\"Parsing durations (optimized)...\")\n            df['duration_minutes'] = parse_duration_vectorized(df['legs0_duration'])\n            # Fast median fill\n            median_val = np.nanmedian(df['duration_minutes'])\n            df['duration_minutes'].fillna(median_val, inplace=True)\n        else:\n            df['duration_minutes'] = 180  # Default 3 hours\n    \n    return train, test\n\ndef create_features_fast(df):\n    \"\"\"Optimized feature creation with minimal features for speed\"\"\"\n    print(\"Creating features (fast mode)...\")\n    \n    # Essential price features only\n    df['tax_rate'] = df['taxes'] / (df['totalPrice'] + 1e-5)\n    \n    # Fast group statistics using transform (avoids merge)\n    df['price_min'] = df.groupby('ranker_id')['totalPrice'].transform('min')\n    df['price_mean'] = df.groupby('ranker_id')['totalPrice'].transform('mean')\n    df['duration_min'] = df.groupby('ranker_id')['duration_minutes'].transform('min')\n    \n    # Core relative features\n    df['price_ratio'] = df['totalPrice'] / (df['price_min'] + 1e-5)\n    df['duration_ratio'] = df['duration_minutes'] / (df['duration_min'] + 1e-5)\n    df['is_cheapest'] = (df['totalPrice'] == df['price_min']).astype(int)\n    df['is_fastest'] = (df['duration_minutes'] == df['duration_min']).astype(int)\n    \n    # Simple rankings\n    df['price_rank'] = df.groupby('ranker_id')['totalPrice'].rank(method='min')\n    df['duration_rank'] = df.groupby('ranker_id')['duration_minutes'].rank(method='min')\n    df['combined_rank'] = df['price_rank'] + df['duration_rank']\n    \n    # Direct flight check (simplified)\n    segment_cols = [col for col in df.columns if 'segments1_departureFrom' in col]\n    df['is_direct'] = segment_cols[0] if segment_cols else 1\n    df['is_direct'] = df['is_direct'].isna().astype(int)\n    \n    # Time features (simplified)\n    df['request_hour'] = pd.to_datetime(df['requestDate']).dt.hour\n    \n    # Policy compliance\n    if 'pricingInfo_isAccessTP' in df.columns:\n        df['policy_compliant'] = df['pricingInfo_isAccessTP'].fillna(0).astype(int)\n    else:\n        df['policy_compliant'] = 1\n    \n    # Group size (useful for model)\n    df['group_size'] = df.groupby('ranker_id')['ranker_id'].transform('count')\n    \n    return df\n\ndef get_essential_features():\n    \"\"\"Return minimal feature set for speed\"\"\"\n    return [\n        'totalPrice', 'tax_rate',\n        'price_ratio', 'is_cheapest', 'price_rank',\n        'duration_minutes', 'duration_ratio', 'is_fastest', 'duration_rank',\n        'combined_rank', 'is_direct',\n        'request_hour', 'policy_compliant', 'group_size'\n    ]\n\ndef train_fast_model(train_df, feature_cols):\n    \"\"\"Train faster model with RandomForest\"\"\"\n    print(f\"\\nTraining fast model with {N_FOLDS}-fold CV...\")\n    \n    X = train_df[feature_cols].fillna(0)\n    y = train_df['selected']\n    groups = train_df['ranker_id']\n    \n    gkf = GroupKFold(n_splits=N_FOLDS)\n    models = []\n    scores = []\n    \n    for fold, (train_idx, val_idx) in enumerate(gkf.split(X, y, groups)):\n        print(f\"Fold {fold + 1}/{N_FOLDS}...\")\n        \n        X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]\n        y_train, y_val = y.iloc[train_idx], y.iloc[val_idx]\n        \n        # RandomForest is faster than GradientBoosting\n        model = RandomForestClassifier(\n            n_estimators=N_ESTIMATORS,\n            max_depth=10,\n            min_samples_split=20,\n            min_samples_leaf=10,\n            max_features='sqrt',\n            n_jobs=-1,  # Use all cores\n            random_state=RANDOM_STATE + fold,\n            verbose=0\n        )\n        \n        model.fit(X_train, y_train)\n        models.append(model)\n        \n        # Quick validation\n        val_pred = model.predict_proba(X_val)[:, 1]\n        val_df = train_df.iloc[val_idx][['ranker_id', 'selected']].copy()\n        val_df['score'] = val_pred\n        \n        # Fast metric calculation\n        hit_rate = calculate_hitrate3_fast(val_df)\n        scores.append(hit_rate)\n        print(f\"  HitRate@3: {hit_rate:.4f}\")\n    \n    print(f\"\\nAverage HitRate@3: {np.mean(scores):.4f}\")\n    return models\n\ndef calculate_hitrate3_fast(df):\n    \"\"\"Optimized HitRate@3 calculation\"\"\"\n    # Filter groups with >10 options\n    group_sizes = df.groupby('ranker_id').size()\n    valid_groups = group_sizes[group_sizes > 10].index\n    df_filtered = df[df['ranker_id'].isin(valid_groups)]\n    \n    if len(df_filtered) == 0:\n        return 0\n    \n    # Get top 3 for each group\n    top3_idx = df_filtered.groupby('ranker_id')['score'].nlargest(3).index.get_level_values(1)\n    top3_selected = df_filtered.loc[top3_idx, 'selected'].groupby(\n        df_filtered.loc[top3_idx, 'ranker_id']\n    ).sum()\n    \n    hits = (top3_selected > 0).sum()\n    total = len(valid_groups)\n    \n    return hits / total if total > 0 else 0\n\ndef create_submission_fast(test_df, models, feature_cols):\n    \"\"\"Fast submission creation\"\"\"\n    print(\"\\nCreating submission...\")\n    \n    X_test = test_df[feature_cols].fillna(0)\n    \n    # Fast ensemble prediction\n    test_scores = np.mean([\n        model.predict_proba(X_test)[:, 1] for model in models\n    ], axis=0)\n    \n    # Create submission efficiently\n    submission = pd.DataFrame({\n        'Id': test_df['Id'],\n        'ranker_id': test_df['ranker_id'],\n        'score': test_scores\n    })\n    \n    # Fast ranking\n    submission['selected'] = submission.groupby('ranker_id')['score'].rank(\n        method='first',\n        ascending=False\n    ).astype(int)\n    \n    return submission[['Id', 'ranker_id', 'selected']]\n\ndef main():\n    \"\"\"Main execution - optimized for speed\"\"\"\n    import time\n    start_time = time.time()\n    \n    # Load data\n    train, test = load_and_prepare_data()\n    \n    # Create features\n    train = create_features_fast(train)\n    test = create_features_fast(test)\n    \n    # Get features\n    feature_cols = get_essential_features()\n    feature_cols = [col for col in feature_cols if col in train.columns and col in test.columns]\n    print(f\"\\nUsing {len(feature_cols)} features for speed\")\n    \n    # Train model\n    models = train_fast_model(train, feature_cols)\n    \n    # Create submission\n    submission = create_submission_fast(test, models, feature_cols)\n    \n    # Quick validation\n    print(\"\\nValidating submission...\")\n    assert len(submission) == len(test), \"Submission length mismatch\"\n    \n    # Save\n    submission.to_csv('submission.csv', index=False)\n    print(\"\\nSubmission saved to submission.csv\")\n    print(submission.head())\n    \n    # Time taken\n    elapsed = time.time() - start_time\n    print(f\"\\nTotal time: {elapsed:.1f} seconds\")\n    \n    # Top features\n    if hasattr(models[0], 'feature_importances_'):\n        importance = pd.DataFrame({\n            'feature': feature_cols,\n            'importance': np.mean([m.feature_importances_ for m in models], axis=0)\n        }).sort_values('importance', ascending=False)\n        print(\"\\nTop 10 Features:\")\n        print(importance.head(10))\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-06T17:15:05.591900Z","iopub.execute_input":"2025-07-06T17:15:05.592098Z","iopub.status.idle":"2025-07-06T17:21:29.263455Z","shell.execute_reply.started":"2025-07-06T17:15:05.592077Z","shell.execute_reply":"2025-07-06T17:21:29.257678Z"}},"outputs":[],"execution_count":null}]}