Back to Article
Personal light exposure: Data preparation
Download Source

Personal light exposure: Data preparation

Individual, behavioural, and environmental determinants of personal light exposure in daily life: a multi-country wearable and experience-sampling study

Last modified:

April 13, 2026

Preface

This supplement documents the import and preprocessing of personal light exposure data for the MeLiDos field study analysis. The goal is to derive at three sets of data:

  1. An unaggregated set of light exposure data, both for the eye-level and chest-level wearing position where
  • Values > 120 000 lx are removed (set to NA)
  • non-wear periods, that are not also sleep periods, are removed
  1. A further processed set (from 1.) where
  • hours with less than 50% data availability are removed
  • days with less than 80% data availability (after the previous step) are removed
  1. A further processed set (from 2.) where personal light exposure metrics are calculated
  1. per participant:
  • dynamics-based:
    • IS: interdaily stability
    • IV: intradaily variability
  1. per participant-day:
  • level-based:
    • Mean: geometric mean of melanopic EDI (lx)
    • M10mean: geometric mean of melanopic EDI during 10 brightest hours of the day (lx)
    • L5mean: geometric mean of melanopic EDI during 5 darkest hours of the day (lx)
  • duration-based:
    • TAT1000: time above 1000 lx melanopic EDI (minutes)
    • TAT250: time above 250 lx melanopic EDI (during wake) (hours)
    • TBT10: time below 10 lx melanopic EDI (during evening, i.e. 3 hours before sleep) (hours)
    • TBT1: time below 1 lx melanopic EDI (during sleep) (hours)
    • PAT250: longest period above 250lx melanopic EDI (minutes)
  • timing-based:
    • FLIT250: first time above 250 lx melanopic EDI (HH:MM)
    • LLIT250: last time above 250 lx melanopic EDI (HH:MM)
    • M10: midpoint of brightest 10 hours (HH:MM)
    • L5: midpoint of darkest 5 hours (HH:MM)
  • exposure-history-based:
    • Dose: melanopic EDI dose (lx·h)
  • spectrum-based:
    • MDER: melanopic daylight efficacy ratio
  1. per participant-hour:
  • level-based:
    • Mean: geometric mean of melanopic EDI (lx)

Setup

In [1]:
library(tidyverse)
Warning: package 'ggplot2' was built under R version 4.5.2
Warning: package 'tibble' was built under R version 4.5.2
Warning: package 'tidyr' was built under R version 4.5.2
Warning: package 'readr' was built under R version 4.5.2
Warning: package 'purrr' was built under R version 4.5.2
Warning: package 'dplyr' was built under R version 4.5.2
Warning: package 'lubridate' was built under R version 4.5.2
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.2     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(LightLogR)
Warning: package 'LightLogR' was built under R version 4.5.2
library(melidosData)
library(cowplot)

Attaching package: 'cowplot'

The following object is masked from 'package:lubridate':

    stamp

Importing data

Light

In the first step, we import the unaggregated light eposure data for the glasses and the chest. These will be loaded from the GitHub project page. These data have been imported already, trimmed by trial dates, checked for irregular data and gaps. All of them run on a 10 second interval.

In [2]:
light_glasses <- load_data("light_glasses")
These datasets are comparatively large (~50MB per site). Download may take a while.
loading modality: light_glasses ■■■■                              11% |  ETA: 2…
loading modality: light_glasses ■■■■■■■■                          22% |  ETA: 4…
loading modality: light_glasses ■■■■■■■■■■■                       33% |  ETA:  …
loading modality: light_glasses ■■■■■■■■■■■■■■                    44% |  ETA: 3…
loading modality: light_glasses ■■■■■■■■■■■■■■■■■■                56% |  ETA: 2…
loading modality: light_glasses ■■■■■■■■■■■■■■■■■■■■■             67% |  ETA: 1…
loading modality: light_glasses ■■■■■■■■■■■■■■■■■■■■■■■■          78% |  ETA: 1…
loading modality: light_glasses ■■■■■■■■■■■■■■■■■■■■■■■■■■■■      89% |  ETA:  …
light_chest <- load_data("light_chest")
Remove site MPI, as there were no chest-worn devices, and a different type of wrist-worn devices
These datasets are comparatively large (~50MB per site). Download may take a while.
loading modality: light_chest ■■■■■                             12% |  ETA:  2m
loading modality: light_chest ■■■■■■■■■                         25% |  ETA:  1m
loading modality: light_chest ■■■■■■■■■■■■                      38% |  ETA: 49s
loading modality: light_chest ■■■■■■■■■■■■■■■■■■■■              62% |  ETA: 23s
loading modality: light_chest ■■■■■■■■■■■■■■■■■■■■■■■           75% |  ETA: 14s

