{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":105399,"databundleVersionId":12733338,"sourceType":"competition"},{"sourceId":12671746,"sourceType":"datasetVersion","datasetId":8007857}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-04T16:27:29.333759Z","iopub.execute_input":"2025-08-04T16:27:29.333971Z","iopub.status.idle":"2025-08-04T16:27:32.046445Z","shell.execute_reply.started":"2025-08-04T16:27:29.333950Z","shell.execute_reply":"2025-08-04T16:27:32.045144Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport lightgbm as lgb\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nimport seaborn as sns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-04T16:28:13.624454Z","iopub.execute_input":"2025-08-04T16:28:13.624787Z","iopub.status.idle":"2025-08-04T16:28:20.907379Z","shell.execute_reply.started":"2025-08-04T16:28:13.624760Z","shell.execute_reply":"2025-08-04T16:28:20.906528Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(\"/kaggle/input/sample-for-aeroclub-recsys-2025/ranking_sample.csv\")\ndf.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"🔍 1. Feature Engineering","metadata":{}},{"cell_type":"code","source":"from sklearn.preprocessing import MultiLabelBinarizer\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-04T16:45:32.133559Z","iopub.execute_input":"2025-08-04T16:45:32.134431Z","iopub.status.idle":"2025-08-04T16:45:32.139498Z","shell.execute_reply.started":"2025-08-04T16:45:32.134377Z","shell.execute_reply":"2025-08-04T16:45:32.138332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(df.columns.tolist())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-04T16:40:53.057216Z","iopub.execute_input":"2025-08-04T16:40:53.058061Z","iopub.status.idle":"2025-08-04T16:40:53.063958Z","shell.execute_reply.started":"2025-08-04T16:40:53.058030Z","shell.execute_reply":"2025-08-04T16:40:53.062466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.preprocessing import MultiLabelBinarizer\nimport pandas as pd\n\ndef process(df):\n    df = df.copy()\n    \n    # Преобразования durations\n    df['legs0_duration'] = pd.to_numeric(df['legs0_duration'], errors='coerce').fillna(0)\n    df['legs1_duration'] = pd.to_numeric(df['legs1_duration'], errors='coerce').fillna(0)\n    df['total_duration'] = df['legs0_duration'] + df['legs1_duration']\n\n    # tax_ratio = taxes / totalPrice\n    df['taxes'] = pd.to_numeric(df['taxes'], errors='coerce').fillna(0)\n    df['totalPrice'] = pd.to_numeric(df['totalPrice'], errors='coerce').replace(0, 1)\n    df['tax_ratio'] = df['taxes'] / df['totalPrice']\n\n    # days_before_departure = (legs0_departureAt - requestDate).days\n    df['legs0_departureAt'] = pd.to_datetime(df['legs0_departureAt'], errors='coerce')\n    df['requestDate'] = pd.to_datetime(df['requestDate'], errors='coerce')\n    df['days_before_departure'] = (df['legs0_departureAt'] - df['requestDate']).dt.days.fillna(-1)\n\n    # price_per_passenger\n    df['pricingInfo_passengerCount'] = pd.to_numeric(df['pricingInfo_passengerCount'], errors='coerce').replace(0, 1)\n    df['price_per_passenger'] = df['totalPrice'] / df['pricingInfo_passengerCount']\n\n    # has_baggage — если суммарно в legs0_segments0_baggageAllowance_quantity есть багаж\n    df['legs0_segments0_baggageAllowance_quantity'] = pd.to_numeric(\n        df['legs0_segments0_baggageAllowance_quantity'], errors='coerce').fillna(0)\n    df['has_baggage'] = (df['legs0_segments0_baggageAllowance_quantity'] > 0).astype(int)\n\n    # refundable и exchangeable из статусов правил\n    df['miniRules0_statusInfos'] = df['miniRules0_statusInfos'].fillna(0).astype(int)\n    df['miniRules1_statusInfos'] = df['miniRules1_statusInfos'].fillna(0).astype(int)\n    df['is_refundable'] = (df['miniRules0_statusInfos'] != 0).astype(int)\n    df['is_exchangeable'] = (df['miniRules1_statusInfos'] != 0).astype(int)\n\n    # Обработка frequentFlyer (one-hot)\n    df['frequentFlyer'] = df['frequentFlyer'].fillna('None')\n    df['frequentFlyer_split'] = df['frequentFlyer'].astype(str).str.split('/')\n    from sklearn.preprocessing import MultiLabelBinarizer\n    mlb = MultiLabelBinarizer()\n    ff_encoded = pd.DataFrame(\n        mlb.fit_transform(df['frequentFlyer_split']),\n        columns=[f'ff_{c}' for c in mlb.classes_],\n        index=df.index\n    )\n    df = df.drop(columns=['frequentFlyer', 'frequentFlyer_split'])\n    df = pd.concat([df, ff_encoded], axis=1)\n\n    # Приведение бинарных к int\n    for col in ['isVip', 'bySelf']:\n        if col in df.columns:\n            df[col] = df[col].fillna(0).astype(int)\n\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-04T16:45:37.357214Z","iopub.execute_input":"2025-08-04T16:45:37.357629Z","iopub.status.idle":"2025-08-04T16:45:37.370666Z","shell.execute_reply.started":"2025-08-04T16:45:37.357603Z","shell.execute_reply":"2025-08-04T16:45:37.369644Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = process(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-04T16:45:41.443141Z","iopub.execute_input":"2025-08-04T16:45:41.443484Z","iopub.status.idle":"2025-08-04T16:45:41.473312Z","shell.execute_reply.started":"2025-08-04T16:45:41.443452Z","shell.execute_reply":"2025-08-04T16:45:41.472456Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"2. Подготовка признаков и группы","metadata":{}},{"cell_type":"code","source":"features = [\n    'tax_ratio',\n    'days_before_departure',\n    'price_per_passenger',\n    'has_baggage',\n    'is_refundable',\n    'is_exchangeable',\n    'isVip',\n    'bySelf',\n    'total_duration',\n]\n\n# Добавляем все one-hot колонки frequentFlyer\nfeatures += [col for col in train_df.columns if col.startswith('ff_')]\n\nX_train = train_df[features]\ny_train = train_df['selected']\ngroup_train = train_df.groupby('ranker_id').size().to_numpy()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-04T16:49:45.609935Z","iopub.execute_input":"2025-08-04T16:49:45.610237Z","iopub.status.idle":"2025-08-04T16:49:45.619579Z","shell.execute_reply.started":"2025-08-04T16:49:45.610218Z","shell.execute_reply":"2025-08-04T16:49:45.618273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import lightgbm as lgb\n\nmodel = lgb.LGBMRanker(\n    objective=\"lambdarank\",\n    metric=\"ndcg\",\n    ndcg_eval_at=[3],\n    num_leaves=31,\n    learning_rate=0.1,\n    n_estimators=100,\n    random_state=42\n)\n\nmodel.fit(X_train, y_train, group=group_train)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-04T16:50:47.908994Z","iopub.execute_input":"2025-08-04T16:50:47.909932Z","iopub.status.idle":"2025-08-04T16:50:48.084010Z","shell.execute_reply.started":"2025-08-04T16:50:47.909890Z","shell.execute_reply":"2025-08-04T16:50:48.082887Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}