{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":7391,"databundleVersionId":44328,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Explore and prepare the data","metadata":{}},{"cell_type":"markdown","source":"## Preprocessing","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\nimport os\n\n# Duyệt toàn bộ thư mục con bên trong thư mục gốc (file zip)\nfor dirname, _, filenames in os.walk('/kaggle/input/favorita-grocery-sales-forecasting'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:09:32.441211Z","iopub.execute_input":"2025-10-24T07:09:32.441872Z","iopub.status.idle":"2025-10-24T07:09:33.685681Z","shell.execute_reply.started":"2025-10-24T07:09:32.441835Z","shell.execute_reply":"2025-10-24T07:09:33.684468Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install py7zr","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:09:33.687242Z","iopub.execute_input":"2025-10-24T07:09:33.687718Z","iopub.status.idle":"2025-10-24T07:09:40.951850Z","shell.execute_reply.started":"2025-10-24T07:09:33.687690Z","shell.execute_reply":"2025-10-24T07:09:40.950196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import py7zr\nfrom subprocess import check_output\n\nfor dirname, _, filenames in os.walk('/kaggle/input/favorita-grocery-sales-forecasting'):\n    for filename in filenames:\n        # Mở lần lượt từng file zip\n        archive = py7zr.SevenZipFile(os.path.join(dirname, filename), mode='r')\n        # Giải nén lần lượt từng file zip rồi xuất ra output\n        archive.extractall(path=\"/kaggle/working\")\n        archive.close()\n\n# Giải nén thành công thì in ra màn hình để thông báo\nprint(check_output([\"ls\", \"../working\"]).decode(\"utf8\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:09:40.953413Z","iopub.execute_input":"2025-10-24T07:09:40.953767Z","iopub.status.idle":"2025-10-24T07:11:30.011207Z","shell.execute_reply.started":"2025-10-24T07:09:40.953728Z","shell.execute_reply":"2025-10-24T07:11:30.008782Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Đọc từng file csv\ntrain = pd.read_csv('../working/train.csv', parse_dates=['date'], low_memory = False)\ntest = pd.read_csv('../working/test.csv', parse_dates=['date'])\noil = pd.read_csv('../working/oil.csv', parse_dates=['date'])\nstores = pd.read_csv('../working/stores.csv')\nitems = pd.read_csv('../working/items.csv')\ntransactions = pd.read_csv('../working/transactions.csv', parse_dates=['date'])\nholidays = pd.read_csv('../working/holidays_events.csv', parse_dates=['date'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:11:30.015194Z","iopub.execute_input":"2025-10-24T07:11:30.016826Z","iopub.status.idle":"2025-10-24T07:13:30.139923Z","shell.execute_reply.started":"2025-10-24T07:11:30.016778Z","shell.execute_reply":"2025-10-24T07:13:30.138135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:30.142818Z","iopub.execute_input":"2025-10-24T07:13:30.143288Z","iopub.status.idle":"2025-10-24T07:13:30.180655Z","shell.execute_reply.started":"2025-10-24T07:13:30.143248Z","shell.execute_reply":"2025-10-24T07:13:30.179491Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.tail()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:30.181782Z","iopub.execute_input":"2025-10-24T07:13:30.182059Z","iopub.status.idle":"2025-10-24T07:13:30.194315Z","shell.execute_reply.started":"2025-10-24T07:13:30.182037Z","shell.execute_reply":"2025-10-24T07:13:30.193352Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Trước khi đưa dữ liệu vào mô hình dự báo, cần kiểm tra xem dữ liệu có tồn tại missing values hay không?","metadata":{}},{"cell_type":"code","source":"# Kiểm tra từng cột trong từng bộ dữ liệu xem thử cột nào xuất hiện missing values\nprint(\"Nulls in Oil columns: {0} => {1}\".format(oil.columns.values,oil.isnull().any().values))\nprint(\"=\"*70)\nprint(\"Nulls in holiday_events columns: {0} => {1}\".format(holidays.columns.values,holidays.isnull().any().values))\nprint(\"=\"*70)\nprint(\"Nulls in stores columns: {0} => {1}\".format(stores.columns.values,stores.isnull().any().values))\nprint(\"=\"*70)\nprint(\"Nulls in transactions columns: {0} => {1}\".format(transactions.columns.values,transactions.isnull().any().values))\nprint(\"=\"*70)\nprint(\"Nulls in train columns: {0} => {1}\".format(train.columns.values,train.isnull().any().values))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:30.195598Z","iopub.execute_input":"2025-10-24T07:13:30.195982Z","iopub.status.idle":"2025-10-24T07:13:36.455913Z","shell.execute_reply.started":"2025-10-24T07:13:30.195948Z","shell.execute_reply":"2025-10-24T07:13:36.454416Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nhìn chung, chỉ có cột **\"dcoilwtico\"** trong **\"oil.csv\"** và **\"onpromotion\"** trong **\"train.csv\"** bị thiếu dữ liệu. \n\nGiá dầu có thể không được cập nhật thông tin vào các ngày nghỉ, còn thông tin khuyến mãi có thể không áp dụng cho mọi sản phẩm. Điều này không phải lỗi dữ liệu, mà là đặc trưng của chuỗi thời gian thực tế, cần phải xử lý trước khi đưa vào mô hình dự báo.\n\nTiếp theo, cần kiểm tra xem những cột đó có missing values nhiều hay ít:","metadata":{}},{"cell_type":"code","source":"datasets = {\n    'train': train,\n    'test': test,\n    'oil': oil,\n    'stores': stores,\n    'items': items,\n    'transactions': transactions,\n    'holidays': holidays\n}\n\nfor name, df in datasets.items():\n    print(f\"\\n===== Missing values in {name} =====\")\n    print(df.isna().sum())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:36.457341Z","iopub.execute_input":"2025-10-24T07:13:36.457863Z","iopub.status.idle":"2025-10-24T07:13:43.755185Z","shell.execute_reply.started":"2025-10-24T07:13:36.457831Z","shell.execute_reply":"2025-10-24T07:13:43.753964Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"oil['dcoilwtico'] = oil['dcoilwtico'].interpolate()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:43.756768Z","iopub.execute_input":"2025-10-24T07:13:43.757070Z","iopub.status.idle":"2025-10-24T07:13:43.770185Z","shell.execute_reply.started":"2025-10-24T07:13:43.757044Z","shell.execute_reply":"2025-10-24T07:13:43.768645Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Đối với cột **\"dcoilwtico\"**, dùng nội suy tuyến tính (linear interpolation) nhằm ước lượng giá trị bị thiếu bằng cách nối thẳng hai điểm dữ liệu có thật xung quanh nó nhằm giúp chuỗi giá dầu hợp lý, mượt hơn.","metadata":{}},{"cell_type":"code","source":"oil['dcoilwtico'] = oil['dcoilwtico'].bfill()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:43.774399Z","iopub.execute_input":"2025-10-24T07:13:43.774861Z","iopub.status.idle":"2025-10-24T07:13:43.802076Z","shell.execute_reply.started":"2025-10-24T07:13:43.774835Z","shell.execute_reply":"2025-10-24T07:13:43.800802Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"oil.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:43.803210Z","iopub.execute_input":"2025-10-24T07:13:43.803678Z","iopub.status.idle":"2025-10-24T07:13:43.829575Z","shell.execute_reply.started":"2025-10-24T07:13:43.803650Z","shell.execute_reply":"2025-10-24T07:13:43.828041Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Kiểm tra lại lần nữa, bảng **\"oil\"** không còn missing values nào.","metadata":{}},{"cell_type":"code","source":"train.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:43.830832Z","iopub.execute_input":"2025-10-24T07:13:43.831132Z","iopub.status.idle":"2025-10-24T07:13:50.923655Z","shell.execute_reply.started":"2025-10-24T07:13:43.831110Z","shell.execute_reply":"2025-10-24T07:13:50.922604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train['onpromotion'] = train['onpromotion'].fillna('False')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:13:50.924539Z","iopub.execute_input":"2025-10-24T07:13:50.924789Z","iopub.status.idle":"2025-10-24T07:14:03.144062Z","shell.execute_reply.started":"2025-10-24T07:13:50.924770Z","shell.execute_reply":"2025-10-24T07:14:03.142733Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Đối với cột **\"onpromotion\"**, những dữ liệu bị trống có nghĩa là thông tin khuyến mãi không áp dụng cho những loại sản phẩm đó, vậy nên cần điền False nhằm hiểu rõ dữ liệu hơn","metadata":{}},{"cell_type":"code","source":"train.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:14:03.145466Z","iopub.execute_input":"2025-10-24T07:14:03.145846Z","iopub.status.idle":"2025-10-24T07:14:10.821596Z","shell.execute_reply.started":"2025-10-24T07:14:03.145815Z","shell.execute_reply":"2025-10-24T07:14:10.820539Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Kiểm tra lại lần nữa, bảng **\"train\"** không còn xuất hiện missing values nào.","metadata":{}},{"cell_type":"markdown","source":"## EDA","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom datetime import datetime\nfrom matplotlib import gridspec\nfrom matplotlib.dates import DateFormatter","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:14:10.822918Z","iopub.execute_input":"2025-10-24T07:14:10.823208Z","iopub.status.idle":"2025-10-24T07:14:12.611609Z","shell.execute_reply.started":"2025-10-24T07:14:10.823187Z","shell.execute_reply":"2025-10-24T07:14:12.610300Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# === Cấu hình ===\ntrain_path = \"/kaggle/working/train.csv\"\nsample_frac = 0.1\nchunksize = 2_000_000\nseed = 1234\n\n# === Đọc theo khối và lấy mẫu ===\nsample_list = []\nreader = pd.read_csv(\n    train_path,\n    usecols=['id', 'date', 'store_nbr', 'item_nbr', 'unit_sales', 'onpromotion'],\n    parse_dates=['date'],\n    chunksize=chunksize,\n    low_memory=False\n)\n\nfor i, chunk in enumerate(reader, start=1):\n    sample_chunk = chunk.sample(frac=sample_frac, random_state=seed)\n    sample_list.append(sample_chunk)\n    print(f\"Đã xử lý xong chunk {i}\")\n\n# === Ghép toàn bộ mẫu ===\ntrain_sample = pd.concat(sample_list, ignore_index=True)\n\nprint(f\"\\n Hoàn tất! Số dòng sau khi lấy mẫu: {len(train_sample):,}\")\nprint(train_sample.head())\ntrain_sample.info(memory_usage='deep')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:14:12.612747Z","iopub.execute_input":"2025-10-24T07:14:12.613178Z","iopub.status.idle":"2025-10-24T07:16:04.958923Z","shell.execute_reply.started":"2025-10-24T07:14:12.613153Z","shell.execute_reply":"2025-10-24T07:16:04.956855Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Vì tập train quá lớn, nếu đọc toàn bộ sẽ gây tràn RAM. Do đó, ta chia dữ liệu thành các khối nhỏ (chunks) và chỉ lấy 10% mẫu ngẫu nhiên trong mỗi khối để giảm tải bộ nhớ mà vẫn đảm bảo tính đại diện cho EDA.","metadata":{}},{"cell_type":"markdown","source":"## 1. Data Overview & Cleaning (hiểu dữ liệu, loại outlier)","metadata":{}},{"cell_type":"markdown","source":"### 1.1. train.csv","metadata":{}},{"cell_type":"code","source":"train.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:04.960002Z","iopub.execute_input":"2025-10-24T07:16:04.960295Z","iopub.status.idle":"2025-10-24T07:16:04.973657Z","shell.execute_reply.started":"2025-10-24T07:16:04.960264Z","shell.execute_reply":"2025-10-24T07:16:04.972391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\n\n# Tách nhóm\nnum_cols = train_sample.select_dtypes(include=[np.number]).columns\ntrain_sample[num_cols] = train_sample[num_cols].replace([np.inf, -np.inf], np.nan)\n\n# Đưa về số dễ nhận xét\npd.options.display.float_format = '{:,.0f}'.format\n\ndesc_num = train_sample.describe()\ndesc_cat = train_sample.describe(include=['object', 'bool'])\n\nprint(\"\\nThống kê mô tả\")\nprint(desc_num)\nprint(\"=\"*10)\nprint(desc_cat)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:04.975296Z","iopub.execute_input":"2025-10-24T07:16:04.975643Z","iopub.status.idle":"2025-10-24T07:16:10.766925Z","shell.execute_reply.started":"2025-10-24T07:16:04.975619Z","shell.execute_reply":"2025-10-24T07:16:10.765400Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nhận xét tổng quan:\n- Dữ liệu thời gian của **train.csv** dài và lớn với khoảng 12.5 triệu dòng, với 54 cửa hàng (**store_nbr**) và hơn 2 triệu mã sản phẩm (**item_nbr**), rất phù hợp cho mô hình dự báo chuỗi thời gian.\n\n- **unit_sales** là biến mục tiêu (target feature) của bộ dữ liệu, thể hiện số lượng sản phẩm cụ thể được bán tại cửa hàng trong ngày đó. Các giá trị âm biểu thị rằng sản phẩm đó bị trả lại, nên không cần xử lý những giá trị này.\n\n- Biến **onpromotion** có thể dùng như một feature quan trọng trong dự báo vì biến này được mã hóa  dưới dạng True/False. Thuộc tính này có nhiều giá trị bị thiếu (NA) nhưng đã được xử lý ở bước tiền xử lý dữ liệu.\n\n- Thời gian biến **date** được phân bố trải dài liên tục và không thiếu ngày, được ghi nhận từ ngày 01/01/2013 đến ngày 15/08/2017, khoảng hơn 4 năm rưỡi dữ liệu, thuận lợi cho mô hình dự báo chuỗi thời gian.","metadata":{}},{"cell_type":"markdown","source":"### 1.2. test.csv","metadata":{}},{"cell_type":"code","source":"test.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:10.768445Z","iopub.execute_input":"2025-10-24T07:16:10.768848Z","iopub.status.idle":"2025-10-24T07:16:10.783658Z","shell.execute_reply.started":"2025-10-24T07:16:10.768826Z","shell.execute_reply":"2025-10-24T07:16:10.781853Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tách nhóm dữ liệu\nnum_cols = test.select_dtypes(include=[np.number]).columns\ntest[num_cols] = test[num_cols].replace([np.inf, -np.inf], np.nan)\n\n# Hiển thị số\npd.options.display.float_format = '{:,.0f}'.format\n\n# Thống kê mô tả\ndesc_num = test.describe()                      \ndesc_cat = test.describe(include=['object', 'bool'])  # cho bool/object\n\nprint(\"Thống kê mô tả\")\nprint(desc_num)\n\nprint(\"=\"*10)\nprint(desc_cat)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:10.784975Z","iopub.execute_input":"2025-10-24T07:16:10.785275Z","iopub.status.idle":"2025-10-24T07:16:11.394770Z","shell.execute_reply.started":"2025-10-24T07:16:10.785253Z","shell.execute_reply":"2025-10-24T07:16:11.393290Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nhận xét tổng quan:\n- Thời gian ghi nhận của bộ dữ liệu này từ 16/08/2017 đến 31/08/2017, tổng cộng 16 ngày (2 tuần cuối tháng 8/2017), là giai đoạn ngay sau mốc cuối của tập train. Bộ dữ liệu này là tập dữ liệu dự báo (forecasting target), tức là những dòng mà mô hình sẽ phải dự đoán giá trị unit_sales.\n\n- Các cột định lượng (**store_nbr**, **item_nbr**) có phạm vi, trung bình và độ lệch chuẩn tương đương với train.csv. Đáng chú ý là một số mã sản phẩm (**item_nbr**) trong tập test không xuất hiện trong tập train, điều này cho thấy mô hình sẽ phải dự đoán cho các sản phẩm hoàn toàn mới.\n\n- Cột **\"onpromotion\"** có phân phối hợp lý, khớp với tỷ lệ sản phẩm khuyến mãi trong tập huấn luyện.","metadata":{}},{"cell_type":"markdown","source":"### 1.3. oil.csv","metadata":{}},{"cell_type":"code","source":"oil.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.396832Z","iopub.execute_input":"2025-10-24T07:16:11.397278Z","iopub.status.idle":"2025-10-24T07:16:11.409441Z","shell.execute_reply.started":"2025-10-24T07:16:11.397248Z","shell.execute_reply":"2025-10-24T07:16:11.407786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tách nhóm dữ liệu\nnum_cols = oil.select_dtypes(include=[np.number]).columns\n\n# Hiển thị số\npd.options.display.float_format = '{:,.0f}'.format\n\n# Thống kê mô tả\ndesc_num = oil.describe()      \n\nprint(\"Thống kê mô tả\")\nprint(desc_num)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.410724Z","iopub.execute_input":"2025-10-24T07:16:11.411007Z","iopub.status.idle":"2025-10-24T07:16:11.443184Z","shell.execute_reply.started":"2025-10-24T07:16:11.410986Z","shell.execute_reply":"2025-10-24T07:16:11.441807Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nhận xét tổng quan:\n- Đây là bộ dữ liệu chuỗi thời gian đơn giản về giá dầu vào một ngày nhất định. Khoảng thời gian của dữ liệu liên tục từ ngày 01/01/2013 đến ngày 31/08/2017, phù hợp với giai đoạn của train.csv.\n\n- Giá trị trung bình (mean) của giá dầu nằm trong khoảng 68 USD/thùng, cho thấy mặt bằng giá trung bình trung tính trong giai đoạn. Biến động giá được thể hiện qua độ lệch chuẩn khá lớn, dẫn đến việc giá dầu biến động mạnh (đặc biệt các giai đoạn 2014–2016). \n\n- Một số giá trị bị thiếu (NaN) trong biến **\"dcoilwtico\"** đã được xử lý bằng cách nội suy để đảm bảo tính liên tục của dữ liệu.\n\n- Bộ dữ liệu này phản ánh rõ xu hướng biến động giá dầu toàn cầu trong giai đoạn 2013–2017.\nBiến **\"dcoilwtico\"** được sử dụng như một biến ngoại sinh cho mô hình dự báo doanh số (unit_sales) vì có thể đưa ra các nhận định như: ***\"Giá dầu ảnh hưởng trực tiếp đến chi phí vận chuyển và giá bán lẻ hàng hóa\"*** hay ***\"Mức biến động lớn có thể tương quan với nhu cầu tiêu dùng hoặc lạm phát\"***.\n","metadata":{}},{"cell_type":"markdown","source":"### 1.4. stores.csv","metadata":{}},{"cell_type":"code","source":"stores.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.444983Z","iopub.execute_input":"2025-10-24T07:16:11.445337Z","iopub.status.idle":"2025-10-24T07:16:11.476367Z","shell.execute_reply.started":"2025-10-24T07:16:11.445315Z","shell.execute_reply":"2025-10-24T07:16:11.474616Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tách nhóm dữ liệu\nnum_cols = stores.select_dtypes(include=np.number).columns\ncat_cols = stores.select_dtypes(include=['object']).columns\n\n# Hiển thị số\npd.options.display.float_format = '{:,.0f}'.format\n\n# Thống kê mô tả\ndesc_num = stores[num_cols].describe()\nprint(desc_num)\nprint(\"=\"*10)\ndesc_cat = stores[cat_cols].describe()\nprint(desc_cat)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.478032Z","iopub.execute_input":"2025-10-24T07:16:11.478451Z","iopub.status.idle":"2025-10-24T07:16:11.520072Z","shell.execute_reply.started":"2025-10-24T07:16:11.478422Z","shell.execute_reply":"2025-10-24T07:16:11.518341Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nhận xét tổng quan:\n- Với 54 chi nhánh cửa hàng hoạt động đồng thời, được phân thành 17 cụm (các cụm này thường đại diện cho nhóm cửa hàng có đặc điểm khách hàng và quy mô tương đồng), phân bổ rộng rãi trên toàn quốc với 22 thành phố có cửa hàng, trải đều trong 16 bang hoặc khu vực hành chính. Các cửa hàng được phân chia thành 5 loại cửa hàng khác nhau (A–E).\n\n- Bộ dữ liệu này mô tả đặc điểm định danh và phân loại của từng cửa hàng. Các biến phối hợp với nhau thể hiện rõ sự phân loại cửa hàng khi huấn luyện mô hình dự báo bằng cách thể hiện rõ đặc điểm kinh tế từng vùng/miền/địa lý, hành vi tiêu dùng của khách hàng và quy mô cửa hàng.","metadata":{}},{"cell_type":"markdown","source":"### 1.5. items.csv","metadata":{}},{"cell_type":"code","source":"items.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.520924Z","iopub.execute_input":"2025-10-24T07:16:11.521998Z","iopub.status.idle":"2025-10-24T07:16:11.537818Z","shell.execute_reply.started":"2025-10-24T07:16:11.521954Z","shell.execute_reply":"2025-10-24T07:16:11.536393Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tách nhóm theo kiểu dữ liệu\nnum_cols = items.select_dtypes(include=np.number).columns\ncat_cols = items.select_dtypes(include=['object']).columns\n\n# Hiển thị số\npd.options.display.float_format = '{:,.0f}'.format\n\n# Thống kê mô tả\ndesc_num = items[num_cols].describe()\nprint(desc_num)\nprint(\"=\"*10)\ndesc_cat = items[cat_cols].describe()\nprint(desc_cat)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.539542Z","iopub.execute_input":"2025-10-24T07:16:11.539870Z","iopub.status.idle":"2025-10-24T07:16:11.583638Z","shell.execute_reply.started":"2025-10-24T07:16:11.539846Z","shell.execute_reply":"2025-10-24T07:16:11.582311Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nhận xét tổng quan:\n- Sản phẩm trong dữ liệu rất đa dạng, với 4,100 mặt hàng chia thành 33 nhóm chính, phản ánh danh mục sản phẩm rộng rãi và cân đối.\n\n- Cấu trúc mã hóa sản phẩm (**item_nbr**, **class**) có giá trị liên tục.\n\n- Tỷ lệ hàng hóa dễ hư hỏng chiếm tỷ lệ rất thấp, phần lớn hàng hóa là hàng khô, bền (như nhu yếu phẩm, đồ gia dụng, đóng hộp,...).\n\n- Nhóm sản phẩm “GROCERY I” chiếm nhiều nhất, với hơn 1/3 tổng số hàng hóa, cho thấy nhóm hàng tạp hóa là nhóm trọng tâm trong kinh doanh.","metadata":{}},{"cell_type":"markdown","source":"### 1.6. transactions.csv","metadata":{}},{"cell_type":"code","source":"transactions.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.584759Z","iopub.execute_input":"2025-10-24T07:16:11.585042Z","iopub.status.idle":"2025-10-24T07:16:11.598179Z","shell.execute_reply.started":"2025-10-24T07:16:11.585021Z","shell.execute_reply":"2025-10-24T07:16:11.596923Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tách nhóm dữ liệu\nnum_cols = transactions.select_dtypes(include=[np.number]).columns\ntransactions[num_cols] = transactions[num_cols].replace([np.inf, -np.inf], np.nan)\n\n# Hiển thị số\npd.options.display.float_format = '{:,.0f}'.format\n\n# Thống kê mô tả\ndesc_num = transactions.describe()      \n\nprint(\"Thống kê mô tả\")\nprint(desc_num)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.603669Z","iopub.execute_input":"2025-10-24T07:16:11.604030Z","iopub.status.idle":"2025-10-24T07:16:11.644656Z","shell.execute_reply.started":"2025-10-24T07:16:11.604007Z","shell.execute_reply":"2025-10-24T07:16:11.643292Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nhận xét tổng quan:\n- Số lượng giao dịch mỗi ngày của từng cửa hàng được thể hiện liên tục từ năm 2013 đến năm 2017, trùng khớp với khung thời gian của **train.csv**. Trung bình mỗi ngày một cửa hàng có khoảng 1,695 giao dịch, dao động trong khoảng từ 5 đến 8,359 giao dịch với độ lệch chuẩn là 963 giao dịch, thể hiện mức biến động khá cao, phản ánh rõ sự khác biệt về quy mô hoặc doanh thu của cửa hàng theo thời gian.\n\n- Các ngày có số lượng giao dịch rất cao (trên 8.000) có thể là dịp lễ, khuyến mãi hoặc cửa hàng đặc biệt đông khách.\t\t\n\n- Biến **transactions** có phân phối lệch phải (right-skewed), nghĩa là đa phần cửa hàng có số giao dịch trung bình thấp, nhưng một số cửa hàng có doanh số đột biến.\n\n- Dữ liệu này phù hợp để dự báo unit_sales vì số giao dịch đại diện cho lượng khách hàng thực hiện hành vi mua tại cửa hàng. Ngoài ra còn dùng để nhận diện các cửa hàng hoạt động mạnh hay yếu theo thời gian hoặc theo khu vực.","metadata":{}},{"cell_type":"markdown","source":"### 1.7. holidays_events.csv","metadata":{}},{"cell_type":"code","source":"holidays.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.645665Z","iopub.execute_input":"2025-10-24T07:16:11.645931Z","iopub.status.idle":"2025-10-24T07:16:11.661237Z","shell.execute_reply.started":"2025-10-24T07:16:11.645911Z","shell.execute_reply":"2025-10-24T07:16:11.659852Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tách nhóm dữ liệu\nnum_cols = holidays.select_dtypes(include=[np.number]).columns\nholidays[num_cols] = holidays[num_cols].replace([np.inf, -np.inf], np.nan)\n\n# Hiển thị số\npd.options.display.float_format = '{:,.0f}'.format\n\n# Thống kê mô tả\ndesc_num = holidays.describe()                      \ndesc_cat = holidays.describe(include=['object', 'bool'])  # cho bool/object\n\nprint(\"Thống kê mô tả\")\nprint(desc_num)\n\nprint(\"=\"*10)\nprint(desc_cat)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.662530Z","iopub.execute_input":"2025-10-24T07:16:11.662820Z","iopub.status.idle":"2025-10-24T07:16:11.710133Z","shell.execute_reply.started":"2025-10-24T07:16:11.662798Z","shell.execute_reply":"2025-10-24T07:16:11.708960Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Nhận xét tổng quan:\n- Bộ dữ liệu này thể hiện rõ mùa vụ và yếu tố đặc biệt có thể tác động đến doanh số bán hàng.\n\n- Thời gian của bộ dữ liệu này bao phủ toàn bộ giai đoạn 2013–2017, trùng khớp với tập train.csv. Dữ liệu không bị thiếu ngày.\n\n- \"Holiday\" là sự kiện chiếm tỷ lệ áp đảo, nên tập dữ liệu này tập trung chủ yếu vào các ngày nghỉ lễ chính thức. \"National\" cũng là cấp độ phạm vi ảnh hưởng chiếm nhiều nhất, nên các sự kiện quy mô lớn ảnh hướng hướng đến toàn hệ thống cửa hàng, nên có khả năng gây ra các biến động mạnh về unit_sales.\n\n- Phần lớn các ngày lễ được tổ chức đúng ngày gốc, chỉ một số ít được dời.\t\n\n- Một số ngày lễ có thể làm tăng hoặc giảm nhu cầu tiêu dùng, chẳng hạn như lễ hội hóa trang Carnaval giúp nhu cầu người tiêu dùng tăng mạnh; còn các ngày Work Day làm cho nhu cầu của người tiêu dùng giảm nhẹ.","metadata":{}},{"cell_type":"markdown","source":"## 2. Trend Analysis (xem xu hướng dài hạn)","metadata":{}},{"cell_type":"code","source":"# Giả sử bạn đã có train_sample (đã parse date và có unit_sales)\ndf = train_sample.copy()\n\n# ----------- PLOT 1: Tổng doanh số theo ngày -----------\ndaily_sales = (\n    df.groupby('date', as_index=False)['unit_sales']\n    .sum()\n    .rename(columns={'unit_sales': 'sales'})\n)\n\nplt.figure(figsize=(14, 5))\nplt.plot(daily_sales['date'], daily_sales['sales'], color='blue')\nplt.title(\"Daily Total Sales Over Time\")\nplt.xlabel(\"Date\")\nplt.ylabel(\"Total Unit Sales\")\nplt.grid(alpha=0.3)\nplt.tight_layout()\nplt.show()\n\n# ----------- PLOT 2: Heatmap doanh số trung bình theo thứ và tháng -----------\n# Tạo cột weekday và month\ndf['wday'] = df['date'].dt.day_name()\ndf['month'] = df['date'].dt.month_name()\n\n# Chuẩn hóa thứ tự thứ trong tuần\nweekday_order = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']\nmonth_order = [\n    'January', 'February', 'March', 'April', 'May', 'June',\n    'July', 'August', 'September', 'October', 'November', 'December'\n]\n\n# Tính trung bình doanh số theo thứ và tháng\nheatmap_data = (\n    df.groupby(['wday', 'month'], as_index=False)['unit_sales']\n    .mean()\n    .rename(columns={'unit_sales': 'mean_sales'})\n)\n\n# Pivot để hiển thị heatmap\npivot_table = heatmap_data.pivot(index='wday', columns='month', values='mean_sales')\npivot_table = pivot_table.reindex(index=weekday_order, columns=month_order)\n\nplt.figure(figsize=(14, 6))\nsns.heatmap(pivot_table, cmap='Spectral', linewidths=0.5)\nplt.title(\"Average Unit Sales by Day of Week and Month\")\nplt.xlabel(\"Month of the Year\")\nplt.ylabel(\"Day of the Week\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:11.711840Z","iopub.execute_input":"2025-10-24T07:16:11.712155Z","iopub.status.idle":"2025-10-24T07:16:24.292849Z","shell.execute_reply.started":"2025-10-24T07:16:11.712123Z","shell.execute_reply":"2025-10-24T07:16:24.290135Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biểu đồ Daily Total Sales Over Time cho thấy doanh số bán hàng có xu hướng tăng dần theo thời gian, đặc biệt tăng mạnh kể từ năm 2015. Doanh số thể hiện dao động theo chu kỳ tuần và năm, thường tăng cao vào các ngày cuối tuần. Tuy nhiên, một số ngày ghi nhận doanh số bằng 0, có thể xuất phát từ thiếu dữ liệu hoặc việc tạm ngừng hoạt động của cửa hàng trong những thời điểm đó.\n\nBiểu đồ Average Unit Sales by Day of Week and Month cho thấy xu hướng tương tự: doanh số đạt đỉnh rõ rệt vào tháng 12 – thời điểm diễn ra Giáng sinh và các hoạt động mua sắm cuối năm, cùng với mức tăng đáng kể trong tháng 11. Ngoài ra, cuối tuần (Thứ 7 và Chủ nhật) luôn ghi nhận doanh số cao vượt trội, phản ánh thói quen mua sắm tập trung vào cuối tuần. Ngược lại, các ngày giữa tuần như Thứ 3 và Thứ 5 có xu hướng tiêu dùng giảm, doanh số thấp hơn rõ rệt.","metadata":{}},{"cell_type":"code","source":"df = train_sample.copy()\n\n# Tổng doanh số theo ngày\ndaily_sales = (\n    df.groupby('date', as_index=False)['unit_sales']\n    .sum()\n    .rename(columns={'unit_sales': 'total_sales'})\n)\n\n# Tính rolling mean (30 ngày)\ndaily_sales['rolling_30'] = daily_sales['total_sales'].rolling(window=30, min_periods=1).mean()\n\n# Vẽ biểu đồ\nplt.figure(figsize=(14, 6))\nplt.plot(\n    daily_sales['date'], daily_sales['total_sales'],\n    color='lightgray', label='Daily Total Sales', alpha=0.6\n)\nplt.plot(\n    daily_sales['date'], daily_sales['rolling_30'],\n    color='red', linewidth=2, label='30-Day Rolling Average'\n)\nplt.title('30-Day Rolling Average – Smoothed Sales Trend', fontsize=14, pad=10)\nplt.xlabel('Date', fontsize=12)\nplt.ylabel('Total Unit Sales', fontsize=12)\nplt.legend()\nplt.grid(alpha=0.3)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:24.294666Z","iopub.execute_input":"2025-10-24T07:16:24.295043Z","iopub.status.idle":"2025-10-24T07:16:25.960057Z","shell.execute_reply.started":"2025-10-24T07:16:24.295011Z","shell.execute_reply":"2025-10-24T07:16:25.958845Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biểu đồ trên cũng cho thấy đỉnh doanh số lặp lại định kỳ hằng năm, đặc biệt vào cuối năm. Giữa các chu kỳ tăng thì có những giai đoạn giảm nhẹ, thể hiện biến động tạm thời trong nhu cầu tiêu dùng","metadata":{}},{"cell_type":"code","source":"# Giả sử bạn đang dùng bản train_sample (10%)\ndf = train_sample.copy()\n\n# Chọn 1 sản phẩm và 1 cửa hàng cụ thể\nitem_id = 1503844\nstore_id = 1\n\n# Kiểm tra xem item và store có tồn tại trong mẫu không\nif (item_id in df['item_nbr'].unique()) and (store_id in df['store_nbr'].unique()):\n    # Lọc dữ liệu\n    sample = df[(df['item_nbr'] == item_id) & (df['store_nbr'] == store_id)]\n\n    if not sample.empty:\n        # Tính tổng tích lũy theo ngày\n        sales_by_date = (\n            sample.groupby('date', as_index=False)['unit_sales']\n            .sum()\n            .rename(columns={'unit_sales': 'daily_sales'})\n        )\n        sales_by_date['cumulative_sales'] = sales_by_date['daily_sales'].cumsum()\n\n        # Vẽ biểu đồ tích lũy\n        plt.figure(figsize=(14, 6))\n        plt.plot(sales_by_date['date'], sales_by_date['cumulative_sales'],\n                 color='blue', linewidth=2)\n        plt.title(f'Cumulative Sales Over Time – Item {item_id} at Store {store_id}', fontsize=14, pad=10)\n        plt.xlabel('Date', fontsize=12)\n        plt.ylabel('Cumulative Unit Sales', fontsize=12)\n        plt.grid(alpha=0.3)\n        plt.tight_layout()\n        plt.show()\n    else:\n        print(f\"Không tìm thấy dữ liệu cho item {item_id} tại store {store_id} trong sample.\")\nelse:\n    print(f\"Item {item_id} hoặc store {store_id} không có trong sample (10%).\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:25.961251Z","iopub.execute_input":"2025-10-24T07:16:25.961641Z","iopub.status.idle":"2025-10-24T07:16:27.091548Z","shell.execute_reply.started":"2025-10-24T07:16:25.961615Z","shell.execute_reply":"2025-10-24T07:16:27.090461Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Dữ liệu tại một cửa hàng cụ thể cho ta thấy doanh số cũng tăng đều và tăng mạnh từ năm 2015 trở đi.","metadata":{}},{"cell_type":"markdown","source":"## 3. Seasonality Detection (tìm pattern theo tháng, tuần)","metadata":{}},{"cell_type":"code","source":"train_dates = train[['date']].drop_duplicates().copy()\ntrain_dates['dset'] = 'train'\n\ntest_dates = test[['date']].drop_duplicates().copy()\ntest_dates['dset'] = 'test'\n\nfoo = pd.concat([train_dates, test_dates], ignore_index=True)\nfoo['year'] = foo['date'].dt.year\n\n\ndef safe_replace_year(x):\n    try:\n        return x.replace(year=2017)\n    except ValueError:\n        return datetime(2017, 2, 28)\n\nfoo['date'] = foo['date'].apply(safe_replace_year)\nfoo = foo.dropna(subset=['date'])\n\n# =====================\n# Vẽ biểu đồ timeline\n# =====================\nsns.set_theme(style=\"whitegrid\")\n\nfig, ax = plt.subplots(figsize=(14, 4))\n\nsns.scatterplot(\n    data=foo,\n    x='date',\n    y='year',\n    hue='dset',\n    palette={'train': '#3182bd', 'test': '#de2d26'},\n    s=180,\n    marker='|',\n    ax=ax\n)\n\n\n# Hiển thị đầy đủ các năm\nyears_sorted = sorted(foo['year'].unique())\nax.set_yticks(years_sorted)\nax.set_yticklabels(years_sorted)\n\n# Trục X hiển thị tháng\nax.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter(\"%B\"))\nplt.xticks(rotation=45, ha='right')\n\n# Tiêu đề & nhãn\nplt.title(\"Timeline of Train/Test Date Coverage\", fontsize=14, weight='bold', pad=20)\nplt.xlabel(\"Month (2017)\", labelpad=10)\nplt.ylabel(\"Year\")\n\n# Đảo trục Y\nax.invert_yaxis()\n\n# === Legend góc phải dưới, nằm ngoài khung ===\nplt.legend(\n    title=\"Data set\",\n    loc='lower right',\n    bbox_to_anchor=(1.15, -0.05),  # 🔹 đẩy legend ra khỏi vùng plot\n    frameon=True,\n    facecolor='white',\n    edgecolor='gray'\n)\n\n# === Tăng khoảng trống để không bị cắt ===\nplt.subplots_adjust(right=0.85, bottom=0.2, top=0.9)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:27.092869Z","iopub.execute_input":"2025-10-24T07:16:27.093438Z","iopub.status.idle":"2025-10-24T07:16:29.515948Z","shell.execute_reply.started":"2025-10-24T07:16:27.093404Z","shell.execute_reply":"2025-10-24T07:16:29.514432Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biều đồ giúp quan sát nhanh cấu trúc thời gian liên tục, không bị mất mốc. Khoảng thời gian của tập test nằm ở cuối năm 2017 tách biệt hoàn toàn khỏi dữ liệu để đảm bảo tính khách quan.","metadata":{}},{"cell_type":"code","source":"foo = (\n    train[['item_nbr', 'unit_sales']]\n    .merge(items[['item_nbr', 'family', 'class']], on='item_nbr', how='left')\n)\n\n# --- Tổng doanh số theo family ---\nsales_by_family = (\n    foo.groupby('family', as_index=False)['unit_sales']\n    .sum()\n    .sort_values('unit_sales', ascending=False)\n)\nsales_by_family['family_short'] = sales_by_family['family'].str.slice(0, 19)\n\n# --- Top 10 class bán chạy nhất ---\nsales_by_class = (\n    foo.groupby('class', as_index=False)['unit_sales']\n    .sum()\n    .sort_values('unit_sales', ascending=False)\n    .head(10)\n)\n\n# === Vẽ layout 2 biểu đồ ngang ===\nsns.set(style=\"whitegrid\")\nfig = plt.figure(figsize=(14, 9))\ngs = gridspec.GridSpec(2, 1, height_ratios=[3, 1])\n\n# --- Biểu đồ 1: Sales theo Family (nằm ngang + log scale) ---\nax1 = plt.subplot(gs[0])\nsns.barplot(\n    data=sales_by_family.head(20),  # hiển thị top 20 family cho dễ nhìn\n    y='family_short',\n    x='unit_sales',\n    palette='crest',\n    ax=ax1\n)\nax1.set_xscale('log')\nax1.set_title(\"Top 20 Item Families by Total Sales\", fontsize=14, weight='bold', pad=10)\nax1.set_xlabel(\"Total Sales (log scale)\", fontsize=12)\nax1.set_ylabel(\"Item Family\", fontsize=12)\nax1.tick_params(axis='y', labelsize=9)\nsns.despine(ax=ax1, left=True, bottom=True)\n\n# --- Biểu đồ 2: Top 10 Class ---\nax2 = plt.subplot(gs[1])\nsns.barplot(\n    data=sales_by_class,\n    y='class',\n    x='unit_sales',\n    palette='coolwarm',\n    ax=ax2\n)\nax2.set_title(\"Top 10 Best-Selling Item Classes\", fontsize=13, pad=10)\nax2.set_xlabel(\"Total Sales\", fontsize=12)\nax2.set_ylabel(\"Item Class\", fontsize=12)\nax2.tick_params(axis='y', labelsize=9)\nsns.despine(ax=ax2, left=True, bottom=True)\n\n# --- Điều chỉnh bố cục ---\nplt.tight_layout(pad=3)\nplt.subplots_adjust(right=0.95, bottom=0.08, top=0.93)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:16:29.517055Z","iopub.execute_input":"2025-10-24T07:16:29.517427Z","iopub.status.idle":"2025-10-24T07:17:04.375807Z","shell.execute_reply.started":"2025-10-24T07:16:29.517401Z","shell.execute_reply":"2025-10-24T07:17:04.374585Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Doanh số tập trung vào một số nhóm hàng chủ lực, trong đó Grocery I, Beverages, và Produce là ba nhóm có tổng doanh số cao nhất. Điều này phản ánh nhu cầu tiêu dùng thiết yếu chiếm phần lớn doanh thu của toàn hệ thống.\n\nCác nhóm như Cleaning, Dairy, Bread/Bakery và Meats cũng duy trì doanh số ổn định, cho thấy đặc trưng của ngành hàng tiêu dùng nhanh (FMCG) với mức bán lặp lại thường xuyên.\n\nBiểu đồ thang logarit (log scale) giúp làm rõ sự chênh lệch đáng kể giữa các nhóm hàng, một số nhóm có doanh số cao gấp hàng chục lần so với nhóm thấp nhất.\n\nỞ cấp độ chi tiết hơn, top 10 class cho thấy một số phân lớp sản phẩm có doanh số vượt trội, là đại diện tiêu biểu trong nhóm hàng bán chạy.","metadata":{}},{"cell_type":"markdown","source":"## 4. External Factors (oil, promo, holiday)","metadata":{}},{"cell_type":"code","source":"# --- Tổng sales theo ngày ---\nfoo = train.groupby('date', as_index=False)['unit_sales'].sum().rename(columns={'unit_sales': 'sales'})\n\n# --- Lọc dữ liệu oil theo thời gian tương ứng ---\noil_back = oil[oil['date'] > foo['date'].min()].copy()\n\n# --- Chuẩn hóa oilprice theo tỷ lệ sales ---\nmin_sales, max_sales = foo['sales'].min(), foo['sales'].max()\nmin_oil, max_oil = oil_back['dcoilwtico'].min(), oil_back['dcoilwtico'].max()\n\noil_back['oilprice_scaled'] = (\n    min_sales +\n    (oil_back['dcoilwtico'] - min_oil) / (max_oil - min_oil) * (max_sales - min_sales)\n)\n\n# --- Vẽ biểu đồ ---\nplt.figure(figsize=(14,6))\n\n# Tổng doanh số (đen)\nplt.plot(foo['date'], foo['sales'], color='black', label='Total Sales')\n\n# Giá dầu đã scale (xanh)\nplt.plot(oil_back['date'], oil_back['oilprice_scaled'], color='blue', label='Oil Price (scaled)')\n\n# Tiêu đề và format\nplt.title(\"Total Sales (black) with Oil Price (blue)\", fontsize=14, weight='bold')\nplt.xlabel(\"Date\")\nplt.ylabel(\"Total Sales (scaled units)\")\nplt.legend()\nplt.grid(alpha=0.3)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:17:04.376918Z","iopub.execute_input":"2025-10-24T07:17:04.377185Z","iopub.status.idle":"2025-10-24T07:17:08.786601Z","shell.execute_reply.started":"2025-10-24T07:17:04.377166Z","shell.execute_reply":"2025-10-24T07:17:08.785355Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Giá dầu và doanh số có xu hướng ngược chiều nhẹ: Khi giá dầu giảm mạnh (2012-2016), doanh số bán hàng lại tăng dần ổn định, cho thấy mối quan hệ nghịch nhẹ – giá dầu thấp giúp giảm chi phí vận chuyển, khuyến khích tiêu dùng.\n\nTính chu kỳ rõ rệt: Doanh số (màu đen) dao động lặp lại theo tuần và năm, trong khi giá dầu (màu xanh) biến động theo chu kỳ thị trường năng lượng toàn cầu.\n\nGiai đoạn sau 2016: Khi giá dầu phục hồi, doanh số vẫn duy trì ổn định, chứng tỏ tác động của giá dầu không mạnh nhưng có ảnh hưởng gián tiếp đến sức mua.\n","metadata":{}},{"cell_type":"code","source":"print(train)\nprint(\"=\")\nprint(oil)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:17:08.787626Z","iopub.execute_input":"2025-10-24T07:17:08.788124Z","iopub.status.idle":"2025-10-24T07:17:08.804905Z","shell.execute_reply.started":"2025-10-24T07:17:08.788081Z","shell.execute_reply":"2025-10-24T07:17:08.802791Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"foo = (\n    train.groupby('date', as_index=False)['unit_sales']\n    .sum()\n    .rename(columns={'unit_sales': 'sales'})\n)\n\n\nbar = (\n    foo.merge(oil, on='date', how='left')\n    .assign(\n        oil1=lambda df: df['dcoilwtico'] - df['dcoilwtico'].shift(1),\n        oil7=lambda df: df['dcoilwtico'] - df['dcoilwtico'].shift(7),\n        oil30=lambda df: df['dcoilwtico'] - df['dcoilwtico'].shift(30),\n        sales1=lambda df: df['sales'] - df['sales'].shift(1),\n        sales7=lambda df: df['sales'] - df['sales'].shift(7),\n        sales30=lambda df: df['sales'] - df['sales'].shift(30)\n    )\n    .dropna(subset=['oil1', 'oil7', 'oil30', 'sales1', 'sales7', 'sales30'])\n)\n\n\ncols = ['oil1', 'oil7', 'oil30', 'sales1', 'sales7', 'sales30']\ncorr_matrix = bar[cols].corr(method='spearman')\n\ncorr_matrix = corr_matrix.dropna(how='all', axis=0).dropna(how='all', axis=1)\ncorr_matrix = corr_matrix.fillna(0)\n\nmask = np.triu(np.ones_like(corr_matrix, dtype=bool))\n\nplt.figure(figsize=(8, 6))\nsns.heatmap(\n    corr_matrix,\n    mask=mask,                # chỉ hiện tam giác dưới\n    annot=True, fmt=\".2f\",    # hiển thị số, làm tròn 2 chữ số\n    cmap=\"coolwarm\", center=0,\n    cbar=True, square=True,\n    linewidths=0.5,\n    annot_kws={\"size\": 10, \"weight\": \"bold\"}\n)\nplt.title(\"Spearman Correlation: Oil vs Sales Changes (1, 7, 30 days)\", fontsize=14, weight='bold', pad=15)\nplt.xticks(rotation=45, ha='right')\nplt.yticks(rotation=0)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:17:08.807181Z","iopub.execute_input":"2025-10-24T07:17:08.807698Z","iopub.status.idle":"2025-10-24T07:17:12.817907Z","shell.execute_reply.started":"2025-10-24T07:17:08.807669Z","shell.execute_reply":"2025-10-24T07:17:12.815588Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Kết quả thể hiện rằng các hệ số tương quan giữa giá dầu và doanh số đều thấp (|r| < 0.3), chứng tỏ giá dầu không ảnh hưởng mạnh trực tiếp đến doanh số bán hàng. Tuy nhiên, ở chu kỳ 30 ngày, doanh số có tương quan dương nhẹ (r ≈ 0.29) với chính nó, phản ánh xu hướng ổn định và tính chu kỳ tự nhiên của nhu cầu tiêu dùng.\n\nTrong khi đó, các biến oil1, oil7, oil30 có tương quan khá cao với nhau (0.46–0.48), cho thấy biến động giá dầu thường kéo dài theo thời gian, nhưng không lan truyền đáng kể sang doanh số.\n\nTóm lại, giá dầu chỉ có tác động gián tiếp và yếu đến doanh thu, trong khi yếu tố mùa vụ và hành vi tiêu dùng nội tại có khả năng chi phối mạnh hơn, một phát hiện quan trọng để cân nhắc khi chọn biến đầu vào cho mô hình dự báo.\n","metadata":{}},{"cell_type":"code","source":"# --- 1. Tính median doanh số cho từng sản phẩm theo trạng thái khuyến mãi ---\nfoo = (\n    train.groupby(['item_nbr', 'onpromotion'], as_index=False)['unit_sales']\n    .median()\n    .rename(columns={'unit_sales': 'med_sales'})\n    .dropna(subset=['onpromotion'])\n)\n\n# --- 2. Pivot để tách TRUE / FALSE ---\npivot = foo.pivot(index='item_nbr', columns='onpromotion', values='med_sales').reset_index()\npivot.columns.name = None\npivot = pivot.rename(columns={True: 'promo_true', False: 'promo_false'})\n\n# --- 3. Giữ lại chỉ các sản phẩm có cả 2 loại dữ liệu ---\npivot = pivot.dropna(subset=['promo_true', 'promo_false'])\n\n# --- 4. Gom lại dữ liệu để vẽ ---\nfoo_long = pivot.melt(\n    id_vars='item_nbr',\n    value_vars=['promo_true', 'promo_false'],\n    var_name='promo',\n    value_name='med_sales'\n)\nfoo_long['promo'] = foo_long['promo'].replace({'promo_true': 'Promotion', 'promo_false': 'No Promotion'})\n\n# --- 5. Kết hợp với thông tin sản phẩm ---\nbar = (\n    foo_long.merge(items, on='item_nbr', how='left')\n)\n\n# --- 6. Biểu đồ 1: So sánh median sales giữa có và không khuyến mãi ---\nplt.figure(figsize=(8,6))\nsns.boxplot(data=foo_long, x='promo', y='med_sales', palette='Set2')\nplt.yscale('log')\nplt.title(\"Median Sales Distribution: With vs Without Promotion\", fontsize=14, weight='bold')\nplt.xlabel(\"Promotion Status\")\nplt.ylabel(\"Median Unit Sales (log scale)\")\nplt.tight_layout()\nplt.show()\n\n# --- 7. Biểu đồ 2: So sánh theo nhóm sản phẩm (family) ---\nbar['family_short'] = bar['family'].str.slice(0, 19)\n\nplt.figure(figsize=(14,7))\nsns.boxplot(\n    data=bar,\n    x='family_short',\n    y='med_sales',\n    hue='promo',\n    palette='coolwarm'\n)\nplt.yscale('log')\nplt.title(\"Median Sales by Product Family (Promotion vs No Promotion)\", fontsize=14, weight='bold')\nplt.xlabel(\"Product Family (truncated)\")\nplt.ylabel(\"Median Unit Sales (log scale)\")\nplt.xticks(rotation=45, ha='right')\nplt.legend(title=\"Promotion\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:17:12.819588Z","iopub.execute_input":"2025-10-24T07:17:12.819928Z","iopub.status.idle":"2025-10-24T07:17:35.681453Z","shell.execute_reply.started":"2025-10-24T07:17:12.819905Z","shell.execute_reply":"2025-10-24T07:17:35.680054Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Quan sát cho thấy, doanh số trung vị của nhóm có khuyến mãi (Promotion) cao hơn rõ rệt so với nhóm không khuyến mãi. Mặc dù cả hai nhóm đều có nhiều ngoại lệ (outliers) phản ánh các sản phẩm bán chạy đột biến, nhưng phần lớn dữ liệu của nhóm Promotion nằm ở mức doanh số cao hơn.\n\nĐiều này cho thấy chương trình khuyến mãi có tác động tích cực đến sức mua, giúp tăng sản lượng bán ra trung bình. Tuy nhiên, mức độ tác động không đồng đều giữa các sản phẩm, thể hiện qua sự phân tán rộng của dữ liệu.\n\nTóm lại, khuyến mãi là yếu tố quan trọng thúc đẩy doanh số, đặc biệt đối với các mặt hàng tiêu dùng nhanh, và cần được xem xét như một biến ảnh hưởng trong mô hình dự báo xu hướng tiêu dùng.\n","metadata":{}},{"cell_type":"code","source":"sns.set(style=\"whitegrid\")\n\n# --- Biểu đồ 1: Số lượng ngày lễ theo loại (type) ---\nplt.figure(figsize=(6,4))\nsns.countplot(data=holidays, x='type', palette='Set2')\nplt.title(\"Number of Holidays by Type\", fontsize=13, weight='bold')\nplt.xlabel(\"Holiday Type\")\nplt.ylabel(\"Count\")\nplt.xticks(rotation=30, ha='right')\nplt.tight_layout()\nplt.show()\n\n# --- Biểu đồ 2: Số lượng ngày lễ theo khu vực (locale) ---\nplt.figure(figsize=(6,4))\nsns.countplot(data=holidays, x='locale', palette='coolwarm')\nplt.title(\"Number of Holidays by Locale\", fontsize=13, weight='bold')\nplt.xlabel(\"Locale (National / Regional / Local)\")\nplt.ylabel(\"Count\")\nplt.tight_layout()\nplt.show()\n\n# --- Biểu đồ 3: 12 mô tả ngày lễ phổ biến nhất ---\ndesc_freq = (\n    holidays.groupby('description')\n    .size()\n    .reset_index(name='count')\n    .sort_values('count', ascending=False)\n    .head(12)\n)\n\nprint(desc_freq.head())  # giờ sẽ ra ['description', 'count']\nplt.figure(figsize=(8,5))\nsns.barplot(\n    data=desc_freq,\n    y='description',\n    x='count',\n    color='skyblue'\n)\nplt.title(\"Top 12 Most Frequent Holiday Descriptions\", fontsize=13, weight='bold')\nplt.xlabel(\"Frequency\")\nplt.ylabel(\"Holiday Description\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:17:35.682904Z","iopub.execute_input":"2025-10-24T07:17:35.683618Z","iopub.status.idle":"2025-10-24T07:17:36.949675Z","shell.execute_reply.started":"2025-10-24T07:17:35.683584Z","shell.execute_reply":"2025-10-24T07:17:36.948320Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biểu đồ Number of Holidays by Type cho thấy “Holiday” là loại chiếm tỷ trọng lớn nhất, tiếp theo là “Event” và “Additional”, trong khi các loại khác xuất hiện rất ít. Điều này chứng tỏ các ngày lễ chính thức và sự kiện đặc biệt đóng vai trò quan trọng, có thể tác động đáng kể đến biến động doanh số, đặc biệt trong ngành hàng tiêu dùng nhanh (FMCG).\n\nBiểu đồ Number of Holidays by Locale chỉ ra rằng đa số ngày lễ mang phạm vi quốc gia (National) và địa phương (Local), trong khi ngày lễ khu vực (Regional) chiếm tỷ lệ nhỏ. Điều này phản ánh ảnh hưởng mạnh mẽ của các ngày lễ có quy mô rộng đến hành vi tiêu dùng và nhu cầu thị trường.\n\nCuối cùng, biểu đồ Top 12 Most Frequent Holiday Descriptions cho thấy Carnaval là dịp lễ xuất hiện thường xuyên nhất, tiếp theo là Fundacion (kỷ niệm ngày thành lập các thành phố) và Navidad (Giáng sinh). Kết quả này cho thấy các lễ hội văn hóa và tôn giáo lặp lại hàng năm có ảnh hưởng rõ rệt đến thói quen mua sắm của người tiêu dùng, đặc biệt trong các ngành hàng thực phẩm, đồ uống và quà tặng, những yếu tố quan trọng cần được tích hợp vào mô hình dự báo tiêu dùng theo mùa vụ.","metadata":{}},{"cell_type":"markdown","source":"## 5. Region & Product (khác biệt vùng, nhóm hàng)\n","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\", category=FutureWarning)\n\n# --- Gộp dữ liệu train với thông tin cửa hàng ---\nfoo = (\n    train[['store_nbr', 'date', 'unit_sales']]\n    .merge(stores[['store_nbr', 'cluster']], on='store_nbr', how='left')\n)\n\n# --- Biểu đồ 1: Total Sales by Store Cluster ---\nsales_by_cluster = (\n    foo.groupby('cluster', as_index=False)['unit_sales']\n    .sum()\n    .sort_values('unit_sales', ascending=False)\n)\n\nplt.figure(figsize=(8,5))\nsns.barplot(\n    data=sales_by_cluster,\n    x='cluster',\n    y='unit_sales',\n    palette='crest'\n)\nplt.title(\"Total Sales by Store Cluster\", fontsize=14, weight='bold')\nplt.xlabel(\"Store Cluster\")\nplt.ylabel(\"Total Unit Sales\")\nplt.xticks(rotation=0)\nplt.tight_layout()\nplt.show()\n\n# --- Biểu đồ 2: Sales Trend by Cluster (Time Series) ---\nsales_trend = (\n    foo.groupby(['date', 'cluster'], as_index=False)['unit_sales']\n    .sum()\n)\n\nplt.figure(figsize=(12,6))\nsns.lineplot(\n    data=sales_trend,\n    x='date',\n    y='unit_sales',\n    hue='cluster',\n    palette='tab10'\n)\nplt.title(\"Sales Trend by Cluster (Time Series)\", fontsize=14, weight='bold')\nplt.xlabel(\"Date\")\nplt.ylabel(\"Total Unit Sales\")\nplt.legend(title='Cluster', bbox_to_anchor=(1.05, 1), loc='upper left')\nplt.grid(alpha=0.3)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:17:36.950903Z","iopub.execute_input":"2025-10-24T07:17:36.951324Z","iopub.status.idle":"2025-10-24T07:17:59.812120Z","shell.execute_reply.started":"2025-10-24T07:17:36.951291Z","shell.execute_reply":"2025-10-24T07:17:59.810987Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biểu đồ Total Sales by Store Cluster cho thấy sự khác biệt rõ rệt về doanh số giữa các cụm cửa hàng.\nCụ thể, cluster 14 đạt tổng doanh số cao nhất, vượt trội so với các nhóm khác, cho thấy đây có thể là khu vực kinh doanh trọng điểm hoặc đô thị lớn. Trong khi một số cụm như 7, 12 và 16 ghi nhận mức bán thấp hơn đáng kể. \n\nQuan sát biểu đồ Sales Trend by Cluster (Time Series) cho thấy các cụm có xu hướng tăng trưởng ổn định theo thời gian, đặc biệt là cluster 14, nổi bật với mức doanh số cao vượt trội và biên độ dao động lớn, chứng tỏ đây là khu vực kinh doanh sôi động, có tính mùa vụ mạnh. Các cụm khác như 6, 8 và 10 cũng cho thấy xu hướng tăng đều, trong khi các cụm nhỏ hơn duy trì mức doanh số ổn định hơn.\n\nĐiều này phản ánh rằng doanh số phụ thuộc mạnh vào đặc điểm khu vực, và việc phân cụm cửa hàng giúp doanh nghiệp xác định vùng có tiềm năng cao, từ đó ưu tiên nguồn lực, chiến dịch khuyến mãi hoặc dự báo riêng cho từng khu vực nhằm nâng cao hiệu quả kinh doanh.","metadata":{}},{"cell_type":"code","source":"# --- Gộp dữ liệu train + items ---\nfoo = (\n    train[['item_nbr', 'date', 'unit_sales']]\n    .merge(items[['item_nbr', 'family']], on='item_nbr', how='left')\n)\n\n# ---  Top 10 Product Families by Total Sales ---\nsales_by_family = (\n    foo.groupby('family', as_index=False)['unit_sales']\n    .sum()\n    .sort_values('unit_sales', ascending=False)\n    .head(10)\n)\n\nplt.figure(figsize=(10,5))\nsns.barplot(\n    data=sales_by_family,\n    x='unit_sales',\n    y='family',\n    palette='crest'\n)\nplt.title(\"Top 10 Product Families by Total Sales\", fontsize=14, weight='bold')\nplt.xlabel(\"Total Unit Sales\")\nplt.ylabel(\"Product Family\")\nplt.tight_layout()\nplt.show()\n\n# ---  Seasonal Pattern by Family (family × month) ---\nfoo['month'] = foo['date'].dt.month_name()\n\n# Tính doanh số trung bình theo tháng và family\nseasonal_pattern = (\n    foo.groupby(['family', 'month'], as_index=False)['unit_sales']\n    .mean()\n)\n\n# Sắp xếp tháng đúng thứ tự\nmonth_order = [\n    'January', 'February', 'March', 'April', 'May', 'June',\n    'July', 'August', 'September', 'October', 'November', 'December'\n]\nseasonal_pattern['month'] = pd.Categorical(seasonal_pattern['month'], categories=month_order, ordered=True)\n\nplt.figure(figsize=(14,6))\nsns.heatmap(\n    seasonal_pattern.pivot_table(index='family', columns='month', values='unit_sales'),\n    cmap='YlGnBu',\n    linewidths=0.5\n)\nplt.title(\"Seasonal Pattern by Product Family (Average Monthly Sales)\", fontsize=14, weight='bold')\nplt.xlabel(\"Month\")\nplt.ylabel(\"Product Family\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:17:59.813472Z","iopub.execute_input":"2025-10-24T07:17:59.813849Z","iopub.status.idle":"2025-10-24T07:19:54.522594Z","shell.execute_reply.started":"2025-10-24T07:17:59.813825Z","shell.execute_reply":"2025-10-24T07:19:54.520282Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Các nhóm GROCERY I và BEVERAGES chiếm tỷ trọng doanh số lớn nhất. Các nhóm tiếp theo như PRODUCE, CLEANING và DAIRY cũng đạt mức doanh số cao, cho thấy nhu cầu ổn định quanh năm. Nhóm hàng thiết yếu dẫn đầu doanh số, là trọng tâm cần ưu tiên trong dự báo và tối ưu chuỗi cung ứng.\n\nCác nhóm như PRODUCE và POULTRY có mức tiêu thụ cao quanh năm, phản ánh nhu cầu ổn định của hàng tươi sống. Trong khi đó, nhóm GROCERY I và DAIRY có xu hướng tăng mạnh vào tháng 12, cho thấy hiệu ứng mùa lễ hội. Một số nhóm như BOOKS hay CELEBRATION có doanh số đột biến trong các dịp đặc biệt, còn AUTOMOTIVE và HARDWARE duy trì ở mức thấp. Hành vi tiêu dùng thể hiện rõ tính mùa vụ, đặc biệt ở các nhóm hàng thực phẩm và tiêu dùng nhanh, là yếu tố cần thiết khi xây dựng mô hình dự báo theo tháng.\n\n","metadata":{}},{"cell_type":"markdown","source":"## 6. Event & Correlation (động đất, mối quan hệ biến số)","metadata":{}},{"cell_type":"code","source":"# === Giả sử bạn đã có: train_sample (10%) và stores ===\n# train_sample: cột ['date', 'store_nbr', 'unit_sales']\n# stores: cột ['store_nbr', 'state', 'city', 'cluster', ...]\n\n# Gộp dữ liệu với thông tin cửa hàng\ntrain_store = train_sample.merge(stores, on='store_nbr', how='left')\n\n# Lọc ra 2 tỉnh chịu ảnh hưởng động đất\neq_states = ['Manabi', 'Esmeraldas']\nregion_sales = (\n    train_store[train_store['state'].isin(eq_states)]\n    .groupby('date', as_index=False)['unit_sales']\n    .sum()\n)\n\n# Tính trung bình trượt (14 ngày)\nregion_sales['rolling_mean'] = region_sales['unit_sales'].rolling(window=14, center=True).mean()\n\n# === Vẽ biểu đồ ===\nplt.figure(figsize=(12,6))\nsns.lineplot(data=region_sales, x='date', y='unit_sales', color='tomato', alpha=0.5, label='Daily Sales')\nsns.lineplot(data=region_sales, x='date', y='rolling_mean', color='red', linewidth=2, label='14-day Rolling Avg')\n\n# Ngày xảy ra động đất\nearthquake_date = pd.to_datetime('2016-04-16')\n\n# Đánh dấu trên biểu đồ\nplt.axvline(earthquake_date, color='black', linestyle='--', linewidth=1.3)\nplt.text(earthquake_date, region_sales['unit_sales'].max()*0.9,\n         'Earthquake\\n2016-04-16', rotation=90, ha='right', fontsize=9, color='black')\n\n# Tùy chỉnh trục & style\nplt.title('Impact of 2016 Earthquake on Sales (Manabí & Esmeraldas)', fontsize=14, weight='bold')\nplt.xlabel('Date')\nplt.ylabel('Total Unit Sales (10% Sample)')\nplt.grid(alpha=0.3)\nplt.legend()\nplt.gca().xaxis.set_major_formatter(DateFormatter('%b %Y'))\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:19:54.525944Z","iopub.execute_input":"2025-10-24T07:19:54.526527Z","iopub.status.idle":"2025-10-24T07:19:58.163837Z","shell.execute_reply.started":"2025-10-24T07:19:54.526466Z","shell.execute_reply":"2025-10-24T07:19:58.162235Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biểu đồ Impact of 2016 Earthquake on Sales (Manabí & Esmeraldas) thể hiện xu hướng doanh số tại hai tỉnh bị ảnh hưởng bởi trận động đất năm 2016.\n\nTrước tháng 4/2016, doanh số tăng trưởng ổn định với biến động nhẹ theo chu kỳ. Tuy nhiên, ngay sau ngày 16/04/2016, doanh số giảm mạnh trong ngắn hạn, phản ánh sự gián đoạn hoạt động bán lẻ và nhu cầu tiêu dùng. Sau đó, đường trung bình 14 ngày cho thấy sự phục hồi dần, khi các cửa hàng và chuỗi cung ứng được khôi phục.\n\nTrận động đất gây sụt giảm doanh số tạm thời, nhưng thị trường đã phục hồi khá nhanh, cho thấy sức chống chịu và khả năng thích ứng của khu vực bán lẻ.\n","metadata":{}},{"cell_type":"code","source":"# === Đọc dữ liệu cần thiết ===\noil_path = \"/kaggle/working/oil.csv\"\nholidays_path = \"/kaggle/working/holidays_events.csv\"\n\noil = pd.read_csv(oil_path, parse_dates=['date'])\nholidays = pd.read_csv(holidays_path, parse_dates=['date'])\n\n# --- Giả sử bạn đã có train_sample = 10% dữ liệu ---\n# train_sample: ['date', 'store_nbr', 'item_nbr', 'unit_sales', 'onpromotion']\n\n# Tổng doanh số theo ngày (chỉ từ mẫu 10%)\nsales_daily = (\n    train_sample.groupby('date', as_index=False)['unit_sales']\n    .sum()\n    .rename(columns={'unit_sales': 'total_sales'})\n)\n\n# Thêm giá dầu\nmerged = sales_daily.merge(oil, on='date', how='left')\n\n# Tính tổng số sản phẩm đang được khuyến mãi mỗi ngày\npromo_daily = (\n    train_sample.groupby('date', as_index=False)['onpromotion']\n    .sum()\n    .rename(columns={'onpromotion': 'total_promos'})\n)\nmerged = merged.merge(promo_daily, on='date', how='left')\n\n# Thêm cột đánh dấu ngày lễ\nholiday_days = holidays['date'].unique()\nmerged['holiday_flag'] = merged['date'].isin(holiday_days).astype(int)\n\n# Chuẩn hóa tên cột (để dễ nhìn khi vẽ)\nmerged = merged.rename(columns={'dcoilwtico': 'oil_price'})\n\n# --- Tính tương quan ---\ncorr = merged[['total_sales', 'oil_price', 'total_promos', 'holiday_flag']].corr()\n\n# --- Vẽ Heatmap ---\nplt.figure(figsize=(7,5))\nsns.heatmap(\n    corr, annot=True, cmap='coolwarm', fmt=\".2f\", center=0,\n    linewidths=0.5, cbar_kws={\"shrink\": 0.8}\n)\nplt.title(\"Correlation between Sales and External Factors (10% Sample)\", fontsize=13, weight='bold')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:19:58.165190Z","iopub.execute_input":"2025-10-24T07:19:58.165564Z","iopub.status.idle":"2025-10-24T07:19:59.962023Z","shell.execute_reply.started":"2025-10-24T07:19:58.165542Z","shell.execute_reply":"2025-10-24T07:19:59.960115Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biểu đồ heatmap thể hiện mối tương quan giữa doanh số và các yếu tố bên ngoài. Kết quả cho thấy giá dầu có tương quan âm mạnh với doanh số (-0.70), nghĩa là khi giá dầu tăng, doanh số có xu hướng giảm nhẹ. Khuyến mãi (total_promos) có tương quan dương (0.57), cho thấy hoạt động giảm giá giúp kích cầu rõ rệt. Trong khi đó, ngày lễ (holiday_flag) có tương quan yếu (0.10), phản ánh ảnh hưởng không đáng kể so với hai yếu tố còn lại. Nhìn chung, khuyến mãi là yếu tố tác động tích cực mạnh nhất, còn giá dầu là yếu tố kinh tế ảnh hưởng ngược chiều đến tiêu dùng.","metadata":{}},{"cell_type":"markdown","source":"## FEATURE ENGINEERING","metadata":{}},{"cell_type":"markdown","source":"## 1. Time-related Features (tạo các đặc trưng liên quan đến thời gian)","metadata":{}},{"cell_type":"code","source":"train['day_of_week'] = train['date'].dt.dayofweek \ntrain['month'] = train['date'].dt.month\ntrain['year'] = train['date'].dt.year\ntrain['week_of_year'] = train['date'].dt.isocalendar().week\ntrain['is_weekend'] = train['day_of_week'].isin([5, 6]).astype(int)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-24T07:19:59.964318Z","iopub.execute_input":"2025-10-24T07:19:59.965794Z","iopub.status.idle":"2025-10-24T07:20:27.220086Z","shell.execute_reply.started":"2025-10-24T07:19:59.965745Z","shell.execute_reply":"2025-10-24T07:20:27.217914Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Tạo đặc trưng thời gian:\n- **day_of_week** chuẩn hóa 0 = Monday (Thứ Hai), cứ thể tiếp tục đến 6 = Sunday (Chủ Nhật) giúp mô hình hiểu được xu hướng bán hàng theo các ngày trong tuần.\n\n- month, year, week_of_year chuẩn hóa theo tháng, năm và tuần của năm, cho phép phát hiện tính mùa vụ hoặc xu hướng theo năm/tháng.\n\n- is_weekend chuẩn hóa theo biến nhị phân 0 = Trong tuần (Monday đến Friday), 1 = Cuối tuần (Saturday và Sunday).","metadata":{}},{"cell_type":"markdown","source":"## 2. Lag features (tạo các đặc trưng trễ)","metadata":{}}]}