Sleep

The sleep data comes from a morning sleep diary. They contain both sleep and wake times.

In [3]:
sleepdiary <- load_data("sleepdiaries")
loading modality: sleepdiary ■■■■■■■■■■■                       33% |  ETA:  2s
In [4]:
# sleepdiary <- sleepdiary |> flatten_data()
# trial_times <- load_data("trial_times") |> flatten_data()
In [5]:
sleep_tester <- function(sleepdiary, trial_times, filter) {
  trial_times <- trial_times |> filter(site == filter) |> drop_na(start, end)
  
  sleepdiary |> 
  group_by(Id) |> 
  filter(site == filter) |> 
  ggplot(aes(y = Id)) +
  geom_linerange(aes(xmin = sleepprep, xmax = wake),
                 col = "red", linewidth = 2) +
  geom_point(data = trial_times, aes(x = start), col = "green3", size = 4) +
  geom_point(data = trial_times, aes(x = end), col = "green3", size = 4) +
  facet_wrap(~Id, ncol = 1, scales = "free") +
  scale_x_datetime(date_breaks = "24 hours",
                   date_labels = "%H:%M %D",
                   limits = LightLogR::Datetime_limits,
                   )+
  labs(y = NULL) +
  # theme_cowplot() +
  theme_sub_strip(background = element_blank(),
                  text = element_blank()) +
  coord_cartesian(clip = "off")
}
In [6]:
# sleep_tester(sleepdiary, trial_times, "KNUST")

Wear log

The non-wear data comes from an app-based wear log that participants filled in whenever they removed or put on the device(s).

In [7]:
wearlog <- load_data("wearlog")
loading modality: wearlog ■■■■■■■■■■■■■■■■■■■■■■■■■■■■      89% |  ETA:  0s
# wearlog <- wearlog |> flatten_data()
In [8]:
wearlog_tester <- function(wearlog, trial_times, filter) {
  trial_times <- trial_times |> filter(site == filter) |> drop_na(start, end)
  wearlog |> 
  group_by(Id) |> 
  filter(site == filter) |> 
  ggplot(aes(y = Id)) +
  geom_linerange(aes(xmin = start, xmax = end, colour = state), linewidth = 2) +
  geom_point(data = trial_times, aes(x = start), col = "green3", size = 4) +
  geom_point(data = trial_times, aes(x = end), col = "green3", size = 4) +
  facet_wrap(~Id, ncol = 1, scales = "free") +
  scale_x_datetime(date_breaks = "24 hours",
                   date_labels = "%H:%M %D",
                   limits = LightLogR::Datetime_limits,
                   )+
  labs(y = NULL) +
  # theme_cowplot() +
  theme_sub_strip(background = element_blank(),
                  text = element_blank()) +
  coord_cartesian(clip = "off")
}
In [9]:
# wearlog_tester(wearlog, trial_times, "KNUST")

Combine light exposure data with log and diary data

For the sleepdiary, a selection of sleep and wake times will be added. For the wearlog, the start and end times of a removal, as well as the type (state) of removal will be used.

Preparation

In [10]:

sleepdiary_adj <-
  sleepdiary |>
  map(\(x) x |>
        select(Id, sleepprep, wake) |>
        group_by(Id) |>
        pivot_longer(-Id, names_to = "sleep", values_to = "Datetime") |>
        sc2interval(Statechange.colname = sleep, starting.state = "wake") |>
        sleep_int2Brown(sleep.state = "sleepprep", Brown.day = "wake", 
                        Brown.evening = "pre-sleep", Brown.night = "sleep") |>
        mutate(sleep = case_when(is.na(sleep) & State.Brown == "pre-sleep" ~ "wake",
                                 .default = sleep))
  )
Adding missing grouping variables: `Id`
Adding missing grouping variables: `Id`
Adding missing grouping variables: `Id`
Adding missing grouping variables: `Id`
Adding missing grouping variables: `Id`
Adding missing grouping variables: `Id`
Adding missing grouping variables: `Id`
Adding missing grouping variables: `Id`
Adding missing grouping variables: `Id`

wearlog_adj <-
  wearlog |>  
    map(\(x) x |> select(Id, start, end, wear = state))

Combination

In [11]:
light_chest_expanded <- 
light_chest |> 
  imap(\(x, idx) x |>
    select(Id, Datetime, MEDI, LIGHT, is.implicit) |>
    add_states(sleepdiary_adj[[idx]], start = Interval, end = Interval) |>
    add_states(wearlog_adj[[idx]]) |> 
    aggregate_Datetime("1 min", type = "floor")
  )

light_glasses_expanded <- 
light_glasses |> 
  imap(\(x, idx) x |> 
    select(Id, Datetime, MEDI, LIGHT, is.implicit) |> 
    add_states(sleepdiary_adj[[idx]], start = Interval, end = Interval) |> 
    add_states(wearlog_adj[[idx]]) |> 
    aggregate_Datetime("1 min", type = "floor")
  )

Preprocessing step 1

In this step, we will remove non-wear instances that are neither marked as sleep, nor fall into a sleep window (sleepdiary). We will also add photoperiod, and remove instances ≥ 1.2*10^5 lx melanopic EDI.

In [12]:
light_chest_expanded <- 
light_chest_expanded |> 
  imap(\(x, idx) x |> 
    mutate(MEDI = replace_when(MEDI, 
                               wear == "off" & (State.Brown != "sleep" | is.na(State.Brown)) ~ NA,
                               MEDI >= 120000 ~ NA
                               )) |> 
      add_photoperiod(melidos_coordinates[[idx]])
  )

light_glasses_expanded <- 
light_glasses_expanded |> 
  imap(\(x, idx) x |> 
    mutate(MEDI = replace_when(MEDI, 
                               wear == "off" & (State.Brown != "sleep" | is.na(State.Brown)) ~ NA,
                               MEDI >= 120000 ~ NA
                               )) |> 
      add_photoperiod(melidos_coordinates[[idx]])
  )

light_chest_processed1 <- 
  structure(light_chest_expanded, class = "melidos_data") |> flatten_data() |> group_by(site, Id)

light_glasses_processed1 <- 
  structure(light_glasses_expanded, class = "melidos_data") |> flatten_data() |> group_by(site, Id)

This marks our first dataset

In [13]:
save(light_chest_processed1, file = "data/preprocessed_chest_1.RData")
save(light_glasses_processed1, file = "data/preprocessed_glasses_1.RData")

Preprocessing step 2

Here we further process the data: - hours with less than 50% data availability are removed - days with less than 80% data availability (after the previous step) are removed

In [14]:
light_chest_processed2 <- 
  light_chest_processed1 |> 
  cut_Datetime(unit = "1 hour", group_by = TRUE, type = "floor") |> 
  remove_partial_data(MEDI, threshold.missing = 0.5) |> 
  ungroup(Datetime.rounded) |> 
  select(-Datetime.rounded) |> 
  add_Date_col(group.by = TRUE) |> 
  gap_handler(full.days = TRUE) |> 
  remove_partial_data(MEDI, threshold.missing = 0.2) |> 
  ungroup(Date)

light_glasses_processed2 <- 
  light_glasses_processed1 |> 
  cut_Datetime(unit = "1 hour", group_by = TRUE, type = "floor") |> 
  remove_partial_data(MEDI, threshold.missing = 0.5) |> 
  ungroup(Datetime.rounded) |> 
  select(-Datetime.rounded) |> 
  add_Date_col(group.by = TRUE) |> 
  gap_handler(full.days = TRUE) |> 
  remove_partial_data(MEDI, threshold.missing = 0.2) |> 
  ungroup(Date)

This marks our second dataset

In [15]:
save(light_chest_processed2, file = "data/preprocessed_chest_2.RData")
save(light_glasses_processed2, file = "data/preprocessed_glasses_2.RData")

Preprocessing step 3

Per participant metric

  • dynamics-based:
    • IS: interdaily stability
    • IV: intradaily variability
In [16]:
metric_chest_participant <- 
  light_chest_processed2 |> 
  summarize(
    interdaily_stability(MEDI, Datetime, na.rm = TRUE, as.df = TRUE),
    intradaily_variability(MEDI, Datetime, na.rm = TRUE, as.df = TRUE),
  )
Warning: There were 240 warnings in `summarize()`.
The first warning was:
ℹ In argument: `interdaily_stability(MEDI, Datetime, na.rm = TRUE, as.df =
  TRUE)`.
ℹ In group 1: `site = "BAUA"`, `Id = "BAUA_S001"`.
Caused by warning in `interdaily_stability()`:
! Data contains some hours with only missing values
These hours contain only missing values: 
[1] "2025-06-12 15:00:00 UTC" "2025-06-12 16:00:00 UTC"
[3] "2025-06-13 15:00:00 UTC" "2025-06-13 16:00:00 UTC"
[5] "2025-06-14 15:00:00 UTC" "2025-06-14 16:00:00 UTC"
ℹ Run `dplyr::last_dplyr_warnings()` to see the 239 remaining warnings.
`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by site and Id.
ℹ Output is grouped by site.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(site, Id))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.
metric_glasses_participant <- 
  light_glasses_processed2 |> 
  summarize(
    interdaily_stability(MEDI, Datetime, na.rm = TRUE, as.df = TRUE),
    intradaily_variability(MEDI, Datetime, na.rm = TRUE, as.df = TRUE),
  )
Warning: There were 224 warnings in `summarize()`.
The first warning was:
ℹ In argument: `interdaily_stability(MEDI, Datetime, na.rm = TRUE, as.df =
  TRUE)`.
ℹ In group 1: `site = "BAUA"`, `Id = "BAUA_S001"`.
Caused by warning in `interdaily_stability()`:
! Data contains some hours with only missing values
These hours contain only missing values: 
[1] "2025-06-12 15:00:00 UTC" "2025-06-12 16:00:00 UTC"
[3] "2025-06-13 15:00:00 UTC" "2025-06-13 16:00:00 UTC"
[5] "2025-06-14 15:00:00 UTC" "2025-06-14 16:00:00 UTC"
ℹ Run `dplyr::last_dplyr_warnings()` to see the 223 remaining warnings.
`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by site and Id.
ℹ Output is grouped by site.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(site, Id))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.

Per participant-day metric

  • level-based:
    • Mean: geometric mean of melanopic EDI (lx)
    • M10mean: geometric mean of melanopic EDI during 10 brightest hours of the day (lx)
    • L5mean: geometric mean of melanopic EDI during 5 darkest hours of the day (lx)
  • duration-based:
    • TAT1000: time above 1000 lx melanopic EDI (minutes)
    • TAT250: time above 250 lx melanopic EDI (during wake) (hours)
    • TBT10: time below 10 lx melanopic EDI (during evening, i.e. 3 hours before sleep) (hours)
    • TBT1: time below 1 lx melanopic EDI (during sleep) (hours)
    • PAT250: longest period above 250lx melanopic EDI (minutes)
  • timing-based:
    • FLIT250: first time above 250 lx melanopic EDI (HH:MM)
    • LLIT250: last time above 250 lx melanopic EDI (HH:MM)
    • M10: midpoint of brightest 10 hours (HH:MM)
    • L10: midpoint of darkest 10 hours (HH:MM)
  • exposure-history-based:
    • Dose: melanopic EDI dose (lx·h)
  • spectrum-based:
    • MDER: melanopic daylight efficacy ratio
In [17]:
metric_chest_participantday <-
  light_chest_processed2 |> 
  group_by(Date, .add = TRUE) |> 
  summarize(
    Mean = MEDI |> log_zero_inflated() |> mean(na.rm = TRUE) |> exp_zero_inflated(),
    bright_dark_period(log_zero_inflated(MEDI), Datetime, "brightest", "10 hours", na.rm = TRUE, as.df = TRUE),
    bright_dark_period(log_zero_inflated(MEDI), Datetime, "darkest", "10 hours", loop = TRUE, na.rm = TRUE, as.df = TRUE),
    duration_above_threshold(MEDI, Datetime, "above", 1000, na.rm = TRUE, as.df = TRUE),
    duration_above_threshold(MEDI, Datetime, "above", 250, na.rm = TRUE, as.df = TRUE),
    period_above_threshold(MEDI, Datetime, "above", 250, na.rm = TRUE, as.df = TRUE),
    timing_above_threshold(MEDI, Datetime, "above", 250, na.rm = TRUE, as.df = TRUE),
    dose(MEDI, Datetime, na.rm = TRUE, as.df = TRUE),
    MDER = mean(MEDI / LIGHT, na.rm = TRUE),
    .groups = "drop_last"
  ) |> 
  mutate(MDER = ifelse(abs(MDER) == Inf, NA, MDER),
         across(c(brightest_10h_mean, darkest_10h_mean), exp_zero_inflated)
  )
