{"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":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 1. Tổng quan","metadata":{}},{"cell_type":"markdown","source":"Mục tiêu là khám phá các thuật toán học máy giám sát để phát hiện hành vi dựa trên dữ liệu cảm biến. Chúng ta sẽ xử lý các vấn đề như giá trị thiếu, xây dựng đường ống (pipelines), tạo ra sơ đồ phụ thuộc, và áp dụng mã hóa một lần.\n\nChúng ta sẽ sử dụng dữ liệu từ các cảm biến để dự đoán hành vi của các đối tượng trong môi trường. Các thuật toán học máy sẽ giúp phân loại hành vi dựa trên các đặc điểm đo lường được từ dữ liệu cảm biến. Mục tiêu cuối cùng là xây dựng một mô hình có khả năng dự đoán chính xác hành vi dựa trên các tính năng cảm biến được cung cấp.","metadata":{}},{"cell_type":"markdown","source":"# Outline","metadata":{}},{"cell_type":"markdown","source":"## 1.1 Tải và Khám Phá Dữ Liệu","metadata":{}},{"cell_type":"markdown","source":"Tải dữ liệu và tiến hành kiểm tra sơ bộ (kích thước, loại dữ liệu, và các thuộc tính).\n\nPhân tích thống kê cho các tính năng trong dataset, bao gồm:\n* Giá trị thiếu và cách xử lý.\n* Các tính năng đặc biệt như giới tính, tuổi, hành vi.\n* Kiểm tra sự phân phối mục tiêu và đặc tính dữ liệu qua biểu đồ phân phối và biểu đồ hộp.","metadata":{}},{"cell_type":"markdown","source":"## 1.2 Tiền Xử Lý Dữ Liệu","metadata":{}},{"cell_type":"markdown","source":"* Xử lý các giá trị thiếu bằng các phương pháp phù hợp.\n* Tách biệt dữ liệu thành các nhóm: cảm biến IMU, ToF, và mục tiêu.\n* Chuẩn hóa và chuẩn bị dữ liệu cho mô hình.\n* Feature Engineering để tối ưu hóa đặc trưng dữ liệu.","metadata":{}},{"cell_type":"markdown","source":"## 1.3 Khám Phá Dữ Liệu và Trực Quan Hóa","metadata":{}},{"cell_type":"markdown","source":"Trực quan hóa mối quan hệ giữa các tính năng:\n* Tương quan Pearson giữa các tính năng.\n* Biểu đồ phân tán giữa các tính năng chính (ví dụ: chiều cao, trọng lượng, v.v.).\n* Biểu đồ hình tròn cho các tính năng phân loại như giới tính.\n* Tính toán mối tương quan giữa các dữ liệu cảm biến (IMU, ToF, v.v.).","metadata":{}},{"cell_type":"markdown","source":"## 1.4 Xây Dựng Mô Hình Học Máy","metadata":{}},{"cell_type":"markdown","source":"Chọn mô hình học máy phù hợp: Linear Regression, Decision Tree, Random Forest, XGBoost, MLP, và các mô hình hồi quy khác.\n\nTối ưu hóa mô hình qua Grid Search và Cross-Validation (K-fold).\n\nTối ưu hóa siêu tham số để cải thiện độ chính xác.","metadata":{}},{"cell_type":"markdown","source":"## 1.5 Đánh Giá Mô Hình và Tối Ưu Hóa","metadata":{}},{"cell_type":"markdown","source":"Đánh giá mô hình dựa trên các chỉ số như F1-score, accuracy, precision, recall.\n\nSử dụng ensemble learning như bagging và boosting để kết hợp các mô hình và cải thiện hiệu suất.\n\nTối ưu hóa mô hình cuối cùng để đảm bảo tính ổn định và khả năng dự đoán chính xác trong các bộ dữ liệu thực tế.","metadata":{}},{"cell_type":"markdown","source":"## 1.6 Kết Quả và Thảo Luận\n","metadata":{}},{"cell_type":"markdown","source":"Trình bày kết quả mô hình với các chỉ số đánh giá chi tiết.\n\nPhân tích các yếu tố ảnh hưởng đến độ chính xác của mô hình.\n\nKết luận về mô hình tối ưu và khả năng áp dụng vào thực tế.","metadata":{}},{"cell_type":"markdown","source":"# 2. Tổng quan dữ liệu","metadata":{}},{"cell_type":"markdown","source":"## 2.1 Tải dữ liệu và thư viện","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport warnings\nfrom matplotlib.ticker import MaxNLocator\nfrom mpl_toolkits.mplot3d import Axes3D\nimport os\nimport polars as pl\nimport kaggle_evaluation.cmi_inference_server\nfrom sklearn.metrics import accuracy_score, f1_score\nimport joblib\nfrom scipy.spatial.transform import Rotation as R","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:05.625437Z","iopub.execute_input":"2025-10-06T07:21:05.625802Z","iopub.status.idle":"2025-10-06T07:21:05.631403Z","shell.execute_reply.started":"2025-10-06T07:21:05.625778Z","shell.execute_reply":"2025-10-06T07:21:05.630374Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tải tập dữ liệu\n#Dữ liệu cảm biến cho tập huấn luyện (Train Sensor Data): \ntrain_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv\")\n\n#Thông tin nhân khẩu học của đối tượng trong tập huấn luyện (giới tính, tuổi, ...): \ntrain_dem_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv\")\n\n#Dữ liệu cảm biến cho tập kiểm tra:\ntest_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv\")\n\n# Thông tin nhân khẩu học của đối tượng trong tập kiểm tra:\ntest_dem_df = pd.read_csv(\"/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:05.632897Z","iopub.execute_input":"2025-10-06T07:21:05.633222Z","iopub.status.idle":"2025-10-06T07:21:27.114287Z","shell.execute_reply.started":"2025-10-06T07:21:05.633201Z","shell.execute_reply":"2025-10-06T07:21:27.113554Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2.1 Khám phá dữ liệu","metadata":{}},{"cell_type":"markdown","source":"### 2.1.3 Tóm tắt thống kê các tính năng ","metadata":{}},{"cell_type":"code","source":"datasets = {\n    \"Train Data\": train_df,\n    \"Train Demographics\": train_dem_df,\n    \"Test Data\": test_df,\n    \"Test Demographics\": test_dem_df,\n}\n\nfor name, df in datasets.items():\n    print(f\"Missing values in {name}:\")\n    missing = df.isnull().sum()\n    missing = missing[missing > 0]\n    if not missing.empty:\n        print(missing)\n    else:\n        print(\"  ✅ No missing values.\")\n    print()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:27.115548Z","iopub.execute_input":"2025-10-06T07:21:27.115830Z","iopub.status.idle":"2025-10-06T07:21:27.999921Z","shell.execute_reply.started":"2025-10-06T07:21:27.115802Z","shell.execute_reply":"2025-10-06T07:21:27.999060Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.1.5 kiểm tra giá trị trùng lặp","metadata":{}},{"cell_type":"code","source":"#Kiểm tra trùng lặp (Duplicate Rows): .duplicated(): Trả về Series boolean, dòng nào trùng lặp sẽ là True.\n#                                     .sum(): Đếm tổng số dòng trùng lặp.\n#Kiểm tra xem dữ liệu có bản ghi nào bị trùng không — quan trọng cho việc làm sạch dữ liệu.\n\n# Đếm các hàng trùng lặp trong train_df\ntrain_duplicates = train_df.duplicated().sum()\n\n# Đếm các hàng trùng lặp trong  test_df\ntest_duplicates = test_df.duplicated().sum()\n\n# Đếm các hàng trùng lặp trong train_dem_df (optional)\ntrain_dem_duplicates = train_dem_df.duplicated().sum()\n# Đếm các hàng trùng lặp trong test_dem_df (optional)\ntest_dem_duplicates = test_dem_df.duplicated().sum()\n\n# In số lượng dòng trùng:\nprint(f\"Number of duplicate rows in train_df: {train_duplicates}\")\nprint(f\"Number of duplicate rows in test_df: {test_duplicates}\")\nprint(f\"Number of duplicate rows in train_dem_df: {train_dem_duplicates}\")\nprint(f\"Number of duplicate rows in test_dem_df: {test_dem_duplicates}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:28.000782Z","iopub.execute_input":"2025-10-06T07:21:28.001423Z","iopub.status.idle":"2025-10-06T07:21:33.725803Z","shell.execute_reply.started":"2025-10-06T07:21:28.001397Z","shell.execute_reply":"2025-10-06T07:21:33.725056Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Tiền xử lý dữ liệu","metadata":{}},{"cell_type":"markdown","source":"### Tỷ lệ phần trăm Null","metadata":{}},{"cell_type":"markdown","source":"#### UTILS/HELPERS","metadata":{}},{"cell_type":"code","source":"def null_percent(df):\n    per=((df.isnull().sum()/len(df))*100).round(2)\n    return per[per>0]\n\nprint(\"Nan Values in Train data\")\nprint(null_percent(train_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:33.727804Z","iopub.execute_input":"2025-10-06T07:21:33.728484Z","iopub.status.idle":"2025-10-06T07:21:34.567277Z","shell.execute_reply.started":"2025-10-06T07:21:33.728456Z","shell.execute_reply":"2025-10-06T07:21:34.566576Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Các cột có tỷ lệ NaN cao nhất là các cột có tên bắt đầu với tof_5_v (5.24% NaN).\n\nCác cột khác như rot_x, rot_w có tỷ lệ NaN thấp hơn nhiều (0.64%).","metadata":{}},{"cell_type":"markdown","source":"## 2.2 Quan sát hoặc xu hướng ban đầu","metadata":{}},{"cell_type":"code","source":"# --- 2.4 Hợp nhất dữ liệu nhân khẩu học và cảm biến ---\nmerged_train = pd.merge(train_df, train_dem_df, on=\"subject\", how=\"left\")\nmerged_test = pd.merge(test_df, test_dem_df, on=\"subject\", how=\"left\")\n\nprint(\"Merged Train Shape:\", merged_train.shape)\nprint(\"Merged Test Shape:\", merged_test.shape)\ndisplay(merged_train.head(2))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:34.567988Z","iopub.execute_input":"2025-10-06T07:21:34.568240Z","iopub.status.idle":"2025-10-06T07:21:37.455997Z","shell.execute_reply.started":"2025-10-06T07:21:34.568221Z","shell.execute_reply":"2025-10-06T07:21:37.455315Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Merged Train Shape:** (574945, 348) - Sau khi hợp nhất, merged_train có 574,945 dòng và 348 cột, tức là dữ liệu từ train_df (574,945 dòng và 341 cột) đã được kết hợp với 7 cột từ train_dem_df (vì train_dem_df có 8 cột, nhưng 1 cột là subject đã được sử dụng làm khóa hợp nhất).\n\n**Merged Test Shape:** (107, 343) - Tương tự, merged_test có 107 dòng và 343 cột, kết quả là hợp nhất giữa test_df (107 dòng và 336 cột) với test_dem_df (8 cột, với 1 cột subject là khóa hợp nhất).","metadata":{}},{"cell_type":"code","source":"# --- Thống kê mô tả toàn bộ merged_train ---\nmerged_train.describe().T\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:37.456921Z","iopub.execute_input":"2025-10-06T07:21:37.457208Z","iopub.status.idle":"2025-10-06T07:21:53.946640Z","shell.execute_reply.started":"2025-10-06T07:21:37.457187Z","shell.execute_reply":"2025-10-06T07:21:53.945769Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Kiểm tra thiếu giá trị & thống kê cảm biến: \n#Danh sách các cột không thuộc cảm biến (có thể là ID, thông tin khác). \n\nexcluded_prefixes = ('acc_', 'rot_', 'thm_', 'tof_')\nsensor_cols = [col for col in train_df.columns if not col.startswith(excluded_prefixes)]\n\n# Sensor Data Summary for TRAIN\n#isnull().sum(): Đếm số giá trị bị thiếu.\nmissing_sensor_train = pd.DataFrame({\n    'Feature': sensor_cols,\n    '[TRAIN] Missing Count': train_df[sensor_cols].isnull().sum().values,\n    '[TRAIN] Missing %': (train_df[sensor_cols].isnull().sum().values / len(train_df)) * 100\n})\n\n#nunique(): Đếm số lượng giá trị duy nhất.\nunique_sensor_train = pd.DataFrame({\n    'Feature': sensor_cols,\n    'Unique Values [TRAIN]': train_df[sensor_cols].nunique().values\n})\n\n#dtypes: Lấy kiểu dữ liệu của từng cột.\ndtypes_sensor = pd.DataFrame({\n    'Feature': sensor_cols,\n    'Data Type': train_df[sensor_cols].dtypes.values\n})\n\n# Merge all summaries (NO test set)\n#merge: Gộp các bảng thống kê thành bảng duy nhất theo Feature.\nsensor_summary = missing_sensor_train \\\n    .merge(unique_sensor_train, on='Feature', how='left') \\\n    .merge(dtypes_sensor, on='Feature', how='left')\n\n# Display styled DataFrame (mask NaNs just for styling)\n#fillna(0): Điền giá trị thiếu bằng 0 (cho đẹp mắt khi hiển thị).\n#.style.background_gradient: Tô màu nền theo giá trị giúp dễ nhìn.\nstyled_df = sensor_summary.fillna(0)\nstyled_df.style.background_gradient(cmap='viridis')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:53.947548Z","iopub.execute_input":"2025-10-06T07:21:53.947852Z","iopub.status.idle":"2025-10-06T07:21:54.956614Z","shell.execute_reply.started":"2025-10-06T07:21:53.947826Z","shell.execute_reply":"2025-10-06T07:21:54.955805Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Không có giá trị thiếu: Tất cả các cột trong bảng thống kê đều không có giá trị thiếu (Missing Count = 0), cho thấy dữ liệu của bạn rất đầy đủ.\n\n- Sự đa dạng trong dữ liệu: Các cột như row_id, sequence_id, và subject có số lượng giá trị duy nhất khá lớn, trong khi các cột như sequence_type, phase, và behavior có ít giá trị duy nhất hơn, cho thấy chúng có thể là các thông tin phân loại.\n\n- Kiểu dữ liệu: Hầu hết các cột đều có kiểu dữ liệu object, ngoại trừ sequence_counter có kiểu int64, điều này cho thấy hầu hết các cột chứa thông tin phân loại (string), chỉ có một cột chứa giá trị số nguyên.","metadata":{}},{"cell_type":"code","source":"#Thống kê tương tự cho nhân khẩu học: Tương tự như phần thống kê cảm biến nhưng áp dụng cho dữ liệu nhân khẩu học.\n\n# Cột nhân khẩu học (không loại trừ)\ndem_cols = train_dem_df.columns\n\n# Giá trị bị thiếu trong dữ liệu nhân khẩu học của train \nmissing_demo_train = pd.DataFrame({\n    'Feature': dem_cols,\n    '[TRAIN DEMO] Missing Count': train_dem_df[dem_cols].isnull().sum().values,\n    '[TRAIN DEMO] Missing %': (train_dem_df[dem_cols].isnull().sum().values / len(train_dem_df)) * 100\n})\n\n# Giá trị duy nhất được tính trong dữ liệu nhân khẩu học của train \nunique_demo_train = pd.DataFrame({\n    'Feature': dem_cols,\n    'Unique Values [TRAIN DEMO]': train_dem_df[dem_cols].nunique().values\n})\n\n# Data types\ndtypes_demo = pd.DataFrame({\n    'Feature': dem_cols,\n    'Data Type': train_dem_df[dem_cols].dtypes.values\n})\n\n# Tóm tắt hợp nhất (chỉ dành cho đào tạo)\ndemo_summary = (\n    missing_demo_train\n    .merge(unique_demo_train, on='Feature', how='left')\n    .merge(dtypes_demo, on='Feature', how='left')\n)\n\n# Hiển thị tóm tắt theo phong cách\ndemo_summary.style.background_gradient(cmap='viridis')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:54.957429Z","iopub.execute_input":"2025-10-06T07:21:54.957634Z","iopub.status.idle":"2025-10-06T07:21:54.981938Z","shell.execute_reply.started":"2025-10-06T07:21:54.957618Z","shell.execute_reply":"2025-10-06T07:21:54.981087Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- Không có giá trị thiếu: Các cột nhân khẩu học đều không có giá trị thiếu, cho thấy dữ liệu đầy đủ và sẵn sàng để phân tích.\n\n- Sự đa dạng trong dữ liệu: Các cột như subject, age, và height_cm có sự đa dạng về giá trị duy nhất, trong khi các cột như sex, handedness có ít giá trị duy nhất hơn, cho thấy đây là các cột phân loại.\n\n- Kiểu dữ liệu: Các cột phần lớn có kiểu dữ liệu int64 hoặc float64, cho thấy dữ liệu chủ yếu là số nguyên hoặc số thực. Các cột như subject có kiểu dữ liệu object, có thể là các chuỗi ký tự.","metadata":{}},{"cell_type":"markdown","source":"### 2.2.2. Sensor-Based Features (Tính năng dựa trên cảm biến)","metadata":{}},{"cell_type":"markdown","source":"#### **NOTE:**\n* acc_: Gia tốc kế.\n* rot_: Cảm biến quay.\n* thm_: Nhiệt độ, độ ẩm.\n* tof_: Khoảng cách.\n","metadata":{}},{"cell_type":"markdown","source":"# 3. EDA","metadata":{}},{"cell_type":"markdown","source":"## 3.1. Numerical Feature Distribution Analysis (Phân tích phân phối tính năng số) (Phân tích đơn biến)","metadata":{}},{"cell_type":"markdown","source":"### 3.1.1 Participant & Contextual Features (Tính năng của người tham gia và bối cảnh)","metadata":{}},{"cell_type":"markdown","source":"### 3.1.2 Sensor-Based Features (Tính năng dựa trên cảm biến)","metadata":{}},{"cell_type":"markdown","source":"**Mục tiêu:**\nTrích xuất các đặc trưng thống kê (mean, std, max) từ dữ liệu sensor (acceleration, rotation, temperature, ToF) theo từng sequence_id để phục vụ mô hình học máy hoặc phân tích dữ liệu.","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\n\n# 1) Sao chép đào tạo và kiểm tra để chúng ta không sửa đổi DataFrame gốc\ntrain_temp = train_df.copy()\ntest_temp  = test_df.copy()\n\n# 2) GIA TỐC KẾ: tính toán độ lớn tại mỗi dấu thời gian\ntrain_temp['acc_mag'] = np.sqrt(\n    train_temp['acc_x']**2 + train_temp['acc_y']**2 + train_temp['acc_z']**2\n)\ntest_temp['acc_mag'] = np.sqrt(\n    test_temp['acc_x']**2 + test_temp['acc_y']**2 + test_temp['acc_z']**2\n)\n\n# 3) ROTATION: tính toán “góc quay” từ thành phần quaternion w\n# (Lưu ý: rot_w nằm trong [-1,1], do đó arccos hợp lệ. Chúng tôi bỏ qua NaN nếu có.)\ntrain_temp['rot_angle'] = 2 * np.arccos(train_temp['rot_w'].clip(-1,1))\ntest_temp['rot_angle']  = 2 * np.arccos(test_temp['rot_w'].clip(-1,1))\n\n# 4) Nhóm theo sequence_id và tổng hợp các tóm tắt gia tốc kế\nacc_agg_funcs = {\n    'acc_mag': ['mean', 'std', 'max']\n}\ntrain_acc_summary = train_temp.groupby('sequence_id').agg(acc_agg_funcs)\ntest_acc_summary  = test_temp.groupby('sequence_id').agg(acc_agg_funcs)\n\n# Làm phẳng cột MultiIndex\ntrain_acc_summary.columns = ['acc_mag_' + stat for stat in ['mean', 'std', 'max']]\ntest_acc_summary.columns  = ['acc_mag_' + stat for stat in ['mean', 'std', 'max']]\n\n# 5) Nhóm theo sequence_id và tổng hợp tóm tắt vòng quay\nrot_agg_funcs = {\n    'rot_angle': ['mean', 'std', 'max']\n}\ntrain_rot_summary = train_temp.groupby('sequence_id').agg(rot_agg_funcs)\ntest_rot_summary  = test_temp.groupby('sequence_id').agg(rot_agg_funcs)\n\ntrain_rot_summary.columns = ['rot_angle_' + stat for stat in ['mean', 'std', 'max']]\ntest_rot_summary.columns  = ['rot_angle_' + stat for stat in ['mean', 'std', 'max']]\n\n# 6) NHIỆT ĐỘ: năm cảm biến thm_1 … thm_5\nthm_cols = [f\"thm_{i}\" for i in range(1, 6)]\n\n# Xác định hàm tổng hợp: trung bình + độ lệch chuẩn\nthm_agg_funcs = {col: ['mean', 'std'] for col in thm_cols}\n\ntrain_thm_summary = train_temp.groupby('sequence_id').agg(thm_agg_funcs)\ntest_thm_summary  = test_temp.groupby('sequence_id').agg(thm_agg_funcs)\n\n# Làm phẳng các cột MultiIndex\nflattened_thm_cols = []\nfor sensor in thm_cols:\n    for stat in ['mean','std']:\n        flattened_thm_cols.append(f\"{sensor}_{stat}\")\n\ntrain_thm_summary.columns = flattened_thm_cols\ntest_thm_summary.columns  = flattened_thm_cols\n\n# 7) THỜI GIAN BAY: mỗi cảm biến i có 64 cột pixel: tof_i_v0 … tof_i_v63\n# Chúng ta sẽ tạo một “tof_i_mean_at_ts” cho mỗi dấu thời gian, sau đó tổng hợp theo từng chuỗi.\n\ndef compute_tof_sequence_summary(df):\n    # Khởi tạo một dict để giữ DataFrames theo từng chuỗi\n    seq_summaries = {}\n\n    for i in range(1, 6):\n        # Xây dựng danh sách các cột cho cảm biến i\n        tof_cols = [f\"tof_{i}_v{pix}\" for pix in range(64)]\n        # Thay thế -1 bằng NaN để chúng không làm lệch giá trị trung bình; chuyển đổi sang float\n        ts_grid = df[tof_cols].replace(-1, np.nan).astype(float)\n        # Tính toán “trung bình trên tất cả 64 pixel” cho mỗi dấu thời gian\n        df[f\"tof_{i}_mean_at_ts\"] = ts_grid.mean(axis=1)\n    \n   # Bây giờ, nhóm theo id chuỗi và tính giá trị trung bình và độ lệch chuẩn của các giá trị trung bình đó\n    agg_dict = {f\"tof_{i}_mean_at_ts\": ['mean','std'] for i in range(1, 6)}\n    summary = df.groupby('sequence_id').agg(agg_dict)\n    # Làm phẳng các cột MultiIndex\n    flat_cols = [f\"tof_{i}_{stat}\" for i in range(1, 6) for stat in ['mean','std']]\n    summary.columns = flat_cols\n    return summary\n\ntrain_tof_summary = compute_tof_sequence_summary(train_temp)\ntest_tof_summary  = compute_tof_sequence_summary(test_temp)\n\n# 8) Hợp nhất các tóm tắt accel, rotation, thm, tof (trên sequence_id)\ntrain_sensor_summary = (\n    train_acc_summary\n    .join(train_rot_summary, how='outer')\n    .join(train_thm_summary, how='outer')\n    .join(train_tof_summary, how='outer')\n)\n\ntest_sensor_summary = (\n    test_acc_summary\n    .join(test_rot_summary, how='outer')\n    .join(test_thm_summary, how='outer')\n    .join(test_tof_summary, how='outer')\n)\n\n# 9) Thêm cột “Dataset” để chúng ta có thể thực hiện box+hist song song\ntrain_sensor_summary['Dataset'] = 'Train'\ntest_sensor_summary['Dataset']  = 'Test'\n\n# 10) Ghép nối thành một DataFrame để vẽ đồ thị\ncombined_sensor_summary = pd.concat(\n    [train_sensor_summary, test_sensor_summary],\n    axis=0\n).reset_index(drop=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:21:54.982898Z","iopub.execute_input":"2025-10-06T07:21:54.983298Z","iopub.status.idle":"2025-10-06T07:22:00.486850Z","shell.execute_reply.started":"2025-10-06T07:21:54.983271Z","shell.execute_reply":"2025-10-06T07:22:00.486120Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3.3 Target Feature Analysis (Univariate Analysis)","metadata":{}},{"cell_type":"markdown","source":"### 3.4.2 Sensor‐Based Differentiation Across Demographics and Physiology Features","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\n\n# (1) Sáp nhập train_demographics vào train_df nếu chưa thực hiện\ntrain_df = train_df.merge(\n    train_dem_df,\n    on=\"subject\",\n    how=\"left\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:00.489650Z","iopub.execute_input":"2025-10-06T07:22:00.489913Z","iopub.status.idle":"2025-10-06T07:22:01.138759Z","shell.execute_reply.started":"2025-10-06T07:22:00.489893Z","shell.execute_reply":"2025-10-06T07:22:01.138056Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Xử lý missing values","metadata":{}},{"cell_type":"markdown","source":"### 1. IMU – Gia tốc/quaternion (acc_*, rot_w/x/y/z)","metadata":{}},{"cell_type":"code","source":"def fix_imu(df):\n    imu_cols = [c for c in df.columns if c.startswith((\"acc_\", \"rot_\"))]\n    def _seq_fix(g):\n        g[imu_cols] = g[imu_cols].interpolate(limit_direction=\"both\")\n        # renormalize quaternion\n        q = g[[\"rot_w\",\"rot_x\",\"rot_y\",\"rot_z\"]].to_numpy(float)\n        n = (q**2).sum(1)**0.5\n        n[n==0] = 1.0\n        q = q / n[:,None]\n        g[[\"rot_w\",\"rot_x\",\"rot_y\",\"rot_z\"]] = q\n        return g\n    return df.groupby(\"sequence_id\", group_keys=False).apply(_seq_fix)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.139575Z","iopub.execute_input":"2025-10-06T07:22:01.139842Z","iopub.status.idle":"2025-10-06T07:22:01.145467Z","shell.execute_reply.started":"2025-10-06T07:22:01.139815Z","shell.execute_reply":"2025-10-06T07:22:01.144653Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2. Thermopile (thm_1..thm_5)","metadata":{}},{"cell_type":"code","source":"def fix_thm(df):\n    thm = [f\"thm_{i}\" for i in range(1,6)]\n    return df.groupby(\"sequence_id\", group_keys=False)[thm].apply(\n        lambda g: g.interpolate(limit_direction=\"both\").ffill().bfill()\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.146423Z","iopub.execute_input":"2025-10-06T07:22:01.146710Z","iopub.status.idle":"2025-10-06T07:22:01.163303Z","shell.execute_reply.started":"2025-10-06T07:22:01.146684Z","shell.execute_reply":"2025-10-06T07:22:01.162500Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3. ToF – 5 cảm biến × 64 pixel (tof_i_vj)","metadata":{}},{"cell_type":"code","source":"def fix_tof_and_reduce(df):\n    # 1) sentinel -> NaN + flag\n    tof_pix = [c for c in df.columns if c.startswith(\"tof_\") and \"_v\" in c]\n    df[tof_pix] = df[tof_pix].replace(-1, np.nan)\n    # optional flags per sensor\n    for i in range(1,6):\n        cols = [f\"tof_{i}_v{p}\" for p in range(64) if f\"tof_{i}_v{p}\" in df]\n        df[f\"tof_{i}_missing_frac\"] = df[cols].isna().mean(axis=1)\n        df[f\"tof_{i}_mean\"] = df[cols].mean(axis=1)  # dimension reduction\n    # 2) interpolate the 5 means\n    mean_cols = [f\"tof_{i}_mean\" for i in range(1,6)]\n    df[mean_cols] = df.groupby(\"sequence_id\", group_keys=False)[mean_cols].apply(\n        lambda g: g.interpolate(limit_direction=\"both\").ffill().bfill()\n    )\n    # 3) drop heavy pixel columns to save RAM\n    df.drop(columns=tof_pix, inplace=True)\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.164280Z","iopub.execute_input":"2025-10-06T07:22:01.164862Z","iopub.status.idle":"2025-10-06T07:22:01.178682Z","shell.execute_reply.started":"2025-10-06T07:22:01.164835Z","shell.execute_reply":"2025-10-06T07:22:01.177935Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 4. Pha/nhãn & metadata","metadata":{}},{"cell_type":"code","source":"from sklearn.preprocessing import LabelEncoder\nle_gesture = LabelEncoder().fit(train_df[\"gesture\"].astype(str))\ntrain_df[\"gesture_id\"] = le_gesture.transform(train_df[\"gesture\"].astype(str))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.179505Z","iopub.execute_input":"2025-10-06T07:22:01.179826Z","iopub.status.idle":"2025-10-06T07:22:01.309937Z","shell.execute_reply.started":"2025-10-06T07:22:01.179808Z","shell.execute_reply":"2025-10-06T07:22:01.309134Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 5. Pipeline tiền xử lý","metadata":{}},{"cell_type":"code","source":"def minimal_preprocess(df):\n    df = df.copy()\n    # 1) IMU\n    df = fix_imu(df)\n    # 2) Thermopile\n    thm_fixed = fix_thm(df)  # returns only thm cols\n    df[thm_fixed.columns] = thm_fixed\n    # 3) ToF -> means + flags, drop pixels\n    df = fix_tof_and_reduce(df)\n    # 4) Feature cơ bản (ví dụ):\n    df[\"acc_mag\"] = np.sqrt(df[\"acc_x\"]**2 + df[\"acc_y\"]**2 + df[\"acc_z\"]**2)\n    df[\"rot_angle\"] = 2*np.arccos(np.clip(df[\"rot_w\"], -1, 1))\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.310794Z","iopub.execute_input":"2025-10-06T07:22:01.311098Z","iopub.status.idle":"2025-10-06T07:22:01.316428Z","shell.execute_reply.started":"2025-10-06T07:22:01.311073Z","shell.execute_reply":"2025-10-06T07:22:01.315653Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Notebook 1 – Baseline Classical ML\n\nBenchmark đơn giản để so sánh với mô hình deep learning.\n\n**Nội dung:**\n- Feature Engineering: mean, std, skewness, FFT energy, ToF spatial variance,…\n- Train Logistic Regression, Random Forest, XGBoost/LightGBM (nếu sẵn có môi trường).\n- Cross-validation (StratifiedKFold), macro F1.\n- Kết quả: so sánh IMU-only vs Multimodal (IMU + ToF).\n- Chiến lược feature selection: Mutual Information / PCA.\n- Visualization feature importance: Permutation Importance + (tùy chọn) SHAP.\n\n👉 **Vai trò:** baseline classical ML để đối chiếu với deep learning.","metadata":{}},{"cell_type":"code","source":"# %% [setup] Imports & environment checks\nimport os, sys, math, warnings, json\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nfrom scipy.stats import skew, kurtosis\nfrom numpy.fft import rfft\nimport matplotlib.pyplot as plt\n\nfrom sklearn.model_selection import StratifiedKFold, cross_val_score\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.compose import ColumnTransformer\nfrom sklearn.metrics import f1_score, make_scorer\nfrom sklearn.feature_selection import mutual_info_classif\nfrom sklearn.decomposition import PCA\nfrom sklearn.inspection import permutation_importance\n\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.ensemble import RandomForestClassifier, HistGradientBoostingClassifier\n\nwarnings.filterwarnings('ignore')\nRANDOM_STATE = 42\nnp.random.seed(RANDOM_STATE)\n\n# Optional packages\nHAS_XGB = False\nHAS_LGBM = False\nHAS_SHAP = False\ntry:\n    from xgboost import XGBClassifier\n    HAS_XGB = True\nexcept Exception:\n    pass\ntry:\n    from lightgbm import LGBMClassifier\n    HAS_LGBM = True\nexcept Exception:\n    pass\ntry:\n    import shap\n    HAS_SHAP = True\nexcept Exception:\n    pass\n\nprint({'xgboost': HAS_XGB, 'lightgbm': HAS_LGBM, 'shap': HAS_SHAP})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.317414Z","iopub.execute_input":"2025-10-06T07:22:01.317939Z","iopub.status.idle":"2025-10-06T07:22:01.335064Z","shell.execute_reply.started":"2025-10-06T07:22:01.317918Z","shell.execute_reply":"2025-10-06T07:22:01.334262Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 1) Data loading\n\nĐiền đường dẫn dataset của bạn vào `DATA_PATH`. Notebook sẽ cố gắng tự động nhận diện nhóm cột IMU và ToF theo **prefix** phổ biến:\n- IMU: `'acc'`, `'gyro'`, `'mag'`, `'imu'`, `ax, ay, az, gx, gy, gz`\n- ToF: `'tof'`, `'distance'`, `'tof_'`, `'tof'`\n\nNếu không tìm thấy file, notebook sẽ **khởi tạo synthetic dataset** để bạn có thể chạy thử toàn bộ pipeline.","metadata":{}},{"cell_type":"code","source":"# %% [data] Read CSV or simulate\nDATA_PATH = ''  # ví dụ: '/mnt/data/train.csv' hoặc để rỗng để dùng synthetic\nLABEL_COL = 'label'\n\ndef simulate_dataset(n=1200, n_imu=6, n_tof=8, n_classes=5):\n    # IMU features: 6 kênh (ax, ay, az, gx, gy, gz)\n    imu = np.random.randn(n, n_imu)\n    # ToF pixels (ví dụ 8 tia)\n    tof = np.abs(np.random.randn(n, n_tof) * 0.5 + 2.5)\n    # latent class influence\n    y = np.random.randint(0, n_classes, size=n)\n    for c in range(n_classes):\n        idx = y == c\n        imu[idx] += (c - n_classes/2) * 0.15\n        tof[idx] += (n_classes/2 - c) * 0.1\n    cols_imu = [f\"ax\", \"ay\", \"az\", \"gx\", \"gy\", \"gz\"][:n_imu]\n    cols_tof = [f\"tof_{i+1}\" for i in range(n_tof)]\n    df = pd.DataFrame(np.hstack([imu, tof]), columns=cols_imu+cols_tof)\n    df[LABEL_COL] = y\n    return df\n\nif DATA_PATH and os.path.exists(DATA_PATH):\n    df_raw = pd.read_csv(DATA_PATH)\n    print(f\"Loaded: {DATA_PATH}\")\nelse:\n    df_raw = simulate_dataset()\n    print(\"Using synthetic dataset (override DATA_PATH to use your data)\")\n\ndf_raw.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.335895Z","iopub.execute_input":"2025-10-06T07:22:01.336219Z","iopub.status.idle":"2025-10-06T07:22:01.365501Z","shell.execute_reply.started":"2025-10-06T07:22:01.336191Z","shell.execute_reply":"2025-10-06T07:22:01.364825Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2) Feature Engineering\n\n### Auto-detect nhóm cột\nNotebook sẽ tự tách **IMU columns** và **ToF columns** theo tên cột. Sau đó sinh thêm các đặc trưng tổng hợp:\n- Cho từng kênh: `mean`, `std`, `skewness` (trên *window* nếu có), và **FFT energy** (ước lượng năng lượng tần số cơ bản từ một vector *giá trị theo thời điểm*). Ở trường hợp dữ liệu dạng hàng (mỗi hàng là 1 mẫu đã tổng hợp), FFT energy được tính trực tiếp trên vector (1 chiều) của mỗi hàng — nếu không có chiều thời gian, năng lượng FFT sẽ không mang nhiều ý nghĩa nhưng vẫn đảm bảo pipeline chạy.\n- Cho ToF: **spatial variance** (phương sai theo chiều pixel ToF của mỗi hàng).","metadata":{}},{"cell_type":"code","source":"# %% [fe] Auto-detect columns & feature builders (optimized)\ndef detect_columns(df):\n    \"\"\"Tự động nhận diện nhóm cột IMU và ToF\"\"\"\n    cols = [c for c in df.columns if c != LABEL_COL]\n    imu_prefixes = ['acc', 'gyro', 'mag', 'imu']\n    imu_exact = {'ax', 'ay', 'az', 'gx', 'gy', 'gz'}\n    tof_prefixes = ['tof', 'distance']\n\n    imu_cols, tof_cols, others = [], [], []\n    for c in cols:\n        cl = c.lower()\n        if cl in imu_exact or any(cl.startswith(p) for p in imu_prefixes):\n            imu_cols.append(c)\n        elif any(cl.startswith(p) for p in tof_prefixes):\n            tof_cols.append(c)\n        else:\n            others.append(c)\n\n    print(f\"✅ IMU cols: {imu_cols}\")\n    print(f\"✅ ToF cols: {tof_cols}\")\n    if others:\n        print(f\"ℹ️ Other cols: {others}\")\n    return imu_cols, tof_cols, others\n\n\ndef fft_energy(vec):\n    \"\"\"Tính năng lượng phổ FFT (bỏ DC component)\"\"\"\n    v = np.asarray(vec)\n    if v.ndim == 0 or len(v) < 2:\n        return 0.0\n    spec = np.abs(rfft(v))\n    return float(np.sum(spec[1:] ** 2))\n\n\ndef build_row_features(df, imu_cols, tof_cols):\n    \"\"\"\n    Sinh đặc trưng cơ bản cho dữ liệu dạng hàng (1 mẫu / dòng)\n    Nếu dữ liệu time-series, thay df -> groupby(window_id)\n    \"\"\"\n    X = pd.DataFrame(index=df.index)\n\n    # --- IMU features ---\n    for c in imu_cols:\n        val = df[c]\n        X[f'{c}_mean'] = val\n        # Khi không có dữ liệu theo thời gian -> std/skew/fft = 0\n        X[f'{c}_std'] = 0.0\n        X[f'{c}_skew'] = 0.0\n        X[f'{c}_fft_energy'] = 0.0\n\n    # --- ToF features ---\n    if len(tof_cols) > 1:\n        tof_block = df[tof_cols]\n        X['tof_spatial_mean'] = tof_block.mean(axis=1)\n        X['tof_spatial_var'] = tof_block.var(axis=1)\n        X['tof_spatial_min'] = tof_block.min(axis=1)\n        X['tof_spatial_max'] = tof_block.max(axis=1)\n        X['tof_spatial_range'] = X['tof_spatial_max'] - X['tof_spatial_min']\n    elif len(tof_cols) == 1:\n        X['tof_spatial_mean'] = df[tof_cols[0]]\n        X['tof_spatial_var'] = 0.0\n        X['tof_spatial_min'] = df[tof_cols[0]]\n        X['tof_spatial_max'] = df[tof_cols[0]]\n        X['tof_spatial_range'] = 0.0\n    else:\n        # Không có ToF\n        X['tof_spatial_mean'] = 0.0\n        X['tof_spatial_var'] = 0.0\n        X['tof_spatial_range'] = 0.0\n\n    return X\n\n\n# --- Thực thi ---\nimu_cols, tof_cols, other_cols = detect_columns(df_raw)\nX_fe = build_row_features(df_raw, imu_cols, tof_cols)\ny = df_raw[LABEL_COL].values\n\nprint(\"✅ Feature matrix shape:\", X_fe.shape)\nprint(\"✅ First few rows:\")\ndisplay(X_fe.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.366245Z","iopub.execute_input":"2025-10-06T07:22:01.366461Z","iopub.status.idle":"2025-10-06T07:22:01.410106Z","shell.execute_reply.started":"2025-10-06T07:22:01.366444Z","shell.execute_reply":"2025-10-06T07:22:01.409459Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3) Model zoo & evaluation helper\n\n- Models: Logistic Regression, Random Forest, XGBoost/LightGBM (nếu có), và fallback **HistGradientBoosting**.\n- CV: StratifiedKFold(k=5), scoring = **macro F1**.","metadata":{}},{"cell_type":"code","source":"print(X_fe.dtypes)\nprint(X_fe.isna().sum())\nprint(X_fe.nunique())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.410819Z","iopub.execute_input":"2025-10-06T07:22:01.411111Z","iopub.status.idle":"2025-10-06T07:22:01.422738Z","shell.execute_reply.started":"2025-10-06T07:22:01.411089Z","shell.execute_reply":"2025-10-06T07:22:01.422059Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"constant_cols = [col for col in X_fe.columns if X_fe[col].nunique() == 1]\nprint(\"Constant features:\", constant_cols)\n\nX_fe = X_fe.drop(columns=constant_cols)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.423477Z","iopub.execute_input":"2025-10-06T07:22:01.423670Z","iopub.status.idle":"2025-10-06T07:22:01.436344Z","shell.execute_reply.started":"2025-10-06T07:22:01.423654Z","shell.execute_reply":"2025-10-06T07:22:01.435564Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"New shape:\", X_fe.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.437279Z","iopub.execute_input":"2025-10-06T07:22:01.437579Z","iopub.status.idle":"2025-10-06T07:22:01.448684Z","shell.execute_reply.started":"2025-10-06T07:22:01.437553Z","shell.execute_reply":"2025-10-06T07:22:01.447979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_models():\n    models = {\n        'LogReg': Pipeline([\n            ('scaler', StandardScaler(with_mean=True, with_std=True)),\n            ('clf', LogisticRegression(max_iter=2000, n_jobs=None, solver='saga', random_state=RANDOM_STATE))\n        ]),\n        'RandomForest': RandomForestClassifier(\n            n_estimators=300, max_depth=None, random_state=RANDOM_STATE, n_jobs=-1\n        )\n    }\n\n    if HAS_XGB:\n        models['XGBoost'] = XGBClassifier(\n            n_estimators=400, max_depth=6, learning_rate=0.05,\n            subsample=0.9, colsample_bytree=0.9, eval_metric='mlogloss',\n            random_state=RANDOM_STATE, n_jobs=-1\n        )\n    else:\n        models['HGB'] = HistGradientBoostingClassifier(random_state=RANDOM_STATE)\n\n    if HAS_LGBM:\n        models['LightGBM'] = LGBMClassifier(\n            n_estimators=600,\n            learning_rate=0.05,\n            subsample=0.9,\n            colsample_bytree=0.9,\n            random_state=RANDOM_STATE,\n            force_col_wise=True,       # bỏ cảnh báo overhead\n            min_gain_to_split=0.0,     # cho phép chia nhánh dù gain nhỏ\n            min_data_in_leaf=1,        # giảm yêu cầu số lượng mẫu để chia\n            max_depth=-1,              # không giới hạn độ sâu\n            num_leaves=64,             # tăng số lá cho mô hình học linh hoạt hơn\n            verbose=-1                 # tắt cảnh báo không cần thiết\n        )\n\n    return models\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.449466Z","iopub.execute_input":"2025-10-06T07:22:01.449690Z","iopub.status.idle":"2025-10-06T07:22:01.463752Z","shell.execute_reply.started":"2025-10-06T07:22:01.449666Z","shell.execute_reply":"2025-10-06T07:22:01.463000Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results_preview = evaluate_models(X_fe, y)\nresults_preview\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:01.464652Z","iopub.execute_input":"2025-10-06T07:22:01.465198Z","iopub.status.idle":"2025-10-06T07:22:28.015306Z","shell.execute_reply.started":"2025-10-06T07:22:01.465173Z","shell.execute_reply":"2025-10-06T07:22:28.014491Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4) IMU-only vs Multimodal\n\nSo sánh các bộ đặc trưng:\n- **IMU-only**: chỉ dùng đặc trưng từ IMU\n- **ToF-only** *(nếu có)*\n- **Multimodal**: IMU + ToF","metadata":{}},{"cell_type":"code","source":"print(\"Check feature stats before training:\")\nprint(X_fe.describe().T[['mean','std','min','max']].head(10))\n\n# Check cột hằng\nconstant_cols = [c for c in X_fe.columns if X_fe[c].nunique() <= 1]\nif constant_cols:\n    print(\"⚠️ Constant columns removed:\", constant_cols)\n    X_fe = X_fe.drop(columns=constant_cols)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:28.016201Z","iopub.execute_input":"2025-10-06T07:22:28.016434Z","iopub.status.idle":"2025-10-06T07:22:28.042048Z","shell.execute_reply.started":"2025-10-06T07:22:28.016414Z","shell.execute_reply":"2025-10-06T07:22:28.041422Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import f1_score\nimport numpy as np\nimport pandas as pd\n\ndef compute_compound_metrics(y_true, y_pred):\n    \"\"\"\n    Tính toán Binary F1, Macro F1 và chỉ số tổng hợp (combined).\n    - Binary F1: đánh giá đúng/sai giữa target gesture (1) và non-target (0)\n    - Macro F1: trung bình F1 trên tất cả class (coi non-target là 1 class)\n    - Combined: trung bình của cả 2 metric\n    \"\"\"\n\n    # Nếu y có nhiều hơn 2 class → coi class 0 là non-target, còn lại là target\n    unique_classes = np.unique(y_true)\n    if len(unique_classes) > 2:\n        y_true_binary = (y_true != 0).astype(int)\n        y_pred_binary = (y_pred != 0).astype(int)\n    else:\n        y_true_binary = y_true\n        y_pred_binary = y_pred\n\n    # F1 nhị phân (target vs non-target)\n    binary_f1 = f1_score(y_true_binary, y_pred_binary, average=\"binary\", zero_division=0)\n\n    # F1 macro trên toàn bộ gesture\n    macro_f1 = f1_score(y_true, y_pred, average=\"macro\", zero_division=0)\n\n    # Combined score = trung bình cộng (có thể thay đổi trọng số)\n    combined = (binary_f1 + macro_f1) / 2\n\n    return {\n        \"binary_f1\": binary_f1,\n        \"macro_f1\": macro_f1,\n        \"combined\": combined\n    }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:28.042792Z","iopub.execute_input":"2025-10-06T07:22:28.043085Z","iopub.status.idle":"2025-10-06T07:22:28.049085Z","shell.execute_reply.started":"2025-10-06T07:22:28.043059Z","shell.execute_reply":"2025-10-06T07:22:28.048403Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# %% [compare] IMU-only vs ToF-only vs Multimodal\ndef subset_features(X, use_imu=True, use_tof=True):\n    cols = []\n    if use_imu:\n        cols += [c for c in X.columns if any(c.startswith(prefix) for prefix in [\n            'ax_', 'ay_', 'az_', 'gx_', 'gy_', 'gz_', 'acc', 'gyro', 'mag', 'imu'\n        ])]\n    if use_tof:\n        cols += [c for c in X.columns if c.startswith('tof_') or c.startswith('tof')]\n        cols += [c for c in X.columns if c in ['tof_spatial_var','tof_spatial_mean']]\n    cols = list(dict.fromkeys(cols))\n    return X[cols] if cols else X\n\nexperiments = {}\nX_imu = subset_features(X_fe, use_imu=True, use_tof=False)\nX_tof = subset_features(X_fe, use_imu=False, use_tof=True)\nX_multi = subset_features(X_fe, use_imu=True, use_tof=True)\n\nexperiments['IMU_only'] = evaluate_models(X_imu, y)\nif X_tof.shape[1] > 0:\n    experiments['ToF_only'] = evaluate_models(X_tof, y)\nexperiments['Multimodal'] = evaluate_models(X_multi, y)\n\nfor name, dfres in experiments.items():\n    print('\\n===', name, '===')\n    display(dfres)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:22:28.049846Z","iopub.execute_input":"2025-10-06T07:22:28.050107Z","iopub.status.idle":"2025-10-06T07:23:34.263126Z","shell.execute_reply.started":"2025-10-06T07:22:28.050087Z","shell.execute_reply":"2025-10-06T07:23:34.262318Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5) Feature Selection – Mutual Information / PCA\n\nHai hướng giảm chiều:\n1. **Mutual Information (MI)**: chọn Top-K đặc trưng có MI cao nhất với nhãn.\n2. **PCA**: giữ số thành phần để đạt ngưỡng phương sai (ví dụ 95%).","metadata":{}},{"cell_type":"code","source":"print(\"Keys of score_lr_mi:\", score_lr_mi.keys())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:23:34.264111Z","iopub.execute_input":"2025-10-06T07:23:34.264440Z","iopub.status.idle":"2025-10-06T07:23:34.268829Z","shell.execute_reply.started":"2025-10-06T07:23:34.264400Z","shell.execute_reply":"2025-10-06T07:23:34.267971Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# %% [fs] Mutual Information & PCA selection\nfrom sklearn.impute import SimpleImputer\n\ndef mi_select(X, y, k=64):\n    imp = SimpleImputer(strategy='median')\n    X_imp = pd.DataFrame(imp.fit_transform(X), columns=X.columns)\n    mi = mutual_info_classif(X_imp, y, random_state=RANDOM_STATE, discrete_features=False)\n    order = np.argsort(mi)[::-1][:min(k, X.shape[1])]\n    cols_sel = X.columns[order]\n    return X[cols_sel], pd.Series(mi, index=X.columns).sort_values(ascending=False)\n\ndef pca_transform(X, var_keep=0.95):\n    scaler = StandardScaler()\n    Xs = scaler.fit_transform(X)\n    pca = PCA(n_components=var_keep, random_state=RANDOM_STATE)\n    Xp = pca.fit_transform(Xs)\n    return Xp, pca, scaler\n\n# Run on Multimodal as default\nXmm = X_multi.copy()\nXmi, mi_scores = mi_select(Xmm, y, k=64)\nprint('MI-selected shape:', Xmi.shape)\nXp, pca, scaler = pca_transform(Xmm, var_keep=0.95)\nprint('PCA shape:', Xp.shape)\n\n# Evaluate\nres_mi = evaluate_models(Xmi, y)\nres_pca = evaluate_models(pd.DataFrame(Xp), y)\ndisplay(res_mi)\ndisplay(res_pca)\n\n# Plot MI scores (Top 20)\ntop_mi = mi_scores.head(20)\nplt.figure()\ntop_mi[::-1].plot(kind='barh')\nplt.title('Top-20 Mutual Information scores')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:23:34.272206Z","iopub.execute_input":"2025-10-06T07:23:34.272450Z","iopub.status.idle":"2025-10-06T07:24:25.225754Z","shell.execute_reply.started":"2025-10-06T07:23:34.272432Z","shell.execute_reply":"2025-10-06T07:24:25.224839Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Model evaluation ---\ndef safe_extract_metrics(res):\n    \"\"\"Chuẩn hóa output từ evaluate_models() hoặc logistic thành dict gồm avg_f1, accuracy, macro_f1.\"\"\"\n    if isinstance(res, dict):\n        # trường hợp compute_compound_metrics hoặc evaluate_models()\n        return {\n            'avg_f1': res.get('combined', np.nan),     # dùng 'combined' làm chỉ số tổng hợp\n            'accuracy': np.nan,                        # không có accuracy trong compute_compound_metrics()\n            'macro_f1': res.get('macro_f1', np.nan)\n        }\n    elif isinstance(res, pd.DataFrame):\n        return {\n            'avg_f1': res['combined'].mean() if 'combined' in res else np.nan,\n            'accuracy': np.nan,\n            'macro_f1': res['macro_f1'].mean() if 'macro_f1' in res else np.nan,\n        }\n    else:\n        return {'avg_f1': np.nan, 'accuracy': np.nan, 'macro_f1': np.nan}\n\n# Evaluate tree-based\nres_mi_tree_raw = evaluate_models(Xmi, y)\nres_pca_tree_raw = evaluate_models(Xp_df, y)\n\nres_mi_tree = safe_extract_metrics(res_mi_tree_raw)\nres_pca_tree = safe_extract_metrics(res_pca_tree_raw)\n\n# Logistic Regression (tuyến tính)\nX_train, X_val, y_train, y_val = train_test_split(Xmm, y, test_size=0.2, stratify=y, random_state=RANDOM_STATE)\n\n# Logistic + MI\nlr_mi = Pipeline([\n    ('imp', SimpleImputer(strategy='median')),\n    ('scaler', StandardScaler()),\n    ('clf', LogisticRegression(max_iter=2000, random_state=RANDOM_STATE))\n])\nlr_mi.fit(Xmi.loc[X_train.index], y_train)\ny_pred_lr_mi = lr_mi.predict(Xmi.loc[X_val.index])\nscore_lr_mi = safe_extract_metrics(compute_compound_metrics(y_val, y_pred_lr_mi))\n\n# Logistic + PCA\nlr_pca = Pipeline([\n    ('scaler', StandardScaler()),\n    ('pca', PCA(n_components=0.95, random_state=RANDOM_STATE)),\n    ('clf', LogisticRegression(max_iter=2000, random_state=RANDOM_STATE))\n])\nlr_pca.fit(X_train, y_train)\ny_pred_lr_pca = lr_pca.predict(X_val)\nscore_lr_pca = safe_extract_metrics(compute_compound_metrics(y_val, y_pred_lr_pca))\n\n# --- Summary table ---\nsummary_df = pd.DataFrame([\n    ['LightGBM (Tree)', 'MI', res_mi_tree['avg_f1'], res_mi_tree['accuracy'], res_mi_tree['macro_f1']],\n    ['LightGBM (Tree)', 'PCA', res_pca_tree['avg_f1'], res_pca_tree['accuracy'], res_pca_tree['macro_f1']],\n    ['LogisticRegression (Linear)', 'MI', score_lr_mi['avg_f1'], score_lr_mi['accuracy'], score_lr_mi['macro_f1']],\n    ['LogisticRegression (Linear)', 'PCA', score_lr_pca['avg_f1'], score_lr_pca['accuracy'], score_lr_pca['macro_f1']],\n], columns=['Model Type', 'Feature Method', 'avg_f1 (combined)', 'accuracy', 'macro_f1'])\n\ndisplay(summary_df.style.background_gradient(cmap='Blues', subset=['avg_f1 (combined)', 'macro_f1']))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:24:25.226823Z","iopub.execute_input":"2025-10-06T07:24:25.227174Z","iopub.status.idle":"2025-10-06T07:25:16.162898Z","shell.execute_reply.started":"2025-10-06T07:24:25.227145Z","shell.execute_reply":"2025-10-06T07:25:16.162252Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6) Feature Importance Visualization\n\n- **Permutation Importance**: áp dụng cho bất kỳ model nào sau khi fit.\n- **SHAP**: nếu thư viện `shap` sẵn có — ưu tiên với mô hình cây (RF/XGB/LGBM).","metadata":{}},{"cell_type":"code","source":"# %% [viz] Permutation Importance & SHAP (optional)\nfrom sklearn.model_selection import train_test_split\n\nX_train, X_test, y_train, y_test = train_test_split(Xmm, y, test_size=0.2, stratify=y, random_state=RANDOM_STATE)\n\nrf = RandomForestClassifier(n_estimators=400, random_state=RANDOM_STATE, n_jobs=-1)\nrf.fit(X_train, y_train)\npi = permutation_importance(rf, X_test, y_test, scoring=make_scorer(f1_score, average='macro'), n_repeats=10, random_state=RANDOM_STATE, n_jobs=-1)\npi_mean = pd.Series(pi.importances_mean, index=Xmm.columns).sort_values(ascending=False)\ntop20 = pi_mean.head(20)\n\nplt.figure()\ntop20[::-1].plot(kind='barh')\nplt.title('Permutation Importance (RF) – Top 20')\nplt.tight_layout()\nplt.show()\n\nif HAS_SHAP:\n    try:\n        explainer = shap.TreeExplainer(rf)\n        shap_values = explainer.shap_values(X_test)\n        # summary plot (requires matplotlib, generated inline)\n        shap.summary_plot(shap_values, X_test, show=True)\n    except Exception as e:\n        print('SHAP failed:', e)\nelse:\n    print('SHAP not available; skipping SHAP plots.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:27:37.662723Z","iopub.execute_input":"2025-10-06T07:27:37.663150Z","iopub.status.idle":"2025-10-06T07:27:55.651845Z","shell.execute_reply.started":"2025-10-06T07:27:37.663122Z","shell.execute_reply":"2025-10-06T07:27:55.650881Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7) Save results\nLưu lại bảng so sánh kết quả và danh sách feature MI vào `/mnt/data/` để tiện theo dõi.","metadata":{}},{"cell_type":"code","source":"# %% [save] Export result tables\nout_dir = Path('/mnt/data')\nout_dir.mkdir(parents=True, exist_ok=True)\n\ndef concat_results(experiments):\n    rows = []\n    for name, dfres in experiments.items():\n        tmp = dfres.copy()\n        tmp.insert(0, 'feature_set', name)\n        rows.append(tmp)\n    return pd.concat(rows, axis=0, ignore_index=True)\n\nall_res = concat_results(experiments)\nres_path = out_dir / 'baseline_results.csv'\nmi_path = out_dir / 'mi_scores.csv'\nall_res.to_csv(res_path, index=False)\nmi_scores.to_csv(mi_path, header=['mi_score'])\nprint('Saved:', res_path)\nprint('Saved:', mi_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:37:04.521799Z","iopub.execute_input":"2025-10-06T07:37:04.522162Z","iopub.status.idle":"2025-10-06T07:37:04.534820Z","shell.execute_reply.started":"2025-10-06T07:37:04.522140Z","shell.execute_reply":"2025-10-06T07:37:04.533895Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Ghi chú\n- Nếu dữ liệu của bạn là **time-series theo cửa sổ**, hãy thay thế hàm `build_row_features` bằng cách `groupby(window_id)` rồi tính các stats (mean/std/skew/FFT energy) trên từng **chuỗi theo thời gian**. Khi đó FFT energy mới phản ánh đúng năng lượng tín hiệu.\n- Bạn có thể mở rộng ToF spatial features (ví dụ: min/max/median, entropy, center-of-mass theo ma trận ToF, v.v.).","metadata":{}},{"cell_type":"code","source":"import time\nfrom sklearn.metrics import accuracy_score\n\ndef evaluate_with_timing(model, X_train, X_val, y_train, y_val):\n    \"\"\"Huấn luyện + đánh giá model và đo thời gian.\"\"\"\n    start_train = time.time()\n    model.fit(X_train, y_train)\n    train_time = time.time() - start_train\n\n    start_infer = time.time()\n    y_pred = model.predict(X_val)\n    inference_time = time.time() - start_infer\n\n    # Tính các chỉ số\n    metrics = compute_compound_metrics(y_val, y_pred)\n    acc = accuracy_score(y_val, y_pred)\n\n    return {\n        **metrics,\n        \"val_acc\": acc,\n        \"train_time\": train_time,\n        \"inference_time\": inference_time\n    }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:40:29.676834Z","iopub.execute_input":"2025-10-06T07:40:29.677262Z","iopub.status.idle":"2025-10-06T07:40:29.683417Z","shell.execute_reply.started":"2025-10-06T07:40:29.677236Z","shell.execute_reply":"2025-10-06T07:40:29.682570Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Logistic Regression + MI ---\nlr_mi = Pipeline([\n    ('imp', SimpleImputer(strategy='median')),\n    ('scaler', StandardScaler()),\n    ('clf', LogisticRegression(max_iter=2000, random_state=RANDOM_STATE, solver='liblinear'))\n])\nres_lr_mi = evaluate_with_timing(lr_mi, Xmi.loc[X_train.index], X_val=Xmi.loc[X_val.index], y_train=y_train, y_val=y_val)\n\n# --- Logistic Regression + PCA ---\nlr_pca = Pipeline([\n    ('scaler', StandardScaler()),\n    ('pca', PCA(n_components=0.95, random_state=RANDOM_STATE)),\n    ('clf', LogisticRegression(max_iter=2000, random_state=RANDOM_STATE, solver='liblinear'))\n])\nres_lr_pca = evaluate_with_timing(lr_pca, X_train, X_val, y_train, y_val)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:40:36.916113Z","iopub.execute_input":"2025-10-06T07:40:36.916784Z","iopub.status.idle":"2025-10-06T07:40:36.960765Z","shell.execute_reply.started":"2025-10-06T07:40:36.916760Z","shell.execute_reply":"2025-10-06T07:40:36.959952Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Ghi lại kết quả dạng dict để sau này append vào bảng\nscore_lr_mi = {\n    \"binary_f1\": res_lr_mi[\"binary_f1\"],\n    \"macro_f1\": res_lr_mi[\"macro_f1\"],\n    \"combined\": res_lr_mi[\"combined\"],\n    \"val_acc\": res_lr_mi[\"val_acc\"],\n    \"train_time\": res_lr_mi[\"train_time\"],\n    \"inference_time\": res_lr_mi[\"inference_time\"],\n}\n\nscore_lr_pca = {\n    \"binary_f1\": res_lr_pca[\"binary_f1\"],\n    \"macro_f1\": res_lr_pca[\"macro_f1\"],\n    \"combined\": res_lr_pca[\"combined\"],\n    \"val_acc\": res_lr_pca[\"val_acc\"],\n    \"train_time\": res_lr_pca[\"train_time\"],\n    \"inference_time\": res_lr_pca[\"inference_time\"],\n}\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:40:41.933648Z","iopub.execute_input":"2025-10-06T07:40:41.933987Z","iopub.status.idle":"2025-10-06T07:40:41.940035Z","shell.execute_reply.started":"2025-10-06T07:40:41.933963Z","shell.execute_reply":"2025-10-06T07:40:41.938893Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"summary_df = pd.DataFrame([\n    [\n        \"Logistic Regression (MI)\", runtime_id, notebook_name, \"MI\", window_size, \"liblinear\", 0.01,\n        score_lr_mi[\"binary_f1\"], score_lr_mi[\"macro_f1\"], score_lr_mi[\"combined\"],\n        score_lr_mi[\"val_acc\"], score_lr_mi[\"train_time\"], score_lr_mi[\"inference_time\"], notes\n    ],\n    [\n        \"Logistic Regression (PCA)\", runtime_id, notebook_name, \"PCA\", window_size, \"liblinear\", 0.01,\n        score_lr_pca[\"binary_f1\"], score_lr_pca[\"macro_f1\"], score_lr_pca[\"combined\"],\n        score_lr_pca[\"val_acc\"], score_lr_pca[\"train_time\"], score_lr_pca[\"inference_time\"], notes\n    ],\n], columns=[\n    \"model_name\",\"runtime_id\",\"notebook\",\"features_used\",\"window_size\",\"optimizer/solver\",\"params (M)\",\n    \"Binary\",\"Macro\",\"Final Score\",\"val_acc\",\"train_time\",\"inference_time\",\"notes\"\n])\n\ndisplay(summary_df.style.background_gradient(cmap='Blues', subset=['Binary','Macro','Final Score']))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-06T07:40:47.679188Z","iopub.execute_input":"2025-10-06T07:40:47.679994Z","iopub.status.idle":"2025-10-06T07:40:47.698802Z","shell.execute_reply.started":"2025-10-06T07:40:47.679965Z","shell.execute_reply":"2025-10-06T07:40:47.698075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}