{"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":53666,"databundleVersionId":6589269,"sourceType":"competition"}],"dockerImageVersionId":31153,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:48:51.539870Z","iopub.execute_input":"2025-10-31T18:48:51.540143Z","iopub.status.idle":"2025-10-31T18:48:52.560162Z","shell.execute_reply.started":"2025-10-31T18:48:51.540118Z","shell.execute_reply":"2025-10-31T18:48:52.559229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:51:38.642402Z","iopub.execute_input":"2025-10-31T18:51:38.642685Z","iopub.status.idle":"2025-10-31T18:51:38.647526Z","shell.execute_reply.started":"2025-10-31T18:51:38.642664Z","shell.execute_reply":"2025-10-31T18:51:38.646609Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------- 1) Load files ----------\nDATA_DIR = Path('.')  # adjust if needed\ntrain_events = pd.read_csv('/kaggle/input/child-mind-institute-detect-sleep-states/train_events.csv')\ntest_series = pd.read_parquet('/kaggle/input/child-mind-institute-detect-sleep-states/test_series.parquet')\nsample_sub = pd.read_csv('/kaggle/input/child-mind-institute-detect-sleep-states/sample_submission.csv')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:51:36.277689Z","iopub.execute_input":"2025-10-31T18:51:36.277954Z","iopub.status.idle":"2025-10-31T18:51:36.306326Z","shell.execute_reply.started":"2025-10-31T18:51:36.277934Z","shell.execute_reply":"2025-10-31T18:51:36.305637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"train_events rows:\", len(train_events))\nprint(\"test_series rows:\", len(test_series))\nprint(\"sample_sub rows:\", len(sample_sub))\ndisplay(train_events.head())\ndisplay(test_series.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:51:52.466594Z","iopub.execute_input":"2025-10-31T18:51:52.466892Z","iopub.status.idle":"2025-10-31T18:51:52.501296Z","shell.execute_reply.started":"2025-10-31T18:51:52.466871Z","shell.execute_reply":"2025-10-31T18:51:52.500406Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\nTest series columns:\", test_series.columns.tolist())\nprint(\"Series count:\", test_series['series_id'].nunique())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:52:11.953096Z","iopub.execute_input":"2025-10-31T18:52:11.953718Z","iopub.status.idle":"2025-10-31T18:52:11.961769Z","shell.execute_reply.started":"2025-10-31T18:52:11.953690Z","shell.execute_reply":"2025-10-31T18:52:11.960585Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def detect_sleep_window(df_series,\n                        enmo_col='enmo',\n                        step_col='step',\n                        roll_win=30,\n                        low_percentile=20,\n                        min_length_steps=30):\n    \"\"\"\n    Input: df_series - DataFrame of single series sorted by step.\n    Returns: (onset_step, wakeup_step) or None if not found.\n    \"\"\"\n    s = df_series.copy()\n    if enmo_col not in s.columns:\n        return None\n\n    # Ensure sorted\n    s = s.sort_values(step_col).reset_index(drop=True)\n\n    # Rolling median to smooth short spikes\n    s['enmo_roll'] = s[enmo_col].rolling(roll_win, center=True, min_periods=1).median()\n\n    # threshold: percentile of rolling enmo (per-series adaptive)\n    threshold = np.percentile(s['enmo_roll'].dropna(), low_percentile)\n    # create boolean mask low activity\n    s['low'] = (s['enmo_roll'] <= threshold).astype(int)\n\n    # find contiguous runs of low==1\n    # create group id for every change in mask\n    s['grp'] = (s['low'] != s['low'].shift(1)).cumsum()\n    runs = s.groupby(['grp', 'low']).agg(\n        start_idx=('index', 'first') if 'index' in s.columns else (step_col, 'first'),\n        end_idx=(step_col, 'last'),\n        count=(step_col, 'count')\n    ).reset_index()\n    # keep only low runs\n    low_runs = runs[runs['low'] == 1].copy()\n    if low_runs.empty:\n        return None\n\n    # choose the longest low run (by count)\n    best = low_runs.sort_values('count', ascending=False).iloc[0]\n    if best['count'] < min_length_steps:\n        # run too short -> no valid sleep window found\n        return None\n\n    # map start and end steps\n    # start step is first step where low==1 in that group\n    grp_id = best['grp']\n    grp_rows = s[s['grp'] == grp_id]\n    onset_step = int(grp_rows[step_col].iloc[0])\n    wakeup_step = int(grp_rows[step_col].iloc[-1])\n    return onset_step, wakeup_step\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:56:11.489879Z","iopub.execute_input":"2025-10-31T18:56:11.490229Z","iopub.status.idle":"2025-10-31T18:56:11.500207Z","shell.execute_reply.started":"2025-10-31T18:56:11.490207Z","shell.execute_reply":"2025-10-31T18:56:11.499177Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Better implementation for contiguous detection (works without 'index' column)\ndef detect_sleep_window_v2(df_series,\n                           enmo_col='enmo',\n                           step_col='step',\n                           roll_win=30,\n                           low_percentile=20,\n                           min_length_steps=30):\n    s = df_series.copy().sort_values(step_col).reset_index(drop=True)\n    if enmo_col not in s.columns:\n        return None\n    s['enmo_roll'] = s[enmo_col].rolling(roll_win, center=True, min_periods=1).median()\n    thresh = np.percentile(s['enmo_roll'].dropna(), low_percentile)\n    mask = (s['enmo_roll'] <= thresh).astype(int).values\n    if mask.sum() == 0:\n        return None\n\n    # find contiguous True segments\n    segments = []\n    start = None\n    for i, v in enumerate(mask):\n        if v == 1 and start is None:\n            start = i\n        elif v == 0 and start is not None:\n            segments.append((start, i-1))\n            start = None\n    if start is not None:\n        segments.append((start, len(mask)-1))\n\n    if not segments:\n        return None\n\n    # choose longest segment\n    seg_lengths = [(s_e - s_s + 1, s_s, s_e) for (s_s, s_e) in segments]\n    seg_lengths.sort(reverse=True)\n    longest = seg_lengths[0]\n    length, s_idx, e_idx = longest\n    if length < min_length_steps:\n        return None\n\n    onset_step = int(s[step_col].iloc[s_idx])\n    wakeup_step = int(s[step_col].iloc[e_idx])\n    return onset_step, wakeup_step","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:57:27.531922Z","iopub.execute_input":"2025-10-31T18:57:27.532858Z","iopub.status.idle":"2025-10-31T18:57:27.541902Z","shell.execute_reply.started":"2025-10-31T18:57:27.532817Z","shell.execute_reply":"2025-10-31T18:57:27.541172Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------- 4) Run detection across all series in test set ----------\n# We'll run series-by-series. If test set is large, this may take time.\n# Use tqdm for progress bar.\nout_rows = []\nseries_ids = test_series['series_id'].unique()\nprint(\"Total series to process (test):\", len(series_ids))\n\n# Parameters you may tune:\nROLL_WIN = 60           # smoothing window (samples)\nLOW_PERCENTILE = 20     # percentile to define low activity\nMIN_LENGTH_STEPS = 30   # minimum contiguous low region length (samples) to be considered sleep\n\nfor sid in tqdm(series_ids):\n    s_df = test_series[test_series['series_id'] == sid][['series_id', 'step', 'enmo']].copy()\n    # if enmo missing, skip\n    if s_df['enmo'].isna().all():\n        continue\n\n    res = detect_sleep_window_v2(s_df,\n                                 enmo_col='enmo',\n                                 step_col='step',\n                                 roll_win=ROLL_WIN,\n                                 low_percentile=LOW_PERCENTILE,\n                                 min_length_steps=MIN_LENGTH_STEPS)\n    if res is None:\n        # No predicted sleep window -> skip or optionally predict nothing\n        continue\n    onset, wakeup = res\n\n    # Add two predictions: onset and wakeup, with a heuristic score (1.0)\n    # Row ordering / row_id will be generated later to match sample submission format\n    out_rows.append({'series_id': sid, 'step': onset, 'event': 'onset', 'score': 1.0})\n    out_rows.append({'series_id': sid, 'step': wakeup, 'event': 'wakeup', 'score': 1.0})\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:57:44.554453Z","iopub.execute_input":"2025-10-31T18:57:44.554756Z","iopub.status.idle":"2025-10-31T18:57:44.603494Z","shell.execute_reply.started":"2025-10-31T18:57:44.554732Z","shell.execute_reply":"2025-10-31T18:57:44.602260Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------- 5) Build submission DataFrame ----------\npred_df = pd.DataFrame(out_rows)\nprint(\"Predictions count:\", len(pred_df))\ndisplay(pred_df.head())\n\n# If sample_submission contains a specific ordering or enumerated 'row_id', we must follow it.\n# The sample submission format in this competition uses a 'row_id' which is a simple enumeration.\n# We'll create a submission dataframe with columns: row_id, series_id, step, event, score\nif 'row_id' in sample_sub.columns:\n    # create row_id in the order we generated predictions\n    pred_df = pred_df.reset_index(drop=True)\n    pred_df.insert(0, 'row_id', pred_df.index.astype(int))\n    sub_df = pred_df[['row_id', 'series_id', 'step', 'event', 'score']]\nelse:\n    # If sample submission doesn't have row_id, just use our columns\n    sub_df = pred_df[['series_id', 'step', 'event', 'score']]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T18:58:10.199285Z","iopub.execute_input":"2025-10-31T18:58:10.199850Z","iopub.status.idle":"2025-10-31T18:58:10.213970Z","shell.execute_reply.started":"2025-10-31T18:58:10.199822Z","shell.execute_reply":"2025-10-31T18:58:10.213261Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------- 6) Save submission ----------\nout_path = DATA_DIR / 'submission.csv'\nsub_df.to_csv(out_path, index=False)\nprint(\"Saved submission to:\", out_path)\n\n# ---------- 7) Quick evaluation on train (optional) ----------\n# We can apply the same heuristic on training series and compare to train_events (VERY rough).\n# This helps check if the heuristic is doing something sensible.\ndo_quick_train_check = True\nif do_quick_train_check:\n    # load train series if available (train_series.parquet). Many public kernels have train_series.parquet.\n    try:\n        train_series = pd.read_parquet(DATA_DIR / 'train_series.parquet')\n        # run detection on each train series and compare to train_events\n        # We'll compute how many labeled events we found (onset/wakeup)\n        found = 0\n        total_events = len(train_events)\n        # build predicted events for train\n        preds = []\n        for sid in train_series['series_id'].unique():\n            s_df = train_series[train_series['series_id'] == sid][['series_id','step','enmo']].copy()\n            res = detect_sleep_window_v2(s_df,\n                                         enmo_col='enmo',\n                                         step_col='step',\n                                         roll_win=ROLL_WIN,\n                                         low_percentile=LOW_PERCENTILE,\n                                         min_length_steps=MIN_LENGTH_STEPS)\n            if res is None:\n                continue\n            onset, wakeup = res\n            preds.append((sid, onset, 'onset'))\n            preds.append((sid, wakeup, 'wakeup'))\n        preds_df = pd.DataFrame(preds, columns=['series_id','step','event'])\n        # naive matching: exact match on (series_id, step, event)\n        merged = train_events.merge(preds_df, on=['series_id','step','event'], how='inner')\n        found = len(merged)\n        print(f\"Quick train check: found {found} of {total_events} labelled events by exact match (very strict).\")\n    except FileNotFoundError:\n        print(\"train_series.parquet not found — skipping train check (optional).\")\n    except Exception as e:\n        print(\"Train check error (skipping).\", e)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-31T19:00:27.420618Z","iopub.execute_input":"2025-10-31T19:00:27.420938Z","iopub.status.idle":"2025-10-31T19:00:27.432171Z","shell.execute_reply.started":"2025-10-31T19:00:27.420915Z","shell.execute_reply":"2025-10-31T19:00:27.430897Z"}},"outputs":[],"execution_count":null}]}