{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.12.12"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":31254,"databundleVersionId":3103714,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport gc\nfrom pathlib import Path\nfrom sklearn.preprocessing import StandardScaler,LabelEncoder\nfrom sklearn.decomposition import PCA\nfrom sklearn.cluster import KMeans\nfrom sklearn.metrics import silhouette_score","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:38:11.120354Z","iopub.execute_input":"2026-02-04T19:38:11.120566Z","iopub.status.idle":"2026-02-04T19:38:15.517541Z","shell.execute_reply.started":"2026-02-04T19:38:11.120542Z","shell.execute_reply":"2026-02-04T19:38:15.516699Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Chargement du dataset et exploration de sa structure","metadata":{}},{"cell_type":"code","source":"DATA_PATH = Path(\"../input/h-and-m-personalized-fashion-recommendations\")\narticles = pd.read_csv(DATA_PATH / \"articles.csv\")\ntransactions = pd.read_csv(DATA_PATH / \"transactions_train.csv\")","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:38:20.231868Z","iopub.execute_input":"2026-02-04T19:38:20.232566Z","iopub.status.idle":"2026-02-04T19:39:15.25823Z","shell.execute_reply.started":"2026-02-04T19:38:20.232533Z","shell.execute_reply":"2026-02-04T19:39:15.25723Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset Articles","metadata":{}},{"cell_type":"markdown","source":"Le jeu de données **Articles** regroupe toutes les informations liées aux produits vendus dans les magasins H&M.","metadata":{}},{"cell_type":"code","source":"print(\"Shape du DataFrame Articles :\", articles.shape)\narticles.head()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:03.020186Z","iopub.execute_input":"2026-02-04T19:43:03.020631Z","iopub.status.idle":"2026-02-04T19:43:03.061227Z","shell.execute_reply.started":"2026-02-04T19:43:03.0206Z","shell.execute_reply":"2026-02-04T19:43:03.060518Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"***Description des features du dataset Articles***\n\nIdentifiant unique d’un article :\n* ```article_id``` \n\n5 colonnes liées au produit:\n* ```product_code```– code du produit\n* ```prod_name``` - nom du produit\n* ```product_type_no``` - numéro du type de produit\n* ```product_type_name``` – nom du type de produit, équivalent de product_type_no\n* ```product_group_name``` – nom du groupe de produits\n\n2 colonnes liées au motif (pattern):\n* ```graphical_appearance_no``` – code du motif\n* ```graphical_appearance_name``` – nom du motif\n\n2 colonnes liées à la couleur :\n* ```colour_group_code``` – code de la couleur\n* ```colour_group_name``` – nom de la couleur\n\n4 colonnes liées à la couleur perçue (ton général) :\n* ```perceived_colour_value_id``` – identifiant de la couleur perçue\n* ```perceived_colour_value_name``` – nom de la couleur perçue\n* ```perceived_colour_master_id``` – identifiant principal de la couleur perçue\n* ```perceived_colour_master_name``` – nom principal de la couleur perçue\n\n2 colonnes liées au département:\n* ```department_no``` – numéro du département\n* ```department_name``` – nom du département\n\n4 colonnes liées à l’index (top-level category):\n* ```index_code``` – code de l’index\n* ```index_name``` – nom de l’index\n* ```index_group_no``` – code du groupe d’index\n* ```index_group_name``` – nom du groupe d’index\n\n2 colonnes liées à la section :\n* ```section_no``` – numéro de la section\n* ```section_name``` – nom de la section\n\n2 colonnes liées au groupe de vêtements :\n* ```garment_group_n``` – numéro du groupe de vêtements\n* ```garment_group_name``` – nom du groupe de vêtements\n\n1 colonne contenant une description détaillée de l’article :\n* ```detail_desc``` – description détaillée de l’article\n","metadata":{}},{"cell_type":"code","source":"articles.info()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:09.715732Z","iopub.execute_input":"2026-02-04T19:43:09.716045Z","iopub.status.idle":"2026-02-04T19:43:09.807199Z","shell.execute_reply.started":"2026-02-04T19:43:09.716019Z","shell.execute_reply":"2026-02-04T19:43:09.80645Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"***Gestion des valeurs manquantes***","metadata":{}},{"cell_type":"code","source":"articles = articles.drop(columns=['detail_desc'])","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:13.18413Z","iopub.execute_input":"2026-02-04T19:43:13.185086Z","iopub.status.idle":"2026-02-04T19:43:13.207794Z","shell.execute_reply.started":"2026-02-04T19:43:13.185047Z","shell.execute_reply":"2026-02-04T19:43:13.207157Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"***Gestion des redondances***","metadata":{}},{"cell_type":"code","source":"cols_to_drop = [\n    'product_code',\n    'product_type_no',\n    'graphical_appearance_no',\n    'colour_group_code',\n    'perceived_colour_value_id',\n    'perceived_colour_master_id',\n    'department_no',\n    'index_group_no',\n    'section_no',\n    'garment_group_no'\n]\n\ndf= articles.drop(columns=cols_to_drop)","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:15.764386Z","iopub.execute_input":"2026-02-04T19:43:15.765254Z","iopub.status.idle":"2026-02-04T19:43:15.790241Z","shell.execute_reply.started":"2026-02-04T19:43:15.765222Z","shell.execute_reply":"2026-02-04T19:43:15.789571Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Verification de l'unicite de l'ID d'article\ndf['article_id'].duplicated().sum()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:19.024023Z","iopub.execute_input":"2026-02-04T19:43:19.024818Z","iopub.status.idle":"2026-02-04T19:43:19.033785Z","shell.execute_reply.started":"2026-02-04T19:43:19.024786Z","shell.execute_reply":"2026-02-04T19:43:19.033074Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verification des lignes identiques à 100 %\ndf.duplicated().sum()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:21.689186Z","iopub.execute_input":"2026-02-04T19:43:21.689818Z","iopub.status.idle":"2026-02-04T19:43:21.804518Z","shell.execute_reply.started":"2026-02-04T19:43:21.689792Z","shell.execute_reply":"2026-02-04T19:43:21.803612Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_final = df[[\n    'article_id',\n    'product_type_name',\n    'product_group_name',\n    'graphical_appearance_name',\n    'colour_group_name',\n    'perceived_colour_value_name',\n    'perceived_colour_master_name',\n    'department_name',\n    'index_name',\n    'index_group_name',\n    'section_name',\n    'garment_group_name'\n]]","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:26.111895Z","iopub.execute_input":"2026-02-04T19:43:26.112243Z","iopub.status.idle":"2026-02-04T19:43:26.131769Z","shell.execute_reply.started":"2026-02-04T19:43:26.112213Z","shell.execute_reply":"2026-02-04T19:43:26.130863Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Features initiales \nfeatures = [c for c in df_final.columns ]\n\nprint(\"Features:\", features)","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:28.655605Z","iopub.execute_input":"2026-02-04T19:43:28.656531Z","iopub.status.idle":"2026-02-04T19:43:28.660827Z","shell.execute_reply.started":"2026-02-04T19:43:28.656493Z","shell.execute_reply":"2026-02-04T19:43:28.660045Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Groupes de produits\ndf_final['product_group_name'].value_counts().plot(\n    kind='bar', figsize=(8,4), title='Répartition des groupes de produits'\n)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:30.327436Z","iopub.execute_input":"2026-02-04T19:43:30.327735Z","iopub.status.idle":"2026-02-04T19:43:30.678118Z","shell.execute_reply.started":"2026-02-04T19:43:30.327709Z","shell.execute_reply":"2026-02-04T19:43:30.677315Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"***Encodage des features categorielles***","metadata":{}},{"cell_type":"code","source":"summary = pd.DataFrame({\n    'nb_valeurs_uniques': df_final.select_dtypes(include='object').nunique(),\n    'nb_valeurs_manquantes': df_final.select_dtypes(include='object').isnull().sum()\n})\n\nsummary\n","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:34.635965Z","iopub.execute_input":"2026-02-04T19:43:34.636313Z","iopub.status.idle":"2026-02-04T19:43:34.807739Z","shell.execute_reply.started":"2026-02-04T19:43:34.636284Z","shell.execute_reply":"2026-02-04T19:43:34.807005Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_final = df_final.copy()\n\nfor col in df_final.columns:\n    if col !='article_id':\n        le = LabelEncoder()\n        df_final[col] = le.fit_transform(\n            df_final[col].astype(str)\n        )\n\ndf_final.info()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:38.403696Z","iopub.execute_input":"2026-02-04T19:43:38.404018Z","iopub.status.idle":"2026-02-04T19:43:38.615064Z","shell.execute_reply.started":"2026-02-04T19:43:38.403989Z","shell.execute_reply":"2026-02-04T19:43:38.614312Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset Transactions\n\nLe jeu de données **Transactions** contient des informations sur le nombre de transaction de chaque article.","metadata":{}},{"cell_type":"code","source":"print(\"Shape du DataFrame Transactions :\", transactions.shape)\ntransactions.head()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:43.623648Z","iopub.execute_input":"2026-02-04T19:43:43.623922Z","iopub.status.idle":"2026-02-04T19:43:43.633767Z","shell.execute_reply.started":"2026-02-04T19:43:43.6239Z","shell.execute_reply":"2026-02-04T19:43:43.632849Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"***Description des features du dataset Articles***\n* ```t_dat``` -date de la transaction au format YYYY-MM-DD\n* ```customer_id``` - identifiant du client\n* ```article_id``` - identifiant du produit\n* ```price``` - prix de produit\n* ```sales_channel_id``` - canal de vente","metadata":{}},{"cell_type":"code","source":"transactions.info()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:47.915788Z","iopub.execute_input":"2026-02-04T19:43:47.916368Z","iopub.status.idle":"2026-02-04T19:43:47.924122Z","shell.execute_reply.started":"2026-02-04T19:43:47.916341Z","shell.execute_reply":"2026-02-04T19:43:47.923296Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Vérifier les valeurs manquantes\ntransactions.isnull().sum()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:43:51.99586Z","iopub.execute_input":"2026-02-04T19:43:51.996196Z","iopub.status.idle":"2026-02-04T19:43:55.160684Z","shell.execute_reply.started":"2026-02-04T19:43:51.996168Z","shell.execute_reply":"2026-02-04T19:43:55.159988Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Vérifier les doublons\ntransactions.duplicated().sum()\ntransactions = transactions.drop_duplicates()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:44:00.527417Z","iopub.execute_input":"2026-02-04T19:44:00.527712Z","iopub.status.idle":"2026-02-04T19:44:38.881119Z","shell.execute_reply.started":"2026-02-04T19:44:00.527686Z","shell.execute_reply":"2026-02-04T19:44:38.880466Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Convertir t_dat en datetime\ntransactions['t_dat'] = pd.to_datetime(transactions['t_dat'])\nbegin = transactions['t_dat'].min()\nend = transactions['t_dat'].max()\nprint('Date range is from {} to {}.'.format(begin.date(), end.date()))","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:45:29.385126Z","iopub.execute_input":"2026-02-04T19:45:29.385992Z","iopub.status.idle":"2026-02-04T19:45:32.80081Z","shell.execute_reply.started":"2026-02-04T19:45:29.385958Z","shell.execute_reply":"2026-02-04T19:45:32.800014Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Répartition des canaux de vente\ntransactions['sales_channel_id'].value_counts().plot(kind='pie', autopct='%1.1f%%', \n                            title='Sales Channel Split')    \nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T21:51:50.957256Z","iopub.execute_input":"2026-02-04T21:51:50.958279Z","iopub.status.idle":"2026-02-04T21:51:51.202328Z","shell.execute_reply.started":"2026-02-04T21:51:50.958238Z","shell.execute_reply":"2026-02-04T21:51:51.201446Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Feature Engeneering","metadata":{}},{"cell_type":"markdown","source":"### Statistiques de prix\n\nL'objectif est de capturer les caractéristiques de prix de chaque article qui peuvent influencer les recommandations","metadata":{}},{"cell_type":"code","source":"df_features = df_final.copy()\nprice_stats = transactions.groupby('article_id')['price'].agg(['mean', 'max', 'std']).reset_index()\nprice_stats.columns = ['article_id', 'price_mean', 'price_max', 'price_std']\n\n# Gérer les valeurs manquantes dans price_std (articles avec 1 seule transaction)\nprice_stats['price_std'] = price_stats['price_std'].fillna(0)\n\ndf_features = df_features.merge(price_stats, on='article_id', how='left')\n\n# Optimisation mémoire, Supprimer le dataframe intermédiaire\ndel price_stats\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:46:49.072416Z","iopub.execute_input":"2026-02-04T19:46:49.072759Z","iopub.status.idle":"2026-02-04T19:46:50.563931Z","shell.execute_reply.started":"2026-02-04T19:46:49.072729Z","shell.execute_reply":"2026-02-04T19:46:50.563284Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Popularité (90 derniers jours) ","metadata":{}},{"cell_type":"code","source":"max_date = transactions['t_dat'].max()\nrecent_trans = transactions[transactions['t_dat'] > (max_date - pd.Timedelta(days=90))]\npopularity = recent_trans.groupby('article_id').size().reset_index(name='recent_sales_count')\n\ndf_features = df_features.merge(popularity, on='article_id', how='left')\n\ndel recent_trans\ndel popularity\ngc.collect()","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:46:55.31192Z","iopub.execute_input":"2026-02-04T19:46:55.312771Z","iopub.status.idle":"2026-02-04T19:46:55.841209Z","shell.execute_reply.started":"2026-02-04T19:46:55.312721Z","shell.execute_reply":"2026-02-04T19:46:55.840415Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Identifier le canal de vente (en ligne vs magazin)","metadata":{}},{"cell_type":"code","source":"channel_stats = transactions.groupby('article_id')['sales_channel_id'].mean().reset_index(name='avg_sales_channel')\ndf_features = df_features.merge(channel_stats, on='article_id', how='left')\n\ndel channel_stats\ngc.collect()\n\n# GÉRER LES VALEURS MANQUANTES\ndf_features['recent_sales_count'] = df_features['recent_sales_count'].fillna(0)\ndf_features = df_features.fillna(df_features.mean(numeric_only=True))\n\nprint(f\"Shape du dataframe: {df_features.shape}\")","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:48:09.226261Z","iopub.execute_input":"2026-02-04T19:48:09.226816Z","iopub.status.idle":"2026-02-04T19:48:10.400996Z","shell.execute_reply.started":"2026-02-04T19:48:09.226788Z","shell.execute_reply":"2026-02-04T19:48:10.400091Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Standarisation","metadata":{}},{"cell_type":"code","source":"X = df_features.drop(columns=['article_id'])\nscaler = StandardScaler()\nX_scaled = scaler.fit_transform(X)\n\nprint(f\"X Shape: {X_scaled.shape}\")","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:49:44.698765Z","iopub.execute_input":"2026-02-04T19:49:44.699743Z","iopub.status.idle":"2026-02-04T19:49:44.736913Z","shell.execute_reply.started":"2026-02-04T19:49:44.69971Z","shell.execute_reply":"2026-02-04T19:49:44.736226Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### PCA (Principal Component Analysis)\n\nLes données haute-dimensionnelle sont difficiles à visualiser et à analyser. PCA réduit la dimensionnalité en gardant la max de la variance","metadata":{}},{"cell_type":"code","source":"pca = PCA(n_components=0.95,random_state=42)  \nX_latent = pca.fit_transform(X_scaled)\nprint(f\"Reduced shape: {X_latent.shape}\")","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:51:27.086256Z","iopub.execute_input":"2026-02-04T19:51:27.086595Z","iopub.status.idle":"2026-02-04T19:51:27.112438Z","shell.execute_reply.started":"2026-02-04T19:51:27.086567Z","shell.execute_reply":"2026-02-04T19:51:27.111024Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## K-Means – Clustering des Articles\n\nAppliquons l’algorithme **K-Means** afin de regrouper les articles similaires en clusters.\n\n\n### Mesure de qualité : Silhouette Score\n\n**Silhouette Score** est une métrique de validation qui varie entre -1 et 1 qui permet d’évaluer la qualité du clustering en mesurant à quel point il est bien regroupé avec les éléments de son propre cluster,et bien séparé des autres clusters.\n\nPour chaque point nous avons:\n\n- **a** : distance moyenne entre ce point et les autres points de son cluster\n- **b** : distance moyenne entre ce point et les points du cluster le plus proche\n\nLa valeur de la silhouette est donnée par la formule :\n\n\n$$s = \\frac{b - a}{\\max(a, b)}$$\nPlus le score est proche de 1, plus les clusters sont denses et bien séparés.\n\n","metadata":{}},{"cell_type":"code","source":"def evaluate_kmeans(X, k_values):\n    results = {}\n\n    for k in k_values:\n        print(f\"Testing K-Means (K={k})\")\n\n        kmeans = KMeans(n_clusters=k, random_state=42)\n        labels = kmeans.fit_predict(X)\n        sil = silhouette_score(X, labels)\n        results[k] = sil\n\n    results_df = (\n        pd.DataFrame.from_dict(results, orient=\"index\", columns=[\"Silhouette\"])\n        .sort_values(\"Silhouette\", ascending=False)\n    )\n\n    best_k = results_df.index[0]\n\n    print(f\"\\nBest K selected: {best_k}\")\n\n    return results_df, best_k\n","metadata":{"execution":{"iopub.status.busy":"2026-02-04T19:51:43.24181Z","iopub.execute_input":"2026-02-04T19:51:43.242749Z","iopub.status.idle":"2026-02-04T19:51:43.248355Z","shell.execute_reply.started":"2026-02-04T19:51:43.242714Z","shell.execute_reply":"2026-02-04T19:51:43.247452Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"k_values = [10, 15, 20, 25]\n\nresults_df, best_k = evaluate_kmeans(X_latent, k_values)\n\ndisplay(results_df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T20:09:42.528707Z","iopub.execute_input":"2026-02-04T20:09:42.529292Z","iopub.status.idle":"2026-02-04T20:16:58.753905Z","shell.execute_reply.started":"2026-02-04T20:09:42.529264Z","shell.execute_reply":"2026-02-04T20:16:58.753178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_kmeans = KMeans(n_clusters=best_k,random_state=42)\nbest_kmeans.fit(X_latent)\ndf_features[\"cluster\"] = best_kmeans.labels_\n","metadata":{"execution":{"iopub.status.busy":"2026-02-04T20:02:11.906273Z","iopub.execute_input":"2026-02-04T20:02:11.906974Z","iopub.status.idle":"2026-02-04T20:02:12.498473Z","shell.execute_reply.started":"2026-02-04T20:02:11.90694Z","shell.execute_reply":"2026-02-04T20:02:12.497618Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Fonction de Recommandation (basée sur le clustering K-Means)\n","metadata":{}},{"cell_type":"code","source":"def recommend_kmeans(article_id, df_features, kmeans_model, top_k=5):\n \n    try:\n        idx = df_features[df_features['article_id'] == article_id].index[0]\n        \n        cluster_label = kmeans_model.labels_[idx]\n        \n        cluster_indices = np.where(kmeans_model.labels_ == cluster_label)[0]\n        cluster_indices = cluster_indices[cluster_indices != idx]\n        \n        recommended_ids = df_features.iloc[cluster_indices]['article_id'].values[:top_k]\n        return recommended_ids\n    \n    except IndexError:\n        print(f\"Error: Article ID {article_id} not found.\")\n        return []","metadata":{"execution":{"iopub.status.busy":"2026-02-04T21:37:55.301379Z","iopub.execute_input":"2026-02-04T21:37:55.301661Z","iopub.status.idle":"2026-02-04T21:37:55.30789Z","shell.execute_reply.started":"2026-02-04T21:37:55.301629Z","shell.execute_reply":"2026-02-04T21:37:55.30721Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## KNN (K-Nearest Neighbors) ","metadata":{}},{"cell_type":"markdown","source":"### VISUALIZATION","metadata":{}},{"cell_type":"code","source":"import matplotlib.image as mpimg\n\ndef display_image(article_id, ax=None, title=None):\n    \"\"\"\n    Helper function to display an image for a given article ID using matplotlib.\n    Handles path structure: 012/0123456789.jpg\n    Avoids PIL as requested.\n    \"\"\"\n    \n    \n    str_id = str(article_id).zfill(10)\n    folder = str_id[:3]\n    # Adjust path if running locally vs Kaggle\n    #fichiere h&m mndmin f 01 7ta 023 lhad sabab kanakhdou mn path li 3ndou nfss debut bach matloaditch file kamlin\n    path = DATA_PATH / f\"images/{folder}/{str_id}.jpg\"\n    \n    # Placeholder if not found / error handling\n    if not path.exists():\n        if ax:\n            ax.text(0.5, 0.5, \"Image Not Found\", ha='center', va='center')\n            ax.axis('off')\n            if title: ax.set_title(title, fontsize=8)\n        return\n\n    try:\n        # Use matplotlib's imread directly\n        img = mpimg.imread(str(path))\n        # ax grid mode active ou non peut etre 2 a 3\n        if ax:\n            ax.imshow(img)\n            ax.axis('off')\n            if title: ax.set_title(title, fontsize=8)\n        else:\n            plt.imshow(img)\n            plt.axis('off')\n            if title: plt.title(title)\n            plt.show()\n    except Exception as e:\n        if ax:\n            #va and ha for text centering from axis\n            ax.text(0.5, 0.5, \"Error Loading\", ha='center', va='center')\n            ax.axis('off')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T20:02:26.217267Z","iopub.execute_input":"2026-02-04T20:02:26.217594Z","iopub.status.idle":"2026-02-04T20:02:26.225213Z","shell.execute_reply.started":"2026-02-04T20:02:26.217565Z","shell.execute_reply":"2026-02-04T20:02:26.224515Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Recomenendation using KNN ","metadata":{}},{"cell_type":"markdown","source":"This function implements a **k-Nearest Neighbors (k-NN)** approach by calculating the  norm (Euclidean distance) between a target vector  and all vectors  in the latent space .\n\n### Mathematical Logic\n\nThe similarity is determined by minimizing the distance function:\n\n$$d(u, v) = \\sqrt{\\sum_{i=1}^{n} (u_i - v_i)^2}$$\n\nwhere the top  items with the smallest  values are selected as the most similar recommendations.\n","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics.pairwise import euclidean_distances\n\n\ndef recommend_similar_items(article_id, df_features, X_latent, top_k=5):\n    \"\"\"\n    (Distance-Based)  best because AE latent space PCA is defined as the orthogonal projection that minimizes Euclidean error.:\n    1. Look up the 'Latent Vector' for the input article.\n    2. Calculate the distance between this vector and ALL other items.\n    3. Return the items with the smallest distance (most similar).\n    \"\"\"\n    try:\n        # 1. trouver l'index dyal had l'article_id f df_features\n        # Note: We assume df_features is aligned with X_latent indices\n        idx = df_features[df_features['article_id'] == article_id].index[0]\n        \n        # 2. rji3 l'vector dyal had l'article (reshape to 1 row)\n        source_vector = X_latent[idx].reshape(1, -1)\n        \n        # 3. Calculate Euclidean Distance to ALL items\n        # (This is fast: calculating 1 vs 100k takes milliseconds)\n        dists = euclidean_distances(source_vector, X_latent).flatten()\n        \n        # 4. Get indices of the smallest distances\n        # argsort gives us the indices sorted from smallest distance to largest\n        # We skip the first one (index 0) because that is the item itself (distance=0)\n        closest_indices = dists.argsort()[1:top_k+1]\n        \n        # 5. Retrieve Article IDs for these indices\n        recommended_ids = df_features.iloc[closest_indices]['article_id'].values\n        \n        return recommended_ids\n\n    except IndexError:\n        print(f\"Error: Article ID {article_id} not found in processed data.\")\n        return []","metadata":{"execution":{"iopub.status.busy":"2026-02-04T20:02:30.846852Z","iopub.execute_input":"2026-02-04T20:02:30.84754Z","iopub.status.idle":"2026-02-04T20:02:30.853288Z","shell.execute_reply.started":"2026-02-04T20:02:30.847509Z","shell.execute_reply":"2026-02-04T20:02:30.852556Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndef recommend_and_visualize(article_id, df_features, X_latent, articles_meta, top_k=5 , kmeans_model=best_kmeans, use_kmeans=False ):\n    \"\"\"\n    Visualizes the source item and its recommendations side-by-side.\n    Now requires X_latent instead of 'labels'.\n    \"\"\"\n    # Call the recommendation function avec distance euclidean logic list\n    if use_kmeans and kmeans_model is not None:\n        recs = recommend_kmeans(article_id, df_features, kmeans_model, top_k)\n    else:\n        recs = recommend_similar_items(article_id, df_features, X_latent, top_k)\n    \n    # Early exit if no recommendations\n    if len(recs) == 0:\n        return\n\n    # Setup Plot\n    n_cols = top_k + 1\n    fig, axes = plt.subplots(1, n_cols, figsize=(3 * n_cols, 4))\n    if n_cols == 1: axes = [axes] # Handle single plot case\n\n    # 1. Plot Source\n    try:\n        source_info = articles_meta[articles_meta['article_id'] == article_id].iloc[0]\n        title_text = f\"SOURCE:\\n{source_info['prod_name'][:15]}...\\n({source_info['product_type_name']})\"\n        display_image(article_id, axes[0], title=title_text)\n    except:\n         display_image(article_id, axes[0], title=\"Source (Metadata Missing)\")\n\n    # 2. Plot Recommendations\n    for i, rec_id in enumerate(recs):\n        try:\n            rec_info = articles_meta[articles_meta['article_id'] == rec_id].iloc[0]\n            title_text = f\"REC #{i+1}:\\n{rec_info['prod_name'][:15]}...\\n({rec_info['product_type_name']})\"\n            display_image(rec_id, axes[i+1], title=title_text)\n        except IndexError:\n            display_image(rec_id, axes[i+1], title=f\"REC #{i+1}\")\n\n    plt.tight_layout()\n    plt.show()\n","metadata":{"execution":{"iopub.status.busy":"2026-02-04T20:02:34.627961Z","iopub.execute_input":"2026-02-04T20:02:34.62868Z","iopub.status.idle":"2026-02-04T20:02:34.636397Z","shell.execute_reply.started":"2026-02-04T20:02:34.628648Z","shell.execute_reply":"2026-02-04T20:02:34.635612Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# TEST pipeline","metadata":{}},{"cell_type":"markdown","source":"### COMPARAISON : K-Means vs Distance-Based Recommendation","metadata":{}},{"cell_type":"code","source":"random_id = df_features['article_id'].sample(1).iloc[0]\nprint(f\"Testing Random Article ID: {random_id}\")\nprint(\"=\"*50)\n\n# --- COMPARISON 1: CLUSTER-BASED ---\nprint(\"\\nMETHOD 1: K-Means Constrained Search\")\nprint(\"Focus: Finds items within the same semantic cluster.\")\nrecommend_and_visualize(random_id, df_features, X_latent, articles, top_k=5, use_kmeans=True)\n\nprint(\"\\n\" + \"-\"*50)\n\n# --- COMPARISON 2: PURE DISTANCE ---\nprint(\"\\nMETHOD 2: Global Euclidean Search (Pure Distance)\")\nprint(\"Focus: Finds mathematically closest items across the whole dataset.\")\nrecommend_and_visualize(random_id, df_features, X_latent, articles, top_k=5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T20:06:02.310515Z","iopub.execute_input":"2026-02-04T20:06:02.310853Z","iopub.status.idle":"2026-02-04T20:06:04.973075Z","shell.execute_reply.started":"2026-02-04T20:06:02.310823Z","shell.execute_reply":"2026-02-04T20:06:04.972211Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# SAVE MODELS","metadata":{}},{"cell_type":"code","source":"import joblib\nimport pickle\nimport pandas as pd\n\n\n# 1. Save Scaler \njoblib.dump(scaler, \"scaler.joblib\")\n\n\n# 2. Save PCA (Dimensionality reduction matrix)\nwith open(\"best_pca.pkl\", \"wb\") as f:\n    pickle.dump(pca, f)\n\n\n# 3. Save K-Means Model (Centroids and labels)\n# Note: cuML KMeans handles pickle for GPU-trained models\nwith open(\"best_kmeans.pkl\", \"wb\") as f:\n    pickle.dump(best_kmeans, f)\n\n# 4. Save Cluster Mapping (CSV for instant lookup)\n# Links Article ID to Cluster ID: f(ID) -> Cluster\ndf_clusters = df_features[['article_id']].copy()\ndf_clusters['cluster'] = best_labels\ndf_clusters.to_csv(\"article_clusters.csv\", index=False)\n\nprint(\"\\nAll pipeline files are ready for production.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T18:03:04.497114Z","iopub.execute_input":"2026-02-04T18:03:04.49741Z","iopub.status.idle":"2026-02-04T18:03:04.587994Z","shell.execute_reply.started":"2026-02-04T18:03:04.497384Z","shell.execute_reply":"2026-02-04T18:03:04.587397Z"}},"outputs":[],"execution_count":null}]}