diff --git a/DESCRIPTION b/DESCRIPTION index e5cd8b64..47e18bff 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -10,37 +10,39 @@ Description: Investigate if a manufactured product adheres to a defined set of q License: GPL (>= 2) Encoding: UTF-8 Imports: - car, - cowplot, - daewr, - desirability, - DoE.base, - EnvStats, - FAdist, - fitdistrplus, - FrF2, - ggplot2, - ggrepel, - goftest, - ggpp, - irr, - jaspBase, - jaspDescriptives, - jaspGraphs, - lubridate, - mle.tools, - psych, - qcc, - rsm, - Rspc, - tidyr, - tibble, - vipor, - weibullness, - utils + car, + cowplot, + daewr, + desirability, + DoE.base, + EnvStats, + FAdist, + fitdistrplus, + FrF2, + ggplot2, + ggrepel, + goftest, + ggpp, + irr, + jaspBase, + jaspDescriptives, + jaspGraphs, + lubridate, + mle.tools, + psych, + qc, + qcc, + rsm, + Rspc, + tidyr, + tibble, + vipor, + weibullness, + utils, + ggh4x Remotes: jasp-stats/jaspBase, jasp-stats/jaspDescriptives, jasp-stats/jaspGraphs Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.2 +RoxygenNote: 7.3.3 diff --git a/NAMESPACE b/NAMESPACE index ccae865c..f5ed363e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,7 @@ # Generated by roxygen2: do not edit by hand export(attributesCharts) +export(bayesianProcessCapabilityStudies) export(doeAnalysis) export(doeFactorial) export(doeResponseSurfaceMethodology) @@ -12,10 +13,11 @@ export(msaTestRetest) export(msaType1Gauge) export(probabilityOfDetection) export(processCapabilityStudies) -export(timeWeightedCharts) export(rareEventCharts) +export(timeWeightedCharts) export(variablesChartsIndividuals) export(variablesChartsSubgroups) +importFrom(jaspBase,"%setOrRetrieve%") importFrom(jaspBase,.extractErrorMessage) importFrom(jaspBase,.hasErrors) importFrom(jaspBase,.readDataSetToEnd) @@ -25,3 +27,5 @@ importFrom(jaspBase,createJaspPlot) importFrom(jaspBase,createJaspState) importFrom(jaspBase,createJaspTable) importFrom(jaspBase,isTryError) +importFrom(jaspBase,jaspDeps) +importFrom(rlang,.data) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R new file mode 100644 index 00000000..5dfd1106 --- /dev/null +++ b/R/bayesianProcessCapabilityStudies.R @@ -0,0 +1,924 @@ +# +# Copyright (C) 2013-2025 University of Amsterdam +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +#'@importFrom jaspBase jaspDeps %setOrRetrieve% +#'@importFrom rlang .data + + +#'@export +bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { + + fit <- .bpcsCapabilityTable(jaspResults, dataset, options, position = 1) + priorFit <- .bpcsSamplePosteriorOrPrior(jaspResults, dataset, options, prior = TRUE) + + .bpcsCapabilityPlot(jaspResults, options, fit, priorFit, position = 2) + .bpcsCapabilityPlot(jaspResults, options, fit, priorFit, position = 3, base = "priorDistributionPlot") + + .bpcsIntervalTable(jaspResults, options, fit, position = 4) + + .bpcsSequentialPointEstimatePlot( jaspResults, dataset, options, fit, position = 5) + .bpcsSequentialIntervalEstimatePlot(jaspResults, dataset, options, fit, position = 6) + + .bpcsPlotPredictive(jaspResults, options, fit, position = 7, base = "posteriorPredictiveDistributionPlot") + .bpcsPlotPredictive(jaspResults, options, priorFit, position = 8, base = "priorPredictiveDistributionPlot") + +} + +.bpcsIsReady <- function(options) { + # hasData <- if (options[["dataFormat"]] == "longFormat") { + # length(options[["measurementLongFormat"]]) > 0L && options[["measurementLongFormat"]] != "" + # } else { + # length(options[["measurementsWideFormat"]]) > 0L + # } + hasData <- length(options[["measurementLongFormat"]]) > 0L && options[["measurementLongFormat"]] != "" + hasData && + options[["lowerSpecificationLimit"]] && + options[["upperSpecificationLimit"]] && + options[["target"]] +} + +.bpcsStateDeps <- function() { + c( + # data + # "dataFormat", "measurementLongFormat", "measurementsWideFormat", + # "subgroupSizeType", "manualSubgroupSizeValue", "subgroup", "groupingVariableMethod", + # "stagesLongFormat", "stagesWideFormat", + "measurementLongFormat", + # specification + "target", "lowerSpecificationLimit", "upperSpecificationLimit", + "targetValue", "lowerSpecificationLimitValue", "upperSpecificationLimitValue", + # likelihood + "capabilityStudyType", + # prior + "priorSettings", "normalModelComponentsList", "tModelComponentsList", + # MCMC settings + "noIterations", "noWarmup", "noChains" + ) +} + +.bpcsDefaultDeps <- function() { + c( + .bpcsStateDeps(), + "axisLabels", + # metrics + "Cp", "Cpu", "Cpl", "Cpk", "Cpc", "Cpm" + ) +} + +.bpcsPlotLayoutDeps <- function(base, hasPrior = TRUE, hasEstimate = TRUE, hasCi = TRUE, hasType = FALSE, hasAxes = TRUE) { + c( + base, + if (hasEstimate) .bpcsPlotLayoutEstimateDeps(base), + if (hasCi) .bpcsPlotLayoutCiDeps(base), + if (hasType) .bpcsPlotLayoutTypeDeps(base), + if (hasAxes) .bpcsPlotLayoutAxesDeps(base), + if (hasPrior) .bpcsPlotLayoutPriorDeps(base) + ) +} + +.bpcsPlotLayoutEstimateDeps <- function(base) { paste0(base, c("IndividualPointEstimate", "IndividualPointEstimateType")) } +.bpcsPlotLayoutCiDeps <- function(base) { paste0(base, c("IndividualCi", "IndividualCiType", "IndividualCiMass", "IndividualCiLower", "IndividualCiUpper", "IndividualCiBf")) } +.bpcsPlotLayoutTypeDeps <- function(base) { paste0(base, c("TypeLower", "TypeUpper")) } +.bpcsPlotLayoutAxesDeps <- function(base) { paste0(base, c("PanelLayout", "Axes", "custom_x_min", "custom_x_max", "custom_y_min", "custom_y_max")) } +.bpcsPlotLayoutPriorDeps <- function(base) { paste0(base, "PriorDistribution") } + +.bpcsProcessCriteriaDeps <- function() { + c(paste0("interval", 1:4), paste0("intervalLabel", 1:5)) +} + +.bpcsPriorComponentByName <- function(options, name) { + components <- options$normalModelComponentsList + for (comp in components) { + if (comp$name == name) + return(comp) + } + return(NULL) +} + +.bpcsPriorFromComponent <- function(optionsPrior, paramName) { + if (is.null(optionsPrior)) + return(NULL) + + if (optionsPrior$type == "jeffreys") + return(paste0("Jeffreys_", paramName)) + + arguments <- list() + + arguments[["distribution"]] <- switch( + optionsPrior[["type"]], + "gammaAB" = "gamma", + "gammaK0" = "gamma", + optionsPrior[["type"]] + ) + + arguments[["parameters"]] <- switch( + optionsPrior[["type"]], + "normal" = list("mean" = optionsPrior[["mu"]], "sd" = optionsPrior[["sigma"]]), + "t" = list("location" = optionsPrior[["mu"]], "scale" = optionsPrior[["sigma"]], "df" = optionsPrior[["nu"]]), + "cauchy" = list("location" = optionsPrior[["mu"]], "scale" = optionsPrior[["theta"]]), + "gammaAB" = list("shape" = optionsPrior[["alpha"]], "rate" = optionsPrior[["beta"]]), + "gammaK0" = list("shape" = optionsPrior[["k"]], "rate" = 1/optionsPrior[["theta"]]), + "invgamma" = list("shape" = optionsPrior[["alpha"]], "scale" = optionsPrior[["beta"]]), + "lognormal" = list("meanlog" = optionsPrior[["mu"]], "sdlog" = optionsPrior[["sigma"]]), + "beta" = list("alpha" = optionsPrior[["alpha"]], "beta" = optionsPrior[["beta"]]), + "uniform" = list("a" = optionsPrior[["a"]], "b" = optionsPrior[["b"]]), + "exponential" = list("rate" = optionsPrior[["lambda"]]), + "spike" = list("location" = optionsPrior[["x0"]]) + ) + + if(!arguments[["distribution"]] %in% c("spike", "uniform")) { + arguments[["truncation"]] <- list( + lower = optionsPrior[["truncationLower"]], + upper = optionsPrior[["truncationUpper"]] + ) + } + + return(do.call(BayesTools::prior, arguments)) +} + +.bpcsMuPriorFromOptions <- function(options) { + if (options$priorSettings == "default") { + return("Jeffreys_mu") + } else { + comp <- .bpcsPriorComponentByName(options, "mean") + return(.bpcsPriorFromComponent(comp, "mu")) + } +} + +.bpcsSigmaPriorFromOptions <- function(options) { + if (options$priorSettings == "default") { + return("Jeffreys_sigma") + } else { + comp <- .bpcsPriorComponentByName(options, "sigma") + return(.bpcsPriorFromComponent(comp, "sigma")) + } +} +.bpcsTPriorFromOptions <- function(options) { + + switch(options[["capabilityStudyType"]], + "normalCapabilityAnalysis" = NULL, + "tCapabilityAnalysis" = .bpcsPriorFromComponent(.bpcsPriorComponentByName(options, "df"), "df"), + + stop("Unknown capability study type: ", options[["capabilityStudyType"]]) + ) +} + +# Tables ---- +.bpcsCapabilityTable <- function(jaspResults, dataset, options, position) { + + # Check if we already have the results cached + if (!is.null(jaspResults[["bpcsCapabilityTable"]])) + return(.bpcsSamplePosteriorOrPrior(jaspResults, dataset, options)) # will return object from state (if it exists) + + table <- .bpcsCapabilityTableMeta(jaspResults, options, position = position) + if (!.bpcsIsReady(options)) { + + if (options[["measurementLongFormat"]] != "" || length(options[["measurementsWideFormat"]]) > 0) + table$addFootnote(gettext( + "Please specify the Lower Specification Limit, Upper Specification Limit, and Target Value to compute the capability measures." + )) + + return(NULL) + } + + resultsObject <- .bpcsSamplePosteriorOrPrior(jaspResults, dataset, options) + + .bpcsCapabilityTableFill(table, resultsObject, options) + return(resultsObject) + +} + +.bpcsSamplePosteriorOrPrior <- function(jaspResults, dataset, options, prior = FALSE) { + + base <- if (prior) "bpcsPriors" else "bpcs" + if (prior && !.bpcsCanSampleFromPriors(options)) + return(NULL) + + if (!is.null(jaspResults[[paste0(base, "ResultsObject")]])) + return(jaspResults[[paste0(base, "ResultsObject")]]$object) + + rawfit <- jaspResults[[paste0(base, "State")]] %setOrRetrieve% ( + qc::bpc( + dataset[[1L]], chains = 1, warmup = 1000, iter = 5000, silent = TRUE, seed = 1, + target = options[["targetValue"]], + LSL = options[["lowerSpecificationLimitValue"]], + USL = options[["upperSpecificationLimitValue"]], + prior_mu = .bpcsMuPriorFromOptions(options), + prior_sigma = .bpcsSigmaPriorFromOptions(options), + prior_nu = .bpcsTPriorFromOptions(options), + sample_priors = prior + ) |> + createJaspState(jaspDeps(.bpcsStateDeps())) + ) + + summaryObject <- jaspResults[[paste0(base, "SummaryState")]] %setOrRetrieve% ( + summary( + rawfit, ci.level = options[["credibleIntervalWidth"]] + ) |> + createJaspState(jaspDeps( + options = c(.bpcsStateDeps(), "credibleIntervalWidth") + )) + ) + + resultsObject <- list( + rawfit = rawfit, + summaryObject = summaryObject + ) + + jaspResults[[paste0(base, "ResultsObject")]] <- createJaspState(resultsObject) + + return(resultsObject) +} + +.bpcsCanSampleFromPriors <- function(options) { + options$priorSettings != "default" +} + +.bpcsCapabilityTableMeta <- function(jaspResults, options, position) { + + table <- createJaspTable(title = gettext("Capability Table"), position = position) + table$addColumnInfo(name = "metric", title = gettext("Measure"), type = "string") + table$addColumnInfo(name = "mean", title = gettext("Mean"), type = "number") + table$addColumnInfo(name = "median", title = gettext("Median"), type = "number") + table$addColumnInfo(name = "sd", title = gettext("Std"), type = "number") + + overtitle <- gettextf("%s%% Credible Interval", 100 * options[["credibleIntervalWidth"]]) + table$addColumnInfo(name = "lower", title = gettext("Lower"), type = "number", overtitle = overtitle) + table$addColumnInfo(name = "upper", title = gettext("Upper"), type = "number", overtitle = overtitle) + + table$dependOn(c(.bpcsDefaultDeps(), "credibleIntervalWidth")) + + jaspResults[["bpcsCapabilityTable"]] <- table + return(table) + +} + +.bpcsGetSelectedMetrics <- function(options) { + allMetrics <- c("Cp", "CpU", "CpL", "Cpk", "Cpc", "Cpm") + selectedMetrics <- allMetrics[c(options[["Cp"]], options[["Cpu"]], options[["Cpl"]], + options[["Cpk"]], options[["Cpc"]], options[["Cpm"]])] + return(selectedMetrics) +} + +getCustomAxisLimits <- function(options, base) { + keys <- c(paste0(base, "custom_x_", c("min", "max")), paste0(base, "custom_y_", c("min", "max"))) + values <- lapply(keys, function(k) options[[k]]) + names(values) <- c("xmin", "xmax", "ymin", "ymax") + values +} +# end utils + +.bpcsCapabilityTableFill <- function(table, resultsObject, options) { + + df <- as.data.frame(resultsObject[["summaryObject"]][["summary"]]) + + # Filter metrics based on user selection + selectedMetrics <- .bpcsGetSelectedMetrics(options) + + if (length(selectedMetrics) > 0) { + df <- df[df$metric %in% selectedMetrics, , drop = FALSE] + } + + table$setData(df) + +} + +.bpcsIntervalTable <- function(jaspResults, options, fit, position) { + + if (!options[["intervalTable"]]) + return() + + table <- .bpcsIntervalTableMeta(jaspResults, options, position) + if (!.bpcsIsReady(options) || is.null(fit)) + return() + + selectedMetrics <- .bpcsGetSelectedMetrics(options) + tryCatch({ + + # qc does c(-Inf, interval_probability, Inf) + interval_probability <- unlist(options[paste0("interval", 1:4)], use.names = FALSE) + interval_summary <- summary(fit[["rawfit"]], interval_probability = interval_probability)[["interval_summary"]] + colnames(interval_summary) <- c("metric", paste0("interval", 1:5)) + interval_summary <- subset(interval_summary, metric %in% selectedMetrics) + table$setData(interval_summary) + + }, error = function(e) { + + table$setError(gettextf("Unexpected error in interval table: %s", e$message)) + + }) + + return() +} + +.bpcsIntervalTableMeta <- function(jaspResults, options, position) { + + table <- createJaspTable(title = gettext("Interval Table"), position = position) + + table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), type = "string") + + intervalBounds <- c(-Inf, unlist(options[paste0("interval", 1:4)], use.names = FALSE), Inf) + intervalNames <- unlist(options[paste0("intervalLabel", 1:5)], use.names = FALSE) + n <- length(intervalBounds) + + # custom format helper. we don't use e.g., %.3f directly because that adds trailing zeros (2.000 instead of 2) + fmt <- \(x) formatC(x, digits = 3, format = "f", drop0trailing = TRUE) + for (i in 1:(n - 1)) { + j <- i + 1 + lhs <- if (i == 1) "(" else "[" + rhs <- if (i == n - 1) ")" else "]" + title <- sprintf("%s %s%s, %s%s", intervalNames[i], lhs, fmt(intervalBounds[i]), fmt(intervalBounds[j]), rhs) + table$addColumnInfo(name = paste0("interval", i), title = title, type = "number") + } + table$dependOn(c("intervalTable", .bpcsDefaultDeps(), .bpcsProcessCriteriaDeps())) + + jaspResults[["bpcsIntervalTable"]] <- table + return(table) +} + + +# Plots ---- +.bpcsCapabilityPlot <- function(jaspResults, options, fit, priorFit, position, base = "posteriorDistributionPlot") { + + if (!options[[base]] || !is.null(jaspResults[[base]])) + return() + + singlePanel <- options[[paste0(base, "PanelLayout")]] != "multiplePanels" + + isPost <- base == "posteriorDistributionPlot" + summaryObject <- if (isPost) fit$summaryObject else priorFit$summaryObject + # only if the user asked for it + priorSummaryObject <- if (isPost && options[[paste0(base, "PriorDistribution")]]) priorFit$summaryObject else NULL + + jaspPlt <- createJaspPlot( + title = if (isPost) gettext("Posterior Distribution") else gettext("Prior Distribution"), + width = 400 * (if (singlePanel) 1 else 3), + height = 400 * (if (singlePanel) 1 else 2), + position = position, + dependencies = jaspDeps( + options = c( + .bpcsDefaultDeps(), + # .bpcsPosteriorPlotDeps(options), + .bpcsPlotLayoutDeps(base, hasType = FALSE) + ) + ) + ) + jaspResults[[base]] <- jaspPlt + + if (!.bpcsIsReady(options) || (isPost && is.null(fit))) + return() + + if (!isPost && !.bpcsCanSampleFromPriors(options)) { + jaspPlt$width <- 400 + jaspPlt$height <- 400 + jaspPlt$setError(gettext("Prior distribution cannot be shown for improper priors.")) + return() + } + + tryCatch({ + + # Get selected metrics + selectedMetrics <- .bpcsGetSelectedMetrics(options) + + if (length(selectedMetrics) == 0) { + NULL + } else { + + jaspPlt$plotObject <- qc::plot_density( + summaryObject, + what = selectedMetrics, + point_estimate = if (options[[paste0(base, "IndividualPointEstimate")]]) options[[paste0(base, "IndividualPointEstimateType")]] else "none", + ci = if (options[[paste0(base, "IndividualCi")]]) options[[paste0(base, "IndividualCiType")]] else "none", + ci_level = options[[paste0(base, "IndividualCiMass")]], + ci_custom_left = options[[paste0(base, "IndividualCiLower")]], + ci_custom_right = options[[paste0(base, "IndividualCiUpper")]], + bf_support = options[[paste0(base, "IndividualCiBf")]], + single_panel = singlePanel, + axes = options[[paste0(base, "Axes")]], + axes_custom = getCustomAxisLimits(options, base), + priorSummaryObject = priorSummaryObject + ) + + jaspGraphs::geom_rangeframe() + + jaspGraphs::themeJaspRaw() + + + } + }, error = function(e) { + jaspPlt$width <- 400 + jaspPlt$height <- 400 + jaspPlt$setError( + if (isPost) gettextf("Unexpected error in posterior distribution plot: %s", e$message) + else gettextf("Unexpected error in prior distribution plot: %s", e$message) + ) + }) + +} + +# .bpcsPosteriorPlotDeps <- function(options) { +# c( +# "posteriorDistributionPlot", +# "posteriorDistributionPlotIndividualPointEstimate", +# "posteriorDistributionPlotIndividualPointEstimateType", +# "posteriorDistributionPlotPriorDistribution", +# "posteriorDistributionPlotIndividualCi", +# "posteriorDistributionPlotIndividualCiType", +# # these match which options are conditionally enabled in the qml file. +# switch(options[["posteriorDistributionPlotIndividualCiType"]], +# "central" = "posteriorDistributionPlotIndividualCiMass", +# "HPD" = "posteriorDistributionPlotIndividualCiMass", +# "custom" = c("posteriorDistributionPlotIndividualCiLower", "posteriorDistributionPlotIndividualCiUpper"), +# "support" = "posteriorDistributionPlotIndividualCiBf" +# ) +# ) +# } + +.bpcsSequentialPointEstimatePlot <- function(jaspResults, dataset, options, fit, position) { + + base <- "sequentialAnalysisPointEstimatePlot" + # "sequentialAnalysisPointIntervalPlot" + if (!options[[base]] || !is.null(jaspResults[[base]])) + return() + + w <- 400 + plt <- createJaspPlot(title = gettext("Sequential Analysis Point Estimate"), width = 3*w, height = 2*w, + position = position, + dependencies = jaspDeps(c( + .bpcsDefaultDeps(), + .bpcsPlotLayoutDeps(base, hasPrior = FALSE), + "sequentialAnalysisPlotAdditionalInfo" + ))) + jaspResults[[base]] <- plt + + if (!.bpcsIsReady(options) || jaspResults$getError()) return() + + sequentialPlotData <- .bpcsGetSequentialAnalysis(jaspResults, dataset, options, fit) + + if (!is.null(sequentialPlotData$error)) { + plt$setError(sequentialPlotData$error) + } else { + tryCatch({ + plt$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData$data, options, base) + }, error = function(e) { + plt$setError(gettextf("Unexpected error in sequential analysis point estimate plot: %s", e$message)) + } + ) + } +} + +.bpcsSequentialIntervalEstimatePlot <- function(jaspResults, dataset, options, fit, position) { + + # base <- "sequentialAnalysisPointEstimatePlot" + base <- "sequentialAnalysisPointIntervalPlot" + if (!options[[base]] || !is.null(jaspResults[[base]])) + return() + + w <- 400 + plt <- createJaspPlot(title = gettext("Sequential Analysis Interval Estimate"), width = 3*w, height = 2*w, + position = position, + dependencies = jaspDeps(c( + .bpcsDefaultDeps(), + .bpcsPlotLayoutDeps(base, hasPrior = FALSE) + ))) + jaspResults[[base]] <- plt + + if (!.bpcsIsReady(options) || jaspResults$getError()) return() + + sequentialPlotData <- .bpcsGetSequentialAnalysis(jaspResults, dataset, options, fit) + + if (!is.null(sequentialPlotData$error)) { + plt$setError(sequentialPlotData$error) + } else { + tryCatch({ + plt$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData$data, options, base, custom = TRUE) + }, error = function(e) { + plt$setError(gettextf("Unexpected error in sequential analysis interval estimate plot: %s", e$message)) + } + ) + } +} + +.bpcsGetSequentialAnalysis <- function(jaspResults, dataset, options, fit) { + + if (!.bpcsIsReady(options) || jaspResults$getError()) return() + + base1 <- "sequentialAnalysisPointEstimatePlot" + base2 <- "sequentialAnalysisPointIntervalPlot" + + baseData <- "SequentialAnalysisData" + tryCatch({ + sequentialPlotData <- jaspResults[[baseData]] %setOrRetrieve% ( + .bpcsComputeSequentialAnalysis(dataset, options, fit) |> + createJaspState(dependencies = jaspDeps( + options = c(.bpcsStateDeps(), + paste0(base2, c("TypeLower", "TypeUpper"))) + )) + ) + + return(list(data = sequentialPlotData, error = NULL)) + + }, error = function(e) { + + return(list(data = NULL, error = e$message)) + + }) + +} + +.bpcsComputeSequentialAnalysis <- function(dataset, options, fit) { + + n <- nrow(dataset) + nfrom <- min(n, 3L) # Gaussian could do 2, but let's not push it + nto <- n + nby <- 1L + nseq <- seq(nfrom, nto, by = nby) + estimates <- array(NA, c(6, 5, length(nseq))) + + hasCustom <- options$sequentialAnalysisPointIntervalPlot + customBounds <- c(options$sequentialAnalysisPointIntervalPlotTypeLower, + options$sequentialAnalysisPointIntervalPlotTypeUpper) + + keys <- c("mean", "median", "lower", "upper", "custom") + dimnames(estimates) <- list(list(), keys, list()) + + x <- dataset[[1L]] + + jaspBase::startProgressbar(length(nseq), label = gettext("Running sequential analysis")) + + priorMu <- .bpcsMuPriorFromOptions(options) + priorSigma <- .bpcsSigmaPriorFromOptions(options) + priorNu <- .bpcsTPriorFromOptions(options) + for (i in seq_along(nseq)) { + + x_i <- x[1:nseq[i]] + fit_i <- qc::bpc( + x_i, chains = 1, warmup = 1000, iter = 5000, silent = TRUE, seed = 1, + target = options[["targetValue"]], + LSL = options[["lowerSpecificationLimitValue"]], + USL = options[["upperSpecificationLimitValue"]], + prior_mu = priorMu, + prior_sigma = priorSigma, + prior_nu = priorNu + ) + + sum_fit_i <- summary(fit_i, interval_probability = customBounds) + sum_i <- sum_fit_i$summary + custom_i <- sum_fit_i$interval_summary[, 3, drop = FALSE] + colnames(custom_i) <- "custom" + sum_i <- cbind(sum_i, custom_i) + + if (is.null(rownames(estimates))) + rownames(estimates) <- sum_i$metric + + estimates[, , i] <- as.matrix(sum_i[keys]) + jaspBase::progressbarTick() + } + + attr(estimates, "nseq") <- nseq + + # we could use this one, but only if the CI width is exactly equal to the one requested here. + # that would be nice to add at some point so the values in the table are identical to those in the plot + # sum_n <- summary(fit)$summary + # estimates[, , n] <- as.matrix(sum_n[keys]) + + return(estimates) +} + +.bpcsMakeSequentialPlot <- function(estimates, options, base, custom = FALSE) { + + # this function should move to qc, and these are the arguments that should be passed to the arguments of that function + single_panel <- options[[paste0(base, "PanelLayout")]] != "multiplePanels" + axes <- options[[paste0(base, "Axes")]] + axes_custom <- getCustomAxisLimits(options, base) + + pointEstimateOption <- paste0(base, "IndividualPointEstimateType") + pointEstimateName <- if (options[[pointEstimateOption]] == "mean") "mean" else "median" + add_additional_info <- options[["sequentialAnalysisPlotAdditionalInfo"]] + + selectedMetrics <- .bpcsGetSelectedMetrics(options) + if (length(selectedMetrics) == 0L) + return(NULL) + + ciOption <- paste0(base, "IndividualCi") + has_ci <- options[[ciOption]] + + if (custom) { + has_ci <- FALSE + pointEstimateName <- "custom" + add_additional_info <- FALSE + y_limits <- c(0, 1) + y_title <- gettextf("P(%1$.3f \u2264 x \u2264 %2$.3f)", + options$sequentialAnalysisPointIntervalPlotTypeLower, + options$sequentialAnalysisPointIntervalPlotTypeUpper) + } else { + + y_title <- if (has_ci) { + gettext("Estimate with 95% credible interval") + } else { + gettext("Estimate") + } + } + + # this is somewhat ugly, but we convert the 3d array to a tibble for plotting + # we don't create the tibble immediately in the previous function, because + # it takes up more space in the state (which means larger jasp files) + + categoryNames <- c(gettext("Incapable"), gettext("Capable"), gettext("Satisfactory"), gettext("Excellent"), gettext("Super")) + gridLines <- c(1, 4/3, 3/2, 2) + # the extrema are missing here, these should be determined based on any leftover space. + defaultCategoryPositions <- (gridLines[-1] + gridLines[-length(gridLines)]) / 2 + + nseq <- attr(estimates, "nseq") + + tb <- tibble::tibble( + metric = factor(rep(rownames(estimates), times = length(nseq))), + n = rep(nseq, each = nrow(estimates)), + mean = as.vector(estimates[, pointEstimateName, ]), + lower = as.vector(estimates[, "lower", ]), + upper = as.vector(estimates[, "upper", ]), + ) + tb <- tb[tb$metric %in% selectedMetrics, , drop = FALSE] + if (length(selectedMetrics) == 1L) + single_panel <- TRUE + + # get y scales per facet + if (single_panel) { + + observedRange <- range(tb$lower, tb$upper, na.rm = TRUE) + dist <- observedRange[2L] - observedRange[1L] + + observedRange[1L] <- min(observedRange[1L], gridLines[1L] - 0.1 * dist) + observedRange[2L] <- max(observedRange[2L], gridLines[length(gridLines)] + 0.1 * dist) + + leftBreaks <- jaspGraphs::getPrettyAxisBreaks(observedRange) + leftLimits <- range(leftBreaks) + + rightAxis <- ggplot2::waiver() + if (add_additional_info) { + rightBreaksShown <- c( + (leftLimits[1L] + gridLines[1L]) / 2, + defaultCategoryPositions, + (leftLimits[2L] + gridLines[length(gridLines)]) / 2 + ) + rightBreaks <- numeric(2L*length(rightBreaksShown) + 1L) + rightBreaks[1L] <- leftLimits[1L] + rightBreaks[seq(2, length(rightBreaks), 2)] <- rightBreaksShown + rightBreaks[seq(3, length(rightBreaks) - 2, 2)] <- gridLines + rightBreaks[length(rightBreaks)] <- leftLimits[2L] + + rightLabels <- character(length(rightBreaks)) + rightLabels[seq(2, length(rightLabels), 2)] <- categoryNames + rightAxis <- ggplot2::sec_axis(identity, breaks = rightBreaks, labels = rightLabels) + } + + y_breaks_per_scale <- ggplot2::scale_y_continuous(breaks = leftBreaks, limits = range(leftBreaks), + minor_breaks = gridLines, + sec.axis = rightAxis) + + } else { + y_breaks_per_scale <- tapply(tb, tb$metric, \(x) { + + # x <- tb[tb$metric == tb$metric[1L], , drop = FALSE] + observedRange <- range(x$lower, x$upper, na.rm = TRUE) + dist <- observedRange[2L] - observedRange[1L] + + observedRange[1L] <- min(observedRange[1L], gridLines[1L] - 0.1 * dist) + observedRange[2L] <- max(observedRange[2L], gridLines[length(gridLines)] + 0.1 * dist) + + if (custom) { + observedRange[1L] <- max(observedRange[1L], y_limits[1L]) + observedRange[2L] <- min(observedRange[2L], y_limits[2L]) + } + + leftBreaks <- jaspGraphs::getPrettyAxisBreaks(observedRange) + leftLimits <- range(leftBreaks) + + rightAxis <- ggplot2::waiver() + if (add_additional_info) { + rightBreaksShown <- c( + (leftLimits[1L] + gridLines[1L]) / 2, + defaultCategoryPositions, + (leftLimits[2L] + gridLines[length(gridLines)]) / 2 + ) + rightBreaks <- numeric(2L*length(rightBreaksShown) + 1L) + rightBreaks[1L] <- leftLimits[1L] + rightBreaks[seq(2, length(rightBreaks), 2)] <- rightBreaksShown + rightBreaks[seq(3, length(rightBreaks) - 2, 2)] <- gridLines + rightBreaks[length(rightBreaks)] <- leftLimits[2L] + + rightLabels <- character(length(rightBreaks)) + rightLabels[seq(2, length(rightLabels), 2)] <- categoryNames + rightAxis <- ggplot2::sec_axis(identity, breaks = rightBreaks, labels = rightLabels) + } + + ggplot2::scale_y_continuous(breaks = leftBreaks, limits = range(leftBreaks), + minor_breaks = gridLines, + sec.axis = rightAxis) + }, simplify = FALSE) + } + + ribbon <- NULL + if (has_ci) + ribbon <- ggplot2::geom_ribbon(ggplot2::aes(ymin = .data$lower, ymax = .data$upper), alpha = 0.3) + + extraTheme <- gridLinesLayer <- NULL + sides <- "bl" + if (add_additional_info) { + # there are 11 ticks, the outermost we hide (NA) because one of their bounds is infinite + # the inner ticks alternate between black and NA, so there is a tick at the grid lines + # but no tick at the criteria text (which is secretly an axis tick label). + rightTickColors <- c(NA, rep(c(NA, "black"), length.out = 9), NA) + extraTheme <- ggplot2::theme(axis.ticks.y.right = ggplot2::element_line(colour = rightTickColors)) + sides <- "blr" + # I tried using minor.breaks for this, but these are not drawn properly with facet_grid and facetted_pos_scales + gridLinesLayer <- ggplot2::geom_hline( + data = data.frame(yintercept = gridLines), + ggplot2::aes(yintercept = .data$yintercept), + # show.legend = FALSE, + linewidth = .5, color = "lightgray", linetype = "dashed" + ) + + } + + scale_x <- scale_facet <- facet <- NULL + noMetrics <- nrow(estimates) + if (noMetrics == 1L || single_panel) { + xBreaks <- jaspGraphs::getPrettyAxisBreaks(tb$n) + xLimits <- range(tb$n) + scale_x <- ggplot2::scale_x_continuous(breaks = xBreaks, limits = xLimits) + scale_facet <- y_breaks_per_scale + } else { + scales <- switch(axes, + "automatic" = "free_y", + "fixed" = "fixed", + "free" = "free_y", + "custom" = "fixed", + stop("Unknown axes option.") + ) + if (axes == "custom") { + if (!is.null(axes_custom[["xmin"]]) && !is.null(axes_custom[["xmax"]])) { + xbreaks <- jaspGraphs::getPrettyAxisBreaks(c(axes_custom[["xmin"]], axes_custom[["xmax"]])) + scale_x <- ggplot2::scale_x_continuous(limits = sort(c(axes_custom[["xmin"]], axes_custom[["xmax"]]))) + } + if (!is.null(axes_custom[["ymin"]]) && !is.null(axes_custom[["ymax"]])) { + ybreaks <- jaspGraphs::getPrettyAxisBreaks(c(axes_custom[["ymin"]], axes_custom[["ymax"]])) + leftLimits <- sort(c(axes_custom[["ymin"]], axes_custom[["ymax"]])) + rightAxis <- ggplot2::waiver() + if (add_additional_info) { + rightBreaksShown <- c( + (leftLimits[1L] + gridLines[1L]) / 2, + defaultCategoryPositions, + (leftLimits[2L] + gridLines[length(gridLines)]) / 2 + ) + rightBreaks <- numeric(2L*length(rightBreaksShown) + 1L) + rightBreaks[1L] <- leftLimits[1L] + rightBreaks[seq(2, length(rightBreaks), 2)] <- rightBreaksShown + rightBreaks[seq(3, length(rightBreaks) - 2, 2)] <- gridLines + rightBreaks[length(rightBreaks)] <- leftLimits[2L] + + rightLabels <- character(length(rightBreaks)) + rightLabels[seq(2, length(rightLabels), 2)] <- categoryNames + rightAxis <- ggplot2::sec_axis(identity, breaks = rightBreaks, labels = rightLabels) + } + scale_facet <- ggplot2::scale_y_continuous(breaks = ybreaks, limits = leftLimits, + minor_breaks = gridLines, sec.axis = rightAxis) + } + } else if (axes == "automatic" || axes == "free") { + scale_facet <- ggh4x::facetted_pos_scales(y = y_breaks_per_scale) + } + facet <- ggplot2::facet_wrap(~metric, scales = scales) + } + + ggplot2::ggplot(tb, ggplot2::aes(x = .data$n, y = .data$mean, group = .data$metric, + color = .data$metric, fill = .data$metric)) + + gridLinesLayer + + ribbon + + ggplot2::geom_line(linewidth = 1) + + facet + scale_facet + scale_x + + ggplot2::labs( + x = gettext("Number of observations"), + y = y_title, + color = gettext("Metric"), + fill = gettext("Metric") + ) + + jaspGraphs::geom_rangeframe(sides = sides) + + jaspGraphs::themeJaspRaw(legend.position = if (single_panel) "right" else "none") + + extraTheme + +} + +# Additional plot functions ---- +.bpcsPlotPredictive <- function(jaspResults, options, fit, position, base = c("posteriorPredictiveDistributionPlot", "priorPredictiveDistributionPlot")) { + + base <- match.arg(base) + isPrior <- base == "priorPredictiveDistributionPlot" + + if (!options[[base]] || !is.null(jaspResults[[base]])) + return() + + plot <- createJaspPlot( + title = if (isPrior) gettext("Prior predictive distribution") else gettext("Posterior Predictive Distribution"), + width = 400, + height = 400, + position = position, + dependencies = c( + .bpcsDefaultDeps(), + base, + paste0(base, "IndividualPointEstimate"), + paste0(base, "IndividualPointEstimateType"), + paste0(base, "IndividualCi"), + paste0(base, "IndividualCiType"), + paste0(base, "IndividualCiMass"), + paste0(base, "IndividualCiLower"), + paste0(base, "IndividualCiUpper") + )) + + jaspResults[[base]] <- plot + + if (!.bpcsIsReady(options) || is.null(fit) || jaspResults$getError()) return() + + tryCatch({ + raw_samples <- qc:::extract_samples(fit$rawfit, bootstrap = FALSE) + samples <- qc:::samples_to_mu_and_sigma(raw_samples) + predictiveSamples <- qc:::samples_to_posterior_predictives(samples) + + plt <- jaspGraphs::jaspHistogram( + predictiveSamples, + xName = if (isPrior) gettext("Prior predictive") else gettext("Posterior predictive"), + density = TRUE + ) + + # Calculate density for positioning elements above histogram + dens <- stats::density(predictiveSamples) + maxDensity <- max(dens$y) + + # Add point estimate if requested + if (options[[paste0(base, "IndividualPointEstimate")]]) { + pointEstimateType <- options[[paste0(base, "IndividualPointEstimateType")]] + pointEstimate <- switch(pointEstimateType, + "mean" = mean(predictiveSamples), + "median" = stats::median(predictiveSamples), + "mode" = dens$x[which.max(dens$y)] + ) + plt <- plt + ggplot2::geom_point( + data = data.frame(x = pointEstimate, y = 0), + ggplot2::aes(x = .data$x, y = .data$y), + size = 3, + inherit.aes = FALSE + ) + } + + # Add CI if requested + if (options[[paste0(base, "IndividualCi")]]) { + ciType <- options[[paste0(base, "IndividualCiType")]] + + ciInterval <- if (ciType == "custom") { + c(options[[paste0(base, "IndividualCiLower")]], + options[[paste0(base, "IndividualCiUpper")]]) + } else { + ciMass <- options[[paste0(base, "IndividualCiMass")]] / 100 + if (ciType == "central") { + stats::quantile(predictiveSamples, probs = c((1 - ciMass) / 2, (1 + ciMass) / 2)) + } else if (ciType == "HPD") { + # For HPD, we need HDInterval package or implement it + if (requireNamespace("HDInterval", quietly = TRUE)) { + HDInterval::hdi(predictiveSamples, credMass = ciMass) + } else { + # Fallback to central interval + stats::quantile(predictiveSamples, probs = c((1 - ciMass) / 2, (1 + ciMass) / 2)) + } + } + } + + # Position errorbar above the histogram + yPosition <- maxDensity * 1.1 + plt <- plt + ggplot2::geom_errorbarh( + data = data.frame(x = mean(ciInterval), xmin = ciInterval[1], xmax = ciInterval[2], y = yPosition), + ggplot2::aes(x = .data$x, xmin = .data$xmin, xmax = .data$xmax, y = .data$y), + height = maxDensity * 0.05, + linewidth = 0.75, + inherit.aes = FALSE + ) + } + + plot$plotObject <- plt + }, error = function(e) { + plot$setError( + if (isPrior) gettextf("Unexpected error in prior predictive distribution plot: %s", e$message) + else gettextf("Unexpected error in posterior predictive distribution plot: %s", e$message) + ) + }) +} diff --git a/example.csv b/example.csv new file mode 100644 index 00000000..c44d9b0a --- /dev/null +++ b/example.csv @@ -0,0 +1,51 @@ +"x" +-0.410208141645563 +1.28869313968671 +0.0817165750903773 +-0.0686356396501049 +-0.0589226209836481 +0.555475416432164 +-0.294680841233279 +0.422743712149071 +0.428073502390818 +1.76937764118663 +0.464949886950047 +-1.02211993709862 +-0.227070527653629 +0.111891767810994 +0.112577948928265 +-0.190865217539092 +-0.35316235325419 +-0.298407824715572 +0.238127738565468 +0.760187723885965 +1.08752327910633 +-0.780714189131605 +-0.236024109255296 +-0.18879779045873 +-0.524187380397202 +-0.401928776043767 +-0.0598257599490922 +-0.0385345622509108 +-0.164056314050029 +-0.508110824869724 +-1.01827413755015 +0.927040891732172 +-0.43052682984179 +0.649646238402503 +0.489082281331912 +-0.227586109270053 +-0.604552534585311 +0.479358861347001 +-0.208346274791059 +-0.336026180481188 +0.176234681992498 +0.155297268400196 +0.236724975748945 +0.426712263096906 +0.0718331012938521 +-0.20341360336477 +0.810567498058392 +-0.105134751449411 +2.11547527131408 +0.799016074792607 diff --git a/inst/Description.qml b/inst/Description.qml index a11591ce..ebb72747 100644 --- a/inst/Description.qml +++ b/inst/Description.qml @@ -123,4 +123,11 @@ Description func: "doeAnalysis" requiresData: true } + + Analysis + { + title: qsTr("Bayesian Process Capability Study") + func: "bayesianProcessCapabilityStudies" + preloadData: true + } } diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml new file mode 100644 index 00000000..9b0de256 --- /dev/null +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -0,0 +1,462 @@ +// Copyright (C) 2013-2018 University of Amsterdam +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// You should have received a copy of the GNU Affero General Public +// License along with this program. If not, see +// . +// + +import QtQuick +import QtQuick.Layouts +import JASP.Controls + +import "./common" as Common + +Form +{ + function sortIntervalValues() { + + var values = [ + interval1.displayValue, + interval2.displayValue, + interval3.displayValue, + interval4.displayValue + ] + values.sort(function(a, b) { return a - b }) + interval1.value = values[0] + interval2.value = values[1] + interval3.value = values[2] + interval4.value = values[3] + interval1b.value = values[0] + interval2b.value = values[1] + interval3b.value = values[2] + interval4b.value = values[3] + } + function sortIntervalValuesb() { + + var values = [ + interval1b.displayValue, + interval2b.displayValue, + interval3b.displayValue, + interval4b.displayValue + ] + values.sort(function(a, b) { return a - b }) + interval1.value = values[0] + interval2.value = values[1] + interval3.value = values[2] + interval4.value = values[3] + interval1b.value = values[0] + interval2b.value = values[1] + interval3b.value = values[2] + interval4b.value = values[3] + } + columns: 2 + + VariablesForm + { + id: variablesFormLongFormat + + AvailableVariablesList + { + name: "variablesFormLongFormat" + } + + AssignedVariablesList + { + name: "measurementLongFormat" + title: qsTr("Measurement") + id: measurementLongFormat + allowedColumns: ["scale"] + singleVariable: true + } + + } + + + // Section + // { + // title: qsTr("Process capability options") + + Group + { + title: qsTr("Type of data distribution") + + + RadioButtonGroup + { + name: "capabilityStudyType" + id: capabilityStudyType + + RadioButton + { + name: "normalCapabilityAnalysis" + id : normalCapabilityAnalysis + label: qsTr("Normal distribution") + checked: true + } + + RadioButton + { + name: "tCapabilityAnalysis" + id : tCapabilityAnalysis + label: qsTr("Student's t-distribution") + // checked: true + } + + } + } + + Group + { + columns: 2 + title: qsTr("Metrics") + info: qsTr("Select the process capability metrics to report.") + CheckBox { name: "Cp"; label: qsTr("Cp"); checked: true } + CheckBox { name: "Cpu"; label: qsTr("Cpu"); checked: true } + CheckBox { name: "Cpl"; label: qsTr("Cpl"); checked: true } + CheckBox { name: "Cpk"; label: qsTr("Cpk"); checked: true } + CheckBox { name: "Cpc"; label: qsTr("Cpc"); checked: true } + CheckBox { name: "Cpm"; label: qsTr("Cpm"); checked: true } + } + + Group + { + title: qsTr("Capability Study") + + CheckBox + { + name: "lowerSpecificationLimit" + label: qsTr("Lower specification limit") + id: lowerSpecificationLimit + childrenOnSameRow: true + + DoubleField + { + name: "lowerSpecificationLimitValue" + id: lowerSpecificationLimitValue + negativeValues: true + defaultValue: -1 + decimals: 9 + } + + } + + CheckBox + { + name: "target" + label: qsTr("Target value") + id: target + childrenOnSameRow: true + + DoubleField + { + name: "targetValue" + id: targetValue + negativeValues: true + defaultValue: 0 + decimals: 9 + } + } + + CheckBox + { + name: "upperSpecificationLimit" + label: qsTr("Upper specification limit") + id: upperSpecificationLimit + childrenOnSameRow: true + + DoubleField + { + name: "upperSpecificationLimitValue" + id: upperSpecificationLimitValue + negativeValues: true + defaultValue: 1 + decimals: 9 + } + + } + + } + + Group + { + + title: qsTr("Process Criteria") + GridLayout + { + // title: qsTr("Process Criteria") + columns: 5 + columnSpacing: 2 + rowSpacing: jaspTheme.rowGridSpacing / 3 + id: intervalRow + property int dbWidth: 50 + property int txtWidth: 100 + + // Row 0: Headers + Label {text: qsTr("Left bound")} + Item{} + Label {text: qsTr("Classification")} + Item{} + Label {text: qsTr("Right bound")} + + // Row 1: Incapable + Item{} + Item{} + TextField { name: "intervalLabel1"; defaultValue: qsTr("Incapable"); fieldWidth: intervalRow.txtWidth} + Label { text: "<"; } + DoubleField { name: "interval1"; id: interval1; fieldWidth: intervalRow.dbWidth; defaultValue: 1.00; onEditingFinished: sortIntervalValues() } + + // Row 2: Capable + DoubleField { name: "interval1b";id: interval1b; fieldWidth: intervalRow.dbWidth; editable: true; value: interval1.value; onEditingFinished: {sortIntervalValuesb()} } + Label { text: "<"; } + TextField { name: "intervalLabel2"; defaultValue: qsTr("Capable"); fieldWidth: intervalRow.txtWidth} + Label { text: "≤"; } + DoubleField { name: "interval2"; id: interval2; fieldWidth: intervalRow.dbWidth; defaultValue: 1.33; onEditingFinished: sortIntervalValues() } + + // Row 3: Satisfactory + DoubleField { name: "interval2b"; id: interval2b; fieldWidth: intervalRow.dbWidth; editable: true; value: interval2.value; onEditingFinished: {sortIntervalValuesb()} } + Label { text: "<"; } + TextField { name: "intervalLabel3"; defaultValue: qsTr("Satisfactory"); fieldWidth: intervalRow.txtWidth} + Label { text: "≤"; } + DoubleField { name: "interval3"; id: interval3; fieldWidth: intervalRow.dbWidth; defaultValue: 1.50; onEditingFinished: sortIntervalValues() } + + // Row 4: Excellent + DoubleField { name: "interval3b"; id: interval3b; fieldWidth: intervalRow.dbWidth; editable: true; value: interval3.value; onEditingFinished: {sortIntervalValuesb()} } + Label { text: "<"; } + TextField { name: "intervalLabel4"; defaultValue: qsTr("Excellent"); fieldWidth: intervalRow.txtWidth} + Label { text: "≤"; } + DoubleField { name: "interval4"; id: interval4; fieldWidth: intervalRow.dbWidth; defaultValue: 2.00; onEditingFinished: sortIntervalValues() } + + // Row 5: Super + DoubleField { name: "interval4b"; id: interval4b; fieldWidth: intervalRow.dbWidth; editable: true; value: interval4.value; onEditingFinished: {sortIntervalValuesb()} } + Label { text: ">"; } + TextField { name: "intervalLabel5"; defaultValue: qsTr("Super"); fieldWidth: intervalRow.txtWidth} + Item{} + Item{} + } + } + + // } + + // Section + // { + // title: qsTr("Prior distributions") + + // } + + Section + { + title: qsTr("Tables") + CheckBox + { + name: "intervalTable" + label: qsTr("Interval table") + info: qsTr("Show the posterior probabilities of the interval specified with the input on the right. Note that the input is automatically sorted and that the first and last fields are always negative and positive infinity.") + } + CIField + { + name: "credibleIntervalWidth" + label: qsTr("Credible interval") + info: qsTr("Width of the credible interval used for the posterior distribution in the Capability table.") + } + } + + Section + { + + title: qsTr("Prior and Posterior Inference") + + Common.PlotLayout {} + + Common.PlotLayout + { + baseName: "priorDistributionPlot" + baseLabel: qsTr("Prior distribution") + hasPrior: false + } + + } + + Section + { + title: qsTr("Sequential Analysis") + + Common.PlotLayout + { + id: sequentialAnalysisPointEstimatePlot + baseName: "sequentialAnalysisPointEstimatePlot" + baseLabel: qsTr("Point estimate plot") + hasPrior: false + } + + Common.PlotLayout + { + id: sequentialAnalysisIntervalEstimatePlot + baseName: "sequentialAnalysisPointIntervalPlot" + baseLabel: qsTr("Interval estimate plot") + hasPrior: false + hasEstimate: false + hasCi: false + hasType: true + } + + Group + { + CheckBox + { + enabled: sequentialAnalysisPointEstimatePlot.checked || sequentialAnalysisIntervalEstimatePlot.checked + id: sequentialAnalysisAdditionalInfo + name: "sequentialAnalysisPlotAdditionalInfo" + label: qsTr("Show process criteria") + checked: true + info: qsTr("Add a secondary right axis with condition bounds for the process") + } + + CheckBox + { + // TODO: + enabled: sequentialAnalysisPointEstimatePlot.checked || sequentialAnalysisIntervalEstimatePlot.checked + name: "sequentialAnalysisUpdatingTable" + label: qsTr("Posterior updating table") + checked: false + info: qsTr("Show the data from the sequential analysis in a table. Will show both the information for the point estimate and interval estimate plots, if both are selected.") + } + } + } + + Section + { + + title: qsTr("Prior and Posterior Predictive Plots") + + Common.PlotLayout + { + baseName: "posteriorPredictiveDistributionPlot" + baseLabel: qsTr("Posterior predictive distribution") + hasPrior: false + hasAxes: false + hasPanels: false + } + + Common.PlotLayout + { + baseName: "priorPredictiveDistributionPlot" + baseLabel: qsTr("Prior predictive distribution") + hasPrior: false + hasAxes: false + hasPanels: false + } + + } + + + Section + { + title: qsTr("Prior distributions") + + // TODO: this dropdown should just show the same GUI as the custom one + // but disable e.g., the DropDown itself and instead show the prior + // also disable all truncation for non-custom ones + // NOTE: the above is done, but default values cannot be set yet. + + DropDown + { + id: priorSettings + name: "priorSettings" + label: qsTr("Prior distributions") + values: + [ + {label: qsTr("Default"), value: "default"}, + {label: qsTr("Weakly informed conjugate"), value: "conjugate"}, + {label: qsTr("Informed conjugate"), value: "weaklyInformativeConjugate"}, + {label: qsTr("Informed uniform"), value: "weaklyInformativeUniform"}, + {label: qsTr("Custom informative"), value: "customInformative"}, + ] + } + + Common.PriorsNew + { + + // visible: priorSettings.currentValue === "customInformative" + priorType: capabilityStudyType.value === "normalCapabilityAnalysis" ? "normalModel" : "tModel" + + hasTruncation: priorSettings.currentValue === "customInformative" + hasParameters: priorSettings.currentValue !== "default" + + dropDownValuesMap: { + switch (priorSettings.currentValue) { + case "default": + return { + "mean": [{ label: qsTr("Jeffreys"), value: "jeffreys"}], + "sigma": [{ label: qsTr("Jeffreys"), value: "jeffreys"}], + "df": [{ label: qsTr("Gamma(α,β)"), value: "gammaAB" }] + } + case "conjugate": + return { + "mean": [{ label: qsTr("Normal(μ,σ)"), value: "normal"}], + "sigma": [{ label: qsTr("Gamma(α,β)"), value: "gammaAB" }], + "df": [{ label: qsTr("Gamma(α,β)"), value: "gammaAB" }] + }; + case "weaklyInformativeConjugate": + return { + "mean": [{ label: qsTr("Normal(μ,σ)"), value: "normal"}], + "sigma": [{ label: qsTr("Gamma(α,β)"), value: "gammaAB" }], + "df": [{ label: qsTr("Gamma(α,β)"), value: "gammaAB" }] + } + case "weaklyInformativeUniform": + return { + "mean": [{ label: qsTr("Uniform(a,b)"), value: "uniform"}], + "sigma": [{ label: qsTr("Uniform(a,b)"), value: "uniform"}], + "df": [{ label: qsTr("Gamma(α,β)"), value: "gammaAB" }] + } + case "customInformative": + return undefined; + } + } + } + } + + Section + { + title: qsTr("Advanced options") + + Group + { + title: qsTr("MCMC Settings") + info: qsTr("Adjust the Markov Chain Monte Carlo (MCMC) settings for estimating the posterior distribution.") + IntegerField + { + name: "noIterations" + label: qsTr("No. iterations") + defaultValue: 5000 + min: 100 + max: 100000000 + info: qsTr("Number of MCMC iterations used for estimating the posterior distribution.") + } + IntegerField + { + name: "noWarmup" + label: qsTr("No. warmup samples") + defaultValue: 1000 + min: 0 + max: 100000000 + info: qsTr("Number of initial MCMC samples to discard.") + } + IntegerField + { + name: "noChains" + label: qsTr("No. chains") + defaultValue: 1 + min: 1 + max: 128 + info: qsTr("Number of MCMC chains to run.") + } + } + } +} diff --git a/inst/qml/common/PlotLayout.qml b/inst/qml/common/PlotLayout.qml new file mode 100644 index 00000000..abc3957c --- /dev/null +++ b/inst/qml/common/PlotLayout.qml @@ -0,0 +1,266 @@ +// +// Copyright (C) 2013-2018 University of Amsterdam +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public +// License along with this program. If not, see +// . +// +import QtQuick +import QtQuick.Layouts +import JASP +import JASP.Controls + + +Group +{ + id: root + property string baseName: "posteriorDistributionPlot" + property string baseLabel: qsTr("Posterior distribution") + property bool hasPrior: true + property bool hasEstimate: true + property bool hasCi: true + property bool hasType: false + property bool hasAxes: true + property bool hasPanels: true + + readonly property alias checked: mainCheckBox.checked + + CheckBox + { + id: mainCheckBox + name: baseName + label: baseLabel + + // Group so the options are shown in a 2-column layout + Group + { + + columns: 2 + columnSpacing: 10 * jaspTheme.columnGroupSpacing + + // Group so point estimate and CI options are shown in a single column + Group + { + enabled: hasEstimate || hasCi + visible: hasEstimate || hasCi + + CheckBox + { + enabled: hasEstimate + visible: hasEstimate + label: qsTr("Point estimate") + name: baseName + "IndividualPointEstimate" + childrenOnSameRow: true + + DropDown + { + name: baseName + "IndividualPointEstimateType" + label: "" + values: [ + {label: qsTr("mean"), value: "mean"}, + {label: qsTr("median"), value: "median"}, + {label: qsTr("mode"), value: "mode"} + ] + } + } + + // Group so CI checkbox and options are shown in a single column (with subgroup so CI options are indented) + Group + { + enabled: hasCi + visible: hasCi + + columns: 1 + CheckBox + { + name: baseName + "IndividualCi" + label: qsTr("CI") + id: posteriorPlotIndividualCI + childrenOnSameRow: true + + DropDown + { + name: baseName + "IndividualCiType" + label: "" + id: posteriorPlotIndividualType + values: [ + {label: qsTr("central"), value: "central"}, + {label: qsTr("HPD"), value: "HPD"}, + {label: qsTr("custom"), value: "custom"}//, + // {label: qsTr("support"), value: "support"} + ] + } + } + + Group + { + columns: 2 + indent: true + enabled: posteriorPlotIndividualCI.checked + + CIField + { + visible: posteriorPlotIndividualType.currentValue === "central" || posteriorPlotIndividualType.currentValue === "HPD" + name: baseName + "IndividualCiMass" + label: qsTr("Mass") + fieldWidth: 50 + defaultValue: 95 + min: 1 + max: 100 + inclusive: JASP.MinMax + } + + DoubleField + { + visible: posteriorPlotIndividualType.currentValue === "custom" + name: baseName + "IndividualCiLower" + label: qsTr("Lower") + id: plotsPosteriorLower + fieldWidth: 50 + defaultValue: 0 + negativeValues: true + inclusive: JASP.MinMax + } + + DoubleField + { + visible: posteriorPlotIndividualType.currentValue === "custom" + name: baseName + "IndividualCiUpper" + label: qsTr("Upper") + id: plotsPosteriorUpper + fieldWidth: 50 + defaultValue: 1 + negativeValues: true + inclusive: JASP.MinMax + } + + FormulaField + { + visible: posteriorPlotIndividualType.currentValue === "support" + name: baseName + "IndividualCiBf" + label: qsTr("BF") + fieldWidth: 50 + defaultValue: "1" + min: 0 + inclusive: JASP.None + } + } + } + } + + Group + { + enabled: hasType + visible: hasType + + title: qsTr("Type") + + columns: 2 + FormulaField + { + name: baseName + "TypeLower" + label: qsTr("Lower") + id: typeLower + fieldWidth: 50 + defaultValue: 0.0 + max: typeUpper.value + } + + FormulaField + { + name: baseName + "TypeUpper" + label: qsTr("Upper") + id: typeUpper + fieldWidth: 50 + defaultValue: 1.0 + min: typeLower.value + + } + } + + RadioButtonGroup + { + enabled: hasPanels + visible: hasPanels + name: baseName + "PanelLayout" + title: qsTr("Layout") + id: posteriorDistributionPlotPanelLayout + + RadioButton { value: "multiplePanels"; label: qsTr("One plot per metric"); checked: true } + RadioButton { value: "singlePanel"; label: qsTr("All metrics in one plot") } + + } + + RadioButtonGroup + { + enabled: hasAxes + visible: hasAxes + name: baseName + "Axes" + title: qsTr("Axes") + id: posteriorDistributionPlotAxes + + RadioButton { value: "free"; label: qsTr("Automatic"); checked: true } + RadioButton { value: "fixed"; label: qsTr("Identical across panels"); enabled: posteriorDistributionPlotPanelLayout.value === "multiplePanels" } + RadioButton { value: "custom"; label: qsTr("Custom axes"); } + } + + Group + { + + title: qsTr("Custom axes") + enabled: hasAxes && posteriorDistributionPlotAxes.value === "custom" + visible: hasAxes && posteriorDistributionPlotAxes.value === "custom" + + GridLayout + { + columns: 5 + columnSpacing: 2 + rowSpacing: jaspTheme.rowGridSpacing / 3 + id: customAxesLayout + property int dbWidth: 50 + property int txtWidth: 100 + + // Row 0: Headers + Label {text: qsTr("Axis")} + Item{} + Label {text: qsTr("Min")} + Item{} + Label {text: qsTr("Max")} + + // Row 1: x axis + Label { text: qsTr("x axis"); } + Item{} + DoubleField { name: baseName + "custom_x_min"; id: custom_x_min; fieldWidth: customAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: true; max: custom_x_max.value} + Item{} + DoubleField { name: baseName + "custom_x_max"; id: custom_x_max; fieldWidth: customAxesLayout.dbWidth; defaultValue: 1.00; negativeValues: true; min: custom_x_min.value} + + // Row 2: y axis + Label { text: qsTr("y axis"); } + Item{} + DoubleField { name: baseName + "custom_y_min"; id: custom_y_min; fieldWidth: customAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: false; max: custom_y_max.value} + Item{} + DoubleField { name: baseName + "custom_y_max"; id: custom_y_max; fieldWidth: customAxesLayout.dbWidth; defaultValue: 1.00; negativeValues: false; min: custom_y_min.value} + } + } + + CheckBox + { + enabled: hasPrior + visible: hasPrior + name: baseName + "PriorDistribution" + label: qsTr("Show prior distribution") + checked: false + } + } + } +} \ No newline at end of file diff --git a/inst/qml/common/Priors.qml b/inst/qml/common/Priors.qml new file mode 100644 index 00000000..49bf75ba --- /dev/null +++ b/inst/qml/common/Priors.qml @@ -0,0 +1,211 @@ +// +// Copyright (C) 2013-2018 University of Amsterdam +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public +// License along with this program. If not, see +// . +// +import QtQuick +import QtQuick.Layouts +import JASP.Controls + +Group +{ + columns: 2 + + property string baseName: "populationMeanPrior" + property string baseLabel: qsTr("Population mean") + + property bool fullRealLLine: true + property bool hasJeffreys: true + +// TODO: should be dropdown, see RBMAP + RadioButtonGroup + { + id: priorChoice + title: baseLabel + name: baseName + + RadioButton + { + visible: hasJeffreys + enabled: hasJeffreys + label: qsTr("Jeffreys") + name: baseName + "jeffreys" + checked: hasJeffreys + } + + RadioButton + { + id: uniformInformative + label: qsTr("Uniform") + name: baseName + "uniform" + checked: !hasJeffreys + childrenOnSameRow: true + + DoubleField + { + id: uniformLower + label: qsTr("Lower:") + name: baseName + "uniformLower" + visible: uniformInformative.checked + defaultValue: fullRealLLine ? -3 : 0 + negativeValues: fullRealLLine ? true : false + max: uniformUpper.value + } + + DoubleField + { + id: uniformUpper + label: qsTr("Upper:"); + name: baseName + "uniformUpper" + visible: uniformInformative.checked + defaultValue: 3 + fieldWidth: 50 + negativeValues: fullRealLLine ? true : false + min: uniformLower.value + } + } + + RadioButton + { + id: cauchyInformative + label: fullRealLLine ? qsTr("Cauchy") : qsTr("Truncated Cauchy") + name: baseName + "cauchy" + childrenOnSameRow: true + + DoubleField + { + label: qsTr("location:") + name: baseName + "cauchyLocation" + visible: cauchyInformative.checked + defaultValue: 0 + negativeValues: true + } + + DoubleField + { + label: qsTr("scale:"); + name: baseName + "cauchyScale" + visible: cauchyInformative.checked + defaultValue: 0.707 + fieldWidth: 50 + } + } + + RadioButton + { + id: normalInformative + label: fullRealLLine ? qsTr("Normal") : qsTr("Truncated Normal") + name: baseName + "normal" + childrenOnSameRow: true + + DoubleField + { + label: qsTr("mean:") + name: baseName + "normalMean" + visible: normalInformative.checked + defaultValue: 0 + negativeValues: true + } + + DoubleField + { + label: qsTr("std:") + name: baseName + "normalSd" + visible: normalInformative.checked + defaultValue: 0.707 + fieldWidth: 50 + } + + + } + + RadioButton + { + id: tInformative + label: fullRealLLine ? qsTr("t") : qsTr("Truncated t") + name: baseName + "t" + childrenOnSameRow: true + + DoubleField + { + label: qsTr("location:") + name: baseName + "tLocation" + visible: tInformative.checked + defaultValue: 0 + negativeValues: true + } + + DoubleField + { + label: qsTr("scale:") + name: baseName + "tScale" + visible: tInformative.checked + defaultValue: 0.707 + fieldWidth: 50 + } + + IntegerField + { + label: qsTr("df:"); + name: baseName + "tDf"; + visible: tInformative.checked; + min: 1 + defaultValue: 1 + } + } + } + + Group + { + title: qsTr("Truncation") + enabled: priorChoice.value !== baseName + "jeffreys" + visible: priorChoice.value !== baseName + "jeffreys" + + CheckBox + { + name: baseName + "truncationLowerBound" + childrenOnSameRow: true + + FormulaField + { + id: lowerTT + name: baseName + "truncationLowerBoundValue" + label: qsTr("Lower bound:") + fieldWidth: 50 + defaultValue: fullRealLLine ? -Infinity : 0 + max: upperTT.value + min: fullRealLLine ? -Infinity : 0 + } + } + + CheckBox + { + name: baseName + "truncationUpperBound" + childrenOnSameRow: true + + FormulaField + { + id: upperTT + name: baseName + "truncationUpperBoundValue" + label: qsTr("Upper bound:") + fieldWidth: 50 + defaultValue: Infinity + min: Math.max(fullRealLLine ? -Infinity : 0, lowerTT.value) + + } + } + } + +} diff --git a/inst/qml/common/PriorsNew.qml b/inst/qml/common/PriorsNew.qml new file mode 100644 index 00000000..9d6105db --- /dev/null +++ b/inst/qml/common/PriorsNew.qml @@ -0,0 +1,352 @@ +// +// Copyright (C) 2013-2018 University of Amsterdam +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public +// License along with this program. If not, see +// . +// +import QtQuick +import QtQuick.Layouts +import JASP.Controls +import JASP + +ColumnLayout +{ + spacing: 0 + property string priorType: "normalModel" + property bool hasTruncation: false + property bool hasParameters: true + + Component.onCompleted: { + console.log("Component completed, priorType: " + priorType); + console.log("Current component values: " + JSON.stringify(currentComponentValues)); + } + + onPriorTypeChanged: { + // this is not shown? + console.log("Prior type changed to: " + priorType); + } + + // TODO: these should not be fixed, no? + // property var meanValues: { "name": "mean", "type": "normal", "mu": "0", "sigma": "1" } + // property var sigmaValues: { "name": "sigma", "type": "invgamma", "alpha": "1", "beta": "0.15", "truncationLower": 0 } + // property var dfValues: { "name": "t", "type": "invgamma", "alpha": "1", "beta": "0.15", "truncationLower": 0, "hasJeffreys": false } + + property var nameMap: { + "mean": "Mean", + "sigma": "Sigma", + "df": "df" + } + property var defaultDistributionMap: { + "mean": "normal", + "sigma": "invgamma", + "df": "invgamma" + } + property var truncationLowerMap: { + "mean": -Infinity, + "sigma": 0, + "df": 0 + } + property var allPriors : [ + { label: qsTr("Normal(μ,σ)"), value: "normal"}, + { label: qsTr("Student-t(μ,σ,v)"), value: "t"}, + { label: qsTr("Cauchy(x₀,θ)"), value: "cauchy"}, + { label: qsTr("Jeffreys"), value: "jeffreys"}, + { label: qsTr("Gamma(α,β)"), value: "gammaAB"}, + { label: qsTr("Gamma(k,θ)"), value: "gammaK0"}, + { label: qsTr("Inverse-Gamma(α,β)"), value: "invgamma"}, + { label: qsTr("Log-Normal(μ,σ)"), value: "lognormal"}, + { label: qsTr("Beta(α,β)"), value: "beta"}, + { label: qsTr("Uniform(a,b)"), value: "uniform"} + ] + property var priorTruncationMap: { + "normal" : [-Infinity, Infinity], + "t" : [-Infinity, Infinity], + "cauchy" : [-Infinity, Infinity], + "jeffreys": [-Infinity, Infinity], + "gammaAB": [0, Infinity], + "gammaK0": [0, Infinity], + "invgamma": [0, Infinity], + "lognormal": [0, Infinity], + "beta": [0, 1 ], + "uniform": [-Infinity, Infinity] + } + property var defaultDropDownValuesMap: { + "mean": allPriors, + "sigma": allPriors, + "df": allPriors.filter(p => p.value !== "jeffreys") + } + property var dropDownValuesMap: undefined + property var activeDropDownValuesMap: dropDownValuesMap !== undefined ? dropDownValuesMap : defaultDropDownValuesMap + property var hasJeffreysMap: { + "mean": true, + "sigma": true, + "df": false + } + + onDropDownValuesMapChanged: console.log("dropDownValuesMap changed: " + dropDownValuesMap) + // property var defaultParametersMap: { + // "mean": { "mu": "0", "sigma": "1" }, + // "sigma": { "alpha": "1", "beta": "0.15", "truncationLower": 0 }, + // "t": { "alpha": "1", "beta": "0.15", "truncationLower": 0, "hasJeffreys": false } + // } + + property var currentComponentValues: { + switch (priorType) { + case "normalModel": + return [ "mean", "sigma" ]; + case "tModel": + return [ "mean", "sigma", "df" ]; + } + // switch (priorType) { + // case "normalModel": + // return [ meanValues, sigmaValues ]; + // case "tModel": + // return [ meanValues, sigmaValues, dfValues ]; + // } + } + + + // TODO: this could also be a gridLayout, no? + property double width1: 70 * preferencesModel.uiScale; + property double width2: 140 * preferencesModel.uiScale; + property double width3: 155 * preferencesModel.uiScale; + property double width4: 130 * preferencesModel.uiScale; + + RowLayout + { + Label { text: qsTr("Parameter"); Layout.preferredWidth: width1; Layout.leftMargin: 5 * preferencesModel.uiScale} + Label { text: qsTr("Distribution"); Layout.preferredWidth: width2; Layout.leftMargin: 5 * preferencesModel.uiScale} + Label { text: qsTr("Parameters"); Layout.preferredWidth: width3 ; visible: hasParameters } + Label { text: qsTr("Truncation"); Layout.preferredWidth: width4 ; visible: hasTruncation } + } + + + ComponentsList + { + name: priorType + "ComponentsList" + optionKey: "name" + + addItemManually: false + + // defaultValues: currentComponentValues + values: currentComponentValues + + rowComponent: RowLayout + { + Row + { + spacing: 4 * preferencesModel.uiScale + Layout.preferredWidth: width1 + Label { text: nameMap[rowValue] } + } + + Row + { + spacing: 4 * preferencesModel.uiScale + Layout.preferredWidth: width2 + + DropDown + { + visible: activeDropDownValuesMap[rowValue].length > 1 + id: typeItem + name: "type" + useExternalBorder: true + value: defaultDistributionMap[rowValue] + values: activeDropDownValuesMap[rowValue] + } + + Label + { + visible: activeDropDownValuesMap[rowValue].length === 1 + text: activeDropDownValuesMap[rowValue][0].label + } + } + + Row + { + spacing: 4 * preferencesModel.uiScale + Layout.preferredWidth: width3 + visible: hasParameters + + FormulaField + { + label: "μ" + name: "mu" + visible: typeItem.currentValue === "normal" || + typeItem.currentValue === "lognormal" || + typeItem.currentValue === "t" + value: "0" + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + + } + FormulaField + { + label: "x₀" + name: "x0" + visible: typeItem.currentValue === "cauchy" || + typeItem.currentValue === "spike" + value: "0" + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + label: "σ" + name: "sigma" + id: sigma + visible: typeItem.currentValue === "normal" || + typeItem.currentValue === "lognormal" || + typeItem.currentValue === "t" + value: "1" + min: 0 + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + label: "k " + name: "k" + visible: typeItem.currentValue === "gammaK0" + value: "1" + min: 0 + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + } + FormulaField + { + label: "θ" + name: "theta" + visible: typeItem.currentValue === "cauchy" || + typeItem.currentValue === "gammaK0" + value: "1" + min: 0 + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + label: "ν" + name: "nu" + visible: typeItem.currentValue === "t" + value: "2" + min: 1 + inclusive: JASP.MinOnly + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + label: "α " + name: "alpha" + visible: typeItem.currentValue === "gammaAB" || + typeItem.currentValue === "invgamma" || + typeItem.currentValue === "beta" + value: "1" + min: 0 + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + label: "β" + name: "beta" + visible: typeItem.currentValue === "gammaAB" || + typeItem.currentValue === "invgamma" || + typeItem.currentValue === "beta" + value: "0.15" + min: 0 + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + label: "a " + name: "a" + id: a + visible: typeItem.currentValue === "uniform" + value: "0" + max: b.value + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + label: "b" + name: "b" + id: b + visible: typeItem.currentValue === "uniform" + value: "1" + min: a.value + inclusive: JASP.None + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + } + + Row + { + spacing: 4 * preferencesModel.uiScale + Layout.preferredWidth: width4 + + FormulaField + { + id: truncationLower + label: qsTr("lower") + name: "truncationLower" + visible: hasTruncation && typeItem.currentValue !== "spike" && typeItem.currentValue !== "uniform" && typeItem.currentValue !== "jeffreys" + value: Math.max(priorTruncationMap[typeItem.currentValue][0], truncationLowerMap[rowValue]) + min: Math.max(priorTruncationMap[typeItem.currentValue][0], truncationLowerMap[rowValue]) + max: truncationUpper.value + inclusive: JASP.MinOnly + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + id: truncationUpper + label: qsTr("upper") + name: "truncationUpper" + visible: hasTruncation && typeItem.currentValue !== "spike" && typeItem.currentValue !== "uniform" && typeItem.currentValue !== "jeffreys" + value: priorTruncationMap[typeItem.currentValue][1] + max: priorTruncationMap[typeItem.currentValue][1] + min: truncationLower ? truncationLower.value : 0 + inclusive: JASP.MaxOnly + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + } + } + } + +} diff --git a/jaspQualityControl.Rproj b/jaspQualityControl.Rproj index 27aef215..3afa55d1 100644 --- a/jaspQualityControl.Rproj +++ b/jaspQualityControl.Rproj @@ -18,4 +18,6 @@ StripTrailingWhitespace: Yes BuildType: Package PackageUseDevtools: Yes +PackageCleanBeforeInstall: No PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace diff --git a/man/dot-PRESS.Rd b/man/dot-PRESS.Rd new file mode 100644 index 00000000..4d5e7692 --- /dev/null +++ b/man/dot-PRESS.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/doeAnalysis.R +\name{.PRESS} +\alias{.PRESS} +\title{calculate the predictive residuals +calculate the PRESS} +\usage{ +.PRESS(linear.model) +} +\description{ +calculate the predictive residuals +calculate the PRESS +} diff --git a/man/dot-pred_r_squared.Rd b/man/dot-pred_r_squared.Rd new file mode 100644 index 00000000..4446c563 --- /dev/null +++ b/man/dot-pred_r_squared.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/doeAnalysis.R +\name{.pred_r_squared} +\alias{.pred_r_squared} +\title{Use anova() to get the sum of squares for the linear model +Calculate the total sum of squares} +\usage{ +.pred_r_squared(linear.model) +} +\description{ +Use anova() to get the sum of squares for the linear model +Calculate the total sum of squares +} diff --git a/renv.lock b/renv.lock index b1b103a3..23089b87 100644 --- a/renv.lock +++ b/renv.lock @@ -1705,6 +1705,14 @@ "stats" ] }, + "renv": { + "Package": "renv", + "Version": "1.1.4", + "OS_type": null, + "NeedsCompilation": "no", + "Repository": "CRAN", + "Source": "Repository" + }, "rlang": { "Package": "rlang", "Version": "1.1.6",