{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%reload_ext autoreload\n", "%autoreload 2\n", "\n", "import numpy as np\n", "import pandas as pd\n", "import matplotlib.pyplot as plt\n", "from datetime import datetime, timedelta" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Bedtimes Near Midnight\n", "\n", "Sleep times near midnight span the day boundary. Late bedtimes like 23:30 and early ones like 00:30 are actually close together — but since the 24-hour clock wraps at 0:00, those times get split across the boundary unless we treat time as circular." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "url = \"https://raw.githubusercontent.com/timpyrkov/circleclust/refs/heads/master/tests/sleep.csv\"\n", "\n", "# Read sleep-wake data csv from url (sleep.jpg from github repository)\n", "df = pd.read_csv(url, parse_dates=[\"sleep_start_datetime\", \"sleep_end_datetime\"])\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Show Sleep-Wake actogram" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Create a raw array of activity for 2025 (365 days x 24 hours x 60 minutes)\n", "sleep_array = np.ones(365 * 24 * 60, dtype=int) # Initialize with ones (awake)\n", "\n", "# Fill in sleep intervals as zeros\n", "year_start = datetime(2025, 1, 1)\n", "year_end = datetime(2025, 12, 31, 23, 59)\n", "for _, row in df.iterrows():\n", " # Get start-end of the sleep interval\n", " start = row['sleep_start_datetime']\n", " end = row['sleep_end_datetime']\n", " # Calculate start-end indices for the sleep interval\n", " start_idx = int((start - year_start).total_seconds() // 60)\n", " end_idx = int((end - year_start).total_seconds() // 60)\n", " # Mark sleep interval as zeros\n", " sleep_array[start_idx:end_idx] = 0 \n", "# Reshape to daily samples (365 days x 1440 minutes)\n", "daily_sleep = sleep_array.reshape(365, 1440)\n", "\n", "# Plot as a heatmap\n", "plt.figure(figsize=(12, 6))\n", "plt.title('Sleep-Wake Actogram for 2025', fontsize=16)\n", "\n", "plt.imshow(daily_sleep.T, aspect='auto', cmap='cividis', origin='lower', alpha=0.8)\n", "plt.colorbar(label='State (0=Sleep, 1=Awake)')\n", "\n", "# Set xticks\n", "month_starts = [datetime(2025, m, 1) for m in range(1, 13)] + [datetime(2026, 1, 1)]\n", "xtick_days = [(m - datetime(2025, 1, 1)).days for m in month_starts]\n", "xtick_labels = [m.strftime('%b %d') for m in month_starts]\n", "plt.xticks(xtick_days, xtick_labels, rotation=45)\n", "\n", "# Set yticks\n", "ytick_minutes = [0, 360, 720, 1080, 1440] # 12 AM, 6 AM, 12 PM, 6 PM, 12 AM\n", "ytick_labels = ['12 AM', '6 AM', '12 PM', '6 PM', '12 AM']\n", "plt.yticks(ytick_minutes, ytick_labels)\n", "\n", "plt.show()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Show histogram of go-to-sleep and wake-up time [minutes past midnight]\n", "plt.figure(figsize=(10, 5))\n", "plt.title('Histogram of go-to-sleep/wake-up times', fontsize=14)\n", "\n", "t = pd.concat([df[\"sleep_start_datetime\"], df[\"sleep_end_datetime\"]]).dt\n", "t = (t.hour * 60 + t.minute + t.second / 60.0).values\n", "plt.hist(t, bins=np.linspace(0, 1440, 50), width=25, alpha=.3)\n", "\n", "plt.xticks([0, 360, 720, 1080, 1440], ['12 AM', '6 AM', '12 PM', '6 PM', '12 AM'])\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Find Sleep-Wake Times\n", " \n", " Detect go-to-sleep and wake-up time from distribution" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from circleclust import CircleClust\n", "\n", "# Initialize and run CircleClust.fit() to find pixel group centroids\n", "cc = CircleClust(verbose=False)\n", "cc.fit(t, period=1440) # Important: provide correct range of data values period!\n", "\n", "# Print detected centroids\n", "cc.get_centroids()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Show detected centroids\n", "cc.show_centroids()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sleep-Wake Stats\n", "\n", "Note that the \"Go-to-sleep\" times cluster near the area of wrap at 12 AM (midnight). Both times clsoe to 1:00 AM and 11:00 PM are attributed to the same single \"Go-to-sleep\" cluster." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Convert minutes to HH:MM format\n", "def format_minutes_to_hhmm(minutes):\n", " hours = int(minutes // 60) % 24\n", " mins = int(minutes % 60)\n", " return f\"{hours:02d}:{mins:02d}\"\n", "\n", "# Get detected centroids\n", "centroids = cc.get_centroids()\n", "labels = [\"Wake-up\", \"Go-to-sleep\"]\n", "\n", "# Put detected time ranges in a DataFrame\n", "data = []\n", "for i, label in enumerate(labels):\n", " center_minutes = centroids['centroid'][i]\n", " range_minutes = centroids['std'][i]\n", " start_minutes = center_minutes - range_minutes\n", " end_minutes = center_minutes + range_minutes\n", "\n", " center_time = format_minutes_to_hhmm(center_minutes)\n", " start_time = format_minutes_to_hhmm(start_minutes)\n", " end_time = format_minutes_to_hhmm(end_minutes)\n", "\n", " data.append({\n", " \"Event\": label,\n", " \"Time\": center_time,\n", " \"± Std Dev (min)\": int(range_minutes),\n", " \"Range\": f\"{start_time} - {end_time}\"\n", " })\n", "\n", "# Create and display the DataFrame\n", "df = pd.DataFrame(data)\n", "df.set_index(\"Event\", inplace=True)\n", "df" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.12.11" }, "varInspector": { "cols": { "lenName": 16, "lenType": 16, "lenVar": 40 }, "kernels_config": { "python": { "delete_cmd_postfix": "", "delete_cmd_prefix": "del ", "library": "var_list.py", "varRefreshCmd": "print(var_dic_list())" }, "r": { "delete_cmd_postfix": ") ", "delete_cmd_prefix": "rm(", "library": "var_list.r", "varRefreshCmd": "cat(var_dic_list()) " } }, "types_to_exclude": [ "module", "function", "builtin_function_or_method", "instance", "_Feature" ], "window_display": false } }, "nbformat": 4, "nbformat_minor": 1 }