{"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":7391,"databundleVersionId":44328,"isSourceIdPinned":false}],"dockerImageVersionId":31328,"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,"execution":{"iopub.status.busy":"2026-05-13T20:56:26.940884Z","iopub.execute_input":"2026-05-13T20:56:26.941818Z","iopub.status.idle":"2026-05-13T20:56:28.080152Z","shell.execute_reply.started":"2026-05-13T20:56:26.941757Z","shell.execute_reply":"2026-05-13T20:56:28.078922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# IE212 - PoC 2: Wavelet Transform Preprocessing\n# Dataset: Kaggle Favorita Grocery Sales Forecasting bản cũ (.csv.7z)\n# SAFE RAM VERSION\n# ============================================================\n\n# ============================================================\n# CELL 1 - Install libraries\n# ============================================================\n!pip install -q PyWavelets lightgbm py7zr\n\n\n# ============================================================\n# CELL 2 - Import libraries\n# ============================================================\nimport os\nimport gc\nimport tempfile\nimport warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport py7zr\nimport pywt\nfrom lightgbm import LGBMRegressor\nfrom sklearn.metrics import mean_squared_error\n\n\n# ============================================================\n# CELL 3 - Config\n# ============================================================\nDATA_DIR = \"/kaggle/input/competitions/favorita-grocery-sales-forecasting\"\n\nTRAIN_PATH = f\"{DATA_DIR}/train.csv.7z\"\nTEST_PATH = f\"{DATA_DIR}/test.csv.7z\"\nITEMS_PATH = f\"{DATA_DIR}/items.csv.7z\"\nOIL_PATH = f\"{DATA_DIR}/oil.csv.7z\"\nHOLIDAYS_PATH = f\"{DATA_DIR}/holidays_events.csv.7z\"\nSTORES_PATH = f\"{DATA_DIR}/stores.csv.7z\"\nTRANSACTIONS_PATH = f\"{DATA_DIR}/transactions.csv.7z\"\n\nRANDOM_STATE = 42\nVALID_DAYS = 30\nTARGET_COL = \"unit_sales\"\nGROUP_COLS = [\"store_nbr\", \"item_nbr\"]\n\n# IMPORTANT:\n# True: chạy nhẹ để không sập kernel.\n# False: chỉ dùng khi máy/Kaggle đủ RAM.\nDEBUG_MODE = True\n\n# Số dòng train đọc ban đầu khi debug.\n# Nếu vẫn nặng, giảm xuống 500_000.\nDEBUG_READ_ROWS = 1_000_000\n\n# Số chuỗi store-item giữ lại để test Wavelet.\nDEBUG_N_GROUPS = 80\n\nN_ESTIMATORS_DEBUG = 100\nN_ESTIMATORS_FULL = 500\n\n\n# ============================================================\n# CELL 4 - Extract .7z\n# ============================================================\ndef extract_7z(file_path):\n    temp_dir = tempfile.mkdtemp()\n    with py7zr.SevenZipFile(file_path, mode=\"r\") as z:\n        z.extractall(path=temp_dir)\n\n    csv_files = [f for f in os.listdir(temp_dir) if f.endswith(\".csv\")]\n    if len(csv_files) == 0:\n        raise FileNotFoundError(f\"No csv found inside {file_path}\")\n\n    return os.path.join(temp_dir, csv_files[0])\n\n\ntrain_csv = extract_7z(TRAIN_PATH)\nstores_csv = extract_7z(STORES_PATH)\nitems_csv = extract_7z(ITEMS_PATH)\noil_csv = extract_7z(OIL_PATH)\nholidays_csv = extract_7z(HOLIDAYS_PATH)\ntransactions_csv = extract_7z(TRANSACTIONS_PATH)\n\nprint(\"Extract done\")\n\n\n# ============================================================\n# CELL 5 - Load data safely\n# ============================================================\n# Lưu ý: KHÔNG đọc full train khi debug vì file rất lớn.\n# Chỉ đọc các cột cần thiết.\n\ntrain_usecols = [\"id\", \"date\", \"store_nbr\", \"item_nbr\", \"unit_sales\", \"onpromotion\"]\n\nif DEBUG_MODE:\n    train = pd.read_csv(\n        train_csv,\n        usecols=train_usecols,\n        parse_dates=[\"date\"],\n        nrows=DEBUG_READ_ROWS\n    )\nelse:\n    train = pd.read_csv(\n        train_csv,\n        usecols=train_usecols,\n        parse_dates=[\"date\"]\n    )\n\nstores = pd.read_csv(stores_csv)\nitems = pd.read_csv(items_csv)\noil = pd.read_csv(oil_csv, parse_dates=[\"date\"])\nholidays = pd.read_csv(holidays_csv, parse_dates=[\"date\"])\ntransactions = pd.read_csv(transactions_csv, parse_dates=[\"date\"])\n\nprint(\"train:\", train.shape)\nprint(\"stores:\", stores.shape)\nprint(\"items:\", items.shape)\nprint(\"oil:\", oil.shape)\nprint(\"holidays:\", holidays.shape)\nprint(\"transactions:\", transactions.shape)\nprint(\"train columns:\", train.columns.tolist())\ntrain.head()\n\n\n# ============================================================\n# CELL 6 - Reduce memory\n# ============================================================\ndef reduce_memory(df):\n    for col in df.columns:\n        col_type = df[col].dtype\n\n        if np.issubdtype(col_type, np.datetime64):\n            continue\n\n        if col_type == object or str(col_type) == \"category\":\n            continue\n\n        c_min = df[col].min()\n        c_max = df[col].max()\n\n        if str(col_type).startswith(\"int\"):\n            if c_min >= np.iinfo(np.int8).min and c_max <= np.iinfo(np.int8).max:\n                df[col] = df[col].astype(np.int8)\n            elif c_min >= np.iinfo(np.int16).min and c_max <= np.iinfo(np.int16).max:\n                df[col] = df[col].astype(np.int16)\n            elif c_min >= np.iinfo(np.int32).min and c_max <= np.iinfo(np.int32).max:\n                df[col] = df[col].astype(np.int32)\n        else:\n            df[col] = df[col].astype(np.float32)\n\n    return df\n\n\ntrain[TARGET_COL] = np.clip(train[TARGET_COL], 0, None).astype(np.float32)\ntrain[\"onpromotion\"] = train[\"onpromotion\"].fillna(False).astype(np.int8)\n\ntrain = reduce_memory(train)\nstores = reduce_memory(stores)\nitems = reduce_memory(items)\noil = reduce_memory(oil)\ntransactions = reduce_memory(transactions)\n\ngc.collect()\n\n\n# ============================================================\n# CELL 7 - DEBUG FILTER BEFORE MERGE\n# ============================================================\n# Đây là cell quan trọng để tránh sập kernel.\n# Lọc chuỗi store-item trước, rồi mới merge metadata.\n\nif DEBUG_MODE:\n    group_sizes = (\n        train.groupby(GROUP_COLS)\n        .size()\n        .reset_index(name=\"n\")\n        .sort_values(\"n\", ascending=False)\n        .head(DEBUG_N_GROUPS)\n    )\n\n    train_small = train.merge(group_sizes[GROUP_COLS], on=GROUP_COLS, how=\"inner\")\n    train_small = train_small.sort_values(GROUP_COLS + [\"date\"]).reset_index(drop=True)\n    print(\"DEBUG train_small:\", train_small.shape)\nelse:\n    train_small = train.sort_values(GROUP_COLS + [\"date\"]).reset_index(drop=True)\n    print(\"FULL train_small:\", train_small.shape)\n\n# Giải phóng train gốc nếu debug để giảm RAM\nif DEBUG_MODE:\n    del train\n    gc.collect()\n\n\n# ============================================================\n# CELL 8 - Merge raw data AFTER filtering\n# ============================================================\ndef prepare_base_df(train_df, stores, items, oil, holidays, transactions):\n    df = train_df.copy()\n\n    # Merge stores\n    store_cols = [\"store_nbr\", \"city\", \"state\", \"type\", \"cluster\"]\n    df = df.merge(stores[store_cols], on=\"store_nbr\", how=\"left\")\n\n    # Merge items\n    item_cols = [\"item_nbr\", \"family\", \"class\", \"perishable\"]\n    df = df.merge(items[item_cols], on=\"item_nbr\", how=\"left\")\n\n    # Merge oil\n    df = df.merge(oil[[\"date\", \"dcoilwtico\"]], on=\"date\", how=\"left\")\n    df[\"dcoilwtico\"] = df[\"dcoilwtico\"].ffill().bfill().fillna(0)\n\n    # Merge transactions\n    df = df.merge(transactions[[\"date\", \"store_nbr\", \"transactions\"]], on=[\"date\", \"store_nbr\"], how=\"left\")\n    df[\"transactions\"] = df[\"transactions\"].fillna(0)\n\n    # Holiday features\n    hol = holidays.copy()\n    hol[\"is_holiday\"] = 1\n    hol[\"is_transferred\"] = (hol[\"transferred\"] == True).astype(int)\n\n    hol_agg = hol.groupby(\"date\", as_index=False).agg(\n        is_holiday=(\"is_holiday\", \"max\"),\n        is_transferred=(\"is_transferred\", \"max\")\n    )\n\n    df = df.merge(hol_agg, on=\"date\", how=\"left\")\n    df[\"is_holiday\"] = df[\"is_holiday\"].fillna(0).astype(np.int8)\n    df[\"is_transferred\"] = df[\"is_transferred\"].fillna(0).astype(np.int8)\n\n    # Date features\n    df[\"dayofweek\"] = df[\"date\"].dt.dayofweek.astype(np.int8)\n    df[\"month\"] = df[\"date\"].dt.month.astype(np.int8)\n    df[\"year\"] = df[\"date\"].dt.year.astype(np.int16)\n    df[\"day\"] = df[\"date\"].dt.day.astype(np.int8)\n    df[\"is_weekend\"] = df[\"dayofweek\"].isin([5, 6]).astype(np.int8)\n\n    return reduce_memory(df)\n\n\ndf = prepare_base_df(train_small, stores, items, oil, holidays, transactions)\nprint(\"After merge:\", df.shape)\nprint(\"Columns:\", df.columns.tolist())\ndf.head()\n\n\ngc.collect()\n\n\n# ============================================================\n# CELL 9 - Wavelet denoising functions\n# ============================================================\ndef wavelet_denoise_array(values, wavelet=\"db4\", threshold_mode=\"soft\", level=3):\n    values = np.asarray(values, dtype=float)\n\n    if len(values) < 16:\n        return np.clip(values, 0, None).astype(np.float32)\n\n    s = pd.Series(values).ffill().bfill().fillna(0).values\n\n    max_level = pywt.dwt_max_level(\n        data_len=len(s),\n        filter_len=pywt.Wavelet(wavelet).dec_len\n    )\n    level = min(level, max_level)\n\n    if level < 1:\n        return np.clip(s, 0, None).astype(np.float32)\n\n    coeffs = pywt.wavedec(s, wavelet=wavelet, mode=\"periodization\", level=level)\n\n    detail = coeffs[-1]\n    sigma = np.median(np.abs(detail - np.median(detail))) / 0.6745\n    threshold = sigma * np.sqrt(2 * np.log(len(s)))\n\n    new_coeffs = [coeffs[0]]\n    for c in coeffs[1:]:\n        new_coeffs.append(pywt.threshold(c, value=threshold, mode=threshold_mode))\n\n    reconstructed = pywt.waverec(new_coeffs, wavelet=wavelet, mode=\"periodization\")\n    reconstructed = reconstructed[:len(s)]\n    reconstructed = np.clip(reconstructed, 0, None)\n\n    return reconstructed.astype(np.float32)\n\n\ndef apply_wavelet_full_dataset(df, group_cols=GROUP_COLS, target_col=TARGET_COL):\n    df_out = df.sort_values(group_cols + [\"date\"]).copy()\n\n    df_out[\"smoothed_wavelet_hard\"] = np.nan\n    df_out[\"smoothed_wavelet_soft\"] = np.nan\n\n    grouped = df_out.groupby(group_cols, sort=False)\n    total_groups = grouped.ngroups\n\n    for i, (keys, group) in enumerate(grouped, start=1):\n        if i % 50 == 0:\n            print(f\"Processed {i}/{total_groups} groups...\")\n\n        idx = group.index\n        y = group[target_col].values\n\n        df_out.loc[idx, \"smoothed_wavelet_hard\"] = wavelet_denoise_array(\n            y, wavelet=\"db4\", threshold_mode=\"hard\", level=3\n        )\n        df_out.loc[idx, \"smoothed_wavelet_soft\"] = wavelet_denoise_array(\n            y, wavelet=\"db4\", threshold_mode=\"soft\", level=3\n        )\n\n    df_out[\"smoothed_wavelet_hard\"] = np.clip(df_out[\"smoothed_wavelet_hard\"], 0, None).astype(np.float32)\n    df_out[\"smoothed_wavelet_soft\"] = np.clip(df_out[\"smoothed_wavelet_soft\"], 0, None).astype(np.float32)\n\n    return reduce_memory(df_out)\n\n\n# ============================================================\n# CELL 10 - Apply Wavelet\n# ============================================================\ndf_wavelet = apply_wavelet_full_dataset(df)\n\nprint(df_wavelet[[TARGET_COL, \"smoothed_wavelet_hard\", \"smoothed_wavelet_soft\"]].describe())\nprint(\"Min values:\")\nprint(df_wavelet[[TARGET_COL, \"smoothed_wavelet_hard\", \"smoothed_wavelet_soft\"]].min())\ndf_wavelet.head()\n\n\n# ============================================================\n# CELL 11 - Visual check: Actual vs Hard vs Soft\n# ============================================================\ndef get_available_example(df):\n    ex = (\n        df.groupby(GROUP_COLS)\n        .size()\n        .reset_index(name=\"n\")\n        .sort_values(\"n\", ascending=False)\n        .iloc[0]\n    )\n    return int(ex[\"store_nbr\"]), int(ex[\"item_nbr\"])\n\n\ndef plot_wavelet_check(df, store_nbr=None, item_nbr=None, start_date=None, end_date=None):\n    if store_nbr is None or item_nbr is None:\n        store_nbr, item_nbr = get_available_example(df)\n\n    temp = df[(df[\"store_nbr\"] == store_nbr) & (df[\"item_nbr\"] == item_nbr)].copy()\n\n    if start_date is not None:\n        temp = temp[temp[\"date\"] >= pd.to_datetime(start_date)]\n    if end_date is not None:\n        temp = temp[temp[\"date\"] <= pd.to_datetime(end_date)]\n\n    plt.figure(figsize=(16, 5))\n    plt.plot(temp[\"date\"], temp[TARGET_COL], label=\"Actual\", alpha=0.6)\n    plt.plot(temp[\"date\"], temp[\"smoothed_wavelet_hard\"], label=\"Wavelet Hard\")\n    plt.plot(temp[\"date\"], temp[\"smoothed_wavelet_soft\"], label=\"Wavelet Soft\")\n    plt.title(f\"Actual vs Wavelet Smoothing - Store {store_nbr} - Item {item_nbr}\")\n    plt.xlabel(\"Date\")\n    plt.ylabel(\"Unit Sales\")\n    plt.legend()\n    plt.show()\n\nplot_wavelet_check(df_wavelet)\n\n\n# ============================================================\n# CELL 12 - Feature engineering\n# ============================================================\ndef create_features(df, signal_col):\n    df_feat = df.sort_values(GROUP_COLS + [\"date\"]).copy()\n\n    for lag in [1, 7, 14, 28]:\n        df_feat[f\"unit_sales_lag_{lag}\"] = df_feat.groupby(GROUP_COLS)[TARGET_COL].shift(lag)\n        df_feat[f\"{signal_col}_lag_{lag}\"] = df_feat.groupby(GROUP_COLS)[signal_col].shift(lag)\n\n    for window in [7, 14, 28]:\n        df_feat[f\"unit_sales_roll_mean_{window}\"] = (\n            df_feat.groupby(GROUP_COLS)[TARGET_COL]\n            .transform(lambda s: s.shift(1).rolling(window).mean())\n        )\n        df_feat[f\"unit_sales_roll_std_{window}\"] = (\n            df_feat.groupby(GROUP_COLS)[TARGET_COL]\n            .transform(lambda s: s.shift(1).rolling(window).std())\n        )\n        df_feat[f\"{signal_col}_roll_mean_{window}\"] = (\n            df_feat.groupby(GROUP_COLS)[signal_col]\n            .transform(lambda s: s.shift(1).rolling(window).mean())\n        )\n\n    df_feat = df_feat.fillna(0)\n    return reduce_memory(df_feat)\n\n\n# ============================================================\n# CELL 13 - Metric NWRMSLE\n# ============================================================\ndef nwrmsle(y_true, y_pred, weights=None):\n    y_true = np.asarray(y_true)\n    y_pred = np.clip(np.asarray(y_pred), 0, None)\n\n    if weights is None:\n        weights = np.ones_like(y_true)\n    else:\n        weights = np.asarray(weights)\n\n    log_error = np.log1p(y_pred) - np.log1p(y_true)\n    return np.sqrt(np.sum(weights * log_error ** 2) / np.sum(weights))\n\n\n# ============================================================\n# CELL 14 - Train / validate LightGBM\n# ============================================================\ndef train_evaluate_lgbm(df, signal_col):\n    df_model = create_features(df, signal_col=signal_col)\n    df_model = df_model.sort_values(\"date\").reset_index(drop=True)\n\n    unique_dates = np.sort(df_model[\"date\"].unique())\n\n    if len(unique_dates) < 5:\n        raise ValueError(\n            \"Không đủ số ngày để train/validate. Hãy tăng DEBUG_READ_ROWS hoặc DEBUG_N_GROUPS.\"\n        )\n\n    # DEBUG_MODE dùng split 80/20 theo thời gian để tránh train hoặc valid bị rỗng.\n    # FULL_MODE dùng 30 ngày cuối như validation.\n    if DEBUG_MODE:\n        split_idx = int(len(unique_dates) * 0.8)\n        split_idx = max(1, min(split_idx, len(unique_dates) - 1))\n        valid_start = pd.to_datetime(unique_dates[split_idx])\n    else:\n        max_date = pd.to_datetime(df_model[\"date\"].max())\n        valid_start = max_date - pd.Timedelta(days=VALID_DAYS)\n\n    train_df = df_model[df_model[\"date\"] < valid_start].copy()\n    valid_df = df_model[df_model[\"date\"] >= valid_start].copy()\n\n    print(\"Date min:\", df_model[\"date\"].min())\n    print(\"Date max:\", df_model[\"date\"].max())\n    print(\"Unique dates:\", len(unique_dates))\n    print(\"Valid start:\", valid_start)\n    print(\"Train shape:\", train_df.shape)\n    print(\"Valid shape:\", valid_df.shape)\n\n    if train_df.empty or valid_df.empty:\n        raise ValueError(\n            f\"Train hoặc validation đang rỗng. Train={train_df.shape}, Valid={valid_df.shape}. \"\n            \"Hãy tăng DEBUG_READ_ROWS hoặc giảm VALID_DAYS.\"\n        )\n        \n    cat_cols = [\"family\", \"city\", \"state\", \"type\"]\n    for col in cat_cols:\n        if col in train_df.columns:\n            train_df[col] = train_df[col].astype(\"category\")\n            valid_df[col] = valid_df[col].astype(\"category\")\n\n    drop_cols = [\"id\", \"date\", TARGET_COL]\n    feature_cols = [c for c in train_df.columns if c not in drop_cols]\n\n    # Bỏ các cột object chưa convert được, LightGBM không nhận object thô.\n    usable_feature_cols = []\n    for c in feature_cols:\n        if str(train_df[c].dtype) == \"object\":\n            continue\n        usable_feature_cols.append(c)\n    feature_cols = usable_feature_cols\n\n    if len(feature_cols) == 0:\n        raise ValueError(\"Không còn feature nào để train sau khi lọc cột.\")\n\n    X_train = train_df[feature_cols]\n    y_train = train_df[TARGET_COL]\n    X_valid = valid_df[feature_cols]\n    y_valid = valid_df[TARGET_COL]\n\n    print(\"Feature count:\", len(feature_cols))\n    print(\"X_train:\", X_train.shape)\n    print(\"X_valid:\", X_valid.shape)\n\n    n_estimators = N_ESTIMATORS_DEBUG if DEBUG_MODE else N_ESTIMATORS_FULL\n\n    model = LGBMRegressor(\n        objective=\"regression\",\n        n_estimators=n_estimators,\n        learning_rate=0.03,\n        num_leaves=64,\n        subsample=0.8,\n        colsample_bytree=0.8,\n        random_state=RANDOM_STATE,\n        n_jobs=-1\n    )\n\n    model.fit(\n        X_train,\n        y_train,\n        eval_set=[(X_valid, y_valid)],\n        eval_metric=\"rmse\",\n        categorical_feature=[c for c in cat_cols if c in feature_cols]\n    )\n\n    pred = np.clip(model.predict(X_valid), 0, None)\n\n    if \"perishable\" in valid_df.columns:\n        weights = 1 + 0.25 * valid_df[\"perishable\"].values\n    else:\n        weights = None\n\n    score_nwrmsle = nwrmsle(y_valid.values, pred, weights=weights)\n    rmse = np.sqrt(mean_squared_error(y_valid.values, pred))\n\n    print(\"=\" * 60)\n    print(f\"Result for {signal_col}\")\n    print(f\"NWRMSLE: {score_nwrmsle:.5f}\")\n    print(f\"RMSE: {rmse:.5f}\")\n\n    return {\n        \"signal_col\": signal_col,\n        \"model\": model,\n        \"feature_cols\": feature_cols,\n        \"valid_df\": valid_df,\n        \"pred\": pred,\n        \"nwrmsle\": score_nwrmsle,\n        \"rmse\": rmse\n    }\n\n# ============================================================\n# CELL 15 - PoC: Compare Hard vs Soft\n# ============================================================\nresult_hard = train_evaluate_lgbm(df_wavelet, signal_col=\"smoothed_wavelet_hard\")\nresult_soft = train_evaluate_lgbm(df_wavelet, signal_col=\"smoothed_wavelet_soft\")\n\ncomparison = pd.DataFrame([\n    {\"method\": \"Wavelet Hard\", \"NWRMSLE\": result_hard[\"nwrmsle\"], \"RMSE\": result_hard[\"rmse\"]},\n    {\"method\": \"Wavelet Soft\", \"NWRMSLE\": result_soft[\"nwrmsle\"], \"RMSE\": result_soft[\"rmse\"]}\n])\n\ncomparison","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-13T21:23:44.703176Z","iopub.execute_input":"2026-05-13T21:23:44.703596Z","iopub.status.idle":"2026-05-13T21:25:15.403157Z","shell.execute_reply.started":"2026-05-13T21:23:44.703567Z","shell.execute_reply":"2026-05-13T21:25:15.402081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# ============================================================\n# CELL 16 - Select best version\n# ============================================================\nbest_result = result_soft if result_soft[\"nwrmsle\"] <= result_hard[\"nwrmsle\"] else result_hard\nbest_signal = best_result[\"signal_col\"]\n\nprint(\"Best Wavelet Signal:\", best_signal)\nprint(\"Best NWRMSLE:\", best_result[\"nwrmsle\"])\n\n\n# ============================================================\n# CELL 17 - Visualization: Actual vs Wavelet Prediction\n# Nếu có prediction Kalman từ baseline, truyền kalman_df vào.\n# kalman_df cần có columns: date, store_nbr, item_nbr, pred_kalman\n# ============================================================\ndef plot_prediction_peak(result, store_nbr=None, item_nbr=None, start_date=None, end_date=None, kalman_df=None):\n    valid_df = result[\"valid_df\"].copy()\n    valid_df[\"pred_wavelet\"] = result[\"pred\"]\n\n    if store_nbr is None or item_nbr is None:\n        store_nbr, item_nbr = get_available_example(valid_df)\n\n    temp = valid_df[(valid_df[\"store_nbr\"] == store_nbr) & (valid_df[\"item_nbr\"] == item_nbr)].copy()\n\n    if start_date is not None:\n        temp = temp[temp[\"date\"] >= pd.to_datetime(start_date)]\n    if end_date is not None:\n        temp = temp[temp[\"date\"] <= pd.to_datetime(end_date)]\n\n    if kalman_df is not None:\n        temp = temp.merge(\n            kalman_df[[\"date\", \"store_nbr\", \"item_nbr\", \"pred_kalman\"]],\n            on=[\"date\", \"store_nbr\", \"item_nbr\"],\n            how=\"left\"\n        )\n\n    plt.figure(figsize=(16, 5))\n    plt.plot(temp[\"date\"], temp[TARGET_COL], label=\"Actual\", linewidth=2)\n\n    if \"pred_kalman\" in temp.columns:\n        plt.plot(temp[\"date\"], temp[\"pred_kalman\"], label=\"Prediction Kalman\", linestyle=\"--\")\n\n    plt.plot(temp[\"date\"], temp[\"pred_wavelet\"], label=\"Prediction Wavelet\", linestyle=\"--\")\n    plt.title(f\"Actual vs Kalman vs Wavelet - Store {store_nbr} - Item {item_nbr}\")\n    plt.xlabel(\"Date\")\n    plt.ylabel(\"Unit Sales\")\n    plt.legend()\n    plt.show()\n\nplot_prediction_peak(best_result)\n\n\n# ============================================================\n# CELL 18 - Find peak examples for report/demo\n# ============================================================\ndef find_peak_examples(df, top_n=10):\n    temp = df.copy()\n    temp[\"sales_rank\"] = temp.groupby(GROUP_COLS)[TARGET_COL].rank(pct=True)\n    peaks = temp[temp[\"sales_rank\"] >= 0.995].copy()\n    peaks = peaks.sort_values(TARGET_COL, ascending=False)\n\n    cols = [\"date\", \"store_nbr\", \"item_nbr\", TARGET_COL]\n    for c in [\"family\", \"class\", \"perishable\", \"onpromotion\", \"is_holiday\"]:\n        if c in peaks.columns:\n            cols.append(c)\n\n    return peaks[cols].head(top_n)\n\npeak_examples = find_peak_examples(df_wavelet, top_n=10)\npeak_examples\n\n\n# ============================================================\n# CELL 19 - Save outputs\n# ============================================================\ndf_wavelet.to_csv(\"/kaggle/working/ie212_wavelet_processed_dataset.csv\", index=False)\ncomparison.to_csv(\"/kaggle/working/ie212_wavelet_hard_soft_comparison.csv\", index=False)\n\nprint(\"Saved files:\")\nprint(\"/kaggle/working/ie212_wavelet_processed_dataset.csv\")\nprint(\"/kaggle/working/ie212_wavelet_hard_soft_comparison.csv\")\n\n\n# ============================================================\n# CELL 20 - Report conclusion draft\n# ============================================================\nprint(\"\"\"\nKết luận:\n\nTrong hướng cải tiến tiền xử lý, nhóm thay thế Kalman Filter bằng Wavelet Transform sử dụng họ sóng Daubechies 4 (db4).\nVới bộ dữ liệu Favorita bản cũ, doanh số mục tiêu là unit_sales và chuỗi thời gian được xử lý theo từng cặp store_nbr-item_nbr.\n\nWavelet được áp dụng nhằm khử nhiễu doanh số nhưng vẫn giữ lại các biến động cục bộ quan trọng.\nNhóm thử nghiệm hai cơ chế thresholding gồm Hard Thresholding và Soft Thresholding.\nKết quả được đánh giá bằng NWRMSLE trên tập validation theo thời gian.\nPhiên bản có NWRMSLE thấp hơn được chọn làm phiên bản Wavelet tốt nhất.\n\nSo với Kalman Filter, Wavelet có lợi thế trong việc bảo toàn các đỉnh doanh số vào dịp lễ hoặc promotion.\nĐiều này có ý nghĩa thực tiễn trong supply chain vì doanh nghiệp cần dự báo đúng nhu cầu tăng đột biến để tránh thiếu hàng.\n\"\"\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-13T21:25:15.405167Z","iopub.execute_input":"2026-05-13T21:25:15.405451Z","iopub.status.idle":"2026-05-13T21:25:15.642148Z","shell.execute_reply.started":"2026-05-13T21:25:15.405416Z","shell.execute_reply":"2026-05-13T21:25:15.641256Z"}},"outputs":[],"execution_count":null}]}