Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to several smaller issues relating to visualreport, diary handling, and simplifying usage of MVPA parameters #1254

Merged
merged 12 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,26 @@
# CHANGES IN GGIR VERSION 3.1-??

- Part 2: Fix bug in determining the number of files to be included in the part 2 report.
- Part 2:

- Part 4: Further improvements to handling dates in sleeplog as a follow-up to work on #1243
- Fix bug in determining the number of files to be included in the part 2 report. This does not affect subsequent GGIR parts but does affect the part 2 report which may have had less recordings than expected. #1252

- Part 2 and 4: Both activity diary and sleep diary are now always reloaded if the derived copy of it (in .RData) is older than the diary itself.
- Speed up activity diary extraction. #1250

- Part 2: Make sure event diary is saved to csv with intended sep and dec arguments rather than default.
- Make sure event diary is saved to csv with intended sep and dec arguments rather than default.

- Simplify MVPA parameter specification in part 2 (mvpathreshold, boutcriter) by copying values from corresponding parameters in part 5 (threshold.mod and boutcriter.mvpa) if not specified. #1247

- Part 2 and 4: Both activity diary and sleep diary are now always reloaded if the derived copy of it (in .RData) is older than the diary itself. #1250

- Visual report: Recently added visual report is now also able to handle recordings with only 1 valid data, these were previously skipped. #1246

- Part 4:

- Improvements to handling dates in sleeplog as a follow-up to work on #1243

- Added warning when there are two accelerometer recordings with the same ID.

- Change extraction of imputation code from sleep diary, which is now assumed to correspond to preceding night. This is now also documented. #1251

# CHANGES IN GGIR VERSION 3.1-10

Expand Down
42 changes: 24 additions & 18 deletions R/check_params.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@ check_params = function(params_sleep = c(), params_metrics = c(),
x = params[[parname]]
if (parclass == "numeric") {
if (!is.numeric(x)) {
stop(paste0("\n", category, " argument ", parname, " is not ", parclass))
stop(paste0("\n", category, " parameter ", parname, " is not ", parclass))
}
}
if (parclass == "boolean") {
if (!is.logical(x)) {
stop(paste0("\n", category, " argument ", parname, " is not ", parclass))
stop(paste0("\n", category, " parameter ", parname, " is not ", parclass))
}
}
if (parclass == "character") {
if (!is.character(x)) {
stop(paste0("\n", category, " argument ", parname, " is not ", parclass))
stop(paste0("\n", category, " parameter ", parname, " is not ", parclass))
}
}
}
Expand Down Expand Up @@ -168,14 +168,14 @@ check_params = function(params_sleep = c(), params_metrics = c(),
if (params_metrics[["do.brondcounts"]] == TRUE) {
stop(paste0("\nThe brondcounts option has been deprecated following issues with the ",
"activityCounts package. We will reinsert brondcounts ",
"once the issues are resolved. Consider using argument do.neishabouricounts, ",
"once the issues are resolved. Consider using parameter do.neishabouricounts, ",
"for more information see package documentation."), call. = FALSE)
}
}

