From 9092ed0f2e00fc4dee6f21c73717e73a46123393 Mon Sep 17 00:00:00 2001 From: Jairo H Migueles Date: Thu, 5 Oct 2023 07:51:52 +0200 Subject: [PATCH 01/14] sleep onset to onset window implemented in part 5 and documentation expanded --- R/g.part5.R | 12 ++++++++++-- R/g.part5.definedays.R | 19 +++++++++++++++++++ R/g.report.part5.R | 8 ++++---- man/GGIR.Rd | 2 +- vignettes/GGIR.Rmd | 6 +++--- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/R/g.part5.R b/R/g.part5.R index bd5fc401b..750a300d1 100644 --- a/R/g.part5.R +++ b/R/g.part5.R @@ -410,6 +410,7 @@ g.part5 = function(datadir = c(), metadatadir = c(), f0=c(), f1=c(), #------------------------------- # ignore all nights in 'inights' before the first waking up and after the last waking up FM = which(diff(ts$diur) == -1) + SO = which(diff(ts$diur) == 1) # now 0.5+6+0.5 midnights and 7 days for (timewindowi in params_output[["timewindow"]]) { nightsi = nightsi_bu @@ -419,6 +420,11 @@ g.part5 = function(datadir = c(), metadatadir = c(), f0=c(), f1=c(), # ignore first and last midnight because we did not do sleep detection on it nightsi = nightsi[nightsi > FM[1] & nightsi < FM[length(FM)]] } + } else if (timewindowi == "OO") { + if (length(SO) > 0) { + # ignore data before the first sleep onset and after the last sleep onset + nightsi = nightsi[nightsi > SO[1] & nightsi < SO[length(SO)]] + } } else { # if first night is missing then nights needs to align with diur startend_sleep = which(abs(diff(ts$diur)) == 1) @@ -428,8 +434,10 @@ g.part5 = function(datadir = c(), metadatadir = c(), f0=c(), f1=c(), } if (timewindowi == "MM") { Nwindows = length(nightsi) + 1 - } else { + } else if (timewindowi == "WW") { Nwindows = length(which(diff(ts$diur) == -1)) + } else if (timewindowi == "OO") { + Nwindows = length(which(diff(ts$diur) == 1)) } indjump = 1 qqq_backup = c() @@ -442,11 +450,11 @@ g.part5 = function(datadir = c(), metadatadir = c(), f0=c(), f1=c(), } for (wi in 1:Nwindows) { #loop through 7 windows (+1 to include the data after last awakening) # Define indices of start and end of the day window (e.g. midnight-midnight, or waking-up or wakingup - defdays = g.part5.definedays(nightsi, wi, indjump, nightsi_bu, epochSize = ws3new, qqq_backup, ts, timewindowi, Nwindows, qwindow = params_247[["qwindow"]], ID = ID) + print(ts[defdays$qqq, 1:2]) qqq = defdays$qqq qqq_backup = defdays$qqq_backup segments = defdays$segments diff --git a/R/g.part5.definedays.R b/R/g.part5.definedays.R index d968b99c3..6e83bd90a 100644 --- a/R/g.part5.definedays.R +++ b/R/g.part5.definedays.R @@ -156,6 +156,25 @@ g.part5.definedays = function(nightsi, wi, indjump, nightsi_bu, names(segments) = paste(start, end, sep = "-") segments_names = "WW" } + } else if (timewindowi == "OO") { + if (wi <= (Nwindows - 1)) { # all full wake to wake days + qqq[1] = which(diff(ts$diur) == 1)[wi] + 1 + qqq[2] = which(diff(ts$diur) == 1)[wi + 1] + } else { + # time after last reliable waking up (this can be more than 24 hours) + # ignore this day, because if the night was ignored for sleep analysis + # then the description of the day in part 5 including that night is + # not informative. + qqq = c(NA, NA) + } + # build up segments + if (!is.na(qqq[1]) & !is.na(qqq[2])) { + segments = list(qqq) + start = substr(ts$time[qqq[1]], 12, 19) + end = substr(ts$time[qqq[2]], 12, 19) + names(segments) = paste(start, end, sep = "-") + segments_names = "OO" + } } return(invisible(list(qqq = qqq, qqq_backup = qqq_backup, segments = segments, segments_names = segments_names))) diff --git a/R/g.report.part5.R b/R/g.report.part5.R index c93c8b60e..24b3ac19c 100644 --- a/R/g.report.part5.R +++ b/R/g.report.part5.R @@ -45,7 +45,7 @@ g.report.part5 = function(metadatadir = c(), f0 = c(), f1 = c(), loglocation = c } # Note: Below we intentionally only sets a criteria on daytime, because for # the night time we only need start and end of the SPT window. - if (window == "WW") { + if (window == "WW" | window == "OO") { indices = which(x$nonwear_perc_day <= maxpernwday & x$dur_spt_min > 0 & x$dur_day_min > 0 & include_window == TRUE) } else if (window == "MM") { @@ -153,8 +153,8 @@ g.report.part5 = function(metadatadir = c(), f0 = c(), f1 = c(), loglocation = c # split results to different spreadsheets in order to minimize individual # filesize and to ease organising dataset uwi = as.character(unique(outputfinal$window)) - if (!all(uwi %in% c("MM", "WW"))) { - uwi = c(uwi[uwi %in% c("MM", "WW")], "Segments") + if (!all(uwi %in% c("MM", "WW", "OO"))) { + uwi = c(uwi[uwi %in% c("MM", "WW", "OO")], "Segments") } uTRLi = as.character(unique(outputfinal$TRLi)) uTRMi = as.character(unique(outputfinal$TRMi)) @@ -197,7 +197,7 @@ g.report.part5 = function(metadatadir = c(), f0 = c(), f1 = c(), loglocation = c "-", uTRVi[h3], "-", usleepparam[h4])) } select_window = as.character(outputfinal$window) == uwi[j] - if (!(uwi[j] %in% c("MM", "WW"))) select_window = !(as.character(outputfinal$window) %in% c("MM", "WW")) + if (!(uwi[j] %in% c("MM", "WW", "OO"))) select_window = !(as.character(outputfinal$window) %in% c("MM", "WW", "OO")) seluwi = which(select_window & as.character(outputfinal$TRLi) == uTRLi[h1] & as.character(outputfinal$TRMi) == uTRMi[h2] & diff --git a/man/GGIR.Rd b/man/GGIR.Rd index 9c7107c0d..f3eb14dfd 100755 --- a/man/GGIR.Rd +++ b/man/GGIR.Rd @@ -1269,7 +1269,7 @@ GGIR(mode = 1:5, Character (default = c("MM", "WW")). In \link{g.part5}: Timewindow over which summary statistics are derived. Value can be "MM" (midnight to midnight), "WW" (waking time to waking time), - or both c("MM","WW").} + "OO" (sleep onset to sleep onset), or any combination of them.} \item{save_ms5rawlevels}{ Boolean (default = FALSE). diff --git a/vignettes/GGIR.Rmd b/vignettes/GGIR.Rmd index d566862a3..c8587253d 100644 --- a/vignettes/GGIR.Rmd +++ b/vignettes/GGIR.Rmd @@ -482,8 +482,8 @@ For an explanation on how time use analysis is performed see section Here, the full 25 minutes would count towards the duration of the MVPA bout. - `timewindow` to specify whether days should be defined from midnight - to midnight `"MM"`, from waking-up to waking-up `"WW"`, or both - `c("MM","WW")`. + to midnight `"MM"`, from waking-up to waking-up `"WW"`, from sleep onset + to sleep onset `"OO"`, or any combination of them. - Configure durations of bouts: `boutdur.mvpa`, `boutdur.in`, and `boutdur.lig`. Note that this can be a vector of multiple values indicating the minimum and maximum duration of subsequent bout @@ -1612,7 +1612,7 @@ correspond to the date of analysis. | SleepPeriodTime | Is 1 if SPT is detected, 0 if not. Note that this refers to the combined usage of guider and detected sustained inactivity bouts (rest periods).| | invalidepoch | Is 1 if epoch was detect as invalid (e.g. non-wear), 0 if not. | | guider | Is 1 if guider method detect epoch as SPT (e.g. sleeplog, HDCZA), 0 if not. You will not find here which guider is used, for this see other GGIR output. | -| window | Numeric indicator of the analysis window in the recording. If timewindow = "MM" then these correspond to calendar days, if timewindow = "WW" then these correspond to which wakingup-wakingup window in the recording. So, in a recording of one week you may find window numbers 1, 2, 3, 4, 5 and 6.| +| window | Numeric indicator of the analysis window in the recording. If timewindow = "MM" then these correspond to calendar days, if timewindow = "WW" then these correspond to which wakingup-wakingup window in the recording, if timewindow = "OO" then these correspond to which sleeponset-sleeponset window in the recording. So, in a recording of one week you may find window numbers 1, 2, 3, 4, 5 and 6.| | class_id | The behavioural class codes are documented in the exported csv file meta/ms5outraw/behaviouralcodes.csv. Class codes above class 8 will be analysis specific, because it depends on the number time variants of the bouts used. For example, if you look at MVPA lasting 1-10, 10-20, 30-40 then all of them will have their own class_id. | | invalid_fullwindow | Fraction of the window (see above) that represents invalid data. I added this to make it easier to filter the timeseries based on whether days are valid or not. | | invalid_sleepperiod | Fraction of SPT within the current window that represents invalid data. | From 8c71177303ea7f8ae246125cafd2d986fb52023a Mon Sep 17 00:00:00 2001 From: Jairo H Migueles Date: Thu, 5 Oct 2023 07:54:42 +0200 Subject: [PATCH 02/14] update NEWS --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index c57b52edd..6d5eee42e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,8 @@ - Fix recently introduced bug where GGIR environment was not exported to cluster in GGIR part 1, 2, 3, and 5 #910 +- Part 5: Implemented sleep onset to sleep onset timewindow ("OO") #931 + # CHANGES IN GGIR VERSION 2.10-3 - Part 1: Fixed minor bug in ismovisens that failed when datadir started with "./" #897 From 01823d56ff23c316b8907ed60f1d0229de3a2423 Mon Sep 17 00:00:00 2001 From: Jairo H Migueles Date: Thu, 5 Oct 2023 08:16:07 +0200 Subject: [PATCH 03/14] remove line introduced for debugging purposes --- R/g.part5.R | 1 - 1 file changed, 1 deletion(-) diff --git a/R/g.part5.R b/R/g.part5.R index 750a300d1..89b45113a 100644 --- a/R/g.part5.R +++ b/R/g.part5.R @@ -454,7 +454,6 @@ g.part5 = function(datadir = c(), metadatadir = c(), f0=c(), f1=c(), nightsi_bu, epochSize = ws3new, qqq_backup, ts, timewindowi, Nwindows, qwindow = params_247[["qwindow"]], ID = ID) - print(ts[defdays$qqq, 1:2]) qqq = defdays$qqq qqq_backup = defdays$qqq_backup segments = defdays$segments From 99bfc46bd19c6eac5bf1fa6560f686b9acef08da Mon Sep 17 00:00:00 2001 From: Jairo H Migueles Date: Thu, 5 Oct 2023 10:52:34 +0200 Subject: [PATCH 04/14] test for timewindow = c(MM, WW, OO) --- tests/testthat/test_chainof5parts.R | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/testthat/test_chainof5parts.R b/tests/testthat/test_chainof5parts.R index b6247a089..ad4f39507 100644 --- a/tests/testthat/test_chainof5parts.R +++ b/tests/testthat/test_chainof5parts.R @@ -148,7 +148,7 @@ test_that("chainof5parts", { overwrite = TRUE, excludefirstlast = FALSE, do.parallel = do.parallel, frag.metrics = "all", save_ms5rawlevels = TRUE, part5_agg2_60seconds = TRUE, do.sibreport = TRUE, nap_model = "hip3yr", - iglevels = 1) + iglevels = 1, timewindow = c("MM", "WW", "OO")) sibreport_dirname = "output_test/meta/ms5.outraw/sib.reports" expect_true(dir.exists(sibreport_dirname)) expect_true(file.exists(paste0(sibreport_dirname, "/sib_report_123A_testaccfile_T5A5.csv"))) @@ -161,9 +161,11 @@ test_that("chainof5parts", { expect_true(dir.exists(dirname)) expect_true(file.exists(rn[1])) - expect_that(nrow(output),equals(4)) + expect_that(nrow(output),equals(5)) # changed because OO window is exported expect_that(ncol(output),equals(197)) expect_that(round(as.numeric(output$wakeup[2]), digits = 4), equals(36)) + expect_that(as.numeric(output$dur_day_spt_min[4]), equals(1150)) # WW window duration + expect_that(as.numeric(output$dur_day_spt_min[5]), equals(1680)) # OO window duration dirname_raw = "output_test/meta/ms5.outraw/40_100_400" rn2 = dir(dirname_raw,full.names = TRUE, recursive = T) expect_true(file.exists(rn2[1])) @@ -187,6 +189,9 @@ test_that("chainof5parts", { expect_true(file.exists("output_test/results/part4_nightsummary_sleep_cleaned.csv")) expect_true(file.exists("output_test/results/part4_summary_sleep_cleaned.csv")) expect_true(file.exists("output_test/results/file summary reports/Report_123A_testaccfile.csv.pdf")) + expect_true(file.exists("output_test/results/part5_daysummary_MM_L40M100V400_T5A5.csv")) + expect_true(file.exists("output_test/results/part5_daysummary_WW_L40M100V400_T5A5.csv")) + expect_true(file.exists("output_test/results/part5_daysummary_OO_L40M100V400_T5A5.csv")) dn = "output_test" #======================= From 968723033a966049229d3d493571c25be437ce46 Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 11:56:49 +0200 Subject: [PATCH 05/14] do not delete start_end_window column --- R/g.report.part5.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/g.report.part5.R b/R/g.report.part5.R index 215623030..cf3c31d8e 100644 --- a/R/g.report.part5.R +++ b/R/g.report.part5.R @@ -210,7 +210,7 @@ g.report.part5 = function(metadatadir = c(), f0 = c(), f1 = c(), loglocation = c CN = colnames(outputfinal) outputfinal2 = outputfinal colnames(outputfinal2) = CN - delcol = grep(pattern = "window|TRLi|TRMi|TRVi|sleepparam", + delcol = grep(pattern = "TRLi|TRMi|TRVi|sleepparam", x = colnames(outputfinal2)) if (uwi[j] != "Segments") { delcol = c(delcol, which(colnames(outputfinal2) == "window")) From 70b5bd754cf6137a81b0f38e5ff4a1a2f2059246 Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 11:59:12 +0200 Subject: [PATCH 06/14] fix redundancy + do not extract time by assuming character positions this does not work with midnight --- R/g.part5.definedays.R | 42 +++++++++++++----------------------------- 1 file changed, 13 insertions(+), 29 deletions(-) diff --git a/R/g.part5.definedays.R b/R/g.part5.definedays.R index 6e83bd90a..a4d6de27a 100644 --- a/R/g.part5.definedays.R +++ b/R/g.part5.definedays.R @@ -87,7 +87,7 @@ g.part5.definedays = function(nightsi, wi, indjump, nightsi_bu, # in MM, also define segments of the day based on qwindow if (!is.na(qqq[1]) & !is.na(qqq[2])) { fullQqq = qqq[1]:qqq[2] - lastepoch = substr(ts$time[qqq[2]], 12, 19) + lastepoch = format(ts$time[qqq[2]], "%H:%M:%S") qnames = NULL if (is.data.frame(qwindow)) { date_of_interest = substr(ts$time[qqq[1]], 1, 10) @@ -137,13 +137,14 @@ g.part5.definedays = function(nightsi, wi, indjump, nightsi_bu, } names(segments) = segments_timing } - } else if (timewindowi == "WW") { - if (wi <= (Nwindows - 1)) { # all full wake to wake days - qqq[1] = which(diff(ts$diur) == -1)[wi] + 1 - qqq[2] = which(diff(ts$diur) == -1)[wi + 1] + } else if (timewindowi == "WW" || timewindowi == "OO") { + windowEdge = ifelse(timewindowi == "WW", yes = -1, no = 1) + if (wi <= (Nwindows - 1)) { # all full windows + qqq[1] = which(diff(ts$diur) == windowEdge)[wi] + 1 + qqq[2] = which(diff(ts$diur) == windowEdge)[wi + 1] } else { - # time after last reliable waking up (this can be more than 24 hours) - # ignore this day, because if the night was ignored for sleep analysis + # time after last reliable waking up or onset (this can be more than 24 hours) + # ignore this window, because if the night was ignored for sleep analysis # then the description of the day in part 5 including that night is # not informative. qqq = c(NA, NA) @@ -151,30 +152,13 @@ g.part5.definedays = function(nightsi, wi, indjump, nightsi_bu, # build up segments if (!is.na(qqq[1]) & !is.na(qqq[2])) { segments = list(qqq) - start = substr(ts$time[qqq[1]], 12, 19) - end = substr(ts$time[qqq[2]], 12, 19) + start = format(ts$time[qqq[1]], "%H:%M:%S") + end = format(ts$time[qqq[2]], "%H:%M:%S") + if (timewindowi == "OO") browser() names(segments) = paste(start, end, sep = "-") - segments_names = "WW" - } - } else if (timewindowi == "OO") { - if (wi <= (Nwindows - 1)) { # all full wake to wake days - qqq[1] = which(diff(ts$diur) == 1)[wi] + 1 - qqq[2] = which(diff(ts$diur) == 1)[wi + 1] - } else { - # time after last reliable waking up (this can be more than 24 hours) - # ignore this day, because if the night was ignored for sleep analysis - # then the description of the day in part 5 including that night is - # not informative. - qqq = c(NA, NA) - } - # build up segments - if (!is.na(qqq[1]) & !is.na(qqq[2])) { - segments = list(qqq) - start = substr(ts$time[qqq[1]], 12, 19) - end = substr(ts$time[qqq[2]], 12, 19) - names(segments) = paste(start, end, sep = "-") - segments_names = "OO" + segments_names = timewindowi } + } return(invisible(list(qqq = qqq, qqq_backup = qqq_backup, segments = segments, segments_names = segments_names))) From ed843d8a8c8d889bdd946bcef9d5f86d9ffeffd4 Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 12:01:11 +0200 Subject: [PATCH 07/14] Account for OO scenario in g.part5_onsetwaketiming --- R/g.part5.onsetwaketiming.R | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/R/g.part5.onsetwaketiming.R b/R/g.part5.onsetwaketiming.R index 1bdf1e926..333cd722f 100644 --- a/R/g.part5.onsetwaketiming.R +++ b/R/g.part5.onsetwaketiming.R @@ -2,22 +2,23 @@ g.part5.onsetwaketiming = function(qqq, ts, min, sec, hour, timewindowi) { onset = wake = 0 skiponset = TRUE; skipwake = TRUE # Onset index - if (timewindowi == "WW") { - onseti = c(qqq[1]:qqq[2])[which(diff(ts$diur[qqq[1]:(qqq[2]-1)]) == 1)+1] - if (length(onseti) > 1) { - onseti = onseti[length(onseti)] # in the case if MM use last onset - } + if (timewindowi == "OO") { + # For OO onset is by definition the start and end of the window + onseti = qqq[2] + 1 } else { - onseti = c(qqq[1]:qqq[2])[which(diff(ts$diur[qqq[1]:(qqq[2]-1)]) == 1)+1] + # For WW and MM onset needs to be search in the window + onseti = c(qqq[1]:qqq[2])[which(diff(ts$diur[qqq[1]:(qqq[2] - 1)]) == 1) + 1] if (length(onseti) > 1) { onseti = onseti[length(onseti)] # in the case if MM use last onset } } # Wake index if (timewindowi == "WW") { - wakei = qqq[2]+1 + # For WW wake is by definition the start and end of the window + wakei = qqq[2] + 1 } else { - wakei = c(qqq[1]:qqq[2])[which(diff(ts$diur[qqq[1]:(qqq[2]-1)]) == -1)+1] + # For OO and MM wake needs to be search in the window + wakei = c(qqq[1]:qqq[2])[which(diff(ts$diur[qqq[1]:(qqq[2] - 1)]) == -1) + 1] if (length(wakei) > 1) wakei = wakei[1] # in the case if MM use first wake-up time } # Onset time From d9ffefaf627fd580232f83e5cd1172d78430e7be Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 12:01:56 +0200 Subject: [PATCH 08/14] account for OO in g.part5_analyseSegment --- R/g.part5_analyseSegment.R | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/R/g.part5_analyseSegment.R b/R/g.part5_analyseSegment.R index 69685c8b7..adf386a6a 100644 --- a/R/g.part5_analyseSegment.R +++ b/R/g.part5_analyseSegment.R @@ -49,7 +49,7 @@ g.part5_analyseSegment = function(indexlog, timeList, levelList, # This code extract them the time series (ts) object create in g.part5 # Note that this means that for MM windows there can be multiple or no wake or onsets date = as.Date(ts$time[segStart + 1]) - if (add_one_day_to_next_date == TRUE & timewindowi == "WW") { # see below for explanation + if (add_one_day_to_next_date == TRUE & timewindowi %in% c("WW", "OO")) { # see below for explanation date = date + 1 add_one_day_to_next_date = FALSE } @@ -73,6 +73,12 @@ g.part5_analyseSegment = function(indexlog, timeList, levelList, # So, for next window we have to do date = date + 1 add_one_day_to_next_date = TRUE } + if (onset < 24 & timewindowi == "OO") { + # onset before midnight means that next OO window + # will start a day before the day we refer to when discussing it's SPT + # So, for next window we have to do date = date + 1 + add_one_day_to_next_date = TRUE + } # Add to dsummary output matrix if (skiponset == FALSE) { dsummary[si,fi] = onset @@ -440,7 +446,7 @@ g.part5_analyseSegment = function(indexlog, timeList, levelList, } } fi = fi + Nluxt - if (timewindowi == "WW") { + if (timewindowi %in% c("WW", "OO")) { # LUX per segment of the day luxperseg = g.part5.lux_persegment(ts, sse, LUX_day_segments = params_247[["LUX_day_segments"]], @@ -487,7 +493,6 @@ g.part5_analyseSegment = function(indexlog, timeList, levelList, columnIndex = fi) timeList = list(ts = ts, epochSize = ws3new) - invisible(list( indexlog = indexlog, ds_names = ds_names, From 607cee96055dee0129796ade81e8c03bc6123c04 Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 13:49:05 +0200 Subject: [PATCH 09/14] move time stamp conversion outside agg2_60seconds block to standardise time format regardless of aggregation --- R/g.part5.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/g.part5.R b/R/g.part5.R index 89b45113a..4f77f6484 100644 --- a/R/g.part5.R +++ b/R/g.part5.R @@ -276,8 +276,10 @@ g.part5 = function(datadir = c(), metadatadir = c(), f0=c(), f1=c(), # Add first waking up time, if it is missing: ts = g.part5.addfirstwake(ts, summarysleep = summarysleep_tmp2, nightsi, sleeplog, ID, Nepochsinhour, SPTE_end) + # Convert time column regardless of whether it is aggregated to secure standard time format + ts$time = iso8601chartime2POSIX(ts$time,tz = params_general[["desiredtz"]]) if (params_general[["part5_agg2_60seconds"]] == TRUE) { # Optionally aggregate to 1 minute epoch: - ts$time_num = floor(as.numeric(iso8601chartime2POSIX(ts$time,tz = params_general[["desiredtz"]])) / 60) * 60 + ts$time_num = floor(as.numeric(ts$time) / 60) * 60 # only include angle if angle is present angleColName = ifelse("angle" %in% names(ts), yes = "angle", no = NULL) From bc26dd05dd49105c392342045e16314874054bd5 Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 13:51:37 +0200 Subject: [PATCH 10/14] handle missing sleep for first and last night because ignored in part 4, ensure OO window has correct date and timeings, fix onset index --- R/g.part5.addfirstwake.R | 8 ++++---- R/g.part5.definedays.R | 8 +++----- R/g.part5.onsetwaketiming.R | 4 ++-- R/g.part5_analyseSegment.R | 22 +++++++++++++--------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/R/g.part5.addfirstwake.R b/R/g.part5.addfirstwake.R index 1fd0337f5..ac292813c 100644 --- a/R/g.part5.addfirstwake.R +++ b/R/g.part5.addfirstwake.R @@ -1,4 +1,4 @@ -g.part5.addfirstwake =function(ts, summarysleep, nightsi, sleeplog, ID, +g.part5.addfirstwake = function(ts, summarysleep, nightsi, sleeplog, ID, Nepochsinhour, SPTE_end) { # Note related to if first and last night were ignored in part 4: # - diur lacks the first and last night at this point in the code. @@ -72,7 +72,7 @@ g.part5.addfirstwake =function(ts, summarysleep, nightsi, sleeplog, ID, } if (is.na(wake_night1_index)) wake_night1_index = 0 if (wake_night1_index < firstwake & wake_night1_index > 1 & (wake_night1_index-1) > nightsi[1]) { - ts$diur[nightsi[1]:(wake_night1_index-1)] = 1 + ts$diur[1:(wake_night1_index-1)] = 1 } else { # Person slept only during the afternoon on day 2 # And there is no sleep data available for the first night @@ -82,8 +82,8 @@ g.part5.addfirstwake =function(ts, summarysleep, nightsi, sleeplog, ID, # and merging of the sleep variables is still consistent with # the other recording. dummywake = max(firstonset - round(Nepochsinhour/12), nightsi[1] + round(Nepochsinhour * 6)) - ts$diur[nightsi[1]:dummywake] = 1 - ts$nonwear[nightsi[1]:firstonset] = 1 + ts$diur[1:dummywake] = 1 + ts$nonwear[1:firstonset] = 1 } } return(ts) diff --git a/R/g.part5.definedays.R b/R/g.part5.definedays.R index a4d6de27a..576c92855 100644 --- a/R/g.part5.definedays.R +++ b/R/g.part5.definedays.R @@ -74,14 +74,13 @@ g.part5.definedays = function(nightsi, wi, indjump, nightsi_bu, } } } else { + qqq = c(NA, NA) if (length(qqq_backup) > 1) { # if there is remaining time after previous day... - if (Nts > qqq_backup[2]) { + # and if this is less than 24 hours (necessary if sleep is ignored for last night + if (Nts - qqq_backup[2] < 24 * (60 / epochSize) * 60) { qqq = c(qqq_backup[2] + 1, Nts) } - } else { - # else, do not analyse this day - qqq = c(NA, NA) } } # in MM, also define segments of the day based on qwindow @@ -154,7 +153,6 @@ g.part5.definedays = function(nightsi, wi, indjump, nightsi_bu, segments = list(qqq) start = format(ts$time[qqq[1]], "%H:%M:%S") end = format(ts$time[qqq[2]], "%H:%M:%S") - if (timewindowi == "OO") browser() names(segments) = paste(start, end, sep = "-") segments_names = timewindowi } diff --git a/R/g.part5.onsetwaketiming.R b/R/g.part5.onsetwaketiming.R index 333cd722f..aa3fbce6e 100644 --- a/R/g.part5.onsetwaketiming.R +++ b/R/g.part5.onsetwaketiming.R @@ -4,10 +4,10 @@ g.part5.onsetwaketiming = function(qqq, ts, min, sec, hour, timewindowi) { # Onset index if (timewindowi == "OO") { # For OO onset is by definition the start and end of the window - onseti = qqq[2] + 1 + onseti = qqq[1] + 1 } else { # For WW and MM onset needs to be search in the window - onseti = c(qqq[1]:qqq[2])[which(diff(ts$diur[qqq[1]:(qqq[2] - 1)]) == 1) + 1] + onseti = c(qqq[1]:qqq[2])[which(diff(ts$diur[qqq[1]:(qqq[2] - 1)]) == 1)] + 1 if (length(onseti) > 1) { onseti = onseti[length(onseti)] # in the case if MM use last onset } diff --git a/R/g.part5_analyseSegment.R b/R/g.part5_analyseSegment.R index adf386a6a..a34b99b00 100644 --- a/R/g.part5_analyseSegment.R +++ b/R/g.part5_analyseSegment.R @@ -48,7 +48,7 @@ g.part5_analyseSegment = function(indexlog, timeList, levelList, # The following is to avoid issue with merging sleep variables from part 4 # This code extract them the time series (ts) object create in g.part5 # Note that this means that for MM windows there can be multiple or no wake or onsets - date = as.Date(ts$time[segStart + 1]) + date = as.Date(ts$time[segStart + 1], tz = params_general[["desiredtz"]]) if (add_one_day_to_next_date == TRUE & timewindowi %in% c("WW", "OO")) { # see below for explanation date = date + 1 add_one_day_to_next_date = FALSE @@ -73,9 +73,9 @@ g.part5_analyseSegment = function(indexlog, timeList, levelList, # So, for next window we have to do date = date + 1 add_one_day_to_next_date = TRUE } - if (onset < 24 & timewindowi == "OO") { - # onset before midnight means that next OO window - # will start a day before the day we refer to when discussing it's SPT + if (onset > 24 & timewindowi == "OO") { + # onset after midnight means that next OO window + # will start a day after the day we refer to when discussing it's SPT # So, for next window we have to do date = date + 1 add_one_day_to_next_date = TRUE } @@ -427,11 +427,15 @@ g.part5_analyseSegment = function(indexlog, timeList, levelList, # LIGHT, IF AVAILABLE if ("lightpeak" %in% colnames(ts) & length(params_247[["LUX_day_segments"]]) > 0) { # mean LUX - dsummary[si,fi] = round(max(ts$lightpeak[sse[ts$diur[sse] == 0]], na.rm = TRUE), digits = 1) - dsummary[si,fi + 1] = round(mean(ts$lightpeak[sse[ts$diur[sse] == 0]], na.rm = TRUE), digits = 1) - dsummary[si,fi + 2] = round(mean(ts$lightpeak[sse[ts$diur[sse] == 1]], na.rm = TRUE), digits = 1) - dsummary[si,fi + 3] = round(mean(ts$lightpeak[sse[ts$diur[sse] == 0 & ts$ACC[sse] > TRMi]], na.rm = TRUE), - digits = 1) + if (length(which(ts$diur[sse] == 0)) > 0 & length(which(ts$diur[sse] == 1)) > 0) { + dsummary[si,fi] = round(max(ts$lightpeak[sse[ts$diur[sse] == 0]], na.rm = TRUE), digits = 1) + dsummary[si,fi + 1] = round(mean(ts$lightpeak[sse[ts$diur[sse] == 0]], na.rm = TRUE), digits = 1) + dsummary[si,fi + 2] = round(mean(ts$lightpeak[sse[ts$diur[sse] == 1]], na.rm = TRUE), digits = 1) + dsummary[si,fi + 3] = round(mean(ts$lightpeak[sse[ts$diur[sse] == 0 & ts$ACC[sse] > TRMi]], na.rm = TRUE), + digits = 1) + } else { + dsummary[si,fi:(fi + 3)] = NA + } ds_names[fi:(fi + 3)] = c("LUX_max_day", "LUX_mean_day", "LUX_mean_spt", "LUX_mean_day_mvpa"); fi = fi + 4 # time in LUX ranges Nluxt = length(params_247[["LUXthresholds"]]) From 4fba2354db299b98f8f2fb6e2b32c729f2b365f6 Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 13:56:19 +0200 Subject: [PATCH 11/14] rephrase code comment --- R/g.part5.definedays.R | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/R/g.part5.definedays.R b/R/g.part5.definedays.R index 576c92855..e500bd4a3 100644 --- a/R/g.part5.definedays.R +++ b/R/g.part5.definedays.R @@ -77,7 +77,9 @@ g.part5.definedays = function(nightsi, wi, indjump, nightsi_bu, qqq = c(NA, NA) if (length(qqq_backup) > 1) { # if there is remaining time after previous day... - # and if this is less than 24 hours (necessary if sleep is ignored for last night + # but only do this if there is less than 24 hours + # this is necessary if sleep is ignored for last night as + # that would include the entire last day if (Nts - qqq_backup[2] < 24 * (60 / epochSize) * 60) { qqq = c(qqq_backup[2] + 1, Nts) } From 499c175e0238ca84dbc0f22f6a96783a7352bbca Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 14:07:22 +0200 Subject: [PATCH 12/14] remove conversion to iso8601 from unit-test because g.part5.definedays no longer expects this --- tests/testthat/test_part5_qwindow.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/testthat/test_part5_qwindow.R b/tests/testthat/test_part5_qwindow.R index e82ae80cb..ebc6f8ebf 100644 --- a/tests/testthat/test_part5_qwindow.R +++ b/tests/testthat/test_part5_qwindow.R @@ -6,10 +6,9 @@ test_that("g.part5.definedays considers qwindow to generate segments", { # set windows timestamp = seq.POSIXt(from = as.POSIXct("2022-01-01 00:00:00"), to = as.POSIXct("2022-01-04 23:59:00"), by = 60) - timestamp = POSIXtime2iso8601(timestamp, tz = "") ts = data.frame(timestamp = timestamp, ACC = 0) - nightsi = grep("00:00:00", ts$timestamp) + nightsi = grep("00:00:00", format(ts$timestamp)) qwindow = c(7, 18) definedays = g.part5.definedays(nightsi = nightsi, wi = 1, indjump = 1, From 63646e016e5ee3a08d7f4e4b3e9289dc6a978939 Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Wed, 11 Oct 2023 14:29:32 +0200 Subject: [PATCH 13/14] minor edits to code comments --- R/g.part5.R | 3 ++- R/g.part5.definedays.R | 9 +++++---- R/g.part5_analyseSegment.R | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/R/g.part5.R b/R/g.part5.R index 4f77f6484..59229cdde 100644 --- a/R/g.part5.R +++ b/R/g.part5.R @@ -276,7 +276,8 @@ g.part5 = function(datadir = c(), metadatadir = c(), f0=c(), f1=c(), # Add first waking up time, if it is missing: ts = g.part5.addfirstwake(ts, summarysleep = summarysleep_tmp2, nightsi, sleeplog, ID, Nepochsinhour, SPTE_end) - # Convert time column regardless of whether it is aggregated to secure standard time format + # Convert time column from iso8601 to POSIX regardless of whether it is aggregated + # to ensure the format is consistent ts$time = iso8601chartime2POSIX(ts$time,tz = params_general[["desiredtz"]]) if (params_general[["part5_agg2_60seconds"]] == TRUE) { # Optionally aggregate to 1 minute epoch: ts$time_num = floor(as.numeric(ts$time) / 60) * 60 diff --git a/R/g.part5.definedays.R b/R/g.part5.definedays.R index e500bd4a3..b1fcce462 100644 --- a/R/g.part5.definedays.R +++ b/R/g.part5.definedays.R @@ -76,10 +76,11 @@ g.part5.definedays = function(nightsi, wi, indjump, nightsi_bu, } else { qqq = c(NA, NA) if (length(qqq_backup) > 1) { - # if there is remaining time after previous day... - # but only do this if there is less than 24 hours - # this is necessary if sleep is ignored for last night as - # that would include the entire last day + # If there is remaining time after previous day... + # but only do this if there is less than 24 hours. + # This is necessary if sleep is ignored for last night. + # In that case the last two calendar days should be ignored + # as no sleep onset will be available. if (Nts - qqq_backup[2] < 24 * (60 / epochSize) * 60) { qqq = c(qqq_backup[2] + 1, Nts) } diff --git a/R/g.part5_analyseSegment.R b/R/g.part5_analyseSegment.R index a34b99b00..2689e0caf 100644 --- a/R/g.part5_analyseSegment.R +++ b/R/g.part5_analyseSegment.R @@ -69,13 +69,13 @@ g.part5_analyseSegment = function(indexlog, timeList, levelList, skiponset = onsetwaketiming$skiponset; skipwake = onsetwaketiming$skipwake if (wake < 24 & timewindowi == "WW") { # waking up before midnight means that next WW window - # will start a day before the day we refer to when discussing it's SPT + # will start a day before the date we refer to when discussing it's SPT # So, for next window we have to do date = date + 1 add_one_day_to_next_date = TRUE } if (onset > 24 & timewindowi == "OO") { # onset after midnight means that next OO window - # will start a day after the day we refer to when discussing it's SPT + # will start a date after the date we refer to when discussing it;s SPT # So, for next window we have to do date = date + 1 add_one_day_to_next_date = TRUE } From f0e114689e3b0b1e175f11b442a276009b83e86d Mon Sep 17 00:00:00 2001 From: Vincent van Hees Date: Thu, 12 Oct 2023 17:10:37 +0200 Subject: [PATCH 14/14] minor rephrasing of changelog --- NEWS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index a59765066..f757aaf21 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,9 @@ - Part 2: Arguments hrs.del.start and hrs.del.end when combined with strategy = 3 and strategy = 5 now count relative to start and end of the most active time window as identified. #905 +- Part 5: Added optioned "OO" to argument timewindow, which defines windows from +sleep Onset to sleep Onset #931 + # CHANGES IN GGIR VERSION 2.10-4 - Part 4: Now better able to handle nights without sustained inactivity bouts (rest) #911 @@ -25,8 +28,6 @@ relative to start and end of the most active time window as identified. #905 - Fix recently introduced bug where GGIR environment was not exported to cluster in GGIR part 1, 2, 3, and 5 #910 -- Part 5: Implemented sleep onset to sleep onset timewindow ("OO") #931 - # CHANGES IN GGIR VERSION 2.10-3 - Part 1: Fixed minor bug in ismovisens that failed when datadir started with "./" #897