Warning: There were 4 warnings in `summarize()`.
The first warning was:
ℹ In argument: `bright_dark_period(...)`.
ℹ In group 141: `site = "FUSPCEU"`, `Id = "FUSPCEU_S007"`, `Date = 2024-10-27`.
Caused by warning in `bright_dark_period()`:
! `Time.vector` is not regularly spaced. Calculated results may be incorrect!
ℹ Run `dplyr::last_dplyr_warnings()` to see the 3 remaining warnings.
metric_glasses_participantday <-
  light_glasses_processed2 |> 
  group_by(Date, .add = TRUE) |> 
  summarize(
    Mean = MEDI |> log_zero_inflated() |> mean(na.rm = TRUE) |> exp_zero_inflated(),
    bright_dark_period(log_zero_inflated(MEDI), Datetime, "brightest", "10 hours", na.rm = TRUE, as.df = TRUE),
    bright_dark_period(log_zero_inflated(MEDI), Datetime, "darkest", "10 hours", loop = TRUE, na.rm = TRUE, as.df = TRUE),
    duration_above_threshold(MEDI, Datetime, "above", 1000, na.rm = TRUE, as.df = TRUE),
    duration_above_threshold(MEDI, Datetime, "above", 250, na.rm = TRUE, as.df = TRUE),
    period_above_threshold(MEDI, Datetime, "above", 250, na.rm = TRUE, as.df = TRUE),
    timing_above_threshold(MEDI, Datetime, "above", 250, na.rm = TRUE, as.df = TRUE),
    dose(MEDI, Datetime, na.rm = TRUE, as.df = TRUE),
    MDER = mean(MEDI / LIGHT, na.rm = TRUE),
    .groups = "drop_last"
  ) |> 
  mutate(MDER = ifelse(abs(MDER) == Inf, NA, MDER),
         across(c(brightest_10h_mean, darkest_10h_mean), exp_zero_inflated)
  )
Warning: There were 8 warnings in `summarize()`.
The first warning was:
ℹ In argument: `bright_dark_period(...)`.
ℹ In group 134: `site = "FUSPCEU"`, `Id = "FUSPCEU_S007"`, `Date = 2024-10-27`.
Caused by warning in `bright_dark_period()`:
! `Time.vector` is not regularly spaced. Calculated results may be incorrect!
ℹ Run `dplyr::last_dplyr_warnings()` to see the 7 remaining warnings.
In [18]:
metric_chest_participantday2 <- 
  light_chest_processed2 |> 
  group_by(Date, State.Brown, .add = TRUE) |> 
  summarize(
    duration_above_threshold(MEDI, Datetime, "above", 250, na.rm = TRUE, as.df = TRUE),
    duration_above_threshold(MEDI, Datetime, "below", 10, na.rm = TRUE, as.df = TRUE),
    duration_above_threshold(MEDI, Datetime, "below", 1, na.rm = TRUE, as.df = TRUE),
    .groups = "drop_last"
  ) |> 
  pivot_longer(-c(site:State.Brown), names_to = "metric") |> 
  filter_out(
    !(State.Brown == "wake" & metric == "duration_above_250"),
    !(State.Brown == "pre-sleep" & metric == "duration_below_10"),
    !(State.Brown == "sleep" & metric == "duration_below_1"),
    ) |>
  drop_na(State.Brown) |> 
  unite(metric, metric, State.Brown) |> 
  pivot_wider(id_cols = c(site, Id, Date), values_from = value, names_from = metric)

metric_glasses_participantday2 <- 
  light_glasses_processed2 |> 
  group_by(Date, State.Brown, .add = TRUE) |> 
  summarize(
    duration_above_threshold(MEDI, Datetime, "above", 250, na.rm = TRUE, as.df = TRUE),
    duration_above_threshold(MEDI, Datetime, "below", 10, na.rm = TRUE, as.df = TRUE),
    duration_above_threshold(MEDI, Datetime, "below", 1, na.rm = TRUE, as.df = TRUE),
    .groups = "drop_last"
  ) |> 
  pivot_longer(-c(site:State.Brown), names_to = "metric") |> 
  filter_out(
    !(State.Brown == "wake" & metric == "duration_above_250"),
    !(State.Brown == "pre-sleep" & metric == "duration_below_10"),
    !(State.Brown == "sleep" & metric == "duration_below_1"),
    ) |>
  drop_na(State.Brown) |> 
  unite(metric, metric, State.Brown) |> 
  pivot_wider(id_cols = c(site, Id, Date), values_from = value, names_from = metric)
