Calculating common activity and light metrics#

After preprocessing actigraphy data using the methods defined in the Raw class, users can begin calculating metrics based on the activity and light time series. These include common circadian rhythm metrics such as Interdaily Stability (IS) and Relative Amplitude (RA), among others.

Contrast with pyActigraphy

A key architectural difference between circStudio and pyActigraphy lies in how metric functions are implemented. In pyActigraphy, metrics are defined as methods of the BaseRaw class, which inherits from the mixin subclasses MetricsMixin, ScoringMixin and SleepBoutMixin, and initializes a LightRecording instance to store the light intensity data.

In contrast, circStudio separates data structure from metric computation. The Raw class contains only the data attributes of the actigraphy recording and methods for transforming them (e.g., masking, binarization, imputation).

Metric computation functions in circStudio are not bound to a class. Instead, they are standalone functions that accept input data directly. This design gives users the flexibility to either use the Raw class for preprocessing or bypass it entirely and use their own custom-preprocessed data to compute metrics using circStudio's core functionality.

As before, we will import circStudio and os:

import circstudio as cs
from circstudio.analysis import *
import os
import plotly.io as pio
pio.renderers.default = "notebook"

Open the sample RPX file containing the data that will be used in this tutorial:

# Create file path for sample files within the circStudio package
fpath = os.path.join(os.path.dirname(cs.__file__))

# Create a new Raw instance using awd RPX adaptor
raw = cs.io.read_rpx(os.path.join(fpath, 'data', 'test_sample_rpx_eng.csv'),
                             start_time='2015-07-04 12:00:00',
                             light_mode='White Light',
                             period='6 days',
                             language='ENG_UK')

In the next code blocks, we demonstrate how to compute several circadian rhythm metrics. These represent just a small subset of the available metrics in circStudio. Users can also implement their own custom metrics by following a similar function design.

Activity#

Average daily activity (ADAT)#

adat(data=raw.activity)
np.float64(508148.60612669773)

Interdaily stability (IS)#

IS(data=raw.activity)
np.float64(0.469182266891273)

Interdaily variability (IV)#

IV(data=raw.activity)
np.float64(0.44299552530257036)

Ten most active hours of the day (M10)#

Returns a tuple with the onset and value of the M10.

m10(data=raw.activity)
(Timedelta('1 days 08:13:30'), np.float64(318.8208333333333))

Five least active hours of the day (L5)#

Returns a tuple with the onset and value of the L5.

l5(data=raw.activity)
(Timedelta('0 days 22:13:00'), np.float64(6.60361111111111))

Light#

Average daily light profile#

By default, daily_profile function returns a pd.Series containing the average daily light intensity values. However, its behavior can be modified to generate an interactive daily profile plot instead. For example, instead of:

daily_profile(data=raw.light)
0 days 00:00:00    0.033333
0 days 00:00:30    0.033333
0 days 00:01:00    0.033333
0 days 00:01:30    0.033333
0 days 00:02:00    0.033333
                     ...   
0 days 23:57:30    0.033333
0 days 23:58:00    0.033333
0 days 23:58:30    0.033333
0 days 23:59:00    0.033333
0 days 23:59:30    0.033333
Freq: 30s, Name: White Light, Length: 2880, dtype: float64

Simply do:

daily_profile(data=raw.light, plot=True, log=False)

Interdaily stability (IS)#

IS(data=raw.light)
np.float64(0.16743251076838125)

Interdaily variability (IV)#

IV(data=raw.activity)
np.float64(0.44299552530257036)

Ten brightest hours of the day (M10)#

m10(data=raw.light)
(Timedelta('0 days 08:20:00'), np.float64(1411.567527777778))

Five least illuminated hours of the day (L5)#

l5(data=raw.light)
(Timedelta('0 days 23:50:30'), np.float64(0.011161111111111111))

Sleep#

Automatic inactivity detection algorithms: Crespo and Roenneberg#

circStudio contains several algorithms to detect inactivity periods in actigraphy recordings. In this tutorial, we will illustrate this functionality using the Crespo and Roenneberg (also known as MASDA) algorithms. You can use the ‘plot’ parameter to determine whether a pd.Series or an interactive plot must be returned.

If you set plot to True:

Crespo(data=raw.activity, frequency=raw.frequency, plot=True)

By default, plot is set to False, thus returning a pd.Series:

Crespo(data=raw.activity, frequency=raw.frequency)
2015-07-04 12:00:00    0
2015-07-04 12:00:30    1
2015-07-04 12:01:00    1
2015-07-04 12:01:30    1
2015-07-04 12:02:00    1
                      ..
2015-07-10 11:58:00    1
2015-07-10 11:58:30    1
2015-07-10 11:59:00    1
2015-07-10 11:59:30    1
2015-07-10 12:00:00    0
Length: 17281, dtype: int64

