Use case #02: Case, light sensitivity
Open and reproducible analysis of light exposure and visual experience data (Advanced)
1 Preface
A single patient reports worse sleep after bright days. Consequently, personal light exposure and activity was captured with wearable devices for several weeks. Additionally, morning and evening questionnaires were used to capture sleep, performance, and well-being self evaluations. In this tutorial, we focus on the ActLumus device worn on the chest and the self evaluations.
The tutorial focuses on
import and merging of diary data with light data
calculation of metrics based on sleep-wake cycles instead of calendar days
relating exposure metrics to self assessments of consecutive sleep, performance, and wellbeing
assessing compliance to Brown et al. (2022)1 recommendations
2 How this page works
This document runs a self‑contained version of R completely in your browser2. No setup or installation is required.
As soon as as webR has finished loading in the background, the Run Code button on code cells will become available. You can change the code and execute it either by clicking Run Code or by hitting CTRL+Enter (Windows) or CMD+Enter (MacOS). Some code lines have commments below. These indicate code-cell line numbers
You can execute the same script in a traditional R environment, but this browser‑based approach has several advantages:
- You can get started in seconds, avoiding configuration differences across machines and getting to the interesting part quickly.
- Unlike a static tutorial, you can modify code to test the effects of different arguments and functions and receive immediate feedback.
- Because everything runs locally in your browser, there are no additional server‑side security risks and minimal network‑related slowdowns.
This approach also comes with a few drawbacks:
- R and all required packages are loaded every time you load the page. If you close the page or navigate elsewhere in the same tab, webR must be re‑initialized and your session state is lost.
- Certain functions do not behave as they would in a traditional runtime. For example, saving plot images directly to your local machine (e.g., with
ggsave()) is not supported. If you need these capabilities, run the static version of the script on your local R installation. In most cases, however, you can interact with the code as you would locally. Known cases where webR does not produce the desired output are marked specifically in this script and static images of outputs are displayed. - After running a command for more than 30 seconds, each code cell will go into a time out. If that happens on your browser, try reducing the complexity of commands or choose the local installation.
- Depending on your browser and system settings, functionality or output may differ. Common differences include default fonts and occasional plot background colors. If you encounter an issue, please describe it in detail—along with your system information (hardware, OS, browser)—in the issues section of the GitHub repository. This helps us to improve your experience moving forward.
3 Setup
We start by loading the necessary packages.
4 Import
We require both light and log data to be loaded into R before we are able to merge them.
4.1 Light
Light data were captured with ActLumus devices. The exported data files sit in data/case_light_sensitivity/.
- As there is only one participant, we set a manual
Id
This dataset spans about three weeks. We know that the diaries only start on 24 September 2025 - thus we remove prior data. We also test for irregular data or gaps in this trimmed dataset.
If we have a hard-set beginning or end time,
filter_Date()orfilter_Datetime()provide convenient cut-off functionality.As we are dealing with a single participant, we don’t require grouping by
Id.
We can continue, seeing as there are no issues with the regular time series. We start by plotting the full series
Reduce the interval by averaging over 15 minute periods
Easily change the label format. Look at the help page of
?strptimefor the available syntax.
We can see the light intensity varies greatly. Let’s look at the extremes.
- Adding, and grouping by, the date gives us 20 groups, each covering one calender day
4-7. Select the three groups with the highest duration above 250 lx mel EDI
Looking at the first half of 02 October 2025, we can already see that this likely a period of non-wear, or sleep - this, we have to come back to later.
- Select the three groups with the lowest duration above 250 lx mel EDI
4.2 State data
We need information about sleep times. These can be found in the diaries.
The morning diary is all about last nights sleep, how long the needed to fall asleep, wake-ups during the night, when they got up, how rested they feel and about sleep medication. See Figure 2 for an excerpt.
The evening diary is about the prior day, how well and they feel and about their performance during the day, alcohol consumation, when they got to bed, daytime naps and self-assessments about low activity and bright days. See Figure 3 for an excerpt.
In the next step we import these data and combine them to a large table. While none of these steps contain LightLogR functions, it is a crucial step to set the data up right for merging - particularly in that we assign the morning diaries to the prior day, and also add information about performance and fatigue on a given day to the prior day.
7-8. Store the date information of the evening diaries as is, but the morning diary will be set one day back.
- By joining these two diaries by their (adjusted) date, morning diary information is related to the prior day evening diary.
22-23. We add the next days performance and fatigue level to all days.
The following table gives a short overview of the states. We will later get to whether a higher rating is better or worse for the individual items.
5 Merging sleep data
Now that the diaries are prepared, we can extract sleep data from the states. The goal for the next code cell is to end with a statechange variable, that has a datetime for every timepoint when a new state occurs.
Calculate sleep timing by adding bedtime and sleepdelay
Reduce the dataset to only sleep and wake times
We transform the data from wide to long format (i.e., consecutive wake-sleep statesd)
6-8. We add an initial wake for the first day
While we are at it, we can also create states according to the Brown et al. (2022) recommendations, which distinguish daytime (wake), evening (pre-sleep), and nighttime (sleep) hours. The LightLogR function sleep_int2Brown() makes this conversion a breeze. By default, the evening phase is set to a length of 3 hours.
- Convert the
statechangesintointervals, i.e. from datetimes when a state changes to intervals where a state is consideredactive.
4-6. Add a variable (State.Brown by default) containing the Brown et al. (2022) phases. This function automatically extends the rows to include the pre-sleep phase.
Next, we combine the light dataset with the sleep data.
- Combine the two datasets
4-5. Get the start and end datetimes for each state from the Interval column
Assures that the times from the
sleep_datadataset are matched up with the light data, even though the light data uses theCentral European Time, whereas the sleep states use the defaultUTCtime zone.Remove times that don’t relate to any state
6 Brown et al. (2022) recommendations
Let’s visualize the newly extended dataset with regards to the Brown et al. (2022) states.
3&11. Grouping by week so that the plot is automatically sectioned into three rows
8-9. The expansion of factor levels is purely so that the order in the legend is automatically kept in sensible order.
- Setting the geom to
ribbonenables a filled band with the lower band set at 0.
15-17. Coloring and filling by the Brown states will only look good, if each section is kept individually. consecutive_id() ensures just this.
18-20. Manually setting the datetime limits ensures that each row is kept at 7 days
23-24. We add a check whether the recommendations were fulfilled and color by it.
- Adjusting the height of the state bands ensures readability of the plot despite other color aspects.
32-33. As the week grouping is just for row-setting, we are not interested in the strip and thus remove it.
We can further summarize the compliance to the recommendations in a table.
7 Calculate metrics
In this section, we calculate some light metrics that we can correlate with sleep quality. We calculate these metrics not by the 24-hour day, but rather by wake-sleep rhythms.
7.1 Sleep-wake cycles
Create a consecutive numbering per sleep-wake cycle. Beware if states are missing in other analyses!
Format the sleep-wake cycles nicely
Let’s look at the sleep times as a doubleplot.
Create a logical as the main variable that is
TRUEwhen the state issleepShowing the next day in the doubleplot
5-6. Remove the default fill settings to provide your own
Again, we can look at the data as a table.
Based on these results, we will remove cycle 01 and cycle 20, as they have only partial data.
7.2 Light exposure metrics
For the last step, we will calculate two relevant light exposure metrics, time above 250 lx mel EDI and dose. and add this information to the original states.
This step relates the sleep cycles to the date where the wake-state starts.
Adding the self-assessments to our summaries. The data (4) provides the right assignment.
We bring this information together into a comprehensive table. Variables that have no meaningful variance are removed.
8 Correlations
To visualize simple correlations between the self-assessments and the light exposure metrics we create a helper function:
This allows us to easily look at the various parameters
Try these for yourself by copying them into the active code cell. Could there be a meaningful relationship?
9 Conclusion
Congratulations! You have finished this section of the advanced course. If you go back to the homepage, you can select one of the other use cases.
Footnotes
https://doi.org/10.1371/journal.pbio.3001571↩︎
If you want to know more about
webRand theQuarto-liveextension that powers this document, you can visit the documentation page↩︎



