{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":81933,"databundleVersionId":9643020,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# %% Cell 0 — 安裝所需套件\n!pip install polars tqdm keras torch colorama lightgbm xgboost catboost\n!pip install mord","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-03T11:30:08.677691Z","iopub.execute_input":"2025-06-03T11:30:08.678172Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 1 ── 基本 import 與全域設定 =====\nimport os, gc, warnings, numpy as np, pandas as pd\nfrom tqdm import tqdm\nfrom concurrent.futures import ThreadPoolExecutor\n\n# sklearn\nfrom sklearn.model_selection   import train_test_split, GridSearchCV\nfrom sklearn.pipeline          import Pipeline\nfrom sklearn.compose           import ColumnTransformer\nfrom sklearn.impute            import SimpleImputer\nfrom sklearn.preprocessing     import StandardScaler, OneHotEncoder\nfrom sklearn.feature_selection import SelectKBest, f_regression\nfrom sklearn.base              import clone\nfrom sklearn.metrics           import (\n    mean_squared_error, mean_absolute_error, r2_score,\n    classification_report, accuracy_score, cohen_kappa_score\n)\n\n# models\nfrom xgboost          import XGBRegressor, XGBClassifier\nfrom catboost         import CatBoostRegressor, CatBoostClassifier\nfrom sklearn.ensemble import AdaBoostRegressor, AdaBoostClassifier\n\nwarnings.filterwarnings('ignore')\npd.options.display.max_columns = None\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 2 ── 讀取 train.csv，處理 Season，刪除 PCIAT-PCIAT_01~20，去除 sii 缺失 =====\nDATA_DIR = '/kaggle/input/child-mind-institute-problematic-internet-use'\ntrain = pd.read_csv(f'{DATA_DIR}/train.csv')\n\n# Season → 數值\nseason_map = {'Spring':0, 'Summer':1, 'Fall':2, 'Winter':3}\nfor c in train.columns:\n    if c.endswith('Season'):\n        train[c] = train[c].map(season_map)\n\n# 刪除 PCIAT-PCIAT_01~20\ndrop_pciat = [c for c in train.columns if c.startswith('PCIAT-PCIAT_') and c != 'PCIAT-PCIAT_Total']\ntrain.drop(columns=drop_pciat, inplace=True)\n\n# 丟掉 sii 缺失\ntrain = train.dropna(subset=['sii']).reset_index(drop=True)\nprint(\"clean train shape:\", train.shape)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 3 ── 時序特徵萃取函式（與之前相同）=====\ndef process_file(dirname):\n    df = pd.read_parquet(os.path.join(dirname,'part-0.parquet'))\n    df.drop(columns='step', errors='ignore', inplace=True)\n    feats = np.hstack([\n        df.mean().values, df.std().values,\n        df.min().values,  df.max().values,\n        (df[['X','Y','Z']]!=0).mean().values,\n        [(df['enmo']<0.01).mean()]\n    ])\n    cols = df.columns.tolist()\n    names = ([f\"{c}_mean\" for c in cols]+[f\"{c}_std\" for c in cols]+\n             [f\"{c}_min\"  for c in cols]+[f\"{c}_max\" for c in cols]+\n             [f\"{c}_active_ratio\" for c in ['X','Y','Z']] + ['enmo_still_ratio'])\n    sid = os.path.basename(dirname).split('=')[1]\n    return feats, names, sid\n\ndef load_time_series(base):\n    subdirs=[os.path.join(base,d) for d in os.listdir(base)]\n    res=[]\n    with ThreadPoolExecutor() as ex:\n        for r in tqdm(ex.map(process_file, subdirs), total=len(subdirs), desc='TS'):\n            res.append(r)\n    feats, names, ids = zip(*res)\n    df = pd.DataFrame(np.vstack(feats), columns=names[0]); df['id']=ids\n    return df\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 4 ── 合併時序特徵 =====\ntrain_ts = load_time_series(f'{DATA_DIR}/series_train.parquet')\ntrain = train.merge(train_ts,on='id',how='left')\nprint(\"merged shape:\", train.shape)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 5 ── 切 labels，分 80/20，先不做任何變換 =====\ny_clf = train['sii'].astype(int)\ny_reg = train['PCIAT-PCIAT_Total'].astype(float)\nX_raw = train.drop(columns=['sii','PCIAT-PCIAT_Total','id'])\n\nX_tr_raw, X_val_raw, y_tr_clf, y_val_clf, y_tr_reg, y_val_reg = train_test_split(\n    X_raw, y_clf, y_reg, test_size=0.20, random_state=42, stratify=y_clf)\nprint(\"split shapes:\", X_tr_raw.shape, X_val_raw.shape)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 6 ── 改良的前處理與特徵篩選 (修正版) =====\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.preprocessing import OneHotEncoder\nfrom sklearn.feature_selection import SelectKBest, f_regression\n\n# 1) 將數值 / 二元 / 類別 列出\nnum_cols = [c for c in X_tr_raw.columns\n            if X_tr_raw[c].dtype in (np.float64, np.int64) and\n               len(X_tr_raw[c].dropna().unique()) > 2]\n\nbin_cols = [c for c in X_tr_raw.columns\n            if set(X_tr_raw[c].dropna().unique()) <= {0, 1}]\n\ncat_cols = [c for c in X_tr_raw.columns if c not in num_cols + bin_cols]\n\nprint(f\"Numeric: {len(num_cols)}, Binary: {len(bin_cols)}, Categorical: {len(cat_cols)}\")\n\n# 2) ColumnTransformer\nnumeric_pipe = Pipeline([\n    ('imp', SimpleImputer(strategy='median')),\n    ('sc',  StandardScaler())\n])\n\nbinary_pipe = Pipeline([\n    ('imp', SimpleImputer(strategy='most_frequent'))     # 0/1 缺失 → 補眾數\n])\n\ncategorical_pipe = Pipeline([\n    ('imp', SimpleImputer(strategy='most_frequent')),    # 類別缺失 → 補眾數\n    ('one', OneHotEncoder(handle_unknown='ignore',\n                          sparse=False,\n                          dtype=np.float32))\n])\n\npreprocessor = ColumnTransformer([\n    ('num', numeric_pipe, num_cols),\n    ('bin', binary_pipe,  bin_cols),\n    ('cat', categorical_pipe, cat_cols)\n])\n\n# 3) fit_transform / transform\nX_tr_prep = preprocessor.fit_transform(X_tr_raw)\nX_val_prep = preprocessor.transform(X_val_raw)\n\n# 4) SelectKBest 取前 k 個相關特徵\nk = 9\nselector = SelectKBest(score_func=f_regression, k=min(k, X_tr_prep.shape[1]))\nselector.fit(X_tr_prep, y_tr_reg)\nmask = selector.get_support()\n\n# 取得對應特徵名稱\ndef get_feature_names(ct):\n    names = []\n    for name, trans, cols in ct.transformers_:\n        if name == 'remainder':\n            continue\n        if isinstance(trans, Pipeline):\n            last = trans.steps[-1][1]\n            if isinstance(last, OneHotEncoder):\n                names.extend(last.get_feature_names_out(cols))\n            else:\n                names.extend(cols)\n        elif isinstance(trans, OneHotEncoder):\n            names.extend(trans.get_feature_names_out(cols))\n        else:\n            names.extend(cols)\n    return names\n\nall_feat_names = get_feature_names(preprocessor)\nselected_names = [f for f, keep in zip(all_feat_names, mask) if keep]\n\nprint(f\"\\nSelectKBest 保留 {len(selected_names)} 個特徵：\")\nfor feat in selected_names:\n    print(\" -\", feat)\n\n# 最終輸出給模型\nX_train = selector.transform(X_tr_prep)\nX_val   = selector.transform(X_val_prep)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 7 ── 共用前處理 (imputer+scaler 已內嵌於 X_tr / X_val) =====\n# 將已篩選後的矩陣作為模型輸入，不再額外 scaler\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 8 ── 模型超參數配置不變 =====\nparam_reg = {\n    'xgb': {'model': XGBRegressor(objective='reg:squarederror', random_state=42, n_jobs=-1),\n            'grid' : {'model__learning_rate':[0.03,0.1],\n                      'model__n_estimators':[300,400]}},\n    'cat': {'model': CatBoostRegressor(verbose=0, loss_function='RMSE', random_state=42),\n            'grid' : {'model__depth':[6,8]}},\n    'ada': {'model': AdaBoostRegressor(random_state=42),\n            'grid' : {'model__n_estimators':[100,200],\n                      'model__learning_rate':[0.05,0.1]}}\n}\n\nparam_clf = {\n    'xgb': {'model': XGBClassifier(use_label_encoder=False, eval_metric='mlogloss', random_state=42),\n            'grid' : {'model__learning_rate':[0.03,0.1],\n                      'model__n_estimators':[300,400]}},\n    'cat': {'model': CatBoostClassifier(verbose=0, random_state=42),\n            'grid' : {'model__depth':[6,8]}},\n    'ada': {'model': AdaBoostClassifier(random_state=42),\n            'grid' : {'model__n_estimators':[100,200],\n                      'model__learning_rate':[0.05,0.1]}}\n}\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 9 ── 回歸模型 GridSearchCV + 自動閾值搜索 + 報告 =====\ndef raw2sii(v, th):  # th = (t1, t2, t3)\n    return np.digitize(v, bins=th)\n\nlabels = [0, 1, 2, 3]\n\nfor name, spec in param_reg.items():\n    pipe = Pipeline([('model', spec['model'])])\n    gs = GridSearchCV(pipe,\n                      spec['grid'],\n                      cv=5,\n                      scoring='neg_root_mean_squared_error',\n                      n_jobs=-1)\n    gs.fit(X_train, y_tr_reg)   # 使用 y_tr_reg 而非 y_train_reg\n    best = gs.best_estimator_\n    y_pred = best.predict(X_val)\n\n    print(f\"\\n=== {name.upper()} 回歸最佳參數 ===\\n{gs.best_params_}\\n\")\n    print(f\"[{name.upper()} Regression] \"\n          f\"MSE={mean_squared_error(y_val_reg, y_pred):.4f}  \"\n          f\"MAE={mean_absolute_error(y_val_reg, y_pred):.4f}  \"\n          f\"R²={r2_score(y_val_reg, y_pred):.4f}\")\n\n    # --- 自動閾值搜尋 (QWK) ---\n    mins, maxs = y_pred.min(), y_pred.max()\n    cand = np.linspace(mins, maxs, 50)\n    best_qwk, best_th = -1, None\n\n    for t1 in cand:\n        for t2 in cand:\n            if t2 <= t1:\n                continue\n            for t3 in cand:\n                if t3 <= t2:\n                    continue\n                mk = raw2sii(y_pred, (t1, t2, t3))\n                qwk = cohen_kappa_score(y_val_clf, mk, weights='quadratic')\n                if qwk > best_qwk:\n                    best_qwk, best_th = qwk, (t1, t2, t3)\n\n    mapped = raw2sii(y_pred, best_th)\n    print(f\"\\n[{name.upper()} 最佳閾值] \"\n          f\"{tuple(round(x, 3) for x in best_th)} → QWK={best_qwk:.4f}\")\n    print(classification_report(y_val_clf, mapped, labels=labels, digits=4))\n    print(f\"  Accuracy={accuracy_score(y_val_clf, mapped):.4f}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 10 ── 分類模型 GridSearchCV + 報告 =====\nfor name, spec in param_clf.items():\n    pipe = Pipeline([('model', spec['model'])])\n    gs = GridSearchCV(pipe,\n                      spec['grid'],\n                      cv=5,\n                      scoring='accuracy',\n                      n_jobs=-1)\n    gs.fit(X_train, y_tr_clf)  # 使用 y_tr_clf 而非 y_train_clf\n    best = gs.best_estimator_\n    y_pred = best.predict(X_val)\n    print(f\"\\n=== {name.upper()} 分類最佳參數 ===\\n{gs.best_params_}\\n\")\n    print(classification_report(y_val_clf, y_pred, labels=labels, digits=4))\n    print(f\"  Accuracy={accuracy_score(y_val_clf, y_pred):.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===== Cell 11 ── （可選）清理記憶體 =====\ndel train, train_ts, X_raw, X_tr_raw, X_val_raw\ngc.collect()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}