{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.18","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":31091,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Requirements","metadata":{}},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport os","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:02.394131Z","iopub.execute_input":"2025-08-12T15:55:02.394463Z","iopub.status.idle":"2025-08-12T15:55:05.714120Z","shell.execute_reply.started":"2025-08-12T15:55:02.394434Z","shell.execute_reply":"2025-08-12T15:55:05.709707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install polars","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:05.716548Z","iopub.execute_input":"2025-08-12T15:55:05.716871Z","iopub.status.idle":"2025-08-12T15:55:13.984063Z","shell.execute_reply.started":"2025-08-12T15:55:05.716845Z","shell.execute_reply":"2025-08-12T15:55:13.979611Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import polars as pl","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:13.986249Z","iopub.execute_input":"2025-08-12T15:55:13.986505Z","iopub.status.idle":"2025-08-12T15:55:14.641269Z","shell.execute_reply.started":"2025-08-12T15:55:13.986473Z","shell.execute_reply":"2025-08-12T15:55:14.635577Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import re","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:14.643392Z","iopub.execute_input":"2025-08-12T15:55:14.643668Z","iopub.status.idle":"2025-08-12T15:55:14.655511Z","shell.execute_reply.started":"2025-08-12T15:55:14.643643Z","shell.execute_reply":"2025-08-12T15:55:14.650168Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Reading Data","metadata":{}},{"cell_type":"code","source":"dft = pl.read_parquet('/kaggle/input/aeroclub-recsys-2025/train.parquet')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:14.658033Z","iopub.execute_input":"2025-08-12T15:55:14.658276Z","iopub.status.idle":"2025-08-12T15:55:16.874507Z","shell.execute_reply.started":"2025-08-12T15:55:14.658251Z","shell.execute_reply":"2025-08-12T15:55:16.868389Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Analysis","metadata":{}},{"cell_type":"code","source":"pl.read_parquet_schema('/kaggle/input/aeroclub-recsys-2025/train.parquet')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:16.875293Z","iopub.execute_input":"2025-08-12T15:55:16.875554Z","iopub.status.idle":"2025-08-12T15:55:16.902423Z","shell.execute_reply.started":"2025-08-12T15:55:16.875512Z","shell.execute_reply":"2025-08-12T15:55:16.898830Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:16.905390Z","iopub.execute_input":"2025-08-12T15:55:16.905635Z","iopub.status.idle":"2025-08-12T15:55:16.987905Z","shell.execute_reply.started":"2025-08-12T15:55:16.905605Z","shell.execute_reply":"2025-08-12T15:55:16.983278Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Cleaning Data & Feature Engineering","metadata":{}},{"cell_type":"code","source":"dft= dft.drop('bySelf','sex','legs0_segments0_cabinClass','nationality','legs0_segments0_marketingCarrier_code','legs0_segments2_marketingCarrier_code','legs0_segments3_marketingCarrier_code','legs1_segments0_marketingCarrier_code','legs1_segments2_marketingCarrier_code','legs1_segments3_marketingCarrier_code')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:16.990150Z","iopub.execute_input":"2025-08-12T15:55:16.990713Z","iopub.status.idle":"2025-08-12T15:55:17.106384Z","shell.execute_reply.started":"2025-08-12T15:55:16.990687Z","shell.execute_reply":"2025-08-12T15:55:17.100403Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft = dft.drop('legs1_segments0_duration')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:17.108441Z","iopub.execute_input":"2025-08-12T15:55:17.109124Z","iopub.status.idle":"2025-08-12T15:55:17.161696Z","shell.execute_reply.started":"2025-08-12T15:55:17.109096Z","shell.execute_reply":"2025-08-12T15:55:17.155977Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft = dft.with_columns(\n    TotalPrice = pl.col(\"totalPrice\") + pl.col(\"taxes\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:17.163764Z","iopub.execute_input":"2025-08-12T15:55:17.164017Z","iopub.status.idle":"2025-08-12T15:55:17.249740Z","shell.execute_reply.started":"2025-08-12T15:55:17.163993Z","shell.execute_reply":"2025-08-12T15:55:17.242725Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft['TotalPrice']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:17.252747Z","iopub.execute_input":"2025-08-12T15:55:17.252982Z","iopub.status.idle":"2025-08-12T15:55:17.268326Z","shell.execute_reply.started":"2025-08-12T15:55:17.252958Z","shell.execute_reply":"2025-08-12T15:55:17.264315Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft = dft.drop('legs0_segments0_duration','legs0_segments1_duration','legs0_segments2_duration','legs0_segments3_duration')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:17.271772Z","iopub.execute_input":"2025-08-12T15:55:17.272030Z","iopub.status.idle":"2025-08-12T15:55:17.333495Z","shell.execute_reply.started":"2025-08-12T15:55:17.271989Z","shell.execute_reply":"2025-08-12T15:55:17.327429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft = dft.drop('legs1_segments1_duration','legs1_segments2_duration','legs1_segments3_duration')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:17.335613Z","iopub.execute_input":"2025-08-12T15:55:17.335866Z","iopub.status.idle":"2025-08-12T15:55:17.386735Z","shell.execute_reply.started":"2025-08-12T15:55:17.335842Z","shell.execute_reply":"2025-08-12T15:55:17.381380Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft['legs0_departureAt',\n'legs0_arrivalAt',\n'legs0_duration',\n'legs1_departureAt',\n'legs1_arrivalAt',\n'legs1_duration']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:17.387560Z","iopub.execute_input":"2025-08-12T15:55:17.387805Z","iopub.status.idle":"2025-08-12T15:55:17.403166Z","shell.execute_reply.started":"2025-08-12T15:55:17.387779Z","shell.execute_reply":"2025-08-12T15:55:17.399719Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft= dft.with_columns(\n    pl.col('legs0_departureAt',\n'legs0_arrivalAt',\n'legs0_duration',\n'legs1_departureAt',\n'legs1_arrivalAt',\n'legs1_duration').str.replace(r\"T\", \"  \"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:17.406972Z","iopub.execute_input":"2025-08-12T15:55:17.407823Z","iopub.status.idle":"2025-08-12T15:55:18.009142Z","shell.execute_reply.started":"2025-08-12T15:55:17.407771Z","shell.execute_reply":"2025-08-12T15:55:18.003377Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft['legs0_departureAt',\n'legs0_arrivalAt',\n'legs0_duration',\n'legs1_departureAt',\n'legs1_arrivalAt',\n'legs1_duration']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:18.011434Z","iopub.execute_input":"2025-08-12T15:55:18.011710Z","iopub.status.idle":"2025-08-12T15:55:18.024865Z","shell.execute_reply.started":"2025-08-12T15:55:18.011686Z","shell.execute_reply":"2025-08-12T15:55:18.021567Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft= dft.with_columns(\n    pl.col('legs0_departureAt',\n'legs0_arrivalAt',\n'legs1_departureAt',\n'legs1_arrivalAt').str.to_datetime(\"%Y-%m-%d %H:%M:%S\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:18.029512Z","iopub.execute_input":"2025-08-12T15:55:18.029940Z","iopub.status.idle":"2025-08-12T15:55:18.192102Z","shell.execute_reply.started":"2025-08-12T15:55:18.029900Z","shell.execute_reply":"2025-08-12T15:55:18.185275Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft['legs0_departureAt',\n'legs0_arrivalAt',\n'legs1_departureAt',\n'legs1_arrivalAt']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:18.192911Z","iopub.execute_input":"2025-08-12T15:55:18.193146Z","iopub.status.idle":"2025-08-12T15:55:18.209566Z","shell.execute_reply.started":"2025-08-12T15:55:18.193121Z","shell.execute_reply":"2025-08-12T15:55:18.203777Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft['legs0_duration',\n'legs1_duration']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:18.210711Z","iopub.execute_input":"2025-08-12T15:55:18.210954Z","iopub.status.idle":"2025-08-12T15:55:18.255032Z","shell.execute_reply.started":"2025-08-12T15:55:18.210930Z","shell.execute_reply":"2025-08-12T15:55:18.249446Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft = dft.with_columns(\n    pl.col('legs0_duration',\n'legs1_duration').str.replace_all(r\"\\.\\d{2}\", \"\")\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:18.257140Z","iopub.execute_input":"2025-08-12T15:55:18.257388Z","iopub.status.idle":"2025-08-12T15:55:18.410642Z","shell.execute_reply.started":"2025-08-12T15:55:18.257364Z","shell.execute_reply":"2025-08-12T15:55:18.406146Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft=dft.with_columns(\n    pl.col('legs0_duration',\n'legs1_duration').str.to_time(\"%H:%M:%S\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:18.412423Z","iopub.execute_input":"2025-08-12T15:55:18.412647Z","iopub.status.idle":"2025-08-12T15:55:18.493796Z","shell.execute_reply.started":"2025-08-12T15:55:18.412624Z","shell.execute_reply":"2025-08-12T15:55:18.489546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft['legs0_departureAt',\n'legs0_arrivalAt',\n'legs0_duration',\n'legs1_departureAt',\n'legs1_arrivalAt',\n'legs1_duration']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:18.495638Z","iopub.execute_input":"2025-08-12T15:55:18.495856Z","iopub.status.idle":"2025-08-12T15:55:18.511496Z","shell.execute_reply.started":"2025-08-12T15:55:18.495833Z","shell.execute_reply":"2025-08-12T15:55:18.505850Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt = pl.read_parquet('/kaggle/input/aeroclub-recsys-2025/test.parquet')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:18.513863Z","iopub.execute_input":"2025-08-12T15:55:18.514090Z","iopub.status.idle":"2025-08-12T15:55:19.194054Z","shell.execute_reply.started":"2025-08-12T15:55:18.514068Z","shell.execute_reply":"2025-08-12T15:55:19.188966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt= dftt.drop('bySelf','sex','legs0_segments0_cabinClass','nationality','legs0_segments0_marketingCarrier_code','legs0_segments2_marketingCarrier_code','legs0_segments3_marketingCarrier_code','legs1_segments0_marketingCarrier_code','legs1_segments2_marketingCarrier_code','legs1_segments3_marketingCarrier_code',)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.196262Z","iopub.execute_input":"2025-08-12T15:55:19.196507Z","iopub.status.idle":"2025-08-12T15:55:19.232492Z","shell.execute_reply.started":"2025-08-12T15:55:19.196483Z","shell.execute_reply":"2025-08-12T15:55:19.227262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt = dftt.with_columns(\n    TotalPrice = pl.col(\"totalPrice\") + pl.col(\"taxes\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.234264Z","iopub.execute_input":"2025-08-12T15:55:19.234471Z","iopub.status.idle":"2025-08-12T15:55:19.264376Z","shell.execute_reply.started":"2025-08-12T15:55:19.234450Z","shell.execute_reply":"2025-08-12T15:55:19.260343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt = dftt.drop('legs1_segments0_duration')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.266069Z","iopub.execute_input":"2025-08-12T15:55:19.266427Z","iopub.status.idle":"2025-08-12T15:55:19.298558Z","shell.execute_reply.started":"2025-08-12T15:55:19.266405Z","shell.execute_reply":"2025-08-12T15:55:19.292724Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt = dftt.drop('legs0_segments0_duration','legs0_segments1_duration','legs0_segments2_duration','legs0_segments3_duration')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.300313Z","iopub.execute_input":"2025-08-12T15:55:19.300542Z","iopub.status.idle":"2025-08-12T15:55:19.334232Z","shell.execute_reply.started":"2025-08-12T15:55:19.300503Z","shell.execute_reply":"2025-08-12T15:55:19.329857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt = dftt.drop('legs1_segments1_duration','legs1_segments2_duration','legs1_segments3_duration')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.334952Z","iopub.execute_input":"2025-08-12T15:55:19.335146Z","iopub.status.idle":"2025-08-12T15:55:19.367154Z","shell.execute_reply.started":"2025-08-12T15:55:19.335125Z","shell.execute_reply":"2025-08-12T15:55:19.362079Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt['legs0_departureAt',\n'legs0_arrivalAt',\n'legs0_duration',\n'legs1_departureAt',\n'legs1_arrivalAt',\n'legs1_duration']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.369375Z","iopub.execute_input":"2025-08-12T15:55:19.369968Z","iopub.status.idle":"2025-08-12T15:55:19.386309Z","shell.execute_reply.started":"2025-08-12T15:55:19.369943Z","shell.execute_reply":"2025-08-12T15:55:19.380980Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt= dftt.with_columns(\n    pl.col('legs0_departureAt',\n'legs0_arrivalAt',\n'legs0_duration',\n'legs1_departureAt',\n'legs1_arrivalAt',\n'legs1_duration').str.replace(r\"T\", \"  \"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.388755Z","iopub.execute_input":"2025-08-12T15:55:19.388991Z","iopub.status.idle":"2025-08-12T15:55:19.543007Z","shell.execute_reply.started":"2025-08-12T15:55:19.388970Z","shell.execute_reply":"2025-08-12T15:55:19.537427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt= dftt.with_columns(\n    pl.col('legs0_departureAt',\n'legs0_arrivalAt',\n'legs1_departureAt',\n'legs1_arrivalAt').str.to_datetime(\"%Y-%m-%d %H:%M:%S\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.544817Z","iopub.execute_input":"2025-08-12T15:55:19.545038Z","iopub.status.idle":"2025-08-12T15:55:19.618850Z","shell.execute_reply.started":"2025-08-12T15:55:19.545016Z","shell.execute_reply":"2025-08-12T15:55:19.613113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt = dftt.with_columns(\n    pl.col('legs0_duration',\n'legs1_duration').str.replace_all(r\"\\.\\d{2}\", \"\")\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.621171Z","iopub.execute_input":"2025-08-12T15:55:19.621422Z","iopub.status.idle":"2025-08-12T15:55:19.689740Z","shell.execute_reply.started":"2025-08-12T15:55:19.621399Z","shell.execute_reply":"2025-08-12T15:55:19.686171Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt=dftt.with_columns(\n    pl.col('legs0_duration',\n'legs1_duration').str.to_time(\"%H:%M:%S\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.693090Z","iopub.execute_input":"2025-08-12T15:55:19.693325Z","iopub.status.idle":"2025-08-12T15:55:19.741731Z","shell.execute_reply.started":"2025-08-12T15:55:19.693302Z","shell.execute_reply":"2025-08-12T15:55:19.736010Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dftt['legs0_departureAt',\n'legs0_arrivalAt',\n'legs0_duration',\n'legs1_departureAt',\n'legs1_arrivalAt',\n'legs1_duration']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.743309Z","iopub.execute_input":"2025-08-12T15:55:19.743550Z","iopub.status.idle":"2025-08-12T15:55:19.756975Z","shell.execute_reply.started":"2025-08-12T15:55:19.743509Z","shell.execute_reply":"2025-08-12T15:55:19.753188Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def preprocess(df: pl.DataFrame) -> pl.DataFrame:\n    datetime_cols = [\"legs0_departureAt\", \"legs0_arrivalAt\",\n                     \"legs1_departureAt\", \"legs1_arrivalAt\"]\n\n    for col in datetime_cols:\n        # Ensure datetime type\n        df = df.with_columns(\n            pl.col(col).cast(pl.Datetime(time_unit=\"us\"))\n        )\n\n        # Basic datetime features\n        df = df.with_columns([\n            pl.col(col).dt.hour().alias(f\"{col}_hour\"),\n            pl.col(col).dt.weekday().alias(f\"{col}_dow\"),\n            pl.col(col).dt.month().alias(f\"{col}_month\"),\n            (pl.col(col).dt.weekday() >= 5).cast(pl.Int8).alias(f\"{col}_is_weekend\"),\n        ])\n\n        # Cyclical hour encoding\n        df = df.with_columns([\n            (pl.col(f\"{col}_hour\") * (2 * np.pi) / 24).sin().alias(f\"{col}_hour_sin\"),\n            (pl.col(f\"{col}_hour\") * (2 * np.pi) / 24).cos().alias(f\"{col}_hour_cos\"),\n        ])\n\n    # Duration to minutes\n    duration_cols = [\"legs0_duration\", \"legs1_duration\"]\n    for col in duration_cols:\n    # If already a Duration, just convert to minutes\n        if df[col].dtype == pl.Duration(time_unit=\"us\"):\n            df = df.with_columns(\n                (pl.col(col).dt.seconds() / 60).alias(col)\n            )\n        # If it's a Time (HH:MM:SS)\n        elif df[col].dtype == pl.Time:\n            df = df.with_columns(\n                (pl.col(col).dt.hour() * 60 +\n                 pl.col(col).dt.minute() +\n                 pl.col(col).dt.second() / 60).alias(col)\n            )\n        # If it's a string like \"02:40:00\"\n        else:\n            df = df.with_columns(\n                (pl.col(col).str.strptime(pl.Time, \"%H:%M:%S\", strict=False).dt.hour() * 60 +\n                 pl.col(col).str.strptime(pl.Time, \"%H:%M:%S\", strict=False).dt.minute() +\n                 pl.col(col).str.strptime(pl.Time, \"%H:%M:%S\", strict=False).dt.second() / 60\n                ).alias(col)\n            )\n    # Layover\n    if all(c in df.columns for c in [\"legs1_departureAt\", \"legs0_arrivalAt\"]):\n    \n        df = df.with_columns(\n            (\n                pl.col(\"legs1_departureAt\").cast(pl.Datetime(\"us\")) -\n                pl.col(\"legs0_arrivalAt\").cast(pl.Datetime(\"us\"))\n            ).dt.total_seconds() / 60\n            \n        )\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.761020Z","iopub.execute_input":"2025-08-12T15:55:19.761236Z","iopub.status.idle":"2025-08-12T15:55:19.779483Z","shell.execute_reply.started":"2025-08-12T15:55:19.761215Z","shell.execute_reply":"2025-08-12T15:55:19.773891Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Preprocessing...\")\ndft = preprocess(dft)\ndftt = preprocess(dftt)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:19.781899Z","iopub.execute_input":"2025-08-12T15:55:19.782127Z","iopub.status.idle":"2025-08-12T15:55:21.079605Z","shell.execute_reply.started":"2025-08-12T15:55:19.782104Z","shell.execute_reply":"2025-08-12T15:55:21.074657Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Feature Selection","metadata":{}},{"cell_type":"code","source":"x = dft['Id','TotalPrice','searchRoute','corporateTariffCode','profileId','companyID',\n'legs0_duration' ,\n'legs1_duration','legs0_segments0_seatsAvailable','legs0_segments1_seatsAvailable','legs0_segments2_seatsAvailable','legs0_segments3_seatsAvailable','legs1_segments0_seatsAvailable','legs1_segments1_seatsAvailable','legs1_segments2_seatsAvailable','legs1_segments3_seatsAvailable',\n'miniRules0_monetaryAmount' ,\n'miniRules0_percentage',\n'miniRules0_statusInfos' ,\n'miniRules1_monetaryAmount' ,\n'miniRules1_percentage' ,\n'miniRules1_statusInfos' ,\n'pricingInfo_isAccessTP' ,\n'pricingInfo_passengerCount','legs0_segments0_baggageAllowance_quantity','legs0_segments1_baggageAllowance_quantity','legs0_segments2_baggageAllowance_quantity','legs0_segments3_baggageAllowance_quantity','legs1_segments0_baggageAllowance_quantity','legs1_segments1_baggageAllowance_quantity','legs1_segments2_baggageAllowance_quantity','legs1_segments3_baggageAllowance_quantity'\n,'legs0_segments0_arrivalTo_airport_iata','legs0_segments1_arrivalTo_airport_iata','legs0_segments2_arrivalTo_airport_iata','legs0_segments3_arrivalTo_airport_iata',\n'legs1_segments0_arrivalTo_airport_iata','legs1_segments1_arrivalTo_airport_iata','legs1_segments2_arrivalTo_airport_iata','legs1_segments3_arrivalTo_airport_iata',\n'legs0_segments0_arrivalTo_airport_city_iata','legs0_segments1_arrivalTo_airport_city_iata','legs0_segments2_arrivalTo_airport_city_iata','legs0_segments3_arrivalTo_airport_city_iata',\n'legs1_segments0_arrivalTo_airport_city_iata','legs1_segments1_arrivalTo_airport_city_iata','legs1_segments2_arrivalTo_airport_city_iata','legs1_segments3_arrivalTo_airport_city_iata']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.082571Z","iopub.execute_input":"2025-08-12T15:55:21.082852Z","iopub.status.idle":"2025-08-12T15:55:21.097826Z","shell.execute_reply.started":"2025-08-12T15:55:21.082827Z","shell.execute_reply":"2025-08-12T15:55:21.089852Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"string_columns = [col_name for col_name, dtype in zip(x.columns, x.dtypes) if dtype == pl.Utf8]\nprint(string_columns)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.100366Z","iopub.execute_input":"2025-08-12T15:55:21.100622Z","iopub.status.idle":"2025-08-12T15:55:21.108674Z","shell.execute_reply.started":"2025-08-12T15:55:21.100599Z","shell.execute_reply":"2025-08-12T15:55:21.105318Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"CATEGORICAL_COLS = ['searchRoute', 'legs0_segments0_arrivalTo_airport_iata', 'legs0_segments1_arrivalTo_airport_iata', 'legs0_segments2_arrivalTo_airport_iata', 'legs0_segments3_arrivalTo_airport_iata', 'legs1_segments0_arrivalTo_airport_iata', 'legs1_segments1_arrivalTo_airport_iata', 'legs1_segments2_arrivalTo_airport_iata', 'legs1_segments3_arrivalTo_airport_iata', 'legs0_segments0_arrivalTo_airport_city_iata', 'legs0_segments1_arrivalTo_airport_city_iata', 'legs0_segments2_arrivalTo_airport_city_iata', 'legs0_segments3_arrivalTo_airport_city_iata', 'legs1_segments0_arrivalTo_airport_city_iata', 'legs1_segments1_arrivalTo_airport_city_iata', 'legs1_segments2_arrivalTo_airport_city_iata', 'legs1_segments3_arrivalTo_airport_city_iata']\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.109881Z","iopub.execute_input":"2025-08-12T15:55:21.110083Z","iopub.status.idle":"2025-08-12T15:55:21.120963Z","shell.execute_reply.started":"2025-08-12T15:55:21.110062Z","shell.execute_reply":"2025-08-12T15:55:21.116329Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"xt = dftt['Id','TotalPrice','searchRoute','corporateTariffCode','profileId'\n,'companyID','legs0_duration',\n'legs1_duration','legs0_segments0_seatsAvailable','legs0_segments1_seatsAvailable','legs0_segments2_seatsAvailable','legs0_segments3_seatsAvailable','legs1_segments0_seatsAvailable','legs1_segments1_seatsAvailable','legs1_segments2_seatsAvailable','legs1_segments3_seatsAvailable',\n'miniRules0_monetaryAmount','miniRules0_percentage','miniRules0_statusInfos',\n'miniRules1_monetaryAmount','miniRules1_percentage','miniRules1_statusInfos',\n'pricingInfo_isAccessTP',\n'pricingInfo_passengerCount','legs0_segments0_baggageAllowance_quantity','legs0_segments1_baggageAllowance_quantity','legs0_segments2_baggageAllowance_quantity','legs0_segments3_baggageAllowance_quantity','legs1_segments0_baggageAllowance_quantity','legs1_segments1_baggageAllowance_quantity','legs1_segments2_baggageAllowance_quantity','legs1_segments3_baggageAllowance_quantity'\n,'legs0_segments0_arrivalTo_airport_iata','legs0_segments1_arrivalTo_airport_iata','legs0_segments2_arrivalTo_airport_iata','legs0_segments3_arrivalTo_airport_iata',\n'legs1_segments0_arrivalTo_airport_iata','legs1_segments1_arrivalTo_airport_iata','legs1_segments2_arrivalTo_airport_iata','legs1_segments3_arrivalTo_airport_iata',\n'legs0_segments0_arrivalTo_airport_city_iata','legs0_segments1_arrivalTo_airport_city_iata','legs0_segments2_arrivalTo_airport_city_iata','legs0_segments3_arrivalTo_airport_city_iata',\n'legs1_segments0_arrivalTo_airport_city_iata','legs1_segments1_arrivalTo_airport_city_iata','legs1_segments2_arrivalTo_airport_city_iata','legs1_segments3_arrivalTo_airport_city_iata']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.123145Z","iopub.execute_input":"2025-08-12T15:55:21.123350Z","iopub.status.idle":"2025-08-12T15:55:21.136419Z","shell.execute_reply.started":"2025-08-12T15:55:21.123331Z","shell.execute_reply":"2025-08-12T15:55:21.130721Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TARGET_COL = \"selected\"\nGROUP_COL = \"ranker_id\"\nMODEL_OUT = \"flight_ranker.cbm\"\nPREDICTIONS_OUT = \"ranked_predictions.csv\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.138358Z","iopub.execute_input":"2025-08-12T15:55:21.138616Z","iopub.status.idle":"2025-08-12T15:55:21.149738Z","shell.execute_reply.started":"2025-08-12T15:55:21.138591Z","shell.execute_reply":"2025-08-12T15:55:21.143901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"exclude_cols = [TARGET_COL, GROUP_COL]\nfeature_cols = [c for c in x.columns if c not in exclude_cols]\n\n# CatBoost categorical column indices\ncat_feature_indices = [feature_cols.index(c) for c in CATEGORICAL_COLS if c in feature_cols]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.151511Z","iopub.execute_input":"2025-08-12T15:55:21.151846Z","iopub.status.idle":"2025-08-12T15:55:21.162033Z","shell.execute_reply.started":"2025-08-12T15:55:21.151825Z","shell.execute_reply":"2025-08-12T15:55:21.156747Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"feature_cols","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.163751Z","iopub.execute_input":"2025-08-12T15:55:21.163961Z","iopub.status.idle":"2025-08-12T15:55:21.177636Z","shell.execute_reply.started":"2025-08-12T15:55:21.163941Z","shell.execute_reply":"2025-08-12T15:55:21.173137Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cat_feature_indices","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.180808Z","iopub.execute_input":"2025-08-12T15:55:21.181035Z","iopub.status.idle":"2025-08-12T15:55:21.194725Z","shell.execute_reply.started":"2025-08-12T15:55:21.181015Z","shell.execute_reply":"2025-08-12T15:55:21.188922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"dft['legs0_departureAt',\n 'legs0_arrivalAt',\n 'legs0_duration',\n 'legs1_departureAt',\n 'legs1_arrivalAt',\n 'legs1_duration']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.197768Z","iopub.execute_input":"2025-08-12T15:55:21.198038Z","iopub.status.idle":"2025-08-12T15:55:21.213446Z","shell.execute_reply.started":"2025-08-12T15:55:21.198013Z","shell.execute_reply":"2025-08-12T15:55:21.208035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Trainig Model","metadata":{}},{"cell_type":"code","source":"! pip install catboost","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:21.215879Z","iopub.execute_input":"2025-08-12T15:55:21.216121Z","iopub.status.idle":"2025-08-12T15:55:36.014363Z","shell.execute_reply.started":"2025-08-12T15:55:21.216100Z","shell.execute_reply":"2025-08-12T15:55:36.008170Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom catboost import CatBoostRanker, Pool","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:36.015712Z","iopub.execute_input":"2025-08-12T15:55:36.015976Z","iopub.status.idle":"2025-08-12T15:55:38.503101Z","shell.execute_reply.started":"2025-08-12T15:55:36.015945Z","shell.execute_reply":"2025-08-12T15:55:38.497075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Check for nulls in critical columns\nprint(\"NaNs in target:\", dft[TARGET_COL].is_null().sum())\nprint(\"NaNs in group_id:\", dft[GROUP_COL].is_null().sum())\n\n# 2. Fill missing group_id with a placeholder\ndft = dft.with_columns(\n    pl.col(GROUP_COL).fill_null(-1)  # or some valid integer ID\n)\n\n# 3. Fill or drop missing target\ndft = dft.filter(pl.col(TARGET_COL).is_not_null())  # safest for classification\n\n# 4. CatBoost can handle NaNs in numeric features, but ensure proper type casting for cat_features\nfor cat_col in cat_feature_indices:\n    dft = dft.with_columns(pl.col(feature_cols[cat_col]).cast(pl.Utf8))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:38.505196Z","iopub.execute_input":"2025-08-12T15:55:38.506142Z","iopub.status.idle":"2025-08-12T15:55:39.102628Z","shell.execute_reply.started":"2025-08-12T15:55:38.506113Z","shell.execute_reply":"2025-08-12T15:55:39.096909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in feature_cols:\n    if dft[col].dtype in [pl.Int64, pl.Int32, pl.UInt32, pl.UInt64, pl.Float64, pl.Float32]:\n        dft = dft.with_columns(pl.col(col).cast(pl.Float64))\n    else:\n        # keep categorical columns as string\n        dft = dft.with_columns(pl.col(col).cast(pl.Utf8))\n\n# Fill nulls in numeric cols with np.nan explicitly\ndft = dft.fill_null(np.nan)\n\n# Make sure group_id and label have no NaNs\ndft = dft.filter(\n    (pl.col(GROUP_COL).is_not_null()) &\n    (pl.col(TARGET_COL).is_not_null())\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:39.104267Z","iopub.execute_input":"2025-08-12T15:55:39.104505Z","iopub.status.idle":"2025-08-12T15:55:41.580071Z","shell.execute_reply.started":"2025-08-12T15:55:39.104481Z","shell.execute_reply":"2025-08-12T15:55:41.573428Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in feature_cols:\n    if dft[col].dtype in [pl.Int8, pl.Int16, pl.Int32, pl.Int64,\n                          pl.UInt8, pl.UInt16, pl.UInt32, pl.UInt64,\n                          pl.Float32, pl.Float64]:\n        dft = dft.with_columns(pl.col(col).cast(pl.Float64))\n\n# Replace nulls in numeric with np.nan\ndft = dft.fill_null(np.nan)\n\n# Drop rows with missing label or group_id\ndft = dft.filter(\n    pl.col(TARGET_COL).is_not_null() & pl.col(GROUP_COL).is_not_null()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:41.581944Z","iopub.execute_input":"2025-08-12T15:55:41.582179Z","iopub.status.idle":"2025-08-12T15:55:42.775789Z","shell.execute_reply.started":"2025-08-12T15:55:41.582156Z","shell.execute_reply":"2025-08-12T15:55:42.770212Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport polars as pl\nfrom catboost import Pool\n\n# 1. Ensure numeric columns are floats → np.nan\nfor col in feature_cols:\n    if dft[col].dtype in [pl.Int8, pl.Int16, pl.Int32, pl.Int64,\n                          pl.UInt8, pl.UInt16, pl.UInt32, pl.UInt64,\n                          pl.Float32, pl.Float64]:\n        dft = dft.with_columns(pl.col(col).cast(pl.Float64))\n\n# 2. Handle categorical columns\nfor idx in cat_feature_indices:\n    col = feature_cols[idx]\n    dft = dft.with_columns(pl.col(col).cast(pl.Utf8))\n    dft = dft.with_columns(pl.col(col).fill_null(\"__MISSING__\"))\n\n# 3. Fill remaining nulls in numeric with np.nan\ndft = dft.fill_null(np.nan)\n\n# 4. Drop rows where label or group_id is null\ndft = dft.filter(\n    pl.col(TARGET_COL).is_not_null() & pl.col(GROUP_COL).is_not_null()\n)\n\n# 5. Create Pool\ntrain_pool = Pool(\n    data=dft.select(feature_cols).to_numpy(),\n    label=dft[TARGET_COL].to_numpy(),\n    group_id=dft[GROUP_COL].to_numpy(),\n    cat_features=cat_feature_indices\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:55:42.778790Z","iopub.execute_input":"2025-08-12T15:55:42.779046Z","iopub.status.idle":"2025-08-12T15:59:38.833574Z","shell.execute_reply.started":"2025-08-12T15:55:42.779019Z","shell.execute_reply":"2025-08-12T15:59:38.828110Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Check for nulls in critical columns\nprint(\"NaNs in group_id:\", dftt[GROUP_COL].is_null().sum())\n\n# 2. Fill missing group_id with a placeholder\ndftt = dftt.with_columns(\n    pl.col(GROUP_COL).fill_null(-1)  # or some valid integer ID\n)\n\n\n# 4. CatBoost can handle NaNs in numeric features, but ensure proper type casting for cat_features\nfor cat_col in cat_feature_indices:\n    dftt = dftt.with_columns(pl.col(feature_cols[cat_col]).cast(pl.Utf8))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:59:38.836479Z","iopub.execute_input":"2025-08-12T15:59:38.836780Z","iopub.status.idle":"2025-08-12T15:59:39.190330Z","shell.execute_reply.started":"2025-08-12T15:59:38.836751Z","shell.execute_reply":"2025-08-12T15:59:39.184580Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in feature_cols:\n    if dftt[col].dtype in [pl.Int64, pl.Int32, pl.UInt32, pl.UInt64, pl.Float64, pl.Float32]:\n        dftt = dftt.with_columns(pl.col(col).cast(pl.Float64))\n    else:\n        # keep categorical columns as string\n        dftt = dftt.with_columns(pl.col(col).cast(pl.Utf8))\n\n# Fill nulls in numeric cols with np.nan explicitly\ndftt = dftt.fill_null(np.nan)\n\n# Make sure group_id and label have no NaNs\ndftt = dftt.filter(\n    (pl.col(GROUP_COL).is_not_null()) )\n   \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:59:39.192895Z","iopub.execute_input":"2025-08-12T15:59:39.193149Z","iopub.status.idle":"2025-08-12T15:59:40.348715Z","shell.execute_reply.started":"2025-08-12T15:59:39.193123Z","shell.execute_reply":"2025-08-12T15:59:40.342965Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in feature_cols:\n    if dftt[col].dtype in [pl.Int8, pl.Int16, pl.Int32, pl.Int64,\n                          pl.UInt8, pl.UInt16, pl.UInt32, pl.UInt64,\n                          pl.Float32, pl.Float64]:\n        dftt = dftt.with_columns(pl.col(col).cast(pl.Float64))\n\n# Replace nulls in numeric with np.nan\ndftt = dftt.fill_null(np.nan)\n\n# Drop rows with missing label or group_id\ndftt = dftt.filter(\n    pl.col(GROUP_COL).is_not_null()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:59:40.350269Z","iopub.execute_input":"2025-08-12T15:59:40.350507Z","iopub.status.idle":"2025-08-12T15:59:40.908688Z","shell.execute_reply.started":"2025-08-12T15:59:40.350485Z","shell.execute_reply":"2025-08-12T15:59:40.903195Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Ensure numeric columns are floats → np.nan\nfor col in feature_cols:\n    if dftt[col].dtype in [pl.Int8, pl.Int16, pl.Int32, pl.Int64,\n                          pl.UInt8, pl.UInt16, pl.UInt32, pl.UInt64,\n                          pl.Float32, pl.Float64]:\n        dftt = dftt.with_columns(pl.col(col).cast(pl.Float64))\n\n# 2. Handle categorical columns\nfor idx in cat_feature_indices:\n    col = feature_cols[idx]\n    dftt = dftt.with_columns(pl.col(col).cast(pl.Utf8))\n    dftt = dftt.with_columns(pl.col(col).fill_null(\"__MISSING__\"))\n\n# 3. Fill remaining nulls in numeric with np.nan\ndftt = dftt.fill_null(np.nan)\n\n# 4. Drop rows where label or group_id is null\ndftt = dftt.filter(\n     pl.col(GROUP_COL).is_not_null()\n)\ntest_pool = Pool(\n    data=dftt.select(feature_cols).to_numpy(),\n    group_id=dftt[GROUP_COL].to_numpy(),\n    cat_features=cat_feature_indices\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T15:59:40.909613Z","iopub.execute_input":"2025-08-12T15:59:40.909837Z","iopub.status.idle":"2025-08-12T16:01:05.281050Z","shell.execute_reply.started":"2025-08-12T15:59:40.909813Z","shell.execute_reply":"2025-08-12T16:01:05.275550Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Training CatBoostRanker...\")\nmodel = CatBoostRanker(\n    iterations=300,\n    learning_rate=0.05,\n    depth=8,\n    loss_function=\"YetiRank\",\n    eval_metric=\"NDCG\",\n    random_seed=42,\n    verbose=100\n)\nmodel.fit(train_pool)\nmodel.save_model('/kaggle/working/MODEL_OUT')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T16:01:05.282293Z","iopub.execute_input":"2025-08-12T16:01:05.282502Z","iopub.status.idle":"2025-08-12T17:43:00.619162Z","shell.execute_reply.started":"2025-08-12T16:01:05.282480Z","shell.execute_reply":"2025-08-12T17:43:00.613679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Predict & rank\nprint(\"Predicting...\")\nscores = model.predict(test_pool)\ntest_df = dftt.with_columns(pl.Series(\"score\", scores))\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T17:53:22.178608Z","iopub.execute_input":"2025-08-12T17:53:22.178954Z","iopub.status.idle":"2025-08-12T17:53:22.576234Z","shell.execute_reply.started":"2025-08-12T17:53:22.178927Z","shell.execute_reply":"2025-08-12T17:53:22.570893Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Ranking per group...\")\ntest_df = test_df.with_columns(\n    pl.col(\"score\").rank(\"dense\", descending=True).over(GROUP_COL).alias(\"rank\")\n)\n\n# Save\ntest_df.sort(['Id',GROUP_COL, \"rank\"]).write_csv('/kaggle/working/PREDICTIONS_OUT')\nprint(f\"Predictions saved to {PREDICTIONS_OUT}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-12T17:53:37.072874Z","iopub.execute_input":"2025-08-12T17:53:37.073194Z","iopub.status.idle":"2025-08-12T17:54:40.215189Z","shell.execute_reply.started":"2025-08-12T17:53:37.073167Z","shell.execute_reply":"2025-08-12T17:54:40.209135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import  pandas as pd\nz = pd.read_csv('ranked_predictions.csv')\nz.shape\nz.head(20)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}