{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":"markdown","source":"This notebook presents an XGBoost-based solution adapted from Kirill's CatBoost ranking baseline.\n\nReference: https://www.kaggle.com/code/ka1242/catboost-ranker-baseline-flightrank-2025","metadata":{}},{"cell_type":"code","source":"# Import necessary libraries\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import GroupShuffleSplit\nfrom sklearn.metrics import log_loss\nimport matplotlib.pyplot as plt\n!pip install xgboost\nimport xgboost as xgb\n# from kaggle.api.kaggle_api_extended import KaggleApi\n\n# # Set display options for better readability\n# pd.set_option('display.max_columns', 50)\n# plt.style.use('seaborn-v0_8-darkgrid')","metadata":{"_uuid":"eb709c28-5c3e-48f1-a817-54725992ac2b","_cell_guid":"f117c0e5-240d-469e-b1ad-3e5911b42c7f","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:30:27.331940Z","iopub.execute_input":"2025-06-24T00:30:27.332211Z","iopub.status.idle":"2025-06-24T00:30:56.400069Z","shell.execute_reply.started":"2025-06-24T00:30:27.332188Z","shell.execute_reply":"2025-06-24T00:30:56.394210Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1. Configuration","metadata":{"_uuid":"b07fa03b-69e8-4061-a531-69a5fb8137eb","_cell_guid":"e82400c7-c236-43cc-a6d0-7231217b0074","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Global parameters\nTRAIN_SAMPLE_FRAC = 0.2  # Sample 20% of data for faster iteration\nRANDOM_STATE = 42\nnp.random.seed(RANDOM_STATE)\n\n# Initialize Kaggle API\n# api = KaggleApi()\n# api.authenticate()","metadata":{"_uuid":"9996055b-1c8a-4e0e-90de-4e962148ac79","_cell_guid":"5378e408-b084-4201-9d39-c6e573c1901f","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:30:56.401878Z","iopub.execute_input":"2025-06-24T00:30:56.402205Z","iopub.status.idle":"2025-06-24T00:30:56.410591Z","shell.execute_reply.started":"2025-06-24T00:30:56.402184Z","shell.execute_reply":"2025-06-24T00:30:56.406372Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Load Data","metadata":{"_uuid":"5f3fb35b-5781-42b2-beb7-a0ea555ddeb9","_cell_guid":"55dc0a5a-8409-430d-bb8f-2437bd24a8ab","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Load parquet files\ntrain = pd.read_parquet('/kaggle/input/aeroclub-recsys-2025/train.parquet')\ntest = pd.read_parquet('/kaggle/input/aeroclub-recsys-2025/test.parquet')","metadata":{"_uuid":"4acc25c1-865f-4329-a64a-a0d9a57f634a","_cell_guid":"9b56dc16-2d26-4734-9484-7f6d136ee010","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:30:56.412700Z","iopub.execute_input":"2025-06-24T00:30:56.412894Z","iopub.status.idle":"2025-06-24T00:31:28.377519Z","shell.execute_reply.started":"2025-06-24T00:30:56.412875Z","shell.execute_reply":"2025-06-24T00:31:28.373037Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Train shape: {train.shape}, Test shape: {test.shape}\")\nprint(f\"Unique ranker_ids in train: {train['ranker_id'].nunique():,}\")\nprint(f\"Selected rate: {train['selected'].mean():.3f}\")","metadata":{"_uuid":"b995d4e3-640a-470b-af72-85eeb8b8a6eb","_cell_guid":"4794c6a0-6c6b-40e0-8055-f72249014faf","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:31:28.379744Z","iopub.execute_input":"2025-06-24T00:31:28.380004Z","iopub.status.idle":"2025-06-24T00:31:29.746565Z","shell.execute_reply.started":"2025-06-24T00:31:28.379980Z","shell.execute_reply":"2025-06-24T00:31:29.741211Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Data Sampling & Preprocessing","metadata":{"_uuid":"916b8b29-61e9-4fb1-8b86-41e105632cc9","_cell_guid":"df23fa34-b4d8-49b3-9420-571b84a0ae37","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Sample by ranker_id to keep groups intact\nif TRAIN_SAMPLE_FRAC < 1.0:\n    unique_rankers = train['ranker_id'].unique()\n    n_sample = int(len(unique_rankers) * TRAIN_SAMPLE_FRAC)\n    sampled_rankers = np.random.RandomState(RANDOM_STATE).choice(\n        unique_rankers, size=n_sample, replace=False\n    )\n    train = train[train['ranker_id'].isin(sampled_rankers)]\n    print(f\"Sampled train to {len(train):,} rows ({train['ranker_id'].nunique():,} groups)\")","metadata":{"_uuid":"dd25751c-7074-431b-ae4f-8381fcc7b357","_cell_guid":"9f0b4652-01bb-4d1f-97af-5eeea5f85a04","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:31:29.748168Z","iopub.execute_input":"2025-06-24T00:31:29.748422Z","iopub.status.idle":"2025-06-24T00:31:40.180905Z","shell.execute_reply.started":"2025-06-24T00:31:29.748398Z","shell.execute_reply":"2025-06-24T00:31:40.177390Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert ranker_id to string for CatBoost\ntrain['ranker_id'] = train['ranker_id'].astype(str)\ntest['ranker_id'] = test['ranker_id'].astype(str)","metadata":{"_uuid":"dcc6931b-af8a-4c1c-b291-334b5feaa0ba","_cell_guid":"88d5b5a1-3851-4f5f-8717-81c0b509b68c","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:31:40.183607Z","iopub.execute_input":"2025-06-24T00:31:40.183842Z","iopub.status.idle":"2025-06-24T00:31:40.410114Z","shell.execute_reply.started":"2025-06-24T00:31:40.183820Z","shell.execute_reply":"2025-06-24T00:31:40.404654Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Feature Engineering","metadata":{"_uuid":"995a3183-a37a-4863-b65a-d3a897cb0c1c","_cell_guid":"3589b28b-8402-458d-bf7a-b09e6e6b67b4","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Define categorical features\ncat_features = [\n    'nationality', 'searchRoute', 'corporateTariffCode',\n    # Leg 0 segments 0-1\n    'legs0_segments0_aircraft_code', 'legs0_segments0_arrivalTo_airport_city_iata',\n    'legs0_segments0_arrivalTo_airport_iata', 'legs0_segments0_departureFrom_airport_iata',\n    'legs0_segments0_marketingCarrier_code', 'legs0_segments0_operatingCarrier_code',\n    'legs0_segments1_aircraft_code', 'legs0_segments1_arrivalTo_airport_city_iata',\n    'legs0_segments1_arrivalTo_airport_iata', 'legs0_segments1_departureFrom_airport_iata',\n    'legs0_segments1_marketingCarrier_code', 'legs0_segments1_operatingCarrier_code',\n    # Leg 1 segments 0-1\n    'legs1_segments0_aircraft_code', 'legs1_segments0_arrivalTo_airport_city_iata',\n    'legs1_segments0_arrivalTo_airport_iata', 'legs1_segments0_departureFrom_airport_iata',\n    'legs1_segments0_marketingCarrier_code', 'legs1_segments0_operatingCarrier_code',\n    'legs1_segments1_aircraft_code', 'legs1_segments1_arrivalTo_airport_city_iata',\n    'legs1_segments1_arrivalTo_airport_iata', 'legs1_segments1_departureFrom_airport_iata',\n    'legs1_segments1_marketingCarrier_code', 'legs1_segments1_operatingCarrier_code'\n]","metadata":{"_uuid":"df92fa96-76f3-4f1a-b61d-fc363c14a90d","_cell_guid":"1cad1de5-6728-408c-9473-3b6eb8c62a87","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:31:40.412182Z","iopub.execute_input":"2025-06-24T00:31:40.412449Z","iopub.status.idle":"2025-06-24T00:31:40.421958Z","shell.execute_reply.started":"2025-06-24T00:31:40.412424Z","shell.execute_reply":"2025-06-24T00:31:40.417703Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def duration_to_minutes(duration_str):\n    \"\"\"Convert time format (HH:MM:SS) to minutes\"\"\"\n    if pd.isna(duration_str) or duration_str is None:\n        return np.nan\n    try:\n        parts = str(duration_str).split(':')\n        if len(parts) == 3:\n            hours, minutes, seconds = map(int, parts)\n            return hours * 60 + minutes + seconds / 60\n        return np.nan\n    except:\n        return np.nan\n\ndef create_features(df):\n    \"\"\"Create features for flight ranking\"\"\"\n    # Convert duration columns to minutes\n    duration_cols = ['legs0_duration', 'legs1_duration']\n    for leg in [0, 1]:\n        for seg in range(4):\n            duration_cols.append(f'legs{leg}_segments{seg}_duration')\n    \n    for col in duration_cols:\n        if col in df.columns:\n            df[col] = df[col].apply(duration_to_minutes)\n    \n    # Price features\n    df['price_per_tax'] = df['totalPrice'] / (df['taxes'] + 1)\n    df['tax_rate'] = df['taxes'] / (df['totalPrice'] + 1)\n    \n    # Duration features\n    df['total_duration'] = df['legs0_duration'].fillna(0) + df['legs1_duration'].fillna(0)\n    df['duration_ratio'] = df['legs0_duration'] / (df['legs1_duration'].fillna(df['legs0_duration']) + 1)\n    \n    # Count segments\n    for leg in [0, 1]:\n        segments = [f'legs{leg}_segments{i}_duration' for i in range(2)]\n        df[f'n_segments_leg{leg}'] = df[segments].notna().sum(axis=1)\n    df['total_segments'] = df['n_segments_leg0'] + df['n_segments_leg1']\n    \n    # Trip type\n    df['is_one_way'] = df['legs1_duration'].isna().astype(int)\n    \n    # Ranking features within group\n    df['price_rank'] = df.groupby('ranker_id')['totalPrice'].rank()\n    df['price_pct_rank'] = df.groupby('ranker_id')['totalPrice'].rank(pct=True)\n    df['duration_rank'] = df.groupby('ranker_id')['total_duration'].rank()\n    \n    # Binary features\n    df['frequentFlyer'] = pd.to_numeric(df['frequentFlyer'], errors='coerce').fillna(0)\n    df['is_vip_freq'] = ((df['isVip'] == 1) | (df['frequentFlyer'] == 1)).astype(int)\n    df['has_return'] = (~df['legs1_duration'].isna()).astype(int)\n    df['has_corporate_tariff'] = (~df['corporateTariffCode'].isna()).astype(int)\n    \n    # Baggage allowance\n    df['baggage_total'] = (df['legs0_segments0_baggageAllowance_quantity'].fillna(0) + \n                          df['legs1_segments0_baggageAllowance_quantity'].fillna(0))\n    \n    # Fees\n    df['total_fees'] = (df['miniRules0_monetaryAmount'].fillna(0) + \n                       df['miniRules1_monetaryAmount'].fillna(0))\n    df['has_fees'] = (df['total_fees'] > 0).astype(int)\n    df['fee_rate'] = df['total_fees'] / (df['totalPrice'] + 1)\n    \n    # Time features\n    for col in ['legs0_departureAt', 'legs0_arrivalAt', 'legs1_departureAt', 'legs1_arrivalAt']:\n        if col in df.columns:\n            df[col] = pd.to_datetime(df[col], errors='coerce')\n            df[f'{col}_hour'] = df[col].dt.hour\n            df[f'{col}_weekday'] = df[col].dt.weekday\n    \n    # Direct flight features\n    df['is_direct_leg0'] = (df['n_segments_leg0'] == 1).astype(int)\n    df['is_direct_leg1'] = (df['n_segments_leg1'] == 1).astype(int)\n    df['both_direct'] = (df['is_direct_leg0'] & df['is_direct_leg1']).astype(int)\n    \n    # Access features\n    df['has_access_tp'] = (df['pricingInfo_isAccessTP'] == 1).astype(int)\n    \n    # Handle categorical NaNs\n    for col in cat_features:\n        if col in df.columns:\n            if df[col].dtype.name == 'Int64':\n                df[col] = df[col].astype('Int64').astype(str).replace('<NA>', 'missing')\n            else:\n                df[col] = df[col].fillna('missing').astype(str)\n    \n    # Cabin class features\n    df['avg_cabin_class'] = df[['legs0_segments0_cabinClass', 'legs1_segments0_cabinClass']].mean(axis=1)\n    df['cabin_class_diff'] = df['legs0_segments0_cabinClass'] - df['legs1_segments0_cabinClass']\n    \n    return df","metadata":{"_uuid":"0463183c-1d96-47cb-b95f-878cb4ed9862","_cell_guid":"5cef2f37-397a-44d1-81a1-df02222ae396","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:31:40.423769Z","iopub.execute_input":"2025-06-24T00:31:40.423983Z","iopub.status.idle":"2025-06-24T00:31:40.443780Z","shell.execute_reply.started":"2025-06-24T00:31:40.423962Z","shell.execute_reply":"2025-06-24T00:31:40.439597Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Apply feature engineering\ntrain = create_features(train)\ntest = create_features(test)","metadata":{"_uuid":"32ea010a-02ac-47ec-8042-168ec75caf39","_cell_guid":"e97b1d88-0c8d-4e53-81a8-fb079af6cdb5","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:31:40.445750Z","iopub.execute_input":"2025-06-24T00:31:40.445967Z","iopub.status.idle":"2025-06-24T00:34:17.016921Z","shell.execute_reply.started":"2025-06-24T00:31:40.445948Z","shell.execute_reply":"2025-06-24T00:34:17.012547Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Feature Selection","metadata":{"_uuid":"cccc3213-710a-4c99-bc42-42d29f0a11c1","_cell_guid":"bc6335da-4f50-423f-8431-de2f5b0e7d3a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Exclude columns\nexclude_cols = ['Id', 'ranker_id', 'selected', 'profileId', 'requestDate',\n                'legs0_departureAt', 'legs0_arrivalAt', 'legs1_departureAt', 'legs1_arrivalAt',\n                'miniRules0_percentage', 'miniRules1_percentage',  # >90% missing\n                'frequentFlyer']  # Already processed\n\n# Exclude segment 2-3 columns (>98% missing)\nfor leg in [0, 1]:\n    for seg in [2, 3]:\n        for suffix in ['aircraft_code', 'arrivalTo_airport_city_iata', 'arrivalTo_airport_iata',\n                      'baggageAllowance_quantity', 'baggageAllowance_weightMeasurementType',\n                      'cabinClass', 'departureFrom_airport_iata', 'duration', 'flightNumber',\n                      'marketingCarrier_code', 'operatingCarrier_code', 'seatsAvailable']:\n            exclude_cols.append(f'legs{leg}_segments{seg}_{suffix}')\n\nfeature_cols = [col for col in train.columns if col not in exclude_cols]\ncat_features_final = [col for col in cat_features if col in feature_cols]\n\nprint(f\"Using {len(feature_cols)} features ({len(cat_features_final)} categorical)\")","metadata":{"_uuid":"86b76408-0da2-4c15-b2aa-af2b4fc8e714","_cell_guid":"5a0dafba-ebbd-4399-93d4-1e77cbf4eac1","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:34:17.018935Z","iopub.execute_input":"2025-06-24T00:34:17.019147Z","iopub.status.idle":"2025-06-24T00:34:17.030039Z","shell.execute_reply.started":"2025-06-24T00:34:17.019126Z","shell.execute_reply":"2025-06-24T00:34:17.025177Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Train/Validation Split","metadata":{"_uuid":"fedb84a7-a2d0-48ba-99de-5082cc6d3210","_cell_guid":"54a1762b-8ecc-45b3-beca-548a0286da81","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Prepare data\nX_train = train[feature_cols]\ny_train = train['selected']\ngroups_train = train['ranker_id']\n\nX_test = test[feature_cols]\ngroups_test = test['ranker_id']\n\n# Group-based split\ngss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=RANDOM_STATE)\ntrain_idx, val_idx = next(gss.split(X_train, y_train, groups_train))\n\nX_tr, X_val = X_train.iloc[train_idx], X_train.iloc[val_idx]\ny_tr, y_val = y_train.iloc[train_idx], y_train.iloc[val_idx]\ngroups_tr, groups_val = groups_train.iloc[train_idx], groups_train.iloc[val_idx]\n\nprint(f\"Train: {len(X_tr):,} rows, Val: {len(X_val):,} rows, Test: {len(X_test):,} rows\")","metadata":{"_uuid":"74f3b008-f65c-46e7-9ebd-ac46a5f23d7a","_cell_guid":"f5b90475-9a40-4c1f-8cf6-e93bb6885a03","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:34:17.032273Z","iopub.execute_input":"2025-06-24T00:34:17.032505Z","iopub.status.idle":"2025-06-24T00:34:29.165162Z","shell.execute_reply.started":"2025-06-24T00:34:17.032484Z","shell.execute_reply":"2025-06-24T00:34:29.160664Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Quick data exploration\nfig, axes = plt.subplots(1, 2, figsize=(12, 4))\n\n# Selection rate by price rank\ntrain_sample = train.sample(min(10000, len(train)))\nprice_rank_selection = train_sample.groupby('price_rank')['selected'].mean()\naxes[0].plot(price_rank_selection.index[:20], price_rank_selection.values[:20], marker='o')\naxes[0].set_xlabel('Price Rank within Group')\naxes[0].set_ylabel('Selection Rate')\naxes[0].set_title('Selection Rate by Price Rank')\n\n# Direct vs connecting flights\ndirect_selection = train.groupby('total_segments')['selected'].mean()\naxes[1].bar(direct_selection.index, direct_selection.values)\naxes[1].set_xlabel('Total Segments')\naxes[1].set_ylabel('Selection Rate')\naxes[1].set_title('Selection Rate by Number of Segments')\n\nplt.tight_layout()\nplt.show()","metadata":{"_uuid":"a80b68e0-e262-433e-adff-cc96c70e0046","_cell_guid":"648a2f4a-d041-40da-b5d6-6b93eb5a609b","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:34:29.166859Z","iopub.execute_input":"2025-06-24T00:34:29.167072Z","iopub.status.idle":"2025-06-24T00:34:29.798917Z","shell.execute_reply.started":"2025-06-24T00:34:29.167052Z","shell.execute_reply":"2025-06-24T00:34:29.794803Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Model Training","metadata":{"_uuid":"1c40c8ec-39c6-4886-8fc4-4ba0ff2e2c95","_cell_guid":"0649f96b-ae1c-443b-9406-64bc30a35cf3","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"for df in [X_tr, X_val, X_test]:\n    for col in df.columns:\n        if df[col].dtype == 'object' or str(df[col].dtype) == 'category':\n            df[col], _ = pd.factorize(df[col])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Grupların uzunluklarını al\ngroup_sizes_tr = groups_tr.value_counts().sort_index().tolist()\ngroup_sizes_val = groups_val.value_counts().sort_index().tolist()\n\n# DMatrix oluştur\ndtrain = xgb.DMatrix(X_tr, label=y_tr)\ndtrain.set_group(group_sizes_tr)\n\ndval = xgb.DMatrix(X_val, label=y_val)\ndval.set_group(group_sizes_val)\n","metadata":{"_uuid":"3b8d362f-8cf6-41e4-bc44-89c97cdbc9a4","_cell_guid":"afc38e72-fe9e-4f01-ab1b-696b95a9a993","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:40:01.951978Z","iopub.execute_input":"2025-06-24T00:40:01.952246Z","iopub.status.idle":"2025-06-24T00:40:04.878509Z","shell.execute_reply.started":"2025-06-24T00:40:01.952222Z","shell.execute_reply":"2025-06-24T00:40:04.873610Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"params = {\n    \"objective\": \"rank:pairwise\",\n    \"learning_rate\": 0.1,\n    \"max_depth\": 6,\n    \"eval_metric\": \"ndcg@3\",  # or \"map@3\"\n    \"random_state\": RANDOM_STATE,\n    \"tree_method\": \"hist\"  # Use 'gpu_hist' if GPU available\n}\n\nmodel = xgb.train(\n    params=params,\n    dtrain=dtrain,\n    num_boost_round=200,\n    evals=[(dtrain, \"train\"), (dval, \"val\")],\n    early_stopping_rounds=20,\n    verbose_eval=10\n)\n","metadata":{"_uuid":"15627d88-0b6b-464e-8577-13ad7dbea45d","_cell_guid":"f38c9523-d682-4531-9822-6c96ff44cef2","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:40:07.786113Z","iopub.execute_input":"2025-06-24T00:40:07.786406Z","iopub.status.idle":"2025-06-24T00:40:42.731482Z","shell.execute_reply.started":"2025-06-24T00:40:07.786380Z","shell.execute_reply":"2025-06-24T00:40:42.724392Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Model Evaluation","metadata":{"_uuid":"8af4984a-81e7-4f3c-8a6b-3a3c7b0450f8","_cell_guid":"f460561e-e03e-43f1-865d-21c644f6be73","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Test prediction\ndtest = xgb.DMatrix(X_test)\ntest_preds = model.predict(dtest)\n\n# Validation prediction for metric\ndval_full = xgb.DMatrix(X_val)\nval_preds = model.predict(dval_full)\n\n# Evaluation\ndef sigmoid(x):\n    return 1 / (1 + np.exp(-x / 10))\n\nval_df = pd.DataFrame({\n    'ranker_id': groups_val,\n    'pred': val_preds,\n    'selected': y_val\n})\n\ntop_preds = val_df.loc[val_df.groupby('ranker_id')['pred'].idxmax()]\ntop_preds['prob'] = sigmoid(top_preds['pred'])\n\nval_logloss = log_loss(top_preds['selected'], top_preds['prob'])\nval_accuracy = (top_preds['selected'] == 1).mean()\n\nprint(f\"Validation metrics:\")\nprint(f\"LogLoss: {val_logloss:.4f}\")\nprint(f\"Top-1 Accuracy: {val_accuracy:.4f}\")","metadata":{"_uuid":"3a2263c8-c6e0-449e-b3db-f948128aecfa","_cell_guid":"f084bea7-eb96-4741-a0b9-f1f511b26a53","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:42:48.822055Z","iopub.execute_input":"2025-06-24T00:42:48.822367Z","iopub.status.idle":"2025-06-24T00:42:55.274083Z","shell.execute_reply.started":"2025-06-24T00:42:48.822325Z","shell.execute_reply":"2025-06-24T00:42:55.269621Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Feature importance\nimportance_dict = model.get_score(importance_type='gain')\nfeature_importance = pd.DataFrame({\n    'feature': list(importance_dict.keys()),\n    'importance': list(importance_dict.values())\n}).sort_values(by='importance', ascending=False)\n\n# Plot top 20 features\nplt.figure(figsize=(10, 8))\ntop_features = feature_importance.head(20)\nplt.barh(range(len(top_features)), top_features['importance'])\nplt.yticks(range(len(top_features)), top_features['feature'])\nplt.xlabel('Feature Importance (gain)')\nplt.title('Top 20 Most Important Features')\nplt.gca().invert_yaxis()\nplt.tight_layout()\nplt.show()\n","metadata":{"_uuid":"980f186e-133e-49f4-808c-b5e5672c58b1","_cell_guid":"dc65554d-8fe7-4837-b31c-c4f0f65d5656","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:43:30.805371Z","iopub.execute_input":"2025-06-24T00:43:30.805696Z","iopub.status.idle":"2025-06-24T00:43:31.073582Z","shell.execute_reply.started":"2025-06-24T00:43:30.805669Z","shell.execute_reply":"2025-06-24T00:43:31.070033Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Generate Predictions","metadata":{"_uuid":"04c88c08-cfc0-4176-91fd-02728c509a4a","_cell_guid":"f9631ff4-2b38-4053-870b-f434df5bff99","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"group_sizes_test = groups_test.value_counts().sort_index().tolist()\ndtest = xgb.DMatrix(X_test)\ndtest.set_group(group_sizes_test)\n\ntest_preds = model.predict(dtest)","metadata":{"_uuid":"668b8545-8a51-4e56-a09d-ba1feff07600","_cell_guid":"3eb73931-b0e1-42b4-8d20-ea9c37c3a9b9","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:44:22.269736Z","iopub.execute_input":"2025-06-24T00:44:22.270013Z","iopub.status.idle":"2025-06-24T00:44:28.696531Z","shell.execute_reply.started":"2025-06-24T00:44:22.269990Z","shell.execute_reply":"2025-06-24T00:44:28.691892Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create submission\nsubmission = test[['Id', 'ranker_id']].copy()\nsubmission['pred_score'] = test_preds\n\n# Assign ranks (1 = best option)\nsubmission['selected'] = submission.groupby('ranker_id')['pred_score'].rank(\n    ascending=False, method='first'\n).astype(int)","metadata":{"_uuid":"0b3b0eca-1b7f-4ff5-9bb0-f67299f2f05e","_cell_guid":"af3c32ec-0674-46f4-a672-3819574342aa","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:44:30.376597Z","iopub.execute_input":"2025-06-24T00:44:30.377016Z","iopub.status.idle":"2025-06-24T00:44:33.600214Z","shell.execute_reply.started":"2025-06-24T00:44:30.376978Z","shell.execute_reply":"2025-06-24T00:44:33.594868Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verify ranking integrity\nassert submission.groupby('ranker_id')['selected'].apply(\n    lambda x: sorted(x.tolist()) == list(range(1, len(x)+1))\n).all(), \"Invalid ranking!\"","metadata":{"_uuid":"a4754667-fe8f-428e-b591-a4681ee0f041","_cell_guid":"2cf232eb-b042-478d-aff1-ef1fd5f8dcbb","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:44:34.777669Z","iopub.execute_input":"2025-06-24T00:44:34.778024Z","iopub.status.idle":"2025-06-24T00:44:37.332656Z","shell.execute_reply.started":"2025-06-24T00:44:34.777992Z","shell.execute_reply":"2025-06-24T00:44:37.324528Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save submission\n# submission[['Id', 'ranker_id', 'selected']].to_parquet('submission.parquet', index=False)\nsubmission[['Id', 'ranker_id', 'selected']].to_csv('submission_xgboost.csv', index=False)\nprint(f\"Submission saved. Shape: {submission.shape}\")","metadata":{"_uuid":"54f4899a-880e-40f1-907f-d11bb0bf0a00","_cell_guid":"46904f68-bc19-4558-aca8-fa7a9896f69c","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-06-24T00:45:07.926146Z","iopub.execute_input":"2025-06-24T00:45:07.926443Z","iopub.status.idle":"2025-06-24T00:45:20.132370Z","shell.execute_reply.started":"2025-06-24T00:45:07.926417Z","shell.execute_reply":"2025-06-24T00:45:20.128749Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"60546e56-0724-40ca-b257-1a966cd2b85e","_cell_guid":"3ef0563c-8873-4af2-8145-625fa7c0f2fb","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null}]}