Back to Article
Research Question 2
Download Source

Is there a relationship between personal light exposure and participants’ behaviour and environment?

Author

Johannes Zauner

Last modified:

April 15, 2026

Preface

This document contains the analysis for Research Question 2, as defined in the preregistration.

Research Question 1:
Is there a relationship between personal light exposure and participants’ behaviour and environment?

H3: There is a significant relationship between hourly self-reported light exposure categories and hourly geometric mean of melanopic EDI.

H4: There is a significant relationship between hourly self-reported activities and hourly geometric mean of melanopic EDI.

H5: There is a significant relationship between LEBA-factors and pre-selected personal light exposure metrics.

H6: There is a significant relationship between the day type, daily exercise, and sleep with personal light exposure metrics

H7: There is a ceiling effect of (absolute) latitude and photoperiod with level, duration, and exposure-history-based metrics

Setup

In this section, we load in the preprocessed and prepared data, site data, and load required packages.

In [1]:
Code cell - Setup
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
Code cell - Setup
library(melidosData)
library(LightLogR)
Warning: package 'LightLogR' was built under R version 4.5.2
Code cell - Setup
library(mgcv)
Loading required package: nlme

Attaching package: 'nlme'

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

    collapse

This is mgcv 1.9-4. For overview type '?mgcv'.
Code cell - Setup
library(lme4)
Warning: package 'lme4' was built under R version 4.5.2
Loading required package: Matrix

Attaching package: 'Matrix'

The following objects are masked from 'package:tidyr':

    expand, pack, unpack


Attaching package: 'lme4'

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

    lmList
Code cell - Setup
library(performance)
Warning: package 'performance' was built under R version 4.5.2
Code cell - Setup
library(cowplot)

Attaching package: 'cowplot'

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

    stamp
Code cell - Setup
library(ggridges)
library(ggtext)
library(sjPlot)

Attaching package: 'sjPlot'

The following objects are masked from 'package:cowplot':

    plot_grid, save_plot

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

    set_theme
Code cell - Setup
library(broom)
Warning: package 'broom' was built under R version 4.5.2
Code cell - Setup
library(itsadug)
Warning: package 'itsadug' was built under R version 4.5.2
Loading required package: plotfunctions
Warning: package 'plotfunctions' was built under R version 4.5.2

Attaching package: 'plotfunctions'

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

    alpha

Loaded package itsadug 2.5 (see 'help("itsadug")' ).
Code cell - Setup
library(gratia)
Warning: package 'gratia' was built under R version 4.5.2

Attaching package: 'gratia'

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

    dispersion

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

    boundary
Code cell - Setup
library(emmeans)
Warning: package 'emmeans' was built under R version 4.5.2
Welcome to emmeans.
Caution: You lose important information if you filter this package's results.
See '? untidy'
Code cell - Setup
library(patchwork)

Attaching package: 'patchwork'

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

    align_plots
Code cell - Setup
library(rlang)
Warning: package 'rlang' was built under R version 4.5.2

Attaching package: 'rlang'

The following objects are masked from 'package:purrr':

    flatten, flatten_chr, flatten_dbl, flatten_int, flatten_lgl,
    flatten_raw, invoke, splice
Code cell - Setup
library(broom.mixed)
Warning: package 'broom.mixed' was built under R version 4.5.2
Code cell - Setup
library(gt)
Warning: package 'gt' was built under R version 4.5.2

Attaching package: 'gt'

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

    as_gtable
Code cell - Setup
library(performance)
library(glmmTMB)
Warning: package 'glmmTMB' was built under R version 4.5.2
Warning in check_dep_version(dep_pkg = "TMB"): package version mismatch: 
glmmTMB was built with TMB package version 1.9.19
Current TMB package version is 1.9.21
Please re-install glmmTMB from source or restore original 'TMB' package (see '?reinstalling' for more information)
Code cell - Setup
library(gghighlight)
library(gtsummary)
Warning: package 'gtsummary' was built under R version 4.5.2
Code cell - Setup
source("scripts/fitting.R")
source("scripts/summaries.R")
source("scripts/RQ2_specific.R")
In [2]:
Code
load("data/metrics_glasses.RData")
load("data/preprocessed_glasses_2.RData")
metrics <- metrics_glasses
hourly_data <-  light_glasses_processed2
H3 <- "Hypothesis: There is a significant relationship between hourly self-reported light exposure categories and hourly geometric mean of melanopic EDI."
H4 <- "Hypothesis: There is a significant relationship between hourly self-reported activities and hourly geometric mean of melanopic EDI."
H5 <- "Hypothesis: There is a significant relationship between LEBA-factors and pre-selected personal light exposure metrics."
H6 <- "Hypothesis: There is a significant relationship between the day type, daily exercise, and sleep with the hourly geometric mean of light exposure"
H7 <- "Hypothesis: There is a ceiling effect of photoperiod with level, duration, and exposure-history-based metrics."
sig.level <- 0.05

#for chest:
# load("data/metrics_chest.RData")
# metrics <- metrics_chest
# melidos_cities <- melidos_cities[names(melidos_cities)!="MPI"]

Hypothesis 3

Details

Hypothesis 3 states:

There is a significant relationship between hourly self-reported light exposure categories and hourly geometric mean of melanopic EDI.

Type of model: GLMM

Base formula: \(\text{mel EDI} \sim \text{mH-LEA}*\text{Site} + (1|\text{Site}:\text{Participant})\)

Notes: - site as a random effect with random slopes for mH-LEA was unstable. Thus, site will be used as an interaction - mH-LEA data was used to collect the primary_lightsource, which is the variable used in the model - The confirmatory test looks at the base model compared to the Null model for mH-LEA, the other comparisons are exploratory - Site was sum-coded, so as to calculate an average effect of site - Error family is Tweedie

Specific formulas: - \(\text{mel EDI} \sim \text{Site} + (1|\text{Site}:\text{Participant})\) (Null-model, mH-LEA) - \(\text{mel EDI} \sim \text{mH-LEA} + \text{Site} + (1|\text{Site}:\text{Participant})\) (Null-model interaction) - \(\text{mel EDI} \sim \text{mH-LEA} + (1|\text{Site}:\text{Participant})\) (Null-model, Site)

False-discovery-rate correction: - non for confirmatory test (1 comparison) - 9 for site-effects - 4 for mH-LEA

Preparation

In [3]:
Code
Load hourly self-reported light exposure
mHLEA <- load_data("lightexposurediary") |> flatten_data()
loading modality: lightexposurediary ■■■■■■■■■■■                       33% |  E…
Code
mHLEA_labels <- 
  mHLEA|> extract_labels() |> 
  enframe() |> 
  filter(str_detect(name, "act_")) |> 
  mutate(name = name |> str_remove("act_"))
In [4]:
Code
hourly_data <- 
  hourly_data |> 
  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)
In [5]:
Code
Merge light data with self-reports
hourly_data <- 
  hourly_data |> 
  group_by(site, Id, Date) |> 
  add_states(mHLEA)
In [6]:
Code
Plot overview of light against self-reports

addline_format <- function(x,...){
    gsub('\\s','\n',x)
}

hourly_data |> 
  filter_out(is.na(lightsource_primary)) |> 
  ggplot(aes(x=lightsource_primary, y = MEDI)) +
  geom_boxplot() +
  scale_x_discrete(labels = addline_format) +
  scale_y_continuous(trans = "symlog", 
                     breaks = c(0,1,10,100,1000,10000,10^5),
                     labels = expression(0,1,10,10^2,10^3,10^4,10^5)) +
  theme_cowplot() +
  labs(y = "Melanopic EDI (lx)", x = "Primary lightsource")
Warning: Removed 1151 rows containing non-finite outside the scale range
(`stat_boxplot()`).

In [7]:
Code
hourly_data |> ungroup() |> count(lightsource_primary)
# A tibble: 8 × 2
  lightsource_primary                          n
  <fct>                                    <int>
1 Electric light source indoors             9459
2 Electric light source outdoors             431
3 Daylight indoors                          9899
4 Daylight outdoors (including shade)       3285
5 Emissive display light                    1317
6 Darkness during sleep                    10144
7 Light entering from outside during sleep  1711
8 <NA>                                      1479

We will remove any subgroup that has less than 0.1 % of the total datapoints, i.e., 20 observations within a site, and then 1% less than overall, i.e., 200 observations. We will also drop “Light entering from outside during sleep”, as it is very unevenly populated and dropped site levels will lead to issues later.

In [8]:
Code
hourly_data2 <-
  hourly_data |> 
  group_by(site, lightsource_primary) |> 
  mutate(n = n(),
         lightsource_primary = lightsource_primary |> 
           replace_when((n < 20 ) ~ NA)
           ) |> 
  group_by(lightsource_primary) |> 
  mutate(n = n(),
         lightsource_primary = lightsource_primary |> 
           replace_when((n < 200 ) ~ NA,
                        lightsource_primary == "Light entering from outside during sleep" ~ NA),
         site = factor(site)
           )

Analysis

In [9]:
Code
Fit the model for H3
H3_formula_1 <- geo.MEDI ~ site*lightsource_primary + (1|Id)
H3_formula_0 <- geo.MEDI ~ site + (1|Id)
H3_formula_1_ni <- geo.MEDI ~ site + lightsource_primary + (1|Id)
H3_formula_1_ns <- geo.MEDI ~ lightsource_primary + (1|Id)

