{"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":84493,"databundleVersionId":11305158,"sourceType":"competition"}],"dockerImageVersionId":31089,"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":"2025-09-18T09:38:53.087955Z","iopub.execute_input":"2025-09-18T09:38:53.088715Z","iopub.status.idle":"2025-09-18T09:38:53.258651Z","shell.execute_reply.started":"2025-09-18T09:38:53.088675Z","shell.execute_reply":"2025-09-18T09:38:53.257922Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"데이터 불러오기","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\n# 일부 파티션만 사용해서 sample 관찰 \nsample_path = \"/kaggle/input/jane-street-real-time-market-data-forecasting/train.parquet/partition_id=0/part-0.parquet\"\n\n# 데이터 불러오기 (처음 100행만)\ndf = pd.read_parquet(sample_path)\ndf_sample = df.head(100)\n\n# 전체 컬럼 보기\nprint(\"전체 컬럼 수:\", df_sample.shape[1])\nprint(\"컬럼 목록:\\n\", df_sample.columns.tolist())\n\n# 데이터 샘플 확인\ndisplay(df_sample)\n\n# 데이터 타입 확인\nprint(\"\\n컬럼별 데이터 타입:\")\nprint(df_sample.dtypes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:42:11.670704Z","iopub.execute_input":"2025-09-18T09:42:11.671456Z","iopub.status.idle":"2025-09-18T09:42:12.925795Z","shell.execute_reply.started":"2025-09-18T09:42:11.671427Z","shell.execute_reply":"2025-09-18T09:42:12.925086Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"--------------------\n\n✅ 1단계: NaN 분포 자체를 정량적으로 파악\n\n[파티션 반복] ──> [NaN 비율 누적] ──> [NaN 많은 feature 골라냄] ──> [해당 feature에 대해 symbol/time/responder 관계 분석]\n","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport glob\n\npartition_paths = glob.glob('/kaggle/input/jane-street-real-time-market-data-forecasting/train.parquet/partition_id=*/part-0.parquet')\nnan_results = []\n\nfor path in partition_paths:\n    df = pd.read_parquet(path)\n    \n    # NaN 비율 계산\n    nan_ratio = df.isnull().mean()\n    nan_ratio.name = path.split('/')[-2]  # 예: partition_id=3\n    nan_results.append(nan_ratio)\n\n# 파티션별 NaN 비율 결과를 하나의 DataFrame으로 합치기\nnan_df = pd.DataFrame(nan_results)\n\n# 행 = 파티션, 열 = feature\nnan_df.index.name = 'partition'\nnan_df = nan_df.T  # feature 기준으로 보기 쉽게 전치\ndisplay(nan_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:46:07.517853Z","iopub.execute_input":"2025-09-18T10:46:07.518255Z","iopub.status.idle":"2025-09-18T10:47:37.440501Z","shell.execute_reply.started":"2025-09-18T10:46:07.518224Z","shell.execute_reply":"2025-09-18T10:47:37.439683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.figure(figsize=(12, 18))\nplt.imshow(nan_df.values, aspect='auto', cmap='Reds')\nplt.colorbar(label='NaN Ratio')\nplt.yticks(range(len(nan_df.index)), nan_df.index)\nplt.xticks(range(len(nan_df.columns)), nan_df.columns, rotation=90)\nplt.title(\"NaN Ratio by Feature and Partition\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:47:40.879662Z","iopub.execute_input":"2025-09-18T10:47:40.880132Z","iopub.status.idle":"2025-09-18T10:47:41.858791Z","shell.execute_reply.started":"2025-09-18T10:47:40.880104Z","shell.execute_reply":"2025-09-18T10:47:41.857787Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. partition_id=숫자 순서로 컬럼 정렬\nnan_df_sorted = nan_df[sorted(\n    nan_df.columns,\n    key=lambda x: int(x.split('=')[1])  # '=' 뒤 숫자를 기준으로 정렬\n)]\n\n# 2. NaN 비율이 0보다 큰 feature만 추출\nnan_nonzero = nan_df_sorted[nan_df_sorted.max(axis=1) > 0]\n\n# 3. 확인\ndisplay(nan_nonzero)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:47:45.726520Z","iopub.execute_input":"2025-09-18T10:47:45.726859Z","iopub.status.idle":"2025-09-18T10:47:45.759802Z","shell.execute_reply.started":"2025-09-18T10:47:45.726834Z","shell.execute_reply":"2025-09-18T10:47:45.758901Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"✅ 지금 결과 해석\n\nfeature_00 ~ feature_04\n\n- partition_id=0 에서 NaN=1.0 (100% 전부 NaN)\n\n- partition_id=1 에서는 44% 정도 NaN → 👉 이건 특정 파티션/조건에서만 값이 아예 없는 feature라는 뜻\n\nfeature_21, 26, 27, 31\n\n- partition_id=1,2,0 에서 100% NaN\n\n- 다른 파티션에선 부분적으로만 NaN → 👉 특정 symbol/time 구간에서만 정의된 feature일 가능성이 큼\n\nfeature_39, 42, 50, 53\n\n- NaN 비율이 7~15%로 꾸준히 존재\n\n- 특정 파티션에서만 확 뛰는 경우도 있음 → 👉 NaN 자체가 구조적인 의미를 가질 가능성이 있음\n\nfeature_15, 17, 32, 33, 41, 44, 52, 55, 58, 73, 74\n\n- NaN 비율이 1~3% 정도로 낮음 (거의 결측치 수준) → 👉 단순 missing 처리 가능\n\n----------------\n\n✅ 다음 단계: 어떻게 쓸 수 있나?\n\n📌 NaN 처리 분류 체계 (4단계)\n\n1) 완전 NaN → 드랍 후보\n\n- 의미: 모든 partition, 모든 행에서 해당 feature 값이 전부 NaN.\n\n- 구조적 해석: 데이터셋에서 실제로 정의되지 않은 변수. “빈 껍데기 컬럼”에 불과.\n\n- 처리 전략: 아무런 정보가 없으니 드랍(제거)해도 됨.\n\n- 예시: feature_00이 train 전체에서 100% NaN이라면 학습에 기여 불가 → 드랍.\n\n2) 일부 partition 100% NaN → indicator 권장\n\n- 의미: 특정 partition에서는 해당 feature가 아예 정의되지 않음(100% NaN). 그러나 다른 partition에서는 값이 존재.\n\n- 구조적 해석: 데이터 수집/시장 조건별로 “존재 여부 자체”가 중요한 변수일 수 있음. 즉, NaN이 단순 결측이 아니라 **“그 상황에선 변수 자체가 무의미하다”**라는 신호.\n\n- 처리 전략: indicator 컬럼 추가 (isnull() → 0/1), 값은 평균/중앙값/0으로 채움\n\n- 예시: feature_21이 partition_id=0~2에서는 100% NaN, partition_id=3~9에서는 값이 있음 → NaN 여부가 partition 구분 신호가 될 수 있으니 indicator 필요.\n\n3) 부분적 NaN (10~30% 정도 꾸준히) → indicator + 대체\n\n- 의미: 모든 partition에서 값은 있지만, 일정 비율(예: 15%)이 NaN.\n\n- 구조적 해석: 단순 오류가 아니라 데이터 생성 메커니즘상 일부 케이스에서만 정의되지 않는 변수일 가능성. NaN 비율이 높아서 무시하면 데이터 손실 큼.\n\n- 처리 전략: indicator 컬럼 추가 (isnull() 표시), 값은 중앙값/평균 등으로 대체\n\n- 예시: feature_39가 전체 partition에서 12% 정도 NaN → 그냥 drop하기 아까움 → indicator + 대체.\n\n4) 희소 NaN (1~5% 미만) → 단순 대체\n\n- 의미: 결측치가 전체에서 아주 드물게 발생.\n\n- 구조적 해석: 데이터 오류, 수집 누락 등 단순 missing noise일 가능성이 높음. 모델에 영향이 적으므로 복잡한 indicator까지는 필요 없음.\n\n- 처리 전략: 그냥 평균/중앙값/0 등으로 간단히 채움.\n\n- 예시: feature_15가 전체에서 2% NaN → 그냥 평균값으로 대체.","metadata":{}},{"cell_type":"code","source":"# =====================================\n# NaN 처리 규칙 적용 (간단 버전, 데모)\n# - nan_nonzero 기준으로 feature 분류 후 변환\n# =====================================\n\nimport numpy as np\n\n# ---- 1) Feature 분류 함수 ----\ndef classify_nan_features(nan_df, sparse_max=0.05, partial_min=0.07, partial_max=0.30):\n    \"\"\"\n    nan_df: index=feature, columns=partition_id=숫자, 값=NaN 비율\n    \"\"\"\n    full_nan = []          # 전체 100% NaN → 드랍\n    partition_100 = []     # 일부 partition 100% NaN → indicator 권장\n    partial_nan = []       # 7%~30% 정도 꾸준히 NaN → indicator 권장\n    sparse_nan = []        # 5% 이하 희소 NaN → 단순 대체\n    \n    for feat in nan_df.index:\n        ratios = nan_df.loc[feat].values\n        overall = ratios.mean()\n        \n        if np.all(ratios >= 0.9999):   # 전체 100% NaN\n            full_nan.append(feat)\n        elif np.any(ratios >= 0.9999): # 일부 partition에서만 100% NaN\n            partition_100.append(feat)\n        elif overall <= sparse_max:\n            sparse_nan.append(feat)\n        elif partial_min <= overall <= partial_max:\n            partial_nan.append(feat)\n        else:\n            # 전체 NaN 비율이 크지만 100%는 아닌 경우 → partial 취급\n            partial_nan.append(feat)\n    \n    return {\n        \"full_nan_drop\": full_nan,\n        \"partition_100_indicator\": partition_100,\n        \"partial_indicator\": partial_nan,\n        \"sparse_impute_only\": sparse_nan\n    }\n\n# ---- 2) 실제 NaN 처리 함수 ----\ndef process_dataframe(df, classes, impute_strategy=\"zero\"):\n    \"\"\"\n    df: 원본 DataFrame\n    classes: classify_nan_features 결과 dict\n    impute_strategy: \"zero\" or \"median\"\n    \"\"\"\n    df = df.copy()\n    \n    # 완전 NaN → 드랍\n    df.drop(columns=classes[\"full_nan_drop\"], errors=\"ignore\", inplace=True)\n    \n    # indicator 대상 피처\n    indicator_feats = classes[\"partition_100_indicator\"] + classes[\"partial_indicator\"]\n    \n    # indicator 추가\n    for feat in indicator_feats:\n        if feat in df.columns:\n            df[f\"{feat}_isnan\"] = df[feat].isna().astype(np.int8)\n    \n    # impute 대상 = indicator_feats + sparse_impute_only\n    impute_feats = indicator_feats + classes[\"sparse_impute_only\"]\n    \n    for feat in impute_feats:\n        if feat in df.columns:\n            if impute_strategy == \"zero\":\n                df[feat] = df[feat].fillna(0.0)\n            elif impute_strategy == \"median\":\n                med = df[feat].median(skipna=True)\n                df[feat] = df[feat].fillna(med if not np.isnan(med) else 0.0)\n    \n    return df\n\n# ---- 3) 실행 예시 ----\n\n# Feature 분류\nclasses = classify_nan_features(nan_nonzero)\n\nprint(\"=== Feature 분류 결과 ===\")\nfor k, v in classes.items():\n    print(f\"{k}: {len(v)}개\")\n\n# 예시: partition_id=0 데이터 하나 불러오기\nsample_path = \"/kaggle/input/jane-street-real-time-market-data-forecasting/train.parquet/partition_id=0/part-0.parquet\"\ndf_raw = pd.read_parquet(sample_path)\n\nprint(\"원본 shape:\", df_raw.shape)\n\n# NaN 처리 적용\ndf_proc = process_dataframe(df_raw, classes, impute_strategy=\"zero\")\n\nprint(\"처리 후 shape:\", df_proc.shape)\ndf_proc.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:47:50.760544Z","iopub.execute_input":"2025-09-18T10:47:50.760933Z","iopub.status.idle":"2025-09-18T10:47:53.010501Z","shell.execute_reply.started":"2025-09-18T10:47:50.760904Z","shell.execute_reply":"2025-09-18T10:47:53.009472Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# indicator 붙은 컬럼 확인\n[col for col in df_proc.columns if col.endswith(\"_isnan\")]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:44:54.831592Z","iopub.execute_input":"2025-09-18T09:44:54.831897Z","iopub.status.idle":"2025-09-18T09:44:54.837932Z","shell.execute_reply.started":"2025-09-18T09:44:54.831875Z","shell.execute_reply":"2025-09-18T09:44:54.837135Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"-------\n\n📊 왜 responders.csv가 필요한가?\n\n우리가 모델을 학습하려면 (X, y) 쌍이 있어야 하잖아요.\n\ntrain.parquet에는 **X (피처)**만 있고, **y (정답)**가 없음.\n\n그 정답(y)이 바로 responders.csv에 들어 있어요.\n\n👉 그래서 train.parquet(피처) + responders.csv(타깃)을 merge해서 학습용 데이터셋을 만드는 거예요.\n\n-------\n\n흐름 요약\n\n1. Train + responders → NaN 처리 → X, y 준비\n\n2. 샘플링된 데이터(X_sample, y_sample) 준비\n\n3. LightGBM 같은 모델로 학습 (작은 데이터라 금방 됨)\n\n4. model.feature_importances_ 확인 → 중요 feature 정렬\n\n5. 상위 N개 feature만 선택\n\n6. 이 feature subset으로 test 데이터도 변환 후 학습/예측\n\n--------\n\n📌 샘플링 전략 추천\n\n <Row-based 랜덤 샘플링 (추천 👍)>\n\n데이터가 균등하게 익명화돼 있기 때문에, 무작위 추출이 제일 무난해요.\n\n예: 전체의 1~5% 정도만 랜덤으로 뽑기.\n\n장점: 데이터 분포를 크게 왜곡하지 않음.\n\n단점: 희귀 패턴이 소실될 수 있지만, baseline 목적엔 충분.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\nRESP_PATH = \"/kaggle/input/jane-street-real-time-market-data-forecasting/responders.csv\"\n\n# 헤더만 출력\nhdr = pd.read_csv(RESP_PATH, nrows=0)\nprint(\"컬럼 목록:\", hdr.columns.tolist())\n\n# 데이터 일부 확인\ndf_check = pd.read_csv(RESP_PATH, nrows=5)\ndisplay(df_check)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:44:57.375755Z","iopub.execute_input":"2025-09-18T09:44:57.376446Z","iopub.status.idle":"2025-09-18T09:44:57.438802Z","shell.execute_reply.started":"2025-09-18T09:44:57.376411Z","shell.execute_reply":"2025-09-18T09:44:57.437919Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_path = \"/kaggle/input/jane-street-real-time-market-data-forecasting/train.parquet/partition_id=0/part-0.parquet\"\ndf_check = pd.read_parquet(sample_path)\nprint(df_check.columns.tolist())\ndisplay(df_check.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:44:59.234301Z","iopub.execute_input":"2025-09-18T09:44:59.234643Z","iopub.status.idle":"2025-09-18T09:45:00.215919Z","shell.execute_reply.started":"2025-09-18T09:44:59.234617Z","shell.execute_reply":"2025-09-18T09:45:00.215219Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================================================\n# Step 1) X / y / w 분리\n# df_proc: NaN 처리 끝난 DataFrame (예: partition0)\n# ================================================\n\n# 1. feature 컬럼\nfeat_cols   = [c for c in df_proc.columns if c.startswith(\"feature_\") and not c.endswith(\"_isnan\")]\nisnan_cols  = [c for c in df_proc.columns if c.endswith(\"_isnan\")]\nX_cols      = feat_cols + isnan_cols\n\n# 2. 타깃 선택 (예: responder_0 하나만)\nTARGET_COL = \"responder_0\"\ny = df_proc[TARGET_COL].astype(\"float32\")\n\n# 3. 가중치 (weight가 있으면 활용)\nw = df_proc[\"weight\"].astype(\"float32\") if \"weight\" in df_proc.columns else None\n\n# 4. 최종 X 행렬\nX = df_proc[X_cols].astype(\"float32\")\n\nprint(f\"[X] {X.shape} (features={len(X_cols)})\")\nprint(f\"[y] {y.shape}, target={TARGET_COL}\")\nif w is not None:\n    print(f\"[w] {w.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:45:02.075225Z","iopub.execute_input":"2025-09-18T09:45:02.075521Z","iopub.status.idle":"2025-09-18T09:45:02.940018Z","shell.execute_reply.started":"2025-09-18T09:45:02.075499Z","shell.execute_reply":"2025-09-18T09:45:02.939102Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================================================\n# Step 2) 샘플링\n# - 전체 데이터 중 일부만 랜덤 선택\n# ================================================\nSAMPLE_N = 100_000  # 원하는 크기 조절 (ex: 50k, 100k)\n\nN = len(X)\nif N > SAMPLE_N:\n    sample_idx = np.random.RandomState(42).choice(N, size=SAMPLE_N, replace=False)\nelse:\n    sample_idx = np.arange(N)\n\nX_s = X.iloc[sample_idx].reset_index(drop=True)\ny_s = y.iloc[sample_idx].reset_index(drop=True)\nw_s = w.iloc[sample_idx].reset_index(drop=True) if w is not None else None\n\nprint(f\"[Sample] {len(X_s)} rows selected from {N}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:45:18.951787Z","iopub.execute_input":"2025-09-18T09:45:18.952915Z","iopub.status.idle":"2025-09-18T09:45:19.189700Z","shell.execute_reply.started":"2025-09-18T09:45:18.952882Z","shell.execute_reply":"2025-09-18T09:45:19.188967Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"📌 LightGBM Baseline 학습 ","metadata":{}},{"cell_type":"code","source":"# Step 1) 학습/검증 분리\n\nfrom sklearn.model_selection import train_test_split\n\n# 샘플링된 데이터 (X_s, y_s, w_s) 사용\nX_tr, X_va, y_tr, y_va = train_test_split(\n    X_s, y_s, test_size=0.2, random_state=42\n)\n\nw_tr = w_s.iloc[X_tr.index] if w_s is not None else None\nw_va = w_s.iloc[X_va.index] if w_s is not None else None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:46:09.280643Z","iopub.execute_input":"2025-09-18T09:46:09.280993Z","iopub.status.idle":"2025-09-18T09:46:09.324492Z","shell.execute_reply.started":"2025-09-18T09:46:09.280968Z","shell.execute_reply":"2025-09-18T09:46:09.323779Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 2) 학습\n\nimport lightgbm as lgb\nfrom sklearn.metrics import mean_squared_error\n\n# 기본 파라미터 (빠르게 baseline 만들기)\nmodel = lgb.LGBMRegressor(\n    n_estimators=400,\n    learning_rate=0.05,\n    num_leaves=64,\n    subsample=0.8,\n    colsample_bytree=0.8,\n    random_state=42,\n    n_jobs=-1\n)\n\n# fit 인자 구성\nfit_kwargs = {\n    \"X\": X_tr, \"y\": y_tr,\n    \"eval_set\": [(X_va, y_va)],\n    \"eval_metric\": \"l2\",\n    # verbose 대신 callback으로 로그 제어\n    \"callbacks\": [lgb.log_evaluation(period=50)]\n}\nif w_tr is not None:\n    fit_kwargs[\"sample_weight\"] = w_tr\n    fit_kwargs[\"eval_sample_weight\"] = [w_va]\n\n# 학습\nmodel.fit(**fit_kwargs)\n\n# 검증 MSE 확인\npred_va = model.predict(X_va)\nmse = mean_squared_error(y_va, pred_va, sample_weight=w_va) if w_va is not None else mean_squared_error(y_va, pred_va)\nprint(f\"[Valid] MSE = {mse:.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:46:55.110616Z","iopub.execute_input":"2025-09-18T09:46:55.111108Z","iopub.status.idle":"2025-09-18T09:47:01.998698Z","shell.execute_reply.started":"2025-09-18T09:46:55.111084Z","shell.execute_reply":"2025-09-18T09:47:01.997947Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 3) Feature Importance 계산\n\n# 중요도 추출\nimportances = model.feature_importances_\nfi = (\n    pd.DataFrame({\"feature\": X_s.columns, \"importance\": importances})\n    .sort_values(\"importance\", ascending=False)\n    .reset_index(drop=True)\n)\n\nprint(\"[Feature Importance] top 20\")\ndisplay(fi.head(20))\n\n# 상위 20개 feature 이름 뽑기\ntop20_features = fi.head(20)[\"feature\"].tolist()\n\n# 그 20개만 번호순으로 정렬\ntop20_sorted = sorted(top20_features, key=lambda x: int(x.split(\"_\")[1]))\ntop20_sorted\n\n# 상위 N개 피처 저장\n#TOP_N = 80\n#top_features = fi.head(TOP_N)[\"feature\"].tolist()\n\n# 파일로 저장 (선택)\n#fi.to_csv(\"/kaggle/working/feature_importance.csv\", index=False)\n#with open(\"/kaggle/working/top_features.txt\", \"w\") as f:\n    #for feat in top_features:\n        #f.write(feat + \"\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:54:13.105359Z","iopub.execute_input":"2025-09-18T09:54:13.106145Z","iopub.status.idle":"2025-09-18T09:54:13.119404Z","shell.execute_reply.started":"2025-09-18T09:54:13.106117Z","shell.execute_reply":"2025-09-18T09:54:13.118737Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"📌 지금까지 진행한 단계 요약\n\n데이터 로딩\n\n- train.parquet에서 feature_00 ~ feature_78, responder_0 ~ responder_8, weight 등 확인.\n\n- responders.csv는 실제 타깃 값이 아니라 메타 정보라서 학습에는 사용하지 않음.\n\nNaN 처리\n\n- 처리 후 _isnan indicator 컬럼 일부 생성.\n\n학습 준비\n\n- X = feature + isnan indicator, y = responder_0 (단일 타깃), w = weight\n\n- 데이터가 너무 크므로 샘플링 (100k 행)\n\n모델 학습 (LightGBM)\n\n- train/valid 분리 (80k/20k)\n\n- Baseline 학습 결과: 검증 MSE = 0.6477 => **“어떤 feature가 target(여기서는 responder_x 값들)을 예측하는 데 중요한 역할을 했는가”**\n\n- Feature Importance 확인 : 상위 20개 feature 출력 → feature_48, feature_36, feature_34, feature_61 … 등이 상위권. → 트리에서 분할할 때 가장 자주 쓰이거나, 쓸 때마다 손실 감소 효과가 컸다는 뜻.\n\n- NaN indicator(*_isnan)는 Top 20에는 없음. 👉 NaN 여부 자체가 신호로 크게 작동하진 않았다는 의미.\n\n📊 결과 해석\n\n주요 피처: feature_48, feature_36, feature_34, feature_61 등이 가장 많이 모델 분할(split)에 사용됨.\n\nNaN indicator 부재: 최소한 responder_0을 예측하는 데에는 “NaN 여부” 자체가 상위 신호로는 작동하지 않았음.\n\n익명화 데이터임에도 불구하고 특정 feature들이 뚜렷하게 높은 중요도를 가지는 걸 보면, 이 피처들이 responder_0과 강한 상관 관계를 가질 가능성이 큼. 반면 많은 feature들은 거의 중요도가 0에 가까워서, 버려도 무방할 수 있음.\n\n✅ 의미하는 바\n\nBaseline 성립\n\n- 샘플 데이터만으로도 모델이 학습하고 예측 가능하다는 걸 확인.\n\n- importance를 통해 feature selection의 근거도 확보.\n\n다음 단계 방향성\n\n- 상위 중요도 feature 중심으로 subset을 만들고, 재학습하면 메모리와 속도 절약 가능.\n\n- 다른 responder(1~8)에 대해서도 같은 과정을 반복해야 전체 대회 문제를 풀 수 있음.\n\n- indicator가 낮게 나왔다고 해서 무조건 무의미한 건 아님 → 다른 타깃에선 의미 있을 수 있음.\n\n👉 요약: 현재 결과는 responder_0 기준 baseline이고, 피처 중요도 Top 20 안에 특정 feature들이 강하게 자리 잡고 있음.\n이걸 토대로 feature subset을 정리해서 확장/최적화하는 게 다음 단계예요.","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# 상위 20개 feature importance 시각화\ntop20 = fi.head(20)\n\nplt.figure(figsize=(10, 6))\nplt.barh(top20[\"feature\"], top20[\"importance\"], color=\"skyblue\")\nplt.gca().invert_yaxis()  # 높은 importance가 위로 오도록\nplt.xlabel(\"Importance Score\")\nplt.title(\"Top 20 Feature Importances (LightGBM)\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T09:56:19.417727Z","iopub.execute_input":"2025-09-18T09:56:19.418327Z","iopub.status.idle":"2025-09-18T09:56:19.874959Z","shell.execute_reply.started":"2025-09-18T09:56:19.418299Z","shell.execute_reply":"2025-09-18T09:56:19.874099Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"📌 다음 단계 \n\n1. 20개 feature로 재학습\n\n아까 뽑아둔 importance 상위 20개 feature만 남겨서 다시 LightGBM 학습.\n\n목표: 성능(MSE)이 유지되는지 / 떨어지는지 확인.\n\n2. Cross Validation 도입\n\n단순 train/valid split 대신 K-Fold (예: 5-Fold) 적용.\n\n각 fold마다 학습 → 평균 MSE 계산. 이렇게 하면 “특정 split 때문에 성능이 좋게/나쁘게 보이는” 문제 방지. 더 안정적이고 일반화된 성능 확인 가능.\n\n3. 다른 responder 학습 시도\n\n지금은 responder_0 기준으로 했지만, responder_1~8도 필요. 우선 responder_6을 학습 → importance / MSE 비교.\n\n확인 포인트: responder_6은 responder_0과 같은 feature가 중요할까, 아니면 전혀 다른 feature가 중요할까? 최종적으로 responder별 “핵심 feature 세트”를 추려내는 게 가능.\n\n","metadata":{}},{"cell_type":"code","source":"import lightgbm as lgb\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error\n\n# -----------------------------\n# 1) 사용할 feature 준비\n# -----------------------------\ntop20_features = top20_sorted  # importance 기준 상위 20개 리스트\ntarget = \"responder_0\"         # 예시: responder_0\n\nusecols = [\"date_id\", \"time_id\", \"symbol_id\", \"weight\"] + top20_features + [target]\n\n# -----------------------------\n# 2) 데이터 로드\n# -----------------------------\nsample_path = \"/kaggle/input/jane-street-real-time-market-data-forecasting/train.parquet/partition_id=0/part-0.parquet\"\ndf_raw = pd.read_parquet(sample_path, columns=usecols)\n\n# NaN 처리 (이미 indicator 처리된 경우면 pass, 아니면 fillna)\ndf_raw = df_raw.fillna(0)\n\n# -----------------------------\n# 3) Train/Valid 분리\n# -----------------------------\nX = df_raw[top20_features]\ny = df_raw[target]\nw = df_raw[\"weight\"]\n\nX_tr, X_va, y_tr, y_va, w_tr, w_va = train_test_split(X, y, w, test_size=0.2, random_state=42)\n\n# -----------------------------\n# 4) 모델 학습\n# -----------------------------\nmodel = lgb.LGBMRegressor(\n    n_estimators=500,\n    learning_rate=0.05,\n    max_depth=-1,\n    random_state=42,\n    n_jobs=-1,\n)\n\nmodel.fit(\n    X_tr, y_tr,\n    sample_weight=w_tr,\n    eval_set=[(X_va, y_va)],\n    eval_sample_weight=[w_va],\n    eval_metric=\"l2\"\n)\n\n\n# -----------------------------\n# 5) 성능 평가\n# -----------------------------\ny_pred = model.predict(X_va)\nmse = mean_squared_error(y_va, y_pred, sample_weight=w_va)\nprint(f\"[Valid] MSE = {mse:.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:02:44.364027Z","iopub.execute_input":"2025-09-18T10:02:44.364656Z","iopub.status.idle":"2025-09-18T10:03:23.608551Z","shell.execute_reply.started":"2025-09-18T10:02:44.364629Z","shell.execute_reply":"2025-09-18T10:03:23.607766Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"📊 로그 해석\n\nTotal Bins 5016 → LightGBM이 feature를 쪼갤 때 만든 구간(bin)의 개수.\n→ 데이터가 꽤 많지만 20개 feature만 쓰니까 bin 수도 줄었음.\n\nNumber of data points in the train set: 1,555,368\n→ 지금 train set에 약 155만 행이 들어갔네요. 아까는 partition 일부였는데, 더 크게 가져온 거 같아요.\n\nNumber of used features: 20\n→ importance 상위 20개만 쓰는 설정이 적용된 것 확인됨.\n\nStart training from score 0.005893\n→ target 평균값을 기반으로 초기 예측 시작했다는 의미.\n\n--------\n\n✅ 다음에 할 일\n\n성능 확인\n\nmean_squared_error(y_va, y_pred, sample_weight=w_va)로 MSE 찍어보기.\n\n전체 feature 썼을 때 MSE랑 비교해서 20개 feature로 줄여도 성능이 유지되는지 확인.","metadata":{}},{"cell_type":"code","source":"from sklearn.metrics import mean_squared_error\n\n# 검증 데이터 예측\ny_pred = model.predict(X_va)\n\n# 가중치 포함 MSE 계산\nmse = mean_squared_error(y_va, y_pred, sample_weight=w_va)\n\nprint(f\"[Valid] MSE = {mse:.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:03:45.952151Z","iopub.execute_input":"2025-09-18T10:03:45.952963Z","iopub.status.idle":"2025-09-18T10:03:49.922309Z","shell.execute_reply.started":"2025-09-18T10:03:45.952933Z","shell.execute_reply":"2025-09-18T10:03:49.921433Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"방금 나온 결과 [Valid] MSE = 0.630157은 이전에 전체 feature(79개 + indicator)로 돌렸을 때보다 약간 더 낮은 값이 나왔네요.\n\n📊 이게 의미하는 것\n\n20개 feature만 써도 충분하다\n\n- 성능(MSE)이 떨어지지 않고 오히려 소폭 개선됨.\n\n- 즉, 중요도가 낮은 feature는 잡음(Noise) 역할만 하고 있었을 가능성이 높음.\n\nFeature Selection 효과 확인\n\n- 모델이 더 가볍고, 학습 속도도 빨라짐.\n\n- Kaggle처럼 큰 데이터를 다루는 환경에서는 “필요한 feature만 남긴다”는 게 큰 이점임.\n\n다음 단계 준비 완료\n\n- 이제 Cross Validation을 적용해서 결과가 “우연이 아닌지” 검증 가능.\n\n- 그리고 responder_6 같은 다른 타깃에도 같은 방식 적용해서 비교 가능.\n\n👉 정리하면:\n**“20개 feature로 줄여도 responder_0 예측 성능이 유지/개선됨 → 다른 responder에서도 같은 방식 적용해볼 가치가 있다”**는 결론이에요.","metadata":{}},{"cell_type":"code","source":"# responder 6으로 성능 췤\n\nimport lightgbm as lgb\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error\n\n# -----------------------------\n# 1) 사용할 feature 준비\n# -----------------------------\ntop20_features = top20_sorted   # importance 기준 상위 20개 리스트\ntarget = \"responder_6\"          # 이번엔 responder_6\n\nusecols = [\"date_id\", \"time_id\", \"symbol_id\", \"weight\"] + top20_features + [target]\n\n# -----------------------------\n# 2) 데이터 로드\n# -----------------------------\nsample_path = \"/kaggle/input/jane-street-real-time-market-data-forecasting/train.parquet/partition_id=0/part-0.parquet\"\ndf_raw = pd.read_parquet(sample_path, columns=usecols)\n\n# NaN 처리\ndf_raw = df_raw.fillna(0)\n\n# -----------------------------\n# 3) Train/Valid 분리\n# -----------------------------\nX = df_raw[top20_features]\ny = df_raw[target]\nw = df_raw[\"weight\"]\n\nX_tr, X_va, y_tr, y_va, w_tr, w_va = train_test_split(X, y, w, test_size=0.2, random_state=42)\n\n# -----------------------------\n# 4) 모델 학습\n# -----------------------------\nmodel = lgb.LGBMRegressor(\n    n_estimators=500,\n    learning_rate=0.05,\n    max_depth=-1,\n    random_state=42,\n    n_jobs=-1,\n)\n\nmodel.fit(\n    X_tr, y_tr,\n    sample_weight=w_tr,\n    eval_set=[(X_va, y_va)],\n    eval_sample_weight=[w_va],\n    eval_metric=\"l2\"\n)\n\n# -----------------------------\n# 5) 성능 평가\n# -----------------------------\ny_pred = model.predict(X_va)\nmse = mean_squared_error(y_va, y_pred, sample_weight=w_va)\nprint(f\"[Valid - responder_6] MSE = {mse:.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:07:01.964260Z","iopub.execute_input":"2025-09-18T10:07:01.965484Z","iopub.status.idle":"2025-09-18T10:07:35.475038Z","shell.execute_reply.started":"2025-09-18T10:07:01.965400Z","shell.execute_reply":"2025-09-18T10:07:35.473806Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"📊 결과 비교\n\nresponder_0 (20개 feature) → MSE = 0.630157\n\nresponder_6 (20개 feature) → MSE = 0.637088\n\n📌 해석\n\n1. 성능 차이\n\n두 responder 모두 비슷한 수준의 MSE를 보여줌 (0.63대). responder_6이 약간 더 높은 MSE → 즉, 예측이 responder_0보다 조금 어렵거나, 중요한 feature 구성이 다를 가능성이 있음.\n\n2. 의미\n\nresponder별로 난이도가 다르다는 걸 보여줌.\n\nresponder_6은 “20개 feature만으로는 다 설명하기 부족할 수 있다”는 신호.\n\n→ 여기서는 30개, 40개 feature로 늘려서 다시 돌려보면 성능이 개선될 수 있음.\n\n3. 긍정적인 부분\n\n여전히 79개 전부 쓰지 않고도, 20개만으로 꽤 낮은 MSE를 유지.\n\n즉, feature selection 전략이 responder_6에도 통한다는 걸 보여줌.\n\n✅ 요약하면:\n\nresponder_6도 잘 학습됐고, 성능은 responder_0과 큰 차이는 없음.\n\n다만 responder_6은 feature 개수를 조금 더 늘려볼 필요가 있어 보임.\n\n다음 스텝은 → responder_6에 대해 30개, 40개 feature로 확장 학습 → 성능 비교 해보는 것.","metadata":{}},{"cell_type":"code","source":"# responder_6을 대상으로 상위 30개 feature써서 학습\n\nimport lightgbm as lgb\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error\n\n# -----------------------------\n# 1) 사용할 feature 준비\n# -----------------------------\ntop30_features = fi.head(30)[\"feature\"].tolist()   # 중요도 상위 30개 feature 리스트\ntarget = \"responder_6\"\n\nusecols = [\"date_id\", \"time_id\", \"symbol_id\", \"weight\"] + top30_features + [target]\n\n# -----------------------------\n# 2) 데이터 로드\n# -----------------------------\nsample_path = \"/kaggle/input/jane-street-real-time-market-data-forecasting/train.parquet/partition_id=0/part-0.parquet\"\ndf_raw = pd.read_parquet(sample_path, columns=usecols)\n\n# NaN 처리\ndf_raw = df_raw.fillna(0)\n\n# -----------------------------\n# 3) Train/Valid 분리\n# -----------------------------\nX = df_raw[top30_features]\ny = df_raw[target]\nw = df_raw[\"weight\"]\n\nX_tr, X_va, y_tr, y_va, w_tr, w_va = train_test_split(X, y, w, test_size=0.2, random_state=42)\n\n# -----------------------------\n# 4) 모델 학습\n# -----------------------------\nmodel = lgb.LGBMRegressor(\n    n_estimators=500,\n    learning_rate=0.05,\n    max_depth=-1,\n    random_state=42,\n    n_jobs=-1,\n)\n\nmodel.fit(\n    X_tr, y_tr,\n    sample_weight=w_tr,\n    eval_set=[(X_va, y_va)],\n    eval_sample_weight=[w_va],\n    eval_metric=\"l2\"\n)\n\n# -----------------------------\n# 5) 성능 평가\n# -----------------------------\ny_pred = model.predict(X_va)\nmse = mean_squared_error(y_va, y_pred, sample_weight=w_va)\nprint(f\"[Valid - responder_6 | top30 features] MSE = {mse:.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:10:35.278203Z","iopub.execute_input":"2025-09-18T10:10:35.278989Z","iopub.status.idle":"2025-09-18T10:11:17.553703Z","shell.execute_reply.started":"2025-09-18T10:10:35.278956Z","shell.execute_reply":"2025-09-18T10:11:17.552865Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"📊 성능 비교 (responder_6)\n\nTop20 feature → MSE = 0.637088\n\nTop30 feature → MSE = 0.628115\n\n📌 해석\n\n성능 개선\n\nfeature를 20개 → 30개로 늘렸더니 MSE가 약 0.009 감소.\n\n→ responder_6은 20개보다 30개쯤이 더 충분한 설명력을 줌.\n\nfeature 중요성 차이 : responder_0은 20개만으로도 충분했는데, responder_6은 더 많은 변수를 활용해야 제대로 예측 가능.\n\n즉, responder마다 최적 feature 개수가 다를 수 있다는 의미.\n\n실무적 결론\n\nresponder별로 “성능 vs feature 개수” 트레이드오프를 점검해야 함.\n\n예: responder_0 → 20개, responder_6 → 30개 이상 필요.\n\n✅ 요약하면:\n\nresponder_6은 최소 30개 feature 정도는 써야 안정적인 성능을 내는 걸 확인.\n\n이 결과는 responder마다 feature selection 전략을 다르게 가져가야 한다는 걸 시사함.","metadata":{}},{"cell_type":"markdown","source":"------\n\n📌 Cross Validation 개념\n1. 기본 아이디어\n\n데이터셋을 K개로 나눔 (예: 5개 → 5-Fold CV).\n\n1개는 검증(Validation), 나머지 K-1개는 학습(Train)으로 사용.\n\n이걸 K번 반복해서, 모든 데이터가 한 번씩은 Validation에 들어가게 함.\n\n마지막에 MSE(또는 원하는 metric)를 평균해서 “이 모델이 얼마나 안정적인지” 평가.\n\n2. 왜 필요한가?\n\n단일 split은 “운”에 따라 성능이 달라짐.\n(→ 운 좋으면 좋은 성능, 운 나쁘면 성능이 나쁘게 나옴)\n\nCross Validation은 여러 split에서 검증 → 결과를 평균내므로 일반화 성능을 더 정확히 파악.\n\nKaggle 같은 대회에서는 거의 필수적인 평가 방법.\n\n3. 적용 방법 (LightGBM 예시)\n\nsklearn.model_selection.KFold로 fold 나누기.\n\n각 fold마다 train_idx, valid_idx 뽑아서 → 학습 & 평가.\n\nfold별 MSE 기록 → 마지막에 평균내기.\n\n4. 해석\n\nCV 평균 MSE = 모델의 “일반화된 예측 성능”.\n\nCV 표준편차(std)까지 같이 보면 → 모델이 얼마나 안정적인지도 알 수 있음.\n\n--------\n\n📌 Cross Validation 할 때 데이터 범위 선택\n\n1. 이론적으로는 전체 데이터 사용\n\nCV는 보통 “가능한 한 많은 데이터”를 쓴 게 좋아요.\n\n→ 전체 train.parquet (partition=0~9 모두 합침)으로 학습+검증하면 가장 안정적인 결과.\n\n2. 현실적 제약 (메모리/시간)\n\nJane Street 데이터는 수천만 행이라, Kaggle 노트북에서 전부 메모리에 올리기 어려움.\n\n지금도 partition 하나(약 155만 행)만 써도 학습에 꽤 시간이 걸리죠.\n\n→ 그래서 보통은 일부 partition만 샘플링해서 CV를 돌립니다.\n\n3. 추천 전략\n\nStep 1 (가볍게): partition 하나(예: id=0)로 CV 실험 → CV 코드가 잘 돌아가는지 확인.\n\nStep 2 (중간 규모): partition 여러 개 (예: id=0~2, 약 500만 행) 합쳐서 CV → 더 안정적인 결과.\n\nStep 3 (최종): 가능하다면 전체 합치고 CV → 최종 모델 성능 검증.\n\n4. 정리\n\nCV = 전체 데이터로 하면 가장 좋지만, Kaggle 환경에선 샘플링 후 확장 방식으로 가야 안전합니다.\n\n지금 단계라면 partition=0 데이터만 가지고 5-Fold CV 먼저 돌려보는 게 적절해요.","metadata":{}},{"cell_type":"markdown","source":"-------\n\n📊 지금 사용한 데이터\n\nJane Street train 데이터 전체를 10개 partition(0~9)으로 나눈 것 중 partition=0 하나.\n\n행 개수: 약 155만 행 (1,555,368 rows)\n\nfeature: importance 상위 30개\n\ntarget: responder_6\n\n즉,\n👉 지금은 전체 train 데이터(수천만 행)가 아니라, partition=0 (약 10분의 1 크기) 만 가져와서\nresponder_6 + 상위 30개 feature로 학습 & 5-Fold Cross Validation 돌린 거예요.","metadata":{}},{"cell_type":"code","source":"import lightgbm as lgb\nfrom sklearn.model_selection import KFold\nfrom sklearn.metrics import mean_squared_error\nimport numpy as np\n\n# -----------------------------\n# 1) 사용할 feature 준비\n# -----------------------------\ntop30_features = fi.head(30)[\"feature\"].tolist()   # importance 기준 상위 30개\ntarget = \"responder_6\"\nusecols = [\"date_id\", \"time_id\", \"symbol_id\", \"weight\"] + top30_features + [target]\n\n# -----------------------------\n# 2) 데이터 로드\n# -----------------------------\nsample_path = \"/kaggle/input/jane-street-real-time-market-data-forecasting/train.parquet/partition_id=0/part-0.parquet\"\ndf_raw = pd.read_parquet(sample_path, columns=usecols).fillna(0)\n\nX = df_raw[top30_features].values\ny = df_raw[target].values\nw = df_raw[\"weight\"].values\n\n# -----------------------------\n# 3) K-Fold 설정\n# -----------------------------\nkf = KFold(n_splits=5, shuffle=True, random_state=42)\n\nmse_scores = []\n\n# -----------------------------\n# 4) Fold별 학습 & 평가\n# -----------------------------\nfor fold, (tr_idx, va_idx) in enumerate(kf.split(X)):\n    print(f\"\\n[Fold {fold+1}]\")\n    \n    X_tr, X_va = X[tr_idx], X[va_idx]\n    y_tr, y_va = y[tr_idx], y[va_idx]\n    w_tr, w_va = w[tr_idx], w[va_idx]\n    \n    model = lgb.LGBMRegressor(\n        n_estimators=500,\n        learning_rate=0.05,\n        max_depth=-1,\n        random_state=42,\n        n_jobs=-1,\n    )\n    \n    model.fit(\n        X_tr, y_tr,\n        sample_weight=w_tr,\n        eval_set=[(X_va, y_va)],\n        eval_sample_weight=[w_va],\n        eval_metric=\"l2\"\n    )\n    \n    y_pred = model.predict(X_va)\n    mse = mean_squared_error(y_va, y_pred, sample_weight=w_va)\n    mse_scores.append(mse)\n    print(f\"[Fold {fold+1}] MSE = {mse:.6f}\")\n\n# -----------------------------\n# 5) CV 평균 & 표준편차\n# -----------------------------\nprint(\"\\n===============================\")\nprint(f\"[CV 결과] 평균 MSE = {np.mean(mse_scores):.6f} | 표준편차 = {np.std(mse_scores):.6f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:15:27.315304Z","iopub.execute_input":"2025-09-18T10:15:27.316012Z","iopub.status.idle":"2025-09-18T10:18:44.850066Z","shell.execute_reply.started":"2025-09-18T10:15:27.315983Z","shell.execute_reply":"2025-09-18T10:18:44.849237Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"📊 결과 요약\n\nFold별 MSE: 0.621 ~ 0.630\n\nCV 평균 MSE = 0.626979\n\n표준편차 = 0.003247\n\n📌 해석\n\n안정성\n\n표준편차가 0.003 정도 → fold마다 성능 변동이 거의 없음.\n\n즉, responder_6 + top30 feature 모델이 안정적으로 성능을 내고 있다는 뜻.\n\n성능 수준\n\n단일 split 결과(0.628115)와 거의 동일.\n\n→ CV로 검증해도 과적합 걱정은 크지 않고, 모델이 일반화되고 있다는 걸 확인.\n\n의미\n\nresponder_6의 경우, top30 feature로 학습하면 안정적이고 무난한 성능을 낸다고 결론낼 수 있음.\n\n이제 responder_0, responder_6 외에 다른 responder에도 같은 과정을 반복하면, 전체적으로 어떤 feature 구성이 좋은지 윤곽이 나옴.\n\n✅ 요약:\nCross Validation 결과 → responder_6은 top30 feature로 안정적이고 신뢰할 만한 성능을 확보했다.\n→ 이제 나머지 responder들도 같은 방식으로 확인해가면 됨.","metadata":{}},{"cell_type":"code","source":"# (feature, importance) 튜플로 보기\ntop30_pairs = list(zip(fi.head(30)[\"feature\"], fi.head(30)[\"importance\"]))\nfor f, imp in top30_pairs:\n    print(f\"{f}: {imp}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:20:44.832925Z","iopub.execute_input":"2025-09-18T10:20:44.833740Z","iopub.status.idle":"2025-09-18T10:20:44.839454Z","shell.execute_reply.started":"2025-09-18T10:20:44.833705Z","shell.execute_reply":"2025-09-18T10:20:44.838598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 중요도 순(= 모델이 가장 많이 쓴 순) Top30 목록\ntop30_importance = fi.head(30)[\"feature\"].tolist()\nprint(\"[Top30 by importance]\")\nprint(top30_importance)\n\n# 번호순 정렬한 Top30 목록\ntop30_sorted = sorted(top30_importance, key=lambda x: int(x.split(\"_\")[1]))\nprint(\"\\n[Top30 by feature index]\")\nprint(top30_sorted)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-18T10:21:04.153502Z","iopub.execute_input":"2025-09-18T10:21:04.153814Z","iopub.status.idle":"2025-09-18T10:21:04.159952Z","shell.execute_reply.started":"2025-09-18T10:21:04.153792Z","shell.execute_reply":"2025-09-18T10:21:04.159060Z"}},"outputs":[],"execution_count":null}]}