{"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":"# 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-08-07T02:15:55.022719Z","iopub.execute_input":"2025-08-07T02:15:55.023058Z","iopub.status.idle":"2025-08-07T02:15:55.028737Z","shell.execute_reply.started":"2025-08-07T02:15:55.023019Z","shell.execute_reply":"2025-08-07T02:15:55.027670Z"}},"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-08-07T02:15:55.124849Z","iopub.execute_input":"2025-08-07T02:15:55.125186Z","iopub.status.idle":"2025-08-07T02:16:18.721794Z","shell.execute_reply.started":"2025-08-07T02:15:55.125161Z","shell.execute_reply":"2025-08-07T02:16:18.720857Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2.2 Khám phá dữ liệu","metadata":{}},{"cell_type":"markdown","source":"### 2.2.1 Mẫu dữ liệu \n","metadata":{}},{"cell_type":"code","source":"train_df.sample(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:18.723555Z","iopub.execute_input":"2025-08-07T02:16:18.723879Z","iopub.status.idle":"2025-08-07T02:16:18.769977Z","shell.execute_reply.started":"2025-08-07T02:16:18.723849Z","shell.execute_reply":"2025-08-07T02:16:18.768966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dem_df.sample(5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:18.770838Z","iopub.execute_input":"2025-08-07T02:16:18.771085Z","iopub.status.idle":"2025-08-07T02:16:18.783332Z","shell.execute_reply.started":"2025-08-07T02:16:18.771065Z","shell.execute_reply":"2025-08-07T02:16:18.782186Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:18.784733Z","iopub.execute_input":"2025-08-07T02:16:18.785606Z","iopub.status.idle":"2025-08-07T02:16:18.818692Z","shell.execute_reply.started":"2025-08-07T02:16:18.785569Z","shell.execute_reply":"2025-08-07T02:16:18.817811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dem_df.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:18.820662Z","iopub.execute_input":"2025-08-07T02:16:18.820945Z","iopub.status.idle":"2025-08-07T02:16:18.842789Z","shell.execute_reply.started":"2025-08-07T02:16:18.820922Z","shell.execute_reply":"2025-08-07T02:16:18.841810Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.describe().T","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:18.843821Z","iopub.execute_input":"2025-08-07T02:16:18.844172Z","iopub.status.idle":"2025-08-07T02:16:27.434448Z","shell.execute_reply.started":"2025-08-07T02:16:18.844142Z","shell.execute_reply":"2025-08-07T02:16:27.433211Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.2.2 Kích thước ","metadata":{}},{"cell_type":"code","source":"print(\"No. of rows : \", train_df.shape[0])\nprint(\"No. of columns : \", train_df.shape[1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:27.435470Z","iopub.execute_input":"2025-08-07T02:16:27.435743Z","iopub.status.idle":"2025-08-07T02:16:27.440959Z","shell.execute_reply.started":"2025-08-07T02:16:27.435720Z","shell.execute_reply":"2025-08-07T02:16:27.439946Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"No. of rows : \", test_df.shape[0])\nprint(\"No. of columns : \", test_df.shape[1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:27.441969Z","iopub.execute_input":"2025-08-07T02:16:27.442344Z","iopub.status.idle":"2025-08-07T02:16:27.458989Z","shell.execute_reply.started":"2025-08-07T02:16:27.442308Z","shell.execute_reply":"2025-08-07T02:16:27.457915Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"No. of rows : \", train_dem_df.shape[0])\nprint(\"No. of columns : \", train_dem_df.shape[1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:27.459880Z","iopub.execute_input":"2025-08-07T02:16:27.460224Z","iopub.status.idle":"2025-08-07T02:16:27.475586Z","shell.execute_reply.started":"2025-08-07T02:16:27.460195Z","shell.execute_reply":"2025-08-07T02:16:27.474679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"No. of rows : \", test_dem_df.shape[0])\nprint(\"No. of columns : \", test_dem_df.shape[1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:27.476591Z","iopub.execute_input":"2025-08-07T02:16:27.476869Z","iopub.status.idle":"2025-08-07T02:16:27.495019Z","shell.execute_reply.started":"2025-08-07T02:16:27.476835Z","shell.execute_reply":"2025-08-07T02:16:27.494143Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.2.3 Tóm tắt thống kê các tính năng ","metadata":{}},{"cell_type":"code","source":"train_df.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:27.495919Z","iopub.execute_input":"2025-08-07T02:16:27.496245Z","iopub.status.idle":"2025-08-07T02:16:35.720447Z","shell.execute_reply.started":"2025-08-07T02:16:27.496210Z","shell.execute_reply":"2025-08-07T02:16:35.719494Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dem_df.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:35.721559Z","iopub.execute_input":"2025-08-07T02:16:35.721902Z","iopub.status.idle":"2025-08-07T02:16:35.745481Z","shell.execute_reply.started":"2025-08-07T02:16:35.721869Z","shell.execute_reply":"2025-08-07T02:16:35.744679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:35.746226Z","iopub.execute_input":"2025-08-07T02:16:35.746524Z","iopub.status.idle":"2025-08-07T02:16:36.176860Z","shell.execute_reply.started":"2025-08-07T02:16:35.746502Z","shell.execute_reply":"2025-08-07T02:16:36.175942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dem_df.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:36.181191Z","iopub.execute_input":"2025-08-07T02:16:36.181819Z","iopub.status.idle":"2025-08-07T02:16:36.208117Z","shell.execute_reply.started":"2025-08-07T02:16:36.181787Z","shell.execute_reply":"2025-08-07T02:16:36.206922Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.2.4 Giá trị bị thiếu ","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()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:36.209263Z","iopub.execute_input":"2025-08-07T02:16:36.209867Z","iopub.status.idle":"2025-08-07T02:16:37.102854Z","shell.execute_reply.started":"2025-08-07T02:16:36.209837Z","shell.execute_reply":"2025-08-07T02:16:37.101905Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.2.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-08-07T02:16:37.103871Z","iopub.execute_input":"2025-08-07T02:16:37.104201Z","iopub.status.idle":"2025-08-07T02:16:42.611111Z","shell.execute_reply.started":"2025-08-07T02:16:37.104169Z","shell.execute_reply":"2025-08-07T02:16:42.610213Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.2.6 Giá trị duy nhất","metadata":{}},{"cell_type":"code","source":"train_df.nunique().to_frame(\"# of unique values\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:42.612093Z","iopub.execute_input":"2025-08-07T02:16:42.612466Z","iopub.status.idle":"2025-08-07T02:16:45.647439Z","shell.execute_reply.started":"2025-08-07T02:16:42.612393Z","shell.execute_reply":"2025-08-07T02:16:45.646560Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dem_df.nunique().to_frame(\"# of unique values\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:45.648311Z","iopub.execute_input":"2025-08-07T02:16:45.648663Z","iopub.status.idle":"2025-08-07T02:16:45.658761Z","shell.execute_reply.started":"2025-08-07T02:16:45.648640Z","shell.execute_reply":"2025-08-07T02:16:45.657777Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2.3. Tiền xử lý dữ liệu","metadata":{}},{"cell_type":"markdown","source":"### 2.3.1. Gesture (Target Column)","metadata":{}},{"cell_type":"code","source":"gesture_counts = train_df['gesture'].value_counts()\n\n# Tính toán tỷ lệ phần trăm\npercentages = (gesture_counts / gesture_counts.sum()) * 100\n\n# Tạo nhãn bao gồm cả số lượng và phần trăm\nlabels = [f'{label} ({count}, {perc:.1f}%)' for label, count, perc in zip(gesture_counts.index, gesture_counts.values, percentages)]\n\n# Tạo biểu đồ tròn\nplt.figure(figsize=(8, 8)) # Set figure size for better readability\nplt.pie(gesture_counts, labels=labels, startangle=160, counterclock=False, wedgeprops={'edgecolor': 'black'})\nplt.title('Distribution of Gestures')\nplt.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:45.659706Z","iopub.execute_input":"2025-08-07T02:16:45.660016Z","iopub.status.idle":"2025-08-07T02:16:46.146573Z","shell.execute_reply.started":"2025-08-07T02:16:45.659995Z","shell.execute_reply":"2025-08-07T02:16:46.145636Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Biểu đồ tròn\nfig, ax = plt.subplots(figsize=(8, 8))\n\n# Tính phần trăm cho các nhãn (tùy chọn, cũng có thể sử dụng autopct)\norientation_counts = train_df[\"orientation\"].value_counts().head()\ntotal_count = orientation_counts.sum()\nlabels = [f'{label} ({count})' for label, count in orientation_counts.items()]\n\nwedges, texts, autotexts = ax.pie(\n    orientation_counts,\n    autopct='%1.1f%%',\n    startangle=90,\n    colors=plt.cm.Paired.colors,\n    pctdistance=0.85, # Distance of the percentage labels from the center\n    wedgeprops=dict(width=0.3, edgecolor='w'), # Create the donut hole\n    labels=labels\n)\n\n# Vẽ một vòng tròn ở trung tâm để biến nó thành biểu đồ donut\ncentre_circle = plt.Circle((0,0), 0.70, fc='white')\nfig.gca().add_artist(centre_circle)\n\nax.set_title(\"Subject's orientation (Donut Chart)\")\nax.axis('equal')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:46.147617Z","iopub.execute_input":"2025-08-07T02:16:46.147904Z","iopub.status.idle":"2025-08-07T02:16:46.375436Z","shell.execute_reply.started":"2025-08-07T02:16:46.147881Z","shell.execute_reply":"2025-08-07T02:16:46.374493Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Biểu đồ cột\nfig, ax = plt.subplots(figsize=(8, 6))\norientation_counts.sort_values(ascending=False).plot( # Often good to sort descending for vertical bars\n    kind=\"bar\", color='purple', ax=ax, title=\"Target (Vertical Bar)\"\n)\nax.set_xlabel(\"Target\")\nax.set_ylabel(\"Count\")\nplt.xticks(rotation=20, ha='right') # Rotate x-axis labels for better readability if they overlap\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:46.376623Z","iopub.execute_input":"2025-08-07T02:16:46.376857Z","iopub.status.idle":"2025-08-07T02:16:46.667586Z","shell.execute_reply.started":"2025-08-07T02:16:46.376839Z","shell.execute_reply":"2025-08-07T02:16:46.666758Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.3.2. Sequence Type (Target vs Non Target)","metadata":{}},{"cell_type":"code","source":"# Biểu đồ tròn\nfig, ax = plt.subplots(figsize=(8, 8))\n\n# Tính phần trăm cho các nhãn (không bắt buộc, cũng có thể dùng autopct)\nsequence_type_counts = train_df[\"sequence_type\"].value_counts().head()\ntotal_count = sequence_type_counts.sum()\nlabels = [f'{label} ({count})' for label, count in sequence_type_counts.items()]\n\nwedges, texts, autotexts = ax.pie(\n    sequence_type_counts,\n    autopct='%1.1f%%',\n    startangle=90,\n    colors=plt.cm.Paired.colors,\n    pctdistance=0.85, # Distance of the percentage labels from the center\n    wedgeprops=dict(width=0.3, edgecolor='w'), # Create the donut hole\n    labels=labels\n)\n\n# Vẽ hình tròn ở giữa để tạo biểu đồ vành khuyên\ncentre_circle = plt.Circle((0,0), 0.70, fc='white')\nfig.gca().add_artist(centre_circle)\n\nax.set_title(\"Target vs Non-target (Donut Chart)\")\nax.axis('equal')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:46.668522Z","iopub.execute_input":"2025-08-07T02:16:46.668833Z","iopub.status.idle":"2025-08-07T02:16:46.857558Z","shell.execute_reply.started":"2025-08-07T02:16:46.668803Z","shell.execute_reply":"2025-08-07T02:16:46.856522Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Biểu đồ cột dọc\nfig, ax = plt.subplots(figsize=(8, 6))\nsequence_type_counts.sort_values(ascending=False).plot( # Often good to sort descending for vertical bars\n    kind=\"bar\", color='lightgreen', ax=ax, title=\"Target vs Non Target (Vertical Bar)\"\n)\nax.set_xlabel(\"Target\")\nax.set_ylabel(\"Count\")\nplt.xticks(rotation=20, ha='right') # Rotate x-axis labels for better readability if they overlap\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:46.858577Z","iopub.execute_input":"2025-08-07T02:16:46.859197Z","iopub.status.idle":"2025-08-07T02:16:47.061713Z","shell.execute_reply.started":"2025-08-07T02:16:46.859165Z","shell.execute_reply":"2025-08-07T02:16:47.060566Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.3.3.Behavior","metadata":{}},{"cell_type":"code","source":"# Biểu đồ tròn\nfig, ax = plt.subplots(figsize=(8, 8))\n\n# Vẽ hình tròn ở giữa để tạo biểu đồ vành khuyên\nbehavior_counts = train_df[\"behavior\"].value_counts().head()\ntotal_count = behavior_counts.sum()\nlabels = [f'{label} ({count})' for label, count in behavior_counts.items()]\n\nwedges, texts, autotexts = ax.pie(\n    behavior_counts,\n    autopct='%1.1f%%',\n    startangle=90,\n    colors=plt.cm.Paired.colors,\n    pctdistance=0.85, # Distance of the percentage labels from the center\n    wedgeprops=dict(width=0.3, edgecolor='w'), # Create the donut hole\n    labels=labels\n)\n\ncentre_circle = plt.Circle((0,0), 0.70, fc='white')\nfig.gca().add_artist(centre_circle)\n\nax.set_title(\"Subject's Behavior During the Sequence (Donut Chart)\")\nax.axis('equal')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:47.062765Z","iopub.execute_input":"2025-08-07T02:16:47.063052Z","iopub.status.idle":"2025-08-07T02:16:47.281289Z","shell.execute_reply.started":"2025-08-07T02:16:47.063000Z","shell.execute_reply":"2025-08-07T02:16:47.280299Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Biểu đồ cột dọc\nfig, ax = plt.subplots(figsize=(8, 6))\nbehavior_counts.sort_values(ascending=False).plot( # Often good to sort descending for vertical bars\n    kind=\"bar\", color='skyblue', ax=ax, title=\"Subject's Behavior During the Sequence (Vertical Bar)\"\n)\nax.set_xlabel(\"Behavior\")\nax.set_ylabel(\"Count\")\nplt.xticks(rotation=20, ha='right') # Rotate x-axis labels for better readability if they overlap\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:47.282336Z","iopub.execute_input":"2025-08-07T02:16:47.282631Z","iopub.status.idle":"2025-08-07T02:16:47.489478Z","shell.execute_reply.started":"2025-08-07T02:16:47.282606Z","shell.execute_reply":"2025-08-07T02:16:47.488489Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.3.4. Ma trận tương quan của dữ liệu nhân khẩu học","metadata":{}},{"cell_type":"code","source":"# Ma trận tương quan\nplt.figure(figsize = (8, 4))\nsns.heatmap(data = train_dem_df.corr(numeric_only = True), linewidth = 0.5, annot = True, linecolor = \"black\")\nplt.title('Train Data Demographic Correlation Heatmap')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:47.490523Z","iopub.execute_input":"2025-08-07T02:16:47.490785Z","iopub.status.idle":"2025-08-07T02:16:47.860405Z","shell.execute_reply.started":"2025-08-07T02:16:47.490764Z","shell.execute_reply":"2025-08-07T02:16:47.859486Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.3.5. Tỷ lệ giới tính","metadata":{}},{"cell_type":"code","source":"# Lấy số lượng từng giá trị trong cột giới tính\nvc = train_dem_df['sex'].value_counts()\n\n# Trích xuất số lượng (giả sử cột 'count' chưa tồn tại)\ncounts = vc.values\n\n# Tạo nhãn (giả sử cột 'count' chưa tồn tại)\nlabels = ['Male', 'Female']  # Assuming 'Male' maps to '1' and 'Female' to '0'\n\n# Vẽ biểu đồ hình tròn\nplt.pie(counts, labels=labels, autopct=\"%1.1f%%\")  # Add percentages to pie slices\nplt.title('Gender')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:47.861590Z","iopub.execute_input":"2025-08-07T02:16:47.861875Z","iopub.status.idle":"2025-08-07T02:16:47.948410Z","shell.execute_reply.started":"2025-08-07T02:16:47.861853Z","shell.execute_reply":"2025-08-07T02:16:47.947479Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.3.6. Phân bố độ tuổi theo giới tính","metadata":{}},{"cell_type":"code","source":"#train' là một DataFrame (khung dữ liệu)\n_, axs = plt.subplots(2, 1, sharex=True)\n\nfor sex in range(2):\n  ax = axs.ravel()[sex]\n\n  # Lọc theo giới tính bằng cách sử dụng chỉ mục boolean\n  sex_filter = train_dem_df['sex'] == sex\n  vc = train_dem_df[sex_filter]['age'].value_counts()  # Access column after filtering\n\n  # Vẽ biểu đồ cột\n  ax.bar(vc.index, vc.values, color=['lightblue', 'coral'][sex], label=['Male', 'Female'][sex])\n  ax.xaxis.set_major_locator(MaxNLocator(integer=True))\n  ax.set_ylabel('count')\n  ax.legend()\n\nplt.suptitle('Age distribution with respect to Gender')\naxs.ravel()[1].set_xlabel('years')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:47.949459Z","iopub.execute_input":"2025-08-07T02:16:47.949785Z","iopub.status.idle":"2025-08-07T02:16:48.438314Z","shell.execute_reply.started":"2025-08-07T02:16:47.949756Z","shell.execute_reply":"2025-08-07T02:16:48.437389Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.3.7. Gia tốc tuyến tính từ cảm biến IMU","metadata":{}},{"cell_type":"code","source":"# Khởi tạo biểu đồ 3D\nfig = plt.figure(figsize=(10, 10)) # Adjust figure size for better visibility\nax = fig.add_subplot(111, projection='3d')\n\n# Lấy các giá trị duy nhất từ cột 'gesture' để tô màu\nunique_gestures = train_df['gesture'].unique()\n\n# Sử dụng bản đồ màu để có được màu sắc riêng biệt cho từng loại cử chỉ\n# matplotlib.colormaps là cách được khuyến nghị để truy cập bản đồ màu hiện nay\ncolors = plt.colormaps['tab10'].resampled(len(unique_gestures)) # 'tab10' is good for categorical data\n\n# Lặp lại các cử chỉ độc đáo và vẽ chúng bằng các màu khác nhau\nfor i, gesture_type in enumerate(unique_gestures):\n    subset = train_df[train_df['gesture'] == gesture_type]\n    ax.scatter(subset['acc_x'], subset['acc_y'], subset['acc_z'],\n               color=colors(i),    # Chỉ định một màu duy nhất từ bản đồ màu\n               label=gesture_type, # Nhãn cho huyền thoại\n               alpha=0.7,          # Độ mờ đục (phù hợp với độ mờ đục Plotly của bạn)\n               s=2,\n               rasterized=True)                # Kích thước của các điểm (tương tự như size_max=1 của Plotly, được điều chỉnh để dễ nhìn)\n\n# Đặt nhãn và tiêu đề\nax.set_xlabel('acc_x', fontsize=14)\nax.set_ylabel('acc_y', fontsize=14)\nax.set_zlabel('acc_z', fontsize=14)\nax.set_title('Linear acceleration along X, Y, Z axes', fontsize=16)\n\n# Thêm chú thích để phân biệt các loại 'cử chỉ'\nax.legend(title='Gesture Type', fontsize=10, title_fontsize=12, loc='upper right')\n\n# Đặt kích thước phông chữ nhãn đánh dấu\nax.tick_params(axis='both', which='major', labelsize=12)\n\n# --- Mô phỏng mẫu 'plotly_dark' của Plotly ---\n# Đặt màu nền của ô\nax.set_facecolor('black')\n\n# Làm cho nền của khung trong suốt\nax.xaxis.pane.fill = False\nax.yaxis.pane.fill = False\nax.zaxis.pane.fill = False\n\n# Đặt màu cạnh khung\nax.xaxis.pane.edgecolor = 'grey'\nax.yaxis.pane.edgecolor = 'grey'\nax.zaxis.pane.edgecolor = 'grey'\n\n# Đặt màu mặt khung thực tế thành trong suốt\nax.xaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\nax.yaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\nax.zaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\n\n# Đặt màu đường lưới\nax.grid(color='gray', linestyle='--', linewidth=0.5, alpha=0.5)\n\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:48.439748Z","iopub.execute_input":"2025-08-07T02:16:48.440116Z","iopub.status.idle":"2025-08-07T02:16:58.523017Z","shell.execute_reply.started":"2025-08-07T02:16:48.440084Z","shell.execute_reply":"2025-08-07T02:16:58.522119Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biểu đồ phân tán 3D hiển thị gia tốc tuyến tính theo trục x, y và z. Màu sắc đóng vai trò là chiều thứ tư và phân biệt các điểm khác nhau dựa trên cột mục tiêu, tức là cử chỉ.","metadata":{}},{"cell_type":"code","source":"# Khởi tạo biểu đồ 3D\nfig = plt.figure(figsize=(10, 10)) # Điều chỉnh kích thước hình để dễ nhìn hơn\nax = fig.add_subplot(111, projection='3d')\n\n# Lấy các giá trị duy nhất từ 'sequence_type' để tô màu\nunique_sequence_types = train_df['sequence_type'].unique()\n\n# --- XỬ LÝ CẢNH BÁO VỀ VIỆC KHÔNG SỬ DỤNG NỮA 1: get_cmap ---\n# Sử dụng matplotlib.colormaps[name] thay vì plt.cm.get_cmap(name)\ncolors = plt.colormaps['viridis'].resampled(len(unique_sequence_types))\n\n# Lặp lại qua các loại trình tự duy nhất và biểu diễn chúng bằng các màu khác nhau\nfor i, seq_type in enumerate(unique_sequence_types):\n    subset = train_df[train_df['sequence_type'] == seq_type]\n    ax.scatter(subset['acc_x'], subset['acc_y'], subset['acc_z'],\n               color=colors(i), # Gán một màu duy nhất\n               label=seq_type,  # Nhãn cho chú giải\n               alpha=0.7,       # Độ mờ đục\n               s=10)            # Kích thước của các điểm (tương tự như size_max của Plotly)\n\n# Đặt nhãn và tiêu đề\nax.set_xlabel('acc_x', fontsize=14)\nax.set_ylabel('acc_y', fontsize=14)\nax.set_zlabel('acc_z', fontsize=14)\nax.set_title('Linear acceleration along X, Y, Z axes', fontsize=16)\n\n# Thêm chú thích để phân biệt 'sequence_type'\nax.legend(title='Sequence Type', fontsize=12, title_fontsize=14)\n\n# Đặt kích thước phông chữ nhãn đánh dấu\nax.tick_params(axis='both', which='major', labelsize=12)\n\n# Đặt màu nền tối và điều chỉnh màu lưới/khung\nax.set_facecolor('black')\n\n# --- XỬ LÝ CẢNH BÁO SỬA ĐỔI 2, 3, 4: w_xaxis, w_yaxis, w_zaxis ---\n# Sử dụng .xaxis, .yaxis, .zaxis trực tiếp cho các ngăn\nax.xaxis.pane.fill = False\nax.yaxis.pane.fill = False\nax.zaxis.pane.fill = False\n\nax.xaxis.pane.edgecolor = 'grey'\nax.yaxis.pane.edgecolor = 'grey'\nax.zaxis.pane.edgecolor = 'grey'\n\n# Đặt màu khung thành trong suốt để có hiệu ứng tối thực sự\n# Giờ đây, bạn có thể truy cập trực tiếp thông qua .xaxis.pane.set_facecolor\nax.xaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\nax.yaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\nax.zaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\n\nax.grid(color='gray', linestyle='--', linewidth=0.5, alpha=0.5)\n\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:16:58.524166Z","iopub.execute_input":"2025-08-07T02:16:58.524449Z","iopub.status.idle":"2025-08-07T02:17:11.695540Z","shell.execute_reply.started":"2025-08-07T02:16:58.524428Z","shell.execute_reply":"2025-08-07T02:17:11.694470Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Biểu đồ phân tán 3D thể hiện gia tốc tuyến tính theo trục x, y và z. Màu sắc đóng vai trò là chiều thứ tư và phân biệt các điểm dựa trên cột loại chuỗi i.s mục tiêu và không phải mục tiêu.","metadata":{}},{"cell_type":"markdown","source":"### 2.3.8. Sự quay của con quay hồi chuyển từ cảm biến IMU","metadata":{}},{"cell_type":"code","source":"fig = plt.figure(figsize=(14, 8)) # Adjust figure size as needed\nax = fig.add_subplot(111, projection='3d')\n\n# Vẽ đường 3D\n# Về màu sắc, plot3D của matplotlib không hỗ trợ trực tiếp đối số 'color' liên tục\n# như px.line_3d. Thông thường, bạn sẽ sử dụng một bản đồ màu và ánh xạ 'rot_w' vào đó.\n# Ví dụ này sử dụng một màu đường đơn giản. Đối với màu liên tục, bạn cần\n# phân đoạn dữ liệu hoặc sử dụng scatter3D với c=train['rot_w'].\n# ax.plot(train['rot_x'], train['rot_y'], train['rot_z'], color='skyblue', linewidth=0.5)\n\n# Nếu bạn muốn tô màu theo 'rot_w' với gradient, thường tốt hơn nên sử dụng scatter3D\n# nếu không nhất thiết phải dùng đường liên tục, hoặc phân đoạn đường.\n# Đối với biểu đồ đường thẳng thực sự với màu liên tục, việc này phức tạp hơn trong matplotlib.\n# Ví dụ về scatter3D với màu:\nsc = ax.scatter(train_df['rot_x'], train_df['rot_y'], train_df['rot_z'], c=train_df['rot_w'], cmap='viridis', s=1)\nfig.colorbar(sc, ax=ax, label='rot_w')\n\n\nax.set_xlabel('rot_x')\nax.set_ylabel('rot_y')\nax.set_zlabel('rot_z')\nax.set_title('3D Path of Gyroscope Rotation Over Time')\n\n# Điều chỉnh kích thước phông chữ nếu cần (tương tự như update_layout trong Plotly)\nax.tick_params(axis='both', which='major', labelsize=12)\nax.xaxis.label.set_size(14)\nax.yaxis.label.set_size(14)\nax.zaxis.label.set_size(14)\nax.title.set_size(16)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:11.696647Z","iopub.execute_input":"2025-08-07T02:17:11.697248Z","iopub.status.idle":"2025-08-07T02:17:20.006652Z","shell.execute_reply.started":"2025-08-07T02:17:11.697218Z","shell.execute_reply":"2025-08-07T02:17:20.005716Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.3.9. Nhiệt độ từ nhiệt điện của cảm biến IMU","metadata":{}},{"cell_type":"code","source":"# Làm tan chảy DataFrame để vẽ đồ thị dễ dàng hơn\ndf_melted = train_df.melt(id_vars='sequence_type', \n                    value_vars=['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5'],\n                    var_name='sensor', \n                    value_name='temperature')\n\n# Plot\nplt.figure(figsize=(10, 6))\nsns.boxplot(x='sequence_type', y='temperature', hue='sensor', data=df_melted)\nplt.title('Thermopile Sensor Readings by Sequence Type')\nplt.xlabel('Sequence Type')\nplt.ylabel('Temperature (°C)')\nplt.legend(title='Sensor')\nplt.xticks(rotation=45)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:20.007623Z","iopub.execute_input":"2025-08-07T02:17:20.007976Z","iopub.status.idle":"2025-08-07T02:17:23.595525Z","shell.execute_reply.started":"2025-08-07T02:17:20.007941Z","shell.execute_reply":"2025-08-07T02:17:23.594566Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Nhóm theo sequence_type và lấy giá trị trung bình\nimport numpy as np\ndf_grouped = train_df.groupby('sequence_type')[['thm_1','thm_2','thm_3','thm_4','thm_5']].mean()\n\ncategories = ['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5']\nN = len(categories)\n\nangles = [n / float(N) * 2 * np.pi for n in range(N)]\nangles += angles[:1]  # Close the loop\n\nfig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))\n\nfor idx, row in df_grouped.iterrows():\n    values = row.values.tolist()\n    values += values[:1]  # Close the loop\n    ax.plot(angles, values, marker='o', label=idx)\n    ax.fill(angles, values, alpha=0.1)\n\nax.set_xticks(angles[:-1])\nax.set_xticklabels(categories)\nplt.legend(title=\"Sequence Type\")\nplt.title(\"Radar Chart of Average Thermopile Readings by Sequence Type\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:23.596439Z","iopub.execute_input":"2025-08-07T02:17:23.596687Z","iopub.status.idle":"2025-08-07T02:17:24.012241Z","shell.execute_reply.started":"2025-08-07T02:17:23.596666Z","shell.execute_reply":"2025-08-07T02:17:24.011131Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Bằng cách phân tích biểu đồ hộp và biểu đồ radar ở trên, chúng ta có thể thấy nhiệt độ tại điểm mục tiêu cao hơn một chút so với điểm không phải mục tiêu. Tiếp theo, hãy xem biểu đồ hộp thể hiện giá trị nhiệt điện so với cử động cột và hành vi cột.","metadata":{}},{"cell_type":"code","source":"# Làm tan chảy DataFrame để vẽ đồ thị dễ dàng hơn\ndf_melted = train_df.melt(id_vars='gesture', \n                    value_vars=['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5'],\n                    var_name='sensor', \n                    value_name='temperature')\n\n# Plot\nplt.figure(figsize=(10, 6))\nsns.boxplot(x='gesture', y='temperature', hue='sensor', data=df_melted)\nplt.title('Thermopile Sensor Readings by Gesture')\nplt.xlabel('Gesture')\nplt.ylabel('Temperature (°C)')\nplt.legend(title='Sensor')\nplt.xticks(rotation=90)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:24.013244Z","iopub.execute_input":"2025-08-07T02:17:24.013507Z","iopub.status.idle":"2025-08-07T02:17:30.196057Z","shell.execute_reply.started":"2025-08-07T02:17:24.013482Z","shell.execute_reply":"2025-08-07T02:17:30.195345Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Làm tan chảy DataFrame để vẽ đồ thị dễ dàng hơn\ndf_melted = train_df.melt(id_vars='behavior', \n                    value_vars=['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5'],\n                    var_name='sensor', \n                    value_name='temperature')\n\n# Plot\nplt.figure(figsize=(10, 6))\nsns.boxplot(x='behavior', y='temperature', hue='sensor', data=df_melted)\nplt.title('Thermopile Sensor Readings by Behavior')\nplt.xlabel('Behavior')\nplt.ylabel('Temperature (°C)')\nplt.legend(title='Sensor')\nplt.xticks(rotation=25)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:30.197087Z","iopub.execute_input":"2025-08-07T02:17:30.197419Z","iopub.status.idle":"2025-08-07T02:17:34.221852Z","shell.execute_reply.started":"2025-08-07T02:17:30.197389Z","shell.execute_reply":"2025-08-07T02:17:34.220868Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.3.10. Tỷ lệ phần trăm Null","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-08-07T02:17:34.222878Z","iopub.execute_input":"2025-08-07T02:17:34.223162Z","iopub.status.idle":"2025-08-07T02:17:35.100722Z","shell.execute_reply.started":"2025-08-07T02:17:34.223139Z","shell.execute_reply":"2025-08-07T02:17:35.099732Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2.4. Quan sát hoặc xu hướng ban đầu","metadata":{}},{"cell_type":"markdown","source":"### 2.4.1. Tổng hợp dữ liệu vào dictionary","metadata":{}},{"cell_type":"code","source":"#Tổng hợp dữ liệu vào dictionary: Tạo dictionary datasets chứa 4 bộ dữ liệu chính, giúp dễ quản lý và xử lý lặp đi lặp lại.\ndatasets = {\n    \"Train Data\": train_df,\n    \"Train Demographics\": train_dem_df,\n    \"Test Data\": test_df,\n    \"Test Demographics\": test_dem_df,\n}\n\n# Print shapes\n#Giải thích: .items(): Duyệt qua từng cặp tên và DataFrame.\n#            .shape: Trả về tuple (số dòng, số cột).\n#            print(...): In ra tên dataset, số dòng và số cột, giúp bạn hình dung dữ liệu có bao nhiêu bản ghi và thuộc tính.\n\n\nfor name, df in datasets.items():\n    num_rows, num_cols = df.shape\n    print(f\"{name}:\")\n    print(f\"  Number of Rows: {num_rows}\")\n    print(f\"  Number of Columns: {num_cols}\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:35.101792Z","iopub.execute_input":"2025-08-07T02:17:35.102125Z","iopub.status.idle":"2025-08-07T02:17:35.109008Z","shell.execute_reply.started":"2025-08-07T02:17:35.102094Z","shell.execute_reply":"2025-08-07T02:17:35.107918Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.4.2. Tổng hợp dữ liệu vào dictionary","metadata":{}},{"cell_type":"code","source":"# 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-08-07T02:17:35.109885Z","iopub.execute_input":"2025-08-07T02:17:35.110145Z","iopub.status.idle":"2025-08-07T02:17:35.859233Z","shell.execute_reply.started":"2025-08-07T02:17:35.110125Z","shell.execute_reply":"2025-08-07T02:17:35.858329Z"}},"outputs":[],"execution_count":null},{"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-08-07T02:17:35.860114Z","iopub.execute_input":"2025-08-07T02:17:35.860338Z","iopub.status.idle":"2025-08-07T02:17:46.694445Z","shell.execute_reply.started":"2025-08-07T02:17:35.860319Z","shell.execute_reply":"2025-08-07T02:17:46.693500Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.4.3. Phân phối hành vi","metadata":{}},{"cell_type":"code","source":"# --- Phân phối hành vi ---\nsns.countplot(data=merged_train, x='behavior', order=merged_train['behavior'].value_counts().index)\nplt.title(\"Phân phối các hành vi trong tập huấn luyện\")\nplt.xticks(rotation=45)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:46.695631Z","iopub.execute_input":"2025-08-07T02:17:46.695940Z","iopub.status.idle":"2025-08-07T02:17:47.153883Z","shell.execute_reply.started":"2025-08-07T02:17:46.695911Z","shell.execute_reply":"2025-08-07T02:17:47.152866Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### 2.4.4. Hành vi theo pha và loại trình tự","metadata":{}},{"cell_type":"code","source":"# --- Hành vi theo pha và loại trình tự---\nsns.countplot(data=merged_train, x='behavior', hue='phase')\nplt.title(\"Behavior theo Phase\")\nplt.xticks(rotation=45)\nplt.show()\n\nsns.countplot(data=merged_train, x='behavior', hue='sequence_type')\nplt.title(\"Behavior theo Sequence Type\")\nplt.xticks(rotation=45)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:47.161725Z","iopub.execute_input":"2025-08-07T02:17:47.162011Z","iopub.status.idle":"2025-08-07T02:17:48.820655Z","shell.execute_reply.started":"2025-08-07T02:17:47.161990Z","shell.execute_reply":"2025-08-07T02:17:48.819496Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.4.5. Phát hiện Outlier trong độ tuổi ","metadata":{}},{"cell_type":"code","source":"# ---Phát hiện Outlier trong độ tuổi ---\nplt.figure(figsize=(6, 3))\nsns.boxplot(x=merged_train['age'])\nplt.title(\"Boxplot - Độ tuổi\")\nplt.show()\n\n# Lọc nếu cần\nmerged_train = merged_train[merged_train['age'] <= 100]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:48.821490Z","iopub.execute_input":"2025-08-07T02:17:48.821816Z","iopub.status.idle":"2025-08-07T02:17:51.175243Z","shell.execute_reply.started":"2025-08-07T02:17:48.821788Z","shell.execute_reply":"2025-08-07T02:17:51.174267Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.4.6. Biểu đồ histogram các đặc trưng cảm biến acc_","metadata":{}},{"cell_type":"code","source":"# --- Biểu đồ histogram các đặc trưng cảm biến acc_ ---\nacc_cols = [col for col in merged_train.columns if col.startswith('acc_')]\nmerged_train[acc_cols].hist(figsize=(16, 12), bins=30)\nplt.suptitle(\"Phân phối các đặc trưng gia tốc (acc_)\", fontsize=16)\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:51.176234Z","iopub.execute_input":"2025-08-07T02:17:51.176573Z","iopub.status.idle":"2025-08-07T02:17:51.996804Z","shell.execute_reply.started":"2025-08-07T02:17:51.176539Z","shell.execute_reply":"2025-08-07T02:17:51.995584Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.4.7. Ma trận tương quan acc_","metadata":{}},{"cell_type":"code","source":"# --- Ma trận tương quan acc_ ---\nplt.figure(figsize=(12, 10))\nsns.heatmap(merged_train[acc_cols].corr(), cmap='coolwarm', center=0)\nplt.title(\"Tương quan giữa các đặc trưng gia tốc (acc_)\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:51.997778Z","iopub.execute_input":"2025-08-07T02:17:51.998131Z","iopub.status.idle":"2025-08-07T02:17:52.303256Z","shell.execute_reply.started":"2025-08-07T02:17:51.998106Z","shell.execute_reply":"2025-08-07T02:17:52.302106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Phân phối hành vi theo giới tính ---\nsns.countplot(data=merged_train, x='behavior', hue='sex', order=merged_train['behavior'].value_counts().index)\nplt.title(\"Phân phối hành vi theo giới tính\")\nplt.xticks(rotation=45)\nplt.show()\n\n# --- Phân phối tuổi theo hành vi ---\nsns.boxplot(data=merged_train, x='behavior', y='age')\nplt.title(\"Tuổi trung bình theo hành vi\")\nplt.xticks(rotation=45)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:52.304223Z","iopub.execute_input":"2025-08-07T02:17:52.304652Z","iopub.status.idle":"2025-08-07T02:17:53.545620Z","shell.execute_reply.started":"2025-08-07T02:17:52.304628Z","shell.execute_reply":"2025-08-07T02:17:53.544486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Hiển thị dữ liệu mẫu: .head(2): Xem trước 2 dòng đầu của mỗi bảng.\n#                      display(...): Dùng trong notebook để hiển thị bảng dữ liệu đẹp, rõ ràng.\n\ndisplay(train_df.head(2))\ndisplay(train_dem_df.head(2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:53.546703Z","iopub.execute_input":"2025-08-07T02:17:53.547079Z","iopub.status.idle":"2025-08-07T02:17:53.577641Z","shell.execute_reply.started":"2025-08-07T02:17:53.547044Z","shell.execute_reply":"2025-08-07T02:17:53.576541Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"display(test_df.head(2))\ndisplay(test_dem_df.head(2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:53.579058Z","iopub.execute_input":"2025-08-07T02:17:53.579487Z","iopub.status.idle":"2025-08-07T02:17:53.618635Z","shell.execute_reply.started":"2025-08-07T02:17:53.579455Z","shell.execute_reply":"2025-08-07T02:17:53.617608Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2.5. Participant & Contextual Features (Tính năng của người tham gia và bối cảnh)","metadata":{}},{"cell_type":"code","source":"#Chuẩn bị hàm thống kê mô tả có lọc cột: \n# excluded_prefixes: Bộ tiền tố để loại các cột cảm biến như: 'acc_': Gia tốc kế, 'rot_': Cảm biến xoay, 'thm_': Nhiệt độ/Độ ẩm. 'tof_': Time-of-Flight (cảm biến khoảng cách).\n\n# Define excluded prefixes\nexcluded_prefixes = ('acc_', 'rot_', 'thm_', 'tof_')\n\n#Hàm filtered_describe: Lọc các cột không thuộc cảm biến và có kiểu số.\n#                       Dùng .describe().T để thống kê mô tả (mean, std, min, max,...) và chuyển vị bảng cho dễ nhìn.\n#                       display: Hiển thị bảng thống kê.\n\ndef filtered_describe(df, name):\n    # Exclude sensor columns\n    filtered_cols = [col for col in df.columns \n                     if not col.startswith(excluded_prefixes) and pd.api.types.is_numeric_dtype(df[col])]\n    \n    # Describe and style\n    print(f'\\n➡️ Description of numerical columns in {name}')\n    return df[filtered_cols].describe().T.style.background_gradient(cmap='viridis')\n\n# Only analyze train sets!\ndisplay(filtered_describe(train_df, \"train_df\"))\ndisplay(filtered_describe(train_dem_df, \"train_dem_df\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:53.619694Z","iopub.execute_input":"2025-08-07T02:17:53.620687Z","iopub.status.idle":"2025-08-07T02:17:53.796630Z","shell.execute_reply.started":"2025-08-07T02:17:53.620648Z","shell.execute_reply":"2025-08-07T02:17:53.795431Z"}},"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-08-07T02:17:53.797681Z","iopub.execute_input":"2025-08-07T02:17:53.798215Z","iopub.status.idle":"2025-08-07T02:17:54.911724Z","shell.execute_reply.started":"2025-08-07T02:17:53.798188Z","shell.execute_reply":"2025-08-07T02:17:54.910984Z"}},"outputs":[],"execution_count":null},{"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-08-07T02:17:54.912417Z","iopub.execute_input":"2025-08-07T02:17:54.912629Z","iopub.status.idle":"2025-08-07T02:17:54.941110Z","shell.execute_reply.started":"2025-08-07T02:17:54.912612Z","shell.execute_reply":"2025-08-07T02:17:54.939815Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2.6. 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":"code","source":"import pandas as pd\n\n# Nhóm cột cảm biến (chỉ sử dụng train_df)\n# Phân nhóm các biến cảm biến:\nacc_cols = [col for col in train_df.columns if col.startswith('acc_')]\nrot_cols = [col for col in train_df.columns if col.startswith('rot_')]\nthm_cols = [col for col in train_df.columns if col.startswith('thm_')]\ntof_cols = [col for col in train_df.columns if col.startswith('tof_')]\n\n# Hàm trợ giúp cho số liệu thống kê tóm tắt (chỉ dành cho đào tạo)\n#Hàm thống kê chi tiết từng nhóm cảm biến (Tỷ lệ thiếu giá trị; Giá trị nhỏ nhất, lớn nhất; Trung bình, độ lệch chuẩn.)\ndef sensor_summary(df, cols, name, dataset_name):\n    summary = pd.DataFrame({\n        'Feature': cols,\n        f'{dataset_name} Missing %': df[cols].isnull().mean().values * 100,\n        f'{dataset_name} Min': df[cols].min().values,\n        f'{dataset_name} Max': df[cols].max().values,\n        f'{dataset_name} Mean': df[cols].mean().values,\n        f'{dataset_name} Std': df[cols].std().values\n    })\n    summary.insert(0, 'Sensor', name)\n    return summary\n\n# Tính toán tóm tắt quá trình đào tạo cho từng loại cảm biến\n#Thống kê tổng hợp cho tập train: Duyệt từng nhóm cảm biến và gom kết quả thống kê lại.\n# pd.concat: Ghép bảng lại thành bảng thống kê tổng hợp.\ndef train_sensor_summary(train_df, sensor_cols_dict):\n    all_train = []\n    for sensor_name, cols in sensor_cols_dict.items():\n        all_train.append(sensor_summary(train_df, cols, sensor_name, 'Train'))\n    train_summary = pd.concat(all_train, ignore_index=True)\n    return train_summary\n\n# Nhóm cảm biến để xử lý\nsensor_cols_dict = {\n    'acc': acc_cols,\n    'rot': rot_cols,\n    'thm': thm_cols,\n    'tof': tof_cols\n}\n\n# Chỉ tóm tắt về chuyến tàu chạy\nsensor_train_summary = train_sensor_summary(train_df, sensor_cols_dict)\n\n# Trung bình theo nhóm cảm biến: \n#                             .groupby('Sensor'): Gom nhóm theo loại cảm biến.\n#                             .mean(numeric_only=True): Trung bình các chỉ số số học.\n#                             Tô màu bảng kết quả giúp dễ phân tích.\n\n# Hiển thị mức trung bình cấp cảm biến cho tàu hỏa\nsummary_by_group = sensor_train_summary.groupby('Sensor').mean(numeric_only=True)\n\n# Hiển thị bảng theo kiểu\nsummary_by_group.style.background_gradient(cmap='viridis')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:17:54.942409Z","iopub.execute_input":"2025-08-07T02:17:54.942736Z","iopub.status.idle":"2025-08-07T02:18:02.069505Z","shell.execute_reply.started":"2025-08-07T02:17:54.942702Z","shell.execute_reply":"2025-08-07T02:18:02.068463Z"}},"outputs":[],"execution_count":null},{"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":"code","source":"#Ẩn cảnh báo không cần thiết:  Ẩn cảnh báo tương lai của thư viện, Ẩn cảnh báo về ký tự bị thiếu font khi vẽ biểu đồ.\n\nwarnings.simplefilter(action='ignore', category=FutureWarning)\nwarnings.filterwarnings(\"ignore\", message=\"Glyph.*missing from current font\")\n\ncustom_palette = ['#3498db', '#e74c3c', '#2ecc71']\nsensor_prefixes = ('acc_', 'rot_', 'thm_', 'tof_')\ncategorical_columns = ['phase', 'behavior', 'orientation', 'sequence_type',\n                       'adult_child', 'sex', 'handedness']\ntarget_columns = ['gesture']\ncategorical_tracker = categorical_columns.copy()\n\ndef get_numerical_variables(df, excluded_prefixes, excluded_columns):\n    return [col for col in df.columns \n            if pd.api.types.is_numeric_dtype(df[col])\n            and not col.startswith(excluded_prefixes)\n            and col not in excluded_columns]\n\n# ONLY TRAIN SETS!\ntrain_main = train_df.copy()\ntrain_demo = train_dem_df.copy()\n\ntrain_main['Dataset'] = 'Train'\ntrain_demo['Dataset'] = 'Train'\n\nmain_data = train_main\ndemo_data = train_demo\n\nmain_numeric_vars = get_numerical_variables(main_data, sensor_prefixes, categorical_columns)\ndemo_numeric_vars = get_numerical_variables(demo_data, (), categorical_columns)\n\ndef create_variable_plots(variable, dataset_label, data):\n    sns.set_style('whitegrid')\n    fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n\n    # Box plot\n    plt.subplot(1, 2, 1)\n    sns.boxplot(data=data, x=variable, y=\"Dataset\", palette=custom_palette)\n    plt.xlabel(variable)\n    plt.title(f\" Box Plot for {variable} — {dataset_label}\")\n\n    # Histogram\n    plt.subplot(1, 2, 2)\n    for label, color in zip(data['Dataset'].unique(), custom_palette):\n        subset = data[data['Dataset'] == label]\n        sns.histplot(data=subset, x=variable, kde=True, bins=30, label=label, color=color)\n    plt.xlabel(variable)\n    plt.ylabel(\"Frequency\")\n    plt.title(f\" Histogram for {variable} — {dataset_label}\")\n    plt.legend()\n\n    plt.tight_layout()\n    plt.show()\n\n# Chỉ vẽ các tính năng bối cảnh của bộ đào tạo\nfor var in main_numeric_vars:\n    create_variable_plots(var, \"Context Features\", main_data)\n\n# Chỉ vẽ các đặc điểm nhân khẩu học của bộ huấn luyện\nfor var in demo_numeric_vars:\n    create_variable_plots(var, \"Demographic Features\", demo_data)\n\n# --- Cleanup\ntrain_main.drop('Dataset', axis=1, inplace=True)\ntrain_demo.drop('Dataset', axis=1, inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:02.070629Z","iopub.execute_input":"2025-08-07T02:18:02.070961Z","iopub.status.idle":"2025-08-07T02:18:09.791680Z","shell.execute_reply.started":"2025-08-07T02:18:02.070937Z","shell.execute_reply":"2025-08-07T02:18:09.790698Z"}},"outputs":[],"execution_count":null},{"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":"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-08-07T02:18:09.793068Z","iopub.execute_input":"2025-08-07T02:18:09.793484Z","iopub.status.idle":"2025-08-07T02:18:15.886608Z","shell.execute_reply.started":"2025-08-07T02:18:09.793456Z","shell.execute_reply":"2025-08-07T02:18:15.885442Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\nimport matplotlib.pyplot as plt\n\n# Tái sử dụng bảng màu tùy chỉnh của bạn\ncustom_palette = ['#3498db', '#e74c3c', '#2ecc71']\n\n# Chỉ lọc các mẫu \"Đào tạo\"\ntrain_sensor_summary = combined_sensor_summary[combined_sensor_summary['Dataset'] == 'Train']\n\n# Xác định hàm vẽ đồ thị (bây giờ chỉ sử dụng train_sensor_summary)\ndef create_sensor_summary_plots(variable, dataset_label, data):\n    sns.set_style('whitegrid')\n    fig, axes = plt.subplots(1, 2, figsize=(12, 4))\n\n    # Box Plot (left)\n    plt.subplot(1, 2, 1)\n    sns.boxplot(data=data, x=variable, y=\"Dataset\", palette=[custom_palette[0]])\n    plt.xlabel(variable)\n    plt.title(f\"Box Plot for {variable} — {dataset_label}\")\n\n    # Histogram (right)\n    plt.subplot(1, 2, 2)\n    sns.histplot(data=data, x=variable, kde=True, bins=30, color=custom_palette[0], label=\"Train\")\n    plt.xlabel(variable)\n    plt.ylabel(\"Frequency\")\n    plt.title(f\"Histogram for {variable} — {dataset_label}\")\n    plt.legend()\n\n    plt.tight_layout()\n    plt.show()\n\n# Liệt kê tất cả các biến tóm tắt cảm biến (loại trừ 'Bộ dữ liệu')\nsensor_summary_vars = [col for col in train_sensor_summary.columns if col != 'Dataset']\n\n# Plot each\nfor var in sensor_summary_vars:\n    create_sensor_summary_plots(var, \"Per-Sequence Sensor Summaries\", train_sensor_summary)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:15.887783Z","iopub.execute_input":"2025-08-07T02:18:15.888127Z","iopub.status.idle":"2025-08-07T02:18:33.227293Z","shell.execute_reply.started":"2025-08-07T02:18:15.888097Z","shell.execute_reply":"2025-08-07T02:18:33.226266Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3.2 Phân tích tính năng phân loại (Phân tích đơn biến)","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\nimport pandas as pd\nimport textwrap\n\n# Xác định bảng màu\npie_chart_palette = ['#33638d', '#28ae80', '#d3eb0c', '#ff9a0b', '#7e03a8', '#35b779',\n                     '#fde725', '#440154', '#90d743', '#482173', '#22a884', '#f8961e']\ncustom_palette = ['#3498db']  # Only Train\n\n# 🔹Các biến phân loại để phân tích (không bao gồm 'cử chỉ')\ncategorical_variables = [col for col in categorical_tracker if col != 'gesture']\n\n#--- Chuẩn bị tập dữ liệu chính (chỉ đào tạo)\ntrain_main = train_df.copy()\ntrain_main['dataset'] = 'train'\nmain_combined = train_main.copy()\n\n# --- Chuẩn bị bộ dữ liệu nhân khẩu học (chỉ đào tạo)\ntrain_demo = train_dem_df.copy()\ntrain_demo['dataset'] = 'train'\ndemo_combined = train_demo.copy()\n\n#--- Chức năng vẽ đồ thị thống nhất\ndef create_categorical_plots(variable, data, source_name):\n    sns.set_style('whitegrid')\n    fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n\n    # Biểu đồ tròn (bên trái)\n    plt.subplot(1, 2, 1)\n    value_counts = data[variable].value_counts()\n\n    # Thu gọn các danh mục nhỏ\n    threshold = 0.05 * value_counts.sum()\n    filtered_values = value_counts.copy()\n    filtered_values[value_counts < threshold] = 0\n    filtered_values = filtered_values[filtered_values > 0]\n    other_count = value_counts.sum() - filtered_values.sum()\n    if other_count > 0:\n        filtered_values['Other'] = other_count\n\n    wedges, texts, autotexts = plt.pie(\n        filtered_values,\n        autopct=lambda p: f'{p:.1f}%' if p > 5 else '',\n        colors=pie_chart_palette[:len(filtered_values)],\n        startangle=140,\n        wedgeprops=dict(width=0.3),\n        explode=[0.05 if p > 5 else 0 for p in filtered_values],\n        textprops={'fontsize': 10}\n    )\n\n    plt.title(\"\\n\".join(textwrap.wrap(f\"Pie Chart for {variable} — {source_name}\", width=50)))\n    plt.legend(filtered_values.index, loc=\"upper left\", bbox_to_anchor=(1, 1))\n\n    # Biểu đồ đếm ngang (bên phải)\n    plt.subplot(1, 2, 2)\n    sns.countplot(\n        data=data,\n        y=variable,\n        hue='dataset',  # vẫn còn, nhưng chỉ có \"tàu hỏa\"\n        palette=custom_palette,\n        alpha=0.85\n    )\n    plt.ylabel(variable)\n    plt.xlabel(\"Count\")\n    plt.title(\"\\n\".join(textwrap.wrap(f\"Countplot for {variable} — {source_name}\", width=50)))\n    plt.tight_layout()\n    plt.show()\n\n# --- Vẽ biểu đồ tất cả các biến phân loại từ tập dữ liệu chính (chỉ huấn luyện)\nfor var in categorical_variables:\n    if var in main_combined.columns:\n        create_categorical_plots(var, main_combined, \"Context Features\")\n\n# --- Vẽ biểu đồ tất cả các biến phân loại từ tập dữ liệu nhân khẩu học (chỉ đào tạo)\nfor var in categorical_variables:\n    if var in demo_combined.columns:\n        create_categorical_plots(var, demo_combined, \"Demographic Features\")\n\n# Cleanup\ntrain_main.drop('dataset', axis=1, inplace=True)\ntrain_demo.drop('dataset', axis=1, inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:33.228395Z","iopub.execute_input":"2025-08-07T02:18:33.228675Z","iopub.status.idle":"2025-08-07T02:18:40.600615Z","shell.execute_reply.started":"2025-08-07T02:18:33.228652Z","shell.execute_reply":"2025-08-07T02:18:40.599694Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3.3 Target Feature Analysis (Univariate Analysis)","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport seaborn as sns\nimport pandas as pd\nimport textwrap\n\n# Xác định bảng màu\npie_chart_palette = ['#33638d', '#28ae80', '#d3eb0c', '#ff9a0b', '#7e03a8', '#35b779',\n                     '#fde725', '#440154', '#90d743', '#482173', '#22a884', '#f8961e']\n\n# Xác định cột mục tiêu\ntarget_columns = ['gesture']\n# Chuẩn bị dữ liệu chính (CHỈ tập huấn luyện)\ntrain_main = train_df.copy()\ntrain_main['dataset'] = 'train'  # giữ lại nếu bạn muốn thêm lại logic sau\n\n# Hàm vẽ đồ thị thống nhất cho các biến mục tiêu (chỉ đào tạo)\ndef create_target_plots(variable, data, source_name):\n    sns.set_style('whitegrid')\n    fig, axes = plt.subplots(1, 2, figsize=(16, 6))\n\n    # Pie Chart (left)\n    plt.subplot(1, 2, 1)\n    value_counts = data[variable].value_counts()\n\n    # Thu gọn các danh mục nhỏ thành 'Khác'\n    threshold = 0.05 * value_counts.sum()\n    filtered_values = value_counts.copy()\n    filtered_values[value_counts < threshold] = 0\n    filtered_values = filtered_values[filtered_values > 0]\n    other_count = value_counts.sum() - filtered_values.sum()\n    if other_count > 0:\n        filtered_values['Other'] = other_count\n\n    wedges, texts, autotexts = plt.pie(\n        filtered_values,\n        autopct=lambda p: f'{p:.1f}%' if p > 5 else '',\n        colors=pie_chart_palette[:len(filtered_values)],\n        startangle=140,\n        wedgeprops=dict(width=0.3),\n        explode=[0.05 if p > 5 else 0 for p in filtered_values],\n        textprops={'fontsize': 10}\n    )\n\n    plt.title(\"\\n\".join(textwrap.wrap(f\"Pie Chart for {variable} — {source_name}\", width=50)))\n    plt.legend(filtered_values.index, loc=\"upper left\", bbox_to_anchor=(1, 1))\n\n    # Biểu đồ đếm ngang (bên phải)\n    plt.subplot(1, 2, 2)\n    sns.countplot(\n        data=data,\n        y=variable,\n        color='#3498db',  # just use a single color (train)\n        alpha=0.85\n    )\n    plt.ylabel(variable)\n    plt.xlabel(\"Count\")\n    plt.title(\"\\n\".join(textwrap.wrap(f\"Countplot for {variable} — {source_name}\", width=50)))\n    plt.tight_layout()\n    plt.show()\n\n# Vẽ cột mục tiêu (chỉ đào tạo)\nfor var in target_columns:\n    if var in train_main.columns:\n        create_target_plots(var, train_main, \"Target Variable\")\n\n# Cleanup\ntrain_main.drop('dataset', axis=1, inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:40.604787Z","iopub.execute_input":"2025-08-07T02:18:40.605635Z","iopub.status.idle":"2025-08-07T02:18:43.666163Z","shell.execute_reply.started":"2025-08-07T02:18:40.605578Z","shell.execute_reply":"2025-08-07T02:18:43.665215Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3.4 Mối quan hệ tính năng đa biến","metadata":{}},{"cell_type":"markdown","source":"### 3.4.1 Phân tích tính năng cảm biến cho tất cả cử chỉ (Chủ thể ngẫu nhiên đơn lẻ)","metadata":{}},{"cell_type":"code","source":"#fix code dòng dưới\nsubject_id = train_dem_df['subject'].sample(1).iloc[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:43.667315Z","iopub.execute_input":"2025-08-07T02:18:43.667613Z","iopub.status.idle":"2025-08-07T02:18:43.676664Z","shell.execute_reply.started":"2025-08-07T02:18:43.667593Z","shell.execute_reply":"2025-08-07T02:18:43.675130Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"subj_info = train_dem_df[train_dem_df['subject'] == subject_id].iloc[0]\n\nprint(f\"⏩ Details of the random subject being considered\")\nprint(f\"Subject ID:              {subject_id}\")\nprint(f\"  • Age:                 {subj_info['age']} years\")\nprint(f\"  • Adult/Child:         {'Adult' if subj_info['adult_child']==1 else 'Child'}\")\nprint(f\"  • Sex:                 {'Male' if subj_info['sex']==1 else 'Female'}\")\nprint(f\"  • Handedness:          {'Right-handed' if subj_info['handedness']==1 else 'Left-handed'}\")\nprint(f\"  • Height:              {subj_info['height_cm']} cm\")\nprint(f\"  • Shoulder→Wrist:      {subj_info['shoulder_to_wrist_cm']} cm\")\nprint(f\"  • Elbow→Wrist:         {subj_info['elbow_to_wrist_cm']} cm\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:43.679155Z","iopub.execute_input":"2025-08-07T02:18:43.680704Z","iopub.status.idle":"2025-08-07T02:18:43.703675Z","shell.execute_reply.started":"2025-08-07T02:18:43.680460Z","shell.execute_reply":"2025-08-07T02:18:43.702745Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### ⏩ 1. ** BIỂU ĐỒ IMU**\n#### **📌 Hướng dẫn giải thích**\n   * Độ lớn gia tốc (`acc_mag`): cho biết cổ tay di chuyển “mạnh” như thế nào. Các đỉnh thường tương ứng với chuyển động tay nhanh.\n   * Góc xoay (`rot_angle`): đại diện cho mức độ cổ tay bị vặn lệch so với tư thế trung tính ban đầu. Những cú nhảy đột ngột có thể cho thấy các động tác lật cổ tay nhanh.\n   * Vùng được tô xám “Transition” thể hiện khoảng thời gian tay đang di chuyển tới mục tiêu thực hiện cử chỉ; vùng màu đỏ “Gesture” thể hiện khoảng thời gian tiếp xúc thực sự hoặc động tác chính diễn ra.\n   * So sánh hai đường biểu diễn này trong từng chuỗi dữ liệu giúp phân biệt, ví dụ, một hành động BFRB như giật tóc (gia tốc cao kèm góc xoay đột ngột) với một chuyển động nhẹ nhàng không phải BFRB.","metadata":{}},{"cell_type":"code","source":"# ─── Ô 1: Biểu đồ chuỗi thời gian IMU ──────────────────────────────────────────────────────── ─────────────────────────────────────────\n# Ô này tính toán độ lớn gia tốc (√(acc_x²+acc_y²+acc_z²)) và góc quay (θ = 2·arccos(rot_w))\n# cho mỗi dấu thời gian trong chuỗi của đối tượng đã chọn. Sau đó, nó vẽ hai đường thẳng cho mỗi chuỗi cử chỉ:\n# • acc_mag (độ lớn gia tốc)\n# • rot_angle (góc định hướng)\n# Nền được tô bóng theo ‘pha’: “Chuyển tiếp” so với “Cử chỉ” với các màu khác nhau.\n# Mỗi tiêu đề biểu đồ phụ hiển thị tên cử chỉ và sequence_type của biểu đồ đó. Mỗi hàng có hai biểu đồ.\n\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# Chọn một chủ đề (ví dụ: đầu tiên trong danh sách)\nsubject_id = train_df['subject'].unique()[0]\nsubj_df = train_df[train_df['subject'] == subject_id].copy()\n\n# Tính toán các tính năng IMU được suy ra\nsubj_df['acc_mag'] = np.sqrt(\n    subj_df['acc_x']**2 + subj_df['acc_y']**2 + subj_df['acc_z']**2\n)\nsubj_df['rot_w_clipped'] = subj_df['rot_w'].clip(-1, 1)\nsubj_df['rot_angle'] = 2 * np.arccos(subj_df['rot_w_clipped'])\n\n# Chọn một sequence_id cho mỗi nhãn cử chỉ duy nhất\n# Nhóm theo cột 'cử chỉ' được mã hóa theo số nguyên, sau đó lấy sequence_id đầu tiên trong mỗi nhóm\ngesture_to_seq = subj_df.groupby('gesture')['sequence_id'].first().to_dict()\n# Chuyển đổi thành danh sách sequence_ids\nseq_ids = list(gesture_to_seq.values())\n\nn = len(seq_ids)\nncols = 2\nnrows = int(np.ceil(n / ncols))\n\nfig, axes = plt.subplots(nrows, ncols, figsize=(14, 4 * nrows), sharex=False, sharey=False)\naxes = axes.flatten()\n\nfor i, seq in enumerate(seq_ids):\n    ax = axes[i]\n    seq_df = subj_df[subj_df['sequence_id'] == seq].sort_values('sequence_counter')\n    times = seq_df['sequence_counter']\n\n    # Vẽ hai đường IMU đã suy ra\n    ax.plot(times, seq_df['acc_mag'],\n            label='Acceleration Magnitude', color='tab:blue', linewidth=1.5)\n    ax.plot(times, seq_df['rot_angle'],\n            label='Rotation Angle (rad)', color='tab:orange', linewidth=1.5)\n\n    # Tô bóng theo giai đoạn: “Chuyển tiếp” so với “Cử chỉ”\n    used_labels = set()\n    for phase_label, color in [('Transition', 'lightgray'),\n                               ('Gesture', 'lightcoral')]:\n        mask = seq_df['phase'] == phase_label\n        if mask.any():\n            # Tìm các khoảng liên tiếp trong đó pha == nhãn pha\n            idxs = seq_df.index[mask]\n            breaks = np.where(np.diff(idxs) != 1)[0]\n            spans = []\n            start_idx = idxs[0]\n            for b in breaks:\n                end_idx = idxs[b]\n                spans.append((start_idx, end_idx))\n                start_idx = idxs[b + 1]\n            spans.append((start_idx, idxs[-1]))\n            # Vẽ từng khoảng; chỉ ghi nhãn một lần cho mỗi pha trong sơ đồ phụ này\n            for (start_i, end_i) in spans:\n                t0 = seq_df.loc[start_i, 'sequence_counter']\n                t1 = seq_df.loc[end_i, 'sequence_counter']\n                label_arg = phase_label if phase_label not in used_labels else None\n                ax.axvspan(t0, t1, color=color, alpha=0.3, label=label_arg)\n                used_labels.add(phase_label)\n\n    # Tiêu đề và nhãn của cốt truyện phụ\n    gesture_name = seq_df['gesture'].iloc[0]\n    seq_type = seq_df['sequence_type'].iloc[0]\n    ax.set_title(f\"Subject {subject_id} – Seq {seq} – {gesture_name} ({seq_type})\",\n                 fontsize=10)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Value\", fontsize=9)\n    ax.legend(loc='upper right', fontsize='small')\n    ax.grid(True)\n\n# Tắt bất kỳ ô phụ trống nào\nfor j in range(i + 1, nrows * ncols):\n    fig.delaxes(axes[j])\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:43.704871Z","iopub.execute_input":"2025-08-07T02:18:43.705193Z","iopub.status.idle":"2025-08-07T02:18:50.888481Z","shell.execute_reply.started":"2025-08-07T02:18:43.705161Z","shell.execute_reply":"2025-08-07T02:18:50.886853Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### ⏩ 2. ** Biểu đồ Thermopile**\n#### **📌  Hướng dẫn phân tích**\n* Mỗi đường cong `thm_i` đại diện cho nhiệt độ gần da đo được từ 5 cảm biến. Khi giá trị thm_i tăng lên, điều đó cho thấy cổ tay đang tiến gần đến một bộ phận ấm trên cơ thể (ví dụ: mặt, cổ).\n\n* Đường đậm “`Average Temperature`” là trung bình của cả 5 cảm biến, giúp làm rõ các sự kiện tiếp xúc (đỉnh nhiệt độ tăng) so với các giai đoạn ở xa (vùng nhiệt độ thấp, phẳng).\n\n* Vùng được tô màu theo “Transition” và “Gesture” giúp xác nhận rằng sự gia tăng nhiệt độ chủ yếu diễn ra trong giai đoạn “Gesture” (tức là khi có tiếp xúc thực sự).","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\n\n# Sử dụng cùng subject_id và seq_ids (một cho mỗi cử chỉ duy nhất) từ Ô 1\ntherm_df = subj_df.copy()\n\n# Thu thập các cột nhiệt điện và tính toán giá trị trung bình theo dấu thời gian của chúng\nthm_cols = [f'thm_{i}' for i in range(1, 6)]\ntherm_df['thm_mean'] = therm_df[thm_cols].mean(axis=1)\n\nfig, axes = plt.subplots(nrows, ncols, figsize=(14, 4 * nrows), sharex=False, sharey=False)\naxes = axes.flatten()\n\nfor i, seq in enumerate(seq_ids):\n    ax = axes[i]\n    seq_thm = therm_df[therm_df['sequence_id'] == seq].sort_values('sequence_counter')\n    times = seq_thm['sequence_counter']\n    # Xác định phạm vi y để tô bóng (không nhất thiết phải có vì axvspan trải dài toàn bộ trục y)\n    # Vẽ từng kênh nhiệt điện\n    for col in thm_cols:\n        ax.plot(times, seq_thm[col], label=col, linewidth=1)\n    # Vẽ giá trị trung bình dưới dạng đường màu đen đậm\n    ax.plot(times, seq_thm['thm_mean'], label='Average Temperature', color='black', linewidth=2.5)\n    # Tô bóng theo pha bằng axvspan\n    for phase_label, color in [('Transition', 'lightgray'), ('Gesture', 'lightcoral')]:\n        mask = seq_thm['phase'] == phase_label\n        if mask.any():\n            idxs = seq_thm.index[mask]\n            breaks = np.where(np.diff(idxs) != 1)[0]\n            spans = []\n            start_idx = idxs[0]\n            for b in breaks:\n                end_idx = idxs[b]\n                spans.append((start_idx, end_idx))\n                start_idx = idxs[b + 1]\n            spans.append((start_idx, idxs[-1]))\n            for (start_i, end_i) in spans:\n                t0 = seq_thm.loc[start_i, 'sequence_counter']\n                t1 = seq_thm.loc[end_i, 'sequence_counter']\n                ax.axvspan(t0, t1, color=color, alpha=0.3, label=phase_label)\n    # Nhãn và chú thích cho cốt truyện phụ này\n    gesture_name = seq_thm['gesture'].iloc[0]\n    seq_type = seq_thm['sequence_type'].iloc[0]\n    ax.set_title(f\"Subject {subject_id} – Seq {seq} – {gesture_name} ({seq_type})\", fontsize=10)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Temperature (°C)\", fontsize=9)\n    ax.legend(loc='upper right', fontsize='small')\n    ax.grid(True)\n\n# Tắt bất kỳ phần phụ nào không sử dụng\nfor j in range(i + 1, nrows * ncols):\n    fig.delaxes(axes[j])\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:50.889922Z","iopub.execute_input":"2025-08-07T02:18:50.890270Z","iopub.status.idle":"2025-08-07T02:18:57.673597Z","shell.execute_reply.started":"2025-08-07T02:18:50.890242Z","shell.execute_reply":"2025-08-07T02:18:57.672641Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### ⏩ 3. **ToF Plots**\n#### **📌 Interpretation Guide**\n   * Mỗi giá trị `tof_i_mean` là trung bình của lưới cảm biến 8×8, thể hiện mức độ “gần” của cổ tay so với một vật thể (ví dụ: da). Giá trị thấp (\\~0–50) → rất gần; giá trị cao hơn (\\~100–200) → xa hơn; giá trị –1 (bị che/mất tín hiệu) → không có tín hiệu phản hồi.\n   * Theo dõi giá trị trung bình của cả năm cảm biến theo thời gian giúp xác định thời điểm tay tiến lại gần (giá trị đột ngột giảm) và khi tay rời xa.\n   * Phần tô màu theo “Transition” và “Gesture” xác nhận rằng khoảng cách thấp nhất trùng với thời điểm tiếp xúc thực sự (diễn ra trong giai đoạn “Gesture”).","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\n\n# Sử dụng cùng subject_id và seq_ids (một cho mỗi cử chỉ duy nhất) từ Ô 1\ntof_df = subj_df.copy()\n\n# Xác định 64 cột cho mỗi cảm biến ToF và tính giá trị trung bình theo dấu thời gian của chúng\nmean_cols = []\nfor i_sensor in range(1, 6):\n    pixel_cols = [f'tof_{i_sensor}_v{pix}' for pix in range(64)]\n    tof_df[f'tof_{i_sensor}_mean'] = tof_df[pixel_cols].replace(-1, np.nan).mean(axis=1)\n    mean_cols.append(f'tof_{i_sensor}_mean')\n\nfig, axes = plt.subplots(nrows, ncols, figsize=(14, 4 * nrows), sharex=False, sharey=False)\naxes = axes.flatten()\n\nfor i, seq in enumerate(seq_ids):\n    ax = axes[i]\n    seq_tof = tof_df[tof_df['sequence_id'] == seq].sort_values('sequence_counter')\n    times = seq_tof['sequence_counter']\n\n    # Vẽ giá trị trung bình của từng cảm biến ToF\n    for col in mean_cols:\n        ax.plot(times, seq_tof[col], label=col, linewidth=1)\n\n    # Tô bóng theo pha bằng axvspan\n    for phase_label, color in [('Transition', 'lightgray'), ('Gesture', 'lightcoral')]:\n        mask = seq_tof['phase'] == phase_label\n        if mask.any():\n            idxs = seq_tof.index[mask]\n            breaks = np.where(np.diff(idxs) != 1)[0]\n            spans = []\n            start_idx = idxs[0]\n            for b in breaks:\n                end_idx = idxs[b]\n                spans.append((start_idx, end_idx))\n                start_idx = idxs[b + 1]\n            spans.append((start_idx, idxs[-1]))\n            for (start_i, end_i) in spans:\n                t0 = seq_tof.loc[start_i, 'sequence_counter']\n                t1 = seq_tof.loc[end_i, 'sequence_counter']\n                ax.axvspan(t0, t1, color=color, alpha=0.3, label=phase_label)\n\n    # Nhãn và chú thích cho cốt truyện phụ này\n    gesture_name = seq_tof['gesture'].iloc[0]\n    seq_type     = seq_tof['sequence_type'].iloc[0]\n    ax.set_title(f\"Subject {subject_id} – Seq {seq} – {gesture_name} ({seq_type})\", fontsize=10)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Mean ToF Distance\", fontsize=9)\n    ax.legend(loc='upper right', fontsize='small')\n    ax.grid(True)\n\n# Tắt bất kỳ phần phụ nào không sử dụng\nfor j in range(i + 1, nrows * ncols):\n    fig.delaxes(axes[j])\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:18:57.674662Z","iopub.execute_input":"2025-08-07T02:18:57.675123Z","iopub.status.idle":"2025-08-07T02:19:04.084314Z","shell.execute_reply.started":"2025-08-07T02:18:57.675073Z","shell.execute_reply":"2025-08-07T02:19:04.083067Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.4.2 Sensor‐Based Differentiation Across Demographics and Physiology Features","metadata":{}},{"cell_type":"markdown","source":"#### 📌 Đặc trưng đang xem xét: (Thuận tay) **Handedness**\n\nTrong phần này, ta trực quan hóa cách những người thuận tay trái và thuận tay phải thực hiện cùng một cử chỉ (“Write name on leg”). Ta lấy một chuỗi dữ liệu từ mỗi nhóm và vẽ song song bốn loại cảm biến để so sánh.\n\nBằng cách so sánh giữa người thuận tay trái và tay phải, ta tìm kiếm sự khác biệt ổn định trong cách các cảm biến trên cổ tay phản hồi khi tay tiến lại gần và viết lên chân. Do các cảm biến Helios được bố trí xung quanh cổ tay, việc thuận tay nào sẽ ảnh hưởng đến cảm biến thermopile hoặc ToF nào đối diện với chân trước, đồng thời làm thay đổi hướng chuyển động trên các trục IMU.\n\n**⏩ Cách phân tích & Hàm ý thực tiễn (theo từng cảm biến):**\n\n* **IMU (acc\\_x/y/z):**  Quan sát trục gia tốc nào có đỉnh sớm hơn hoặc lớn hơn khi tay di chuyển vào trong. Một người thuận tay phải khi viết có thể tạo ra giá trị acc_x (lateral – ngang) dương lớn hơn do cổ tay xoay khác với tay trái. Mô hình có thể học cách hoán đổi hoặc căn chỉnh lại thông tin trục dựa vào handedness để tránh hiểu nhầm thời điểm bắt đầu cử chỉ.\n* **Rotation (rot\\_w/x/y/z):** Theo dõi xu hướng nghiêng: người thuận tay phải có thể xoay cổ tay vào trong (ví dụ rot_y tăng sớm hơn), còn tay trái có thể xoay ra ngoài trước. Nếu góc xoay (rot_angle) tăng chậm hơn ở một bên, điều đó cho biết cánh tay vặn khác nhau — cần điều chỉnh căn thời gian để tránh nhầm lẫn do handedness.\n* **Thermopiles (thm\\_1–5):** Để ý cảm biến nhiệt nào tăng trước khi nhiệt da lọt vào vùng nhìn 50°. Với người thuận tay phải, một cảm biến cụ thể (ví dụ thm_2 ở mặt trong cổ tay) có thể nhận tín hiệu nhiệt sớm hơn so với tay trái. Nhờ đó, thuật toán có thể chọn cảm biến nhiệt phù hợp dựa trên thông tin handedness khai báo.\n* **ToF Mean Distance (tof\\_1–5):** Xác định cảm biến ToF nào có khoảng cách trung bình giảm sớm nhất khi tay tiếp cận chân. Người thuận tay phải có thể khiến ToF mặt trước (ví dụ tof_3_mean) nhận biết khoảng cách gần sớm hơn, và điều này ngược lại với người thuận tay trái. Việc huấn luyện trên sự lệch thời gian có hệ thống của ToF theo handedness giúp mô hình tránh bị overfitting vào một kiểu tay cụ thể.\n","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-08-07T02:19:04.085399Z","iopub.execute_input":"2025-08-07T02:19:04.085688Z","iopub.status.idle":"2025-08-07T02:19:04.786699Z","shell.execute_reply.started":"2025-08-07T02:19:04.085658Z","shell.execute_reply":"2025-08-07T02:19:04.785841Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"⏩ Vẽ sơ đồ cho mọi đối tượng chỉ với một cử chỉ","metadata":{}},{"cell_type":"code","source":"import warnings, numpy as np, pandas as pd, seaborn as sns, matplotlib.pyplot as plt\nwarnings.filterwarnings('ignore')\n\n# ─── 1. Chọn biến nhóm ────────────────────────────────────────────────────────────────────────────────\ngroup_col = \"handedness\"\ngroup_map = {0: \"Left-handed\", 1: \"Right-handed\"}\n\n# ─── 2. Xây dựng bảng tóm tắt cấp trình tự ──────────────────────────────────────────────────────────\nseq_summaries = []\n\nfor seq_id, seq in train_df.groupby(\"sequence_id\"):\n    \n    g_label = seq[group_col].iloc[0]\n    if pd.isna(g_label):                   # bỏ qua nếu thiếu thông tin nhân khẩu học\n        continue\n    \n    # ----- Mặt nạ pha -----\n    gest_mask = seq[\"phase\"] == \"Gesture\"\n    \n    # ----- 1) Gia tốc giật -----\n    # giật = |Δacc_mag / Δt| ; sử dụng max trên toàn bộ chuỗi\n    acc_mag = np.sqrt(seq[\"acc_x\"]**2 + seq[\"acc_y\"]**2 + seq[\"acc_z\"]**2).values\n    jerk = np.abs(np.diff(acc_mag))                         # Δt = 1 frame\n    max_jerk = jerk.max() if len(jerk) else np.nan\n    \n    # ----- 2) Phạm vi góc quay (Chỉ pha cử chỉ) -----\n    rot_angle = 2*np.arccos(seq[\"rot_w\"].clip(-1,1))\n    rot_range = rot_angle[gest_mask].max() - rot_angle[gest_mask].min() if gest_mask.any() else np.nan\n    \n    # ----- 3) Đỉnh nhiệt điện ΔT (Đỉnh cử chỉ trừ đi đường cơ sở chuyển tiếp) -----\n    thm_cols = [f\"thm_{i}\" for i in range(1,6)]\n    thm_mean = seq[thm_cols].ffill().bfill().mean(axis=1)\n    baseline = thm_mean[seq[\"phase\"]==\"Transition\"].median()\n    peak_dt  = thm_mean[gest_mask].max() - baseline if gest_mask.any() else np.nan\n    \n    # ----- 4) Khoảng cách trung bình gần nhất của ToF (tối thiểu trên tất cả 5 cảm biến) -----\n    tof_means = []\n    for i in range(1,6):\n        col = f\"tof_{i}_mean\"\n        if col not in seq:\n            # tính toán tức thời (thay thế -1 bằng NaN)\n            pix = seq[[f\"tof_{i}_v{p}\" for p in range(64)]].replace(-1, np.nan)\n            seq[col] = pix.mean(axis=1)\n        tof_means.append(seq[col])\n    tof_overall = pd.concat(tof_means, axis=1).mean(axis=1)\n    min_tof = tof_overall.min()\n    \n    seq_summaries.append({\n        \"sequence_id\":       seq_id,\n        group_col:           g_label,\n        \"max_jerk\":          max_jerk,\n        \"rot_range\":         rot_range,\n        \"thm_peak_delta\":    peak_dt,\n        \"tof_min_distance\":  min_tof\n    })\n\nsummary_df = pd.DataFrame(seq_summaries).dropna()\n\n# Ánh xạ nhãn nhóm thành chuỗi có thể đọc được\nsummary_df[\"Group\"] = summary_df[group_col].map(group_map)\n\n# ─── 3. Phân phối đồ thị ─ ...\nsns.set_style(\"whitegrid\")\nfig, axes = plt.subplots(2, 2, figsize=(14, 10))\nplt.subplots_adjust(hspace=0.35, wspace=0.25)\n\nmetrics = [\n    (\"max_jerk\",         \"Max Jerk |Δacc| (m/s²)\"),\n    (\"rot_range\",        \"Gesture Rot-Angle Range (rad)\"),\n    (\"thm_peak_delta\",   \"Thermopile ΔT Peak (°C)\"),\n    (\"tof_min_distance\", \"Min ToF Mean Dist. (units)\")\n]\n\nfor ax, (metric, nice_name) in zip(axes.flatten(), metrics):\n    sns.violinplot(\n        data=summary_df,\n        x=\"Group\", y=metric, palette=\"Set2\",\n        inner=None, ax=ax\n    )\n    sns.boxplot(\n        data=summary_df,\n        x=\"Group\", y=metric,\n        width=0.2, showcaps=True, boxprops={'facecolor':'white'},\n        showfliers=False, whiskerprops={'linewidth':2},\n        ax=ax\n    )\n    ax.set_title(nice_name, fontsize=12)\n    ax.set_xlabel(\"\")\n    ax.set_ylabel(nice_name, fontsize=10)\n\nfig.suptitle(f\"{group_map[0]} vs. {group_map[1]} – Sequence-Level Sensor Features (*Gesture Phase Only)\", fontsize=16)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:19:04.787720Z","iopub.execute_input":"2025-08-07T02:19:04.787975Z","iopub.status.idle":"2025-08-07T02:20:38.598663Z","shell.execute_reply.started":"2025-08-07T02:19:04.787954Z","shell.execute_reply":"2025-08-07T02:20:38.597560Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# ─── Tính toán thời lượng pha theo từng chuỗi ───────────────────────────────────────────────────────────────────────\n# Giả sử train_df đã hợp nhất dữ liệu nhân khẩu học và cột 'pha'\n\n# Đếm số khung hình trên mỗi pha, trên mỗi chuỗi\ndur_df = (\n    train_df\n    .groupby(['sequence_id', 'handedness'])['phase']\n    .value_counts()\n    .unstack(fill_value=0)\n    .reset_index()\n)\n\n# Chỉ giữ lại Chuyển tiếp & Cử chỉ (trong trường hợp Tạm dừng tồn tại)\ndur_df = dur_df[['sequence_id', 'handedness', 'Transition', 'Gesture']]\n\n# Chuyển sang dạng dài cho biểu đồ hộp\ndur_long = dur_df.melt(\n    id_vars=['sequence_id', 'handedness'],\n    value_vars=['Transition', 'Gesture'],\n    var_name='phase',\n    value_name='duration_frames'\n)\n\n# Bản đồ thuận tay trái thành nhãn\ndur_long['Group'] = dur_long['handedness'].map({0: 'Left-handed', 1: 'Right-handed'})\n\n# ─── Vẽ các hộp biểu đồ ngang cạnh nhau ─ ...\nsns.set_style(\"whitegrid\")\nfig, axes = plt.subplots(1, 2, figsize=(14, 6), sharey=True)\nplt.subplots_adjust(wspace=0.2)\n\nfor ax, group_label in zip(axes, ['Left-handed', 'Right-handed']):\n    subset = dur_long[dur_long['Group'] == group_label]\n    sns.boxplot(\n        data=subset,\n        x='duration_frames',\n        y='phase',\n        orient='h',\n        palette=['lightgray', 'lightcoral'],\n        ax=ax\n    )\n    ax.set_title(f\"{group_label}\", fontsize=12)\n    ax.set_xlabel(\"Duration (frames)\", fontsize=10)\n    ax.set_ylabel(\"Phase\", fontsize=10)\n    ax.grid(True)\n\nfig.suptitle(\"Phase Durations by Handedness: Transition vs. Gesture\", fontsize=14, y=1.02)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:20:38.599736Z","iopub.execute_input":"2025-08-07T02:20:38.600012Z","iopub.status.idle":"2025-08-07T02:20:39.275488Z","shell.execute_reply.started":"2025-08-07T02:20:38.599986Z","shell.execute_reply":"2025-08-07T02:20:39.274779Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"⏩ Biểu đồ cho các đối tượng được chọn ngẫu nhiên và một cử chỉ","metadata":{}},{"cell_type":"code","source":"# ─── Ô A: So sánh độ thuận tay của IMU, Rot, Thermopile và ToF ───────────────────────────────────────────────\n\n# (2) Chọn một cử chỉ để so sánh (thay đổi nếu cần)\ngesture_to_plot = \"Write name on leg\"\n\n# (3) Lọc chỉ các hàng có cử chỉ đó và phân chia theo tay thuận\ndf_gesture = train_df[train_df[\"gesture\"] == gesture_to_plot].copy()\n\n# Nhóm thuận tay trái (thuận tay == 0) và Nhóm thuận tay phải (thuận tay == 1)\nleft_group  = df_gesture[df_gesture[\"handedness\"] == 0]\nright_group = df_gesture[df_gesture[\"handedness\"] == 1]\n\n# (4) Từ mỗi nhóm, chọn ngẫu nhiên một sequence_id (hoặc đầu tiên) để vẽ đồ thị\n# Nếu không có ví dụ nào dành cho người thuận tay trái, bạn có thể cần một cử chỉ khác.\nif left_group[\"sequence_id\"].nunique() == 0:\n    raise ValueError(\"No left‐handed example of that gesture found. Choose a different gesture.\")\n\nleft_seq  = left_group[\"sequence_id\"].unique()[0]\nright_seq = right_group[\"sequence_id\"].unique()[0]\n\n# (5) Trích xuất hai chuỗi, sắp xếp theo sequence_counter\ndf_left  = train_df[(train_df[\"sequence_id\"] == left_seq)].sort_values(\"sequence_counter\")\ndf_right = train_df[(train_df[\"sequence_id\"] == right_seq)].sort_values(\"sequence_counter\")\n\n# (6)Tính toán các đặc điểm có nguồn gốc từ IMU: acc_mag và rot_angle\nfor df in (df_left, df_right):\n    df[\"acc_mag\"] = np.sqrt(df[\"acc_x\"]**2 + df[\"acc_y\"]**2 + df[\"acc_z\"]**2)\n    df[\"rot_w_clipped\"] = df[\"rot_w\"].clip(-1, 1)\n    df[\"rot_angle\"] = 2 * np.arccos(df[\"rot_w_clipped\"])\n\n# (7) Vẽ trợ lý để tô bóng các pha\ndef shade_phases(ax, seq_df):\n    used = set()\n    for phase_label, color in [(\"Transition\", \"lightgray\"), (\"Gesture\", \"lightcoral\")]:\n        mask = seq_df[\"phase\"] == phase_label\n        if not mask.any():\n            continue\n        idxs = seq_df.index[mask]\n        diffs = np.where(np.diff(idxs) != 1)[0]\n        spans = []\n        start = idxs[0]\n        for b in diffs:\n            end = idxs[b]\n            spans.append((start, end))\n            start = idxs[b + 1]\n        spans.append((start, idxs[-1]))\n        for (s, e) in spans:\n            t0 = seq_df.loc[s, \"sequence_counter\"]\n            t1 = seq_df.loc[e, \"sequence_counter\"]\n            label = phase_label if phase_label not in used else None\n            ax.axvspan(t0, t1, color=color, alpha=0.3, label=label)\n            used.add(phase_label)\n\n# (8) Bây giờ hãy tạo **bốn** hình riêng biệt (mỗi hình có 2 biểu đồ phụ cạnh nhau):\n\n# --------------------------------------------------------------------------------\n# A.1: IMU: Độ lớn gia tốc\n# --------------------------------------------------------------------------------\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_left, df_right],\n    [f\"Left‐handed (Subj: {df_left['subject'].iloc[0]})\",\n     f\"Right‐handed (Subj: {df_right['subject'].iloc[0]})\"]\n):\n    ax.plot(df_seq[\"sequence_counter\"], df_seq[\"acc_mag\"],\n            color=\"tab:blue\", lw=1.5, label=\"|acc|(m/s²)\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Acceleration Magnitude\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Handedness: Acceleration Magnitude – Left vs. Right\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# A.2: IMU: Góc quay\n# --------------------------------------------------------------------------------\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_left, df_right],\n    [f\"Left‐handed (Subj: {df_left['subject'].iloc[0]})\",\n     f\"Right‐handed (Subj: {df_right['subject'].iloc[0]})\"]\n):\n    ax.plot(df_seq[\"sequence_counter\"], df_seq[\"rot_angle\"],\n            color=\"tab:orange\", lw=1.5, label=\"rot_angle (rad)\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Rotation Angle (rad)\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Handedness: Rotation Angle – Left vs. Right\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# A.3: Nhiệt điện: 5 kênh + Trung bình\n# --------------------------------------------------------------------------------\n# (Tính giá trị trung bình của nhiệt điện nếu chưa thực hiện)\nfor df in (df_left, df_right):\n    thm_cols = [f\"thm_{i}\" for i in range(1,6)]\n    df[\"thm_mean\"] = df[thm_cols].mean(axis=1)\n\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_left, df_right],\n    [f\"Left‐handed (Subj: {df_left['subject'].iloc[0]})\",\n     f\"Right‐handed (Subj: {df_right['subject'].iloc[0]})\"]\n):\n    times = df_seq[\"sequence_counter\"]\n    # Vẽ năm kênh thô\n    for col in [f\"thm_{i}\" for i in range(1,6)]:\n        ax.plot(times, df_seq[col], lw=1, label=col, alpha=0.7)\n    #Vẽ biểu đồ trung bình dưới dạng in đậm\n    ax.plot(times, df_seq[\"thm_mean\"], color=\"black\", lw=2.5, label=\"thm_mean\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Thermopile (°C)\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"x-small\", ncol=2)\n    ax.grid(True)\nplt.suptitle(\"Handedness: Thermopile – Left vs. Right\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# A.4: ToF: Năm kênh khoảng cách trung bình\n# --------------------------------------------------------------------------------\n# (Tính toán ToF trung bình trên mỗi cảm biến)\nfor df in (df_left, df_right):\n    for i_sensor in range(1,6):\n        pixel_cols = [f\"tof_{i_sensor}_v{pix}\" for pix in range(64)]\n        df[f\"tof_{i_sensor}_mean\"] = df[pixel_cols].replace(-1, np.nan).mean(axis=1)\n\nmean_cols = [f\"tof_{i}_mean\" for i in range(1,6)]\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_left, df_right],\n    [f\"Left‐handed (Subj: {df_left['subject'].iloc[0]})\",\n     f\"Right‐handed (Subj: {df_right['subject'].iloc[0]})\"]\n):\n    times = df_seq[\"sequence_counter\"]\n    for col in mean_cols:\n        ax.plot(times, df_seq[col], lw=1, label=col)\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"ToF Mean Distance\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Handedness: ToF Mean Distance – Left vs. Right\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:20:39.276940Z","iopub.execute_input":"2025-08-07T02:20:39.277345Z","iopub.status.idle":"2025-08-07T02:20:42.555574Z","shell.execute_reply.started":"2025-08-07T02:20:39.277313Z","shell.execute_reply":"2025-08-07T02:20:42.554482Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"📌 Đặc trưng đang xem xét: Adult vs. Child\nỞ đây, ta chọn một cử chỉ cụ thể (ví dụ: “Neck – pinch skin”) và so sánh giữa dữ liệu của một người lớn và một trẻ em.\n\nMỗi biểu đồ đối chiếu cách trẻ em và người lớn thực hiện cùng một cử chỉ. Trẻ em thường di chuyển đột ngột hơn (các đỉnh “acc_mag” cao hơn) và có thể tạo ra các đặc điểm nhiệt/tiếp cận khác do chiều dài tay ngắn hơn.\n\n⏩ Cách phân tích & Hàm ý thực tiễn (theo từng cảm biến):\n\nAcceleration magnitude: Nếu đường cong “acc_mag” của trẻ có nhiều đỉnh sắc nhọn và biên độ lớn hơn, mô hình cần hiểu rằng các đợt gia tốc IMU mạnh không phải lúc nào cũng là dấu hiệu của BFRB, mà có thể phản ánh sự vận động tự nhiên, mạnh mẽ hơn của trẻ em.\nRotation angle: Người lớn có thể giữ ổn định góc xoay hơn trong giai đoạn tạm dừng, trong khi “rot_angle” của trẻ có thể dao động nhiều hơn. Điều này giúp mô hình nhận biết rằng những dao động nhỏ có thể là bình thường nếu đối tượng là trẻ em.\nThermopile: Giá trị nhiệt của trẻ có thể tăng nhanh hơn (tiếp xúc gần hơn) nhưng cũng hạ nhiệt nhanh hơn; tích hợp đặc điểm này giúp mô hình phân biệt tiếp xúc thực sự và các pha bật ra nhanh điển hình ở trẻ em.\nToF: Nếu khoảng cách trung bình của ToF ở trẻ giảm sớm hơn (tay ở gần hơn), mô hình có thể học cách điều chỉnh ngưỡng khoảng cách theo độ tuổi, tránh nhầm lẫn các chuyển động lớn của trẻ với những cử chỉ khác.","metadata":{}},{"cell_type":"markdown","source":"⏩ Plots for All Subjects and One Gesture","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# ─── 1. Xây dựng tóm tắt cấp độ trình tự cho Người lớn so với Trẻ em ─────────────────────────────\nseq_summaries = []\nfor seq_id, seq in train_df.groupby(\"sequence_id\"):\n    grp = seq[\"adult_child\"].iloc[0]  # 0=child, 1=adult\n    # Bỏ qua nếu không có nhãn pha\n    if seq[\"phase\"].isna().all():\n        continue\n    # 1) Max Jerk (IMU)\n    acc_mag = np.sqrt(seq[\"acc_x\"]**2 + seq[\"acc_y\"]**2 + seq[\"acc_z\"]**2).values\n    jerk    = np.abs(np.diff(acc_mag))\n    max_jerk = jerk.max() if len(jerk) else np.nan\n    # 2) Phạm vi xoay trong khi cử chỉ\n    rot_angle = 2 * np.arccos(seq[\"rot_w\"].clip(-1,1))\n    mask_g = seq[\"phase\"]==\"Gesture\"\n    rot_range = rot_angle[mask_g].max() - rot_angle[mask_g].min() if mask_g.any() else np.nan\n    # 3) Đỉnh nhiệt điện ΔT\n    thm = seq[[f\"thm_{i}\" for i in range(1,6)]].ffill().bfill().mean(axis=1)\n    base = thm[seq[\"phase\"]==\"Transition\"].median()\n    peak_dt = thm[mask_g].max() - base if mask_g.any() else np.nan\n    # 4) Khoảng cách tối thiểu ToF\n    tof_means = []\n    for i in range(1,6):\n        col = f\"tof_{i}_mean\"\n        if col not in seq:\n            pix = seq[[f\"tof_{i}_v{p}\" for p in range(64)]].replace(-1,np.nan)\n            seq[col] = pix.mean(axis=1)\n        tof_means.append(seq[col])\n    tof_min = pd.concat(tof_means,axis=1).mean(axis=1).min()\n    seq_summaries.append({\n        \"sequence_id\": seq_id,\n        \"adult_child\": grp,\n        \"max_jerk\": max_jerk,\n        \"rot_range\": rot_range,\n        \"thm_peak_delta\": peak_dt,\n        \"tof_min_distance\": tof_min\n    })\n\nsummary_df = pd.DataFrame(seq_summaries).dropna()\nsummary_df[\"Group\"] = summary_df[\"adult_child\"].map({0:\"Child\", 1:\"Adult\"})\n\n# ─── 2. Vẽ đồ thị phân phối cạnh nhau ─────────────────────────────────────────────────────────────\nsns.set_style(\"whitegrid\")\nfig, axes = plt.subplots(2, 2, figsize=(14,10), sharey=False)\nplt.subplots_adjust(hspace=0.4, wspace=0.3)\n\nmetrics = [\n    (\"max_jerk\",         \"Max Jerk |Δacc| (m/s²)\"),\n    (\"rot_range\",        \"Gesture Rot-Range (rad)\"),\n    (\"thm_peak_delta\",   \"Thermopile ΔT Peak (°C)\"),\n    (\"tof_min_distance\", \"Min ToF Mean Dist. (units)\")\n]\n\nfor ax, (col, label) in zip(axes.flatten(), metrics):\n    sns.violinplot(\n        x=\"Group\", y=col, data=summary_df,\n        palette=\"Set2\", inner=None, ax=ax\n    )\n    sns.boxplot(\n        x=\"Group\", y=col, data=summary_df,\n        width=0.2, showcaps=True, boxprops={'facecolor':'white'},\n        showfliers=False, whiskerprops={'linewidth':2}, ax=ax\n    )\n    ax.set_title(label, fontsize=12)\n    ax.set_xlabel(\"\")\n    ax.set_ylabel(label, fontsize=10)\n\nfig.suptitle(\"Adult vs. Child – Sequence-Level Sensor Feature Distributions (*Gesture Phase Only)\", fontsize=16)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:20:42.556971Z","iopub.execute_input":"2025-08-07T02:20:42.557349Z","iopub.status.idle":"2025-08-07T02:22:18.441646Z","shell.execute_reply.started":"2025-08-07T02:20:42.557311Z","shell.execute_reply":"2025-08-07T02:22:18.440604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport warnings\n\nwarnings.filterwarnings('ignore')\nsns.set_style(\"whitegrid\")\n\n# ─── Tính toán thời lượng pha theo trình tự cho Người lớn so với Trẻ em ────────────────────────────────────────────────────────────────────\ndur_df = (\n    train_df\n    .groupby(['sequence_id', 'adult_child'])['phase']\n    .value_counts()\n    .unstack(fill_value=0)\n    .reset_index()\n)\n\n# Chỉ giữ lại các cột Chuyển tiếp & Cử chỉ\ndur_df = dur_df[['sequence_id', 'adult_child', 'Transition', 'Gesture']]\n\n# Tan chảy thành dạng dài\ndur_long = dur_df.melt(\n    id_vars=['sequence_id', 'adult_child'],\n    value_vars=['Transition', 'Gesture'],\n    var_name='phase',\n    value_name='duration_frames'\n)\n\n# Ánh xạ adult_child vào nhãn\ndur_long['Group'] = dur_long['adult_child'].map({0: 'Child', 1: 'Adult'})\n\n# ─── Vẽ các biểu đồ hộp ngang cạnh nhau ─ ...\nfig, axes = plt.subplots(1, 2, figsize=(14, 6), sharey=True)\nplt.subplots_adjust(wspace=0.3)\n\nfor ax, group_label in zip(axes, ['Child', 'Adult']):\n    subset = dur_long[dur_long['Group'] == group_label]\n    sns.boxplot(\n        data=subset,\n        x='duration_frames',\n        y='phase',\n        orient='h',\n        palette=['lightgray', 'lightcoral'],\n        ax=ax\n    )\n    ax.set_title(f\"{group_label}\", fontsize=12)\n    ax.set_xlabel(\"Duration (frames)\", fontsize=10)\n    ax.set_ylabel(\"Phase\", fontsize=10)\n    ax.grid(True)\n\nplt.suptitle(\"Phase Durations by Adult vs. Child\", fontsize=14, y=1.02)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:22:18.442846Z","iopub.execute_input":"2025-08-07T02:22:18.443253Z","iopub.status.idle":"2025-08-07T02:22:19.039040Z","shell.execute_reply.started":"2025-08-07T02:22:18.443215Z","shell.execute_reply":"2025-08-07T02:22:19.038094Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"⏩ Biểu đồ cho các đối tượng được chọn ngẫu nhiên và một cử chỉ","metadata":{}},{"cell_type":"code","source":"# ─── Tế bào B: So sánh giữa người lớn và trẻ em đối với IMU, Rot, Thermopile và ToF ──────────────────────────────────────────────\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# (2) Chọn một cử chỉ để so sánh (thay đổi nếu cần)\ngesture_to_plot = \"Neck - pinch skin\"\n\n# (3)Lọc chỉ các hàng có cử chỉ đó, chia theo adult_child (0=child, 1=adult)\ndf_gesture = train_df[train_df[\"gesture\"] == gesture_to_plot].copy()\nchild_group = df_gesture[df_gesture[\"adult_child\"] == 0]\nadult_group = df_gesture[df_gesture[\"adult_child\"] == 1]\n\nif (child_group[\"sequence_id\"].nunique() == 0) or (adult_group[\"sequence_id\"].nunique() == 0):\n    raise ValueError(\"Insufficient examples in one of the groups. Pick another gesture.\")\n\nchild_seq = child_group[\"sequence_id\"].unique()[0]\nadult_seq = adult_group[\"sequence_id\"].unique()[0]\n\ndf_child = train_df[train_df[\"sequence_id\"] == child_seq].sort_values(\"sequence_counter\")\ndf_adult = train_df[train_df[\"sequence_id\"] == adult_seq].sort_values(\"sequence_counter\")\n\n# Tính toán các tính năng IMU được lấy từ mỗi\nfor df in (df_child, df_adult):\n    df[\"acc_mag\"] = np.sqrt(df[\"acc_x\"]**2 + df[\"acc_y\"]**2 + df[\"acc_z\"]**2)\n    df[\"rot_w_clipped\"] = df[\"rot_w\"].clip(-1, 1)\n    df[\"rot_angle\"] = 2 * np.arccos(df[\"rot_w_clipped\"])\n\n# Trình trợ giúp vẽ đồ thị để tô bóng các pha (chức năng sử dụng lại từ Ô A)\ndef shade_phases(ax, seq_df):\n    used = set()\n    for phase_label, color in [(\"Transition\", \"lightgray\"), (\"Gesture\", \"lightcoral\")]:\n        mask = seq_df[\"phase\"] == phase_label\n        if not mask.any():\n            continue\n        idxs = seq_df.index[mask]\n        diffs = np.where(np.diff(idxs) != 1)[0]\n        spans = []\n        start = idxs[0]\n        for b in diffs:\n            end = idxs[b]\n            spans.append((start, end))\n            start = idxs[b + 1]\n        spans.append((start, idxs[-1]))\n        for (s, e) in spans:\n            t0 = seq_df.loc[s, \"sequence_counter\"]\n            t1 = seq_df.loc[e, \"sequence_counter\"]\n            label = phase_label if phase_label not in used else None\n            ax.axvspan(t0, t1, color=color, alpha=0.3, label=label)\n            used.add(phase_label)\n\n# --------------------------------------------------------------------------------\n# B.1: IMU – Độ lớn gia tốc\n# --------------------------------------------------------------------------------\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_child, df_adult],\n    [f\"Child (Subj: {df_child['subject'].iloc[0]}, Age: {df_child['age'].iloc[0]})\",\n     f\"Adult (Subj: {df_adult['subject'].iloc[0]}, Age: {df_adult['age'].iloc[0]})\"]\n):\n    ax.plot(df_seq[\"sequence_counter\"], df_seq[\"acc_mag\"],\n            color=\"tab:blue\", lw=1.5, label=\"|acc|(m/s²)\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Acc Magnitude\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Adult vs. Child: Acceleration Magnitude\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# B.2: IMU – Góc quay\n# --------------------------------------------------------------------------------\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_child, df_adult],\n    [f\"Child (Subj: {df_child['subject'].iloc[0]}, Age: {df_child['age'].iloc[0]})\",\n     f\"Adult (Subj: {df_adult['subject'].iloc[0]}, Age: {df_adult['age'].iloc[0]})\"]\n):\n    ax.plot(df_seq[\"sequence_counter\"], df_seq[\"rot_angle\"],\n            color=\"tab:orange\", lw=1.5, label=\"rot_angle (rad)\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Rot Angle (rad)\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Adult vs. Child: Rotation Angle\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# B.3: Nhiệt điện – 5 kênh + Trung bình\n# --------------------------------------------------------------------------------\nfor df in (df_child, df_adult):\n    thm_cols = [f\"thm_{i}\" for i in range(1,6)]\n    df[\"thm_mean\"] = df[thm_cols].mean(axis=1)\n\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_child, df_adult],\n    [f\"Child (Subj: {df_child['subject'].iloc[0]}, Age: {df_child['age'].iloc[0]})\",\n     f\"Adult (Subj: {df_adult['subject'].iloc[0]}, Age: {df_adult['age'].iloc[0]})\"]\n):\n    times = df_seq[\"sequence_counter\"]\n    for col in [f\"thm_{i}\" for i in range(1,6)]:\n        ax.plot(times, df_seq[col], lw=1, label=col, alpha=0.7)\n    ax.plot(times, df_seq[\"thm_mean\"], color=\"black\", lw=2.5, label=\"thm_mean\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Thermopile (°C)\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\", ncol=2)\n    ax.grid(True)\nplt.suptitle(\"Adult vs. Child: Thermopile\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# B.4: ToF – Năm kênh khoảng cách trung bình\n# --------------------------------------------------------------------------------\nfor df in (df_child, df_adult):\n    for i_sensor in range(1,6):\n        pixel_cols = [f\"tof_{i_sensor}_v{pix}\" for pix in range(64)]\n        df[f\"tof_{i_sensor}_mean\"] = df[pixel_cols].replace(-1, np.nan).mean(axis=1)\n\nmean_cols = [f\"tof_{i}_mean\" for i in range(1,6)]\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_child, df_adult],\n    [f\"Child (Subj: {df_child['subject'].iloc[0]}, Age: {df_child['age'].iloc[0]})\",\n     f\"Adult (Subj: {df_adult['subject'].iloc[0]}, Age: {df_adult['age'].iloc[0]})\"]\n):\n    times = df_seq[\"sequence_counter\"]\n    for col in mean_cols:\n        ax.plot(times, df_seq[col], lw=1, label=col)\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"ToF Mean Distance\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Adult vs. Child: ToF Mean Distance\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:22:19.040313Z","iopub.execute_input":"2025-08-07T02:22:19.040665Z","iopub.status.idle":"2025-08-07T02:22:22.443662Z","shell.execute_reply.started":"2025-08-07T02:22:19.040623Z","shell.execute_reply":"2025-08-07T02:22:22.442656Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### 📌 Đặc trưng đang xem xét:  **Sex (Female vs. Male)**\n\nTrong phần này, ta so sánh các tín hiệu cảm biến giữa nam và nữ đối với một cử chỉ cụ thể (ví dụ: “Forehead – scratch”). Ta tạo cùng lúc bốn biểu đồ: IMU acc_mag, IMU rot_angle, Thermopile, và ToF. Những khác biệt có thể xuất hiện do sự chênh lệch về sức mạnh cơ bắp trung bình, kích thước bàn tay hoặc phong cách thực hiện cử chỉ.\n\n**⏩ HCách phân tích & Hàm ý thực tiễn:**\n\n* **IMU – Acceleration:** Nếu nam giới thể hiện các đỉnh gia tốc cao hơn một cách nhất quán (do sức mạnh cơ bắp trung bình lớn hơn), mô hình có thể chuẩn hóa cường độ IMU hoặc học cách hiệu chỉnh theo giới tính.\n* **Rotation Angle:**Nam và nữ có thể có kiểu xoay cổ tay khác nhau; bộ phân loại không nên quá phụ thuộc vào một mẫu rot_angle nhất định mà bỏ qua yếu tố giới tính.\n* **Thermopile:** Nhiệt độ da cơ bản của nữ thường khác nhẹ so với nam, do đó các đường nhiệt có thể bắt đầu ở các giá trị khác nhau. Mô hình cần học cách tập trung vào sự thay đổi tương đối (relative changes) thay vì tuyệt đối, từ đó tăng độ ổn định khi dự đoán.\n* **ToF:** Hình dạng bàn tay của nam có thể tạo ra các cụm điểm ToF với kích thước khác biệt. Việc đưa giới tính vào mô hình giúp điều chỉnh ngưỡng khoảng cách và mô hình hóa không gian tiếp cận, giảm thiểu các lỗi phân loại khi đánh giá mức độ gần của tay đối với cơ thể.\n\n\n\n\n\n\n\n\n\n","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# ─── 1. Chọn biến nhóm cho ô này ─ ...\ngroup_col = \"sex\"\ngroup_map = {0: \"Female\", 1: \"Male\"}\n\n# ─── 2. Xây dựng tóm tắt cấp trình tự (nếu chưa tạo) ───────────────────────────────────────────────────\n# Sử dụng lại logic từ ô tổng quan trước đó, nhưng nhóm vào 'giới tính' thay vì 'thuận tay'.\n\nseq_stats = []\nfor seq_id, seq in train_df.groupby(\"sequence_id\"):\n    g = seq[group_col].iloc[0]\n    if pd.isna(g):\n        continue\n\n    # Phase masks\n    gest = seq[\"phase\"] == \"Gesture\"\n\n    # 1) IMU jerk\n    acc = np.sqrt(seq[\"acc_x\"]**2 + seq[\"acc_y\"]**2 + seq[\"acc_z\"]**2).values\n    max_jerk = np.abs(np.diff(acc)).max() if len(acc)>1 else np.nan\n\n    # 2) Phạm vi xoay trong cử chỉ\n    rot = 2*np.arccos(seq[\"rot_w\"].clip(-1,1))\n    rot_range = rot[gest].max() - rot[gest].min() if gest.any() else np.nan\n\n    # 3) Delta đỉnh nhiệt điện\n    thms = seq[[f\"thm_{i}\" for i in range(1,6)]].ffill().bfill().mean(axis=1)\n    baseline = thms[seq[\"phase\"]==\"Transition\"].median() if (seq[\"phase\"]==\"Transition\").any() else np.nan\n    thm_peak = thms[gest].max() - baseline if gest.any() else np.nan\n\n    # 4) Khoảng cách trung bình tối thiểu ToF\n    tcols = []\n    for i in range(1,6):\n        col = f\"tof_{i}_mean\"\n        if col not in seq:\n            pix = seq[[f\"tof_{i}_v{p}\" for p in range(64)]].replace(-1, np.nan)\n            seq[col] = pix.mean(axis=1)\n        tcols.append(seq[col])\n    tof_all = pd.concat(tcols, axis=1).mean(axis=1)\n    tof_min = tof_all.min()\n\n    seq_stats.append({\n        \"sequence_id\": seq_id,\n        group_col: g,\n        \"max_jerk\": max_jerk,\n        \"rot_range\": rot_range,\n        \"thm_peak_delta\": thm_peak,\n        \"tof_min_distance\": tof_min\n    })\n\nsummary = pd.DataFrame(seq_stats).dropna()\nsummary[\"Group\"] = summary[group_col].map(group_map)\n\n#─── 3. Vẽ biểu đồ phân phối được điều chỉnh ───────────────────────────────────────────────────────────────────────────\nsns.set_style(\"whitegrid\")\nfig, axes = plt.subplots(2, 2, figsize=(14, 10))\nplt.subplots_adjust(hspace=0.35, wspace=0.25)\n\nmetrics = [\n    (\"max_jerk\",         \"Max Jerk |Δacc| (m/s²)\"),\n    (\"rot_range\",        \"Rotation Range (rad)\"),\n    (\"thm_peak_delta\",   \"Thermopile ΔT Peak (°C)\"),\n    (\"tof_min_distance\", \"Min ToF Mean Dist.\")\n]\n\nfor ax, (col, lbl) in zip(axes.flatten(), metrics):\n    sns.violinplot(\n        data=summary,\n        x=\"Group\", y=col,\n        palette=\"Set2\",\n        inner=None,\n        ax=ax\n    )\n    sns.boxplot(\n        data=summary,\n        x=\"Group\", y=col,\n        width=0.2,\n        showcaps=True,\n        boxprops={'facecolor':'white'},\n        showfliers=False,\n        whiskerprops={'linewidth':2},\n        ax=ax\n    )\n    ax.set_title(lbl, fontsize=12)\n    ax.set_xlabel(\"\")\n    ax.set_ylabel(lbl, fontsize=10)\n\nfig.suptitle(\"Sex Comparison: Sequence‐Level Sensor Summaries (*Gesture Phase Only)\", fontsize=16)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:22:22.444769Z","iopub.execute_input":"2025-08-07T02:22:22.445044Z","iopub.status.idle":"2025-08-07T02:23:58.048570Z","shell.execute_reply.started":"2025-08-07T02:22:22.445000Z","shell.execute_reply":"2025-08-07T02:23:58.047557Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# ─── Tính toán thời lượng pha theo từng chuỗi để so sánh Giới tính ───────────────────────────────────────────────────────────\n# Giả sử train_df đã được hợp nhất với train_dem_df\n\n# Nhóm theo sequence_id và giới tính, đếm số khung hình trong mỗi pha\ndur_df_sex = (\n    train_df\n    .groupby(['sequence_id', 'sex'])['phase']\n    .value_counts()\n    .unstack(fill_value=0)\n    .reset_index()\n)\n\n# Chỉ giữ lại Chuyển tiếp & Cử chỉ\ndur_df_sex = dur_df_sex[['sequence_id', 'sex', 'Transition', 'Gesture']]\n\n# Tan chảy thành dạng dài\ndur_long_sex = dur_df_sex.melt(\n    id_vars=['sequence_id', 'sex'],\n    value_vars=['Transition', 'Gesture'],\n    var_name='phase',\n    value_name='duration_frames'\n)\n\n# Ánh xạ giới tính vào nhãn\ndur_long_sex['Group'] = dur_long_sex['sex'].map({0: 'Female', 1: 'Male'})\n\n# ─── Vẽ các hộp biểu đồ ngang cạnh nhau ─ ...\nsns.set_style(\"whitegrid\")\nfig, axes = plt.subplots(1, 2, figsize=(14, 6), sharey=True)\nplt.subplots_adjust(wspace=0.2)\n\nfor ax, group_label in zip(axes, ['Female', 'Male']):\n    subset = dur_long_sex[dur_long_sex['Group'] == group_label]\n    sns.boxplot(\n        data=subset,\n        x='duration_frames',\n        y='phase',\n        orient='h',\n        palette={'Transition':'lightgray', 'Gesture':'lightcoral'},\n        ax=ax\n    )\n    ax.set_title(f\"{group_label}\", fontsize=12)\n    ax.set_xlabel(\"Duration (frames)\", fontsize=10)\n    ax.set_ylabel(\"Phase\", fontsize=10)\n    ax.grid(True)\n\nfig.suptitle(\"Sex Comparison: Transition vs. Gesture Duration\", fontsize=14, y=1.02)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:23:58.049700Z","iopub.execute_input":"2025-08-07T02:23:58.049988Z","iopub.status.idle":"2025-08-07T02:23:58.662125Z","shell.execute_reply.started":"2025-08-07T02:23:58.049963Z","shell.execute_reply":"2025-08-07T02:23:58.661102Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ─── Tế bào C: So sánh giới tính của IMU, Rot, Thermopile và ToF ────────────────────────────────────────────────────────────────────────\n# (2) Chọn một cử chỉ để so sánh (thay đổi nếu cần)\ngesture_to_plot = \"Forehead - scratch\"\n\n# (3) Lọc chỉ các hàng có cử chỉ đó, chia theo giới tính (0=nữ, 1=nam)\ndf_gesture = train_df[train_df[\"gesture\"] == gesture_to_plot].copy()\nfemale_group = df_gesture[df_gesture[\"sex\"] == 0]\nmale_group   = df_gesture[df_gesture[\"sex\"] == 1]\n\nif (female_group[\"sequence_id\"].nunique() == 0) or (male_group[\"sequence_id\"].nunique() == 0):\n    raise ValueError(\"Not enough examples for each sex. Try a different gesture.\")\n\nfemale_seq = female_group[\"sequence_id\"].unique()[0]\nmale_seq   = male_group[\"sequence_id\"].unique()[0]\n\ndf_fem = train_df[train_df[\"sequence_id\"] == female_seq].sort_values(\"sequence_counter\")\ndf_male= train_df[train_df[\"sequence_id\"] == male_seq].sort_values(\"sequence_counter\")\n\n# Tính toán các tính năng IMU được suy ra\nfor df in (df_fem, df_male):\n    df[\"acc_mag\"] = np.sqrt(df[\"acc_x\"]**2 + df[\"acc_y\"]**2 + df[\"acc_z\"]**2)\n    df[\"rot_w_clipped\"] = df[\"rot_w\"].clip(-1, 1)\n    df[\"rot_angle\"] = 2 * np.arccos(df[\"rot_w_clipped\"])\n\n# Tái sử dụng chức năng đổ bóng\ndef shade_phases(ax, seq_df):\n    used = set()\n    for phase_label, color in [(\"Transition\", \"lightgray\"), (\"Gesture\", \"lightcoral\")]:\n        mask = seq_df[\"phase\"] == phase_label\n        if not mask.any():\n            continue\n        idxs = seq_df.index[mask]\n        diffs = np.where(np.diff(idxs) != 1)[0]\n        spans = []\n        start = idxs[0]\n        for b in diffs:\n            end = idxs[b]\n            spans.append((start, end))\n            start = idxs[b + 1]\n        spans.append((start, idxs[-1]))\n        for (s, e) in spans:\n            t0 = seq_df.loc[s, \"sequence_counter\"]\n            t1 = seq_df.loc[e, \"sequence_counter\"]\n            label = phase_label if phase_label not in used else None\n            ax.axvspan(t0, t1, color=color, alpha=0.3, label=label)\n            used.add(phase_label)\n\n# --------------------------------------------------------------------------------\n# C.1: IMU – Độ lớn gia tốc\n# --------------------------------------------------------------------------------\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_fem, df_male],\n    [f\"Female (Subj: {df_fem['subject'].iloc[0]}, Sex=0)\",\n     f\"Male   (Subj: {df_male['subject'].iloc[0]}, Sex=1)\"]\n):\n    ax.plot(df_seq[\"sequence_counter\"], df_seq[\"acc_mag\"],\n            color=\"tab:blue\", lw=1.5, label=\"|acc|(m/s²)\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Acceleration Magnitude\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Sex: Acceleration Magnitude – Female vs. Male\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# C.2: IMU – Góc quay\n# --------------------------------------------------------------------------------\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_fem, df_male],\n    [f\"Female (Subj: {df_fem['subject'].iloc[0]}, Sex=0)\",\n     f\"Male   (Subj: {df_male['subject'].iloc[0]}, Sex=1)\"]\n):\n    ax.plot(df_seq[\"sequence_counter\"], df_seq[\"rot_angle\"],\n            color=\"tab:orange\", lw=1.5, label=\"rot_angle (rad)\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Rotation Angle (rad)\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Sex: Rotation Angle – Female vs. Male\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# C.3: Nhiệt điện – 5 kênh + Trung bình\n# --------------------------------------------------------------------------------\nfor df in (df_fem, df_male):\n    thm_cols = [f\"thm_{i}\" for i in range(1,6)]\n    df[\"thm_mean\"] = df[thm_cols].mean(axis=1)\n\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_fem, df_male],\n    [f\"Female (Subj: {df_fem['subject'].iloc[0]}, Sex=0)\",\n     f\"Male   (Subj: {df_male['subject'].iloc[0]}, Sex=1)\"]\n):\n    times = df_seq[\"sequence_counter\"]\n    for col in [f\"thm_{i}\" for i in range(1,6)]:\n        ax.plot(times, df_seq[col], lw=1, label=col, alpha=0.7)\n    ax.plot(times, df_seq[\"thm_mean\"], color=\"black\", lw=2.5, label=\"thm_mean\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Thermopile (°C)\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\", ncol=2)\n    ax.grid(True)\nplt.suptitle(\"Sex: Thermopile – Female vs. Male\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# C.4: ToF – Năm kênh khoảng cách trung bình\n# --------------------------------------------------------------------------------\nfor df in (df_fem, df_male):\n    for i_sensor in range(1,6):\n        pixel_cols = [f\"tof_{i_sensor}_v{pix}\" for pix in range(64)]\n        df[f\"tof_{i_sensor}_mean\"] = df[pixel_cols].replace(-1, np.nan).mean(axis=1)\n\nmean_cols = [f\"tof_{i}_mean\" for i in range(1,6)]\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_fem, df_male],\n    [f\"Female (Subj: {df_fem['subject'].iloc[0]}, Sex=0)\",\n     f\"Male   (Subj: {df_male['subject'].iloc[0]}, Sex=1)\"]\n):\n    times = df_seq[\"sequence_counter\"]\n    for col in mean_cols:\n        ax.plot(times, df_seq[col], lw=1, label=col)\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"ToF Mean Distance\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Sex: ToF Mean Distance – Female vs. Male\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:23:58.663184Z","iopub.execute_input":"2025-08-07T02:23:58.663426Z","iopub.status.idle":"2025-08-07T02:24:02.101879Z","shell.execute_reply.started":"2025-08-07T02:23:58.663406Z","shell.execute_reply":"2025-08-07T02:24:02.100644Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### 📌  Đặc trưng đang xem xét: **Chiều dài từ vai đến cổ tay**\n\nTrong phần này, dữ liệu được tách theo nhóm chiều dài tay (tay ngắn vs. tay dài) cho một cử chỉ cụ thể (ví dụ: “Eyelash – pull hair”). Ta trực quan hóa cùng lúc bốn tín hiệu cảm biến. Người có cánh tay dài hơn có thể tiếp cận khuôn mặt với góc độ và tốc độ khác, từ đó ảnh hưởng tới toàn bộ dấu hiệu từ cảm biến.\n\n**⏩ How to Interpret & Implications:**\n\n* **IMU:** Những người có tay dài thường có đồ thị acc_mag mượt hơn (ít thay đổi đột ngột hơn) vì cánh tay dài tạo ra gia tốc góc chậm hơn. Mô hình nên học cách chuẩn hóa theo chiều dài tay để tránh nhầm lẫn giữa độ mượt của chuyển động với việc không thực hiện cử chỉ mạnh.\n* **Rotation Angle:**  Người tay dài có thể có đỉnh rot_angle thấp hơn (do quỹ đạo lớn hơn nhưng tốc độ xoay chậm hơn). Bộ lọc thời gian của mô hình cần điều chỉnh cho phù hợp, tránh hiểu nhầm xoay chậm thành cử chỉ khác.\n* **Thermopile:** Tay dài có thể khiến các cảm biến nhiệt phát hiện nhiệt muộn hơn (do cổ tay phải di chuyển xa hơn mới tiếp cận được bề mặt cơ thể). Việc huấn luyện mô hình với cả hai nhóm chiều dài tay giúp tránh việc nhầm lẫn sự trễ này thành không có tiếp xúc.\n* **ToF:** Đối với người tay dài, khoảng cách ToF bắt đầu giảm ở giá trị xa hơn (tay di chuyển qua nhiều không gian hơn trước khi chạm vào mục tiêu). Mô hình cần học cách đặt ngưỡng khoảng cách phù hợp theo chiều dài tay để giảm lỗi bỏ sót khi tiếp xúc diễn ra ở xa hơn.","metadata":{}},{"cell_type":"markdown","source":"⏩ Plots for All Subjects and One Gesture","metadata":{}},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# ─── Tóm tắt cấp độ chuỗi cho cẳng tay ngắn so với cẳng tay dài ───────────────────────────────────────────────────────────────────\n# Chúng tôi xác định 4 số liệu được điều chỉnh như trước, nhưng nhóm theo\n# 'ngắn' = cm từ vai đến cổ tay ≤ trung vị\n# 'dài' = cm từ vai đến cổ tay > trung vị\n\n# 1) Chọn cử chỉ (giống như vẽ ô)\ngesture_to_plot = \"Eyelash - pull hair\"\ndf_g = train_df[train_df[\"gesture\"] == gesture_to_plot].copy()\n\n# 2) Xác định chiều dài cẳng tay trung bình\nmedian_len = df_g[\"shoulder_to_wrist_cm\"].median()\n\n# 3) Tóm tắt xây dựng\nrecords = []\nfor seq_id, seq in train_df[train_df[\"gesture\"] == gesture_to_plot].groupby(\"sequence_id\"):\n    L = seq[\"shoulder_to_wrist_cm\"].iloc[0]\n    grp = \"Short\" if L <= median_len else \"Long\"\n    # IMU jerk\n    acc_mag = np.sqrt(seq[\"acc_x\"]**2 + seq[\"acc_y\"]**2 + seq[\"acc_z\"]**2)\n    jerks = np.abs(np.diff(acc_mag))\n    max_jerk = jerks.max() if len(jerks)>0 else np.nan\n    # Phạm vi quay trong Cử chỉ\n    rot = 2*np.arccos(seq[\"rot_w\"].clip(-1,1))\n    rot_rng = rot[seq[\"phase\"]==\"Gesture\"].max() - rot[seq[\"phase\"]==\"Gesture\"].min() \\\n              if (seq[\"phase\"]==\"Gesture\").any() else np.nan\n    # Delta đỉnh nhiệt điện\n    thm_cols = [f\"thm_{i}\" for i in range(1,6)]\n    thm_mean = seq[thm_cols].mean(axis=1)\n    base = thm_mean[seq[\"phase\"]==\"Transition\"].median()\n    peak_dt = thm_mean[seq[\"phase\"]==\"Gesture\"].max() - base \\\n              if (seq[\"phase\"]==\"Gesture\").any() else np.nan\n    # ToF min trung bình\n    tof_means = []\n    for i in range(1,6):\n        pix = seq[[f\"tof_{i}_v{p}\" for p in range(64)]].replace(-1,np.nan)\n        tof_means.append(pix.mean(axis=1))\n    tof_all = pd.concat(tof_means,axis=1).mean(axis=1)\n    min_tof = tof_all.min()\n    records.append({\n        \"Group\":         grp,\n        \"max_jerk\":      max_jerk,\n        \"rot_range\":     rot_rng,\n        \"thm_peak_dt\":   peak_dt,\n        \"tof_min_dist\":  min_tof\n    })\n\nsummary = pd.DataFrame(records).dropna()\n\n# ─── Vẽ violin+box được thiết kế riêng cho từng số liệu ─ ...\nsns.set_style(\"whitegrid\")\nfig, axes = plt.subplots(2, 2, figsize=(14,10))\nplt.subplots_adjust(hspace=0.4, wspace=0.3)\n\nmetrics = [\n    (\"max_jerk\",     \"Max Jerk |Δacc| (m/s²)\"),\n    (\"rot_range\",    \"Rotation Range in Gesture (rad)\"),\n    (\"thm_peak_dt\",  \"Thermopile Peak ΔT (°C)\"),\n    (\"tof_min_dist\", \"Minimum ToF Mean Dist.\")\n]\n\nfor ax, (col, label) in zip(axes.flatten(), metrics):\n    sns.violinplot(data=summary, x=\"Group\", y=col, palette=\"Set2\", ax=ax, inner=None)\n    sns.boxplot(data=summary, x=\"Group\", y=col,\n                width=0.2, showcaps=True, boxprops={'facecolor':'white'},\n                showfliers=False, whiskerprops={'linewidth':2}, ax=ax)\n    ax.set_title(label, fontsize=12)\n    ax.set_xlabel(\"\")\n    ax.set_ylabel(label, fontsize=10)\n\nfig.suptitle(f\"Forearm Length Comparison (≤{median_len:.1f}cm vs >{median_len:.1f}cm) (*Gesture Phase Only)\", fontsize=16)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:24:02.102928Z","iopub.execute_input":"2025-08-07T02:24:02.103357Z","iopub.status.idle":"2025-08-07T02:24:09.611447Z","shell.execute_reply.started":"2025-08-07T02:24:02.103325Z","shell.execute_reply":"2025-08-07T02:24:09.610570Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# ─── Tính toán thời lượng pha cho mỗi chuỗi cho tất cả các chuỗi ──────────────────────────────────────────────────\n# Giả sử train_df đã có cột shoulder_to_wrist_cm được hợp nhất và cột 'pha'\n\n# Đánh dấu mỗi chuỗi là Cánh tay ngắn so với Cánh tay dài dựa trên chiều dài trung bình của vai_to_wrist_cm trên tất cả các chuỗi\nmed = train_df.groupby('sequence_id')['shoulder_to_wrist_cm'].first().median()\nseq_meta = (\n    train_df\n    .groupby('sequence_id')\n    .agg({'shoulder_to_wrist_cm':'first'})\n    .reset_index()\n)\nseq_meta['ArmGroup'] = np.where(seq_meta['shoulder_to_wrist_cm'] <= med,\n                                'Short Arm', 'Long Arm')\n\n# Đếm số khung hình trên mỗi pha trên mỗi chuỗi\ndur = (\n    train_df\n    .groupby(['sequence_id','phase'])['sequence_counter']\n    .count()\n    .unstack(fill_value=0)\n    .reset_index()\n)\n\n# Hợp nhất trong nhãn nhóm cánh tay\ndur = dur.merge(seq_meta[['sequence_id','ArmGroup']], on='sequence_id')\n\n# Tan chảy thành dạng dài\ndur_long = dur.melt(id_vars=['sequence_id','ArmGroup'],\n                    value_vars=['Transition','Gesture'],\n                    var_name='Phase', value_name='Duration')\n\n# ─── Vẽ biểu đồ hộp theo chiều ngang: Cánh tay ngắn so với cánh tay dài ─ ...\nsns.set_style(\"whitegrid\")\nfig, axes = plt.subplots(1, 2, figsize=(14,5), sharey=True)\nfor ax, grp in zip(axes, ['Short Arm','Long Arm']):\n    sub = dur_long[dur_long['ArmGroup']==grp]\n    sns.boxplot(x='Duration', y='Phase', data=sub,\n                palette=['lightgray','lightcoral'], ax=ax)\n    ax.set_title(f\"{grp} (≤ {med:.1f} cm)\", fontsize=12)\n    ax.set_xlabel(\"Duration (frames)\", fontsize=10)\n    ax.set_ylabel(\"Phase\", fontsize=10)\n    ax.grid(True)\n\nplt.suptitle(\"Phase Durations by Arm Length Group\", fontsize=14, y=1.05)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:24:09.612545Z","iopub.execute_input":"2025-08-07T02:24:09.612865Z","iopub.status.idle":"2025-08-07T02:24:10.292591Z","shell.execute_reply.started":"2025-08-07T02:24:09.612838Z","shell.execute_reply":"2025-08-07T02:24:10.291614Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"⏩ Biểu đồ cho các đối tượng được chọn ngẫu nhiên và một cử chỉ","metadata":{}},{"cell_type":"code","source":"# ─── Ô D: So sánh chiều dài từ vai đến cổ tay (ngắn so với dài) đối với IMU, Rot, Thermopile và ToF ───────────\n# (2) Chọn một cử chỉ để so sánh (thay đổi nếu cần)\ngesture_to_plot = \"Eyelash - pull hair\"\n\n# (3) Lọc theo cử chỉ đó và chia theo chiều dài từ vai đến cổ tay bằng cách sử dụng trung vị\ndf_gesture = train_df[train_df[\"gesture\"] == gesture_to_plot].copy()\nmedian_shoulder = df_gesture[\"shoulder_to_wrist_cm\"].median()\n\nshort_group = df_gesture[df_gesture[\"shoulder_to_wrist_cm\"] <= median_shoulder]\nlong_group  = df_gesture[df_gesture[\"shoulder_to_wrist_cm\"]  > median_shoulder]\n\nif (short_group[\"sequence_id\"].nunique() == 0) or (long_group[\"sequence_id\"].nunique() == 0):\n    raise ValueError(\"Not enough examples in one of the length‐groups. Try another gesture or adjust threshold.\")\n\nshort_seq = short_group[\"sequence_id\"].unique()[0]\nlong_seq  = long_group[\"sequence_id\"].unique()[0]\n\ndf_short = train_df[train_df[\"sequence_id\"] == short_seq].sort_values(\"sequence_counter\")\ndf_long  = train_df[train_df[\"sequence_id\"] == long_seq].sort_values(\"sequence_counter\")\n\n# Tính toán các tính năng có nguồn gốc từ IMU\nfor df in (df_short, df_long):\n    df[\"acc_mag\"] = np.sqrt(df[\"acc_x\"]**2 + df[\"acc_y\"]**2 + df[\"acc_z\"]**2)\n    df[\"rot_w_clipped\"] = df[\"rot_w\"].clip(-1, 1)\n    df[\"rot_angle\"] = 2 * np.arccos(df[\"rot_w_clipped\"])\n\n# Tái sử dụng chức năng đổ bóng\ndef shade_phases(ax, seq_df):\n    used = set()\n    for phase_label, color in [(\"Transition\", \"lightgray\"), (\"Gesture\", \"lightcoral\")]:\n        mask = seq_df[\"phase\"] == phase_label\n        if not mask.any():\n            continue\n        idxs = seq_df.index[mask]\n        diffs = np.where(np.diff(idxs) != 1)[0]\n        spans = []\n        start = idxs[0]\n        for b in diffs:\n            end = idxs[b]\n            spans.append((start, end))\n            start = idxs[b + 1]\n        spans.append((start, idxs[-1]))\n        for (s, e) in spans:\n            t0 = seq_df.loc[s, \"sequence_counter\"]\n            t1 = seq_df.loc[e, \"sequence_counter\"]\n            label = phase_label if phase_label not in used else None\n            ax.axvspan(t0, t1, color=color, alpha=0.3, label=label)\n            used.add(phase_label)\n\n# --------------------------------------------------------------------------------\n# D.1: IMU – Độ lớn gia tốc\n# --------------------------------------------------------------------------------\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_short, df_long],\n    [f\"Short Arm (Shoulder‐Wrist ≤ {median_shoulder:.1f} cm)\",\n     f\"Long Arm (Shoulder‐Wrist > {median_shoulder:.1f} cm)\"]\n):\n    ax.plot(df_seq[\"sequence_counter\"], df_seq[\"acc_mag\"],\n            color=\"tab:blue\", lw=1.5, label=\"|acc|(m/s²)\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Acceleration Magnitude\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Arm Length: Acceleration Magnitude – Short vs. Long\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# D.2: IMU – Góc quay\n# --------------------------------------------------------------------------------\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_short, df_long],\n    [f\"Short Arm (Shoulder‐Wrist ≤ {median_shoulder:.1f} cm)\",\n     f\"Long Arm (Shoulder‐Wrist > {median_shoulder:.1f} cm)\"]\n):\n    ax.plot(df_seq[\"sequence_counter\"], df_seq[\"rot_angle\"],\n            color=\"tab:orange\", lw=1.5, label=\"rot_angle (rad)\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Rotation Angle (rad)\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Arm Length: Rotation Angle – Short vs. Long\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# D.3: Nhiệt điện – 5 kênh + Trung bình\n# --------------------------------------------------------------------------------\nfor df in (df_short, df_long):\n    thm_cols = [f\"thm_{i}\" for i in range(1,6)]\n    df[\"thm_mean\"] = df[thm_cols].mean(axis=1)\n\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_short, df_long],\n    [f\"Short Arm (Shoulder‐Wrist ≤ {median_shoulder:.1f} cm)\",\n     f\"Long Arm (Shoulder‐Wrist > {median_shoulder:.1f} cm)\"]\n):\n    times = df_seq[\"sequence_counter\"]\n    for col in [f\"thm_{i}\" for i in range(1,6)]:\n        ax.plot(times, df_seq[col], lw=1, label=col, alpha=0.7)\n    ax.plot(times, df_seq[\"thm_mean\"], color=\"black\", lw=2.5, label=\"thm_mean\")\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"Thermopile (°C)\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\", ncol=2)\n    ax.grid(True)\nplt.suptitle(\"Arm Length: Thermopile – Short vs. Long\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()\n\n# --------------------------------------------------------------------------------\n# D.4: ToF – Năm kênh khoảng cách trung bình\n# --------------------------------------------------------------------------------\nfor df in (df_short, df_long):\n    for i_sensor in range(1,6):\n        pixel_cols = [f\"tof_{i_sensor}_v{pix}\" for pix in range(64)]\n        df[f\"tof_{i_sensor}_mean\"] = df[pixel_cols].replace(-1, np.nan).mean(axis=1)\n\nmean_cols = [f\"tof_{i}_mean\" for i in range(1,6)]\nfig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)\nfor ax, df_seq, title in zip(\n    axes,\n    [df_short, df_long],\n    [f\"Short Arm (Shoulder‐Wrist ≤ {median_shoulder:.1f} cm)\",\n     f\"Long Arm (Shoulder‐Wrist > {median_shoulder:.1f} cm)\"]\n):\n    times = df_seq[\"sequence_counter\"]\n    for col in mean_cols:\n        ax.plot(times, df_seq[col], lw=1, label=col)\n    shade_phases(ax, df_seq)\n    ax.set_title(f\"{title}\\nGesture: {gesture_to_plot}\", fontsize=11)\n    ax.set_xlabel(\"Sequence Counter\", fontsize=9)\n    ax.set_ylabel(\"ToF Mean Distance\", fontsize=9)\n    ax.legend(loc=\"upper right\", fontsize=\"small\")\n    ax.grid(True)\nplt.suptitle(\"Arm Length: ToF Mean Distance – Short vs. Long\", fontsize=13)\nplt.tight_layout(rect=[0, 0, 1, 0.95])\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:24:10.293936Z","iopub.execute_input":"2025-08-07T02:24:10.294303Z","iopub.status.idle":"2025-08-07T02:24:13.781689Z","shell.execute_reply.started":"2025-08-07T02:24:10.294278Z","shell.execute_reply":"2025-08-07T02:24:13.780533Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3.3.3 Correlation analysis","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport pandas as pd\n\n# Kết hợp đào tạo và kiểm tra cho cả hai tập dữ liệu\ncombined_main = pd.concat([train_df, test_df], axis=0)\ncombined_demo = pd.concat([train_dem_df, test_dem_df], axis=0)\n\n# Đặt mục tiêu\ntarget_variable = 'gesture'\n\n# Xác định tiền tố cảm biến và danh sách loại trừ\nsensor_prefixes = ('acc_', 'rot_', 'thm_', 'tof_')\nexcluded_columns = [target_variable]\n\n# Xác định các biến số có thể sử dụng (không phải cảm biến, chỉ số)\ndef get_numerical_columns(df, exclude_prefixes, excluded_cols):\n    return [col for col in df.columns \n            if pd.api.types.is_numeric_dtype(df[col]) \n            and not col.startswith(exclude_prefixes)\n            and col not in excluded_cols]\n\nmain_vars = get_numerical_columns(combined_main, sensor_prefixes, excluded_columns)\ndemo_vars = get_numerical_columns(combined_demo, (), excluded_columns)\n\n# Kết hợp cả hai để có mối tương quan toàn cầu\ncombined_data = pd.concat([\n    combined_main[main_vars].reset_index(drop=True),\n    combined_demo[demo_vars].reset_index(drop=True)\n], axis=1)\n\n# Tính toán tương quan và che dấu tam giác trên\ncorr_all = combined_data.corr()\nmask_all = np.triu(np.ones_like(corr_all, dtype=bool))\n\n# Plot\nplt.figure(figsize=(18, 10))\nax = sns.heatmap(\n    corr_all, mask=mask_all, cmap='viridis', annot=True, \n    square=False, linewidths=.5, annot_kws={\"size\": 12}\n)\nplt.title('Correlation Heatmap — Combined (Demographic + Main) Data', fontsize=16)\n\n# Rotate x-axis tick labels\nax.set_xticklabels(ax.get_xticklabels(), rotation=45, ha='right', fontsize=11)\nax.set_yticklabels(ax.get_yticklabels(), fontsize=11)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:24:13.783201Z","iopub.execute_input":"2025-08-07T02:24:13.783491Z","iopub.status.idle":"2025-08-07T02:24:15.444135Z","shell.execute_reply.started":"2025-08-07T02:24:13.783468Z","shell.execute_reply":"2025-08-07T02:24:15.443104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.preprocessing import LabelEncoder\n\n# Sao chép và mã hóa mục tiêu\ntrain_corr_df = train_df.copy()\ntrain_corr_df[target_variable] = LabelEncoder().fit_transform(train_corr_df[target_variable])\n\n# Trích xuất các đặc điểm số hợp lệ chỉ từ tàu hỏa\ntrain_main_vars = get_numerical_columns(train_corr_df, sensor_prefixes, excluded_columns)\n\n# Bao gồm cả các đặc điểm nhân khẩu học\ntrain_demo_vars = get_numerical_columns(train_dem_df, (), excluded_columns)\n\n# Kết hợp cả hai\ntrain_all_corr = pd.concat([train_corr_df[train_main_vars], train_dem_df[train_demo_vars],\n                            train_corr_df[[target_variable]]], axis=1)\n\n# Chỉ tương quan với mục tiêu\ncorr_target_only = train_all_corr.corr()[[target_variable]].T\n\n# Plot\nplt.figure(figsize=(12, 3))\nsns.heatmap(corr_target_only, cmap='viridis', annot=True, linewidths=0.5, cbar=False, annot_kws={\"size\": 10})\nplt.title(\"Correlation with Target (gesture) — Train Data\", fontsize=13)\nplt.xticks(rotation=45, ha='right')\nplt.yticks(rotation=0)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-07T02:24:15.445189Z","iopub.execute_input":"2025-08-07T02:24:15.445510Z","iopub.status.idle":"2025-08-07T02:24:18.126545Z","shell.execute_reply.started":"2025-08-07T02:24:15.445486Z","shell.execute_reply":"2025-08-07T02:24:18.125699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}