{"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":"none","dataSources":[{"sourceType":"competition","sourceId":31254,"databundleVersionId":3103714}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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,"scrolled":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:05.184079Z","iopub.execute_input":"2026-03-17T08:47:05.184400Z","iopub.status.idle":"2026-03-17T08:47:41.418159Z","shell.execute_reply.started":"2026-03-17T08:47:05.184375Z","shell.execute_reply":"2026-03-17T08:47:41.416682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:41.419875Z","iopub.execute_input":"2026-03-17T08:47:41.420409Z","iopub.status.idle":"2026-03-17T08:47:41.424176Z","shell.execute_reply.started":"2026-03-17T08:47:41.420386Z","shell.execute_reply":"2026-03-17T08:47:41.423394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pd.set_option('display.float_format', '{:.0f}'.format)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:41.425004Z","iopub.execute_input":"2026-03-17T08:47:41.425203Z","iopub.status.idle":"2026-03-17T08:47:41.439583Z","shell.execute_reply.started":"2026-03-17T08:47:41.425183Z","shell.execute_reply":"2026-03-17T08:47:41.438741Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Customers","metadata":{}},{"cell_type":"code","source":"customer_df = pd.read_csv('/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/customers.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:41.440490Z","iopub.execute_input":"2026-03-17T08:47:41.440731Z","iopub.status.idle":"2026-03-17T08:47:44.362727Z","shell.execute_reply.started":"2026-03-17T08:47:41.440711Z","shell.execute_reply":"2026-03-17T08:47:44.361992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"customer_df.shape\ncustomer_df.info()\ncustomer_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:44.364554Z","iopub.execute_input":"2026-03-17T08:47:44.364827Z","iopub.status.idle":"2026-03-17T08:47:44.606583Z","shell.execute_reply.started":"2026-03-17T08:47:44.364807Z","shell.execute_reply":"2026-03-17T08:47:44.605634Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## NaN\n","metadata":{}},{"cell_type":"code","source":"customer_df[customer_df[\"fashion_news_frequency\"].isna()].shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:44.607589Z","iopub.execute_input":"2026-03-17T08:47:44.607803Z","iopub.status.idle":"2026-03-17T08:47:44.663708Z","shell.execute_reply.started":"2026-03-17T08:47:44.607786Z","shell.execute_reply":"2026-03-17T08:47:44.662457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"customer_df[\n    (customer_df[\"Active\"].isna()) &\n    (customer_df[\"fashion_news_frequency\"].notna())\n].shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:44.664509Z","iopub.execute_input":"2026-03-17T08:47:44.664762Z","iopub.status.idle":"2026-03-17T08:47:44.796670Z","shell.execute_reply.started":"2026-03-17T08:47:44.664741Z","shell.execute_reply":"2026-03-17T08:47:44.795942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Có nhiều trường null có thể là do không thu thập được thông tin / người dùng không cung cấp\nnan_count = customer_df.isna().sum()\nnan_percent = customer_df.isna().mean() * 100\n\nnan_summary = pd.DataFrame({\n    \"NaN_count\": nan_count,\n    \"NaN_percent\": nan_percent\n})\n\nprint(nan_summary)\n\nplt.figure(figsize=(8,4))\n\nbars = plt.barh(nan_count.index, nan_count)\n\n# thêm text % vào cuối mỗi cột\nfor i, v in enumerate(nan_count):\n    percent = nan_percent.iloc[i]\n    plt.text(v, i, f\" {percent:.2f}%\", va=\"center\")\n\nplt.xlabel(\"Number of NaN\")\nplt.title(\"Missing Values per Column\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:44.797570Z","iopub.execute_input":"2026-03-17T08:47:44.797806Z","iopub.status.idle":"2026-03-17T08:47:45.339588Z","shell.execute_reply.started":"2026-03-17T08:47:44.797781Z","shell.execute_reply":"2026-03-17T08:47:45.338901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cols = [\"FN\", \"Active\", \"club_member_status\", \"fashion_news_frequency\"]\n\nnan_pattern = customer_df[cols].isna()\n\nsummary = nan_pattern.value_counts().reset_index()\nsummary.columns = cols + [\"count\"]\n\n# đổi True -> NaN, False -> _\nsummary[cols] = summary[cols].replace({True: \"NaN\", False: \"_\"})\n\nsummary[\"percent\"] = (summary[\"count\"] / len(customer_df) * 100).round(2)\n\n# ---- thêm dòng FN NaN riêng ----\nfn_nan_count = customer_df[\"FN\"].isna().sum()\n\nfn_row = pd.DataFrame({\n    \"FN\": [\"NaN\"],\n    \"Active\": [\"_\"],\n    \"club_member_status\": [\"_\"],\n    \"fashion_news_frequency\": [\"_\"],\n    \"count\": [fn_nan_count],\n    \"percent\": [round(fn_nan_count / len(customer_df) * 100, 2)]\n})\n\nsummary = pd.concat([fn_row, summary], ignore_index=True)\n\n# ---- đếm số NaN ----\nsummary[\"nan_total\"] = (summary[cols] == \"NaN\").sum(axis=1)\n\n# ---- sort ----\nsummary = summary.sort_values([\"nan_total\", \"count\"], ascending=[True, False])\n\n# bỏ cột phụ\nsummary = summary.drop(columns=\"nan_total\")\n\nprint(summary)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:45.340455Z","iopub.execute_input":"2026-03-17T08:47:45.340743Z","iopub.status.idle":"2026-03-17T08:47:45.550770Z","shell.execute_reply.started":"2026-03-17T08:47:45.340715Z","shell.execute_reply":"2026-03-17T08:47:45.549451Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def describe_df(df):\n    list_item = []\n    for col in df.columns:\n        list_item.append([\n            col,\n            df[col].dtype,\n            df[col].isna().sum(),\n            round(df[col].isna().sum()/len(df[col])*100, 2),\n            df[col].nunique(),\n            round(df[col].nunique()/len(df[col])*100, 2),\n            list(df[col].unique()[:5])\n        ])\n    return pd.DataFrame(\n        columns=['feature', 'type', '# null', '% null', '# unique', '% unique', 'sample'],\n        data = list_item\n    )\n\n\nassert customer_df.customer_id.nunique() == customer_df.shape[0]\ndescribe_df(customer_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:45.551911Z","iopub.execute_input":"2026-03-17T08:47:45.552138Z","iopub.status.idle":"2026-03-17T08:47:49.725503Z","shell.execute_reply.started":"2026-03-17T08:47:45.552119Z","shell.execute_reply":"2026-03-17T08:47:49.724904Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## EDA","metadata":{}},{"cell_type":"code","source":"print(len(customer_df))\nprint(customer_df.columns)\nprint(f\"Số lượng khách: \", customer_df['customer_id'].nunique(dropna=False))\n\nprint(\"Số lượng giá trị unique của từng cột:\\n\")\n\n# Phát hiện nhiều cột chứa NaN nma hình như khphai lỗi để tối về plot hình thử xem\nfor col in customer_df.columns:\n    unique_count = customer_df[col].nunique(dropna=False)\n    print(f\"Cột {col}: {unique_count}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:49.726338Z","iopub.execute_input":"2026-03-17T08:47:49.726567Z","iopub.status.idle":"2026-03-17T08:47:51.121138Z","shell.execute_reply.started":"2026-03-17T08:47:49.726548Z","shell.execute_reply":"2026-03-17T08:47:51.120205Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_pie_distribution(df, column, explode=None, colors=None):\n\n    counts = df[column].value_counts(dropna=False)\n\n    if explode is None:\n        explode = [0] * len(counts)\n\n    plt.figure(figsize=(6,6))\n\n    wedges, texts, autotexts = plt.pie(\n        counts,\n        colors=colors,\n        autopct=\"%1.3f%%\",\n        startangle=90,\n        pctdistance=0.6,\n        explode=explode\n    )\n\n    plt.legend(\n        wedges,\n        counts.index,\n        title=column,\n        loc=\"center left\",\n        bbox_to_anchor=(1, 0.5)\n    )\n    plt.title(f\"{column} Distribution\", pad=30)\n    plt.tight_layout()\n    plt.show()\n\n    print(counts)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:51.122034Z","iopub.execute_input":"2026-03-17T08:47:51.122212Z","iopub.status.idle":"2026-03-17T08:47:51.128490Z","shell.execute_reply.started":"2026-03-17T08:47:51.122194Z","shell.execute_reply":"2026-03-17T08:47:51.127754Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_pie_distribution(customer_df, \"fashion_news_frequency\", explode=[0, 0, 0.3, 0.5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:51.129497Z","iopub.execute_input":"2026-03-17T08:47:51.129747Z","iopub.status.idle":"2026-03-17T08:47:51.267946Z","shell.execute_reply.started":"2026-03-17T08:47:51.129726Z","shell.execute_reply":"2026-03-17T08:47:51.266868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_pie_distribution(\n    customer_df,\n    \"club_member_status\",\n    explode=[0, 0, 0.3, 0.5]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:51.271031Z","iopub.execute_input":"2026-03-17T08:47:51.271285Z","iopub.status.idle":"2026-03-17T08:47:51.400810Z","shell.execute_reply.started":"2026-03-17T08:47:51.271259Z","shell.execute_reply":"2026-03-17T08:47:51.399826Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_pie_distribution(customer_df, \"FN\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:51.401681Z","iopub.execute_input":"2026-03-17T08:47:51.401919Z","iopub.status.idle":"2026-03-17T08:47:51.495237Z","shell.execute_reply.started":"2026-03-17T08:47:51.401892Z","shell.execute_reply":"2026-03-17T08:47:51.494517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_pie_distribution(customer_df, \"Active\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:51.496182Z","iopub.execute_input":"2026-03-17T08:47:51.496462Z","iopub.status.idle":"2026-03-17T08:47:51.586620Z","shell.execute_reply.started":"2026-03-17T08:47:51.496436Z","shell.execute_reply":"2026-03-17T08:47:51.585834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nage_counts = customer_df[\"age\"].value_counts().sort_index()\n\nmean_age = customer_df[\"age\"].mean()\nmedian_age = customer_df[\"age\"].median()\n\nplt.figure(figsize=(12,5))\n\nplt.bar(age_counts.index, age_counts.values)\n\n# vạch mean\nplt.axvline(mean_age, color=\"red\",linestyle=\"--\", label=f\"Mean: {mean_age:.1f}\")\n\n# vạch median\nplt.axvline(median_age, color=\"red\",linestyle=\"-.\", label=f\"Median: {median_age:.1f}\")\n\nplt.title(\"Age Distribution\")\nplt.xlabel(\"Age\")\nplt.ylabel(\"Count\")\n\nplt.xticks(range(int(age_counts.index.min()), int(age_counts.index.max())+1, 3))\n\nplt.legend()\n\nplt.tight_layout()\nplt.show()\n\nprint(\"Age range:\", customer_df[\"age\"].min(), \"-\", customer_df[\"age\"].max())\nprint(\"Mean age:\", round(mean_age,2))\nprint(\"Median age:\", median_age)\nprint(\"Most popular age:\", age_counts.idxmax(), \"- Count:\", age_counts.max())\nprint(\"Least popular age:\", age_counts.idxmin(), \"- Count:\", age_counts.min())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:51.587374Z","iopub.execute_input":"2026-03-17T08:47:51.587543Z","iopub.status.idle":"2026-03-17T08:47:51.900138Z","shell.execute_reply.started":"2026-03-17T08:47:51.587527Z","shell.execute_reply":"2026-03-17T08:47:51.899007Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n\ndf_corr = customer_df.copy()\n\n# bỏ cột ID và postal_code\ndf_corr = df_corr.drop(columns=[\"customer_id\", \"postal_code\"])\n\n# encode categorical\ndf_corr[\"club_member_status\"] = df_corr[\"club_member_status\"].astype(\"category\").cat.codes\ndf_corr[\"fashion_news_frequency\"] = df_corr[\"fashion_news_frequency\"].astype(\"category\").cat.codes\n\n# correlation\ncorr = df_corr.corr()\n\nplt.figure(figsize=(7,5))\nsns.heatmap(corr, annot=True, cmap=\"coolwarm\")\n\nplt.title(\"Customer Feature Correlation\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:51.901465Z","iopub.execute_input":"2026-03-17T08:47:51.901791Z","iopub.status.idle":"2026-03-17T08:47:52.571940Z","shell.execute_reply.started":"2026-03-17T08:47:51.901765Z","shell.execute_reply":"2026-03-17T08:47:52.571302Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import matplotlib.pyplot as plt\n\n# # ---- age group ----\n# bins = [16,20,30,40,50,60,70,100]\n# labels = [\"16-20\",\"20-30\",\"30-40\",\"40-50\",\"50-60\",\"60-70\",\"70+\"]\n\n# customer_df[\"age_group\"] = pd.cut(customer_df[\"age\"], bins=bins, labels=labels, right=False)\n\n# # ---- giữ NaN để phân tích ----\n# customer_df[\"fashion_news_freq_full\"] = customer_df[\"fashion_news_frequency\"].fillna(\"NaN\")\n\n# # ---- bảng số lượng ----\n# count_table = pd.crosstab(\n#     customer_df[\"age_group\"],\n#     customer_df[\"fashion_news_freq_full\"]\n# )\n\n# print(\"Count table:\")\n# print(count_table)\n\n# # ---- bảng phần trăm ----\n# percent_table = pd.crosstab(\n#     customer_df[\"age_group\"],\n#     customer_df[\"fashion_news_freq_full\"],\n#     normalize=\"index\"\n# ) * 100\n\n# percent_table = percent_table.round(1)\n\n# print(\"\\nPercent table:\")\n# print(percent_table)\n\n# # ---- vẽ bar chart ----\n# plot_table = percent_table.drop(columns=[\"NaN\", \"Monthly\"], errors=\"ignore\")\n\n# ax = plot_table.plot(kind=\"bar\", figsize=(9,5))\n\n# plt.title(\"Fashion News Frequency by Age Group\")\n# plt.xlabel(\"Age Group\")\n# plt.ylabel(\"Percentage (%)\")\n\n# # legend ra ngoài\n# plt.legend(title=\"Fashion News Frequency\", bbox_to_anchor=(1.02,1), loc=\"upper left\")\n\n# # % trên đầu cột\n# for container in ax.containers:\n#     ax.bar_label(container, fmt=\"%.1f%%\", padding=3, fontsize=9)\n\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:52.573027Z","iopub.execute_input":"2026-03-17T08:47:52.573252Z","iopub.status.idle":"2026-03-17T08:47:52.577879Z","shell.execute_reply.started":"2026-03-17T08:47:52.573220Z","shell.execute_reply":"2026-03-17T08:47:52.577051Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Transaction","metadata":{}},{"cell_type":"code","source":"path = \"/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/transactions_train.csv\"\ntrans_df = pd.read_csv(path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:47:52.579073Z","iopub.execute_input":"2026-03-17T08:47:52.579320Z","iopub.status.idle":"2026-03-17T08:48:19.772427Z","shell.execute_reply.started":"2026-03-17T08:47:52.579299Z","shell.execute_reply":"2026-03-17T08:48:19.771430Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trans_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:48:19.773398Z","iopub.execute_input":"2026-03-17T08:48:19.773739Z","iopub.status.idle":"2026-03-17T08:48:19.782797Z","shell.execute_reply.started":"2026-03-17T08:48:19.773709Z","shell.execute_reply":"2026-03-17T08:48:19.781802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trans_df.shape\ntrans_df.info()\ntrans_df.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:48:19.783888Z","iopub.execute_input":"2026-03-17T08:48:19.784179Z","iopub.status.idle":"2026-03-17T08:48:21.866312Z","shell.execute_reply.started":"2026-03-17T08:48:19.784156Z","shell.execute_reply":"2026-03-17T08:48:21.865551Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#assert trans_df.customer_id.nunique() == trans_df.shape[0]\ndescribe_df(trans_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:48:21.867157Z","iopub.execute_input":"2026-03-17T08:48:21.867352Z","iopub.status.idle":"2026-03-17T08:48:52.467514Z","shell.execute_reply.started":"2026-03-17T08:48:21.867335Z","shell.execute_reply":"2026-03-17T08:48:52.466715Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trans_df.duplicated().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:48:52.468429Z","iopub.execute_input":"2026-03-17T08:48:52.468693Z","iopub.status.idle":"2026-03-17T08:49:09.202995Z","shell.execute_reply.started":"2026-03-17T08:48:52.468671Z","shell.execute_reply":"2026-03-17T08:49:09.202394Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trans_df[trans_df.duplicated()]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:49:09.203788Z","iopub.execute_input":"2026-03-17T08:49:09.204031Z","iopub.status.idle":"2026-03-17T08:49:25.686737Z","shell.execute_reply.started":"2026-03-17T08:49:09.204010Z","shell.execute_reply":"2026-03-17T08:49:25.685725Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trans_df[trans_df.duplicated(keep=False)].sort_values(\n    [\"customer_id\",\"article_id\",\"t_dat\"]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:49:25.687680Z","iopub.execute_input":"2026-03-17T08:49:25.687880Z","iopub.status.idle":"2026-03-17T08:49:47.876148Z","shell.execute_reply.started":"2026-03-17T08:49:25.687860Z","shell.execute_reply":"2026-03-17T08:49:47.875323Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(trans_df[\"t_dat\"].min(), \"-\", trans_df[\"t_dat\"].max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:49:47.877856Z","iopub.execute_input":"2026-03-17T08:49:47.878133Z","iopub.status.idle":"2026-03-17T08:49:50.717264Z","shell.execute_reply.started":"2026-03-17T08:49:47.878115Z","shell.execute_reply":"2026-03-17T08:49:50.716531Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"purchase_count = trans_df.groupby(\"customer_id\").size()\n\nprint(purchase_count.describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:49:50.718069Z","iopub.execute_input":"2026-03-17T08:49:50.718277Z","iopub.status.idle":"2026-03-17T08:50:00.483225Z","shell.execute_reply.started":"2026-03-17T08:49:50.718260Z","shell.execute_reply":"2026-03-17T08:50:00.482376Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"purchase_count.hist(bins=50)\nplt.title(\"Purchase Frequency Distribution\")\nplt.xlabel(\"Number of purchases\")\nplt.ylabel(\"Customers\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:00.484346Z","iopub.execute_input":"2026-03-17T08:50:00.484640Z","iopub.status.idle":"2026-03-17T08:50:00.681464Z","shell.execute_reply.started":"2026-03-17T08:50:00.484587Z","shell.execute_reply":"2026-03-17T08:50:00.680704Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# convert sang datetime\ntrans_df[\"t_dat\"] = pd.to_datetime(trans_df[\"t_dat\"], errors=\"coerce\")\n\n# các dòng có date lỗi\ninvalid_date = trans_df[trans_df[\"t_dat\"].isna()]\n\nprint(\"Invalid t_dat rows:\", len(invalid_date))\ninvalid_date.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:00.682865Z","iopub.execute_input":"2026-03-17T08:50:00.683146Z","iopub.status.idle":"2026-03-17T08:50:03.123236Z","shell.execute_reply.started":"2026-03-17T08:50:00.683118Z","shell.execute_reply":"2026-03-17T08:50:03.122509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"invalid_customer = trans_df[\n    ~trans_df[\"customer_id\"].isin(customer_df[\"customer_id\"])\n]\n\nprint(\"Invalid customer_id rows:\", len(invalid_customer))\ninvalid_customer.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:03.124151Z","iopub.execute_input":"2026-03-17T08:50:03.124426Z","iopub.status.idle":"2026-03-17T08:50:09.241028Z","shell.execute_reply.started":"2026-03-17T08:50:03.124397Z","shell.execute_reply":"2026-03-17T08:50:09.240423Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"articles_df = pd.read_csv(\"/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/articles.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:09.241829Z","iopub.execute_input":"2026-03-17T08:50:09.242045Z","iopub.status.idle":"2026-03-17T08:50:09.637622Z","shell.execute_reply.started":"2026-03-17T08:50:09.242022Z","shell.execute_reply":"2026-03-17T08:50:09.636855Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"invalid_article = trans_df[\n    ~trans_df[\"article_id\"].isin(articles_df[\"article_id\"])\n]\n\nprint(\"Invalid article_id rows:\", len(invalid_article))\ninvalid_article.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:09.638422Z","iopub.execute_input":"2026-03-17T08:50:09.638614Z","iopub.status.idle":"2026-03-17T08:50:09.997842Z","shell.execute_reply.started":"2026-03-17T08:50:09.638578Z","shell.execute_reply":"2026-03-17T08:50:09.997110Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trans_df[\"price\"].max()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:09.998709Z","iopub.execute_input":"2026-03-17T08:50:09.998903Z","iopub.status.idle":"2026-03-17T08:50:10.030989Z","shell.execute_reply.started":"2026-03-17T08:50:09.998885Z","shell.execute_reply":"2026-03-17T08:50:10.030197Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trans_df[\"price\"].min()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.031949Z","iopub.execute_input":"2026-03-17T08:50:10.032228Z","iopub.status.idle":"2026-03-17T08:50:10.065653Z","shell.execute_reply.started":"2026-03-17T08:50:10.032198Z","shell.execute_reply":"2026-03-17T08:50:10.064695Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_pie_distribution(trans_df, column=\"sales_channel_id\", explode=None, colors=None)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.066651Z","iopub.execute_input":"2026-03-17T08:50:10.066911Z","iopub.status.idle":"2026-03-17T08:50:10.251562Z","shell.execute_reply.started":"2026-03-17T08:50:10.066889Z","shell.execute_reply":"2026-03-17T08:50:10.250690Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import matplotlib.pyplot as plt\n\n# # 1️⃣ Convert t_dat sang datetime\n# trans_df[\"t_dat\"] = pd.to_datetime(trans_df[\"t_dat\"])\n\n# # 2️⃣ Tạo cột year_month\n# trans_df[\"year_month\"] = trans_df[\"t_dat\"].dt.to_period(\"M\")\n\n# # 3️⃣ Đếm số transaction theo tháng\n# monthly_sales = trans_df.groupby(\"year_month\").size()\n\n# # 4️⃣ Convert index để plot\n# monthly_sales.index = monthly_sales.index.astype(str)\n\n# # 5️⃣ Vẽ line chart\n# plt.figure(figsize=(10,5))\n\n# plt.plot(monthly_sales.index, monthly_sales.values, marker=\"o\")\n\n# plt.title(\"Number of Transactions per Month\")\n# plt.xlabel(\"Month\")\n# plt.ylabel(\"Transactions\")\n\n# plt.xticks(rotation=45)\n\n# # 6️⃣ Hiển thị số trên từng điểm\n# for x, y in zip(monthly_sales.index, monthly_sales.values):\n#     plt.text(x, y, f\"{y}\", ha=\"center\", va=\"bottom\", fontsize=8)\n\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.252571Z","iopub.execute_input":"2026-03-17T08:50:10.252835Z","iopub.status.idle":"2026-03-17T08:50:10.257152Z","shell.execute_reply.started":"2026-03-17T08:50:10.252808Z","shell.execute_reply":"2026-03-17T08:50:10.256103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import matplotlib.pyplot as plt\n\n# # 1️⃣ Convert date\n# trans_df[\"t_dat\"] = pd.to_datetime(trans_df[\"t_dat\"])\n\n# # 2️⃣ Tạo cột tháng\n# trans_df[\"year_month\"] = trans_df[\"t_dat\"].dt.to_period(\"M\")\n\n# # 3️⃣ Group theo tháng và sales channel\n# monthly_sales = (\n#     trans_df\n#     .groupby([\"year_month\", \"sales_channel_id\"])\n#     .size()\n#     .unstack()\n# )\n\n# # đổi index sang string để plot\n# monthly_sales.index = monthly_sales.index.astype(str)\n\n# # rename cho dễ hiểu\n# monthly_sales = monthly_sales.rename(columns={\n#     1: \"Store\",\n#     2: \"Online\"\n# })\n\n# # 4️⃣ Vẽ line chart\n# plt.figure(figsize=(10,5))\n\n# plt.plot(monthly_sales.index, monthly_sales[\"Store\"], marker=\"o\", label=\"Store\")\n# plt.plot(monthly_sales.index, monthly_sales[\"Online\"], marker=\"o\", label=\"Online\")\n\n# plt.title(\"Transactions per Month (Online vs Store)\")\n# plt.xlabel(\"Month\")\n# plt.ylabel(\"Transactions\")\n\n# plt.xticks(rotation=45)\n\n# plt.legend()\n\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.258133Z","iopub.execute_input":"2026-03-17T08:50:10.258456Z","iopub.status.idle":"2026-03-17T08:50:10.272665Z","shell.execute_reply.started":"2026-03-17T08:50:10.258436Z","shell.execute_reply":"2026-03-17T08:50:10.271859Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# import pandas as pd\n# import matplotlib.pyplot as plt\n\n# # 1️⃣ Convert date\n# trans_df[\"t_dat\"] = pd.to_datetime(trans_df[\"t_dat\"])\n\n# # 2️⃣ Tạo cột month\n# trans_df[\"year_month\"] = trans_df[\"t_dat\"].dt.to_period(\"M\")\n\n# # 3️⃣ Group theo month + sales channel\n# monthly_sales = (\n#     trans_df\n#     .groupby([\"year_month\", \"sales_channel_id\"])\n#     .size()\n#     .unstack()\n# )\n\n# # 4️⃣ đổi tên cột\n# monthly_sales = monthly_sales.rename(columns={\n#     1: \"Store\",\n#     2: \"Online\"\n# })\n\n# # 5️⃣ fill NaN để line không bị đứt\n# monthly_sales = monthly_sales.fillna(0)\n\n# # 6️⃣ convert index sang string\n# monthly_sales.index = monthly_sales.index.astype(str)\n\n# # 7️⃣ in bảng số lượng\n# print(\"Monthly Sales Table:\")\n# print(monthly_sales)\n\n# # 8️⃣ vẽ chart\n# plt.figure(figsize=(10,5))\n\n# plt.plot(monthly_sales.index, monthly_sales[\"Store\"], marker=\"o\", label=\"Store\")\n# plt.plot(monthly_sales.index, monthly_sales[\"Online\"], marker=\"o\", label=\"Online\")\n\n# plt.title(\"Transactions per Month (Online vs Store)\")\n# plt.xlabel(\"Month\")\n# plt.ylabel(\"Transactions\")\n\n# plt.xticks(rotation=45)\n\n# plt.legend()\n\n# # 9️⃣ in số lên từng điểm\n# for x, y in zip(monthly_sales.index, monthly_sales[\"Store\"]):\n#     plt.text(x, y, f\"{int(y)}\", ha=\"center\", va=\"bottom\", fontsize=8)\n\n# for x, y in zip(monthly_sales.index, monthly_sales[\"Online\"]):\n#     plt.text(x, y, f\"{int(y)}\", ha=\"center\", va=\"bottom\", fontsize=8)\n\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.273860Z","iopub.execute_input":"2026-03-17T08:50:10.274356Z","iopub.status.idle":"2026-03-17T08:50:10.292829Z","shell.execute_reply.started":"2026-03-17T08:50:10.274327Z","shell.execute_reply":"2026-03-17T08:50:10.291961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# def show_article_image(article_id, base_path=\"/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/images\"):\n    \n#     article_id = str(article_id)\n    \n#     folder = article_id[:3]\n    \n#     img_path = os.path.join(base_path, folder, f\"{article_id}.jpg\")\n    \n#     if os.path.exists(img_path):\n#         img = Image.open(img_path)\n        \n#         plt.imshow(img)\n#         plt.axis(\"off\")\n#         plt.title(f\"Article {article_id}\")\n#         plt.show()\n        \n#     else:\n#         print(\"Image not found:\", img_path)\n\n# show_article_image('0711483004')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.293729Z","iopub.execute_input":"2026-03-17T08:50:10.293936Z","iopub.status.idle":"2026-03-17T08:50:10.311583Z","shell.execute_reply.started":"2026-03-17T08:50:10.293912Z","shell.execute_reply":"2026-03-17T08:50:10.310534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# df =pd.read_csv(\"/kaggle/input/competitions/h-and-m-personalized-fashion-recommendations/articles.csv\")\n\n# cols = [\n#     \"graphical_appearance_no\",\n#     \"graphical_appearance_name\",\n#     \"colour_group_code\",\n#     \"colour_group_name\",\n#     \"perceived_colour_value_id\",\n#     \"perceived_colour_value_name\",\n#     \"perceived_colour_master_id\",\n#     \"perceived_colour_master_name\"\n# ]\n\n# for col in cols:\n#     count_neg1 = (df[col] == -1).sum()\n#     count_unknown = (df[col].astype(str).str.lower() == \"unknown\").sum()\n#     count_nan = df[col].isna().sum()\n\n#     print(f\"\\nColumn: {col}\")\n#     print(f\"-1: {count_neg1}\")\n#     print(f\"Unknown: {count_unknown}\")\n#     print(f\"NaN: {count_nan}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.315306Z","iopub.execute_input":"2026-03-17T08:50:10.315512Z","iopub.status.idle":"2026-03-17T08:50:10.330725Z","shell.execute_reply.started":"2026-03-17T08:50:10.315491Z","shell.execute_reply":"2026-03-17T08:50:10.329703Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# purchase_counts = trans_df.groupby(\"customer_id\").size()\n\n# print(purchase_counts.head())\n\n# print(purchase_counts.describe())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.331634Z","iopub.execute_input":"2026-03-17T08:50:10.331864Z","iopub.status.idle":"2026-03-17T08:50:10.345330Z","shell.execute_reply.started":"2026-03-17T08:50:10.331841Z","shell.execute_reply":"2026-03-17T08:50:10.344580Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# plt.figure(figsize=(8,5))\n\n# plt.hist(purchase_counts, bins=50)\n\n# plt.title(\"Distribution of Number of Purchases per Customer\")\n# plt.xlabel(\"Number of Purchases\")\n# plt.ylabel(\"Number of Customers\")\n\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.346209Z","iopub.execute_input":"2026-03-17T08:50:10.346458Z","iopub.status.idle":"2026-03-17T08:50:10.363281Z","shell.execute_reply.started":"2026-03-17T08:50:10.346439Z","shell.execute_reply":"2026-03-17T08:50:10.362486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# min_purchase = purchase_counts.min()\n\n# print(\"Minimum purchases:\", min_purchase)\n\n# print(\"Number of customers with this purchase:\", \n#       (purchase_counts == min_purchase).sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.364176Z","iopub.execute_input":"2026-03-17T08:50:10.364444Z","iopub.status.idle":"2026-03-17T08:50:10.379103Z","shell.execute_reply.started":"2026-03-17T08:50:10.364425Z","shell.execute_reply":"2026-03-17T08:50:10.378303Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# max_purchase = purchase_counts.max()\n\n# print(\"Maximum purchases:\", max_purchase)\n\n# print(\"Number of customers with this purchase:\", \n#       (purchase_counts == max_purchase).sum())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.380229Z","iopub.execute_input":"2026-03-17T08:50:10.380556Z","iopub.status.idle":"2026-03-17T08:50:10.395230Z","shell.execute_reply.started":"2026-03-17T08:50:10.380535Z","shell.execute_reply":"2026-03-17T08:50:10.394493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# df = trans_df.merge(\n#     customer_df[[\"customer_id\",\"age\"]],\n#     on=\"customer_id\",\n#     how=\"left\"\n# )\n\n# bins = [16,20,30,40,50,60,70,100]\n# labels = [\"16-20\",\"20-30\",\"30-40\",\"40-50\",\"50-60\",\"60-70\",\"70+\"]\n\n# df[\"age_group\"] = pd.cut(df[\"age\"], bins=bins, labels=labels, right=False)\n# age_purchase = df[\"age_group\"].value_counts().sort_index()\n# age_percent = (age_purchase / age_purchase.sum() * 100).round(1)\n\n# result_table = pd.DataFrame({\n#     \"Purchased Quantity\": age_purchase,\n#     \"Percentage (%)\": age_percent\n# })\n\n# print(result_table)\n\n# plt.figure(figsize=(8,5))\n\n# bars = plt.bar(result_table.index, result_table[\"Percentage (%)\"])\n\n# plt.title(\"Purchased Quantity by Age Group\")\n# plt.xlabel(\"Age Group\")\n# plt.ylabel(\"Purchased Quantity (%)\")\n\n# # hiển thị % trên đầu cột\n# for bar, val in zip(bars, result_table[\"Percentage (%)\"]):\n#     plt.text(\n#         bar.get_x() + bar.get_width()/2,\n#         val,\n#         f\"{val}%\",\n#         ha=\"center\",\n#         va=\"bottom\",\n#         fontsize=10\n#     )\n\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.396036Z","iopub.execute_input":"2026-03-17T08:50:10.396210Z","iopub.status.idle":"2026-03-17T08:50:10.413678Z","shell.execute_reply.started":"2026-03-17T08:50:10.396192Z","shell.execute_reply":"2026-03-17T08:50:10.412728Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# df = trans_df.merge(\n#     customer_df[[\"customer_id\",\"age\"]],\n#     on=\"customer_id\",\n#     how=\"left\"\n# )\n\n# # Age group\n# bins = [16,20,30,40,50,60,70,100]\n# labels = [\"16-20\",\"20-30\",\"30-40\",\"40-50\",\"50-60\",\"60-70\",\"70+\"]\n\n# df[\"age_group\"] = pd.cut(df[\"age\"], bins=bins, labels=labels, right=False)\n\n# # ---- Purchased quantity ----\n# age_purchase = df[\"age_group\"].value_counts().sort_index()\n# age_percent = (age_purchase / age_purchase.sum() * 100).round(1)\n\n# # ---- Revenue ----\n# age_revenue = df.groupby(\"age_group\")[\"price\"].sum()\n# revenue_percent = (age_revenue / age_revenue.sum() * 100).round(1)\n\n# # ---- Result table ----\n# result_table = pd.DataFrame({\n#     \"Purchased Quantity\": age_purchase,\n#     \"Purchase %\": age_percent,\n#     \"Revenue\": age_revenue,\n#     \"Revenue %\": revenue_percent\n# })\n\n# print(result_table)\n\n# # bảng đã gộp\n# summary_table = pd.DataFrame({\n#     \"Purchase (%)\": age_percent,\n#     \"Revenue (%)\": revenue_percent\n# })\n\n# x = np.arange(len(summary_table.index))\n# width = 0.35\n\n# plt.figure(figsize=(9,5))\n\n# bars1 = plt.bar(x - width/2, summary_table[\"Purchase (%)\"], width, label=\"Purchase %\")\n# bars2 = plt.bar(x + width/2, summary_table[\"Revenue (%)\"], width, label=\"Revenue %\")\n\n# plt.xticks(x, summary_table.index)\n# plt.xlabel(\"Age Group\")\n# plt.ylabel(\"Percentage (%)\")\n# plt.title(\"Purchase vs Revenue by Age Group\")\n\n# plt.legend()\n\n# # hiển thị % trên đầu cột\n# for bar in bars1:\n#     h = bar.get_height()\n#     plt.text(bar.get_x()+bar.get_width()/2, h, f\"{h:.1f}%\", ha=\"center\", va=\"bottom\")\n\n# for bar in bars2:\n#     h = bar.get_height()\n#     plt.text(bar.get_x()+bar.get_width()/2, h, f\"{h:.1f}%\", ha=\"center\", va=\"bottom\")\n\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.414673Z","iopub.execute_input":"2026-03-17T08:50:10.414965Z","iopub.status.idle":"2026-03-17T08:50:10.430617Z","shell.execute_reply.started":"2026-03-17T08:50:10.414940Z","shell.execute_reply":"2026-03-17T08:50:10.429756Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# df = trans_df.merge(\n#     customer_df[[\"customer_id\",\"fashion_news_frequency\"]],\n#     on=\"customer_id\",\n#     how=\"left\"\n# )\n\n# # giữ NaN thành 1 nhóm riêng\n# df[\"fashion_news_frequency\"] = df[\"fashion_news_frequency\"].fillna(\"NaN\")\n# freq_purchase = df[\"fashion_news_frequency\"].value_counts()\n\n# freq_percent = (freq_purchase / freq_purchase.sum() * 100).round(3)\n\n# print(freq_percent)\n\n# plt.figure(figsize=(8,5))\n\n# bars = plt.bar(freq_percent.index, freq_percent.values)\n\n# plt.title(\"Purchased quantity by Fashion News Frequency\")\n# plt.xlabel(\"Fashion News Frequency\")\n# plt.ylabel(\"Purchased Quantity (%)\")\n\n# # hiển thị số %\n# for bar,val in zip(bars,freq_percent.values):\n#     plt.text(\n#         bar.get_x()+bar.get_width()/2,\n#         val,\n#         f\"{val}\",\n#         ha=\"center\",\n#         va=\"bottom\",\n#         fontsize=11\n#     )\n\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.431728Z","iopub.execute_input":"2026-03-17T08:50:10.432008Z","iopub.status.idle":"2026-03-17T08:50:10.450952Z","shell.execute_reply.started":"2026-03-17T08:50:10.431980Z","shell.execute_reply":"2026-03-17T08:50:10.450104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = trans_df.merge(\n    customer_df[[\"customer_id\",\"club_member_status\"]],\n    on=\"customer_id\",\n    how=\"left\"\n)\n\n# giữ NaN thành 1 nhóm riêng\ndf[\"club_member_status\"] = df[\"club_member_status\"].fillna(\"NaN\")\nfreq_purchase = df[\"club_member_status\"].value_counts()\n\nfreq_percent = (freq_purchase / freq_purchase.sum() * 100).round(3)\n\nprint(freq_percent)\n\nplt.figure(figsize=(8,5))\n\nbars = plt.bar(freq_percent.index, freq_percent.values)\n\nplt.title(\"Purchased quantity by club_member_status\")\nplt.xlabel(\"club_member_status\")\nplt.ylabel(\"Purchased Quantity (%)\")\n\n# hiển thị số %\nfor bar,val in zip(bars,freq_percent.values):\n    plt.text(\n        bar.get_x()+bar.get_width()/2,\n        val,\n        f\"{val}\",\n        ha=\"center\",\n        va=\"bottom\",\n        fontsize=11\n    )\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:05:23.432268Z","iopub.execute_input":"2026-03-17T09:05:23.433721Z","iopub.status.idle":"2026-03-17T09:05:41.616404Z","shell.execute_reply.started":"2026-03-17T09:05:23.433674Z","shell.execute_reply":"2026-03-17T09:05:41.614578Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # merge transaction với customer\n# df = trans_df.merge(\n#     customer_df[[\"customer_id\",\"Active\",\"FN\"]],\n#     on=\"customer_id\",\n#     how=\"left\"\n# )\n\n# # =========================\n# # ACTIVE\n# # =========================\n# active_counts = df[\"Active\"].fillna(\"NaN\").value_counts()\n# active_percent = (active_counts / active_counts.sum() * 100).round(3)\n\n# plt.figure(figsize=(7,4))\n# bars = plt.bar(active_percent.index.astype(str), active_percent.values)\n\n# plt.title(\"Purchased Quantity by Active Status\")\n# plt.xlabel(\"Active\")\n# plt.ylabel(\"Purchased Quantity (%)\")\n\n# for bar, val in zip(bars, active_percent.values):\n#     plt.text(bar.get_x()+bar.get_width()/2, val, f\"{val:.3f}\",\n#              ha=\"center\", va=\"bottom\")\n\n# plt.show()\n\n\n# # =========================\n# # FN\n# # =========================\n# fn_counts = df[\"FN\"].fillna(\"NaN\").value_counts()\n# fn_percent = (fn_counts / fn_counts.sum() * 100).round(3)\n\n# plt.figure(figsize=(7,4))\n# bars = plt.bar(fn_percent.index.astype(str), fn_percent.values)\n\n# plt.title(\"Purchased Quantity by FN Status\")\n# plt.xlabel(\"FN\")\n# plt.ylabel(\"Purchased Quantity (%)\")\n\n# for bar, val in zip(bars, fn_percent.values):\n#     plt.text(bar.get_x()+bar.get_width()/2, val, f\"{val:.3f}\",\n#              ha=\"center\", va=\"bottom\")\n\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.451880Z","iopub.execute_input":"2026-03-17T08:50:10.452171Z","iopub.status.idle":"2026-03-17T08:50:10.470135Z","shell.execute_reply.started":"2026-03-17T08:50:10.452151Z","shell.execute_reply":"2026-03-17T08:50:10.468929Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# df = customer_df.copy()\n\n# bins = [16,20,30,40,50,60,70,100]\n# labels = [\"16-20\",\"20-30\",\"30-40\",\"40-50\",\"50-60\",\"60-70\",\"70+\"]\n\n# df[\"age_group\"] = pd.cut(df[\"age\"], bins=bins, labels=labels, right=False)\n\n# # ép kiểu Active\n# df[\"Active\"] = df[\"Active\"].astype(\"string\").fillna(\"NaN\")\n\n# active_table = pd.crosstab(\n#     df[\"age_group\"],\n#     df[\"Active\"],\n#     normalize=\"index\"\n# ) * 100\n\n# print(active_table.round(1))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.471076Z","iopub.execute_input":"2026-03-17T08:50:10.471350Z","iopub.status.idle":"2026-03-17T08:50:10.486575Z","shell.execute_reply.started":"2026-03-17T08:50:10.471330Z","shell.execute_reply":"2026-03-17T08:50:10.485651Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # vẽ bar chart\n# ax = active_table.plot(\n#     kind=\"bar\",\n#     figsize=(9,5),\n#     width=0.8\n# )\n\n# plt.title(\"Account Activity by Age Group\", fontweight=\"bold\", size=16)\n# plt.xlabel(\"Age Group\", fontweight=\"bold\")\n# plt.ylabel(\"Percentage (%)\", fontweight=\"bold\")\n\n# # hiện % trên đầu cột\n# for container in ax.containers:\n#     ax.bar_label(container, fmt=\"%.1f%%\", padding=3)\n\n# plt.grid(axis=\"y\", linestyle=\"--\", alpha=0.7)\n\n# plt.legend(title=\"Active Status\")\n\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.487963Z","iopub.execute_input":"2026-03-17T08:50:10.488195Z","iopub.status.idle":"2026-03-17T08:50:10.502389Z","shell.execute_reply.started":"2026-03-17T08:50:10.488176Z","shell.execute_reply":"2026-03-17T08:50:10.501688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# df = customer_df.copy()\n\n# bins = [16,20,30,40,50,60,70,100]\n# labels = [\"16-20\",\"20-30\",\"30-40\",\"40-50\",\"50-60\",\"60-70\",\"70+\"]\n\n# df[\"age_group\"] = pd.cut(df[\"age\"], bins=bins, labels=labels, right=False)\n\n# # ép kiểu Active\n# df[\"FN\"] = df[\"FN\"].astype(\"string\").fillna(\"NaN\")\n\n# active_table = pd.crosstab(\n#     df[\"age_group\"],\n#     df[\"FN\"],\n#     normalize=\"index\"\n# ) * 100\n\n# print(active_table.round(1))\n\n# # vẽ bar chart\n# ax = active_table.plot(\n#     kind=\"bar\",\n#     figsize=(9,5),\n#     width=0.8\n# )\n\n# plt.title(\"FN by Age Group\", fontweight=\"bold\", size=16)\n# plt.xlabel(\"FN\", fontweight=\"bold\")\n# plt.ylabel(\"Percentage (%)\", fontweight=\"bold\")\n\n# # hiện % trên đầu cột\n# for container in ax.containers:\n#     ax.bar_label(container, fmt=\"%.1f%%\", padding=3)\n\n# plt.grid(axis=\"y\", linestyle=\"--\", alpha=0.7)\n\n# plt.legend(title=\"Status\")\n\n# plt.tight_layout()\n# plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:50:10.503330Z","iopub.execute_input":"2026-03-17T08:50:10.503662Z","iopub.status.idle":"2026-03-17T08:50:10.522765Z","shell.execute_reply.started":"2026-03-17T08:50:10.503637Z","shell.execute_reply":"2026-03-17T08:50:10.521937Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"missing_ids = set(customer_df[\"customer_id\"]) - set(trans_df[\"customer_id\"])\n\nprint(\"Số customer_id bị thiếu:\", len(missing_ids))\nprint(list(missing_ids)[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T08:52:33.239925Z","iopub.execute_input":"2026-03-17T08:52:33.240552Z","iopub.status.idle":"2026-03-17T08:52:38.490541Z","shell.execute_reply.started":"2026-03-17T08:52:33.240532Z","shell.execute_reply":"2026-03-17T08:52:38.489684Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# lọc dữ liệu từ missing_ids\nsubset = customer_df[customer_df[\"customer_id\"].isin(missing_ids)]\n\n# tính NaN\nnan_count = subset.isna().sum()\nnan_percent = (subset.isna().mean() * 100).round(2)\n\n# tạo bảng\nnan_table = pd.DataFrame({\n    \"NaN_count\": nan_count,\n    \"NaN_percent\": nan_percent\n})\n\nprint(nan_table)\n\nimport matplotlib.pyplot as plt\n\nplt.figure(figsize=(8,5))\n\nbars = plt.barh(nan_table.index, nan_table[\"NaN_count\"])\n\nplt.title(\"Missing Values per Column (Missing Customers)\")\nplt.xlabel(\"Number of NaN\")\n\n# hiển thị cả count + %\nfor i, (count, pct) in enumerate(zip(nan_table[\"NaN_count\"], nan_table[\"NaN_percent\"])):\n    plt.text(count, i, f\"{pct:.2f}%\", va=\"center\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:09:34.129525Z","iopub.execute_input":"2026-03-17T09:09:34.129831Z","iopub.status.idle":"2026-03-17T09:09:34.371006Z","shell.execute_reply.started":"2026-03-17T09:09:34.129808Z","shell.execute_reply":"2026-03-17T09:09:34.369496Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_pie_distribution(subset, \"fashion_news_frequency\", explode=[0, 0, 0.3, 0.5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:11:42.945081Z","iopub.execute_input":"2026-03-17T09:11:42.945632Z","iopub.status.idle":"2026-03-17T09:11:43.048726Z","shell.execute_reply.started":"2026-03-17T09:11:42.945566Z","shell.execute_reply":"2026-03-17T09:11:43.047640Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plot_pie_distribution(subset, \"club_member_status\", explode=[0, 0, 0.3, 0.5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-17T09:12:02.735894Z","iopub.execute_input":"2026-03-17T09:12:02.736575Z","iopub.status.idle":"2026-03-17T09:12:02.843709Z","shell.execute_reply.started":"2026-03-17T09:12:02.736548Z","shell.execute_reply":"2026-03-17T09:12:02.842395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}