{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"},{"sourceId":255117627,"sourceType":"kernelVersion"}],"dockerImageVersionId":31040,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# <div style=\"color:black; border:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:10px; text-align:center\">⭐ CMI - Detect BFRB with Sensor Data</div>\n\n![Skin picking and hair pulling are the most common types of BFRB](https://my.clevelandclinic.org/-/scassets/images/org/health/articles/body-focused-repetitive-behavior-bfrb)\n\nSeveral studies found that between 0.5% and 4.4% of people have a clinical BFRB diagnosis. However, the rate of occurrence is actually much higher because many people don’t reach out to a healthcare provider for help with these conditions. Skin picking and hair pulling are the most common types of BFRB [5]\n\n# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">📌 Objective of Competition</div>\n\nThe CMI - Detect Behavior with Sensor Data  competition aims to develop a predictive model that can distinguish BFRB-like gestures  (e.g., hair pulling, skin pinching) from non-BFRB-like gestures  (e.g., drinking from a cup, texting).  \n\n# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🌎 Real-World Impact</div>\n1. **Improved BFRB Detection :** Enhances wearable tech to accurately identify harmful behaviors like hair-pulling or skin-picking, enabling early intervention and personalized treatment for individuals with anxiety or OCD.  \n2. **Sensor Optimization :** Determines if advanced sensors (thermopiles/ToF) justify their cost, guiding cost-effective design of future devices for broader accessibility.  \n3. **Mental Health Impact :** Advances tools for monitoring and managing BFRBs, reducing physical harm and improving quality of life for affected individuals through data-driven insights.\n\n# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">❓ What is Exocriation (Skin picking) ?</div>\nFor some teenagers, repetitive skin picking, often targeting the face or hands, goes beyond a mere bad habit and is recognized as a body-focused repetitive behavior related to OCD. Teens who engage in this feel an irresistible urge to pick, even when it results in scabs or permanent scarring, often driven by stress or a desire to smooth perceived imperfections (functional picking) or to self-soothe upsetting emotions (compulsive picking). This behavior can lead to significant shame and social withdrawal, sometimes even depression. Stopping skin picking is very difficult without help, and parental interventions like telling a child to stop are often ineffective. Treatment typically requires the teen's willingness to change and may involve covering affected areas, medication for underlying anxiety or depression, or cognitive behavioral therapy (CBT) to help them identify triggers and develop healthier coping mechanisms. [1]\n\nThe video below further explains skin picking [4]\n<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/o7bx3eVh4DE?si=nd8VUJ-JoxojI_wF\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>\n\n# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">❔ What is Trichotillomania (Hair pulling) ?</div>\nTrichotillomania is a compulsive hair-pulling disorder, categorized alongside OCD, where individuals pull hair from various body parts, often to alleviate discomfort, anxiety, or due to a pleasurable sensation. Those affected frequently attempt to stop but are unsuccessful, leading to significant distress, embarrassment, and potential social difficulties due to visible hair loss. The disorder manifests in two forms: automatic pulling, where individuals are often unaware of the behavior, and focused pulling, which is intentional and often done in private. Effective treatment begins with identifying a specialist familiar with trichotillomania who can determine the type and triggers of the pulling. Traditional treatment, habit reversal therapy (HRT), involves developing a \"competing response\"—a safer alternative behavior to replace hair pulling. A newer, more comprehensive approach, \"comprehensive behavioral treatment\" (ComB), integrates HRT with other therapies like dialectical behavioral therapy (DBT) and acceptance and commitment therapy (ACT) to help individuals manage emotions and stress in healthier ways, reducing the urge to pull. [2]\n\n# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🪙 Gold Standard for Treating OCD</div>\nPhD Dr. Jerry Bubrick explains **Exposure with Preventive Response** as the gold standard for treating OCD [3]\n<iframe width=\"720\" height=\"405\" src=\"https://www.youtube.com/embed/ml9ViuZ5j3w\" title=\"Treating OCD: What Is Exposure With Response Prevention?\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen></iframe>\n\n# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🚆 Model Training</div>\nDue to time limitations, model was trained separately in this notebook\n[https://www.kaggle.com/code/taimour/cmi-catboost-training](https://www.kaggle.com/code/taimour/cmi-catboost-training)\n\n# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🖼️ Pipleline Picture</div>\n![](data:image/png;base64,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)","metadata":{}},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">📚 Libraries / Packages</div>","metadata":{}},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom matplotlib import pyplot as plt\nimport seaborn as sns\nfrom matplotlib.ticker import MaxNLocator\nimport matplotlib.pyplot as plt\nfrom mpl_toolkits.mplot3d import Axes3D\nimport os\nimport polars as pl\nimport kaggle_evaluation.cmi_inference_server\nfrom sklearn.metrics import accuracy_score, f1_score\nimport joblib\nfrom scipy.spatial.transform import Rotation as R","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:46:59.660392Z","iopub.execute_input":"2025-07-28T06:46:59.660836Z","iopub.status.idle":"2025-07-28T06:47:03.552529Z","shell.execute_reply.started":"2025-07-28T06:46:59.660810Z","shell.execute_reply":"2025-07-28T06:47:03.551810Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">📖 Load Data</div>","metadata":{}},{"cell_type":"code","source":"train_pl = pl.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train.csv')\ntrain_demo_pl = pl.read_csv('/kaggle/input/cmi-detect-behavior-with-sensor-data/train_demographics.csv')\ntrain = train_pl.to_pandas()\ntrain_demo = train_demo_pl.to_pandas()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:03.553274Z","iopub.execute_input":"2025-07-28T06:47:03.553669Z","iopub.status.idle":"2025-07-28T06:47:16.050397Z","shell.execute_reply.started":"2025-07-28T06:47:03.553645Z","shell.execute_reply":"2025-07-28T06:47:16.049788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:16.051748Z","iopub.execute_input":"2025-07-28T06:47:16.052024Z","iopub.status.idle":"2025-07-28T06:47:16.080557Z","shell.execute_reply.started":"2025-07-28T06:47:16.052003Z","shell.execute_reply":"2025-07-28T06:47:16.080005Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">📊 Exploratory Data Analysis</div>","metadata":{}},{"cell_type":"code","source":"train.head(2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:16.081123Z","iopub.execute_input":"2025-07-28T06:47:16.081339Z","iopub.status.idle":"2025-07-28T06:47:16.129656Z","shell.execute_reply.started":"2025-07-28T06:47:16.081322Z","shell.execute_reply":"2025-07-28T06:47:16.129098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:16.130294Z","iopub.execute_input":"2025-07-28T06:47:16.130904Z","iopub.status.idle":"2025-07-28T06:47:22.272876Z","shell.execute_reply.started":"2025-07-28T06:47:16.130880Z","shell.execute_reply":"2025-07-28T06:47:22.272136Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🎯 Gesture (Target Column)</div>","metadata":{}},{"cell_type":"code","source":"gesture_counts = train['gesture'].value_counts()\n\n# Calculate percentages\npercentages = (gesture_counts / gesture_counts.sum()) * 100\n\n# Create labels with both value counts and percentages\nlabels = [f'{label} ({count}, {perc:.1f}%)' for label, count, perc in zip(gesture_counts.index, gesture_counts.values, percentages)]\n\n# Create the pie chart\nplt.figure(figsize=(8, 8)) # Set figure size for better readability\nplt.pie(gesture_counts, labels=labels, startangle=160, counterclock=False, wedgeprops={'edgecolor': 'black'})\nplt.title('Distribution of Gestures')\nplt.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:22.273514Z","iopub.execute_input":"2025-07-28T06:47:22.273753Z","iopub.status.idle":"2025-07-28T06:47:22.601412Z","shell.execute_reply.started":"2025-07-28T06:47:22.273735Z","shell.execute_reply":"2025-07-28T06:47:22.600600Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">⭕ Orientation</div>","metadata":{}},{"cell_type":"code","source":"# Donut Chart\nfig, ax = plt.subplots(figsize=(8, 8))\n\n# Calculate percentages for labels (optional, can also use autopct)\norientation_counts = train[\"orientation\"].value_counts().head()\ntotal_count = orientation_counts.sum()\nlabels = [f'{label} ({count})' for label, count in orientation_counts.items()]\n\nwedges, texts, autotexts = ax.pie(\n    orientation_counts,\n    autopct='%1.1f%%',\n    startangle=90,\n    colors=plt.cm.Paired.colors,\n    pctdistance=0.85, # Distance of the percentage labels from the center\n    wedgeprops=dict(width=0.3, edgecolor='w'), # Create the donut hole\n    labels=labels\n)\n\n# Draw a circle in the middle to make it a donut chart\ncentre_circle = plt.Circle((0,0), 0.70, fc='white')\nfig.gca().add_artist(centre_circle)\n\nax.set_title(\"Subject's orientation (Donut Chart)\")\nax.axis('equal')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:22.602294Z","iopub.execute_input":"2025-07-28T06:47:22.602536Z","iopub.status.idle":"2025-07-28T06:47:22.751048Z","shell.execute_reply.started":"2025-07-28T06:47:22.602517Z","shell.execute_reply":"2025-07-28T06:47:22.750465Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Vertical Bar Chart\nfig, ax = plt.subplots(figsize=(8, 6))\norientation_counts.sort_values(ascending=False).plot( # Often good to sort descending for vertical bars\n    kind=\"bar\", color='purple', ax=ax, title=\"Target (Vertical Bar)\"\n)\nax.set_xlabel(\"Target\")\nax.set_ylabel(\"Count\")\nplt.xticks(rotation=20, ha='right') # Rotate x-axis labels for better readability if they overlap\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:22.751865Z","iopub.execute_input":"2025-07-28T06:47:22.752131Z","iopub.status.idle":"2025-07-28T06:47:23.035688Z","shell.execute_reply.started":"2025-07-28T06:47:22.752106Z","shell.execute_reply":"2025-07-28T06:47:23.034921Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🟢 Sequence Type (Target vs Non Target)</div>","metadata":{}},{"cell_type":"code","source":"# Donut Chart\nfig, ax = plt.subplots(figsize=(8, 8))\n\n# Calculate percentages for labels (optional, can also use autopct)\nsequence_type_counts = train[\"sequence_type\"].value_counts().head()\ntotal_count = sequence_type_counts.sum()\nlabels = [f'{label} ({count})' for label, count in sequence_type_counts.items()]\n\nwedges, texts, autotexts = ax.pie(\n    sequence_type_counts,\n    autopct='%1.1f%%',\n    startangle=90,\n    colors=plt.cm.Paired.colors,\n    pctdistance=0.85, # Distance of the percentage labels from the center\n    wedgeprops=dict(width=0.3, edgecolor='w'), # Create the donut hole\n    labels=labels\n)\n\n# Draw a circle in the middle to make it a donut chart\ncentre_circle = plt.Circle((0,0), 0.70, fc='white')\nfig.gca().add_artist(centre_circle)\n\nax.set_title(\"Target vs Non-target (Donut Chart)\")\nax.axis('equal')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:23.038401Z","iopub.execute_input":"2025-07-28T06:47:23.038614Z","iopub.status.idle":"2025-07-28T06:47:23.173847Z","shell.execute_reply.started":"2025-07-28T06:47:23.038598Z","shell.execute_reply":"2025-07-28T06:47:23.173187Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Vertical Bar Chart\nfig, ax = plt.subplots(figsize=(8, 6))\nsequence_type_counts.sort_values(ascending=False).plot( # Often good to sort descending for vertical bars\n    kind=\"bar\", color='lightgreen', ax=ax, title=\"Target vs Non Target (Vertical Bar)\"\n)\nax.set_xlabel(\"Target\")\nax.set_ylabel(\"Count\")\nplt.xticks(rotation=20, ha='right') # Rotate x-axis labels for better readability if they overlap\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:23.174542Z","iopub.execute_input":"2025-07-28T06:47:23.174760Z","iopub.status.idle":"2025-07-28T06:47:23.325426Z","shell.execute_reply.started":"2025-07-28T06:47:23.174737Z","shell.execute_reply":"2025-07-28T06:47:23.324798Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🔵 Behavior</div>","metadata":{}},{"cell_type":"code","source":"# Donut Chart\nfig, ax = plt.subplots(figsize=(8, 8))\n\n# Calculate percentages for labels (optional, can also use autopct)\nbehavior_counts = train[\"behavior\"].value_counts().head()\ntotal_count = behavior_counts.sum()\nlabels = [f'{label} ({count})' for label, count in behavior_counts.items()]\n\nwedges, texts, autotexts = ax.pie(\n    behavior_counts,\n    autopct='%1.1f%%',\n    startangle=90,\n    colors=plt.cm.Paired.colors,\n    pctdistance=0.85, # Distance of the percentage labels from the center\n    wedgeprops=dict(width=0.3, edgecolor='w'), # Create the donut hole\n    labels=labels\n)\n\n# Draw a circle in the middle to make it a donut chart\ncentre_circle = plt.Circle((0,0), 0.70, fc='white')\nfig.gca().add_artist(centre_circle)\n\nax.set_title(\"Subject's Behavior During the Sequence (Donut Chart)\")\nax.axis('equal')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:23.326159Z","iopub.execute_input":"2025-07-28T06:47:23.326390Z","iopub.status.idle":"2025-07-28T06:47:23.503610Z","shell.execute_reply.started":"2025-07-28T06:47:23.326373Z","shell.execute_reply":"2025-07-28T06:47:23.502890Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Vertical Bar Chart\nfig, ax = plt.subplots(figsize=(8, 6))\nbehavior_counts.sort_values(ascending=False).plot( # Often good to sort descending for vertical bars\n    kind=\"bar\", color='skyblue', ax=ax, title=\"Subject's Behavior During the Sequence (Vertical Bar)\"\n)\nax.set_xlabel(\"Behavior\")\nax.set_ylabel(\"Count\")\nplt.xticks(rotation=20, ha='right') # Rotate x-axis labels for better readability if they overlap\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:23.504600Z","iopub.execute_input":"2025-07-28T06:47:23.504907Z","iopub.status.idle":"2025-07-28T06:47:23.663905Z","shell.execute_reply.started":"2025-07-28T06:47:23.504883Z","shell.execute_reply":"2025-07-28T06:47:23.663209Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">✳️ Demographics Correlation Matrix</div>\nCorrelation heatmap for training data","metadata":{}},{"cell_type":"code","source":"# correlation matrix\nplt.figure(figsize = (8, 4))\nsns.heatmap(data = train_demo.corr(numeric_only = True), linewidth = 0.5, annot = True, linecolor = \"black\")\nplt.title('Train Data Demographic Correlation Heatmap')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:23.664666Z","iopub.execute_input":"2025-07-28T06:47:23.664894Z","iopub.status.idle":"2025-07-28T06:47:23.929866Z","shell.execute_reply.started":"2025-07-28T06:47:23.664872Z","shell.execute_reply":"2025-07-28T06:47:23.929222Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">✴️ Gender Distribution</div>","metadata":{}},{"cell_type":"code","source":"# Get value counts for gender\nvc = train_demo['sex'].value_counts()\n\n# Extract counts (assuming 'count' column doesn't exist)\ncounts = vc.values\n\n# Create labels (assuming 'count' column doesn't exist)\nlabels = ['Male', 'Female']  # Assuming 'Male' maps to '1' and 'Female' to '0'\n\n# Plot the pie chart\nplt.pie(counts, labels=labels, autopct=\"%1.1f%%\")  # Add percentages to pie slices\nplt.title('Gender')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:23.930741Z","iopub.execute_input":"2025-07-28T06:47:23.931007Z","iopub.status.idle":"2025-07-28T06:47:23.998544Z","shell.execute_reply.started":"2025-07-28T06:47:23.930982Z","shell.execute_reply":"2025-07-28T06:47:23.998012Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🎗️ Age Distribution with respect to Gender</div>","metadata":{}},{"cell_type":"code","source":"#'train' is DataFrame\n_, axs = plt.subplots(2, 1, sharex=True)\n\nfor sex in range(2):\n  ax = axs.ravel()[sex]\n\n  # Filter by sex using boolean indexing\n  sex_filter = train_demo['sex'] == sex\n  vc = train_demo[sex_filter]['age'].value_counts()  # Access column after filtering\n\n  # Plot the bar chart\n  ax.bar(vc.index, vc.values, color=['lightblue', 'coral'][sex], label=['Male', 'Female'][sex])\n  ax.xaxis.set_major_locator(MaxNLocator(integer=True))\n  ax.set_ylabel('count')\n  ax.legend()\n\nplt.suptitle('Age distribution with respect to Gender')\naxs.ravel()[1].set_xlabel('years')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:23.999136Z","iopub.execute_input":"2025-07-28T06:47:23.999386Z","iopub.status.idle":"2025-07-28T06:47:24.294405Z","shell.execute_reply.started":"2025-07-28T06:47:23.999371Z","shell.execute_reply":"2025-07-28T06:47:24.293636Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🚄 Linear Acceleration from IMU Sensor</div>\nMeasure linear acceleration along three axes in meters per second squared from the IMU sensor.","metadata":{}},{"cell_type":"code","source":"\n# Initialize the 3D plot\nfig = plt.figure(figsize=(10, 10)) # Adjust figure size for better visibility\nax = fig.add_subplot(111, projection='3d')\n\n# Get unique values from the 'gesture' column for coloring\nunique_gestures = train['gesture'].unique()\n\n# Use a colormap to get distinct colors for each gesture type\n# matplotlib.colormaps is the recommended way to access colormaps now\ncolors = plt.colormaps['tab10'].resampled(len(unique_gestures)) # 'tab10' is good for categorical data\n\n# Iterate through unique gestures and plot them with different colors\nfor i, gesture_type in enumerate(unique_gestures):\n    subset = train[train['gesture'] == gesture_type]\n    ax.scatter(subset['acc_x'], subset['acc_y'], subset['acc_z'],\n               color=colors(i),    # Assign a unique color from the colormap\n               label=gesture_type, # Label for the legend\n               alpha=0.7,          # Opacity (matches your Plotly opacity)\n               s=2,\n               rasterized=True)                # Size of points (similar to Plotly's size_max=1, adjusted for visibility)\n\n# Set labels and title\nax.set_xlabel('acc_x', fontsize=14)\nax.set_ylabel('acc_y', fontsize=14)\nax.set_zlabel('acc_z', fontsize=14)\nax.set_title('Linear acceleration along X, Y, Z axes', fontsize=16)\n\n# Add a legend to differentiate 'gesture' types\nax.legend(title='Gesture Type', fontsize=10, title_fontsize=12, loc='upper right')\n\n# Set tick label font size\nax.tick_params(axis='both', which='major', labelsize=12)\n\n# --- Mimic Plotly's 'plotly_dark' template ---\n# Set the background color of the plot\nax.set_facecolor('black')\n\n# Make the pane background transparent\nax.xaxis.pane.fill = False\nax.yaxis.pane.fill = False\nax.zaxis.pane.fill = False\n\n# Set the pane edge colors\nax.xaxis.pane.edgecolor = 'grey'\nax.yaxis.pane.edgecolor = 'grey'\nax.zaxis.pane.edgecolor = 'grey'\n\n# Set the actual pane face colors to transparent\nax.xaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\nax.yaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\nax.zaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\n\n# Set grid lines color\nax.grid(color='gray', linestyle='--', linewidth=0.5, alpha=0.5)\n\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:24.295138Z","iopub.execute_input":"2025-07-28T06:47:24.295368Z","iopub.status.idle":"2025-07-28T06:47:32.871831Z","shell.execute_reply.started":"2025-07-28T06:47:24.295352Z","shell.execute_reply":"2025-07-28T06:47:32.871065Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"3D Scatter plot shows linear accelartion along x, y and z axes. Color acts as the fourth dimension and differenciates different points based on the target column i.e gesture. ","metadata":{}},{"cell_type":"code","source":"# Initialize the 3D plot\nfig = plt.figure(figsize=(10, 10)) # Adjust figure size for better visibility\nax = fig.add_subplot(111, projection='3d')\n\n# Get unique values from 'sequence_type' for coloring\nunique_sequence_types = train['sequence_type'].unique()\n\n# --- ADDRESSING DEPRECATION WARNING 1: get_cmap ---\n# Use matplotlib.colormaps[name] instead of plt.cm.get_cmap(name)\ncolors = plt.colormaps['viridis'].resampled(len(unique_sequence_types))\n\n# Iterate through unique sequence types and plot them with different colors\nfor i, seq_type in enumerate(unique_sequence_types):\n    subset = train[train['sequence_type'] == seq_type]\n    ax.scatter(subset['acc_x'], subset['acc_y'], subset['acc_z'],\n               color=colors(i), # Assign a unique color\n               label=seq_type,  # Label for the legend\n               alpha=0.7,       # Opacity\n               s=10)            # Size of points (similar to Plotly's size_max)\n\n# Set labels and title\nax.set_xlabel('acc_x', fontsize=14)\nax.set_ylabel('acc_y', fontsize=14)\nax.set_zlabel('acc_z', fontsize=14)\nax.set_title('Linear acceleration along X, Y, Z axes', fontsize=16)\n\n# Add a legend to differentiate 'sequence_type'\nax.legend(title='Sequence Type', fontsize=12, title_fontsize=14)\n\n# Set tick label font size\nax.tick_params(axis='both', which='major', labelsize=12)\n\n# Set background color to dark and adjust grid/pane colors\nax.set_facecolor('black')\n\n# --- ADDRESSING DEPRECATION WARNINGS 2, 3, 4: w_xaxis, w_yaxis, w_zaxis ---\n# Use .xaxis, .yaxis, .zaxis directly for panes\nax.xaxis.pane.fill = False\nax.yaxis.pane.fill = False\nax.zaxis.pane.fill = False\n\nax.xaxis.pane.edgecolor = 'grey'\nax.yaxis.pane.edgecolor = 'grey'\nax.zaxis.pane.edgecolor = 'grey'\n\n# Set pane colors to transparent for a truly dark effect\n# These are now accessed directly through .xaxis.pane.set_facecolor\nax.xaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\nax.yaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\nax.zaxis.pane.set_facecolor((0.0, 0.0, 0.0, 0.0))\n\nax.grid(color='gray', linestyle='--', linewidth=0.5, alpha=0.5)\n\nplt.tight_layout() # Adjust layout to prevent labels from overlapping\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:32.872621Z","iopub.execute_input":"2025-07-28T06:47:32.872833Z","iopub.status.idle":"2025-07-28T06:47:43.972547Z","shell.execute_reply.started":"2025-07-28T06:47:32.872817Z","shell.execute_reply":"2025-07-28T06:47:43.971856Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"3D Scatter plot shows linear accelartion along x, y and z axes. Color acts as the fourth dimension and differenciates points based on sequence_type column i.e target and non-target. ","metadata":{}},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🔃 Rotation of Gyroscope from IMU Sensor</div>","metadata":{}},{"cell_type":"code","source":"fig = plt.figure(figsize=(14, 8)) # Adjust figure size as needed\nax = fig.add_subplot(111, projection='3d')\n\n# Plot the 3D line\n# For color, matplotlib's plot3D doesn't directly support a continuous 'color'\n# argument like px.line_3d. You'd typically use a colormap and map 'rot_w' to it.\n# This example uses a simple line color. For continuous color, you'd need\n# to segment your data or use scatter3D with c=train['rot_w'].\n# ax.plot(train['rot_x'], train['rot_y'], train['rot_z'], color='skyblue', linewidth=0.5)\n\n# If you want to color by 'rot_w' with a gradient, it's often better to use scatter3D\n# if a continuous line isn't strictly necessary, or segment the line.\n# For a true line plot with continuous color, it's more complex in matplotlib.\n# Example for scatter3D with color:\nsc = ax.scatter(train['rot_x'], train['rot_y'], train['rot_z'], c=train['rot_w'], cmap='viridis', s=1)\nfig.colorbar(sc, ax=ax, label='rot_w')\n\n\nax.set_xlabel('rot_x')\nax.set_ylabel('rot_y')\nax.set_zlabel('rot_z')\nax.set_title('3D Path of Gyroscope Rotation Over Time')\n\n# Adjust font sizes if needed (similar to update_layout in Plotly)\nax.tick_params(axis='both', which='major', labelsize=12)\nax.xaxis.label.set_size(14)\nax.yaxis.label.set_size(14)\nax.zaxis.label.set_size(14)\nax.title.set_size(16)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:43.973604Z","iopub.execute_input":"2025-07-28T06:47:43.973825Z","iopub.status.idle":"2025-07-28T06:47:50.902557Z","shell.execute_reply.started":"2025-07-28T06:47:43.973808Z","shell.execute_reply":"2025-07-28T06:47:50.901868Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🌡️ Temperature from Thermopile of IMU Sensors</div>","metadata":{}},{"cell_type":"code","source":"# Melt the DataFrame for easier plotting\ndf_melted = train.melt(id_vars='sequence_type', \n                    value_vars=['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5'],\n                    var_name='sensor', \n                    value_name='temperature')\n\n# Plot\nplt.figure(figsize=(10, 6))\nsns.boxplot(x='sequence_type', y='temperature', hue='sensor', data=df_melted)\nplt.title('Thermopile Sensor Readings by Sequence Type')\nplt.xlabel('Sequence Type')\nplt.ylabel('Temperature (°C)')\nplt.legend(title='Sensor')\nplt.xticks(rotation=45)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:50.903450Z","iopub.execute_input":"2025-07-28T06:47:50.903710Z","iopub.status.idle":"2025-07-28T06:47:53.967944Z","shell.execute_reply.started":"2025-07-28T06:47:50.903688Z","shell.execute_reply":"2025-07-28T06:47:53.967281Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Group by sequence_type and get mean values\ndf_grouped = train.groupby('sequence_type')[['thm_1','thm_2','thm_3','thm_4','thm_5']].mean()\n\ncategories = ['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5']\nN = len(categories)\n\nangles = [n / float(N) * 2 * np.pi for n in range(N)]\nangles += angles[:1]  # Close the loop\n\nfig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(polar=True))\n\nfor idx, row in df_grouped.iterrows():\n    values = row.values.tolist()\n    values += values[:1]  # Close the loop\n    ax.plot(angles, values, marker='o', label=idx)\n    ax.fill(angles, values, alpha=0.1)\n\nax.set_xticks(angles[:-1])\nax.set_xticklabels(categories)\nplt.legend(title=\"Sequence Type\")\nplt.title(\"Radar Chart of Average Thermopile Readings by Sequence Type\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:53.968739Z","iopub.execute_input":"2025-07-28T06:47:53.968940Z","iopub.status.idle":"2025-07-28T06:47:54.264761Z","shell.execute_reply.started":"2025-07-28T06:47:53.968924Z","shell.execute_reply":"2025-07-28T06:47:54.264085Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"By analyzing the Boxplot and Radar Chart above we can say that temperature for target is slightly higher than non-target.\nNext lets see box plot of thermopile values against column gesture and column behavior.","metadata":{}},{"cell_type":"code","source":"# Melt the DataFrame for easier plotting\ndf_melted = train.melt(id_vars='gesture', \n                    value_vars=['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5'],\n                    var_name='sensor', \n                    value_name='temperature')\n\n# Plot\nplt.figure(figsize=(10, 6))\nsns.boxplot(x='gesture', y='temperature', hue='sensor', data=df_melted)\nplt.title('Thermopile Sensor Readings by Gesture')\nplt.xlabel('Gesture')\nplt.ylabel('Temperature (°C)')\nplt.legend(title='Sensor')\nplt.xticks(rotation=90)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:54.265540Z","iopub.execute_input":"2025-07-28T06:47:54.265841Z","iopub.status.idle":"2025-07-28T06:47:59.316521Z","shell.execute_reply.started":"2025-07-28T06:47:54.265815Z","shell.execute_reply":"2025-07-28T06:47:59.315718Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Melt the DataFrame for easier plotting\ndf_melted = train.melt(id_vars='behavior', \n                    value_vars=['thm_1', 'thm_2', 'thm_3', 'thm_4', 'thm_5'],\n                    var_name='sensor', \n                    value_name='temperature')\n\n# Plot\nplt.figure(figsize=(10, 6))\nsns.boxplot(x='behavior', y='temperature', hue='sensor', data=df_melted)\nplt.title('Thermopile Sensor Readings by Behavior')\nplt.xlabel('Behavior')\nplt.ylabel('Temperature (°C)')\nplt.legend(title='Sensor')\nplt.xticks(rotation=25)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:47:59.317438Z","iopub.execute_input":"2025-07-28T06:47:59.317704Z","iopub.status.idle":"2025-07-28T06:48:02.559429Z","shell.execute_reply.started":"2025-07-28T06:47:59.317683Z","shell.execute_reply":"2025-07-28T06:48:02.558774Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">0️⃣ Null Percentage</div>","metadata":{}},{"cell_type":"code","source":"def null_percent(df):\n    per=((df.isnull().sum()/len(df))*100).round(2)\n    return per[per>0]\n\nprint(\"Nan Values in Train data\")\nprint(null_percent(train))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:48:02.560176Z","iopub.execute_input":"2025-07-28T06:48:02.560431Z","iopub.status.idle":"2025-07-28T06:48:03.318418Z","shell.execute_reply.started":"2025-07-28T06:48:02.560414Z","shell.execute_reply":"2025-07-28T06:48:03.317671Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🚀 Load Model and Encoding</div>","metadata":{}},{"cell_type":"code","source":"le = joblib.load('/kaggle/input/cmi-voting-classifier-ensemble-training/Target_LabelEncoder.joblib')\nrobustscaler = joblib.load('/kaggle/input/cmi-voting-classifier-ensemble-training/robustscaler.joblib')\nmodel_cat =  joblib.load('/kaggle/input/cmi-voting-classifier-ensemble-training/model_cat.joblib')\nmodel_lgb =  joblib.load('/kaggle/input/cmi-voting-classifier-ensemble-training/model_lgb.joblib')\nmodel_xgb =  joblib.load('/kaggle/input/cmi-voting-classifier-ensemble-training/model_xgb.joblib')\nmeta_model =  joblib.load('/kaggle/input/cmi-voting-classifier-ensemble-training/meta_model.joblib')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-08T13:12:08.547771Z","iopub.execute_input":"2025-08-08T13:12:08.548007Z","iopub.status.idle":"2025-08-08T13:12:08.629545Z","shell.execute_reply.started":"2025-08-08T13:12:08.547984Z","shell.execute_reply":"2025-08-08T13:12:08.628717Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">💎 Feature Engineering</div>","metadata":{}},{"cell_type":"code","source":"def remove_gravity_from_acc(acc_data, rot_data):\n\n    if isinstance(acc_data, pd.DataFrame):\n        acc_values = acc_data[['acc_x', 'acc_y', 'acc_z']].values\n    else:\n        acc_values = acc_data\n\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n\n    num_samples = acc_values.shape[0]\n    linear_accel = np.zeros_like(acc_values)\n    \n    gravity_world = np.array([0, 0, 9.81])\n\n    for i in range(num_samples):\n        if np.all(np.isnan(quat_values[i])) or np.all(np.isclose(quat_values[i], 0)):\n            linear_accel[i, :] = acc_values[i, :] \n            continue\n\n        try:\n            rotation = R.from_quat(quat_values[i])\n            gravity_sensor_frame = rotation.apply(gravity_world, inverse=True)\n            linear_accel[i, :] = acc_values[i, :] - gravity_sensor_frame\n        except ValueError:\n             linear_accel[i, :] = acc_values[i, :]\n             \n    return linear_accel\n\ndef calculate_angular_velocity_from_quat(rot_data, time_delta=1/200): # Assuming 200Hz sampling rate\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n\n    num_samples = quat_values.shape[0]\n    angular_vel = np.zeros((num_samples, 3))\n\n    for i in range(num_samples - 1):\n        q_t = quat_values[i]\n        q_t_plus_dt = quat_values[i+1]\n\n        if np.all(np.isnan(q_t)) or np.all(np.isclose(q_t, 0)) or \\\n           np.all(np.isnan(q_t_plus_dt)) or np.all(np.isclose(q_t_plus_dt, 0)):\n            continue\n\n        try:\n            rot_t = R.from_quat(q_t)\n            rot_t_plus_dt = R.from_quat(q_t_plus_dt)\n\n            # Calculate the relative rotation\n            delta_rot = rot_t.inv() * rot_t_plus_dt\n            \n            # Convert delta rotation to angular velocity vector\n            # The rotation vector (Euler axis * angle) scaled by 1/dt\n            # is a good approximation for small delta_rot\n            angular_vel[i, :] = delta_rot.as_rotvec() / time_delta\n        except ValueError:\n            # If quaternion is invalid, angular velocity remains zero\n            pass\n            \n    return angular_vel\n\ndef calculate_angular_distance(rot_data):\n    if isinstance(rot_data, pd.DataFrame):\n        quat_values = rot_data[['rot_x', 'rot_y', 'rot_z', 'rot_w']].values\n    else:\n        quat_values = rot_data\n\n    num_samples = quat_values.shape[0]\n    angular_dist = np.zeros(num_samples)\n\n    for i in range(num_samples - 1):\n        q1 = quat_values[i]\n        q2 = quat_values[i+1]\n\n        if np.all(np.isnan(q1)) or np.all(np.isclose(q1, 0)) or \\\n           np.all(np.isnan(q2)) or np.all(np.isclose(q2, 0)):\n            angular_dist[i] = 0 # Или np.nan, в зависимости от желаемого поведения\n            continue\n        try:\n            # Converting quaternions to Rotation objects\n            r1 = R.from_quat(q1)\n            r2 = R.from_quat(q2)\n\n            # Calculating the angular distance: 2 * arccos(|real(p * q*)|)\n            # where q* is the conjugate of quaternion q\n            # In scipy.spatial.transform.Rotation, r1.inv() * r2 gives the relative rotation.\n            # The angle of this relative rotation is the angular distance.\n            relative_rotation = r1.inv() * r2\n            \n            # The angle of the rotation vector corresponds to the angular distance\n            # The norm of the rotation vector is the angle in radians\n            angle = np.linalg.norm(relative_rotation.as_rotvec())\n            angular_dist[i] = angle\n        except ValueError:\n            angular_dist[i] = 0 # In case of invalid quaternions\n            pass\n            \n    return angular_dist\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:48:03.458959Z","iopub.status.idle":"2025-07-28T06:48:03.459329Z","shell.execute_reply.started":"2025-07-28T06:48:03.459141Z","shell.execute_reply":"2025-07-28T06:48:03.459157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def feature_engineering_imu(data:pl.DataFrame):\n    data = data.to_pandas()\n    data['acc_mag'] = np.sqrt(data['acc_x']**2 + data['acc_y']**2 + data['acc_z']**2)\n    data['rot_angle'] = 2 * np.arccos(data['rot_w'].clip(-1, 1))\n    data['acc_mag_jerk'] = data.groupby('sequence_id')['acc_mag'].diff().fillna(0)\n    data['rot_angle_vel'] = data.groupby('sequence_id')['rot_angle'].diff().fillna(0)\n\n    linear_accel_list = []\n    for _, group in data.groupby('sequence_id'):\n        acc_data_group = group[['acc_x', 'acc_y', 'acc_z']]\n        rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n        linear_accel_group = remove_gravity_from_acc(acc_data_group, rot_data_group)\n        linear_accel_list.append(pd.DataFrame(linear_accel_group, columns=['linear_acc_x', 'linear_acc_y', 'linear_acc_z'], index=group.index))\n    \n    df_linear_accel = pd.concat(linear_accel_list)\n    data = pd.concat([data, df_linear_accel], axis=1)\n    data['linear_acc_mag'] = np.sqrt(data['linear_acc_x']**2 + data['linear_acc_y']**2 + data['linear_acc_z']**2)\n    data['linear_acc_mag_jerk'] = data.groupby('sequence_id')['linear_acc_mag'].diff().fillna(0)\n    angular_vel_list = []\n    for _, group in data.groupby('sequence_id'):\n        rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n        angular_vel_group = calculate_angular_velocity_from_quat(rot_data_group)\n        angular_vel_list.append(pd.DataFrame(angular_vel_group, columns=['angular_vel_x', 'angular_vel_y', 'angular_vel_z'], index=group.index))\n    \n    df_angular_vel = pd.concat(angular_vel_list)\n    data = pd.concat([data, df_angular_vel], axis=1)\n    \n    print(\"  Calculating angular distance between successive quaternions...\")\n    angular_distance_list = []\n    for _, group in data.groupby('sequence_id'):\n        rot_data_group = group[['rot_x', 'rot_y', 'rot_z', 'rot_w']]\n        angular_dist_group = calculate_angular_distance(rot_data_group)\n        angular_distance_list.append(pd.DataFrame(angular_dist_group, columns=['angular_distance'], index=group.index))\n    \n    df_angular_distance = pd.concat(angular_distance_list)\n    data = pd.concat([data, df_angular_distance], axis=1)\n    data = pl.from_pandas(data)\n    return data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:48:03.460398Z","iopub.status.idle":"2025-07-28T06:48:03.460716Z","shell.execute_reply.started":"2025-07-28T06:48:03.460556Z","shell.execute_reply":"2025-07-28T06:48:03.460571Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def feature_engineering_stat(data:pl.DataFrame):\n    non_sensor_cols = []\n    if \"gesture\" in data.columns:\n        non_sensor_cols = [\"gesture\"]\n        \n    # All numeric sensor columns (everything except id, demo, target)\n    stat_cols = [\n        c for c in data.columns\n        if c not in non_sensor_cols + [\"sequence_id\", \"row_id\",\"sequence_counter\",\"subject\"]\n    ]\n    \n    # Build aggregation expressions\n    agg_exprs = []\n    \n    # full-stats bundle for sensor columns\n    for c in stat_cols:\n        agg_exprs += [\n            pl.col(c).mean().alias(f\"{c}_mean\"),\n            pl.col(c).std().alias(f\"{c}_std\"),\n            pl.col(c).var().alias(f\"{c}_var\"),\n            pl.col(c).quantile(0.25).alias(f\"{c}_q25\"),\n            pl.col(c).median().alias(f\"{c}_q50\"),\n            pl.col(c).quantile(0.75).alias(f\"{c}_q75\"),\n            pl.col(c).max().alias(f\"{c}_max\"),\n            pl.col(c).min().alias(f\"{c}_min\"),\n            pl.col(c).first().alias(f\"{c}_first\"),\n            pl.col(c).last().alias(f\"{c}_last\"),\n            pl.col(c).quantile(0.25, \"nearest\").alias(f\"{c}_t25\"),\n            pl.col(c).quantile(0.75, \"nearest\").alias(f\"{c}_t75\"),\n            (pl.col(c).last() - pl.col(c).first()).alias(f\"{c}_delta\"),\n            pl.corr(\"sequence_counter\", c).alias(f\"{c}_corr_time\"),\n            pl.col(c).diff().mean().alias(f\"{c}_diff_mean\"),\n            pl.col(c).diff().std().alias(f\"{c}_diff_std\"),\n            pl.col(c).skew().alias(f\"{c}_skew\"),\n            pl.col(c).kurtosis().alias(f\"{c}_kurt\"),\n            pl.col(c).diff().abs().gt(0).sum().alias(f\"{c}_n_changes\")\n        ]\n        agg_exprs += [\n            pl.when(pl.col(\"sequence_counter\") < 0.1 * pl.max(\"sequence_counter\"))\n              .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg1_mean\"),\n            pl.when(pl.col(\"sequence_counter\") > 0.9 * pl.max(\"sequence_counter\"))\n              .then(pl.col(c)).otherwise(None).mean().alias(f\"{c}_seg3_mean\"),\n        ]\n    \n    # first() for demographics and target\n    agg_exprs += [\n        pl.col(c).first().alias(c) for c in non_sensor_cols\n    ]\n    \n    # Group-by and aggregate\n    cleaned_data = (\n        data\n        .group_by(\"sequence_id\", maintain_order=True)\n        .agg(agg_exprs)\n    )\n    return cleaned_data","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:48:03.461914Z","iopub.status.idle":"2025-07-28T06:48:03.462135Z","shell.execute_reply.started":"2025-07-28T06:48:03.462024Z","shell.execute_reply":"2025-07-28T06:48:03.462033Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🌟 Predict</div>","metadata":{}},{"cell_type":"code","source":"def predict(sequence: pl.DataFrame, demographics: pl.DataFrame) -> str:\n    # data =sequence\n    data = sequence.join(demographics,on=\"subject\",how=\"left\")\n    # print(data.schema)\n    data = feature_engineering_imu(data)\n    cleaned_data = feature_engineering_stat(data)\n\n    pdf = cleaned_data.to_pandas().drop(columns=[\"sequence_id\"])\n\n    pdf_scaled = robustscaler.transform(pdf)  # Shape: (1, n_features)\n    \n    # Get predictions from base models (probabilities)\n    p_cat = model_cat.predict_proba(pdf_scaled)  # (1, num_classes)\n    p_lgb = model_lgb.predict_proba(pdf_scaled)\n    p_xgb = model_xgb.predict_proba(pdf_scaled)\n    \n    # Stack probabilities for meta-model\n    meta_features = np.hstack([p_cat, p_lgb, p_xgb])  # (1, 3*num_classes)\n    \n    # Final prediction using meta-model\n    y_pred_encoded = meta_model.predict(meta_features)  # Array of length 1\n    class_label = le.inverse_transform(y_pred_encoded)[0]\n    return class_label","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">🪐 Inference Server</div>","metadata":{}},{"cell_type":"code","source":"inference_server = kaggle_evaluation.cmi_inference_server.CMIInferenceServer(predict)\n\nif os.getenv('KAGGLE_IS_COMPETITION_RERUN'):\n    inference_server.serve()\nelse:\n    inference_server.run_local_gateway(\n        data_paths=(\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test.csv',\n            '/kaggle/input/cmi-detect-behavior-with-sensor-data/test_demographics.csv',\n        )\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-28T06:48:03.464430Z","iopub.status.idle":"2025-07-28T06:48:03.464632Z","shell.execute_reply.started":"2025-07-28T06:48:03.464537Z","shell.execute_reply":"2025-07-28T06:48:03.464546Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# <div style=\"color:#2d83ed; font-family: 'Segoe UI'; text-align: center; border-top:5px solid green; padding-left:10px; background-color:#F8F9F9; padding:10px; border-radius:5px;font-weight: bold\">📘 References</div>\n\n1. Garey, J. (2025). What Is Excoriation, or Skin-Picking? Child Mind Institute. https://childmind.org/article/excoriation-or-skin-picking/\n2. Martinelli, K. (2025). What is Trichotillomania? Child Mind Institute. https://childmind.org/article/what-is-trichotillomania/\n3. https://www.youtube.com/watch?v=ml9ViuZ5j3w\n4. https://www.youtube.com/watch?v=o7bx3eVh4DE\n5. https://my.clevelandclinic.org/health/diseases/body-focused-repetitive-behavior-bfrb","metadata":{}}]}