From 9a826347e04f74413d2a66a9693c32b8074aeac6 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 6 Sep 2023 17:27:06 +0200 Subject: [PATCH 01/36] Version 23.09.1: Feat/26 extend x scale (#29) * extract function to add x scale * extend x axis label, replace aes_string with aes * integrate checkbox for extending labels * update news.md * update test * update extendXAxis function * fix docu --- DESCRIPTION | 2 +- NAMESPACE | 2 +- NEWS.md | 6 +++ R/NAMESPACE.R | 2 +- R/plots.R | 80 +++++++++++++++++++++++++++---------- inst/app/server.R | 12 ++++-- inst/app/ui.R | 5 ++- man/extendXAxis.Rd | 19 +++++++++ man/plotTime.Rd | 3 ++ tests/testthat/test-plots.R | 17 ++++++++ 10 files changed, 120 insertions(+), 28 deletions(-) create mode 100644 man/extendXAxis.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 4486f4c..4f48f9f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.04.1.7 +Version: 23.09.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/NAMESPACE b/NAMESPACE index 05ea271..fcac8ab 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -51,7 +51,7 @@ importFrom(dplyr,arrange) importFrom(dplyr,bind_rows) importFrom(dplyr,distinct) importFrom(dplyr,slice) -importFrom(ggplot2,aes_string) +importFrom(ggplot2,aes) importFrom(ggplot2,coord_cartesian) importFrom(ggplot2,element_line) importFrom(ggplot2,element_text) diff --git a/NEWS.md b/NEWS.md index ddb8959..b179a0c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # OsteoBioR +## Version 23.09.1 + +### New Features +- tab _Model_, section _Credibility intervals over time_: new checkbox to extend the x-axis labels +to the _lower_ and _upper x limit_ (#26) + ## Version 23.04.1 ### Bug Fixes diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 49b09aa..0a7984c 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -15,7 +15,7 @@ #' @importFrom DataTools checkAnyNonNumericColumns importDataUI importDataServer remoteModelsUI #' remoteModelsServer tryCatchWithWarningsAndErrors #' @importFrom dplyr arrange bind_rows distinct slice -#' @importFrom ggplot2 aes_string element_line element_text ggplot geom_line geom_point geom_ribbon +#' @importFrom ggplot2 aes element_line element_text ggplot geom_line geom_point geom_ribbon #' labs scale_x_continuous theme ggtitle scale_y_continuous geom_vline coord_cartesian sec_axis #' @importFrom htmltools save_html #' @importFrom jsonlite toJSON diff --git a/R/plots.R b/R/plots.R index 7584404..75ad257 100644 --- a/R/plots.R +++ b/R/plots.R @@ -22,6 +22,8 @@ #' @param sizeAxisX size text x axis label #' @param sizeAxisY size text y axis label #' @param secAxis boolean secondary axis on right side? +#' @param extendLabels boolean if TRUE, extend the labels of the x-axis to the x-axis limits. +#' If FALSE, the range of the data defines the range of x-axis labels. #' @param ... arguments handed to \code{\link{getShiftTime}} #' #' @return a \link[ggplot2]{ggplot} object. @@ -32,25 +34,20 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, oldPlot = NULL, oldXAxisData = data.frame(), deriv = "1", colorL = NULL, colorU = NULL, alphaL = 0.9, alphaU = 0.1, sizeTextY = 12, sizeTextX = 12, sizeAxisX = 12, sizeAxisY = 12, secAxis = FALSE, - xAxisLabel = "Time", yAxisLabel = "Estimate", + xAxisLabel = "Time", yAxisLabel = "Estimate", extendLabels = FALSE, ...){ stopifnot(prop < 1) x <- getPlotData(object, prop = prop, deriv = deriv) x$time <- adjustTimeColumn(objectTime = object@time, deriv = deriv) - newXAxisData <- getXAxisData(object, oldXAxisData = oldXAxisData) - breaks <- getBreaks(time = newXAxisData$time, deriv = deriv) - labels <- getLabel(xAxisData = newXAxisData, deriv = deriv) - if (nrow(x) > 1) lineFct <- geom_line else lineFct <- geom_point if(is.null(oldPlot)){ - p <- ggplot(x, aes_string(x = "time")) + - lineFct(aes_string(y = "median"), colour = colorL, alpha = alphaL) + - lineFct(aes_string(y = "lower"), size = 0.05, colour = colorL, alpha = alphaL) + - lineFct(aes_string(y = "upper"), size = 0.05, colour = colorL, alpha = alphaL) + - geom_point(aes_string(x = "time", y = "median"), colour = colorL, alpha = alphaL) + - scale_x_continuous(breaks = breaks, labels = labels) + + p <- ggplot(x, aes(x = .data[["time"]])) + + lineFct(aes(y = .data[["median"]]), colour = colorL, alpha = alphaL) + + lineFct(aes(y = .data[["lower"]]), size = 0.05, colour = colorL, alpha = alphaL) + + lineFct(aes(y = .data[["upper"]]), size = 0.05, colour = colorL, alpha = alphaL) + + geom_point(aes(x = .data[["time"]], y = .data[["median"]]), colour = colorL, alpha = alphaL) + coord_cartesian(ylim = yLim, xlim = xLim) + labs(title = paste0(prop * 100, "%-Credibility-Interval for isotopic values over time"), x = xAxisLabel, y = yAxisLabel) + theme(panel.grid.major.x = element_line(size = 0.1)) + @@ -58,7 +55,7 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, axis.title.y = element_text(size = sizeTextY), axis.text.x = element_text(size = sizeAxisX), axis.text.y = element_text(size = sizeAxisY)) - if (nrow(x) > 1) p <- p + geom_ribbon(aes_string(ymin = "lower", ymax = "upper"), + if (nrow(x) > 1) p <- p + geom_ribbon(aes(ymin = .data[["lower"]], ymax = .data[["upper"]]), linetype = 2, alpha = alphaU, fill = colorU) } else { if(secAxis){ @@ -73,12 +70,11 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, x$lower <- (x$lower - center ) / scale x$upper <- (x$upper - center ) / scale } - p <- oldPlot + geom_line(data = x, aes_string(y = "median"), colour = colorL, alpha = alphaL) + - geom_line(data = x, aes_string(y = "lower"), size = 0.05, colour = colorL, alpha = alphaL) + - geom_line(data = x, aes_string(y = "upper"), size = 0.05, colour = colorL, alpha = alphaL) + - geom_ribbon(data = x, aes_string(ymin = "lower", ymax = "upper"), linetype = 2, alpha = alphaU, fill = colorU) + - geom_point(data = x, aes_string(x = "time", y = "median"), colour = colorL, alpha = alphaL) + - scale_x_continuous(breaks = breaks, labels = labels) + + p <- oldPlot + geom_line(data = x, aes(y = .data[["median"]]), colour = colorL, alpha = alphaL) + + geom_line(data = x, aes(y = .data[["lower"]]), size = 0.05, colour = colorL, alpha = alphaL) + + geom_line(data = x, aes(y = .data[["upper"]]), size = 0.05, colour = colorL, alpha = alphaL) + + geom_ribbon(data = x, aes(ymin = .data[["lower"]], ymax = .data[["upper"]]), linetype = 2, alpha = alphaU, fill = colorU) + + geom_point(data = x, aes(x = .data[["time"]], y = .data[["median"]]), colour = colorL, alpha = alphaL) + labs(title = paste0(prop * 100, "%-Credibility-Interval for isotopic values over time"), x = "Time", y = "Estimation") @@ -92,12 +88,23 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, ) } } - + xAxisData <- getXAxisData(object = object, oldXAxisData = oldXAxisData) + xAxisData <- xAxisData %>% + extendXAxis(deriv = deriv, + xLim = ifelse(extendLabels, xLim, range(xAxisData))) + + breaks <- getBreaks(time = xAxisData$time, deriv = deriv) + labels <- getLabel(xAxisData = xAxisData, deriv = deriv) + + p <- p + + scale_x_continuous(breaks = breaks, labels = labels) + if (plotShifts){ index <- getShiftIndex(object, ...) p <- p + geom_vline(xintercept = breaks[which(index)] + 0.5, col = "darkgrey") } + p } @@ -147,7 +154,6 @@ adjustTimeColumn <- function(objectTime, deriv){ res } - #' Get X-Axis Data #' #' @inheritParams plotTime @@ -170,6 +176,40 @@ getXAxisData <- function(object, oldXAxisData = data.frame()){ xAxisData } +#' Extend X Axis +#' +#' Add breaks and labels for x axis +#' +#' @param xAxisData (data.frame) data.frame containing "time", "lower" and "upper" columns used for +#' the x axis. +#' @inheritParams plotTime +extendXAxis <- function(xAxisData, deriv, xLim) { + if (min(xLim) < min(xAxisData)) { + # add new row at the beginning + newFirstRow <- data.frame( + "time" = mean(c(min(xLim), min(xAxisData))), + "lower" = min(xLim), + "upper" = min(xAxisData) + ) + + xAxisData <- rbind(newFirstRow, + xAxisData) + } + + if (max(xLim) > max(xAxisData)) { + # add new row at the end + newLastRow <- data.frame( + "time" = mean(c(max(xAxisData), max(xLim))), + "lower" = max(xAxisData), + "upper" = max(xLim) + ) + + xAxisData <- rbind(xAxisData, + newLastRow) + } + + xAxisData +} #' Get Label #' diff --git a/inst/app/server.R b/inst/app/server.R index 0d10a78..784f75e 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -485,7 +485,8 @@ shinyServer(function(input, output, session) { colorL = input$colorL, colorU = input$colorU, alphaL = input$alphaL, alphaU = input$alphaU, xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, - sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY) + sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, + extendLabels = input$extendLabels) intervalTimePlot(p) savedPlot(p) #savedXAxisData(getXAxisData(fitForTimePlot())) @@ -501,7 +502,8 @@ shinyServer(function(input, output, session) { colorL = input$colorL, colorU = input$colorU, alphaL = input$alphaL, alphaU = input$alphaU, xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, - sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY) + sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, + extendLabels = input$extendLabels) intervalTimePlot(p) savedPlot(p) #savedXAxisData(getXAxisData(fitForTimePlot())) @@ -515,7 +517,8 @@ shinyServer(function(input, output, session) { colorL = input$colorL, colorU = input$colorU, alphaL = input$alphaL, alphaU = input$alphaU, xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, - sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY) + sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, + extendLabels = input$extendLabels) intervalTimePlot(p) savedPlot(p) #savedXAxisData(getXAxisData(fitForTimePlot())) @@ -533,7 +536,8 @@ shinyServer(function(input, output, session) { alphaL = input$alphaL, alphaU = input$alphaU, sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, - sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, secAxis = input$secAxis) + sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, secAxis = input$secAxis, + extendLabels = input$extendLabels) intervalTimePlot(p) savedPlot(p) #savedXAxisData(getXAxisData(object = fitForTimePlot(), oldXAxisData = oldXAxisData)) diff --git a/inst/app/ui.R b/inst/app/ui.R index ab1483f..830d1a6 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -183,7 +183,10 @@ tagList( value = defaultInputsForUI()$xmax), textInput("xAxisLabel", label = "X-Axis title", value = "Time"), numericInput(inputId = "sizeTextX", label = "Font size x-axis title", value = 24), - numericInput(inputId = "sizeAxisX", label = "Font size x-axis", value = 18) + numericInput(inputId = "sizeAxisX", label = "Font size x-axis", value = 18), + checkboxInput(inputId = "extendLabels", + label = "Extend x-axis labels to lower and upper limits", + value = FALSE) ), column(2, numericInput("ymin", "Lower y limit", diff --git a/man/extendXAxis.Rd b/man/extendXAxis.Rd new file mode 100644 index 0000000..331fb7e --- /dev/null +++ b/man/extendXAxis.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plots.R +\name{extendXAxis} +\alias{extendXAxis} +\title{Extend X Axis} +\usage{ +extendXAxis(xAxisData, deriv, xLim) +} +\arguments{ +\item{xAxisData}{(data.frame) data.frame containing "time", "lower" and "upper" columns used for +the x axis.} + +\item{deriv}{character "1" for absolute values, "2" for first differences} + +\item{xLim}{numeric vector of length 2: range of x axis} +} +\description{ +Add breaks and labels for x axis +} diff --git a/man/plotTime.Rd b/man/plotTime.Rd index a1ed767..17eeb66 100644 --- a/man/plotTime.Rd +++ b/man/plotTime.Rd @@ -24,6 +24,7 @@ plotTime( secAxis = FALSE, xAxisLabel = "Time", yAxisLabel = "Estimate", + extendLabels = FALSE, ... ) } @@ -66,6 +67,8 @@ plotTime( \item{yAxisLabel}{character label y-axis} +\item{extendLabels}{boolean if TRUE, extend the labels of the x-axis to the x-axis limits. If FALSE, the range of the data defines the range of x-axis labels.} + \item{...}{arguments handed to \code{\link{getShiftTime}}} } \value{ diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R index 2edd086..fb46635 100644 --- a/tests/testthat/test-plots.R +++ b/tests/testthat/test-plots.R @@ -195,3 +195,20 @@ testthat::test_that("getDefaultPlotRange, deriv = 2", { c(xmin = 1, xmax = 5.5, ymin = -4.2100298618177, ymax = 3.69646935224212)) }) + +testthat::test_that("extendXAxis", { + oldXAxisData <- structure(list( + time = c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5), + lower = c(0, 1, 2, 3, 4, 5), + upper = c(1, 2, 3, 4, 5, 6)), + class = "data.frame", + row.names = c(NA, -6L)) + + xAxisData <- getXAxisData(object = testObjectDefault1, oldXAxisData = oldXAxisData) + + testXAxisData <- xAxisData %>% + extendXAxis(deriv = "1", + xLim = c(-1, 8)) + + expect_equal(nrow(xAxisData) + 2, nrow(testXAxisData)) +}) From 6fe29ef2b0808339505ebe9069e4c038e3fe2f38 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Thu, 7 Sep 2023 18:23:49 +0200 Subject: [PATCH 02/36] remove not needed --- DESCRIPTION | 2 +- R/plots.R | 9 ++++----- man/extendXAxis.Rd | 4 +--- tests/testthat/test-plots.R | 3 +-- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4f48f9f..4ff9b32 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.09.1 +Version: 23.09.1.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/plots.R b/R/plots.R index 75ad257..dc68c61 100644 --- a/R/plots.R +++ b/R/plots.R @@ -91,8 +91,7 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, xAxisData <- getXAxisData(object = object, oldXAxisData = oldXAxisData) xAxisData <- xAxisData %>% - extendXAxis(deriv = deriv, - xLim = ifelse(extendLabels, xLim, range(xAxisData))) + extendXAxis(xLim = ifelse(extendLabels, xLim, range(xAxisData))) breaks <- getBreaks(time = xAxisData$time, deriv = deriv) labels <- getLabel(xAxisData = xAxisData, deriv = deriv) @@ -183,7 +182,7 @@ getXAxisData <- function(object, oldXAxisData = data.frame()){ #' @param xAxisData (data.frame) data.frame containing "time", "lower" and "upper" columns used for #' the x axis. #' @inheritParams plotTime -extendXAxis <- function(xAxisData, deriv, xLim) { +extendXAxis <- function(xAxisData, xLim) { if (min(xLim) < min(xAxisData)) { # add new row at the beginning newFirstRow <- data.frame( @@ -193,7 +192,7 @@ extendXAxis <- function(xAxisData, deriv, xLim) { ) xAxisData <- rbind(newFirstRow, - xAxisData) + xAxisData) } if (max(xLim) > max(xAxisData)) { @@ -205,7 +204,7 @@ extendXAxis <- function(xAxisData, deriv, xLim) { ) xAxisData <- rbind(xAxisData, - newLastRow) + newLastRow) } xAxisData diff --git a/man/extendXAxis.Rd b/man/extendXAxis.Rd index 331fb7e..761dc4c 100644 --- a/man/extendXAxis.Rd +++ b/man/extendXAxis.Rd @@ -4,14 +4,12 @@ \alias{extendXAxis} \title{Extend X Axis} \usage{ -extendXAxis(xAxisData, deriv, xLim) +extendXAxis(xAxisData, xLim) } \arguments{ \item{xAxisData}{(data.frame) data.frame containing "time", "lower" and "upper" columns used for the x axis.} -\item{deriv}{character "1" for absolute values, "2" for first differences} - \item{xLim}{numeric vector of length 2: range of x axis} } \description{ diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R index fb46635..2f10f92 100644 --- a/tests/testthat/test-plots.R +++ b/tests/testthat/test-plots.R @@ -207,8 +207,7 @@ testthat::test_that("extendXAxis", { xAxisData <- getXAxisData(object = testObjectDefault1, oldXAxisData = oldXAxisData) testXAxisData <- xAxisData %>% - extendXAxis(deriv = "1", - xLim = c(-1, 8)) + extendXAxis(xLim = c(-1, 8)) expect_equal(nrow(xAxisData) + 2, nrow(testXAxisData)) }) From 360666d88efc37c94e636dcd235e9547780f20f1 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 12 Sep 2023 09:29:52 +0200 Subject: [PATCH 03/36] add column name --- DESCRIPTION | 2 +- R/plots.R | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 4ff9b32..46c0d15 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.09.1.1 +Version: 23.09.1.2 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/plots.R b/R/plots.R index dc68c61..858b9f4 100644 --- a/R/plots.R +++ b/R/plots.R @@ -183,23 +183,23 @@ getXAxisData <- function(object, oldXAxisData = data.frame()){ #' the x axis. #' @inheritParams plotTime extendXAxis <- function(xAxisData, xLim) { - if (min(xLim) < min(xAxisData)) { + if (min(xLim) < min(xAxisData[["lower"]])) { # add new row at the beginning newFirstRow <- data.frame( - "time" = mean(c(min(xLim), min(xAxisData))), + "time" = mean(c(min(xLim), min(xAxisData[["lower"]]))), "lower" = min(xLim), - "upper" = min(xAxisData) + "upper" = min(xAxisData[["lower"]]) ) xAxisData <- rbind(newFirstRow, xAxisData) } - if (max(xLim) > max(xAxisData)) { + if (max(xLim) > max(xAxisData[["upper"]])) { # add new row at the end newLastRow <- data.frame( - "time" = mean(c(max(xAxisData), max(xLim))), - "lower" = max(xAxisData), + "time" = mean(c(max(xAxisData[["upper"]]), max(xLim))), + "lower" = max(xAxisData[["upper"]]), "upper" = max(xLim) ) From 42953de7e3679b0d0290af30bb97bf22358974ee Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 12 Sep 2023 13:22:31 +0200 Subject: [PATCH 04/36] add test, fix labs --- DESCRIPTION | 2 +- R/plots.R | 11 +++++------ tests/testthat/test-plots.R | 8 +++++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 46c0d15..dfe2723 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.09.1.2 +Version: 23.09.1.3 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/plots.R b/R/plots.R index 858b9f4..a5ca88b 100644 --- a/R/plots.R +++ b/R/plots.R @@ -49,8 +49,7 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, lineFct(aes(y = .data[["upper"]]), size = 0.05, colour = colorL, alpha = alphaL) + geom_point(aes(x = .data[["time"]], y = .data[["median"]]), colour = colorL, alpha = alphaL) + coord_cartesian(ylim = yLim, xlim = xLim) + - labs(title = paste0(prop * 100, "%-Credibility-Interval for isotopic values over time"), - x = xAxisLabel, y = yAxisLabel) + theme(panel.grid.major.x = element_line(size = 0.1)) + + theme(panel.grid.major.x = element_line(size = 0.1)) + theme(axis.title.x = element_text(size = sizeTextX), axis.title.y = element_text(size = sizeTextY), axis.text.x = element_text(size = sizeAxisX), @@ -74,9 +73,7 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, geom_line(data = x, aes(y = .data[["lower"]]), size = 0.05, colour = colorL, alpha = alphaL) + geom_line(data = x, aes(y = .data[["upper"]]), size = 0.05, colour = colorL, alpha = alphaL) + geom_ribbon(data = x, aes(ymin = .data[["lower"]], ymax = .data[["upper"]]), linetype = 2, alpha = alphaU, fill = colorU) + - geom_point(data = x, aes(x = .data[["time"]], y = .data[["median"]]), colour = colorL, alpha = alphaL) + - labs(title = paste0(prop * 100, "%-Credibility-Interval for isotopic values over time"), - x = "Time", y = "Estimation") + geom_point(data = x, aes(x = .data[["time"]], y = .data[["median"]]), colour = colorL, alpha = alphaL) if(secAxis){ p <- p + @@ -97,7 +94,9 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, labels <- getLabel(xAxisData = xAxisData, deriv = deriv) p <- p + - scale_x_continuous(breaks = breaks, labels = labels) + scale_x_continuous(breaks = breaks, labels = labels) + + labs(title = paste0(prop * 100, "%-Credibility-Interval for isotopic values over time"), + x = xAxisLabel, y = yAxisLabel) if (plotShifts){ index <- getShiftIndex(object, ...) diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R index 2f10f92..414499b 100644 --- a/tests/testthat/test-plots.R +++ b/tests/testthat/test-plots.R @@ -209,5 +209,11 @@ testthat::test_that("extendXAxis", { testXAxisData <- xAxisData %>% extendXAxis(xLim = c(-1, 8)) - expect_equal(nrow(xAxisData) + 2, nrow(testXAxisData)) + testthat::expect_equal(nrow(xAxisData) + 2, nrow(testXAxisData)) + + breaks <- getBreaks(time = testXAxisData$time, deriv = "1") + labels <- getLabel(xAxisData = testXAxisData, deriv = "1") + + testthat::expect_equal(breaks, c(-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 7)) + testthat::expect_equal(labels, c("[-1-0]", "[0-1]", "[1-2]", "[2-3]", "[3-4]", "[4-5]", "[5-6]", "[6-8]")) }) From a0850677000e6ae1a449e7abff4acbf7d50ef0cb Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 4 Oct 2023 18:16:15 +0200 Subject: [PATCH 05/36] fix label for upper xLim (#32) --- DESCRIPTION | 2 +- R/plots.R | 21 ++++++++++++--------- man/extendXAxis.Rd | 4 ++-- man/plotTime.Rd | 3 ++- tests/testthat/test-plots.R | 2 +- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index dfe2723..2251102 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.09.1.3 +Version: 23.09.1.4 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/plots.R b/R/plots.R index a5ca88b..fe91f78 100644 --- a/R/plots.R +++ b/R/plots.R @@ -87,8 +87,11 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, } xAxisData <- getXAxisData(object = object, oldXAxisData = oldXAxisData) + + xLabelLim <- range(xAxisData) + if (extendLabels) xLabelLim <- xLim xAxisData <- xAxisData %>% - extendXAxis(xLim = ifelse(extendLabels, xLim, range(xAxisData))) + extendXAxis(xLabelLim = xLabelLim) breaks <- getBreaks(time = xAxisData$time, deriv = deriv) labels <- getLabel(xAxisData = xAxisData, deriv = deriv) @@ -180,13 +183,13 @@ getXAxisData <- function(object, oldXAxisData = data.frame()){ #' #' @param xAxisData (data.frame) data.frame containing "time", "lower" and "upper" columns used for #' the x axis. -#' @inheritParams plotTime -extendXAxis <- function(xAxisData, xLim) { - if (min(xLim) < min(xAxisData[["lower"]])) { +#' @param xLabelLim numeric vector of length 2: range of labels of x axis +extendXAxis <- function(xAxisData, xLabelLim) { + if (min(xLabelLim) < min(xAxisData[["lower"]])) { # add new row at the beginning newFirstRow <- data.frame( - "time" = mean(c(min(xLim), min(xAxisData[["lower"]]))), - "lower" = min(xLim), + "time" = mean(c(min(xLabelLim), min(xAxisData[["lower"]]))), + "lower" = min(xLabelLim), "upper" = min(xAxisData[["lower"]]) ) @@ -194,12 +197,12 @@ extendXAxis <- function(xAxisData, xLim) { xAxisData) } - if (max(xLim) > max(xAxisData[["upper"]])) { + if (max(xLabelLim) > max(xAxisData[["upper"]])) { # add new row at the end newLastRow <- data.frame( - "time" = mean(c(max(xAxisData[["upper"]]), max(xLim))), + "time" = mean(c(max(xAxisData[["upper"]]), max(xLabelLim))), "lower" = max(xAxisData[["upper"]]), - "upper" = max(xLim) + "upper" = max(xLabelLim) ) xAxisData <- rbind(xAxisData, diff --git a/man/extendXAxis.Rd b/man/extendXAxis.Rd index 761dc4c..98fe339 100644 --- a/man/extendXAxis.Rd +++ b/man/extendXAxis.Rd @@ -4,13 +4,13 @@ \alias{extendXAxis} \title{Extend X Axis} \usage{ -extendXAxis(xAxisData, xLim) +extendXAxis(xAxisData, xLabelLim) } \arguments{ \item{xAxisData}{(data.frame) data.frame containing "time", "lower" and "upper" columns used for the x axis.} -\item{xLim}{numeric vector of length 2: range of x axis} +\item{xLabelLim}{numeric vector of length 2: range of labels of x axis} } \description{ Add breaks and labels for x axis diff --git a/man/plotTime.Rd b/man/plotTime.Rd index 17eeb66..2c93a23 100644 --- a/man/plotTime.Rd +++ b/man/plotTime.Rd @@ -67,7 +67,8 @@ plotTime( \item{yAxisLabel}{character label y-axis} -\item{extendLabels}{boolean if TRUE, extend the labels of the x-axis to the x-axis limits. If FALSE, the range of the data defines the range of x-axis labels.} +\item{extendLabels}{boolean if TRUE, extend the labels of the x-axis to the x-axis limits. +If FALSE, the range of the data defines the range of x-axis labels.} \item{...}{arguments handed to \code{\link{getShiftTime}}} } diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R index 414499b..fa95fa1 100644 --- a/tests/testthat/test-plots.R +++ b/tests/testthat/test-plots.R @@ -207,7 +207,7 @@ testthat::test_that("extendXAxis", { xAxisData <- getXAxisData(object = testObjectDefault1, oldXAxisData = oldXAxisData) testXAxisData <- xAxisData %>% - extendXAxis(xLim = c(-1, 8)) + extendXAxis(xLabelLim = c(-1, 8)) testthat::expect_equal(nrow(xAxisData) + 2, nrow(testXAxisData)) From dcb82cf643fc8e109686089fa9a090f2133364ee Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Fri, 6 Oct 2023 21:31:26 +0200 Subject: [PATCH 06/36] Feat/ apply model import from dataTools (#33) * prepare usage of modules from dataTools * integrate upload from import module * update docu * fix import of model files * update UI, remove right sidebar in model tab * add renaming of models after upload if names already exist * clean up namespace, add package to call * remove comments * clean up --- DESCRIPTION | 5 +- NAMESPACE | 9 +- NEWS.md | 6 + R/02-downAndUploadModel.R | 204 ------------------------------ R/02_extractSavedModels.R | 61 +++++++++ R/NAMESPACE.R | 4 +- inst/app/server.R | 102 ++++++++++++--- inst/app/ui.R | 16 +-- inst/config.yaml | 4 + man/combineDataAndModelOutputs.Rd | 16 +++ man/downloadModel.Rd | 31 ----- man/extractModel.Rd | 15 --- man/extractModelOutputs.Rd | 14 ++ man/extractSavedModels.Rd | 14 ++ man/removeModelOutputs.Rd | 14 ++ man/uploadModel.Rd | 52 -------- 16 files changed, 228 insertions(+), 339 deletions(-) delete mode 100644 R/02-downAndUploadModel.R create mode 100644 R/02_extractSavedModels.R create mode 100644 inst/config.yaml create mode 100644 man/combineDataAndModelOutputs.Rd delete mode 100644 man/downloadModel.Rd delete mode 100644 man/extractModel.Rd create mode 100644 man/extractModelOutputs.Rd create mode 100644 man/extractSavedModels.Rd create mode 100644 man/removeModelOutputs.Rd delete mode 100644 man/uploadModel.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 2251102..5258cac 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.09.1.4 +Version: 23.10.0 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -18,7 +18,7 @@ Imports: colourpicker, dplyr, ggplot2 (>= 2.2.1), - DataTools (>= 23.06.0.1), + DataTools (>= 23.09.6), futile.logger, htmltools, jsonlite, @@ -36,6 +36,7 @@ Imports: shinyWidgets, shinythemes, xlsx, + yaml, zip LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: diff --git a/NAMESPACE b/NAMESPACE index fcac8ab..a74c5d5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,8 +4,6 @@ export(TemporalIso) export(cleanAndSplitData) export(computeResult) export(defaultInputsForUI) -export(downloadModel) -export(downloadModelUI) export(estimateIntervals) export(estimateTimePoint) export(exportCSV) @@ -30,8 +28,6 @@ export(plotTime) export(setVarsForUncMatrix) export(startApplication) export(updateMatrixNamesInput) -export(uploadModel) -export(uploadModelUI) exportClasses(TemporalIso) exportMethods(export) exportMethods(plot) @@ -41,10 +37,11 @@ import(rstantools) import(shiny) import(shinythemes) importFrom(DataTools,checkAnyNonNumericColumns) +importFrom(DataTools,downloadModelServer) +importFrom(DataTools,downloadModelUI) importFrom(DataTools,importDataServer) importFrom(DataTools,importDataUI) -importFrom(DataTools,remoteModelsServer) -importFrom(DataTools,remoteModelsUI) +importFrom(DataTools,renameExistingNames) importFrom(DataTools,tryCatchWithWarningsAndErrors) importFrom(colourpicker,colourInput) importFrom(dplyr,arrange) diff --git a/NEWS.md b/NEWS.md index b179a0c..6748316 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # OsteoBioR +## Version 23.10.0 + +### New Features +- _Import of models_: + - option to import models from Pandora platform + ## Version 23.09.1 ### New Features diff --git a/R/02-downAndUploadModel.R b/R/02-downAndUploadModel.R deleted file mode 100644 index 0c97cbf..0000000 --- a/R/02-downAndUploadModel.R +++ /dev/null @@ -1,204 +0,0 @@ -#' Download model module -#' -#' UI function to download a zip file with notes and a list of models -#' -#' @param id id of module -#' @param label label of module -#' -#' @rdname downloadModel -#' -#' @export -downloadModelUI <- function(id, label) { - ns <- NS(id) - - tagList( - tags$h5(label), - pickerInput(ns("selectedModels"), - choices = NULL, - options = list(`actions-box` = TRUE), - multiple = T), - checkboxInput(ns("onlyInputs"), "Store only input data and model options"), - textAreaInput(ns("notes"), "Notes"), - HTML("
"), - downloadButton(ns("downloadModel"), "Download"), - tags$br() - ) -} - - -#' Server function download model -#' -#' Backend for download model module -#' -#' @param input shiny input -#' @param output shiny output -#' @param session shiny session -#' @param savedModels (reactive) list of models of class \code{\link{TemporalIso}} -#' @param uploadedNotes (reactive) variable that stores content for README.txt -#' -#' @export -downloadModel <- function(input, output, session, savedModels, uploadedNotes){ - - observe({ - req(savedModels()) - updatePickerInput(session, "selectedModels", choices = names(savedModels()), - selected = names(savedModels())[length(savedModels())]) - updateTextAreaInput(session, "notes", value = uploadedNotes()) - }) - - output$downloadModel <- downloadHandler( - filename = function() { - gsub("[ ]", "_", paste0(Sys.time(), "_OsteoBioR.zip")) - }, - content = function(file) { - zipdir <- tempdir() - modelfile <- file.path(zipdir, "model.rds") - notesfile <- file.path(zipdir, "README.txt") - helpfile <- file.path(zipdir, "help.html") - - req(savedModels(), input$selectedModels) - - if (input$onlyInputs) { - model <- lapply(savedModels()[input$selectedModels], - function(thisModel) { - thisModel$fit <- NULL - thisModel - }) - } else { - model <- savedModels()[input$selectedModels] - } - - - req(model) - saveRDS(list(model = model, - version = packageVersion("OsteoBioR")), - file = modelfile) - writeLines(input$notes, notesfile) - save_html(getHelp(input$tab), helpfile) - zipr(file, c(modelfile, notesfile, helpfile)) - } - ) -} - - -#' Upload model module -#' -#' UI function to upload a zip file with notes and a list of models -#' -#' @param id id of module -#' @param label label of module -#' -#' @rdname uploadModel -#' -#' @export -uploadModelUI <- function(id, label) { - ns <- NS(id) - - tagList( - tags$h5(label), - fileInput(ns("uploadModel"), label = "Load local model"), - remoteModelsUI(ns("remoteModels")), - tags$br(), tags$br(), tags$br() - ) -} - - -#' Server function upload model -#' -#' Backend for upload model module -#' -#' @param input shiny input -#' @param output shiny output -#' @param session shiny session -#' @param savedModels (reactive) list of models of class \code{\link{TemporalIso}} -#' @param uploadedNotes (reactive) variable that stores content of README.txt -#' @param fit (reactive) model of class \code{\link{TemporalIso}} that is currently displayed -#' @param uploadedModelSpecInputs (reactive) modelSpecifications for the current fit -#' @param uploadedDataMatrix (reactive) shinyMatrix matrixInput -#' @param uploadedDataMatrixSD (reactive) shinyMatrix matrixInput -#' @param uploadedIsotope (reactive) shinyMatrix matrixInput -#' -#' @export -uploadModel <- function(input, output, session, savedModels, uploadedNotes, fit, - uploadedModelSpecInputs, uploadedDataMatrix, uploadedDataMatrixSD, uploadedIsotope){ - pathToModel <- reactiveVal(NULL) - - observeEvent(input$uploadModel, { - pathToModel(input$uploadModel$datapath) - }) - - pathToRemote <- remoteModelsServer("remoteModels", - githubRepo = "osteo-bior", - rPackageName = "OsteoBioR", - rPackageVersion = "OsteoBioR" %>% - packageVersion() %>% - as.character()) - - observeEvent(pathToRemote(), { - pathToModel(pathToRemote()) - }) - - observeEvent(pathToModel(), { - req(pathToModel) - model <- NULL - - res <- try({ - zip::unzip(pathToModel()) - modelImport <- readRDS("model.rds") - uploadedNotes(readLines("README.txt")) - }) - - if (inherits(res, "try-error")) { - shinyjs::alert("Could not load file.") - return() - } - - if (file.exists("model.rds")) file.remove("model.rds") - if (file.exists("README.txt")) file.remove("README.txt") - if (file.exists("help.html")) file.remove("help.html") - - if (!exists("modelImport")) { - shinyjs::alert("File format not valid. Model object not found.") - return() - } - - if (is.null(modelImport$model)) { - shinyjs::alert("Empty model object.") - return() - } - - savedModels(c(savedModels(), extractModel(modelImport$model))) - - currentModel <- savedModels()[[length(savedModels())]] - - fit(currentModel$fit) - uploadedModelSpecInputs(currentModel$modelSpecifications) - uploadedDataMatrix(currentModel$inputDataMatrix) - uploadedDataMatrixSD(currentModel$inputDataMatrixSD) - uploadedIsotope(currentModel$inputIsotope) - - alert("Model loaded") - }) - -} - - -#' Extract Model -#' -#' Extract model fit and possibly model specification inputs from a previously exported -#' model object. -#' @param modelFromImport imported model object -extractModel <- function(modelFromImport) { - if (all(names(modelFromImport[[1]]) %in% c("fit", "modelSpecifications", - "inputDataMatrix", "inputIsotope"))) { - # current format of exported models: list(fit = fit, modelSpecifications = modelSpecifications) - return(modelFromImport) - } else { - # former format of exported models: fit - return(lapply(modelFromImport, function(fit) list(fit = fit, - modelSpecifications = list(), - inputDataMatrix = NULL, - inputIsotope = NULL) - )) - } -} diff --git a/R/02_extractSavedModels.R b/R/02_extractSavedModels.R new file mode 100644 index 0000000..bbabfc3 --- /dev/null +++ b/R/02_extractSavedModels.R @@ -0,0 +1,61 @@ +#' Extract Saved Models +#' +#' @param upload (list) list of saved model objects with entries "data", "inputs" and "model" +extractSavedModels <- function(upload) { + # old format < 23.10.0 + if (length(upload[["data"]]) == 0) { + uploadedData <- upload[["model"]] + return(uploadedData) + } + + if (length(upload[["model"]]) > 0) { + # model output was saved + uploadedData <- combineDataAndModelOutputs( + modelData = upload[["data"]], + modelOutput = upload[["model"]] + ) + return(uploadedData) + } else { + # only data and inputs were saved + uploadedData <- upload[["data"]] + } + + return(uploadedData) +} + +#' Combine Data and Model Outputs +#' +#' @param modelData list of model data objects +#' @param modelOutput list of model output objects +combineDataAndModelOutputs <- function(modelData, modelOutput) { + stopifnot("Cannot combine data and model output!" = all(names(modelData) == names(modelOutput))) + + model <- modelData + for (name in names(model)) { + model[[name]] <- c(model[[name]], modelOutput[[name]]) + } + + model +} + +#' Remove Model Outputs +#' +#' @param models list of model objects +removeModelOutputs <- function(models) { + lapply(models, function(model) { + model$fit <- NULL + model + }) +} + +#' Extract Model Outputs +#' +#' @param models list of model objects +extractModelOutputs <- function(models) { + lapply(models, function(model) { + model$modelSpecifications <- NULL + model$inputDataMatrix <- NULL + model$inputIsotope <- NULL + model + }) +} diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 0a7984c..bf1f4d9 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -12,8 +12,8 @@ #' @import shiny #' @import shinythemes #' @importFrom colourpicker colourInput -#' @importFrom DataTools checkAnyNonNumericColumns importDataUI importDataServer remoteModelsUI -#' remoteModelsServer tryCatchWithWarningsAndErrors +#' @importFrom DataTools checkAnyNonNumericColumns downloadModelUI downloadModelServer importDataUI +#' importDataServer renameExistingNames tryCatchWithWarningsAndErrors #' @importFrom dplyr arrange bind_rows distinct slice #' @importFrom ggplot2 aes element_line element_text ggplot geom_line geom_point geom_ribbon #' labs scale_x_continuous theme ggtitle scale_y_continuous geom_vline coord_cartesian sec_axis diff --git a/inst/app/server.R b/inst/app/server.R index 784f75e..cebf22e 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -7,9 +7,14 @@ library(dplyr) library(ggplot2) library(xlsx) library(rstan) +library(yaml) options(shiny.maxRequestSize = 200*1024^2) +# load config variables +configFile <- system.file("config.yaml", package = "OsteoBioR") +appConfig <- yaml::read_yaml(configFile) + shinyServer(function(input, output, session) { # DATA ------------------------------------------------- dat <- reactiveValues() @@ -21,8 +26,9 @@ shinyServer(function(input, output, session) { ## Upload Renewal rates ---- importedData <- DataTools::importDataServer( "fileData", - defaultSource = "file", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)) + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + defaultSource = appConfig$defaultSourceData, + rPackageName = appConfig$rPackageName ) observe({ @@ -35,8 +41,9 @@ shinyServer(function(input, output, session) { ## Upload Renewal rates uncertainty (optional) ---- importedDataSD <- DataTools::importDataServer( "fileDataSD", - defaultSource = "file", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)) + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + defaultSource = appConfig$defaultSourceData, + rPackageName = appConfig$rPackageName ) observe({ @@ -49,8 +56,9 @@ shinyServer(function(input, output, session) { ## Upload Measurements ---- importedIso <- DataTools::importDataServer( "fileIso", - defaultSource = "file", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)) + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + defaultSource = appConfig$defaultSourceData, + rPackageName = appConfig$rPackageName ) observe({ @@ -381,16 +389,70 @@ shinyServer(function(input, output, session) { }) ## Down- / Upload Models ---- + observe({ + updateSelectInput(session, "selectedModels", choices = names(savedModels()), + selected = names(savedModels())[length(savedModels())]) + }) + uploadedNotes <- reactiveVal(NULL) - callModule(downloadModel, "modelDownload", - savedModels = savedModels, uploadedNotes = uploadedNotes) - callModule(uploadModel, "modelUpload", - savedModels = savedModels, uploadedNotes = uploadedNotes, - fit = fit, uploadedModelSpecInputs = uploadedModelSpecInputs, - uploadedDataMatrix = uploadedDataMatrix, - uploadedDataMatrixSD = uploadedDataMatrixSD, - uploadedIsotope = uploadedIsotope - ) + DataTools::downloadModelServer("modelDownload", + dat = reactive(savedModels()[input$selectedModels] %>% + removeModelOutputs()), + inputs = reactiveValues(), + model = reactive(savedModels()[input$selectedModels] %>% + extractModelOutputs()), + rPackageName = appConfig$rPackageName, + fileExtension = appConfig$fileExtension, + modelNotes = uploadedNotes, + triggerUpdate = reactive(TRUE)) + + uploadedValues <- DataTools::importDataServer("modelUpload", + title = "Import Model", + defaultSource = appConfig$defaultSourceModel, + importType = "model", + rPackageName = appConfig$rPackageName, + fileExtension = appConfig$fileExtension, + ignoreWarnings = TRUE) + + observe({ + req(length(uploadedValues()) > 0) + # update notes in tab down-/upload ---- + uploadedNotes(uploadedValues()[[1]][["notes"]]) + + # extract model object(s) + uploadedData <- extractSavedModels(upload = uploadedValues()[[1]]) + + # rename model if name already exists + uploadedData <- uploadedData %>% + DataTools::renameExistingNames(oldList = savedModels()) + + # load model object(s) + savedModels(c(savedModels(), uploadedData)) + + currentModel <- savedModels()[[length(savedModels())]] + + if (!is.null(uploadedDataMatrix())) { + showNotification("Updating input data under 'Data' ...", + duration = 10, + closeButton = TRUE, + type = "message") + } + uploadedDataMatrix(currentModel$inputDataMatrix) + uploadedDataMatrixSD(currentModel$inputDataMatrixSD) + uploadedIsotope(currentModel$inputIsotope) + + if (!is.null(uploadedModelSpecInputs())) { + showNotification("Updating model input values under 'Model' ...", + duration = 10, + closeButton = TRUE, + type = "message") + } + uploadedModelSpecInputs(currentModel$modelSpecifications) + + fit(currentModel$fit) + showNotification("Model loaded", duration = 10, closeButton = TRUE, type = "message") + }) %>% + bindEvent(uploadedValues()) ## Export Summary ---- observeEvent(input$exportSummary, { @@ -807,8 +869,9 @@ shinyServer(function(input, output, session) { importedStayTime <- DataTools::importDataServer( "stayTimeData", - defaultSource = "file", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)) + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + defaultSource = appConfig$defaultSourceData, + rPackageName = appConfig$rPackageName ) observe({ @@ -908,8 +971,9 @@ shinyServer(function(input, output, session) { importedHistData <- DataTools::importDataServer( "fileHistData", - defaultSource = "file", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)) + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + defaultSource = appConfig$defaultSourceData, + rPackageName = appConfig$rPackageName ) observe({ diff --git a/inst/app/ui.R b/inst/app/ui.R index 830d1a6..0c9cbf1 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -127,8 +127,15 @@ tagList( sidebarPanel( width = 2, style = "position:fixed; width:15%; max-width:350px; overflow-y:auto; height:85%", + DataTools::importDataUI("modelUpload", label = "Import Model"), + tags$hr(), modelSpecificationsUI("modelSpecification", "Model Specification"), - actionButton("fitModel", "Fit Model") + actionButton("fitModel", "Fit Model"), + tags$hr(), + selectInput("selectedModels", label = "Download model object(s)", + choices = c("Save or upload models ..." = ""), + multiple = T), + DataTools::downloadModelUI("modelDownload", label = "Download") ), ## main panel ---- mainPanel( @@ -309,13 +316,6 @@ tagList( verbatimTextOutput("userDefined") %>% withSpinner(color ="#20c997") ) ) - ), - ## right sidebar ---- - sidebarPanel( - width = 2, - style = "position:fixed; width:15%; max-width:350px; overflow-y:auto; height:85%", - uploadModelUI("modelUpload", "Upload Model"), - downloadModelUI("modelDownload", "Download Model") ) ) ), diff --git a/inst/config.yaml b/inst/config.yaml new file mode 100644 index 0000000..048ceda --- /dev/null +++ b/inst/config.yaml @@ -0,0 +1,4 @@ +defaultSourceData: "file" # for import of data +defaultSourceModel: "file" # for import of models +fileExtension: "osteobior" # for download of models +rPackageName: "OsteoBioR" # for download and import of models diff --git a/man/combineDataAndModelOutputs.Rd b/man/combineDataAndModelOutputs.Rd new file mode 100644 index 0000000..979eb97 --- /dev/null +++ b/man/combineDataAndModelOutputs.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02_extractSavedModels.R +\name{combineDataAndModelOutputs} +\alias{combineDataAndModelOutputs} +\title{Combine Data and Model Outputs} +\usage{ +combineDataAndModelOutputs(modelData, modelOutput) +} +\arguments{ +\item{modelData}{list of model data objects} + +\item{modelOutput}{list of model output objects} +} +\description{ +Combine Data and Model Outputs +} diff --git a/man/downloadModel.Rd b/man/downloadModel.Rd deleted file mode 100644 index 9bc2804..0000000 --- a/man/downloadModel.Rd +++ /dev/null @@ -1,31 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-downAndUploadModel.R -\name{downloadModelUI} -\alias{downloadModelUI} -\alias{downloadModel} -\title{Download model module} -\usage{ -downloadModelUI(id, label) - -downloadModel(input, output, session, savedModels, uploadedNotes) -} -\arguments{ -\item{id}{id of module} - -\item{label}{label of module} - -\item{input}{shiny input} - -\item{output}{shiny output} - -\item{session}{shiny session} - -\item{savedModels}{(reactive) list of models of class \code{\link{TemporalIso}}} - -\item{uploadedNotes}{(reactive) variable that stores content for README.txt} -} -\description{ -UI function to download a zip file with notes and a list of models - -Backend for download model module -} diff --git a/man/extractModel.Rd b/man/extractModel.Rd deleted file mode 100644 index 3030d23..0000000 --- a/man/extractModel.Rd +++ /dev/null @@ -1,15 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-downAndUploadModel.R -\name{extractModel} -\alias{extractModel} -\title{Extract Model} -\usage{ -extractModel(modelFromImport) -} -\arguments{ -\item{modelFromImport}{imported model object} -} -\description{ -Extract model fit and possibly model specification inputs from a previously exported -model object. -} diff --git a/man/extractModelOutputs.Rd b/man/extractModelOutputs.Rd new file mode 100644 index 0000000..b11b08a --- /dev/null +++ b/man/extractModelOutputs.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02_extractSavedModels.R +\name{extractModelOutputs} +\alias{extractModelOutputs} +\title{Extract Model Outputs} +\usage{ +extractModelOutputs(models) +} +\arguments{ +\item{models}{list of model objects} +} +\description{ +Extract Model Outputs +} diff --git a/man/extractSavedModels.Rd b/man/extractSavedModels.Rd new file mode 100644 index 0000000..517b077 --- /dev/null +++ b/man/extractSavedModels.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02_extractSavedModels.R +\name{extractSavedModels} +\alias{extractSavedModels} +\title{Extract Saved Models} +\usage{ +extractSavedModels(upload) +} +\arguments{ +\item{upload}{(list) list of saved model objects with entries "data", "inputs" and "model"} +} +\description{ +Extract Saved Models +} diff --git a/man/removeModelOutputs.Rd b/man/removeModelOutputs.Rd new file mode 100644 index 0000000..0b7a2b2 --- /dev/null +++ b/man/removeModelOutputs.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02_extractSavedModels.R +\name{removeModelOutputs} +\alias{removeModelOutputs} +\title{Remove Model Outputs} +\usage{ +removeModelOutputs(models) +} +\arguments{ +\item{models}{list of model objects} +} +\description{ +Remove Model Outputs +} diff --git a/man/uploadModel.Rd b/man/uploadModel.Rd deleted file mode 100644 index edc2b39..0000000 --- a/man/uploadModel.Rd +++ /dev/null @@ -1,52 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-downAndUploadModel.R -\name{uploadModelUI} -\alias{uploadModelUI} -\alias{uploadModel} -\title{Upload model module} -\usage{ -uploadModelUI(id, label) - -uploadModel( - input, - output, - session, - savedModels, - uploadedNotes, - fit, - uploadedModelSpecInputs, - uploadedDataMatrix, - uploadedDataMatrixSD, - uploadedIsotope -) -} -\arguments{ -\item{id}{id of module} - -\item{label}{label of module} - -\item{input}{shiny input} - -\item{output}{shiny output} - -\item{session}{shiny session} - -\item{savedModels}{(reactive) list of models of class \code{\link{TemporalIso}}} - -\item{uploadedNotes}{(reactive) variable that stores content of README.txt} - -\item{fit}{(reactive) model of class \code{\link{TemporalIso}} that is currently displayed} - -\item{uploadedModelSpecInputs}{(reactive) modelSpecifications for the current fit} - -\item{uploadedDataMatrix}{(reactive) shinyMatrix matrixInput} - -\item{uploadedDataMatrixSD}{(reactive) shinyMatrix matrixInput} - -\item{uploadedIsotope}{(reactive) shinyMatrix matrixInput} -} -\description{ -UI function to upload a zip file with notes and a list of models - -Backend for upload model module -} From 6834d106b2588bcbef32a4f8b642b5477a987e34 Mon Sep 17 00:00:00 2001 From: Jan Abel <106665518+jan-abel-inwt@users.noreply.github.com> Date: Wed, 1 Nov 2023 15:02:04 +0100 Subject: [PATCH 07/36] Update cosign-installer version --- .github/workflows/docker-publish.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 3ce34bf..1dbedaa 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -40,9 +40,9 @@ jobs: # https://github.com/sigstore/cosign-installer - name: Install cosign if: github.event_name != 'pull_request' - uses: sigstore/cosign-installer@1e95c1de343b5b0c23352d6417ee3e48d5bcd422 + uses: sigstore/cosign-installer@v3.1.1 with: - cosign-release: 'v1.4.0' + cosign-release: 'v2.1.1' # Workaround: https://github.com/docker/build-push-action/issues/461 From 250b7de1b89f498a764377224773731d676deeca Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 6 Dec 2023 14:02:58 +0100 Subject: [PATCH 08/36] update cran mirror in Rprofile --- .Rprofile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.Rprofile b/.Rprofile index bf5a7c4..050551b 100644 --- a/.Rprofile +++ b/.Rprofile @@ -1,7 +1,7 @@ .First <- function() { options(repos = c( - CRAN = "https://mran.microsoft.com/snapshot/2021-01-01", + CRAN = "https://packagemanager.posit.co/cran/2021-01-01", INWTLab = "https://inwtlab.github.io/drat/", PANDORA = "https://Pandora-IsoMemo.github.io/drat/" )) From b2e608bf3d8bc5737a61ea21c2da34071084de60 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 6 Dec 2023 14:08:22 +0100 Subject: [PATCH 09/36] fix in model down- and upload --- DESCRIPTION | 2 +- NAMESPACE | 3 +++ NEWS.md | 5 +++++ R/02_extractSavedModels.R | 3 +++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 5258cac..682ea6f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.10.0 +Version: 23.10.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/NAMESPACE b/NAMESPACE index a74c5d5..6f7d4c4 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -10,6 +10,8 @@ export(exportCSV) export(exportFilename) export(exportJSON) export(exportXLSX) +export(extractModelOutputs) +export(extractSavedModels) export(getDefaultPlotRange) export(getEntry) export(getExampleDataMatrix) @@ -25,6 +27,7 @@ export(logging) export(modelSpecificationsServer) export(modelSpecificationsUI) export(plotTime) +export(removeModelOutputs) export(setVarsForUncMatrix) export(startApplication) export(updateMatrixNamesInput) diff --git a/NEWS.md b/NEWS.md index 6748316..1c5ce4b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # OsteoBioR +## Version 23.10.1 + +### Bug Fixes +- fix for model down- and upload: add missing export to package functions + ## Version 23.10.0 ### New Features diff --git a/R/02_extractSavedModels.R b/R/02_extractSavedModels.R index bbabfc3..e5b23a6 100644 --- a/R/02_extractSavedModels.R +++ b/R/02_extractSavedModels.R @@ -1,6 +1,7 @@ #' Extract Saved Models #' #' @param upload (list) list of saved model objects with entries "data", "inputs" and "model" +#' @export extractSavedModels <- function(upload) { # old format < 23.10.0 if (length(upload[["data"]]) == 0) { @@ -41,6 +42,7 @@ combineDataAndModelOutputs <- function(modelData, modelOutput) { #' Remove Model Outputs #' #' @param models list of model objects +#' @export removeModelOutputs <- function(models) { lapply(models, function(model) { model$fit <- NULL @@ -51,6 +53,7 @@ removeModelOutputs <- function(models) { #' Extract Model Outputs #' #' @param models list of model objects +#' @export extractModelOutputs <- function(models) { lapply(models, function(model) { model$modelSpecifications <- NULL From 0cf23e1d0dccf99f6a7cb026175484a128c1b260 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Fri, 8 Dec 2023 09:56:19 +0100 Subject: [PATCH 10/36] print elapsed time of model fit --- DESCRIPTION | 2 +- R/01-helperFunctions.R | 5 ++++- inst/app/server.R | 48 ++++++++++++++++++++++++------------------ 3 files changed, 33 insertions(+), 22 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 682ea6f..e87e337 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.10.1 +Version: 23.10.2 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/01-helperFunctions.R b/R/01-helperFunctions.R index 068e1dd..51a0f3e 100644 --- a/R/01-helperFunctions.R +++ b/R/01-helperFunctions.R @@ -107,7 +107,10 @@ updateMatrixNamesInput <- function(session, inputId, value, value2) { #' #' @export setVarsForUncMatrix <- function(timeVars, indVar, renewalRates, renewalRatesUnc = NULL) { - if (is.null(renewalRatesUnc)) { + if (length(renewalRatesUnc) == 0 || nrow(renewalRates) != nrow(renewalRatesUnc)) { + if (length(renewalRatesUnc) > 0 && nrow(renewalRatesUnc) > 1) { + warning("The number of rows differs between renewal rates uncertainty and renewal rates. Uncertainty will be ignored!") + } notIndOrTime <- colnames(renewalRates)[!colnames(renewalRates) %in% c(timeVars, indVar)] renewalRatesUnc <- data.frame(renewalRates) renewalRatesUnc[notIndOrTime] <- 0 diff --git a/inst/app/server.R b/inst/app/server.R index cebf22e..f8cc87e 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -196,26 +196,34 @@ shinyServer(function(input, output, session) { req(modDat) fit(NULL) - fitted <- try({ - lapply(1:length(modDat), function(x){ - withProgress({ - boneVars <- modelSpecInputs()$boneVars[ - which(modelSpecInputs()$boneVars %in% colnames(modDat[[x]])) - ] - estimateIntervals(renewalRates = data.frame(modDat[[x]]), - timeVars = modelSpecInputs()$timeVars, - boneVars = boneVars, - indVar = names(modDat)[x], - isoMean = unlist(isoMean()[[x]]), - isoSigma = unlist(isoSigma()[[x]]), - renewalRatesSD = data.frame(modDatSD[[x]]), - iter = modelSpecInputs()$iter, - burnin = modelSpecInputs()$burnin, - chains = modelSpecInputs()$chains) - }, value = x / length(modDat), - message = paste0("Calculate model for individual nr.", x)) - }) - }, silent = TRUE) + + elapsedTime <- system.time({ + fitted <- try({ + lapply(1:length(modDat), function(x){ + withProgress({ + boneVars <- modelSpecInputs()$boneVars[ + which(modelSpecInputs()$boneVars %in% colnames(modDat[[x]])) + ] + estimateIntervals(renewalRates = data.frame(modDat[[x]]), + timeVars = modelSpecInputs()$timeVars, + boneVars = boneVars, + indVar = names(modDat)[x], + isoMean = unlist(isoMean()[[x]]), + isoSigma = unlist(isoSigma()[[x]]), + renewalRatesSD = data.frame(modDatSD[[x]]), + iter = modelSpecInputs()$iter, + burnin = modelSpecInputs()$burnin, + chains = modelSpecInputs()$chains) + }, value = x / length(modDat), + message = paste0("Calculate model for individual nr.", x)) + }) + }, silent = TRUE) + })[3] + + cat(sprintf("Elapsed time of model fitting for %s individuals: %5.2f minutes\n", + length(modDat), + elapsedTime / 60)) + if (inherits(fitted, "try-error")) { shinyjs::alert(fitted[[1]]) } else { From 88aa487f23b9d5eacc4681978f4f30d2ecf1e8b8 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Mon, 11 Dec 2023 10:09:43 +0100 Subject: [PATCH 11/36] add elapsed model fitting time to UI --- DESCRIPTION | 2 +- inst/app/server.R | 14 ++++++++++---- inst/app/ui.R | 18 +++++++++++------- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e87e337..9e08bec 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.10.2 +Version: 23.10.3 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/inst/app/server.R b/inst/app/server.R index f8cc87e..207e0dc 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -172,6 +172,8 @@ shinyServer(function(input, output, session) { savedModels <- reactiveVal(list()) intervalTimePlot <- reactiveVal() + modelFittingTimeTxt <- reactiveVal() + ## Fit Model ---- observeEvent(input$fitModel, { if(!(all(length(nIndividuals1())==length(nIndividuals2())) && all(nIndividuals1()==nIndividuals2()))){ @@ -196,7 +198,7 @@ shinyServer(function(input, output, session) { req(modDat) fit(NULL) - + modelFittingTimeTxt(NULL) elapsedTime <- system.time({ fitted <- try({ lapply(1:length(modDat), function(x){ @@ -220,9 +222,11 @@ shinyServer(function(input, output, session) { }, silent = TRUE) })[3] - cat(sprintf("Elapsed time of model fitting for %s individuals: %5.2f minutes\n", - length(modDat), - elapsedTime / 60)) + fittingTime <- sprintf("Elapsed time of model fitting for all %s individuals: %5.2f minutes\n", + length(modDat), + elapsedTime / 60) + modelFittingTimeTxt(fittingTime) + cat(fittingTime) if (inherits(fitted, "try-error")) { shinyjs::alert(fitted[[1]]) @@ -247,6 +251,8 @@ shinyServer(function(input, output, session) { } }) + output$fittingTimeTxt <- renderUI(HTML(modelFittingTimeTxt())) + allXAxisData <- reactiveVal(data.frame()) observeEvent(savedModels(), { diff --git a/inst/app/ui.R b/inst/app/ui.R index 0c9cbf1..b5f06c3 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -145,18 +145,22 @@ tagList( tags$br(), tags$br(), fluidRow( - column(width = 3, + column(width = 3, + style = "margin-top: 0.7em;", + htmlOutput("fittingTimeTxt")), + column(width = 3, + style = "margin-top: -0.3em;", selectInput("savedModels", label = "Load Model", choices = NULL)), column(width = 1, - style = "margin-top: 14px;", - actionButton("loadModel", "Load")), + style = "margin-top: 1em;", + actionButton("loadModel", "Load", width = "100%")), column(width = 3, - offset = 2, - style = "margin-top: -12px;", + offset = 1, + style = "margin-top: -0.7em;", textInput("modelName", label = "Save Model", placeholder = "model name")), column(width = 1, - style = "margin-top: 14px;", - actionButton("saveModel", "Save")) + style = "margin-top: 1em;", + actionButton("saveModel", "Save", width = "100%")) ), tags$hr() ), From 8d70d81eb84bd99c4a461d3a7c9419f6001bcd51 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Mon, 11 Dec 2023 11:05:18 +0100 Subject: [PATCH 12/36] fixed seed option (#39) --- DESCRIPTION | 2 +- NAMESPACE | 1 + NEWS.md | 5 ++++ R/estimateIntervals.R | 25 +++++++++++++++++--- inst/app/server.R | 2 ++ inst/app/ui.R | 50 ++++++++++++++++++++++------------------ man/estimateIntervals.Rd | 3 +++ man/getSeed.Rd | 16 +++++++++++++ 8 files changed, 78 insertions(+), 26 deletions(-) create mode 100644 man/getSeed.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 9e08bec..e31f63d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.10.3 +Version: 23.12.0 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/NAMESPACE b/NAMESPACE index 6f7d4c4..3588f1d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,6 +17,7 @@ export(getEntry) export(getExampleDataMatrix) export(getExampleIsotopeVals) export(getHelp) +export(getSeed) export(getShiftTime) export(getSiteStayTimes) export(getUserDefinedEst) diff --git a/NEWS.md b/NEWS.md index 1c5ce4b..7dad08f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # OsteoBioR +## Version 23.12.0 + +#### New Features +- option to use a fixed seed when fitting the model (#37) + ## Version 23.10.1 ### Bug Fixes diff --git a/R/estimateIntervals.R b/R/estimateIntervals.R index c8f5314..37424bd 100644 --- a/R/estimateIntervals.R +++ b/R/estimateIntervals.R @@ -54,9 +54,9 @@ #' y_sigma <- c(2, 1.5, 2.5, 2.5) #' #' -############################### -#estimate values -############################### +#' ############################### +#' #estimate values +#' ############################### #' fit <- estimateIntervals(renewalRates = data, #' timeVars = "intStart", #' boneVars = c("bone1", "bone2", "tooth1", "tooth2"), @@ -213,6 +213,25 @@ estimateIntervals <- function(renewalRates, timeUpper = timeUpper)) } +#' Get Seed +#' +#' @param fixSeed (logical) if TRUE use fixed ssed, if FALSE use random seed when fitting the model +#' @param seedValue (numeric) value for seed if \code{fixSeed == TRUE} +#' +#' @export +getSeed <- function(fixSeed, seedValue) { + notValidSeed <- is.null(seedValue) || is.na(seedValue) || seedValue == "" + + # random seed + if (!fixSeed || notValidSeed) { + if (fixSeed && notValidSeed) warning("Not valid input for seed! Using random seed.") + return(as.numeric(Sys.time())) + } + + # fixed seed if seed is valid + return(seedValue) +} + #' Function to estimate isotopic value for specific time point(s) #' #' @description Once the isotopic values in time course are estimated along diff --git a/inst/app/server.R b/inst/app/server.R index 207e0dc..fa7c76e 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -200,6 +200,8 @@ shinyServer(function(input, output, session) { fit(NULL) modelFittingTimeTxt(NULL) elapsedTime <- system.time({ + set.seed(getSeed(fixSeed = !input$rndmSeed, seedValue = input$fixedSeed)) + fitted <- try({ lapply(1:length(modDat), function(x){ withProgress({ diff --git a/inst/app/ui.R b/inst/app/ui.R index b5f06c3..f5922c4 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -69,29 +69,29 @@ tagList( ), conditionalPanel( condition = "input.renewUnc == true", - HTML("
Renewal rates uncertainty dataset (standard deviation, optional)
"), - matrixInput( - inputId = "dataMatrixSD", - #inputClass = "matrix-input-rownames", - class = "numeric", - value = matrix(0, ncol = 6, dimnames = list( - c(""), c("individual", "intStart", "intEnd", "bone1", "bone2", "tooth1") - )), - #copy = TRUE, - #paste = TRUE, - cols = list( - names = TRUE, - extend = FALSE, - delta = 1, - editableNames = FALSE + HTML("
Renewal rates uncertainty dataset (standard deviation, optional)
"), + matrixInput( + inputId = "dataMatrixSD", + #inputClass = "matrix-input-rownames", + class = "numeric", + value = matrix(0, ncol = 6, dimnames = list( + c(""), c("individual", "intStart", "intEnd", "bone1", "bone2", "tooth1") + )), + #copy = TRUE, + #paste = TRUE, + cols = list( + names = TRUE, + extend = FALSE, + delta = 1, + editableNames = FALSE + ), + rows = list( + names = FALSE, + editableNames = FALSE, + extend = FALSE, + delta = 1 + ) ), - rows = list( - names = FALSE, - editableNames = FALSE, - extend = FALSE, - delta = 1 - ) - ), ), # To do: Add time cuts: Split predictions into groups at the following points in time # for the selected individual @@ -130,6 +130,12 @@ tagList( DataTools::importDataUI("modelUpload", label = "Import Model"), tags$hr(), modelSpecificationsUI("modelSpecification", "Model Specification"), + checkboxInput("rndmSeed", label = "Random seed", value = TRUE), + conditionalPanel( + condition = "input.rndmSeed == false", + numericInput("fixedSeed", label = "Fixed seed value", value = 12345), + tags$br() + ), actionButton("fitModel", "Fit Model"), tags$hr(), selectInput("selectedModels", label = "Download model object(s)", diff --git a/man/estimateIntervals.Rd b/man/estimateIntervals.Rd index 112fcb1..729f45e 100644 --- a/man/estimateIntervals.Rd +++ b/man/estimateIntervals.Rd @@ -105,6 +105,9 @@ y_mean <- c(-10, -7, -12, -9) y_sigma <- c(2, 1.5, 2.5, 2.5) +############################### +#estimate values +############################### fit <- estimateIntervals(renewalRates = data, timeVars = "intStart", boneVars = c("bone1", "bone2", "tooth1", "tooth2"), diff --git a/man/getSeed.Rd b/man/getSeed.Rd new file mode 100644 index 0000000..b6a442c --- /dev/null +++ b/man/getSeed.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/estimateIntervals.R +\name{getSeed} +\alias{getSeed} +\title{Get Seed} +\usage{ +getSeed(fixSeed, seedValue) +} +\arguments{ +\item{fixSeed}{(logical) if TRUE use fixed ssed, if FALSE use random seed when fitting the model} + +\item{seedValue}{(numeric) value for seed if \code{fixSeed == TRUE}} +} +\description{ +Get Seed +} From bc4e2ab1a1ede6120fc312a7034428bb89835068 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Mon, 11 Dec 2023 16:28:59 +0100 Subject: [PATCH 13/36] add seed to model download --- DESCRIPTION | 2 +- R/03-module_modelSpecifications.R | 23 ++++++++++++++++++++++- inst/app/server.R | 4 +++- inst/app/ui.R | 6 ------ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index e31f63d..a981a50 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.12.0 +Version: 23.12.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/03-module_modelSpecifications.R b/R/03-module_modelSpecifications.R index 938258b..da4cfdd 100644 --- a/R/03-module_modelSpecifications.R +++ b/R/03-module_modelSpecifications.R @@ -50,6 +50,13 @@ modelSpecificationsUI <- function(id, title) { sliderInput(inputId = ns("chains"), label = "MCMC chains:", min = 1, max = 12, step = 1, value = defaultModelSpecValues()$chains), + checkboxInput(ns("rndmSeed"), label = "Random seed", value = defaultModelSpecValues()$rndmSeed), + conditionalPanel( + condition = "input.rndmSeed == false", + ns = ns, + numericInput(ns("fixedSeed"), label = "Fixed seed value", value = defaultModelSpecValues()$fixedSeed), + tags$br() + ), tags$br() ) } @@ -84,6 +91,8 @@ modelSpecificationsServer <- function(id, dataMatrix, uploadedModelSpecInputs = updateSliderInput(session, "iter", value = defaultModelSpecValues()$iter) updateSliderInput(session, "burnin", value = defaultModelSpecValues()$burnin) updateSliderInput(session, "chains", value = defaultModelSpecValues()$chains) + updateCheckboxInput(session, "rndmSeed", value = defaultModelSpecValues()$rndmSeed) + updateNumericInput(session, "fixedSeed", value = defaultModelSpecValues()$fixedSeed) } req(uploadedModelSpecInputs()) @@ -96,6 +105,8 @@ modelSpecificationsServer <- function(id, dataMatrix, uploadedModelSpecInputs = updateSliderInput(session, "iter", value = uploadedModelSpecInputs()$iter) updateSliderInput(session, "burnin", value = uploadedModelSpecInputs()$burnin) updateSliderInput(session, "chains", value = uploadedModelSpecInputs()$chains) + updateCheckboxInput(session, "rndmSeed", value = uploadedModelSpecInputs()$rndmSeed) + updateNumericInput(session, "fixedSeed", value = uploadedModelSpecInputs()$fixedSeed) }) observeEvent(input$timeVars, { @@ -124,6 +135,14 @@ modelSpecificationsServer <- function(id, dataMatrix, uploadedModelSpecInputs = values$chains <- input$chains }) + observeEvent(input$rndmSeed, { + values$rndmSeed <- input$rndmSeed + }) + + observeEvent(input$fixedSeed, { + values$fixedSeed <- input$fixedSeed + }) + reactive(values) }) } @@ -131,5 +150,7 @@ modelSpecificationsServer <- function(id, dataMatrix, uploadedModelSpecInputs = defaultModelSpecValues <- function() { list(iter = 2000, burnin = 500, - chains = 4) + chains = 4, + rndmSeed = TRUE, + fixedSeed = 12345) } \ No newline at end of file diff --git a/inst/app/server.R b/inst/app/server.R index fa7c76e..97160b8 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -199,8 +199,10 @@ shinyServer(function(input, output, session) { req(modDat) fit(NULL) modelFittingTimeTxt(NULL) + elapsedTime <- system.time({ - set.seed(getSeed(fixSeed = !input$rndmSeed, seedValue = input$fixedSeed)) + set.seed(getSeed(fixSeed = !input[["modelSpecification-rndmSeed"]], + seedValue = input[["modelSpecification-fixedSeed"]])) fitted <- try({ lapply(1:length(modDat), function(x){ diff --git a/inst/app/ui.R b/inst/app/ui.R index f5922c4..9580cc2 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -130,12 +130,6 @@ tagList( DataTools::importDataUI("modelUpload", label = "Import Model"), tags$hr(), modelSpecificationsUI("modelSpecification", "Model Specification"), - checkboxInput("rndmSeed", label = "Random seed", value = TRUE), - conditionalPanel( - condition = "input.rndmSeed == false", - numericInput("fixedSeed", label = "Fixed seed value", value = 12345), - tags$br() - ), actionButton("fitModel", "Fit Model"), tags$hr(), selectInput("selectedModels", label = "Download model object(s)", From a9244e56ec8e81a43f17ff80ec391b76a96a2702 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 19 Dec 2023 11:22:16 +0100 Subject: [PATCH 14/36] new DataTools version, update config --- DESCRIPTION | 8 +++---- NAMESPACE | 4 ++-- NEWS.md | 11 ++++++++++ R/00-config.R | 8 +++++++ R/NAMESPACE.R | 4 ++-- inst/app/server.R | 48 +++++++++++++++++++++++------------------- inst/config.yaml | 12 +++++++++++ man/config.Rd | 14 ++++++++++++ mpi-temporal-iso.Rproj | 2 +- 9 files changed, 79 insertions(+), 32 deletions(-) create mode 100644 R/00-config.R create mode 100644 man/config.Rd diff --git a/DESCRIPTION b/DESCRIPTION index a981a50..f903c88 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.12.1 +Version: 23.12.2 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -18,7 +18,7 @@ Imports: colourpicker, dplyr, ggplot2 (>= 2.2.1), - DataTools (>= 23.09.6), + DataTools (>= 23.12.2), futile.logger, htmltools, jsonlite, @@ -35,9 +35,7 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, - xlsx, - yaml, - zip + yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: lintr, diff --git a/NAMESPACE b/NAMESPACE index 3588f1d..d561503 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -3,6 +3,7 @@ export(TemporalIso) export(cleanAndSplitData) export(computeResult) +export(config) export(defaultInputsForUI) export(estimateIntervals) export(estimateTimePoint) @@ -84,8 +85,7 @@ importFrom(stats,median) importFrom(stats,quantile) importFrom(stats,sd) importFrom(utils,combn) -importFrom(utils,packageVersion) importFrom(utils,write.csv) importFrom(utils,write.table) -importFrom(zip,zipr) +importFrom(yaml,read_yaml) useDynLib(OsteoBioR, .registration = TRUE) diff --git a/NEWS.md b/NEWS.md index 7dad08f..0acf336 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,16 @@ # OsteoBioR +## Version 23.12.2 + +### New Features +- _Import of models from Pandora_: + - display of "About" information that is associated to a selected Pandora Repository + +### Bug Fixes +- _Import of models from Pandora_: + - an error message occurred when trying to load a model from pandora. + - fix: adding the missing download of the zip file from the url before unpacking the zip + ## Version 23.12.0 #### New Features diff --git a/R/00-config.R b/R/00-config.R new file mode 100644 index 0000000..b330f6a --- /dev/null +++ b/R/00-config.R @@ -0,0 +1,8 @@ +#' Config +#' +#' @return (list) configuration parameters for import of data and models +#' @export +config <- function() { + config_path <- system.file("config.yaml", package = "OsteoBioR") + read_yaml(config_path) +} diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index bf1f4d9..2325b13 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -26,8 +26,8 @@ #' @importFrom shinyjs alert #' @importFrom shinyWidgets pickerInput updatePickerInput #' @importFrom stats approx dnorm lm median quantile sd -#' @importFrom utils write.csv write.table combn packageVersion -#' @importFrom zip zipr +#' @importFrom utils write.csv write.table combn +#' @importFrom yaml read_yaml #' @references #' Stan Development Team (NA). RStan: the R interface to Stan. R package version NA. http://mc-stan.org #' diff --git a/inst/app/server.R b/inst/app/server.R index 97160b8..09e4964 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -7,14 +7,9 @@ library(dplyr) library(ggplot2) library(xlsx) library(rstan) -library(yaml) options(shiny.maxRequestSize = 200*1024^2) -# load config variables -configFile <- system.file("config.yaml", package = "OsteoBioR") -appConfig <- yaml::read_yaml(configFile) - shinyServer(function(input, output, session) { # DATA ------------------------------------------------- dat <- reactiveValues() @@ -27,8 +22,9 @@ shinyServer(function(input, output, session) { importedData <- DataTools::importDataServer( "fileData", customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), - defaultSource = appConfig$defaultSourceData, - rPackageName = appConfig$rPackageName + defaultSource = config()[["defaultSourceData"]], + ckanFileTypes = config()[["ckanFileTypes"]], + rPackageName = config()[["rPackageName"]] ) observe({ @@ -42,8 +38,9 @@ shinyServer(function(input, output, session) { importedDataSD <- DataTools::importDataServer( "fileDataSD", customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), - defaultSource = appConfig$defaultSourceData, - rPackageName = appConfig$rPackageName + defaultSource = config()[["defaultSourceData"]], + ckanFileTypes = config()[["ckanFileTypes"]], + rPackageName = config()[["rPackageName"]] ) observe({ @@ -57,8 +54,9 @@ shinyServer(function(input, output, session) { importedIso <- DataTools::importDataServer( "fileIso", customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), - defaultSource = appConfig$defaultSourceData, - rPackageName = appConfig$rPackageName + defaultSource = config()[["defaultSourceData"]], + ckanFileTypes = config()[["ckanFileTypes"]], + rPackageName = config()[["rPackageName"]] ) observe({ @@ -301,11 +299,13 @@ shinyServer(function(input, output, session) { if(length(fits) == 0){ return("Please select a model / individual") } + shiftTime <- lapply(fits, function(x) getShiftTime(x, type = ifelse(input$shiftTimeAbsOrRel == "absolute", TRUE, FALSE), slope = ifelse(input$slope == "slope", TRUE, FALSE), threshold = input$shiftTimeThreshold, probability = input$shiftTimeProb)) + shiftTime <- do.call("rbind", lapply(1:length(shiftTime), function(x){ if(NROW(shiftTime[[x]]) > 0){ @@ -315,7 +315,7 @@ shinyServer(function(input, output, session) { } } )) - ifelse(nrow(shiftTime) == 0, stop("No shifts were found"), return(shiftTime)) + ifelse(nrow(shiftTime) == 0, return("No shifts were found"), return(shiftTime)) }) observeEvent(input$savedModelsUserDefined, { @@ -419,18 +419,20 @@ shinyServer(function(input, output, session) { inputs = reactiveValues(), model = reactive(savedModels()[input$selectedModels] %>% extractModelOutputs()), - rPackageName = appConfig$rPackageName, - fileExtension = appConfig$fileExtension, + rPackageName = config()[["rPackageName"]], + fileExtension = config()[["fileExtension"]], modelNotes = uploadedNotes, triggerUpdate = reactive(TRUE)) uploadedValues <- DataTools::importDataServer("modelUpload", title = "Import Model", - defaultSource = appConfig$defaultSourceModel, importType = "model", - rPackageName = appConfig$rPackageName, - fileExtension = appConfig$fileExtension, - ignoreWarnings = TRUE) + ckanFileTypes = config()[["ckanModelTypes"]], + ignoreWarnings = TRUE, + defaultSource = config()[["defaultSourceModel"]], + mainFolder = config()[["mainFolder"]], + fileExtension = config()[["fileExtension"]], + rPackageName = config()[["rPackageName"]]) observe({ req(length(uploadedValues()) > 0) @@ -888,8 +890,9 @@ shinyServer(function(input, output, session) { importedStayTime <- DataTools::importDataServer( "stayTimeData", customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), - defaultSource = appConfig$defaultSourceData, - rPackageName = appConfig$rPackageName + defaultSource = config()[["defaultSourceData"]], + ckanFileTypes = config()[["ckanFileTypes"]], + rPackageName = config()[["rPackageName"]] ) observe({ @@ -990,8 +993,9 @@ shinyServer(function(input, output, session) { importedHistData <- DataTools::importDataServer( "fileHistData", customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), - defaultSource = appConfig$defaultSourceData, - rPackageName = appConfig$rPackageName + defaultSource = config()[["defaultSourceData"]], + ckanFileTypes = config()[["ckanFileTypes"]], + rPackageName = config()[["rPackageName"]] ) observe({ diff --git a/inst/config.yaml b/inst/config.yaml index 048ceda..c3dd992 100644 --- a/inst/config.yaml +++ b/inst/config.yaml @@ -1,4 +1,16 @@ defaultSourceData: "file" # for import of data defaultSourceModel: "file" # for import of models +# file types that are allowed for import from Pandora ("ckan") +ckanFileTypes: + - "xls" + - "xlsx" + - "csv" + - "odt" + - "txt" +ckanModelTypes: + - "zip" + - "osteobior" +githubRepo: "osteobior" # for import of models +mainFolder: "predefinedModels" # for import of models fileExtension: "osteobior" # for download of models rPackageName: "OsteoBioR" # for download and import of models diff --git a/man/config.Rd b/man/config.Rd new file mode 100644 index 0000000..6f3c852 --- /dev/null +++ b/man/config.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/00-config.R +\name{config} +\alias{config} +\title{Config} +\usage{ +config() +} +\value{ +(list) configuration parameters for import of data and models +} +\description{ +Config +} diff --git a/mpi-temporal-iso.Rproj b/mpi-temporal-iso.Rproj index 5a4617f..3535545 100644 --- a/mpi-temporal-iso.Rproj +++ b/mpi-temporal-iso.Rproj @@ -14,4 +14,4 @@ LaTeX: pdfLaTeX BuildType: Package PackageInstallArgs: --no-multiarch --with-keep.source -PackageRoxygenize: rd,collate,namespace +PackageRoxygenize: rd,collate,namespace,vignette From 0ff6dbdb2416e1a49de3a53ab4b778481c87ab2c Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 19 Dec 2023 12:29:32 +0100 Subject: [PATCH 15/36] remove library xlsx --- DESCRIPTION | 2 +- inst/app/server.R | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f903c88..0de94e1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.12.2 +Version: 23.12.2.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/inst/app/server.R b/inst/app/server.R index 09e4964..7d29fd7 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -5,7 +5,6 @@ library(colourpicker) library(shinyMatrix) library(dplyr) library(ggplot2) -library(xlsx) library(rstan) options(shiny.maxRequestSize = 200*1024^2) From 9a4662ed238ab6fa939bddf3e63ec52836ae0d4c Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 9 Jan 2024 13:54:21 +0100 Subject: [PATCH 16/36] exclude first column in check during import (#40) --- DESCRIPTION | 5 +++-- NAMESPACE | 3 ++- R/NAMESPACE.R | 5 +++-- inst/app/server.R | 16 ++++++++++------ 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0de94e1..ea47cce 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.12.2.1 +Version: 23.12.2.2 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -18,7 +18,7 @@ Imports: colourpicker, dplyr, ggplot2 (>= 2.2.1), - DataTools (>= 23.12.2), + DataTools (>= 23.12.2.2), futile.logger, htmltools, jsonlite, @@ -26,6 +26,7 @@ Imports: methods, modules, openxlsx, + parallel, rlang, rstan (>= 2.18.1), rstantools (>= 1.5.0), diff --git a/NAMESPACE b/NAMESPACE index d561503..8003f29 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -41,7 +41,7 @@ import(methods) import(rstantools) import(shiny) import(shinythemes) -importFrom(DataTools,checkAnyNonNumericColumns) +importFrom(DataTools,checkNonNumericColumnsExceptFirst) importFrom(DataTools,downloadModelServer) importFrom(DataTools,downloadModelUI) importFrom(DataTools,importDataServer) @@ -72,6 +72,7 @@ importFrom(htmltools,save_html) importFrom(jsonlite,toJSON) importFrom(magrittr,"%>%") importFrom(openxlsx,write.xlsx) +importFrom(parallel,detectCores) importFrom(rlang,.data) importFrom(rstan,extract) importFrom(rstan,sampling) diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 2325b13..1ddb959 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -12,8 +12,8 @@ #' @import shiny #' @import shinythemes #' @importFrom colourpicker colourInput -#' @importFrom DataTools checkAnyNonNumericColumns downloadModelUI downloadModelServer importDataUI -#' importDataServer renameExistingNames tryCatchWithWarningsAndErrors +#' @importFrom DataTools checkNonNumericColumnsExceptFirst downloadModelUI downloadModelServer +#' importDataUI importDataServer renameExistingNames tryCatchWithWarningsAndErrors #' @importFrom dplyr arrange bind_rows distinct slice #' @importFrom ggplot2 aes element_line element_text ggplot geom_line geom_point geom_ribbon #' labs scale_x_continuous theme ggtitle scale_y_continuous geom_vline coord_cartesian sec_axis @@ -21,6 +21,7 @@ #' @importFrom jsonlite toJSON #' @importFrom magrittr %>% #' @importFrom openxlsx write.xlsx +#' @importFrom parallel detectCores #' @importFrom rlang .data #' @importFrom rstan sampling extract #' @importFrom shinyjs alert diff --git a/inst/app/server.R b/inst/app/server.R index 7d29fd7..a9a6e2a 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -7,7 +7,11 @@ library(dplyr) library(ggplot2) library(rstan) -options(shiny.maxRequestSize = 200*1024^2) +options(shiny.maxRequestSize = 200*1024^2, + # Set mc.cores option + mc.cores = parallel::detectCores()) + +rstan_options(auto_write = TRUE) shinyServer(function(input, output, session) { # DATA ------------------------------------------------- @@ -20,7 +24,7 @@ shinyServer(function(input, output, session) { ## Upload Renewal rates ---- importedData <- DataTools::importDataServer( "fileData", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], rPackageName = config()[["rPackageName"]] @@ -36,7 +40,7 @@ shinyServer(function(input, output, session) { ## Upload Renewal rates uncertainty (optional) ---- importedDataSD <- DataTools::importDataServer( "fileDataSD", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], rPackageName = config()[["rPackageName"]] @@ -52,7 +56,7 @@ shinyServer(function(input, output, session) { ## Upload Measurements ---- importedIso <- DataTools::importDataServer( "fileIso", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], rPackageName = config()[["rPackageName"]] @@ -888,7 +892,7 @@ shinyServer(function(input, output, session) { importedStayTime <- DataTools::importDataServer( "stayTimeData", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], rPackageName = config()[["rPackageName"]] @@ -991,7 +995,7 @@ shinyServer(function(input, output, session) { importedHistData <- DataTools::importDataServer( "fileHistData", - customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), + customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], rPackageName = config()[["rPackageName"]] From c5a54ced03c471befa877d52011cb486d1542fa5 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 10 Jan 2024 18:35:20 +0100 Subject: [PATCH 17/36] add custom help text --- DESCRIPTION | 4 ++-- NAMESPACE | 1 + R/NAMESPACE.R | 2 +- inst/app/server.R | 17 +++++++++++------ 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index ea47cce..aeae173 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.12.2.2 +Version: 23.12.2.3 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -18,7 +18,7 @@ Imports: colourpicker, dplyr, ggplot2 (>= 2.2.1), - DataTools (>= 23.12.2.2), + DataTools (>= 23.12.2.3), futile.logger, htmltools, jsonlite, diff --git a/NAMESPACE b/NAMESPACE index 8003f29..915ab7b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -46,6 +46,7 @@ importFrom(DataTools,downloadModelServer) importFrom(DataTools,downloadModelUI) importFrom(DataTools,importDataServer) importFrom(DataTools,importDataUI) +importFrom(DataTools,importOptions) importFrom(DataTools,renameExistingNames) importFrom(DataTools,tryCatchWithWarningsAndErrors) importFrom(colourpicker,colourInput) diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 1ddb959..0918f11 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -13,7 +13,7 @@ #' @import shinythemes #' @importFrom colourpicker colourInput #' @importFrom DataTools checkNonNumericColumnsExceptFirst downloadModelUI downloadModelServer -#' importDataUI importDataServer renameExistingNames tryCatchWithWarningsAndErrors +#' importDataUI importDataServer importOptions renameExistingNames tryCatchWithWarningsAndErrors #' @importFrom dplyr arrange bind_rows distinct slice #' @importFrom ggplot2 aes element_line element_text ggplot geom_line geom_point geom_ribbon #' labs scale_x_continuous theme ggtitle scale_y_continuous geom_vline coord_cartesian sec_axis diff --git a/inst/app/server.R b/inst/app/server.R index a9a6e2a..c02d05c 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -27,7 +27,10 @@ shinyServer(function(input, output, session) { customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], - rPackageName = config()[["rPackageName"]] + options = DataTools::importOptions( + rPackageName = config()[["rPackageName"]], + customHelpText = helpText("The first column in your file should contain the IDs for individuals.") + ) ) observe({ @@ -43,7 +46,7 @@ shinyServer(function(input, output, session) { customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], - rPackageName = config()[["rPackageName"]] + options = DataTools::importOptions(rPackageName = config()[["rPackageName"]]) ) observe({ @@ -59,7 +62,9 @@ shinyServer(function(input, output, session) { customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], - rPackageName = config()[["rPackageName"]] + options = DataTools::importOptions( + rPackageName = config()[["rPackageName"]], + customHelpText = helpText("The first column in your file should contain the IDs for individuals.")) ) observe({ @@ -435,7 +440,7 @@ shinyServer(function(input, output, session) { defaultSource = config()[["defaultSourceModel"]], mainFolder = config()[["mainFolder"]], fileExtension = config()[["fileExtension"]], - rPackageName = config()[["rPackageName"]]) + options = DataTools::importOptions(rPackageName = config()[["rPackageName"]])) observe({ req(length(uploadedValues()) > 0) @@ -895,7 +900,7 @@ shinyServer(function(input, output, session) { customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], - rPackageName = config()[["rPackageName"]] + options = DataTools::importOptions(rPackageName = config()[["rPackageName"]]) ) observe({ @@ -998,7 +1003,7 @@ shinyServer(function(input, output, session) { customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], - rPackageName = config()[["rPackageName"]] + options = DataTools::importOptions(rPackageName = config()[["rPackageName"]]) ) observe({ From 77289cf1646b5144697f3e85e5ce50459c761c0b Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Fri, 12 Jan 2024 13:11:20 +0100 Subject: [PATCH 18/36] fix issue that prevented using multiple cures --- DESCRIPTION | 2 +- NEWS.md | 6 ++++++ R/estimateIntervals.R | 4 ++-- inst/app/server.R | 3 ++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index aeae173..0d333c9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 23.12.2.3 +Version: 24.01.0 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/NEWS.md b/NEWS.md index 0acf336..174a735 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # OsteoBioR +## Version 24.01.0 + +### Bug Fixes +- fix a bug that prevented the usage of multiple cores during model estimation. In essence only a + single core was used. + ## Version 23.12.2 ### New Features diff --git a/R/estimateIntervals.R b/R/estimateIntervals.R index 37424bd..1f25a1c 100644 --- a/R/estimateIntervals.R +++ b/R/estimateIntervals.R @@ -186,7 +186,7 @@ estimateIntervals <- function(renewalRates, xsd <- (xhigh - xlow) / 2 xsd[is.na(xsd)] <- 0 - #cores <- getOption("mc.cores", if (mc) min(4, chains) else 1) + cores <- getOption("mc.cores", if (mc) min(4, chains) else 1) model <- suppressWarnings(sampling(stanmodels$linRegGP, data = list(N = N, NT = NT, @@ -198,7 +198,7 @@ estimateIntervals <- function(renewalRates, chains = chains, iter = iter, warmup = burnin, - cores = 1, + cores = cores, # verbose = FALSE, # refresh = 0, control = list(adapt_delta = adapt_delta, diff --git a/inst/app/server.R b/inst/app/server.R index c02d05c..3e105cc 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -9,7 +9,8 @@ library(rstan) options(shiny.maxRequestSize = 200*1024^2, # Set mc.cores option - mc.cores = parallel::detectCores()) + #mc.cores = parallel::detectCores() # the number of cores is set directly inside the estimation function + ) rstan_options(auto_write = TRUE) From c843d5094b8eaae4258760208f1c011bcce9e99e Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 16 Jan 2024 09:28:17 +0100 Subject: [PATCH 19/36] fix left comma --- DESCRIPTION | 2 +- inst/app/server.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0d333c9..067ee8f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.01.0 +Version: 24.01.0.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/inst/app/server.R b/inst/app/server.R index 3e105cc..c2e78fc 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -7,7 +7,7 @@ library(dplyr) library(ggplot2) library(rstan) -options(shiny.maxRequestSize = 200*1024^2, +options(shiny.maxRequestSize = 200*1024^2 # Set mc.cores option #mc.cores = parallel::detectCores() # the number of cores is set directly inside the estimation function ) From c4b165ed02b33750aa3588baa90decf17142e6de Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Mon, 29 Jan 2024 19:37:10 +0100 Subject: [PATCH 20/36] new checkbox to use rownames as indVar (#42) * new checkbox to use rownames as indVar * fix issues with rownames * extract getTime from inputs, use inputs of modelSpecification directly * update news.md * fix label * fix docu * keep rownames of uncertainty * remove wrong warning --- DESCRIPTION | 2 +- NAMESPACE | 5 +- NEWS.md | 6 + R/01-cleanAndSplitData.R | 33 ++- R/01-helperFunctions.R | 6 +- R/03-module_modelSpecifications.R | 58 +++++- R/NAMESPACE.R | 2 +- R/estimateIntervals.R | 4 +- inst/app/server.R | 89 ++++---- inst/app/ui.R | 10 +- man/extractIndividuals.Rd | 19 ++ man/getTimeMax.Rd | 21 ++ man/getTimeMin.Rd | 21 ++ tests/testthat/test-cleanAndSplitData.R | 257 ++++++++++++++++++------ tests/testthat/test-helperFunctions.R | 30 --- 15 files changed, 414 insertions(+), 149 deletions(-) create mode 100644 man/extractIndividuals.Rd create mode 100644 man/getTimeMax.Rd create mode 100644 man/getTimeMin.Rd delete mode 100644 tests/testthat/test-helperFunctions.R diff --git a/DESCRIPTION b/DESCRIPTION index 067ee8f..2b8750b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.01.0.1 +Version: 24.01.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/NAMESPACE b/NAMESPACE index 915ab7b..fee755c 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -11,6 +11,7 @@ export(exportCSV) export(exportFilename) export(exportJSON) export(exportXLSX) +export(extractIndividuals) export(extractModelOutputs) export(extractSavedModels) export(getDefaultPlotRange) @@ -21,6 +22,8 @@ export(getHelp) export(getSeed) export(getShiftTime) export(getSiteStayTimes) +export(getTimeMax) +export(getTimeMin) export(getUserDefinedEst) export(getXAxisData) export(logDebug) @@ -41,7 +44,7 @@ import(methods) import(rstantools) import(shiny) import(shinythemes) -importFrom(DataTools,checkNonNumericColumnsExceptFirst) +importFrom(DataTools,checkAnyNonNumericColumns) importFrom(DataTools,downloadModelServer) importFrom(DataTools,downloadModelUI) importFrom(DataTools,importDataServer) diff --git a/NEWS.md b/NEWS.md index 174a735..629a18e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,11 @@ # OsteoBioR +## Version 24.01.1 + +### New Features +- Option to use rownames of data as individual variable. This allows to use non-numeric values for + individuals since rownames do not need to be numeric (#35) + ## Version 24.01.0 ### Bug Fixes diff --git a/R/01-cleanAndSplitData.R b/R/01-cleanAndSplitData.R index ffb7833..51cca9e 100644 --- a/R/01-cleanAndSplitData.R +++ b/R/01-cleanAndSplitData.R @@ -9,15 +9,18 @@ cleanAndSplitData <- function(indVar, renewalRates, renewalRatesUnc) { if (is.null(indVar)) return(NULL) + + splitVar <- extractIndividuals(matrix = renewalRates, indVar = indVar) # clean up renewalRates - renewalRates <- renewalRates %>% data.frame() + renewalRates <- renewalRates %>% + data.frame() validCols <- colSums(!is.na(renewalRates)) > 0 renewalRates <- renewalRates %>% .[validCols] #%>% #filter(complete.cases(.)) - renewalRatesPerInd <- split(renewalRates, renewalRates[, indVar]) + renewalRatesPerInd <- split(renewalRates, splitVar) validColsPerInd <- lapply(renewalRatesPerInd, function(x) { !apply(x, 2, function(y) @@ -30,14 +33,16 @@ cleanAndSplitData <- }) names(renewalRatesPerInd) <- names(validColsPerInd) + splitVarUnc <- extractIndividuals(matrix = renewalRatesUnc, indVar = indVar) + # clean up renewalRatesUnc respectively and set NA to zero - renewalRatesUnc <- renewalRatesUnc %>% data.frame() + renewalRatesUnc <- renewalRatesUnc %>% + data.frame() renewalRatesUnc <- renewalRatesUnc %>% .[validCols] #%>% #filter(complete.cases(.)) - renewalRatesUncPerInd <- - split(renewalRatesUnc, renewalRatesUnc[, indVar]) + renewalRatesUncPerInd <- split(renewalRatesUnc, splitVarUnc) renewalRatesUncPerInd <- lapply(seq_along(renewalRatesUncPerInd), function(x) { # select valid columns @@ -51,3 +56,21 @@ cleanAndSplitData <- list(renewalRatesPerInd = renewalRatesPerInd, renewalRatesUncPerInd = renewalRatesUncPerInd) } + +#' Extract Individuals +#' +#' @param matrix (matrix) matrix of data +#' @param indVar (character) name if individuals column or empty if rownames +#' @return (character) vector of individuals or row names with individuals +#' +#' @export +extractIndividuals <- function(matrix, indVar) { + if (!(indVar %in% colnames(matrix)) && !is.null(rownames(matrix)) && all(sapply(rownames(matrix), function(x) x != ""))) { + rownames(matrix) %>% + as.character() + } else { + matrix[, indVar] %>% + unname() %>% + as.character() + } +} diff --git a/R/01-helperFunctions.R b/R/01-helperFunctions.R index 51a0f3e..dd4768f 100644 --- a/R/01-helperFunctions.R +++ b/R/01-helperFunctions.R @@ -114,13 +114,15 @@ setVarsForUncMatrix <- function(timeVars, indVar, renewalRates, renewalRatesUnc notIndOrTime <- colnames(renewalRates)[!colnames(renewalRates) %in% c(timeVars, indVar)] renewalRatesUnc <- data.frame(renewalRates) renewalRatesUnc[notIndOrTime] <- 0 + rownamesUnc <- rownames(renewalRates) } else { + rownamesUnc <- rownames(renewalRatesUnc) renewalRatesUnc <- data.frame(renewalRatesUnc) - if ((!is.null(timeVars) && all(timeVars != "")) || (!is.null(indVar) && indVar != "")) + if ((!is.null(timeVars) && all(timeVars != "")) || (length(indVar) != 0 && indVar != "")) renewalRatesUnc[c(timeVars, indVar)] <- data.frame(renewalRates)[c(timeVars, indVar)] } renewalRatesUnc <- as.matrix(renewalRatesUnc) - rownames(renewalRatesUnc) <- rep("", nrow(renewalRatesUnc)) + rownames(renewalRatesUnc) <- rownamesUnc renewalRatesUnc } diff --git a/R/03-module_modelSpecifications.R b/R/03-module_modelSpecifications.R index da4cfdd..c749dc1 100644 --- a/R/03-module_modelSpecifications.R +++ b/R/03-module_modelSpecifications.R @@ -35,10 +35,15 @@ modelSpecificationsUI <- function(id, title) { multiple = TRUE ), tags$br(), - selectizeInput( - inputId = ns("indVar"), - label = "Individual variable", - choices = character(0) + checkboxInput(ns("rownamesAsIndVar"), label = "Use rownames as individual variable", value = FALSE), + conditionalPanel( + ns = ns, + condition = "input.rownamesAsIndVar == false", + selectizeInput( + inputId = ns("indVar"), + label = "Individual variable:", + choices = character(0) + ), ), tags$br(), sliderInput(inputId = ns("iter"), @@ -110,8 +115,10 @@ modelSpecificationsServer <- function(id, dataMatrix, uploadedModelSpecInputs = }) observeEvent(input$timeVars, { - values$timeMinimum <- dataMatrix()[, input$timeVars[1]] %>% min - values$timeMaximum <- dataMatrix()[, input$timeVars[1]] %>% max + values$timeMinimum <- getTimeMin(mtrx = dataMatrix(), + timeVars = input$timeVars) + values$timeMaximum <- getTimeMax(mtrx = dataMatrix(), + timeVars = input$timeVars) values$timeVars <- input$timeVars }) @@ -123,6 +130,15 @@ modelSpecificationsServer <- function(id, dataMatrix, uploadedModelSpecInputs = values$indVar <- input$indVar }) + observeEvent(input$rownamesAsIndVar, { + if (!input$rownamesAsIndVar) { + values$indVar <- input$indVar + } else { + values$indVar <- character(0) + updateSelectizeInput(session = session, "indVar", selected = character(0)) + } + }) + observeEvent(input$iter, { values$iter <- input$iter }) @@ -147,6 +163,36 @@ modelSpecificationsServer <- function(id, dataMatrix, uploadedModelSpecInputs = }) } +#' Get Time Minimum +#' +#' @param mtrx (matrix) data matrix +#' @param timeVars (character) column names of time variables +#' @param default (numeric) default result +#' +#' @return (numeric) minimal time +#' @export +getTimeMin <- function(mtrx, timeVars, default = 0) { + if (length(timeVars) == 0 || any(sapply(timeVars, function(x) x == ""))) { + return(default) + } + mtrx[, timeVars] %>% min() +} + +#' Get Time Maximum +#' +#' @param mtrx (matrix) data matrix +#' @param timeVars (character) column names of time variables +#' @param default (numeric) default result +#' +#' @return (numeric) maximal time +#' @export +getTimeMax <- function(mtrx, timeVars, default = 1) { + if (length(timeVars) == 0 || any(sapply(timeVars, function(x) x == ""))) { + return(default) + } + mtrx[, timeVars] %>% max() +} + defaultModelSpecValues <- function() { list(iter = 2000, burnin = 500, diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 0918f11..ba17579 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -12,7 +12,7 @@ #' @import shiny #' @import shinythemes #' @importFrom colourpicker colourInput -#' @importFrom DataTools checkNonNumericColumnsExceptFirst downloadModelUI downloadModelServer +#' @importFrom DataTools checkAnyNonNumericColumns downloadModelUI downloadModelServer #' importDataUI importDataServer importOptions renameExistingNames tryCatchWithWarningsAndErrors #' @importFrom dplyr arrange bind_rows distinct slice #' @importFrom ggplot2 aes element_line element_text ggplot geom_line geom_point geom_ribbon diff --git a/R/estimateIntervals.R b/R/estimateIntervals.R index 1f25a1c..98f50c6 100644 --- a/R/estimateIntervals.R +++ b/R/estimateIntervals.R @@ -176,10 +176,10 @@ estimateIntervals <- function(renewalRates, # must always have the same dimensions: renewalRates[boneVars] and renewalRatesSD[boneVars] if (ncol(renewalRates[boneVars]) != ncol(renewalRatesSD[boneVars])) stop(paste0("Error: Number of valid bone variables differ between renewal rates and renewal rates ", - "uncertainty for individual ", indVar, ".")) + "uncertainty for individuals ", indVar, ".")) if (nrow(renewalRates[boneVars]) != nrow(renewalRatesSD[boneVars])) stop(paste0("Error: Number of valid rows differ between renewal rates and renewal rates ", - "uncertainty for individual ", indVar, ".")) + "uncertainty for individuals ", indVar, ".")) xlow <- t(as.matrix(apply(pmax(as.matrix(renewalRates[boneVars]- renewalRatesSD[boneVars]), 0), 2, calcInfluence))) xhigh <- t(as.matrix(apply(pmin(as.matrix(renewalRates[boneVars]+ renewalRatesSD[boneVars]), 100), 2, calcInfluence))) diff --git a/inst/app/server.R b/inst/app/server.R index c2e78fc..ce9d2fa 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -25,7 +25,7 @@ shinyServer(function(input, output, session) { ## Upload Renewal rates ---- importedData <- DataTools::importDataServer( "fileData", - customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], options = DataTools::importOptions( @@ -44,7 +44,7 @@ shinyServer(function(input, output, session) { ## Upload Renewal rates uncertainty (optional) ---- importedDataSD <- DataTools::importDataServer( "fileDataSD", - customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], options = DataTools::importOptions(rPackageName = config()[["rPackageName"]]) @@ -60,7 +60,7 @@ shinyServer(function(input, output, session) { ## Upload Measurements ---- importedIso <- DataTools::importDataServer( "fileIso", - customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], options = DataTools::importOptions( @@ -99,8 +99,8 @@ shinyServer(function(input, output, session) { if (!input$renewUnc) { zeroSD <- setVarsForUncMatrix(renewalRates = dat$dataFile, renewalRatesUnc = NULL, - timeVars = modelSpecInputs()$timeVars, - indVar = modelSpecInputs()$indVar) + timeVars = input[["modelSpecification-timeVars"]], + indVar = input[["modelSpecification-indVar"]]) # use row- and column names from dataMatrix but content from dataMatrixSD updateMatrixNamesInput(session, "dataMatrixSD", value = input$dataMatrix, value2 = zeroSD) @@ -154,27 +154,41 @@ shinyServer(function(input, output, session) { uploadedModelSpecInputs = uploadedModelSpecInputs) isoMean <- eventReactive(input$isotope, { + splitVar <- extractIndividuals(matrix = input$isotope, indVar = input[["modelSpecification-indVar"]]) ret <- (input$isotope %>% data.frame() %>% .[colSums(!is.na(.)) > 0] #%>% #filter(complete.cases(.)) ) - lapply(split(ret[,2], ret[,1]), function(x) as.numeric(na.omit(x))) + # use pre-last columns to get mean, because ind column does not exists when rownames are used + lapply(split(ret[, length(ret)-1], splitVar), function(x) as.numeric(na.omit(x))) }) isoSigma <- eventReactive(input$isotope, { + splitVar <- extractIndividuals(matrix = input$isotope, indVar = input[["modelSpecification-indVar"]]) ret <- (input$isotope %>% data.frame() %>% .[colSums(!is.na(.)) > 0] #%>% #filter(complete.cases(.)) ) - lapply(split(ret[,3], ret[,1]), function(x) as.numeric(na.omit(x))) + # use last columns to get sigma, because ind column does not exists when rownames are used + lapply(split(ret[, length(ret)], splitVar), function(x) as.numeric(na.omit(x))) }) fit <- reactiveVal(NULL) - nIndividuals1 <- reactive({(unique(na.omit(input$isotope[,1])))}) - nIndividuals2 <- reactive({(unique(na.omit(input$dataMatrix[,1])))}) + nIndividuals1 <- reactive({ + extractIndividuals(matrix = input$isotope, + indVar = input[["modelSpecification-indVar"]]) %>% + na.omit() %>% + unique() + }) + nIndividuals2 <- reactive({ + extractIndividuals(matrix = input$dataMatrix, + indVar = input[["modelSpecification-indVar"]]) %>% + na.omit() %>% + unique() + }) savedModels <- reactiveVal(list()) intervalTimePlot <- reactiveVal() @@ -190,14 +204,14 @@ shinyServer(function(input, output, session) { if (!input$renewUnc) { # update zero SD matrix regarding selected vars renewalRatesUnc <- setVarsForUncMatrix(renewalRates = input$dataMatrix, - renewalRatesUnc = input$dataMatrixSD, - timeVars = modelSpecInputs()$timeVars, - indVar = modelSpecInputs()$indVar) + renewalRatesUnc = NULL, + timeVars = input[["modelSpecification-timeVars"]], + indVar = input[["modelSpecification-indVar"]]) } else { renewalRatesUnc <- input$dataMatrixSD } - splittedData <- cleanAndSplitData(indVar = modelSpecInputs()$indVar, + splittedData <- cleanAndSplitData(indVar = input[["modelSpecification-indVar"]], renewalRates = input$dataMatrix, renewalRatesUnc = renewalRatesUnc) modDat <- splittedData$renewalRatesPerInd @@ -214,19 +228,19 @@ shinyServer(function(input, output, session) { fitted <- try({ lapply(1:length(modDat), function(x){ withProgress({ - boneVars <- modelSpecInputs()$boneVars[ - which(modelSpecInputs()$boneVars %in% colnames(modDat[[x]])) + boneVars <- input[["modelSpecification-boneVars"]][ + which(input[["modelSpecification-boneVars"]] %in% colnames(modDat[[x]])) ] estimateIntervals(renewalRates = data.frame(modDat[[x]]), - timeVars = modelSpecInputs()$timeVars, + timeVars = input[["modelSpecification-timeVars"]], boneVars = boneVars, indVar = names(modDat)[x], isoMean = unlist(isoMean()[[x]]), isoSigma = unlist(isoSigma()[[x]]), renewalRatesSD = data.frame(modDatSD[[x]]), - iter = modelSpecInputs()$iter, - burnin = modelSpecInputs()$burnin, - chains = modelSpecInputs()$chains) + iter = input[["modelSpecification-iter"]], + burnin = input[["modelSpecification-burnin"]], + chains = input[["modelSpecification-chains"]]) }, value = x / length(modDat), message = paste0("Calculate model for individual nr.", x)) }) @@ -246,7 +260,13 @@ shinyServer(function(input, output, session) { allModels <- allModels[!grepl("Current", names(allModels))] # kept for the case if models # from versions before 22.11.1 were uploaded for(i in 1:length(fitted)){ - newName <- paste0(modelSpecInputs()$indVar, "_", names(modDat)[i]) + if (length(input[["modelSpecification-indVar"]]) == 0 || input[["modelSpecification-indVar"]] == "") { + prefix <- "individual" + } else { + prefix <- input[["modelSpecification-indVar"]] + } + + newName <- paste0(prefix, "_", names(modDat)[i]) allModels <- allModels[!grepl(newName, names(allModels))] newModel <- setNames(list( list(modelSpecifications = reactiveValuesToList(modelSpecInputs()), @@ -640,18 +660,19 @@ shinyServer(function(input, output, session) { }) observe({ - if (is.null(modelSpecInputs()$timeMinimum) || is.null(modelSpecInputs()$timeMaximum)) { - updateNumericInput(session, "from", value = defaultInputsForUI()$from) - updateNumericInput(session, "to", value = defaultInputsForUI()$to) - updateNumericInput(session, "from2", value = defaultInputsForUI()$from2) - updateNumericInput(session, "to2", value = defaultInputsForUI()$to2) - } - - req(modelSpecInputs()$timeMinimum, modelSpecInputs()$timeMaximum) - updateNumericInput(session, "from", value = modelSpecInputs()$timeMinimum) - updateNumericInput(session, "to", value = modelSpecInputs()$timeMaximum) - updateNumericInput(session, "from2", value = modelSpecInputs()$timeMinimum) - updateNumericInput(session, "to2", value = modelSpecInputs()$timeMaximum) + updateNumericInput(session, "from", + value = getTimeMin(mtrx = input$dataMatrix, + timeVars = input[["modelSpecification-timeVars"]])) + updateNumericInput(session, "to", + value = getTimeMax(mtrx = input$dataMatrix, + timeVars = input[["modelSpecification-timeVars"]]) + ) + updateNumericInput(session, "from2", + value = getTimeMin(mtrx = input$dataMatrix, + timeVars = input[["modelSpecification-timeVars"]])) + updateNumericInput(session, "to2", + value = getTimeMax(mtrx = input$dataMatrix, + timeVars = input[["modelSpecification-timeVars"]])) }) observeEvent(input$savedModelsTime, { @@ -898,7 +919,7 @@ shinyServer(function(input, output, session) { importedStayTime <- DataTools::importDataServer( "stayTimeData", - customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], options = DataTools::importOptions(rPackageName = config()[["rPackageName"]]) @@ -1001,7 +1022,7 @@ shinyServer(function(input, output, session) { importedHistData <- DataTools::importDataServer( "fileHistData", - customErrorChecks = list(reactive(DataTools::checkNonNumericColumnsExceptFirst)), + customErrorChecks = list(reactive(DataTools::checkAnyNonNumericColumns)), defaultSource = config()[["defaultSourceData"]], ckanFileTypes = config()[["ckanFileTypes"]], options = DataTools::importOptions(rPackageName = config()[["rPackageName"]]) diff --git a/inst/app/ui.R b/inst/app/ui.R index 9580cc2..b22ec86 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -61,7 +61,7 @@ tagList( editableNames = TRUE ), rows = list( - names = FALSE, + names = TRUE, editableNames = TRUE, extend = TRUE, delta = 1 @@ -86,7 +86,7 @@ tagList( editableNames = FALSE ), rows = list( - names = FALSE, + names = TRUE, editableNames = FALSE, extend = FALSE, delta = 1 @@ -110,7 +110,7 @@ tagList( delta = 0 ), rows = list( - names = FALSE, + names = TRUE, editableNames = FALSE, extend = TRUE, delta = 1 @@ -357,7 +357,7 @@ tagList( editableNames = TRUE ), rows = list( - names = FALSE, + names = TRUE, editableNames = TRUE, extend = TRUE ) @@ -456,7 +456,7 @@ tagList( editableNames = TRUE ), rows = list( - names = FALSE, + names = TRUE, editableNames = TRUE, extend = TRUE ) diff --git a/man/extractIndividuals.Rd b/man/extractIndividuals.Rd new file mode 100644 index 0000000..e7747d9 --- /dev/null +++ b/man/extractIndividuals.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/01-cleanAndSplitData.R +\name{extractIndividuals} +\alias{extractIndividuals} +\title{Extract Individuals} +\usage{ +extractIndividuals(matrix, indVar) +} +\arguments{ +\item{matrix}{(matrix) matrix of data} + +\item{indVar}{(character) name if individuals column or empty if rownames} +} +\value{ +(character) vector of individuals or row names with individuals +} +\description{ +Extract Individuals +} diff --git a/man/getTimeMax.Rd b/man/getTimeMax.Rd new file mode 100644 index 0000000..4d2221d --- /dev/null +++ b/man/getTimeMax.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-module_modelSpecifications.R +\name{getTimeMax} +\alias{getTimeMax} +\title{Get Time Maximum} +\usage{ +getTimeMax(mtrx, timeVars, default = 1) +} +\arguments{ +\item{mtrx}{(matrix) data matrix} + +\item{timeVars}{(character) column names of time variables} + +\item{default}{(numeric) default result} +} +\value{ +(numeric) maximal time +} +\description{ +Get Time Maximum +} diff --git a/man/getTimeMin.Rd b/man/getTimeMin.Rd new file mode 100644 index 0000000..8794880 --- /dev/null +++ b/man/getTimeMin.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-module_modelSpecifications.R +\name{getTimeMin} +\alias{getTimeMin} +\title{Get Time Minimum} +\usage{ +getTimeMin(mtrx, timeVars, default = 0) +} +\arguments{ +\item{mtrx}{(matrix) data matrix} + +\item{timeVars}{(character) column names of time variables} + +\item{default}{(numeric) default result} +} +\value{ +(numeric) minimal time +} +\description{ +Get Time Minimum +} diff --git a/tests/testthat/test-cleanAndSplitData.R b/tests/testthat/test-cleanAndSplitData.R index 6f32b1d..e057a18 100644 --- a/tests/testthat/test-cleanAndSplitData.R +++ b/tests/testthat/test-cleanAndSplitData.R @@ -1,4 +1,4 @@ -testthat::test_that("cleanAndSplitData", { +testthat::test_that("setVarsForUncMatrix and cleanAndSplitData", { # test example data testRenewalRates <- structure( c(1, 1, 1, 11, 11, 2, 2, 2, 2, 2, 2, @@ -14,15 +14,23 @@ testthat::test_that("cleanAndSplitData", { ) # test zero uncertainty - testRenewalRatesUnc <- structure( - c(1, 1, 1, 11, 11, 2, 2, 2, 2, 2, 2, - 0, 1, 2, 4, 5, 0, 1, 2, 3, 4, 5, - 1, 2, 3, 5, 6, 1, 2, 3, 4, 5, 6, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), - dim = c(11L, 7L), - dimnames = list(c("", "", "", "", "", "", "", "", "", "", ""), - c("individual", "intStart", "intEnd", "bone1", "bone2", "tooth1", "tooth2"))) + testRenewalRatesUnc <- setVarsForUncMatrix(testRenewalRates, + timeVars = c("intStart", "intEnd"), + indVar = "individual", + renewalRatesUnc = NULL) + + expect_equal( + testRenewalRatesUnc, + structure( + c(1, 1, 1, 11, 11, 2, 2, 2, 2, 2, 2, + 0, 1, 2, 4, 5, 0, 1, 2, 3, 4, 5, + 1, 2, 3, 5, 6, 1, 2, 3, 4, 5, 6, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + dim = c(11L, 7L), + dimnames = list(c("", "", "", "", "", "", "", "", "", "", ""), + c("individual", "intStart", "intEnd", "bone1", "bone2", "tooth1", "tooth2"))) + ) splittedData <- cleanAndSplitData(indVar = "individual", renewalRates = testRenewalRates, @@ -47,22 +55,6 @@ testthat::test_that("cleanAndSplitData", { row.names = c("X", "X.1", "X.2") ), - `2` = structure( - list( - individual = c(2, 2, 2, - 2, 2, 2), - intStart = c(0, 1, 2, 3, 4, 5), - intEnd = c(1, 2, 3, - 4, 5, 6), - bone1 = c(100, 80, 30, 8, 15, 4), - bone2 = c(90, 40, - 5, 1, 12, 1), - tooth1 = c(0, 80, 20, 0, 0, 0) - ), - class = "data.frame", - row.names = c("X.5", - "X.6", "X.7", "X.8", "X.9", "X.10") - ), `11` = structure( list( individual = c(11, @@ -78,6 +70,22 @@ testthat::test_that("cleanAndSplitData", { class = "data.frame", row.names = c("X.3", "X.4") + ), + `2` = structure( + list( + individual = c(2, 2, 2, + 2, 2, 2), + intStart = c(0, 1, 2, 3, 4, 5), + intEnd = c(1, 2, 3, + 4, 5, 6), + bone1 = c(100, 80, 30, 8, 15, 4), + bone2 = c(90, 40, + 5, 1, 12, 1), + tooth1 = c(0, 80, 20, 0, 0, 0) + ), + class = "data.frame", + row.names = c("X.5", + "X.6", "X.7", "X.8", "X.9", "X.10") ) ) ) @@ -101,6 +109,22 @@ testthat::test_that("cleanAndSplitData", { row.names = c("X", "X.1", "X.2") ), + `11` = structure( + list( + individual = c(11, + 11), + intStart = c(4, 5), + intEnd = c(5, 6), + bone1 = c(0, 0), + bone2 = c(0, + 0), + tooth1 = c(0, 0), + tooth2 = c(0, 0) + ), + class = "data.frame", + row.names = c("X.3", + "X.4") + ), `2` = structure( list( individual = c(2, 2, 2, @@ -116,27 +140,136 @@ testthat::test_that("cleanAndSplitData", { class = "data.frame", row.names = c("X.5", "X.6", "X.7", "X.8", "X.9", "X.10") + ) + ) + ) +}) + + +testthat::test_that("with rownames: setVarsForUncMatrix and cleanAndSplitData", { + # test example data with rownames + testRenewalRates <- structure( + c(0, 1, 2, 4, 5, 0, 1, 2, 3, 4, 5, + 1, 2, 3, 5, 6, 1, 2, 3, 4, 5, 6, + 100, 50, 20, 5, 2, 100, 80, 30, 8, 15, 4, + 100, 10, 5, 1, 1, 90, 40, 5, 1, 12, 1, + 0, 100, 0, 0, 0, 0, 80, 20, 0, 0, 0, + 0, 0, 100, 0, 0, NA, NA, NA, NA, NA, NA), + dim = c(11L, 6L), + dimnames = list(as.character(c(1, 1, 1, 11, 11, 2, 2, 2, 2, 2, 2)), + c("intStart", "intEnd", "bone1", "bone2", "tooth1", "tooth2")) + ) + + testIndVar <- "" + attr(testIndVar, "useRownames") <- TRUE + + testRenewalRatesUnc <- setVarsForUncMatrix(testRenewalRates, + timeVars = c("intStart", "intEnd"), + indVar = testIndVar) + + expect_equal( + testRenewalRatesUnc, + structure( + c(0, 1, 2, 4, 5, 0, 1, 2, 3, 4, 5, + 1, 2, 3, 5, 6, 1, 2, 3, 4, 5, 6, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), + dim = c(11L, 6L), + dimnames = list(as.character(c(1, 1, 1, 11, 11, 2, 2, 2, 2, 2, 2)), + c("intStart", "intEnd", "bone1", "bone2", "tooth1", "tooth2"))) + ) + + + splittedData <- cleanAndSplitData(indVar = testIndVar, + renewalRates = testRenewalRates, + renewalRatesUnc = testRenewalRatesUnc) + + testthat::expect_equal( + splittedData$renewalRatesPerInd, + list( + `1` = structure( + list( + intStart = c(0, 1, 2), + intEnd = c(1, 2, 3), + bone1 = c(100, 50, 20), + bone2 = c(100, 10, 5), + tooth1 = c(0, 100, 0), + tooth2 = c(0, 0, 100) + ), + class = "data.frame", + row.names = c("X1", "X1.1", "X1.2") + ), + `11` = structure( + list( + intStart = c(4, 5), + intEnd = c(5, 6), + bone1 = c(5, 2), + bone2 = c(1, 1), + tooth1 = c(0, 0), + tooth2 = c(0, 0) + ), + class = "data.frame", + row.names = c("X11", + "X11.1") + ), + `2` = structure( + list( + intStart = c(0, 1, 2, 3, 4, 5), + intEnd = c(1, 2, 3, 4, 5, 6), + bone1 = c(100, 80, 30, 8, 15, 4), + bone2 = c(90, 40, 5, 1, 12, 1), + tooth1 = c(0, 80, 20, 0, 0, 0) + ), + class = "data.frame", + row.names = c("X2", "X2.1", "X2.2", "X2.3", "X2.4", "X2.5") + ) + ) + ) + + testthat::expect_equal( + splittedData$renewalRatesUncPerInd, + list( + `1` = structure( + list( + intStart = c(0, 1, 2), + intEnd = c(1, 2, 3), + bone1 = c(0, 0, 0), + bone2 = c(0, 0, 0), + tooth1 = c(0, 0, 0), + tooth2 = c(0, 0, 0) + ), + class = "data.frame", + row.names = c("X1", "X1.1", "X1.2") ), `11` = structure( list( - individual = c(11, - 11), intStart = c(4, 5), intEnd = c(5, 6), bone1 = c(0, 0), - bone2 = c(0, - 0), + bone2 = c(0, 0), tooth1 = c(0, 0), tooth2 = c(0, 0) ), class = "data.frame", - row.names = c("X.3", - "X.4") + row.names = c("X11", "X11.1") + ), + `2` = structure( + list( + intStart = c(0, 1, 2, 3, 4, 5), + intEnd = c(1, 2, 3, 4, 5, 6), + bone1 = c(0, 0, 0, 0, 0, 0), + bone2 = c(0, 0, 0, 0, 0, 0), + tooth1 = c(0, 0, 0, 0, 0, 0) + ), + class = "data.frame", + row.names = c("X2", "X2.1", "X2.2", "X2.3", "X2.4", "X2.5") ) ) ) + }) + testthat::test_that("cleanAndSplitData with missing values", { # test example data with missing values testRenewalRates <- structure( @@ -185,21 +318,6 @@ testthat::test_that("cleanAndSplitData with missing values", { row.names = c("X.1", "X.2") ), - `2` = structure( - list( - individual = c(2, 2, 2, 2, 2, 2), - intStart = c(0, 1, 2, 3, 4, 5), - intEnd = c(1, 2, 3, 4, 5, - 6), - bone1 = c(100, 80, 30, 8, 15, 4), - bone2 = c(90, 40, 5, 1, - 12, 1), - tooth1 = c(0, 80, 20, 0, 0, 0) - ), - class = "data.frame", - row.names = c("X.5", - "X.6", "X.7", "X.8", "X.9", "X.10") - ), `11` = structure( list( individual = c(11, @@ -215,6 +333,21 @@ testthat::test_that("cleanAndSplitData with missing values", { class = "data.frame", row.names = c("X.3", "X.4") + ), + `2` = structure( + list( + individual = c(2, 2, 2, 2, 2, 2), + intStart = c(0, 1, 2, 3, 4, 5), + intEnd = c(1, 2, 3, 4, 5, + 6), + bone1 = c(100, 80, 30, 8, 15, 4), + bone2 = c(90, 40, 5, 1, + 12, 1), + tooth1 = c(0, 80, 20, 0, 0, 0) + ), + class = "data.frame", + row.names = c("X.5", + "X.6", "X.7", "X.8", "X.9", "X.10") ) ) ) @@ -238,6 +371,21 @@ testthat::test_that("cleanAndSplitData with missing values", { row.names = c("X", "X.1", "X.2") ), + `11` = structure( + list( + individual = c(11, 11), + intStart = c(4, 5), + intEnd = c(5, + 6), + bone1 = c(0, 0), + bone2 = c(0, 0), + tooth1 = c(0, 0), + tooth2 = c(0, + 0) + ), + row.names = c("X.3", "X.4"), + class = "data.frame" + ), `2` = structure( list( individual = c(2, 2, 2, @@ -253,21 +401,6 @@ testthat::test_that("cleanAndSplitData with missing values", { row.names = c("X.5", "X.6", "X.7", "X.8", "X.9", "X.10"), class = "data.frame" - ), - `11` = structure( - list( - individual = c(11, 11), - intStart = c(4, 5), - intEnd = c(5, - 6), - bone1 = c(0, 0), - bone2 = c(0, 0), - tooth1 = c(0, 0), - tooth2 = c(0, - 0) - ), - row.names = c("X.3", "X.4"), - class = "data.frame" ) ) ) diff --git a/tests/testthat/test-helperFunctions.R b/tests/testthat/test-helperFunctions.R deleted file mode 100644 index 11075ec..0000000 --- a/tests/testthat/test-helperFunctions.R +++ /dev/null @@ -1,30 +0,0 @@ -testthat::test_that("cleanAndSplitData", { - # test example data - testRenewalRates <- structure( - c(1, 1, 1, 11, 11, 2, 2, 2, 2, 2, 2, - 0, 1, 2, 4, 5, 0, 1, 2, 3, 4, 5, - 1, 2, 3, 5, 6, 1, 2, 3, 4, 5, 6, - 100, 50, 20, 5, 2, 100, 80, 30, 8, 15, 4, - 100, 10, 5, 1, 1, 90, 40, 5, 1, 12, 1, - 0, 100, 0, 0, 0, 0, 80, 20, 0, 0, 0, - 0, 0, 100, 0, 0, NA, NA, NA, NA, NA, NA), - dim = c(11L, 7L), - dimnames = list(c("", "", "", "", "", "", "", "", "", "", ""), - c("individual", "intStart", "intEnd", "bone1", "bone2", "tooth1", "tooth2")) - ) - - expect_equal( - setVarsForUncMatrix(testRenewalRates, - timeVars = c("intStart", "intEnd"), - indVar = "individual"), - structure( - c(1, 1, 1, 11, 11, 2, 2, 2, 2, 2, 2, - 0, 1, 2, 4, 5, 0, 1, 2, 3, 4, 5, - 1, 2, 3, 5, 6, 1, 2, 3, 4, 5, 6, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), - dim = c(11L, 7L), - dimnames = list(c("", "", "", "", "", "", "", "", "", "", ""), - c("individual", "intStart", "intEnd", "bone1", "bone2", "tooth1", "tooth2"))) - ) -}) From 94147a81f522b4190f2b1f6cca5f5ab3a4547409 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 24 Apr 2024 15:44:07 +0200 Subject: [PATCH 21/36] apply module plotPoints from shinyTools (#44) * apply module plotPoints from shinyTools * fix news.md * update titles * update version * update news.md * switch to latest shinyTools Version --------- Co-authored-by: Jan Abel --- DESCRIPTION | 5 +++-- NAMESPACE | 3 +++ NEWS.md | 5 +++++ R/NAMESPACE.R | 1 + R/plots.R | 10 +++++++--- inst/app/server.R | 14 ++++++++++---- inst/app/ui.R | 9 ++++++++- inst/config.yaml | 10 ++++++++++ man/OsteoBioR-package.Rd | 1 + man/plotTime.Rd | 3 +++ 10 files changed, 51 insertions(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 2b8750b..2a0c698 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.01.1 +Version: 24.04.0 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -36,6 +36,7 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, + shinyTools (>= 24.04.0), yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: @@ -43,5 +44,5 @@ Suggests: testthat (>= 3.0.0) SystemRequirements: GNU make NeedsCompilation: yes -RoxygenNote: 7.2.3 +RoxygenNote: 7.3.1 Config/testthat/edition: 3 diff --git a/NAMESPACE b/NAMESPACE index fee755c..7010e02 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -80,6 +80,9 @@ importFrom(parallel,detectCores) importFrom(rlang,.data) importFrom(rstan,extract) importFrom(rstan,sampling) +importFrom(shinyTools,formatPointsOfGGplot) +importFrom(shinyTools,plotPointsServer) +importFrom(shinyTools,plotPointsUI) importFrom(shinyWidgets,pickerInput) importFrom(shinyWidgets,updatePickerInput) importFrom(shinyjs,alert) diff --git a/NEWS.md b/NEWS.md index 629a18e..c18e0ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # OsteoBioR +## Version 24.04.0 + +### New Features +- option to change symbol, color, size of points in "Model: Credibility intervals over time" (#43) + ## Version 24.01.1 ### New Features diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index ba17579..6d47c0a 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -25,6 +25,7 @@ #' @importFrom rlang .data #' @importFrom rstan sampling extract #' @importFrom shinyjs alert +#' @importFrom shinyTools plotPointsUI plotPointsServer formatPointsOfGGplot #' @importFrom shinyWidgets pickerInput updatePickerInput #' @importFrom stats approx dnorm lm median quantile sd #' @importFrom utils write.csv write.table combn diff --git a/R/plots.R b/R/plots.R index fe91f78..1e5ed10 100644 --- a/R/plots.R +++ b/R/plots.R @@ -25,6 +25,7 @@ #' @param extendLabels boolean if TRUE, extend the labels of the x-axis to the x-axis limits. #' If FALSE, the range of the data defines the range of x-axis labels. #' @param ... arguments handed to \code{\link{getShiftTime}} +#' @inheritParams shinyTools::formatPointsOfGGplot #' #' @return a \link[ggplot2]{ggplot} object. #' @@ -35,6 +36,7 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, colorU = NULL, alphaL = 0.9, alphaU = 0.1, sizeTextY = 12, sizeTextX = 12, sizeAxisX = 12, sizeAxisY = 12, secAxis = FALSE, xAxisLabel = "Time", yAxisLabel = "Estimate", extendLabels = FALSE, + pointStyle = config()[["defaultPointStyle"]], ...){ stopifnot(prop < 1) @@ -47,13 +49,14 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, lineFct(aes(y = .data[["median"]]), colour = colorL, alpha = alphaL) + lineFct(aes(y = .data[["lower"]]), size = 0.05, colour = colorL, alpha = alphaL) + lineFct(aes(y = .data[["upper"]]), size = 0.05, colour = colorL, alpha = alphaL) + - geom_point(aes(x = .data[["time"]], y = .data[["median"]]), colour = colorL, alpha = alphaL) + coord_cartesian(ylim = yLim, xlim = xLim) + theme(panel.grid.major.x = element_line(size = 0.1)) + theme(axis.title.x = element_text(size = sizeTextX), axis.title.y = element_text(size = sizeTextY), axis.text.x = element_text(size = sizeAxisX), axis.text.y = element_text(size = sizeAxisY)) + p <- p %>% + formatPointsOfGGplot(data = x, aes(x = .data[["time"]], y = .data[["median"]]), pointStyle = pointStyle) if (nrow(x) > 1) p <- p + geom_ribbon(aes(ymin = .data[["lower"]], ymax = .data[["upper"]]), linetype = 2, alpha = alphaU, fill = colorU) } else { @@ -72,8 +75,9 @@ plotTime <- function(object, prop = 0.8, plotShifts = FALSE, p <- oldPlot + geom_line(data = x, aes(y = .data[["median"]]), colour = colorL, alpha = alphaL) + geom_line(data = x, aes(y = .data[["lower"]]), size = 0.05, colour = colorL, alpha = alphaL) + geom_line(data = x, aes(y = .data[["upper"]]), size = 0.05, colour = colorL, alpha = alphaL) + - geom_ribbon(data = x, aes(ymin = .data[["lower"]], ymax = .data[["upper"]]), linetype = 2, alpha = alphaU, fill = colorU) + - geom_point(data = x, aes(x = .data[["time"]], y = .data[["median"]]), colour = colorL, alpha = alphaL) + geom_ribbon(data = x, aes(ymin = .data[["lower"]], ymax = .data[["upper"]]), linetype = 2, alpha = alphaU, fill = colorU) + p <- p %>% + formatPointsOfGGplot(data = x, aes(x = .data[["time"]], y = .data[["median"]]), pointStyle = pointStyle) if(secAxis){ p <- p + diff --git a/inst/app/server.R b/inst/app/server.R index ce9d2fa..dde8e6a 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -582,6 +582,8 @@ shinyServer(function(input, output, session) { savedPlot <- reactiveVal(list()) #savedXAxisData <- reactiveVal(data.frame()) + pointStyle <- shinyTools::plotPointsServer("pointStyle", type = "ggplot", initStyle = config()[["defaultPointStyle"]]) + observeEvent(input$credIntTimePlot, { req(savedModels(), input$credIntTimePlot) fits <- getEntry(savedModels()[input$credIntTimePlot], "fit") @@ -597,7 +599,8 @@ shinyServer(function(input, output, session) { xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, - extendLabels = input$extendLabels) + extendLabels = input$extendLabels, + pointStyle = pointStyle) intervalTimePlot(p) savedPlot(p) #savedXAxisData(getXAxisData(fitForTimePlot())) @@ -614,7 +617,8 @@ shinyServer(function(input, output, session) { xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, - extendLabels = input$extendLabels) + extendLabels = input$extendLabels, + pointStyle = pointStyle) intervalTimePlot(p) savedPlot(p) #savedXAxisData(getXAxisData(fitForTimePlot())) @@ -629,7 +633,8 @@ shinyServer(function(input, output, session) { xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, - extendLabels = input$extendLabels) + extendLabels = input$extendLabels, + pointStyle = pointStyle) intervalTimePlot(p) savedPlot(p) #savedXAxisData(getXAxisData(fitForTimePlot())) @@ -648,7 +653,8 @@ shinyServer(function(input, output, session) { sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, secAxis = input$secAxis, - extendLabels = input$extendLabels) + extendLabels = input$extendLabels, + pointStyle = pointStyle) intervalTimePlot(p) savedPlot(p) #savedXAxisData(getXAxisData(object = fitForTimePlot(), oldXAxisData = oldXAxisData)) diff --git a/inst/app/ui.R b/inst/app/ui.R index b22ec86..0e7f5d9 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -188,6 +188,7 @@ tagList( tags$br(), fluidRow( column(2, + tags$h4("X-Axis"), numericInput("xmin", "Lower x limit", value = defaultInputsForUI()$xmin), numericInput("xmax", "Upper x limit", @@ -200,6 +201,7 @@ tagList( value = FALSE) ), column(2, + tags$h4("Y-Axis"), numericInput("ymin", "Lower y limit", value = defaultInputsForUI()$ymin), numericInput("ymax", "Upper y limit", @@ -208,7 +210,8 @@ tagList( numericInput(inputId = "sizeTextY", label = "Font size y-axis title", value = 24), numericInput(inputId = "sizeAxisY", label = "Font size y-axis", value = 18) ), - column(4, + column(2, + tags$h4("Lines"), colourInput(inputId = "colorL", label = "Color line", value = rgb(0, 35 / 255, 80 / 255, alpha = 0.6)), @@ -220,7 +223,11 @@ tagList( value = rgb(0, 35 / 255, 80 / 255, alpha = 0.6)), sliderInput("alphaU", "Transparency uncertainty region", min = 0, max = 1, value = 0.1) ), + column(2, + shinyTools::plotPointsUI(id = "pointStyle", initStyle = config()[["defaultPointStyle"]]) + ), column(4, + tags$h4("Plot"), checkboxInput("secAxis", "Add new secondary axis to existing plot", value = F), radioButtons("deriv", "Type", choices = c("Absolute values" = "1", "First derivate" = "2")), sliderInput("modCredInt", diff --git a/inst/config.yaml b/inst/config.yaml index c3dd992..4405bfa 100644 --- a/inst/config.yaml +++ b/inst/config.yaml @@ -14,3 +14,13 @@ githubRepo: "osteobior" # for import of models mainFolder: "predefinedModels" # for import of models fileExtension: "osteobior" # for download of models rPackageName: "OsteoBioR" # for download and import of models +# default point style +defaultPointStyle: + dataPoints: + symbol: 19 + color: "#002350" + colorBg: "#002350" + size: 2 + alpha: 1 + lineWidthBg: 2 + hide: false diff --git a/man/OsteoBioR-package.Rd b/man/OsteoBioR-package.Rd index 107c880..03c293a 100644 --- a/man/OsteoBioR-package.Rd +++ b/man/OsteoBioR-package.Rd @@ -2,6 +2,7 @@ % Please edit documentation in R/NAMESPACE.R \docType{package} \name{OsteoBioR-package} +\alias{-package} \alias{OsteoBioR-package} \alias{OsteoBioR} \title{The 'OsteoBioR' package.} diff --git a/man/plotTime.Rd b/man/plotTime.Rd index 2c93a23..ecd523d 100644 --- a/man/plotTime.Rd +++ b/man/plotTime.Rd @@ -25,6 +25,7 @@ plotTime( xAxisLabel = "Time", yAxisLabel = "Estimate", extendLabels = FALSE, + pointStyle = config()[["defaultPointStyle"]], ... ) } @@ -70,6 +71,8 @@ plotTime( \item{extendLabels}{boolean if TRUE, extend the labels of the x-axis to the x-axis limits. If FALSE, the range of the data defines the range of x-axis labels.} +\item{pointStyle}{(list) named list with style definitions, or output of \code{plotPointsServer}} + \item{...}{arguments handed to \code{\link{getShiftTime}}} } \value{ From ca5158ed9ec7bdb8b21ce73bf3aae776c7c8b4af Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 24 Apr 2024 18:16:37 +0200 Subject: [PATCH 22/36] Feat/43 multi plot (#46) * apply module plotPoints from shinyTools * fix news.md * update titles * extract functions basePlotTime, layerPlotTime and add tests, add ui to select multiple models * extractDrawLinesAndRibbon * apply basePlotTime and layerPlotTime in plotTime * rename functions * return parameter oldXAxisData * extract: extractAllXAxisData() * remove old UI for newPlot and addPlot * apply plotTitles module * apply plotRanges module * fixes for checks * style points/lines per model, rescaling for second axis * extract module timePlotFormatting, integrate second axis * clean up UI * remove old function definition * update version * update news.md * integrate renderPlot end exportPlot into submodule * update news.md * Feat/43 export plot data (#47) * format, display and export time plot data * apply module from shinyTools to export the plot * remove duplicted variable * disable export buttons if there is no data * inline extractPlotData * add columns for time_lower and time_upper * fix missing plot limits * add sd to displayData * update news.md * fix format * Feat/43 legend (#50) * prepare integration of legend * scales for style of models, update plotTime() * integrate second axis * update shinyTools for legend options, update UI * update tests * shift plot functions into separate files, fix duplicated application of deriv with breaks * fix namespace and test * update description and news.md * try fix: rename param in basePlotTime * try fix: element_line --- DESCRIPTION | 5 +- NAMESPACE | 28 +- NEWS.md | 87 ++-- R/01-plotFormatting.R | 43 ++ R/01-plotSecondYAxis.R | 67 ++++ R/01-plotSetXAxisLabels.R | 118 ++++++ R/03-timePlotFormatting.R | 329 +++++++++++++++ R/NAMESPACE.R | 16 +- R/estimateIntervals.R | 2 +- R/plots.R | 374 +++++++++--------- RScripts/example.R | 2 +- inst/app/server.R | 192 +-------- inst/app/ui.R | 67 +--- inst/config.yaml | 31 +- man/add_na_row.Rd | 14 + man/estimateIntervals.Rd | 2 +- man/extendXAxis.Rd | 2 +- man/getBreaks.Rd | 2 +- man/getDefaultPlotRange.Rd | 16 - man/getLabel.Rd | 2 +- man/pasteLabelForDerivation.Rd | 2 +- man/plotTime.Rd | 17 +- man/timePlotFormattingServer.Rd | 16 + man/timePlotFormattingUI.Rd | 17 + man/updateTime.Rd | 18 + mpi-temporal-iso.Rproj | 1 + tests/testthat/shinyTest_startApplication.R | 14 + tests/testthat/startApplication/app.R | 1 + tests/testthat/test-plotTime.R | 254 ++++++++++++ tests/testthat/test-plots.R | 85 ++-- .../{ => testdata}/testObjectDefault.RData | Bin .../{ => testdata}/testObjectGap.RData | Bin 32 files changed, 1269 insertions(+), 555 deletions(-) create mode 100644 R/01-plotFormatting.R create mode 100644 R/01-plotSecondYAxis.R create mode 100644 R/01-plotSetXAxisLabels.R create mode 100644 R/03-timePlotFormatting.R create mode 100644 man/add_na_row.Rd delete mode 100644 man/getDefaultPlotRange.Rd create mode 100644 man/timePlotFormattingServer.Rd create mode 100644 man/timePlotFormattingUI.Rd create mode 100644 man/updateTime.Rd create mode 100644 tests/testthat/shinyTest_startApplication.R create mode 100644 tests/testthat/startApplication/app.R create mode 100644 tests/testthat/test-plotTime.R rename tests/testthat/{ => testdata}/testObjectDefault.RData (100%) rename tests/testthat/{ => testdata}/testObjectGap.RData (100%) diff --git a/DESCRIPTION b/DESCRIPTION index 2a0c698..f45512f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.04.0 +Version: 24.04.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -36,11 +36,12 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, - shinyTools (>= 24.04.0), + shinyTools (>= 24.04.1), yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: lintr, + shinytest, testthat (>= 3.0.0) SystemRequirements: GNU make NeedsCompilation: yes diff --git a/NAMESPACE b/NAMESPACE index 7010e02..f546cee 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -14,7 +14,6 @@ export(exportXLSX) export(extractIndividuals) export(extractModelOutputs) export(extractSavedModels) -export(getDefaultPlotRange) export(getEntry) export(getExampleDataMatrix) export(getExampleIsotopeVals) @@ -35,6 +34,8 @@ export(plotTime) export(removeModelOutputs) export(setVarsForUncMatrix) export(startApplication) +export(timePlotFormattingServer) +export(timePlotFormattingUI) export(updateMatrixNamesInput) exportClasses(TemporalIso) exportMethods(export) @@ -54,9 +55,17 @@ importFrom(DataTools,renameExistingNames) importFrom(DataTools,tryCatchWithWarningsAndErrors) importFrom(colourpicker,colourInput) importFrom(dplyr,arrange) +importFrom(dplyr,bind_cols) importFrom(dplyr,bind_rows) +importFrom(dplyr,cur_group_id) importFrom(dplyr,distinct) +importFrom(dplyr,do) +importFrom(dplyr,group_by) +importFrom(dplyr,mutate) +importFrom(dplyr,n) +importFrom(dplyr,select) importFrom(dplyr,slice) +importFrom(dplyr,ungroup) importFrom(ggplot2,aes) importFrom(ggplot2,coord_cartesian) importFrom(ggplot2,element_line) @@ -66,8 +75,13 @@ importFrom(ggplot2,geom_point) importFrom(ggplot2,geom_ribbon) importFrom(ggplot2,geom_vline) importFrom(ggplot2,ggplot) +importFrom(ggplot2,ggplot_build) importFrom(ggplot2,ggtitle) importFrom(ggplot2,labs) +importFrom(ggplot2,scale_colour_manual) +importFrom(ggplot2,scale_fill_manual) +importFrom(ggplot2,scale_shape_manual) +importFrom(ggplot2,scale_size_manual) importFrom(ggplot2,scale_x_continuous) importFrom(ggplot2,scale_y_continuous) importFrom(ggplot2,sec_axis) @@ -80,16 +94,28 @@ importFrom(parallel,detectCores) importFrom(rlang,.data) importFrom(rstan,extract) importFrom(rstan,sampling) +importFrom(shinyTools,dataExportButton) +importFrom(shinyTools,dataExportServer) importFrom(shinyTools,formatPointsOfGGplot) +importFrom(shinyTools,formatRangesOfGGplot) +importFrom(shinyTools,formatTitlesOfGGplot) +importFrom(shinyTools,plotExportButton) +importFrom(shinyTools,plotExportServer) importFrom(shinyTools,plotPointsServer) importFrom(shinyTools,plotPointsUI) +importFrom(shinyTools,plotRangesServer) +importFrom(shinyTools,plotRangesUI) +importFrom(shinyTools,plotTitlesServer) +importFrom(shinyTools,plotTitlesUI) importFrom(shinyWidgets,pickerInput) importFrom(shinyWidgets,updatePickerInput) +importFrom(shinycssloaders,withSpinner) importFrom(shinyjs,alert) importFrom(stats,approx) importFrom(stats,dnorm) importFrom(stats,lm) importFrom(stats,median) +importFrom(stats,na.omit) importFrom(stats,quantile) importFrom(stats,sd) importFrom(utils,combn) diff --git a/NEWS.md b/NEWS.md index c18e0ae..998a63e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,106 +1,119 @@ -# OsteoBioR - -## Version 24.04.0 - -### New Features +# OsteoBioR 24.04.1 + +## New Features +- when generating multiple plots in Credible intervals over time have the option to select which +ones to show/hide (#45) +- when having two y-axes (one on the left and another one on the right of the plot). Have the option +to switch their positions. (#43) +- option to add/edit plot(s) legend +- table output of data for the "Credible intervals over time" plot (#43) +- option to export values for displayed graph under "Credibility intervals over time" (#43) +- option to export data for single plots or batch export (#43) + - for the latter a single file is generated with data for different individuals separated by a + fully empty row. + - individual ID’s are given in a first column and for all x values + - a column gives row number (1,2,3…) for each estimate + +# OsteoBioR 24.04.0 + +## New Features - option to change symbol, color, size of points in "Model: Credibility intervals over time" (#43) -## Version 24.01.1 +# OsteoBioR 24.01.1 -### New Features -- Option to use rownames of data as individual variable. This allows to use non-numeric values for +## New Features +- option to use rownames of data as individual variable. This allows to use non-numeric values for individuals since rownames do not need to be numeric (#35) -## Version 24.01.0 +# OsteoBioR 24.01.0 -### Bug Fixes +## Bug Fixes - fix a bug that prevented the usage of multiple cores during model estimation. In essence only a single core was used. -## Version 23.12.2 +# OsteoBioR 23.12.2 -### New Features +## New Features - _Import of models from Pandora_: - display of "About" information that is associated to a selected Pandora Repository -### Bug Fixes +## Bug Fixes - _Import of models from Pandora_: - an error message occurred when trying to load a model from pandora. - fix: adding the missing download of the zip file from the url before unpacking the zip -## Version 23.12.0 +# OsteoBioR 23.12.0 -#### New Features +## New Features - option to use a fixed seed when fitting the model (#37) -## Version 23.10.1 +# OsteoBioR 23.10.1 -### Bug Fixes +## Bug Fixes - fix for model down- and upload: add missing export to package functions -## Version 23.10.0 +# OsteoBioR 23.10.0 -### New Features +## New Features - _Import of models_: - option to import models from Pandora platform -## Version 23.09.1 +# OsteoBioR 23.09.1 -### New Features +## New Features - tab _Model_, section _Credibility intervals over time_: new checkbox to extend the x-axis labels to the _lower_ and _upper x limit_ (#26) -## Version 23.04.1 +# OsteoBioR 23.04.1 -### Bug Fixes +## Bug Fixes - add missing logic for default values if "Use renewal rates uncertainty" is unchecked (#24) - add an error message if the number of rows or columns do not match between renewal rates and their uncertainties - add tests to check new logic -## Version 23.03.2 +# OsteoBioR 23.03.2 -### Updates +## Updates - remote models are loaded from the github folder `inst/app/predefinedModels` of the respective repository - if there is no internet connection remote models are taken from the models that were saved with the last deployed app version -## Version 23.03.1 +# OsteoBioR 23.03.1 -### Bug fixes +## Bug fixes - add remote package to enable the _Import Data_ module -## Version 23.02.2 +# OsteoBioR 23.02.2 -### Updates +## Updates - add more tryCatch statements -## Version 23.02.1 +# OsteoBioR 23.02.1 -### New Features +## New Features - the _Import Data_ module is now imported from the new package DataTools (#15, PR #16) - additionally to file import, now import from _URL_ or from _Pandora Platform_ is possible - all redundant code was removed - using "file" as default source in _Import Data_ - now, sidebars are fixed with auto scroll in all tabs (iso-app #4) -## Version 23.01.1 +# OsteoBioR 23.01.1 -### New Features +## New Features - option to use renewal rates uncertainties -## Version 22.11.1 +# OsteoBioR 22.11.1 -### Updates +## Updates - added column names to exported interval data - shifted the UI to load/save a model from the right sidebar to the main panel above the tabs - the content of the input _Individual varaible_ is now used to create default model names instead of "Current" - new model name and note in remote Test data containing only model inputs and data -### Bug Fixes - +## Bug Fixes - fixed export of interval data (#12) - fixed reactive behavior after model upload in the tab _Credibility intervals over time_ (#12) - fixed error when trying to export credibility interval plot diff --git a/R/01-plotFormatting.R b/R/01-plotFormatting.R new file mode 100644 index 0000000..d14391b --- /dev/null +++ b/R/01-plotFormatting.R @@ -0,0 +1,43 @@ +getStyleForIndividuals <- function(pointStyleList, input) { + # pointStyleList are reactive values -> lapply over the names and not(!) the list itself + style <- lapply(names(pointStyleList), function(x) {pointStyleList[[x]][input]}) %>% + unlist() + names(style) <- names(pointStyleList) + + style +} + +getDefaultPointFormatForModels <- function(pointStyleList, modelNames) { + # default colours + defaultColours <- ggplot2::scale_color_discrete()$palette(n = length(modelNames)) + names(defaultColours) <- modelNames + + # setup lists with default values for style specs + for (i in modelNames) { + if (is.null(pointStyleList[[i]])) + pointStyleList[[i]] <- config()[["defaultPointStyle"]][["dataPoints"]] + # use default colour per model + pointStyleList[[i]]["color"] <- defaultColours[i] + } + + return(pointStyleList) +} + +getDefaultTextFormat <- function() { + list(legendTitle = config()[["defaultIntervalTimePlotTitle"]], + legendText = config()[["defaultIntervalTimePlotText"]], + plotTitle = config()[["defaultIntervalTimePlotTitle"]], + xAxisTitle = config()[["defaultIntervalTimePlotTitle"]], + yAxisTitle = config()[["defaultIntervalTimePlotTitle"]], + xAxisText = config()[["defaultIntervalTimePlotText"]], + yAxisText = config()[["defaultIntervalTimePlotText"]]) +} + +setLegendPosition <- function(plot, hideLegend, legendPosition) { + if (hideLegend) { + legendPosition <- "none" + } + + plot + + theme(legend.position = legendPosition) +} diff --git a/R/01-plotSecondYAxis.R b/R/01-plotSecondYAxis.R new file mode 100644 index 0000000..59b0be9 --- /dev/null +++ b/R/01-plotSecondYAxis.R @@ -0,0 +1,67 @@ +setSecondYAxis <- function(plot, + rescaling, + titleFormat = NULL, + textFormat = NULL, + yAxisLabel = "Estimate", + yAxisTitleColor = NULL) { + if (identical(rescaling, list(scale = 1, center = 0))) return(plot) + + scale <- rescaling$scale + center <- rescaling$center + + # format equal to first axis: + if (is.null(titleFormat)) titleFormat <- config()[["defaultIntervalTimePlotTitle"]] + if (is.null(textFormat)) textFormat <- config()[["defaultIntervalTimePlotText"]] + # custom format for second axis: + if (is.null(yAxisTitleColor)) yAxisTitleColor <- config()[["defaultIntervalTimePlotTitle"]][["color"]] + + plot <- plot + + theme(axis.title.y.right = element_text(family = "Arial", + size = titleFormat[["size"]], + face = titleFormat[["fontType"]], + color = yAxisTitleColor, + hjust = 0.5), + axis.text.y.right = element_text(family = "Arial", + size = textFormat[["size"]], + face = textFormat[["fontType"]], + color = textFormat[["color"]], + hjust = 0.5)) + + scale_y_continuous( + # Features of the first axis + # Add a second axis and specify its features + sec.axis = sec_axis(~(.* scale) + center, name = yAxisLabel) + ) + + plot +} + +rescaleSecondAxisData <- function(plotData, individual, rescaling) { + if (is.null(individual) || individual == "") return(plotData) + + # get index for filter + index <- plotData$individual == individual + + if (nrow(plotData[index, ]) == 0 || + identical(rescaling, list(scale = 1, center = 0))) return(plotData) + + # rescale data + scale <- rescaling$scale + center <- rescaling$center + + plotData[index, ]$median <- (plotData[index, ]$median - center ) / scale + plotData[index, ]$lower <- (plotData[index, ]$lower - center ) / scale + plotData[index, ]$upper <- (plotData[index, ]$upper - center ) / scale + + plotData +} + +getRescaleParams <- function(oldLimits, newLimits = NULL, secAxis = FALSE) { + if (length(newLimits) == 0 || !secAxis) return(list(scale = 1, center = 0)) + + b <- seq(min(newLimits),max(newLimits), length.out = 100) + a <- seq(min(oldLimits),max(oldLimits), length.out = 100) + res <- lm(b~a) + + list(scale = res$coefficients[2], + center = res$coefficients[1]) +} diff --git a/R/01-plotSetXAxisLabels.R b/R/01-plotSetXAxisLabels.R new file mode 100644 index 0000000..6726e0c --- /dev/null +++ b/R/01-plotSetXAxisLabels.R @@ -0,0 +1,118 @@ +setXAxisLabels <- function(plot, xAxisData, extendLabels, deriv, xLim = NULL) { + # set limits + if (length(xLim) == 2) { + xPlotLim <- xLim + } else { + xPlotLim <- xLabelLim <- range(xAxisData) + } + + # extend labels to full x axis range + if (extendLabels && length(xLim) == 2) { + xLabelLim <- xLim + xAxisData <- xAxisData %>% + extendXAxis(xLabelLim = xLabelLim) + } + + breaks <- getBreaks(time = xAxisData$time, deriv = deriv) + labels <- getLabel(xAxisData = xAxisData, deriv = deriv) + + plot <- plot + + scale_x_continuous(breaks = breaks, labels = labels, limits = xPlotLim) + + plot +} + +extractAllXAxisData <- function(extractedPlotDataList) { + extractedPlotDataList %>% + bind_rows() %>% + select("time", "time_lower", "time_upper") %>% + distinct() %>% + arrange(.data$time) +} + +#' Extend X Axis +#' +#' Add breaks and labels for x axis +#' +#' @param xAxisData (data.frame) data.frame containing "time", "lower" and "upper" columns used for +#' the x axis. +#' @param xLabelLim numeric vector of length 2: range of labels of x axis +extendXAxis <- function(xAxisData, xLabelLim) { + if (min(xLabelLim) < min(xAxisData[["time_lower"]])) { + # add new row at the beginning + newFirstRow <- data.frame( + "time" = mean(c(min(xLabelLim), min(xAxisData[["time_lower"]]))), + "time_lower" = min(xLabelLim), + "time_upper" = min(xAxisData[["time_lower"]]) + ) + + xAxisData <- rbind(newFirstRow, + xAxisData) + } + + if (max(xLabelLim) > max(xAxisData[["time_upper"]])) { + # add new row at the end + newLastRow <- data.frame( + "time" = mean(c(max(xAxisData[["time_upper"]]), max(xLabelLim))), + "time_lower" = max(xAxisData[["time_upper"]]), + "time_upper" = max(xLabelLim) + ) + + xAxisData <- rbind(xAxisData, + newLastRow) + } + + xAxisData +} + +#' Get Breaks +#' +#' @param time (numeric) time points of x axis +#' @inheritParams plotTime +getBreaks <- function(time, deriv){ + breaks <- time + + if(deriv == "2"){ + breaks <- c(breaks, joinTimeForDerivation(breaks)) %>% sort() + } + + breaks +} + +#' Get Label +#' +#' @param xAxisData data.frame of time data of object, output of getXAxisData +#' @inheritParams plotTime +#' @param hidePastedLabels (logical) if TRUE then dont't show pasted labels +getLabel <- function(xAxisData, deriv, hidePastedLabels = TRUE){ + if(any(xAxisData$time_lower != xAxisData$time_upper)){ + labels <- c(paste0("[", as.character(xAxisData$time_lower),"-", as.character(xAxisData$time_upper), "]")) + } else { + labels <- unique(sort(as.numeric(c(xAxisData$time_lower, xAxisData$time, xAxisData$time_upper)))) + } + + if(deriv == "2"){ + labels <- pasteLabelForDerivation(labels, hidePastedLabels = hidePastedLabels) + } + + labels +} + +#' Paste Label for Derivation Plot +#' +#' Get the labels for the x axis of the plot in case of showing the derivation +#' +#' @param axesLabel (character) output of \code{\link{getLabel}} +#' @inheritParams getLabel +pasteLabelForDerivation <- function(axesLabel, hidePastedLabels){ + n <- length(axesLabel) + pastedLabels <- paste0(axesLabel[1:(n - 1)], ",", axesLabel[2:n]) + pastedLabels <- lapply(pastedLabels, function(x) gsub("\\],\\[", ",", x)) %>% unlist(use.names = FALSE) + + if (hidePastedLabels) { + pastedLabels <- rep("", length(pastedLabels)) + } + + # order both vectors with alternating indices + c(axesLabel, pastedLabels)[order(c(seq_along(axesLabel), seq_along(pastedLabels)))] +} diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R new file mode 100644 index 0000000..4bcf4d8 --- /dev/null +++ b/R/03-timePlotFormatting.R @@ -0,0 +1,329 @@ +# We need a separate namespace for formatting inputs for the model down- and upload. +# With grepl we can get the relevant inputs for formatting + +#' UI function of time plot formatting +#' +#' @param id module id +#' +#' @return actionButton +#' @export +timePlotFormattingUI <- function(id) { + ns <- NS(id) + tagList( + tags$h4("Time Plot"), + plotOutput(ns("plotTime")) %>% withSpinner(color = "#20c997"), + tags$br(), + fluidRow( + column(8, + selectizeInput(ns("plotTimeModels"), "Display Models / Individuals", + choices = c("Fit / import a model ..." = ""), + multiple = TRUE, + selected = "", + width = "100%")), + column(1, + align = "right", + style = "margin-top: 1.2em;", + actionButton(ns("applyFormatToTimePlot"), "Apply")), + column(2, + selectizeInput(ns("formatTimePlot"), "Format Model / Individual", + choices = c("Fit / import a model ..." = ""), + width = "100%")), + column(1, + align = "right", + style = "margin-top: 1.2em;", + actionButton(ns("applyFormatToTimePlotModel"), "Apply")) + ), + tags$br(), + fluidRow( + column(3, + tags$h4("Data"), + radioButtons(ns("deriv"), + "Type", + choices = c("Absolute values" = "1", "First derivate" = "2")), + sliderInput(ns("modCredInt"), + "Credibility interval:", + min = 0, + max = .99, + value = .8, + step = .05, + width = "100%"), + tags$br(), + sliderInput(ns("alphaU"), + "Transparency of uncertainty region", + min = 0, + max = 1, + value = 0.1, + step = 0.05), + sliderInput(ns("alphaL"), + "Transparency of points / lines", + min = 0, + max = 1, + value = 0.9, + step = 0.05), + tags$br(), + fluidRow( + column(6, selectInput(inputId = ns("legendPosition"), + label = "Legend position", + choices = c("right", "top", "bottom", "left"))), + column(6, + style = "margin-top: 1.5em;", + checkboxInput(inputId = ns("hideLegend"), + label = "Hide legend", + value = FALSE)) + ) + ), + column(3, + shinyTools::plotTitlesUI( + id = ns("plotLabels"), + title = "Text", + type = "ggplot", + initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]]) + ) + ), + column(3, + shinyTools::plotRangesUI( + id = ns("plotRanges"), + title = "Axis", + initRanges = list(xAxis = config()[["plotRange"]], + yAxis = config()[["plotRange"]]) + ), + checkboxInput(inputId = ns("extendLabels"), + label = "Extend x-axis labels to full range", + value = FALSE), + tags$br(), + selectizeInput(ns("secAxisModel"), "Add a new secondary y axis", + choices = c("Choose one Model / Individual ..." = "")), + helpText("The first element of 'Display Models / Individuals' is always used for the first (left) axis."), + conditionalPanel( + ns = ns, + condition = "input.secAxisModel != ''", + fluidRow( + column(6, textInput(ns("secAxisText"), label = "Title", + value = "", + placeholder = "Custom title ...")), + column(6, colourInput(ns("secAxisColor"), + label = "Title color", + value = config()[["defaultIntervalTimePlotTitle"]][["color"]])) + )), + + ), + column(3, + shinyTools::plotPointsUI(id = ns("pointStyle"), + title = "Points / Lines", + initStyle = config()[["defaultPointStyle"]]) + ) + ), + fluidRow(column(12, + style = "margin-top: -3em;", + align = "right", + plotExportButton(ns("exportCredIntTimePlot")))), + tags$hr(), + tags$h4("Plot Data"), + tableOutput(ns("plotData")), + tags$br(), + fluidRow(column(12, align = "right", dataExportButton(ns("exportCredIntTimeData")))), + tags$br() + ) +} + +#' Server function of time plot formatting +#' +#' Backend for plot formatting module +#' +#' @param id namespace id +#' @param savedModels list of models of class \code{\link{TemporalIso}} +#' +#' @export +timePlotFormattingServer <- function(id, savedModels) { + moduleServer(id, + function(input, output, session) { + ns <- session$ns + + formattedPlot <- reactiveVal() + + plotTexts <- shinyTools::plotTitlesServer( + "plotLabels", + type = "ggplot", + availableElements = c("title", "axis", "legend"), + initText = getDefaultTextFormat() + ) + plotRanges <- shinyTools::plotRangesServer( + "plotRanges", + type = "ggplot", + initRanges = list(xAxis = config()[["plotRange"]], + yAxis = config()[["plotRange"]]) + ) + + pointStyle <- shinyTools::plotPointsServer( + "pointStyle", + type = "ggplot", + initStyle = config()[["defaultPointStyle"]], + hideInput = c("hide", "alpha", "colorBg") + ) + + pointStyleList <- reactiveValues() + + observe({ + req(length(savedModels()) > 0) + modelNames <- names(savedModels()) + + pointStyleList <- pointStyleList %>% + getDefaultPointFormatForModels(modelNames = modelNames) + + updateSelectizeInput(session, "plotTimeModels", + choices = modelNames, + selected = modelNames[length(modelNames)]) + }) %>% + bindEvent(savedModels()) + + observe({ + # choices for formatting of lines and points + updateSelectizeInput(session, "formatTimePlot", + choices = input[["plotTimeModels"]]) + + # choices for secondary axis + nDisplayedModels <- length(input[["plotTimeModels"]]) + if (nDisplayedModels > 1) { + # remove first element that always gives the first axis + secAxisChoices <- input[["plotTimeModels"]][2:nDisplayedModels] + updateSelectizeInput(session, "secAxisModel", + choices = c("Choose one Model / Individual ..." = "", + secAxisChoices)) + } else { + updateSelectizeInput(session, "secAxisModel", + choices = c("Choose one Model / Individual ..." = "")) + } + }) %>% + bindEvent(input[["plotTimeModels"]]) + + observe({ + req(input[["formatTimePlot"]]) + # observe point style + pointStyleList[[input[["formatTimePlot"]]]] <- pointStyle[["dataPoints"]] + }) %>% + bindEvent(input[["applyFormatToTimePlotModel"]]) + + allFits <- reactive({ + getEntry(savedModels(), "fit") + }) + + extractedPlotDataList <- reactive({ + # extract plot data from model object + lapply(allFits(), function(x) { + getPlotData(object = x, prop = input$modCredInt, deriv = input$deriv) %>% + updateTime(object = x, deriv = input$deriv) + }) + }) + + extractedPlotDataDF <- reactive({ + extractPlotDataDF(plotDataList = extractedPlotDataList(), + models = input[["plotTimeModels"]], + credInt = input$modCredInt) + }) + + output$plotData <- renderTable({ + validate(need(input[["plotTimeModels"]], + "Choose at least one element from 'Display Models / Individuals' ...")) + extractedPlotDataDF() + }) + + plotDataExport <- reactiveVal() + + observe({ + plotDataExport(extractedPlotDataDF()) + }) %>% + bindEvent(input[["plotTimeModels"]]) + + dataExportServer("exportCredIntTimeData", + reactive(function() {plotDataExport()})) + + # set default: no rescaling + rescalingSecAxis <- reactiveVal(list(scale = 1, center = 0)) + observe({ + req(input[["plotTimeModels"]]) + + plotData <- extractedPlotDataDF() %>% + na.omit() + # get index for filter + index <- plotData$individual == input[["secAxisModel"]] + + # get rescaling parameters + req(nrow(plotData[index, ]) > 0) + + # update title of second axis + updateTextInput(session, "secAxisText", + value = sprintf("%s Estimate", input[["secAxisModel"]])) + + ## use always data based newYLimits, we only set global limits not(!) per model + rescaling <- getRescaleParams(oldLimits = getYRange(plotData) %>% unlist(), + newLimits = getYRange(plotData[index, ]) %>% unlist(), + secAxis = TRUE) + rescalingSecAxis(rescaling) + + + }) %>% + bindEvent(input[["secAxisModel"]]) + + observe({ + req(savedModels(), input[["plotTimeModels"]]) + p <- extractedPlotDataDF() %>% + na.omit() %>% + rescaleSecondAxisData(individual = input[["secAxisModel"]], + rescaling = rescalingSecAxis()) %>% + basePlotTime(xLim = getLim(plotRanges = plotRanges, axis = "xAxis"), + yLim = getLim(plotRanges = plotRanges, axis = "yAxis")) %>% + setDefaultTitles(prop = input$modCredInt) %>% + shinyTools::formatTitlesOfGGplot(text = plotTexts) %>% + shinyTools::formatRangesOfGGplot(ranges = plotRanges) %>% + setXAxisLabels( + xAxisData = extractedPlotDataList() %>% + extractAllXAxisData(), # labels for all x axis data + extendLabels = input$extendLabels, + xLim = getLim(plotRanges = plotRanges, axis = "xAxis"), + deriv = "1" # input$deriv already included within extractedPlotDataList() + ) %>% + drawLinesAndRibbon( + pointStyleList = pointStyleList, + alphaL = input[["alphaL"]], + alphaU = input[["alphaU"]], + legendName = plotTexts[["legendTitle"]][["text"]]) %>% + setSecondYAxis(rescaling = rescalingSecAxis(), + titleFormat = plotTexts[["yAxisTitle"]], + textFormat = plotTexts[["yAxisText"]], + yAxisLabel = input[["secAxisText"]], + yAxisTitleColor = input[["secAxisColor"]]) %>% + setLegendPosition(hideLegend = input[["hideLegend"]], + legendPosition = input[["legendPosition"]]) + + formattedPlot(p) + }) %>% + bindEvent(list(input[["applyFormatToTimePlot"]], + input[["applyFormatToTimePlotModel"]])) + + output$plotTime <- renderPlot({ + validate(need(formattedPlot(), "Choose at least one element from 'Display Models / Individuals' and press 'Apply' ...")) + formattedPlot() + }) + + plotExportServer("exportCredIntTimePlot", + plotFun = reactive(function() formattedPlot()), + filename = sprintf("%s_Credibility_Intervals_Over_Time", + gsub("-", "", Sys.Date())) + ) + + return(reactive(formattedPlot())) + }) +} + +#' Add NA Row +#' +#' Function to add a row with NA values at the end of a data.frame +#' +#' @param df (data.frame) data.frame +add_na_row <- function(df) { + na_row <- matrix(rep(NA, ncol(df)), + nrow = 1, + dimnames = list("", colnames(df))) %>% + as.data.frame() + bind_rows(df, na_row) +} diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 6d47c0a..2a59a78 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -14,9 +14,12 @@ #' @importFrom colourpicker colourInput #' @importFrom DataTools checkAnyNonNumericColumns downloadModelUI downloadModelServer #' importDataUI importDataServer importOptions renameExistingNames tryCatchWithWarningsAndErrors -#' @importFrom dplyr arrange bind_rows distinct slice -#' @importFrom ggplot2 aes element_line element_text ggplot geom_line geom_point geom_ribbon -#' labs scale_x_continuous theme ggtitle scale_y_continuous geom_vline coord_cartesian sec_axis +#' @importFrom dplyr arrange bind_cols bind_rows cur_group_id distinct do group_by mutate n select +#' slice ungroup +#' @importFrom ggplot2 aes coord_cartesian element_line element_text +#' geom_line geom_point geom_ribbon geom_vline ggplot ggplot_build ggtitle labs +#' scale_colour_manual scale_fill_manual scale_shape_manual scale_size_manual +#' scale_x_continuous scale_y_continuous sec_axis theme #' @importFrom htmltools save_html #' @importFrom jsonlite toJSON #' @importFrom magrittr %>% @@ -24,10 +27,13 @@ #' @importFrom parallel detectCores #' @importFrom rlang .data #' @importFrom rstan sampling extract +#' @importFrom shinycssloaders withSpinner #' @importFrom shinyjs alert -#' @importFrom shinyTools plotPointsUI plotPointsServer formatPointsOfGGplot +#' @importFrom shinyTools dataExportButton dataExportServer formatPointsOfGGplot +#' formatRangesOfGGplot formatTitlesOfGGplot plotExportButton plotExportServer +#' plotPointsServer plotPointsUI plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI #' @importFrom shinyWidgets pickerInput updatePickerInput -#' @importFrom stats approx dnorm lm median quantile sd +#' @importFrom stats approx dnorm lm median na.omit quantile sd #' @importFrom utils write.csv write.table combn #' @importFrom yaml read_yaml #' @references diff --git a/R/estimateIntervals.R b/R/estimateIntervals.R index 98f50c6..f69d45d 100644 --- a/R/estimateIntervals.R +++ b/R/estimateIntervals.R @@ -73,7 +73,7 @@ #' ############################### #' plotTime(fit, plotShifts = TRUE, threshold = 0.5) #' plotTime(fit, plotShifts = TRUE, threshold = 0.5, -#' absolute = TRUE, probability = 0.5) +#' type = TRUE, probability = 0.5) #' #' getShiftTime(fit, threshold = 0.5) #' diff --git a/R/plots.R b/R/plots.R index 1e5ed10..a181f10 100644 --- a/R/plots.R +++ b/R/plots.R @@ -8,13 +8,10 @@ #' @param plotShifts boolean if shifts shall be marked or not. Defaults to False. #' @param yLim numeric vector of length 2: range of y axis #' @param xLim numeric vector of length 2: range of x axis -#' @param oldPlot ggplot object -#' @param oldXAxisData data.frame of time data from object #' @param deriv character "1" for absolute values, "2" for first differences -#' @param colorL color of line (RGB) -#' @param colorU color of uncertainty region (RGB) -#' @param alphaL Color line -#' @param alphaU Color uncertainty region +#' @param color color of line and uncertainty region (RGB) +#' @param alphaL opacity line +#' @param alphaU opacity uncertainty region #' @param sizeTextY size plot y label #' @param xAxisLabel character label x-axis #' @param yAxisLabel character label y-axis @@ -32,87 +29,156 @@ #' @export plotTime <- function(object, prop = 0.8, plotShifts = FALSE, yLim = c(0,1), xLim = c(0,1), - oldPlot = NULL, oldXAxisData = data.frame(), deriv = "1", colorL = NULL, - colorU = NULL, alphaL = 0.9, alphaU = 0.1, + deriv = "1", + color = NULL, alphaL = 0.9, alphaU = 0.1, sizeTextY = 12, sizeTextX = 12, sizeAxisX = 12, sizeAxisY = 12, secAxis = FALSE, xAxisLabel = "Time", yAxisLabel = "Estimate", extendLabels = FALSE, pointStyle = config()[["defaultPointStyle"]], ...){ stopifnot(prop < 1) - x <- getPlotData(object, prop = prop, deriv = deriv) - x$time <- adjustTimeColumn(objectTime = object@time, deriv = deriv) - - if (nrow(x) > 1) lineFct <- geom_line else lineFct <- geom_point - if(is.null(oldPlot)){ - p <- ggplot(x, aes(x = .data[["time"]])) + - lineFct(aes(y = .data[["median"]]), colour = colorL, alpha = alphaL) + - lineFct(aes(y = .data[["lower"]]), size = 0.05, colour = colorL, alpha = alphaL) + - lineFct(aes(y = .data[["upper"]]), size = 0.05, colour = colorL, alpha = alphaL) + - coord_cartesian(ylim = yLim, xlim = xLim) + - theme(panel.grid.major.x = element_line(size = 0.1)) + - theme(axis.title.x = element_text(size = sizeTextX), - axis.title.y = element_text(size = sizeTextY), - axis.text.x = element_text(size = sizeAxisX), - axis.text.y = element_text(size = sizeAxisY)) - p <- p %>% - formatPointsOfGGplot(data = x, aes(x = .data[["time"]], y = .data[["median"]]), pointStyle = pointStyle) - if (nrow(x) > 1) p <- p + geom_ribbon(aes(ymin = .data[["lower"]], ymax = .data[["upper"]]), - linetype = 2, alpha = alphaU, fill = colorU) + x <- getPlotData(object, prop = prop, deriv = deriv) %>% + updateTime(object = object, deriv = deriv) + + x$individual <- "current" + pointStyleList <- list() + pointStyleList[["current"]] <- config()[["defaultPointStyle"]][["dataPoints"]] + pointStyleList[["current"]]["color"] <- color + + p <- basePlotTime(df = x, + xLim = xLim, yLim = yLim, + sizeTextX = sizeTextX, sizeTextY = sizeTextY, + sizeAxisX = sizeAxisX, sizeAxisY = sizeAxisY) %>% + setDefaultTitles(prop, xAxisLabel, yAxisLabel) + + p <- p %>% + drawLinesAndRibbon(pointStyleList = pointStyleList, alphaL = alphaL, alphaU = alphaU) %>% + setXAxisLabels(xAxisData = getXAxisData(object = object), + extendLabels = extendLabels, + xLim = xLim, + deriv = deriv) %>% + drawShiftLines(object = object, + deriv = deriv, + plotShifts = plotShifts, + ...) + + p + theme(legend.position = "none") +} + +basePlotTime <- function(df, + xLim = NULL, yLim = NULL, + sizeTextX = 12, sizeTextY = 12, + sizeAxisX = 12, sizeAxisY = 12) { + p <- ggplot(df, aes(x = .data[["time"]])) + + theme(panel.grid.major.x = element_line(size = 0.1)) + # no error only in most recent version: element_line(linewidth = 0.1) + theme(axis.title.x = element_text(size = sizeTextX), + axis.text.x = element_text(size = sizeAxisX), + axis.title.y = element_text(size = sizeTextY), + axis.text.y = element_text(size = sizeAxisY)) + + p <- p %>% + setPlotLimits(xLim = xLim, yLim = yLim) + + p +} + + + +drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName = "individual") { + # default legend name + if (legendName == "") legendName <- "individual" + + # draw lines "upper", "mdeian", "lower" + if (nrow(plot$data) > 1) { + plot <- plot + + geom_line(aes(y = .data[["median"]], colour = .data[["individual"]]), + alpha = alphaL) + + geom_line(aes(y = .data[["lower"]], colour = .data[["individual"]]), + linewidth = 0.05, alpha = alphaL) + + geom_line(aes(y = .data[["upper"]], colour = .data[["individual"]]), + linewidth = 0.05, alpha = alphaL) } else { - if(secAxis){ - oldCoord <- oldPlot$coordinates$limits$y - b <- seq(min(yLim),max(yLim), length.out = 100) - a <- seq(min(oldCoord),max(oldCoord), length.out = 100) - res <- lm(b~a) - - scale <- res$coefficients[2] - center <- res$coefficients[1] - x$median <- (x$median - center ) / scale - x$lower <- (x$lower - center ) / scale - x$upper <- (x$upper - center ) / scale - } - p <- oldPlot + geom_line(data = x, aes(y = .data[["median"]]), colour = colorL, alpha = alphaL) + - geom_line(data = x, aes(y = .data[["lower"]]), size = 0.05, colour = colorL, alpha = alphaL) + - geom_line(data = x, aes(y = .data[["upper"]]), size = 0.05, colour = colorL, alpha = alphaL) + - geom_ribbon(data = x, aes(ymin = .data[["lower"]], ymax = .data[["upper"]]), linetype = 2, alpha = alphaU, fill = colorU) - p <- p %>% - formatPointsOfGGplot(data = x, aes(x = .data[["time"]], y = .data[["median"]]), pointStyle = pointStyle) - - if(secAxis){ - p <- p + - theme(axis.title.y = element_text(size = sizeTextY), - axis.text.y = element_text(size = sizeAxisY)) + scale_y_continuous( - # Features of the first axis - # Add a second axis and specify its features - sec.axis = sec_axis(~(.* scale) + center, name=yAxisLabel) - ) - } + plot <- plot + + geom_point(aes(y = .data[["median"]], colour = .data[["individual"]]), + alpha = alphaL) + + geom_point(aes(y = .data[["lower"]], colour = .data[["individual"]]), + size = 0.05, alpha = alphaL) + + geom_point(aes(y = .data[["upper"]], colour = .data[["individual"]]), + size = 0.05, alpha = alphaL) + } + + # draw ribbon + if (nrow(plot$data) > 1) { + plot <- plot + + geom_ribbon(aes(ymin = .data[["lower"]], ymax = .data[["upper"]], + fill = .data[["individual"]]), + linetype = 2, alpha = alphaU) } - xAxisData <- getXAxisData(object = object, oldXAxisData = oldXAxisData) + # draw points "median" + plot <- plot + + geom_point(aes(y = .data[["median"]], + colour = .data[["individual"]], + shape = .data[["individual"]], + size = .data[["individual"]], + fill = .data[["individual"]]), + alpha = alphaL) - xLabelLim <- range(xAxisData) - if (extendLabels) xLabelLim <- xLim - xAxisData <- xAxisData %>% - extendXAxis(xLabelLim = xLabelLim) + # set scales for each "individual" + lineColors <- getStyleForIndividuals(pointStyleList, input = "color") + fillColors <- getStyleForIndividuals(pointStyleList, input = "color") + pointShapes <- getStyleForIndividuals(pointStyleList, input = "symbol") + pointSize <- getStyleForIndividuals(pointStyleList, input = "size") - breaks <- getBreaks(time = xAxisData$time, deriv = deriv) - labels <- getLabel(xAxisData = xAxisData, deriv = deriv) + plot + + scale_colour_manual(name = legendName, values = lineColors) + # former colorL + scale_fill_manual(name = legendName, values = fillColors)+ # former colorU + scale_shape_manual(name = legendName, values = pointShapes) + + scale_size_manual(name = legendName, values = pointSize) +} + +setDefaultTitles <- function(plot, prop, xAxisLabel = "Time", yAxisLabel = "Estimate") { + stopifnot(prop < 1) - p <- p + - scale_x_continuous(breaks = breaks, labels = labels) + + plot + labs(title = paste0(prop * 100, "%-Credibility-Interval for isotopic values over time"), x = xAxisLabel, y = yAxisLabel) +} +drawShiftLines <- function(plot, object, deriv, plotShifts, ...) { + # breaks within data range + breaks <- getBreaks(time = object@time, deriv = deriv) + if (plotShifts){ index <- getShiftIndex(object, ...) - p <- p + geom_vline(xintercept = breaks[which(index)] + 0.5, col = "darkgrey") + plot <- plot + geom_vline(xintercept = breaks[which(index)] + 0.5, col = "darkgrey") } - p + plot } +extractPlotDataDF <- function(plotDataList, models, credInt) { + # filter for displayed models: + plotDataList[models] %>% + bind_rows(.id = "individual") %>% + # add column with + mutate(cred_interval = sprintf("%.0f%%", credInt * 100)) %>% + group_by(.data$time) %>% + # add id for each x value: + mutate(id_time = cur_group_id()) %>% + ungroup() %>% + group_by(.data$individual) %>% + # add id for each individual: + mutate(id_model = cur_group_id()) %>% + # add an empty line after each individual (containing only NA values): + do(add_na_row(.)) %>% + ungroup() %>% + # select order of columns for display and export data + select("id_model", "individual", "id_time", "time_lower", "time_upper", "time", + "cred_interval", "lower", "median", "upper", "sd") %>% + # remove last line containing only NA values + slice(1:(n() - 1)) +} #' Get Plot Data #' @@ -133,17 +199,38 @@ getPlotData <- function(object, prop = 0.8, time = NULL, deriv = "1"){ } } - out <- as.data.frame( + # extract quantiles + intervalQuantiles <- as.data.frame( t(apply(dat, 2, quantile, probs = c(lLim, 0.5, uLim))) ) - names(out) <- c("lower", "median", "upper") + names(intervalQuantiles) <- c("lower", "median", "upper") + + # extract sd + intervalSD <- data.frame( + sd = apply(dat, 2, sd) + ) + # combine data + out <- bind_cols(intervalQuantiles, intervalSD) + + # add time column if (is.null(time)) time <- 1:ncol(dat) out$time <- time return(out) } +#' Extract Plot Data +#' +#' @param plotData (data.frame) plot data +#' @inheritParams plotTime +updateTime <- function(plotData, object, deriv = "1") { + plotData$time <- adjustTimeColumn(objectTime = object@time, deriv = deriv) + plotData$time_lower <- adjustTimeColumn(objectTime = object@timeLower, deriv = deriv) + plotData$time_upper <- adjustTimeColumn(objectTime = object@timeUpper, deriv = deriv) + + plotData +} #' Adjust Time Column #' @@ -159,6 +246,16 @@ adjustTimeColumn <- function(objectTime, deriv){ res } +#' Get Breaks for Derivation +#' +#' Get the time for the x axis of the plot in case of showing the derivation +#' +#' @param time (numeric) time points of x axis +joinTimeForDerivation <- function(time){ + as.numeric(time)[1:(length(time) - 1)] + + diff(as.numeric(time)) / 2 +} + #' Get X-Axis Data #' #' @inheritParams plotTime @@ -169,8 +266,8 @@ getXAxisData <- function(object, oldXAxisData = data.frame()){ if (is.null(object)) return(data.frame()) xAxisData <- data.frame(time = object@time, - lower = object@timeLower, - upper = object@timeUpper) + time_lower = object@timeLower, + time_upper = object@timeUpper) if (nrow(oldXAxisData) > 0) { xAxisData <- bind_rows(xAxisData, oldXAxisData) %>% @@ -181,140 +278,43 @@ getXAxisData <- function(object, oldXAxisData = data.frame()){ xAxisData } -#' Extend X Axis -#' -#' Add breaks and labels for x axis -#' -#' @param xAxisData (data.frame) data.frame containing "time", "lower" and "upper" columns used for -#' the x axis. -#' @param xLabelLim numeric vector of length 2: range of labels of x axis -extendXAxis <- function(xAxisData, xLabelLim) { - if (min(xLabelLim) < min(xAxisData[["lower"]])) { - # add new row at the beginning - newFirstRow <- data.frame( - "time" = mean(c(min(xLabelLim), min(xAxisData[["lower"]]))), - "lower" = min(xLabelLim), - "upper" = min(xAxisData[["lower"]]) - ) - - xAxisData <- rbind(newFirstRow, - xAxisData) - } +setPlotLimits <- function(plot, newData = NULL, xLim = NULL, yLim = NULL) { + allData <- plot$data + if(!is.null(newData)) allData <- bind_rows(plot$data, newData) %>% distinct() - if (max(xLabelLim) > max(xAxisData[["upper"]])) { - # add new row at the end - newLastRow <- data.frame( - "time" = mean(c(max(xAxisData[["upper"]]), max(xLabelLim))), - "lower" = max(xAxisData[["upper"]]), - "upper" = max(xLabelLim) - ) - - xAxisData <- rbind(xAxisData, - newLastRow) - } + if (length(xLim) == 0) xLim <- getXRange(allData) %>% unlist() + if (length(yLim) == 0) yLim <- getYRange(allData) %>% unlist() - xAxisData + plot + coord_cartesian(ylim = yLim, xlim = xLim) } -#' Get Label -#' -#' @param xAxisData data.frame of time data of object, output of getXAxisData -#' @inheritParams plotTime -#' @param hidePastedLabels (logical) if TRUE then dont't show pasted labels -getLabel <- function(xAxisData, deriv, hidePastedLabels = TRUE){ - if(any(xAxisData$lower != xAxisData$upper)){ - labels <- c(paste0("[", as.character(xAxisData$lower),"-", as.character(xAxisData$upper), "]")) - } else { - labels <- unique(sort(as.numeric(c(xAxisData$lower, xAxisData$time, xAxisData$upper)))) - } - - if(deriv == "2"){ - labels <- pasteLabelForDerivation(labels, hidePastedLabels = hidePastedLabels) - } +getLim <- function(plotRanges, axis = c("xAxis", "yAxis")) { + axis <- match.arg(axis) - labels -} - - -#' Paste Label for Derivation Plot -#' -#' Get the labels for the x axis of the plot in case of showing the derivation -#' -#' @param axesLabel (character) output of \code{\link{getLabel}} -#' @inheritParams getLabel -pasteLabelForDerivation <- function(axesLabel, hidePastedLabels){ - n <- length(axesLabel) - pastedLabels <- paste0(axesLabel[1:(n - 1)], ",", axesLabel[2:n]) - pastedLabels <- lapply(pastedLabels, function(x) gsub("\\],\\[", ",", x)) %>% unlist(use.names = FALSE) - - if (hidePastedLabels) { - pastedLabels <- rep("", length(pastedLabels)) - } + if (plotRanges[[axis]][["fromData"]]) return(numeric(0)) - # order both vectors with alternating indices - c(axesLabel, pastedLabels)[order(c(seq_along(axesLabel), seq_along(pastedLabels)))] + c(plotRanges[[axis]][["min"]], plotRanges[[axis]][["max"]]) } - -#' Get Breaks -#' -#' @param time (numeric) time points of x axis -#' @inheritParams plotTime -getBreaks <- function(time, deriv){ - breaks <- time +getXRange <- function(dat) { + if (nrow(dat) == 0) return(list(xmin = defaultInputsForUI()$xmin, + xmax = defaultInputsForUI()$xmax)) - if(deriv == "2"){ - breaks <- c(breaks, joinTimeForDerivation(breaks)) %>% sort() - } + xmin <- min(dat$time, na.rm = TRUE) + xmax <- max(dat$time, na.rm = TRUE) - breaks -} - - -#' Get Breaks for Derivation -#' -#' Get the time for the x axis of the plot in case of showing the derivation -#' -#' @param time (numeric) time points of x axis -joinTimeForDerivation <- function(time){ - as.numeric(time)[1:(length(time) - 1)] + - diff(as.numeric(time)) / 2 + list(xmin = xmin, + xmax = xmax) } -#' Get Default Plot Range -#' -#' Min/max values in x/y directions -#' -#' @param savedModels list of models of class \code{\link{TemporalIso}} -#' @inheritParams plotTime -#' -#' @export -getDefaultPlotRange <- function(savedModels, deriv = "1"){ - - dat <- lapply(savedModels, function(model){ - if (is.null(model$fit)) return(NULL) - fit <- model$fit - x <- getPlotData(fit, prop = 0.8, deriv = deriv) - x$time <- adjustTimeColumn(objectTime = fit@time, deriv = deriv) - x - }) - - dat <- dat %>% bind_rows() - - if (nrow(dat) == 0) return(list(xmin = defaultInputsForUI()$xmin, - xmax = defaultInputsForUI()$xmax, - ymin = defaultInputsForUI()$ymin, +getYRange <- function(dat) { + if (nrow(dat) == 0) return(list(ymin = defaultInputsForUI()$ymin, ymax = defaultInputsForUI()$ymax)) - xmin <- min(dat$time, na.rm = TRUE) - xmax <- max(dat$time, na.rm = TRUE) - ymin <- min(dat$lower, na.rm = TRUE) ymax <- max(dat$upper, na.rm = TRUE) rangeY <- ymax - ymin - list(xmin = xmin, - xmax = xmax, - ymin = ymin - 0.1*rangeY, + list(ymin = ymin - 0.1*rangeY, ymax = ymax + 0.1*rangeY) } diff --git a/RScripts/example.R b/RScripts/example.R index 333d34d..c3086ea 100644 --- a/RScripts/example.R +++ b/RScripts/example.R @@ -34,7 +34,7 @@ estimateTimePoint(fit, time = seq(0,5, by = 0.5)) ############################### plotTime(fit, plotShifts = TRUE, threshold = 0.5) plotTime(fit, plotShifts = TRUE, threshold = 0.5, - absolute = TRUE, probability = 0.5) + type = TRUE, probability = 0.5) getShiftTime(fit, threshold = 0.5) diff --git a/inst/app/server.R b/inst/app/server.R index dde8e6a..64f4a53 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -6,6 +6,7 @@ library(shinyMatrix) library(dplyr) library(ggplot2) library(rstan) +library(stats) options(shiny.maxRequestSize = 200*1024^2 # Set mc.cores option @@ -284,40 +285,18 @@ shinyServer(function(input, output, session) { output$fittingTimeTxt <- renderUI(HTML(modelFittingTimeTxt())) - allXAxisData <- reactiveVal(data.frame()) - observeEvent(savedModels(), { req(length(savedModels()) > 0) modelChoices <- names(savedModels()) + selectedModel <- names(savedModels())[length(savedModels())] updateSelectInput(session, "savedModels", choices = modelChoices, selected = selectedModel) - fit(savedModels()[[length(savedModels())]]$fit) - - # inputs in tab "Credibility intervals over time" - updateSelectizeInput(session, "credIntTimePlot", - choices = modelChoices, selected = selectedModel) - - updateNumericInput(session, "xmin", - value = getDefaultPlotRange(savedModels(), deriv = "1")$xmin) - updateNumericInput(session, "xmax", - value = getDefaultPlotRange(savedModels(), deriv = "1")$xmax) - updateNumericInput(session, "ymin", - value = getDefaultPlotRange(savedModels(), deriv = "1")$ymin) - updateNumericInput(session, "ymax", - value = getDefaultPlotRange(savedModels(), deriv = "1")$ymax) + fit(savedModels()[[selectedModel]]$fit) - # to draw x axis ticks and labels at all possible points in time present in savedModls() - for (i in 1:length(savedModels())) { - allXAxisData(getXAxisData(savedModels()[[i]]$fit, oldXAxisData = allXAxisData())) - } - - # other tabs - updatePickerInput(session, "savedModelsShift", - choices = modelChoices, selected = selectedModel) - updatePickerInput(session, "savedModelsTime", - choices = modelChoices, selected = selectedModel) + updatePickerInput(session, "savedModelsShift", choices = modelChoices, selected = selectedModel) + updatePickerInput(session, "savedModelsTime", choices = modelChoices, selected = selectedModel) updatePickerInput(session, "savedModelsUserDefined", choices = modelChoices, selected = selectedModel) }) @@ -406,6 +385,8 @@ shinyServer(function(input, output, session) { } else { fitToSave <- fit() } + # plot format cannot be saved, this would require refactoring of select inputs to save/load a + # single model, or download a list of models, selecting models for the timePlot newModel <- setNames(list( list(modelSpecifications = reactiveValuesToList(modelSpecInputs()), inputDataMatrix = input$dataMatrix, @@ -428,7 +409,8 @@ shinyServer(function(input, output, session) { uploadedModelSpecInputs(currentModel$modelSpecifications) fit(currentModel$fit) - updateSelectizeInput(session, "credIntTimePlot", selected = input$savedModels) + updateSelectizeInput(session, "timePlotFormat-plotTimeModels", selected = input$savedModels) + updateSelectizeInput(session, "timePlotFormat-formatTimePlot", selected = input$savedModels) updatePickerInput(session, "savedModelsShift", selected = input$savedModels) updatePickerInput(session, "savedModelsTime", selected = input$savedModels) updatePickerInput(session, "savedModelsUserDefined", selected = input$savedModels) @@ -459,7 +441,6 @@ shinyServer(function(input, output, session) { ckanFileTypes = config()[["ckanModelTypes"]], ignoreWarnings = TRUE, defaultSource = config()[["defaultSourceModel"]], - mainFolder = config()[["mainFolder"]], fileExtension = config()[["fileExtension"]], options = DataTools::importOptions(rPackageName = config()[["rPackageName"]])) @@ -545,125 +526,15 @@ shinyServer(function(input, output, session) { } ) - observeEvent(input$deriv,{ - req(input$savedModels, input$deriv) - req((savedModels()[[input$savedModels]])$fit) - - updateNumericInput(session, "xmin", value = getDefaultPlotRange(savedModels(), deriv = input$deriv)$xmin) - updateNumericInput(session, "xmax", value = getDefaultPlotRange(savedModels(), deriv = input$deriv)$xmax) - updateNumericInput(session, "ymin", value = getDefaultPlotRange(savedModels(), deriv = input$deriv)$ymin) - updateNumericInput(session, "ymax", value = getDefaultPlotRange(savedModels(), deriv = input$deriv)$ymax) - }) - - # observeEvent(input$ymin, - # { - # ymin <- input$ymin - # if(!exists("ymax") || ymax == c()){ - # ymax <- input$ymax - # } - # yLim <- c(ymin, ymax) - # }) - # observeEvent(input$ymax, - # { - # if(!exists("ymin") || ymin == c()){ - # ymin <- input$ymin - # } - # ymax <- input$ymax - # yLim <- c(ymin, ymax) - # }) - output$plot <- renderPlot({ req(fit()) #OsteoBioR::plot(fit(), prop = input$modCredInt) plot(fit(), prop = input$modCredInt) }) - fitForTimePlot <- reactiveVal() - savedPlot <- reactiveVal(list()) - #savedXAxisData <- reactiveVal(data.frame()) - - pointStyle <- shinyTools::plotPointsServer("pointStyle", type = "ggplot", initStyle = config()[["defaultPointStyle"]]) - - observeEvent(input$credIntTimePlot, { - req(savedModels(), input$credIntTimePlot) - fits <- getEntry(savedModels()[input$credIntTimePlot], "fit") - req(length(fits) > 0) - fitForTimePlot(fits[[length(fits)]]) - - # show default plot if plot is empty but data received - req(is.null(intervalTimePlot()), fitForTimePlot()) - p <- plotTime(fitForTimePlot(), prop = input$modCredInt, yLim = c(input$ymin, input$ymax), - xLim = c(input$xmin, input$xmax), deriv = input$deriv, - oldXAxisData = allXAxisData(), # draws ticks at all data's times of x axis - colorL = input$colorL, colorU = input$colorU, alphaL = input$alphaL, alphaU = input$alphaU, - xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, - sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, - sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, - extendLabels = input$extendLabels, - pointStyle = pointStyle) - intervalTimePlot(p) - savedPlot(p) - #savedXAxisData(getXAxisData(fitForTimePlot())) - }) - - observeEvent(fit(), { - # not as default plot, when fit() is ready, xLim & yLim are not - req(fit(), intervalTimePlot()) - - p <- plotTime(fit(), prop = input$modCredInt, yLim = c(input$ymin, input$ymax), - xLim = c(input$xmin, input$xmax), deriv = input$deriv, - oldXAxisData = allXAxisData(), # draws ticks at all data's times of x axis - colorL = input$colorL, colorU = input$colorU, alphaL = input$alphaL, alphaU = input$alphaU, - xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, - sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, - sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, - extendLabels = input$extendLabels, - pointStyle = pointStyle) - intervalTimePlot(p) - savedPlot(p) - #savedXAxisData(getXAxisData(fitForTimePlot())) - }) - - observeEvent(input$newPlot, { - req(fitForTimePlot()) - p <- plotTime(fitForTimePlot(), prop = input$modCredInt, yLim = c(input$ymin, input$ymax), - xLim = c(input$xmin, input$xmax), deriv = input$deriv, - oldXAxisData = allXAxisData(), # draws ticks at all data's times of x axis - colorL = input$colorL, colorU = input$colorU, alphaL = input$alphaL, alphaU = input$alphaU, - xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, - sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, - sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, - extendLabels = input$extendLabels, - pointStyle = pointStyle) - intervalTimePlot(p) - savedPlot(p) - #savedXAxisData(getXAxisData(fitForTimePlot())) - }) - - observeEvent(input$addPlot, { - req(fitForTimePlot(), length(savedPlot()) > 0) - oldPlot <- savedPlot() - #oldXAxisData <- savedXAxisData() - p <- plotTime(fitForTimePlot(), prop = input$modCredInt, yLim = c(input$ymin, input$ymax), - xLim = c(input$xmin, input$xmax), deriv = input$deriv, oldPlot = oldPlot, - #oldXAxisData = oldXAxisData, - oldXAxisData = allXAxisData(), # draws ticks at all data's times of x axis - colorL = input$colorL, colorU = input$colorU, - alphaL = input$alphaL, alphaU = input$alphaU, - sizeTextY = input$sizeTextY , sizeTextX = input$sizeTextX, - xAxisLabel = input$xAxisLabel, yAxisLabel = input$yAxisLabel, - sizeAxisX = input$sizeAxisX, sizeAxisY = input$sizeAxisY, secAxis = input$secAxis, - extendLabels = input$extendLabels, - pointStyle = pointStyle) - intervalTimePlot(p) - savedPlot(p) - #savedXAxisData(getXAxisData(object = fitForTimePlot(), oldXAxisData = oldXAxisData)) - }) - - output$plotTime <- renderPlot({ - req(intervalTimePlot()) - intervalTimePlot() - }) + # create plotTime ---- + formattedTimePlot <- timePlotFormattingServer(id = "timePlotFormat", + savedModels = savedModels) observe({ updateNumericInput(session, "from", @@ -875,45 +746,6 @@ shinyServer(function(input, output, session) { ) }) - observeEvent(input$exportCredIntTimePlot, { - - plotOutputElement <- renderPlot({ savedPlot() }) - exportTypeChoices <- c("png", "pdf", "svg", "tiff") - - showModal(modalDialog( - title = "Export Graphic", - footer = modalButton("OK"), - plotOutputElement, - selectInput( - "exportType", "Filetype", - choices = exportTypeChoices - ), - numericInput("width", "Width (px)", value = 1280), - numericInput("height", "Height (px)", value = 800), - downloadButton("exportExecute", "Export"), - easyClose = TRUE - )) - - output$exportExecute <- downloadHandler( - filename = function(){ - paste0(gsub("-", "", Sys.Date()), "_", "Credibility_Intervals_Over_Time", ".", input$exportType) - }, - content = function(file){ - switch( - input$exportType, - png = png(file, width = input$width, height = input$height), - pdf = pdf(file, width = input$width / 72, height = input$height / 72), - tiff = tiff(file, width = input$width, height = input$height), - svg = svg(file, width = input$width / 72, height = input$height / 72) - ) - print( savedPlot() ) - - dev.off() - } - ) - }) - - # RESIDING TIME ------------------------------------------ datStayTime <- reactiveValues() diff --git a/inst/app/ui.R b/inst/app/ui.R index 0e7f5d9..fe642ae 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -135,6 +135,7 @@ tagList( selectInput("selectedModels", label = "Download model object(s)", choices = c("Save or upload models ..." = ""), multiple = T), + helpText("Model inputs and outputs are stored, any custom formatting of plots is not included."), DataTools::downloadModelUI("modelDownload", label = "Download") ), ## main panel ---- @@ -150,7 +151,8 @@ tagList( htmlOutput("fittingTimeTxt")), column(width = 3, style = "margin-top: -0.3em;", - selectInput("savedModels", label = "Load Model", choices = NULL)), + selectInput("savedModels", label = "Load Model", + choices = c("Fit or import a model ..." = ""))), column(width = 1, style = "margin-top: 1em;", actionButton("loadModel", "Load", width = "100%")), @@ -182,68 +184,7 @@ tagList( tabPanel( "Credibility intervals over time", value = "credibilityIntervalsOverTimeTab", - HTML("
"), - plotOutput("plotTime") %>% withSpinner(color = "#20c997"), - tags$br(), - tags$br(), - fluidRow( - column(2, - tags$h4("X-Axis"), - numericInput("xmin", "Lower x limit", - value = defaultInputsForUI()$xmin), - numericInput("xmax", "Upper x limit", - value = defaultInputsForUI()$xmax), - textInput("xAxisLabel", label = "X-Axis title", value = "Time"), - numericInput(inputId = "sizeTextX", label = "Font size x-axis title", value = 24), - numericInput(inputId = "sizeAxisX", label = "Font size x-axis", value = 18), - checkboxInput(inputId = "extendLabels", - label = "Extend x-axis labels to lower and upper limits", - value = FALSE) - ), - column(2, - tags$h4("Y-Axis"), - numericInput("ymin", "Lower y limit", - value = defaultInputsForUI()$ymin), - numericInput("ymax", "Upper y limit", - value = defaultInputsForUI()$ymax), - textInput("yAxisLabel", label = "Y-Axis title", value = "Estimate"), - numericInput(inputId = "sizeTextY", label = "Font size y-axis title", value = 24), - numericInput(inputId = "sizeAxisY", label = "Font size y-axis", value = 18) - ), - column(2, - tags$h4("Lines"), - colourInput(inputId = "colorL", - label = "Color line", - value = rgb(0, 35 / 255, 80 / 255, alpha = 0.6)), - sliderInput("alphaL", "Transparency lines", min = 0, max = 1, value = 0.9), - tags$br(), - tags$br(), - colourInput(inputId = "colorU", - label = "Color uncertainty region", - value = rgb(0, 35 / 255, 80 / 255, alpha = 0.6)), - sliderInput("alphaU", "Transparency uncertainty region", min = 0, max = 1, value = 0.1) - ), - column(2, - shinyTools::plotPointsUI(id = "pointStyle", initStyle = config()[["defaultPointStyle"]]) - ), - column(4, - tags$h4("Plot"), - checkboxInput("secAxis", "Add new secondary axis to existing plot", value = F), - radioButtons("deriv", "Type", choices = c("Absolute values" = "1", "First derivate" = "2")), - sliderInput("modCredInt", - "Credibility interval:", - min = 0, - max = .99, - value = .8, - step = .05), - tags$br(), - tags$br(), - selectizeInput("credIntTimePlot", "Select Models / Individuals", choices = NULL), - actionButton("newPlot", "New Plot"), - actionButton("addPlot", "Add Plot"), - actionButton("exportCredIntTimePlot", "Export Plot") - ) - ) + timePlotFormattingUI(id = "timePlotFormat") ), tabPanel( "Shift detection", diff --git a/inst/config.yaml b/inst/config.yaml index 4405bfa..20db365 100644 --- a/inst/config.yaml +++ b/inst/config.yaml @@ -14,7 +14,23 @@ githubRepo: "osteobior" # for import of models mainFolder: "predefinedModels" # for import of models fileExtension: "osteobior" # for download of models rPackageName: "OsteoBioR" # for download and import of models -# default point style + +# parameters for plot styles +# default format of graph and axis titles of intervalTimePlot for all models +defaultIntervalTimePlotTitle: + text: "" + fontType: "plain" + color: "#000000" + size: 24 + hide: false + +defaultIntervalTimePlotText: + fontType: "plain" + color: "#000000" + size: 18 + hide: false + +# default point style for each model defaultPointStyle: dataPoints: symbol: 19 @@ -24,3 +40,16 @@ defaultPointStyle: alpha: 1 lineWidthBg: 2 hide: false + +# default range of axis for all models +plotRange: + min: 0 + max: 1 + fromData: true + +# default line style for each model +defaultLineStyle: + colorL: "#002350" + colorU: "#002350" + alphaL: 0.9 + alphaU: 0.1 diff --git a/man/add_na_row.Rd b/man/add_na_row.Rd new file mode 100644 index 0000000..4e76622 --- /dev/null +++ b/man/add_na_row.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-timePlotFormatting.R +\name{add_na_row} +\alias{add_na_row} +\title{Add NA Row} +\usage{ +add_na_row(df) +} +\arguments{ +\item{df}{(data.frame) data.frame} +} +\description{ +Function to add a row with NA values at the end of a data.frame +} diff --git a/man/estimateIntervals.Rd b/man/estimateIntervals.Rd index 729f45e..bbe3e27 100644 --- a/man/estimateIntervals.Rd +++ b/man/estimateIntervals.Rd @@ -124,7 +124,7 @@ estimateTimePoint(fit, time = seq(0,5, by = 0.5)) ############################### plotTime(fit, plotShifts = TRUE, threshold = 0.5) plotTime(fit, plotShifts = TRUE, threshold = 0.5, - absolute = TRUE, probability = 0.5) + type = TRUE, probability = 0.5) getShiftTime(fit, threshold = 0.5) diff --git a/man/extendXAxis.Rd b/man/extendXAxis.Rd index 98fe339..86b0166 100644 --- a/man/extendXAxis.Rd +++ b/man/extendXAxis.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plots.R +% Please edit documentation in R/01-plotSetXAxisLabels.R \name{extendXAxis} \alias{extendXAxis} \title{Extend X Axis} diff --git a/man/getBreaks.Rd b/man/getBreaks.Rd index 9de82e7..97d1f3f 100644 --- a/man/getBreaks.Rd +++ b/man/getBreaks.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plots.R +% Please edit documentation in R/01-plotSetXAxisLabels.R \name{getBreaks} \alias{getBreaks} \title{Get Breaks} diff --git a/man/getDefaultPlotRange.Rd b/man/getDefaultPlotRange.Rd deleted file mode 100644 index b079632..0000000 --- a/man/getDefaultPlotRange.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plots.R -\name{getDefaultPlotRange} -\alias{getDefaultPlotRange} -\title{Get Default Plot Range} -\usage{ -getDefaultPlotRange(savedModels, deriv = "1") -} -\arguments{ -\item{savedModels}{list of models of class \code{\link{TemporalIso}}} - -\item{deriv}{character "1" for absolute values, "2" for first differences} -} -\description{ -Min/max values in x/y directions -} diff --git a/man/getLabel.Rd b/man/getLabel.Rd index fb9e688..1d5c08a 100644 --- a/man/getLabel.Rd +++ b/man/getLabel.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plots.R +% Please edit documentation in R/01-plotSetXAxisLabels.R \name{getLabel} \alias{getLabel} \title{Get Label} diff --git a/man/pasteLabelForDerivation.Rd b/man/pasteLabelForDerivation.Rd index b17eb21..ec1abb5 100644 --- a/man/pasteLabelForDerivation.Rd +++ b/man/pasteLabelForDerivation.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/plots.R +% Please edit documentation in R/01-plotSetXAxisLabels.R \name{pasteLabelForDerivation} \alias{pasteLabelForDerivation} \title{Paste Label for Derivation Plot} diff --git a/man/plotTime.Rd b/man/plotTime.Rd index ecd523d..ec1dac5 100644 --- a/man/plotTime.Rd +++ b/man/plotTime.Rd @@ -10,11 +10,8 @@ plotTime( plotShifts = FALSE, yLim = c(0, 1), xLim = c(0, 1), - oldPlot = NULL, - oldXAxisData = data.frame(), deriv = "1", - colorL = NULL, - colorU = NULL, + color = NULL, alphaL = 0.9, alphaU = 0.1, sizeTextY = 12, @@ -40,19 +37,13 @@ plotTime( \item{xLim}{numeric vector of length 2: range of x axis} -\item{oldPlot}{ggplot object} - -\item{oldXAxisData}{data.frame of time data from object} - \item{deriv}{character "1" for absolute values, "2" for first differences} -\item{colorL}{color of line (RGB)} - -\item{colorU}{color of uncertainty region (RGB)} +\item{color}{color of line and uncertainty region (RGB)} -\item{alphaL}{Color line} +\item{alphaL}{opacity line} -\item{alphaU}{Color uncertainty region} +\item{alphaU}{opacity uncertainty region} \item{sizeTextY}{size plot y label} diff --git a/man/timePlotFormattingServer.Rd b/man/timePlotFormattingServer.Rd new file mode 100644 index 0000000..0677c8e --- /dev/null +++ b/man/timePlotFormattingServer.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-timePlotFormatting.R +\name{timePlotFormattingServer} +\alias{timePlotFormattingServer} +\title{Server function of time plot formatting} +\usage{ +timePlotFormattingServer(id, savedModels) +} +\arguments{ +\item{id}{namespace id} + +\item{savedModels}{list of models of class \code{\link{TemporalIso}}} +} +\description{ +Backend for plot formatting module +} diff --git a/man/timePlotFormattingUI.Rd b/man/timePlotFormattingUI.Rd new file mode 100644 index 0000000..7193555 --- /dev/null +++ b/man/timePlotFormattingUI.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-timePlotFormatting.R +\name{timePlotFormattingUI} +\alias{timePlotFormattingUI} +\title{UI function of time plot formatting} +\usage{ +timePlotFormattingUI(id) +} +\arguments{ +\item{id}{module id} +} +\value{ +actionButton +} +\description{ +UI function of time plot formatting +} diff --git a/man/updateTime.Rd b/man/updateTime.Rd new file mode 100644 index 0000000..2eaa5a1 --- /dev/null +++ b/man/updateTime.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/plots.R +\name{updateTime} +\alias{updateTime} +\title{Extract Plot Data} +\usage{ +updateTime(plotData, object, deriv = "1") +} +\arguments{ +\item{plotData}{(data.frame) plot data} + +\item{object}{model of class \code{\link{TemporalIso}}} + +\item{deriv}{character "1" for absolute values, "2" for first differences} +} +\description{ +Extract Plot Data +} diff --git a/mpi-temporal-iso.Rproj b/mpi-temporal-iso.Rproj index 3535545..1788e68 100644 --- a/mpi-temporal-iso.Rproj +++ b/mpi-temporal-iso.Rproj @@ -13,5 +13,6 @@ RnwWeave: Sweave LaTeX: pdfLaTeX BuildType: Package +PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source PackageRoxygenize: rd,collate,namespace,vignette diff --git a/tests/testthat/shinyTest_startApplication.R b/tests/testthat/shinyTest_startApplication.R new file mode 100644 index 0000000..03174e9 --- /dev/null +++ b/tests/testthat/shinyTest_startApplication.R @@ -0,0 +1,14 @@ +context("startApplication") +# This file is for testing the applications in the apps/ directory. + +library(shinytest) + +test_that("startApplication() works", { + # Don't run these tests on the CRAN build servers + skip_on_cran() + + # Use compareImages=FALSE because the expected image screenshots were created + # on a Mac ..., and they will differ from screenshots taken on the CI platform, + # which runs on Linux. + expect_pass(testApp(file.path(testthat::test_path(), "startApplication"), compareImages = FALSE)) +}) diff --git a/tests/testthat/startApplication/app.R b/tests/testthat/startApplication/app.R new file mode 100644 index 0000000..02cd07f --- /dev/null +++ b/tests/testthat/startApplication/app.R @@ -0,0 +1 @@ +startApplication() diff --git a/tests/testthat/test-plotTime.R b/tests/testthat/test-plotTime.R new file mode 100644 index 0000000..fb0f1e5 --- /dev/null +++ b/tests/testthat/test-plotTime.R @@ -0,0 +1,254 @@ +load(testthat::test_path("testdata/testObjectDefault.RData")) +load(testthat::test_path("testdata/testObjectGap.RData")) + +testthat::test_that("plotTime", { + plot <- plotTime(object = testObjectDefault1, prop = 0.8, + plotShifts = FALSE, + deriv = "1", + yLim = c(-10,-5), xLim = c(0, 8), + color = "#002350") + + expect_equal(plot$labels, list(x = "Time", y = "Estimate", + title = "80%-Credibility-Interval for isotopic values over time", + colour = "individual", + ymin = "lower", ymax = "upper", + fill = "individual", + shape = "individual", + size = "individual")) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, + c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5)) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, + c("[0-1]", "[1-2]", "[2-3]", "[3-4]", "[4-5]", "[5-6]")) + + # second derivation + plot <- plotTime(object = testObjectDefault1, prop = 0.8, + plotShifts = FALSE, + deriv = "2", + yLim = c(-4,4), xLim = c(0, 8), + color = "#002350") + + expect_equal(plot$labels, list(x = "Time", y = "Estimate", + title = "80%-Credibility-Interval for isotopic values over time", + colour = "individual", + ymin = "lower", ymax = "upper", + fill = "individual", + shape = "individual", + size = "individual")) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, + c(0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5)) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, + c("[0-1]", "", "[1-2]", "", "[2-3]", "", "[3-4]", "", "[4-5]", "", "[5-6]")) + + # shifts + plot <- plotTime(object = testObjectGap1, prop = 0.8, + plotShifts = TRUE, + yLim = c(-10,-5), xLim = c(0, 4), + color = "#002350") + + expect_equal(plot$labels, list(x = "Time", y = "Estimate", + title = "80%-Credibility-Interval for isotopic values over time", + colour = "individual", + ymin = "lower", ymax = "upper", + fill = "individual", + shape = "individual", + size = "individual", + xintercept = "xintercept")) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, + c(0.5, 1.5, 2.5)) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, + c("[0-1]", "[1-2]", "[2-3]")) +}) + +testthat::test_that("basePlotTime", { + plotData <- getPlotData(object = testObjectDefault1, prop = 0.8, deriv = "1") %>% + updateTime(object = testObjectDefault1, deriv = "1") + plotDataDF <- list(ind_1 = plotData) %>% + extractPlotDataDF(models = "ind_1", + credInt = 0.8) %>% + na.omit() + + pointStyleList <- list() %>% + getDefaultPointFormatForModels(modelNames = c("ind_1")) + + # specify x, y limits + plot <- basePlotTime(df = plotDataDF, + yLim = c(-10,-5), + xLim = c(0, 8)) %>% + setDefaultTitles(prop = 0.8, xAxisLabel = "Time", yAxisLabel = "Estimate") %>% + setXAxisLabels(xAxisData = list(ind_1 = plotData) %>% + extractAllXAxisData(), # labels for all x axis data + extendLabels = FALSE, + xLim = c(0, 8), + deriv = FALSE) %>% + drawLinesAndRibbon( + pointStyleList = pointStyleList, + alphaL = 0.7, + alphaU = 0.1, + legendName = "testLegend" + ) %>% + setLegendPosition(hideLegend = FALSE, + legendPosition = "top") + + expect_equal( + plot$labels, + list( + x = "Time", + y = "Estimate", + title = "80%-Credibility-Interval for isotopic values over time", + colour = "individual", + ymin = "lower", + ymax = "upper", + fill = "individual", + shape = "individual", + size = "individual" + ) + ) + expect_equal(plot$coordinates$limits, list(x = c(0, 8), y = c(-10, -5))) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, + c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5)) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, + c("[0-1]", "[1-2]", "[2-3]", "[3-4]", "[4-5]", "[5-6]")) + + # default x, y limits + plot <- basePlotTime(df = plotDataDF) %>% + setXAxisLabels(xAxisData = list(ind_1 = plotData) %>% + extractAllXAxisData(), # labels for all x axis data + extendLabels = FALSE, + deriv = FALSE) %>% + drawLinesAndRibbon( + pointStyleList = pointStyleList, + alphaL = 0.7, + alphaU = 0.1, + legendName = "testLegend" + ) + + expect_equal( + plot$labels, + list( + x = "time", + y = "median", + colour = "individual", + ymin = "lower", + ymax = "upper", + fill = "individual", + shape = "individual", + size = "individual" + ) + ) + expect_equal(plot$coordinates$limits, + list( + x = c(xmin = 0.5, xmax = 5.5), + y = c( + ymin = -12.7512327337153, + ymax = -4.60003088235379 + ) + )) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, + c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5)) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, + c("[0-1]", "[1-2]", "[2-3]", "[3-4]", "[4-5]", "[5-6]")) +}) + +testthat::test_that("drawLinesAndRibbon", { + # second derivation + plotData1 <- getPlotData(object = testObjectDefault1, prop = 0.8, deriv = "2") %>% + updateTime(object = testObjectDefault1, deriv = "2") + plotData2 <- getPlotData(object = testObjectGap1, prop = 0.8, deriv = "2") %>% + updateTime(object = testObjectGap1, deriv = "2") + allPlotDataDF <- list(ind_1 = plotData1, + ind_2 = plotData2) %>% + extractPlotDataDF(models = c("ind_1", "ind_2"), + credInt = 0.8) %>% + na.omit() + + pointStyleList <- list() %>% + getDefaultPointFormatForModels(modelNames = c("ind_1", "ind_2")) + + plot <- basePlotTime(df = allPlotDataDF) %>% + setXAxisLabels(xAxisData = list(ind_1 = plotData1, + ind_2 = plotData2) %>% + extractAllXAxisData(), # labels for all x axis data + extendLabels = FALSE, + deriv = "2") %>% # if "1" is set here, breaks in-between are left out + drawLinesAndRibbon( + pointStyleList = pointStyleList, + alphaL = 0.7, + alphaU = 0.1, + legendName = "testLegend" + ) + + expect_equal( + plot$labels, + list( + x = "time", + y = "median", + colour = "individual", + ymin = "lower", + ymax = "upper", + fill = "individual", + shape = "individual", + size = "individual" + ) + ) + expect_equal(plot$coordinates$limits, + list(x = c(xmin = 1, xmax = 5), y = c(ymin = -4.17929722147457, + ymax = 3.35841030846769))) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, + c(1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5)) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, + c("[0.5-1.5]", "", "[1.5-2.5]", "", "[2.5-3.5]", "", "[3.5-4.5]", "", "[4.5-5.5]")) +}) + +testthat::test_that("setSecondYAxis", { + plotData1 <- getPlotData(object = testObjectDefault1, prop = 0.8, deriv = "1") %>% + updateTime(object = testObjectDefault1, deriv = "1") + plotData2 <- getPlotData(object = testObjectGap1, prop = 0.8, deriv = "1") %>% + updateTime(object = testObjectGap1, deriv = "1") + allPlotDataDF <- list(ind_1 = plotData1, + ind_2 = plotData2) %>% + extractPlotDataDF(models = c("ind_1", "ind_2"), + credInt = 0.8) %>% + na.omit() + + pointStyleList <- list() %>% + getDefaultPointFormatForModels(modelNames = c("ind_1", "ind_2")) + plotTexts <- getDefaultTextFormat() + + index <- allPlotDataDF$individual == "ind_2" + rescaling <- getRescaleParams(oldLimits = getYRange(allPlotDataDF) %>% unlist(), + newLimits = getYRange(allPlotDataDF[index, ]) %>% unlist(), + secAxis = TRUE) + + plot <- allPlotDataDF %>% + rescaleSecondAxisData(individual = "ind_2", + rescaling = rescaling) %>% + basePlotTime() %>% + setXAxisLabels(xAxisData = list(ind_1 = plotData1, + ind_2 = plotData2) %>% + extractAllXAxisData(), # labels for all x axis data + extendLabels = FALSE, + deriv = FALSE) %>% + shinyTools::formatTitlesOfGGplot(text = plotTexts) %>% + drawLinesAndRibbon( + pointStyleList = pointStyleList, + alphaL = 0.7, + alphaU = 0.1, + legendName = "testLegend" + ) %>% + setSecondYAxis(rescaling = rescaling, + titleFormat = plotTexts[["yAxisTitle"]], + textFormat = plotTexts[["yAxisText"]], + yAxisLabel = "axis2Test", + yAxisTitleColor = "#002350") + + expect_equal(plot$labels, list(x = "time", y = "median", colour = "individual", ymin = "lower", + ymax = "upper", fill = "individual", shape = "individual", + size = "individual")) + expect_equal(plot$coordinates$limits, + list(x = c(xmin = 0.5, xmax = 5.5), + y = c(ymin = -12.7512327337153, ymax = -4.60003088235379))) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, + c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5)) + expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, + c("[0-1]", "[1-2]", "[2-3]", "[3-4]", "[4-5]", "[5-6]")) +}) diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R index fa95fa1..c04cfb3 100644 --- a/tests/testthat/test-plots.R +++ b/tests/testthat/test-plots.R @@ -1,5 +1,5 @@ -load("testObjectDefault.RData") -load("testObjectGap.RData") +load(testthat::test_path("testdata/testObjectDefault.RData")) +load(testthat::test_path("testdata/testObjectGap.RData")) testthat::test_that("getPlotData: two date inputs", { plotData1 <- getPlotData(testObjectDefault1, prop = 0.8, time = NULL, deriv = "1") @@ -15,6 +15,8 @@ testthat::test_that("getPlotData: two date inputs", { upper = c(-6.42936455233924,-7.75138719574058, -7.33261350484481,-6.06113063226461,-5.50042606430566, -5.27929770330058), + sd = c(1.33713829843568, 1.38779685928719, + 1.61491770752276, 2.2435686892909, 2.59687117559807, 2.70290343638949), time = c(1L, 2L, 3L, 4L, 5L, 6L) ) @@ -25,6 +27,8 @@ testthat::test_that("getPlotData: two date inputs", { 0.419012970657708,0.166373227374931,0.0766007232233719), upper = c(0.541802095090819,2.13142150909996, 2.73026801430583,2.30758491892875,2.34360361651566), + sd = c(1.64719396911037, + 1.62120832948596, 1.83064197559426, 1.81419080780957, 1.88032877584619), time = c(1L, 2L, 3L, 4L, 5L) ) @@ -47,6 +51,9 @@ testthat::test_that("getPlotData: one date input", { upper = c(-6.4244288140299,-7.81582599339432, -7.38509654640456,-6.30377374939198,-5.64332388194448, -5.54158337328692), + sd = c(1.34885080466708, 1.35703875903698, + 1.5895692220176, 2.20307418350219, 2.52256867044183, 2.66143079125866 + ), time = c(1L, 2L, 3L, 4L, 5L, 6L) ) @@ -57,6 +64,9 @@ testthat::test_that("getPlotData: one date input", { 0.372613606234133,0.142404333952154,0.0421910046768903), upper = c(0.584632457040193,2.11626139721327, 2.58128427989846,2.40079211685847,2.29467532444726), + sd = c(1.63417082215553, + 1.58450922848466, 1.79798685302716, 1.81358388856865, 1.89855532316841 + ), time = c(1L, 2L, 3L, 4L, 5L) ) @@ -91,33 +101,33 @@ testthat::test_that("getXAxisData", { testthat::expect_equal(xAxisData1, data.frame( time = c(0.5, 1.5, 2.5), - lower = c(0, 1, 2), - upper = c(1, 2, 3) + time_lower = c(0, 1, 2), + time_upper = c(1, 2, 3) )) testthat::expect_equal(xAxisData2, data.frame( time = c(0.5, 1.5, 2.5, 4.5, 5.5), - lower = c(0, 1, 2, 4, 5), - upper = c(1, 2, 3, 5, 6) + time_lower = c(0, 1, 2, 4, 5), + time_upper = c(1, 2, 3, 5, 6) )) }) testthat::test_that("getLabel, deriv = 1", { label1 <- getLabel(xAxisData = data.frame(time = c(0.5, 1.5, 2.5), - lower = c(0, 1, 2), - upper = c(1, 2, 3)), + time_lower = c(0, 1, 2), + time_upper = c(1, 2, 3)), deriv = "1") label2 <- getLabel(xAxisData = data.frame(time = c(0.5, 1.5, 2.5, 4.5, 5.5), - lower = c(0, 1, 2, 4, 5), - upper = c(1, 2, 3, 5, 6)), + time_lower = c(0, 1, 2, 4, 5), + time_upper = c(1, 2, 3, 5, 6)), deriv = "1") label3 <- getLabel(xAxisData = data.frame(time = c(0, 1, 2, 3, 4, 5), - lower = c(0, 1, 2, 3, 4, 5), - upper = c(0, 1, 2, 3, 4, 5)), + time_lower = c(0, 1, 2, 3, 4, 5), + time_upper = c(0, 1, 2, 3, 4, 5)), deriv = "1") label4 <- getLabel(xAxisData = data.frame(time = c(1, 2, 3, 4, 5, 6), - lower = c(1, 2, 3, 4, 5, 6), - upper = c(1, 2, 3, 4, 5, 6)), + time_lower = c(1, 2, 3, 4, 5, 6), + time_upper = c(1, 2, 3, 4, 5, 6)), deriv = "1") testthat::expect_equal(label1, c("[0-1]", "[1-2]", "[2-3]")) @@ -129,21 +139,21 @@ testthat::test_that("getLabel, deriv = 1", { testthat::test_that("getLabel, deriv = 2", { label1 <- getLabel(xAxisData = data.frame(time = c(0.5, 1.5, 2.5), - lower = c(0, 1, 2), - upper = c(1, 2, 3)), + time_lower = c(0, 1, 2), + time_upper = c(1, 2, 3)), deriv = "2") label2 <- getLabel(xAxisData = data.frame(time = c(0.5, 1.5, 2.5, 4.5, 5.5), - lower = c(0, 1, 2, 4, 5), - upper = c(1, 2, 3, 5, 6)), + time_lower = c(0, 1, 2, 4, 5), + time_upper = c(1, 2, 3, 5, 6)), deriv = "2", hidePastedLabels = FALSE) label3 <- getLabel(xAxisData = data.frame(time = c(0, 1, 2, 3, 4, 5), - lower = c(0, 1, 2, 3, 4, 5), - upper = c(0, 1, 2, 3, 4, 5)), + time_lower = c(0, 1, 2, 3, 4, 5), + time_upper = c(0, 1, 2, 3, 4, 5)), deriv = "2") label4 <- getLabel(xAxisData = data.frame(time = c(1, 2, 3, 4, 5, 6), - lower = c(1, 2, 3, 4, 5, 6), - upper = c(1, 2, 3, 4, 5, 6)), + time_lower = c(1, 2, 3, 4, 5, 6), + time_upper = c(1, 2, 3, 4, 5, 6)), deriv = "2", hidePastedLabels = FALSE) @@ -177,30 +187,11 @@ testthat::test_that("getBreaks, deriv = 2", { testthat::expect_equal(breaks3, c(0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5)) }) - -testthat::test_that("getDefaultPlotRange, deriv = 2", { - savedModels <- list( - list(fit = testObjectDefault1), - list(fit = testObjectDefault2), - list(fit = testObjectGap1), - list(fit = testObjectGap11) - ) - testRange1 <- getDefaultPlotRange(savedModels, deriv = "1") - testRange2 <- getDefaultPlotRange(savedModels, deriv = "2") - - testthat::expect_equal(testRange1 %>% unlist(), - c(xmin = 0.5, xmax = 6, - ymin = -12.9045490738921, ymax = -4.58609303324681)) - testthat::expect_equal(testRange2 %>% unlist(), - c(xmin = 1, xmax = 5.5, - ymin = -4.2100298618177, ymax = 3.69646935224212)) -}) - testthat::test_that("extendXAxis", { oldXAxisData <- structure(list( time = c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5), - lower = c(0, 1, 2, 3, 4, 5), - upper = c(1, 2, 3, 4, 5, 6)), + time_lower = c(0, 1, 2, 3, 4, 5), + time_upper = c(1, 2, 3, 4, 5, 6)), class = "data.frame", row.names = c(NA, -6L)) @@ -217,3 +208,11 @@ testthat::test_that("extendXAxis", { testthat::expect_equal(breaks, c(-0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 7)) testthat::expect_equal(labels, c("[-1-0]", "[0-1]", "[1-2]", "[2-3]", "[3-4]", "[4-5]", "[5-6]", "[6-8]")) }) + +testthat::test_that("getLim", { + testRanges <- list(xAxis = list(min = 0L, max = 1L, fromData = TRUE), + yAxis = list(min = 0L, max = 1L, fromData = FALSE)) + + expect_equal(getLim(testRanges, axis = "xAxis"), numeric(0)) + expect_equal(getLim(testRanges, axis = "yAxis"), 0:1) +}) diff --git a/tests/testthat/testObjectDefault.RData b/tests/testthat/testdata/testObjectDefault.RData similarity index 100% rename from tests/testthat/testObjectDefault.RData rename to tests/testthat/testdata/testObjectDefault.RData diff --git a/tests/testthat/testObjectGap.RData b/tests/testthat/testdata/testObjectGap.RData similarity index 100% rename from tests/testthat/testObjectGap.RData rename to tests/testthat/testdata/testObjectGap.RData From b9a161cbab3fd50c9b9cb7f37990f054fe65e932 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Thu, 25 Apr 2024 13:47:40 +0200 Subject: [PATCH 23/36] fix overlapping of UIs --- DESCRIPTION | 2 +- R/03-timePlotFormatting.R | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index f45512f..11300c1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.04.1 +Version: 24.04.1.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index 4bcf4d8..da0efd8 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -114,7 +114,7 @@ timePlotFormattingUI <- function(id) { ) ), fluidRow(column(12, - style = "margin-top: -3em;", + style = "margin-top: -1em;", align = "right", plotExportButton(ns("exportCredIntTimePlot")))), tags$hr(), From 83656af16584b8fac1ff4f50ed6808b0fb3f0c40 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Thu, 6 Jun 2024 11:41:44 +0200 Subject: [PATCH 24/36] update config --- DESCRIPTION | 2 +- R/03-timePlotFormatting.R | 3 ++- inst/config.yaml | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 11300c1..0a69c9f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.04.1.1 +Version: 24.04.1.2 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index da0efd8..980d125 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -77,7 +77,8 @@ timePlotFormattingUI <- function(id) { id = ns("plotLabels"), title = "Text", type = "ggplot", - initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]]) + initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], + xAxisText = config()[["defaultIntervalTimePlotText"]]) ) ), column(3, diff --git a/inst/config.yaml b/inst/config.yaml index 20db365..7fab6e3 100644 --- a/inst/config.yaml +++ b/inst/config.yaml @@ -17,18 +17,36 @@ rPackageName: "OsteoBioR" # for download and import of models # parameters for plot styles # default format of graph and axis titles of intervalTimePlot for all models +availableElements: + title: + plot title: "plotTitle" + axis: + x axis title: "xAxisTitle" + x axis text: "xAxisText" + y axis title: "yAxisTitle" + y axis text: "yAxisText" + legend: + legend title: "legendTitle" + legend text: "legendText" + + defaultIntervalTimePlotTitle: text: "" + fontFamily: "sans" fontType: "plain" color: "#000000" size: 24 hide: false defaultIntervalTimePlotText: + fontFamily: "sans" fontType: "plain" color: "#000000" size: 18 hide: false + angle: 0 + hjust: 0.5 + vjust: 0.5 # default point style for each model defaultPointStyle: From bbb45bce54054abef62049bb0a006292b8dfd287 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 7 Aug 2024 15:56:05 +0200 Subject: [PATCH 25/36] OsteoBioR 24.08.0: prevent dropUp (#58) * prevent dropUp * prevent dropup in all picker inputs * apply new simpler import module for model import * update news.md --- DESCRIPTION | 2 +- NAMESPACE | 2 + NEWS.md | 6 +++ R/03-module_modelSpecifications.R | 9 +++-- R/NAMESPACE.R | 2 +- inst/app/server.R | 18 +++++---- inst/app/ui.R | 62 ++++++++++++++++++++++--------- inst/config.yaml | 2 +- 8 files changed, 72 insertions(+), 31 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 0a69c9f..184a10f 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.04.1.2 +Version: 24.08.0 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/NAMESPACE b/NAMESPACE index f546cee..004a1a2 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -51,6 +51,8 @@ importFrom(DataTools,downloadModelUI) importFrom(DataTools,importDataServer) importFrom(DataTools,importDataUI) importFrom(DataTools,importOptions) +importFrom(DataTools,importServer) +importFrom(DataTools,importUI) importFrom(DataTools,renameExistingNames) importFrom(DataTools,tryCatchWithWarningsAndErrors) importFrom(colourpicker,colourInput) diff --git a/NEWS.md b/NEWS.md index 998a63e..5e2d84d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# OsteoBioR 24.08.0 + +## Bug Fixes +- prevent dropUp of inputs for `Element` and `Time` variables in the tab _Model_ and other + 'pickerInputs' (#56) + # OsteoBioR 24.04.1 ## New Features diff --git a/R/03-module_modelSpecifications.R b/R/03-module_modelSpecifications.R index c749dc1..6b86bad 100644 --- a/R/03-module_modelSpecifications.R +++ b/R/03-module_modelSpecifications.R @@ -15,9 +15,11 @@ modelSpecificationsUI <- function(id, title) { label = "Time variable(s):", choices = character(0), options = list( - "actions-box" = FALSE, - "none-selected-text" = 'No variables selected', - "max-options" = 2 + `actions-box` = FALSE, + `dropup-auto` = FALSE, + size = 10, + `none-selected-text` = 'No variables selected', + `max-options` = 2 ), multiple = TRUE ), @@ -29,6 +31,7 @@ modelSpecificationsUI <- function(id, title) { choices = character(0), options = list( `actions-box` = TRUE, + `dropup-auto` = FALSE, size = 10, `none-selected-text` = "No variables selected" ), diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 2a59a78..061dab2 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -13,7 +13,7 @@ #' @import shinythemes #' @importFrom colourpicker colourInput #' @importFrom DataTools checkAnyNonNumericColumns downloadModelUI downloadModelServer -#' importDataUI importDataServer importOptions renameExistingNames tryCatchWithWarningsAndErrors +#' importDataUI importDataServer importUI importServer importOptions renameExistingNames tryCatchWithWarningsAndErrors #' @importFrom dplyr arrange bind_cols bind_rows cur_group_id distinct do group_by mutate n select #' slice ungroup #' @importFrom ggplot2 aes coord_cartesian element_line element_text diff --git a/inst/app/server.R b/inst/app/server.R index 64f4a53..d47d4f1 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -435,14 +435,16 @@ shinyServer(function(input, output, session) { modelNotes = uploadedNotes, triggerUpdate = reactive(TRUE)) - uploadedValues <- DataTools::importDataServer("modelUpload", - title = "Import Model", - importType = "model", - ckanFileTypes = config()[["ckanModelTypes"]], - ignoreWarnings = TRUE, - defaultSource = config()[["defaultSourceModel"]], - fileExtension = config()[["fileExtension"]], - options = DataTools::importOptions(rPackageName = config()[["rPackageName"]])) + uploadedValues <- DataTools::importServer("modelUpload", + title = "Import Model", + importType = "model", + ckanFileTypes = config()[["ckanModelTypes"]], + ignoreWarnings = TRUE, + defaultSource = config()[["defaultSourceModel"]], + fileExtension = config()[["fileExtension"]], + options = DataTools::importOptions( + rPackageName = config()[["rPackageName"]] + )) observe({ req(length(uploadedValues()) > 0) diff --git a/inst/app/ui.R b/inst/app/ui.R index fe642ae..1cf798e 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -127,7 +127,7 @@ tagList( sidebarPanel( width = 2, style = "position:fixed; width:15%; max-width:350px; overflow-y:auto; height:85%", - DataTools::importDataUI("modelUpload", label = "Import Model"), + DataTools::importUI("modelUpload", label = "Import Model"), tags$hr(), modelSpecificationsUI("modelSpecification", "Model Specification"), actionButton("fitModel", "Fit Model"), @@ -195,8 +195,13 @@ tagList( pickerInput("savedModelsShift", "Select Models / Individuals", choices = NULL, multiple = TRUE, - options = list(`actions-box` = TRUE)) - ), + options = list( + `actions-box` = TRUE, + `dropup-auto` = FALSE, + size = 10, + `none-selected-text` = "No variables selected" + )) + ), column(3, radioButtons( "shiftTimeAbsOrRel", @@ -227,8 +232,16 @@ tagList( tags$br(), fluidRow( column(4, - pickerInput("savedModelsTime", "Select Models / Individuals", choices = NULL, multiple = TRUE, - options = list(`actions-box` = TRUE)), + pickerInput("savedModelsTime", + "Select Models / Individuals", + choices = NULL, + multiple = TRUE, + options = list( + `actions-box` = TRUE, + `dropup-auto` = FALSE, + size = 10, + `none-selected-text` = "No variables selected" + )), tags$br(), actionButton("estSpecTimePoint", "Estimate") ), @@ -251,8 +264,16 @@ tagList( HTML("
"), fluidRow( column(4, - pickerInput("savedModelsUserDefined", "Select Models / Individuals", choices = NULL, multiple = TRUE, - options = list(`actions-box` = TRUE)) + pickerInput("savedModelsUserDefined", + "Select Models / Individuals", + choices = NULL, + multiple = TRUE, + options = list( + `actions-box` = TRUE, + `dropup-auto` = FALSE, + size = 10, + `none-selected-text` = "No variables selected" + )) ), column(4, radioButtons("typeEstUser", "Type", @@ -340,9 +361,11 @@ tagList( label = "Time Variable:", choices = character(0), options = list( - "actions-box" = FALSE, - "none-selected-text" = 'No variables selected', - "max-options" = 1 + `max-options` = 1, + `actions-box` = FALSE, + `dropup-auto` = FALSE, + size = 10, + `none-selected-text` = "No variables selected" ), multiple = TRUE ), @@ -352,7 +375,8 @@ tagList( label = "Variables for elements:", choices = character(0), options = list( - `actions-box` = FALSE, + `actions-box` = TRUE, + `dropup-auto` = FALSE, size = 10, `none-selected-text` = "No variables selected" ), @@ -364,9 +388,11 @@ tagList( label = "Measurement mean:", choices = character(0), options = list( - "actions-box" = FALSE, - "none-selected-text" = 'No variables selected', - "max-options" = 1 + `actions-box` = FALSE, + `dropup-auto` = FALSE, + size = 10, + `none-selected-text` = "No variables selected", + `max-options` = 1 ), multiple = TRUE ), @@ -376,9 +402,11 @@ tagList( label = "Measurement standard deviation:", choices = character(0), options = list( - "actions-box" = FALSE, - "none-selected-text" = 'No variables selected', - "max-options" = 1 + `max-options` = 1, + `actions-box` = FALSE, + `dropup-auto` = FALSE, + size = 10, + `none-selected-text` = "No variables selected" ), multiple = TRUE ), diff --git a/inst/config.yaml b/inst/config.yaml index 7fab6e3..20d8351 100644 --- a/inst/config.yaml +++ b/inst/config.yaml @@ -8,8 +8,8 @@ ckanFileTypes: - "odt" - "txt" ckanModelTypes: - - "zip" - "osteobior" + - "zip" githubRepo: "osteobior" # for import of models mainFolder: "predefinedModels" # for import of models fileExtension: "osteobior" # for download of models From 3b980e97397f1b33248b08cafa0d41def27b6f0e Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Fri, 9 Aug 2024 12:19:34 +0200 Subject: [PATCH 26/36] catch error if empty model output (#60) --- DESCRIPTION | 2 +- NAMESPACE | 2 +- NEWS.md | 5 +++++ R/03-timePlotFormatting.R | 18 +++++++++++++----- R/NAMESPACE.R | 3 ++- R/plots.R | 21 +++++++++++++++++++++ inst/app/server.R | 4 ++-- 7 files changed, 45 insertions(+), 10 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 184a10f..7640be1 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.08.0 +Version: 24.08.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/NAMESPACE b/NAMESPACE index 004a1a2..7e2d938 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -54,7 +54,6 @@ importFrom(DataTools,importOptions) importFrom(DataTools,importServer) importFrom(DataTools,importUI) importFrom(DataTools,renameExistingNames) -importFrom(DataTools,tryCatchWithWarningsAndErrors) importFrom(colourpicker,colourInput) importFrom(dplyr,arrange) importFrom(dplyr,bind_cols) @@ -109,6 +108,7 @@ importFrom(shinyTools,plotRangesServer) importFrom(shinyTools,plotRangesUI) importFrom(shinyTools,plotTitlesServer) importFrom(shinyTools,plotTitlesUI) +importFrom(shinyTools,shinyTryCatch) importFrom(shinyWidgets,pickerInput) importFrom(shinyWidgets,updatePickerInput) importFrom(shinycssloaders,withSpinner) diff --git a/NEWS.md b/NEWS.md index 5e2d84d..c6872ad 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# OsteoBioR 24.08.1 + +## Bug Fixes +- catch error when trying to display plot and table of models that failed and have no samples (#55) + # OsteoBioR 24.08.0 ## Bug Fixes diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index 980d125..90085b4 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -213,18 +213,26 @@ timePlotFormattingServer <- function(id, savedModels) { lapply(allFits(), function(x) { getPlotData(object = x, prop = input$modCredInt, deriv = input$deriv) %>% updateTime(object = x, deriv = input$deriv) - }) + }) %>% + shinyTryCatch(errorTitle = "'Credibility intervals over time': Error in extracting plot data", + warningTitle = "'Credibility intervals over time': Warning in extracting plot data", + alertStyle = "shinyalert") }) extractedPlotDataDF <- reactive({ - extractPlotDataDF(plotDataList = extractedPlotDataList(), - models = input[["plotTimeModels"]], - credInt = input$modCredInt) + extractedPlotDataList() %>% + extractPlotDataDF(models = input[["plotTimeModels"]], + credInt = input$modCredInt) %>% + shinyTryCatch(errorTitle = "'Credibility intervals over time': Error in extracting table data", + warningTitle = "'Credibility intervals over time': Warning in extracting table data", + alertStyle = "shinyalert") }) output$plotData <- renderTable({ validate(need(input[["plotTimeModels"]], - "Choose at least one element from 'Display Models / Individuals' ...")) + "Choose at least one element from 'Display Models / Individuals' ..."), + need(nrow(extractedPlotDataDF()) > 0, + "No data available for selected models ...")) extractedPlotDataDF() }) diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 061dab2..4824875 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -13,7 +13,7 @@ #' @import shinythemes #' @importFrom colourpicker colourInput #' @importFrom DataTools checkAnyNonNumericColumns downloadModelUI downloadModelServer -#' importDataUI importDataServer importUI importServer importOptions renameExistingNames tryCatchWithWarningsAndErrors +#' importDataUI importDataServer importUI importServer importOptions renameExistingNames #' @importFrom dplyr arrange bind_cols bind_rows cur_group_id distinct do group_by mutate n select #' slice ungroup #' @importFrom ggplot2 aes coord_cartesian element_line element_text @@ -32,6 +32,7 @@ #' @importFrom shinyTools dataExportButton dataExportServer formatPointsOfGGplot #' formatRangesOfGGplot formatTitlesOfGGplot plotExportButton plotExportServer #' plotPointsServer plotPointsUI plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI +#' shinyTryCatch #' @importFrom shinyWidgets pickerInput updatePickerInput #' @importFrom stats approx dnorm lm median na.omit quantile sd #' @importFrom utils write.csv write.table combn diff --git a/R/plots.R b/R/plots.R index a181f10..87c6cd4 100644 --- a/R/plots.R +++ b/R/plots.R @@ -158,6 +158,23 @@ drawShiftLines <- function(plot, object, deriv, plotShifts, ...) { } extractPlotDataDF <- function(plotDataList, models, credInt) { + # remove elements with no rows + plotDataList <- plotDataList[sapply(plotDataList, nrow) > 0] + + # empty of selected models + emptyModels <- setdiff(models, names(plotDataList)) + + # warning that no data is available for some selected models + if (length(emptyModels) > 0) { + warning(paste("No data available for model(s):", + paste(emptyModels, collapse = ", "), + ". Model(s) not displayed in table 'Plot Data'.")) + } + + models <- intersect(models, names(plotDataList)) + + if (length(models) == 0) return(data.frame()) + # filter for displayed models: plotDataList[models] %>% bind_rows(.id = "individual") %>% @@ -191,6 +208,8 @@ getPlotData <- function(object, prop = 0.8, time = NULL, deriv = "1"){ uLim <- 1 - lLim dat <- rstan::extract(object)$interval + if (is.null(dat)) return(data.frame()) + if(deriv == "2"){ if (ncol(dat) > 2) { dat <- t(apply(dat, 1, diff)) @@ -225,6 +244,8 @@ getPlotData <- function(object, prop = 0.8, time = NULL, deriv = "1"){ #' @param plotData (data.frame) plot data #' @inheritParams plotTime updateTime <- function(plotData, object, deriv = "1") { + if (nrow(plotData) == 0) return(plotData) + plotData$time <- adjustTimeColumn(objectTime = object@time, deriv = deriv) plotData$time_lower <- adjustTimeColumn(objectTime = object@timeLower, deriv = deriv) plotData$time_upper <- adjustTimeColumn(objectTime = object@timeUpper, deriv = deriv) diff --git a/inst/app/server.R b/inst/app/server.R index d47d4f1..487db0b 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -792,7 +792,7 @@ shinyServer(function(input, output, session) { siteMeans = stayTimeDat()[, 1] %>% unlist(), siteSigma = stayTimeDat()[, 2] %>% unlist(), print = FALSE) %>% - DataTools::tryCatchWithWarningsAndErrors() + shinyTools::shinyTryCatch(errorTitle = "Calculation failed", alertStyle = "shinyalert") }) output$estimatedStayTimes <- renderPrint({ estimatedStayTimes() }) @@ -901,7 +901,7 @@ shinyServer(function(input, output, session) { meanVar = input$meanVarHist, sdVar = input$sdVarHist ) %>% - DataTools::tryCatchWithWarningsAndErrors(), + shinyTools::shinyTryCatch(errorTitle = "Calculation failed", alertStyle = "shinyalert"), sapply(isoHistDat() %>% select(input$boneVarsHist), quantile, probs = c(0.025, 0.975)) %>% t() ) From c6f8370045dfb6a74033f56d302ad9e8f5427dd6 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 28 Aug 2024 15:43:14 +0200 Subject: [PATCH 27/36] OsteoBioR 24.08.2: include break point detection with mcp package (#62) * try include mcp package * update dockerfile * fix dockerfile * break point detection with example data * update examples for segments and priors * update example files * include plot data, hide example data * add missing package * update docu for breakpoints, add info button, reset button for plot, update rescaling of 2nd axis * update news.md * update docu and test * format UI * update messages, use shinyjs to update button * update namespace * fix character --- DESCRIPTION | 6 +- Dockerfile | 7 +- NAMESPACE | 10 + NEWS.md | 13 + R/01-plotFormatting.R | 9 - R/01-plotSecondYAxis.R | 12 +- R/02-breakPoints.R | 215 +++++++++++ ...tSavedModels.R => 02-extractSavedModels.R} | 0 R/02-matrixModule.R | 187 ++++++++++ R/03-breakPointDetection.R | 282 +++++++++++++++ R/03-timePlotFormatting.R | 338 +++++++++++------- R/NAMESPACE.R | 15 +- inst/app/data/example_breakPointPriors.csv | 4 + inst/app/data/example_breakPointSegments.csv | 4 + inst/app/data/example_breakPoints.csv | 15 + inst/app/server.R | 3 +- man/breakPointDetectionServer.Rd | 21 ++ man/cleanComb.Rd | 18 + man/combineDataAndModelOutputs.Rd | 2 +- man/compareWithLoo.Rd | 17 + man/extractModelOutputs.Rd | 2 +- man/extractSavedModels.Rd | 2 +- man/formulasServer.Rd | 19 + man/getCellChoices.Rd | 19 + man/getComb.Rd | 21 ++ man/infoButtonServer.Rd | 27 ++ man/matrixServer.Rd | 42 +++ man/mcpUI.Rd | 14 + man/messageNoModelsToPlot.Rd | 11 + man/readExampleMatrix.Rd | 14 + man/removeModelOutputs.Rd | 2 +- man/runMcp.Rd | 19 + man/setFormulasAndPriors.Rd | 17 + man/splitComb.Rd | 17 + man/validateFormula.Rd | 17 + tests/testthat/test-breakPoints.R | 98 +++++ tests/testthat/test-plotTime.R | 3 +- .../testdata/test_detectBreakPoints.csv | 15 + 38 files changed, 1374 insertions(+), 163 deletions(-) create mode 100644 R/02-breakPoints.R rename R/{02_extractSavedModels.R => 02-extractSavedModels.R} (100%) create mode 100644 R/02-matrixModule.R create mode 100644 R/03-breakPointDetection.R create mode 100644 inst/app/data/example_breakPointPriors.csv create mode 100644 inst/app/data/example_breakPointSegments.csv create mode 100644 inst/app/data/example_breakPoints.csv create mode 100644 man/breakPointDetectionServer.Rd create mode 100644 man/cleanComb.Rd create mode 100644 man/compareWithLoo.Rd create mode 100644 man/formulasServer.Rd create mode 100644 man/getCellChoices.Rd create mode 100644 man/getComb.Rd create mode 100644 man/infoButtonServer.Rd create mode 100644 man/matrixServer.Rd create mode 100644 man/mcpUI.Rd create mode 100644 man/messageNoModelsToPlot.Rd create mode 100644 man/readExampleMatrix.Rd create mode 100644 man/runMcp.Rd create mode 100644 man/setFormulasAndPriors.Rd create mode 100644 man/splitComb.Rd create mode 100644 man/validateFormula.Rd create mode 100644 tests/testthat/test-breakPoints.R create mode 100644 tests/testthat/testdata/test_detectBreakPoints.csv diff --git a/DESCRIPTION b/DESCRIPTION index 7640be1..a0277ee 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.08.1 +Version: 24.08.2 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -22,7 +22,9 @@ Imports: futile.logger, htmltools, jsonlite, + loo, magrittr, + mcp, methods, modules, openxlsx, @@ -36,7 +38,7 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, - shinyTools (>= 24.04.1), + shinyTools (>= 24.08.0), yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: diff --git a/Dockerfile b/Dockerfile index 5ec74aa..8f15919 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,9 @@ FROM ghcr.io/pandora-isomemo/base-image:latest ADD . . -RUN installPackage +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + jags \ + && installPackage -CMD ["Rscript", "-e", "OsteoBioR::startApplication(3838)"] +CMD ["Rscript", "-e", "library(OsteoBioR);OsteoBioR::startApplication(3838)"] diff --git a/NAMESPACE b/NAMESPACE index 7e2d938..7f9e323 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -89,7 +89,10 @@ importFrom(ggplot2,sec_axis) importFrom(ggplot2,theme) importFrom(htmltools,save_html) importFrom(jsonlite,toJSON) +importFrom(loo,loo) +importFrom(loo,loo_compare) importFrom(magrittr,"%>%") +importFrom(mcp,mcp) importFrom(openxlsx,write.xlsx) importFrom(parallel,detectCores) importFrom(rlang,.data) @@ -97,11 +100,14 @@ importFrom(rstan,extract) importFrom(rstan,sampling) importFrom(shinyTools,dataExportButton) importFrom(shinyTools,dataExportServer) +importFrom(shinyTools,formatLegendOfGGplot) importFrom(shinyTools,formatPointsOfGGplot) importFrom(shinyTools,formatRangesOfGGplot) importFrom(shinyTools,formatTitlesOfGGplot) importFrom(shinyTools,plotExportButton) importFrom(shinyTools,plotExportServer) +importFrom(shinyTools,plotLegendServer) +importFrom(shinyTools,plotLegendUI) importFrom(shinyTools,plotPointsServer) importFrom(shinyTools,plotPointsUI) importFrom(shinyTools,plotRangesServer) @@ -113,7 +119,10 @@ importFrom(shinyWidgets,pickerInput) importFrom(shinyWidgets,updatePickerInput) importFrom(shinycssloaders,withSpinner) importFrom(shinyjs,alert) +importFrom(shinyjs,enable) +importFrom(shinyjs,runjs) importFrom(stats,approx) +importFrom(stats,as.formula) importFrom(stats,dnorm) importFrom(stats,lm) importFrom(stats,median) @@ -121,6 +130,7 @@ importFrom(stats,na.omit) importFrom(stats,quantile) importFrom(stats,sd) importFrom(utils,combn) +importFrom(utils,read.csv) importFrom(utils,write.csv) importFrom(utils,write.table) importFrom(yaml,read_yaml) diff --git a/NEWS.md b/NEWS.md index c6872ad..d66f5b0 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,16 @@ +# OsteoBioR 24.08.2 + +## New Features +- option to run a break point detection based on the mcp package (#49, #61) + +## Updates +- _Credibility intervals over time_ (#59): + - secondary axis: set the range if "Range detected from data" is unchecked and a second axis is selected + - option to choose a custom legend position + +## Bug Fixes +- _Credibility intervals over time_ in secondary axis: fix that removes the axis if the model was removed from the input and the plot was recreated (#59) + # OsteoBioR 24.08.1 ## Bug Fixes diff --git a/R/01-plotFormatting.R b/R/01-plotFormatting.R index d14391b..df5b61a 100644 --- a/R/01-plotFormatting.R +++ b/R/01-plotFormatting.R @@ -32,12 +32,3 @@ getDefaultTextFormat <- function() { xAxisText = config()[["defaultIntervalTimePlotText"]], yAxisText = config()[["defaultIntervalTimePlotText"]]) } - -setLegendPosition <- function(plot, hideLegend, legendPosition) { - if (hideLegend) { - legendPosition <- "none" - } - - plot + - theme(legend.position = legendPosition) -} diff --git a/R/01-plotSecondYAxis.R b/R/01-plotSecondYAxis.R index 59b0be9..8bfe39a 100644 --- a/R/01-plotSecondYAxis.R +++ b/R/01-plotSecondYAxis.R @@ -16,12 +16,12 @@ setSecondYAxis <- function(plot, if (is.null(yAxisTitleColor)) yAxisTitleColor <- config()[["defaultIntervalTimePlotTitle"]][["color"]] plot <- plot + - theme(axis.title.y.right = element_text(family = "Arial", + theme(axis.title.y.right = element_text(family = titleFormat[["fontFamily"]], size = titleFormat[["size"]], face = titleFormat[["fontType"]], color = yAxisTitleColor, hjust = 0.5), - axis.text.y.right = element_text(family = "Arial", + axis.text.y.right = element_text(family = textFormat[["fontFamily"]], size = textFormat[["size"]], face = textFormat[["fontType"]], color = textFormat[["color"]], @@ -65,3 +65,11 @@ getRescaleParams <- function(oldLimits, newLimits = NULL, secAxis = FALSE) { list(scale = res$coefficients[2], center = res$coefficients[1]) } + +getSecondAxisTitle <- function(secAxisTitle, secAxisModel) { + if (is.null(secAxisModel) || secAxisModel == "") return("") + + if (secAxisTitle == "") return(sprintf("%s Estimate", secAxisModel)) + + return(secAxisTitle) +} diff --git a/R/02-breakPoints.R b/R/02-breakPoints.R new file mode 100644 index 0000000..5594754 --- /dev/null +++ b/R/02-breakPoints.R @@ -0,0 +1,215 @@ +#' Get Example Matrix +#' +#' @param path path to example matrix +readExampleMatrix <- function(path) { + df <- read.csv(path) + + df[is.na(df)] <- "" + + res <- df %>% as.matrix() + colnames(res) <- 1:ncol(res) + rownames(res) <- 1:nrow(res) + + res +} + +#' Get the comb matrix +#' +#' Get the combined matrix of segments and priors. +#' Process the segments and priors matrices, identify relevant rows and columns, concatenate them, +#' and generate possible cell combinations while ensuring data consistency. +#' +#' @param segments A matrix containing the segmented formulas +#' @param priors A matrix containing the priors +#' +#' @return A matrix containing the segmented formulas and priors +getComb <- function(segments, priors) { + # find rows and columns in segments that are not all "" + row_indices <- apply(segments, 1, function(x) + any(x != "")) + col_indices <- apply(segments, 2, function(x) + any(x != "")) + + # subset segments and priors based on these indices + segments <- segments[row_indices, col_indices, drop = FALSE] + priors <- priors[row_indices, col_indices, drop = FALSE] + + # concat matrices + concatenated_matrix <- matrix( + paste(segments, priors, sep = "*+*"), + nrow = nrow(segments), + ncol = ncol(segments) + ) + + #SET: change number of concatenated_matrix below so that it matches the number of rows s_rows + + # List all possible cell combinations among the matrix considering only cells in different columns and must follow column order + + col_list <- lapply(1:ncol(segments), function(x) + concatenated_matrix[, x]) + + comb <- do.call(expand.grid, col_list) + + # Data conversion to avoid warnings + + comb[] <- lapply(comb, as.character) + + comb +} + +#' Clean the comb matrix +#' +#' Check each row for '*+*' and if found replace that cell and all following cells in the row with "". +#' Remove Blank Rows. Remove duplicate rows. +#' +#' @param comb A matrix containing the segmented formulas and priors +#' +#' @return A cleaned matrix +cleanComb <- function(comb) { + n_rows <- nrow(comb) + + # Check each row for '*+*' and if found replace that cell and all following cells in the row with "" + for (i in 1:n_rows) { + # Get the indices of the cells equal to "*+*" + replace_indices <- which(comb[i, ] == "*+*") + + # if '*+*' is found in the row replace it and following elements with "" + if (length(replace_indices) > 0) { + comb[i, replace_indices[1]:ncol(comb)] <- "" + } + } + + # Remove Blank Rows + comb <- comb[apply(comb, 1, function(x) + any(x != "")), , drop = FALSE] + + # Remove duplicate rows + comb <- unique(comb) + + comb +} + +#' Split the comb matrix into two matrices +#' +#' Split the comb matrix into two matrices based on the separator '*+*'. +#' +#' @param comb A matrix containing the segmented formulas and priors +#' +#' @return A list of two matrices +splitComb <- function(comb) { + # Create two empty matrices with the same dimensions as comb + mat1 <- matrix(ncol = ncol(comb), nrow = nrow(comb)) + mat2 <- matrix(ncol = ncol(comb), nrow = nrow(comb)) + + # Write a loop to iterate through each cell of comb + for (i in seq_len(nrow(comb))) { + for (j in seq_len(ncol(comb))) { + # Check if "*+*" is in the cell value + if (grepl("\\+", as.character(comb[i, j]))) { + # Split the cell value using the separator "*+*" + split_vals <- strsplit(as.character(comb[i, j]), split = "\\*\\+\\*")[[1]] + + # If "*+*" is found, split the cell value into two, assigning each part to the corresponding cell in mat1 and mat2 + mat1[i, j] <- split_vals[1] + mat2[i, j] <- split_vals[2] + + } else { + # If "*+*" is not found in the cell value, assign "" to the corresponding cells in mat1 and mat2 + mat1[i, j] <- "" + mat2[i, j] <- "" + } + } + } + + # Replacing NA values with empty string + mat1[is.na(mat1)] <- "" + mat2[is.na(mat2)] <- "" + + # Return the two matrices + list(mat1 = mat1, mat2 = mat2) +} + +#' Set formulas and priors +#' +#' @param splittedComb A list of two matrices containing the segmented formulas and priors +#' +#' @return A list of lists containing the segmented formulas and priors +setFormulasAndPriors <- function(splittedComb) { + mat1 <- splittedComb$mat1 + mat2 <- splittedComb$mat2 + + # Creating lists to hold all the lists + lists_seg <- vector("list", nrow(mat1)) + lists_prior <- vector("list", nrow(mat1)) + + # Looping to convert each string in the matrix to a formula and adding to the respective list. + + for (i in 1:nrow(mat1)) { + lists_seg[[i]] <- list() + lists_prior[[i]] <- list() + + for (j in 1:ncol(mat1)) { + if (mat1[i, j] != "") { + lists_seg[[i]] <- append(lists_seg[[i]], as.formula(mat1[i, j])) + + # For priors + if (mat2[i, j] != "") { + # first split string by commas corresponding to different priors + splits <- strsplit(mat2[i, j], split = ";")[[1]] + + for (k in 1:length(splits)) { + # split the string by = + split_str <- strsplit(splits[k], "=")[[1]] + + if (!is.na(split_str[1]) && !is.na(split_str[2])) { + lists_prior[[i]] <- append(lists_prior[[i]], trimws(split_str[2])) + names(lists_prior[[i]])[length(lists_prior[[i]])] <- trimws(split_str[1]) + } + } + } + } + } + + } + + list(lists_seg = lists_seg, lists_prior = lists_prior) +} + +#' Run mcp model +#' +#' @param lists A list of lists containing the segmented formulas and priors +#' @param ... Additional arguments to pass to mcp +#' +#' @return A list of mcp model fits +runMcp <- function(lists, ...) { + lists_seg <- lists$lists_seg + lists_prior <- lists$lists_prior + + # Loop through each list and run model + fit <- vector(mode = "list", length = length(lists_seg)) + + for (i in 1:length(lists_seg)) { + fit[[i]] <- mcp(model = lists_seg[[i]], prior = lists_prior[[i]], ...) + } + + fit +} + +#' Compare models using loo +#' +#' @param fit A list of mcp model fits +#' +#' @return A loo model comparison object +compareWithLoo <- function(fit) { + #Comparing models using loo + + #Define list + loo_model <- vector("list", length(fit)) + + for (i in 1:length(fit)) { + loo_model[[i]] <- loo(fit[[i]]) + } + + #Results of model comparison + loo_compare(loo_model) +} diff --git a/R/02_extractSavedModels.R b/R/02-extractSavedModels.R similarity index 100% rename from R/02_extractSavedModels.R rename to R/02-extractSavedModels.R diff --git a/R/02-matrixModule.R b/R/02-matrixModule.R new file mode 100644 index 0000000..c1f17a1 --- /dev/null +++ b/R/02-matrixModule.R @@ -0,0 +1,187 @@ +#' Matrix Module +#' +#' @param title title of the matrix UI +#' @param maxRows maximum number of rows +#' @param maxColumns maximum number of columns +#' @param defaultCellContent default cell content +#' @param exampleLabel example label +#' @rdname matrixServer +matrixUI <- function(id, + title = "Matrix", + maxRows = 10, + maxColumns = 5, + defaultCellContent = "", + exampleLabel = "Example Matrix") { + ns <- NS(id) + tagList( + if (!is.null(title)) + tags$h4(title) + else + NULL, + fluidRow( + column( + width = 2, + selectInput( + ns("rows"), + "No. of rows", + selected = 1, + choices = 1:maxRows + ) + ), + column( + width = 2, + selectInput( + ns("cols"), + "No. of columns", + selected = 1, + choices = 1:maxColumns + ) + ), + column( + width = 2, + selectInput( + ns("cellID"), + "Cell ID (row, column)", + selected = 1, + choices = 1:1 + ) + ), + column( + width = 3, + textInput(ns("cell"), "Cell content", value = defaultCellContent) + ), + column( + width = 1, + style = "margin-top: 1.75em;", + actionButton(ns("set"), "Set") + ), + column( + width = 2, + align = "right", + style = "margin-top: 1.75em;", + actionButton(ns("example"), exampleLabel) + ) + ), + # output matrix + tags$br(), + tableOutput(ns("outputMatrix")) + ) +} + +#' Matrix Server +#' +#' @param id namespace id +#' @param exampleFunction function to load example input +#' @param validateCellFunction function to validate the cell content +#' @param ... additional arguments to example function +matrixServer <- function(id, + exampleFunction, + validateCellFunction = NULL, + ...) { + if (is.null(validateCellFunction)) { + validateCellFunction <- function(x) + x + } + moduleServer(id, function(input, output, session) { + dataMatrix <- reactiveVal() + + observe({ + # define empty matrix with number of rows and columns inputs + req(input[["rows"]], input[["cols"]]) + nrow <- input[["rows"]] %>% as.numeric() + ncol <- input[["cols"]] %>% as.numeric() + new_matrix <- matrix( + data = "", + nrow = nrow, + ncol = ncol, + byrow = TRUE + ) + colnames(new_matrix) <- 1:ncol + rownames(new_matrix) <- 1:nrow + dataMatrix(new_matrix) + updateSelectInput(session, "cellID", choices = getCellChoices(nrow = nrow, ncol = ncol)) + }) %>% + bindEvent(list(input[["rows"]], input[["cols"]])) + + output$outputMatrix <- renderTable({ + validate(need( + dataMatrix(), + "Please click 'Set' first or load 'Example Priors'" + )) + dataMatrix() + }, rownames = TRUE) + + observe({ + req(input[["cellID"]], input[["cell"]], length(dataMatrix())) + # set new value + id <- as.numeric(input[["cellID"]]) + new_matrix <- dataMatrix() + + { + new_matrix[id] <- input[["cell"]] %>% + validateCellFunction() + } %>% + shinyTryCatch(errorTitle = "Error in syntax") + dataMatrix(new_matrix) + + # inc id + maxID <- length(dataMatrix()) + newID <- ifelse(id == maxID, 1, id + 1) + updateSelectInput(session, "cellID", selected = newID) + }) %>% + bindEvent(input[["set"]]) + + observe({ + newMatrix <- exampleFunction(...) + dataMatrix(newMatrix) + updateSelectInput(session, "cellID", choices = getCellChoices( + nrow = nrow(newMatrix), + ncol = ncol(newMatrix) + )) + updateTextInput(session, "cell", value = newMatrix[1]) + }) %>% + bindEvent(input[["example"]]) + + return(dataMatrix) + }) +} + +#' Get cell choices +#' +#' @param nrow Number of rows +#' @param ncol Number of columns +#' +#' @return A named vector of cell choices +getCellChoices <- function(nrow, ncol) { + getCellLabel <- function(cellID, matrixDim) { + cellInd <- arrayInd(cellID, matrixDim) + + sprintf("(%s, %s)", cellInd[1], cellInd[2]) + } + + choices <- 1:(nrow * ncol) + + # get ID labels + labels <- sapply(choices, getCellLabel, matrixDim = c(nrow, ncol)) + names(choices) <- labels + + choices +} + +#' Check formula syntax +#' +#' Check if the formula syntax is valid +#' +#' @param formula A formula to check +#' +#' @return A formula if valid, otherwise an error +validateFormula <- function(formula) { + formula_check <- try(as.formula(formula), silent = TRUE) + + if (inherits(formula_check, "try-error")) { + warning("No formula, invalid syntax. Please check your input!") + return(formula) + } else { + return(formula) + } +} diff --git a/R/03-breakPointDetection.R b/R/03-breakPointDetection.R new file mode 100644 index 0000000..84af4f4 --- /dev/null +++ b/R/03-breakPointDetection.R @@ -0,0 +1,282 @@ +#' Break Point Detection UI +#' +#' UI of the module +#' +#' @rdname breakPointDetectionServer +breakPointDetectionUI <- function(id) { + ns <- NS(id) + tagList(tags$br(), + tabsetPanel( + id = ns("breakPointTabs"), + tabPanel("MCP Lists", formulasUI(ns("formulas"))), + tabPanel( + "MCP Model", + mcpUI(ns("mcp")), + tags$h4("Comparing models using loo"), + verbatimTextOutput(ns("compareWithLoo")) %>% withSpinner(color = "#20c997") + ) + ), + tags$br()) +} + +#' MCP UI +#' +#' @param id The module id +mcpUI <- function(id) { + ns <- NS(id) + tagList(tags$br(), + fluidRow( + column( + 3, + numericInput(ns("adapt"), "Burn in length", value = 5000), + helpText("Increase for better conversion (makes the run slower).") + ), + column(3, numericInput( + ns("chains"), + "Number of chains", + value = 3, + min = 1 + )), + column( + 3, + numericInput( + ns("iter"), + "Number of iterations", + value = 3000, + min = 1 + ) + ), + column( + 3, + align = "right", + style = "margin-top: 1.75em;", + #actionButton(ns("loadExampleDf"), "Load Example Data"), + actionButton(ns("apply"), "Run MCP Model", disabled = TRUE) + ) + ), + tags$br()) +} + + +#' Break Point Detection Server +#' +#' Server function of the module +#' +#' @param id The module id +#' @param plotData The reactive plot data +breakPointDetectionServer <- function(id, plotData) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + mcpData <- reactiveVal() + mcpFit <- reactiveVal() + + # load data ---- + observe({ + req(nrow(plotData()) > 0) + # select relevant columns + newData <- plotData()[, c("time", "median")] + # rename columns + colnames(newData) <- c("x", "y") + # remove rows with NA + newData <- na.omit(newData) + + mcpData(newData) + }) %>% bindEvent(plotData()) + + # observe({ + # mcpData(read.csv(file.path("data", "example_breakPoints.csv"))) + # }) %>% + # bindEvent(input[["mcp-loadExampleDf"]]) + + # Formulas tab ---- + formulasAndPriors <- formulasServer("formulas") + + observe({ + req(formulasAndPriors(), mcpData()) + + # enable the 'Run Model' button + shinyjs::enable(ns("mcp-apply"), asis = TRUE) + #updateActionButton(session, "mcp-apply", disabled = FALSE) # not working with current version in docker + }) %>% bindEvent(formulasAndPriors(), mcpData()) + + # Model tab ---- + + ## run the mcp model ---- + observe({ + mcpFit( + runMcp( + lists = formulasAndPriors(), + data = mcpData(), + adapt = input[["mcp-adapt"]], + chains = input[["mcp-chains"]], + iter = input[["mcp-iter"]] + ) + ) %>% + shinyTryCatch(errorTitle = "Error in fitting mcp model", warningTitle = "Warning in fitting mcp model") %>% + withProgress(message = "Fitting MCP model...", value = 0.5) + }) %>% + bindEvent(input[["mcp-apply"]]) + + ## render the output of comparing with loo ---- + output$compareWithLoo <- renderPrint({ + validate(need( + formulasAndPriors(), + "Please 'Create MCP Lists' and 'Run MCP Model' first" + )) + validate(need(mcpData(), "Please load 'Plot Data' and 'Run MCP Model' first")) + validate(need(mcpFit(), "Please 'Run MCP Model' first")) + mcpFit() %>% + compareWithLoo() %>% + shinyTryCatch(errorTitle = "Error in comparing with loo", warningTitle = "Warning in comparing with loo") + }) + }) +} + +#' Formulas UI +#' +#' @rdname formulasServer +formulasUI <- function(id) { + ns <- NS(id) + tagList( + tags$br(), + fluidRow(column(8, tags$h4("Segments")), column( + 4, align = "right", infoButtonUI(ns("formulaInfo"), label = "Segment Description") + )), + matrixUI( + ns("segments"), + title = NULL, + defaultCellContent = "y ~ 1 + x", + exampleLabel = "Example Segments" + ), + tags$br(), + fluidRow(column(8, tags$h4("Priors")), column( + 4, align = "right", infoButtonUI(ns("PriorInfo"), label = "Prior Description") + )), + matrixUI( + ns("priors"), + title = NULL, + defaultCellContent = "x_1 = dunif(-4, -0.5);", + exampleLabel = "Example Priors" + ), + tags$br(), + fluidRow( + column(10, verbatimTextOutput(ns("mcpFormulas"))), + column( + 2, + align = "right", + actionButton(ns("apply"), "Create MCP Lists", disabled = TRUE) + ) + ) + ) +} + +#' Formulas Server +#' +#' @param id The module id +formulasServer <- function(id) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + formulasAndPriors <- reactiveVal() + + segmentsMatrix <- matrixServer( + "segments", + exampleFunction = readExampleMatrix, + validateCellFunction = validateFormula, + path = file.path("data", "example_breakPointSegments.csv") + ) + priorsMatrix <- matrixServer( + "priors", + exampleFunction = readExampleMatrix, + path = file.path("data", "example_breakPointPriors.csv") + ) + + infoButtonServer( + id = "formulaInfo", + title = "'Formula' (segment) description", + text = "The 'formula' argument in mcp defines the model structure. It specifies the relationship between + variables and the change points in your data. The formula should be written similarly to formulas + in base R, with additional support for change points.", + link = "https://lindeloev.github.io/mcp/articles/formulas.html" + ) + + infoButtonServer( + id = "PriorInfo", + title = "'Prior' description", + text = "The 'prior' argument in mcp specifies the prior distributions for the parameters in your model. + It should be a named list where the names correspond to the model parameters, + and the values are prior distributions in JAGS notation. If you don't specify a prior for a parameter, + mcp will use a default weakly informative prior.", + link = "https://lindeloev.github.io/mcp/articles/priors.html" + ) + + observe({ + req(segmentsMatrix(), priorsMatrix()) + + # enable the 'Create MCP Formulas' button + shinyjs::enable(ns("apply"), asis = TRUE) + #updateActionButton(session, "apply", disabled = FALSE) # not working with current version in docker + }) %>% bindEvent(list(segmentsMatrix(), priorsMatrix())) + + observe({ + newFormulasAndPriors <- getComb(segments = segmentsMatrix(), priors = priorsMatrix()) %>% + cleanComb() %>% + splitComb() %>% + setFormulasAndPriors() %>% + shinyTryCatch(errorTitle = "Error in creating MCP lists", warningTitle = "Warning in creating MCP lists") + + formulasAndPriors(newFormulasAndPriors) + }) %>% + bindEvent(input[["apply"]]) + + ## render the output of creating formulas ---- + output$mcpFormulas <- renderPrint({ + validate(need(formulasAndPriors(), "Please 'Set' Segments and Priors first ...")) + formulasAndPriors() + }) + + return(formulasAndPriors) + }) +} + +#' Info Button UI +#' +#' @param label The label of the button +#' @rdname infoButtonServer +infoButtonUI <- function(id, label = "Description") { + ns <- NS(id) + tagList(actionButton(ns("show_info"), icon = icon("info-circle"), label)) +} + +#' Info Button Server +#' +#' @param id The module id +#' @param title The title of the modal +#' @param text The text to display in the modal +#' @param link The link to the documentation +infoButtonServer <- function(id, + title = "Description", + text, + link = NULL) { + moduleServer(id, function(input, output, session) { + observe({ + showModal( + modalDialog( + title = title, + p(text), + # Add a hyperlink to the github help page + if (!is.null(link)) { + p( + "For more details, visit the", + a("documentation", href = link, target = "_blank"), + "." + ) + } else + NULL, + footer = modalButton("Close"), + easyClose = TRUE + ) + ) + }) %>% + bindEvent(input$show_info) + }) +} diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index 90085b4..b6767fd 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -1,3 +1,8 @@ +#' Message for no models to plot +messageNoModelsToPlot <- function() { + "Select 'Model(s) / Individual(s) to display' and press 'Draw Plot' first ..." +} + # We need a separate namespace for formatting inputs for the model down- and upload. # With grepl we can get the relevant inputs for formatting @@ -9,120 +14,152 @@ #' @export timePlotFormattingUI <- function(id) { ns <- NS(id) + + defaultChoices <- "" + names(defaultChoices) <- messageNoModelsToPlot() + tagList( - tags$h4("Time Plot"), - plotOutput(ns("plotTime")) %>% withSpinner(color = "#20c997"), - tags$br(), + # Time Plot ---- fluidRow( - column(8, - selectizeInput(ns("plotTimeModels"), "Display Models / Individuals", - choices = c("Fit / import a model ..." = ""), + column(3, tags$h3("Time Plot")), + column(6, + selectizeInput(ns("plotTimeModels"), "Model(s) / Individual(s) to display", + choices = c("Fit or import a model ..." = ""), multiple = TRUE, selected = "", width = "100%")), - column(1, - align = "right", - style = "margin-top: 1.2em;", - actionButton(ns("applyFormatToTimePlot"), "Apply")), - column(2, - selectizeInput(ns("formatTimePlot"), "Format Model / Individual", - choices = c("Fit / import a model ..." = ""), - width = "100%")), - column(1, + column(3, align = "right", style = "margin-top: 1.2em;", - actionButton(ns("applyFormatToTimePlotModel"), "Apply")) + actionButton(ns("applyFormatToTimePlot"), "Draw Plot"), + plotExportButton(ns("exportCredIntTimePlot"))) ), tags$br(), - fluidRow( - column(3, - tags$h4("Data"), - radioButtons(ns("deriv"), - "Type", - choices = c("Absolute values" = "1", "First derivate" = "2")), - sliderInput(ns("modCredInt"), - "Credibility interval:", - min = 0, - max = .99, - value = .8, - step = .05, - width = "100%"), - tags$br(), - sliderInput(ns("alphaU"), - "Transparency of uncertainty region", - min = 0, - max = 1, - value = 0.1, - step = 0.05), - sliderInput(ns("alphaL"), - "Transparency of points / lines", - min = 0, - max = 1, - value = 0.9, - step = 0.05), - tags$br(), - fluidRow( - column(6, selectInput(inputId = ns("legendPosition"), - label = "Legend position", - choices = c("right", "top", "bottom", "left"))), - column(6, - style = "margin-top: 1.5em;", - checkboxInput(inputId = ns("hideLegend"), - label = "Hide legend", - value = FALSE)) - ) - ), - column(3, - shinyTools::plotTitlesUI( - id = ns("plotLabels"), - title = "Text", - type = "ggplot", - initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], - xAxisText = config()[["defaultIntervalTimePlotText"]]) - ) + plotOutput(ns("plotTime")) %>% withSpinner(color = "#20c997"), + tags$br(), + tabsetPanel( + id = ns("plotDataTabs"), + tabPanel( + # Format Plot ---- + "Format Model / Individual", + value = "formatPlotTab", + tags$br(), + fluidRow( + column(3, + style = "margin-top: 1.2em;", + tags$h4("Format Time Plot")), + column(6, + selectizeInput(ns("formatTimePlot"), "'Apply' formatting for Model / Individual:", + choices = defaultChoices, + width = "100%")), + column(3, + align = "right", + style = "margin-top: 1.2em;", + actionButton(ns("applyFormatToTimePlotModel"), "Apply"), + actionButton(ns("resetFormatTimePlotModel"), "Reset Format")) + ), + tags$br(), + fluidRow( + column(3, + tags$h4("Data"), + radioButtons(ns("deriv"), + "Type", + choices = c("Absolute values" = "1", "First derivate" = "2")), + sliderInput(ns("modCredInt"), + "Credibility interval:", + min = 0, + max = .99, + value = .8, + step = .05, + width = "100%"), + tags$br(), + sliderInput(ns("alphaU"), + "Transparency of uncertainty region", + min = 0, + max = 1, + value = 0.1, + step = 0.05), + sliderInput(ns("alphaL"), + "Transparency of points / lines", + min = 0, + max = 1, + value = 0.9, + step = 0.05), + tags$br(), + plotLegendUI(ns("legend")) + ), + column(3, + shinyTools::plotTitlesUI( + id = ns("plotLabels"), + title = "Text", + type = "ggplot", + initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], + xAxisText = config()[["defaultIntervalTimePlotText"]]) + ) + ), + column(3, + shinyTools::plotRangesUI( + id = ns("plotRanges"), + title = "Axis", + initRanges = list(xAxis = config()[["plotRange"]], + yAxis = config()[["plotRange"]]) + ), + conditionalPanel( + ns = ns, + condition = "!input['plotRanges-fromData'] & input['plotRanges-labelName'] == 'xAxis'", + checkboxInput(inputId = ns("extendLabels"), + label = "Extend x-axis labels to full range", + value = FALSE) + ), + tags$br(), tags$br(), + selectizeInput(ns("secAxisModel"), "Add a new secondary y axis", + choices = c("Choose one Model / Individual ..." = "")), + helpText("The first element of 'Model(s) / Individual(s) to display' is always used for the first (left) axis."), + conditionalPanel( + ns = ns, + condition = "input.secAxisModel != ''", + fluidRow( + column(6, textInput(ns("secAxisText"), label = "Title", + value = "", + placeholder = "Custom title ...")), + column(6, colourInput(ns("secAxisColor"), + label = "Title color", + value = config()[["defaultIntervalTimePlotTitle"]][["color"]])) + ), + conditionalPanel( + ns = ns, + condition = "!input['plotRanges-fromData'] & input['plotRanges-labelName'] == 'yAxis'", + fluidRow( + column(6, numericInput(ns("secAxisYMin"), "Min", value = 0)), + column(6, numericInput(ns("secAxisYMax"), "Max", value = 1)) + ) + ) + ), + + ), + column(3, + shinyTools::plotPointsUI(id = ns("pointStyle"), + title = "Points / Lines", + initStyle = config()[["defaultPointStyle"]]) + ) + ) ), - column(3, - shinyTools::plotRangesUI( - id = ns("plotRanges"), - title = "Axis", - initRanges = list(xAxis = config()[["plotRange"]], - yAxis = config()[["plotRange"]]) - ), - checkboxInput(inputId = ns("extendLabels"), - label = "Extend x-axis labels to full range", - value = FALSE), - tags$br(), - selectizeInput(ns("secAxisModel"), "Add a new secondary y axis", - choices = c("Choose one Model / Individual ..." = "")), - helpText("The first element of 'Display Models / Individuals' is always used for the first (left) axis."), - conditionalPanel( - ns = ns, - condition = "input.secAxisModel != ''", - fluidRow( - column(6, textInput(ns("secAxisText"), label = "Title", - value = "", - placeholder = "Custom title ...")), - column(6, colourInput(ns("secAxisColor"), - label = "Title color", - value = config()[["defaultIntervalTimePlotTitle"]][["color"]])) - )), - + tabPanel( + # Plot Data ---- + "Plot Data", + value = "plotDataTab", + tags$br(), + tableOutput(ns("plotData")), + fluidRow(column(12, align = "right", dataExportButton(ns("exportCredIntTimeData")))) ), - column(3, - shinyTools::plotPointsUI(id = ns("pointStyle"), - title = "Points / Lines", - initStyle = config()[["defaultPointStyle"]]) + tabPanel( + # Break Point Detection ---- + "Break point detection", + value = "breakPointTab", + breakPointDetectionUI(ns("breakPointDetection")) ) - ), - fluidRow(column(12, - style = "margin-top: -1em;", - align = "right", - plotExportButton(ns("exportCredIntTimePlot")))), - tags$hr(), - tags$h4("Plot Data"), - tableOutput(ns("plotData")), - tags$br(), - fluidRow(column(12, align = "right", dataExportButton(ns("exportCredIntTimeData")))), + ) + , tags$br() ) } @@ -179,8 +216,13 @@ timePlotFormattingServer <- function(id, savedModels) { observe({ # choices for formatting of lines and points - updateSelectizeInput(session, "formatTimePlot", - choices = input[["plotTimeModels"]]) + if (is.null(input[["plotTimeModels"]])) { + newChoices <- "" + names(newChoices) <- messageNoModelsToPlot() + } else { + newChoices <- input[["plotTimeModels"]] + } + updateSelectizeInput(session, "formatTimePlot", choices = newChoices) # choices for secondary axis nDisplayedModels <- length(input[["plotTimeModels"]]) @@ -195,7 +237,7 @@ timePlotFormattingServer <- function(id, savedModels) { choices = c("Choose one Model / Individual ..." = "")) } }) %>% - bindEvent(input[["plotTimeModels"]]) + bindEvent(input[["plotTimeModels"]], ignoreNULL = FALSE, ignoreInit = TRUE) observe({ req(input[["formatTimePlot"]]) @@ -228,14 +270,15 @@ timePlotFormattingServer <- function(id, savedModels) { alertStyle = "shinyalert") }) + # render plot data table ---- output$plotData <- renderTable({ - validate(need(input[["plotTimeModels"]], - "Choose at least one element from 'Display Models / Individuals' ..."), + validate(need(input[["plotTimeModels"]], messageNoModelsToPlot()), need(nrow(extractedPlotDataDF()) > 0, "No data available for selected models ...")) extractedPlotDataDF() }) + # export plot data ---- plotDataExport <- reactiveVal() observe({ @@ -246,36 +289,40 @@ timePlotFormattingServer <- function(id, savedModels) { dataExportServer("exportCredIntTimeData", reactive(function() {plotDataExport()})) - # set default: no rescaling - rescalingSecAxis <- reactiveVal(list(scale = 1, center = 0)) - observe({ - req(input[["plotTimeModels"]]) - + rescalingSecAxis <- reactive({ plotData <- extractedPlotDataDF() %>% na.omit() + + if (length(input[["secAxisModel"]]) > 0 && input[["secAxisModel"]] != "") { # get index for filter index <- plotData$individual == input[["secAxisModel"]] # get rescaling parameters req(nrow(plotData[index, ]) > 0) - # update title of second axis - updateTextInput(session, "secAxisText", - value = sprintf("%s Estimate", input[["secAxisModel"]])) - + if (input[["plotRanges-fromData"]] && input[["plotRanges-labelName"]] == "yAxis") { + # use data based limits + oldLimits <- getYRange(plotData) %>% unlist() + newLimits <- getYRange(plotData[index, ]) %>% unlist() + } else { + # use custom limits + oldLimits <- c(ymin = input[["plotRanges-min"]], ymax = input[["plotRanges-max"]]) + newLimits <- c(ymin = input[["secAxisYMin"]], ymax = input[["secAxisYMax"]]) + } ## use always data based newYLimits, we only set global limits not(!) per model - rescaling <- getRescaleParams(oldLimits = getYRange(plotData) %>% unlist(), - newLimits = getYRange(plotData[index, ]) %>% unlist(), - secAxis = TRUE) - rescalingSecAxis(rescaling) - - - }) %>% - bindEvent(input[["secAxisModel"]]) + res <- getRescaleParams(oldLimits = oldLimits, + newLimits = newLimits, + secAxis = TRUE) + } else { + # set default: no rescaling + list(scale = 1, center = 0) + } + }) - observe({ - req(savedModels(), input[["plotTimeModels"]]) - p <- extractedPlotDataDF() %>% + legend <- plotLegendServer("legend") + + newPlot <- reactive({ + extractedPlotDataDF() %>% na.omit() %>% rescaleSecondAxisData(individual = input[["secAxisModel"]], rescaling = rescalingSecAxis()) %>% @@ -295,32 +342,55 @@ timePlotFormattingServer <- function(id, savedModels) { pointStyleList = pointStyleList, alphaL = input[["alphaL"]], alphaU = input[["alphaU"]], - legendName = plotTexts[["legendTitle"]][["text"]]) %>% + legendName = plotTexts[["legendTitle"]][["text"]] + ) %>% setSecondYAxis(rescaling = rescalingSecAxis(), titleFormat = plotTexts[["yAxisTitle"]], textFormat = plotTexts[["yAxisText"]], - yAxisLabel = input[["secAxisText"]], + yAxisLabel = input[["secAxisText"]] %>% + getSecondAxisTitle(secAxisModel = input[["secAxisModel"]]), yAxisTitleColor = input[["secAxisColor"]]) %>% - setLegendPosition(hideLegend = input[["hideLegend"]], - legendPosition = input[["legendPosition"]]) - + shinyTools::formatLegendOfGGplot(legend = legend) + }) + + observe({ + req(savedModels(), input[["plotTimeModels"]]) + p <- newPlot() formattedPlot(p) }) %>% bindEvent(list(input[["applyFormatToTimePlot"]], input[["applyFormatToTimePlotModel"]])) + observe({ + req(savedModels(), input[["plotTimeModels"]]) + + modelNames <- names(savedModels()) + defaultStyle <- pointStyleList %>% + reactiveValuesToList() %>% + getDefaultPointFormatForModels(modelNames = modelNames) + pointStyleList[[input[["formatTimePlot"]]]] <- defaultStyle[[input[["formatTimePlot"]]]] + + p <- newPlot() + formattedPlot(p) + }) %>% + bindEvent(input[["resetFormatTimePlotModel"]]) + + # render plot ---- output$plotTime <- renderPlot({ - validate(need(formattedPlot(), "Choose at least one element from 'Display Models / Individuals' and press 'Apply' ...")) + validate(need(formattedPlot(), messageNoModelsToPlot())) formattedPlot() }) + # export plot ---- plotExportServer("exportCredIntTimePlot", plotFun = reactive(function() formattedPlot()), filename = sprintf("%s_Credibility_Intervals_Over_Time", gsub("-", "", Sys.Date())) ) - return(reactive(formattedPlot())) + # Break point detection ---- + + breakPointDetectionServer(id = "breakPointDetection", plotData = extractedPlotDataDF) }) } diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 4824875..dc51fa6 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -22,20 +22,23 @@ #' scale_x_continuous scale_y_continuous sec_axis theme #' @importFrom htmltools save_html #' @importFrom jsonlite toJSON +#' @importFrom loo loo loo_compare #' @importFrom magrittr %>% +#' @importFrom mcp mcp #' @importFrom openxlsx write.xlsx #' @importFrom parallel detectCores #' @importFrom rlang .data #' @importFrom rstan sampling extract #' @importFrom shinycssloaders withSpinner -#' @importFrom shinyjs alert -#' @importFrom shinyTools dataExportButton dataExportServer formatPointsOfGGplot -#' formatRangesOfGGplot formatTitlesOfGGplot plotExportButton plotExportServer -#' plotPointsServer plotPointsUI plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI +#' @importFrom shinyjs alert enable runjs +#' @importFrom shinyTools dataExportButton dataExportServer +#' formatLegendOfGGplot formatPointsOfGGplot formatRangesOfGGplot formatTitlesOfGGplot +#' plotExportButton plotExportServer plotLegendServer plotLegendUI plotPointsServer plotPointsUI +#' plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI #' shinyTryCatch #' @importFrom shinyWidgets pickerInput updatePickerInput -#' @importFrom stats approx dnorm lm median na.omit quantile sd -#' @importFrom utils write.csv write.table combn +#' @importFrom stats approx as.formula dnorm lm median na.omit quantile sd +#' @importFrom utils read.csv write.csv write.table combn #' @importFrom yaml read_yaml #' @references #' Stan Development Team (NA). RStan: the R interface to Stan. R package version NA. http://mc-stan.org diff --git a/inst/app/data/example_breakPointPriors.csv b/inst/app/data/example_breakPointPriors.csv new file mode 100644 index 0000000..d01fcc1 --- /dev/null +++ b/inst/app/data/example_breakPointPriors.csv @@ -0,0 +1,4 @@ +"V1","V2","V3","V4" +"x_1 = dunif(-4, -0.5);","","","" +"","","","" +"","","","" diff --git a/inst/app/data/example_breakPointSegments.csv b/inst/app/data/example_breakPointSegments.csv new file mode 100644 index 0000000..8bafe3a --- /dev/null +++ b/inst/app/data/example_breakPointSegments.csv @@ -0,0 +1,4 @@ +"V1","V2","V3","V4" +"y ~ 1 + x","y ~ 1 ~ 0 + x","y ~ 1 ~ 0 + x","y ~ 1 ~ 0 + x" +"y ~ 1 + x","","","" +"","","","" diff --git a/inst/app/data/example_breakPoints.csv b/inst/app/data/example_breakPoints.csv new file mode 100644 index 0000000..4760dbd --- /dev/null +++ b/inst/app/data/example_breakPoints.csv @@ -0,0 +1,15 @@ +x,y +0.25,12.53 +0.75,12.03 +1.25,11.54 +1.75,11.5 +2.25,11.5 +2.75,11.53 +3.25,11.67 +3.75,11.75 +4.75,11.99 +5.25,12.01 +6.75,11.68 +7.25,11.66 +7.75,11.66 +8.25,11.68 diff --git a/inst/app/server.R b/inst/app/server.R index 487db0b..228e38e 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -535,8 +535,7 @@ shinyServer(function(input, output, session) { }) # create plotTime ---- - formattedTimePlot <- timePlotFormattingServer(id = "timePlotFormat", - savedModels = savedModels) + timePlotFormattingServer(id = "timePlotFormat", savedModels = savedModels) observe({ updateNumericInput(session, "from", diff --git a/man/breakPointDetectionServer.Rd b/man/breakPointDetectionServer.Rd new file mode 100644 index 0000000..a2e97f8 --- /dev/null +++ b/man/breakPointDetectionServer.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-breakPointDetection.R +\name{breakPointDetectionUI} +\alias{breakPointDetectionUI} +\alias{breakPointDetectionServer} +\title{Break Point Detection UI} +\usage{ +breakPointDetectionUI(id) + +breakPointDetectionServer(id, plotData) +} +\arguments{ +\item{id}{The module id} + +\item{plotData}{The reactive plot data} +} +\description{ +UI of the module + +Server function of the module +} diff --git a/man/cleanComb.Rd b/man/cleanComb.Rd new file mode 100644 index 0000000..29deb0e --- /dev/null +++ b/man/cleanComb.Rd @@ -0,0 +1,18 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{cleanComb} +\alias{cleanComb} +\title{Clean the comb matrix} +\usage{ +cleanComb(comb) +} +\arguments{ +\item{comb}{A matrix containing the segmented formulas and priors} +} +\value{ +A cleaned matrix +} +\description{ +Check each row for '*+*' and if found replace that cell and all following cells in the row with "". +Remove Blank Rows. Remove duplicate rows. +} diff --git a/man/combineDataAndModelOutputs.Rd b/man/combineDataAndModelOutputs.Rd index 979eb97..656c5f6 100644 --- a/man/combineDataAndModelOutputs.Rd +++ b/man/combineDataAndModelOutputs.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02_extractSavedModels.R +% Please edit documentation in R/02-extractSavedModels.R \name{combineDataAndModelOutputs} \alias{combineDataAndModelOutputs} \title{Combine Data and Model Outputs} diff --git a/man/compareWithLoo.Rd b/man/compareWithLoo.Rd new file mode 100644 index 0000000..42169b3 --- /dev/null +++ b/man/compareWithLoo.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{compareWithLoo} +\alias{compareWithLoo} +\title{Compare models using loo} +\usage{ +compareWithLoo(fit) +} +\arguments{ +\item{fit}{A list of mcp model fits} +} +\value{ +A loo model comparison object +} +\description{ +Compare models using loo +} diff --git a/man/extractModelOutputs.Rd b/man/extractModelOutputs.Rd index b11b08a..73c42fd 100644 --- a/man/extractModelOutputs.Rd +++ b/man/extractModelOutputs.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02_extractSavedModels.R +% Please edit documentation in R/02-extractSavedModels.R \name{extractModelOutputs} \alias{extractModelOutputs} \title{Extract Model Outputs} diff --git a/man/extractSavedModels.Rd b/man/extractSavedModels.Rd index 517b077..80a16ac 100644 --- a/man/extractSavedModels.Rd +++ b/man/extractSavedModels.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02_extractSavedModels.R +% Please edit documentation in R/02-extractSavedModels.R \name{extractSavedModels} \alias{extractSavedModels} \title{Extract Saved Models} diff --git a/man/formulasServer.Rd b/man/formulasServer.Rd new file mode 100644 index 0000000..6fdbb26 --- /dev/null +++ b/man/formulasServer.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-breakPointDetection.R +\name{formulasUI} +\alias{formulasUI} +\alias{formulasServer} +\title{Formulas UI} +\usage{ +formulasUI(id) + +formulasServer(id) +} +\arguments{ +\item{id}{The module id} +} +\description{ +Formulas UI + +Formulas Server +} diff --git a/man/getCellChoices.Rd b/man/getCellChoices.Rd new file mode 100644 index 0000000..6004e76 --- /dev/null +++ b/man/getCellChoices.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-matrixModule.R +\name{getCellChoices} +\alias{getCellChoices} +\title{Get cell choices} +\usage{ +getCellChoices(nrow, ncol) +} +\arguments{ +\item{nrow}{Number of rows} + +\item{ncol}{Number of columns} +} +\value{ +A named vector of cell choices +} +\description{ +Get cell choices +} diff --git a/man/getComb.Rd b/man/getComb.Rd new file mode 100644 index 0000000..8729ae7 --- /dev/null +++ b/man/getComb.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{getComb} +\alias{getComb} +\title{Get the comb matrix} +\usage{ +getComb(segments, priors) +} +\arguments{ +\item{segments}{A matrix containing the segmented formulas} + +\item{priors}{A matrix containing the priors} +} +\value{ +A matrix containing the segmented formulas and priors +} +\description{ +Get the combined matrix of segments and priors. +Process the segments and priors matrices, identify relevant rows and columns, concatenate them, +and generate possible cell combinations while ensuring data consistency. +} diff --git a/man/infoButtonServer.Rd b/man/infoButtonServer.Rd new file mode 100644 index 0000000..62b5806 --- /dev/null +++ b/man/infoButtonServer.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-breakPointDetection.R +\name{infoButtonUI} +\alias{infoButtonUI} +\alias{infoButtonServer} +\title{Info Button UI} +\usage{ +infoButtonUI(id, label = "Description") + +infoButtonServer(id, title = "Description", text, link = NULL) +} +\arguments{ +\item{id}{The module id} + +\item{label}{The label of the button} + +\item{title}{The title of the modal} + +\item{text}{The text to display in the modal} + +\item{link}{The link to the documentation} +} +\description{ +Info Button UI + +Info Button Server +} diff --git a/man/matrixServer.Rd b/man/matrixServer.Rd new file mode 100644 index 0000000..e3079a9 --- /dev/null +++ b/man/matrixServer.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-matrixModule.R +\name{matrixUI} +\alias{matrixUI} +\alias{matrixServer} +\title{Matrix Module} +\usage{ +matrixUI( + id, + title = "Matrix", + maxRows = 10, + maxColumns = 5, + defaultCellContent = "", + exampleLabel = "Example Matrix" +) + +matrixServer(id, exampleFunction, validateCellFunction = NULL, ...) +} +\arguments{ +\item{id}{namespace id} + +\item{title}{title of the matrix UI} + +\item{maxRows}{maximum number of rows} + +\item{maxColumns}{maximum number of columns} + +\item{defaultCellContent}{default cell content} + +\item{exampleLabel}{example label} + +\item{exampleFunction}{function to load example input} + +\item{validateCellFunction}{function to validate the cell content} + +\item{...}{additional arguments to example function} +} +\description{ +Matrix Module + +Matrix Server +} diff --git a/man/mcpUI.Rd b/man/mcpUI.Rd new file mode 100644 index 0000000..d49b573 --- /dev/null +++ b/man/mcpUI.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-breakPointDetection.R +\name{mcpUI} +\alias{mcpUI} +\title{MCP UI} +\usage{ +mcpUI(id) +} +\arguments{ +\item{id}{The module id} +} +\description{ +MCP UI +} diff --git a/man/messageNoModelsToPlot.Rd b/man/messageNoModelsToPlot.Rd new file mode 100644 index 0000000..65c9ef1 --- /dev/null +++ b/man/messageNoModelsToPlot.Rd @@ -0,0 +1,11 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-timePlotFormatting.R +\name{messageNoModelsToPlot} +\alias{messageNoModelsToPlot} +\title{Message for no models to plot} +\usage{ +messageNoModelsToPlot() +} +\description{ +Message for no models to plot +} diff --git a/man/readExampleMatrix.Rd b/man/readExampleMatrix.Rd new file mode 100644 index 0000000..09ca641 --- /dev/null +++ b/man/readExampleMatrix.Rd @@ -0,0 +1,14 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{readExampleMatrix} +\alias{readExampleMatrix} +\title{Get Example Matrix} +\usage{ +readExampleMatrix(path) +} +\arguments{ +\item{path}{path to example matrix} +} +\description{ +Get Example Matrix +} diff --git a/man/removeModelOutputs.Rd b/man/removeModelOutputs.Rd index 0b7a2b2..b8bbe0c 100644 --- a/man/removeModelOutputs.Rd +++ b/man/removeModelOutputs.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02_extractSavedModels.R +% Please edit documentation in R/02-extractSavedModels.R \name{removeModelOutputs} \alias{removeModelOutputs} \title{Remove Model Outputs} diff --git a/man/runMcp.Rd b/man/runMcp.Rd new file mode 100644 index 0000000..9e36fe6 --- /dev/null +++ b/man/runMcp.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{runMcp} +\alias{runMcp} +\title{Run mcp model} +\usage{ +runMcp(lists, ...) +} +\arguments{ +\item{lists}{A list of lists containing the segmented formulas and priors} + +\item{...}{Additional arguments to pass to mcp} +} +\value{ +A list of mcp model fits +} +\description{ +Run mcp model +} diff --git a/man/setFormulasAndPriors.Rd b/man/setFormulasAndPriors.Rd new file mode 100644 index 0000000..f660bea --- /dev/null +++ b/man/setFormulasAndPriors.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{setFormulasAndPriors} +\alias{setFormulasAndPriors} +\title{Set formulas and priors} +\usage{ +setFormulasAndPriors(splittedComb) +} +\arguments{ +\item{splittedComb}{A list of two matrices containing the segmented formulas and priors} +} +\value{ +A list of lists containing the segmented formulas and priors +} +\description{ +Set formulas and priors +} diff --git a/man/splitComb.Rd b/man/splitComb.Rd new file mode 100644 index 0000000..7760078 --- /dev/null +++ b/man/splitComb.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{splitComb} +\alias{splitComb} +\title{Split the comb matrix into two matrices} +\usage{ +splitComb(comb) +} +\arguments{ +\item{comb}{A matrix containing the segmented formulas and priors} +} +\value{ +A list of two matrices +} +\description{ +Split the comb matrix into two matrices based on the separator '*+*'. +} diff --git a/man/validateFormula.Rd b/man/validateFormula.Rd new file mode 100644 index 0000000..efce95d --- /dev/null +++ b/man/validateFormula.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-matrixModule.R +\name{validateFormula} +\alias{validateFormula} +\title{Check formula syntax} +\usage{ +validateFormula(formula) +} +\arguments{ +\item{formula}{A formula to check} +} +\value{ +A formula if valid, otherwise an error +} +\description{ +Check if the formula syntax is valid +} diff --git a/tests/testthat/test-breakPoints.R b/tests/testthat/test-breakPoints.R new file mode 100644 index 0000000..8c6870e --- /dev/null +++ b/tests/testthat/test-breakPoints.R @@ -0,0 +1,98 @@ +testthat::test_that("detectBreakPoints", { + # load test data + df <- testthat::test_path("testdata/test_detectBreakPoints.csv") %>% + read.csv() + + segmentsMatrix <- matrix( + c( + "d15N ~ 1 + time", + "d15N ~ 1 ~ 0 + time", + "d15N ~ 1 ~ 0 + time", + "d15N ~ 1 ~ 0 + time", + "d15N ~ 1 + time", + "", + "", + "", + "", + "", + "", + "" + ), + nrow = 3, + ncol = 4, + byrow = TRUE + ) + + priorsMatrix <- matrix( + c( + "time_1 = dunif(-4, -0.5);", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ), + nrow = 3, + ncol = 4, + byrow = TRUE + ) + + comb <- getComb(segments = segmentsMatrix, priors = priorsMatrix) + + testthat::expect_equal(comb[1, 1], "d15N ~ 1 + time*+*time_1 = dunif(-4, -0.5);") + testthat::expect_equal(dim(comb), c(16, 4)) + testthat::expect_equal(comb %>% cleanComb() %>% dim(), c(8, 4)) + + twoMatrices <- comb %>% cleanComb() %>% splitComb() + + testthat::expect_equal(twoMatrices$mat1[1, 1], "d15N ~ 1 + time") + testthat::expect_equal(twoMatrices$mat2[1, 1], "time_1 = dunif(-4, -0.5);") + testthat::expect_equal(twoMatrices$mat1 %>% dim(), c(8, 4)) + testthat::expect_equal(twoMatrices$mat2 %>% dim(), c(8, 4)) + + lists <- comb %>% + cleanComb() %>% + splitComb() %>% + setFormulasAndPriors() + + res <- runMcp(lists = lists, data = df) + + testthat::expect_equal( + compareWithLoo(res) %>% colnames() %>% suppressWarnings(), + c( + "elpd_diff", + "se_diff", + "elpd_loo", + "se_elpd_loo", + "p_loo", + "se_p_loo", + "looic", + "se_looic" + ) + ) +}) + +testthat::test_that("validateFormula", { + testthat::expect_equal(validateFormula("y ~ x + 1"), "y ~ x + 1") + testthat::expect_equal(validateFormula("~ 1 + x"), "~ 1 + x") + testthat::expect_equal(validateFormula("~ I(x^2) + exp(x) + sin(x)"), + "~ I(x^2) + exp(x) + sin(x)") + testthat::expect_equal(validateFormula("~sigma(1)"), "~sigma(1)") + testthat::expect_equal(validateFormula("~sigma(rel(1) + I(x^2))"), + "~sigma(rel(1) + I(x^2))") + testthat::expect_equal(validateFormula("~ar(1)"), "~ar(1)") + testthat::expect_warning(validateFormula("y <- x + 1")) +}) + +testthat::test_that("getCellChoices", { + testthat::expect_equal(getCellChoices(nrow = 3, ncol = 2), structure( + 1:6, + names = c("(1, 1)", "(2, 1)", "(3, 1)", "(1, 2)", "(2, 2)", "(3, 2)") + )) +}) diff --git a/tests/testthat/test-plotTime.R b/tests/testthat/test-plotTime.R index fb0f1e5..69bcce4 100644 --- a/tests/testthat/test-plotTime.R +++ b/tests/testthat/test-plotTime.R @@ -86,8 +86,7 @@ testthat::test_that("basePlotTime", { alphaU = 0.1, legendName = "testLegend" ) %>% - setLegendPosition(hideLegend = FALSE, - legendPosition = "top") + formatLegendOfGGplot(legend = list(position = "top")) expect_equal( plot$labels, diff --git a/tests/testthat/testdata/test_detectBreakPoints.csv b/tests/testthat/testdata/test_detectBreakPoints.csv new file mode 100644 index 0000000..9d37af8 --- /dev/null +++ b/tests/testthat/testdata/test_detectBreakPoints.csv @@ -0,0 +1,15 @@ +time,d15N +0.25,12.53 +0.75,12.03 +1.25,11.54 +1.75,11.5 +2.25,11.5 +2.75,11.53 +3.25,11.67 +3.75,11.75 +4.75,11.99 +5.25,12.01 +6.75,11.68 +7.25,11.66 +7.75,11.66 +8.25,11.68 From 09ef45dee195d360762c0771bf749c872261e9d4 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Thu, 26 Sep 2024 16:26:35 +0200 Subject: [PATCH 28/36] OsteoBioR 24.09.0: two more methods for model comparison (#65) * two more methods for model comparison * update news.md * add mcp model summary * extract modules, fix legend * update news.md --- .Rprofile | 6 +- DESCRIPTION | 4 +- NAMESPACE | 1 + NEWS.md | 14 +- R/02-breakPoints.R | 55 ++- R/03-breakPointDetection.R | 412 ++++++++++++++---- R/NAMESPACE.R | 2 +- R/plots.R | 11 +- man/compareWithHeuristic.Rd | 17 + man/compareWithWAIC.Rd | 17 + man/mcpCompareModelsServer.Rd | 25 ++ ...formulasServer.Rd => mcpFormulasServer.Rd} | 16 +- man/mcpModelingServer.Rd | 23 + man/mcpOutServer.Rd | 43 ++ man/mcpShowSingleModelServer.Rd | 25 ++ man/mcpUI.Rd | 14 - 16 files changed, 563 insertions(+), 122 deletions(-) create mode 100644 man/compareWithHeuristic.Rd create mode 100644 man/compareWithWAIC.Rd create mode 100644 man/mcpCompareModelsServer.Rd rename man/{formulasServer.Rd => mcpFormulasServer.Rd} (50%) create mode 100644 man/mcpModelingServer.Rd create mode 100644 man/mcpOutServer.Rd create mode 100644 man/mcpShowSingleModelServer.Rd delete mode 100644 man/mcpUI.Rd diff --git a/.Rprofile b/.Rprofile index 050551b..6084747 100644 --- a/.Rprofile +++ b/.Rprofile @@ -1,7 +1,7 @@ .First <- function() { options(repos = c( - CRAN = "https://packagemanager.posit.co/cran/2021-01-01", + getOption('repos'), INWTLab = "https://inwtlab.github.io/drat/", PANDORA = "https://Pandora-IsoMemo.github.io/drat/" )) @@ -18,3 +18,7 @@ } .First() + +if (interactive()) { + rstantools::rstan_config() +} diff --git a/DESCRIPTION b/DESCRIPTION index 5462592..88a94fe 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.09.2 +Version: 24.09.3 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -38,7 +38,7 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, - shinyTools (>= 24.08.0), + shinyTools (>= 24.09.0), yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: diff --git a/NAMESPACE b/NAMESPACE index 7f9e323..f955ca5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -91,6 +91,7 @@ importFrom(htmltools,save_html) importFrom(jsonlite,toJSON) importFrom(loo,loo) importFrom(loo,loo_compare) +importFrom(loo,waic) importFrom(magrittr,"%>%") importFrom(mcp,mcp) importFrom(openxlsx,write.xlsx) diff --git a/NEWS.md b/NEWS.md index b995c06..c5773e5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,4 +1,16 @@ -# 24.09.1 +# OsteoBioR 24.09.3 + +## Updates +- break point detection (#49, #61) + - new outputs in tab '2. MCP Modeling': plot, waic + - new methods for model comparison in tab '3. Comparison of Models': _waic_, _heuristic_ +- _Credibility intervals over time_: new option to use notation for sub- and superscripts in titles + of a plots, axis or the legend (#59) + +## Bug Fixes +- fix missing filter for legend content in _Credibility intervals over time_ (#63) + +# OsteoBioR 24.09.1 ## Fixes - Clean up of rstan files and src folder. Add command to generate cpp files to Dockerfile. diff --git a/R/02-breakPoints.R b/R/02-breakPoints.R index 5594754..ed353a6 100644 --- a/R/02-breakPoints.R +++ b/R/02-breakPoints.R @@ -201,8 +201,6 @@ runMcp <- function(lists, ...) { #' #' @return A loo model comparison object compareWithLoo <- function(fit) { - #Comparing models using loo - #Define list loo_model <- vector("list", length(fit)) @@ -213,3 +211,56 @@ compareWithLoo <- function(fit) { #Results of model comparison loo_compare(loo_model) } + +#' Compare models using waic +#' +#' @param fit A list of mcp model fits +#' +#' @return A loo model comparison object +compareWithWAIC <- function(fit) { + #Define list + waic_model <- vector("list", length(fit)) + + for (i in 1:length(fit)) { + waic_model[[i]] <- waic(fit[[i]]) + } + + #Results of model comparison + loo_compare(waic_model) +} + +#' Compare models using heuristic +#' +#' The heuristic method is a model comparison method that combines the number of parameters and the elpd_diff value. +#' +#' @param fit A list of mcp model fits +#' +#' @return A loo model comparison object +compareWithHeuristic <- function(fit) { + comparison <- compareWithLoo(fit) + + row_names <- rownames(comparison) + model_number <- as.integer(gsub(".*model([0-9]*).*", "\\1", row_names)) + numEntries <- array(0, nrow(comparison)) + k <- 1 + for (i in model_number) { + numEntries[k] <- length(unlist(fit[[i]][3])) + k <- k + 1 + } + + comparison <- cbind(comparison, numEntries) + # calculate the ratio and use ifelse function to assign zero if division by zero occurs + new_column <- abs(ifelse(comparison[,2] != 0, comparison[,1]/comparison[,2], 0)) + comparison <- cbind(comparison, new_column) + + # Remove rows where the last column value is bigger than 5 + comparison <- comparison[comparison[,10] < 5, , drop = FALSE] + + #Order by lowest number of model parameters and for equal number of parameters by higher elpd_diff value + if (nrow(comparison) > 1) { + comparison <- comparison[order(comparison[,9], -comparison[,1]),] + comparison <- cbind(comparison, "Rank" = 1:nrow(comparison)) + } + + comparison +} diff --git a/R/03-breakPointDetection.R b/R/03-breakPointDetection.R index 84af4f4..81c693e 100644 --- a/R/03-breakPointDetection.R +++ b/R/03-breakPointDetection.R @@ -8,56 +8,19 @@ breakPointDetectionUI <- function(id) { tagList(tags$br(), tabsetPanel( id = ns("breakPointTabs"), - tabPanel("MCP Lists", formulasUI(ns("formulas"))), + tabPanel("1. MCP Lists from Segments & Priors", mcpFormulasUI(ns("formulas"))), tabPanel( - "MCP Model", - mcpUI(ns("mcp")), - tags$h4("Comparing models using loo"), - verbatimTextOutput(ns("compareWithLoo")) %>% withSpinner(color = "#20c997") - ) - ), - tags$br()) -} - -#' MCP UI -#' -#' @param id The module id -mcpUI <- function(id) { - ns <- NS(id) - tagList(tags$br(), - fluidRow( - column( - 3, - numericInput(ns("adapt"), "Burn in length", value = 5000), - helpText("Increase for better conversion (makes the run slower).") - ), - column(3, numericInput( - ns("chains"), - "Number of chains", - value = 3, - min = 1 - )), - column( - 3, - numericInput( - ns("iter"), - "Number of iterations", - value = 3000, - min = 1 - ) + "2. MCP Modeling", + mcpModelingUI(ns("mcp")), + mcpShowSingleModelUI(ns("singleModelOut")) ), - column( - 3, - align = "right", - style = "margin-top: 1.75em;", - #actionButton(ns("loadExampleDf"), "Load Example Data"), - actionButton(ns("apply"), "Run MCP Model", disabled = TRUE) - ) + tabPanel("3. Comparison of Models", mcpCompareModelsUI(ns( + "compareModelsOut" + ))) ), tags$br()) } - #' Break Point Detection Server #' #' Server function of the module @@ -68,9 +31,7 @@ breakPointDetectionServer <- function(id, plotData) { moduleServer(id, function(input, output, session) { ns <- session$ns mcpData <- reactiveVal() - mcpFit <- reactiveVal() - # load data ---- observe({ req(nrow(plotData()) > 0) # select relevant columns @@ -83,59 +44,41 @@ breakPointDetectionServer <- function(id, plotData) { mcpData(newData) }) %>% bindEvent(plotData()) + # example data instead of plot data # observe({ # mcpData(read.csv(file.path("data", "example_breakPoints.csv"))) # }) %>% # bindEvent(input[["mcp-loadExampleDf"]]) - # Formulas tab ---- - formulasAndPriors <- formulasServer("formulas") + formulasAndPriors <- mcpFormulasServer(id = "formulas") - observe({ - req(formulasAndPriors(), mcpData()) - - # enable the 'Run Model' button - shinyjs::enable(ns("mcp-apply"), asis = TRUE) - #updateActionButton(session, "mcp-apply", disabled = FALSE) # not working with current version in docker - }) %>% bindEvent(formulasAndPriors(), mcpData()) + mcpFitList <- mcpModelingServer(id = "mcp", + mcpData = mcpData, + formulasAndPriors = formulasAndPriors) - # Model tab ---- - - ## run the mcp model ---- - observe({ - mcpFit( - runMcp( - lists = formulasAndPriors(), - data = mcpData(), - adapt = input[["mcp-adapt"]], - chains = input[["mcp-chains"]], - iter = input[["mcp-iter"]] - ) - ) %>% - shinyTryCatch(errorTitle = "Error in fitting mcp model", warningTitle = "Warning in fitting mcp model") %>% - withProgress(message = "Fitting MCP model...", value = 0.5) - }) %>% - bindEvent(input[["mcp-apply"]]) + mcpShowSingleModelServer( + id = "singleModelOut", + mcpData = mcpData, + formulasAndPriors = formulasAndPriors, + mcpFitList = mcpFitList + ) - ## render the output of comparing with loo ---- - output$compareWithLoo <- renderPrint({ - validate(need( - formulasAndPriors(), - "Please 'Create MCP Lists' and 'Run MCP Model' first" - )) - validate(need(mcpData(), "Please load 'Plot Data' and 'Run MCP Model' first")) - validate(need(mcpFit(), "Please 'Run MCP Model' first")) - mcpFit() %>% - compareWithLoo() %>% - shinyTryCatch(errorTitle = "Error in comparing with loo", warningTitle = "Warning in comparing with loo") - }) + mcpCompareModelsServer( + id = "compareModelsOut", + mcpData = mcpData, + formulasAndPriors = formulasAndPriors, + mcpFitList = mcpFitList + ) }) } -#' Formulas UI + +# 1. MCP Segments & Priors ---- + +#' MCP Formulas UI #' -#' @rdname formulasServer -formulasUI <- function(id) { +#' @rdname mcpFormulasServer +mcpFormulasUI <- function(id) { ns <- NS(id) tagList( tags$br(), @@ -170,10 +113,10 @@ formulasUI <- function(id) { ) } -#' Formulas Server +#' MCP Formulas Server #' #' @param id The module id -formulasServer <- function(id) { +mcpFormulasServer <- function(id) { moduleServer(id, function(input, output, session) { ns <- session$ns formulasAndPriors <- reactiveVal() @@ -228,9 +171,11 @@ formulasServer <- function(id) { }) %>% bindEvent(input[["apply"]]) - ## render the output of creating formulas ---- output$mcpFormulas <- renderPrint({ - validate(need(formulasAndPriors(), "Please 'Set' Segments and Priors first ...")) + validate(need( + formulasAndPriors(), + "Please 'Set' Segments and Priors first ..." + )) formulasAndPriors() }) @@ -280,3 +225,288 @@ infoButtonServer <- function(id, bindEvent(input$show_info) }) } + + +# 2. MCP Modeling ---- + +#' MCP Modeling UI +#' +#' @rdname mcpModelingServer +mcpModelingUI <- function(id) { + ns <- NS(id) + tagList(tags$br(), + fluidRow( + column( + 3, + numericInput(ns("adapt"), "Burn in length", value = 5000), + helpText("Increase for better conversion (makes the run slower).") + ), + column(3, numericInput( + ns("chains"), + "Number of chains", + value = 3, + min = 1 + )), + column( + 3, + numericInput( + ns("iter"), + "Number of iterations", + value = 3000, + min = 1 + ) + ), + column( + 3, + align = "right", + style = "margin-top: 1.75em;", + # load example data instead of plot data: + #actionButton(ns("loadExampleDf"), "Load Example Data"), + actionButton(ns("apply"), "Run MCP", disabled = TRUE) + ) + ), + tags$hr()) +} + +#' MCP Modeling Server +#' +#' @inheritParams mcpOutServer +mcpModelingServer <- function(id, formulasAndPriors, mcpData) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + + mcpFitList <- reactiveVal() + + observe({ + req(formulasAndPriors(), mcpData()) + # enable the 'Run Model' button + shinyjs::enable(ns("apply"), asis = TRUE) # use this instead of updateActionButton + #updateActionButton(session, "apply", disabled = FALSE) # not working with current version in docker + }) %>% bindEvent(formulasAndPriors(), mcpData()) + + observe({ + res <- runMcp( + lists = formulasAndPriors(), + data = mcpData(), + adapt = input[["adapt"]], + chains = input[["chains"]], + iter = input[["iter"]] + ) %>% + shinyTryCatch(errorTitle = "Error in fitting mcp model", warningTitle = "Warning in fitting mcp model") %>% + withProgress(message = "Fitting MCP model...", value = 0.5) + + mcpFitList(res) + }) %>% + bindEvent(input[["apply"]]) + + return(mcpFitList) + }) +} + +#' MCP Show Single Model UI +#' +#' @rdname mcpShowSingleModelServer +mcpShowSingleModelUI <- function(id) { + ns <- NS(id) + tagList( + selectInput( + ns("showModel"), + "Show MCP model", + choices = c("'Run MCP' first ..." = "") + ), + tags$br(), + fluidRow(column( + 6, + mcpOutUI( + id = ns("summary"), + title = "Model Summary", + outFUN = verbatimTextOutput, + showWidth = TRUE + ) + ), column( + 6, + mcpOutUI( + id = ns("waic"), + title = "Model WAIC", + outFUN = verbatimTextOutput + ) + )), + mcpOutUI( + id = ns("plot"), + title = "Model Plot", + outFUN = plotOutput + ) + ) +} + +#' MCP Show Single Model Server +#' +#' @inheritParams mcpOutServer +mcpShowSingleModelServer <- function(id, + mcpData, + formulasAndPriors, + mcpFitList) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + mcpModel <- reactiveVal() + + observe({ + mcpModels <- seq_along(mcpFitList()) + names(mcpModels) <- paste("Model", mcpModels) + if (length(mcpModels) > 0) + mcpSelected <- 1 + else + mcpSelected <- NULL + updateSelectInput(session, + "showModel", + choices = mcpModels, + selected = mcpSelected) + }) %>% + bindEvent(mcpFitList()) + + observe({ + req(mcpFitList(), input[["showModel"]]) + res <- mcpFitList()[[as.numeric(input[["showModel"]])]] + mcpModel(res) + }) %>% bindEvent(input[["showModel"]]) + + mcpOutServer( + id = "summary", + formulasAndPriors = formulasAndPriors, + mcpData = mcpData, + mcpFitList = mcpFitList, + mcpModel = mcpModel, + outFUN = summary, + renderFUN = renderPrint + ) + + mcpOutServer( + id = "waic", + formulasAndPriors = formulasAndPriors, + mcpData = mcpData, + mcpFitList = mcpFitList, + mcpModel = mcpModel, + outFUN = waic, + renderFUN = renderPrint + ) + + mcpOutServer( + id = "plot", + formulasAndPriors = formulasAndPriors, + mcpData = mcpData, + mcpFitList = mcpFitList, + mcpModel = mcpModel, + outFUN = plot, + renderFUN = renderPlot + ) + }) +} + +#' MCP Output UI +#' +#' @param title The title of the output +#' @param showWidth Show the width slider +#' @rdname mcpOutServer +mcpOutUI <- function(id, + title, + outFUN = verbatimTextOutput, + showWidth = FALSE) { + ns <- NS(id) + + tagList(tags$h4(title), + outFUN(ns("modelOut")) %>% withSpinner(color = "#20c997"), + if (showWidth) { + sliderInput( + ns("width"), + "Summary width", + min = 0.01, + max = 0.99, + value = 0.95, + step = 0.01, + width = "100%" + ) + }) +} + +#' MCP Output Server +#' +#' @param id The module id +#' @param formulasAndPriors The reactive formulas and priors +#' @param mcpData The reactive mcp data +#' @param mcpFitList The reactive mcp fit list +#' @param mcpModel The reactive mcp model +#' @param outFUN The output function +#' @param renderFUN The render function +mcpOutServer <- function(id, + formulasAndPriors, + mcpData, + mcpFitList, + mcpModel, + outFUN, + renderFUN = renderPrint) { + moduleServer(id, function(input, output, session) { + output$modelOut <- renderFUN({ + validate(need( + formulasAndPriors(), + "Please 'Create MCP Lists' and 'Run MCP' first ..." + )) + validate(need(mcpData(), "Please load 'Plot Data' and 'Run MCP' first ...")) + validate(need(mcpFitList(), "Please 'Run MCP' first ...")) + validate(need(mcpModel(), "Please select MCP model first ...")) + + params <- ifelse(is.null(input[["width"]]), list(), list(width = input[["width"]])) + + do.call(outFUN, c(list(mcpModel()), params)) %>% + shinyTryCatch( + errorTitle = sprintf("Error during creating '%s' output", id), + warningTitle = sprintf("Warning during creating '%s' output", id) + ) + }) + }) +} + +# 3. Comparison of Models ---- + +#' MCP Compare Models UI +#' +#' @rdname mcpCompareModelsServer +mcpCompareModelsUI <- function(id) { + ns <- NS(id) + + tagList( + selectInput(ns("method"), "Method", c("loo", "waic", "heuristic")), + tags$br(), + verbatimTextOutput(ns("compareModels")) %>% withSpinner(color = "#20c997") + ) +} + +#' MCP Compare Models Server +#' +#' @inheritParams mcpOutServer +mcpCompareModelsServer <- function(id, + formulasAndPriors, + mcpData, + mcpFitList) { + moduleServer(id, function(input, output, session) { + ns <- session$ns + compareModels <- reactiveVal() + + output$compareModels <- renderPrint({ + validate(need( + formulasAndPriors(), + "Please 'Create MCP Lists' and 'Run MCP' first ..." + )) + validate(need(mcpData(), "Please load 'Plot Data' and 'Run MCP' first ...")) + validate(need(mcpFitList(), "Please 'Run MCP' first ...")) + + compareFUN <- switch(input[["method"]], + loo = compareWithLoo, + waic = compareWithWAIC, + heuristic = compareWithHeuristic) + + mcpFitList() %>% + compareFUN() %>% + shinyTryCatch(errorTitle = "Error in model comparison", warningTitle = "Warning in model comparison") + }) + }) +} diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index dc51fa6..4c8b895 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -22,7 +22,7 @@ #' scale_x_continuous scale_y_continuous sec_axis theme #' @importFrom htmltools save_html #' @importFrom jsonlite toJSON -#' @importFrom loo loo loo_compare +#' @importFrom loo loo loo_compare waic #' @importFrom magrittr %>% #' @importFrom mcp mcp #' @importFrom openxlsx write.xlsx diff --git a/R/plots.R b/R/plots.R index 87c6cd4..5e3b3e7 100644 --- a/R/plots.R +++ b/R/plots.R @@ -174,9 +174,9 @@ extractPlotDataDF <- function(plotDataList, models, credInt) { models <- intersect(models, names(plotDataList)) if (length(models) == 0) return(data.frame()) - + # filter for displayed models: - plotDataList[models] %>% + res <- plotDataList[models] %>% bind_rows(.id = "individual") %>% # add column with mutate(cred_interval = sprintf("%.0f%%", credInt * 100)) %>% @@ -195,6 +195,13 @@ extractPlotDataDF <- function(plotDataList, models, credInt) { "cred_interval", "lower", "median", "upper", "sd") %>% # remove last line containing only NA values slice(1:(n() - 1)) + + # drop levels of the individual column (important for legend content) + res$individual <- res$individual %>% + as.factor() %>% + droplevels() + + res } #' Get Plot Data diff --git a/man/compareWithHeuristic.Rd b/man/compareWithHeuristic.Rd new file mode 100644 index 0000000..f9a55fd --- /dev/null +++ b/man/compareWithHeuristic.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{compareWithHeuristic} +\alias{compareWithHeuristic} +\title{Compare models using heuristic} +\usage{ +compareWithHeuristic(fit) +} +\arguments{ +\item{fit}{A list of mcp model fits} +} +\value{ +A loo model comparison object +} +\description{ +The heuristic method is a model comparison method that combines the number of parameters and the elpd_diff value. +} diff --git a/man/compareWithWAIC.Rd b/man/compareWithWAIC.Rd new file mode 100644 index 0000000..024d47d --- /dev/null +++ b/man/compareWithWAIC.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/02-breakPoints.R +\name{compareWithWAIC} +\alias{compareWithWAIC} +\title{Compare models using waic} +\usage{ +compareWithWAIC(fit) +} +\arguments{ +\item{fit}{A list of mcp model fits} +} +\value{ +A loo model comparison object +} +\description{ +Compare models using waic +} diff --git a/man/mcpCompareModelsServer.Rd b/man/mcpCompareModelsServer.Rd new file mode 100644 index 0000000..8efe604 --- /dev/null +++ b/man/mcpCompareModelsServer.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-breakPointDetection.R +\name{mcpCompareModelsUI} +\alias{mcpCompareModelsUI} +\alias{mcpCompareModelsServer} +\title{MCP Compare Models UI} +\usage{ +mcpCompareModelsUI(id) + +mcpCompareModelsServer(id, formulasAndPriors, mcpData, mcpFitList) +} +\arguments{ +\item{id}{The module id} + +\item{formulasAndPriors}{The reactive formulas and priors} + +\item{mcpData}{The reactive mcp data} + +\item{mcpFitList}{The reactive mcp fit list} +} +\description{ +MCP Compare Models UI + +MCP Compare Models Server +} diff --git a/man/formulasServer.Rd b/man/mcpFormulasServer.Rd similarity index 50% rename from man/formulasServer.Rd rename to man/mcpFormulasServer.Rd index 6fdbb26..6b8f1bc 100644 --- a/man/formulasServer.Rd +++ b/man/mcpFormulasServer.Rd @@ -1,19 +1,19 @@ % Generated by roxygen2: do not edit by hand % Please edit documentation in R/03-breakPointDetection.R -\name{formulasUI} -\alias{formulasUI} -\alias{formulasServer} -\title{Formulas UI} +\name{mcpFormulasUI} +\alias{mcpFormulasUI} +\alias{mcpFormulasServer} +\title{MCP Formulas UI} \usage{ -formulasUI(id) +mcpFormulasUI(id) -formulasServer(id) +mcpFormulasServer(id) } \arguments{ \item{id}{The module id} } \description{ -Formulas UI +MCP Formulas UI -Formulas Server +MCP Formulas Server } diff --git a/man/mcpModelingServer.Rd b/man/mcpModelingServer.Rd new file mode 100644 index 0000000..0902a52 --- /dev/null +++ b/man/mcpModelingServer.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-breakPointDetection.R +\name{mcpModelingUI} +\alias{mcpModelingUI} +\alias{mcpModelingServer} +\title{MCP Modeling UI} +\usage{ +mcpModelingUI(id) + +mcpModelingServer(id, formulasAndPriors, mcpData) +} +\arguments{ +\item{id}{The module id} + +\item{formulasAndPriors}{The reactive formulas and priors} + +\item{mcpData}{The reactive mcp data} +} +\description{ +MCP Modeling UI + +MCP Modeling Server +} diff --git a/man/mcpOutServer.Rd b/man/mcpOutServer.Rd new file mode 100644 index 0000000..a835efb --- /dev/null +++ b/man/mcpOutServer.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-breakPointDetection.R +\name{mcpOutUI} +\alias{mcpOutUI} +\alias{mcpOutServer} +\title{MCP Output UI} +\usage{ +mcpOutUI(id, title, outFUN = verbatimTextOutput, showWidth = FALSE) + +mcpOutServer( + id, + formulasAndPriors, + mcpData, + mcpFitList, + mcpModel, + outFUN, + renderFUN = renderPrint +) +} +\arguments{ +\item{id}{The module id} + +\item{title}{The title of the output} + +\item{outFUN}{The output function} + +\item{showWidth}{Show the width slider} + +\item{formulasAndPriors}{The reactive formulas and priors} + +\item{mcpData}{The reactive mcp data} + +\item{mcpFitList}{The reactive mcp fit list} + +\item{mcpModel}{The reactive mcp model} + +\item{renderFUN}{The render function} +} +\description{ +MCP Output UI + +MCP Output Server +} diff --git a/man/mcpShowSingleModelServer.Rd b/man/mcpShowSingleModelServer.Rd new file mode 100644 index 0000000..4db6db9 --- /dev/null +++ b/man/mcpShowSingleModelServer.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/03-breakPointDetection.R +\name{mcpShowSingleModelUI} +\alias{mcpShowSingleModelUI} +\alias{mcpShowSingleModelServer} +\title{MCP Show Single Model UI} +\usage{ +mcpShowSingleModelUI(id) + +mcpShowSingleModelServer(id, mcpData, formulasAndPriors, mcpFitList) +} +\arguments{ +\item{id}{The module id} + +\item{mcpData}{The reactive mcp data} + +\item{formulasAndPriors}{The reactive formulas and priors} + +\item{mcpFitList}{The reactive mcp fit list} +} +\description{ +MCP Show Single Model UI + +MCP Show Single Model Server +} diff --git a/man/mcpUI.Rd b/man/mcpUI.Rd deleted file mode 100644 index d49b573..0000000 --- a/man/mcpUI.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/03-breakPointDetection.R -\name{mcpUI} -\alias{mcpUI} -\title{MCP UI} -\usage{ -mcpUI(id) -} -\arguments{ -\item{id}{The module id} -} -\description{ -MCP UI -} From 74175302dba81c6d186164429632a4540164783b Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Fri, 27 Sep 2024 08:31:03 +0200 Subject: [PATCH 29/36] OsteoBioR 24.09.3.1: Hide parse button for sub and superscripts (#68) * hide parse button * two more methods for model comparison * update news.md * add mcp model summary * extract modules, fix legend * version up --- DESCRIPTION | 2 +- R/03-timePlotFormatting.R | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 88a94fe..d934dde 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.09.3 +Version: 24.09.3.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index c9f8f41..a2556f5 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -183,6 +183,7 @@ timePlotFormattingServer <- function(id, savedModels) { "plotLabels", type = "ggplot", availableElements = c("title", "axis", "legend"), + showParseButton = FALSE, initText = getDefaultTextFormat() ) plotRanges <- shinyTools::plotRangesServer( @@ -199,6 +200,8 @@ timePlotFormattingServer <- function(id, savedModels) { hideInput = c("hide", "alpha", "colorBg") ) + legend <- shinyTools::plotLegendServer("legend") + pointStyleList <- reactiveValues() observe({ @@ -319,8 +322,6 @@ timePlotFormattingServer <- function(id, savedModels) { } }) - legend <- plotLegendServer("legend") - newPlot <- reactive({ extractedPlotDataDF() %>% na.omit() %>% @@ -350,7 +351,8 @@ timePlotFormattingServer <- function(id, savedModels) { yAxisLabel = input[["secAxisText"]] %>% getSecondAxisTitle(secAxisModel = input[["secAxisModel"]]), yAxisTitleColor = input[["secAxisColor"]]) %>% - shinyTools::formatLegendOfGGplot(legend = legend) + shinyTools::formatLegendOfGGplot(legend = legend) %>% + shinyTools::shinyTryCatch(errorTitle = "Plotting failed") }) observe({ From a8c0b22f8bf743ccae286e96160ace14abdc66b4 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 15 Oct 2024 10:50:38 +0200 Subject: [PATCH 30/36] OsteoBioR 24.10.0: Feat/51 updates for time plot (#73) * optionally format a second y axis, optionally transform an axis * update news.md * update news.md * remove shinyTryCatch in reactive * update tests * remove old inputs * integrate new labeles from rangesUI fix issues with rescaling * update news.md * integrate second axis formatting from shinyTools * extract functions getScaleYLimits and addSecAxisTitle * add logging, use rescaling function from shinyTools, remove old code * fix error for plot of emoty models * fix issue with too many warnings * update news.md --- .Rprofile | 3 + DESCRIPTION | 4 +- NAMESPACE | 4 +- NEWS.md | 11 +++ R/01-plotFormatting.R | 4 +- R/01-plotSecondYAxis.R | 97 +++++++++--------------- R/01-plotSetXAxisLabels.R | 21 +++--- R/03-timePlotFormatting.R | 132 +++++++++++---------------------- R/NAMESPACE.R | 4 +- R/plots.R | 65 ++++++++-------- inst/config.yaml | 3 + man/extendXAxis.Rd | 4 +- tests/testthat/test-plotTime.R | 64 +--------------- tests/testthat/test-plots.R | 8 +- 14 files changed, 166 insertions(+), 258 deletions(-) diff --git a/.Rprofile b/.Rprofile index beb1c90..9f0c9c2 100644 --- a/.Rprofile +++ b/.Rprofile @@ -22,4 +22,7 @@ if (interactive()) { rstantools::rstan_config() + + library(futile.logger) + futile.logger::flog.threshold(DEBUG) } diff --git a/DESCRIPTION b/DESCRIPTION index 89f415d..a66d834 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.09.3.1 +Version: 24.10.0 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -36,7 +36,7 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, - shinyTools (>= 24.09.0), + shinyTools (>= 24.10.0), yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: diff --git a/NAMESPACE b/NAMESPACE index f955ca5..d9616bd 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -99,11 +99,13 @@ importFrom(parallel,detectCores) importFrom(rlang,.data) importFrom(rstan,extract) importFrom(rstan,sampling) +importFrom(shinyTools,calculateRescalingFactors) importFrom(shinyTools,dataExportButton) importFrom(shinyTools,dataExportServer) +importFrom(shinyTools,extractTitle) importFrom(shinyTools,formatLegendOfGGplot) importFrom(shinyTools,formatPointsOfGGplot) -importFrom(shinyTools,formatRangesOfGGplot) +importFrom(shinyTools,formatScalesOfGGplot) importFrom(shinyTools,formatTitlesOfGGplot) importFrom(shinyTools,plotExportButton) importFrom(shinyTools,plotExportServer) diff --git a/NEWS.md b/NEWS.md index c5773e5..df362ee 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,14 @@ +# OsteoBioR 24.10.0 + +## Updates +- _Credibility intervals over time_ updates (availbale with shinyTools 24.10.0) (#51): + - option to format and set ranges of a second y-axis analog to other axes + - option to use transformations for the x or y axis in order to handle extreme values + - update example for mathematical annotation in titles + +## Bug Fixes +- removing duplicated warnings when plotting models with no samples (#55) + # OsteoBioR 24.09.3 ## Updates diff --git a/R/01-plotFormatting.R b/R/01-plotFormatting.R index df5b61a..64552db 100644 --- a/R/01-plotFormatting.R +++ b/R/01-plotFormatting.R @@ -29,6 +29,8 @@ getDefaultTextFormat <- function() { plotTitle = config()[["defaultIntervalTimePlotTitle"]], xAxisTitle = config()[["defaultIntervalTimePlotTitle"]], yAxisTitle = config()[["defaultIntervalTimePlotTitle"]], + yAxisTitle2 = config()[["defaultIntervalTimePlotTitle"]], xAxisText = config()[["defaultIntervalTimePlotText"]], - yAxisText = config()[["defaultIntervalTimePlotText"]]) + yAxisText = config()[["defaultIntervalTimePlotText"]], + yAxisText2 = config()[["defaultIntervalTimePlotText"]]) } diff --git a/R/01-plotSecondYAxis.R b/R/01-plotSecondYAxis.R index 8bfe39a..cecf519 100644 --- a/R/01-plotSecondYAxis.R +++ b/R/01-plotSecondYAxis.R @@ -1,75 +1,52 @@ -setSecondYAxis <- function(plot, - rescaling, - titleFormat = NULL, - textFormat = NULL, - yAxisLabel = "Estimate", - yAxisTitleColor = NULL) { - if (identical(rescaling, list(scale = 1, center = 0))) return(plot) - - scale <- rescaling$scale - center <- rescaling$center - - # format equal to first axis: - if (is.null(titleFormat)) titleFormat <- config()[["defaultIntervalTimePlotTitle"]] - if (is.null(textFormat)) textFormat <- config()[["defaultIntervalTimePlotText"]] - # custom format for second axis: - if (is.null(yAxisTitleColor)) yAxisTitleColor <- config()[["defaultIntervalTimePlotTitle"]][["color"]] - - plot <- plot + - theme(axis.title.y.right = element_text(family = titleFormat[["fontFamily"]], - size = titleFormat[["size"]], - face = titleFormat[["fontType"]], - color = yAxisTitleColor, - hjust = 0.5), - axis.text.y.right = element_text(family = textFormat[["fontFamily"]], - size = textFormat[["size"]], - face = textFormat[["fontType"]], - color = textFormat[["color"]], - hjust = 0.5)) + - scale_y_continuous( - # Features of the first axis - # Add a second axis and specify its features - sec.axis = sec_axis(~(.* scale) + center, name = yAxisLabel) - ) - - plot -} - -rescaleSecondAxisData <- function(plotData, individual, rescaling) { - if (is.null(individual) || individual == "") return(plotData) +rescaleSecondAxisData <- function(plotData, individual, plotRanges) { + # no rescaling if individual is not set + if (length(individual) == 0 || individual == "") + return(plotData) # get index for filter index <- plotData$individual == individual - if (nrow(plotData[index, ]) == 0 || - identical(rescaling, list(scale = 1, center = 0))) return(plotData) + # no rescaling if individual is not found + if (nrow(plotData[index, ]) == 0) + return(plotData) + + # get rescaling factors + rescaling <- calculateRescalingFactors( + oldLimits = getScaleYLimits(plotData = plotData, scaleParams = plotRanges$yAxis), + newLimits = getScaleYLimits(plotData = plotData, # (re-)scale to full data range + #plotData = plotData[index, ], # rescale to selected data range + scaleParams = plotRanges$yAxis2) + ) - # rescale data + # rescale data for index scale <- rescaling$scale center <- rescaling$center - plotData[index, ]$median <- (plotData[index, ]$median - center ) / scale - plotData[index, ]$lower <- (plotData[index, ]$lower - center ) / scale - plotData[index, ]$upper <- (plotData[index, ]$upper - center ) / scale + plotData[index, ]$median <- (plotData[index, ]$median - center) / scale + plotData[index, ]$lower <- (plotData[index, ]$lower - center) / scale + plotData[index, ]$upper <- (plotData[index, ]$upper - center) / scale plotData } -getRescaleParams <- function(oldLimits, newLimits = NULL, secAxis = FALSE) { - if (length(newLimits) == 0 || !secAxis) return(list(scale = 1, center = 0)) - - b <- seq(min(newLimits),max(newLimits), length.out = 100) - a <- seq(min(oldLimits),max(oldLimits), length.out = 100) - res <- lm(b~a) - - list(scale = res$coefficients[2], - center = res$coefficients[1]) +getScaleYLimits <- function(plotData, scaleParams) { + if (scaleParams$fromData) { + # use data based limits + getYRange(plotData) + } else { + # use custom limits + c(scaleParams$min, scaleParams$max) + } } -getSecondAxisTitle <- function(secAxisTitle, secAxisModel) { - if (is.null(secAxisModel) || secAxisModel == "") return("") - - if (secAxisTitle == "") return(sprintf("%s Estimate", secAxisModel)) - - return(secAxisTitle) +getSecAxisTitle <- function(modelName, customTitle) { + # no second axis if modelName is not set + if (is.null(modelName) || modelName == "") return(NULL) + + customTitle <- extractTitle(customTitle) + if (is.null(customTitle) || customTitle == "") { + modelName + } else { + customTitle + } } diff --git a/R/01-plotSetXAxisLabels.R b/R/01-plotSetXAxisLabels.R index 6726e0c..3fd8e5b 100644 --- a/R/01-plotSetXAxisLabels.R +++ b/R/01-plotSetXAxisLabels.R @@ -1,18 +1,18 @@ + +# this function is in essence deprecated, but it is still usend for examples with plotTime() setXAxisLabels <- function(plot, xAxisData, extendLabels, deriv, xLim = NULL) { - # set limits + # set limits for plot if (length(xLim) == 2) { xPlotLim <- xLim } else { - xPlotLim <- xLabelLim <- range(xAxisData) + xPlotLim <- range(xAxisData) } - # extend labels to full x axis range - if (extendLabels && length(xLim) == 2) { - xLabelLim <- xLim - xAxisData <- xAxisData %>% - extendXAxis(xLabelLim = xLabelLim) - } + # update x axis data + xAxisData <- xAxisData %>% + extendXAxis(xLabelLim = xLim, extendLabels = extendLabels) + # get breaks and labels breaks <- getBreaks(time = xAxisData$time, deriv = deriv) labels <- getLabel(xAxisData = xAxisData, deriv = deriv) @@ -37,7 +37,10 @@ extractAllXAxisData <- function(extractedPlotDataList) { #' @param xAxisData (data.frame) data.frame containing "time", "lower" and "upper" columns used for #' the x axis. #' @param xLabelLim numeric vector of length 2: range of labels of x axis -extendXAxis <- function(xAxisData, xLabelLim) { +#' @param extendLabels (logical) if TRUE then extend the labels of the x axis +extendXAxis <- function(xAxisData, xLabelLim, extendLabels = FALSE) { + if (length(xLabelLim) != 2 || !extendLabels) return(xAxisData) + if (min(xLabelLim) < min(xAxisData[["time_lower"]])) { # add new row at the beginning newFirstRow <- data.frame( diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index a2556f5..2606089 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -98,12 +98,7 @@ timePlotFormattingUI <- function(id) { ) ), column(3, - shinyTools::plotRangesUI( - id = ns("plotRanges"), - title = "Axis", - initRanges = list(xAxis = config()[["plotRange"]], - yAxis = config()[["plotRange"]]) - ), + shinyTools::plotRangesUI(id = ns("plotRanges"), title = "Axis"), conditionalPanel( ns = ns, condition = "!input['plotRanges-fromData'] & input['plotRanges-labelName'] == 'xAxis'", @@ -114,28 +109,7 @@ timePlotFormattingUI <- function(id) { tags$br(), tags$br(), selectizeInput(ns("secAxisModel"), "Add a new secondary y axis", choices = c("Choose one Model / Individual ..." = "")), - helpText("The first element of 'Model(s) / Individual(s) to display' is always used for the first (left) axis."), - conditionalPanel( - ns = ns, - condition = "input.secAxisModel != ''", - fluidRow( - column(6, textInput(ns("secAxisText"), label = "Title", - value = "", - placeholder = "Custom title ...")), - column(6, colourInput(ns("secAxisColor"), - label = "Title color", - value = config()[["defaultIntervalTimePlotTitle"]][["color"]])) - ), - conditionalPanel( - ns = ns, - condition = "!input['plotRanges-fromData'] & input['plotRanges-labelName'] == 'yAxis'", - fluidRow( - column(6, numericInput(ns("secAxisYMin"), "Min", value = 0)), - column(6, numericInput(ns("secAxisYMax"), "Max", value = 1)) - ) - ) - ), - + helpText("The first element of 'Model(s) / Individual(s) to display' is always used for the first (left) axis.") ), column(3, shinyTools::plotPointsUI(id = ns("pointStyle"), @@ -182,7 +156,7 @@ timePlotFormattingServer <- function(id, savedModels) { plotTexts <- shinyTools::plotTitlesServer( "plotLabels", type = "ggplot", - availableElements = c("title", "axis", "legend"), + availableElements = c("title", "axis", "yaxis2", "legend"), showParseButton = FALSE, initText = getDefaultTextFormat() ) @@ -190,7 +164,9 @@ timePlotFormattingServer <- function(id, savedModels) { "plotRanges", type = "ggplot", initRanges = list(xAxis = config()[["plotRange"]], - yAxis = config()[["plotRange"]]) + yAxis = config()[["plotRange"]], + yAxis2 = config()[["plotRange"]]), + axes = c("x axis" = "xAxis", "y axis" = "yAxis", "2nd y axis" = "yAxis2") ) pointStyle <- shinyTools::plotPointsServer( @@ -259,17 +235,15 @@ timePlotFormattingServer <- function(id, savedModels) { getPlotData(object = x, prop = input$modCredInt, deriv = input$deriv) %>% updateTime(object = x, deriv = input$deriv) }) %>% - shinyTryCatch(errorTitle = "'Credibility intervals over time': Error in extracting plot data", - warningTitle = "'Credibility intervals over time': Warning in extracting plot data", - alertStyle = "shinyalert") + removeEmptyModels() }) extractedPlotDataDF <- reactive({ extractedPlotDataList() %>% extractPlotDataDF(models = input[["plotTimeModels"]], credInt = input$modCredInt) %>% - shinyTryCatch(errorTitle = "'Credibility intervals over time': Error in extracting table data", - warningTitle = "'Credibility intervals over time': Warning in extracting table data", + shinyTryCatch(errorTitle = "'Credibility intervals over time': Error when extracting data", + warningTitle = "'Credibility intervals over time': Warning when extracting data", alertStyle = "shinyalert") }) @@ -277,7 +251,7 @@ timePlotFormattingServer <- function(id, savedModels) { output$plotData <- renderTable({ validate(need(input[["plotTimeModels"]], messageNoModelsToPlot()), need(nrow(extractedPlotDataDF()) > 0, - "No data available for selected models ...")) + "No data available for selected model(s) ...")) extractedPlotDataDF() }) @@ -292,72 +266,52 @@ timePlotFormattingServer <- function(id, savedModels) { dataExportServer("exportCredIntTimeData", reactive(function() {plotDataExport()})) - rescalingSecAxis <- reactive({ - plotData <- extractedPlotDataDF() %>% - na.omit() - - if (length(input[["secAxisModel"]]) > 0 && input[["secAxisModel"]] != "") { - # get index for filter - index <- plotData$individual == input[["secAxisModel"]] - - # get rescaling parameters - req(nrow(plotData[index, ]) > 0) - - if (input[["plotRanges-fromData"]] && input[["plotRanges-labelName"]] == "yAxis") { - # use data based limits - oldLimits <- getYRange(plotData) %>% unlist() - newLimits <- getYRange(plotData[index, ]) %>% unlist() - } else { - # use custom limits - oldLimits <- c(ymin = input[["plotRanges-min"]], ymax = input[["plotRanges-max"]]) - newLimits <- c(ymin = input[["secAxisYMin"]], ymax = input[["secAxisYMax"]]) - } - ## use always data based newYLimits, we only set global limits not(!) per model - res <- getRescaleParams(oldLimits = oldLimits, - newLimits = newLimits, - secAxis = TRUE) - } else { - # set default: no rescaling - list(scale = 1, center = 0) - } - }) - newPlot <- reactive({ - extractedPlotDataDF() %>% + logDebug("%s: Entering: reactive 'newPlot'", id) + + # setup base plot + p <- extractedPlotDataDF() %>% na.omit() %>% rescaleSecondAxisData(individual = input[["secAxisModel"]], - rescaling = rescalingSecAxis()) %>% - basePlotTime(xLim = getLim(plotRanges = plotRanges, axis = "xAxis"), - yLim = getLim(plotRanges = plotRanges, axis = "yAxis")) %>% + plotRanges = plotRanges) %>% + basePlotTime(xLim = getUserLimits(plotRanges = plotRanges[["xAxis"]]), + yLim = getUserLimits(plotRanges = plotRanges[["yAxis"]])) %>% setDefaultTitles(prop = input$modCredInt) %>% - shinyTools::formatTitlesOfGGplot(text = plotTexts) %>% - shinyTools::formatRangesOfGGplot(ranges = plotRanges) %>% - setXAxisLabels( - xAxisData = extractedPlotDataList() %>% - extractAllXAxisData(), # labels for all x axis data - extendLabels = input$extendLabels, - xLim = getLim(plotRanges = plotRanges, axis = "xAxis"), - deriv = "1" # input$deriv already included within extractedPlotDataList() + shinyTools::formatTitlesOfGGplot(text = plotTexts) + + # specify x-axis labels from x data of all models + allXAxisData <- extractedPlotDataList() %>% + extractAllXAxisData() %>% + extendXAxis(xLabelLim = getUserLimits(plotRanges = plotRanges[["xAxis"]]), + extendLabels = input$extendLabels) + + p %>% + shinyTools::formatScalesOfGGplot( + ranges = plotRanges, + xLabels = list( + # input$deriv already included within extractedPlotDataList() + breaks = getBreaks(time = allXAxisData$time, deriv = "1"), + labels = getLabel(xAxisData = allXAxisData, deriv = "1") + ), + ySecAxisTitle = getSecAxisTitle(modelName = input[["secAxisModel"]], + customTitle = plotTexts[["yAxisTitle2"]]) ) %>% drawLinesAndRibbon( pointStyleList = pointStyleList, alphaL = input[["alphaL"]], alphaU = input[["alphaU"]], + # UPDATE LEGEND HERE <- ------- legendName = plotTexts[["legendTitle"]][["text"]] ) %>% - setSecondYAxis(rescaling = rescalingSecAxis(), - titleFormat = plotTexts[["yAxisTitle"]], - textFormat = plotTexts[["yAxisText"]], - yAxisLabel = input[["secAxisText"]] %>% - getSecondAxisTitle(secAxisModel = input[["secAxisModel"]]), - yAxisTitleColor = input[["secAxisColor"]]) %>% - shinyTools::formatLegendOfGGplot(legend = legend) %>% - shinyTools::shinyTryCatch(errorTitle = "Plotting failed") + shinyTools::formatLegendOfGGplot(legend = legend) }) observe({ req(savedModels(), input[["plotTimeModels"]]) - p <- newPlot() + logDebug("%s: Entering get formattedPlot()", id) + + p <- newPlot() %>% + shinyTools::shinyTryCatch(errorTitle = "Plotting failed") formattedPlot(p) }) %>% bindEvent(list(input[["applyFormatToTimePlot"]], @@ -365,6 +319,7 @@ timePlotFormattingServer <- function(id, savedModels) { observe({ req(savedModels(), input[["plotTimeModels"]]) + logDebug("%s: Entering reset formattedPlot()", id) modelNames <- names(savedModels()) defaultStyle <- pointStyleList %>% @@ -372,7 +327,8 @@ timePlotFormattingServer <- function(id, savedModels) { getDefaultPointFormatForModels(modelNames = modelNames) pointStyleList[[input[["formatTimePlot"]]]] <- defaultStyle[[input[["formatTimePlot"]]]] - p <- newPlot() + p <- newPlot() %>% + shinyTools::shinyTryCatch(errorTitle = "Plotting failed") formattedPlot(p) }) %>% bindEvent(input[["resetFormatTimePlotModel"]]) diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index 4c8b895..d931ea1 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -31,8 +31,8 @@ #' @importFrom rstan sampling extract #' @importFrom shinycssloaders withSpinner #' @importFrom shinyjs alert enable runjs -#' @importFrom shinyTools dataExportButton dataExportServer -#' formatLegendOfGGplot formatPointsOfGGplot formatRangesOfGGplot formatTitlesOfGGplot +#' @importFrom shinyTools calculateRescalingFactors dataExportButton dataExportServer extractTitle +#' formatLegendOfGGplot formatPointsOfGGplot formatScalesOfGGplot formatTitlesOfGGplot #' plotExportButton plotExportServer plotLegendServer plotLegendUI plotPointsServer plotPointsUI #' plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI #' shinyTryCatch diff --git a/R/plots.R b/R/plots.R index 5e3b3e7..f1dc533 100644 --- a/R/plots.R +++ b/R/plots.R @@ -69,7 +69,13 @@ basePlotTime <- function(df, xLim = NULL, yLim = NULL, sizeTextX = 12, sizeTextY = 12, sizeAxisX = 12, sizeAxisY = 12) { - p <- ggplot(df, aes(x = .data[["time"]])) + + if ("time" %in% colnames(df)) { + p <- ggplot(df, aes(x = .data[["time"]])) + } else { + p <- ggplot(df) + } + + p <- p + theme(panel.grid.major.x = element_line(size = 0.1)) + # no error only in most recent version: element_line(linewidth = 0.1) theme(axis.title.x = element_text(size = sizeTextX), axis.text.x = element_text(size = sizeAxisX), @@ -85,10 +91,9 @@ basePlotTime <- function(df, drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName = "individual") { - # default legend name - if (legendName == "") legendName <- "individual" + if (nrow(plot$data) == 0) return(plot) - # draw lines "upper", "mdeian", "lower" + # draw lines "upper", "median", "lower" if (nrow(plot$data) > 1) { plot <- plot + geom_line(aes(y = .data[["median"]], colour = .data[["individual"]]), @@ -124,15 +129,18 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName fill = .data[["individual"]]), alpha = alphaL) - # set scales for each "individual" + # set scales for each "individual" with default legend name lineColors <- getStyleForIndividuals(pointStyleList, input = "color") fillColors <- getStyleForIndividuals(pointStyleList, input = "color") pointShapes <- getStyleForIndividuals(pointStyleList, input = "symbol") pointSize <- getStyleForIndividuals(pointStyleList, input = "size") + # default legend name for empty input + if (legendName == "") legendName <- "individual" + plot + scale_colour_manual(name = legendName, values = lineColors) + # former colorL - scale_fill_manual(name = legendName, values = fillColors)+ # former colorU + scale_fill_manual(name = legendName, values = fillColors) + # former colorU scale_shape_manual(name = legendName, values = pointShapes) + scale_size_manual(name = legendName, values = pointSize) } @@ -157,20 +165,27 @@ drawShiftLines <- function(plot, object, deriv, plotShifts, ...) { plot } -extractPlotDataDF <- function(plotDataList, models, credInt) { +removeEmptyModels <- function(plotDataList) { + # keep names of all models + allModelNames <- names(plotDataList) + # remove elements with no rows plotDataList <- plotDataList[sapply(plotDataList, nrow) > 0] # empty of selected models - emptyModels <- setdiff(models, names(plotDataList)) + emptyModels <- setdiff(allModelNames, names(plotDataList)) # warning that no data is available for some selected models if (length(emptyModels) > 0) { warning(paste("No data available for model(s):", paste(emptyModels, collapse = ", "), - ". Model(s) not displayed in table 'Plot Data'.")) + ". Model(s) not displayed in table 'Plot Data' or in 'Time Plot'.")) } + plotDataList +} + +extractPlotDataDF <- function(plotDataList, models, credInt) { models <- intersect(models, names(plotDataList)) if (length(models) == 0) return(data.frame()) @@ -211,12 +226,11 @@ extractPlotDataDF <- function(plotDataList, models, credInt) { #' @param time (numeric) time vector #' @inheritParams plotTime getPlotData <- function(object, prop = 0.8, time = NULL, deriv = "1"){ - lLim <- (1 - prop) / 2 - uLim <- 1 - lLim dat <- rstan::extract(object)$interval if (is.null(dat)) return(data.frame()) + # apply user derivation if(deriv == "2"){ if (ncol(dat) > 2) { dat <- t(apply(dat, 1, diff)) @@ -226,6 +240,8 @@ getPlotData <- function(object, prop = 0.8, time = NULL, deriv = "1"){ } # extract quantiles + lLim <- (1 - prop) / 2 + uLim <- 1 - lLim intervalQuantiles <- as.data.frame( t(apply(dat, 2, quantile, probs = c(lLim, 0.5, uLim))) ) @@ -310,39 +326,30 @@ setPlotLimits <- function(plot, newData = NULL, xLim = NULL, yLim = NULL) { allData <- plot$data if(!is.null(newData)) allData <- bind_rows(plot$data, newData) %>% distinct() - if (length(xLim) == 0) xLim <- getXRange(allData) %>% unlist() - if (length(yLim) == 0) yLim <- getYRange(allData) %>% unlist() + if (length(xLim) == 0) xLim <- getXRange(allData) + if (length(yLim) == 0) yLim <- getYRange(allData) plot + coord_cartesian(ylim = yLim, xlim = xLim) } -getLim <- function(plotRanges, axis = c("xAxis", "yAxis")) { - axis <- match.arg(axis) +getUserLimits <- function(plotRanges) { + if (is.null(plotRanges[["fromData"]]) || plotRanges[["fromData"]]) return(NULL) - if (plotRanges[[axis]][["fromData"]]) return(numeric(0)) - - c(plotRanges[[axis]][["min"]], plotRanges[[axis]][["max"]]) + c(plotRanges[["min"]], plotRanges[["max"]]) } getXRange <- function(dat) { - if (nrow(dat) == 0) return(list(xmin = defaultInputsForUI()$xmin, - xmax = defaultInputsForUI()$xmax)) - - xmin <- min(dat$time, na.rm = TRUE) - xmax <- max(dat$time, na.rm = TRUE) + if (nrow(dat) == 0) return(c(defaultInputsForUI()$xmin, defaultInputsForUI()$xmax)) - list(xmin = xmin, - xmax = xmax) + range(dat$time, na.rm = TRUE) } getYRange <- function(dat) { - if (nrow(dat) == 0) return(list(ymin = defaultInputsForUI()$ymin, - ymax = defaultInputsForUI()$ymax)) + if (nrow(dat) == 0) return(c(defaultInputsForUI()$ymin, defaultInputsForUI()$ymax)) ymin <- min(dat$lower, na.rm = TRUE) ymax <- max(dat$upper, na.rm = TRUE) rangeY <- ymax - ymin - list(ymin = ymin - 0.1*rangeY, - ymax = ymax + 0.1*rangeY) + c(ymin - 0.1*rangeY, ymax + 0.1*rangeY) } diff --git a/inst/config.yaml b/inst/config.yaml index 20d8351..9861301 100644 --- a/inst/config.yaml +++ b/inst/config.yaml @@ -32,6 +32,8 @@ availableElements: defaultIntervalTimePlotTitle: text: "" + useExpression: false + expression: "" fontFamily: "sans" fontType: "plain" color: "#000000" @@ -64,6 +66,7 @@ plotRange: min: 0 max: 1 fromData: true + transform: "identity" # default line style for each model defaultLineStyle: diff --git a/man/extendXAxis.Rd b/man/extendXAxis.Rd index 86b0166..7f56e9d 100644 --- a/man/extendXAxis.Rd +++ b/man/extendXAxis.Rd @@ -4,13 +4,15 @@ \alias{extendXAxis} \title{Extend X Axis} \usage{ -extendXAxis(xAxisData, xLabelLim) +extendXAxis(xAxisData, xLabelLim, extendLabels = FALSE) } \arguments{ \item{xAxisData}{(data.frame) data.frame containing "time", "lower" and "upper" columns used for the x axis.} \item{xLabelLim}{numeric vector of length 2: range of labels of x axis} + +\item{extendLabels}{(logical) if TRUE then extend the labels of the x axis} } \description{ Add breaks and labels for x axis diff --git a/tests/testthat/test-plotTime.R b/tests/testthat/test-plotTime.R index 69bcce4..7720294 100644 --- a/tests/testthat/test-plotTime.R +++ b/tests/testthat/test-plotTime.R @@ -136,11 +136,8 @@ testthat::test_that("basePlotTime", { ) expect_equal(plot$coordinates$limits, list( - x = c(xmin = 0.5, xmax = 5.5), - y = c( - ymin = -12.7512327337153, - ymax = -4.60003088235379 - ) + x = c(0.5, 5.5), + y = c(-12.7512327337153, -4.60003088235379) )) expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5)) @@ -190,64 +187,9 @@ testthat::test_that("drawLinesAndRibbon", { ) ) expect_equal(plot$coordinates$limits, - list(x = c(xmin = 1, xmax = 5), y = c(ymin = -4.17929722147457, - ymax = 3.35841030846769))) + list(x = c(1, 5), y = c(-4.17929722147457, 3.35841030846769))) expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, c(1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5)) expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, c("[0.5-1.5]", "", "[1.5-2.5]", "", "[2.5-3.5]", "", "[3.5-4.5]", "", "[4.5-5.5]")) }) - -testthat::test_that("setSecondYAxis", { - plotData1 <- getPlotData(object = testObjectDefault1, prop = 0.8, deriv = "1") %>% - updateTime(object = testObjectDefault1, deriv = "1") - plotData2 <- getPlotData(object = testObjectGap1, prop = 0.8, deriv = "1") %>% - updateTime(object = testObjectGap1, deriv = "1") - allPlotDataDF <- list(ind_1 = plotData1, - ind_2 = plotData2) %>% - extractPlotDataDF(models = c("ind_1", "ind_2"), - credInt = 0.8) %>% - na.omit() - - pointStyleList <- list() %>% - getDefaultPointFormatForModels(modelNames = c("ind_1", "ind_2")) - plotTexts <- getDefaultTextFormat() - - index <- allPlotDataDF$individual == "ind_2" - rescaling <- getRescaleParams(oldLimits = getYRange(allPlotDataDF) %>% unlist(), - newLimits = getYRange(allPlotDataDF[index, ]) %>% unlist(), - secAxis = TRUE) - - plot <- allPlotDataDF %>% - rescaleSecondAxisData(individual = "ind_2", - rescaling = rescaling) %>% - basePlotTime() %>% - setXAxisLabels(xAxisData = list(ind_1 = plotData1, - ind_2 = plotData2) %>% - extractAllXAxisData(), # labels for all x axis data - extendLabels = FALSE, - deriv = FALSE) %>% - shinyTools::formatTitlesOfGGplot(text = plotTexts) %>% - drawLinesAndRibbon( - pointStyleList = pointStyleList, - alphaL = 0.7, - alphaU = 0.1, - legendName = "testLegend" - ) %>% - setSecondYAxis(rescaling = rescaling, - titleFormat = plotTexts[["yAxisTitle"]], - textFormat = plotTexts[["yAxisText"]], - yAxisLabel = "axis2Test", - yAxisTitleColor = "#002350") - - expect_equal(plot$labels, list(x = "time", y = "median", colour = "individual", ymin = "lower", - ymax = "upper", fill = "individual", shape = "individual", - size = "individual")) - expect_equal(plot$coordinates$limits, - list(x = c(xmin = 0.5, xmax = 5.5), - y = c(ymin = -12.7512327337153, ymax = -4.60003088235379))) - expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$breaks, - c(0.5, 1.5, 2.5, 3.5, 4.5, 5.5)) - expect_equal(ggplot_build(plot)$layout$panel_scales_x[[1]]$labels, - c("[0-1]", "[1-2]", "[2-3]", "[3-4]", "[4-5]", "[5-6]")) -}) diff --git a/tests/testthat/test-plots.R b/tests/testthat/test-plots.R index c04cfb3..af999a1 100644 --- a/tests/testthat/test-plots.R +++ b/tests/testthat/test-plots.R @@ -198,7 +198,7 @@ testthat::test_that("extendXAxis", { xAxisData <- getXAxisData(object = testObjectDefault1, oldXAxisData = oldXAxisData) testXAxisData <- xAxisData %>% - extendXAxis(xLabelLim = c(-1, 8)) + extendXAxis(xLabelLim = c(-1, 8), extendLabels = TRUE) testthat::expect_equal(nrow(xAxisData) + 2, nrow(testXAxisData)) @@ -209,10 +209,10 @@ testthat::test_that("extendXAxis", { testthat::expect_equal(labels, c("[-1-0]", "[0-1]", "[1-2]", "[2-3]", "[3-4]", "[4-5]", "[5-6]", "[6-8]")) }) -testthat::test_that("getLim", { +testthat::test_that("getUserLimits", { testRanges <- list(xAxis = list(min = 0L, max = 1L, fromData = TRUE), yAxis = list(min = 0L, max = 1L, fromData = FALSE)) - expect_equal(getLim(testRanges, axis = "xAxis"), numeric(0)) - expect_equal(getLim(testRanges, axis = "yAxis"), 0:1) + expect_equal(getUserLimits(testRanges[["xAxis"]]), NULL) + expect_equal(getUserLimits(testRanges[["yAxis"]]), 0:1) }) From 3d743e7f9a8e442c2c57554235259b815baa9072 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 5 Nov 2024 15:02:20 +0100 Subject: [PATCH 31/36] OsteoBioR 24.11.0: integrate ChangeR modules (#74) * integrate ChangeR modules * remove old scripts, tests that now live in ChangeR * clean up namespace * apply dataExport from shinyTools, remove internal data export, clean up namespace * remove example data * require DataTools with fix * apply plotExport from shinyTools * update news.md --- DESCRIPTION | 12 +- NAMESPACE | 19 +- NEWS.md | 6 + R/02-breakPoints.R | 266 --------- R/02-matrixModule.R | 187 ------- R/03-breakPointDetection.R | 512 ------------------ R/03-timePlotFormatting.R | 34 +- R/NAMESPACE.R | 18 +- R/exportData.R | 38 -- inst/app/data/example.csv | 12 + inst/app/data/example_breakPointPriors.csv | 4 - inst/app/data/example_breakPointSegments.csv | 4 - inst/app/data/example_breakPoints.csv | 15 - inst/app/server.R | 330 +++-------- inst/app/ui.R | 12 +- man/breakPointDetectionServer.Rd | 21 - man/cleanComb.Rd | 18 - man/compareWithHeuristic.Rd | 17 - man/compareWithLoo.Rd | 17 - man/compareWithWAIC.Rd | 17 - man/exportCSV.Rd | 20 - man/exportFilename.Rd | 14 - man/exportJSON.Rd | 16 - man/exportXLSX.Rd | 16 - man/getCellChoices.Rd | 19 - man/getComb.Rd | 21 - man/infoButtonServer.Rd | 27 - man/matrixServer.Rd | 42 -- man/mcpCompareModelsServer.Rd | 25 - man/mcpFormulasServer.Rd | 19 - man/mcpModelingServer.Rd | 23 - man/mcpOutServer.Rd | 43 -- man/mcpShowSingleModelServer.Rd | 25 - man/readExampleMatrix.Rd | 14 - man/runMcp.Rd | 19 - man/setFormulasAndPriors.Rd | 17 - man/splitComb.Rd | 17 - man/validateFormula.Rd | 17 - tests/testthat/test-breakPoints.R | 98 ---- .../testdata/test_detectBreakPoints.csv | 15 - 40 files changed, 129 insertions(+), 1937 deletions(-) delete mode 100644 R/02-breakPoints.R delete mode 100644 R/02-matrixModule.R delete mode 100644 R/03-breakPointDetection.R delete mode 100644 R/exportData.R create mode 100644 inst/app/data/example.csv delete mode 100644 inst/app/data/example_breakPointPriors.csv delete mode 100644 inst/app/data/example_breakPointSegments.csv delete mode 100644 inst/app/data/example_breakPoints.csv delete mode 100644 man/breakPointDetectionServer.Rd delete mode 100644 man/cleanComb.Rd delete mode 100644 man/compareWithHeuristic.Rd delete mode 100644 man/compareWithLoo.Rd delete mode 100644 man/compareWithWAIC.Rd delete mode 100644 man/exportCSV.Rd delete mode 100644 man/exportFilename.Rd delete mode 100644 man/exportJSON.Rd delete mode 100644 man/exportXLSX.Rd delete mode 100644 man/getCellChoices.Rd delete mode 100644 man/getComb.Rd delete mode 100644 man/infoButtonServer.Rd delete mode 100644 man/matrixServer.Rd delete mode 100644 man/mcpCompareModelsServer.Rd delete mode 100644 man/mcpFormulasServer.Rd delete mode 100644 man/mcpModelingServer.Rd delete mode 100644 man/mcpOutServer.Rd delete mode 100644 man/mcpShowSingleModelServer.Rd delete mode 100644 man/readExampleMatrix.Rd delete mode 100644 man/runMcp.Rd delete mode 100644 man/setFormulasAndPriors.Rd delete mode 100644 man/splitComb.Rd delete mode 100644 man/validateFormula.Rd delete mode 100644 tests/testthat/test-breakPoints.R delete mode 100644 tests/testthat/testdata/test_detectBreakPoints.csv diff --git a/DESCRIPTION b/DESCRIPTION index a66d834..e0a9230 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.10.0 +Version: 24.11.0 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -13,19 +13,13 @@ LazyData: true ByteCompile: true Depends: R (>= 3.5.0), Rcpp (>= 0.12.0) Imports: - colourpicker, + ChangeR, dplyr, ggplot2 (>= 2.2.1), - DataTools (>= 23.12.2.3), + DataTools (>= 24.10.0.2), futile.logger, - htmltools, - jsonlite, - loo, - magrittr, - mcp, methods, modules, - openxlsx, parallel, rlang, rstan (>= 2.18.1), diff --git a/NAMESPACE b/NAMESPACE index d9616bd..af1fd38 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -7,10 +7,6 @@ export(config) export(defaultInputsForUI) export(estimateIntervals) export(estimateTimePoint) -export(exportCSV) -export(exportFilename) -export(exportJSON) -export(exportXLSX) export(extractIndividuals) export(extractModelOutputs) export(extractSavedModels) @@ -45,6 +41,8 @@ import(methods) import(rstantools) import(shiny) import(shinythemes) +importFrom(ChangeR,changePointsServer) +importFrom(ChangeR,changePointsUI) importFrom(DataTools,checkAnyNonNumericColumns) importFrom(DataTools,downloadModelServer) importFrom(DataTools,downloadModelUI) @@ -54,7 +52,7 @@ importFrom(DataTools,importOptions) importFrom(DataTools,importServer) importFrom(DataTools,importUI) importFrom(DataTools,renameExistingNames) -importFrom(colourpicker,colourInput) +importFrom(dplyr,"%>%") importFrom(dplyr,arrange) importFrom(dplyr,bind_cols) importFrom(dplyr,bind_rows) @@ -87,14 +85,6 @@ importFrom(ggplot2,scale_x_continuous) importFrom(ggplot2,scale_y_continuous) importFrom(ggplot2,sec_axis) importFrom(ggplot2,theme) -importFrom(htmltools,save_html) -importFrom(jsonlite,toJSON) -importFrom(loo,loo) -importFrom(loo,loo_compare) -importFrom(loo,waic) -importFrom(magrittr,"%>%") -importFrom(mcp,mcp) -importFrom(openxlsx,write.xlsx) importFrom(parallel,detectCores) importFrom(rlang,.data) importFrom(rstan,extract) @@ -125,7 +115,6 @@ importFrom(shinyjs,alert) importFrom(shinyjs,enable) importFrom(shinyjs,runjs) importFrom(stats,approx) -importFrom(stats,as.formula) importFrom(stats,dnorm) importFrom(stats,lm) importFrom(stats,median) @@ -133,8 +122,6 @@ importFrom(stats,na.omit) importFrom(stats,quantile) importFrom(stats,sd) importFrom(utils,combn) -importFrom(utils,read.csv) importFrom(utils,write.csv) -importFrom(utils,write.table) importFrom(yaml,read_yaml) useDynLib(OsteoBioR, .registration = TRUE) diff --git a/NEWS.md b/NEWS.md index df362ee..bf3b16e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# OsteoBioR 24.11.0 + +## Updates +- integration of modules from _ChangeR_ package for break-point detection (#72) +- option to set the "x" and "y" columns for mcp modelling + # OsteoBioR 24.10.0 ## Updates diff --git a/R/02-breakPoints.R b/R/02-breakPoints.R deleted file mode 100644 index ed353a6..0000000 --- a/R/02-breakPoints.R +++ /dev/null @@ -1,266 +0,0 @@ -#' Get Example Matrix -#' -#' @param path path to example matrix -readExampleMatrix <- function(path) { - df <- read.csv(path) - - df[is.na(df)] <- "" - - res <- df %>% as.matrix() - colnames(res) <- 1:ncol(res) - rownames(res) <- 1:nrow(res) - - res -} - -#' Get the comb matrix -#' -#' Get the combined matrix of segments and priors. -#' Process the segments and priors matrices, identify relevant rows and columns, concatenate them, -#' and generate possible cell combinations while ensuring data consistency. -#' -#' @param segments A matrix containing the segmented formulas -#' @param priors A matrix containing the priors -#' -#' @return A matrix containing the segmented formulas and priors -getComb <- function(segments, priors) { - # find rows and columns in segments that are not all "" - row_indices <- apply(segments, 1, function(x) - any(x != "")) - col_indices <- apply(segments, 2, function(x) - any(x != "")) - - # subset segments and priors based on these indices - segments <- segments[row_indices, col_indices, drop = FALSE] - priors <- priors[row_indices, col_indices, drop = FALSE] - - # concat matrices - concatenated_matrix <- matrix( - paste(segments, priors, sep = "*+*"), - nrow = nrow(segments), - ncol = ncol(segments) - ) - - #SET: change number of concatenated_matrix below so that it matches the number of rows s_rows - - # List all possible cell combinations among the matrix considering only cells in different columns and must follow column order - - col_list <- lapply(1:ncol(segments), function(x) - concatenated_matrix[, x]) - - comb <- do.call(expand.grid, col_list) - - # Data conversion to avoid warnings - - comb[] <- lapply(comb, as.character) - - comb -} - -#' Clean the comb matrix -#' -#' Check each row for '*+*' and if found replace that cell and all following cells in the row with "". -#' Remove Blank Rows. Remove duplicate rows. -#' -#' @param comb A matrix containing the segmented formulas and priors -#' -#' @return A cleaned matrix -cleanComb <- function(comb) { - n_rows <- nrow(comb) - - # Check each row for '*+*' and if found replace that cell and all following cells in the row with "" - for (i in 1:n_rows) { - # Get the indices of the cells equal to "*+*" - replace_indices <- which(comb[i, ] == "*+*") - - # if '*+*' is found in the row replace it and following elements with "" - if (length(replace_indices) > 0) { - comb[i, replace_indices[1]:ncol(comb)] <- "" - } - } - - # Remove Blank Rows - comb <- comb[apply(comb, 1, function(x) - any(x != "")), , drop = FALSE] - - # Remove duplicate rows - comb <- unique(comb) - - comb -} - -#' Split the comb matrix into two matrices -#' -#' Split the comb matrix into two matrices based on the separator '*+*'. -#' -#' @param comb A matrix containing the segmented formulas and priors -#' -#' @return A list of two matrices -splitComb <- function(comb) { - # Create two empty matrices with the same dimensions as comb - mat1 <- matrix(ncol = ncol(comb), nrow = nrow(comb)) - mat2 <- matrix(ncol = ncol(comb), nrow = nrow(comb)) - - # Write a loop to iterate through each cell of comb - for (i in seq_len(nrow(comb))) { - for (j in seq_len(ncol(comb))) { - # Check if "*+*" is in the cell value - if (grepl("\\+", as.character(comb[i, j]))) { - # Split the cell value using the separator "*+*" - split_vals <- strsplit(as.character(comb[i, j]), split = "\\*\\+\\*")[[1]] - - # If "*+*" is found, split the cell value into two, assigning each part to the corresponding cell in mat1 and mat2 - mat1[i, j] <- split_vals[1] - mat2[i, j] <- split_vals[2] - - } else { - # If "*+*" is not found in the cell value, assign "" to the corresponding cells in mat1 and mat2 - mat1[i, j] <- "" - mat2[i, j] <- "" - } - } - } - - # Replacing NA values with empty string - mat1[is.na(mat1)] <- "" - mat2[is.na(mat2)] <- "" - - # Return the two matrices - list(mat1 = mat1, mat2 = mat2) -} - -#' Set formulas and priors -#' -#' @param splittedComb A list of two matrices containing the segmented formulas and priors -#' -#' @return A list of lists containing the segmented formulas and priors -setFormulasAndPriors <- function(splittedComb) { - mat1 <- splittedComb$mat1 - mat2 <- splittedComb$mat2 - - # Creating lists to hold all the lists - lists_seg <- vector("list", nrow(mat1)) - lists_prior <- vector("list", nrow(mat1)) - - # Looping to convert each string in the matrix to a formula and adding to the respective list. - - for (i in 1:nrow(mat1)) { - lists_seg[[i]] <- list() - lists_prior[[i]] <- list() - - for (j in 1:ncol(mat1)) { - if (mat1[i, j] != "") { - lists_seg[[i]] <- append(lists_seg[[i]], as.formula(mat1[i, j])) - - # For priors - if (mat2[i, j] != "") { - # first split string by commas corresponding to different priors - splits <- strsplit(mat2[i, j], split = ";")[[1]] - - for (k in 1:length(splits)) { - # split the string by = - split_str <- strsplit(splits[k], "=")[[1]] - - if (!is.na(split_str[1]) && !is.na(split_str[2])) { - lists_prior[[i]] <- append(lists_prior[[i]], trimws(split_str[2])) - names(lists_prior[[i]])[length(lists_prior[[i]])] <- trimws(split_str[1]) - } - } - } - } - } - - } - - list(lists_seg = lists_seg, lists_prior = lists_prior) -} - -#' Run mcp model -#' -#' @param lists A list of lists containing the segmented formulas and priors -#' @param ... Additional arguments to pass to mcp -#' -#' @return A list of mcp model fits -runMcp <- function(lists, ...) { - lists_seg <- lists$lists_seg - lists_prior <- lists$lists_prior - - # Loop through each list and run model - fit <- vector(mode = "list", length = length(lists_seg)) - - for (i in 1:length(lists_seg)) { - fit[[i]] <- mcp(model = lists_seg[[i]], prior = lists_prior[[i]], ...) - } - - fit -} - -#' Compare models using loo -#' -#' @param fit A list of mcp model fits -#' -#' @return A loo model comparison object -compareWithLoo <- function(fit) { - #Define list - loo_model <- vector("list", length(fit)) - - for (i in 1:length(fit)) { - loo_model[[i]] <- loo(fit[[i]]) - } - - #Results of model comparison - loo_compare(loo_model) -} - -#' Compare models using waic -#' -#' @param fit A list of mcp model fits -#' -#' @return A loo model comparison object -compareWithWAIC <- function(fit) { - #Define list - waic_model <- vector("list", length(fit)) - - for (i in 1:length(fit)) { - waic_model[[i]] <- waic(fit[[i]]) - } - - #Results of model comparison - loo_compare(waic_model) -} - -#' Compare models using heuristic -#' -#' The heuristic method is a model comparison method that combines the number of parameters and the elpd_diff value. -#' -#' @param fit A list of mcp model fits -#' -#' @return A loo model comparison object -compareWithHeuristic <- function(fit) { - comparison <- compareWithLoo(fit) - - row_names <- rownames(comparison) - model_number <- as.integer(gsub(".*model([0-9]*).*", "\\1", row_names)) - numEntries <- array(0, nrow(comparison)) - k <- 1 - for (i in model_number) { - numEntries[k] <- length(unlist(fit[[i]][3])) - k <- k + 1 - } - - comparison <- cbind(comparison, numEntries) - # calculate the ratio and use ifelse function to assign zero if division by zero occurs - new_column <- abs(ifelse(comparison[,2] != 0, comparison[,1]/comparison[,2], 0)) - comparison <- cbind(comparison, new_column) - - # Remove rows where the last column value is bigger than 5 - comparison <- comparison[comparison[,10] < 5, , drop = FALSE] - - #Order by lowest number of model parameters and for equal number of parameters by higher elpd_diff value - if (nrow(comparison) > 1) { - comparison <- comparison[order(comparison[,9], -comparison[,1]),] - comparison <- cbind(comparison, "Rank" = 1:nrow(comparison)) - } - - comparison -} diff --git a/R/02-matrixModule.R b/R/02-matrixModule.R deleted file mode 100644 index c1f17a1..0000000 --- a/R/02-matrixModule.R +++ /dev/null @@ -1,187 +0,0 @@ -#' Matrix Module -#' -#' @param title title of the matrix UI -#' @param maxRows maximum number of rows -#' @param maxColumns maximum number of columns -#' @param defaultCellContent default cell content -#' @param exampleLabel example label -#' @rdname matrixServer -matrixUI <- function(id, - title = "Matrix", - maxRows = 10, - maxColumns = 5, - defaultCellContent = "", - exampleLabel = "Example Matrix") { - ns <- NS(id) - tagList( - if (!is.null(title)) - tags$h4(title) - else - NULL, - fluidRow( - column( - width = 2, - selectInput( - ns("rows"), - "No. of rows", - selected = 1, - choices = 1:maxRows - ) - ), - column( - width = 2, - selectInput( - ns("cols"), - "No. of columns", - selected = 1, - choices = 1:maxColumns - ) - ), - column( - width = 2, - selectInput( - ns("cellID"), - "Cell ID (row, column)", - selected = 1, - choices = 1:1 - ) - ), - column( - width = 3, - textInput(ns("cell"), "Cell content", value = defaultCellContent) - ), - column( - width = 1, - style = "margin-top: 1.75em;", - actionButton(ns("set"), "Set") - ), - column( - width = 2, - align = "right", - style = "margin-top: 1.75em;", - actionButton(ns("example"), exampleLabel) - ) - ), - # output matrix - tags$br(), - tableOutput(ns("outputMatrix")) - ) -} - -#' Matrix Server -#' -#' @param id namespace id -#' @param exampleFunction function to load example input -#' @param validateCellFunction function to validate the cell content -#' @param ... additional arguments to example function -matrixServer <- function(id, - exampleFunction, - validateCellFunction = NULL, - ...) { - if (is.null(validateCellFunction)) { - validateCellFunction <- function(x) - x - } - moduleServer(id, function(input, output, session) { - dataMatrix <- reactiveVal() - - observe({ - # define empty matrix with number of rows and columns inputs - req(input[["rows"]], input[["cols"]]) - nrow <- input[["rows"]] %>% as.numeric() - ncol <- input[["cols"]] %>% as.numeric() - new_matrix <- matrix( - data = "", - nrow = nrow, - ncol = ncol, - byrow = TRUE - ) - colnames(new_matrix) <- 1:ncol - rownames(new_matrix) <- 1:nrow - dataMatrix(new_matrix) - updateSelectInput(session, "cellID", choices = getCellChoices(nrow = nrow, ncol = ncol)) - }) %>% - bindEvent(list(input[["rows"]], input[["cols"]])) - - output$outputMatrix <- renderTable({ - validate(need( - dataMatrix(), - "Please click 'Set' first or load 'Example Priors'" - )) - dataMatrix() - }, rownames = TRUE) - - observe({ - req(input[["cellID"]], input[["cell"]], length(dataMatrix())) - # set new value - id <- as.numeric(input[["cellID"]]) - new_matrix <- dataMatrix() - - { - new_matrix[id] <- input[["cell"]] %>% - validateCellFunction() - } %>% - shinyTryCatch(errorTitle = "Error in syntax") - dataMatrix(new_matrix) - - # inc id - maxID <- length(dataMatrix()) - newID <- ifelse(id == maxID, 1, id + 1) - updateSelectInput(session, "cellID", selected = newID) - }) %>% - bindEvent(input[["set"]]) - - observe({ - newMatrix <- exampleFunction(...) - dataMatrix(newMatrix) - updateSelectInput(session, "cellID", choices = getCellChoices( - nrow = nrow(newMatrix), - ncol = ncol(newMatrix) - )) - updateTextInput(session, "cell", value = newMatrix[1]) - }) %>% - bindEvent(input[["example"]]) - - return(dataMatrix) - }) -} - -#' Get cell choices -#' -#' @param nrow Number of rows -#' @param ncol Number of columns -#' -#' @return A named vector of cell choices -getCellChoices <- function(nrow, ncol) { - getCellLabel <- function(cellID, matrixDim) { - cellInd <- arrayInd(cellID, matrixDim) - - sprintf("(%s, %s)", cellInd[1], cellInd[2]) - } - - choices <- 1:(nrow * ncol) - - # get ID labels - labels <- sapply(choices, getCellLabel, matrixDim = c(nrow, ncol)) - names(choices) <- labels - - choices -} - -#' Check formula syntax -#' -#' Check if the formula syntax is valid -#' -#' @param formula A formula to check -#' -#' @return A formula if valid, otherwise an error -validateFormula <- function(formula) { - formula_check <- try(as.formula(formula), silent = TRUE) - - if (inherits(formula_check, "try-error")) { - warning("No formula, invalid syntax. Please check your input!") - return(formula) - } else { - return(formula) - } -} diff --git a/R/03-breakPointDetection.R b/R/03-breakPointDetection.R deleted file mode 100644 index 81c693e..0000000 --- a/R/03-breakPointDetection.R +++ /dev/null @@ -1,512 +0,0 @@ -#' Break Point Detection UI -#' -#' UI of the module -#' -#' @rdname breakPointDetectionServer -breakPointDetectionUI <- function(id) { - ns <- NS(id) - tagList(tags$br(), - tabsetPanel( - id = ns("breakPointTabs"), - tabPanel("1. MCP Lists from Segments & Priors", mcpFormulasUI(ns("formulas"))), - tabPanel( - "2. MCP Modeling", - mcpModelingUI(ns("mcp")), - mcpShowSingleModelUI(ns("singleModelOut")) - ), - tabPanel("3. Comparison of Models", mcpCompareModelsUI(ns( - "compareModelsOut" - ))) - ), - tags$br()) -} - -#' Break Point Detection Server -#' -#' Server function of the module -#' -#' @param id The module id -#' @param plotData The reactive plot data -breakPointDetectionServer <- function(id, plotData) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - mcpData <- reactiveVal() - - observe({ - req(nrow(plotData()) > 0) - # select relevant columns - newData <- plotData()[, c("time", "median")] - # rename columns - colnames(newData) <- c("x", "y") - # remove rows with NA - newData <- na.omit(newData) - - mcpData(newData) - }) %>% bindEvent(plotData()) - - # example data instead of plot data - # observe({ - # mcpData(read.csv(file.path("data", "example_breakPoints.csv"))) - # }) %>% - # bindEvent(input[["mcp-loadExampleDf"]]) - - formulasAndPriors <- mcpFormulasServer(id = "formulas") - - mcpFitList <- mcpModelingServer(id = "mcp", - mcpData = mcpData, - formulasAndPriors = formulasAndPriors) - - mcpShowSingleModelServer( - id = "singleModelOut", - mcpData = mcpData, - formulasAndPriors = formulasAndPriors, - mcpFitList = mcpFitList - ) - - mcpCompareModelsServer( - id = "compareModelsOut", - mcpData = mcpData, - formulasAndPriors = formulasAndPriors, - mcpFitList = mcpFitList - ) - }) -} - - -# 1. MCP Segments & Priors ---- - -#' MCP Formulas UI -#' -#' @rdname mcpFormulasServer -mcpFormulasUI <- function(id) { - ns <- NS(id) - tagList( - tags$br(), - fluidRow(column(8, tags$h4("Segments")), column( - 4, align = "right", infoButtonUI(ns("formulaInfo"), label = "Segment Description") - )), - matrixUI( - ns("segments"), - title = NULL, - defaultCellContent = "y ~ 1 + x", - exampleLabel = "Example Segments" - ), - tags$br(), - fluidRow(column(8, tags$h4("Priors")), column( - 4, align = "right", infoButtonUI(ns("PriorInfo"), label = "Prior Description") - )), - matrixUI( - ns("priors"), - title = NULL, - defaultCellContent = "x_1 = dunif(-4, -0.5);", - exampleLabel = "Example Priors" - ), - tags$br(), - fluidRow( - column(10, verbatimTextOutput(ns("mcpFormulas"))), - column( - 2, - align = "right", - actionButton(ns("apply"), "Create MCP Lists", disabled = TRUE) - ) - ) - ) -} - -#' MCP Formulas Server -#' -#' @param id The module id -mcpFormulasServer <- function(id) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - formulasAndPriors <- reactiveVal() - - segmentsMatrix <- matrixServer( - "segments", - exampleFunction = readExampleMatrix, - validateCellFunction = validateFormula, - path = file.path("data", "example_breakPointSegments.csv") - ) - priorsMatrix <- matrixServer( - "priors", - exampleFunction = readExampleMatrix, - path = file.path("data", "example_breakPointPriors.csv") - ) - - infoButtonServer( - id = "formulaInfo", - title = "'Formula' (segment) description", - text = "The 'formula' argument in mcp defines the model structure. It specifies the relationship between - variables and the change points in your data. The formula should be written similarly to formulas - in base R, with additional support for change points.", - link = "https://lindeloev.github.io/mcp/articles/formulas.html" - ) - - infoButtonServer( - id = "PriorInfo", - title = "'Prior' description", - text = "The 'prior' argument in mcp specifies the prior distributions for the parameters in your model. - It should be a named list where the names correspond to the model parameters, - and the values are prior distributions in JAGS notation. If you don't specify a prior for a parameter, - mcp will use a default weakly informative prior.", - link = "https://lindeloev.github.io/mcp/articles/priors.html" - ) - - observe({ - req(segmentsMatrix(), priorsMatrix()) - - # enable the 'Create MCP Formulas' button - shinyjs::enable(ns("apply"), asis = TRUE) - #updateActionButton(session, "apply", disabled = FALSE) # not working with current version in docker - }) %>% bindEvent(list(segmentsMatrix(), priorsMatrix())) - - observe({ - newFormulasAndPriors <- getComb(segments = segmentsMatrix(), priors = priorsMatrix()) %>% - cleanComb() %>% - splitComb() %>% - setFormulasAndPriors() %>% - shinyTryCatch(errorTitle = "Error in creating MCP lists", warningTitle = "Warning in creating MCP lists") - - formulasAndPriors(newFormulasAndPriors) - }) %>% - bindEvent(input[["apply"]]) - - output$mcpFormulas <- renderPrint({ - validate(need( - formulasAndPriors(), - "Please 'Set' Segments and Priors first ..." - )) - formulasAndPriors() - }) - - return(formulasAndPriors) - }) -} - -#' Info Button UI -#' -#' @param label The label of the button -#' @rdname infoButtonServer -infoButtonUI <- function(id, label = "Description") { - ns <- NS(id) - tagList(actionButton(ns("show_info"), icon = icon("info-circle"), label)) -} - -#' Info Button Server -#' -#' @param id The module id -#' @param title The title of the modal -#' @param text The text to display in the modal -#' @param link The link to the documentation -infoButtonServer <- function(id, - title = "Description", - text, - link = NULL) { - moduleServer(id, function(input, output, session) { - observe({ - showModal( - modalDialog( - title = title, - p(text), - # Add a hyperlink to the github help page - if (!is.null(link)) { - p( - "For more details, visit the", - a("documentation", href = link, target = "_blank"), - "." - ) - } else - NULL, - footer = modalButton("Close"), - easyClose = TRUE - ) - ) - }) %>% - bindEvent(input$show_info) - }) -} - - -# 2. MCP Modeling ---- - -#' MCP Modeling UI -#' -#' @rdname mcpModelingServer -mcpModelingUI <- function(id) { - ns <- NS(id) - tagList(tags$br(), - fluidRow( - column( - 3, - numericInput(ns("adapt"), "Burn in length", value = 5000), - helpText("Increase for better conversion (makes the run slower).") - ), - column(3, numericInput( - ns("chains"), - "Number of chains", - value = 3, - min = 1 - )), - column( - 3, - numericInput( - ns("iter"), - "Number of iterations", - value = 3000, - min = 1 - ) - ), - column( - 3, - align = "right", - style = "margin-top: 1.75em;", - # load example data instead of plot data: - #actionButton(ns("loadExampleDf"), "Load Example Data"), - actionButton(ns("apply"), "Run MCP", disabled = TRUE) - ) - ), - tags$hr()) -} - -#' MCP Modeling Server -#' -#' @inheritParams mcpOutServer -mcpModelingServer <- function(id, formulasAndPriors, mcpData) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - - mcpFitList <- reactiveVal() - - observe({ - req(formulasAndPriors(), mcpData()) - # enable the 'Run Model' button - shinyjs::enable(ns("apply"), asis = TRUE) # use this instead of updateActionButton - #updateActionButton(session, "apply", disabled = FALSE) # not working with current version in docker - }) %>% bindEvent(formulasAndPriors(), mcpData()) - - observe({ - res <- runMcp( - lists = formulasAndPriors(), - data = mcpData(), - adapt = input[["adapt"]], - chains = input[["chains"]], - iter = input[["iter"]] - ) %>% - shinyTryCatch(errorTitle = "Error in fitting mcp model", warningTitle = "Warning in fitting mcp model") %>% - withProgress(message = "Fitting MCP model...", value = 0.5) - - mcpFitList(res) - }) %>% - bindEvent(input[["apply"]]) - - return(mcpFitList) - }) -} - -#' MCP Show Single Model UI -#' -#' @rdname mcpShowSingleModelServer -mcpShowSingleModelUI <- function(id) { - ns <- NS(id) - tagList( - selectInput( - ns("showModel"), - "Show MCP model", - choices = c("'Run MCP' first ..." = "") - ), - tags$br(), - fluidRow(column( - 6, - mcpOutUI( - id = ns("summary"), - title = "Model Summary", - outFUN = verbatimTextOutput, - showWidth = TRUE - ) - ), column( - 6, - mcpOutUI( - id = ns("waic"), - title = "Model WAIC", - outFUN = verbatimTextOutput - ) - )), - mcpOutUI( - id = ns("plot"), - title = "Model Plot", - outFUN = plotOutput - ) - ) -} - -#' MCP Show Single Model Server -#' -#' @inheritParams mcpOutServer -mcpShowSingleModelServer <- function(id, - mcpData, - formulasAndPriors, - mcpFitList) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - mcpModel <- reactiveVal() - - observe({ - mcpModels <- seq_along(mcpFitList()) - names(mcpModels) <- paste("Model", mcpModels) - if (length(mcpModels) > 0) - mcpSelected <- 1 - else - mcpSelected <- NULL - updateSelectInput(session, - "showModel", - choices = mcpModels, - selected = mcpSelected) - }) %>% - bindEvent(mcpFitList()) - - observe({ - req(mcpFitList(), input[["showModel"]]) - res <- mcpFitList()[[as.numeric(input[["showModel"]])]] - mcpModel(res) - }) %>% bindEvent(input[["showModel"]]) - - mcpOutServer( - id = "summary", - formulasAndPriors = formulasAndPriors, - mcpData = mcpData, - mcpFitList = mcpFitList, - mcpModel = mcpModel, - outFUN = summary, - renderFUN = renderPrint - ) - - mcpOutServer( - id = "waic", - formulasAndPriors = formulasAndPriors, - mcpData = mcpData, - mcpFitList = mcpFitList, - mcpModel = mcpModel, - outFUN = waic, - renderFUN = renderPrint - ) - - mcpOutServer( - id = "plot", - formulasAndPriors = formulasAndPriors, - mcpData = mcpData, - mcpFitList = mcpFitList, - mcpModel = mcpModel, - outFUN = plot, - renderFUN = renderPlot - ) - }) -} - -#' MCP Output UI -#' -#' @param title The title of the output -#' @param showWidth Show the width slider -#' @rdname mcpOutServer -mcpOutUI <- function(id, - title, - outFUN = verbatimTextOutput, - showWidth = FALSE) { - ns <- NS(id) - - tagList(tags$h4(title), - outFUN(ns("modelOut")) %>% withSpinner(color = "#20c997"), - if (showWidth) { - sliderInput( - ns("width"), - "Summary width", - min = 0.01, - max = 0.99, - value = 0.95, - step = 0.01, - width = "100%" - ) - }) -} - -#' MCP Output Server -#' -#' @param id The module id -#' @param formulasAndPriors The reactive formulas and priors -#' @param mcpData The reactive mcp data -#' @param mcpFitList The reactive mcp fit list -#' @param mcpModel The reactive mcp model -#' @param outFUN The output function -#' @param renderFUN The render function -mcpOutServer <- function(id, - formulasAndPriors, - mcpData, - mcpFitList, - mcpModel, - outFUN, - renderFUN = renderPrint) { - moduleServer(id, function(input, output, session) { - output$modelOut <- renderFUN({ - validate(need( - formulasAndPriors(), - "Please 'Create MCP Lists' and 'Run MCP' first ..." - )) - validate(need(mcpData(), "Please load 'Plot Data' and 'Run MCP' first ...")) - validate(need(mcpFitList(), "Please 'Run MCP' first ...")) - validate(need(mcpModel(), "Please select MCP model first ...")) - - params <- ifelse(is.null(input[["width"]]), list(), list(width = input[["width"]])) - - do.call(outFUN, c(list(mcpModel()), params)) %>% - shinyTryCatch( - errorTitle = sprintf("Error during creating '%s' output", id), - warningTitle = sprintf("Warning during creating '%s' output", id) - ) - }) - }) -} - -# 3. Comparison of Models ---- - -#' MCP Compare Models UI -#' -#' @rdname mcpCompareModelsServer -mcpCompareModelsUI <- function(id) { - ns <- NS(id) - - tagList( - selectInput(ns("method"), "Method", c("loo", "waic", "heuristic")), - tags$br(), - verbatimTextOutput(ns("compareModels")) %>% withSpinner(color = "#20c997") - ) -} - -#' MCP Compare Models Server -#' -#' @inheritParams mcpOutServer -mcpCompareModelsServer <- function(id, - formulasAndPriors, - mcpData, - mcpFitList) { - moduleServer(id, function(input, output, session) { - ns <- session$ns - compareModels <- reactiveVal() - - output$compareModels <- renderPrint({ - validate(need( - formulasAndPriors(), - "Please 'Create MCP Lists' and 'Run MCP' first ..." - )) - validate(need(mcpData(), "Please load 'Plot Data' and 'Run MCP' first ...")) - validate(need(mcpFitList(), "Please 'Run MCP' first ...")) - - compareFUN <- switch(input[["method"]], - loo = compareWithLoo, - waic = compareWithWAIC, - heuristic = compareWithHeuristic) - - mcpFitList() %>% - compareFUN() %>% - shinyTryCatch(errorTitle = "Error in model comparison", warningTitle = "Warning in model comparison") - }) - }) -} diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index 2606089..b530096 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -130,7 +130,7 @@ timePlotFormattingUI <- function(id) { # Break Point Detection ---- "Break point detection", value = "breakPointTab", - breakPointDetectionUI(ns("breakPointDetection")) + changePointsUI(ns("changePoints")) ) ) , @@ -256,15 +256,13 @@ timePlotFormattingServer <- function(id, savedModels) { }) # export plot data ---- - plotDataExport <- reactiveVal() - - observe({ - plotDataExport(extractedPlotDataDF()) - }) %>% - bindEvent(input[["plotTimeModels"]]) - - dataExportServer("exportCredIntTimeData", - reactive(function() {plotDataExport()})) + dataExportServer("exportCredIntTimeData", filename = "timePlotData", reactive(function() { + if (length(input[["plotTimeModels"]]) == 0 || + any(input[["plotTimeModels"]] == "")) + return(NULL) + + extractedPlotDataDF() + })) newPlot <- reactive({ logDebug("%s: Entering: reactive 'newPlot'", id) @@ -347,7 +345,21 @@ timePlotFormattingServer <- function(id, savedModels) { ) # Break point detection ---- - breakPointDetectionServer(id = "breakPointDetection", plotData = extractedPlotDataDF) + changePointData <- reactiveValues() + observe({ + changePointData$mainData <- extractedPlotDataDF() + }) %>% + bindEvent(extractedPlotDataDF()) + + changePointsServer( + "changePoints", + file_data = changePointData, + mcp_columns = c(x = "time", y = "median") + ) + # note: restoring a whole session as in ChangeR will not be possible (much too + # complex) since inputs are send much earlier than all reactive objects are updated. + # As a result the inputs cannot be set correctly and plots will remain empty. + }) } diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index d931ea1..87ac61e 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -11,34 +11,28 @@ #' @import rstantools #' @import shiny #' @import shinythemes -#' @importFrom colourpicker colourInput +#' @importFrom ChangeR changePointsServer changePointsUI #' @importFrom DataTools checkAnyNonNumericColumns downloadModelUI downloadModelServer #' importDataUI importDataServer importUI importServer importOptions renameExistingNames -#' @importFrom dplyr arrange bind_cols bind_rows cur_group_id distinct do group_by mutate n select -#' slice ungroup +#' @importFrom dplyr %>% arrange bind_cols bind_rows cur_group_id distinct do group_by mutate n +#' select slice ungroup #' @importFrom ggplot2 aes coord_cartesian element_line element_text #' geom_line geom_point geom_ribbon geom_vline ggplot ggplot_build ggtitle labs #' scale_colour_manual scale_fill_manual scale_shape_manual scale_size_manual #' scale_x_continuous scale_y_continuous sec_axis theme -#' @importFrom htmltools save_html -#' @importFrom jsonlite toJSON -#' @importFrom loo loo loo_compare waic -#' @importFrom magrittr %>% -#' @importFrom mcp mcp -#' @importFrom openxlsx write.xlsx #' @importFrom parallel detectCores #' @importFrom rlang .data #' @importFrom rstan sampling extract #' @importFrom shinycssloaders withSpinner #' @importFrom shinyjs alert enable runjs -#' @importFrom shinyTools calculateRescalingFactors dataExportButton dataExportServer extractTitle +#' @importFrom shinyTools calculateRescalingFactors dataExportButton dataExportServer extractTitle #' formatLegendOfGGplot formatPointsOfGGplot formatScalesOfGGplot formatTitlesOfGGplot #' plotExportButton plotExportServer plotLegendServer plotLegendUI plotPointsServer plotPointsUI #' plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI #' shinyTryCatch #' @importFrom shinyWidgets pickerInput updatePickerInput -#' @importFrom stats approx as.formula dnorm lm median na.omit quantile sd -#' @importFrom utils read.csv write.csv write.table combn +#' @importFrom stats approx dnorm lm median na.omit quantile sd +#' @importFrom utils write.csv combn #' @importFrom yaml read_yaml #' @references #' Stan Development Team (NA). RStan: the R interface to Stan. R package version NA. http://mc-stan.org diff --git a/R/exportData.R b/R/exportData.R deleted file mode 100644 index f92e8f7..0000000 --- a/R/exportData.R +++ /dev/null @@ -1,38 +0,0 @@ -#' Filename of Export -#' -#' @param fileending character csv or xlsx -#' @export -exportFilename <- function(fileending){ - paste("isotopeData", fileending, sep = ".") -} - -#' Export to csv -#' -#' @param file filename -#' @param dat data.frame -#' @param colseparator column seperator -#' @param decseparator decimal seperator -#' @export -exportCSV <- function(file, dat, colseparator, decseparator){ - write.table(x = dat, file = file, sep = colseparator, - dec = decseparator, row.names = FALSE) -} - -#' Export to xlsx -#' -#' @param file filename -#' @param dat data.frame -#' @export -exportXLSX <- function(file, dat){ - write.xlsx(dat, file) -} - -#' Export to json -#' -#' @param file filename -#' @param dat data.frame -#' @export -exportJSON <- function(file, dat){ - json <- toJSON(dat) - write(json, file) -} \ No newline at end of file diff --git a/inst/app/data/example.csv b/inst/app/data/example.csv new file mode 100644 index 0000000..3aedfcb --- /dev/null +++ b/inst/app/data/example.csv @@ -0,0 +1,12 @@ +"";"individual";"intStart";"intEnd";"bone1";"bone2";"tooth1";"tooth2" +"1";1;0;1;100;100;0;0 +"2";1;1;2;50;10;100;0 +"3";1;2;3;20;5;0;100 +"4";11;4;5;5;1;0;0 +"5";11;5;6;2;1;0;0 +"6";2;0;1;100;90;0;NA +"7";2;1;2;80;40;80;NA +"8";2;2;3;30;5;20;NA +"9";2;3;4;8;1;0;NA +"10";2;4;5;15;12;0;NA +"11";2;5;6;4;1;0;NA diff --git a/inst/app/data/example_breakPointPriors.csv b/inst/app/data/example_breakPointPriors.csv deleted file mode 100644 index d01fcc1..0000000 --- a/inst/app/data/example_breakPointPriors.csv +++ /dev/null @@ -1,4 +0,0 @@ -"V1","V2","V3","V4" -"x_1 = dunif(-4, -0.5);","","","" -"","","","" -"","","","" diff --git a/inst/app/data/example_breakPointSegments.csv b/inst/app/data/example_breakPointSegments.csv deleted file mode 100644 index 8bafe3a..0000000 --- a/inst/app/data/example_breakPointSegments.csv +++ /dev/null @@ -1,4 +0,0 @@ -"V1","V2","V3","V4" -"y ~ 1 + x","y ~ 1 ~ 0 + x","y ~ 1 ~ 0 + x","y ~ 1 ~ 0 + x" -"y ~ 1 + x","","","" -"","","","" diff --git a/inst/app/data/example_breakPoints.csv b/inst/app/data/example_breakPoints.csv deleted file mode 100644 index 4760dbd..0000000 --- a/inst/app/data/example_breakPoints.csv +++ /dev/null @@ -1,15 +0,0 @@ -x,y -0.25,12.53 -0.75,12.03 -1.25,11.54 -1.75,11.5 -2.25,11.5 -2.75,11.53 -3.25,11.67 -3.75,11.75 -4.75,11.99 -5.25,12.01 -6.75,11.68 -7.25,11.66 -7.75,11.66 -8.25,11.68 diff --git a/inst/app/server.R b/inst/app/server.R index 228e38e..a188dd8 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -487,46 +487,19 @@ shinyServer(function(input, output, session) { bindEvent(uploadedValues()) ## Export Summary ---- - observeEvent(input$exportSummary, { - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExecuteSummary", "Export") - )) - }) - - output$exportExecuteSummary <- downloadHandler( - filename = function(){ - exportFilename(input$exportType) - }, - content = function(file){ - req(fit()) + shinyTools::dataExportServer("exportSummary", filename = "isotopeData", dataFun = reactive({ + function() { + if (is.null(fit())) + return(NULL) + exportData <- as.data.frame(extract(fit())$interval) namesStan <- names(fit()) intervalNamesStan <- namesStan[grepl(pattern = "interval", namesStan)] colnames(exportData) <- intervalNamesStan - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) + + exportData } - ) + })) output$plot <- renderPlot({ req(fit()) @@ -607,145 +580,44 @@ shinyServer(function(input, output, session) { # ) # }) + shinyTools::dataExportServer("exportTimePointEst", + filename = "timePointEstimates", + dataFun = reactive({ + function() { + if (length(input$savedModelsTime) == 0 || + any(input$savedModelsTime == "") || + length(input$estSpecTimePoint) == 0 || + input$estSpecTimePoint == 0 || + is.character(estimates())) + return(NULL) + + estimates() + } + })) + + shinyTools::dataExportServer("exportCredIntDat", + filename = "credibilityIntervals", + dataFun = reactive({ + function() { + if (is.null(fit())) + return(NULL) + + as.data.frame(fit()) + } + })) - observeEvent(input$exportTimePointEst, { - req(estimates()) - req(!is.character(estimates())) - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExecuteTimePointEst", "Export") - )) - - colseparator <- reactive({ - input$colseparator - }) - decseparator <- reactive({ - input$decseparator - }) - - output$exportExecuteTimePointEst <- downloadHandler( - filename = function(){ - paste("timePointEstimates", input$exportType, sep = ".") - }, - content = function(file){ - exportData <- estimates() - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) - } - ) - }) - - observeEvent(input$exportCredIntDat, { - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExecuteCredIntDat", "Export") - )) - - colseparator <- reactive({ - input$colseparator - }) - decseparator <- reactive({ - input$decseparator - }) - - output$exportExecuteCredIntDat <- downloadHandler( - filename = function(){ - paste("credibilityIntervals", input$exportType, sep = ".") - }, - content = function(file){ - req(fit()) - exportData <- as.data.frame(fit()) - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) - } - ) - }) - # Downloads Plots ------------------------------------------------- - - observeEvent(input$exportCredIntPlot, { - - plotOutputElement <- renderPlot({ - req(fit()) - plot(fit(), prop = input$modCredInt) - #OsteoBioR::plot(fit(), prop = input$modCredInt) - }) - exportTypeChoices <- c("png", "pdf", "svg", "tiff") - - showModal(modalDialog( - title = "Export Graphic", - footer = modalButton("OK"), - plotOutputElement, - selectInput( - "exportType", "Filetype", - choices = exportTypeChoices - ), - numericInput("width", "Width (px)", value = 1280), - numericInput("height", "Height (px)", value = 800), - downloadButton("exportExecute", "Export"), - easyClose = TRUE - )) - - output$exportExecute <- downloadHandler( - filename = function(){ - paste0(gsub("-", "", Sys.Date()), "_", "Credibility_Intervals", ".", input$exportType) - }, - content = function(file){ - switch( - input$exportType, - png = png(file, width = input$width, height = input$height), - pdf = pdf(file, width = input$width / 72, height = input$height / 72), - tiff = tiff(file, width = input$width, height = input$height), - svg = svg(file, width = input$width / 72, height = input$height / 72) - ) - print({ - req(fit()) - #OsteoBioR::plot(fit(), prop = input$modCredInt) - plot(fit(), prop = input$modCredInt) - }) - - dev.off() - } - ) - }) + shinyTools::plotExportServer("exportCredIntPlot", + plotFun = reactive({ + function() { + if (length(fit()) == 0) return(NULL) + + #OsteoBioR::plot(fit(), prop = input$modCredInt) + plot(fit(), prop = input$modCredInt) + } + }), + plotType = "none", #"ggplot", #<- fix issue with labels first + filename = sprintf("%s_Credibility_Intervals", gsub("-", "", Sys.Date()))) # RESIDING TIME ------------------------------------------ datStayTime <- reactiveValues() @@ -795,51 +667,19 @@ shinyServer(function(input, output, session) { }) output$estimatedStayTimes <- renderPrint({ estimatedStayTimes() }) - observeEvent(input$exportStayTimeDat, { - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExecuteStayTimeDat", "Export") - )) - - colseparator <- reactive({ - input$colseparator - }) - decseparator <- reactive({ - input$decseparator - }) - - output$exportExecuteStayTimeDat <- downloadHandler( - filename = function(){ - paste("stayTimeLengths", input$exportType, sep = ".") - }, - content = function(file){ - resTime <- estimatedStayTimes() - exportData <- as.data.frame(resTime$stayTimes) - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) - } - ) - }) - + shinyTools::dataExportServer("exportStayTimeDat", + filename = "stayTimeLengths", + dataFun = reactive({ + function() { + if (length(fit()) == 0 || + is.null(input$stayingTime) || + input$stayingTime == 0 || length(estimatedStayTimes()) == 0) + return(NULL) + + resTime <- estimatedStayTimes() + as.data.frame(resTime$stayTimes) + } + })) # COMPUTE ISOTOPIC VALUES ------------------------------ datIso <- reactiveValues() @@ -908,49 +748,19 @@ shinyServer(function(input, output, session) { output$isotopicValues <- renderTable({ isotopicValues() }, rownames = TRUE) - observeEvent(input$exportResultsDat, { - showModal(modalDialog( - "Export Data", - easyClose = TRUE, - footer = modalButton("OK"), - selectInput( - "exportType", - "File type", - choices = c("csv", "xlsx", "json"), - selected = "xlsx" - ), - conditionalPanel( - condition = "input['exportType'] == 'csv'", - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("colseparator", "column separator:", value = ",")), - div(style = "display: inline-block;horizontal-align:top; width: 80px;", - textInput("decseparator", "decimal separator:", value = ".")) - ), - downloadButton("exportExportResultsDat", "Export") - )) - - colseparator <- reactive({ - input$colseparator - }) - decseparator <- reactive({ - input$decseparator - }) - - output$exportExportResultsDat <- downloadHandler( - filename = function(){ - paste("isotopicValues", input$exportType, sep = ".") - }, - content = function(file){ - exportData <- isotopicValues() - switch( - input$exportType, - csv = exportCSV(file, exportData, colseparator(), decseparator()), - xlsx = exportXLSX(file, exportData), - json = exportJSON(file, exportData) - ) - } - ) - }) + shinyTools::dataExportServer("exportResultsDat", + filename = "isotopicValues", + dataFun = reactive({ + function() { + if (length(input$historicData) == 0 || + any(input$historicData == "") || + length(input$calcIsotopicValues) == 0 || + input$calcIsotopicValues == 0) + return(NULL) + + isotopicValues() + } + })) observeEvent(input$getHelp, { showModal(modalDialog( diff --git a/inst/app/ui.R b/inst/app/ui.R index 1cf798e..7df62cd 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -171,15 +171,15 @@ tagList( value = "summaryTab", verbatimTextOutput("summary") %>% withSpinner(color = "#20c997"), - actionButton("exportSummary", "Export Interval Data") + shinyTools::dataExportButton("exportSummary", label = "Export Interval Data") ), tabPanel( "Credibility Intervals", value = "credibilityIntervalsTab", plotOutput("plot") %>% withSpinner(color = "#20c997"), - actionButton("exportCredIntPlot", "Export Plot"), - actionButton("exportCredIntDat", "Export Data") + shinyTools::plotExportButton("exportCredIntPlot", label = "Export Plot"), + shinyTools::dataExportButton("exportCredIntDat", label = "Export Data") ), tabPanel( "Credibility intervals over time", @@ -252,7 +252,7 @@ tagList( ), column(4, tags$br(), - actionButton("exportTimePointEst", "Export Time Point Estimates") + shinyTools::dataExportButton("exportTimePointEst", label = "Export Time Point Estimates") ) ), tags$br(), @@ -335,7 +335,7 @@ tagList( HTML("

Results

")), verbatimTextOutput("estimatedStayTimes"), tags$br(), - actionButton("exportStayTimeDat", "Export estimated residence time lengths") + shinyTools::dataExportButton("exportStayTimeDat", label = "Export estimated residence time lengths") ) ) ), @@ -442,7 +442,7 @@ tagList( tableOutput("isotopicValues"), verbatimTextOutput("quant"), tags$br(), - actionButton("exportResultsDat", "Export Isotopic Values") + shinyTools::dataExportButton("exportResultsDat", label = "Export Isotopic Values") ) )) # STYLE of navbarPage ---- diff --git a/man/breakPointDetectionServer.Rd b/man/breakPointDetectionServer.Rd deleted file mode 100644 index a2e97f8..0000000 --- a/man/breakPointDetectionServer.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/03-breakPointDetection.R -\name{breakPointDetectionUI} -\alias{breakPointDetectionUI} -\alias{breakPointDetectionServer} -\title{Break Point Detection UI} -\usage{ -breakPointDetectionUI(id) - -breakPointDetectionServer(id, plotData) -} -\arguments{ -\item{id}{The module id} - -\item{plotData}{The reactive plot data} -} -\description{ -UI of the module - -Server function of the module -} diff --git a/man/cleanComb.Rd b/man/cleanComb.Rd deleted file mode 100644 index 29deb0e..0000000 --- a/man/cleanComb.Rd +++ /dev/null @@ -1,18 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{cleanComb} -\alias{cleanComb} -\title{Clean the comb matrix} -\usage{ -cleanComb(comb) -} -\arguments{ -\item{comb}{A matrix containing the segmented formulas and priors} -} -\value{ -A cleaned matrix -} -\description{ -Check each row for '*+*' and if found replace that cell and all following cells in the row with "". -Remove Blank Rows. Remove duplicate rows. -} diff --git a/man/compareWithHeuristic.Rd b/man/compareWithHeuristic.Rd deleted file mode 100644 index f9a55fd..0000000 --- a/man/compareWithHeuristic.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{compareWithHeuristic} -\alias{compareWithHeuristic} -\title{Compare models using heuristic} -\usage{ -compareWithHeuristic(fit) -} -\arguments{ -\item{fit}{A list of mcp model fits} -} -\value{ -A loo model comparison object -} -\description{ -The heuristic method is a model comparison method that combines the number of parameters and the elpd_diff value. -} diff --git a/man/compareWithLoo.Rd b/man/compareWithLoo.Rd deleted file mode 100644 index 42169b3..0000000 --- a/man/compareWithLoo.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{compareWithLoo} -\alias{compareWithLoo} -\title{Compare models using loo} -\usage{ -compareWithLoo(fit) -} -\arguments{ -\item{fit}{A list of mcp model fits} -} -\value{ -A loo model comparison object -} -\description{ -Compare models using loo -} diff --git a/man/compareWithWAIC.Rd b/man/compareWithWAIC.Rd deleted file mode 100644 index 024d47d..0000000 --- a/man/compareWithWAIC.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{compareWithWAIC} -\alias{compareWithWAIC} -\title{Compare models using waic} -\usage{ -compareWithWAIC(fit) -} -\arguments{ -\item{fit}{A list of mcp model fits} -} -\value{ -A loo model comparison object -} -\description{ -Compare models using waic -} diff --git a/man/exportCSV.Rd b/man/exportCSV.Rd deleted file mode 100644 index 0b9b2c4..0000000 --- a/man/exportCSV.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/exportData.R -\name{exportCSV} -\alias{exportCSV} -\title{Export to csv} -\usage{ -exportCSV(file, dat, colseparator, decseparator) -} -\arguments{ -\item{file}{filename} - -\item{dat}{data.frame} - -\item{colseparator}{column seperator} - -\item{decseparator}{decimal seperator} -} -\description{ -Export to csv -} diff --git a/man/exportFilename.Rd b/man/exportFilename.Rd deleted file mode 100644 index 6226c3d..0000000 --- a/man/exportFilename.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/exportData.R -\name{exportFilename} -\alias{exportFilename} -\title{Filename of Export} -\usage{ -exportFilename(fileending) -} -\arguments{ -\item{fileending}{character csv or xlsx} -} -\description{ -Filename of Export -} diff --git a/man/exportJSON.Rd b/man/exportJSON.Rd deleted file mode 100644 index 1857578..0000000 --- a/man/exportJSON.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/exportData.R -\name{exportJSON} -\alias{exportJSON} -\title{Export to json} -\usage{ -exportJSON(file, dat) -} -\arguments{ -\item{file}{filename} - -\item{dat}{data.frame} -} -\description{ -Export to json -} diff --git a/man/exportXLSX.Rd b/man/exportXLSX.Rd deleted file mode 100644 index 310c692..0000000 --- a/man/exportXLSX.Rd +++ /dev/null @@ -1,16 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/exportData.R -\name{exportXLSX} -\alias{exportXLSX} -\title{Export to xlsx} -\usage{ -exportXLSX(file, dat) -} -\arguments{ -\item{file}{filename} - -\item{dat}{data.frame} -} -\description{ -Export to xlsx -} diff --git a/man/getCellChoices.Rd b/man/getCellChoices.Rd deleted file mode 100644 index 6004e76..0000000 --- a/man/getCellChoices.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-matrixModule.R -\name{getCellChoices} -\alias{getCellChoices} -\title{Get cell choices} -\usage{ -getCellChoices(nrow, ncol) -} -\arguments{ -\item{nrow}{Number of rows} - -\item{ncol}{Number of columns} -} -\value{ -A named vector of cell choices -} -\description{ -Get cell choices -} diff --git a/man/getComb.Rd b/man/getComb.Rd deleted file mode 100644 index 8729ae7..0000000 --- a/man/getComb.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{getComb} -\alias{getComb} -\title{Get the comb matrix} -\usage{ -getComb(segments, priors) -} -\arguments{ -\item{segments}{A matrix containing the segmented formulas} - -\item{priors}{A matrix containing the priors} -} -\value{ -A matrix containing the segmented formulas and priors -} -\description{ -Get the combined matrix of segments and priors. -Process the segments and priors matrices, identify relevant rows and columns, concatenate them, -and generate possible cell combinations while ensuring data consistency. -} diff --git a/man/infoButtonServer.Rd b/man/infoButtonServer.Rd deleted file mode 100644 index 62b5806..0000000 --- a/man/infoButtonServer.Rd +++ /dev/null @@ -1,27 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/03-breakPointDetection.R -\name{infoButtonUI} -\alias{infoButtonUI} -\alias{infoButtonServer} -\title{Info Button UI} -\usage{ -infoButtonUI(id, label = "Description") - -infoButtonServer(id, title = "Description", text, link = NULL) -} -\arguments{ -\item{id}{The module id} - -\item{label}{The label of the button} - -\item{title}{The title of the modal} - -\item{text}{The text to display in the modal} - -\item{link}{The link to the documentation} -} -\description{ -Info Button UI - -Info Button Server -} diff --git a/man/matrixServer.Rd b/man/matrixServer.Rd deleted file mode 100644 index e3079a9..0000000 --- a/man/matrixServer.Rd +++ /dev/null @@ -1,42 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-matrixModule.R -\name{matrixUI} -\alias{matrixUI} -\alias{matrixServer} -\title{Matrix Module} -\usage{ -matrixUI( - id, - title = "Matrix", - maxRows = 10, - maxColumns = 5, - defaultCellContent = "", - exampleLabel = "Example Matrix" -) - -matrixServer(id, exampleFunction, validateCellFunction = NULL, ...) -} -\arguments{ -\item{id}{namespace id} - -\item{title}{title of the matrix UI} - -\item{maxRows}{maximum number of rows} - -\item{maxColumns}{maximum number of columns} - -\item{defaultCellContent}{default cell content} - -\item{exampleLabel}{example label} - -\item{exampleFunction}{function to load example input} - -\item{validateCellFunction}{function to validate the cell content} - -\item{...}{additional arguments to example function} -} -\description{ -Matrix Module - -Matrix Server -} diff --git a/man/mcpCompareModelsServer.Rd b/man/mcpCompareModelsServer.Rd deleted file mode 100644 index 8efe604..0000000 --- a/man/mcpCompareModelsServer.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/03-breakPointDetection.R -\name{mcpCompareModelsUI} -\alias{mcpCompareModelsUI} -\alias{mcpCompareModelsServer} -\title{MCP Compare Models UI} -\usage{ -mcpCompareModelsUI(id) - -mcpCompareModelsServer(id, formulasAndPriors, mcpData, mcpFitList) -} -\arguments{ -\item{id}{The module id} - -\item{formulasAndPriors}{The reactive formulas and priors} - -\item{mcpData}{The reactive mcp data} - -\item{mcpFitList}{The reactive mcp fit list} -} -\description{ -MCP Compare Models UI - -MCP Compare Models Server -} diff --git a/man/mcpFormulasServer.Rd b/man/mcpFormulasServer.Rd deleted file mode 100644 index 6b8f1bc..0000000 --- a/man/mcpFormulasServer.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/03-breakPointDetection.R -\name{mcpFormulasUI} -\alias{mcpFormulasUI} -\alias{mcpFormulasServer} -\title{MCP Formulas UI} -\usage{ -mcpFormulasUI(id) - -mcpFormulasServer(id) -} -\arguments{ -\item{id}{The module id} -} -\description{ -MCP Formulas UI - -MCP Formulas Server -} diff --git a/man/mcpModelingServer.Rd b/man/mcpModelingServer.Rd deleted file mode 100644 index 0902a52..0000000 --- a/man/mcpModelingServer.Rd +++ /dev/null @@ -1,23 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/03-breakPointDetection.R -\name{mcpModelingUI} -\alias{mcpModelingUI} -\alias{mcpModelingServer} -\title{MCP Modeling UI} -\usage{ -mcpModelingUI(id) - -mcpModelingServer(id, formulasAndPriors, mcpData) -} -\arguments{ -\item{id}{The module id} - -\item{formulasAndPriors}{The reactive formulas and priors} - -\item{mcpData}{The reactive mcp data} -} -\description{ -MCP Modeling UI - -MCP Modeling Server -} diff --git a/man/mcpOutServer.Rd b/man/mcpOutServer.Rd deleted file mode 100644 index a835efb..0000000 --- a/man/mcpOutServer.Rd +++ /dev/null @@ -1,43 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/03-breakPointDetection.R -\name{mcpOutUI} -\alias{mcpOutUI} -\alias{mcpOutServer} -\title{MCP Output UI} -\usage{ -mcpOutUI(id, title, outFUN = verbatimTextOutput, showWidth = FALSE) - -mcpOutServer( - id, - formulasAndPriors, - mcpData, - mcpFitList, - mcpModel, - outFUN, - renderFUN = renderPrint -) -} -\arguments{ -\item{id}{The module id} - -\item{title}{The title of the output} - -\item{outFUN}{The output function} - -\item{showWidth}{Show the width slider} - -\item{formulasAndPriors}{The reactive formulas and priors} - -\item{mcpData}{The reactive mcp data} - -\item{mcpFitList}{The reactive mcp fit list} - -\item{mcpModel}{The reactive mcp model} - -\item{renderFUN}{The render function} -} -\description{ -MCP Output UI - -MCP Output Server -} diff --git a/man/mcpShowSingleModelServer.Rd b/man/mcpShowSingleModelServer.Rd deleted file mode 100644 index 4db6db9..0000000 --- a/man/mcpShowSingleModelServer.Rd +++ /dev/null @@ -1,25 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/03-breakPointDetection.R -\name{mcpShowSingleModelUI} -\alias{mcpShowSingleModelUI} -\alias{mcpShowSingleModelServer} -\title{MCP Show Single Model UI} -\usage{ -mcpShowSingleModelUI(id) - -mcpShowSingleModelServer(id, mcpData, formulasAndPriors, mcpFitList) -} -\arguments{ -\item{id}{The module id} - -\item{mcpData}{The reactive mcp data} - -\item{formulasAndPriors}{The reactive formulas and priors} - -\item{mcpFitList}{The reactive mcp fit list} -} -\description{ -MCP Show Single Model UI - -MCP Show Single Model Server -} diff --git a/man/readExampleMatrix.Rd b/man/readExampleMatrix.Rd deleted file mode 100644 index 09ca641..0000000 --- a/man/readExampleMatrix.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{readExampleMatrix} -\alias{readExampleMatrix} -\title{Get Example Matrix} -\usage{ -readExampleMatrix(path) -} -\arguments{ -\item{path}{path to example matrix} -} -\description{ -Get Example Matrix -} diff --git a/man/runMcp.Rd b/man/runMcp.Rd deleted file mode 100644 index 9e36fe6..0000000 --- a/man/runMcp.Rd +++ /dev/null @@ -1,19 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{runMcp} -\alias{runMcp} -\title{Run mcp model} -\usage{ -runMcp(lists, ...) -} -\arguments{ -\item{lists}{A list of lists containing the segmented formulas and priors} - -\item{...}{Additional arguments to pass to mcp} -} -\value{ -A list of mcp model fits -} -\description{ -Run mcp model -} diff --git a/man/setFormulasAndPriors.Rd b/man/setFormulasAndPriors.Rd deleted file mode 100644 index f660bea..0000000 --- a/man/setFormulasAndPriors.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{setFormulasAndPriors} -\alias{setFormulasAndPriors} -\title{Set formulas and priors} -\usage{ -setFormulasAndPriors(splittedComb) -} -\arguments{ -\item{splittedComb}{A list of two matrices containing the segmented formulas and priors} -} -\value{ -A list of lists containing the segmented formulas and priors -} -\description{ -Set formulas and priors -} diff --git a/man/splitComb.Rd b/man/splitComb.Rd deleted file mode 100644 index 7760078..0000000 --- a/man/splitComb.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-breakPoints.R -\name{splitComb} -\alias{splitComb} -\title{Split the comb matrix into two matrices} -\usage{ -splitComb(comb) -} -\arguments{ -\item{comb}{A matrix containing the segmented formulas and priors} -} -\value{ -A list of two matrices -} -\description{ -Split the comb matrix into two matrices based on the separator '*+*'. -} diff --git a/man/validateFormula.Rd b/man/validateFormula.Rd deleted file mode 100644 index efce95d..0000000 --- a/man/validateFormula.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/02-matrixModule.R -\name{validateFormula} -\alias{validateFormula} -\title{Check formula syntax} -\usage{ -validateFormula(formula) -} -\arguments{ -\item{formula}{A formula to check} -} -\value{ -A formula if valid, otherwise an error -} -\description{ -Check if the formula syntax is valid -} diff --git a/tests/testthat/test-breakPoints.R b/tests/testthat/test-breakPoints.R deleted file mode 100644 index 8c6870e..0000000 --- a/tests/testthat/test-breakPoints.R +++ /dev/null @@ -1,98 +0,0 @@ -testthat::test_that("detectBreakPoints", { - # load test data - df <- testthat::test_path("testdata/test_detectBreakPoints.csv") %>% - read.csv() - - segmentsMatrix <- matrix( - c( - "d15N ~ 1 + time", - "d15N ~ 1 ~ 0 + time", - "d15N ~ 1 ~ 0 + time", - "d15N ~ 1 ~ 0 + time", - "d15N ~ 1 + time", - "", - "", - "", - "", - "", - "", - "" - ), - nrow = 3, - ncol = 4, - byrow = TRUE - ) - - priorsMatrix <- matrix( - c( - "time_1 = dunif(-4, -0.5);", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ), - nrow = 3, - ncol = 4, - byrow = TRUE - ) - - comb <- getComb(segments = segmentsMatrix, priors = priorsMatrix) - - testthat::expect_equal(comb[1, 1], "d15N ~ 1 + time*+*time_1 = dunif(-4, -0.5);") - testthat::expect_equal(dim(comb), c(16, 4)) - testthat::expect_equal(comb %>% cleanComb() %>% dim(), c(8, 4)) - - twoMatrices <- comb %>% cleanComb() %>% splitComb() - - testthat::expect_equal(twoMatrices$mat1[1, 1], "d15N ~ 1 + time") - testthat::expect_equal(twoMatrices$mat2[1, 1], "time_1 = dunif(-4, -0.5);") - testthat::expect_equal(twoMatrices$mat1 %>% dim(), c(8, 4)) - testthat::expect_equal(twoMatrices$mat2 %>% dim(), c(8, 4)) - - lists <- comb %>% - cleanComb() %>% - splitComb() %>% - setFormulasAndPriors() - - res <- runMcp(lists = lists, data = df) - - testthat::expect_equal( - compareWithLoo(res) %>% colnames() %>% suppressWarnings(), - c( - "elpd_diff", - "se_diff", - "elpd_loo", - "se_elpd_loo", - "p_loo", - "se_p_loo", - "looic", - "se_looic" - ) - ) -}) - -testthat::test_that("validateFormula", { - testthat::expect_equal(validateFormula("y ~ x + 1"), "y ~ x + 1") - testthat::expect_equal(validateFormula("~ 1 + x"), "~ 1 + x") - testthat::expect_equal(validateFormula("~ I(x^2) + exp(x) + sin(x)"), - "~ I(x^2) + exp(x) + sin(x)") - testthat::expect_equal(validateFormula("~sigma(1)"), "~sigma(1)") - testthat::expect_equal(validateFormula("~sigma(rel(1) + I(x^2))"), - "~sigma(rel(1) + I(x^2))") - testthat::expect_equal(validateFormula("~ar(1)"), "~ar(1)") - testthat::expect_warning(validateFormula("y <- x + 1")) -}) - -testthat::test_that("getCellChoices", { - testthat::expect_equal(getCellChoices(nrow = 3, ncol = 2), structure( - 1:6, - names = c("(1, 1)", "(2, 1)", "(3, 1)", "(1, 2)", "(2, 2)", "(3, 2)") - )) -}) diff --git a/tests/testthat/testdata/test_detectBreakPoints.csv b/tests/testthat/testdata/test_detectBreakPoints.csv deleted file mode 100644 index 9d37af8..0000000 --- a/tests/testthat/testdata/test_detectBreakPoints.csv +++ /dev/null @@ -1,15 +0,0 @@ -time,d15N -0.25,12.53 -0.75,12.03 -1.25,11.54 -1.75,11.5 -2.25,11.5 -2.75,11.53 -3.25,11.67 -3.75,11.75 -4.75,11.99 -5.25,12.01 -6.75,11.68 -7.25,11.66 -7.75,11.66 -8.25,11.68 From e60249c9e960d1bc6822af8eb05e0e8d6cac13eb Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 5 Nov 2024 18:19:25 +0100 Subject: [PATCH 32/36] return needed example data --- DESCRIPTION | 4 ++-- inst/app/data/example_breakPointPriors.csv | 4 ++++ inst/app/data/example_breakPointSegments.csv | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 inst/app/data/example_breakPointPriors.csv create mode 100644 inst/app/data/example_breakPointSegments.csv diff --git a/DESCRIPTION b/DESCRIPTION index e0a9230..2c4f374 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.11.0 +Version: 24.11.0.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -13,7 +13,7 @@ LazyData: true ByteCompile: true Depends: R (>= 3.5.0), Rcpp (>= 0.12.0) Imports: - ChangeR, + ChangeR (>= 24.11.0.4), dplyr, ggplot2 (>= 2.2.1), DataTools (>= 24.10.0.2), diff --git a/inst/app/data/example_breakPointPriors.csv b/inst/app/data/example_breakPointPriors.csv new file mode 100644 index 0000000..d01fcc1 --- /dev/null +++ b/inst/app/data/example_breakPointPriors.csv @@ -0,0 +1,4 @@ +"V1","V2","V3","V4" +"x_1 = dunif(-4, -0.5);","","","" +"","","","" +"","","","" diff --git a/inst/app/data/example_breakPointSegments.csv b/inst/app/data/example_breakPointSegments.csv new file mode 100644 index 0000000..8bafe3a --- /dev/null +++ b/inst/app/data/example_breakPointSegments.csv @@ -0,0 +1,4 @@ +"V1","V2","V3","V4" +"y ~ 1 + x","y ~ 1 ~ 0 + x","y ~ 1 ~ 0 + x","y ~ 1 ~ 0 + x" +"y ~ 1 + x","","","" +"","","","" From b77268691fd1b806b0dad9df32bd9837dd070477 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Tue, 4 Feb 2025 10:19:40 +0100 Subject: [PATCH 33/36] OsteoBioR 25.02.0: apply customPoints module, plotLegend module (#76) * apply customPoints module, plotLegend module * integrate new legend module, update logic to apply user inputs to plot * update namespace * catch errors * apply new header UI * apply custom points to "Credibility Intervals" plot, update tests --- DESCRIPTION | 4 +- NAMESPACE | 6 +- NEWS.md | 7 + R/01-plotFormatting.R | 20 +- R/03-timePlotFormatting.R | 357 ++++++++++++++++++++------------- R/NAMESPACE.R | 7 +- R/help.R | 10 - R/plots.R | 37 +++- inst/app/server.R | 19 +- inst/app/ui.R | 40 +--- inst/app/www/custom.css | 48 +---- man/getHelp.Rd | 14 -- tests/testthat/test-plotTime.R | 12 +- 13 files changed, 293 insertions(+), 288 deletions(-) delete mode 100644 R/help.R delete mode 100644 man/getHelp.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 2c4f374..fa6739e 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 24.11.0.1 +Version: 25.02.0 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -30,7 +30,7 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, - shinyTools (>= 24.10.0), + shinyTools (>= 25.02.0), yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: diff --git a/NAMESPACE b/NAMESPACE index af1fd38..768c0ef 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -13,7 +13,6 @@ export(extractSavedModels) export(getEntry) export(getExampleDataMatrix) export(getExampleIsotopeVals) -export(getHelp) export(getSeed) export(getShiftTime) export(getSiteStayTimes) @@ -89,11 +88,13 @@ importFrom(parallel,detectCores) importFrom(rlang,.data) importFrom(rstan,extract) importFrom(rstan,sampling) +importFrom(shinyTools,addCustomPointsToGGplot) importFrom(shinyTools,calculateRescalingFactors) +importFrom(shinyTools,customPointsServer) +importFrom(shinyTools,customPointsUI) importFrom(shinyTools,dataExportButton) importFrom(shinyTools,dataExportServer) importFrom(shinyTools,extractTitle) -importFrom(shinyTools,formatLegendOfGGplot) importFrom(shinyTools,formatPointsOfGGplot) importFrom(shinyTools,formatScalesOfGGplot) importFrom(shinyTools,formatTitlesOfGGplot) @@ -107,6 +108,7 @@ importFrom(shinyTools,plotRangesServer) importFrom(shinyTools,plotRangesUI) importFrom(shinyTools,plotTitlesServer) importFrom(shinyTools,plotTitlesUI) +importFrom(shinyTools,setLegendThemeOfGGplot) importFrom(shinyTools,shinyTryCatch) importFrom(shinyWidgets,pickerInput) importFrom(shinyWidgets,updatePickerInput) diff --git a/NEWS.md b/NEWS.md index bf3b16e..71435d4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,10 @@ +# OsteoBioR 25.02.0 + +## New Features +- option to add custom points to a plot +- option to use custom legend labels for the plot (#69) +- new logos and links in header + # OsteoBioR 24.11.0 ## Updates diff --git a/R/01-plotFormatting.R b/R/01-plotFormatting.R index 64552db..bf62b78 100644 --- a/R/01-plotFormatting.R +++ b/R/01-plotFormatting.R @@ -1,32 +1,36 @@ getStyleForIndividuals <- function(pointStyleList, input) { # pointStyleList are reactive values -> lapply over the names and not(!) the list itself - style <- lapply(names(pointStyleList), function(x) {pointStyleList[[x]][input]}) %>% + style <- lapply(names(pointStyleList), function(individual) {pointStyleList[[individual]][input]}) %>% unlist() names(style) <- names(pointStyleList) style } -getDefaultPointFormatForModels <- function(pointStyleList, modelNames) { +# Get the default point format for models +# +# @param pointStyleList list of point styles for each model +# @param modelNames names of the models +# @param isFullReset if TRUE, all point styles are reset to the default values +getDefaultPointFormatForModels <- function(pointStyleList, modelNames, isFullReset = FALSE) { # default colours defaultColours <- ggplot2::scale_color_discrete()$palette(n = length(modelNames)) names(defaultColours) <- modelNames # setup lists with default values for style specs for (i in modelNames) { - if (is.null(pointStyleList[[i]])) + if (is.null(pointStyleList[[i]]) || isFullReset) { pointStyleList[[i]] <- config()[["defaultPointStyle"]][["dataPoints"]] - # use default colour per model - pointStyleList[[i]]["color"] <- defaultColours[i] + # use default colour per model + pointStyleList[[i]]["color"] <- defaultColours[i] + } } return(pointStyleList) } getDefaultTextFormat <- function() { - list(legendTitle = config()[["defaultIntervalTimePlotTitle"]], - legendText = config()[["defaultIntervalTimePlotText"]], - plotTitle = config()[["defaultIntervalTimePlotTitle"]], + list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], xAxisTitle = config()[["defaultIntervalTimePlotTitle"]], yAxisTitle = config()[["defaultIntervalTimePlotTitle"]], yAxisTitle2 = config()[["defaultIntervalTimePlotTitle"]], diff --git a/R/03-timePlotFormatting.R b/R/03-timePlotFormatting.R index b45ac6b..4cd0c47 100644 --- a/R/03-timePlotFormatting.R +++ b/R/03-timePlotFormatting.R @@ -1,6 +1,6 @@ #' Message for no models to plot messageNoModelsToPlot <- function() { - "Select 'Model(s) / Individual(s) to display' and press 'Draw Plot' first ..." + "Select 'Model(s) / Individual(s) to display' and press 'Apply' first ..." } # We need a separate namespace for formatting inputs for the model down- and upload. @@ -21,17 +21,29 @@ timePlotFormattingUI <- function(id) { tagList( # Time Plot ---- fluidRow( - column(3, tags$h3("Time Plot")), - column(6, + column(2, tags$h3("Time Plot")), + column(3, selectizeInput(ns("plotTimeModels"), "Model(s) / Individual(s) to display", choices = c("Fit or import a model ..." = ""), multiple = TRUE, selected = "", width = "100%")), column(3, + sliderInput(ns("modCredInt"), + "Credibility interval:", + min = 0, + max = .99, + value = .8, + step = .05, + width = "100%")), + column(2, + radioButtons(ns("deriv"), + "Type", + choices = c("Absolute values" = "1", "First derivate" = "2"))), + column(2, align = "right", style = "margin-top: 1.2em;", - actionButton(ns("applyFormatToTimePlot"), "Draw Plot"), + actionButton(ns("plotTimeModels-apply"), "Apply"), plotExportButton(ns("exportCredIntTimePlot"))) ), tags$br(), @@ -45,77 +57,74 @@ timePlotFormattingUI <- function(id) { value = "formatPlotTab", tags$br(), fluidRow( - column(3, - style = "margin-top: 1.2em;", - tags$h4("Format Time Plot")), - column(6, - selectizeInput(ns("formatTimePlot"), "'Apply' formatting for Model / Individual:", - choices = defaultChoices, - width = "100%")), - column(3, - align = "right", - style = "margin-top: 1.2em;", - actionButton(ns("applyFormatToTimePlotModel"), "Apply"), - actionButton(ns("resetFormatTimePlotModel"), "Reset Format")) - ), - tags$br(), - fluidRow( - column(3, - tags$h4("Data"), - radioButtons(ns("deriv"), - "Type", - choices = c("Absolute values" = "1", "First derivate" = "2")), - sliderInput(ns("modCredInt"), - "Credibility interval:", - min = 0, - max = .99, - value = .8, - step = .05, - width = "100%"), - tags$br(), - sliderInput(ns("alphaU"), - "Transparency of uncertainty region", - min = 0, - max = 1, - value = 0.1, - step = 0.05), - sliderInput(ns("alphaL"), - "Transparency of points / lines", - min = 0, - max = 1, - value = 0.9, - step = 0.05), - tags$br(), - plotLegendUI(ns("legend")) - ), - column(3, - shinyTools::plotTitlesUI( - id = ns("plotLabels"), - title = "Text", - type = "ggplot", - initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], - xAxisText = config()[["defaultIntervalTimePlotText"]]) + column(4, + tags$h4("Plot"), + tabsetPanel( + id = ns("tabs_text_legend"), + selected = "Data", + tabPanel("Data", + sliderInput(ns("alphaU"), + "Transparency of uncertainty region", + min = 0, + max = 1, + value = 0.1, + step = 0.05), + sliderInput(ns("alphaL"), + "Transparency of points / lines", + min = 0, + max = 1, + value = 0.9, + step = 0.05) + ), + tabPanel("Axis", + shinyTools::plotRangesUI(id = ns("plotRanges"), title = NULL), + conditionalPanel( + ns = ns, + condition = "!input['plotRanges-fromData'] & input['plotRanges-labelName'] == 'xAxis'", + checkboxInput(inputId = ns("extendLabels"), + label = "Extend x-axis labels to full range", + value = FALSE) + ), + tags$br(), tags$br(), + selectizeInput(ns("secAxisModel"), "Add a new secondary y axis", + choices = c("Choose one Model / Individual ..." = "")), + helpText("The first element of 'Model(s) / Individual(s) to display' is always used for the first (left) axis."), + actionButton(ns("axis-apply"), "Apply") + ), + tabPanel("Text", + shinyTools::plotTitlesUI( + id = ns("plotLabels"), + title = NULL, + type = "ggplot", + initText = list(plotTitle = config()[["defaultIntervalTimePlotTitle"]], + xAxisText = config()[["defaultIntervalTimePlotText"]]) + ), + actionButton(ns("plotLabels-apply"), "Apply") + ), + tabPanel("Legend", + shinyTools::plotLegendUI(ns("legend")) + ) ) ), - column(3, - shinyTools::plotRangesUI(id = ns("plotRanges"), title = "Axis"), - conditionalPanel( - ns = ns, - condition = "!input['plotRanges-fromData'] & input['plotRanges-labelName'] == 'xAxis'", - checkboxInput(inputId = ns("extendLabels"), - label = "Extend x-axis labels to full range", - value = FALSE) - ), - tags$br(), tags$br(), - selectizeInput(ns("secAxisModel"), "Add a new secondary y axis", - choices = c("Choose one Model / Individual ..." = "")), - helpText("The first element of 'Model(s) / Individual(s) to display' is always used for the first (left) axis.") + column(4, + tags$h4("Model / Individual"), + tabsetPanel( + id = ns("tabs_models"), + selected = "Points & Lines", + tabPanel("Points & Lines", + selectizeInput(ns("formatTimePlot"), "Select model / individual:", + choices = defaultChoices, + width = "100%"), + shinyTools::plotPointsUI(id = ns("pointStyle"), + title = NULL, + initStyle = config()[["defaultPointStyle"]]), + actionButton(ns("pointStyle-apply"), "Apply"), + actionButton(ns("pointStyle-reset"), "Reset Models") + ) + ) ), - column(3, - shinyTools::plotPointsUI(id = ns("pointStyle"), - title = "Points / Lines", - initStyle = config()[["defaultPointStyle"]]) - ) + # custom points ---- + column(4, shinyTools::customPointsUI(ns("customPoints"))) ) ), tabPanel( @@ -151,12 +160,10 @@ timePlotFormattingServer <- function(id, savedModels) { function(input, output, session) { ns <- session$ns - formattedPlot <- reactiveVal() - plotTexts <- shinyTools::plotTitlesServer( "plotLabels", type = "ggplot", - availableElements = c("title", "axis", "yaxis2", "legend"), + availableElements = c("title", "axis", "yaxis2"), showParseButton = FALSE, initText = getDefaultTextFormat() ) @@ -176,16 +183,21 @@ timePlotFormattingServer <- function(id, savedModels) { hideInput = c("hide", "alpha", "colorBg") ) - legend <- shinyTools::plotLegendServer("legend") - - pointStyleList <- reactiveValues() + pointStyleList <- reactiveVal(list()) # empty list, no models yet + plotAxisStyleList <- reactiveVal(list(xAxis = config()[["plotRange"]], + yAxis = config()[["plotRange"]], + yAxis2 = config()[["plotRange"]], + secAxisModel = "", + extendLabels = FALSE)) + plotTextStyleList <- reactiveVal(getDefaultTextFormat()) observe({ req(length(savedModels()) > 0) modelNames <- names(savedModels()) - pointStyleList <- pointStyleList %>% + newList <- pointStyleList() %>% getDefaultPointFormatForModels(modelNames = modelNames) + pointStyleList(newList) updateSelectizeInput(session, "plotTimeModels", choices = modelNames, @@ -218,13 +230,6 @@ timePlotFormattingServer <- function(id, savedModels) { }) %>% bindEvent(input[["plotTimeModels"]], ignoreNULL = FALSE, ignoreInit = TRUE) - observe({ - req(input[["formatTimePlot"]]) - # observe point style - pointStyleList[[input[["formatTimePlot"]]]] <- pointStyle[["dataPoints"]] - }) %>% - bindEvent(input[["applyFormatToTimePlotModel"]]) - allFits <- reactive({ getEntry(savedModels(), "fit") }) @@ -238,14 +243,21 @@ timePlotFormattingServer <- function(id, savedModels) { removeEmptyModels() }) - extractedPlotDataDF <- reactive({ - extractedPlotDataList() %>% + extractedPlotDataDF <- reactiveVal() + observe({ + req(savedModels(), input[["plotTimeModels"]]) + logDebug("%s: Entering get extractedPlotDataDF()", id) + + df <- extractedPlotDataList() %>% extractPlotDataDF(models = input[["plotTimeModels"]], credInt = input$modCredInt) %>% shinyTryCatch(errorTitle = "'Credibility intervals over time': Error when extracting data", warningTitle = "'Credibility intervals over time': Warning when extracting data", alertStyle = "shinyalert") - }) + + extractedPlotDataDF(df) + }) %>% + bindEvent(input[["plotTimeModels-apply"]]) # render plot data table ---- output$plotData <- renderTable({ @@ -264,82 +276,139 @@ timePlotFormattingServer <- function(id, savedModels) { extractedPlotDataDF() })) - newPlot <- reactive({ - logDebug("%s: Entering: reactive 'newPlot'", id) + legend <- shinyTools::plotLegendServer( + "legend", + legend_title = reactive({"individual"}), + legend_labels = reactive({levels(na.omit(extractedPlotDataDF())[["individual"]])})) + + # custom points ---- + custom_points <- reactiveVal(list()) + customPointsServer("customPoints", plot_type = "ggplot", custom_points = custom_points) + + # buttons: input[["plotTimeModels"]] button ----- + # disable if nothing is selected in + buttons <- c("plotTimeModels-apply", "axis-apply", "plotLabels-apply") + lapply(buttons, function(btn) { + observe({ + if (length(input[["plotTimeModels"]]) == 0 || + any(input[["plotTimeModels"]] == "")) { + logDebug("%s: Disable button '%s'", id, btn) + shinyjs::disable(ns(btn), asis = TRUE) + } else { + logDebug("%s: Enable button '%s'", id, btn) + shinyjs::enable(ns(btn), asis = TRUE) + } + }) + }) + + observe({ + logDebug("%s: Apply new point layout", id) + newList <- plotAxisStyleList() + for (name in names(plotRanges)) { + newList[[name]] <- plotRanges[[name]] + } + newList[["extendLabels"]] <- input[["extendLabels"]] + newList[["secAxisModel"]] <- input[["secAxisModel"]] + + plotAxisStyleList(newList) + }) %>% + bindEvent(input[["axis-apply"]]) + + observe({ + logDebug("%s: Apply new text layout", id) + newList <- plotTextStyleList() + for (name in names(plotTexts)) { + newList[[name]] <- plotTexts[[name]] + } - # setup base plot + plotTextStyleList(newList) + }) %>% + bindEvent(input[["plotLabels-apply"]]) + + # button: input[["formatTimePlot"]] ---- + # disable if nothing is selected in + observe({ + if (length(input[["formatTimePlot"]]) == 0 || + any(input[["formatTimePlot"]] == "")) { + logDebug("%s: Disable button 'pointStyle-apply'", id) + shinyjs::disable(ns("pointStyle-apply"), asis = TRUE) + } else { + logDebug("%s: Enable button 'pointStyle-apply'", id) + shinyjs::enable(ns("pointStyle-apply"), asis = TRUE) + } + }) + + observe({ + logDebug("%s: Apply new point layout", id) + + # update point style list + newList <- pointStyleList() + newList[[input[["formatTimePlot"]]]] <- pointStyle[["dataPoints"]] + pointStyleList(newList) + }) %>% + bindEvent(input[["pointStyle-apply"]]) + + observe({ + req(savedModels(), input[["plotTimeModels"]]) + logDebug("%s: Entering reset pointStyleList()", id) + defaultStyle <- pointStyleList() %>% + getDefaultPointFormatForModels(modelNames = names(savedModels()), isFullReset = TRUE) + newList <- pointStyleList() + newList[[input[["formatTimePlot"]]]] <- defaultStyle[[input[["formatTimePlot"]]]] + pointStyleList(newList) + }) %>% + bindEvent(input[["pointStyle-reset"]]) + + # render plot ---- + plotToExport <- reactiveVal(NULL) + output$plotTime <- renderPlot({ + validate(need(extractedPlotDataDF(), messageNoModelsToPlot())) + + logDebug("%s: draw basePlot", id) p <- extractedPlotDataDF() %>% na.omit() %>% - rescaleSecondAxisData(individual = input[["secAxisModel"]], - plotRanges = plotRanges) %>% - basePlotTime(xLim = getUserLimits(plotRanges = plotRanges[["xAxis"]]), - yLim = getUserLimits(plotRanges = plotRanges[["yAxis"]])) %>% - setDefaultTitles(prop = input$modCredInt) %>% - shinyTools::formatTitlesOfGGplot(text = plotTexts) + rescaleSecondAxisData(individual = plotAxisStyleList()[["secAxisModel"]], + plotRanges = plotAxisStyleList()) %>% + basePlotTime(xLim = getUserLimits(plotRanges = plotAxisStyleList()[["xAxis"]]), + yLim = getUserLimits(plotRanges = plotAxisStyleList()[["yAxis"]])) %>% + setDefaultTitles(prop = isolate(input[["modCredInt"]])) %>% + shinyTools::formatTitlesOfGGplot(text = plotTextStyleList()) %>% + shinyTools::shinyTryCatch(errorTitle = "Plotting failed") # specify x-axis labels from x data of all models allXAxisData <- extractedPlotDataList() %>% extractAllXAxisData() %>% - extendXAxis(xLabelLim = getUserLimits(plotRanges = plotRanges[["xAxis"]]), - extendLabels = input$extendLabels) + extendXAxis(xLabelLim = getUserLimits(plotRanges = plotAxisStyleList()[["xAxis"]]), + extendLabels = plotAxisStyleList()[["extendLabels"]]) - p %>% + logDebug("%s: draw lines", id) + p <- p %>% + drawLinesAndRibbon( + pointStyleList = pointStyleList(), + alphaL = input[["alphaL"]], + alphaU = input[["alphaU"]], + legend = reactiveValuesToList(legend) + ) %>% shinyTools::formatScalesOfGGplot( - ranges = plotRanges, + ranges = plotAxisStyleList(), xLabels = list( # input$deriv already included within extractedPlotDataList() breaks = getBreaks(time = allXAxisData$time, deriv = "1"), labels = getLabel(xAxisData = allXAxisData, deriv = "1") ), - ySecAxisTitle = getSecAxisTitle(modelName = input[["secAxisModel"]], - customTitle = plotTexts[["yAxisTitle2"]]) + ySecAxisTitle = getSecAxisTitle(modelName = plotAxisStyleList()[["secAxisModel"]], + customTitle = plotTextStyleList()[["yAxisTitle2"]]) ) %>% - drawLinesAndRibbon( - pointStyleList = pointStyleList, - alphaL = input[["alphaL"]], - alphaU = input[["alphaU"]], - # UPDATE LEGEND HERE <- ------- - legendName = plotTexts[["legendTitle"]][["text"]] - ) %>% - shinyTools::formatLegendOfGGplot(legend = legend) - }) - - observe({ - req(savedModels(), input[["plotTimeModels"]]) - logDebug("%s: Entering get formattedPlot()", id) - - p <- newPlot() %>% + shinyTools::addCustomPointsToGGplot(custom_points = custom_points()) %>% shinyTools::shinyTryCatch(errorTitle = "Plotting failed") - formattedPlot(p) - }) %>% - bindEvent(list(input[["applyFormatToTimePlot"]], - input[["applyFormatToTimePlotModel"]])) - - observe({ - req(savedModels(), input[["plotTimeModels"]]) - logDebug("%s: Entering reset formattedPlot()", id) - modelNames <- names(savedModels()) - defaultStyle <- pointStyleList %>% - reactiveValuesToList() %>% - getDefaultPointFormatForModels(modelNames = modelNames) - pointStyleList[[input[["formatTimePlot"]]]] <- defaultStyle[[input[["formatTimePlot"]]]] - - p <- newPlot() %>% - shinyTools::shinyTryCatch(errorTitle = "Plotting failed") - formattedPlot(p) - }) %>% - bindEvent(input[["resetFormatTimePlotModel"]]) - - # render plot ---- - output$plotTime <- renderPlot({ - validate(need(formattedPlot(), messageNoModelsToPlot())) - formattedPlot() + plotToExport(p) + p }) # export plot ---- plotExportServer("exportCredIntTimePlot", - plotFun = reactive(function() formattedPlot()), + plotFun = reactive(function() plotToExport()), filename = sprintf("%s_Credibility_Intervals_Over_Time", gsub("-", "", Sys.Date())) ) diff --git a/R/NAMESPACE.R b/R/NAMESPACE.R index d7589e7..71a6dbc 100644 --- a/R/NAMESPACE.R +++ b/R/NAMESPACE.R @@ -25,10 +25,11 @@ #' @importFrom rstan sampling extract #' @importFrom shinycssloaders withSpinner #' @importFrom shinyjs alert enable runjs -#' @importFrom shinyTools calculateRescalingFactors dataExportButton dataExportServer extractTitle -#' formatLegendOfGGplot formatPointsOfGGplot formatScalesOfGGplot formatTitlesOfGGplot +#' @importFrom shinyTools addCustomPointsToGGplot calculateRescalingFactors +#' customPointsServer customPointsUI dataExportButton dataExportServer +#' extractTitle formatPointsOfGGplot formatScalesOfGGplot formatTitlesOfGGplot #' plotExportButton plotExportServer plotLegendServer plotLegendUI plotPointsServer plotPointsUI -#' plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI +#' plotRangesServer plotRangesUI plotTitlesServer plotTitlesUI setLegendThemeOfGGplot #' shinyTryCatch #' @importFrom shinyWidgets pickerInput updatePickerInput #' @importFrom stats approx dnorm lm median na.omit quantile sd diff --git a/R/help.R b/R/help.R deleted file mode 100644 index e0a50c5..0000000 --- a/R/help.R +++ /dev/null @@ -1,10 +0,0 @@ -#' Get Text for Help Panel in Shiny App -#' -#' @param id id of selected tab -#' -#' @export -getHelp <- function(id) { - tagList( - tags$p("This is the help") - ) -} diff --git a/R/plots.R b/R/plots.R index f1dc533..fd9001f 100644 --- a/R/plots.R +++ b/R/plots.R @@ -90,10 +90,10 @@ basePlotTime <- function(df, -drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName = "individual") { - if (nrow(plot$data) == 0) return(plot) +drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legend = NULL) { + if (is.null(plot) || nrow(plot$data) == 0) return(plot) - # draw lines "upper", "median", "lower" + # draw lines "upper", "median", "lower" for each "individual" if (nrow(plot$data) > 1) { plot <- plot + geom_line(aes(y = .data[["median"]], colour = .data[["individual"]]), @@ -112,7 +112,7 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName size = 0.05, alpha = alphaL) } - # draw ribbon + # draw ribbon for each "individual" if (nrow(plot$data) > 1) { plot <- plot + geom_ribbon(aes(ymin = .data[["lower"]], ymax = .data[["upper"]], @@ -120,7 +120,7 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName linetype = 2, alpha = alphaU) } - # draw points "median" + # draw points "median" for each "individual" plot <- plot + geom_point(aes(y = .data[["median"]], colour = .data[["individual"]], @@ -129,6 +129,8 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName fill = .data[["individual"]]), alpha = alphaL) + if (length(pointStyleList) == 0) return(plot) + # set scales for each "individual" with default legend name lineColors <- getStyleForIndividuals(pointStyleList, input = "color") fillColors <- getStyleForIndividuals(pointStyleList, input = "color") @@ -136,13 +138,26 @@ drawLinesAndRibbon <- function(plot, pointStyleList, alphaL, alphaU, legendName pointSize <- getStyleForIndividuals(pointStyleList, input = "size") # default legend name for empty input - if (legendName == "") legendName <- "individual" + if (!is.null(legend)) { + legendName <- extractTitle(legend$layout$title[[1]], default = "individual") + legendLabels <- sapply(names(legend$layout$labels), function(name) { + extractTitle(legend$layout$labels[[name]], default = name) + }) + + plot <- plot %>% setLegendThemeOfGGplot(legend = legend) + } else { + legendName <- "individual" + legendLabels <- names(lineColors) + names(legendLabels) <- names(lineColors) + } - plot + - scale_colour_manual(name = legendName, values = lineColors) + # former colorL - scale_fill_manual(name = legendName, values = fillColors) + # former colorU - scale_shape_manual(name = legendName, values = pointShapes) + - scale_size_manual(name = legendName, values = pointSize) + plot <- plot + + scale_colour_manual(name = legendName, labels = legendLabels, values = lineColors) + # former colorL + scale_fill_manual(name = legendName, labels = legendLabels, values = fillColors) + # former colorU + scale_shape_manual(name = legendName, labels = legendLabels, values = pointShapes) + + scale_size_manual(name = legendName, labels = legendLabels, values = pointSize) + + plot } setDefaultTitles <- function(plot, prop, xAxisLabel = "Time", yAxisLabel = "Estimate") { diff --git a/inst/app/server.R b/inst/app/server.R index a188dd8..a467a45 100644 --- a/inst/app/server.R +++ b/inst/app/server.R @@ -501,11 +501,21 @@ shinyServer(function(input, output, session) { } })) + # custom points ---- + custom_points <- reactiveVal(list()) + shinyTools::customPointsServer("customPoints", plot_type = "ggplot", custom_points = custom_points) + + # render "Credibility Intervals" plot ---- + plotToExport <- reactiveVal(NULL) output$plot <- renderPlot({ req(fit()) #OsteoBioR::plot(fit(), prop = input$modCredInt) - plot(fit(), prop = input$modCredInt) - }) + p <- plot(fit(), prop = input$modCredInt) %>% + shinyTools::addCustomPointsToGGplot(custom_points = custom_points()) %>% + shinyTools::shinyTryCatch(errorTitle = "Plotting failed") + plotToExport(p) + p + }) # create plotTime ---- timePlotFormattingServer(id = "timePlotFormat", savedModels = savedModels) @@ -610,10 +620,7 @@ shinyServer(function(input, output, session) { shinyTools::plotExportServer("exportCredIntPlot", plotFun = reactive({ function() { - if (length(fit()) == 0) return(NULL) - - #OsteoBioR::plot(fit(), prop = input$modCredInt) - plot(fit(), prop = input$modCredInt) + plotToExport() } }), plotType = "none", #"ggplot", #<- fix issue with labels first diff --git a/inst/app/ui.R b/inst/app/ui.R index 7df62cd..975998c 100644 --- a/inst/app/ui.R +++ b/inst/app/ui.R @@ -6,6 +6,7 @@ library(OsteoBioR) library(shinyMatrix) library(dplyr) library(shinycssloaders) +library(shinyTools) library(ggplot2) library(rstan) @@ -178,8 +179,11 @@ tagList( value = "credibilityIntervalsTab", plotOutput("plot") %>% withSpinner(color = "#20c997"), - shinyTools::plotExportButton("exportCredIntPlot", label = "Export Plot"), - shinyTools::dataExportButton("exportCredIntDat", label = "Export Data") + fluidRow(column(4, shinyTools::customPointsUI("customPoints")), + column(8, align = "right", + shinyTools::plotExportButton("exportCredIntPlot", label = "Export Plot"), + shinyTools::dataExportButton("exportCredIntDat", label = "Export Data") + )), ), tabPanel( "Credibility intervals over time", @@ -445,36 +449,6 @@ tagList( shinyTools::dataExportButton("exportResultsDat", label = "Export Isotopic Values") ) )) - # STYLE of navbarPage ---- ), - # tags$head( - # tags$link(rel = "stylesheet", type = "text/css", href = "custom.css") - # ), - div( - id = "header-right", - # div( - # id = "logo-mpi", - # tags$a( - # href = "https://www.mpg.de/en", - # img(src = "MPIlogo.png", alt = "Supported by the Max Planck society"), - # target = "_blank" - # ) - # ), - # div( - # id = "logo-isomemo", - # tags$a( - # href = "https://isomemo.com/", - # img(src = "IsoMemoLogo.png", alt = "IsoMemo"), - # target = "_blank" - # ) - # ), - div( - id = "further-help", - tags$button(onclick = "window.open('https://isomemo.com','_blank');", - class = "btn btn-default", - "Further Help") - ), - div(id = "help", - actionButton("getHelp", "?")) - ) + shinyTools::headerButtonsUI(id = "header", help_link = "https://pandora-isomemo.github.io/OsteoBioR/") ) diff --git a/inst/app/www/custom.css b/inst/app/www/custom.css index fec3c03..cc9702c 100644 --- a/inst/app/www/custom.css +++ b/inst/app/www/custom.css @@ -1,5 +1,5 @@ body { - padding-top: 80px; + padding-top: 50px; } * { @@ -48,56 +48,10 @@ h5 { color: black; } -#header-right { - position: fixed; - top: 15px; - right:14px; - white-space: nowrap; - z-index: 9999; - overflow: hidden; - display: inline; -} - -#help { - display:inline; -} - -#further-help { - display: inline; -} - -#help button, #further-help button { - padding: 3px 10px; - background-color: #18bc9c; - border-color: #0f7864; - color: white; - border-radius: 4px; - border-width: 2px; -} - .navbar-brand:hover { color: white; } -#logo-isomemo, #logo-mpi { - display: inline; - width: 150px; - height: 130px; - margin-right: 10px; -} - -#logo-isomemo img { - height: 50px; - width: 75px; - border-radius: 2px; -} - -#logo-mpi img { - height: 50px; - width: 57px; - border-radius: 2px; -} - .matrix-input { overflow-x: auto; } diff --git a/man/getHelp.Rd b/man/getHelp.Rd deleted file mode 100644 index cbee239..0000000 --- a/man/getHelp.Rd +++ /dev/null @@ -1,14 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/help.R -\name{getHelp} -\alias{getHelp} -\title{Get Text for Help Panel in Shiny App} -\usage{ -getHelp(id) -} -\arguments{ -\item{id}{id of selected tab} -} -\description{ -Get Text for Help Panel in Shiny App -} diff --git a/tests/testthat/test-plotTime.R b/tests/testthat/test-plotTime.R index 7720294..3d97b53 100644 --- a/tests/testthat/test-plotTime.R +++ b/tests/testthat/test-plotTime.R @@ -83,10 +83,8 @@ testthat::test_that("basePlotTime", { drawLinesAndRibbon( pointStyleList = pointStyleList, alphaL = 0.7, - alphaU = 0.1, - legendName = "testLegend" - ) %>% - formatLegendOfGGplot(legend = list(position = "top")) + alphaU = 0.1 + ) expect_equal( plot$labels, @@ -117,8 +115,7 @@ testthat::test_that("basePlotTime", { drawLinesAndRibbon( pointStyleList = pointStyleList, alphaL = 0.7, - alphaU = 0.1, - legendName = "testLegend" + alphaU = 0.1 ) expect_equal( @@ -169,8 +166,7 @@ testthat::test_that("drawLinesAndRibbon", { drawLinesAndRibbon( pointStyleList = pointStyleList, alphaL = 0.7, - alphaU = 0.1, - legendName = "testLegend" + alphaU = 0.1 ) expect_equal( From f184860e585f097103ea85406973b916c7e17b6a Mon Sep 17 00:00:00 2001 From: Jan Abel Date: Tue, 4 Feb 2025 11:10:48 +0100 Subject: [PATCH 34/36] update upload-artifacts from v3 to v4 --- .github/workflows/pkgdown.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 2d937cb..2226201 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@v4 - uses: r-lib/actions/setup-pandoc@v2 - + - name: Compile rstan files via rstantools run: | # R is already installed in the base-image @@ -49,7 +49,7 @@ jobs: # Save the generated site to be shared with the next job - name: Upload site artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: site path: docs From 6e1c9252b1ca557e99932fa8c0bcaccf7c3c792e Mon Sep 17 00:00:00 2001 From: Jan Abel <106665518+jan-abel-inwt@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:16:13 +0100 Subject: [PATCH 35/36] update download-artifacts from v3 to v4 --- .github/workflows/pkgdown.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pkgdown.yaml b/.github/workflows/pkgdown.yaml index 2226201..ab17c14 100644 --- a/.github/workflows/pkgdown.yaml +++ b/.github/workflows/pkgdown.yaml @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@v4 - name: Download site artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: site path: docs From b0b95453665ada51cafe11d2bfc373eb7f2a9061 Mon Sep 17 00:00:00 2001 From: Antonia Runge Date: Wed, 5 Feb 2025 20:19:26 +0100 Subject: [PATCH 36/36] push shinyTools version with fix for custom points --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index fa6739e..fb467a6 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,5 +1,5 @@ Package: OsteoBioR -Version: 25.02.0 +Version: 25.02.0.1 Title: Temporal Estimation of Isotopic Values Description: Estimates the temporal changes of isotopic values of bone and teeth data solely based on the renewal rate of different bones/teeth and given measurements. The package furthermore provides plotting and exporting functionalities. Authors@R: c( @@ -30,7 +30,7 @@ Imports: shinyMatrix, shinyWidgets, shinythemes, - shinyTools (>= 25.02.0), + shinyTools (>= 25.02.0.2), yaml LinkingTo: StanHeaders (>= 2.18.0), rstan (>= 2.18.1), BH (>= 1.66.0), Rcpp (>= 0.12.0), RcppEigen (>= 0.3.3.3.0) Suggests: