{"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":"## ⚪ Libraries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.patches as mpatches\nimport matplotlib.animation as animation\nfrom IPython.display import display, Image\nfrom itertools import combinations\nimport seaborn as sns\nimport imageio\nimport random\nimport re\nimport os\n\nsns.set()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:54:22.893383Z","iopub.execute_input":"2025-08-02T21:54:22.893773Z","iopub.status.idle":"2025-08-02T21:54:26.736645Z","shell.execute_reply.started":"2025-08-02T21:54:22.893742Z","shell.execute_reply":"2025-08-02T21:54:26.735795Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install pandas numpy matplotlib imageio","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:54:26.738461Z","iopub.execute_input":"2025-08-02T21:54:26.739055Z","iopub.status.idle":"2025-08-02T21:54:31.709751Z","shell.execute_reply.started":"2025-08-02T21:54:26.739028Z","shell.execute_reply":"2025-08-02T21:54:31.708490Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ⚪ Args","metadata":{}},{"cell_type":"code","source":"Dir = \"/kaggle/input/cmi-detect-behavior-with-sensor-data\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:54:31.711041Z","iopub.execute_input":"2025-08-02T21:54:31.711512Z","iopub.status.idle":"2025-08-02T21:54:31.716984Z","shell.execute_reply.started":"2025-08-02T21:54:31.711464Z","shell.execute_reply":"2025-08-02T21:54:31.715540Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train = pd.read_csv(f\"{Dir}/train.csv\") \ndf_demographic = pd.read_csv(f\"{Dir}/train_demographics.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:54:31.718888Z","iopub.execute_input":"2025-08-02T21:54:31.719139Z","iopub.status.idle":"2025-08-02T21:55:05.403241Z","shell.execute_reply.started":"2025-08-02T21:54:31.719118Z","shell.execute_reply":"2025-08-02T21:55:05.402350Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ⚪ EDA Training data","metadata":{}},{"cell_type":"code","source":"df_demographic.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:05.404185Z","iopub.execute_input":"2025-08-02T21:55:05.404454Z","iopub.status.idle":"2025-08-02T21:55:05.433300Z","shell.execute_reply.started":"2025-08-02T21:55:05.404431Z","shell.execute_reply":"2025-08-02T21:55:05.432590Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:05.434219Z","iopub.execute_input":"2025-08-02T21:55:05.434499Z","iopub.status.idle":"2025-08-02T21:55:05.462283Z","shell.execute_reply.started":"2025-08-02T21:55:05.434478Z","shell.execute_reply":"2025-08-02T21:55:05.461334Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train.shape\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:05.463564Z","iopub.execute_input":"2025-08-02T21:55:05.463903Z","iopub.status.idle":"2025-08-02T21:55:05.477481Z","shell.execute_reply.started":"2025-08-02T21:55:05.463869Z","shell.execute_reply":"2025-08-02T21:55:05.476459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:05.478487Z","iopub.execute_input":"2025-08-02T21:55:05.478764Z","iopub.status.idle":"2025-08-02T21:55:05.639686Z","shell.execute_reply.started":"2025-08-02T21:55:05.478735Z","shell.execute_reply":"2025-08-02T21:55:05.638627Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ⚪ Statistical Analysis","metadata":{}},{"cell_type":"code","source":"df_train.describe(include='all')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:05.640501Z","iopub.execute_input":"2025-08-02T21:55:05.640852Z","iopub.status.idle":"2025-08-02T21:55:14.483850Z","shell.execute_reply.started":"2025-08-02T21:55:05.640822Z","shell.execute_reply":"2025-08-02T21:55:14.482917Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# We know that all records within a single sequence have the same 'sequence_type'.\n# This means we don't need to check every row. We can look at just one row per sequence.\nunique_sequences_df = df_train.drop_duplicates(subset=['sequence_id'])\n# Now, unique_sequences_df contains exactly one row for each of the 8151 sequences.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:14.486866Z","iopub.execute_input":"2025-08-02T21:55:14.487109Z","iopub.status.idle":"2025-08-02T21:55:14.542463Z","shell.execute_reply.started":"2025-08-02T21:55:14.487091Z","shell.execute_reply":"2025-08-02T21:55:14.541680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train.gesture.value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:14.543288Z","iopub.execute_input":"2025-08-02T21:55:14.543581Z","iopub.status.idle":"2025-08-02T21:55:14.595707Z","shell.execute_reply.started":"2025-08-02T21:55:14.543555Z","shell.execute_reply":"2025-08-02T21:55:14.594852Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train.phase.value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:14.596709Z","iopub.execute_input":"2025-08-02T21:55:14.596997Z","iopub.status.idle":"2025-08-02T21:55:14.655621Z","shell.execute_reply.started":"2025-08-02T21:55:14.596967Z","shell.execute_reply":"2025-08-02T21:55:14.654869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train.behavior.value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:14.656786Z","iopub.execute_input":"2025-08-02T21:55:14.657128Z","iopub.status.idle":"2025-08-02T21:55:14.715224Z","shell.execute_reply.started":"2025-08-02T21:55:14.657100Z","shell.execute_reply":"2025-08-02T21:55:14.714259Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":">#### 🟠 Note ✋  \n>##### A breakdown of the event types reveals four distinct phases in the dataset. Among these, we can clearly identify two key transitional states:\n>\n>- **Active Transition:** `Moves hand to target location`  \n>- **Relaxed Transition:** `Relaxes and moves hand to target location`  \n>\n>These transitions represent the moments the hand is in motion, either actively moving towards a goal or relaxing and repositioning Bewegungen. Understanding the distribution of these phases is crucial for modeling the complete sequence of actions. 🎯\n\n>#### ⚠️ Observation  \n>##### While the `phase` column is expected to include `transition`, `pause`, and `gesture`, only two are present in the training set:\n>\n>```\n>Transition    319128  \n>Gesture       255817\n>```\n>\n>The `pause` phase appears to be missing, which may impact phase-based modeling. 📉\n\n>#### 🔍 Clarification 🧩  \n>##### To better understand this discrepancy, we examine the `behavior` column, which contains four detailed action labels:\n>\n>- `Performs gesture` → corresponds to **gesture**  \n>- `Hand at target location` → corresponds to **pause**  \n>- `Moves hand to target location` → part of **transition**  \n>- `Relaxes and moves hand to target location` → part of **transition**  \n>\n>These `behavior` labels provide a finer-grained description of the phases, and indicate that although the `pause` phase is missing from the `phase` column, it is implicitly present via the `Hand at target location` behavior. Therefore, phase reconstruction or relabeling may be necessary for complete phase modeling. 🛠️\n","metadata":{}},{"cell_type":"markdown","source":"## ⚪ Missing Values Analysis","metadata":{}},{"cell_type":"code","source":"# Specify the columns \ncolumns_of_interest = ['rot_x', 'thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5', 'tof_1_v10', 'tof_2_v10', 'tof_3_v10', 'tof_4_v10', 'tof_5_v10'] \n\n# Calculate and print missing value counts for these columns\nprint(\"--- Missing values for specific columns ---\")\nfor col in columns_of_interest:\n    missing_count = df_train[col].isnull().sum()\n    print(f\"{col}: {missing_count}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:14.716297Z","iopub.execute_input":"2025-08-02T21:55:14.716602Z","iopub.status.idle":"2025-08-02T21:55:14.755698Z","shell.execute_reply.started":"2025-08-02T21:55:14.716570Z","shell.execute_reply":"2025-08-02T21:55:14.754682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Calculate the number of missing values for each column\nmissing_values_per_column = df_train.isnull().sum()\n\n# 2. Filter and display only the columns that have at least one missing value\ncolumns_with_missing_values = missing_values_per_column[missing_values_per_column > 0].sort_values(ascending=False)\n\n# 3. Calculate the total number of missing values in the entire DataFrame\ntotal_missing_values = missing_values_per_column.sum()\n\n# 4. Calculate the total percentage of missing values relative to the entire dataset\ntotal_cells = np.product(df_train.shape)\npercentage_missing = (total_missing_values / total_cells) * 100\n\n# Print the Results\nprint(\"--- Missing Values Report ---\")\nprint(\"\")\n\nif total_missing_values > 0:\n    print(f\"✅ Total number of missing values in the dataset: {total_missing_values}\")\n    print(f\"📊 Total percentage of missing data: {percentage_missing:.2f}%\")\n    print(\"---------------------------------------------\")\n    print(\"Number of missing values per column (only for columns with missing data):\")\n    print(columns_with_missing_values)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:14.756566Z","iopub.execute_input":"2025-08-02T21:55:14.756954Z","iopub.status.idle":"2025-08-02T21:55:15.611739Z","shell.execute_reply.started":"2025-08-02T21:55:14.756933Z","shell.execute_reply":"2025-08-02T21:55:15.610843Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> #### 🟠 **Missing Sensor Data Analysis**  🧐\n> \n>Our dataset contains missing values, primarily in the **rotational (`rot_*`)**, **thermopile (`thm_*`)**, and **Time-of-Flight (`tof_*`)** sensor columns. This is a common issue, likely due to sensor dropouts.\n>\n> A key pattern is that missingness appears to be synchronized: when data from one rotational or ToF sensor is missing, its sibling sensors are often missing data for the same timestep as well.\n>\n>---\n>\n>#### ⚠️⚠️⚠️ **CRITICAL ALERT: Potential Hardware Fault** ⚠️⚠️⚠️\n>\n>A severe anomaly has been identified that requires immediate attention:\n>\n>- The **`thm_5`** and **`tof_5`** sensor arrays have approximately **5 times more missing data** than their counterparts.\n>- Since these two sensors are physically adjacent on the device hardware, this pattern strongly suggests a **localized hardware fault or a persistent data logging issue**.\n>\n>This is not random noise and will negatively impact model performance if not handled properly.\n>\n>---\n>\n>### 📋 **Recommendations**\n>\n>1.  **Flag for Exclusion**: The `thm_5` and `tof_5` sensors should be considered highly unreliable. It is strongly recommended to **exclude them from model training**.\n>2.  **Document**: This finding must be documented and communicated to the project team.\n>3.  **Strategy**: Before any modeling, a clear strategy for handling these (and other) missing values is required.\n>\n>🩺 Proactive data quality checks like this are vital for building a robust model.\n>","metadata":{}},{"cell_type":"markdown","source":"## ⚪ Subject Analysis","metadata":{}},{"cell_type":"code","source":"# --- Sequence count per subject analysis ---\n# This analysis shows how many unique sequences each subject has performed.\n\n# Use unique_sequences_df (already created, one row per sequence)\n# Group by 'subject' and count the number of occurrences (i.e., sequences) for each subject.\nsequences_per_subject = unique_sequences_df['subject'].value_counts().sort_index()\n\n# --- Visualization ---\nplt.figure(figsize=(18, 6))\n\n# Barplot: x-axis is subject ID, y-axis is number of sequences for that subject\nsns.barplot(x=sequences_per_subject.index, y=sequences_per_subject.values, color='skyblue')\n\nplt.title('Number of Unique Sequences per Subject', fontsize=16, pad=15)\nplt.xlabel('Subject ID', fontsize=12)\nplt.ylabel('Number of Sequences', fontsize=12)\nplt.xticks(rotation=90, fontsize=9)  # Rotate subject IDs for readability\n\n# Add the count above each bar for clarity (optional, remove if plot is crowded)\n# for i, val in enumerate(sequences_per_subject.values):\n#     plt.text(i, val + 0.5, str(val), ha='center', va='bottom', fontsize=8)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:15.612767Z","iopub.execute_input":"2025-08-02T21:55:15.613112Z","iopub.status.idle":"2025-08-02T21:55:16.978882Z","shell.execute_reply.started":"2025-08-02T21:55:15.613084Z","shell.execute_reply":"2025-08-02T21:55:16.977866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Randomly select a subject_id from the dataframe\nsubject_id = random.choice(unique_sequences_df[\"subject\"].unique())\n\n# Filter the dataframe for the selected subject\nsubject_sequences = unique_sequences_df[unique_sequences_df['subject'] == subject_id]\n\n# Create a pivot table: Rows = gestures, Columns = orientations, Values = sequence count\npivot_table = subject_sequences.pivot_table(\n    index='gesture', \n    columns='orientation', \n    values='sequence_id',  # any column with unique values per sequence works\n    aggfunc='count',\n    fill_value=0\n)\n\n# Display the result\nprint(f\" ------------------------------ Sequence Count Table for {subject_id} : ------------------------------ \")\ndisplay(pivot_table)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:16.979994Z","iopub.execute_input":"2025-08-02T21:55:16.980236Z","iopub.status.idle":"2025-08-02T21:55:17.012964Z","shell.execute_reply.started":"2025-08-02T21:55:16.980212Z","shell.execute_reply":"2025-08-02T21:55:17.012014Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ⚪ Distribution Analysis","metadata":{}},{"cell_type":"code","source":"# --- Analysis: Count Unique Sequences ---\n# The 'sequence_id' column identifies each unique sequence.\nnum_unique_sequences = df_train['sequence_id'].nunique()\n\n# --- Display the Result ---\n# Print the result in a clear, formatted string.\nprint(f\"Total number of records (rows): {len(df_train)}\")\nprint(\"-\" * 47) # A separator for better readability\nprint(f\"Number of unique sequences in the dataset: {num_unique_sequences}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:17.013881Z","iopub.execute_input":"2025-08-02T21:55:17.014156Z","iopub.status.idle":"2025-08-02T21:55:17.050954Z","shell.execute_reply.started":"2025-08-02T21:55:17.014128Z","shell.execute_reply":"2025-08-02T21:55:17.049930Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Distribution of Sequence Counts per Subject ---\n# Group by 'subject' to count the number of unique sequences each subject participated in\nsequence_length = df_train.groupby('sequence_id').size()\nlength_counts = sequence_length.value_counts().sort_index()\n\n# --- Visualization ---\nplt.figure(figsize=(20,5))\n# Histogram: Number of sequences per user\nlength_counts.plot(kind='bar')\nplt.title(\"Distribution of Sequence length\")\nplt.xlabel(\"Sequence length\")\nplt.ylabel(\"Number of Sequence\")\nplt.grid(True)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:17.051866Z","iopub.execute_input":"2025-08-02T21:55:17.052147Z","iopub.status.idle":"2025-08-02T21:55:19.967183Z","shell.execute_reply.started":"2025-08-02T21:55:17.052117Z","shell.execute_reply":"2025-08-02T21:55:19.966302Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"length_counts","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:19.968108Z","iopub.execute_input":"2025-08-02T21:55:19.968348Z","iopub.status.idle":"2025-08-02T21:55:19.975404Z","shell.execute_reply.started":"2025-08-02T21:55:19.968328Z","shell.execute_reply":"2025-08-02T21:55:19.974673Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"length_counts.sort_values()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:19.976495Z","iopub.execute_input":"2025-08-02T21:55:19.976824Z","iopub.status.idle":"2025-08-02T21:55:19.992920Z","shell.execute_reply.started":"2025-08-02T21:55:19.976773Z","shell.execute_reply":"2025-08-02T21:55:19.992043Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Analysis: Count Sequence Types (Target vs. Non-Target) ---\n# W defined unique_sequences_df befor.\n# We can simply count the values in the 'sequence_type' column of this new DataFrame.\nsequence_type_counts = unique_sequences_df['sequence_type'].value_counts()\n\n\n# --- Visualization ---\n# A bar chart is a great way to visualize this distribution.\nplt.figure(figsize=(8, 6)) # Create a figure with a specific size for better visualization\n\n# Create the bar plot using the calculated counts.\n# The index of the Series (e.g., 'Target', 'Non-Target') becomes the x-axis labels.\n# The values of the Series become the height of the bars.\nbars = plt.bar(sequence_type_counts.index, sequence_type_counts.values, color=['#1a80bb', 'salmon'])\n\n# Add labels and a title to make the plot informative.\nplt.title('Distribution of Sequence Types', fontsize=16)\nplt.xlabel('Sequence Type', fontsize=12)\nplt.ylabel('Number of Unique Sequences', fontsize=12)\nplt.xticks(rotation=0) # Keep the x-axis labels horizontal for readability\n\n# Add the exact count on top of each bar for clarity.\nfor bar in bars:\n    yval = bar.get_height()\n    plt.text(bar.get_x() + bar.get_width()/2.0, yval, int(yval), va='bottom', ha='center', fontsize=12) # va='bottom' places text just above the bar\n\nplt.tight_layout() \nplt.show() # Display the plot\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:19.993913Z","iopub.execute_input":"2025-08-02T21:55:19.994242Z","iopub.status.idle":"2025-08-02T21:55:20.196822Z","shell.execute_reply.started":"2025-08-02T21:55:19.994212Z","shell.execute_reply":"2025-08-02T21:55:20.195724Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Analysis: Count Sequences per Gesture ---\n# This part of the code remains the same.\n# We still need the counts for all gestures to plot them.\n\n# Step 1: Group by the gesture and count the number of sequences in each group.\n# The value_counts() method on the 'gesture' column does this directly and efficiently.\ngesture_counts = unique_sequences_df['gesture'].value_counts()\n\n# --- Visualization with Two Colors ---\n# Step 2: Create a color mapping for the plot.\n# We need to create a list of colors where each color corresponds to a gesture in our gesture_counts series.\n\n# First, get the type for each gesture. We can do this by creating a mapping from gesture to sequence_type.\n# drop_duplicates() ensures we have a clean mapping of each gesture to its single type.\ngesture_to_type_map = unique_sequences_df.drop_duplicates(subset=['gesture'])[['gesture', 'sequence_type']].set_index('gesture')['sequence_type']\n\n# Now, create the color list based on the order of gestures in 'gesture_counts.index'.\ncolors = ['#1a80bb' if gesture_to_type_map[gesture] == 'Target' else 'salmon' for gesture in gesture_counts.index]\n# We've chosen a standard blue for 'Target' and orange for 'Non-Targett'.\n\n# Step 3: Plot the distribution using the new color mapping.\nplt.figure(figsize=(14, 8)) # A slightly wider figure for better readability.\n\n# Using Seaborn's barplot, but passing our custom color list to the 'palette' argument.\nax = sns.barplot(x=gesture_counts.index, y=gesture_counts.values, palette=colors)\n\n# Step 4: Add labels, title, and rotate x-axis labels.\nplt.title('Number of Unique Sequences per Gesture (Colored by Type)', fontsize=18, pad=20)\nplt.xlabel('Gesture', fontsize=14)\nplt.ylabel('Number of Unique Sequences', fontsize=14)\nplt.xticks(rotation=45, ha='right') # Rotate labels to prevent them from overlapping.\n\n# Step 5: Add the count value on top of each bar for clarity.\nfor p in ax.patches:\n    ax.annotate(f'{int(p.get_height())}', \n                (p.get_x() + p.get_width() / 2., p.get_height()), \n                ha='center', va='center', \n                xytext=(0, 9), \n                textcoords='offset points')\n\n# Step 6: Create and add a custom legend to explain the colors.\ntarget_patch = mpatches.Patch(color='#1a80bb', label='Target')\nnontarget_patch = mpatches.Patch(color='salmon', label='Non-Target')\nplt.legend(handles=[target_patch, nontarget_patch], title=\"Sequence Type\", fontsize='large')\n\nplt.tight_layout() \nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:20.197726Z","iopub.execute_input":"2025-08-02T21:55:20.197975Z","iopub.status.idle":"2025-08-02T21:55:20.791731Z","shell.execute_reply.started":"2025-08-02T21:55:20.197955Z","shell.execute_reply":"2025-08-02T21:55:20.790780Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> #### 🟠 Analysis of *gesture* Class Distribution (Sequence-Based) 📊\n>\n>This analysis breaks down the distribution of **8,151 unique sequences** across all 18 `gesture` labels. The goal is to identify class imbalances that could directly impact model performance.\n>\n> ---\n>\n> #### 🎯 **Analysis of the `Target` Group (5,113 Sequences)**\n>\n> *   **Status:** ✅ **Highly Balanced**.\n> *   **Key Finding:** The 8 `Target` classes have a nearly uniform distribution, which is ideal for training a robust model without bias.\n> \n>| Gesture (Target)           | Sequence Count |\n| :------------------------- | :------------- |\n| `Forehead - scratch`       | 640            |\n| `Forehead - pull hairline` | 640            |\n| `Neck - scratch`           | 640            |\n| `Neck - pinch skin`        | 640            |\n| `Eyelash - pull hair`      | 640            |\n| `Above ear - pull hair`    | 638            |\n| `Eyebrow - pull hair`      | 638            |\n| `Cheek - pinch skin`       | 637            |\n>\n> ---\n>\n> #### ❌ **Analysis of the `Non-Target` Group (3,038 Sequences)**\n>\n> *   **Status:** ⚠️ **Highly Imbalanced**.\n> *   **Key Finding:** There is a significant disparity between the most and least common gestures in this group, creating a modeling challenge.\n>\n>| Gesture (Non-Target)               | Sequence Count |\n| :--------------------------------- | :------------- |\n| `Text on phone`                    | 640            |\n| `Wave hello`                       | 478            |\n| `Write name in air`                | 477            |\n| `Pull air toward your face`        | 477            |\n| `Feel around in tray... an Object` | 161            |\n| `Write name on leg`                | 161            |\n| `Pinch knee/leg skin`              | 161            |\n| `Scratch knee/leg skin`            | 161            |\n| `Drink from bottle/cup`            | 161            |\n| `Glasses on/off`                   | 161            |\n>\n>---\n>\n> #### 💡 **Final Conclusion**\n>\n>Our dataset has a dual structure: the `Target` group is **well-balanced** and ready for modeling, while the `Non-Target` group is **severely imbalanced**. This requires a careful strategy, potentially using techniques like **resampling** or **weighted loss functions** to prevent the model from ignoring the rare classes.\n","metadata":{}},{"cell_type":"markdown","source":"## ⚪ Comparative  Sequence Analysis","metadata":{}},{"cell_type":"markdown","source":"> #### 🟠 A Quick Guide to Our 4 Data Comparisons 📊\n>\n>We've set up four targeted comparisons to understand what makes motion data unique. For this initial analysis, we are focusing exclusively on the accelerometer data (`acc_x`, `acc_y`, `acc_z`). Each comparison isolates a key factor to see how it changes the sensor patterns.\n>\n> #### **1. Same Subject, Different Gestures** 🧍‍♂️\n> *   **Goal:** See how one person's sensor data changes for different actions.\n> *   **Insight:** Confirms that different gestures create unique signal patterns. It's our basic \"proof of concept.\"\n>\n>#### **2. Same Gesture, Different (Random) Subjects** 👨‍👧\n>*   **Goal:** Compare how an adult and a child perform the same action.\n>*   **Insight:** Reveals differences in movement speed, intensity, and coordination due to age and physical development.\n>\n>#### **3. Same Subject, Same Gesture (Repetitions)** 🔁\n>*   **Goal:** Check how consistently one person repeats the exact same action.\n>*   **Insight:** Shows a person's unique \"motion signature\" while also highlighting the natural, minor variations in human movement.\n>\n>#### **4. Same Gesture, Different Genders (Male vs. Female)** 👫\n>*   **Goal:** Look for differences when an adult male and female perform the same action.\n>*   **Insight:** Explores how factors like height, strength, or biomechanics might lead to subtle variations in motion style between genders.\n","metadata":{}},{"cell_type":"code","source":"# --- Gestures List for choosing as Target for comparison.---\n# <Text on phone>          -----   <Neck - scratch>\n# <Forehead - scratch>     -----   <Eyebrow - pull hair>\n# <Above ear - pull hair>  -----   <Forehead - pull hairline>\n# <Neck - pinch skin>      -----   <Feel around in tray and pull out an object>\n# <Eyelash - pull hair>    -----   <Pull air toward your face>\n# <Cheek - pinch skin>     -----   <Drink from bottle/cup>\n# <Wave hello>             -----   <Write name on leg>\n# <Write name in air>      -----   <Pinch knee/leg skin>\n# <Glasses on/off>         -----   <Scratch knee/leg skin>","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:20.792753Z","iopub.execute_input":"2025-08-02T21:55:20.793029Z","iopub.status.idle":"2025-08-02T21:55:20.797315Z","shell.execute_reply.started":"2025-08-02T21:55:20.793009Z","shell.execute_reply":"2025-08-02T21:55:20.796417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Number 1","metadata":{}},{"cell_type":"code","source":"def find_comparison_pairs(df):\n    \"\"\"\n    Searches the dataframe to find pairs of sequences suitable for Comparison Type 1:\n    - Same Gesture\n    - Same Orientation\n    - Different Subject\n    \"\"\"\n    print(\"Step 1: Searching for suitable sequence pairs...\")\n    \n    # Optimize by using only necessary columns and removing duplicates\n    relevant_cols_df = df[['sequence_id', 'subject', 'gesture', 'orientation']].drop_duplicates()\n    \n    grouped = relevant_cols_df.groupby(['gesture', 'orientation'])\n    valid_pairs = []\n    \n    for (gesture, orientation), group_df in grouped:\n        # If there are fewer than 2 unique subjects in this group, it cannot be used for comparison.\n        if group_df['subject'].nunique() < 2:\n            continue\n            \n        # Map subjects to their sequences within this group\n        subject_to_sequences = group_df.groupby('subject')['sequence_id'].apply(list).to_dict()\n        subjects_in_group = list(subject_to_sequences.keys())\n        \n        # Create all possible pairs of subjects\n        subject_pairs = combinations(subjects_in_group, 2)\n        \n        # For each subject pair, create a sequence pair\n        for sub1, sub2 in subject_pairs:\n            # For simplicity, we take the first sequence from each subject\n            seq1 = subject_to_sequences[sub1][0]\n            seq2 = subject_to_sequences[sub2][0]\n            \n            valid_pairs.append({\n                'gesture': gesture, 'orientation': orientation,\n                'sequence_1': seq1, 'subject_1': sub1,\n                'sequence_2': seq2, 'subject_2': sub2\n            })\n            \n    print(f\"Search complete. Found {len(valid_pairs)} suitable pairs for comparison.\")\n    return pd.DataFrame(valid_pairs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:20.798281Z","iopub.execute_input":"2025-08-02T21:55:20.798587Z","iopub.status.idle":"2025-08-02T21:55:20.820120Z","shell.execute_reply.started":"2025-08-02T21:55:20.798560Z","shell.execute_reply":"2025-08-02T21:55:20.819120Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndef plot_single_sequence(ax, sequence_id, df_train, df_demographic):\n    \"\"\"\n    Plots a single sequence on a given matplotlib axis (ax).\n    \"\"\"\n    sequence_df = df_train[df_train['sequence_id'] == sequence_id].copy()\n    sequence_df.sort_values('sequence_counter', inplace=True)\n    \n    if sequence_df.empty:\n        ax.text(0.5, 0.5, f\"Sequence ID not found:{sequence_id}\", ha='center', va='center')\n        return\n    \n    # Extract metadata for titles and annotations\n    subject_id = sequence_df['subject'].iloc[0]\n    gesture = sequence_df['gesture'].iloc[0]\n    orientation = sequence_df['orientation'].iloc[0]\n    \n    # Get demographic info for the subject\n    subject_info = df_demographic[df_demographic['subject'] == subject_id]\n    age = subject_info['age'].iloc[0] if not subject_info.empty else \"N/A\"\n    sex_str = \"Male\" if not subject_info.empty and subject_info['sex'].iloc[0] == 1 else \"Female\"\n    \n    # Plot acceleration data\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_x', label='acc_x', ax=ax, alpha=0.9)\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_y', label='acc_y', ax=ax, alpha=0.9)\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_z', label='acc_z', ax=ax, alpha=0.9)\n    \n    # Highlight behavior regions with colored backgrounds\n    unique_behaviors = sequence_df['behavior'].unique()\n    colors = sns.color_palette(\"husl\", len(unique_behaviors))\n    behavior_color_map = dict(zip(unique_behaviors, colors))\n    \n    start_time = sequence_df['sequence_counter'].iloc[0]\n    current_behavior = sequence_df['behavior'].iloc[0]\n    for i in range(1, len(sequence_df)):\n        if sequence_df['behavior'].iloc[i] != current_behavior:\n            end_time = sequence_df['sequence_counter'].iloc[i-1]\n            ax.axvspan(start_time, end_time, facecolor=behavior_color_map[current_behavior], alpha=0.2)\n            current_behavior = sequence_df['behavior'].iloc[i]\n            start_time = sequence_df['sequence_counter'].iloc[i]\n    # Add the final behavior span\n    ax.axvspan(start_time, sequence_df['sequence_counter'].iloc[-1], facecolor=behavior_color_map[current_behavior], alpha=0.2)\n\n    # Set titles and labels\n    ax.set_title(f\"Gesture: {gesture} | Orientation: {orientation}Subject: {subject_id}\", fontsize=14, weight='bold')\n    ax.set_xlabel('Time Step', fontsize=12)\n    ax.set_ylabel('Acceleration', fontsize=12)\n    ax.legend(loc='upper right')\n    ax.grid(True, linestyle='--', alpha=0.6)\n    \n    # Add demographic info text below the plot\n    demographic_text = f\"Subject Info: Age={age}, Sex={sex_str}\"\n    ax.text(0.5, -0.25, demographic_text, ha='center', transform=ax.transAxes, fontsize=12,\n            bbox=dict(boxstyle=\"round,pad=0.4\", fc=\"lightgray\", alpha=0.8))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:20.821009Z","iopub.execute_input":"2025-08-02T21:55:20.821265Z","iopub.status.idle":"2025-08-02T21:55:20.837769Z","shell.execute_reply.started":"2025-08-02T21:55:20.821240Z","shell.execute_reply":"2025-08-02T21:55:20.836888Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compare_gesture_across_subjects(sequence_id_1, sequence_id_2, df_train, df_demographic):\n    \"\"\"\n    Compares the same gesture performed by two different subjects.\n    Plots are arranged vertically.\n    \"\"\"\n    sns.set_theme(style=\"whitegrid\")\n    \n    # --- MODIFICATION: Changed layout to 2 rows, 1 column and adjusted figsize ---\n    fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(18, 12)) # Changed figsize for better vertical aspect\n    \n    # Plot each sequence on its respective axis\n    plot_single_sequence(axes[0], sequence_id_1, df_train, df_demographic)\n    plot_single_sequence(axes[1], sequence_id_2, df_train, df_demographic)\n    \n    # Add a main title to the figure\n    gesture = df_train[df_train['sequence_id'] == sequence_id_1]['gesture'].iloc[0]\n    fig.suptitle(f\"Comparison 1: Same Gesture ('{gesture}') by Different Subjects\", fontsize=20, weight='bold')\n    \n    plt.tight_layout(rect=[0, 0, 1, 0.96]) # Adjust rect to make space for suptitle\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:20.838842Z","iopub.execute_input":"2025-08-02T21:55:20.839336Z","iopub.status.idle":"2025-08-02T21:55:20.857704Z","shell.execute_reply.started":"2025-08-02T21:55:20.839302Z","shell.execute_reply":"2025-08-02T21:55:20.856872Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Call the function to find all possible pairs\ncomparison1_pairs_df = find_comparison_pairs(df_train)\n\n# Check if any pairs were found\nif not comparison1_pairs_df.empty:\n    print(\"Step 2: Selecting a pair to plot...\")\n    \n    # Example: Select a pair for the 'Typing' gesture\n    # You can replace 'Cheek - pinch skin' with any other gesture.\n    target_gesture = 'Cheek - pinch skin'\n    selected_pairs = comparison1_pairs_df[comparison1_pairs_df['gesture'] == target_gesture]\n    \n    if not selected_pairs.empty:\n        # Select the first pair found for the target gesture\n        pair_to_plot = selected_pairs.iloc[0]\n        \n        seq_id_1 = pair_to_plot['sequence_1']\n        subj_id_1 = pair_to_plot['subject_1']\n        seq_id_2 = pair_to_plot['sequence_2']\n        subj_id_2 = pair_to_plot['subject_2']\n        \n        print(f\"Plotting the selected pair:\")\n        print(f\"  - Gesture: {pair_to_plot['gesture']}\")\n        print(f\"  - Orientation: {pair_to_plot['orientation']}\")\n        print(f\"  - Sequence 1: {seq_id_1} (from {subj_id_1})\")\n        print(f\"  - Sequence 2: {seq_id_2} (from {subj_id_2})\")\n        \n        print(\"Step 3: Generating the comparison plot...\")\n        # Call the plotting function with the selected sequence IDs\n        compare_gesture_across_subjects(seq_id_1, seq_id_2, df_train, df_demographic)\n        \n    else:\n        print(f\"No pairs were found for the gesture '{target_gesture}'. Please try another gesture.\")\n        # As a fallback, plot the very first pair from the full list\n        print(\"Plotting the first available pair from the list as a fallback...\")\n        pair_to_plot = comparison1_pairs_df.iloc[0]\n        seq_id_1 = pair_to_plot['sequence_1']\n        seq_id_2 = pair_to_plot['sequence_2']\n        compare_gesture_across_subjects(seq_id_1, seq_id_2, df_train, df_demographic)\n        \nelse:\n    print(\"No sequence pairs suitable for Comparison Type 1 were found in the dataset.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:20.863201Z","iopub.execute_input":"2025-08-02T21:55:20.863509Z","iopub.status.idle":"2025-08-02T21:55:22.471782Z","shell.execute_reply.started":"2025-08-02T21:55:20.863483Z","shell.execute_reply":"2025-08-02T21:55:22.470904Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Number 2","metadata":{}},{"cell_type":"code","source":"\ndef find_comparison_pairs_type2(df):\n    \"\"\"\n    Searches the dataframe to find pairs of sequences suitable for Comparison Type 2:\n    - Same Subject\n    - Same Gesture\n    - Different Orientation\n    \"\"\"\n    print(\"Step 1: Searching for suitable sequence pairs for Comparison Type 2...\")\n    \n    # Optimize by using only necessary columns and removing duplicates\n    relevant_cols_df = df[['sequence_id', 'subject', 'gesture', 'orientation']].drop_duplicates()\n    \n    # Group by subject and gesture to find cases with multiple orientations\n    grouped = relevant_cols_df.groupby(['subject', 'gesture'])\n    valid_pairs = []\n    \n    for (subject, gesture), group_df in grouped:\n        # If a subject has performed a gesture in fewer than 2 orientations, they can't be compared.\n        if group_df['orientation'].nunique() < 2:\n            continue\n            \n        # Get all sequences for this subject/gesture combination\n        all_sequences_in_group = group_df[['sequence_id', 'orientation']].to_dict('records')\n        \n        # Create all possible pairs of sequences (which have different orientations by definition of the group)\n        sequence_pairs = combinations(all_sequences_in_group, 2)\n        \n        for seq1_info, seq2_info in sequence_pairs:\n            # Ensure the orientations are actually different, just in case of data duplication\n            if seq1_info['orientation'] != seq2_info['orientation']:\n                valid_pairs.append({\n                    'subject': subject,\n                    'gesture': gesture,\n                    'sequence_1': seq1_info['sequence_id'],\n                    'orientation_1': seq1_info['orientation'],\n                    'sequence_2': seq2_info['sequence_id'],\n                    'orientation_2': seq2_info['orientation'],\n                })\n            \n    print(f\"Search complete. Found {len(valid_pairs)} suitable pairs for comparison.\")\n    return pd.DataFrame(valid_pairs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:22.473010Z","iopub.execute_input":"2025-08-02T21:55:22.473288Z","iopub.status.idle":"2025-08-02T21:55:22.480630Z","shell.execute_reply.started":"2025-08-02T21:55:22.473266Z","shell.execute_reply":"2025-08-02T21:55:22.479735Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndef plot_single_sequence(ax, sequence_id, df_train, df_demographic):\n    \"\"\"\n    Plots a single sequence on a given matplotlib axis (ax).\n    This function remains unchanged from the previous version.\n    \"\"\"\n    sequence_df = df_train[df_train['sequence_id'] == sequence_id].copy()\n    sequence_df.sort_values('sequence_counter', inplace=True)\n    \n    if sequence_df.empty:\n        ax.text(0.5, 0.5, f\"Sequence ID not found:{sequence_id}\", ha='center', va='center')\n        return\n    \n    # Extract metadata for titles and annotations\n    subject_id = sequence_df['subject'].iloc[0]\n    gesture = sequence_df['gesture'].iloc[0]\n    orientation = sequence_df['orientation'].iloc[0]\n    \n    # Get demographic info for the subject\n    subject_info = df_demographic[df_demographic['subject'] == subject_id]\n    age = subject_info['age'].iloc[0] if not subject_info.empty else \"N/A\"\n    sex_str = \"Male\" if not subject_info.empty and subject_info['sex'].iloc[0] == 1 else \"Female\"\n    \n    # Plot acceleration data\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_x', label='acc_x', ax=ax, alpha=0.9)\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_y', label='acc_y', ax=ax, alpha=0.9)\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_z', label='acc_z', ax=ax, alpha=0.9)\n    \n    # Highlight behavior regions with colored backgrounds\n    unique_behaviors = sequence_df['behavior'].unique()\n    colors = sns.color_palette(\"husl\", len(unique_behaviors))\n    behavior_color_map = dict(zip(unique_behaviors, colors))\n    \n    start_time = sequence_df['sequence_counter'].iloc[0]\n    current_behavior = sequence_df['behavior'].iloc[0]\n    for i in range(1, len(sequence_df)):\n        if sequence_df['behavior'].iloc[i] != current_behavior:\n            end_time = sequence_df['sequence_counter'].iloc[i-1]\n            ax.axvspan(start_time, end_time, facecolor=behavior_color_map[current_behavior], alpha=0.2)\n            current_behavior = sequence_df['behavior'].iloc[i]\n            start_time = sequence_df['sequence_counter'].iloc[i]\n    ax.axvspan(start_time, sequence_df['sequence_counter'].iloc[-1], facecolor=behavior_color_map[current_behavior], alpha=0.2)\n\n    # Set titles and labels\n    ax.set_title(f\"Gesture: {gesture} | Orientation: {orientation}Subject: {subject_id}\", fontsize=14, weight='bold')\n    ax.set_xlabel('Time Step', fontsize=12)\n    ax.set_ylabel('Acceleration', fontsize=12)\n    ax.legend(loc='upper right')\n    ax.grid(True, linestyle='--', alpha=0.6)\n    \n    # Add demographic info text below the plot\n    demographic_text = f\"Subject Info: Age={age}, Sex={sex_str}\"\n    ax.text(0.5, -0.25, demographic_text, ha='center', transform=ax.transAxes, fontsize=12,\n            bbox=dict(boxstyle=\"round,pad=0.4\", fc=\"lightgray\", alpha=0.8))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:22.481657Z","iopub.execute_input":"2025-08-02T21:55:22.482217Z","iopub.status.idle":"2025-08-02T21:55:22.504159Z","shell.execute_reply.started":"2025-08-02T21:55:22.482193Z","shell.execute_reply":"2025-08-02T21:55:22.503009Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compare_gesture_across_orientations(sequence_id_1, sequence_id_2, df_train, df_demographic):\n    \"\"\"\n    Compares the same gesture in two different orientations by the same subject.\n    Plots are arranged vertically.\n    \"\"\"\n    sns.set_theme(style=\"whitegrid\")\n    \n    fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(18, 12))\n    \n    plot_single_sequence(axes[0], sequence_id_1, df_train, df_demographic)\n    plot_single_sequence(axes[1], sequence_id_2, df_train, df_demographic)\n    \n    # Add a main title to the figure\n    subject_id = df_train[df_train['sequence_id'] == sequence_id_1]['subject'].iloc[0]\n    gesture = df_train[df_train['sequence_id'] == sequence_id_1]['gesture'].iloc[0]\n    fig.suptitle(f\"Comparison 2: Same Gesture ('{gesture}') by One Subject ('{subject_id}')with Different Orientations\", fontsize=20, weight='bold')\n    \n    plt.tight_layout(rect=[0, 0, 1, 0.95]) # Adjust rect to make space for suptitle\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:22.505199Z","iopub.execute_input":"2025-08-02T21:55:22.506085Z","iopub.status.idle":"2025-08-02T21:55:22.525555Z","shell.execute_reply.started":"2025-08-02T21:55:22.506050Z","shell.execute_reply":"2025-08-02T21:55:22.524533Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Call the function to find all possible pairs for Comparison Type 2\ncomparison2_pairs_df = find_comparison_pairs_type2(df_train)\n\n# Check if any pairs were found\nif not comparison2_pairs_df.empty:\n    print(\"Step 2: Selecting a pair to plot...\")\n    \n    # Example: Select a pair for the 'Forehead - scratch' gesture\n    target_gesture = 'Eyebrow - pull hair'\n    selected_pairs = comparison2_pairs_df[comparison2_pairs_df['gesture'] == target_gesture]\n    \n    if not selected_pairs.empty:\n        # Select the first pair found for the target gesture\n        pair_to_plot = selected_pairs.iloc[0]\n        \n        seq_id_1 = pair_to_plot['sequence_1']\n        orientation_1 = pair_to_plot['orientation_1']\n        seq_id_2 = pair_to_plot['sequence_2']\n        orientation_2 = pair_to_plot['orientation_2']\n        \n        print(f\"Plotting the selected pair:\")\n        print(f\"  - Subject: {pair_to_plot['subject']}\")\n        print(f\"  - Gesture: {pair_to_plot['gesture']}\")\n        print(f\"  - Sequence 1: {seq_id_1} (Orientation: {orientation_1})\")\n        print(f\"  - Sequence 2: {seq_id_2} (Orientation: {orientation_2})\")\n        \n        print(\"Step 3: Generating the comparison plot...\")\n        # Call the plotting function with the selected sequence IDs\n        compare_gesture_across_orientations(seq_id_1, seq_id_2, df_train, df_demographic)\n        \n    else:\n        print(f\"No pairs were found for the gesture '{target_gesture}'. Please try another gesture.\")\n        # As a fallback, plot the very first pair from the full list\n        print(\"Plotting the first available pair from the list as a fallback...\")\n        pair_to_plot = comparison2_pairs_df.iloc[0]\n        seq_id_1 = pair_to_plot['sequence_1']\n        seq_id_2 = pair_to_plot['sequence_2']\n        compare_gesture_across_orientations(seq_id_1, seq_id_2, df_train, df_demographic)\n        \nelse:\n    print(\"No sequence pairs suitable for Comparison Type 2 were found in the dataset.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:22.526514Z","iopub.execute_input":"2025-08-02T21:55:22.526951Z","iopub.status.idle":"2025-08-02T21:55:24.731723Z","shell.execute_reply.started":"2025-08-02T21:55:22.526919Z","shell.execute_reply":"2025-08-02T21:55:24.730860Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Number 3","metadata":{}},{"cell_type":"code","source":"def find_comparison_pairs_type3(df):\n    \"\"\"\n    Searches the dataframe to find pairs of sequences suitable for Comparison Type 3:\n    - Same Subject\n    - Same Gesture\n    - Same Orientation\n    (This checks for repeated recordings of the same activity by the same person.)\n    \"\"\"\n    print(\"Step 1: Searching for suitable sequence pairs for Comparison Type 3...\")\n    \n    # Optimize by using only necessary columns and removing duplicates\n    relevant_cols_df = df[['sequence_id', 'subject', 'gesture', 'orientation']].drop_duplicates()\n    \n    # Group by subject, gesture, AND orientation to find repetitions\n    grouped = relevant_cols_df.groupby(['subject', 'gesture', 'orientation'])\n    valid_pairs = []\n    \n    for (subject, gesture, orientation), group_df in grouped:\n        # If a subject has performed a specific gesture/orientation combo fewer than 2 times,\n        # we cannot compare repetitions.\n        if len(group_df) < 2:\n            continue\n            \n        # Get all sequence_ids for this specific combination\n        sequence_ids = group_df['sequence_id'].tolist()\n        \n        # Create all possible pairs of these repeated sequences\n        sequence_pairs = combinations(sequence_ids, 2)\n        \n        for seq1, seq2 in sequence_pairs:\n            valid_pairs.append({\n                'subject': subject,\n                'gesture': gesture,\n                'orientation': orientation,\n                'sequence_1': seq1,\n                'sequence_2': seq2,\n            })\n            \n    print(f\"Search complete. Found {len(valid_pairs)} suitable pairs for comparison.\")\n    return pd.DataFrame(valid_pairs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:24.732906Z","iopub.execute_input":"2025-08-02T21:55:24.733238Z","iopub.status.idle":"2025-08-02T21:55:24.742782Z","shell.execute_reply.started":"2025-08-02T21:55:24.733207Z","shell.execute_reply":"2025-08-02T21:55:24.741722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_single_sequence(ax, sequence_id, df_train, df_demographic):\n    \"\"\"\n    Plots a single sequence on a given matplotlib axis (ax).\n    This function remains unchanged.\n    \"\"\"\n    sequence_df = df_train[df_train['sequence_id'] == sequence_id].copy()\n    sequence_df.sort_values('sequence_counter', inplace=True)\n    \n    if sequence_df.empty:\n        ax.text(0.5, 0.5, f\"Sequence ID not found:{sequence_id}\", ha='center', va='center')\n        return\n    \n    # Extract metadata for titles and annotations\n    subject_id = sequence_df['subject'].iloc[0]\n    gesture = sequence_df['gesture'].iloc[0]\n    orientation = sequence_df['orientation'].iloc[0]\n    \n    # Get demographic info for the subject\n    subject_info = df_demographic[df_demographic['subject'] == subject_id]\n    age = subject_info['age'].iloc[0] if not subject_info.empty else \"N/A\"\n    sex_str = \"Male\" if not subject_info.empty and subject_info['sex'].iloc[0] == 1 else \"Female\"\n    \n    # Plot acceleration data\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_x', label='acc_x', ax=ax, alpha=0.9)\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_y', label='acc_y', ax=ax, alpha=0.9)\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_z', label='acc_z', ax=ax, alpha=0.9)\n    \n    # Highlight behavior regions\n    unique_behaviors = sequence_df['behavior'].unique()\n    colors = sns.color_palette(\"husl\", len(unique_behaviors))\n    behavior_color_map = dict(zip(unique_behaviors, colors))\n    \n    start_time = sequence_df['sequence_counter'].iloc[0]\n    current_behavior = sequence_df['behavior'].iloc[0]\n    for i in range(1, len(sequence_df)):\n        if sequence_df['behavior'].iloc[i] != current_behavior:\n            end_time = sequence_df['sequence_counter'].iloc[i-1]\n            ax.axvspan(start_time, end_time, facecolor=behavior_color_map[current_behavior], alpha=0.2)\n            current_behavior = sequence_df['behavior'].iloc[i]\n            start_time = sequence_df['sequence_counter'].iloc[i]\n    ax.axvspan(start_time, sequence_df['sequence_counter'].iloc[-1], facecolor=behavior_color_map[current_behavior], alpha=0.2)\n\n    # Set titles and labels\n    ax.set_title(f\"Gesture: {gesture} | Orientation: {orientation}Sequence ID: {sequence_id}\", fontsize=14, weight='bold')\n    ax.set_xlabel('Time Step', fontsize=12)\n    ax.set_ylabel('Acceleration', fontsize=12)\n    ax.legend(loc='upper right')\n    ax.grid(True, linestyle='--', alpha=0.6)\n    \n    # Add demographic info text\n    demographic_text = f\"Subject Info: ID={subject_id}, Age={age}, Sex={sex_str}\"\n    ax.text(0.5, -0.25, demographic_text, ha='center', transform=ax.transAxes, fontsize=12,\n            bbox=dict(boxstyle=\"round,pad=0.4\", fc=\"lightgray\", alpha=0.8))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:24.744095Z","iopub.execute_input":"2025-08-02T21:55:24.744770Z","iopub.status.idle":"2025-08-02T21:55:24.763383Z","shell.execute_reply.started":"2025-08-02T21:55:24.744736Z","shell.execute_reply":"2025-08-02T21:55:24.762581Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compare_repeated_gestures(sequence_id_1, sequence_id_2, df_train, df_demographic):\n    \"\"\"\n    Compares two instances of the same gesture, by the same person, in the same orientation.\n    Plots are arranged vertically.\n    \"\"\"\n    sns.set_theme(style=\"whitegrid\")\n    \n    fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(18, 12))\n    \n    plot_single_sequence(axes[0], sequence_id_1, df_train, df_demographic)\n    plot_single_sequence(axes[1], sequence_id_2, df_train, df_demographic)\n    \n    # Extract info for the main title\n    info = df_train[df_train['sequence_id'] == sequence_id_1].iloc[0]\n    subject_id, gesture, orientation = info['subject'], info['gesture'], info['orientation']\n    \n    fig.suptitle(f\"Comparison 3: Repeated Gesture ('{gesture}') by One Subject ('{subject_id}')(Same Orientation: {orientation})\", fontsize=20, weight='bold')\n    \n    plt.tight_layout(rect=[0, 0, 1, 0.95])\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:24.764689Z","iopub.execute_input":"2025-08-02T21:55:24.764963Z","iopub.status.idle":"2025-08-02T21:55:24.784292Z","shell.execute_reply.started":"2025-08-02T21:55:24.764940Z","shell.execute_reply":"2025-08-02T21:55:24.783461Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Call the function to find all possible pairs for Comparison Type 3\ncomparison3_pairs_df = find_comparison_pairs_type3(df_train)\n\n# Check if any pairs were found\nif not comparison3_pairs_df.empty:\n    print(\"Step 2: Selecting a RANDOM pair to plot...\")\n    \n    # Instead of picking the first pair with .iloc[0], we select one RANDOM pair.\n    pair_to_plot = comparison3_pairs_df.sample(1)\n    \n    # Extracting sequence IDs from the randomly selected pair's dataframe row.\n    # .iloc[0] is used here because pair_to_plot is a dataframe with a single random row.\n    seq_id_1 = pair_to_plot['sequence_1'].iloc[0]\n    seq_id_2 = pair_to_plot['sequence_2'].iloc[0]\n    \n    print(f\"Plotting the randomly selected pair:\")\n    print(f\"  - Subject: {pair_to_plot['subject'].iloc[0]}\")\n    print(f\"  - Gesture: {pair_to_plot['gesture'].iloc[0]}\")\n    print(f\"  - Orientation: {pair_to_plot['orientation'].iloc[0]}\")\n    print(f\"  - Sequence 1: {seq_id_1}\")\n    print(f\"  - Sequence 2: {seq_id_2}\")\n    \n    print(\"Step 3: Generating the comparison plot...\")\n    # Call the plotting function with the selected sequence IDs\n    compare_repeated_gestures(seq_id_1, seq_id_2, df_train, df_demographic)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:24.786072Z","iopub.execute_input":"2025-08-02T21:55:24.786400Z","iopub.status.idle":"2025-08-02T21:55:26.275445Z","shell.execute_reply.started":"2025-08-02T21:55:24.786378Z","shell.execute_reply":"2025-08-02T21:55:26.274478Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Number 4","metadata":{}},{"cell_type":"code","source":"def find_comparison_pairs_type4(df_train, df_demographic):\n    \"\"\"\n    Searches for pairs of sequences suitable for Comparison Type 4:\n    - Same Gesture\n    - Same Orientation\n    - Performed by two different subjects\n    - Both subjects must be Adults\n    - One subject must be Male, the other Female\n    \"\"\"    \n    # Merge train and demographic data to have all info in one place\n    # We only need sequence_id and the grouping keys from df_train\n    merged_df = pd.merge(\n        df_train[['sequence_id', 'subject', 'gesture', 'orientation']].drop_duplicates(),\n        df_demographic,\n        on='subject'\n    )\n    \n    # Filter for adults only, as this is a core requirement\n    adults_df = merged_df[merged_df['adult_child'] == 1].copy()\n    \n    # Separate adults into male and female groups to simplify pairing\n    male_adults_df = adults_df[adults_df['sex'] == 1]\n    female_adults_df = adults_df[adults_df['sex'] == 0]\n    \n    valid_pairs = []\n    \n    # Iterate through each male sequence\n    for _, male_row in male_adults_df.iterrows():\n        # Find all matching female sequences (same gesture and orientation)\n        matching_females = female_adults_df[\n            (female_adults_df['gesture'] == male_row['gesture']) &\n            (female_adults_df['orientation'] == male_row['orientation'])\n        ]\n        \n        # If matches are found, create a pair for each match\n        for _, female_row in matching_females.iterrows():\n            valid_pairs.append({\n                'gesture': male_row['gesture'],\n                'orientation': male_row['orientation'],\n                'subject_male': male_row['subject'],\n                'sequence_male': male_row['sequence_id'],\n                'age_male': male_row['age'],\n                'subject_female': female_row['subject'],\n                'sequence_female': female_row['sequence_id'],\n                'age_female': female_row['age']\n            })\n            \n    result_df = pd.DataFrame(valid_pairs)\n    print(f\"Search complete. Found {len(result_df)} suitable pairs for comparison.\")\n    return result_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:26.276512Z","iopub.execute_input":"2025-08-02T21:55:26.276936Z","iopub.status.idle":"2025-08-02T21:55:26.284825Z","shell.execute_reply.started":"2025-08-02T21:55:26.276902Z","shell.execute_reply":"2025-08-02T21:55:26.283875Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_single_sequence(ax, sequence_id, df_train, df_demographic):\n    \"\"\"\n    Plots a single sequence on a given matplotlib axis (ax).\n    This function is reused from previous comparisons.\n    \"\"\"\n    sequence_df = df_train[df_train['sequence_id'] == sequence_id].copy()\n    sequence_df.sort_values('sequence_counter', inplace=True)\n    \n    if sequence_df.empty:\n        ax.text(0.5, 0.5, f\"Sequence ID not found:\\n{sequence_id}\", ha='center', va='center')\n        return\n    \n    # Extract metadata for titles\n    subject_id = sequence_df['subject'].iloc[0]\n    gesture = sequence_df['gesture'].iloc[0]\n    orientation = sequence_df['orientation'].iloc[0]\n    \n    # Get demographic info\n    subject_info = df_demographic[df_demographic['subject'] == subject_id]\n    age = subject_info['age'].iloc[0] if not subject_info.empty else \"N/A\"\n    sex_str = \"Male\" if not subject_info.empty and subject_info['sex'].iloc[0] == 1 else \"Female\"\n    \n    # Plot acceleration data\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_x', label='acc_x', ax=ax, alpha=0.9)\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_y', label='acc_y', ax=ax, alpha=0.9)\n    sns.lineplot(data=sequence_df, x='sequence_counter', y='acc_z', label='acc_z', ax=ax, alpha=0.9)\n    \n    # Set titles and labels\n    ax.set_title(f\"Subject: {subject_id} ({sex_str}, Age: {age})\\nSequence ID: {sequence_id}\", fontsize=14, weight='bold')\n    ax.set_xlabel('Time Step', fontsize=12)\n    ax.set_ylabel('Acceleration', fontsize=12)\n    ax.legend(loc='upper right')\n    ax.grid(True, linestyle='--', alpha=0.6)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:26.285777Z","iopub.execute_input":"2025-08-02T21:55:26.286066Z","iopub.status.idle":"2025-08-02T21:55:26.308865Z","shell.execute_reply.started":"2025-08-02T21:55:26.286045Z","shell.execute_reply":"2025-08-02T21:55:26.307914Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def compare_male_vs_female_gesture(male_seq_id, female_seq_id, df_train, df_demographic):\n    \"\"\"\n    Compares two sequences (one male, one female) for the same gesture and orientation.\n    Plots are arranged vertically.\n    \"\"\"\n    sns.set_theme(style=\"whitegrid\")\n    fig, axes = plt.subplots(nrows=2, ncols=1, figsize=(18, 12))\n    \n    # Plot the two sequences\n    plot_single_sequence(axes[0], male_seq_id, df_train, df_demographic)\n    plot_single_sequence(axes[1], female_seq_id, df_train, df_demographic)\n    \n    # Extract info for the main title from one of the sequences (gesture/orientation are the same)\n    info = df_train[df_train['sequence_id'] == male_seq_id].iloc[0]\n    gesture, orientation = info['gesture'], info['orientation']\n    \n    fig.suptitle(f\"Comparison 4: Male vs. Female Performing Gesture '{gesture}'\\n(Same Orientation: {orientation}, Both Adults)\", fontsize=20, weight='bold')\n    \n    plt.tight_layout(rect=[0, 0, 1, 0.95])\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:26.309938Z","iopub.execute_input":"2025-08-02T21:55:26.310275Z","iopub.status.idle":"2025-08-02T21:55:26.332828Z","shell.execute_reply.started":"2025-08-02T21:55:26.310246Z","shell.execute_reply":"2025-08-02T21:55:26.331600Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Call the function to find all possible pairs for Comparison Type 4\ncomparison4_pairs_df = find_comparison_pairs_type4(df_train, df_demographic)\n\n# Check if any pairs were found\nif not comparison4_pairs_df.empty:\n    print(\"\\nStep 2: Selecting a RANDOM pair to plot...\")\n    \n    # Randomly sample one pair from the DataFrame of all valid pairs\n    pair_to_plot = comparison4_pairs_df.sample(1)\n    \n    # Extract the sequence IDs from the single-row DataFrame\n    seq_id_male = pair_to_plot['sequence_male'].iloc[0]\n    seq_id_female = pair_to_plot['sequence_female'].iloc[0]\n    \n    print(\"Plotting the randomly selected pair:\")\n    print(f\"  - Common Gesture: {pair_to_plot['gesture'].iloc[0]}\")\n    print(f\"  - Common Orientation: {pair_to_plot['orientation'].iloc[0]}\")\n    print(\"-\" * 20)\n    print(f\"  - Male Subject: {pair_to_plot['subject_male'].iloc[0]} (Age: {pair_to_plot['age_male'].iloc[0]}) | Sequence: {seq_id_male}\")\n    print(f\"  - Female Subject: {pair_to_plot['subject_female'].iloc[0]} (Age: {pair_to_plot['age_female'].iloc[0]}) | Sequence: {seq_id_female}\")\n    \n    print(\"\\nStep 3: Generating the comparison plot...\")\n    # Call the plotting function with the selected sequence IDs\n    compare_male_vs_female_gesture(seq_id_male, seq_id_female, df_train, df_demographic)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:26.334100Z","iopub.execute_input":"2025-08-02T21:55:26.334457Z","iopub.status.idle":"2025-08-02T21:55:34.136729Z","shell.execute_reply.started":"2025-08-02T21:55:26.334424Z","shell.execute_reply":"2025-08-02T21:55:34.135723Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ## ⚪ Gesture Analysis","metadata":{}},{"cell_type":"code","source":"# Step 1: Configuration and Data Preparation\nfeatures_to_plot = ['rot_w', 'rot_x', 'rot_y', 'rot_z']\nsensor_name = \"Rotation (Quaternion)\"\ntarget_sequence_id = 'SEQ_035116'\ntarget_sequence_id = random.choice(df_train[\"sequence_id\"].unique())\n\nsequence_df = df_train[df_train['sequence_id'] == target_sequence_id].copy()\nsequence_df.sort_values('sequence_counter', inplace=True)\n\n# Step 2: Extract Metadata for Title and Footer\nsubject_id = sequence_df['subject'].iloc[0]\ngesture = sequence_df['gesture'].iloc[0]\norientation = sequence_df['orientation'].iloc[0]\n\n# Look up demographic info for the subject\n# Filter the demographic dataframe to find the row for our subject\nsubject_info = df_demographic[df_demographic['subject'] == subject_id]\n\n# Check if the subject was found and extract the data\nif not subject_info.empty:\n    age = subject_info['age'].iloc[0]\n    sex_code = subject_info['sex'].iloc[0]\n    # Convert sex code to a human-readable string\n    sex_str = \"Male\" if sex_code == 1 else \"Female\"\n\n# Step 3: Create Subplots and Plot the Data -\nfig, axes = plt.subplots(\n    nrows=len(features_to_plot),\n    ncols=1,\n    figsize=(20, 5 * len(features_to_plot)),\n    sharex=True\n)\n\nfig.suptitle(\n    f\"Sensor: {sensor_name}  |  Subject: {subject_id}  |  Gesture: {gesture}  |  Orientation: {orientation}\",\n    fontsize=20,\n    fontweight='bold'\n)\n\n# Plotting Logic (No changes here)\nbehavior_changes = sequence_df['behavior'].ne(sequence_df['behavior'].shift())\nchange_indices = sequence_df.index[behavior_changes]\nsegment_starts = sequence_df.loc[change_indices, ['sequence_counter', 'behavior']].to_dict('records')\nlast_timestep = sequence_df['sequence_counter'].iloc[-1]\nsegment_starts.append({'sequence_counter': last_timestep + 1, 'behavior': 'END'})\nunique_behaviors = sequence_df['behavior'].unique()\ncolors = sns.color_palette(\"viridis\", len(unique_behaviors))\nbehavior_color_map = dict(zip(unique_behaviors, colors))\n\nfor i, feature in enumerate(features_to_plot):\n    ax = axes[i]\n    sns.lineplot(data=sequence_df, x='sequence_counter', y=feature, ax=ax, label=feature)\n\n    for j in range(len(segment_starts) - 1):\n        start_time = segment_starts[j]['sequence_counter']\n        end_time = segment_starts[j+1]['sequence_counter'] - 1\n        behavior_name = segment_starts[j]['behavior']\n        ax.axvspan(\n            start_time, end_time,\n            facecolor=behavior_color_map[behavior_name], alpha=0.2\n        )\n        text_y_position = ax.get_ylim()[0] + (ax.get_ylim()[1] - ax.get_ylim()[0]) * 0.9\n        ax.text(\n            start_time + (end_time - start_time) / 2, text_y_position, behavior_name,\n            ha='center', va='center', fontsize=12, fontweight='bold',\n            bbox=dict(boxstyle=\"round,pad=0.3\", fc='white', ec=\"black\", lw=1, alpha=0.7)\n        )\n\n    ax.set_ylabel(feature, fontsize=14, fontweight='bold')\n    ax.legend(loc='upper right')\n    ax.grid(True)\n\naxes[-1].set_xlabel('Time Step (sequence_counter)', fontsize=14)\n\nplt.tight_layout(rect=[0, 0.03, 1, 0.95])\n\n# Add the demographic information as a footer text\n# Create the text string to display\ndemographic_text = f\"Subject Information ==> Age: {age} | Sex: {sex_str}\"\n\n# Add the text to the figure.\n# x=0.5 means horizontally centered.\n# y=0.01 means slightly above the bottom of the figure.\n# ha='center' ensures the text is centered on the x-coordinate.\nfig.text(\n    0.5, 0.01, demographic_text, color='navy',\n    ha='center', fontsize=16, fontweight='bold',\n    bbox=dict(boxstyle=\"round,pad=0.5\", fc='lightgray', alpha=0.5)\n)\nplt.show()\n\n\n\nfig.text(\n    0.5, 0.01,  # x=center, y=bottom\n    demographic_text,\n    ha='center',\n    fontsize=16,\n    fontweight='bold',\n    color='navy',\n    bbox=dict(boxstyle=\"round,pad=0.5\", fc=\"lightcyan\", ec=\"black\", lw=1)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:34.138257Z","iopub.execute_input":"2025-08-02T21:55:34.138566Z","iopub.status.idle":"2025-08-02T21:55:35.565493Z","shell.execute_reply.started":"2025-08-02T21:55:34.138529Z","shell.execute_reply":"2025-08-02T21:55:35.564473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 1: Choose a Sequence ID and Prepare Data\ntarget_sequence_id = 'SEQ_035116'\ntarget_sequence_id = random.choice(df_train[\"sequence_id\"].unique())\n\n# Filter the DataFrame for the chosen sequence\nsequence_df = df_train[df_train['sequence_id'] == target_sequence_id].copy()\nsequence_df.sort_values('sequence_counter', inplace=True)\n\nprint(f\"Analyzing sequence: {target_sequence_id}\")\nprint(f\"Number of data points in this sequence: {len(sequence_df)}\")\nprint(f\"Unique behaviors in this sequence: {sequence_df['behavior'].unique().tolist()}\")\n\n\n#  Step 2: Extract Metadata for Title and Footer\nsubject_id = sequence_df['subject'].iloc[0]\ngesture = sequence_df['gesture'].iloc[0]\norientation = sequence_df['orientation'].iloc[0]\nsensor_name = \"Accelerometer\" # Define the sensor name for the title\n\n# --- Look up demographic info from df_demographic ---\n# Find the row corresponding to our subject\nsubject_info = df_demographic[df_demographic['subject'] == subject_id]\n\n# Extract age and sex, with a fallback for safety\nif not subject_info.empty:\n    age = subject_info['age'].iloc[0]\n    sex_code = subject_info['sex'].iloc[0]\n    # Convert the numeric sex code to a descriptive string\n    sex_str = \"Male\" if sex_code == 1 else \"Female\"\n\n# Step 3: Set up the Plot and Plot Accelerometer Data\nsns.set_theme(style=\"whitegrid\")\nfig, ax = plt.subplots(figsize=(20, 10)) # Increased height slightly for more space\n\n# Plot all three accelerometer axes\nsns.lineplot(data=sequence_df, x='sequence_counter', y='acc_x', label='acc_x', ax=ax)\nsns.lineplot(data=sequence_df, x='sequence_counter', y='acc_y', label='acc_y', ax=ax)\nsns.lineplot(data=sequence_df, x='sequence_counter', y='acc_z', label='acc_z', ax=ax)\n\n\n# Step 4: Identify Behavior Changes and Visualize Segments\nbehavior_changes = sequence_df['behavior'].ne(sequence_df['behavior'].shift())\nchange_indices = sequence_df.index[behavior_changes]\nsegment_starts = sequence_df.loc[change_indices, ['sequence_counter', 'behavior']].to_dict('records')\nlast_timestep = sequence_df['sequence_counter'].iloc[-1]\nsegment_starts.append({'sequence_counter': last_timestep + 1, 'behavior': 'END'})\nunique_behaviors = sequence_df['behavior'].unique()\ncolors = sns.color_palette(\"husl\", len(unique_behaviors))\nbehavior_color_map = dict(zip(unique_behaviors, colors))\n\nfor i in range(len(segment_starts) - 1):\n    start_time = segment_starts[i]['sequence_counter']\n    end_time = segment_starts[i+1]['sequence_counter'] - 1\n    behavior_name = segment_starts[i]['behavior']\n    \n    ax.axvspan(\n        start_time, end_time,\n        facecolor=behavior_color_map[behavior_name], alpha=0.2\n    )\n    \n    text_x_position = start_time + (end_time - start_time) / 2\n    text_y_position = ax.get_ylim()[1] * 0.9\n    \n    ax.text(\n        text_x_position, text_y_position, behavior_name,\n        ha='center', va='center', fontsize=12, fontweight='bold',\n        bbox=dict(boxstyle=\"round,pad=0.3\", fc='yellow', ec=\"black\", lw=1, alpha=0.7)\n    )\n\n# Step 5: Final Touches for Clarity\n# Set a comprehensive main title (suptitle) for the entire figure\nfig.suptitle(\n    f'Sensor: {sensor_name} | Gesture: {gesture} | Orientation: {orientation}',\n    fontsize=20,\n    fontweight='bold'\n)\n\n# Set a specific title for the subplot itself\nax.set_title(\n    f'Signals for sequence id: {target_sequence_id} | Subject: {subject_id}',\n    fontsize=16,\n    y=1.02 \n)\n\nax.set_xlabel('Time Step (sequence_counter)', fontsize=14)\nax.set_ylabel('Acceleration Value', fontsize=14)\nax.legend(title='Sensor Axis')\n\n# --- Add the demographic information below the plot ---\ndemographic_text = f\"Subject information ==> Age: {age} | Sex: {sex_str}\"\n\n# Use fig.text to place text in figure coordinates (relative to the whole figure)\nfig.text(\n    0.5, 0.01,  # x=center, y=bottom\n    demographic_text,\n    ha='center',\n    fontsize=16,\n    fontweight='bold',\n    color='navy',\n    bbox=dict(boxstyle=\"round,pad=0.5\", fc=\"lightcyan\", ec=\"black\", lw=1)\n)\n\n# Adjust layout to prevent elements from overlapping\nplt.tight_layout(rect=[0, 0.05, 1, 0.95]) # Make space for footer and suptitle\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:35.566659Z","iopub.execute_input":"2025-08-02T21:55:35.566969Z","iopub.status.idle":"2025-08-02T21:55:36.129009Z","shell.execute_reply.started":"2025-08-02T21:55:35.566947Z","shell.execute_reply":"2025-08-02T21:55:36.128087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Calculate the length of each unique sequence ---\n# Step 1: We group by 'sequence_id' and use .size() to count the number of rows (timesteps) for each sequence.\nsequence_lengths = df_train.groupby('sequence_id').size().rename('sequence_length')\n\n# We only need the gesture and type, and we'll set the index to 'sequence_id' to easily join it.\nsequence_labels = unique_sequences_df.set_index('sequence_id')[['gesture', 'sequence_type']]\n\n# Step 2: Combine sequence lengths with their labels \ndf_lengths_with_labels = sequence_lengths.to_frame().join(sequence_labels)\n\n# Step 3: Calculate the required statistics (min, mean, max)\nlength_stats = df_lengths_with_labels.groupby(['sequence_type', 'gesture']).agg(\n    min_length=('sequence_length', 'min'),\n    mean_length=('sequence_length', lambda x: x.mean().round(1)),\n    max_length=('sequence_length', 'max')\n).rename(columns={\n    'min_length': 'Min Length',\n    'mean_length': 'Mean Length',\n    'max_length': 'Max Length'\n})\n\n# Step 4: Display the results \n# Displaying the results in a clean format.\nprint(\" ------------ Sequence Length Statistics for Target Gestures ------------ \")\ndisplay(length_stats.loc['Target'])\n\nprint(\" --------------------------- Sequence Length Statistics for Non-Target Gestures --------------------------- \")\ndisplay(length_stats.loc['Non-Target'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:36.130111Z","iopub.execute_input":"2025-08-02T21:55:36.130424Z","iopub.status.idle":"2025-08-02T21:55:36.222203Z","shell.execute_reply.started":"2025-08-02T21:55:36.130397Z","shell.execute_reply":"2025-08-02T21:55:36.221282Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" ## ⚪ TOF - Sensor Analysis","metadata":{}},{"cell_type":"code","source":"    # Define the pattern for ToF column names using a regular expression\n    # Pattern: starts with 'tof_', followed by a number from 1-5, then '_v', and finally one or two digits\n    tof_pattern = re.compile(r'^tof_[1-5]_v\\d{1,2}$')\n\n    # Filter columns that match the defined pattern\n    tof_columns = [col for col in df_train.columns if tof_pattern.match(col)]\n\n    num_tof_columns = len(tof_columns)\n    num_total_records = len(df_train)\n\n    # Calculate the total number of cells\n    total_tof_cells = num_tof_columns * num_total_records\n\n    print(\"--- ToF Sensor Cell Count Analysis ---\")\n    print(f\"Total number of rows (records) in the dataset: {num_total_records:,}\")\n    print(f\"Number of columns identified for ToF sensors: {num_tof_columns}\")\n    print(f\"Calculation: {num_tof_columns} (columns) * {num_total_records:,} (rows)\")\n    print(\"--------------------------------------------------\")\n    print(f\"Total number of cells for ToF sensors: {total_tof_cells:,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:36.223177Z","iopub.execute_input":"2025-08-02T21:55:36.223496Z","iopub.status.idle":"2025-08-02T21:55:36.232131Z","shell.execute_reply.started":"2025-08-02T21:55:36.223467Z","shell.execute_reply":"2025-08-02T21:55:36.230915Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"    # --- Step 1: Identify ToF Columns ---\n    tof_pattern = re.compile(r'^tof_[1-5]_v\\d{1,2}$')\n    tof_columns = [col for col in df_train.columns if tof_pattern.match(col)]\n    \n    # --- Step 2: Define variables for calculation ---\n    num_total_records = len(df_train)\n    num_tof_columns = len(tof_columns)\n    total_tof_cells = num_tof_columns * num_total_records\n\n    print(\"--- Analysis of -1 Values in ToF Sensor Columns ---\")\n    \n    # --- Step 3: Efficiently Count -1 Values ---\n    # Create a new DataFrame containing only the ToF columns.\n    df_tof_only = df_train[tof_columns]\n\n    # Convert the slice to a NumPy array for faster computation and then count the -1s.\n    count_of_minus_one = (df_tof_only.values == -1).sum()\n    \n    # --- Step 4: Calculate Percentage and Display Results ---\n    if total_tof_cells > 0:\n        percentage_minus_one = (count_of_minus_one / total_tof_cells) * 100\n    else:\n        percentage_minus_one = 0\n\n    print(\"--- Calculation Results ---\")\n    print(f\"Total number of cells for ToF sensors: {total_tof_cells:,}\")\n    print(f\"Number of cells with a value of -1: {count_of_minus_one:,}\")\n    print(\"--------------------------------------------------\")\n    print(f\"Percentage of ToF cells that are -1: {percentage_minus_one:.4f}%\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:36.233128Z","iopub.execute_input":"2025-08-02T21:55:36.233408Z","iopub.status.idle":"2025-08-02T21:55:37.134459Z","shell.execute_reply.started":"2025-08-02T21:55:36.233378Z","shell.execute_reply":"2025-08-02T21:55:37.133583Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"total_minus_one_count = 105770214 \n\nprint(\"--- Breakdown of -1 Values per ToF Sensor ---\")\n\n# --- Step 1: Create a dictionary to store the results ---\n# This is a clean way to organize the counts for each sensor.\nsensor_minus_one_counts = {}\n\n# --- Step 2: Loop through each of the 5 sensors ---\nfor i in range(1, 6): # This will loop through numbers 1, 2, 3, 4, 5\n    sensor_id = f\"tof_{i}\"\n    \n    # Create a regular expression pattern to find all columns for this specific sensor.\n    # e.g., for i=1, pattern becomes '^tof_1_v\\d{1,2}$'\n    sensor_pattern = re.compile(f'^{sensor_id}_v\\d{{1,2}}$')\n    \n    # Get the list of column names for the current sensor.\n    sensor_columns = [col for col in df_train.columns if sensor_pattern.match(col)]\n    \n    # Check if we actually found any columns for this sensor\n    if not sensor_columns:\n        print(f\"Warning: No columns found for {sensor_id}. Skipping.\")\n        continue\n        \n    # --- Step 3: Efficiently count -1s for the current sensor ---\n    # Select only the columns for the current sensor.\n    df_sensor_only = df_train[sensor_columns]\n    \n    # Use the same efficient NumPy method to count -1s.\n    count = (df_sensor_only.values == -1).sum()\n    \n    # Store the result in our dictionary.\n    sensor_minus_one_counts[sensor_id] = count\n\n# --- Step 4: Display the results in a clear table ---\nprint(\"--- Calculation Results ---\")\nprint(f\"{'Sensor ID':<15} | {'Count of -1 Values':<25} | {'Percentage of Total -1s':<25}\")\nprint(\"-\" * 75)\n\nif total_minus_one_count > 0:\n    for sensor_id, count in sensor_minus_one_counts.items():\n        # Calculate what percentage of the *total -1s* comes from this sensor.\n        percentage_of_total = (count / total_minus_one_count) * 100\n        \n        # Print a formatted row.\n        print(f\"{sensor_id:<15} | {count:<25,} | {percentage_of_total:.2f}%\")\n    \nprint(\"-\" * 75)\nsum_of_counts = sum(sensor_minus_one_counts.values())\nprint(f\"{'Total (Sum of above)':<15} | {sum_of_counts:<25,}\")\nprint(f\"{'Original Total Count':<15} | {total_minus_one_count:<25,}\")\n\nif sum_of_counts == total_minus_one_count:\n    print(\"Verification successful: The sum of individual sensor counts matches the overall total.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:37.135530Z","iopub.execute_input":"2025-08-02T21:55:37.135798Z","iopub.status.idle":"2025-08-02T21:55:38.064653Z","shell.execute_reply.started":"2025-08-02T21:55:37.135770Z","shell.execute_reply":"2025-08-02T21:55:38.063735Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> #### 🟠 ToF Sensor Data Integrity: A Quick Look 🧐\n>\n> Our analysis of the Time-of-Flight (ToF) sensors shows that **57.49%** of all readings are invalid (marked as `-1`). This affects over 105 million data points out of a total of 184 million.\n>\n> The distribution of these invalid readings is quite even across all five sensors, with each sensor contributing between **17% to 22%** of the total. This suggests a systematic issue rather than a random error from a single sensor. This high volume of missing data is a key challenge that needs to be addressed. ⚙️\n","metadata":{}},{"cell_type":"code","source":"tof_pattern = re.compile(r'^tof_[1-5]_v\\d{1,2}$')\ntof_columns = [col for col in df_train.columns if tof_pattern.match(col)]\n\nprint(f\"Identified {len(tof_columns)} ToF columns for analysis.\")\nprint(\"--- Analyzing sequences with 100% invalid ToF data ---\")\n\n# --- Check each row to see if ALL its ToF values are -1 ---\n# We create a new boolean column 'all_tof_are_minus_one'.\ndf_train['all_tof_are_minus_one'] = (df_train[tof_columns].values == -1).all(axis=1)\n\n# --- Group by sequence_id and check the condition ---\n# We group the DataFrame by 'sequence_id'.\nis_sequence_completely_invalid = df_train.groupby('sequence_id')['all_tof_are_minus_one'].all()\n\n# --- Count the sequences that meet the condition and display results ---\n# We can simply sum the boolean Series, as True is treated as 1 and False as 0.\nnum_invalid_sequences = is_sequence_completely_invalid.sum()\n\n# Get the total number of unique sequences for context.\ntotal_unique_sequences = df_train['sequence_id'].nunique()\n\n# Calculate the percentage.\nif total_unique_sequences > 0:\n    percentage_invalid_sequences = (num_invalid_sequences / total_unique_sequences) * 100\nelse:\n    percentage_invalid_sequences = 0\n\nprint(\"--- Results ---\")\nprint(f\"Total number of unique sequences in the dataset: {total_unique_sequences:,}\")\nprint(f\"Number of sequences where ALL ToF values are -1: {num_invalid_sequences:,}\")\nprint(\"---------------------------------------------------------\")\nprint(f\"Percentage of sequences with completely invalid ToF data: {percentage_invalid_sequences:.2f}%\")\n\n# Optional: Display the IDs of the invalid sequences if there are not too many.\nif 0 < num_invalid_sequences < 20:\n    invalid_sequence_ids = is_sequence_completely_invalid[is_sequence_completely_invalid].index.tolist()\n    print(f\"IDs of the invalid sequences: {invalid_sequence_ids}\")\n\n# Cleanup: It's good practice to remove the temporary column we created.\ndf_train.drop(columns=['all_tof_are_minus_one'], inplace=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:38.065483Z","iopub.execute_input":"2025-08-02T21:55:38.065741Z","iopub.status.idle":"2025-08-02T21:55:39.469956Z","shell.execute_reply.started":"2025-08-02T21:55:38.065721Z","shell.execute_reply":"2025-08-02T21:55:39.468757Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> ### ✨✨✨ A Moment of Unexpected Hope ✨✨✨\n>\n> Initially, I ran this check fully expecting to find at least a few sequences where every single ToF sensor reading was lost to the void (`-1`). The results were... surprising.\n>\n> **Result:** `0` sequences are completely invalid.\n>\n> That's right. Zero. I could have just deleted this cell and pretended this check never happened. But I'm keeping it as a monument to hope😁. It proves that even in the darkest, most corrupted sequences, there is, quite literally, a single point of light (or at least, a single valid data point).\n>\n> However, I wasn't satisfied with this result and decided to dig deeper. You can see the findings of that investigation in the next section.\n> ####  I mean here 👇👇👇👇\n","metadata":{}},{"cell_type":"code","source":"num_tof_columns = len(tof_columns)\nprint(f\"Identified {num_tof_columns} ToF columns for analysis.\")\n\n# --- Define the threshold ---\nthreshold_percentage = 70.0\nprint(f\"Searching for sequences with more than {threshold_percentage}% invalid ToF values...\")\n\n# --- Group by sequence_id and calculate stats ---\n# First, calculate the count of -1s for each row, but only across ToF columns\ndf_train['temp_minus_one_count'] = (df_train[tof_columns] == -1).sum(axis=1)\n\n# Now, group by sequence_id and aggregate the results\n# '.size' gives the number of rows in each sequence.\n# '.sum()' on our temp column gives the total number of -1s in each sequence.\nsequence_stats = df_train.groupby('sequence_id').agg(\n    row_count=('sequence_id', 'size'),\n    total_minus_ones=('temp_minus_one_count', 'sum'))\n\n# Clean up the temporary column as it's no longer needed\ndf_train.drop(columns=['temp_minus_one_count'], inplace=True)\n\n# --- Calculate percentage and filter ---\n# Calculate total ToF cells for each sequence (number of rows * number of ToF columns)\nsequence_stats['total_tof_cells'] = sequence_stats['row_count'] * num_tof_columns\n\n# Calculate the percentage of invalid data for each sequence\nepsilon = 1e-9\nsequence_stats['invalid_percentage'] = \\\n    (sequence_stats['total_minus_ones'] / (sequence_stats['total_tof_cells'] + epsilon)) * 100\n\n# Filter the DataFrame to keep only the sequences that meet our criteria\nsequences_above_threshold = sequence_stats[sequence_stats['invalid_percentage'] > threshold_percentage]\n\n# --- Display the results ---\nnum_sequences_above_threshold = len(sequences_above_threshold)\ntotal_unique_sequences = df_train['sequence_id'].nunique()\n\nprint(\"--- Results ---\")\nprint(f\"Total number of unique sequences: {total_unique_sequences:,}\")\nprint(f\"Number of sequences with > {threshold_percentage}% invalid ToF values: {num_sequences_above_threshold:,}\")\n\nif total_unique_sequences > 0:\n    percentage_of_all_sequences = (num_sequences_above_threshold / total_unique_sequences) * 100\n    print(f\"This represents {percentage_of_all_sequences:.2f}% of all sequences.\")\n\n# Optional: Display the top 10 most invalid sequences\nif num_sequences_above_threshold > 0:\n    print(\"--- Top 10 Sequences with the Highest Percentage of Invalid ToF Data ---\")\n    # Prepare the DataFrame for display\n    display_df = sequences_above_threshold.sort_values(by='invalid_percentage', ascending=False).head(10).copy()\n    display_df = display_df[['total_minus_ones', 'total_tof_cells', 'invalid_percentage']] # Reorder for clarity\n\n    # Formatting for better readability\n    display_df['invalid_percentage'] = display_df['invalid_percentage'].map('{:.2f}%'.format)\n    display_df['total_minus_ones'] = display_df['total_minus_ones'].map('{:,}'.format)\n    display_df['total_tof_cells'] = display_df['total_tof_cells'].map('{:,}'.format)\n    \n    # Rename columns for the final display\n    display_df.rename(columns={\n        'total_minus_ones': 'Invalid Cells (-1)',\n        'total_tof_cells': 'Total ToF Cells',\n        'invalid_percentage': 'Invalid %'\n    }, inplace=True)\n\n    print(display_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:39.471185Z","iopub.execute_input":"2025-08-02T21:55:39.471641Z","iopub.status.idle":"2025-08-02T21:55:41.135530Z","shell.execute_reply.started":"2025-08-02T21:55:39.471606Z","shell.execute_reply":"2025-08-02T21:55:41.134699Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Identified {num_tof_columns} ToF columns for analysis.\")\n\n# --- Step 2: Define the NEW threshold ---\nthreshold_percentage_low = 30.0  # New threshold\nprint(f\"Searching for sequences with LESS than {threshold_percentage_low}% invalid ToF values...\")\n\n# --- Step 3: Group by sequence_id and calculate stats ---\n# Calculate the count of -1s for each row across ToF columns\ndf_train['temp_minus_one_count'] = (df_train[tof_columns] == -1).sum(axis=1)\n\n# Group by sequence_id and aggregate\nsequence_stats = df_train.groupby('sequence_id').agg(\n    row_count=('sequence_id', 'size'),\n    total_minus_ones=('temp_minus_one_count', 'sum')\n)\n\n# Clean up the temporary column\ndf_train.drop(columns=['temp_minus_one_count'], inplace=True)\n\n# --- Step 4: Calculate percentage and filter (with the key change) ---\n# Calculate total ToF cells for each sequence\nsequence_stats['total_tof_cells'] = sequence_stats['row_count'] * num_tof_columns\n\n# Calculate the percentage of invalid data\nepsilon = 1e-9  # To prevent division by zero\nsequence_stats['invalid_percentage'] = \\\n    (sequence_stats['total_minus_ones'] / (sequence_stats['total_tof_cells'] + epsilon)) * 100\n\n# --- THE CRITICAL CHANGE IS HERE ---\n# Filter the DataFrame to keep sequences BELOW the threshold\nsequences_below_threshold = sequence_stats[sequence_stats['invalid_percentage'] < threshold_percentage_low]\n\n# --- Step 5: Display the results ---\nnum_sequences_below_threshold = len(sequences_below_threshold)\ntotal_unique_sequences = df_train['sequence_id'].nunique()\n\nprint(\"--- Results ---\")\nprint(f\"Total number of unique sequences: {total_unique_sequences:,}\")\nprint(f\"Number of sequences with < {threshold_percentage_low}% invalid ToF values: {num_sequences_below_threshold:,}\")\n\nif total_unique_sequences > 0:\n    percentage_of_all_sequences = (num_sequences_below_threshold / total_unique_sequences) * 100\n    print(f\"This represents {percentage_of_all_sequences:.2f}% of all sequences.\")\n\n# Optional: Display the top 10 cleanest sequences (those with the lowest percentage of invalid data)\nif num_sequences_below_threshold > 0:\n    print(\"--- Top 10 Sequences with the LOWEST Percentage of Invalid ToF Data (Cleanest) ---\")\n    # Prepare the DataFrame for display\n    # Sorting is now ascending to show the best sequences first\n    display_df = sequences_below_threshold.sort_values(by='invalid_percentage', ascending=True).head(10).copy()\n    display_df = display_df[['total_minus_ones', 'total_tof_cells', 'invalid_percentage']] # Reorder for clarity\n\n    # Formatting for better readability\n    display_df['invalid_percentage'] = display_df['invalid_percentage'].map('{:.2f}%'.format)\n    display_df['total_minus_ones'] = display_df['total_minus_ones'].map('{:,}'.format)\n    display_df['total_tof_cells'] = display_df['total_tof_cells'].map('{:,}'.format)\n    \n    # Rename columns for the final display\n    display_df.rename(columns={\n        'total_minus_ones': 'Invalid Cells (-1)',\n        'total_tof_cells': 'Total ToF Cells',\n        'invalid_percentage': 'Invalid %'\n    }, inplace=True)\n\n    print(display_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:41.136397Z","iopub.execute_input":"2025-08-02T21:55:41.136712Z","iopub.status.idle":"2025-08-02T21:55:42.604758Z","shell.execute_reply.started":"2025-08-02T21:55:41.136685Z","shell.execute_reply":"2025-08-02T21:55:42.603766Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> #### 🟠 ToF Sensor Data Integrity: A Deep Look 🤔\n>\n>\n> After discovering that no sequence was *entirely* broken, I adjusted the thresholds to get a more practical view of the data quality. The results paint a stark picture of data reliability for the ToF sensors.\n>\n> ---\n>\n> ### The \"Bad\" News: The Vast Majority is Heavily Corrupted 😟\n>\n> We first looked for sequences that are mostly invalid.\n>\n> - **Finding:** **`2,328` sequences (a staggering `28.56%` of the total)** have **more than 70%** of their ToF sensor readings as invalid (`-1`).\n>\n> This means that for over a quarter of our data, we are working with less than 30% of the actual sensor information. Relying on such sparse data for modeling is incredibly risky and likely to introduce more noise than signal.\n>\n> ### The \"Good\" News: Clean Data is a Rarity ✨\n>\n> On the flip side, we searched for high-quality, \"clean\" sequences.\n>\n> - **Finding:** Only **`1,044` sequences (a mere `12.81%` of the total)** have **less than 30%** invalid data.\n>\n> While it's fantastic to see that some sequences (`SEQ_035116`, `SEQ_042079`, etc.) have `0.00%` invalid data, they represent a small and privileged minority.\n>\n> ---\n>\n> ### 🛑🛑🛑 **Conclusion: Don't Bet on ToF** 🛑🛑🛑\n>\n> Placing these two facts side-by-side leads to a critical conclusion: **the ToF sensor data is, for the most part, unreliable for building a robust predictive model.**\n>\n> While we have a small cluster of high-quality sequences, a much larger portion of the dataset is severely compromised. Building features on this sensor data would likely weaken the model's performance and generalization capabilities. Therefore, for our initial modeling efforts, we should probably avoid relying heavily on the ToF features.\n","metadata":{}},{"cell_type":"markdown","source":"## ⚪ Visualizing Helios ToF Sensor ✨","metadata":{}},{"cell_type":"markdown","source":"> #### 🟠 Visualizing ToF Sensor Data in Physical Layout ✨\n>\n>Here, we transform raw Time-of-Flight (ToF) sensor data into an intuitive animation. Instead of displaying the sensor animations in a simple line, we arrange them to mirror the **physical, cross-shaped layout** of the sensors on the Helios device.\n>\n>### Why This Layout Matters 🚀\n>\n>Displaying the sensors side-by-side (e.g., in a 1x5 row) would hide the crucial spatial relationships between them. By arranging them according to their real-world positions, we can instantly understand the physical context of the data:\n>\n>*   **Spatial Correlation:** We can see how an object (like a hand) moving from left to right causes the heatmaps on the \"left\" and \"right\" sensors to activate sequentially.\n>*   **Intuitive Analysis:** This layout transforms abstract data into a visual story of the interaction, making it far easier to identify patterns, gestures, and potential anomalies.\n>\n>### Our Process\n>\n>1.  **Select a Sequence:** We pick one complete activity sequence from the dataset.\n>2.  **Map to Physical Grid:** We arrange the five ToF sensor heatmaps (`8x8` pixels each) onto a `3x3` grid, matching their physical positions (Top, Bottom, Left, Right, Center).\n>3.  **Create Synchronized GIF:** We generate a single GIF to observe how these spatial patterns evolve over time.\n","metadata":{}},{"cell_type":"code","source":"# Step 1: Select a Random Sequence\nif not df_train.empty and 'sequence_id' in df_train.columns:\n    unique_sequences = df_train['sequence_id'].unique()\n    random_sequence_id = random.choice(unique_sequences)\n    \n    sequence_df = df_train[df_train['sequence_id'] == random_sequence_id].copy()\n    sequence_df.sort_values('sequence_counter', inplace=True)\n    \n    subject = sequence_df.iloc[0]['subject']\n    # Placeholders for metadata not present in the main file\n    gesture = \"N/A\"\n    orientation = \"N/A\"\n\n    print(\"Random sequence selected:\")\n    print(f\"  - Sequence ID: {random_sequence_id}\")\n    print(f\"  - Subject: {subject}\")\n    print(f\"  - Number of frames: {len(sequence_df)}\")\nelse:\n    print(\"DataFrame is empty or 'sequence_id' column is missing. Cannot proceed.\")\n    sequence_df = pd.DataFrame()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:42.605868Z","iopub.execute_input":"2025-08-02T21:55:42.606147Z","iopub.status.idle":"2025-08-02T21:55:42.690379Z","shell.execute_reply.started":"2025-08-02T21:55:42.606119Z","shell.execute_reply":"2025-08-02T21:55:42.689565Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to Create a Physically-Arranged Combined Animation\ndef create_physical_layout_animation(sequence_data, sequence_id, subject_id, gesture_info, orientation_info, output_dir=\"tof_animations\"):\n    \"\"\"\n    Generates a single GIF arranging ToF sensor animations to mimic their\n    physical layout on the Helios device (cross-shape).\n    \"\"\"\n    num_sensors = 5\n    pixel_count = 64\n    img_shape = (8, 8)\n    \n    print(\"\\n--- Preparing data for physical layout animation ---\")\n    \n    # Extract all ToF data and find global min/max for consistent coloring\n    tof_columns = [f'tof_{s}_v{p}' for s in range(1, num_sensors + 1) for p in range(pixel_count)]\n    existing_tof_columns = [col for col in tof_columns if col in sequence_data.columns]\n    \n    if not existing_tof_columns:\n        print(\"Error: No ToF columns found in the DataFrame.\")\n        return None\n\n    min_val = sequence_data[existing_tof_columns].min().min()\n    max_val = sequence_data[existing_tof_columns].max().max()\n    print(f\"ToF data values in this sequence range from {min_val:.2f} to {max_val:.2f}.\")\n\n    # Reshape data for each sensor\n    sensor_frames_map = {}\n    for s_num in range(1, num_sensors + 1):\n        s_cols = [f'tof_{s_num}_v{p}' for p in range(pixel_count)]\n        if all(col in sequence_data.columns for col in s_cols):\n            s_data = sequence_data[s_cols].values\n            sensor_frames_map[s_num] = s_data.reshape(-1, img_shape[0], img_shape[1])\n        else:\n            print(f\"Warning: Data for Sensor {s_num} is incomplete. It will not be displayed.\")\n\n    if not sensor_frames_map:\n        print(\"Error: No complete sensor data available to create animation.\")\n        return None\n        \n    num_frames = next(iter(sensor_frames_map.values())).shape[0]\n    combined_images = []\n    \n    print(f\"Generating {num_frames} combined frames for the physical layout...\")\n\n    # Define the grid positions for each sensor\n    # (row, col) in a 3x3 grid\n    sensor_positions = {\n        1: (1, 1),  # Center\n        2: (0, 1),  # Top\n        3: (1, 0),  # Left  (<- Was Right)\n        4: (2, 1),  # Bottom\n        5: (1, 2)   # Right (<- Was Left)\n    }\n\n    for frame_idx in range(num_frames):\n        # Create a 3x3 subplot grid. `constrained_layout=True` helps manage spacing.\n        fig, axes = plt.subplots(3, 3, figsize=(12, 12), constrained_layout=True)\n        \n        # Turn off all axes by default. We'll turn them on only for sensors.\n        for ax in axes.flat:\n            ax.axis('off')\n\n        for sensor_num, pos in sensor_positions.items():\n            if sensor_num in sensor_frames_map:\n                ax = axes[pos[0], pos[1]] # Get the correct subplot\n                ax.axis('on') # Turn it back on\n                frame_data = sensor_frames_map[sensor_num][frame_idx]\n                \n                ax.imshow(frame_data, cmap='viridis', vmin=min_val, vmax=max_val)\n                ax.set_title(f\"ToF Sensor {sensor_num}\")\n                ax.set_xticks([])\n                ax.set_yticks([])\n        \n        # Add a shared main title (suptitle) with all common information\n        shared_info = (f\"Frame: {frame_idx + 1}/{num_frames} | Sequence ID: {sequence_id}\\n\"\n                       f\"Subject: {subject_id} | Gesture: {gesture_info} | Orientation: {orientation_info}\")\n        fig.suptitle(shared_info, fontsize=16)\n        \n        # Convert the entire figure (with the 3x3 grid) into a single image\n        fig.canvas.draw()\n        image = np.frombuffer(fig.canvas.tostring_rgb(), dtype='uint8')\n        image = image.reshape(fig.canvas.get_width_height()[::-1] + (3,))\n        combined_images.append(image)\n        plt.close(fig) # Close the figure to free up memory\n\n    os.makedirs(output_dir, exist_ok=True)\n    gif_path = os.path.join(output_dir, f'physical_layout_seq_{sequence_id}.gif')\n    \n    print(f\"Saving physical layout animation to '{gif_path}'...\")\n    imageio.mimsave(gif_path, combined_images, duration=100, loop=0)\n    print(\"✅ Physical layout animation saved successfully.\")\n    \n    return gif_path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:42.691328Z","iopub.execute_input":"2025-08-02T21:55:42.691629Z","iopub.status.idle":"2025-08-02T21:55:42.705519Z","shell.execute_reply.started":"2025-08-02T21:55:42.691608Z","shell.execute_reply":"2025-08-02T21:55:42.704655Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 2: Generate and Display the Physical Layout Animation -\nif not sequence_df.empty:\n    physical_layout_gif_path = create_physical_layout_animation(\n        sequence_data=sequence_df,\n        sequence_id=random_sequence_id,\n        subject_id=subject,\n        gesture_info=gesture,\n        orientation_info=orientation\n    )\n    if physical_layout_gif_path:\n        # Display the final GIF in the output cell\n        print(f\"\\n👇 Displaying physical layout animation for sequence {random_sequence_id}:\")\n        display(Image(filename=physical_layout_gif_path, width=600)) # Adjust width for optimal viewing","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-02T21:55:42.706452Z","iopub.execute_input":"2025-08-02T21:55:42.706741Z","iopub.status.idle":"2025-08-02T21:56:23.050976Z","shell.execute_reply.started":"2025-08-02T21:55:42.706718Z","shell.execute_reply":"2025-08-02T21:56:23.050085Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Finish for now","metadata":{}},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}