Using now the Roenneberg algorithm:

Roenneberg(data=raw.activity, plot=True)

Sleep transition probabilities#

Calculate Rest->Activity transition probability (kRA) and Rest->Activity transition probability (kAR)

raw.apply_filters(threshold=0)
kRA(data=raw.activity)
np.float64(0.038031890186843)

WASO#

circStudio implements a method to approximate the WASO (wake after sleep onset metric) based on the use of the Roenneberg algorithm (which detects consolitated periods of sleep) and a epoch-by-epoch rest/activity scoring algorithm (either Cole-Kripke, Sadeh or Scripps) to detect wake periods during a consolidated period of sleep. The functionality returns a tuple containing daily waso, as well as mean waso:

waso(data=raw.activity, frequency=raw.frequency, algo='Cole-Kripke', settings='mean') # WASO computed using the Sadeh Cole-Kripke algorithm
(2015-07-04     78.0
 2015-07-05    226.0
 2015-07-06    180.0
 2015-07-07    100.0
 2015-07-08    100.0
 2015-07-09    140.0
 dtype: float64,
 np.float64(137.33333333333334))
waso(data=raw.activity, frequency=raw.frequency, algo='Sadeh') # WASO computed using the Sadeh algorithm
(2015-07-04    104.0
 2015-07-05    262.0
 2015-07-06    214.0
 2015-07-07    114.0
 2015-07-08    114.0
 2015-07-09    134.0
 dtype: float64,
 np.float64(157.0))
waso(data=raw.activity, frequency=raw.frequency, algo='Scripps', settings='mean') # WASO computed using the Scripps algorithm
(2015-07-04    172.0
 2015-07-05    436.0
 2015-07-06    332.0
 2015-07-07    182.0
 2015-07-08    200.0
 2015-07-09    274.0
 dtype: float64,
 np.float64(266.0))

Sleep diaries#

Introduction and opening sample files#

circStudio keeps the ability to read sleep diaries in the form:

SubjectID

your_subject_id

Type

Start

End

Night/Nap/NoWear

YYYY-MM-DD HH:MM

YYYY-MM-DD HH:MM

Similar to pyActigraphy, it is possible to compute summary statistics and visualize the information contained in the sleep diary. In circStudio, functions to compute sleep efficiency (sleep_efficiency) and sleep onset latency (sleep_onset_latency) were implemented. These functions require both a sleep diary and an actigraphy recording containing an activity time series.

To illustrate, load a sample AWD file and the respective sleep diary:

fpath = os.path.join(os.path.dirname(cs.__file__), 'data/')

# Load actigraphy file
raw = cs.io.read_awd(fpath + 'example_01.AWD')

# Load diary
diary = raw.read_sleep_diary(fpath + 'example_01_sleepdiary.ods')

Accessing the dataframe and obtaining summary statistics#

You can access the sleep_diary either by calling the object or by printing it:

# Show sleep diary as a dataframe
raw.sleep_diary()
TYPE START END
0 NAP 1918-01-24 13:00:00 1918-01-24 13:45:00
1 NIGHT 1918-01-24 23:00:00 1918-01-25 07:00:00
2 NAP 1918-01-25 13:00:00 1918-01-25 13:35:00
3 NIGHT 1918-01-25 22:00:00 1918-01-26 07:30:00
4 NAP 1918-01-26 13:10:00 1918-01-26 14:00:00
5 NIGHT 1918-01-27 00:00:00 1918-01-27 07:30:00
6 NAP 1918-01-27 13:00:00 1918-01-27 13:45:00
7 NIGHT 1918-01-27 23:20:00 1918-01-28 05:00:00
8 NOWEAR 1918-01-28 12:00:00 1918-01-28 12:30:00
9 NAP 1918-01-28 16:00:00 1918-01-28 17:30:00
10 NIGHT 1918-01-28 22:30:00 1918-01-29 06:15:00
11 NAP 1918-01-29 13:15:00 1918-01-29 14:00:00
12 NIGHT 1918-01-29 23:20:00 1918-01-30 07:00:00
13 NAP 1918-01-30 13:00:00 1918-01-30 13:45:00
14 NIGHT 1918-01-30 23:15:00 1918-01-31 06:45:00
15 NAP 1918-01-31 13:15:00 1918-01-31 14:00:00
16 NIGHT 1918-01-31 23:15:00 1918-02-01 07:00:00
17 NAP 1918-02-01 13:00:00 1918-02-01 13:45:00
18 NOWEAR 1918-02-01 20:40:00 1918-02-01 21:33:00
19 NIGHT 1918-02-01 23:20:00 1918-02-02 08:00:00
20 NAP 1918-02-02 13:15:00 1918-02-02 14:15:00
21 NIGHT 1918-02-02 23:20:00 1918-02-03 07:45:00
# Print sleep diary as str
print(raw.sleep_diary)
      TYPE               START                 END