#confirmatory
H3_model_1 <- 
  glmmTMB(H3_formula_1, 
          data = hourly_data2 |> drop_na(lightsource_primary), 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
dropping columns from rank-deficient conditional model: site1:lightsource_primaryElectric light source outdoors, site6:lightsource_primaryElectric light source outdoors, site7:lightsource_primaryElectric light source outdoors, site8:lightsource_primaryElectric light source outdoors
Code
H3_model_0 <- 
  glmmTMB(H3_formula_0, 
          data = hourly_data2 |> drop_na(lightsource_primary), 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
#exploratory:
H3_model_1_ni <- 
  glmmTMB(H3_formula_1_ni, 
          data = hourly_data2 |> drop_na(lightsource_primary), 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
H3_model_1_ns <- 
  glmmTMB(H3_formula_1_ns, 
          data = hourly_data2 |> drop_na(lightsource_primary), 
          REML = FALSE, 
          family = tweedie(),
          )

comp_confirmatory <- 
anova(H3_model_0, H3_model_1)
comp_confirmatory
Data: drop_na(hourly_data2, lightsource_primary)
Models:
H3_model_0: geo.MEDI ~ site + (1 | Id), zi=~0, disp=~1
H3_model_1: geo.MEDI ~ site * lightsource_primary + (1 | Id), zi=~0, disp=~1
           Df    AIC    BIC  logLik deviance Chisq Chi Df Pr(>Chisq)    
H3_model_0 12 313274 313375 -156625   313250                            
H3_model_1 53 297240 297686 -148567   297134 16116     41  < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
comp_interaction <- 
anova(H3_model_1, H3_model_1_ni)
comp_interaction
Data: drop_na(hourly_data2, lightsource_primary)
Models:
H3_model_1_ni: geo.MEDI ~ site + lightsource_primary + (1 | Id), zi=~0, disp=~1
H3_model_1: geo.MEDI ~ site * lightsource_primary + (1 | Id), zi=~0, disp=~1
              Df    AIC    BIC  logLik deviance Chisq Chi Df Pr(>Chisq)    
H3_model_1_ni 17 298172 298315 -149069   298138                            
H3_model_1    53 297240 297686 -148567   297134  1004     36  < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
comp_site <- 
anova(H3_model_1_ns, H3_model_1_ni)
comp_site
Data: drop_na(hourly_data2, lightsource_primary)
Models:
H3_model_1_ns: geo.MEDI ~ lightsource_primary + (1 | Id), zi=~0, disp=~1
H3_model_1_ni: geo.MEDI ~ site + lightsource_primary + (1 | Id), zi=~0, disp=~1
              Df    AIC    BIC  logLik deviance  Chisq Chi Df Pr(>Chisq)    
H3_model_1_ns  9 298184 298260 -149083   298166                             
H3_model_1_ni 17 298172 298315 -149069   298138 27.838      8   0.000506 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
AIC(H3_model_1_ni, H3_model_1, H3_model_0, H3_model_1_ns) |> 
  arrange(AIC) |> 
  rownames_to_column("model") |> gt()
model df AIC
H3_model_1 53 297240.2
H3_model_1_ni 17 298172.2
H3_model_1_ns 9 298184.1
H3_model_0 12 313273.8

Both AIC and ∆loglik comparisons suggest that the interaction model is the most relevant one.

In [10]:
Code
check_model(H3_model_1)
`check_outliers()` does not yet support models of class `glmmTMB`.
Cannot simulate residuals for models of class `glmmTMB`. Please try
  `check_model(..., residual_type = "normal")` instead.

In [11]:
Code
tab_model(H3_model_1, CSS = css_theme("cells"))
Model matrix is rank deficient. Parameters
  `site1:lightsource_primaryElectric light source outdoors,
  site6:lightsource_primaryElectric light source outdoors,
  site7:lightsource_primaryElectric light source outdoors,
  site8:lightsource_primaryElectric light source outdoors` were not
  estimable.
  geo.MEDI
Predictors Estimates CI p
(Intercept) 83.56 72.16 – 96.76 <0.001
site1 1.01 0.70 – 1.46 0.961
site2 1.50 1.09 – 2.08 0.014
site3 0.88 0.60 – 1.28 0.501
site4 0.66 0.44 – 0.97 0.035
site5 0.54 0.39 – 0.74 <0.001
site6 1.96 1.29 – 2.97 0.002
site7 2.43 1.60 – 3.67 <0.001
site8 0.94 0.59 – 1.49 0.782
lightsource_primary:
Daylight indoors
0.01 0.01 – 0.03 <0.001
lightsource_primary:
Daylight outdoors
(including shade)
2.28 2.15 – 2.41 <0.001
lightsource_primary:
Emissive display light
6.69 6.18 – 7.25 <0.001
lightsource_primary:
Darkness during sleep
0.30 0.26 – 0.34 <0.001
lightsource_primary:
Light entering from
outside during sleep
0.05 0.04 – 0.05 <0.001
site2:lightsource_primaryElectric light source outdoors 15.33 5.26 – 44.71 <0.001
site3:lightsource_primaryElectric light source outdoors 38.92 13.37 – 113.25 <0.001
site4:lightsource_primaryElectric light source outdoors 56.08 20.14 – 156.18 <0.001
site5:lightsource_primaryElectric light source outdoors 247.33 86.44 – 707.66 <0.001
site1:lightsource_primaryDaylight indoors 1.31 1.12 – 1.54 0.001
site2:lightsource_primaryDaylight indoors 0.67 0.59 – 0.77 <0.001
site3:lightsource_primaryDaylight indoors 1.00 0.87 – 1.16 0.959
site4:lightsource_primaryDaylight indoors 0.61 0.51 – 0.72 <0.001
site5:lightsource_primaryDaylight indoors 1.67 1.47 – 1.89 <0.001
site6:lightsource_primaryDaylight indoors 0.73 0.62 – 0.85 <0.001
site7:lightsource_primaryDaylight indoors 0.65 0.55 – 0.76 <0.001
site8:lightsource_primaryDaylight indoors 1.15 0.96 – 1.37 0.138
site1:lightsource_primaryDaylight outdoors (including shade) 1.20 1.00 – 1.45 0.056
site2:lightsource_primaryDaylight outdoors (including shade) 0.43 0.36 – 0.52 <0.001
site3:lightsource_primaryDaylight outdoors (including shade) 1.36 1.10 – 1.70 0.006
site4:lightsource_primaryDaylight outdoors (including shade) 0.89 0.75 – 1.06 0.192
site5:lightsource_primaryDaylight outdoors (including shade) 1.33 1.12 – 1.58 0.001
site6:lightsource_primaryDaylight outdoors (including shade) 1.73 1.44 – 2.08 <0.001
site7:lightsource_primaryDaylight outdoors (including shade) 0.74 0.61 – 0.89 0.002
site8:lightsource_primaryDaylight outdoors (including shade) 1.07 0.84 – 1.35 0.597
site1:lightsource_primaryEmissive display light 3.17 2.35 – 4.26 <0.001
site2:lightsource_primaryEmissive display light 2.98 2.25 – 3.94 <0.001
site3:lightsource_primaryEmissive display light 2.16 1.53 – 3.04 <0.001
site4:lightsource_primaryEmissive display light 1.03 0.69 – 1.53 0.893
site5:lightsource_primaryEmissive display light 0.30 0.19 – 0.47 <0.001
site6:lightsource_primaryEmissive display light 0.26 0.19 – 0.36 <0.001
site7:lightsource_primaryEmissive display light 2.75 2.13 – 3.55 <0.001
site8:lightsource_primaryEmissive display light 0.13 0.07 – 0.23 <0.001
site1:lightsource_primaryDarkness during sleep 0.97 0.80 – 1.18 0.763
site2:lightsource_primaryDarkness during sleep 0.48 0.40 – 0.57 <0.001
site3:lightsource_primaryDarkness during sleep 1.12 0.93 – 1.34 0.227
site4:lightsource_primaryDarkness during sleep 0.63 0.51 – 0.77 <0.001
site5:lightsource_primaryDarkness during sleep 1.65 1.41 – 1.94 <0.001
site6:lightsource_primaryDarkness during sleep 0.85 0.70 – 1.04 0.123
site7:lightsource_primaryDarkness during sleep 0.52 0.43 – 0.63 <0.001
site8:lightsource_primaryDarkness during sleep 3.44 2.78 – 4.26 <0.001
Random Effects
σ2 1.18
τ00Id 0.61
ICC 0.34
N Id 140
Observations 33356
Marginal R2 / Conditional R2 0.659 / 0.774

Basic table

In [12]:
Code
H3_tab_ran <- 
  H3_model_1 |> 
  tidy() |> 
  mutate(signif = p.value <= sig.level,
         estimate = exp(estimate) |> round(2)) |> 
  filter(effect == "ran_pars") |> 
  select(term, estimate)

H3_tab_reference <-
  H3_model_1 |>
  emmeans(~ lightsource_primary, type = "response",
          at = list(lightsource_primary = "Electric light source indoors")) |> 
  as.tibble() |> 
  rename(estimate = response)
Warning: `as.tibble()` was deprecated in tibble 2.0.0.
ℹ Please use `as_tibble()` instead.
ℹ The signature and semantics have changed, see `?as_tibble`.
NOTE: Results may be misleading due to involvement in interactions
Code
H3_tab_light <- 
  H3_model_1 |> 
  emmeans(~ lightsource_primary, type = "response") |> 
  contrast(method = "trt.vs.ctrl") |> 
  as.tibble() |> 
  rename(estimate = ratio)
NOTE: Results may be misleading due to involvement in interactions
Code
H3_tab_interaction <-
  H3_model_1 |> 
  emmeans(~ site | lightsource_primary, type = "response") |> 
  contrast() |> 
  as.tibble() |> 
  rename(estimate = ratio)

H3_tab_fix <-
  bind_rows(H3_tab_reference, H3_tab_light, H3_tab_interaction) |> 
  mutate(signif = p.value <= sig.level) |> 
  select(-c(df, asymp.LCL, asymp.UCL, null, z.ratio)) |> 
  relocate(contrast, .before = 1) |> 
  mutate(site = contrast |> as.character(),
         lightsource_primary = 
           lightsource_primary |> as.character() |> 
           replace_when(
             str_detect(contrast, "Electric light source indoors") ~ str_remove(contrast, " / Electric light source indoors")
           ),
         site = replace_when(site, 
                             is.na(site) ~ "Overall",
                             str_detect(site, " effect") ~ str_remove(site, " effect"),
                             str_detect(contrast, "Electric light source indoors") ~ "Overall"),
         .before = 1
                              
  ) |> 
  select(-contrast) |> 
  pivot_wider(id_cols = site, names_from = lightsource_primary, values_from = estimate:signif)
  # mutate(
         # across(3:6,
                # \(x) c(x[1], x[-1]*x[1]))
         # )
In [13]:
Code
H3_r2_1 <- r2_helper(H3_model_1, hourly_data2 |> drop_na(lightsource_primary), tweedie(), "geo.MEDI", "(1 | Id)")
H3_r2_0 <- r2_helper(H3_model_0, hourly_data2 |> drop_na(lightsource_primary), tweedie(), "geo.MEDI", "(1 | Id)")
H3_r2_interaction<- r2_helper(H3_model_1_ni, hourly_data2 |> drop_na(lightsource_primary), tweedie(), "geo.MEDI", "(1 | Id)")
H3_r2_site<- r2_helper(H3_model_1_ns, hourly_data2 |> drop_na(lightsource_primary), tweedie(), "geo.MEDI", "(1 | Id)")
H3_tab_r2 <-
  tribble(
    ~type, ~r2,
    "Model", H3_r2_1$R2_conditional,
    "All fixed", H3_r2_1$R2_marginal,
    "Residual", 1-H3_r2_1$R2_conditional,
    "Lightsource", H3_r2_1$R2_marginal - H3_r2_0$R2_marginal,
    "Site", H3_r2_1$R2_marginal - H3_r2_site$R2_marginal,
    "Random", H3_r2_1$R2_conditional - H3_r2_1$R2_marginal,
    "Interaction", H3_r2_1$R2_marginal - H3_r2_interaction$R2_marginal,
  ) |> 
  mutate(r2= r2 |> round(2))
In [14]:
Code
H3_tab <-
H3_tab_fix |> 
  site_conv_mutate(rev = FALSE, other.levels = "Overall") |> 
  arrange(site) |> 
  gt() |> 
  fmt(starts_with("estimate"), 
      fns = \(x) 
      {x <- x |> gt::vec_fmt_number()
      str_c("×", x)}
      ) |> 
  fmt(columns = 2, 
      rows = site == "Overall",
      fns = \(x) 
      {x <- x |> gt::vec_fmt_number()
      str_c(x, "lx")}
      ) |> 
  cols_label_with(fn = \(x) str_remove(x, "estimate_") |> str_remove("lightsource_primary")) |> 
  tab_spanner(
    md(
      paste0("Primary lightsource ($p = ",
             comp_confirmatory$`Pr(>Chisq)`[2] |> style_pvalue(),
             "$, $R^2=", H3_tab_r2$r2[4], 
             "$)")
      ), 
    columns = -site) |> 
  cols_hide((contains(c("p.value", "signif", "SE_")))) |> 
  sub_missing() |> 
  gt_multiple(
    names(H3_tab_fix) |> 
      subset(str_detect(names(H3_tab_fix), "estimate_")) |> 
      str_remove("estimate_"),
    bold_p.) |> 
  tab_style_body(
    style = cell_borders(weight = px(2)),
    rows = 1,
    columns = 2,
    fn = \(x) TRUE
  ) |> 
  gt_multiple(
    melidos_colors |> names(),
    style_rows
  ) |> 
  tab_style(
    style = cell_text(weight = "bold"),
    locations = list(
      cells_column_labels(),
      cells_column_spanners(),
      cells_body(columns = site),
      cells_body(2,1)
    )
  ) |> 
  cols_label(
    site = md(paste0("Site<br>($p = ",
             comp_site$`Pr(>Chisq)`[2] |> style_pvalue(),
             "$, $R^2=", H3_tab_r2$r2[5], 
             "$)"))
    ) |> 
  tab_header(
    title = "Model results for Hypothesis 3",
    subtitle = H3
  ) |> 
  tab_footnote(
    "Reference value, for electric light indoors and a site average. All other values have to be multiplied by the reference value, their respective `Overall` multiplier, and optionally the site interaction to get the estimated mel EDI for a given combination.", locations = cells_body(2,1)
  ) |> 
  tab_footnote(
    md(paste0("Conditional (Model) $R^2=", 
              H3_tab_r2$r2[1],
              "$, Marginal (Fixed effects) $R^2=",
              H3_tab_r2$r2[2],
              "$, Random effect $R^2=",
              H3_tab_r2$r2[6],
              "$, Residual Variance $R^2=",
              H3_tab_r2$r2[3],
              "$; Random effect of participant: $×",
              H3_tab_ran$estimate[1],
              "$; Model based on ",
              hourly_data2 |> drop_na(geo.MEDI, lightsource_primary) |> nrow() |> vec_fmt_integer(sep_mark = "'"),
              " participant-hours."
              ))
  ) |> 
  tab_footnote(
    md(paste0("Interaction effect of primary lightsource with site is significant ($p=", 
              comp_interaction$`Pr(>Chisq)`[2] |> style_pvalue(),
              "$, $R^2 = ",
              H3_tab_r2$r2[7],
              "$)"))
  ) |> 
  tab_footnote(
    md("Values shown in **bold** are significant at $p<0.05$. False-discovery-rate adjustment for n=4 in primary lightsource and n=9 in site")
  ) |> 
  cols_width(everything() ~ px(150))
In [15]:
Code
H3_tab
Model results for Hypothesis 3
Hypothesis: There is a significant relationship between hourly self-reported light exposure categories and hourly geometric mean of melanopic EDI.
Site
(\(p = <0.001\), \(R^2=0.05\))
Primary lightsource (\(p = <0.001\), \(R^2=0.59\))
Electric light source indoors Electric light source outdoors Daylight indoors Daylight outdoors (including shade) Emissive display light Darkness during sleep
Overall 1 83.56lx ×2.28 ×6.69 ×0.30 ×0.05
Borås (SE) ×1.96 ×1.42 ×3.39 ×0.51 ×1.67
Delft (NL) ×2.43 ×1.58 ×1.80 ×6.67 ×1.26
Dortmund (DE) ×1.01 ×1.33 ×1.21 ×3.19 ×0.98
Tübingen (DE) ×0.54 ×0.90 ×0.72 ×0.16 ×0.89
Munich (DE) ×0.94 ×1.07 ×1.00 ×0.12 ×3.22
Madrid (ES) ×1.50 ×1.01 ×0.65 ×4.47 ×0.72
Izmir (TR) ×0.88 ×0.88 ×1.20 ×1.89 ×0.98
San José (CR) ×0.48 ×0.98 ×0.41 ×0.86 ×0.58
Kumasi (GH) ×0.66 ×0.40 ×0.59 ×0.68 ×0.41
Conditional (Model) \(R^2=0.77\), Marginal (Fixed effects) \(R^2=0.66\), Random effect \(R^2=0.12\), Residual Variance \(R^2=0.23\); Random effect of participant: \(×2.18\); Model based on 33’356 participant-hours.
Interaction effect of primary lightsource with site is significant (\(p=<0.001\), \(R^2 = 0.02\))
Values shown in bold are significant at \(p<0.05\). False-discovery-rate adjustment for n=4 in primary lightsource and n=9 in site
1 Reference value, for electric light indoors and a site average. All other values have to be multiplied by the reference value, their respective `Overall` multiplier, and optionally the site interaction to get the estimated mel EDI for a given combination.
Code
gtsave(H3_tab, "tables/H3.png")
file:////var/folders/9p/326_k3kx43qbn_cyl1rqfhb00000gn/T//RtmputNk5I/file14a696ee1d7a8.html screenshot completed

Visualization

In [16]:
Code
visualize H3 result
H3_plot <- 
H3_model_1 |> 
  emmeans(~ site | lightsource_primary, type = "response") |> 
  plot()

H3_plot <-
  H3_plot$data |> 
  as.tibble() |> 
  site_conv_mutate(site = pri.fac) |> 
    ggplot(
      aes(x = the.emmean, y = pri.fac)
    ) +
    geom_crossbar(aes(xmin = lcl, xmax = ucl, fill = pri.fac), col = NA, alpha = 0.7) +
    geom_point() +
  theme_cowplot() +
  theme(panel.grid.major = element_line(color = "grey")) +
  scale_fill_manual(values = melidos_colors) +
  facet_wrap(~lightsource_primary, ncol = 1) +
  scale_x_continuous(trans = "symlog", breaks = c(0, 10^(0:5), 250, 5000)) +
  coord_cartesian(xlim = c(0, 5000)) +
  labs(y = NULL, x = "Melanopic EDI (lx), based on hourly geometric mean") +
    guides(fill = "none")

H3_plot
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_crossbar()`).
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_point()`).

Code
hourly_data2 |> 
    filter_out(is.na(lightsource_primary)) |> 
    site_conv_mutate(rev = FALSE) |> 
    ggplot(aes(x=geo.MEDI)) +
    geom_density_ridges(aes(y=lightsource_primary),
                        linewidth = 1, colour = NA,
                        alpha = 0.5,
                        from = 0,
    ) +
    geom_density_ridges(aes(y=lightsource_primary), fill = NA,
                        alpha = 1, linewidth = 1,
                        quantile_lines = TRUE, quantiles = 2, vline_color = "red",
                        linetype = 1,
                        from = 0,
    ) +
    # scale_fill_manual(values = melidos_colors) +
    scale_alpha_continuous(range = c(0, 1)) +
    scale_x_continuous(trans = "symlog", breaks = c(0, 10^(0:5)),
                       labels = expression(0, 1, 10, 10^2, 10^3, 10^4, 10^5)) +
    # scale_color_manual(values = melidos_colors) +
    guides(fill = "none", color = "none") +
    labs(y = NULL, x = NULL) +
    theme_ridges() +
    coord_cartesian(xlim = c(0, 10^5)) +
    theme_sub_plot(margin = margin(r = 20, t = 20)) +
    theme_sub_axis_left(text = element_text(vjust = 0))
Picking joint bandwidth of 0.189
Warning: Removed 1137 rows containing non-finite outside the scale range
(`stat_density_ridges()`).
Picking joint bandwidth of 0.189
Warning: Removed 1137 rows containing non-finite outside the scale range
(`stat_density_ridges()`).

Interpretation

  • Overall, an average mel EDI of 74.1 lx (based on geometric mean) is reached with electric light indoors. This value somewhat varies between sites, with Sweden, The Netherlands, and Madrid achieving substantially higher values (factor 2.06, 2.17, and 1.49, respectively). They nevertheless do not reach recommended values of 250lx during the day. Tübingen, Germany, and Ghana reach significantly lower levels (factor 0.56 and 0.5, respectively).
  • When the primary lightsource is Daylight indoors, about 2.34 times as much mel EDI can be achieved on average (173.4 lx). The only significant deviation is Ghana, which scores 61% less than the other sites (factor 0.39).
  • When people are receiving Daylight outdoors (including shade), they achieve about 9.81 times more light compared to indoor electric light (727 lx). Sweden and the Netherlands achieve still higher values (times 2.98 and 1.9, respectively), while Ghana has only about half (0.51)
  • Emissive display light as the primary lightsource leads to values of melanopic EDI of 29% of indoor electric light (21.5 lx). There is very high variance between the different sites, with factor ranging between 8% (Munich, Germany) to 623% (The Netherlands).
  • Darkness during sleep translates to about 2% of light levels compared to indoor electric light (~1.5 lx). Values are similar across sites, with three notable exceptions: Munich, Germany has over 6 times the level compared to other sites (6.61), while Spain and Ghana have considerably lower values (0.55 and 0.15, respectively).
  • Overall, the effect of self-reported primary lightsource is astounding - 71% of variance can be explained simply through those categories. While site and interindividual differences are significant, they contribute considerably less (5% and 7%, respectively).

Hypothesis 4

Details

Hypothesis 4 states:

There is a significant relationship between hourly self-reported activities and hourly geometric mean of melanopic EDI.

Type of model: GLMM

Base formula: \(\text{mel EDI} \sim \text{activity}*\text{Site} + (1|\text{Site}:\text{Participant})\)

Notes: - site as a random effect with random slopes for activity was unstable. Thus, site will be used as an interaction - data was used to collect various, which is the variable used in the model - The confirmatory test looks at the base model compared to the Null model for mH-LEA, the other comparisons are exploratory - Site was sum-coded, so as to calculate an average effect of site - Error family is Tweedie - We will drop the level “other” and combine outdoors levels

Specific formulas: - \(\text{mel EDI} \sim \text{Site} + (1|\text{Site}:\text{Participant})\) (Null-model, mH-LEA) - \(\text{mel EDI} \sim \text{mH-LEA} + \text{Site} + (1|\text{Site}:\text{Participant})\) (Null-model interaction) - \(\text{mel EDI} \sim \text{mH-LEA} + (1|\text{Site}:\text{Participant})\) (Null-model, Site)

False-discovery-rate correction: - non for confirmatory test (1 comparison) - 9 for site-effects - 4 for activity

Preparation

In [17]:
Code
activity_data <- 
hourly_data2 |> 
  ungroup() |> 
  select(site, Id, geo.MEDI, starts_with("act_"), Datetime, lightsource_primary) |> 
  pivot_longer(cols = starts_with("act_"), names_to = "activity", names_prefix = "act_", values_to = "value") |> 
  filter(value, !is.na(geo.MEDI)) |> 
  select(-value)
In [18]:
Code
Plot overview of light against self-reported activity
activity_data |> 
  ggplot(aes(x=activity, y = geo.MEDI)) +
  geom_boxplot() +
  scale_x_discrete(labels = addline_format) +
  scale_y_continuous(trans = "symlog", 
                     breaks = c(0,1,10,100,1000,10000,10^5),
                     labels = expression(0,1,10,10^2,10^3,10^4,10^5)) +
  theme_cowplot() +
  labs(y = "Melanopic EDI (lx)", x = "Activity")

In [19]:
Code
activity_data |> count(activity)
# A tibble: 8 × 2
  activity            n
  <chr>           <int>
1 free_outdoor     1333
2 home             9960
3 other             938
4 road_open        1165
5 road_vehicle     1611
6 sleep           11404
7 working_indoor   7185
8 working_outdoor   419

We will drop the other group, and we will combine working_outdoor with free_outdoor and road_open, as they are small or non-existant in in some sites.

In [20]:
Code
activity_data <-
  activity_data |> 
  mutate(activity = 
           fct(activity) |> 
           fct_collapse(outdoor = c("free_outdoor", "working_outdoor", "road_open")) |> 
           fct_recode(NULL = "other") |> 
           fct_relevel("home")
         ) |> 
  drop_na(activity)

Analysis

In [21]:
Code
Fit the model for H4
H4_formula_1 <- geo.MEDI ~ site*activity + (1|Id)
H4_formula_0 <- geo.MEDI ~ site + (1|Id)
H4_formula_1_ni <- geo.MEDI ~ site + activity + (1|Id)
H4_formula_1_ns <- geo.MEDI ~ activity + (1|Id)

#confirmatory
H4_model_1 <- 
  glmmTMB(H4_formula_1, 
          data = activity_data, 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
H4_model_0 <- 
  glmmTMB(H4_formula_0, 
          data = activity_data, 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
#exploratory:
H4_model_1_ni <- 
  glmmTMB(H4_formula_1_ni, 
          data = activity_data, 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
H4_model_1_ns <- 
  glmmTMB(H4_formula_1_ns, 
          data = activity_data, 
          REML = FALSE, 
          family = tweedie(),
          )

comp_confirmatory <- 
anova(H4_model_0, H4_model_1)
comp_confirmatory
Data: activity_data
Models:
H4_model_0: geo.MEDI ~ site + (1 | Id), zi=~0, disp=~1
H4_model_1: geo.MEDI ~ site * activity + (1 | Id), zi=~0, disp=~1
           Df    AIC    BIC  logLik deviance Chisq Chi Df Pr(>Chisq)    
H4_model_0 12 304366 304467 -152171   304342                            
H4_model_1 48 289801 290204 -144852   289705 14638     36  < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
comp_interaction <- 
anova(H4_model_1, H4_model_1_ni)
comp_interaction
Data: activity_data
Models:
H4_model_1_ni: geo.MEDI ~ site + activity + (1 | Id), zi=~0, disp=~1
H4_model_1: geo.MEDI ~ site * activity + (1 | Id), zi=~0, disp=~1
              Df    AIC    BIC  logLik deviance  Chisq Chi Df Pr(>Chisq)    
H4_model_1_ni 16 290733 290868 -145351   290701                             
H4_model_1    48 289801 290204 -144852   289705 996.47     32  < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
comp_site <- 
anova(H4_model_1_ns, H4_model_1_ni)
comp_site
Data: activity_data
Models:
H4_model_1_ns: geo.MEDI ~ activity + (1 | Id), zi=~0, disp=~1
H4_model_1_ni: geo.MEDI ~ site + activity + (1 | Id), zi=~0, disp=~1
              Df    AIC    BIC  logLik deviance  Chisq Chi Df Pr(>Chisq)    
H4_model_1_ns  8 290760 290828 -145372   290744                             
H4_model_1_ni 16 290733 290868 -145351   290701 43.304      8  7.699e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
AIC(H4_model_1_ni, H4_model_1, H4_model_0, H4_model_1_ns) |> 
  arrange(AIC) |> 
  rownames_to_column("model") |> gt()
model df AIC
H4_model_1 48 289800.6
H4_model_1_ni 16 290733.1
H4_model_1_ns 8 290760.4
H4_model_0 12 304366.2

Both AIC and ∆loglik comparisons suggest that the interaction model is the most relevant one.

In [22]:
Code
check_model(H4_model_1)
`check_outliers()` does not yet support models of class `glmmTMB`.
Cannot simulate residuals for models of class `glmmTMB`. Please try
  `check_model(..., residual_type = "normal")` instead.

In [23]:
Code
tab_model(H4_model_1, CSS = css_theme("cells"))
  geo.MEDI
Predictors Estimates CI p
(Intercept) 74.88 64.21 – 87.32 <0.001
site1 2.05 1.42 – 2.97 <0.001
site2 0.80 0.57 – 1.13 0.200
site3 0.74 0.50 – 1.09 0.126
site4 0.42 0.28 – 0.63 <0.001
site5 0.45 0.29 – 0.69 <0.001
site6 1.74 1.14 – 2.65 0.010
site7 2.21 1.45 – 3.37 <0.001
site8 1.20 0.75 – 1.94 0.443
activity: road_vehicle 0.07 0.07 – 0.08 <0.001
activity: working_indoor 3.65 3.33 – 3.99 <0.001
activity: outdoor 2.71 2.57 – 2.87 <0.001
NA 5.69 5.23 – 6.19 <0.001
site1:activitysleep 0.68 0.58 – 0.79 <0.001
site2:activitysleep 0.66 0.56 – 0.77 <0.001
site3:activitysleep 2.24 1.93 – 2.60 <0.001
site4:activitysleep 0.46 0.37 – 0.57 <0.001
site5:activitysleep 0.98 0.82 – 1.18 0.832
site6:activitysleep 0.99 0.85 – 1.16 0.914
site7:activitysleep 0.51 0.43 – 0.61 <0.001
site8:activitysleep 3.96 3.34 – 4.70 <0.001
site1:activityroad_vehicle 0.89 0.73 – 1.08 0.250
site2:activityroad_vehicle 0.86 0.70 – 1.06 0.155
site3:activityroad_vehicle 1.35 1.06 – 1.72 0.013
site4:activityroad_vehicle 2.30 1.76 – 3.01 <0.001
site5:activityroad_vehicle 0.80 0.59 – 1.08 0.140
site6:activityroad_vehicle 1.13 0.92 – 1.38 0.238
site7:activityroad_vehicle 0.76 0.60 – 0.96 0.022
site8:activityroad_vehicle 0.65 0.49 – 0.87 0.003
site1:activityworking_indoor 0.64 0.56 – 0.74 <0.001
site2:activityworking_indoor 1.64 1.44 – 1.87 <0.001
site3:activityworking_indoor 1.18 1.03 – 1.36 0.015
site4:activityworking_indoor 1.75 1.47 – 2.09 <0.001
site5:activityworking_indoor 1.12 0.95 – 1.31 0.170
site6:activityworking_indoor 0.98 0.85 – 1.13 0.793
site7:activityworking_indoor 0.86 0.74 – 0.99 0.036
site8:activityworking_indoor 0.95 0.80 – 1.12 0.523
site1:activityoutdoor 0.69 0.59 – 0.82 <0.001
site2:activityoutdoor 1.00 0.84 – 1.21 0.963
site3:activityoutdoor 1.33 1.10 – 1.61 0.003
site4:activityoutdoor 1.57 1.30 – 1.91 <0.001
site5:activityoutdoor 0.57 0.45 – 0.72 <0.001
site6:activityoutdoor 2.04 1.73 – 2.41 <0.001
site7:activityoutdoor 1.02 0.86 – 1.21 0.844
site8:activityoutdoor 1.03 0.82 – 1.30 0.798
Random Effects
σ2 1.25
τ00Id 0.64
ICC 0.34
N Id 126
Observations 33077
Marginal R2 / Conditional R2 0.637 / 0.761

Basic table

In [24]:
Code
H4_tab_ran <- 
  H4_model_1 |> 
  tidy() |> 
  mutate(signif = p.value <= sig.level,
         estimate = exp(estimate) |> round(2)) |> 
  filter(effect == "ran_pars") |> 
  select(term, estimate)

H4_tab_reference <-
  H4_model_1 |>
  emmeans(~ activity, type = "response",
          at = list(activity = "home")) |> 
  as.tibble() |> 
  rename(estimate = response)
NOTE: Results may be misleading due to involvement in interactions
Code
H4_tab_act <- 
  H4_model_1 |> 
  emmeans(~ activity, type = "response") |> 
  contrast(method = "trt.vs.ctrl") |> 
  as.tibble() |> 
  rename(estimate = ratio)
NOTE: Results may be misleading due to involvement in interactions
Code
H4_tab_interaction <-
  H4_model_1 |> 
  emmeans(~ site | activity, type = "response") |> 
  contrast() |> 
  as.tibble() |> 
  rename(estimate = ratio)

H4_tab_fix <-
  bind_rows(H4_tab_reference, H4_tab_act, H4_tab_interaction) |> 
  mutate(signif = p.value <= sig.level) |> 
  select(-c(df, asymp.LCL, asymp.UCL, null, z.ratio)) |> 
  relocate(contrast, .before = 1) |> 
  mutate(site = contrast |> as.character(),
         activity = 
           activity |> as.character() |> 
           replace_when(
             str_detect(contrast, "home") ~ str_remove(contrast, " / home")
           ),
         site = replace_when(site, 
                             is.na(site) ~ "Overall",
                             str_detect(site, " effect") ~ str_remove(site, " effect"),
                             str_detect(contrast, "home") ~ "Overall"),
         .before = 1
                              
  ) |> 
  select(-contrast) |> 
  mutate(activity = replace_values(activity, from = mHLEA_labels$name, to = mHLEA_labels$value),
         activity = replace_values(activity, 
                                   "outdoor" ~ "Outdoor (free, working, on the road)")) |> 
  pivot_wider(id_cols = site, names_from = activity, values_from = estimate:signif)
  # mutate(
         # across(3:6,
                # \(x) c(x[1], x[-1]*x[1]))
         # )
In [25]:
Code
H4_r2_1 <- r2_helper(H4_model_1, activity_data, tweedie(), "geo.MEDI", "(1 | Id)")
H4_r2_0 <- r2_helper(H4_model_0, activity_data, tweedie(), "geo.MEDI", "(1 | Id)")
H4_r2_interaction<- r2_helper(H4_model_1_ni, activity_data, tweedie(), "geo.MEDI", "(1 | Id)")
H4_r2_site<- r2_helper(H4_model_1_ns, activity_data, tweedie(), "geo.MEDI", "(1 | Id)")
H4_tab_r2 <-
  tribble(
    ~type, ~r2,
    "Model", H4_r2_1$R2_conditional,
    "All fixed", H4_r2_1$R2_marginal,
    "Residual", 1-H4_r2_1$R2_conditional,
    "Activity", H4_r2_1$R2_marginal - H4_r2_0$R2_marginal,
    "Site", H4_r2_1$R2_marginal - H4_r2_site$R2_marginal,
    "Random", H4_r2_1$R2_conditional - H4_r2_1$R2_marginal,
    "Interaction", H4_r2_1$R2_marginal - H4_r2_interaction$R2_marginal,
  ) |> 
  mutate(r2= r2 |> round(2))
In [26]:
Code
H4_tab <-
H4_tab_fix |> 
  site_conv_mutate(rev = FALSE, other.levels = "Overall") |> 
  arrange(site) |> 
  gt() |> 
  fmt(starts_with("estimate"), 
      fns = \(x) 
      {x <- x |> gt::vec_fmt_number()
      str_c("×", x)}
      ) |> 
  fmt(columns = 2, 
      rows = site == "Overall",
      fns = \(x) 
      {x <- x |> gt::vec_fmt_number()
      str_c(x, "lx")}
      ) |> 
  cols_label_with(fn = \(x) str_remove(x, "estimate_") |> str_remove("\\(activity\\)")) |> 
  tab_spanner(
    md(
      paste0("Activity ($p = ",
             comp_confirmatory$`Pr(>Chisq)`[2] |> style_pvalue(),
             "$, $R^2=", H4_tab_r2$r2[4], 
             "$)")
      ), 
    columns = -site) |> 
  cols_hide((contains(c("p.value", "signif", "SE_")))) |> 
  sub_missing() |> 
  gt_multiple(
    names(H4_tab_fix) |> 
      subset(str_detect(names(H4_tab_fix), "estimate_")) |> 
      str_remove("estimate_"),
    bold_p.) |> 
  tab_style_body(
    style = cell_borders(weight = px(2)),
    rows = 1,
    columns = 2,
    fn = \(x) TRUE
  ) |> 
  gt_multiple(
    melidos_colors |> names(),
    style_rows
  ) |> 
  tab_style(
    style = cell_text(weight = "bold"),
    locations = list(
      cells_column_labels(),
      cells_column_spanners(),
      cells_body(columns = site),
      cells_body(2,1)
    )
  ) |> 
  cols_label(
    site = md(paste0("Site<br>($p = ",
             comp_site$`Pr(>Chisq)`[2] |> style_pvalue(),
             "$, $R^2=", H4_tab_r2$r2[5], 
             "$)"))
    ) |> 
  tab_header(
    title = "Model results for Hypothesis 4",
    subtitle = H4
  ) |> 
  tab_footnote(
    "Reference value, for `Awake at home`. All other values have to be multiplied by the reference value, their respective `Overall` multiplier, and optionally the site interaction to get the estimated mel EDI for a given combination.", locations = cells_body(2,1)
  ) |> 
  tab_footnote(
    md(paste0("Conditional (Model) $R^2=", 
              H4_tab_r2$r2[1],
              "$, Marginal (Fixed effects) $R^2=",
              H4_tab_r2$r2[2],
              "$, Random effect $R^2=",
              H4_tab_r2$r2[6],
              "$, Residual Variance $R^2=",
              H4_tab_r2$r2[3],
              "$; Random effect of participant: $×",
              H4_tab_ran$estimate[1],
              "$; Model based on ",
              hourly_data2 |> drop_na(geo.MEDI, lightsource_primary) |> nrow() |> vec_fmt_integer(sep_mark = "'"),
              " participant-hours."
              ))
  ) |> 
  tab_footnote(
    md(paste0("Interaction effect of activity with site is significant ($p=", 
              comp_interaction$`Pr(>Chisq)`[2] |> style_pvalue(),
              "$, $R^2 = ",
              H4_tab_r2$r2[7],
              "$)"))
  ) |> 
  tab_footnote(
    md("Values shown in **bold** are significant at $p<0.05$. False-discovery-rate adjustment for n=4 in activity and n=9 in site")
  ) |> 
  cols_width(everything() ~ px(150))
In [27]:
Code
H4_tab
Model results for Hypothesis 4
Hypothesis: There is a significant relationship between hourly self-reported activities and hourly geometric mean of melanopic EDI.
Site
(\(p = <0.001\), \(R^2=0.09\))
Activity (\(p = <0.001\), \(R^2=0.53\))
Awake at home Sleeping in bed On the road with public transport/car Working in the office/from home Outdoor (free, working, on the road)
Overall 1 74.88lx ×0.07 ×3.65 ×2.71 ×5.69
Borås (SE) ×1.74 ×1.72 ×1.96 ×1.70 ×3.55
Delft (NL) ×2.21 ×1.13 ×1.68 ×1.89 ×2.25
Dortmund (DE) ×2.05 ×1.39 ×1.83 ×1.32 ×1.42
Tübingen (DE) ×0.45 ×0.44 ×0.36 ×0.50 ×0.26
Munich (DE) ×1.20 ×4.77 ×0.79 ×1.14 ×1.24
Madrid (ES) ×0.80 ×0.53 ×0.69 ×1.31 ×0.80
Izmir (TR) ×0.74 ×1.65 ×1.00 ×0.87 ×0.98
San José (CR) ×0.94 ×1.05 ×0.88 ×0.48 ×0.53
Kumasi (GH) ×0.42 ×0.19 ×0.97 ×0.74 ×0.66
Conditional (Model) \(R^2=0.76\), Marginal (Fixed effects) \(R^2=0.64\), Random effect \(R^2=0.12\), Residual Variance \(R^2=0.24\); Random effect of participant: \(×2.23\); Model based on 33’356 participant-hours.
Interaction effect of activity with site is significant (\(p=<0.001\), \(R^2 = 0.03\))
Values shown in bold are significant at \(p<0.05\). False-discovery-rate adjustment for n=4 in activity and n=9 in site
1 Reference value, for `Awake at home`. All other values have to be multiplied by the reference value, their respective `Overall` multiplier, and optionally the site interaction to get the estimated mel EDI for a given combination.
Code
gtsave(H4_tab, "tables/H4.png")
file:////var/folders/9p/326_k3kx43qbn_cyl1rqfhb00000gn/T//RtmputNk5I/file14a697d2ed055.html screenshot completed

Visualization

In [28]:
Code
visualize H4 result
H4_plot <- 
H4_model_1 |> 
  emmeans(~ site | activity, type = "response") |> 
  plot()

H4_plot <-
  H4_plot$data |> 
  as.tibble() |> 
  site_conv_mutate(site = pri.fac) |> 
  mutate(activity = replace_values(activity |> as.character(), 
                                   from = mHLEA_labels$name, to = mHLEA_labels$value |> str_remove(" \\(activity\\)")),
         activity = replace_values(activity, 
                                   "outdoor" ~ "Outdoor (free, working, on the road)")) |> 
    ggplot(
      aes(x = the.emmean, y = pri.fac)
    ) +
    geom_crossbar(aes(xmin = lcl, xmax = ucl, fill = pri.fac), col = NA, alpha = 0.7) +
    geom_point() +
  theme_cowplot() +
  theme(panel.grid.major = element_line(color = "grey")) +
  scale_fill_manual(values = melidos_colors) +
  facet_wrap(~activity, ncol = 1) +
  scale_x_continuous(trans = "symlog", breaks = c(0, 10^(0:5), 250, 5000)) +
  coord_cartesian(xlim = c(0, 5000)) +
  labs(y = NULL, x = "Melanopic EDI (lx), based on hourly geometric mean") +
    guides(fill = "none")

H3_plot + H4_plot
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_crossbar()`).
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_point()`).

Code
ggsave("figures/Fig5.pdf", width = 15, height = 12)
Warning: Removed 4 rows containing missing values or values outside the scale range
(`geom_crossbar()`).
Removed 4 rows containing missing values or values outside the scale range
(`geom_point()`).
Code
activity_data |> 
    filter_out(is.na(activity)) |> 
    site_conv_mutate(rev = FALSE) |> 
    ggplot(aes(x=geo.MEDI)) +
    geom_density_ridges(aes(y=activity),
                        linewidth = 1, colour = NA,
                        alpha = 0.5,
                        from = 0,
    ) +
    geom_density_ridges(aes(y=activity), fill = NA,
                        alpha = 1, linewidth = 1,
                        quantile_lines = TRUE, quantiles = 2, vline_color = "red",
                        linetype = 1,
                        from = 0,
    ) +
    # scale_fill_manual(values = melidos_colors) +
    scale_alpha_continuous(range = c(0, 1)) +
    scale_x_continuous(trans = "symlog", breaks = c(0, 10^(0:5)),
                       labels = expression(0, 1, 10, 10^2, 10^3, 10^4, 10^5)) +
    # scale_color_manual(values = melidos_colors) +
    guides(fill = "none", color = "none") +
    labs(y = NULL, x = NULL) +
    theme_ridges() +
    coord_cartesian(xlim = c(0, 10^5)) +
    theme_sub_plot(margin = margin(r = 20, t = 20)) +
    theme_sub_axis_left(text = element_text(vjust = 0))
Picking joint bandwidth of 0.134
Picking joint bandwidth of 0.134

Interpretation

  • Overall, an average mel EDI of about 60 lx (based on geometric mean) is reached with awake at home. This value somewhat varies between sites, with The Netherlands, Dortmund, and Sweden, achieving substantially higher values (factor 2.35, 2.03, and 1.76, respectively). They nevertheless do not reach recommended values of 250lx during the day. Tübingen, Germany, and Ghana reach substantially lower values (factor 0.46 and 0.32 respectively).
  • When Sleeping in bed, about 4% of the mel EDI can be achieved on average (2.3936). There is very high variance between the different sites, with factor ranging between 44% of that value (Tübingen, Germany) to 757% (The Munich).
  • When people are On the road, they achieve about 4.88 times more light compared to at home (292 lx). Dortmund, Germany, achieves still higher values (times 2.04), while Tübingen, Germany has only about one third (0.35).
  • Working indoors increases the melanopic EDI by a factor of 3.23 (193 lx). The Netherlands and Sweden reach higher levels still (1.8 and 1.79, respectively), while Tübingen, Germany, and Ghana see lower levels of increase (0.57 and 0.62, respectively).
  • Outdoors translates to about x8.82 the amount of light levels compared to at home (~528 lx). Values are similar across sites, with three notable exceptions: Sweden and The Netherlands have significantly higher values still (3.33 and 2.24, respectively), while Munich, Germany sees much lower values (0.23).
  • Overall, the effect of self-reported activity is large - 62% of variance can be explained simply through those categories. While site and interindividual differences are significant, they contribute considerably less (9% each). In summary, activity is a good, albeit worse predictor compared to self-reported light source

Co-occurance of light and activity

In [29]:
Code
activity_data |> 
  count(activity, lightsource_primary) |> 
  pivot_wider(names_from = activity, values_from = n) |> 
  drop_na() |> 
  gt()
lightsource_primary home sleep road_vehicle working_indoor outdoor
Electric light source indoors 4179 183 365 3437 251
Electric light source outdoors 29 4 56 4 242
Daylight indoors 4438 216 493 3184 372
Daylight outdoors (including shade) 216 23 634 152 1995
Emissive display light 667 71 17 384 33
Darkness during sleep 289 8880 16 10 18

As can be seen by the cross-table, the two variables are strongly correlated. However, some differentiating potential lies e.g., in the home and working indoor activity, that provides a very even split between electric light indoors, daylight indoors, and emissive display light. Similarly, daylight outdoors can be split between outdoor and on the road activities.

Hypothesis 5

Hypothesis 5 states:

There is a significant relationship between LEBA-factors and pre-selected personal light exposure metrics.

Type of model: Correlation matrix + LMM

Base formula: \(\text{mel EDI} \sim \text{mH-LEA}*\text{Site} + (1|\text{Site}:\text{Participant})\)

Notes: - site as a random effect with random slopes for mH-LEA was unstable. Thus, site will be used as an interaction - mH-LEA data was used to collect the primary_lightsource, which is the variable used in the model - The confirmatory test looks at the base model compared to the Null model for mH-LEA, the other comparisons are exploratory - Site was sum-coded, so as to calculate an average effect of site - Error family is Tweedie

Specific formulas: - \(\text{mel EDI} \sim \text{Site} + (1|\text{Site}:\text{Participant})\) (Null-model, mH-LEA) - \(\text{mel EDI} \sim \text{mH-LEA} + \text{Site} + (1|\text{Site}:\text{Participant})\) (Null-model interaction) - \(\text{mel EDI} \sim \text{mH-LEA} + (1|\text{Site}:\text{Participant})\) (Null-model, Site)

False-discovery-rate correction: - 4 leba factors x 17 metrics: n = 68

Preparation

In [30]:
Code
Collect LEBA data
leba <- load_data("leba") |> flatten_data() |> select(site, Id, leba_f2:leba_f5)
leba_labels <-
  leba |> extract_labels() |> enframe() |> filter(str_detect(name, "leba_f.$"))
In [31]:
Code
load("data/H1_results.RData")
metrics <- metrics |> select(name, type, metric_type, data, engine, response, family, H1_1_sig, H1_lat_sig, H1_phot_sig)
metrics_leba <-
  metrics |> 
  filter_out(is.na(response)) |> 
  mutate(
    data = 
      map(data, 
          \(x) x |> 
            group_by(site, Id) |> 
            summarize(metric = mean(metric, na.rm = TRUE), .groups = "drop_last") |> 
            left_join(leba, by = c("site", "Id")) |> 
            pivot_longer(leba_f2:leba_f5, names_to = "leba")
          )
  ) |> 
  select(name, type, metric_type, data) |> 
  unnest(data)

Analaysis

In [32]:
Code
Test whether sites vary in their leba factors

# test <-
leba |> 
  mutate(Id = fct(Id),
         site = fct(site)) |> 
  summarize(across(leba_f2:leba_f5,
                    \(x) {
                      data <- pick(cur_column(), site)
                      lm(x~ site, data = data) |> list()
                         }
                    )
                ) |> 
  pivot_longer(everything()) |> 
  mutate(marginals = map(value, \(x) emmeans(x, ~ site)),
         test = map(marginals, \(x)  joint_tests(x) |> as.tibble()),
         contrasts = map(marginals, contrast)) |> 
  unnest(test) |> 
  mutate(p.value = p.adjust(p.value, "fdr", n=4),
         signif = p.value <= sig.level) |> 
  filter(signif) |> 
  select(-p.value) |> 
  mutate(contrasts = map(contrasts, as.tibble)) |> 
  unnest(contrasts) |> 
  filter(p.value <= sig.level) |> 
  select(name, contrast, estimate, SE, df, p.value, signif) |> 
  gt() |> 
  fmt_number() |> 
  fmt(p.value, fns= style_pvalue) |> 
  tab_header("Significant differences in LEBA factors dependent on sites")
Significant differences in LEBA factors dependent on sites
name contrast estimate SE df p.value signif
leba_f2 UCR effect −2.70 0.62 175.00 <0.001 TRUE
leba_f5 BAUA effect −2.24 0.74 175.00 0.025 TRUE

Only to factors are significantly different: F2 and F5. Of those, only UCR for F2 and BAUA for F5 show significant differences from the mean, both being 2-3 points lower (post-hoc test). Thus, we deem that the factors can be analysed independent of site.

In [33]:
Code
create correlation data
H5_corr_matrix <-
  metrics_leba |> 
  group_by(name, leba) |> 
  drop_na(metric, value)

H5_n <- n_groups(H5_corr_matrix)

H5_corr_matrix <- 
H5_corr_matrix |> 
  summarize(
      correlation = cor(metric, value, method = "spearman"),
              p_value = cor.test(metric, value)$p.value |> 
                        p.adjust(method = "fdr", n = H5_n),
              significant = p_value <= sig.level,
              .groups = "drop") |> 
  mutate(
    name = name |> str_replace_all("_", " ") |> str_to_sentence(),
    labels = replace_values(leba, from = leba_labels$name, to = leba_labels$value) |> 
      fct() |> fct_rev()
  )

Visualization

In [34]:
Code
Visualize correlation matrix

H5_corr_matrix |> 
  ggplot(aes(x = name, y = labels, fill = abs(correlation))) +
  geom_blank() +
  geom_tile(data = H5_corr_matrix |> filter(significant), linewidth = 0.25) + 
  geom_text(
    aes(label = 
          paste(
            paste("c=",round(correlation, 2)), 
            p_value %>% style_pvalue(prepend_p = TRUE), 
            sep = "\n"),
        color = significant
        ),
    fontface = "bold",
    size = 1.8) +
  coord_fixed() +
  scale_fill_viridis_c(limits = c(0,1))+
  guides(color = "none", fill = "none")+
  scale_color_manual(values = c("red2", "white"), drop = FALSE)+
  labs(y = "LEBA factors", x = "Metrics",
       fill = "(abs) Correlation")+
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(caption = paste0("p-values are fdr-corrected for n= ", H5_n, " comparisons"))

Code

ggsave("figures/Fig6.pdf", width = 12, height = 5)

Interpretation

  • There is no overarching corelation between light exposure metrics and LEBA factors. We do find a significant correlation of Dose and Duration above 1000 for F2: Spending time outdoors, which makes sense from a theoretical point of view (spending more time outside would yield in a higher dose and duration in daylight levels). These correlations are small in size and only explain a small amount of variance. Dose: \(r=0.27, R^2=0.07, p=0.013\); Duration above 250 lx melanopic EDI: \(r=0.29, R^2=0.08, p=0.032\)

Hypothesis 6

Details

Hypothesis 6 states:

There is a significant relationship between the day type, daily exercise, and sleep with the hourly geometric mean of light exposure

Type of model: GLMM

Base formula: - \(\text{mel EDI} \sim \text{day type}*\text{Site} + \text{daily exercise}*\text{Site} + \text{sleep duration}*\text{Site} +(1|\text{Participant})\)

Notes: - site as a random effect with random slopes was unstable. Thus, site will be used as an interaction - The confirmatory tests look at the base model compared to the Null models for each covariate - Site was sum-coded, so as to calculate an average effect of site - Sleep duration is based on the previous night

Specific formulas: - \(\text{Metric} \sim \text{daily exercise}*\text{Site} + \text{sleep duration}*\text{Site}+ (1|\text{Participant})\) (Null model day type) - \(\text{Metric} \sim \text{day type}*\text{Site} + \text{sleep duration}*\text{Site}+ (1|\text{Participant})\) (Null model exercise) - \(\text{Metric} \sim \text{day type}*\text{Site} + \text{daily exercise}*\text{Site} + (1|\text{Participant})\) (Null model sleep duration)

False-discovery-rate correction: - 3 main effects: n=3; site: n=9

Preparation

In [35]:
Code
Load other data types
sleep <- load_data("sleepdiaries") |> flatten_data()
loading modality: sleepdiary ■■■■■■■■■■■■■■■■■■■■■             67% |  ETA:  1s
Warning in flatten_data(load_data("sleepdiaries")): Not all labels across all
sites were identical. Labels used from: BAUA; sites that have other labels:
RISE
Code
exercise <- load_data("exercisediary") |> flatten_data()
loading modality: exercisediary ■■■■■■■■■■■■■■■■■■■■■             67% |  ETA:  …
Code
sleep <-
  sleep |> 
    mutate(Date = date(wake),
           daytype = daytype2 |> fct_relevel("a work day"),
           sleep_duration = sleep_duration |> as.numeric()) |> 
    select(site, Id, Date, sleep_duration, daytype)

exercise <- 
exercise |> 
  select(site, Id, Date, intensity) |> 
  mutate(intensity = fct_relabel(intensity, \(x) str_extract(x, "^(\\S+)")) |> 
           fct_relevel("None", "Light", "Moderate")) |> 
  rename(exercise = intensity)

H6_contextdata <- 
  full_join(sleep, exercise, by = c("site", "Id", "Date")) 
In [36]:
Code
Merge data
H6_data <-
  hourly_data2 |> 
  left_join(H6_contextdata, by = c("site", "Id", "Date")) |> 
  add_Date_col(Weekday, as.wday = TRUE) |> 
  mutate(Weekend = ifelse(as.numeric(Weekday) >5, "Weekend", "Weekday"))
In [37]:
Code
H6_contextdata <- 
  H6_contextdata |> 
  mutate(Date = factor(Date))
In [38]:
Code
Plot overview of light against self-reported activity
H6_data |> 
  drop_na(daytype) |> 
  ggplot(aes(x=daytype, y = geo.MEDI)) +
  geom_boxplot() +
  scale_y_continuous(trans = "symlog", 
                     breaks = c(0,1,10,100,1000,10000,10^5),
                     labels = expression(0,1,10,10^2,10^3,10^4,10^5)) +
  theme_cowplot() +
  labs(y = "Melanopic EDI (lx)", x = "Day type")
Warning: Removed 1168 rows containing non-finite outside the scale range
(`stat_boxplot()`).

Code
H6_data |> 
  drop_na(exercise) |> 
  ggplot(aes(x=exercise, y = geo.MEDI)) +
  geom_boxplot() +
  scale_y_continuous(trans = "symlog", 
                     breaks = c(0,1,10,100,1000,10000,10^5),
                     labels = expression(0,1,10,10^2,10^3,10^4,10^5)) +
  theme_cowplot() +
  labs(y = "Melanopic EDI (lx)", x = "Exercise")
Warning: Removed 1062 rows containing non-finite outside the scale range
(`stat_boxplot()`).

Code
H6_data |> 
  drop_na(sleep) |> 
  ggplot(aes(x=sleep_duration, y = geo.MEDI)) +
  geom_point() +
  geom_smooth() +
  scale_y_continuous(trans = "symlog", 
                     breaks = c(0,1,10,100,1000,10000,10^5),
                     labels = expression(0,1,10,10^2,10^3,10^4,10^5)) +
  theme_cowplot() +
  labs(y = "Melanopic EDI (lx)", x = "Sleep duration (hrs)")
`geom_smooth()` using method = 'gam' and formula = 'y ~ s(x, bs = "cs")'
Warning: Removed 1089 rows containing non-finite outside the scale range
(`stat_smooth()`).
Warning: Removed 1089 rows containing missing values or values outside the scale range
(`geom_point()`).

In [39]:
Code
Prepare model formulas
metrics <- 
metrics |> 
    mutate(H6_1 = case_when(
                                type == "participant-day" ~ 
                                  "~ site*daytype + site*exercise + site*sleep_duration + (1| Id)"),
         H6_0_daytype = case_when(
                                 type == "participant-day" ~ 
                                  "~ site*exercise + site*sleep_duration + (1| site:Id)"),
         H6_0_exercise = case_when(
                                 type == "participant-day" ~ 
                                  "~ site*daytype + site*sleep_duration + (1|site:Id)"),
         H6_0_sleep = case_when(
                                 type == "participant-day" ~ 
                                  "~ site*daytype + site*exercise + (1|site:Id)"),
         data = data |> map2(type, \(x, type) if(type == "participant") {
           return(x)
           } else {
           x |> left_join(H6_contextdata, by = c("site", "Id", "Date")) |> 
               mutate(sleep_duration = sleep_duration |> as.numeric())
         } 
         )
    )

H6_formulas <-  names(metrics) |> subset(grepl("H6_", names(metrics)))

metrics <- metrics |> relocate(all_of(H6_formulas), .after = family)
H6_fdr_n <- metrics |> filter_out(when_any(is.na(response), type == "participant")) |> nrow() *3
In [40]:
Code cell - Formula creation
metrics <-
  metrics |> 
  mutate(across(all_of(H6_formulas),
                \(x) map2(x, response, \(y,z) make_formula(z, y))
                )
         )

Analysis

In [41]:
Code
Fit the model for H6
H6_formula_1 <- geo.MEDI ~ site*daytype + site*exercise + site*sleep_duration + (1| Id)
H6_formula_0_daytype <- geo.MEDI ~ site*exercise + site*sleep_duration + (1| Id)
H6_formula_0_exercise <- geo.MEDI ~ site*daytype + site*sleep_duration + (1|Id)
H6_formula_0_sleep <- geo.MEDI ~ site*daytype + site*exercise + (1|Id)
H6_formula_0_site <- geo.MEDI ~ daytype + exercise + sleep_duration + (1|Id)

#confirmatory
H6_model_1 <- 
  glmmTMB(H6_formula_1, 
          data = H6_data |> drop_na(geo.MEDI, daytype, exercise, sleep_duration), 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
H6_model_0_daytype <- 
  glmmTMB(H6_formula_0_daytype, 
          data = H6_data |> drop_na(geo.MEDI, daytype, exercise, sleep_duration), 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
H6_model_0_exercise <- 
  glmmTMB(H6_formula_0_exercise, 
          data = H6_data |> drop_na(geo.MEDI, daytype, exercise, sleep_duration), 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
H6_model_0_sleep <- 
  glmmTMB(H6_formula_0_sleep, 
          data = H6_data |> drop_na(geo.MEDI, daytype, exercise, sleep_duration),
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )
H6_model_0_site <- 
  glmmTMB(H6_formula_0_site, 
          data = H6_data |> drop_na(geo.MEDI, daytype, exercise, sleep_duration),
          REML = FALSE, 
          family = tweedie(),
          # contrasts = list(site = "contr.sum")
          )

comp_daytype <- 
anova(H6_model_0_daytype, H6_model_1)
comp_daytype
Data: drop_na(H6_data, geo.MEDI, daytype, exercise, sleep_duration)
Models:
H6_model_0_daytype: geo.MEDI ~ site * exercise + site * sleep_duration + (1 | Id), zi=~0, disp=~1
H6_model_1: geo.MEDI ~ site * daytype + site * exercise + site * sleep_duration + , zi=~0, disp=~1
H6_model_1:     (1 | Id), zi=~0, disp=~1
                   Df    AIC    BIC  logLik deviance Chisq Chi Df Pr(>Chisq)
H6_model_0_daytype 48 292692 293094 -146298   292596                        
H6_model_1         57 292507 292985 -146197   292393 202.8      9  < 2.2e-16
                      
H6_model_0_daytype    
H6_model_1         ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
comp_exercise <- 
anova(H6_model_0_exercise, H6_model_1)
comp_exercise
Data: drop_na(H6_data, geo.MEDI, daytype, exercise, sleep_duration)
Models:
H6_model_0_exercise: geo.MEDI ~ site * daytype + site * sleep_duration + (1 | Id), zi=~0, disp=~1
H6_model_1: geo.MEDI ~ site * daytype + site * exercise + site * sleep_duration + , zi=~0, disp=~1
H6_model_1:     (1 | Id), zi=~0, disp=~1
                    Df    AIC    BIC  logLik deviance  Chisq Chi Df Pr(>Chisq)
H6_model_0_exercise 30 292939 293190 -146440   292879                         
H6_model_1          57 292507 292985 -146197   292393 485.69     27  < 2.2e-16
                       
H6_model_0_exercise    
H6_model_1          ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
comp_sleep <- 
anova(H6_model_0_sleep, H6_model_1)
comp_sleep
Data: drop_na(H6_data, geo.MEDI, daytype, exercise, sleep_duration)
Models:
H6_model_0_sleep: geo.MEDI ~ site * daytype + site * exercise + (1 | Id), zi=~0, disp=~1
H6_model_1: geo.MEDI ~ site * daytype + site * exercise + site * sleep_duration + , zi=~0, disp=~1
H6_model_1:     (1 | Id), zi=~0, disp=~1
                 Df    AIC    BIC  logLik deviance  Chisq Chi Df Pr(>Chisq)    
H6_model_0_sleep 48 292577 292979 -146241   292481                             
H6_model_1       57 292507 292985 -146197   292393 88.014      9  4.071e-15 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Code
AIC(H6_model_0_sleep, H6_model_0_exercise, H6_model_0_daytype, H6_model_1) |> 
  arrange(AIC) |> 
  rownames_to_column("model") |> gt()
model df AIC
H6_model_1 57 292507.4
H6_model_0_sleep 48 292577.4
H6_model_0_daytype 48 292692.2
H6_model_0_exercise 30 292939.1

Both AIC and ∆loglik comparisons suggest that the full interaction model is the most relevant one.

In [42]:
Code
check_model(H6_model_1)
`check_outliers()` does not yet support models of class `glmmTMB`.
Cannot simulate residuals for models of class `glmmTMB`. Please try
  `check_model(..., residual_type = "normal")` instead.

In [43]:
Code
tab_model(H6_model_1, CSS = css_theme("cells"))
  geo.MEDI
Predictors Estimates CI p
(Intercept) 147.15 114.32 – 189.42 <0.001
site1 1.02 0.52 – 1.98 0.956
site2 2.47 1.28 – 4.76 0.007
site3 1.17 0.68 – 2.02 0.565
site4 0.26 0.13 – 0.53 <0.001
site5 0.43 0.26 – 0.71 0.001
site6 1.48 0.73 – 2.99 0.279
site7 0.37 0.16 – 0.85 0.019
site8 4.50 2.01 – 10.09 <0.001
My day involved the
following type of
physical activity: None
0.92 0.86 – 0.98 0.006
My day involved the
following type of
physical activity:
Moderate
1.88 1.73 – 2.04 <0.001
My day involved the
following type of
physical activity:
Vigorous
1.77 1.59 – 1.98 <0.001
NA 1.52 1.34 – 1.72 <0.001
sleep_duration 0.92 0.90 – 0.94 <0.001
site1:daytypea free day 1.96 1.66 – 2.30 <0.001
site2:daytypea free day 0.62 0.52 – 0.74 <0.001
site3:daytypea free day 0.71 0.60 – 0.82 <0.001
site4:daytypea free day 0.69 0.58 – 0.83 <0.001
site5:daytypea free day 0.93 0.82 – 1.04 0.208
site6:daytypea free day 1.38 1.16 – 1.63 <0.001
site7:daytypea free day 1.97 1.67 – 2.32 <0.001
site8:daytypea free day 1.06 0.90 – 1.25 0.502
site1:exerciseLight 0.63 0.52 – 0.77 <0.001
site2:exerciseLight 0.60 0.49 – 0.73 <0.001
site3:exerciseLight 0.67 0.56 – 0.80 <0.001
site4:exerciseLight 3.16 2.44 – 4.09 <0.001
site5:exerciseLight 0.78 0.66 – 0.93 0.006
site6:exerciseLight 1.09 0.90 – 1.30 0.377
site7:exerciseLight 1.95 1.46 – 2.59 <0.001
site8:exerciseLight 0.58 0.47 – 0.73 <0.001
site1:exerciseModerate 0.76 0.61 – 0.94 0.012
site2:exerciseModerate 1.02 0.80 – 1.30 0.881
site3:exerciseModerate 0.90 0.65 – 1.24 0.511
site4:exerciseModerate 8.98 6.37 – 12.65 <0.001
site5:exerciseModerate 0.77 0.62 – 0.95 0.015
site6:exerciseModerate 0.75 0.58 – 0.97 0.031
site7:exerciseModerate 1.39 1.01 – 1.89 0.041
site8:exerciseModerate 0.57 0.40 – 0.83 0.003
site1:exerciseVigorous 0.69 0.52 – 0.92 0.011
site2:exerciseVigorous 0.53 0.39 – 0.74 <0.001
site3:exerciseVigorous 0.76 0.57 – 1.00 0.049
site4:exerciseVigorous 16.13 9.66 – 26.94 <0.001
site5:exerciseVigorous 1.12 0.88 – 1.41 0.355
site6:exerciseVigorous 0.68 0.50 – 0.93 0.016
site7:exerciseVigorous 0.70 0.52 – 0.94 0.018
site8:exerciseVigorous 0.57 0.39 – 0.83 0.003
site1:sleep_duration 1.05 0.98 – 1.13 0.152
site2:sleep_duration 0.93 0.87 – 1.00 0.036
site3:sleep_duration 1.00 0.96 – 1.05 0.986
site4:sleep_duration 0.95 0.88 – 1.02 0.139
site5:sleep_duration 1.10 1.06 – 1.16 <0.001
site6:sleep_duration 1.03 0.96 – 1.11 0.424
site7:sleep_duration 1.15 1.07 – 1.25 <0.001
site8:sleep_duration 0.87 0.80 – 0.94 <0.001
Random Effects
σ2 1.57
τ00Id 0.77
ICC 0.33
N Id 137
Observations 32006
Marginal R2 / Conditional R2 0.155 / 0.434
In [44]:
Code
H6_r2_1 <- r2_helper(H6_model_1, H6_data, tweedie(), "geo.MEDI", "(1 | Id)")
H6_r2_0_daytime <- r2_helper(H6_model_0_daytype, H6_data, tweedie(), "geo.MEDI", "(1 | Id)")
H6_r2_0_exercise <- r2_helper(H6_model_0_exercise, H6_data, tweedie(), "geo.MEDI", "(1 | Id)")
H6_r2_0_sleep <- r2_helper(H6_model_0_sleep, H6_data, tweedie(), "geo.MEDI", "(1 | Id)")
H6_r2_0_site <- r2_helper(H6_model_0_site, H6_data, tweedie(), "geo.MEDI", "(1 | Id)")
H6_tab_r2 <-
  tribble(
    ~type, ~r2,
    "Model", H6_r2_1$R2_conditional,
    "All fixed", H6_r2_1$R2_marginal,
    "Residual", 1-H6_r2_1$R2_conditional,
    "Daytype", H6_r2_1$R2_marginal - H6_r2_0_daytime$R2_marginal,
    "Exercise", H6_r2_1$R2_marginal - H6_r2_0_exercise$R2_marginal,
    "Sleep", H6_r2_1$R2_marginal - H6_r2_0_sleep$R2_marginal,
    "Random", H6_r2_1$R2_conditional - H6_r2_1$R2_marginal,
    "Site", H6_r2_1$R2_marginal - H6_r2_0_site$R2_marginal,
  ) |> 
  mutate(r2= r2 |> round(2))

Basic table

In [45]:
Code
H6_tab_ran <- 
  H6_model_1 |> 
  tidy() |> 
  mutate(signif = p.value <= sig.level,
         estimate = exp(estimate) |> round(2)) |> 
  filter(effect == "ran_pars") |> 
  select(term, estimate)

H6_tab_reference <-
  H6_model_1 |>
  emmeans(~ daytype + exercise + sleep_duration, type = "response",
          at = list(daytype = "a work day",
                    exercise = "None"
                    )) |> 
  as.tibble() |> 
  rename(estimate = response)

H6_tab_daytype <- 
  H6_model_1 |> 
  emmeans(~ daytype, type = "response") |> 
  contrast(method = "trt.vs.ctrl") |> 
  as.tibble() |> 
  rename(estimate = ratio)
NOTE: Results may be misleading due to involvement in interactions
Code
H6_tab_exercise <- 
  H6_model_1 |> 
  emmeans(~ exercise, type = "response") |> 
  contrast(method = "trt.vs.ctrl") |> 
  as.tibble() |> 
  rename(estimate = ratio)
NOTE: Results may be misleading due to involvement in interactions
Code
H6_tab_sleep <-
  H6_model_1 |> 
  emtrends(~ sleep_duration, var = "sleep_duration", type = "response") |> 
  as.tibble() |> 
  rename(estimate = sleep_duration.trend) |> 
  mutate(p.value = comp_sleep$`Pr(>Chisq)`[2],
         estimate = exp(estimate))
NOTE: Results may be misleading due to involvement in interactions
Code
H6_tab_daytype_interaction <-
  H6_model_1 |> 
  emmeans(~ site | daytype, type = "response") |> 
  contrast() |> 
  as.tibble() |> 
  rename(estimate = ratio)

H6_tab_exercise_interaction <-
  H6_model_1 |> 
  emmeans(~ site | exercise, type = "response") |> 
  contrast() |> 
  as.tibble() |> 
  rename(estimate = ratio)

H6_tab_sleep_interaction <-
  H6_model_1 |> 
  emtrends(~ site | sleep_duration, var = "sleep_duration", type = "response") |> 
  contrast() |> 
  as.tibble() |> 
  mutate(estimate = exp(estimate))

H6_tab_fix <-
  bind_rows(H6_tab_reference, 
            H6_tab_daytype, 
            H6_tab_exercise, 
            H6_tab_sleep, 
            H6_tab_daytype_interaction,
            H6_tab_exercise_interaction,
            H6_tab_sleep_interaction
            ) |> 
  mutate(signif = p.value <= sig.level) |> 
  select(-c(df, asymp.LCL, asymp.UCL, null, z.ratio)) |> 
  relocate(contrast, .before = 1) |> 
  mutate(site = contrast |> as.character(),
         daytype = 
           daytype |> as.character() |> 
           replace_when(
             str_detect(contrast, "a work day") ~ str_remove(contrast, " / a work day")
           ),
         exercise = 
           exercise |> as.character() |> 
           replace_when(
             str_detect(contrast, "None") ~ str_remove(contrast, " / None")
           ),
         site = replace_when(site, 
                             is.na(site) ~ "Overall",
                             str_detect(site, " effect") ~ str_remove(site, " effect"),
                             str_detect(contrast, "a work day") ~ "Overall",
                             str_detect(contrast, "None") ~ "Overall"),
         sleep = ifelse(is.na(sleep_duration), NA, "sleep"),
         .before = 1
                              
  ) |> 
  select(-contrast, -sleep_duration) |> 
  pivot_longer(sleep:exercise, names_to = "covariate") |> 
    drop_na(value) |> 
    filter_out(site == "Overall", value == "sleep", is.na(p.value)) |> 
  pivot_wider(id_cols = c(site), names_from = c(covariate, value), values_from = estimate:signif)
In [46]:
Code
H6_tab <-
H6_tab_fix |> 
  site_conv_mutate(rev = FALSE, other.levels = "Overall") |> 
  arrange(site) |> 
  gt() |> 
  fmt(starts_with("estimate"), 
      fns = \(x) 
      {x <- x |> gt::vec_fmt_number()
      str_c("×", x)}
      ) |> 
  fmt(columns = 2:3, 
      rows = site == "Overall",
      fns = \(x) 
      {x <- x |> gt::vec_fmt_number()
      str_c(x, " lx")}
      ) |> 
  cols_label_with(
    fn = 
      \(x) str_remove(x, "estimate_") |> 
            str_remove("daytype_") |> 
            str_remove("sleep_") |> 
            str_remove("exercise_") |> 
            str_to_sentence()
    )|> 
  tab_spanner(
    md(
      paste0("Day type ($p = ",
             comp_daytype$`Pr(>Chisq)`[2] |> style_pvalue(),
             "$, $R^2=", H6_tab_r2$r2[4], 
             "$)")
      ), 
    columns = contains("daytype_")) |> 
  tab_spanner(
    md(
      paste0("Exercise ($p = ",
             comp_exercise$`Pr(>Chisq)`[2] |> style_pvalue(),
             "$, $R^2=", H6_tab_r2$r2[5], 
             "$)")
      ), 
    columns = contains("exercise_"))|> 
      cols_hide((contains(c("p.value_", "signif_")))) |>
      cols_hide((starts_with(c("SE_")))) |>
  # sub_missing() |> 
  gt_multiple(
    names(H6_tab_fix) |> 
      subset(str_detect(names(H6_tab_fix), "estimate_")) |> 
      str_remove("estimate_"),
    bold_p.) |> 
  tab_style_body(
    style = cell_borders(weight = px(2)),
    rows = 1,
    columns = 2:3,
    fn = \(x) TRUE
  ) |> 
  gt_multiple(
    melidos_colors |> names(),
    style_rows
  ) |> 
  tab_style(
    style = cell_text(weight = "bold"),
    locations = list(
      cells_column_labels(),
      cells_column_spanners(),
      cells_body(columns = site),
      cells_body(2:3,1)
    )
  ) |> 
  cols_label(
    site = md(paste0("Site<br>($p = ",
             comp_site$`Pr(>Chisq)`[2] |> style_pvalue(),
             "$, $R^2=", H6_tab_r2$r2[8], 
             "$)")),
    estimate_sleep_sleep =  md(
      paste0("Sleep<br>($p = ",
             comp_sleep$`Pr(>Chisq)`[2] |> style_pvalue(),
             "$, $R^2=", H6_tab_r2$r2[6], 
             "$)")
      )
    ) |> 
  tab_header(
    title = "Model results for Hypothesis 6",
    subtitle = H6
  ) |> 
  tab_footnote(
    md("Values shown in **bold** are significant at $p<0.05$. False-discovery-rate adjustment for n=3 overall, n=3 in exercise, and n=9 in site")
  ) |> 
  cols_width(
             site ~ px(150),
             estimate_sleep_sleep ~ px(150),
             everything() ~ px(100)) |> 
    tab_footnote(
    "Reference value, for `A work day` with an average of `7.9 hours of sleep`, and exercise `None`. All other values have to be multiplied by the reference value, their respective `Overall` multiplier, and optionally the site interaction to get the estimated mel EDI for a given combination.", locations = cells_body(2:3,1)
  ) |> 
  tab_footnote(
    md(paste0("Conditional (Model) $R^2=", 
              H6_tab_r2$r2[1],
              "$, Marginal (Fixed effects) $R^2=",
              H6_tab_r2$r2[2],
              "$, Random effect $R^2=",
              H6_tab_r2$r2[7],
              "$, Residual Variance $R^2=",
              H6_tab_r2$r2[3],
              "$; Random effect of participant: $×",
              H6_tab_ran$estimate[1],
              "$; Model based on ",
              H6_data |> drop_na(geo.MEDI, exercise, daytype, sleep_duration) |> nrow() |> vec_fmt_integer(sep_mark = "'"),
              " participant-hours."
              ))
  ) 
In [47]:
Code
H6_tab
Model results for Hypothesis 6
Hypothesis: There is a significant relationship between the day type, daily exercise, and sleep with the hourly geometric mean of light exposure
Site
(\(p = <0.001\), \(R^2=0.13\))
Day type (\(p = <0.001\), \(R^2=0.01\))
Exercise (\(p = <0.001\), \(R^2=0.05\))
Sleep
(\(p = <0.001\), \(R^2=0.02\))
A work day A free day None Light Moderate Vigorous
Overall 1 75.97 lx ×0.92 1 75.97 lx ×1.88 ×1.77 ×1.52 ×0.92
Borås (SE) ×1.61 ×2.21 ×2.19 ×2.37 ×1.64 ×1.49 ×1.03
Delft (NL) ×1.32 ×2.59 ×1.57 ×3.06 ×2.18 ×1.10 ×1.15
Dortmund (DE) ×1.15 ×2.25 ×2.12 ×1.34 ×1.60 ×1.47 ×1.05
Tübingen (DE) ×0.85 ×0.78 ×0.90 ×0.70 ×0.69 ×1.00 ×1.10
Munich (DE) ×0.98 ×1.04 ×1.53 ×0.89 ×0.88 ×0.87 ×0.87
Madrid (ES) ×1.08 ×0.67 ×1.13 ×0.68 ×1.15 ×0.60 ×0.93
Izmir (TR) ×0.97 ×0.68 ×0.99 ×0.66 ×0.89 ×0.75 ×1.00
San José (CR) ×0.60 ×0.38 ×0.63 ×0.81 ×0.22 ×0.46 ×0.95
Kumasi (GH) ×0.79 ×0.55 ×0.14 ×0.45 ×1.27 ×2.29 ×0.95
Values shown in bold are significant at \(p<0.05\). False-discovery-rate adjustment for n=3 overall, n=3 in exercise, and n=9 in site
Conditional (Model) \(R^2=0.43\), Marginal (Fixed effects) \(R^2=0.15\), Random effect \(R^2=0.28\), Residual Variance \(R^2=0.57\); Random effect of participant: \(×2.41\); Model based on 32’006 participant-hours.
1 Reference value, for `A work day` with an average of `7.9 hours of sleep`, and exercise `None`. All other values have to be multiplied by the reference value, their respective `Overall` multiplier, and optionally the site interaction to get the estimated mel EDI for a given combination.
Code
gtsave(H6_tab, "tables/H6.png", vwidth = 1200)
file:////var/folders/9p/326_k3kx43qbn_cyl1rqfhb00000gn/T//RtmputNk5I/file14a697bda55ff.html screenshot completed

Visualization

In [48]:
Code
H6_plot <- 
H6_model_1 |> 
  emmeans(~ site | daytype, type = "response") |> 
  plot()

H6_plot <-
  H6_plot$data |> 
  as.tibble() |> 
  site_conv_mutate(site = pri.fac) |> 
    ggplot(
      aes(x = the.emmean, y = pri.fac)
    ) +
    geom_crossbar(aes(xmin = lcl, xmax = ucl, fill = pri.fac), col = NA, alpha = 0.7) +
    geom_point() +
  theme_cowplot() +
  theme(panel.grid.major = element_line(color = "grey")) +
  scale_fill_manual(values = melidos_colors) +
  facet_wrap(~daytype, ncol = 1) +
  scale_x_continuous(breaks = seq(0, 600, by = 50)) +
  coord_cartesian(xlim = c(0, 500), ylim = c(0, 10), expand = FALSE, clip = "off") +
  labs(y = NULL, x = "Melanopic EDI (lx), based on hourly geometric mean") +
    guides(fill = "none") +
  theme(plot.margin = margin(10, 20, 10, 10))

H6_plot

Code
ggsave("figures/Fig_H6.pdf", width = 7, height = 7)
In [49]:
Code
H6_model_1 |> 
  emmeans(~ site | exercise, type = "response") |> 
  plot()

In [50]:
Code
H6_model_1 |> 
  emtrends(~ site | sleep_duration, var = "sleep_duration", type = "response",
           at = list(sleep_duration = 8)) |> 
  plot() +
  labs(caption = "Trend is on a log-scale")

Interpretation

  • While there is no overall difference between work days and free days, Sweden, The Netherlands, and Dortmund, Germany show substantially higher melanopic EDI on free days (factors of 2.22, 2.60, and 2.23, respectively), while Costa Rica and Ghana have substantiall lower values (0.39 and 0.53, respectively).
  • For exercise, the overall effect is a substantially higher melanopic EDI as long as there is some amount of exercise (>1.50). Sweden and The Netherlands show partially higher values for activity levels below Vigorous (>2.14), while Ghana, have very low values for No or light exercise (0.15 and 0.45, respectively), and Costarica has lower values at moderate exercise (0.23)
  • There is an overall trend of -8% lower melanopic EDI per additional hour of sleep. Only Tübingen, Germany, shows a flat curve (1.1 x 0.92 = 1.01)
  • While all of the effects are significant (at least with their interaction of site), none are very relevant, with unique contributions to \(R^2\) values between 0.01 and 0.05

Weekend instead of free day

This is a quick test to check whether weekend is a better predictor for melanopic EDI.

In [51]:
Code
H6_formula_weekend <- geo.MEDI ~ site*Weekend + site*exercise + site*sleep_duration + (1| Id)

H6_model_1_weekend <- 
  glmmTMB(H6_formula_weekend, 
          data = H6_data |> drop_na(geo.MEDI, daytype, exercise, sleep_duration), 
          REML = FALSE, 
          family = tweedie(),
          contrasts = list(site = "contr.sum")
          )

AIC(H6_model_1_weekend, H6_model_1)
                   df      AIC
H6_model_1_weekend 57 292575.3
H6_model_1         57 292507.4
In [52]:
Code
H6_model_1_weekend |> 
  emmeans(~ site | Weekend, type = "response") |> 
  plot()

The quick test shows that daytype is the better predictor compared to weekend (∆AIC), and that the results are broadly the same.

Hypothesis 7

Details

Hypothesis 7 states:

There is a ceiling effect of photoperiod with level, duration, and exposure-history-based metrics

Type of model: GAMM

Base formula: - \(\text{Metric} \sim s(\text{photoperiod}) + s(Site, bs = "re") + s(Participant, bs = "re")\)

Notes: - Only metrics that have proven significant in H1 will be used here. These are for -photoperiod and level: Mean, Brightest 10h mean, and Darkest 10h mean -photoperiod and duration: Duration above 1000, above 250 wake, below 10 pre-Sleep, and Period above 250 -photoperiod and exposue history: Dose - The confirmatory tests look at the base model compared to the Null models for each covariate - The tensor product of latitude and photoperiod was removed towards singular effects, as the 2d plot is sparsely populated along latitude

Specific formulas: - \(\text{Metric} \sim s(Site, bs = "re") + s(Participant, bs = "re")\) (Null model)

Preparation

In [53]:
Code
H7_data <- 
metrics |> 
  select(-c(H1_1_sig, H1_lat_sig)) |> 
  filter(H1_phot_sig, metric_type %in% c("level", "duration", "exposure history")) |> 
  mutate(H7_1 = "log_zero_inflated(metric) ~ s(photoperiod) + s(site, bs = 're') + s(Id, bs = 're')",
         H7_0 = "log_zero_inflated(metric) ~  s(site, bs = 're') + s(Id, bs = 're')",
         across(c(H7_1, H7_0), \(x) map(x, as.formula)),
         data = data |> map(\(x) x |> mutate(site = fct(site),
                                             Id = fct(Id)))
         )

Analysis

In [54]:
Code
H7_models <- 
H7_data |> 
  mutate(
    across(c(H7_1, H7_0),
           \(x) pmap(list(data, x),
                     \(data, formula) {
                       bam(formula, 
                            data = data, 
                            discrete = TRUE,
                            method = "fREML",
                            nthreads = 10
                            )
                     })),
    AICs = pmap(list(H7_1, H7_0), AIC),
    dAIC = map_dbl(AICs, \(x) x$AIC[[1]] - x$AIC[[2]]),
    signif = dAIC < -2
  )

The following models are significant:

In [55]:
Code
H7_models |> 
  filter(signif) |> 
  select(metric_type, name, dAIC) |> 
  gt() |> 
  fmt_number()
metric_type name dAIC
level Mean −5.42
level brightest_10h_mean −4.26
level darkest_10h_mean −5.17
duration duration_above_1000 −3.44
duration period_above_250 −3.46
exposure history dose −6.56

Mean

In [56]:
Code
mod <- H7_models |> filter(signif) |> pull(H7_1)

gam_deriv_plot(mod[[1]], H7_models$name[[1]])

Brightest 10h mean

In [57]:
Code
gam_deriv_plot(mod[[2]], "Brightest 10h mean")

Darkest 10h mean

In [58]:
Code
gam_deriv_plot(mod[[3]], "Darkest 10h mean")

Duration above 1000 lx melanopic EDI

In [59]:
Code
gam_deriv_plot(mod[[4]], "Duration above 1000 lx melanopic EDI")

Period above 250 lx melanopic EDI

In [60]:
Code
gam_deriv_plot(mod[[5]], "Period above 250 lx melanopic EDI")

Melanopic EDI dose

In [61]:
Code
gam_deriv_plot(mod[[6]], "Melanopic EDI dose")

Interpretation

  • For level metrics, there is a ceiling effect of photoperiod around 14.7 to 16.4 hours, where increases are no longer significant. This is similar for Duration above 1000 lx melanopic EDI, which has a ceiling effect at 16.6 hours.
  • Period above 250 lx melanopic EDI and Dose don’t show a slow-down effect

Session info

In [62]:
Code cell - Session info
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] gtsummary_2.5.0     gghighlight_0.5.0   glmmTMB_1.1.14     
 [4] gt_1.3.0            broom.mixed_0.2.9.7 rlang_1.2.0        
 [7] patchwork_1.3.2     emmeans_2.0.3       gratia_0.11.2      
[10] itsadug_2.5         plotfunctions_1.5   broom_1.0.12       
[13] sjPlot_2.9.0        ggtext_0.1.2        ggridges_0.5.7     
[16] cowplot_1.2.0       performance_0.16.0  lme4_2.0-1         
[19] Matrix_1.7-3        mgcv_1.9-4          nlme_3.1-168       
[22] LightLogR_0.10.0    melidosData_1.0.3   lubridate_1.9.5    
[25] forcats_1.0.1       stringr_1.6.0       dplyr_1.2.1        
[28] purrr_1.2.2         readr_2.2.0         tidyr_1.3.2        
[31] tibble_3.3.1        ggplot2_4.0.2       tidyverse_2.0.0    

loaded via a namespace (and not attached):
 [1] Rdpack_2.6.6        sandwich_3.1-1      magrittr_2.0.5     
 [4] furrr_0.4.0         otel_0.2.0          compiler_4.5.0     
 [7] systemfonts_1.3.2   vctrs_0.7.3         pkgconfig_2.0.3    
[10] fastmap_1.2.0       backports_1.5.1     labeling_0.4.3     
[13] effectsize_1.0.2    utf8_1.2.6          promises_1.5.0     
[16] rmarkdown_2.31      markdown_2.0        tzdb_0.5.0         
[19] nloptr_2.2.1        ragg_1.5.2          xfun_0.57          
[22] litedown_0.9        jsonlite_2.0.0      later_1.4.8        
[25] sjmisc_2.8.11       parallel_4.5.0      R6_2.6.1           
[28] stringi_1.8.7       RColorBrewer_1.1-3  parallelly_1.47.0  
[31] boot_1.3-31         numDeriv_2016.8-1.1 estimability_1.5.1 
[34] Rcpp_1.1.1-1        knitr_1.51          zoo_1.8-15         
[37] base64enc_0.1-6     parameters_0.28.3   mvnfast_0.2.8      
[40] splines_4.5.0       timechange_0.4.0    tidyselect_1.2.1   
[43] rstudioapi_0.18.0   yaml_2.3.12         websocket_1.4.4    
[46] sjlabelled_1.2.0    TMB_1.9.21          codetools_0.2-20   
[49] processx_3.8.7      listenv_0.10.1      lattice_0.22-7     
[52] bayestestR_0.17.0   withr_3.0.2         S7_0.2.1-1         
[55] coda_0.19-4.1       evaluate_1.0.5      future_1.70.0      
[58] xml2_1.5.2          pillar_1.11.1       renv_1.1.4         
[61] reformulas_0.4.4    insight_1.5.0       generics_0.1.4     
[64] chromote_0.5.1      hms_1.1.4           commonmark_2.0.0   
[67] mirai_2.6.1         scales_1.4.0        minqa_1.2.8        
[70] globals_0.19.1      glue_1.8.0          webshot2_0.1.2     
[73] tools_4.5.0         see_0.13.0          fs_2.0.1           
[76] mvtnorm_1.3-7       grid_4.5.0          ggokabeito_0.1.0   
[79] rbibutils_2.4.1     cards_0.7.1         datawizard_1.3.0   
[82] cli_3.6.6           textshaping_1.0.5   viridisLite_0.4.3  
[85] gtable_0.3.6        tweedie_3.0.17      sass_0.4.10        
[88] digest_0.6.39       nanonext_1.8.2      htmlwidgets_1.6.4  
[91] farver_2.1.2        htmltools_0.5.9     lifecycle_1.0.5    
[94] statmod_1.5.1       gridtext_0.1.6      MASS_7.3-65