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