{"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":"gpu","dataSources":[{"sourceId":31254,"databundleVersionId":3103714,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.execute_input":"2026-02-04T16:14:00.489326Z","iopub.status.busy":"2026-02-04T16:14:00.488668Z","iopub.status.idle":"2026-02-04T16:15:22.131846Z","shell.execute_reply":"2026-02-04T16:15:22.131070Z","shell.execute_reply.started":"2026-02-04T16:14:00.489289Z"},"trusted":true},"outputs":[],"execution_count":null},{"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.execute_input":"2026-02-04T16:16:45.788434Z","iopub.status.busy":"2026-02-04T16:16:45.787852Z","iopub.status.idle":"2026-02-04T16:16:45.793249Z","shell.execute_reply":"2026-02-04T16:16:45.792534Z","shell.execute_reply.started":"2026-02-04T16:16:45.788405Z"},"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(\"/kaggle/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.execute_input":"2026-02-04T16:16:48.347091Z","iopub.status.busy":"2026-02-04T16:16:48.346759Z","iopub.status.idle":"2026-02-04T16:17:45.932665Z","shell.execute_reply":"2026-02-04T16:17:45.932081Z","shell.execute_reply.started":"2026-02-04T16:16:48.347055Z"},"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.execute_input":"2026-02-04T16:17:56.591406Z","iopub.status.busy":"2026-02-04T16:17:56.590798Z","iopub.status.idle":"2026-02-04T16:17:56.623293Z","shell.execute_reply":"2026-02-04T16:17:56.622710Z","shell.execute_reply.started":"2026-02-04T16:17:56.591375Z"},"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.execute_input":"2026-02-04T16:18:02.667287Z","iopub.status.busy":"2026-02-04T16:18:02.666957Z","iopub.status.idle":"2026-02-04T16:18:02.749936Z","shell.execute_reply":"2026-02-04T16:18:02.749353Z","shell.execute_reply.started":"2026-02-04T16:18:02.667260Z"},"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.execute_input":"2026-02-04T16:18:05.877569Z","iopub.status.busy":"2026-02-04T16:18:05.877255Z","iopub.status.idle":"2026-02-04T16:18:05.898187Z","shell.execute_reply":"2026-02-04T16:18:05.897516Z","shell.execute_reply.started":"2026-02-04T16:18:05.877529Z"},"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.execute_input":"2026-02-04T16:18:09.334924Z","iopub.status.busy":"2026-02-04T16:18:09.334625Z","iopub.status.idle":"2026-02-04T16:18:09.357188Z","shell.execute_reply":"2026-02-04T16:18:09.356392Z","shell.execute_reply.started":"2026-02-04T16:18:09.334899Z"},"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.execute_input":"2026-02-04T16:18:11.444739Z","iopub.status.busy":"2026-02-04T16:18:11.444245Z","iopub.status.idle":"2026-02-04T16:18:11.452677Z","shell.execute_reply":"2026-02-04T16:18:11.452095Z","shell.execute_reply.started":"2026-02-04T16:18:11.444711Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verification des lignes identiques à 100 %\ndf.duplicated().sum()","metadata":{"execution":{"iopub.execute_input":"2026-02-04T16:18:12.444629Z","iopub.status.busy":"2026-02-04T16:18:12.444335Z","iopub.status.idle":"2026-02-04T16:18:12.546055Z","shell.execute_reply":"2026-02-04T16:18:12.545318Z","shell.execute_reply.started":"2026-02-04T16:18:12.444603Z"},"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.execute_input":"2026-02-04T16:18:14.183197Z","iopub.status.busy":"2026-02-04T16:18:14.182611Z","iopub.status.idle":"2026-02-04T16:18:14.199715Z","shell.execute_reply":"2026-02-04T16:18:14.198940Z","shell.execute_reply.started":"2026-02-04T16:18:14.183170Z"},"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.execute_input":"2026-02-04T16:18:17.030384Z","iopub.status.busy":"2026-02-04T16:18:17.030091Z","iopub.status.idle":"2026-02-04T16:18:17.034653Z","shell.execute_reply":"2026-02-04T16:18:17.033899Z","shell.execute_reply.started":"2026-02-04T16:18:17.030358Z"},"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.execute_input":"2026-02-04T16:18:19.403631Z","iopub.status.busy":"2026-02-04T16:18:19.402949Z","iopub.status.idle":"2026-02-04T16:18:19.725801Z","shell.execute_reply":"2026-02-04T16:18:19.725020Z","shell.execute_reply.started":"2026-02-04T16:18:19.403595Z"},"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.execute_input":"2026-02-04T16:18:25.784748Z","iopub.status.busy":"2026-02-04T16:18:25.784147Z","iopub.status.idle":"2026-02-04T16:18:25.924594Z","shell.execute_reply":"2026-02-04T16:18:25.923876Z","shell.execute_reply.started":"2026-02-04T16:18:25.784719Z"},"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.execute_input":"2026-02-04T16:18:29.162245Z","iopub.status.busy":"2026-02-04T16:18:29.161918Z","iopub.status.idle":"2026-02-04T16:18:29.349257Z","shell.execute_reply":"2026-02-04T16:18:29.348567Z","shell.execute_reply.started":"2026-02-04T16:18:29.162219Z"},"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.execute_input":"2026-02-04T16:18:34.692356Z","iopub.status.busy":"2026-02-04T16:18:34.692055Z","iopub.status.idle":"2026-02-04T16:18:34.701537Z","shell.execute_reply":"2026-02-04T16:18:34.700865Z","shell.execute_reply.started":"2026-02-04T16:18:34.692323Z"},"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.execute_input":"2026-02-04T16:18:37.809256Z","iopub.status.busy":"2026-02-04T16:18:37.808923Z","iopub.status.idle":"2026-02-04T16:18:37.816559Z","shell.execute_reply":"2026-02-04T16:18:37.815769Z","shell.execute_reply.started":"2026-02-04T16:18:37.809229Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Vérifier les valeurs manquantes\ntransactions.isnull().sum()","metadata":{"execution":{"iopub.execute_input":"2026-02-04T16:18:40.343643Z","iopub.status.busy":"2026-02-04T16:18:40.342913Z","iopub.status.idle":"2026-02-04T16:18:43.177945Z","shell.execute_reply":"2026-02-04T16:18:43.177173Z","shell.execute_reply.started":"2026-02-04T16:18:40.343613Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Vérifier les doublons\ntransactions.duplicated().sum()\ntransactions = transactions.drop_duplicates()","metadata":{"execution":{"iopub.execute_input":"2026-02-04T16:18:45.151489Z","iopub.status.busy":"2026-02-04T16:18:45.150697Z","iopub.status.idle":"2026-02-04T16:19:19.805857Z","shell.execute_reply":"2026-02-04T16:19:19.805284Z","shell.execute_reply.started":"2026-02-04T16:18:45.151458Z"},"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.execute_input":"2026-02-04T16:20:11.331562Z","iopub.status.busy":"2026-02-04T16:20:11.331267Z","iopub.status.idle":"2026-02-04T16:20:14.354259Z","shell.execute_reply":"2026-02-04T16:20:14.353417Z","shell.execute_reply.started":"2026-02-04T16:20:11.331539Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Répartition des canaux de vente\nfig, ax = plt.subplots(figsize=(5,5))\ntransactions['sales_channel_id'].value_counts().plot(kind='pie', autopct='%1.1f%%', ax=ax)\nax.set_title('Sales Channel Split')\n    \nplt.tight_layout()\nplt.show()","metadata":{"execution":{"iopub.execute_input":"2026-02-04T16:23:13.933783Z","iopub.status.busy":"2026-02-04T16:23:13.933106Z","iopub.status.idle":"2026-02-04T16:23:14.160959Z","shell.execute_reply":"2026-02-04T16:23:14.160395Z","shell.execute_reply.started":"2026-02-04T16:23:13.933753Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## FEATURE ENGINEERING","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.execute_input":"2026-02-04T15:40:28.514959Z","iopub.status.busy":"2026-02-04T15:40:28.514218Z","iopub.status.idle":"2026-02-04T15:40:29.822554Z","shell.execute_reply":"2026-02-04T15:40:29.82196Z","shell.execute_reply.started":"2026-02-04T15:40:28.514931Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### FEATURES DE 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.execute_input":"2026-02-04T15:40:33.47006Z","iopub.status.busy":"2026-02-04T15:40:33.469402Z","iopub.status.idle":"2026-02-04T15:40:33.949937Z","shell.execute_reply":"2026-02-04T15:40:33.949384Z","shell.execute_reply.started":"2026-02-04T15:40:33.470036Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Identifier les démographiques clients (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.execute_input":"2026-02-04T15:40:36.943036Z","iopub.status.busy":"2026-02-04T15:40:36.942357Z","iopub.status.idle":"2026-02-04T15:40:37.974086Z","shell.execute_reply":"2026-02-04T15:40:37.97344Z","shell.execute_reply.started":"2026-02-04T15:40:36.942996Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Standarisation","metadata":{}},{"cell_type":"code","source":"X = df_features.drop('article_id', axis=1).values\nscaler = StandardScaler()\nX_scaled = scaler.fit_transform(X)\n\nprint(f\"X Shape: {X_scaled.shape}\")","metadata":{"execution":{"iopub.execute_input":"2026-02-04T15:40:41.390752Z","iopub.status.busy":"2026-02-04T15:40:41.39018Z","iopub.status.idle":"2026-02-04T15:40:41.418506Z","shell.execute_reply":"2026-02-04T15:40:41.417887Z","shell.execute_reply.started":"2026-02-04T15:40:41.390729Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### DIMENSIONALITY REDUCTION (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.execute_input":"2026-02-04T15:40:44.831624Z","iopub.status.busy":"2026-02-04T15:40:44.831119Z","iopub.status.idle":"2026-02-04T15:40:44.857512Z","shell.execute_reply":"2026-02-04T15:40:44.856842Z","shell.execute_reply.started":"2026-02-04T15:40:44.831599Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## K-Means - Clustering des Articles\n\nMaintenant que les données sont réduites et normalisées, nous appliquons **K-Means** pour regrouper les articles similaires.\n\n**Mesure de qualité : Silhouette Score**\n\nLe Silhouette Score est une métrique de validation qui varie entre -1 et 1. Elle mesure si un point est bien « chez lui » (proche de ses voisins de cluster) par rapport au cluster voisin le plus proche.\n\nPlus le score est proche de 1, plus les clusters sont denses et bien séparés.","metadata":{}},{"cell_type":"code","source":"def evaluate_kmeans(X, k_values):\n    print(\"STARTING K-MEANS EVALUATION\\n\")\n\n    results = {}\n    labels_store = {}\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        labels_store[k] = labels\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    best_labels = labels_store[best_k]\n\n    print(f\"\\nBest K selected: {best_k}\")\n\n    return results_df, best_k, best_labels\n","metadata":{"execution":{"iopub.execute_input":"2026-02-04T15:40:48.447141Z","iopub.status.busy":"2026-02-04T15:40:48.446423Z","iopub.status.idle":"2026-02-04T15:40:48.45211Z","shell.execute_reply":"2026-02-04T15:40:48.45134Z","shell.execute_reply.started":"2026-02-04T15:40:48.447112Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"k_values = [10, 15, 20]\n\nresults_df, best_k, best_labels = evaluate_kmeans(X_latent, k_values)\n\ndisplay(results_df)\n","metadata":{"execution":{"iopub.execute_input":"2026-02-04T15:41:02.870776Z","iopub.status.busy":"2026-02-04T15:41:02.87011Z","iopub.status.idle":"2026-02-04T15:46:10.532282Z","shell.execute_reply":"2026-02-04T15:46:10.53171Z","shell.execute_reply.started":"2026-02-04T15:41:02.870748Z"},"trusted":true},"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.execute_input":"2026-02-04T15:46:57.689632Z","iopub.status.busy":"2026-02-04T15:46:57.688819Z","iopub.status.idle":"2026-02-04T15:46:58.264616Z","shell.execute_reply":"2026-02-04T15:46:58.263862Z","shell.execute_reply.started":"2026-02-04T15:46:57.689591Z"},"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.execute_input":"2026-02-04T15:47:05.083272Z","iopub.status.busy":"2026-02-04T15:47:05.082971Z","iopub.status.idle":"2026-02-04T15:47:05.088373Z","shell.execute_reply":"2026-02-04T15:47:05.087701Z","shell.execute_reply.started":"2026-02-04T15:47:05.083247Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### KNN (K-Nearest Neighbors) ","metadata":{}},{"cell_type":"code","source":"def display_image(article_id, ax=None, title=None):\n    \"\"\"\n    Display an image for a given article ID using matplotlib.\n    \n    Handles path structure: 012/0123456789.jpg\n    \"\"\"\n    str_id = str(article_id).zfill(10)\n    folder = str_id[:3]\n    path = DATA_PATH / f\"images/{folder}/{str_id}.jpg\"\n    \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        import matplotlib.image as mpimg\n        img = mpimg.imread(str(path))\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            ax.text(0.5, 0.5, \"Error Loading\", ha='center', va='center')\n            ax.axis('off')\n\nfrom sklearn.metrics.pairwise import euclidean_distances\n\ndef recommend_similar_items(article_id, df_features, X_latent, top_k=5):\n    \"\"\"\n    Recommender Logic: Distance-Based Similarity in Latent Space\n    \n    Steps:\n    1. Find the latent vector for the input article\n    2. Calculate Euclidean distance to all other items\n    3. Return the K items with smallest distance\n    \n    Why latent space?\n    - Captures both content and behavioral patterns\n    - Much more meaningful than raw feature distances\n    \"\"\"\n    try:\n        # 1. Find the index of the source article\n        idx = df_features[df_features['article_id'] == article_id].index[0]\n        \n        # 2. Get the vector for this specific item\n        source_vector = X_latent[idx].reshape(1, -1)\n        \n        # 3. Calculate Euclidean Distance to ALL items\n        dists = euclidean_distances(source_vector, X_latent).flatten()\n        \n        # 4. Get indices of smallest distances (skip first = self)\n        closest_indices = dists.argsort()[1:top_k+1]\n        \n        # 5. Retrieve Article IDs\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 []\n","metadata":{"execution":{"iopub.execute_input":"2026-02-04T16:00:08.391776Z","iopub.status.busy":"2026-02-04T16:00:08.391067Z","iopub.status.idle":"2026-02-04T16:00:08.400277Z","shell.execute_reply":"2026-02-04T16:00:08.399593Z","shell.execute_reply.started":"2026-02-04T16:00:08.391747Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def recommend_and_visualize(article_id, df_features, X_latent, articles_meta, top_k=5, kmeans_model=None, use_kmeans=False):\n    \"\"\"\n    Visualise l'article source et ses recommandations.\n    Si use_kmeans=True, utilise KMeans simple, sinon distance-based.\n    \"\"\"\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    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\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()","metadata":{"execution":{"iopub.execute_input":"2026-02-04T16:00:16.512566Z","iopub.status.busy":"2026-02-04T16:00:16.51203Z","iopub.status.idle":"2026-02-04T16:00:16.519346Z","shell.execute_reply":"2026-02-04T16:00:16.518527Z","shell.execute_reply.started":"2026-02-04T16:00:16.51254Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### COMPARAISON : K-Means vs Distance-Based Recommendation","metadata":{}},{"cell_type":"code","source":"recommend_and_visualize(article_id=816353001, df_features=df_features, X_latent=X_latent, articles_meta=articles, top_k=5,\n                        kmeans_model=best_kmeans, use_kmeans=True)","metadata":{"execution":{"iopub.execute_input":"2026-02-04T16:00:18.958977Z","iopub.status.busy":"2026-02-04T16:00:18.958638Z","iopub.status.idle":"2026-02-04T16:00:20.259048Z","shell.execute_reply":"2026-02-04T16:00:20.258209Z","shell.execute_reply.started":"2026-02-04T16:00:18.95895Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Distance-based \nrecommend_and_visualize(article_id=816353001, df_features=df_features, X_latent=X_latent, articles_meta=articles, top_k=5)\n\n\n","metadata":{"execution":{"iopub.execute_input":"2026-02-04T15:53:39.564406Z","iopub.status.busy":"2026-02-04T15:53:39.564104Z","iopub.status.idle":"2026-02-04T15:53:40.666359Z","shell.execute_reply":"2026-02-04T15:53:40.665645Z","shell.execute_reply.started":"2026-02-04T15:53:39.564381Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}