if (length(params_rawdata) > 0) {
if (params_rawdata[["frequency_tol"]] < 0 | params_rawdata[["frequency_tol"]] > 1) {
stop(paste0("\nArgument frequency_tol is ", params_rawdata[["frequency_tol"]],
stop(paste0("\nParameter frequency_tol is ", params_rawdata[["frequency_tol"]],
" , please adjust such that it is a number between 0 and 1"))
}
}
Expand Down Expand Up @@ -218,7 +218,7 @@ check_params = function(params_sleep = c(), params_metrics = c(),

if (params_sleep[["HASIB.algo"]] %in% c("Sadeh1994", "Galland2012", "ColeKripke1992") == TRUE) {
if (params_sleep[["Sadeh_axis"]] %in% c("X","Y","Z") == FALSE) {
warning("Argument Sadeh_axis does not have meaningful value, it needs to be X, Y or Z (capital)", call. = FALSE)
warning("Parameter Sadeh_axis does not have meaningful value, it needs to be X, Y or Z (capital)", call. = FALSE)
}
if (params_sleep[["Sadeh_axis"]] == "X" & params_metrics[["do.zcx"]] == FALSE) params_metrics[["do.zcx"]] = TRUE
if (params_sleep[["Sadeh_axis"]] == "Y" & params_metrics[["do.zcy"]] == FALSE) params_metrics[["do.zcy"]] = TRUE
Expand All @@ -233,7 +233,7 @@ check_params = function(params_sleep = c(), params_metrics = c(),
params_sleep[["HASPT.algo"]][1] %in% c("notused", "NotWorn") == FALSE) {
if (params_metrics[["do.anglex"]] == FALSE | params_metrics[["do.angley"]] == FALSE | params_metrics[["do.anglez"]] == FALSE) {
warning(paste0("\nWhen working with hip data all three angle metrics are needed,",
"so GGIR now auto-sets arguments do.anglex, do.angley, and do.anglez to TRUE."), call. = FALSE)
"so GGIR now auto-sets parameters do.anglex, do.angley, and do.anglez to TRUE."), call. = FALSE)
params_metrics[["do.anglex"]] = params_metrics[["do.angley"]] = params_metrics[["do.anglez"]] = TRUE
}
if (length(params_sleep[["HASPT.algo"]]) == 1 && params_sleep[["HASPT.algo"]][1] != "HorAngle") {
Expand All @@ -256,7 +256,7 @@ check_params = function(params_sleep = c(), params_metrics = c(),
if (length(params_sleep[["loglocation"]]) > 0 & length(params_sleep[["def.noc.sleep"]]) != 1) {
warning(paste0("\nloglocation was specified and def.noc.sleep does not have length of 1, this is not compatible. ",
" We assume you want to use the sleeplog and misunderstood",
" argument def.noc.sleep. Therefore, we will reset def.noc.sleep to its default value of 1"), call. = FALSE)
" parameter def.noc.sleep. Therefore, we will reset def.noc.sleep to its default value of 1"), call. = FALSE)
params_sleep[["def.noc.sleep"]] = 1
}

Expand All @@ -281,21 +281,21 @@ check_params = function(params_sleep = c(), params_metrics = c(),
params_cleaning[["data_masking_strategy"]] = params_cleaning[["strategy"]]
}
if (params_cleaning[["data_masking_strategy"]] %in% c(2, 4) & params_cleaning[["hrs.del.start"]] != 0) {
warning(paste0("\nSetting argument hrs.del.start in combination with data_masking_strategy = ",
warning(paste0("\nSetting parameter hrs.del.start in combination with data_masking_strategy = ",
params_cleaning[["data_masking_strategy"]]," is not meaningful, because this is only used when straytegy = 1"), call. = FALSE)
}
if (params_cleaning[["data_masking_strategy"]] %in% c(2, 4) & params_cleaning[["hrs.del.end"]] != 0) {
warning(paste0("\nSetting argument hrs.del.end in combination with data_masking_strategy = ",
warning(paste0("\nSetting parameter hrs.del.end in combination with data_masking_strategy = ",
params_cleaning[["data_masking_strategy"]]," is not meaningful, because this is only used when straytegy = 1"), call. = FALSE)
}
if (!(params_cleaning[["data_masking_strategy"]] %in% c(3, 5)) & params_cleaning[["ndayswindow"]] != 7) {
warning(paste0("\nSetting argument ndayswindow in combination with data_masking_strategy = ",
warning(paste0("\nSetting parameter ndayswindow in combination with data_masking_strategy = ",
params_cleaning[["data_masking_strategy"]]," is not meaningful, because this is only used when data_masking_strategy = 3 or data_masking_strategy = 5"), call. = FALSE)
}
if (params_cleaning[["data_masking_strategy"]] == 5 &
params_cleaning[["ndayswindow"]] != round(params_cleaning[["ndayswindow"]])) {
newValue = round(params_cleaning[["ndayswindow"]])
warning(paste0("\nArgument ndayswindow has been rounded from ",
warning(paste0("\nParameter ndayswindow has been rounded from ",
params_cleaning[["ndayswindow"]], " to ", newValue, " days",
"because when data_masking_strategy == 5 we expect an integer value", call. = FALSE))
params_cleaning[["ndayswindow"]] = newValue
Expand Down Expand Up @@ -347,14 +347,20 @@ check_params = function(params_sleep = c(), params_metrics = c(),
if (length(params_phyact) > 0) {
if (length(params_phyact[["bout.metric"]]) > 0 |
length(params_phyact[["closedbout"]]) > 0) {
warning(paste0("\nArguments bout.metric and closedbout are no longer used",
" by GGIR, we now use one piece of code stored in",
warning(paste0("\nParameters bout.metric and closedbout are no longer used",
" by GGIR and ignored, we now use one piece of code stored in",
" function g.getbout."), call. = FALSE)
}
if (length(params_phyact[["mvpadur"]]) != 3) {
params_phyact[["mvpadur"]] = c(1,5,10)
warning("\nmvpadur needs to be a vector with length three, value now reset to default c(1, 5, 10)", call. = FALSE)
}
if (is.null(params_phyact[["mvpathreshold"]]) == TRUE) {
params_phyact[["mvpathreshold"]] = params_phyact[["threshold.mod"]]
}
if (is.null(params_phyact[["boutcriter"]]) == TRUE) {
params_phyact[["boutcriter"]] = params_phyact[["boutcriter.mvpa"]]
}
if ((length(params_phyact[["threshold.lig"]]) == 1 &&
length(params_phyact[["threshold.mod"]]) == 1 &&
length(params_phyact[["threshold.vig"]]) == 1) | is.null(params_phyact[["part6_threshold_combi"]])) {
Expand Down Expand Up @@ -423,7 +429,7 @@ check_params = function(params_sleep = c(), params_metrics = c(),
if (is.null(params_general[["recordingEndSleepHour"]]) & params_general[["expand_tail_max_hours"]] != 0) {
params_general[["recordingEndSleepHour"]] = 24 - params_general[["expand_tail_max_hours"]] # redefine the argument
params_general[["expand_tail_max_hours"]] = NULL # set to null so that it keeps this configuration in the config file for the next run of the script.
stop("\nThe argument expand_tail_max_hours has been replaced by",
stop("\nThe parameter expand_tail_max_hours has been replaced by",
" recordingEndSleepHour which has a different definition. Please",
" see the documentation for further details and replace",
" expand_tail_max_hour in your function call and config.csv file.", call. = FALSE)
Expand Down Expand Up @@ -475,7 +481,7 @@ check_params = function(params_sleep = c(), params_metrics = c(),
}
warning(paste0("\nWhen dataFormat is set to ", params_general[["dataFormat"]],
" we assume that only metric ZCY is extracted and",
" GGIR ignores all other metric requests. So, you should set arguments ",
" GGIR ignores all other metric requests. So, you should set parameters ",
paste0(metricsNotFalse, collapse = " & "), " to FALSE"), call. = FALSE)
# Turn all commonly used metrics to FALSE
params_metrics[["do.anglex"]] = params_metrics[["do.angley"]] = FALSE
Expand Down Expand Up @@ -515,7 +521,7 @@ check_params = function(params_sleep = c(), params_metrics = c(),
}
warning(paste0("\nWhen dataFormat is set to ukbiobank",
" we assume that only metric LFENMO is extracted and",
" GGIR ignores all other metric requests. So, you should set arguments ",
" GGIR ignores all other metric requests. So, you should set parameters ",
paste0(metricsNotFalse, collapse = " & "), " to FALSE"), call. = FALSE)
# Turn all commonly used metrics to FALSE
params_metrics[["do.anglex"]] = params_metrics[["do.angley"]] = FALSE
Expand Down Expand Up @@ -573,7 +579,7 @@ check_params = function(params_sleep = c(), params_metrics = c(),
call. = FALSE)
}
if (length(params_cleaning[["segmentDAYSPTcrit.part5"]]) != 2) {
stop("\nArgument segmentDAYSPTcrit.part5 is expected to be a numeric vector of length 2", call. = FALSE)
stop("\nParameter segmentDAYSPTcrit.part5 is expected to be a numeric vector of length 2", call. = FALSE)
}
if (any(params_cleaning[["segmentDAYSPTcrit.part5"]] < 0) |
any(params_cleaning[["segmentDAYSPTcrit.part5"]] > 1)) {
Expand Down
70 changes: 37 additions & 33 deletions R/g.conv.actlog.R
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
g.conv.actlog = function(qwindow, qwindow_dateformat="%d-%m-%Y", epochSize = 5) {
g.conv.actlog = function(qwindow, qwindow_dateformat = "%d-%m-%Y", epochSize = 5) {
# Function to read activity log and convert it into data.frame
# that has for each ID and date a different qwindow vector
# local functions:
Expand Down Expand Up @@ -32,74 +32,78 @@ g.conv.actlog = function(qwindow, qwindow_dateformat="%d-%m-%Y", epochSize = 5)

# find dates
actlog_vec = unlist(actlog) # turn into vector
actlog_vec = sapply(actlog_vec, function(x) !all(is.na(as.Date(as.character(x),format=qwindow_dateformat))))
actlog_vec = sapply(actlog_vec, FUN = function(x) !all(is.na(as.Date(as.character(x),
format = qwindow_dateformat))))
exampledates = unlist(actlog)[which(actlog_vec == TRUE)]
Ndates = length(which(actlog_vec == TRUE))
dim(actlog_vec) = c(nrow(actlog),ncol(actlog))
dim(actlog_vec) = c(nrow(actlog), ncol(actlog))
# create new qwindow object to archive all extracted information
qwindow = data.frame(ID = rep(0,Ndates), date = rep("",Ndates))
qwindow = data.frame(ID = rep(0, Ndates), date = rep("", Ndates))
qwindow$qwindow_times = qwindow$qwindow_values = qwindow$qwindow_names = c()
cnt = 1
for (i in 1:nrow(actlog)) { # loop over rows in activity log = recordings
datei = which(actlog_vec[i,] == TRUE)
processLogRow = function(actlog, qwindow_dateformat = qwindow_dateformat) {
actlog_vec = unlist(actlog) # turn into vector
actlog_vec = sapply(actlog_vec, FUN = function(x) !all(is.na(as.Date(as.character(x),
format = qwindow_dateformat))))
datei = which(actlog_vec == TRUE)
Ndays = length(datei)
out = data.frame(ID = character(Ndays), date = character(Ndays))
out$qwindow_times = out$qwindow_values = qwindow$qwindow_names = NULL
if (Ndays > 0) {
qwindow$ID[cnt:(cnt+Ndays-1)] = rep(actlog[i,1],Ndays)
qwindow$date[cnt:(cnt+Ndays-1)] = as.character(as.Date(as.character(actlog[i,datei]),format=qwindow_dateformat))
out$ID[1:Ndays] = rep(actlog[1], Ndays)
out$date[1:Ndays] = as.character(as.Date(as.character(actlog[datei]), format = qwindow_dateformat))
# datei are the indices of the date columns
# add number to be able to identify last class after last date
datei = c(datei,max(which(actlog[i,] != "")) + 1)
for (j in 1:(length(datei)-1)) { # loop over days within the recording
datei = c(datei, max(which(actlog != "")) + 1)
for (j in 1:(length(datei) - 1)) { # loop over days within the recording
# Note: qindow is a data.frame and the timestamps for each day will be stored
# as a list object in a single cell of qwindow. The same applies ot the corresponding
# labels.
k = cnt + j - 1
if ((datei[j + 1] - datei[j]) >= 2) {
actlog_tmp = actlog[i,(datei[j] + 1):(datei[j + 1] - 1)]
actlog_tmp = actlog[(datei[j] + 1):(datei[j + 1] - 1)]
actlog_tmp = actlog_tmp[which(is.na(actlog_tmp) == FALSE & actlog_tmp != "")]
qwindow$qwindow_times[k] = list(actlog_tmp) # list of times for that day
out$qwindow_times[k] = list(actlog_tmp) # list of times for that day
# make sure that seconds are multiple of epochSize
seconds = data.table::second(strptime(qwindow$qwindow_times[[k]], format = "%H:%M:%S"))
seconds = data.table::second(strptime(out$qwindow_times[[k]], format = "%H:%M:%S"))
to_next_epoch = which(seconds %% epochSize != 0)
if (length(to_next_epoch) > 0) {
timechar = qwindow$qwindow_times[[k]][to_next_epoch]
timechar = out$qwindow_times[[k]][to_next_epoch]
time = strptime(timechar, format = "%H:%M:%S")
seconds2sum = epochSize - seconds[to_next_epoch] %% epochSize
time = time + seconds2sum
timechar_new = as.character(time)
time_new = unlist(lapply(timechar_new, FUN = function(x) strsplit(x, " ", fixed = T)[[1]][2]))
qwindow$qwindow_times[[k]][to_next_epoch] = time_new
out$qwindow_times[[k]][to_next_epoch] = time_new
}
qwindow$qwindow_values[k] = list(time2numeric(qwindow$qwindow_times[k]))
qwindow$qwindow_names[k] = list(extract_names(qwindow$qwindow_times[k]))
unlisted_qv = unlist(qwindow$qwindow_values[k])
unlisted_qt = unlist(qwindow$qwindow_times[k])
unlisted_qn = unlist(qwindow$qwindow_names[k])

out$qwindow_values[k] = list(time2numeric(out$qwindow_times[k]))
out$qwindow_names[k] = list(extract_names(out$qwindow_times[k]))
unlisted_qv = unlist(out$qwindow_values[k])
if (length(which(is.na(unlisted_qv) == FALSE)) > 0) {
unlisted_qn = unlist(out$qwindow_names[k])
unlisted_qt = unlist(out$qwindow_times[k])
if (min(unlisted_qv, na.rm = TRUE) > 0) {
qwindow$qwindow_values[k] = list(c(0, unlisted_qv))
qwindow$qwindow_times[k] = list(c("00:00", unlisted_qt))
qwindow$qwindow_names[k] = list(c("daystart", unlisted_qn))
out$qwindow_values[k] = list(c(0, unlisted_qv))
out$qwindow_times[k] = list(c("00:00", unlisted_qt))
out$qwindow_names[k] = list(c("daystart", unlisted_qn))
}
if (max(unlisted_qv, na.rm = TRUE) < 24) {
qwindow$qwindow_values[k] = list(c(unlist(qwindow$qwindow_values[k]), 24))
qwindow$qwindow_times[k] = list(c(unlist(qwindow$qwindow_times[k]), "24:00:00"))
qwindow$qwindow_names[k] = list(c(unlist(qwindow$qwindow_names[k]),"dayend"))
out$qwindow_values[k] = list(c(unlist(out$qwindow_values[k]), 24))
out$qwindow_times[k] = list(c(unlist(out$qwindow_times[k]), "24:00:00"))
out$qwindow_names[k] = list(c(unlist(out$qwindow_names[k]),"dayend"))
}
}
} else {
qwindow$qwindow_values[k] = list("")
qwindow$qwindow_names[k] = list("")
qwindow$qwindow_times[k] = list("")
out$qwindow_values[k] = out$qwindow_names[k] = out$qwindow_times[k] = list("")
}
}
cnt = cnt + Ndays
}
return(out)
}

test = apply(X = actlog, MARGIN = 1, FUN = processLogRow, qwindow_dateformat)
qwindow = do.call(what = rbind, args = test)
# When testing the code it seemed sometimes not to recognise the date.
# The following lines should hopefully help to catch any errors people may encounter.
# The following lines should help to catch any errors people may encounter.
if (is.na(as.Date(qwindow$date[1], format = "%y-%m-%d")) == FALSE) {
qwindow$date = as.Date(qwindow$date, format = "%y-%m-%d")
} else {
Expand Down
Loading
Loading