{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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":[{"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":"# 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":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:19:41.264018Z","iopub.execute_input":"2026-02-04T15:19:41.264802Z","iopub.status.idle":"2026-02-04T15:22:34.233077Z","shell.execute_reply.started":"2026-02-04T15:19:41.264771Z","shell.execute_reply":"2026-02-04T15:22:34.232191Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:35:52.988589Z","iopub.execute_input":"2026-02-04T15:35:52.989071Z","iopub.status.idle":"2026-02-04T15:35:56.124706Z","shell.execute_reply.started":"2026-02-04T15:35:52.989036Z","shell.execute_reply":"2026-02-04T15:35:56.123918Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:36:14.063401Z","iopub.execute_input":"2026-02-04T15:36:14.063989Z","iopub.status.idle":"2026-02-04T15:37:17.259522Z","shell.execute_reply.started":"2026-02-04T15:36:14.063959Z","shell.execute_reply":"2026-02-04T15:37:17.258914Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:37:25.262912Z","iopub.execute_input":"2026-02-04T15:37:25.263697Z","iopub.status.idle":"2026-02-04T15:37:25.298371Z","shell.execute_reply.started":"2026-02-04T15:37:25.263646Z","shell.execute_reply":"2026-02-04T15:37:25.297811Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:37:37.568768Z","iopub.execute_input":"2026-02-04T15:37:37.569063Z","iopub.status.idle":"2026-02-04T15:37:37.650593Z","shell.execute_reply.started":"2026-02-04T15:37:37.569039Z","shell.execute_reply":"2026-02-04T15:37:37.649894Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"***Gestion des valeurs manquantes***\n\nD’après notre résumé : UNE seule colonne contient des valeurs manquantes.\n\n* ```detail_desc```: soit 416 valeurs manquantes, représentant environ 0,4 % du total.\n\nLa colonne detail_desc fournit une description textuelle du produit. Cette information n’est pas necessaire pour le clustering.\n\n","metadata":{}},{"cell_type":"code","source":"articles = articles.drop(columns=['detail_desc'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:37:42.230317Z","iopub.execute_input":"2026-02-04T15:37:42.231074Z","iopub.status.idle":"2026-02-04T15:37:42.252495Z","shell.execute_reply.started":"2026-02-04T15:37:42.231046Z","shell.execute_reply":"2026-02-04T15:37:42.251657Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:37:45.911375Z","iopub.execute_input":"2026-02-04T15:37:45.911694Z","iopub.status.idle":"2026-02-04T15:37:45.937217Z","shell.execute_reply.started":"2026-02-04T15:37:45.911646Z","shell.execute_reply":"2026-02-04T15:37:45.936467Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Verification de l'unicite de l'ID d'article\ndf['article_id'].duplicated().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:38:29.622912Z","iopub.execute_input":"2026-02-04T15:38:29.623332Z","iopub.status.idle":"2026-02-04T15:38:29.632518Z","shell.execute_reply.started":"2026-02-04T15:38:29.623302Z","shell.execute_reply":"2026-02-04T15:38:29.631763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Verification des lignes identiques à 100 %\ndf.duplicated().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:38:33.284018Z","iopub.execute_input":"2026-02-04T15:38:33.284311Z","iopub.status.idle":"2026-02-04T15:38:33.388113Z","shell.execute_reply.started":"2026-02-04T15:38:33.284287Z","shell.execute_reply":"2026-02-04T15:38:33.387360Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:38:35.916090Z","iopub.execute_input":"2026-02-04T15:38:35.916582Z","iopub.status.idle":"2026-02-04T15:38:35.933001Z","shell.execute_reply.started":"2026-02-04T15:38:35.916556Z","shell.execute_reply":"2026-02-04T15:38:35.932299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Features initiales \nfeatures = [c for c in df_final.columns ]\n\nprint(\"Features:\", features)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:39:09.166350Z","iopub.execute_input":"2026-02-04T15:39:09.166666Z","iopub.status.idle":"2026-02-04T15:39:09.170991Z","shell.execute_reply.started":"2026-02-04T15:39:09.166642Z","shell.execute_reply":"2026-02-04T15:39:09.170247Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:39:11.431218Z","iopub.execute_input":"2026-02-04T15:39:11.431944Z","iopub.status.idle":"2026-02-04T15:39:11.736093Z","shell.execute_reply.started":"2026-02-04T15:39:11.431916Z","shell.execute_reply":"2026-02-04T15:39:11.735349Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:39:15.770575Z","iopub.execute_input":"2026-02-04T15:39:15.770897Z","iopub.status.idle":"2026-02-04T15:39:15.913262Z","shell.execute_reply.started":"2026-02-04T15:39:15.770870Z","shell.execute_reply":"2026-02-04T15:39:15.912705Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:39:19.196323Z","iopub.execute_input":"2026-02-04T15:39:19.197107Z","iopub.status.idle":"2026-02-04T15:39:19.394779Z","shell.execute_reply.started":"2026-02-04T15:39:19.197068Z","shell.execute_reply":"2026-02-04T15:39:19.394069Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:39:23.590306Z","iopub.execute_input":"2026-02-04T15:39:23.591044Z","iopub.status.idle":"2026-02-04T15:39:23.599955Z","shell.execute_reply.started":"2026-02-04T15:39:23.591016Z","shell.execute_reply":"2026-02-04T15:39:23.599251Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:39:27.638214Z","iopub.execute_input":"2026-02-04T15:39:27.638524Z","iopub.status.idle":"2026-02-04T15:39:27.646451Z","shell.execute_reply.started":"2026-02-04T15:39:27.638499Z","shell.execute_reply":"2026-02-04T15:39:27.645738Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Vérifier les valeurs manquantes\ntransactions.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:39:30.758816Z","iopub.execute_input":"2026-02-04T15:39:30.759115Z","iopub.status.idle":"2026-02-04T15:39:33.644310Z","shell.execute_reply.started":"2026-02-04T15:39:30.759090Z","shell.execute_reply":"2026-02-04T15:39:33.643613Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Les doublons exacts doit etre supprimer:\n\n- Si on les gardes, on comptes plusieurs fois la même transaction par suite biais dans les fréquences de vente.\n\n- Si tu les supprimes, on gardes une transaction par vente réelle par suite plus propre pour le clustering.","metadata":{}},{"cell_type":"code","source":"#Vérifier les doublons\ntransactions.duplicated().sum()\ntransactions = transactions.drop_duplicates()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:39:38.041386Z","iopub.execute_input":"2026-02-04T15:39:38.041694Z","iopub.status.idle":"2026-02-04T15:40:13.058840Z","shell.execute_reply.started":"2026-02-04T15:39:38.041651Z","shell.execute_reply":"2026-02-04T15:40:13.058064Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:40:17.192352Z","iopub.execute_input":"2026-02-04T15:40:17.192965Z","iopub.status.idle":"2026-02-04T15:40:20.282796Z","shell.execute_reply.started":"2026-02-04T15:40:17.192934Z","shell.execute_reply":"2026-02-04T15:40:20.282138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Répartition des canaux de vente\ntransactions['sales_channel_id'].value_counts().plot(\n    kind='bar', title='Répartition des canaux de vente'\n)\nplt.xticks([0,1], ['Magasin', 'En ligne'], rotation=0)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:40:25.064173Z","iopub.execute_input":"2026-02-04T15:40:25.064807Z","iopub.status.idle":"2026-02-04T15:40:25.325480Z","shell.execute_reply.started":"2026-02-04T15:40:25.064779Z","shell.execute_reply":"2026-02-04T15:40:25.324740Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## FEATURE ENGINEERING","metadata":{}},{"cell_type":"markdown","source":"### STATISTIQUES DE PRIX ","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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:40:28.514218Z","iopub.execute_input":"2026-02-04T15:40:28.514959Z","iopub.status.idle":"2026-02-04T15:40:29.822554Z","shell.execute_reply.started":"2026-02-04T15:40:28.514931Z","shell.execute_reply":"2026-02-04T15:40:29.821960Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:40:33.469402Z","iopub.execute_input":"2026-02-04T15:40:33.470060Z","iopub.status.idle":"2026-02-04T15:40:33.949937Z","shell.execute_reply.started":"2026-02-04T15:40:33.470036Z","shell.execute_reply":"2026-02-04T15:40:33.949384Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:40:36.942357Z","iopub.execute_input":"2026-02-04T15:40:36.943036Z","iopub.status.idle":"2026-02-04T15:40:37.974086Z","shell.execute_reply.started":"2026-02-04T15:40:36.942996Z","shell.execute_reply":"2026-02-04T15:40:37.973440Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:40:41.390180Z","iopub.execute_input":"2026-02-04T15:40:41.390752Z","iopub.status.idle":"2026-02-04T15:40:41.418506Z","shell.execute_reply.started":"2026-02-04T15:40:41.390729Z","shell.execute_reply":"2026-02-04T15:40:41.417887Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### DIMENSIONALITY REDUCTION (PCA)\nPourquoi ? Les 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)  # garde 95% de la variance\nX_latent = pca.fit_transform(X_scaled)\nprint(f\"  Reduced shape: {X_latent.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:40:44.831119Z","iopub.execute_input":"2026-02-04T15:40:44.831624Z","iopub.status.idle":"2026-02-04T15:40:44.857512Z","shell.execute_reply.started":"2026-02-04T15:40:44.831599Z","shell.execute_reply":"2026-02-04T15:40:44.856842Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### KMEANS","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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:40:48.446423Z","iopub.execute_input":"2026-02-04T15:40:48.447141Z","iopub.status.idle":"2026-02-04T15:40:48.452110Z","shell.execute_reply.started":"2026-02-04T15:40:48.447112Z","shell.execute_reply":"2026-02-04T15:40:48.451340Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:41:02.870110Z","iopub.execute_input":"2026-02-04T15:41:02.870776Z","iopub.status.idle":"2026-02-04T15:46:10.532282Z","shell.execute_reply.started":"2026-02-04T15:41:02.870748Z","shell.execute_reply":"2026-02-04T15:46:10.531710Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:46:57.688819Z","iopub.execute_input":"2026-02-04T15:46:57.689632Z","iopub.status.idle":"2026-02-04T15:46:58.264616Z","shell.execute_reply.started":"2026-02-04T15:46:57.689591Z","shell.execute_reply":"2026-02-04T15:46:58.263862Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Fonction de recommandation (dans le cluster)","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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:47:05.082971Z","iopub.execute_input":"2026-02-04T15:47:05.083272Z","iopub.status.idle":"2026-02-04T15:47:05.088373Z","shell.execute_reply.started":"2026-02-04T15:47:05.083247Z","shell.execute_reply":"2026-02-04T15:47:05.087701Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### KNN","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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T16:00:08.391067Z","iopub.execute_input":"2026-02-04T16:00:08.391776Z","iopub.status.idle":"2026-02-04T16:00:08.400277Z","shell.execute_reply.started":"2026-02-04T16:00:08.391747Z","shell.execute_reply":"2026-02-04T16:00:08.399593Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T16:00:16.512030Z","iopub.execute_input":"2026-02-04T16:00:16.512566Z","iopub.status.idle":"2026-02-04T16:00:16.519346Z","shell.execute_reply.started":"2026-02-04T16:00:16.512540Z","shell.execute_reply":"2026-02-04T16:00:16.518527Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Comparaison","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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T16:00:18.958638Z","iopub.execute_input":"2026-02-04T16:00:18.958977Z","iopub.status.idle":"2026-02-04T16:00:20.259048Z","shell.execute_reply.started":"2026-02-04T16:00:18.958950Z","shell.execute_reply":"2026-02-04T16:00:20.258209Z"}},"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":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-04T15:53:39.564104Z","iopub.execute_input":"2026-02-04T15:53:39.564406Z","iopub.status.idle":"2026-02-04T15:53:40.666359Z","shell.execute_reply.started":"2026-02-04T15:53:39.564381Z","shell.execute_reply":"2026-02-04T15:53:40.665645Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}