{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":31254,"databundleVersionId":3103714,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install scikit-surprise","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-25T09:54:32.599447Z","iopub.execute_input":"2025-06-25T09:54:32.600426Z","iopub.status.idle":"2025-06-25T09:54:37.453585Z","shell.execute_reply.started":"2025-06-25T09:54:32.600397Z","shell.execute_reply":"2025-06-25T09:54:37.452469Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.preprocessing import LabelEncoder\nfrom scipy.sparse import csr_matrix\nfrom sklearn.metrics.pairwise import cosine_similarity\nfrom surprise import Dataset, Reader, SVD\nfrom surprise.model_selection import train_test_split\nfrom collections import defaultdict\n\n\n#Helper: Safe memory optimizer\ndef optimize(df):\n    for col in df.columns:\n        col_type = df[col].dtypes\n\n        if pd.api.types.is_object_dtype(col_type):\n            if df[col].nunique() / len(df) < 0.5:\n                df[col] = df[col].astype(\"category\")\n\n        elif pd.api.types.is_integer_dtype(col_type):\n            df[col] = pd.to_numeric(df[col], downcast=\"integer\")\n\n        elif pd.api.types.is_float_dtype(col_type):\n            df[col] = pd.to_numeric(df[col], downcast=\"float\")\n    \n    return df\n\n#Load datasets\ntransactions = pd.read_csv(\n    \"/kaggle/input/h-and-m-personalized-fashion-recommendations/transactions_train.csv\",\n    parse_dates=[\"t_dat\"],\n    dtype={\n        \"customer_id\": \"category\",\n        \"article_id\": \"int32\",\n        \"price\": \"float32\",\n        \"sales_channel_id\": \"int8\"\n    }\n)\n\ncustomers = pd.read_csv(\"/kaggle/input/h-and-m-personalized-fashion-recommendations/customers.csv\")\narticles = pd.read_csv(\"/kaggle/input/h-and-m-personalized-fashion-recommendations/articles.csv\")\n\n#Clean and optimize\ntransactions = optimize(transactions).dropna().drop_duplicates()\ncustomers = optimize(customers).dropna().drop_duplicates()\narticles = optimize(articles).dropna().drop_duplicates()\n\n#Merge datasets\nmerged = transactions.merge(articles, on=\"article_id\", how=\"inner\")\nmerged = merged.merge(customers, on=\"customer_id\", how=\"inner\")\n\n#Feature Engineering\n\n#1. Most popular articles\npopular_articles = transactions[\"article_id\"].value_counts().head(10)\nprint(\"Top 10 most bought articles:\\n\", popular_articles)\n\n#2. Purchase frequency per customer\npurchase_freq = transactions.groupby(\"customer_id\")[\"article_id\"].count()\nprint(\"\\nCustomer purchase frequency stats:\\n\", purchase_freq.describe())\n\n#3. Product age estimate\narticles[\"product_code\"] = articles[\"product_code\"].astype(str)\narticles[\"product_age\"] = 2025 - articles[\"prod_name\"].str.extract(r\"(\\d{4})\").fillna(2020).astype(int)\n\n#4. User-Item Interaction Matrix\nuser_encoder = LabelEncoder()\nitem_encoder = LabelEncoder()\n\ntransactions[\"user\"] = user_encoder.fit_transform(transactions[\"customer_id\"])\ntransactions[\"item\"] = item_encoder.fit_transform(transactions[\"article_id\"])\n\ninteraction_matrix = csr_matrix(\n    (np.ones(len(transactions), dtype=np.float32),\n     (transactions[\"user\"], transactions[\"item\"]))\n)\n\nprint(f\"\\nInteraction matrix shape: {interaction_matrix.shape}\")\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-25T09:54:37.455523Z","iopub.execute_input":"2025-06-25T09:54:37.455854Z","iopub.status.idle":"2025-06-25T10:00:11.946270Z","shell.execute_reply.started":"2025-06-25T09:54:37.455820Z","shell.execute_reply":"2025-06-25T10:00:11.945316Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Filter active users and popular items\nmin_items_per_user = 5\nmin_users_per_item = 5\n\nuser_counts = transactions[\"user\"].value_counts()\nitem_counts = transactions[\"item\"].value_counts()\n\nfiltered = transactions[\n    transactions[\"user\"].isin(user_counts[user_counts >= min_items_per_user].index) &\n    transactions[\"item\"].isin(item_counts[item_counts >= min_users_per_item].index)\n]\n\n#Optional: further subsample for performance\ntop_users = filtered[\"user\"].value_counts().head(10000).index\nfiltered = filtered[filtered[\"user\"].isin(top_users)]\n\n#Rebuild sparse interaction matrix\ninteraction_matrix = csr_matrix(\n    (np.ones(len(filtered), dtype=np.float32),\n     (filtered[\"user\"], filtered[\"item\"]))\n)\n\n#Efficient cosine similarity (user-based)\nuser_sim = cosine_similarity(interaction_matrix, dense_output=False)\n\n#Recommendation Function\ndef recommend_cf(user_id, k=12):\n    try:\n        user_idx = user_encoder.transform([user_id])[0]\n        if user_idx >= user_sim.shape[0]:\n            return []  # user out of range\n        sim_scores = user_sim[user_idx].toarray().flatten()\n\n        # Top similar users (exclude self)\n        top_similar = sim_scores.argsort()[::-1][1:6]\n\n        # Sum item interactions of top similar users\n        top_user_items = interaction_matrix[top_similar].sum(axis=0).A1\n        recommended_idx = top_user_items.argsort()[::-1][:k]\n        return item_encoder.inverse_transform(recommended_idx)\n    except:\n        return []\n\n#Test\ntest_user = filtered[\"customer_id\"].iloc[0]\nrecommendations = recommend_cf(test_user)\nprint(f\"Recommendations for user {test_user}:\\n{recommendations}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-25T10:00:11.947257Z","iopub.execute_input":"2025-06-25T10:00:11.947531Z","iopub.status.idle":"2025-06-25T10:00:27.195152Z","shell.execute_reply.started":"2025-06-25T10:00:11.947510Z","shell.execute_reply":"2025-06-25T10:00:27.194325Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Prepare data\nfiltered_df = transactions.groupby(\"customer_id\").filter(lambda x: len(x) >= 5)\n\n# Treat each purchase as an interaction score of 1\ndf_surprise = filtered_df[[\"customer_id\", \"article_id\"]].copy()\ndf_surprise[\"interaction\"] = 1\n\n#Further filter: top 5000 most active users\ntop_users = df_surprise[\"customer_id\"].value_counts().head(5000).index\ndf_surprise = df_surprise[df_surprise[\"customer_id\"].isin(top_users)]\n\n#Filter low-frequency articles\ntop_articles = df_surprise[\"article_id\"].value_counts().head(5000).index\ndf_surprise = df_surprise[df_surprise[\"article_id\"].isin(top_articles)]\n\n#Load Surprise Dataset\nreader = Reader(rating_scale=(0, 1))\ndata = Dataset.load_from_df(df_surprise[[\"customer_id\", \"article_id\", \"interaction\"]], reader)\ntrainset, testset = train_test_split(data, test_size=0.2)\n\n#Train SVD\nmodel = SVD(n_factors=20, n_epochs=5, verbose=True, random_state=42)\nmodel.fit(trainset)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-25T10:00:27.197387Z","iopub.execute_input":"2025-06-25T10:00:27.197728Z","iopub.status.idle":"2025-06-25T10:01:26.820397Z","shell.execute_reply.started":"2025-06-25T10:00:27.197707Z","shell.execute_reply":"2025-06-25T10:01:26.819544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Get top-N predictions\ndef get_top_n(predictions, n=12):\n    top_n = defaultdict(list)\n    for uid, iid, true_r, est, _ in predictions:\n        top_n[uid].append((iid, est))\n    \n    for uid in top_n:\n        top_n[uid] = sorted(top_n[uid], key=lambda x: x[1], reverse=True)[:n]\n        top_n[uid] = [iid for iid, _ in top_n[uid]]\n    \n    return top_n\n\n#Compute Precision at k\ndef precision_at_k(predictions, top_n, k=12):\n    hits = 0\n    total = 0\n    \n    for uid, iid, true_r, est, _ in predictions:\n        if iid in top_n.get(uid, []):\n            hits += 1\n        total += 1\n    \n    return hits / total if total > 0 else 0\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-25T10:01:26.821279Z","iopub.execute_input":"2025-06-25T10:01:26.821956Z","iopub.status.idle":"2025-06-25T10:01:26.828645Z","shell.execute_reply.started":"2025-06-25T10:01:26.821926Z","shell.execute_reply":"2025-06-25T10:01:26.827720Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Predict on test set\npredictions = model.test(testset)\n\n#Generate top-N for each user\ntop_n = get_top_n(predictions, n=12)\n\n#Evaluate\np_at_12 = precision_at_k(predictions, top_n, k=12)\nprint(f\"Precision@12 using optimized SVD: {p_at_12:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-25T10:01:26.829492Z","iopub.execute_input":"2025-06-25T10:01:26.829763Z","iopub.status.idle":"2025-06-25T10:01:27.782076Z","shell.execute_reply.started":"2025-06-25T10:01:26.829744Z","shell.execute_reply":"2025-06-25T10:01:27.781034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#1. Get test users (first 1000 unique customer IDs)\ntest_users = transactions['customer_id'].unique()[:1000]\n\n#2. Generate recommendations using your CF function\nrecommendations = [recommend_cf(user) for user in test_users]\n\n#3. Create DataFrame and save to CSV\nrecommendations_df = pd.DataFrame({\n    \"customer_id\": test_users,\n    \"recommended_articles\": recommendations\n})\n\noutput = '/kaggle/working/recommendations.csv'\nrecommendations_df.to_csv(output, index=False)\n\nprint(f\"Recommendations saved to {output}\")\nprint(f\"First 5 recommendations:\\n{recommendations_df.head()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-25T11:00:33.985688Z","iopub.execute_input":"2025-06-25T11:00:33.986063Z","iopub.status.idle":"2025-06-25T11:37:25.804226Z","shell.execute_reply.started":"2025-06-25T11:00:33.986038Z","shell.execute_reply":"2025-06-25T11:37:25.803182Z"}},"outputs":[],"execution_count":null}]}