Source code for circstudio.analysis.metrics.metrics

import pandas as pd
import numpy as np
from scipy.fft import fft, fftfreq
import re
from ..tools import *
import plotly.graph_objects as go
from ..sleep import AonT, AoffT


[docs] def daily_profile( data, cyclic=False, time_origin=None, whs="1h", plot=False, log=False ): r"""Average daily activity/light/temperature distribution Calculate the daily profile of activity. Data are averaged over all the days. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. cyclic: bool, optional If set to True, two daily profiles are concatenated to ensure continuity between the last point of the day and the first one. Default is False. time_origin: str or pd.Timedelta, optional If not None, origin of the time axis for the daily profile. Original time bins are translated as time delta with respect to this new origin. Default is None Supported time string: 'AonT', 'AoffT', any 'HH:MM:SS' whs: str, optional Window half size parameter for the detection of the activity onset/offset time. Relevant only if time_origin is set to 'AonT' or AoffT'. Default is '1h'. plot: bool, optional Whether to plot the daily profile. Default is False. log: bool, optional Whether the daily profile should be transformed to a log10 scale. Returns ------- raw : pandas.Series A Series containing the daily activity profile with a 24h index. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ def _format(to_plot, to_log, profile): """ Internal function that decides whether to present daily profile as pd.Series or as an interactive Plotly figure. Parameters ---------- to_plot : bool Whether the daily profile should be plotted. to_log : bool Whether the daily profile should be transformed to a log10 scale. profile The daily profile returned by this function. Returns ------- pandas.Series or go.Figure References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ if to_plot: layout = go.Layout( title="Daily profile", xaxis=dict(title="Date time"), yaxis=dict(title="Data"), showlegend=False, ) if to_log: fig = go.Figure( data=[ go.Scatter(x=profile.index.astype(str), y=np.log10(profile + 1)) ], layout=layout, ) return fig else: fig = go.Figure( data=[go.Scatter(x=profile.index.astype(str), y=profile)], layout=layout, ) return fig else: return profile if time_origin is None: _daily_profile = _average_daily_activity(data, cyclic=cyclic) return _format(plot, log, _daily_profile) else: if cyclic is True: raise NotImplementedError( "Setting a time origin while cyclic option is True is not " "implemented." ) avgdaily = _average_daily_activity(data, cyclic=False) if isinstance(time_origin, str): # Regex pattern for HH:MM:SS time string pattern = re.compile(r"^([0-1]\d|2[0-3])(?::([0-5]\d))(?::([0-5]\d))$") if time_origin == "AonT": # Convert width half size from Timedelta to a nr of points whs = int(pd.Timedelta(whs) / data.index.freq) time_origin = _activity_onset_time(avgdaily, whs=whs) elif time_origin == "AoffT": # Convert width half size from Timedelta to a nr of points whs = int(pd.Timedelta(whs) / data.index.freq) time_origin = _activity_offset_time(avgdaily, whs=whs) elif pattern.match(time_origin): time_origin = pd.Timedelta(time_origin) else: raise ValueError( "Time origin format ({}) not supported.\n".format(time_origin) + "Supported format: {}.".format("HH:MM:SS") ) elif not isinstance(time_origin, pd.Timedelta): raise ValueError( "Time origin is neither a time string with a supported" "format, nor a pd.Timedelta." ) # Round time origin to the required frequency time_origin = time_origin.round(data.index.freq) shift = int((pd.Timedelta("12h") - time_origin) / data.index.freq) _daily_profile = _shift_time_axis(avgdaily, shift) return _format(plot, log, _daily_profile)
[docs] def daily_profile_auc(data, start_time=None, stop_time=None, time_origin=None): r"""AUC of the average daily light profile Calculate the area under the curve of the daily profile of light exposure. Data are averaged over all the days. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. start_time: str, optional If not set to None, compute AUC from start time. Supported time string: 'HH:MM:SS' Default is None. stop_time: str, optional If not set to None, compute AUC until stop time. Supported time string: 'HH:MM:SS' Default is None. time_origin: str or pd.Timedelta, optional If not None, origin of the time axis for the daily profile. Original time bins are translated as time delta with respect to this new origin. Default is None Supported time string: 'HH:MM:SS' Returns ------- auc : float Area under the curve. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ # Compute average daily profile avgdaily = _average_daily_activity(data, cyclic=False) if time_origin is not None: if isinstance(time_origin, str): # Regex pattern for HH:MM:SS time string pattern = re.compile(r"^([0-1]\d|2[0-3])(?::([0-5]\d))(?::([0-5]\d))$") if pattern.match(time_origin): time_origin = pd.Timedelta(time_origin) else: raise ValueError( "Time origin format ({}) not supported.\n".format(time_origin) + "Supported format: HH:MM:SS." ) elif not isinstance(time_origin, pd.Timedelta): raise ValueError( "Time origin is neither a time string with a supported " "format, nor a pd.Timedelta." ) # Round time origin to the required frequency time_origin = time_origin.round(data.index.freq) shift = int((pd.Timedelta("12h") - time_origin) / data.index.freq) avgdaily = _shift_time_axis(avgdaily, shift) # Restrict profile to start/stop times if start_time is not None: start_time = pd.Timedelta(start_time) if stop_time is not None: stop_time = pd.Timedelta(stop_time) # In order to avoid indexing with None, check for that too if start_time is not None or stop_time is not None: return avgdaily.loc[start_time:stop_time].sum() else: return avgdaily.sum()
[docs] def adat(data, rescale=True, exclude_ends=False): """Total average daily activity Calculate the total activity counts, averaged over all the days. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. rescale: bool, optional If set to True, the activity counts are rescaled to account for masked periods (if any). Default is True. exclude_ends: bool, optional If set to True, the first and last daily periods are excluded from the calculation. Useful when the recording does start or end at midnigth. Default is False. Returns ------- adat : int References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ return _average_daily_total_activity( data, rescale=rescale, exclude_ends=exclude_ends )
[docs] def adatp(data, period="7D", rescale=True, exclude_ends=False, verbose=False): """Total average daily activity per period Calculate the total activity counts, averaged over each consecutive period contained in the data. The number of periods Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. period: str, optional Time length of the period to be considered. Must be understandable by pandas.Timedelta rescale: bool, optional If set to True, the activity counts are rescaled to account for masked periods (if any). Default is True. exclude_ends: bool, optional If set to True, the first and last daily periods are excluded from the calculation. Useful when the recording does start or end at midnigth. Default is False. verbose: bool, optional If set to True, display the number of periods found in the data. Also display the time not accounted for. Default is False. Returns ------- adatp : list of int References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ intervals = _interval_maker(data.index, period, verbose) results = [ _average_daily_total_activity( data[time[0] : time[1]], rescale=rescale, exclude_ends=exclude_ends ) for time in intervals ] return results
[docs] def l5(data): r"""L5 Mean activity/temperature/light, etc., during the 5 least active hours of the day. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. Returns ------- l5_onset, l5 : float Notes ----- The L5 [1]_ variable is calculated as the mean, per acquisition period, of the average daily activities during the 5 least active hours. .. warning:: The value of this variable depends on the length of the acquisition period. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Van Someren, E.J.W., Lijzenga, C., Mirmiran, M., Swaab, D.F. (1997). Long-Term Fitness Training Improves the Circadian Rest-Activity Rhythm in Healthy Elderly Males. Journal of Biological Rhythms, 12(2), 146–156. http://doi.org/10.1177/074873049701200206 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ l5_onset, l5 = _lmx(data, "5h", lowest=True) return l5_onset, l5
[docs] def m10(data): r"""M10 Mean activity/light/temperature, etc. during the 10 most active hours of the day. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. Returns ------- m10_onset, m10 : float Notes ----- The M10 [1]_ variable is calculated as the mean, per acquisition period , of the average daily activities during the 10 most active hours. .. warning:: The value of this variable depends on the length of the acquisition period. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Van Someren, E.J.W., Lijzenga, C., Mirmiran, M., Swaab, D.F. (1997). Long-Term Fitness Training Improves the Circadian Rest-Activity Rhythm in Healthy Elderly Males. Journal of Biological Rhythms, 12(2), 146–156. http://doi.org/10.1177/074873049701200206 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ m10_onset, m10 = _lmx(data, "10h", lowest=False) return m10_onset, m10
[docs] def ra(data): r"""Relative rest/activity amplitude Relative amplitude between the mean activity during the 10 most active hours of the day and the mean activity during the 5 least active hours of the day. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. Returns ------- ra: float Notes ----- The RA [1]_ variable is calculated as: .. math:: RA = \frac{M10 - L5}{M10 + L5} References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Van Someren, E.J.W., Lijzenga, C., Mirmiran, M., Swaab, D.F. (1997). Long-Term Fitness Training Improves the Circadian Rest-Activity Rhythm in Healthy Elderly Males. Journal of Biological Rhythms, 12(2), 146–156. http://doi.org/10.1177/074873049701200206 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ _, l5 = _lmx(data, "5h", lowest=True) _, m10 = _lmx(data, "10h", lowest=False) return (m10 - l5) / (m10 + l5)
[docs] def l5p(data, period="7D", verbose=False): r"""L5 per period The L5 variable is calculated for each consecutive period found in the actigraphy recording. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. period: str, optional Time period for the calculation of IS Default is '7D'. verbose: bool, optional If set to True, display the number of periods found in the activity recording, as well as the time not accounted for. Default is False. Returns ------- l5p: list of float Notes ----- The L5 [1]_ variable is calculated as the mean, per acquisition period, of the average daily activities during the 5 least active hours. .. warning:: The value of this variable depends on the length of the acquisition period. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Van Someren, E.J.W., Lijzenga, C., Mirmiran, M., Swaab, D.F. (1997). Long-Term Fitness Training Improves the Circadian Rest-Activity Rhythm in Healthy Elderly Males. Journal of Biological Rhythms, 12(2), 146–156. http://doi.org/10.1177/074873049701200206 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ intervals = _interval_maker(data.index, period, verbose) results = [_lmx(data[time[0] : time[1]], "5h", lowest=True) for time in intervals] return [res[1] for res in results]
[docs] def m10p(data, period="7D", verbose=False): r"""M10 per period The M10 variable is calculated for each consecutive period found in the actigraphy recording. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. period: str, optional Time period for the calculation of IS Default is '7D'. If set to True, display the number of periods found in the activity recording, as well as the time not accounted for. Default is False. verbose: bool, optional If set to True, display the number of periods found in the activity recording, as well as the time not accounted for. Default is False. Returns ------- m10p: list of float Notes ----- The M10 [1]_ variable is calculated as the mean, per acquisition period , of the average daily activities during the 10 most active hours. .. warning:: The value of this variable depends on the length of the acquisition period. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Van Someren, E.J.W., Lijzenga, C., Mirmiran, M., Swaab, D.F. (1997). Long-Term Fitness Training Improves the Circadian Rest-Activity Rhythm in Healthy Elderly Males. Journal of Biological Rhythms, 12(2), 146–156. http://doi.org/10.1177/074873049701200206 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ intervals = _interval_maker(data.index, period, verbose) results = [_lmx(data[time[0] : time[1]], "10h", lowest=False) for time in intervals] return [res[1] for res in results]
[docs] def rap(data, period="7D", verbose=False): r"""RA per period The RA variable is calculated for each consecutive period found in the actigraphy recording. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. period: str, optional Time period for the calculation of IS Default is '7D'. verbose: bool, optional If set to True, display the number of periods found in the activity recording, as well as the time not accounted for. Default is False. Returns ------- rap: list of float Notes ----- The RA [1]_ variable is calculated as: .. math:: RA = \frac{M10 - L5}{M10 + L5} References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Van Someren, E.J.W., Lijzenga, C., Mirmiran, M., Swaab, D.F. (1997). Long-Term Fitness Training Improves the Circadian Rest-Activity Rhythm in Healthy Elderly Males. Journal of Biological Rhythms, 12(2), 146–156. http://doi.org/10.1177/074873049701200206 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ intervals = _interval_maker(data.index, period, verbose) results = [] for time in intervals: data_subset = data[time[0] : time[1]] _, l5 = _lmx(data_subset, "5h", lowest=True) _, m10 = _lmx(data_subset, "10h", lowest=False) results.append((m10 - l5) / (m10 + l5)) return results
[docs] def IS(data): r"""Interdaily stability The Interdaily stability (IS) quantifies the repeatibilty of the daily rest-activity pattern over each day contained in the activity recording. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. Returns ------- is: float Notes ----- This variable is defined in ref [1]_: .. math:: IS = \frac{d^{24h}}{d^{1h}} with: .. math:: d^{1h} = \sum_{i}^{n}\frac{\left(x_{i}-\bar{x}\right)^{2}}{n} where :math:`x_{i}` is the number of active (counts higher than a predefined threshold) minutes during the :math:`i^{th}` period, :math:`\bar{x}` is the mean of all data and :math:`n` is the number of periods covered by the actigraphy data and with: .. math:: d^{24h} = \sum_{i}^{p} \frac{ \left( \bar{x}_{h,i} - \bar{x} \right)^{2} }{p} where :math:`\bar{x}^{h,i}` is the average number of active minutes over the :math:`i^{th}` period and :math:`p` is the number of periods per day. The average runs over all the days. For the record, tt is the 24h value from the chi-square periodogram (Sokolove and Bushel1 1978). References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Witting W., Kwa I.H., Eikelenboom P., Mirmiran M., Swaab D.F. Alterations in the circadian rest–activity rhythm in aging and Alzheimer׳s disease. Biol Psychiatry. 1990;27:563–572. [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ d_24h = ( data.groupby([data.index.hour, data.index.minute, data.index.second]) .mean() .var() ) d_1h = data.var() return d_24h / d_1h
[docs] def ISp(data, period="7D", verbose=False): r"""Interdaily stability per period The IS is calculated for each consecutive period found in the actigraphy recording. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. period: str, optional Time period for the calculation of IS Default is '7D'. verbose: bool, optional If set to True, display the number of periods found in the activity recording, as well as the time not accounted for. Default is False. Returns ------- isp: list of float Notes ----- Periods are consecutive and all of the required duration. If the last consecutive period is shorter than required, the IS is not calculated for that period. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ intervals = _interval_maker(data.index, period, verbose) results = [IS(data[time[0] : time[1]]) for time in intervals] return results
[docs] def IV(data): r"""Intradaily variability The Intradaily Variability (IV) quantifies the variability of the activity recording. This variable thus measures the rest or activity fragmentation. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. Returns ------- iv: float Notes ----- It is defined in ref [1]_: .. math:: IV = \frac{c^{1h}}{d^{1h}} with: .. math:: d^{1h} = \sum_{i}^{n}\frac{\left(x_{i}-\bar{x}\right)^{2}}{n} where :math:`x_{i}` is the number of active (counts higher than a predefined threshold) minutes during the :math:`i^{th}` period, :math:`\bar{x}` is the mean of all data and :math:`n` is the number of periods covered by the actigraphy data, and with: .. math:: c^{1h} = \sum_{i}^{n-1} \frac{ \left( x_{i+1} - x_{i} \right)^{2} }{n-1} References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Witting W., Kwa I.H., Eikelenboom P., Mirmiran M., Swaab D.F. Alterations in the circadian rest–activity rhythm in aging and Alzheimer׳s disease. Biol Psychiatry. 1990;27:563–572. [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ c_1h = data.diff(1).pow(2).mean() d_1h = data.var() return c_1h / d_1h
[docs] def IVp(data, period="7D", verbose=False): r"""Intradaily variability per period The IV is calculated for each consecutive period found in the actigraphy recording. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. period: str, optional Time period for the calculation of IS Default is '7D'. verbose: bool, optional If set to True, display the number of periods found in the activity recording, as well as the time not accounted for. Default is False. Returns ------- ivp: list of float Notes ----- Periods are consecutive and all of the required duration. If the last consecutive period is shorter than required, the IV is not calculated for that period. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ intervals = _interval_maker(data.index, period, verbose) results = [IV(data[time[0] : time[1]]) for time in intervals] return results
[docs] def summary_stats(light, bins="24h", agg_func=None): r"""Summary statistics. Calculate summary statistics (ex: mean, median, etc) according to a user-defined (regular or arbitrary) binning. Parameters ---------- light : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (in this case, light). Time and value arrays are extracted from this series. bins : str or list of tuples, optional If set to a string, bins is used to define a regular binning where every bin is of length "bins". Ex: "2h". Otherwise, the list of 2-tuples is used to define an arbitrary binning. Ex: \[('2000-01-01 00:00:00','2000-01-01 11:59:00')\]. Default is '24h'. agg_func : list, optional List of aggregation functions to be used on every bin. Default is \['mean', 'median', 'sum', 'std', 'min', 'max'\]. Returns ------- ss : pd.DataFrame A pandas DataFrame with summary statistics per channel. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ if agg_func is None: agg_func = ["mean", "median", "sum", "std", "min", "max"] if isinstance(bins, str): summary_stats = light.resample(bins).agg(agg_func) elif isinstance(bins, list): df_col = [] for idx, (start, end) in enumerate(bins): df_bins = ( light.loc[start:end, :].apply(agg_func).pivot_table(columns=agg_func) ) channels = df_bins channels = channels.loc[:, agg_func] df_col.append(pd.concat(channels, axis=1)) summary_stats = pd.concat(df_col) return summary_stats
[docs] def light_exposure(light, threshold=None, start_time=None, stop_time=None, agg="mean"): r"""Light exposure level Calculate the aggregated (mean, median, etc) light exposure level per epoch. Parameters ---------- light : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (in this case, light). Time and value arrays are extracted from this series. threshold: float, optional If not set to None, discard data below threshold before computing exposure levels. Default is None. start_time: str, optional If not set to None, discard data before start time, on a daily basis. Supported time string: 'HH:MM:SS' Default is None. stop_time: str, optional If not set to None, discard data after stop time, on a daily basis. Supported time string: 'HH:MM:SS' Default is None. agg: str, optional Aggregating function used to summarize exposure levels. Available functions: 'mean', 'median', 'std', etc. Default is 'mean'. Returns ------- levels : pd.Series A pandas Series with aggreagted light exposure levels per channel References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ light_exposure = _light_exposure( light=light, threshold=threshold, start_time=start_time, stop_time=stop_time ) levels = getattr(light_exposure, agg) return levels()
[docs] def TAT(data, threshold=None, start_time=None, stop_time=None, oformat=None): r"""Time above light threshold. Calculate the total light exposure time above the threshold. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. threshold: float, optional If not set to None, discard data below threshold before computing exposure levels. Default is None. start_time: str, optional If not set to None, discard data before start time, on a daily basis. Supported time string: 'HH:MM:SS' Default is None. stop_time: str, optional If not set to None, discard data after stop time, on a daily basis. Supported time string: 'HH:MM:SS' Default is None. oformat: str, optional Output format. Available formats: 'minute' or 'timedelta'. If set to 'minute', the result is in number of minutes. If set to 'timedelta', the result is a pd.Timedelta. If set to None, the result is in number of epochs. Default is None. Returns ------- tat : pd.Series A pandas Series with aggreagted light exposure levels per channel References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ available_formats = [None, "minute", "timedelta"] if oformat not in available_formats: raise ValueError( "Specified output format ({}) not supported. ".format(oformat) + "Available formats are: {}".format(str(available_formats)) ) light_exposure_counts = _light_exposure( light=data, threshold=threshold, start_time=start_time, stop_time=stop_time ).count() if oformat == "minute": tat = ( light_exposure_counts * pd.Timedelta(data.index.freq) / pd.Timedelta("1min") ) elif oformat == "timedelta": tat = light_exposure_counts * pd.Timedelta(data.index.freq) else: tat = light_exposure_counts return tat
[docs] def TATp(data, threshold=None, start_time=None, stop_time=None, oformat=None): r"""Time above light threshold (per day). Calculate the total light exposure time above the threshold, per calendar day. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. threshold: float, optional If not set to None, discard data below threshold before computing exposure levels. Default is None. start_time: str, optional If not set to None, discard data before start time, on a daily basis. Supported time string: 'HH:MM:SS' Default is None. stop_time: str, optional If not set to None, discard data after stop time, on a daily basis. Supported time string: 'HH:MM:SS' Default is None. oformat: str, optional Output format. Available formats: 'minute' or 'timedelta'. If set to 'minute', the result is in number of minutes. If set to 'timedelta', the result is a pd.Timedelta. If set to None, the result is in number of epochs. Default is None. Returns ------- tatp : pd.DataFrame A pandas DataFrame with aggreagted light exposure levels per channel and per day. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ available_formats = [None, "minute", "timedelta"] if oformat not in available_formats: raise ValueError( "Specified output format ({}) not supported. ".format(oformat) + "Available formats are: {}".format(str(available_formats)) ) light_exposure_counts_per_day = ( _light_exposure( light=data, threshold=threshold, start_time=start_time, stop_time=stop_time ) .groupby(data.index.date) .count() ) if oformat == "minute": tatp = ( light_exposure_counts_per_day * pd.Timedelta(data.index.freq) / pd.Timedelta("1min") ) elif oformat == "timedelta": tatp = light_exposure_counts_per_day * pd.Timedelta(data.index.freq) else: tatp = light_exposure_counts_per_day return tatp
[docs] def VAT(data, threshold=None): r"""Values above light threshold. Returns the light exposure values above the threshold. Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. threshold: float, optional If not set to None, discard data below threshold before computing exposure levels. Default is None. Returns ------- vat : pd.Series A pandas Series with light exposure levels per channel References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ return _light_exposure( light=data, threshold=threshold, start_time=None, stop_time=None )
[docs] def get_time_barycentre(data): # Normalize each epoch to midnight. Y_j = data.index - data.index.normalize() # Convert to indices. Y_j /= pd.Timedelta(data.index.freq) # Compute barycentre bc = data.multiply(Y_j, axis=0).sum() / data.sum() return bc
[docs] def mlit(light, threshold): r"""Mean light timing. Mean light timing above threshold, MLiT^C. Parameters ---------- light : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (in this case, light). Time and value arrays are extracted from this series. threshold: float Threshold value. Returns ------- MLiT : pd.DataFrame A pandas DataFrame with MLiT^C per channel. Notes ----- The MLiT variable is defined in ref [1]_: .. math:: MLiT^C = \frac{\sum_{j}^{m}\sum_{k}^{n} j\times I^{C}_{jk}}{ \sum_{j}^{m}\sum_{k}^{n} I^{C}_{jk}} where :math:`I^{C}_{jk}` is equal to 1 if the light level is higher than the threshold C, m is the total number of epochs per day and n is the number of days covered by the data. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Reid K.J., Santostasi G., Baron K.G., Wilson J., Kang J., Zee P.C., Timing and Intensity of Light Correlate with Body Weight in Adults. PLoS ONE 9(4): e92251. https://doi.org/10.1371/journal.pone.0092251 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ # Binarized data and convert to float in order to handle 'DivideByZero' I_jk = _binarize(data=light, threshold=threshold).astype("float64") MLiT = get_time_barycentre(I_jk) # Scaling factor: MLiT is now expressed in minutes since midnight. MLiT /= pd.Timedelta("1min") / I_jk.index.freq return MLiT
[docs] def mlitp(light, threshold): r"""Mean light timing per day. Mean light timing above threshold, MLiT^C, per calendar day. Parameters ---------- light : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (in this case, light). Time and value arrays are extracted from this series. threshold: float Threshold value. Returns ------- MLiTp : pd.DataFrame A pandas DataFrame with MLiT^C per channel and per day. Notes ----- The MLiT variable is defined in ref [1]_: .. math:: MLiT^C = \frac{\sum_{j}^{m}\sum_{k}^{n} j\times I^{C}_{jk}}{ \sum_{j}^{m}\sum_{k}^{n} I^{C}_{jk}} where :math:`I^{C}_{jk}` is equal to 1 if the light level is higher than the threshold C, m is the total number of epochs per day and n is the number of days covered by the data. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Reid K.J., Santostasi G., Baron K.G., Wilson J., Kang J., Zee P.C., Timing and Intensity of Light Correlate with Body Weight in Adults. PLoS ONE 9(4): e92251. https://doi.org/10.1371/journal.pone.0092251 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ # Binarized data and convert to float in order to handle 'DivideByZero' I_jk = _binarize(data=light, threshold=threshold).astype("float64") # Group data per day: MLiTp = I_jk.groupby(I_jk.index.date).apply(get_time_barycentre) # Scaling factor: MLiT is now expressed in minutes since midnight. MLiTp /= pd.Timedelta("1min") / I_jk.index.freq return MLiTp
[docs] def get_extremum(data, extremum): r"""Light extremum. Return the index and the value of the requested extremum (min or max). Parameters ---------- data : pandas.Series, optional Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. extremum: str Name of the extremum. Available: 'min' or 'max'. Returns ------- ext : pd.DataFrame A pandas DataFrame with extremum info per channel. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [2] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ # Return either the maximum or minimum, as well as the respective timestamp if extremum == "max": return data.idxmax(), data.max() elif extremum == "min": return data.idxmin(), data.min() else: raise ValueError('Extremum must be "min" or "max"')
[docs] def lmx(data, length="5h", lowest=True): r"""Least or Most light period of length X Onset and mean hourly light exposure levels during the X least or most bright hours of the day. Parameters ---------- data : pandas.Series Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. length: str, optional Period length. Default is '5h'. lowest: bool, optional If lowest is set to True, the period of least light exposure is considered. Otherwise, consider the period of most light exposure. Default is True. Returns ------- lmx_t, lmx: (pd.Timedelta, float) Onset and mean hourly light exposure level. Notes ----- The LMX variable is derived from the L5 and M10 defined in [1]_ as the mean hourly activity levels during the 5/10 least/most active hours. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Van Someren, E.J.W., Lijzenga, C., Mirmiran, M., Swaab, D.F. (1997). Long-Term Fitness Training Improves the Circadian Rest-Activity Rhythm in Healthy Elderly Males. Journal of Biological Rhythms, 12(2), 146–156. http://doi.org/10.1177/074873049701200206 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ # Calculate time of LMX and the value of LMX lmx_ts, lmx = _lmx(data, length, lowest=lowest) # Return these values back to the user return lmx_ts, lmx
[docs] def temporal_centroid(data): """ Compute the temporal centroid of a time series. The centroid is calculated as the weighted average of time points, where weights are proportional to the values in the time series. Parameters ---------- data : pandas.Series Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. Returns ------- pd.Timestamp The temporal centroid of the time series, expressed as a timestamp. References ---------- [1] Zauner, J., Hartmeyer, S., & Spitschan, M. (2025). LightLogR: Reproducible analysis of personal light exposure data. Journal of Open Source Software, 10(107), 7601. https://doi.org/10.21105/joss.07601. RRID:SCR_025408 """ weights = data / data.sum() time = data.index.astype(np.int64) temp_centroid = np.round(np.sum(time * weights)) return pd.to_datetime(temp_centroid)
[docs] def spectral_centroid(data): """ Compute the spectral centroid of a time series. Parameters ---------- data : pandas.Series Input data series with a DatetimeIndex, where the index specifies the time points and the values represent the input variable (e.g., activity, light). Time and value arrays are extracted from this series. Returns ------- pd.Timestamp The spectral (frequency) centroid of the time series, expressed as a timestamp. References ---------- [1] Zauner, J., Hartmeyer, S., & Spitschan, M. (2025). LightLogR: Reproducible analysis of personal light exposure data. Journal of Open Source Software, 10(107), 7601. https://doi.org/10.21105/joss.07601. RRID:SCR_025408 """ # Infer sampling rate delta = (data.index[1] - data.index[0]).total_seconds() # Convert sampling rate to Hz (1/samples_per_second) t = 1 / delta # Number of sample points in the recording n = len(data.values) # y axis - magnitude corresponding to each frequency - x(fi) amplitude = np.abs(fft(data.values))[: n // 2] # x axis (frequencies) frequencies = fftfreq(n, delta)[: n // 2] # Avoid dividing by zero if np.sum(amplitude) == 0: return None spec_centroid = np.sum(amplitude * frequencies) / np.sum(amplitude) return spec_centroid
[docs] def pRA(data, start=None, period=None): r"""Rest->Activity transition probability distribution Conditional probability, pRA(t), that an individual would be resting at time (t+1) given that the individual had been continuously active for the preceding t epochs, defined in [1]_ as: .. math:: pRA(t) = p(A|R_t) = \frac{N_t - N_{t+1}}{N_t} with :math:`N_t`, the total number of sequences of rest (i.e. activity below threshold) of duration :math:`t` or longer. Parameters ---------- threshold: int If binarize is set to True, data above this threshold are set to 1 and to 0 otherwise. start: str, optional If not None, the actigraphy recording is truncated to 'start:start+period', each day. Start string format: 'HH:MM:SS'. Default is None period: str, optional Time period for the calculation of pRA. Default is None. Returns ------- pra: pandas.core.series.Series Transition probabilities (pRA(t)), calculated for all t values. pra_weights: pandas.core.series.Series Weights are defined as the square root of the number of activity sequences contributing to each probability estimate. Notes ----- pRA is corrected for discontinuities due to sparse data, as defined in [1]_. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Lim, A. S. P., Yu, L., Costa, M. D., Buchman, A. S., Bennett, D. A., Leurgans, S. E., & Saper, C. B. (2011). Quantification of the Fragmentation of Rest-Activity Patterns in Elderly Individuals Using a State Transition Analysis. Sleep, 34(11), 1569–1581. http://doi.org/10.5665/sleep.1400 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ # Restrict data range to period 'Start, Start+Period' if start is not None: end = _td_format(pd.Timedelta(start) + pd.Timedelta(period)) data = data.between_time(start, end) else: data = data # Rest->Activity transition probability: pRA, pRA_weights = _transition_prob(data, True) return pRA, pRA_weights
[docs] def pAR(data, start=None, period=None): r"""Activity->Rest transition probability distribution Conditional probability, pAR(t), that an individual would be active at time (t+1) given that the individual had been continuously resting for the preceding t epochs, defined in [1]_ as: .. math:: pAR(t) = p(R|A_t) = \frac{N_t - N_{t+1}}{N_t} with :math:`N_t`, the total number of sequences of activity (i.e. activity above threshold) of duration :math:`t` or longer. Parameters ---------- threshold: int If binarize is set to True, data above this threshold are set to 1 and to 0 otherwise. start: str, optional If not None, the actigraphy recording is truncated to 'start:start+period', each day. Start string format: 'HH:MM:SS'. Default is None period: str, optional Time period for the calculation of pAR. Default is None. Returns ------- par: pandas.core.series.Series Transition probabilities (pAR(t)), calculated for all t values. par_weights: pandas.core.series.Series Weights are defined as the square root of the number of activity sequences contributing to each probability estimate. Notes ----- pAR is corrected for discontinuities due to sparse data, as defined in [1]_. References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Lim, A. S. P., Yu, L., Costa, M. D., Buchman, A. S., Bennett, D. A., Leurgans, S. E., & Saper, C. B. (2011). Quantification of the Fragmentation of Rest-Activity Patterns in Elderly Individuals Using a State Transition Analysis. Sleep, 34(11), 1569–1581. http://doi.org/10.5665/sleep.1400 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ # Restrict data range to period 'Start, Start+Period' if start is not None: end = _td_format(pd.Timedelta(start) + pd.Timedelta(period)) data = data.between_time(start, end) else: data = data # Activity->Rest transition probability: pAR, pAR_weights = _transition_prob(data, False) return pAR, pAR_weights
[docs] def kRA(data, start=None, period=None, frac=0.3, it=0, logit=False, offset="15min"): r"""Rest->Activity transition probability Weighted average value of pRA(t) within the constant regions, defined as the longest stretch within which the LOWESS curve varied by no more than 1 standard deviation of the pRA(t) curve [1]_. Parameters ---------- threshold: int Above this threshold, data are classified as active (1) and as rest (0) otherwise. start: str, optional If not None, the actigraphy recording is truncated to 'start:start+period', each day. Start string format: 'HH:MM:SS'. Special keywords ('AonT' or 'AoffT') are allowed. In this case, the start is set to the activity onset ('AonT') or offset ('AoffT') time derived from the daily profile. Cf sleep.AonT/AoffT functions for more informations. Default is None period: str, optional Time period for the calculation of pRA. Default is None. frac: float, optional Fraction of the data used when estimating each value. Default is 0.3. it: int, optional Number of residual-based reweightings to perform. Default is 0. logit: bool, optional If True, the kRA value is logit-transformed (ln(p/1-p)). Useful when kRA is used in a regression model. Default is False. offset: str, optional Time offset with respect to the activity onset and offset times used as start times. Default is '15min'. Returns ------- kra: float References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Lim, A. S. P., Yu, L., Costa, M. D., Buchman, A. S., Bennett, D. A., Leurgans, S. E., & Saper, C. B. (2011). Quantification of the Fragmentation of Rest-Activity Patterns in Elderly Individuals Using a State Transition Analysis. Sleep, 34(11), 1569–1581. http://doi.org/10.5665/sleep.1400 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ if start is not None and re.match(r"AonT|AoffT", start): aont = AonT(data) aofft = AoffT(data) offset = pd.Timedelta(offset) if start == "AonT": start_time = str(aont + offset).split(" ")[-1] period = str( pd.Timedelta("24H") - ((aont + offset) - (aofft - offset)) ).split(" ")[-1] elif start == "AoffT": start_time = str(aofft + offset).split(" ")[-1] period = str( pd.Timedelta("24H") - ((aofft + offset) - (aont - offset)) ).split(" ")[-1] else: start_time = start # Calculate the pRA probabilities and their weights. pra, pra_weights = pRA(data, start=start_time, period=period) # Fit the pRA distribution with a LOWESS and return mean value for # the constant region (i.e. the region where |pRA-lowess|<1SD) kRA = _transition_prob_sustain_region(pra, pra_weights, frac=frac, it=it) return np.log(kRA / (1 - kRA)) if logit else kRA
[docs] def kAR(data, start=None, period=None, frac=0.3, it=0, logit=False, offset="15min"): """Rest->Activity transition probability Weighted average value of pAR(t) within the constant regions, defined as the longest stretch within which the LOWESS curve varied by no more than 1 standard deviation of the pAR(t) curve. Parameters ---------- threshold: int Above this threshold, data are classified as active (1) and as rest (0) otherwise. start: str, optional If not None, the actigraphy recording is truncated to 'start:start+period', each day. Start string format: 'HH:MM:SS'. Special keywords ('AonT' or 'AoffT') are allowed. In this case, the start is set to the activity onset ('AonT') or offset ('AoffT') time derived from the daily profile. Cf sleep.AonT/AoffT functions for more informations. Default is None period: str, optional Time period for the calculation of pRA. Default is None. frac: float Fraction of the data used when estimating each value. Default is 0.3. it: int Number of residual-based reweightings to perform. Default is 0. logit: bool, optional If True, the kRA value is logit-transformed (ln(p/1-p)). Useful when kRA is used in a regression model. Default is False. freq: str, optional Data resampling `frequency string <https://pandas.pydata.org/pandas-docs/stable/timeseries.html>`_ applied to the daily profile if start='AonT' or 'AoffT'. Default is None. offset: str, optional Time offset with respect to the activity onset and offset times used as start times. Default is '15min'. Returns ------- kar: float References ---------- This code is derived from the original implementation in pyActigraphy, distributed under the BSD 3-Clause License. Original author: Grégory Hammad (gregory.hammad@uliege.be). [1] Lim, A. S. P., Yu, L., Costa, M. D., Buchman, A. S., Bennett, D. A., Leurgans, S. E., & Saper, C. B. (2011). Quantification of the Fragmentation of Rest-Activity Patterns in Elderly Individuals Using a State Transition Analysis. Sleep, 34(11), 1569–1581. http://doi.org/10.5665/sleep.1400 [2] Hammad, G., Reyt, M., Beliy, N., Baillet, M., Deantoni, M., Lesoinne, A., Muto, V., & Schmidt, C. (2021). pyActigraphy: Open-source python package for actigraphy data visualization and analysis. PLoS Computational Biology, 17(10), 1009514–1009535. https://doi.org/10.1371/journal.pcbi.1009514 [3] Hammad, G., Wulff, K., Skene, D. J., Münch, M., & Spitschan, M. (2024). Open-Source Python Module for the Analysis of Personalized Light Exposure Data from Wearable Light Loggers and Dosimeters. LEUKOS, 20(4), 380–389. https://doi.org/10.1080/15502724.2023.2296863 """ if start is not None and re.match(r"AonT|AoffT", start): aont = AonT(data) aofft = AoffT(data) offset = pd.Timedelta(offset) if start == "AonT": start_time = str(aont + offset).split(" ")[-1] period = str( pd.Timedelta("24H") - ((aont + offset) - (aofft - offset)) ).split(" ")[-1] elif start == "AoffT": start_time = str(aofft + offset).split(" ")[-1] period = str( pd.Timedelta("24H") - ((aofft + offset) - (aont - offset)) ).split(" ")[-1] else: start_time = start # Calculate the pAR probabilities and their weights. par, par_weights = pAR(data, start=start_time, period=period) # Fit the pAR distribution with a LOWESS and return mean value for # the constant region (i.e. the region where |pAR-lowess|<1SD) kAR = _transition_prob_sustain_region(par, par_weights, frac=frac, it=it) return np.log(kAR / (1 - kAR)) if logit else kAR