{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":38760,"databundleVersionId":4493939,"isSourceIdPinned":false}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"515306b3-567b-4ee1-adba-6285fb855f53","cell_type":"code","source":"!pip install implicit --prefer-binary","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:01:55.867288Z","iopub.execute_input":"2026-03-04T18:01:55.867986Z","iopub.status.idle":"2026-03-04T18:08:37.816625Z","shell.execute_reply.started":"2026-03-04T18:01:55.867954Z","shell.execute_reply":"2026-03-04T18:08:37.815877Z"}},"outputs":[],"execution_count":null},{"id":"ede87a0b-024f-4717-80a4-302da78d7204","cell_type":"code","source":"import polars as pl\nimport numpy as np\nfrom scipy.sparse import csr_matrix\nfrom tqdm import tqdm\nimport implicit\n\nTYPE_MAP = {'clicks': 0, 'carts': 1, 'orders': 2}\nTYPE_WEIGHT = {0: 1, 1: 6, 2: 3}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:08:58.708284Z","iopub.execute_input":"2026-03-04T18:08:58.708893Z","iopub.status.idle":"2026-03-04T18:08:59.875932Z","shell.execute_reply.started":"2026-03-04T18:08:58.708860Z","shell.execute_reply":"2026-03-04T18:08:59.875347Z"}},"outputs":[],"execution_count":null},{"id":"a4b360b0-cd68-4f52-a706-07f210ef5c03","cell_type":"code","source":"def load_jsonl(path):\n    df = (\n        pl.read_ndjson(path)\n        .explode('events')\n        .with_columns([\n            pl.col('events').struct.field('aid').alias('aid'),\n            pl.col('events').struct.field('ts').alias('ts'),\n            pl.col('events').struct.field('type').alias('type'),\n        ])\n        .drop('events')\n        .with_columns(pl.col('type').replace(TYPE_MAP).cast(pl.Int8))\n    )\n    return df\n\ntrain = load_jsonl('/kaggle/input/competitions/otto-recommender-system/test.jsonl')\ntest  = load_jsonl('/kaggle/input/competitions/otto-recommender-system/test.jsonl')\nprint(train.shape, test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:09:25.465517Z","iopub.execute_input":"2026-03-04T18:09:25.466285Z","iopub.status.idle":"2026-03-04T18:09:40.869452Z","shell.execute_reply.started":"2026-03-04T18:09:25.466244Z","shell.execute_reply":"2026-03-04T18:09:40.868801Z"}},"outputs":[],"execution_count":null},{"id":"e655921a-fa7a-4063-8b41-ddb61de61df7","cell_type":"code","source":"all_data = pl.concat([train, test])\n\nsession_ids = all_data['session'].unique().sort().to_list()\naid_ids     = all_data['aid'].unique().sort().to_list()\n\nsession2idx = {s: i for i, s in enumerate(session_ids)}\naid2idx     = {a: i for i, a in enumerate(aid_ids)}\nidx2aid     = {i: a for a, i in aid2idx.items()}\n\nprint(f'sessions: {len(session_ids):,}  aids: {len(aid_ids):,}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:09:47.043355Z","iopub.execute_input":"2026-03-04T18:09:47.043999Z","iopub.status.idle":"2026-03-04T18:09:48.172545Z","shell.execute_reply.started":"2026-03-04T18:09:47.043960Z","shell.execute_reply":"2026-03-04T18:09:48.171803Z"}},"outputs":[],"execution_count":null},{"id":"ebffc81f-0d98-4b7a-8990-c7f13ae910c7","cell_type":"code","source":"agg = (\n    all_data\n    .with_columns(pl.col('type').replace(TYPE_WEIGHT).cast(pl.Int32).alias('w'))\n    .group_by(['session', 'aid'])\n    .agg(pl.col('w').sum())\n)\n\nrow  = agg['session'].map_elements(lambda s: session2idx[s], return_dtype=pl.Int32).to_numpy()\ncol  = agg['aid'].map_elements(lambda a: aid2idx[a],         return_dtype=pl.Int32).to_numpy()\ndata = agg['w'].to_numpy().astype(np.float32)\n\ninteraction_matrix = csr_matrix((data, (row, col)), shape=(len(session_ids), len(aid_ids)))\nprint(interaction_matrix.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:12:24.545288Z","iopub.execute_input":"2026-03-04T18:12:24.545660Z","iopub.status.idle":"2026-03-04T18:12:31.496397Z","shell.execute_reply.started":"2026-03-04T18:12:24.545634Z","shell.execute_reply":"2026-03-04T18:12:31.495596Z"}},"outputs":[],"execution_count":null},{"id":"41851ed4-a759-4f0d-9ab6-127dade31791","cell_type":"code","source":"model = implicit.als.AlternatingLeastSquares(\n    factors=64,\n    iterations=5,\n    regularization=0.05,\n    use_gpu=True,\n    random_state=777,\n)\nmodel.fit(interaction_matrix.T)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:12:38.967325Z","iopub.execute_input":"2026-03-04T18:12:38.968206Z","iopub.status.idle":"2026-03-04T18:12:40.617445Z","shell.execute_reply.started":"2026-03-04T18:12:38.968161Z","shell.execute_reply":"2026-03-04T18:12:40.616650Z"}},"outputs":[],"execution_count":null},{"id":"08d62d2a-58ae-43e9-8352-2b4a49d3382a","cell_type":"code","source":"test_sessions = (\n    test\n    .sort(['session', 'ts'])\n    .group_by('session')\n    .agg([pl.col('aid').alias('aids'), pl.col('type').alias('types')])\n    .sort('session')\n)\n\nbest_sold = (\n    train.filter(pl.col('type') == 2)\n    .group_by('aid').agg(pl.len().alias('cnt'))\n    .sort('cnt', descending=True)\n    .head(20)['aid'].to_list()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:12:49.046761Z","iopub.execute_input":"2026-03-04T18:12:49.047364Z","iopub.status.idle":"2026-03-04T18:12:49.232201Z","shell.execute_reply.started":"2026-03-04T18:12:49.047336Z","shell.execute_reply":"2026-03-04T18:12:49.231466Z"}},"outputs":[],"execution_count":null},{"id":"e6c26a20-2489-48f9-9fb3-a001b95c2094","cell_type":"code","source":"all_test_aids = set()\nfor row in tqdm(test_sessions.iter_rows(named=True), total=len(test_sessions)):\n    all_test_aids.update(row['aids'][:3])\n\nvalid_ids = [aid for aid in tqdm(all_test_aids) if aid in aid2idx]\nindices = np.array([aid2idx[a] for a in valid_ids])\n\nsimilar_ids_batch, _ = model.similar_items(indices, N=21)\n\nprecomputed = {}\nfor aid, sims in tqdm(zip(valid_ids, similar_ids_batch), total=len(valid_ids)):\n    result = []\n    for sid in sims:\n        sid = int(sid)\n        if sid in idx2aid and idx2aid[sid] != aid:\n            result.append(idx2aid[sid])\n    precomputed[aid] = result","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:20:01.993021Z","iopub.execute_input":"2026-03-04T18:20:01.993751Z","iopub.status.idle":"2026-03-04T18:21:06.813495Z","shell.execute_reply.started":"2026-03-04T18:20:01.993722Z","shell.execute_reply":"2026-03-04T18:21:06.812745Z"}},"outputs":[],"execution_count":null},{"id":"35a0fd2a-cc43-4f8e-bb59-f15b9f71b078","cell_type":"code","source":"def get_recommendations(aids, types, n=20):\n    seen = list(dict.fromkeys(reversed(aids)))\n\n    if len(aids) >= 20:\n        w_sum = {}\n        weights_log = np.logspace(0.1, 1, len(aids), base=2, endpoint=True) - 1\n        for aid, w, t in zip(aids, weights_log, types):\n            w_sum[aid] = w_sum.get(aid, 0) + w * TYPE_WEIGHT[t]\n        return sorted(w_sum, key=lambda x: -w_sum[x])[:n]\n\n    candidates = list(seen)\n    seen_set = set(seen)\n\n    for aid in seen[:3]:\n        for a in precomputed.get(aid, []):\n            if a not in seen_set:\n                candidates.append(a)\n                seen_set.add(a)\n            if len(candidates) >= n:\n                return candidates[:n]\n\n    for a in best_sold:\n        if a not in seen_set:\n            candidates.append(a)\n            seen_set.add(a)\n        if len(candidates) >= n:\n            break\n\n    return candidates[:n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:21:51.841194Z","iopub.execute_input":"2026-03-04T18:21:51.841519Z","iopub.status.idle":"2026-03-04T18:21:51.848325Z","shell.execute_reply.started":"2026-03-04T18:21:51.841491Z","shell.execute_reply":"2026-03-04T18:21:51.847637Z"}},"outputs":[],"execution_count":null},{"id":"b14f0cf4-b9b1-4d55-afe3-100cf37a6deb","cell_type":"code","source":"session_types, labels = [], []\n\nfor row in tqdm(test_sessions.iter_rows(named=True), total=len(test_sessions)):\n    session = row['session']\n    recs = get_recommendations(row['aids'], row['types'], n=20)\n    label_str = ' '.join(map(str, recs))\n    for st in ['clicks', 'carts', 'orders']:\n        session_types.append(f'{session}_{st}')\n        labels.append(label_str)\n\nsubmission = pl.DataFrame({'session_type': session_types, 'labels': labels})\nsubmission.write_csv('submission.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-04T18:21:53.554108Z","iopub.execute_input":"2026-03-04T18:21:53.554417Z","iopub.status.idle":"2026-03-04T18:22:21.839129Z","shell.execute_reply.started":"2026-03-04T18:21:53.554377Z","shell.execute_reply":"2026-03-04T18:22:21.838338Z"}},"outputs":[],"execution_count":null}]}