Light exposure analysis with pyActigraphy

Light&Gears

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…