{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":38760,"databundleVersionId":4493939}],"dockerImageVersionId":30301,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<h1 style=\"font-family:verdana;\"> <center>🛍 OTTO – Multi-Objective Recommender System - Baseline</center> </h1>\n\n***","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"markdown","source":"### <span style=\"font-family:verdana; word-spacing:1.5px;\">  Task overview\n    \nЦель этого соревнования — предсказать клики в e-commerce, добавления товаров в корзину и заказы. Вам нужно построить рекомендательную систему с несколькими целями, основанную на предыдущих событиях в пользовательской сессии.\n\nСовременные рекомендательные системы состоят из различных моделей с разными подходами — от простой матричной факторизации до глубоких нейронных сетей типа transformer. Однако не существует одной модели, которая могла бы одновременно оптимизировать несколько целей. В этом соревновании вам нужно построить единое решение для предсказания кликов, добавлений в корзину и конверсий на основе предыдущих событий в той же сессии.","metadata":{}},{"cell_type":"markdown","source":"### <span style=\"font-family:verdana; word-spacing:1.5px;\">   Imports / setup 🚚","metadata":{}},{"cell_type":"code","source":"### Imports ###\n\nimport pandas as pd\nfrom pathlib import Path\nimport os\nimport random\nimport numpy as np\nimport json\nfrom datetime import timedelta\nfrom collections import Counter\nfrom tqdm.notebook import tqdm\nfrom heapq import nlargest\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nsns.set_theme()\n\nimport warnings\nwarnings.filterwarnings('ignore')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T18:31:31.332657Z","iopub.execute_input":"2026-04-04T18:31:31.333350Z","iopub.status.idle":"2026-04-04T18:31:32.237454Z","shell.execute_reply.started":"2026-04-04T18:31:31.333208Z","shell.execute_reply":"2026-04-04T18:31:32.235345Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"### Paths ###\n\nDATA_PATH = Path('../input/otto-recommender-system')\nTRAIN_PATH = DATA_PATH/'train.jsonl'\nTEST_PATH = DATA_PATH/'test.jsonl'\nSAMPLE_SUB_PATH = Path('../input/otto-recommender-system/sample_submission.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T18:31:32.240167Z","iopub.execute_input":"2026-04-04T18:31:32.240691Z","iopub.status.idle":"2026-04-04T18:31:32.248707Z","shell.execute_reply.started":"2026-04-04T18:31:32.240655Z","shell.execute_reply":"2026-04-04T18:31:32.246032Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <span style=\"font-family:verdana; word-spacing:1.5px;\">   Baseline 📈\n    \n\nТестовые данные содержат укороченные сессии, аналогичные тем, что есть в обучающих данных. Задача состоит в том, чтобы предсказать следующий `aid`, по которому будет клик после обрезки сессии, а также оставшиеся `aid`, которые будут добавлены в корзину и заказаны; для каждого типа события можно предсказать до 20 значений.\n\nОценка решений производится по **Recall** для каждого типа действия, а затем три значения recall усредняются с весами:  \n`{'clicks': 0.10, 'carts': 0.30, 'orders': 0.60}`.  \nОчень важно хорошо предсказывать **orders**, так как они имеют наибольший вес :)\n\nДля каждой сессии в тестовых данных нужно предсказать значения `aid` для каждого типа события, которые происходят **после последней временной метки `ts`** в тестовой сессии. Иными словами, тестовые данные содержат сессии, обрезанные по времени, а вы должны предсказать, что произойдет после точки обрезки.\n\nДля **clicks** существует только одно истинное значение для каждой сессии — это следующий `aid`, по которому кликнули в течение сессии  \n(хотя вы все равно можете предсказать до 20 значений `aid`).  \nИстинные значения для **carts** и **orders** содержат все значения `aid`, которые были соответственно добавлены в корзину и заказаны в этой сессии.\n\nКаждая комбинация **session + type** должна находиться в отдельной строке `session_type` в сабмите  \n(по 3 строки на каждую сессию), а предсказания должны быть разделены пробелами. Это можно увидеть в `sample_test_df` ниже.","metadata":{}},{"cell_type":"code","source":"# Идея\n# Этот baseline:\n# 1. Берёт товары, уже встречавшиеся в тестовой сессии.\n# 2. Сортирует их по частоте внутри этой сессии.\n# 3. Оставляет top-20.\n# 4. Если товаров меньше 20, дополняет глобально популярными товарами из train.\n# 5. Делает предсказания отдельно для clicks, carts и order.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T15:28:30.157239Z","iopub.execute_input":"2026-04-04T15:28:30.157672Z","iopub.status.idle":"2026-04-04T15:28:30.164235Z","shell.execute_reply.started":"2026-04-04T15:28:30.157638Z","shell.execute_reply":"2026-04-04T15:28:30.162553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with open(TEST_PATH, 'r') as f:\n    print(f\"We have {len(f.readlines()):,} lines in the test data\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T15:28:33.191059Z","iopub.execute_input":"2026-04-04T15:28:33.191548Z","iopub.status.idle":"2026-04-04T15:28:40.187218Z","shell.execute_reply.started":"2026-04-04T15:28:33.191510Z","shell.execute_reply":"2026-04-04T15:28:40.185806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Load in a sample to a pandas df\n\nsample_size = 150\n\nchunks = pd.read_json(TEST_PATH, lines=True, chunksize = sample_size)\n\nfor c in chunks:\n    sample_test_df = c\n    break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T15:28:40.189119Z","iopub.execute_input":"2026-04-04T15:28:40.189465Z","iopub.status.idle":"2026-04-04T15:28:40.209038Z","shell.execute_reply.started":"2026-04-04T15:28:40.189435Z","shell.execute_reply":"2026-04-04T15:28:40.207788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_test_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T15:28:45.175926Z","iopub.execute_input":"2026-04-04T15:28:45.176415Z","iopub.status.idle":"2026-04-04T15:28:45.221592Z","shell.execute_reply.started":"2026-04-04T15:28:45.176379Z","shell.execute_reply":"2026-04-04T15:28:45.219882Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Ниже показан пример сабмита. Для каждой сессии в тестовом наборе есть предсказание (`labels`). Оно предсказывает, с какими товарами будет следующее взаимодействие в этой сессии. Для каждой сессии есть три типа действий (`clicks`, `carts`, `orders`), и предсказания делаются для всех трех типов.","metadata":{}},{"cell_type":"code","source":"sample_submission = pd.read_csv(SAMPLE_SUB_PATH)\nsample_submission.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T15:28:50.419147Z","iopub.execute_input":"2026-04-04T15:28:50.419584Z","iopub.status.idle":"2026-04-04T15:28:56.712570Z","shell.execute_reply.started":"2026-04-04T15:28:50.419548Z","shell.execute_reply":"2026-04-04T15:28:56.711248Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Давайте найдем самый популярный товар для каждого типа действия.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nfrom collections import defaultdict\nfrom tqdm import tqdm\n\nsample_size = 200_000\nn_chunks = 4\nc_chunks = 0\n\nchunks = pd.read_json(TRAIN_PATH, lines=True, chunksize = sample_size)\n\ntransitions = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))\ntype_transitions = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))\n\nprint(\"Обработка данных...\")\nfor c_idx, chunk in enumerate(chunks):\n        if c_idx >= n_chunks:\n            break\n        for _, row in tqdm(chunk.iterrows(), total=len(chunk), desc=f\"Chunk {c_idx+1}/{n_chunks}\"):\n            events = row.get('events')\n            \n            if not isinstance(events, list) or len(events) < 2:\n                continue\n            \n            for i in range(len(events) - 1):\n                curr_evt = events[i]\n                next_evt = events[i + 1]\n                \n                curr_type = curr_evt.get('type')\n                curr_aid = curr_evt.get('aid')\n                next_type = next_evt.get('type')\n                next_aid = next_evt.get('aid')\n                \n                # Пропускаем некорректные записи\n                if not all([curr_type, curr_aid, next_type, next_aid]):\n                    continue\n                \n                # Формируем ключ текущего состояния и следующего состояния\n                current_key = (curr_type, curr_aid)\n                next_key = (next_type, next_aid)\n                \n                # Инкрементируем счетчик перехода\n                transitions[(curr_type, curr_aid)][next_type][next_aid] += 1\n                type_transitions[curr_type][next_type][next_aid] += 1\n\nprint(\"Шаг 2: Предвычисление Топ-20 списков...\")\nTARGET_TYPES = [\"clicks\", \"carts\", \"orders\"]\n\ntop20_next_item = {}\ntop20_type_fallback = {}\n\ndef compute_top_n(counter_dict, n):\n    \"\"\"Быстрая сортировка и взятие топ-N ключей\"\"\"\n    if not counter_dict: return []\n    return [k for k, _ in sorted(counter_dict.items(), key=lambda x: x[1], reverse=True)[:n]]\n\n\nfor (ct, ca), next_types_dict in transitions.items():\n    entry = {}\n    for t_type in TARGET_TYPES:\n        entry[t_type] = compute_top_n(next_types_dict.get(t_type, {}), 20)\n    top20_next_item[(ct, ca)] = entry\n\n\nfor ct, next_types_dict in type_transitions.items():\n    entry = {}\n    for t_type in TARGET_TYPES:\n        entry[t_type] = compute_top_n(next_types_dict.get(t_type, {}), 20)\n    top20_type_fallback[ct] = entry","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T18:46:57.244276Z","iopub.execute_input":"2026-04-04T18:46:57.245041Z","iopub.status.idle":"2026-04-04T18:52:31.071112Z","shell.execute_reply.started":"2026-04-04T18:46:57.245005Z","shell.execute_reply":"2026-04-04T18:52:31.069451Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Есть некоторое пересечение, но наборы товаров различаются для разных типов действий!\n\nЭтот бейзлайн использует тот факт, что люди часто взаимодействуют с товарами, с которыми они уже взаимодействовали ранее. Предсказание будет состоять из 20 самых частых товаров в данной сессии. Если в сессии меньше 20 товаров, предсказание будет дополнено самыми частыми товарами из обучающих данных, найденными выше.","metadata":{}},{"cell_type":"code","source":"test_data = pd.read_json(TEST_PATH, lines=True, chunksize=200_000)\n\npreds = []\n\nfor c_idx, chunk in enumerate(test_data):\n    for _, row in tqdm(chunk.iterrows(), total=len(chunk)):\n         last_key = (row.get('events')[-1][\"type\"], row.get('events')[-1][\"aid\"])\n         for t_type in TARGET_TYPES:\n             if last_key in top20_next_item:\n                if len(top20_next_item[last_key][t_type]) > 0:\n                    next_aids = top20_next_item[last_key][t_type]\n                else:\n                    next_aids = top20_type_fallback[last_key[0]][t_type]\n             else:\n                next_aids = top20_type_fallback[last_key[0]][t_type]\n             preds.extend([(str(row['session']) + \"_\" + t_type, i, next_aids[i]) for i in range(len(next_aids))])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T18:52:31.073461Z","iopub.execute_input":"2026-04-04T18:52:31.074121Z","iopub.status.idle":"2026-04-04T18:59:39.481734Z","shell.execute_reply.started":"2026-04-04T18:52:31.074084Z","shell.execute_reply":"2026-04-04T18:59:39.480572Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds_df = pd.DataFrame(preds, columns = [\"session_type\", \"order\", \"aid\"])\npreds_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T18:59:39.483393Z","iopub.execute_input":"2026-04-04T18:59:39.483856Z","iopub.status.idle":"2026-04-04T19:00:36.645234Z","shell.execute_reply.started":"2026-04-04T18:59:39.483805Z","shell.execute_reply":"2026-04-04T19:00:36.643786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds_df['aid'] = preds_df['aid'].astype(str)\n\n# 2. Сортируем по session_type и order, чтобы рекомендации шли в правильном приоритете\npreds_df_sorted = preds_df.sort_values(['session_type', 'order'])\n\n# 3. Группируем по session_type и склеиваем aid через пробел\nsubmission_df = (preds_df_sorted.groupby('session_type')['aid']\n                           .agg(' '.join)\n                           .reset_index()\n                           .rename(columns={'aid': 'labels'}))\n\n# 4. Сохраняем в CSV без индекса (стандарт для Kaggle/OTTO)\nsubmission_df.to_csv('submission.csv', index=False)\n\n# Быстрая проверка результата\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T19:00:36.647992Z","iopub.execute_input":"2026-04-04T19:00:36.648511Z","iopub.status.idle":"2026-04-04T19:03:46.526391Z","shell.execute_reply.started":"2026-04-04T19:00:36.648476Z","shell.execute_reply":"2026-04-04T19:03:46.525011Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-04T19:03:46.528026Z","iopub.execute_input":"2026-04-04T19:03:46.528345Z","iopub.status.idle":"2026-04-04T19:03:46.542675Z","shell.execute_reply.started":"2026-04-04T19:03:46.528315Z","shell.execute_reply":"2026-04-04T19:03:46.541064Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}