0      NAP 1918-01-24 13:00:00 1918-01-24 13:45:00
1    NIGHT 1918-01-24 23:00:00 1918-01-25 07:00:00
2      NAP 1918-01-25 13:00:00 1918-01-25 13:35:00
3    NIGHT 1918-01-25 22:00:00 1918-01-26 07:30:00
4      NAP 1918-01-26 13:10:00 1918-01-26 14:00:00
5    NIGHT 1918-01-27 00:00:00 1918-01-27 07:30:00
6      NAP 1918-01-27 13:00:00 1918-01-27 13:45:00
7    NIGHT 1918-01-27 23:20:00 1918-01-28 05:00:00
8   NOWEAR 1918-01-28 12:00:00 1918-01-28 12:30:00
9      NAP 1918-01-28 16:00:00 1918-01-28 17:30:00
10   NIGHT 1918-01-28 22:30:00 1918-01-29 06:15:00
11     NAP 1918-01-29 13:15:00 1918-01-29 14:00:00
12   NIGHT 1918-01-29 23:20:00 1918-01-30 07:00:00
13     NAP 1918-01-30 13:00:00 1918-01-30 13:45:00
14   NIGHT 1918-01-30 23:15:00 1918-01-31 06:45:00
15     NAP 1918-01-31 13:15:00 1918-01-31 14:00:00
16   NIGHT 1918-01-31 23:15:00 1918-02-01 07:00:00
17     NAP 1918-02-01 13:00:00 1918-02-01 13:45:00
18  NOWEAR 1918-02-01 20:40:00 1918-02-01 21:33:00
19   NIGHT 1918-02-01 23:20:00 1918-02-02 08:00:00
20     NAP 1918-02-02 13:15:00 1918-02-02 14:15:00
21   NIGHT 1918-02-02 23:20:00 1918-02-03 07:45:00

Obtain summary statistics:

raw.sleep_diary.summary()
count mean std min 25% 50% 75% max
TYPE
NAP 10 0 days 00:50:30 0 days 00:15:10.494371207 0 days 00:35:00 0 days 00:45:00 0 days 00:45:00 0 days 00:48:45 0 days 01:30:00
NIGHT 10 0 days 07:50:30 0 days 00:59:19.353873949 0 days 05:40:00 0 days 07:32:30 0 days 07:45:00 0 days 08:18:45 0 days 09:30:00
NOWEAR 2 0 days 00:41:30 0 days 00:16:15.807358037 0 days 00:30:00 0 days 00:35:45 0 days 00:41:30 0 days 00:47:15 0 days 00:53:00

Sleep efficiency and sleep onset latency from sleep diary and actigraphy#

circStudio provides functions that integrate data from both the sleep diary and actigraphy recording to estimate sleep efficiency and sleep onset latency. For these functions to return a meaningful result, it is essential that the algorithmically detected sleep onset occurs after the bedtime recorded in the diary. Therefore, participants must be instructed to accurately report the time they go to bed.

Sleep efficiency is computed as the ratio of average total sleep time (as estimated from actigraphy using the Roenneberg algorithm) to average total time in bed (as recorded in the diary):

raw.sleep_diary.sleep_efficiency(data=raw.activity)
0.998026415667198

Sleep onset latency is calculated as the time difference between the diary-reported bedtime and the algorithmically detected sleep onset (Roenneberg algorithm). Days in which the recorded bedtime occurs after, likely due to diary entry errors, the detected sleep onset are excluded from the analysis.

raw.sleep_diary.sleep_onset_latency(data=raw.activity)
(1918-01-27   0 days 00:45:00
 1918-01-28   0 days 01:06:00
 1918-01-29   0 days 00:07:00
 dtype: timedelta64[ns],
 Timedelta('0 days 00:39:20'))

As part of the open development philosophy of circStudio, feedback and suggestions for improving these functions are welcome!

Visualizing sleep diaries#

An interactive plot of the sleep diary can be easily generated using the plot() method:

raw.sleep_diary.plot(data=raw.activity)

Different sleep diary states have a distinct color associated with them, which could be changed:

raw.sleep_diary.state_colour
{'NAP': '#7bc043', 'NIGHT': '#d3d3d3', 'NOWEAR': '#ee4035'}

For example, suppose that you want to change the color associated with the ‘NIGHT’ from light grey to a dark purple. You can run the command:

# Change state color for 'NIGHT' events
raw.sleep_diary.state_colour['NIGHT'] = 'rgb(140,95,148)'

# Draw a new plot
raw.sleep_diary.plot(data=raw.activity)

Next steps#

In the following tutorial, we will explore the application of mathematical models of circadian rhythms to actigraphy data analysis.