{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceType":"competition","sourceId":53666,"databundleVersionId":6589269}],"dockerImageVersionId":31328,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nfor path, dirs, files in os.walk('/kaggle/input'):\n    for f in files:\n        print(os.path.join(path, f))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Import libraries","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport pyarrow.parquet as pq\nimport lightgbm as lgb\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.metrics import classification_report, f1_score\nfrom scipy.ndimage import uniform_filter1d\nimport gc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-19T12:53:28.932802Z","iopub.execute_input":"2026-04-19T12:53:28.933169Z","iopub.status.idle":"2026-04-19T12:53:37.040458Z","shell.execute_reply.started":"2026-04-19T12:53:28.933137Z","shell.execute_reply":"2026-04-19T12:53:37.039354Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# LOAD Dataset","metadata":{}},{"cell_type":"code","source":"def downcast_dtypes(df):\n    # float64 (8 bytes) → float32 (4 bytes): saves 50% on numeric cols\n    for col in df.select_dtypes(include=['float64']).columns:\n        df[col] = df[col].astype(np.float32)\n    # int64 → smallest int type that fits the values\n    for col in df.select_dtypes(include=['int64']).columns:\n        df[col] = pd.to_numeric(df[col], downcast='integer')\n    return df\n\n\ndef load_parquet_smart(filepath):\n    parquet_file = pq.ParquetFile(filepath)\n    chunks = []\n    total = parquet_file.num_row_groups\n    # Process 500,000 rows at a time instead of all at once\n    for i, batch in enumerate(parquet_file.iter_batches(batch_size=500_000)):\n        df_chunk = batch.to_pandas()\n        df_chunk = df_chunk[['series_id', 'step', 'timestamp', 'anglez', 'enmo']] \n        df_chunk = downcast_dtypes(df_chunk)\n        chunks.append(df_chunk)\n        print(f\"  Chunk {i+1}/{total} loaded\")\n    print(\"  Concatenating...\")\n    df = pd.concat(chunks, ignore_index=True)\n    del chunks\n    gc.collect()\n    print(f\"  Done. Shape: {df.shape}\")\n    return df\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-19T12:54:10.159281Z","iopub.execute_input":"2026-04-19T12:54:10.160104Z","iopub.status.idle":"2026-04-19T12:54:10.168185Z","shell.execute_reply.started":"2026-04-19T12:54:10.160065Z","shell.execute_reply":"2026-04-19T12:54:10.167163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Loading data...\")\ndf = load_parquet_smart('/kaggle/input/competitions/child-mind-institute-detect-sleep-states/train_series.parquet')\nevents = pd.read_csv('/kaggle/input/competitions/child-mind-institute-detect-sleep-states/train_events.csv')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-19T12:54:14.456629Z","iopub.execute_input":"2026-04-19T12:54:14.457187Z","iopub.status.idle":"2026-04-19T12:55:46.482752Z","shell.execute_reply.started":"2026-04-19T12:54:14.457153Z","shell.execute_reply":"2026-04-19T12:55:46.481716Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# PREPROCESS \n","metadata":{}},{"cell_type":"code","source":"def preprocess_series(df):\n    # Critical: each subject must be sorted by time step\n    # Mixing subjects or steps out of order breaks sliding windows\n    print(\"  Sorting...\")\n    df['timestamp'] = pd.to_datetime(df['timestamp'])\n    df.sort_values(['series_id', 'step'], inplace=True)\n    df.reset_index(drop=True, inplace=True)\n    \n    print(\"  Cleaning...\")\n    # anglez: fill with median \n    # enmo: fill with 0 (assume no movement when sensor fails)\n    df['anglez'] = df['anglez'].fillna(df['anglez'].median()).clip(-90, 90)\n    df['enmo']  = df['enmo'].fillna(0).clip(0, 10)\n    df['hour']     = df['timestamp'].dt.hour.astype(np.int8)   \n    \n    # is_night is a strong prior: babies mostly sleep between 8pm and 6am\n    df['is_night'] = ((df['hour'] >= 20) | (df['hour'] <= 6)).astype(np.int8)\n    df.drop(columns=['timestamp'], inplace=True)\n    gc.collect() \n    \n    print(\"  Normalizing...\")\n    # Normalize per subject\n    # Each baby has different baseline movement levels.\n    # Global normalization would mix their distributions together (not to apply).\n    #Per-subject normalization is crucial: \n    #very active baby and a calm baby should both have their sleep/wake patterns detected relative to their own baseline.\n    stats = df.groupby('series_id')[['anglez', 'enmo']].agg(['mean', 'std'])\n    stats.columns = ['anglez_mean', 'anglez_std', 'enmo_mean', 'enmo_std']\n    stats['anglez_std'] = stats['anglez_std'].replace(0, 1)\n    stats['enmo_std']   = stats['enmo_std'].replace(0, 1)\n\n    df = df.merge(stats.reset_index(), on='series_id', how='left')\n\n    del stats\n    gc.collect()\n    \n    df['anglez_norm'] = ((df['anglez'] - df['anglez_mean']) / df['anglez_std']).astype(np.float32)\n    df['enmo_norm']   = ((df['enmo']   - df['enmo_mean'])   / df['enmo_std']).astype(np.float32)\n\n    df.drop(columns=['anglez_mean', 'anglez_std', 'enmo_mean', 'enmo_std'], inplace=True)\n    gc.collect()\n    \n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-19T13:01:17.224403Z","iopub.execute_input":"2026-04-19T13:01:17.226063Z","iopub.status.idle":"2026-04-19T13:01:17.236536Z","shell.execute_reply.started":"2026-04-19T13:01:17.22602Z","shell.execute_reply":"2026-04-19T13:01:17.235399Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Preprocessing...\")\ndf = preprocess_series(df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-19T13:01:23.056893Z","iopub.execute_input":"2026-04-19T13:01:23.057557Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.to_parquet('/kaggle/working/preprocessed_df.parquet', index=False)\nprint(\"Saved!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# FEATURE EXTRACTION \n","metadata":{}},{"cell_type":"code","source":"def extract_features_from_window(window, series_id, start, end):\n    enmo   = window['enmo'].values\n    anglez = window['anglez'].values\n    feat   = {'series_id': series_id, 'window_start': start,'window_end': end, 'center_step': (start + end) // 2}\n\n    for name, arr in [('enmo', enmo), ('anglez', anglez)]:\n        feat[f'{name}_mean']  = np.mean(arr)\n        feat[f'{name}_std'] = np.std(arr)\n        feat[f'{name}_median'] = np.median(arr)\n        feat[f'{name}_min'] = np.min(arr)\n        feat[f'{name}_max'] = np.max(arr)\n        feat[f'{name}_range'] = np.ptp(arr)\n        feat[f'{name}_iqr'] = np.percentile(arr, 75) - np.percentile(arr, 25)\n        feat[f'{name}_skew'] = pd.Series(arr).skew()\n        feat[f'{name}_kurtosis'] = pd.Series(arr).kurt()\n        feat[f'{name}_p10'] = np.percentile(arr, 10)\n        feat[f'{name}_p90'] = np.percentile(arr, 90)\n        \n    feat['enmo_above_01']  = np.mean(enmo > 0.1)\n    feat['enmo_above_02'] = np.mean(enmo > 0.2)\n    feat['zero_crossing_enmo'] = np.sum(np.diff(np.sign(enmo - enmo.mean())) != 0)\n    feat['anglez_diff_mean'] = np.mean(np.abs(np.diff(anglez)))\n    feat['anglez_diff_std'] = np.std(np.diff(anglez))\n    feat['body_position_changes']= np.sum(np.abs(np.diff(anglez)) > 30)\n\n    if 'hour' in window.columns:\n        feat['mean_hour'] = window['hour'].mean()\n        feat['is_night_frac']= window['is_night'].mean()\n\n    fft_enmo = np.abs(np.fft.rfft(enmo))\n    freqs = np.fft.rfftfreq(len(enmo))\n    feat['dominant_freq_enmo'] = freqs[np.argmax(fft_enmo[1:]) + 1]\n    feat['fft_power_low'] = np.sum(fft_enmo[freqs < 0.1])\n    feat['fft_power_high'] = np.sum(fft_enmo[freqs >= 0.1])\n    feat['spectral_entropy']  = -np.sum(\n        (fft_enmo / fft_enmo.sum()) * np.log(fft_enmo / fft_enmo.sum() + 1e-10)\n    )\n    return feat\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_window_features(df, window_size=720, step_size=60):\n    features_list = []\n    for series_id, group in df.groupby('series_id'):\n        group = group.reset_index(drop=True)\n        n = len(group)\n        for start in range(0, n - window_size, step_size):\n            end  = start + window_size\n            feat = extract_features_from_window(group.iloc[start:end], series_id, start, end)\n            features_list.append(feat)\n        print(f\"  ✔ {series_id}\")\n    return pd.DataFrame(features_list)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Extracting features...\")\nfeatures_df = extract_window_features(df, window_size=720, step_size=60)\n\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# LABELS ","metadata":{}},{"cell_type":"code","source":"def create_labels(features_df, events_df):\n    labels = []\n    for series_id, feat_group in features_df.groupby('series_id'):\n        series_events = events_df[events_df['series_id'] == series_id]\n        onsets  = series_events[series_events['event'] == 'onset']['step'].values\n        wakeups = series_events[series_events['event'] == 'wakeup']['step'].values\n        sleep_intervals = []\n        for onset in onsets:\n            nxt = wakeups[wakeups > onset]\n            if len(nxt) > 0:\n                sleep_intervals.append((onset, nxt[0]))\n        for _, row in feat_group.iterrows():\n            c = row['center_step']\n            labels.append(int(any(s <= c <= w for s, w in sleep_intervals)))\n    features_df = features_df.copy()\n    features_df['label'] = labels\n    return features_df\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"    print(\"🏷️  Creating labels...\")\n    features_df = create_labels(features_df, events)\n    print(f\"  Dataset: {features_df.shape} | Sleep ratio: {features_df['label'].mean():.2%}\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# TRAIN ","metadata":{}},{"cell_type":"code","source":"def train_sleep_model(features_df):\n    feature_cols = [c for c in features_df.columns\n                    if c not in ['series_id','window_start','window_end','center_step','label']]\n    X      = features_df[feature_cols].fillna(0)\n    y      = features_df['label']\n    groups = features_df['series_id']\n\n    gkf       = GroupKFold(n_splits=5)\n    all_preds = np.zeros(len(y))\n    model     = lgb.LGBMClassifier(\n        n_estimators=500, learning_rate=0.05, max_depth=8,\n        num_leaves=63, class_weight='balanced',\n        subsample=0.8, colsample_bytree=0.8, random_state=42\n    )\n    scaler = StandardScaler()\n\n    for fold, (tr, val) in enumerate(gkf.split(X, y, groups)):\n        X_tr, X_val = X.iloc[tr], X.iloc[val]\n        y_tr, y_val = y.iloc[tr], y.iloc[val]\n        X_tr_sc  = scaler.fit_transform(X_tr)\n        X_val_sc = scaler.transform(X_val)\n        model.fit(X_tr_sc, y_tr,\n                  eval_set=[(X_val_sc, y_val)],\n                  callbacks=[lgb.early_stopping(50), lgb.log_evaluation(100)])\n        preds = model.predict(X_val_sc)\n        all_preds[val] = preds\n        print(f\"  Fold {fold+1} F1: {f1_score(y_val, preds):.4f}\")\n\n    print(\"\\n=== Final Results ===\")\n    print(classification_report(y, all_preds, target_names=['Wake','Sleep']))\n    return model, scaler, feature_cols\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Training...\")\nmodel, scaler, feature_cols = train_sleep_model(features_df)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}