Source code for biopsykit.sleep.sleep_endpoints.sleep_endpoints

"""Functions for computing sleep endpoints, i.e., parameters that characterize a recording during a sleep study."""
from numbers import Number
from typing import Optional, Sequence, Union

import numpy as np
import pandas as pd
from biopsykit.utils.datatype_helper import SleepEndpointDataFrame, SleepEndpointDict, _SleepEndpointDataFrame


[docs]def compute_sleep_endpoints( sleep_wake: pd.DataFrame, bed_interval: Sequence[Union[str, int, np.datetime64]] ) -> SleepEndpointDict: """Compute a set of sleep endpoints based on sleep/wake information and time spent in bed. This functions computes the following sleep endpoints: * ``date``: date of recording if input data is time-aware, ``0`` otherwise. **NOTE**: If the participant went to bed between 12 am and 6 am (i.e, the beginning of ``bed_interval`` between 12 am and 6 am) ``date`` will be set to the day before (because this night is assumed to "belong" to the day before). * ``sleep_onset``: Sleep Onset, i.e., time of falling asleep, in absolute time * ``wake_onset``: Wake Onset, i.e., time of awakening, in absolute time * ``total_sleep_duration``: Total duration spent sleeping, i.e., the duration between the beginning of the first sleep interval and the end of the last sleep interval, in minutes * ``net_sleep_duration``: Net duration spent sleeping, in minutes * ``bed_interval_start``: Bed Interval Start, i.e., time when participant went to bed, in absolute time * ``bed_interval_end``: Bed Interval End, i.e., time when participant left bed, in absolute time * ``sleep_efficiency``: Sleep Efficiency, defined as the ratio between net sleep duration and total sleep duration in percent * ``sleep_onset_latency``: Sleep Onset Latency, i.e., time in bed needed to fall asleep (difference between *Sleep Onset* and *Bed Interval Start*), in minutes * ``getup_latency``: Get Up Latency, i.e., time in bed after awakening until getting up (difference between *Bed Interval End* and *Wake Onset*), in minutes * ``wake_after_sleep_onset``: Wake After Sleep Onset (WASO), i.e., total time awake after falling asleep (after *Sleep Onset* and before *Wake Onset*), in minutes * ``sleep_bouts``: List with start and end times of sleep bouts * ``wake_bouts``: List with start and end times of wake bouts * ``number_wake_bouts``: Total number of wake bouts Parameters ---------- sleep_wake : :class:`~pandas.DataFrame` dataframe with sleep/wake scoring of night. 1 is expected to indicate *sleep*, 0 to indicate *wake* bed_interval : array_like beginning and end of bed interval, i.e., the time spent in bed Returns ------- :obj:`~biopsykit.utils.datatype_helper.SleepEndpointDict` dictionary with computed sleep endpoints """ # slice sleep data = data between sleep onset and wake onset during bed interval sleep_wake = sleep_wake.loc[bed_interval[0] : bed_interval[1]] # total sleep duration = length of sleep data total_sleep_duration = len(sleep_wake) # net sleep duration in minutes = length of 'sleep' predictions (value 1) in sleep data net_sleep_time = sleep_wake[sleep_wake["sleep_wake"].eq(1)] net_sleep_duration = len(net_sleep_time) if net_sleep_time.empty: return {} df_sw_sleep = sleep_wake[net_sleep_time.index[0] : net_sleep_time.index[-1]].copy() # get percent of total time asleep sleep_efficiency = 100.0 * (len(net_sleep_time) / len(sleep_wake)) # wake after sleep onset = duration of wake during first and last 'sleep' sample wake_after_sleep_onset = len(df_sw_sleep) - int(df_sw_sleep.sum()[0]) df_sw_sleep["block"] = df_sw_sleep["sleep_wake"].diff().ne(0).cumsum() df_sw_sleep = df_sw_sleep.reset_index() df_sw_sleep = df_sw_sleep.rename(columns={"index": "time"}) bouts = df_sw_sleep.groupby(by="block") df_start_stop = bouts.first() df_start_stop = df_start_stop.rename(columns={"time": "start"}) df_start_stop["end"] = bouts.last()["time"] # add 1 min to end for continuous time coverage if df_start_stop["end"].dtype == np.int64: df_start_stop["end"] = df_start_stop["end"] + 1 else: df_start_stop["end"] = df_start_stop["end"] + pd.Timedelta("1m") sleep_bouts = df_start_stop[df_start_stop["sleep_wake"].eq(1)].drop(columns=["sleep_wake"]).reset_index(drop=True) wake_bouts = df_start_stop[df_start_stop["sleep_wake"].ne(1)].drop(columns=["sleep_wake"]).reset_index(drop=True) num_wake_bouts = len(wake_bouts) sleep_onset = net_sleep_time.index[0] wake_onset = net_sleep_time.index[-1] # start and end of bed interval bed_start = bed_interval[0] bed_end = bed_interval[1] # sleep onset latency = duration between bed interval start and sleep onset sleep_onset_latency = len(sleep_wake[sleep_wake.index[0] : sleep_onset]) # getup latency = duration between wake onset (last 'sleep' sample) and bed interval end getup_latency = len(sleep_wake[wake_onset : sleep_wake.index[-1]]) if isinstance(bed_start, Number): date = 0 else: bed_start = str(bed_start) bed_end = str(bed_end) sleep_onset = str(sleep_onset) wake_onset = str(wake_onset) date = pd.to_datetime(bed_start) if date.hour < 6: date = date - pd.Timedelta("1d") date = str(date.normalize()) dict_result = { "date": date, "sleep_onset": sleep_onset, "wake_onset": wake_onset, "total_sleep_duration": total_sleep_duration, "net_sleep_duration": net_sleep_duration, "bed_interval_start": bed_start, "bed_interval_end": bed_end, "sleep_efficiency": sleep_efficiency, "sleep_onset_latency": sleep_onset_latency, "getup_latency": getup_latency, "wake_after_sleep_onset": wake_after_sleep_onset, "sleep_bouts": sleep_bouts, "wake_bouts": wake_bouts, "number_wake_bouts": num_wake_bouts, } return dict_result
[docs]def endpoints_as_df(sleep_endpoints: SleepEndpointDict) -> Optional[SleepEndpointDataFrame]: """Convert ``SleepEndpointDict`` into ``SleepEndpointDataFrame``. Parameters ---------- sleep_endpoints : :obj:`~biopsykit.utils.datatype_helper.SleepEndpointDict` dictionary with computed Sleep Endpoints Returns ------- :obj:`~biopsykit.utils.datatype_helper.SleepEndpointDataFrame` dataframe with computed Sleep Endpoints or ``None`` if ``sleep_endpoints`` is ``None`` """ if sleep_endpoints is None: return None sleep_endpoints = sleep_endpoints.copy() sleep_bouts = sleep_endpoints.pop("sleep_bouts", None).to_numpy().tolist() wake_bouts = sleep_endpoints.pop("wake_bouts", None).to_numpy().tolist() sleep_bouts = [tuple(v) for v in sleep_bouts] wake_bouts = [tuple(v) for v in wake_bouts] index = pd.to_datetime(pd.Index([sleep_endpoints["date"]], name="date")) sleep_endpoints.pop("date") df = pd.DataFrame(sleep_endpoints, index=index) df = df.fillna(value=np.nan) df["sleep_bouts"] = None df["wake_bouts"] = None df.at[df.index[0], "sleep_bouts"] = sleep_bouts # noqa: PD008 df.at[df.index[0], "wake_bouts"] = wake_bouts # noqa: PD008 return _SleepEndpointDataFrame(df)