In [19]:
metric_chest_participantday <-
  metric_chest_participantday |> 
  left_join(metric_chest_participantday2, 
            by = c("site", "Id", "Date"))

metric_glasses_participantday <-
  metric_glasses_participantday |> 
  left_join(metric_glasses_participantday2, 
            by = c("site", "Id", "Date"))

Per participant-hour metric

  • level-based:
    • Mean: geometric mean of melanopic EDI (lx)
In [20]:
metric_chest_participanthour <- 
  light_chest_processed2 |> 
  aggregate_Datetime(
    "1 hour",
    type = "floor",
    numeric.handler = \(x) x |> mean(na.rm = TRUE),
    geo.MEDI = MEDI |> log_zero_inflated() |> mean(na.rm = TRUE) |> exp_zero_inflated()
  )|>
  add_Date_col(group.by = TRUE) |> 
  mutate(static = all(MEDI == MEDI[1])) |> 
  filter_out(static) |> 
  select(-static) |> 
  ungroup(Date)

metric_glasses_participanthour <- 
  light_glasses_processed2 |> 
  aggregate_Datetime(
    "30 mins",
    type = "floor",
    numeric.handler = \(x) x |> mean(na.rm = TRUE),
    geo.MEDI = MEDI |> log_zero_inflated() |> mean(na.rm = TRUE) |> exp_zero_inflated()
  )|>
  add_Date_col(group.by = TRUE) |> 
  mutate(static = all(MEDI == MEDI[1])) |> 
  filter_out(static) |> 
  select(-static) |> 
  ungroup(Date)

This marks our third dataset

In [21]:
save(metric_chest_participanthour, 
     metric_chest_participant, 
     metric_chest_participantday, 
     file = "data/metrics_separate_chest.RData")
save(metric_glasses_participanthour, 
     metric_glasses_participant, 
     metric_glasses_participantday, 
     file = "data/metrics_separate_glasses.RData")

Session info

In [22]:
sessionInfo()
R version 4.5.0 (2025-04-11)
Platform: aarch64-apple-darwin20
Running under: macOS 26.3.1

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: Europe/Berlin
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] cowplot_1.2.0     melidosData_1.0.3 LightLogR_0.10.0  lubridate_1.9.5  
 [5] forcats_1.0.1     stringr_1.6.0     dplyr_1.2.1       purrr_1.2.2      
 [9] readr_2.2.0       tidyr_1.3.2       tibble_3.3.1      ggplot2_4.0.2    
[13] tidyverse_2.0.0  

loaded via a namespace (and not attached):
 [1] generics_0.1.4     renv_1.1.4         class_7.3-23       KernSmooth_2.23-26
 [5] stringi_1.8.7      hms_1.1.4          digest_0.6.39      magrittr_2.0.5    
 [9] evaluate_1.0.5     grid_4.5.0         timechange_0.4.0   RColorBrewer_1.1-3
[13] fastmap_1.2.0      jsonlite_2.0.0     slider_0.3.3       e1071_1.7-17      
[17] DBI_1.3.0          scales_1.4.0       cli_3.6.6          rlang_1.2.0       
[21] units_1.0-1        withr_3.0.2        yaml_2.3.12        tools_4.5.0       
[25] tzdb_0.5.0         vctrs_0.7.3        R6_2.6.1           proxy_0.4-29      
[29] lifecycle_1.0.5    classInt_0.4-11    htmlwidgets_1.6.4  warp_0.2.3        
[33] pkgconfig_2.0.3    pillar_1.11.1      gtable_0.3.6       Rcpp_1.1.1-1      
[37] glue_1.8.0         sf_1.1-0           xfun_0.57          tidyselect_1.2.1  
[41] rstudioapi_0.18.0  knitr_1.51         farver_2.1.2       htmltools_0.5.9   
[45] rmarkdown_2.31     suntools_1.1.0     compiler_4.5.0     S7_0.2.1-1