{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":31254,"databundleVersionId":3103714}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\n\ndef print_data_status(file_path):\n    print(\"Đang đọc dữ liệu...\")\n    df = pd.read_csv(file_path)\n    \n    print(\"\\n\" + \"=\"*50)\n    print(\"📊 BÁO CÁO TÌNH TRẠNG DỮ LIỆU TRƯỚC KHI CLEAN\")\n    print(\"=\"*50)\n    \n    # 1. Kích thước tổng quan\n    print(\"\\n1. KÍCH THƯỚC DỮ LIỆU (SHAPE):\")\n    print(f\"- Số lượng mặt hàng (dòng): {df.shape[0]:,}\")\n    print(f\"- Số lượng đặc trưng (cột): {df.shape[1]}\")\n    \n    # 2. Kiểm tra Duplicate (Trùng lặp)\n    print(\"\\n2. DỮ LIỆU TRÙNG LẶP (DUPLICATES):\")\n    duplicates = df.duplicated().sum()\n    print(f\"- Số dòng bị trùng lặp hoàn toàn: {duplicates}\")\n    \n    # 3. Kiểm tra Missing Values (NaN/Null)\n    print(\"\\n3. GIÁ TRỊ KHUYẾT THIẾU THỰC SỰ (NaN/Null):\")\n    missing_data = df.isna().sum()\n    missing_cols = missing_data[missing_data > 0]\n    \n    if len(missing_cols) > 0:\n        for col, count in missing_cols.items():\n            percent = (count / df.shape[0]) * 100\n            print(f\"- Cột '{col}': Thiếu {count:,} dòng ({percent:.2f}%)\")\n    else:\n        print(\"- Không có giá trị NaN nào trong tập dữ liệu.\")\n        \n    # 4. Kiểm tra Dữ liệu rác/Nhiễu ẩn (-1 và 'Unknown')\n    print(\"\\n4. DỮ LIỆU NHIỄU ẨN (Giá trị -1 / 'Unknown'):\")\n    \n    # Các cặp cột cần kiểm tra sự đồng bộ\n    pairs_to_check = [\n        ('product_type_no', 'product_type_name'),\n        ('graphical_appearance_no', 'graphical_appearance_name'),\n        ('colour_group_code', 'colour_group_name'),\n        ('perceived_colour_value_id', 'perceived_colour_value_name'),\n        ('perceived_colour_master_id', 'perceived_colour_master_name')\n    ]\n    \n    found_noise = False\n    for code_col, name_col in pairs_to_check:\n        if code_col in df.columns and name_col in df.columns:\n            count_minus_1 = (df[code_col] == -1).sum()\n            count_unknown = (df[name_col] == 'Unknown').sum()\n            \n            if count_minus_1 > 0 or count_unknown > 0:\n                found_noise = True\n                print(f\"- Cặp thuộc tính: [{code_col}] & [{name_col}]\")\n                print(f\"   + Số lượng mã '-1': {count_minus_1:,}\")\n                print(f\"   + Số lượng nhãn 'Unknown': {count_unknown:,}\")\n                \n                # Kiểm tra xem có bị lệch pha không (Số lượng -1 và Unknown có bằng nhau không)\n                if count_minus_1 != count_unknown:\n                    print(\"   ⚠️ CẢNH BÁO: Đang có sự lệch pha dữ liệu giữa Mã và Tên!\")\n    \n    if not found_noise:\n        print(\"- Không phát hiện nhãn -1 hay 'Unknown' nào trong các cột kiểm tra.\")\n        \n    print(\"\\n\" + \"=\"*50)\n    print(\"Hoàn tất báo cáo!\")\n\n# Gọi hàm kiểm tra\nprint_data_status('/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/articles.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:19:51.651726Z","iopub.execute_input":"2026-03-13T19:19:51.652507Z","iopub.status.idle":"2026-03-13T19:19:52.436515Z","shell.execute_reply.started":"2026-03-13T19:19:51.652475Z","shell.execute_reply":"2026-03-13T19:19:52.435875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# 1. Đọc dữ liệu nguyên bản từ file csv\ndf = pd.read_csv('/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/articles.csv')\n\n# 2. Tạo một danh sách rỗng để chứa thông tin\ndata_info = []\n\n# 3. Lặp qua từng cột để lấy các thông số cần thiết\nfor col in df.columns:\n    data_info.append({\n        'Columns': col,\n        'Data type': str(df[col].dtype),\n        'Missing values': df[col].isna().sum(),\n        'Unique values': df[col].nunique()\n    })\n\n# 4. Chuyển đổi thành dạng Bảng (DataFrame)\ninfo_df = pd.DataFrame(data_info)\n\ndisplay(info_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:19:52.437971Z","iopub.execute_input":"2026-03-13T19:19:52.438293Z","iopub.status.idle":"2026-03-13T19:19:53.172103Z","shell.execute_reply.started":"2026-03-13T19:19:52.438271Z","shell.execute_reply":"2026-03-13T19:19:53.171404Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# ép kiểu String cho các cột ID\ndf = pd.read_csv('/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/articles.csv', \n                 dtype={'article_id': str, 'product_code': str})\n\n# Hiển thị 5 dòng đầu tiên để kiểm tra\ndisplay(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:19:53.173027Z","iopub.execute_input":"2026-03-13T19:19:53.173387Z","iopub.status.idle":"2026-03-13T19:19:53.739116Z","shell.execute_reply.started":"2026-03-13T19:19:53.173353Z","shell.execute_reply":"2026-03-13T19:19:53.738305Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport re\n\ndef clean_hm_articles(file_path):\n    df = pd.read_csv(file_path)\n    print(f\"Kích thước ban đầu: {df.shape}\\n\")\n    \n    # ==========================================\n    print(\"Xử lý Missing Values cơ bản\")\n    # Cột detail_desc là cột duy nhất có giá trị NaN thực sự (do thiếu mô tả)\n    # Ta điền 'no description' để các mô hình ngôn ngữ không bị lỗi khi tạo word embeddings.\n    df['detail_desc'] = df['detail_desc'].fillna('no description')\n    \n    # ==========================================\n    print(\"Điền khuyết đồng bộ các nhãn -1/Unknown\")\n    # Định nghĩa các cặp cột luôn phải đi liền với nhau\n    paired_cols = [\n        ('graphical_appearance_no', 'graphical_appearance_name'),\n        ('colour_group_code', 'colour_group_name'),\n        ('perceived_colour_value_id', 'perceived_colour_value_name'),\n        ('perceived_colour_master_id', 'perceived_colour_master_name')\n    ]\n    \n    # Hàm xử lý cục bộ cho từng nhóm sản phẩm\n    def impute_group(group, code_col, name_col):\n        # Lọc ra các sản phẩm có dữ liệu hợp lệ (khác -1)\n        valid = group[group[code_col] != -1]\n        \n        # Nếu cả nhóm này đều bị -1 giữ nguyên không ép kiểu\n        if len(valid) == 0:\n            return group \n        \n        # Tìm mã Code xuất hiện nhiều nhất trong nhóm này\n        mode_code = valid[code_col].mode().iloc[0]\n        # Trích xuất Tên (Name) tương ứng với mã Code đó\n        mode_name = valid[valid[code_col] == mode_code][name_col].iloc[0]\n        \n        # Thay thế đồng bộ cả 2 cột cho các dòng bị thiếu (-1 hoặc 'Unknown')\n        group.loc[group[code_col] == -1, code_col] = mode_code\n        group.loc[group[name_col] == 'Unknown', name_col] = mode_name\n        \n        return group\n\n    # Áp dụng hàm điền khuyết cho từng cặp thuộc tính\n    # Gom nhóm theo 'product_type_no' (loại sản phẩm chi tiết nhất: áo thun, quần đùi, tất...)\n    for code_col, name_col in paired_cols:\n        unknown_before = (df[code_col] == -1).sum()\n        if unknown_before > 0:\n            df = df.groupby('product_type_no', group_keys=False).apply(\n                lambda g: impute_group(g, code_col, name_col)\n            )\n            unknown_after = (df[code_col] == -1).sum()\n            print(f\"-> [{name_col}] Đã xử lý Unknown: giảm từ {unknown_before} xuống {unknown_after}.\")\n            \n    # Xử lý riêng biệt cho chính cột 'product_type' (nếu nó cũng chứa -1)\n    # Gom nhóm theo cấp độ lớn hơn là 'product_group_name' (Trang phục thân trên, thân dưới...)\n    if (df['product_type_no'] == -1).sum() > 0:\n        df = df.groupby('product_group_name', group_keys=False).apply(\n            lambda g: impute_group(g, 'product_type_no', 'product_type_name')\n        )\n        print(\"-> Đã xử lý Unknown cho [product_type_name].\\n\")\n\n    # ==========================================\n    print(\"Chuẩn hóa dữ liệu văn bản\")\n    # Làm sạch văn bản tự do để tối ưu cho các thuật toán NLP phía sau\n    def clean_text(text):\n        if not isinstance(text, str): return text\n        text = text.lower() # Chuyển thành chữ thường\n        text = re.sub(r'[^a-z0-9\\s]', '', text) # Chỉ giữ lại chữ và số\n        text = re.sub(r'\\s+', ' ', text).strip() # Cắt khoảng trắng thừa\n        return text\n        \n    df['prod_name'] = df['prod_name'].apply(clean_text)\n    df['detail_desc'] = df['detail_desc'].apply(clean_text)\n    print(\"-> Đã chuẩn hóa văn bản ở 'prod_name' và 'detail_desc'.\\n\")\n    \n    # ==========================================\n    df.to_csv('articles_cleaned_full.csv', index=False)\n    \n    return df\n\n# Gọi hàm để chạy pipeline\ncleaned_articles = clean_hm_articles('/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/articles.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:19:53.740982Z","iopub.execute_input":"2026-03-13T19:19:53.741309Z","iopub.status.idle":"2026-03-13T19:19:59.202590Z","shell.execute_reply.started":"2026-03-13T19:19:53.741286Z","shell.execute_reply":"2026-03-13T19:19:59.201709Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom wordcloud import WordCloud, STOPWORDS\nimport numpy as np\nimport math # Thêm thư viện math cho biểu đồ bong bóng vòng tròn\n\n# ==========================================\n# 0. CHUẨN BỊ DỮ LIỆU & CẤU HÌNH GIAO DIỆN\n# ==========================================\n# Tải dữ liệu từ file CSV\ndf = pd.read_csv('articles_cleaned_full.csv')\n\n# Cấu hình phong cách biểu đồ chung\nsns.set_theme(style=\"whitegrid\")\nplt.rcParams.update({'figure.max_open_warning': 0})\n\n# ==========================================\n# 1. PHÂN TÍCH CƠ CẤU DANH MỤC (PRODUCT MIX)\n# ==========================================\nfig, axes = plt.subplots(1, 2, figsize=(16, 6))\n\n# 1a. Biểu đồ thanh ngang: Top 10 Nhóm hàng chủ lực\ntop_product_groups = df['product_group_name'].value_counts().head(10)\nsns.barplot(x=top_product_groups.values, y=top_product_groups.index, \n            ax=axes[0], hue=top_product_groups.index, palette=\"viridis\", legend=False)\naxes[0].set_title(\"Top 10 nhóm hàng chủ yếu (Product Group)\", fontsize=14, weight='bold')\naxes[0].set_xlabel(\"Số lượng sản phẩm\")\naxes[0].set_ylabel(\"Nhóm hàng\")\n\n# 1b. Biểu đồ Donut: Phân chia theo Đối tượng khách hàng\nindex_group_counts = df['index_group_name'].value_counts()\naxes[1].pie(index_group_counts, labels=index_group_counts.index, autopct='%1.1f%%', \n            startangle=140, wedgeprops=dict(width=0.4), colors=sns.color_palette(\"Set2\"))\naxes[1].set_title(\"Đối tượng Khách hàng (Index Group)\", fontsize=14, weight='bold')\n\nplt.tight_layout()\nplt.show()\n\n# ==========================================\n# 2. PHÂN TÍCH MÀU SẮC & KIỂU DÁNG (AESTHETICS)\n# ==========================================\n# 2a. Biểu đồ cột: Top 15 Màu sắc phổ biến nhất\nplt.figure(figsize=(12, 5))\ntop_colors = df['colour_group_name'].value_counts().head(15)\nsns.barplot(x=top_colors.index, y=top_colors.values, \n            hue=top_colors.index, palette=\"mako\", legend=False)\nplt.title(\"Top 15 Màu sắc Phổ biến nhất\", fontsize=14, weight='bold')\nplt.xticks(rotation=45, ha='right')\nplt.ylabel(\"Số lượng sản phẩm\")\nplt.tight_layout()\nplt.show()\n\n# 2b. Biểu đồ Bong bóng Vòng tròn (Radial Layout): Phân bổ Họa tiết\n# Dùng context 'white' để bỏ lưới (grid) phía sau bong bóng cho đẹp\nwith sns.axes_style(\"white\"):\n    fig, ax = plt.subplots(figsize=(10, 10))\n    graphics_counts = df['graphical_appearance_name'].value_counts().head(10).reset_index()\n    graphics_counts.columns = ['Appearance', 'Count']\n\n    n = 10\n    x_pos, y_pos = [0.0], [0.0]\n\n    # Xếp 4 bong bóng vòng trong\n    angle_step = 2 * math.pi / 4\n    for i in range(4):\n        x_pos.append(2.5 * math.cos(i * angle_step + math.pi/4))\n        y_pos.append(2.5 * math.sin(i * angle_step + math.pi/4))\n        \n    # Xếp 5 bong bóng vòng ngoài\n    angle_step = 2 * math.pi / 5\n    for i in range(5):\n        x_pos.append(4.5 * math.cos(i * angle_step))\n        y_pos.append(4.5 * math.sin(i * angle_step))\n\n    # Scale theo diện tích (căn bậc 2)\n    max_count = graphics_counts['Count'].max()\n    sizes = [15000 * math.sqrt(count / max_count) for count in graphics_counts['Count']]\n    colors = sns.color_palette(\"Spectral\", n_colors=10)\n\n    ax.scatter(x=x_pos, y=y_pos, s=sizes, c=colors, alpha=0.85, edgecolors=\"white\", linewidth=2.5)\n\n    # Thêm text\n    for i in range(n):\n        ax.text(x_pos[i], y_pos[i], graphics_counts['Appearance'][i], \n                 ha='center', va='bottom', fontsize=11, fontweight='bold', color='black')\n        ax.text(x_pos[i], y_pos[i] - 0.25, f\"{graphics_counts['Count'][i]:,}\", \n                 ha='center', va='top', fontsize=10, color='#333333')\n\n    ax.set_xlim(-6, 6)\n    ax.set_ylim(-6, 6)\n    ax.axis('off')\n    plt.title(\"Phân bổ họa tiết / Kiểu dáng\", fontsize=16, weight='bold', pad=20)\n    plt.tight_layout()\n    plt.show()\n\n# ==========================================\n# 3. PHÂN TÍCH THÔNG ĐIỆP MARKETING\n# ==========================================\nplt.figure(figsize=(12, 6))\n\ntext = \" \".join(desc for desc in df['detail_desc'].dropna())\nstopwords = set(STOPWORDS)\nstopwords.update([\"with\", \"and\", \"in\", \"of\", \"a\", \"the\", \"for\", \"front\", \"back\", \n                  \"detail\", \"details\", \"cm\", \"mm\", \"size\", \"fit\", \"made\", \"one\", \"two\", \"an\"])\n\nwordcloud = WordCloud(width=1000, height=500, background_color='white', \n                      stopwords=stopwords, colormap='magma', max_words=80).generate(text)\n\nplt.imshow(wordcloud, interpolation='bilinear')\nplt.axis(\"off\")\nplt.title(\"Từ khóa Marketing\", fontsize=16, weight='bold', pad=20)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:19:59.203729Z","iopub.execute_input":"2026-03-13T19:19:59.204019Z","iopub.status.idle":"2026-03-13T19:20:05.925690Z","shell.execute_reply.started":"2026-03-13T19:19:59.203969Z","shell.execute_reply":"2026-03-13T19:20:05.924727Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport gc # Thư viện dọn rác bộ nhớ\n\nsns.set_theme(style=\"whitegrid\")\n\n# 1. Load các bảng dữ liệu\nprint(\"Đang tải dữ liệu...\")\ndf_articles = pd.read_csv('/kaggle/working/articles_cleaned_full.csv')\ndf_customers = pd.read_csv('/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/customers.csv')\n\n# Đọc Transactions: Chỉ lấy 1.5 triệu dòng ngẫu nhiên để làm EDA cho nhẹ máy\ndf_trans = pd.read_csv('/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/transactions_train.csv').sample(n=1500000, random_state=42)\n\n# Chuyển đổi t_dat sang định dạng Datetime\ndf_trans['t_dat'] = pd.to_datetime(df_trans['t_dat'])\n\n# 2. Nối (Merge) 3 bảng lại với nhau\nprint(\"Đang nối bảng...\")\n# Nối Transaction với Articles (Dựa trên article_id)\ndf_merged = pd.merge(df_trans, df_articles[['article_id', 'product_group_name', 'index_group_name', 'colour_group_name', 'garment_group_name']], on='article_id', how='left')\n\n# Nối tiếp kết quả với Customers (Dựa trên customer_id)\ndf_merged = pd.merge(df_merged, df_customers[['customer_id', 'age', 'club_member_status']], on='customer_id', how='left')\n\n# Dọn dẹp RAM các biến không còn dùng\ndel df_trans\ngc.collect()\n\nprint(\"Bảng dữ liệu tổng hợp đã sẵn sàng!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:20:05.926929Z","iopub.execute_input":"2026-03-13T19:20:05.927234Z","iopub.status.idle":"2026-03-13T19:20:51.812344Z","shell.execute_reply.started":"2026-03-13T19:20:05.927209Z","shell.execute_reply":"2026-03-13T19:20:51.811536Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n\n# Tạo nhóm tuổi\nbins = [15, 25, 35, 45, 55, 65, 100]\nlabels = ['15-25', '26-35', '36-45', '46-55', '56-65', '65+']\ndf_merged['age_group'] = pd.cut(df_merged['age'], bins=bins, labels=labels)\n\n# Tính tỷ lệ % mua từng nhóm đồ (Ladieswear, Divided, Baby...) theo từng nhóm tuổi\nage_item_matrix = pd.crosstab(df_merged['age_group'], df_merged['index_group_name'], normalize='index') * 100\n\n# Vẽ Biểu đồ nhiệt (Heatmap) để nhìn rõ sự thật\nplt.figure(figsize=(10, 6))\nsns.heatmap(age_item_matrix, annot=True, fmt=\".1f\", cmap=\"YlGnBu\", linewidths=.5)\nplt.title(\"Tỷ trọng mua sắm các danh mục theo từng nhóm tuổi(%)\", fontsize=14, fontweight='bold')\nplt.xlabel(\"Nhóm Sản Phẩm (Index Group)\")\nplt.ylabel(\"Độ Tuổi Khách Hàng\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:40:31.165526Z","iopub.execute_input":"2026-03-13T19:40:31.166318Z","iopub.status.idle":"2026-03-13T19:40:31.649462Z","shell.execute_reply.started":"2026-03-13T19:40:31.166286Z","shell.execute_reply":"2026-03-13T19:40:31.648805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nsns.set_theme(style=\"whitegrid\")\n\n# Tạo nhóm tuổi nếu chưa có\nbins = [15, 25, 35, 45, 55, 65, 100]\nlabels = ['15-25', '26-35', '36-45', '46-55', '56-65', '65+']\nif 'age_group' not in df_merged.columns:\n    df_merged['age_group'] = pd.cut(df_merged['age'], bins=bins, labels=labels)\n\n# Tính tổng tiền (sum of price) theo nhóm tuổi\nrevenue_age = df_merged.groupby('age_group')['price'].sum()\n\n# Chỉ vẽ 1 biểu đồ Tròn (Pie chart)\nplt.figure(figsize=(8, 8))\nplt.pie(revenue_age.values, labels=revenue_age.index, autopct='%1.1f%%', \n        colors=sns.color_palette('magma', len(labels)), startangle=90, explode=[0.05]*len(labels))\nplt.title('Tỷ trọng tổng doanh thu theo nhóm Tuổi', fontsize=15, fontweight='bold')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:39:01.108353Z","iopub.execute_input":"2026-03-13T19:39:01.109051Z","iopub.status.idle":"2026-03-13T19:39:01.255826Z","shell.execute_reply.started":"2026-03-13T19:39:01.108989Z","shell.execute_reply":"2026-03-13T19:39:01.255196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tạo cột tháng-năm để phân tích xu hướng\ndf_merged['year_month'] = df_merged['t_dat'].dt.to_period('M').astype(str)\n\nplt.figure(figsize=(14, 6))\n\n# Lọc các màu sắc nổi bật muốn phân tích\ntarget_colors = ['Black', 'Yellow', 'Pink', 'Light Blue']\ncolor_trend = df_merged[df_merged['colour_group_name'].isin(target_colors)]\ncolor_trend_grouped = color_trend.groupby(['year_month', 'colour_group_name']).size().unstack()\n\n# Vẽ biểu đồ đường\ncolor_trend_grouped.plot(kind='line', marker='o', linewidth=2.5, \n                         color=['black', 'lightblue', 'pink', 'gold'], ax=plt.gca())\n\nplt.title('Xu hướng mua sắm màu sắc theo thời gian', fontsize=15, fontweight='bold')\nplt.ylabel('Số lượng bán ra', fontsize=12)\nplt.xlabel('Tháng', fontsize=12)\nplt.xticks(rotation=45) # Xoay nhãn trục X cho dễ đọc\nplt.legend(title='Màu sắc', bbox_to_anchor=(1.05, 1), loc='upper left')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:39:11.302718Z","iopub.execute_input":"2026-03-13T19:39:11.303204Z","iopub.status.idle":"2026-03-13T19:39:12.661254Z","shell.execute_reply.started":"2026-03-13T19:39:11.303178Z","shell.execute_reply":"2026-03-13T19:39:12.660482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from collections import Counter\nimport itertools\n\nprint(\"Đang phân tích các giỏ hàng (Basket Analysis)...\")\n\n# Gom nhóm các mặt hàng được 1 khách hàng mua trong CÙNG 1 NGÀY\n# Chú ý: Dùng dropna để loại bỏ các dòng bị thiếu tên nhóm hàng\nbaskets = df_merged.dropna(subset=['garment_group_name']).groupby(['customer_id', 't_dat'])['garment_group_name'].apply(list)\n\n# Đếm các cặp sản phẩm xuất hiện cùng nhau\npair_counter = Counter()\n\nfor basket in baskets:\n    # Lấy các mặt hàng duy nhất trong giỏ (để tránh việc khách mua 2 cái áo thun tính là 1 cặp)\n    unique_items = sorted(set(basket))\n    if len(unique_items) > 1:\n        # Tạo tất cả các tổ hợp chập 2 của các món đồ trong giỏ\n        pairs = itertools.combinations(unique_items, 2)\n        pair_counter.update(pairs)\n\n# Chuyển kết quả thành DataFrame để vẽ biểu đồ\ntop_10_pairs = pair_counter.most_common(10)\npairs_df = pd.DataFrame(top_10_pairs, columns=['Pair', 'Frequency'])\n# Định dạng lại tên cặp cho đẹp\npairs_df['Pair'] = pairs_df['Pair'].apply(lambda x: f\"{x[0]} + {x[1]}\")\n\n# Vẽ biểu đồ ngang\nplt.figure(figsize=(12, 6))\nsns.barplot(x='Frequency', y='Pair', data=pairs_df, palette='cubehelix')\nplt.title('Top 10 cặp mặt hàng thường được mua cùng nhau nhất', fontsize=14, fontweight='bold')\nplt.xlabel('Số lần xuất hiện cùng nhau trong giỏ hàng')\nplt.ylabel('Cặp sản phẩm')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-13T19:39:39.897675Z","iopub.execute_input":"2026-03-13T19:39:39.897984Z","iopub.status.idle":"2026-03-13T19:40:12.689810Z","shell.execute_reply.started":"2026-03-13T19:39:39.897957Z","shell.execute_reply":"2026-03-13T19:40:12.688826Z"}},"outputs":[],"execution_count":null}]}