Light exposure analysis with pyActigraphy¶
Photo by Daniele Levis Pelusi on Unsplash
Disclaimer¶
The development of the pyActigraphy module for analysing light exposure data was led and financially supported by members of the Daylight Academy Project The role of daylight for humans (led by Mirjam Münch, Manuel Spitschan). The module is part of the Human Light Exposure Database. For more information about the project, please see https://daylight.academy/projects/state-of-light-in-humans/.
Introduction¶
Similarly to the analysis of locomotor activity via actigraphy, light exposure data analysis is difficult because of the lack of open-source analysis softwares that gives users access to a list of various analysis metrics.
The light exposure data analysis module of pyActigraphy is meant to fix this issue.
In this tutorial, we will review the light exposure analysis metrics currently available in pyActigraphy:
Exposure levels
Summary statistics
Time above threshold (TAT)
Mean light timing above threshold (MLit)
Extrema (min. and max.)
L5 and M10 (LMX)
IS and IV
Direct access to raw or thresholded data
Imports¶
As usual, first import the necessary packages:
[1]:
import pyActigraphy
[2]:
import plotly.graph_objects as go
[3]:
import numpy as np
[4]:
import os
Log-transformation of light data¶
Light data are (log10+1)-transformed in pyActigraphy. Therefore, when threshold values are meant to applied to the light levels, the corresponding value on the (log10+1) scale should be applied.
NB: an offset of 1 is added to the light data before log10 transformation in order to avoid a divergence of the log10 function when the light data values are zero: $:nbsphinx-math:log_{10}(0) = -:nbsphinx-math:`infty `$.
So, on a (log10+1) scale:
10 lux threshold correspond to a value of log(10+1)~1
100 lux threshold correspond to a value of log(100+1)~2
1000 lux threshold correspond to a value of log(1000+1)~3
…
To simply get the exact value, use the np.log10
function of the numpy package we imported earlier:
[5]:
np.log10(10+1), np.log10(100+1), np.log10(1000+1)
[5]:
(1.0413926851582251, 2.0043213737826426, 3.000434077479319)
Input data¶
Set up a path to your favorite data file. Here, we use a recording made with a Actiwatch Spectrum Pro from Respironic:
[6]:
fpath = os.path.join(
os.path.dirname(pyActigraphy.__file__),
'tests','data', 'test_sample_rpx_ger_with_light.csv'
)
To read such a file, use the corresponding reader function:
[7]:
raw = pyActigraphy.io.read_raw_rpx(
fpath,
language='GER',
delimiter=',',
decimal=','
)
Now, let’s verify which light channels are available for this recording:
[8]:
raw.light.get_channel_list()
[8]:
Index(['Grünes Licht', 'Blaues Licht', 'Rotes Licht', 'Weißes Licht'], dtype='object')
As expected, this device records light exposure data in 4 different channels (White light + RBG).
In the following, we will learn how to analyse these different channels.
Light exposure metrics¶
Exposure level¶
This function returns coarse statistics (mean, median, sum, etc) about the light exposure level. It is possible to define the daily time window during which these statistics are calculated.
[9]:
help(raw.light.light_exposure_level)
Help on method light_exposure_level in module pyActigraphy.light.light_metrics:
light_exposure_level(threshold=None, start_time=None, stop_time=None, agg='mean') method of pyActigraphy.light.light.LightRecording instance
Light exposure level
Calculate the aggregated (mean, median, etc) light exposure level
per epoch.
Parameters
----------
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
Default settings: mean exposure level per acquisition epoch
[10]:
raw.light.light_exposure_level()
[10]:
Grünes Licht 0.980005
Blaues Licht 0.917368
Rotes Licht 1.092138
Weißes Licht 0.826787
dtype: float64
Median exposure level:
[11]:
raw.light.light_exposure_level(agg='median')
[11]:
Grünes Licht 0.795185
Blaues Licht 0.674861
Rotes Licht 1.002166
Weißes Licht 0.000000
dtype: float64
Restricting the time window to ‘08:00’-‘16:00’ on a daily basis:
[12]:
raw.light.light_exposure_level(
start_time='08:00:00',
stop_time='16:00:00',
)
[12]:
Grünes Licht 1.855412
Blaues Licht 1.743363
Rotes Licht 2.036617
Weißes Licht 1.749981
dtype: float64
It is also possible to only consider light exposure data above a certain threshold. For example, to compute the mean light exposure level above 10 lux, between ‘12:00’ and ‘18:00’:
[13]:
raw.light.light_exposure_level(
threshold=1, # on a log10 scale
start_time='12:00',
stop_time='18:00'
)
[13]:
Grünes Licht 2.069842
Blaues Licht 1.991611
Rotes Licht 2.216590
Weißes Licht 2.108550
dtype: float64
Summary statistics (mean, median, s.d., max., min., sum)¶
This function returns various summary statistics about the data aggregated into time bins. The length of these time bins, but also their start and stop times, are configurable.
[14]:
help(raw.light.summary_statistics_per_time_bin)
Help on method summary_statistics_per_time_bin in module pyActigraphy.light.light_metrics:
summary_statistics_per_time_bin(bins='24h', agg_func=['mean', 'median', 'sum', 'std', 'min', 'max']) method of pyActigraphy.light.light.LightRecording instance
Summary statistics.
Calculate summary statistics (ex: mean, median, etc) according to a
user-defined (regular or arbitrary) binning.
Parameters
----------
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.
By default, this function returns the mean, median, sum, standard deviation, as well as the min and max, of data aggregated into consecutive 24h bins, for all light channels separately:
[15]:
raw.light.summary_statistics_per_time_bin()
[15]:
Grünes Licht | Blaues Licht | ... | Rotes Licht | Weißes Licht | |||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
mean | median | sum | std | min | max | mean | median | sum | std | ... | sum | std | min | max | mean | median | sum | std | min | max | |
Date_Time | |||||||||||||||||||||
2019-09-19 | 1.173315 | 1.077364 | 1407.978432 | 0.996510 | 0.0 | 3.793162 | 1.078863 | 0.948413 | 1294.636031 | 0.975758 | ... | 1701.513409 | 1.016335 | 0.0 | 4.763435 | 1.154736 | 1.001301 | 1385.683606 | 1.016960 | 0.0 | 4.500635 |
2019-09-20 | 1.106091 | 0.980458 | 3034.006833 | 1.069593 | 0.0 | 4.586599 | 1.036230 | 0.907949 | 2842.379112 | 1.017715 | ... | 3421.450389 | 1.132033 | 0.0 | 4.902552 | 1.079186 | 0.813913 | 2956.970970 | 1.094877 | 0.0 | 4.064586 |
2019-09-21 | 0.992491 | 0.191407 | 2858.374130 | 1.182352 | 0.0 | 4.056943 | 0.936734 | 0.246745 | 2697.794728 | 1.129741 | ... | 3165.156940 | 1.267207 | 0.0 | 4.957612 | 0.960150 | 0.000000 | 2764.272662 | 1.210351 | 0.0 | 4.067003 |
2019-09-22 | 0.720914 | 0.227372 | 2076.232641 | 0.904947 | 0.0 | 3.529045 | 0.663084 | 0.120245 | 1909.681575 | 0.866469 | ... | 2424.799081 | 0.982395 | 0.0 | 3.642563 | 0.577017 | 0.000000 | 1661.808371 | 0.905502 | 0.0 | 3.591937 |
2019-09-23 | 0.874532 | 0.979093 | 2475.801461 | 0.782379 | 0.0 | 2.855519 | 0.820053 | 0.877947 | 2321.570322 | 0.748673 | ... | 2749.670329 | 0.851562 | 0.0 | 3.021603 | 0.594351 | 0.000000 | 1682.013356 | 0.831829 | 0.0 | 4.727014 |
2019-09-24 | 1.053836 | 1.117271 | 3035.048297 | 0.987129 | 0.0 | 4.193152 | 0.990157 | 1.031004 | 2850.660903 | 0.934035 | ... | 3317.140580 | 1.060961 | 0.0 | 4.795887 | 0.834724 | 0.000000 | 2403.169619 | 1.036907 | 0.0 | 5.157700 |
2019-09-25 | 1.067738 | 1.216164 | 3075.086077 | 0.993249 | 0.0 | 4.394469 | 1.002701 | 1.141447 | 2887.780101 | 0.945765 | ... | 3319.430031 | 1.054134 | 0.0 | 4.960000 | 0.820592 | 0.000000 | 2363.304718 | 1.025948 | 0.0 | 4.383197 |
2019-09-26 | 0.959414 | 0.000000 | 1599.343782 | 1.128436 | 0.0 | 3.518646 | 0.903512 | 0.000000 | 1506.153751 | 1.061862 | ... | 1696.643786 | 1.172189 | 0.0 | 3.406710 | 0.768633 | 0.000000 | 1281.311023 | 1.051666 | 0.0 | 3.442743 |
8 rows × 24 columns
To change the size of the time bins, now focusing on the mean light level, computed every 12h for instance:
[16]:
raw.light.summary_statistics_per_time_bin(bins='12h', agg_func=['mean'])
[16]:
Grünes Licht | Blaues Licht | Rotes Licht | Weißes Licht | |
---|---|---|---|---|
mean | mean | mean | mean | |
Date_Time | ||||
2019-09-19 12:00:00 | 1.173315 | 1.078863 | 1.417928 | 1.154736 |
2019-09-20 00:00:00 | 0.815624 | 0.774231 | 0.879038 | 0.793869 |
2019-09-20 12:00:00 | 1.427098 | 1.325776 | 1.656387 | 1.393846 |
2019-09-21 00:00:00 | 0.672585 | 0.626542 | 0.735661 | 0.650413 |
2019-09-21 12:00:00 | 1.312397 | 1.246927 | 1.463381 | 1.270103 |
2019-09-22 00:00:00 | 0.124203 | 0.092970 | 0.207093 | 0.111713 |
2019-09-22 12:00:00 | 1.317625 | 1.233197 | 1.476795 | 1.042321 |
2019-09-23 00:00:00 | 0.485732 | 0.456116 | 0.536874 | 0.406213 |
2019-09-23 12:00:00 | 1.277029 | 1.196811 | 1.420972 | 0.788981 |
2019-09-24 00:00:00 | 0.763932 | 0.700590 | 0.807024 | 0.769530 |
2019-09-24 12:00:00 | 1.343740 | 1.279522 | 1.496546 | 0.899872 |
2019-09-25 00:00:00 | 0.559163 | 0.519793 | 0.595288 | 0.525671 |
2019-09-25 12:00:00 | 1.576313 | 1.485610 | 1.709871 | 1.115513 |
2019-09-26 00:00:00 | 0.838703 | 0.789260 | 0.883727 | 0.617451 |
2019-09-26 12:00:00 | 1.677144 | 1.582834 | 1.814858 | 1.667534 |
Finally, arbitrary start and stop times can be used to define the time bins;
[17]:
raw.light.summary_statistics_per_time_bin(
bins=[
['2019-09-19 12:00:00','2019-09-19 19:59:00'],
['2019-09-23 12:00:00','2019-09-23 23:59:00']
],
agg_func=['mean','std']
)
[17]:
Blaues Licht | Grünes Licht | Rotes Licht | Weißes Licht | |||||
---|---|---|---|---|---|---|---|---|
mean | std | mean | std | mean | std | mean | std | |
0 | 1.589873 | 0.910144 | 1.693852 | 0.919021 | 1.898340 | 0.926944 | 1.662187 | 0.968826 |
1 | 1.197672 | 0.586441 | 1.277948 | 0.606433 | 1.421994 | 0.639278 | 0.789549 | 0.782999 |
Time above threshold (TAT)¶
This function computes the total time spent with a light exposure level above a certain threshold. Similarly to the light exposure level function, it is possible to configure the daily time window during which this metric is calculated. Various time output format are available.
[18]:
help(raw.light.TAT)
Help on method TAT in module pyActigraphy.light.light_metrics:
TAT(threshold=None, start_time=None, stop_time=None, oformat=None) method of pyActigraphy.light.light.LightRecording instance
Time above light threshold.
Calculate the total light exposure time above the threshold.
Parameters
----------
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
With the default settings (no threshold, no time window restriction):
[19]:
raw.light.TAT()
[19]:
Grünes Licht 19961
Blaues Licht 19960
Rotes Licht 19957
Weißes Licht 19955
dtype: int64
the results are identical to the total number of epochs in the recording.
To calculate the number of epochs spent above a threshold of 10 lux:
[20]:
raw.light.TAT(threshold=1)
[20]:
Grünes Licht 9351
Blaues Licht 9000
Rotes Licht 9983
Weißes Licht 7479
dtype: int64
The parameter oformat
defines the output format. Available formats: * ‘minute’:
[21]:
raw.light.TAT(threshold=1, oformat='minute')
[21]:
Grünes Licht 4675.5
Blaues Licht 4500.0
Rotes Licht 4991.5
Weißes Licht 3739.5
dtype: float64
‘timedelta’:
[22]:
raw.light.TAT(threshold=1, oformat='timedelta')
[22]:
Grünes Licht 3 days 05:55:30
Blaues Licht 3 days 03:00:00
Rotes Licht 3 days 11:11:30
Weißes Licht 2 days 14:19:30
dtype: timedelta64[ns]
Time spent above threshold at specific time periods:
[23]:
raw.light.TAT(
threshold=1, start_time='08:00:00', stop_time='20:00:00', oformat='timedelta'
)
[23]:
Grünes Licht 2 days 16:38:00
Blaues Licht 2 days 14:24:00
Rotes Licht 2 days 20:01:30
Weißes Licht 2 days 11:09:30
dtype: timedelta64[ns]
Time above threshold per period (TATp)¶
Sometimes, it could be useful to compute the TAT on consecutive periods of time in order to assess the evolution of such metric through time or before and after a specific intervention. While it is always to possible to read the same file multiple times and restrict its start and stop time to different periods each time, pyActigraphy allows users to directly compute the TAT on a daily basis.
This function has the same input parameters as the TAT function:
[24]:
help(raw.light.TATp)
Help on method TATp in module pyActigraphy.light.light_metrics:
TATp(threshold=None, start_time=None, stop_time=None, oformat=None) method of pyActigraphy.light.light.LightRecording instance
Time above light threshold (per day).
Calculate the total light exposure time above the threshold,
per calendar day.
Parameters
----------
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.
[25]:
raw.light.TATp(threshold=2, oformat='minute')
[25]:
Grünes Licht | Blaues Licht | Rotes Licht | Weißes Licht | |
---|---|---|---|---|
2019-09-19 | 138.5 | 111.5 | 174.0 | 141.5 |
2019-09-20 | 374.5 | 318.0 | 433.0 | 377.0 |
2019-09-21 | 408.0 | 366.5 | 440.5 | 428.5 |
2019-09-22 | 163.0 | 122.0 | 205.5 | 179.0 |
2019-09-23 | 87.5 | 43.5 | 194.5 | 86.5 |
2019-09-24 | 305.5 | 236.5 | 417.0 | 277.5 |
2019-09-25 | 237.5 | 202.5 | 316.5 | 243.0 |
2019-09-26 | 183.5 | 159.0 | 236.0 | 150.5 |
Mean light timing (MLit)¶
This function calculates the mean light timing above threshold (\(\mathrm{MLit}^{XXX}\)), defined in Reid et al. (2014) as the average clock time of all aggregated data points above XXX lux. In the original paper, a threshold of 500 lux was chosen but in this function, the threshold is configurable.
The results are expressed in number of minutes since midnight, irrespective of the sampling period.
[26]:
help(raw.light.MLiT)
Help on method MLiT in module pyActigraphy.light.light_metrics:
MLiT(threshold) method of pyActigraphy.light.light.LightRecording instance
Mean light timing.
Mean light timing above threshold, MLiT^C.
Parameters
----------
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
----------
.. [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
To compute the \(\mathrm{MLit}^{500}\):
[27]:
# Do not forget that the light exposure data are converted to log10+1.
raw.light.MLiT(threshold=np.log10(500+1))
[27]:
Grünes Licht 800.190773
Blaues Licht 812.883784
Rotes Licht 813.205830
Weißes Licht 791.039116
dtype: float64
[28]:
# To convert this number of minutes into a time of day, simply divide it by the number of minutes per hour:
divmod(791.039116,60)
[28]:
(13.0, 11.039116000000035)
The mean light timing for white light exposure above 500 lux is around 13h11 for this recording.
Just like for the TAT, it might be interesting to compute the \(\mathrm{MLit}^{XXX}\) variable on a daily basis. Again, pyActigraphy makes that operation easy:
[29]:
raw.light.MLiTp(threshold=np.log10(500+1))
[29]:
Grünes Licht | Blaues Licht | Rotes Licht | Weißes Licht | |
---|---|---|---|---|
2019-09-19 | 871.489247 | 872.392857 | 884.867742 | 878.613402 |
2019-09-20 | 774.283981 | 764.430464 | 787.884106 | 716.108051 |
2019-09-21 | 870.485294 | 884.329167 | 866.842282 | 870.277961 |
2019-09-22 | 871.580189 | 874.472222 | 870.177515 | 881.189815 |
2019-09-23 | 770.000000 | 889.000000 | 779.000000 | 618.491228 |
2019-09-24 | 829.362832 | 861.191011 | 829.003185 | 792.220430 |
2019-09-25 | 782.959239 | 779.517606 | 782.586000 | 778.137143 |
2019-09-26 | 637.688172 | 656.558594 | 643.789189 | 671.674528 |
Extrema¶
This function simply returns information (timing and value) about the minimal or maximal light exposure data value.
[30]:
help(raw.light.get_light_extremum)
Help on method get_light_extremum in module pyActigraphy.light.light_metrics:
get_light_extremum(extremum) method of pyActigraphy.light.light.LightRecording instance
Light extremum.
Return the index and the value of the requested extremum (min or max).
Parameters
----------
extremum: str
Name of the extremum.
Available: 'min' or 'max'.
Returns
-------
ext : pd.DataFrame
A pandas DataFrame with extremum info per channel.
[31]:
raw.light.get_light_extremum(extremum='max')
[31]:
channel | index | value | |
---|---|---|---|
0 | Grünes Licht | 2019-09-20 09:06:30 | 4.586599 |
1 | Blaues Licht | 2019-09-24 10:17:30 | 4.033464 |
2 | Rotes Licht | 2019-09-25 14:07:30 | 4.96 |
3 | Weißes Licht | 2019-09-24 15:06:30 | 5.1577 |
[32]:
raw.light.get_light_extremum(extremum='min')
[32]:
channel | index | value | |
---|---|---|---|
0 | Grünes Licht | 2019-09-19 14:35:00 | 0.0 |
1 | Blaues Licht | 2019-09-19 15:43:30 | 0.0 |
2 | Rotes Licht | 2019-09-19 15:43:30 | 0.0 |
3 | Weißes Licht | 2019-09-19 15:43:30 | 0.0 |
L5 and M10 (values and timing)¶
The L5 (M10) variable refers to the 5(10)h-period of daily light exposure profile with the lowest(highest) light exposure levels. In this function, the length of this period can be configured. Information about the timing and the mean hourly light exposure levels are returned.
[33]:
help(raw.light.LMX)
Help on method LMX in module pyActigraphy.light.light_metrics:
LMX(length='5h', lowest=True) method of pyActigraphy.light.light.LightRecording instance
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
----------
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
----------
.. [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
[34]:
raw.light.LMX(length='10h',lowest=False)
[34]:
channel | index | value | |
---|---|---|---|
0 | Grünes Licht | 0 days 08:21:00 | 222.124699 |
1 | Blaues Licht | 0 days 08:21:00 | 208.756896 |
2 | Rotes Licht | 0 days 08:21:00 | 243.791392 |
3 | Weißes Licht | 0 days 08:20:30 | 210.89479 |
It seems that the 10h long period with the highest light exposure levels starts at 8h21.
[35]:
raw.light.LMX(length='5h',lowest=True)
[35]:
channel | index | value | |
---|---|---|---|
0 | Grünes Licht | 0 days 23:55:00 | 0.122553 |
1 | Blaues Licht | 0 days 00:00:00 | 0.11122 |
2 | Rotes Licht | 0 days 00:00:00 | 0.122716 |
3 | Weißes Licht | 0 days 00:00:00 | 0.0 |
It seems that the 5h long period with the lowest light exposure levels starts at 00h00.
It is easy to visually check if these results are correct by computing the average daily profile of light exposure:
[36]:
dlp = raw.light.average_daily_profile(
rsfreq='60min', cyclic=False, channel='Weißes Licht'
)
[37]:
dlp_fig = go.Figure(go.Scatter(x=dlp.index.astype(str),y=dlp))
[38]:
dlp_fig.show()
IS & IV¶
The interdaily stability (IS) and intradaily variability (IV) were first defined in the context of the locomotor activity analysis, to quantify the stability of the circadian activity pattern and the fragmentation of this daily pattern, respectively.
Now, within the pyActigraphy module for light analysis, these metrics can be computed using the light exposure levels as inputs.
NB: the default parameter values for these functions are different from the values used by default for the IS/IV functions for activity data, directly accessible via raw.IS()
or raw.IV()
.
[39]:
help(raw.light.IS)
Help on method IS in module pyActigraphy.light.light_metrics:
IS(binarize=False, threshold=0) method of pyActigraphy.light.light.LightRecording instance
Interdaily stability
The Interdaily stability (IS) quantifies the repeatibilty of the
daily light exposure pattern over each day contained in the activity
recording.
Parameters
----------
binarize: bool, optional
If set to True, the data are binarized.
Default is False.
threshold: int, optional
If binarize is set to True, data above this threshold are set to 1
and to 0 otherwise.
Default is 0.
Returns
-------
is : pd.DataFrame
A pandas DataFrame with IS per channel.
Notes
-----
This variable is derived from the original IS variable defined in
ref [1]_ as:
.. 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, this is the 24h value from the chi-square periodogram
(Sokolove and Bushel, 1978).
References
----------
.. [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.
By default, light exposure data are not binarized before computing the IS:
[40]:
raw.light.IS()
[40]:
channel | IS | |
---|---|---|
0 | Grünes Licht | 0.685174 |
1 | Blaues Licht | 0.673244 |
2 | Rotes Licht | 0.718167 |
3 | Weißes Licht | 0.667941 |
However, it remains possible to do so by applying a threshold (at 100 lux for example):
[41]:
raw.light.IS(binarize=True, threshold=2)
[41]:
channel | IS | |
---|---|---|
0 | Grünes Licht | 0.413717 |
1 | Blaues Licht | 0.381848 |
2 | Rotes Licht | 0.443128 |
3 | Weißes Licht | 0.41415 |
[42]:
help(raw.light.IV)
Help on method IV in module pyActigraphy.light.light_metrics:
IV(binarize=False, threshold=0) method of pyActigraphy.light.light.LightRecording instance
Intradaily variability
The Intradaily Variability (IV) quantifies the variability of the
light exposure pattern.
Parameters
----------
binarize: bool, optional
If set to True, the data are binarized.
Default is False.
threshold: int, optional
If binarize is set to True, data above this threshold are set to 1
and to 0 otherwise.
Default is 4.
Returns
-------
iv: pd.DataFrame
A pandas DataFrame with IV per channel.
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
----------
.. [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.
By default, no data binarization is applied:
[43]:
raw.light.IV()
[43]:
channel | IV | |
---|---|---|
0 | Grünes Licht | 0.076118 |
1 | Blaues Licht | 0.071945 |
2 | Rotes Licht | 0.057085 |
3 | Weißes Licht | 0.157271 |
But, again, data binarization can easily be performed:
[44]:
raw.light.IV(binarize=True, threshold=1)
[44]:
channel | IV | |
---|---|---|
0 | Grünes Licht | 0.16142 |
1 | Blaues Licht | 0.163378 |
2 | Rotes Licht | 0.125533 |
3 | Weißes Licht | 0.212527 |
Access to raw and thresholded data¶
Most of the analyses on light exposure can be performed using the various metrics available in the pyActigraphy light module. However, for the most fearless users that require full access to the light exposure time series, it is possible to directly access the raw data or simply the thresholded data:
[45]:
help(raw.light.VAT)
Help on method VAT in module pyActigraphy.light.light_metrics:
VAT(threshold=None) method of pyActigraphy.light.light.LightRecording instance
Values above light threshold.
Returns the light exposure values above the threshold.
Parameters
----------
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
Inspect the first 5 epochs of raw light data:
[46]:
raw.light.data.head(5)
[46]:
Grünes Licht | Blaues Licht | Rotes Licht | Weißes Licht | |
---|---|---|---|---|
Date_Time | ||||
2019-09-19 14:00:00 | 2.387390 | 2.220108 | 2.372912 | 2.267477 |
2019-09-19 14:00:30 | 2.387390 | 2.220108 | 2.372912 | 2.267477 |
2019-09-19 14:01:00 | 3.793162 | 3.706803 | 4.029424 | 3.786615 |
2019-09-19 14:01:30 | 3.164650 | 3.107549 | 3.484442 | 3.152298 |
2019-09-19 14:02:00 | 2.847573 | 2.795880 | 3.164650 | 2.847351 |
Or access the raw data thresholded at 100 lux:
[47]:
raw.light.VAT(2)
[47]:
Grünes Licht | Blaues Licht | Rotes Licht | Weißes Licht | |
---|---|---|---|---|
Date_Time | ||||
2019-09-19 14:00:00 | 2.387390 | 2.220108 | 2.372912 | 2.267477 |
2019-09-19 14:00:30 | 2.387390 | 2.220108 | 2.372912 | 2.267477 |
2019-09-19 14:01:00 | 3.793162 | 3.706803 | 4.029424 | 3.786615 |
2019-09-19 14:01:30 | 3.164650 | 3.107549 | 3.484442 | 3.152298 |
2019-09-19 14:02:00 | 2.847573 | 2.795880 | 3.164650 | 2.847351 |
... | ... | ... | ... | ... |
2019-09-26 13:57:30 | NaN | NaN | NaN | NaN |
2019-09-26 13:58:00 | NaN | NaN | NaN | NaN |
2019-09-26 13:58:30 | NaN | NaN | NaN | NaN |
2019-09-26 13:59:00 | NaN | NaN | NaN | NaN |
2019-09-26 13:59:30 | NaN | NaN | NaN | NaN |
20160 rows × 4 columns
Et voilà! For now…