From a795b72f2e122f5960a7a7fdaae6d7b25b4fd4ab Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Fri, 13 Jun 2025 09:36:49 +0200 Subject: [PATCH 01/15] initial commit of bayes capability stuff --- DESCRIPTION | 3 +- NAMESPACE | 5 +- R/bayesianProcessCapabilityStudies.R | 89 ++++ inst/Description.qml | 7 + inst/qml/bayesianProcessCapabilityStudies.qml | 438 ++++++++++++++++++ jaspQualityControl.Rproj | 2 + man/dot-PRESS.Rd | 13 + man/dot-pred_r_squared.Rd | 13 + renv.lock | 8 + 9 files changed, 576 insertions(+), 2 deletions(-) create mode 100644 R/bayesianProcessCapabilityStudies.R create mode 100644 inst/qml/bayesianProcessCapabilityStudies.qml create mode 100644 man/dot-PRESS.Rd create mode 100644 man/dot-pred_r_squared.Rd diff --git a/DESCRIPTION b/DESCRIPTION index e5cd8b64..847ba766 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,6 +30,7 @@ Imports: lubridate, mle.tools, psych, + qc, qcc, rsm, Rspc, @@ -43,4 +44,4 @@ Remotes: jasp-stats/jaspDescriptives, jasp-stats/jaspGraphs Roxygen: list(markdown = TRUE) -RoxygenNote: 7.2.2 +RoxygenNote: 7.3.2 diff --git a/NAMESPACE b/NAMESPACE index ccae865c..d55a27cb 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,4 @@ importFrom(jaspBase,createJaspPlot) importFrom(jaspBase,createJaspState) importFrom(jaspBase,createJaspTable) importFrom(jaspBase,isTryError) +importFrom(jaspBase,jaspDeps) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R new file mode 100644 index 00000000..8669a353 --- /dev/null +++ b/R/bayesianProcessCapabilityStudies.R @@ -0,0 +1,89 @@ +# +# 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 . +# + + +#'@export +bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { + + fit <- .bpcsCapabilityTable(jaspResults, dataset, options) + .bpcsCapabilityPlot(jaspResults, fit) + +} + +.bpcsIsReady <- function(options) { + length(options[["measurementLongFormat"]]) > 0L && options[["measurementLongFormat"]] != "" +} + +.bpcsCapabilityTable <- function(jaspResults, dataset, options) { + + if (!is.null(options[["bpcsCapabilityTable"]])) + return() + + table <- .bpcsCapabilityTableMeta(jaspResults, options) + if (!.bpcsIsReady(options)) + return() + + fit <- qc::bpc(dataset[[1L]], chains = 1, warmup = 1000, iter = 5000, silent = TRUE, seed = 1) + + .bpcsCapabilityTableFill(table, fit, options) + return(fit) + +} + +.bpcsCapabilityTableMeta <- function(jaspResults, options) { + + table <- createJaspTable(title = gettext("Capability Table")) + table$addColumnInfo(name = "metric", title = gettext("Capability 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("measurementLongFormat", "credibleIntervalWidth")) + + jaspResults[["bpcsCapabilityTable"]] <- table + return(table) + +} + +.bpcsCapabilityTableFill <- function(table, fit, options) { + + df <- as.data.frame(qc::summarize_capability_metrics(fit, cri_width = options[["credibleIntervalWidth"]])) + table$setData(df) + +} + +#'@importFrom jaspBase jaspDeps %setOrRetrieve% + +.bpcsCapabilityPlot <- function(jaspResults, fit) { + + if (!options[["posteriorDistributionPlot"]]) + return() + + jaspResults[["posteriorDistributionPlot"]] %setOrRetrieve% ( + createJaspPlot( + title = gettext("Posterior Distribution"), + plot = qc::plot_density(fit) + jaspGraphs::geom_rangeframe() + jaspGraphs::themeJaspRaw(), + dependencies = jaspDeps( + options = c("measurementLongFormat", "posteriorDistributionPlot") + ) + ) + ) +} 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..42699100 --- /dev/null +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -0,0 +1,438 @@ +// 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 +{ + columns: 2 + + DropDown + { + name: "dataFormat" + label: qsTr("Data format") + id: dataFormat + indexDefaultValue: 0 + values: + [ + {label: qsTr("Single column"), value: "longFormat"}, + {label: qsTr("Across rows"), value: "wideFormat"}, + ] + // onValueChanged: + // { + // measurementLongFormat.itemDoubleClicked(0) + // measurementsWideFormat.itemDoubleClicked(0) + // } + } + + VariablesForm + { + id: variablesFormLongFormat + visible: dataFormat.currentValue === "longFormat" + + AvailableVariablesList + { + name: "variablesFormLongFormat" + } + + AssignedVariablesList + { + name: "measurementLongFormat" + title: qsTr("Measurement") + id: measurementLongFormat + allowedColumns: ["scale"] + singleVariable: true + } + + AssignedVariablesList + { + name: "subgroup" + title: qsTr("Subgroup") + id: subgroup + singleVariable: true + allowedColumns: ["nominal"] + enabled: subgroupSizeType.value === "groupingVariable" + } + + AssignedVariablesList + { + id: stagesLongFormat + name: "stagesLongFormat" + title: qsTr("Stages") + singleVariable: true + allowedColumns: ["nominal"] + } + } + + VariablesForm + { + id: variablesFormWideFormat + visible: dataFormat.currentValue === "wideFormat" + + AvailableVariablesList + { + name: "variablesFormWideFormat" + } + + AssignedVariablesList + { + name: "measurementsWideFormat" + title: qsTr("Measurements") + id: measurementsWideFormat + allowedColumns: ["scale"] + } + + AssignedVariablesList + { + id: axisLabels + name: "axisLabels" + title: qsTr("Timestamp (optional)") + singleVariable: true + allowedColumns: ["nominal"] + } + + AssignedVariablesList + { + id: stagesWideFormat + name: "stagesWideFormat" + title: qsTr("Stages") + singleVariable: true + allowedColumns: ["nominal"] + } + } + + Group + { + columns: 2 + + RadioButtonGroup + { + name: "subgroupSizeType" + title: qsTr("Specify subgroups") + id: subgroupSizeType + visible: dataFormat.currentValue === "longFormat" + + RadioButton + { + value: "manual" + label: qsTr("Subgroup size") + checked: true + childrenOnSameRow: true + + DoubleField + { + name: "manualSubgroupSizeValue" + id: manualSubgroupSizeValue + min: 1 + max: dataSetModel.rowCount() + negativeValues: false + defaultValue: 5 + + } + } + + RadioButton + { + value: "groupingVariable" + label: qsTr("Through grouping variable") + + DropDown + { + name: "groupingVariableMethod" + id: groupingVariable + label: "Grouping method" + values: + [ + { label: qsTr("Subgroup value change"), value: "newLabel"}, + { label: qsTr("Same subgroup value"), value: "sameLabel"} + ] + indexDefaultValue: 0 + } + } + } + + /* + RadioButtonGroup + { + name: "subgroupSizeUnequal" + title: qsTr("Unequal subgroup sizes") + id: subgroupSizeUnequal + + RadioButton + { + value: "actualSizes" + label: qsTr("Use actual sizes") + checked: true + } + + RadioButton + { + value: "fixedSubgroupSize" + label: qsTr("Use fixed subgroup size") + childrenOnSameRow: true + + IntegerField + { + name: "fixedSubgroupSizeValue" + fieldWidth: 30 + defaultValue: 5 + min: 2 + } + } + } + */ + } + + Section + { + title: qsTr("Process capability options") + + ColumnLayout + { + 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 + } + + /* + RadioButton + { + name: "nonNormalCapabilityAnalysis" + id : nonNormalCapabilityAnalysis + label: qsTr("Non-normal distribution") + + DropDown + { + name: "nonNormalDistribution" + id: nonNormalDistribution + label: qsTr("Specify a distribution") + values: + [ + {label: qsTr("Weibull"), value: "weibull"}, + {label: qsTr("Lognormal"), value: "lognormal"}, + {label: qsTr("3-parameter Weibull"), value: "3ParameterWeibull"}, + {label: qsTr("3-parameter lognormal"), value: "3ParameterLognormal"} + ] + indexDefaultValue: 0 + } + + DropDown + { + name: "nonNormalMethod" + label: qsTr("Non-normal capability statistics") + indexDefaultValue: 0 + values: + [ + {label: qsTr("Percentile"), value: "percentile"}, + {label: qsTr("Non-conformance"), value: "nonConformance" } + ] + } + } + */ + } + } + + + 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: "lowerSpecificationLimitBoundary" + // label: qsTr("Boundary") + // id: lowerSpecificationLimitBoundary + // } + } + + 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 + } + + // CheckBox + // { + // name: "upperSpecificationLimitBoundary" + // label: qsTr("Boundary") + // id: upperSpecificationLimitBoundary + // } + } + + CIField { name: "credibleIntervalWidth"; label: qsTr("Credible interval") } + + + /* + CheckBox + { + name: "processCapabilityPlot" + label: qsTr("Process capability plot") + checked: true + enabled: upperSpecificationLimit.checked || lowerSpecificationLimit.checked + + DoubleField + { + name: "processCapabilityPlotBinNumber" + label: qsTr("Number of bins") + defaultValue: 10 + min: 3; + max: 10000; + enabled: csBinWidthType.currentValue === "manual" + } + + CheckBox + { + name: "processCapabilityPlotDistributions" + label: qsTr("Overlay distribution") + checked: true + } + + CheckBox + { + name: "processCapabilityPlotSpecificationLimits" + label: qsTr("Display specification limits") + checked: true + } + } + + CheckBox + { + name: "processCapabilityTable" + label: qsTr("Process capability tables") + checked: true + enabled: upperSpecificationLimit.checked || lowerSpecificationLimit.checked + + CheckBox + { + name: "processCapabilityTableCi"; + label: qsTr("Confidence intervals") + checked: capabilityStudyType.value === "normalCapabilityAnalysis" + enabled: capabilityStudyType.value === "normalCapabilityAnalysis" + childrenOnSameRow: true + + CIField + { + name: "processCapabilityTableCiLevel" + defaultValue: 90} + } + + } + } + */ + } + + Group + { + + title: qsTr("Plots") + + CheckBox + { + name: "posteriorDistributionPlot" + label: qsTr("posterior distribution") + } + } + + } + + ColumnLayout + { + + Group + { + title: qsTr("Stability of the process") + } + } + } + + Section + { + title: qsTr("Process capability report") + + } + + Section + { + title: qsTr("Advanced options") + + } +} 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", From dadd6467aa4025431078e9e2512f9b5a341292de Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Tue, 17 Jun 2025 22:50:45 +0200 Subject: [PATCH 02/15] functional table and basic posterior plot --- R/bayesianProcessCapabilityStudies.R | 91 +++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 14 deletions(-) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index 8669a353..3a8907e9 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -15,39 +15,95 @@ # along with this program. If not, see . # +#'@importFrom jaspBase jaspDeps %setOrRetrieve% #'@export bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { fit <- .bpcsCapabilityTable(jaspResults, dataset, options) - .bpcsCapabilityPlot(jaspResults, fit) + .bpcsCapabilityPlot(jaspResults, options, fit) } .bpcsIsReady <- function(options) { - length(options[["measurementLongFormat"]]) > 0L && options[["measurementLongFormat"]] != "" + length(options[["measurementLongFormat"]]) > 0L && options[["measurementLongFormat"]] != "" && + options[["lowerSpecificationLimit"]] && + options[["upperSpecificationLimit"]] && + options[["target"]] +} + +.bpcsDefaultDeps <- function() { + c( + # data + "measurementLongFormat", + # specification + "target", "lowerSpecificationLimit", "upperSpecificationLimit", + "targetValue", "lowerSpecificationLimitValue", "upperSpecificationLimitValue", + # likelihood + "capabilityStudyType" + # TODO: prior + ) +} + +.bpcsTpriorFromOptions <- function(options) { + + switch(options[["capabilityStudyType"]], + "normalCapabilityAnalysis" = NULL, + "tCapabilityAnalysis" = BayesTools::prior("exp", list(1)), # TODO: should be more generic + + stop("Unknown capability study type: ", options[["capabilityStudyType"]]) + ) } +# Tables ---- .bpcsCapabilityTable <- function(jaspResults, dataset, options) { if (!is.null(options[["bpcsCapabilityTable"]])) return() table <- .bpcsCapabilityTableMeta(jaspResults, options) - if (!.bpcsIsReady(options)) + if (!.bpcsIsReady(options)) { + + if (options[["measurementLongFormat"]] != "") + table$addFootnote(gettext("Please specify the Lower Specification Limit, Upper Specification Limit, and Target Value to compute the capability measures.")) + return() + } + + rawfit <- jaspResults[["bpsState"]] %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_nu = .bpcsTpriorFromOptions(options) + ) |> + createJaspState(jaspDeps(.bpcsDefaultDeps())) + ) + + summaryObject <- jaspResults[["bpsSummaryState"]] %setOrRetrieve% ( + summary( + rawfit, ci.level = options[["credibleIntervalWidth"]] + ) |> + createJaspState(jaspDeps( + options = c(.bpcsDefaultDeps(), "credibleIntervalWidth") + )) + ) - fit <- qc::bpc(dataset[[1L]], chains = 1, warmup = 1000, iter = 5000, silent = TRUE, seed = 1) + resultsObject <- list( + rawfit = rawfit, + summaryObject = summaryObject + ) - .bpcsCapabilityTableFill(table, fit, options) - return(fit) + .bpcsCapabilityTableFill(table, resultsObject, options) + return(resultsObject) } .bpcsCapabilityTableMeta <- function(jaspResults, options) { table <- createJaspTable(title = gettext("Capability Table")) - table$addColumnInfo(name = "metric", title = gettext("Capability Measure"), type = "string") + table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), 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") @@ -63,16 +119,15 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { } -.bpcsCapabilityTableFill <- function(table, fit, options) { +.bpcsCapabilityTableFill <- function(table, resultsObject, options) { - df <- as.data.frame(qc::summarize_capability_metrics(fit, cri_width = options[["credibleIntervalWidth"]])) + df <- as.data.frame(resultsObject[["summaryObject"]][["summary"]]) table$setData(df) } -#'@importFrom jaspBase jaspDeps %setOrRetrieve% - -.bpcsCapabilityPlot <- function(jaspResults, fit) { +# Plots ---- +.bpcsCapabilityPlot <- function(jaspResults, options, fit) { if (!options[["posteriorDistributionPlot"]]) return() @@ -80,9 +135,17 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { jaspResults[["posteriorDistributionPlot"]] %setOrRetrieve% ( createJaspPlot( title = gettext("Posterior Distribution"), - plot = qc::plot_density(fit) + jaspGraphs::geom_rangeframe() + jaspGraphs::themeJaspRaw(), + plot = if (.bpcsIsReady(options) && !is.null(fit)) { + qc::plot_density(fit$summaryObject) + + jaspGraphs::geom_rangeframe() + + jaspGraphs::themeJaspRaw() + } else { + NULL + }, + width = 320 * 6, + height = 320, dependencies = jaspDeps( - options = c("measurementLongFormat", "posteriorDistributionPlot") + options = c(.bpcsDefaultDeps(), "posteriorDistributionPlot") ) ) ) From 1a168f47c66387576efe21dccbdc1e256f75ae56 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Tue, 1 Jul 2025 08:51:54 +0200 Subject: [PATCH 03/15] add interval table --- R/bayesianProcessCapabilityStudies.R | 55 +++++++++++++++- inst/qml/bayesianProcessCapabilityStudies.qml | 62 ++++++++++++++++++- 2 files changed, 115 insertions(+), 2 deletions(-) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index 3a8907e9..3f15d9c9 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -22,6 +22,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { fit <- .bpcsCapabilityTable(jaspResults, dataset, options) .bpcsCapabilityPlot(jaspResults, options, fit) + .bpcsIntervalTable(jaspResults, options, fit) } @@ -126,6 +127,57 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { } +.bpcsIntervalTable <- function(jaspResults, options, fit) { + + if (!options[["intervalTable"]]) + return() + + table <- .bpcsIntervalTableMeta(jaspResults, options) + if (!.bpcsIsReady(options) || is.null(fit)) + return() + + tryCatch({ + + # qc does c(-Inf, interval_probability, Inf) + interval_probability <- unlist(options[paste0("interval", 1:5)], use.names = FALSE) + interval_summary <- summary(fit[["rawfit"]], interval_probability = interval_probability)[["interval_summary"]] + colnames(interval_summary) <- paste0("interval", 1:6) + table$setData(interval_summary) + + }, error = function(e) { + + table$setError(gettextf("Unexpected error in interval table: %s", e$message)) + + }) + + return() +} + +.bpcsIntervalTableMeta <- function(jaspResults, options) { + + table <- createJaspTable(title = gettext("Interval Table")) + + table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), type = "string") + + intervalBounds <- c(-Inf, unlist(options[paste0("interval", 1:5)], use.names = FALSE), Inf) + n <- length(intervalBounds) + for (i in 1:(n - 1)) { + j <- i + 1 + lhs <- if (i == 1) "(" else "[" + rhs <- if (i == n - 1) ")" else "]" + title <- sprintf("%s%.3f , %.3f%s", lhs, intervalBounds[i], intervalBounds[j], rhs) + table$addColumnInfo(name = paste0("interval", i), title = title, type = "number") + } + table$dependOn(c( + "measurementLongFormat", "intervalTable", "credibleIntervalWidth", + paste0("interval", 1:5), "targetValue", "lowerSpecificationLimitValue", "upperSpecificationLimitValue" + )) + + jaspResults[["bpcsIntervalTable"]] <- table + return(table) +} + + # Plots ---- .bpcsCapabilityPlot <- function(jaspResults, options, fit) { @@ -136,7 +188,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { createJaspPlot( title = gettext("Posterior Distribution"), plot = if (.bpcsIsReady(options) && !is.null(fit)) { - qc::plot_density(fit$summaryObject) + + qc::plot_density(fit$summaryObject, + what = c("Cp", "CpU", "CpL", "Cpk", "Cpm")) + jaspGraphs::geom_rangeframe() + jaspGraphs::themeJaspRaw() } else { diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 42699100..010617a6 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -20,6 +20,23 @@ import JASP.Controls Form { + function sortIntervalValues() { + + console.log("sorting") + var values = [ + interval1.displayValue, + interval2.displayValue, + interval3.displayValue, + interval4.displayValue, + interval5.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] + interval5.value = values[4] + } columns: 2 DropDown @@ -408,8 +425,51 @@ Form CheckBox { name: "posteriorDistributionPlot" - label: qsTr("posterior distribution") + label: qsTr("Posterior distribution") + } + } + + Group + { + + title: qsTr("Tables") + + // CheckBox + // { + // name: "intervalTable" + // label: qsTr("Interval") + // childrenOnSameRow: true + + // Label { text: "-Inf" } + // DoubleField { name: "interval1"; fieldWidth: 30; defaultValue: 0.00 } + // DoubleField { name: "interval2"; fieldWidth: 30; defaultValue: 0.25 } + // DoubleField { name: "interval3"; fieldWidth: 30; defaultValue: 0.50 } + // DoubleField { name: "interval4"; fieldWidth: 30; defaultValue: 0.75 } + // DoubleField { name: "interval5"; fieldWidth: 30; defaultValue: 1.00 } + // Label { text: "-Inf" } + // } + CheckBox + { + name: "intervalTable"; label: qsTr("Interval"); childrenOnSameRow: true + + info: qsTr("Show the posterior probabilities of the interval specifed with the input on the right. Note that the input is automatically sorted") + + Row + { + + spacing: jaspTheme.rowSpacing + DoubleField { id: interval0; name: "interval0"; fieldWidth: 30; negativeValues: true; defaultValue: -Infinity; editable: false } + DoubleField { id: interval1; name: "interval1"; fieldWidth: 30; negativeValues: true; defaultValue: 0.00; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval2; name: "interval2"; fieldWidth: 30; negativeValues: true; defaultValue: 0.25; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval3; name: "interval3"; fieldWidth: 30; negativeValues: true; defaultValue: 0.50; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval4; name: "interval4"; fieldWidth: 30; negativeValues: true; defaultValue: 0.75; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval5; name: "interval5"; fieldWidth: 30; negativeValues: true; defaultValue: 1.00; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval6; name: "interval6"; fieldWidth: 30; negativeValues: true; defaultValue: Infinity; editable: false } + } + + } + } } From 46efefcd6cabfeef5a745ceb636b2403b018962e Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Tue, 1 Jul 2025 09:27:25 +0200 Subject: [PATCH 04/15] tempcommit --- R/bayesianProcessCapabilityStudies.R | 32 ++++- inst/qml/bayesianProcessCapabilityStudies.qml | 113 ++++++++++++++++++ 2 files changed, 142 insertions(+), 3 deletions(-) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index 3f15d9c9..8eb2d330 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -188,8 +188,16 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { createJaspPlot( title = gettext("Posterior Distribution"), plot = if (.bpcsIsReady(options) && !is.null(fit)) { - qc::plot_density(fit$summaryObject, - what = c("Cp", "CpU", "CpL", "Cpk", "Cpm")) + + qc::plot_density( + fit$summaryObject, + what = c("Cp", "CpU", "CpL", "Cpk", "Cpm"), + point_estimate = options[["posteriorDistributionPlotIndividualPointEstimateType"]], + ci = options[["posteriorDistributionPlotIndividualCiType"]], + ci_level = options[["posteriorDistributionPlotIndividualCiMass"]], + ci_custom_left = options[["posteriorDistributionPlotIndividualCiLower"]], + ci_custom_right = options[["posteriorDistributionPlotIndividualCiUpper"]], + bf_support = options[["posteriorDistributionPlotIndividualCiBf"]] + ) + jaspGraphs::geom_rangeframe() + jaspGraphs::themeJaspRaw() } else { @@ -198,8 +206,26 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { width = 320 * 6, height = 320, dependencies = jaspDeps( - options = c(.bpcsDefaultDeps(), "posteriorDistributionPlot") + options = c(.bpcsDefaultDeps(), .bpcsPosteriorPlotDeps(options)) ) ) ) } + +.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" + ) + ) +} diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 010617a6..1d17c979 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -426,6 +426,119 @@ Form { name: "posteriorDistributionPlot" label: qsTr("Posterior distribution") + + CheckBox + { + label: qsTr("Point estimate") + name: "posteriorDistributionPlotIndividualPointEstimate" + childrenOnSameRow: true + + DropDown + { + name: "posteriorDistributionPlotIndividualPointEstimateType" + label: "" + values: [ + {label: qsTr("Mean"), value: "mean"}, + {label: qsTr("Median"), value: "median"}, + {label: qsTr("Mode"), value: "mode"} + ] + } + } + + CheckBox + { + name: "posteriorDistributionPlotIndividualCi" + label: qsTr("CI") + id: plotsPosteriorIndividualCI + childrenOnSameRow: true + + DropDown + { + name: "posteriorDistributionPlotIndividualCiType" + label: "" + id: plotsPosteriorIndividualType + values: [ + {label: qsTr("Central"), value: "central"}, + {label: qsTr("HPD"), value: "HPD"}, + {label: qsTr("Custom"), value: "custom"}, + {label: qsTr("Support"), value: "support"} + ] + } + } + + Group + { + columns: 2 + + CIField + { + visible: plotsPosteriorIndividualType.currentText == "central" | plotsPosteriorIndividualType.currentText == "HPD" + enabled: plotsPosteriorIndividualCI.checked + name: "posteriorDistributionPlotIndividualCiMass" + label: qsTr("Mass") + fieldWidth: 50 + defaultValue: 95 + min: 1 + max: 100 + inclusive: JASP.MinMax + } + + DoubleField + { + visible: plotsPosteriorIndividualType.currentText == "custom" + enabled: plotsPosteriorIndividualCI.checked + name: "posteriorDistributionPlotIndividualCiLower" + label: qsTr("Lower") + id: plotsPosteriorLower + fieldWidth: 50 + defaultValue: analysisType === "binomial" ? 0.25 : -1 + min: analysisType === "binomial" ? 0 : -9999999999 + max: plotsPosteriorUpper.value + inclusive: JASP.MinMax + } + + DoubleField + { + visible: plotsPosteriorIndividualType.currentText == "custom" + enabled: plotsPosteriorIndividualCI.checked + name: "posteriorDistributionPlotIndividualCiUpper" + label: qsTr("Upper") + id: plotsPosteriorUpper + fieldWidth: 50 + defaultValue: analysisType === "binomial" ? 0.75 : 1 + min: plotsPosteriorLower.value + max: analysisType === "binomial" ? 1 : 9999999999 + inclusive: JASP.MinMax + } + + FormulaField + { + visible: plotsPosteriorIndividualType.currentText == "support" + enabled: plotsPosteriorIndividualCI.checked + name: "posteriorDistributionPlotIndividualCiBf" + label: qsTr("BF") + fieldWidth: 50 + defaultValue: "1" + min: 0 + inclusive: JASP.None + } + } + + CheckBox + { + name: "posteriorDistributionPlotPriorDistribution" + label: qsTr("Prior distribution") + checked: false + } + + // CheckBox + // { + // name: "posteriorDistributionPlotObservedProportion" + // label: qsTr("Observed proportion") + // id: plotsPosteriorIndividualProportion + // checked: false + // } + } } From 07afd805d66004e9cc155c2740e0f0f0ce8ce002 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Mon, 22 Sep 2025 15:34:58 +0200 Subject: [PATCH 05/15] updates I should've pushed earlier --- R/bayesianProcessCapabilityStudies.R | 9 ++-- inst/qml/bayesianProcessCapabilityStudies.qml | 51 +++++++------------ 2 files changed, 23 insertions(+), 37 deletions(-) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index 8eb2d330..d9493a5c 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -27,7 +27,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { } .bpcsIsReady <- function(options) { - length(options[["measurementLongFormat"]]) > 0L && options[["measurementLongFormat"]] != "" && + length(options[["measurementLongFormat"]]) > 0L && + options[["measurementLongFormat"]] != "" && options[["lowerSpecificationLimit"]] && options[["upperSpecificationLimit"]] && options[["target"]] @@ -141,7 +142,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { # qc does c(-Inf, interval_probability, Inf) interval_probability <- unlist(options[paste0("interval", 1:5)], use.names = FALSE) interval_summary <- summary(fit[["rawfit"]], interval_probability = interval_probability)[["interval_summary"]] - colnames(interval_summary) <- paste0("interval", 1:6) + colnames(interval_summary) <- c("metric", paste0("interval", 1:6)) table$setData(interval_summary) }, error = function(e) { @@ -191,8 +192,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { qc::plot_density( fit$summaryObject, what = c("Cp", "CpU", "CpL", "Cpk", "Cpm"), - point_estimate = options[["posteriorDistributionPlotIndividualPointEstimateType"]], - ci = options[["posteriorDistributionPlotIndividualCiType"]], + point_estimate = with(options, if (posteriorDistributionPlotIndividualPointEstimate) posteriorDistributionPlotIndividualPointEstimateType else "none"), + ci = with(options, if (posteriorDistributionPlotIndividualCi) posteriorDistributionPlotIndividualCiType else "none"), ci_level = options[["posteriorDistributionPlotIndividualCiMass"]], ci_custom_left = options[["posteriorDistributionPlotIndividualCiLower"]], ci_custom_right = options[["posteriorDistributionPlotIndividualCiUpper"]], diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 1d17c979..7eb35c16 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -472,7 +472,7 @@ Form CIField { - visible: plotsPosteriorIndividualType.currentText == "central" | plotsPosteriorIndividualType.currentText == "HPD" + visible: plotsPosteriorIndividualType.currentValue === "central" || plotsPosteriorIndividualType.currentValue === "HPD" enabled: plotsPosteriorIndividualCI.checked name: "posteriorDistributionPlotIndividualCiMass" label: qsTr("Mass") @@ -485,35 +485,33 @@ Form DoubleField { - visible: plotsPosteriorIndividualType.currentText == "custom" + visible: plotsPosteriorIndividualType.currentValue === "custom" enabled: plotsPosteriorIndividualCI.checked name: "posteriorDistributionPlotIndividualCiLower" label: qsTr("Lower") id: plotsPosteriorLower fieldWidth: 50 - defaultValue: analysisType === "binomial" ? 0.25 : -1 - min: analysisType === "binomial" ? 0 : -9999999999 - max: plotsPosteriorUpper.value + defaultValue: 0 + negativeValues: true inclusive: JASP.MinMax } DoubleField { - visible: plotsPosteriorIndividualType.currentText == "custom" + visible: plotsPosteriorIndividualType.currentValue === "custom" enabled: plotsPosteriorIndividualCI.checked name: "posteriorDistributionPlotIndividualCiUpper" label: qsTr("Upper") id: plotsPosteriorUpper fieldWidth: 50 - defaultValue: analysisType === "binomial" ? 0.75 : 1 - min: plotsPosteriorLower.value - max: analysisType === "binomial" ? 1 : 9999999999 + defaultValue: 1 + negativeValues: true inclusive: JASP.MinMax } FormulaField { - visible: plotsPosteriorIndividualType.currentText == "support" + visible: plotsPosteriorIndividualType.currentValue === "support" enabled: plotsPosteriorIndividualCI.checked name: "posteriorDistributionPlotIndividualCiBf" label: qsTr("BF") @@ -547,37 +545,24 @@ Form title: qsTr("Tables") - // CheckBox - // { - // name: "intervalTable" - // label: qsTr("Interval") - // childrenOnSameRow: true - - // Label { text: "-Inf" } - // DoubleField { name: "interval1"; fieldWidth: 30; defaultValue: 0.00 } - // DoubleField { name: "interval2"; fieldWidth: 30; defaultValue: 0.25 } - // DoubleField { name: "interval3"; fieldWidth: 30; defaultValue: 0.50 } - // DoubleField { name: "interval4"; fieldWidth: 30; defaultValue: 0.75 } - // DoubleField { name: "interval5"; fieldWidth: 30; defaultValue: 1.00 } - // Label { text: "-Inf" } - // } CheckBox { name: "intervalTable"; label: qsTr("Interval"); childrenOnSameRow: true - info: qsTr("Show the posterior probabilities of the interval specifed with the input on the right. Note that the input is automatically sorted") + info: qsTr("Show the posterior probabilities of the interval specifed 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.") Row { - + id: intervalRow spacing: jaspTheme.rowSpacing - DoubleField { id: interval0; name: "interval0"; fieldWidth: 30; negativeValues: true; defaultValue: -Infinity; editable: false } - DoubleField { id: interval1; name: "interval1"; fieldWidth: 30; negativeValues: true; defaultValue: 0.00; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval2; name: "interval2"; fieldWidth: 30; negativeValues: true; defaultValue: 0.25; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval3; name: "interval3"; fieldWidth: 30; negativeValues: true; defaultValue: 0.50; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval4; name: "interval4"; fieldWidth: 30; negativeValues: true; defaultValue: 0.75; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval5; name: "interval5"; fieldWidth: 30; negativeValues: true; defaultValue: 1.00; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval6; name: "interval6"; fieldWidth: 30; negativeValues: true; defaultValue: Infinity; editable: false } + property int fw: 50 + DoubleField { id: interval0; name: "interval0"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: -Infinity; editable: false } + DoubleField { id: interval1; name: "interval1"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 0.00; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval2; name: "interval2"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 0.25; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval3; name: "interval3"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 0.50; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval4; name: "interval4"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 0.75; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval5; name: "interval5"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 1.00; onEditingFinished: sortIntervalValues() } + DoubleField { id: interval6; name: "interval6"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: Infinity; editable: false } } From 8f7b87f7af52326aabab66fd7e7c6b57964df9c9 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Thu, 2 Oct 2025 16:25:27 +0200 Subject: [PATCH 06/15] add sequential analysis plot --- DESCRIPTION | 61 ++++---- R/bayesianProcessCapabilityStudies.R | 130 +++++++++++++++++- example.csv | 51 +++++++ inst/qml/bayesianProcessCapabilityStudies.qml | 31 +++++ 4 files changed, 241 insertions(+), 32 deletions(-) create mode 100644 example.csv diff --git a/DESCRIPTION b/DESCRIPTION index 847ba766..47e18bff 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -10,38 +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, - qc, - 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.3.2 +RoxygenNote: 7.3.3 diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index d9493a5c..ffd72cbb 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -23,6 +23,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { fit <- .bpcsCapabilityTable(jaspResults, dataset, options) .bpcsCapabilityPlot(jaspResults, options, fit) .bpcsIntervalTable(jaspResults, options, fit) + .bpcsSequentialPlot(jaspResults, dataset, options, fit) } @@ -104,7 +105,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { .bpcsCapabilityTableMeta <- function(jaspResults, options) { - table <- createJaspTable(title = gettext("Capability Table")) + table <- createJaspTable(title = gettext("Capability Table"), position = 0) table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), type = "string") table$addColumnInfo(name = "mean", title = gettext("Mean"), type = "number") table$addColumnInfo(name = "median", title = gettext("Median"), type = "number") @@ -158,7 +159,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { table <- createJaspTable(title = gettext("Interval Table")) - table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), type = "string") + table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), type = "string", + position = 3) intervalBounds <- c(-Inf, unlist(options[paste0("interval", 1:5)], use.names = FALSE), Inf) n <- length(intervalBounds) @@ -206,6 +208,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { }, width = 320 * 6, height = 320, + position = 1, dependencies = jaspDeps( options = c(.bpcsDefaultDeps(), .bpcsPosteriorPlotDeps(options)) ) @@ -230,3 +233,126 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { ) ) } + +.bpcsSequentialPlot <- function(jaspResults, dataset, options, fit) { + + if (!options[["sequentialAnalysisPlot"]] || !is.null(jaspResults[["sequentialAnalysisPlot"]])) + return() + + w <- 400 + plt <- createJaspPlot(title = gettext("Sequential Analysis"), width = 2*w, height = w, + position = 2, + dependencies = jaspDeps(c( + .bpcsDefaultDeps(), + "sequentialAnalysisPlot", + "sequentialAnalysisPlotPointEstimateType", + "sequentialAnalysisPlotCi", + "sequentialAnalysisPlotCiMass" + ))) + jaspResults[["sequentialAnalysisPlot"]] <- plt + + if (!.bpcsIsReady(options) || jaspResults$getError()) return() + + tryCatch({ + + sequentialPlotData <- jaspResults[["sequentialAnalysisPlotData"]] %setOrRetrieve% ( + .bpcsComputeSequentialAnalysis(dataset, options, fit) |> + createJaspState(dependencies = jaspDeps(options = c(.bpcsDefaultDeps(), "sequentialAnalysisPlot", "sequentialAnalysisPlotCiMass"))) + ) + + jaspResults[["sequentialAnalysisPlot"]]$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options) + + }, error = function(e) { + + plt$setError(gettextf("Unexpected error in sequential analysis plot: %s", 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, 4, length(nseq))) + + keys <- c("mean", "median", "lower", "upper") + dimnames(estimates) <- list(list(), keys, list()) + + x <- dataset[[1L]] + + jaspBase::startProgressbar(length(nseq), label = gettext("Running sequential analysis")) + + 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_nu = jaspQualityControl:::.bpcsTpriorFromOptions(options) + ) + + sum_i <- summary(fit_i)$summary + + 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 didn't change... + # sum_n <- summary(fit)$summary + # estimates[, , n] <- as.matrix(sum_n[keys]) + + return(estimates) +} + +.bpcsMakeSequentialPlot <- function(estimates, options) { + + # 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) + + nseq <- attr(estimates, "nseq") + + pointEstimateName <- if (options[["sequentialAnalysisPlotPointEstimateType"]] == "mean") "mean" else "median" + 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", ]), + ) + + # get y scales per facet + y_breaks_per_scale <- tapply(tb, tb$metric, \(x) { + breaks <- jaspGraphs::getPrettyAxisBreaks(range(x$lower, x$upper, na.rm = TRUE)) + ggplot2::scale_y_continuous(breaks = breaks, limits = range(breaks)) + }, simplify = FALSE) + + ribbon <- NULL + if (options[["sequentialAnalysisPlotCi"]]) + ribbon <- ggplot2::geom_ribbon(ggplot2::aes(ymin = lower, ymax = upper), alpha = 0.3) + + ggplot2::ggplot(tb, ggplot2::aes(x = n, y = mean)) + + ribbon + + ggplot2::geom_line() + + ggplot2::facet_wrap(~ metric, scales = "free_y") + + ggh4x::facetted_pos_scales(y = y_breaks_per_scale) + + ggplot2::labs( + x = gettext("Number of observations"), + y = gettext("Estimate with 95% credible interval") + ) + + jaspGraphs::geom_rangeframe() + + jaspGraphs::themeJaspRaw() + + ggplot2::theme(panel.grid.major.y = ggplot2::element_line(linewidth = .5, color = "lightgray", linetype = "dashed")) + +} 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/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 7eb35c16..96dfa76c 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -538,6 +538,37 @@ Form // } } + + CheckBox + { + name: "sequentialAnalysisPlot" + label: qsTr("Sequential Analysis") + + DropDown + { + label: qsTr("Type") + name: "sequentialAnalysisPlotPointEstimateType" + values: ["mean", "median"]//, "mode"] + } + + CheckBox + { + name: "sequentialAnalysisPlotCi" + label: qsTr("CI") + childrenOnSameRow: true + + CIField + { + name: "sequentialAnalysisPlotCiMass" + label: qsTr("Mass") + fieldWidth: 50 + defaultValue: 95 + min: 1 + max: 100 + inclusive: JASP.MaxOnly + } + } + } } Group From 28b9ccf59a411ab2c5d5ae23c64dae97685118d7 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Fri, 3 Oct 2025 13:25:19 +0200 Subject: [PATCH 07/15] revamp of criteria + tweaks to sequential plot --- R/bayesianProcessCapabilityStudies.R | 95 +++++++++--- inst/qml/bayesianProcessCapabilityStudies.qml | 138 +++++++++++++++--- 2 files changed, 194 insertions(+), 39 deletions(-) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index ffd72cbb..e521ab26 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -48,6 +48,10 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { ) } +.bpcsProcessCriteriaDeps <- function() { + c(paste0("interval", 1:4), paste0("intervalLabel", 1:5)) +} + .bpcsTpriorFromOptions <- function(options) { switch(options[["capabilityStudyType"]], @@ -115,7 +119,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { 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("measurementLongFormat", "credibleIntervalWidth")) + table$dependOn(.bpcsDefaultDeps()) jaspResults[["bpcsCapabilityTable"]] <- table return(table) @@ -141,9 +145,9 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { tryCatch({ # qc does c(-Inf, interval_probability, Inf) - interval_probability <- unlist(options[paste0("interval", 1:5)], use.names = FALSE) + 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:6)) + colnames(interval_summary) <- c("metric", paste0("interval", 1:5)) table$setData(interval_summary) }, error = function(e) { @@ -157,24 +161,24 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { .bpcsIntervalTableMeta <- function(jaspResults, options) { - table <- createJaspTable(title = gettext("Interval Table")) + table <- createJaspTable(title = gettext("Interval Table"), position = 3) - table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), type = "string", - position = 3) + table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), type = "string") - intervalBounds <- c(-Inf, unlist(options[paste0("interval", 1:5)], use.names = FALSE), Inf) + 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%.3f , %.3f%s", lhs, intervalBounds[i], intervalBounds[j], rhs) + 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( - "measurementLongFormat", "intervalTable", "credibleIntervalWidth", - paste0("interval", 1:5), "targetValue", "lowerSpecificationLimitValue", "upperSpecificationLimitValue" - )) + table$dependOn(c("intervalTable", .bpcsDefaultDeps(), .bpcsProcessCriteriaDeps())) jaspResults[["bpcsIntervalTable"]] <- table return(table) @@ -193,7 +197,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { plot = if (.bpcsIsReady(options) && !is.null(fit)) { qc::plot_density( fit$summaryObject, - what = c("Cp", "CpU", "CpL", "Cpk", "Cpm"), + what = c("Cp", "CpU", "CpL", "Cpk", "Cpc", "Cpm"), point_estimate = with(options, if (posteriorDistributionPlotIndividualPointEstimate) posteriorDistributionPlotIndividualPointEstimateType else "none"), ci = with(options, if (posteriorDistributionPlotIndividualCi) posteriorDistributionPlotIndividualCiType else "none"), ci_level = options[["posteriorDistributionPlotIndividualCiMass"]], @@ -247,7 +251,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { "sequentialAnalysisPlot", "sequentialAnalysisPlotPointEstimateType", "sequentialAnalysisPlotCi", - "sequentialAnalysisPlotCiMass" + "sequentialAnalysisPlotCiMass", + "sequentialAnalysisPlotAdditionalInfo" ))) jaspResults[["sequentialAnalysisPlot"]] <- plt @@ -308,7 +313,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { attr(estimates, "nseq") <- nseq - # we could use this one, but only if the CI width didn't change... + # 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]) @@ -321,6 +327,11 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { # 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") pointEstimateName <- if (options[["sequentialAnalysisPlotPointEstimateType"]] == "mean") "mean" else "median" @@ -334,25 +345,69 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { # get y scales per facet y_breaks_per_scale <- tapply(tb, tb$metric, \(x) { - breaks <- jaspGraphs::getPrettyAxisBreaks(range(x$lower, x$upper, na.rm = TRUE)) - ggplot2::scale_y_continuous(breaks = breaks, limits = range(breaks)) + 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) + + leftBreaks <- jaspGraphs::getPrettyAxisBreaks(observedRange) + leftLimits <- range(leftBreaks) + + rightAxis <- ggplot2::waiver() + if (options[["sequentialAnalysisPlotAdditionalInfo"]]) { + 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 (options[["sequentialAnalysisPlotCi"]]) ribbon <- ggplot2::geom_ribbon(ggplot2::aes(ymin = lower, ymax = upper), alpha = 0.3) + extraTheme <- gridLinesLayer <- NULL + sides <- "bl" + if (options[["sequentialAnalysisPlotAdditionalInfo"]]) { + extraTheme <- ggplot2::theme(axis.ticks.y.right = ggplot2::element_line(colour = rep(c("black", NA), length.out = 11))) + 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 = yintercept), + # show.legend = FALSE, + linewidth = .5, color = "lightgray", linetype = "dashed" + ) + + } + ggplot2::ggplot(tb, ggplot2::aes(x = n, y = mean)) + + gridLinesLayer + ribbon + - ggplot2::geom_line() + + ggplot2::geom_line(linewidth = 1) + ggplot2::facet_wrap(~ metric, scales = "free_y") + ggh4x::facetted_pos_scales(y = y_breaks_per_scale) + ggplot2::labs( x = gettext("Number of observations"), y = gettext("Estimate with 95% credible interval") ) + - jaspGraphs::geom_rangeframe() + + jaspGraphs::geom_rangeframe(sides = sides) + jaspGraphs::themeJaspRaw() + - ggplot2::theme(panel.grid.major.y = ggplot2::element_line(linewidth = .5, color = "lightgray", linetype = "dashed")) + extraTheme } diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 96dfa76c..20dc97da 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -22,20 +22,39 @@ Form { function sortIntervalValues() { - console.log("sorting") var values = [ interval1.displayValue, interval2.displayValue, interval3.displayValue, - interval4.displayValue, - interval5.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] - interval5.value = values[4] + 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 @@ -417,6 +436,64 @@ Form */ } + 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{} + } + } + Group { @@ -542,7 +619,7 @@ Form CheckBox { name: "sequentialAnalysisPlot" - label: qsTr("Sequential Analysis") + label: qsTr("Sequential analysis") DropDown { @@ -568,6 +645,14 @@ Form inclusive: JASP.MaxOnly } } + + CheckBox + { + name: "sequentialAnalysisPlotAdditionalInfo" + label: qsTr("Show process criteria") + checked: true + info: qsTr("Add a secondary right axis with condition bounds for the process") + } } } @@ -578,23 +663,38 @@ Form CheckBox { - name: "intervalTable"; label: qsTr("Interval"); childrenOnSameRow: true + name: "intervalTable"; label: qsTr("Interval"); childrenOnSameRow: false info: qsTr("Show the posterior probabilities of the interval specifed 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.") - Row - { - id: intervalRow - spacing: jaspTheme.rowSpacing - property int fw: 50 - DoubleField { id: interval0; name: "interval0"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: -Infinity; editable: false } - DoubleField { id: interval1; name: "interval1"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 0.00; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval2; name: "interval2"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 0.25; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval3; name: "interval3"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 0.50; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval4; name: "interval4"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 0.75; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval5; name: "interval5"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 1.00; onEditingFinished: sortIntervalValues() } - DoubleField { id: interval6; name: "interval6"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: Infinity; editable: false } - } + // ColumnLayout + // { + // // A poor man's table + // Row + // { + // id: labelRow + // spacing: jaspTheme.rowSpacing + // property int fw: 60 // there are 6 elements of width 50 below, so this one is 6 * 50 / 5 = 60 + // Label { width: labelRow.fw; text: qsTr("Incapable") } + // Label { width: labelRow.fw; text: qsTr("Capable") } + // Label { width: labelRow.fw; text: qsTr("Satisfactory") } + // Label { width: labelRow.fw; text: qsTr("Excellent") } + // Label { width: labelRow.fw; text: qsTr("Super") } + // } + // Row + // { + // id: intervalRow + // spacing: jaspTheme.rowSpacing + // property int fw: 50 + // DoubleField { id: interval0; name: "interval0"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: -Infinity; editable: false } + // DoubleField { id: interval1; name: "interval1"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 1.00; onEditingFinished: sortIntervalValues() } + // DoubleField { id: interval2; name: "interval2"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 1.33; onEditingFinished: sortIntervalValues() } + // DoubleField { id: interval3; name: "interval3"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 1.50; onEditingFinished: sortIntervalValues() } + // DoubleField { id: interval5; name: "interval5"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 2.00; onEditingFinished: sortIntervalValues() } + // DoubleField { id: interval6; name: "interval6"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: Infinity; editable: false } + // } + // } + } From ba22e717f4dc750b40adf4deceda3fd69192bfd2 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Thu, 13 Nov 2025 09:22:52 +0100 Subject: [PATCH 08/15] rework GUI, R part broken for now --- R/bayesianProcessCapabilityStudies.R | 20 +- inst/qml/bayesianProcessCapabilityStudies.qml | 1076 ++++++++++------- inst/qml/common/PlotLayout.qml | 259 ++++ 3 files changed, 916 insertions(+), 439 deletions(-) create mode 100644 inst/qml/common/PlotLayout.qml diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index e521ab26..112e6930 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -110,10 +110,10 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { .bpcsCapabilityTableMeta <- function(jaspResults, options) { table <- createJaspTable(title = gettext("Capability Table"), position = 0) - table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), 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") + 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) @@ -210,8 +210,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { } else { NULL }, - width = 320 * 6, - height = 320, + width = 400 * 3, + height = 400 * 2, position = 1, dependencies = jaspDeps( options = c(.bpcsDefaultDeps(), .bpcsPosteriorPlotDeps(options)) @@ -244,7 +244,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { return() w <- 400 - plt <- createJaspPlot(title = gettext("Sequential Analysis"), width = 2*w, height = w, + plt <- createJaspPlot(title = gettext("Sequential Analysis"), width = 3*w, height = 2*w, position = 2, dependencies = jaspDeps(c( .bpcsDefaultDeps(), @@ -384,7 +384,11 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { extraTheme <- gridLinesLayer <- NULL sides <- "bl" if (options[["sequentialAnalysisPlotAdditionalInfo"]]) { - extraTheme <- ggplot2::theme(axis.ticks.y.right = ggplot2::element_line(colour = rep(c("black", NA), length.out = 11))) + # 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( diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 20dc97da..0824fe93 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -16,7 +16,7 @@ import QtQuick import QtQuick.Layouts import JASP.Controls -// import "./common" as Common +import "./common" as Common Form { @@ -67,7 +67,7 @@ Form values: [ {label: qsTr("Single column"), value: "longFormat"}, - {label: qsTr("Across rows"), value: "wideFormat"}, + {label: qsTr("Across rows"), value: "wideFormat"}, ] // onValueChanged: // { @@ -238,490 +238,704 @@ Form { title: qsTr("Process capability options") - ColumnLayout + Group { - 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 - } - - /* - RadioButton - { - name: "nonNormalCapabilityAnalysis" - id : nonNormalCapabilityAnalysis - label: qsTr("Non-normal distribution") - - DropDown - { - name: "nonNormalDistribution" - id: nonNormalDistribution - label: qsTr("Specify a distribution") - values: - [ - {label: qsTr("Weibull"), value: "weibull"}, - {label: qsTr("Lognormal"), value: "lognormal"}, - {label: qsTr("3-parameter Weibull"), value: "3ParameterWeibull"}, - {label: qsTr("3-parameter lognormal"), value: "3ParameterLognormal"} - ] - indexDefaultValue: 0 - } - - DropDown - { - name: "nonNormalMethod" - label: qsTr("Non-normal capability statistics") - indexDefaultValue: 0 - values: - [ - {label: qsTr("Percentile"), value: "percentile"}, - {label: qsTr("Non-conformance"), value: "nonConformance" } - ] - } - } - */ - } - } + title: qsTr("Type of data distribution") - Group + RadioButtonGroup { - title: qsTr("Capability study") + name: "capabilityStudyType" + id: capabilityStudyType - CheckBox + RadioButton { - name: "lowerSpecificationLimit" - label: qsTr("Lower specification limit") - id: lowerSpecificationLimit - childrenOnSameRow: true - - DoubleField - { - name: "lowerSpecificationLimitValue" - id: lowerSpecificationLimitValue - negativeValues: true - defaultValue: -1 - decimals: 9 - } - - // CheckBox - // { - // name: "lowerSpecificationLimitBoundary" - // label: qsTr("Boundary") - // id: lowerSpecificationLimitBoundary - // } + name: "normalCapabilityAnalysis" + id : normalCapabilityAnalysis + label: qsTr("Normal distribution") + checked: true } - CheckBox + RadioButton { - name: "target" - label: qsTr("Target value") - id: target - childrenOnSameRow: true - - DoubleField - { - name: "targetValue" - id: targetValue - negativeValues: true - defaultValue: 0 - decimals: 9 - } + name: "tCapabilityAnalysis" + id : tCapabilityAnalysis + label: qsTr("Student's t-distribution") + // checked: true } - CheckBox - { - name: "upperSpecificationLimit" - label: qsTr("Upper specification limit") - id: upperSpecificationLimit - childrenOnSameRow: true - - DoubleField - { - name: "upperSpecificationLimitValue" - id: upperSpecificationLimitValue - negativeValues: true - defaultValue: 1 - decimals: 9 - } - - // CheckBox - // { - // name: "upperSpecificationLimitBoundary" - // label: qsTr("Boundary") - // id: upperSpecificationLimitBoundary - // } - } + } + } - CIField { name: "credibleIntervalWidth"; label: qsTr("Credible interval") } + Group + { + 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: "processCapabilityPlot" - label: qsTr("Process capability plot") - checked: true - enabled: upperSpecificationLimit.checked || lowerSpecificationLimit.checked - - DoubleField - { - name: "processCapabilityPlotBinNumber" - label: qsTr("Number of bins") - defaultValue: 10 - min: 3; - max: 10000; - enabled: csBinWidthType.currentValue === "manual" - } - - CheckBox - { - name: "processCapabilityPlotDistributions" - label: qsTr("Overlay distribution") - checked: true - } - - CheckBox - { - name: "processCapabilityPlotSpecificationLimits" - label: qsTr("Display specification limits") - checked: true - } - } + CheckBox + { + name: "lowerSpecificationLimit" + label: qsTr("Lower specification limit") + id: lowerSpecificationLimit + childrenOnSameRow: true - CheckBox + DoubleField { - name: "processCapabilityTable" - label: qsTr("Process capability tables") - checked: true - enabled: upperSpecificationLimit.checked || lowerSpecificationLimit.checked - - CheckBox - { - name: "processCapabilityTableCi"; - label: qsTr("Confidence intervals") - checked: capabilityStudyType.value === "normalCapabilityAnalysis" - enabled: capabilityStudyType.value === "normalCapabilityAnalysis" - childrenOnSameRow: true - - CIField - { - name: "processCapabilityTableCiLevel" - defaultValue: 90} - } - - } + name: "lowerSpecificationLimitValue" + id: lowerSpecificationLimitValue + negativeValues: true + defaultValue: -1 + decimals: 9 } - */ + } - Group + CheckBox { + name: "target" + label: qsTr("Target value") + id: target + childrenOnSameRow: true - title: qsTr("Process Criteria") - GridLayout + DoubleField { - // 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{} + name: "targetValue" + id: targetValue + negativeValues: true + defaultValue: 0 + decimals: 9 } } - Group + CheckBox { + name: "upperSpecificationLimit" + label: qsTr("Upper specification limit") + id: upperSpecificationLimit + childrenOnSameRow: true - title: qsTr("Plots") - - CheckBox + DoubleField { - name: "posteriorDistributionPlot" - label: qsTr("Posterior distribution") - - CheckBox - { - label: qsTr("Point estimate") - name: "posteriorDistributionPlotIndividualPointEstimate" - childrenOnSameRow: true - - DropDown - { - name: "posteriorDistributionPlotIndividualPointEstimateType" - label: "" - values: [ - {label: qsTr("Mean"), value: "mean"}, - {label: qsTr("Median"), value: "median"}, - {label: qsTr("Mode"), value: "mode"} - ] - } - } - - CheckBox - { - name: "posteriorDistributionPlotIndividualCi" - label: qsTr("CI") - id: plotsPosteriorIndividualCI - childrenOnSameRow: true - - DropDown - { - name: "posteriorDistributionPlotIndividualCiType" - label: "" - id: plotsPosteriorIndividualType - values: [ - {label: qsTr("Central"), value: "central"}, - {label: qsTr("HPD"), value: "HPD"}, - {label: qsTr("Custom"), value: "custom"}, - {label: qsTr("Support"), value: "support"} - ] - } - } - - Group - { - columns: 2 - - CIField - { - visible: plotsPosteriorIndividualType.currentValue === "central" || plotsPosteriorIndividualType.currentValue === "HPD" - enabled: plotsPosteriorIndividualCI.checked - name: "posteriorDistributionPlotIndividualCiMass" - label: qsTr("Mass") - fieldWidth: 50 - defaultValue: 95 - min: 1 - max: 100 - inclusive: JASP.MinMax - } - - DoubleField - { - visible: plotsPosteriorIndividualType.currentValue === "custom" - enabled: plotsPosteriorIndividualCI.checked - name: "posteriorDistributionPlotIndividualCiLower" - label: qsTr("Lower") - id: plotsPosteriorLower - fieldWidth: 50 - defaultValue: 0 - negativeValues: true - inclusive: JASP.MinMax - } - - DoubleField - { - visible: plotsPosteriorIndividualType.currentValue === "custom" - enabled: plotsPosteriorIndividualCI.checked - name: "posteriorDistributionPlotIndividualCiUpper" - label: qsTr("Upper") - id: plotsPosteriorUpper - fieldWidth: 50 - defaultValue: 1 - negativeValues: true - inclusive: JASP.MinMax - } - - FormulaField - { - visible: plotsPosteriorIndividualType.currentValue === "support" - enabled: plotsPosteriorIndividualCI.checked - name: "posteriorDistributionPlotIndividualCiBf" - label: qsTr("BF") - fieldWidth: 50 - defaultValue: "1" - min: 0 - inclusive: JASP.None - } - } - - CheckBox - { - name: "posteriorDistributionPlotPriorDistribution" - label: qsTr("Prior distribution") - checked: false - } - - // CheckBox - // { - // name: "posteriorDistributionPlotObservedProportion" - // label: qsTr("Observed proportion") - // id: plotsPosteriorIndividualProportion - // checked: false - // } - + name: "upperSpecificationLimitValue" + id: upperSpecificationLimitValue + negativeValues: true + defaultValue: 1 + decimals: 9 } - CheckBox - { - name: "sequentialAnalysisPlot" - label: qsTr("Sequential analysis") - - DropDown - { - label: qsTr("Type") - name: "sequentialAnalysisPlotPointEstimateType" - values: ["mean", "median"]//, "mode"] - } - - CheckBox - { - name: "sequentialAnalysisPlotCi" - label: qsTr("CI") - childrenOnSameRow: true - - CIField - { - name: "sequentialAnalysisPlotCiMass" - label: qsTr("Mass") - fieldWidth: 50 - defaultValue: 95 - min: 1 - max: 100 - inclusive: JASP.MaxOnly - } - } - - CheckBox - { - name: "sequentialAnalysisPlotAdditionalInfo" - label: qsTr("Show process criteria") - checked: true - info: qsTr("Add a secondary right axis with condition bounds for the process") - } - } } - Group + } + + 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{} + } + } - title: qsTr("Tables") - CheckBox - { - name: "intervalTable"; label: qsTr("Interval"); childrenOnSameRow: false - - info: qsTr("Show the posterior probabilities of the interval specifed 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.") - - // ColumnLayout - // { - // // A poor man's table - // Row - // { - // id: labelRow - // spacing: jaspTheme.rowSpacing - // property int fw: 60 // there are 6 elements of width 50 below, so this one is 6 * 50 / 5 = 60 - // Label { width: labelRow.fw; text: qsTr("Incapable") } - // Label { width: labelRow.fw; text: qsTr("Capable") } - // Label { width: labelRow.fw; text: qsTr("Satisfactory") } - // Label { width: labelRow.fw; text: qsTr("Excellent") } - // Label { width: labelRow.fw; text: qsTr("Super") } - // } - // Row - // { - // id: intervalRow - // spacing: jaspTheme.rowSpacing - // property int fw: 50 - // DoubleField { id: interval0; name: "interval0"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: -Infinity; editable: false } - // DoubleField { id: interval1; name: "interval1"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 1.00; onEditingFinished: sortIntervalValues() } - // DoubleField { id: interval2; name: "interval2"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 1.33; onEditingFinished: sortIntervalValues() } - // DoubleField { id: interval3; name: "interval3"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 1.50; onEditingFinished: sortIntervalValues() } - // DoubleField { id: interval5; name: "interval5"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: 2.00; onEditingFinished: sortIntervalValues() } - // DoubleField { id: interval6; name: "interval6"; fieldWidth: intervalRow.fw; negativeValues: true; defaultValue: Infinity; editable: false } - // } - // } + } + // 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 + } + + // CheckBox + // { + // name: "posteriorDistributionPlot" + // label: qsTr("Posterior distribution") + + // // 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 + // { + // CheckBox + // { + // label: qsTr("Point estimate") + // name: "posteriorDistributionPlotIndividualPointEstimate" + // childrenOnSameRow: true + + // DropDown + // { + // name: "posteriorDistributionPlotIndividualPointEstimateType" + // 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 + // { + // columns: 1 + // CheckBox + // { + // name: "posteriorDistributionPlotIndividualCi" + // label: qsTr("CI") + // id: posteriorPlotIndividualCI + // childrenOnSameRow: true + + // DropDown + // { + // name: "posteriorDistributionPlotIndividualCiType" + // 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: "posteriorDistributionPlotIndividualCiMass" + // label: qsTr("Mass") + // fieldWidth: 50 + // defaultValue: 95 + // min: 1 + // max: 100 + // inclusive: JASP.MinMax + // } + + // DoubleField + // { + // visible: posteriorPlotIndividualType.currentValue === "custom" + // name: "posteriorDistributionPlotIndividualCiLower" + // label: qsTr("Lower") + // id: plotsPosteriorLower + // fieldWidth: 50 + // defaultValue: 0 + // negativeValues: true + // inclusive: JASP.MinMax + // } + + // DoubleField + // { + // visible: posteriorPlotIndividualType.currentValue === "custom" + // name: "posteriorDistributionPlotIndividualCiUpper" + // label: qsTr("Upper") + // id: plotsPosteriorUpper + // fieldWidth: 50 + // defaultValue: 1 + // negativeValues: true + // inclusive: JASP.MinMax + // } + + // FormulaField + // { + // visible: posteriorPlotIndividualType.currentValue === "support" + // name: "posteriorDistributionPlotIndividualCiBf" + // label: qsTr("BF") + // fieldWidth: 50 + // defaultValue: "1" + // min: 0 + // inclusive: JASP.None + // } + // } + // } + // } + + // // Common.PlotLayout {} // <- would be better but does not work... + // RadioButtonGroup + // { + // name: "posteriorDistributionPlotPanelLayout" + // 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 + // { + // name: "posteriorDistributionPlotAxes" + // title: qsTr("Axes") + // id: posteriorDistributionPlotAxes + + // RadioButton { value: "identical"; label: qsTr("Automatic"); checked: true } + // RadioButton { value: "automatic"; label: qsTr("Identical across panels"); enabled: posteriorDistributionPlotPanelLayout.value === "multiplePanels" } + // RadioButton { value: "custom"; label: qsTr("Custom axes"); } + // } + + // Group + // { + + // title: qsTr("Custom axes") + // enabled: posteriorDistributionPlotAxes.value === "custom" + // visible: 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: "custom_x_min"; id: custom_x_min; fieldWidth: customAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: true; max: custom_x_max.value} + // Item{} + // DoubleField { name: "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: "custom_y_min"; id: custom_y_min; fieldWidth: customAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: false; max: custom_y_max.value} + // Item{} + // DoubleField { name: "custom_y_max"; id: custom_y_max; fieldWidth: customAxesLayout.dbWidth; defaultValue: 1.00; negativeValues: false; min: custom_y_min.value} + // } + // } + + // CheckBox + // { + // name: "posteriorDistributionPlotPriorDistribution" + // label: qsTr("Show prior distribution") + // checked: false + // } + // } + // } + + // CheckBox + // { + // name: "priorDistributionPlot" + // label: qsTr("Prior distribution") + + // // 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 + // { + // CheckBox + // { + // label: qsTr("Point estimate") + // name: "priorDistributionPlotIndividualPointEstimate" + // childrenOnSameRow: true + + // DropDown + // { + // name: "priorDistributionPlotIndividualPointEstimateType" + // 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 + // { + // columns: 1 + // CheckBox + // { + // name: "priorDistributionPlotIndividualCi" + // label: qsTr("CI") + // id: priorPlotIndividualCI + // childrenOnSameRow: true + + // DropDown + // { + // name: "priorDistributionPlotIndividualCiType" + // label: "" + // id: priorPlotIndividualType + // 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: priorPlotIndividualCI.checked + + // CIField + // { + // visible: priorPlotIndividualType.currentValue === "central" || priorPlotIndividualType.currentValue === "HPD" + // name: "priorDistributionPlotIndividualCiMass" + // label: qsTr("Mass") + // fieldWidth: 50 + // defaultValue: 95 + // min: 1 + // max: 100 + // inclusive: JASP.MinMax + // } + + // DoubleField + // { + // visible: priorPlotIndividualType.currentValue === "custom" + // name: "priorDistributionPlotIndividualCiLower" + // label: qsTr("Lower") + // id: plotspriorLower + // fieldWidth: 50 + // defaultValue: 0 + // negativeValues: true + // inclusive: JASP.MinMax + // } + + // DoubleField + // { + // visible: priorPlotIndividualType.currentValue === "custom" + // name: "priorDistributionPlotIndividualCiUpper" + // label: qsTr("Upper") + // id: plotspriorUpper + // fieldWidth: 50 + // defaultValue: 1 + // negativeValues: true + // inclusive: JASP.MinMax + // } + + // FormulaField + // { + // visible: priorPlotIndividualType.currentValue === "support" + // name: "priorDistributionPlotIndividualCiBf" + // label: qsTr("BF") + // fieldWidth: 50 + // defaultValue: "1" + // min: 0 + // inclusive: JASP.None + // } + // } + // } + // } + + // RadioButtonGroup + // { + // name: "priorDistributionPlotPanelLayout" + // title: qsTr("Layout") + // id: priorDistributionPlotPanelLayout + + // RadioButton { value: "multiplePanels"; label: qsTr("One plot per metric"); checked: true } + // RadioButton { value: "singlePanel"; label: qsTr("All metrics in one plot") } + + // } + + // RadioButtonGroup + // { + // name: "priorDistributionPlotAxes" + // title: qsTr("Axes") + // id: priorDistributionPlotAxes + + // RadioButton { value: "identical"; label: qsTr("Automatic"); checked: true } + // RadioButton { value: "automatic"; label: qsTr("Identical across panels"); enabled: priorDistributionPlotPanelLayout.value === "multiplePanels" } + // RadioButton { value: "custom"; label: qsTr("Custom axes"); } + // } + + // Group + // { + + // title: qsTr("Custom axes") + // enabled: priorDistributionPlotAxes.value === "custom" + // visible: priorDistributionPlotAxes.value === "custom" + + // GridLayout + // { + // columns: 5 + // columnSpacing: 2 + // rowSpacing: jaspTheme.rowGridSpacing / 3 + // id: priorCustomAxesLayout + // 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: "prior_custom_x_min"; id: prior_custom_x_min; fieldWidth: priorCustomAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: true; max: prior_custom_x_max.value} + // Item{} + // DoubleField { name: "prior_custom_x_max"; id: prior_custom_x_max; fieldWidth: priorCustomAxesLayout.dbWidth; defaultValue: 1.00; negativeValues: true; min: prior_custom_x_min.value} + + // // Row 2: y axis + // Label { text: qsTr("y axis"); } + // Item{} + // DoubleField { name: "prior_custom_y_min"; id: prior_custom_y_min; fieldWidth: priorCustomAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: false; max: prior_custom_y_max.value} + // Item{} + // DoubleField { name: "prior_custom_y_max"; id: prior_custom_y_max; fieldWidth: priorCustomAxesLayout.dbWidth; defaultValue: 1.00; negativeValues: false; min: prior_custom_y_min.value} + // } + // } + // } + // } + + } + Section + { + title: qsTr("Sequential Analysis") + + Common.PlotLayout + { + baseName: "sequentialAnalysisPointEstimatePlot" + baseLabel: qsTr("Point estimate plot") + hasPrior: false } - ColumnLayout + Common.PlotLayout + { + baseName: "sequentialAnalysisPointIntervalPlot" + baseLabel: qsTr("Interval estimate plot") + hasPrior: false + hasEstimate: false + hasCi: false + hasType: true + } + + // CheckBox + // { + // name: "sequentialAnalysisPointEstimatePlot" + // label: qsTr("Point estimate plot") + // id: sequentialAnalysisPointEstimatePlot + + // Group + // { + // columns: 2 + // columnSpacing: 10 * jaspTheme.columnGroupSpacing + + // DropDown + // { + // label: qsTr("Type") + // name: "sequentialAnalysisPlotPointEstimateType" + // values: [ + // {label: qsTr("mean"), value: "mean"}, + // {label: qsTr("median"), value: "median"} + // // mode? + // ] + // } + + // CheckBox + // { + // name: "sequentialAnalysisPlotPointEstimateCi" + // label: qsTr("CI") + // childrenOnSameRow: true + + // CIField + // { + // name: "sequentialAnalysisPlotPointEstimateCiMass" + // label: qsTr("Mass") + // fieldWidth: 50 + // defaultValue: 95 + // min: 1 + // max: 100 + // inclusive: JASP.MaxOnly + // } + // } + + // } + // } + + // CheckBox + // { + // name: "sequentialAnalysisIntervalEstimatePlot" + // label: qsTr("Interval estimate plot") + // id: sequentialAnalysisIntervalEstimatePlot + + // DropDown + // { + // label: qsTr("Type") + // name: "sequentialAnalysisPlotPointEstimateType" + // values: [ + // {label: qsTr("mean"), value: "mean"}, + // {label: qsTr("median"), value: "median"} + // // mode? + // ] + // } + + // CheckBox + // { + // name: "sequentialAnalysisPlotCi" + // label: qsTr("CI") + // childrenOnSameRow: true + + // CIField + // { + // name: "sequentialAnalysisPlotCiMass" + // label: qsTr("Mass") + // fieldWidth: 50 + // defaultValue: 95 + // min: 1 + // max: 100 + // inclusive: JASP.MaxOnly + // } + // } + + // CheckBox + // { + // name: "sequentialAnalysisPlotAdditionalInfo" + // label: qsTr("Show process criteria") + // checked: true + // info: qsTr("Add a secondary right axis with condition bounds for the process") + // } + // } + + Group { + CheckBox + { + name: "sequentialAnalysisPlotAdditionalInfo" + label: qsTr("Show process criteria") + checked: true + info: qsTr("Add a secondary right axis with condition bounds for the process") + } - Group + CheckBox { - title: qsTr("Stability of the process") + 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("Process capability report") - + title: qsTr("Prior distribution") } Section { title: qsTr("Advanced options") - } } diff --git a/inst/qml/common/PlotLayout.qml b/inst/qml/common/PlotLayout.qml new file mode 100644 index 00000000..e023dfc1 --- /dev/null +++ b/inst/qml/common/PlotLayout.qml @@ -0,0 +1,259 @@ +// +// 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 + +// Item +// { +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 + + CheckBox + { + + 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 + { + 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 + { + name: baseName + "Axes" + title: qsTr("Axes") + id: posteriorDistributionPlotAxes + + RadioButton { value: "identical"; label: qsTr("Automatic"); checked: true } + RadioButton { value: "automatic"; label: qsTr("Identical across panels"); enabled: posteriorDistributionPlotPanelLayout.value === "multiplePanels" } + RadioButton { value: "custom"; label: qsTr("Custom axes"); } + } + + Group + { + + title: qsTr("Custom axes") + enabled: posteriorDistributionPlotAxes.value === "custom" + visible: 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 From 487564c7dfce417611a36dcabd4deefb091c993d Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Thu, 13 Nov 2025 10:59:41 +0100 Subject: [PATCH 09/15] add GUI for priors --- inst/qml/bayesianProcessCapabilityStudies.qml | 496 ++---------------- inst/qml/common/Priors.qml | 210 ++++++++ 2 files changed, 256 insertions(+), 450 deletions(-) create mode 100644 inst/qml/common/Priors.qml diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 0824fe93..71785982 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -436,368 +436,6 @@ Form hasPrior: false } - // CheckBox - // { - // name: "posteriorDistributionPlot" - // label: qsTr("Posterior distribution") - - // // 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 - // { - // CheckBox - // { - // label: qsTr("Point estimate") - // name: "posteriorDistributionPlotIndividualPointEstimate" - // childrenOnSameRow: true - - // DropDown - // { - // name: "posteriorDistributionPlotIndividualPointEstimateType" - // 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 - // { - // columns: 1 - // CheckBox - // { - // name: "posteriorDistributionPlotIndividualCi" - // label: qsTr("CI") - // id: posteriorPlotIndividualCI - // childrenOnSameRow: true - - // DropDown - // { - // name: "posteriorDistributionPlotIndividualCiType" - // 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: "posteriorDistributionPlotIndividualCiMass" - // label: qsTr("Mass") - // fieldWidth: 50 - // defaultValue: 95 - // min: 1 - // max: 100 - // inclusive: JASP.MinMax - // } - - // DoubleField - // { - // visible: posteriorPlotIndividualType.currentValue === "custom" - // name: "posteriorDistributionPlotIndividualCiLower" - // label: qsTr("Lower") - // id: plotsPosteriorLower - // fieldWidth: 50 - // defaultValue: 0 - // negativeValues: true - // inclusive: JASP.MinMax - // } - - // DoubleField - // { - // visible: posteriorPlotIndividualType.currentValue === "custom" - // name: "posteriorDistributionPlotIndividualCiUpper" - // label: qsTr("Upper") - // id: plotsPosteriorUpper - // fieldWidth: 50 - // defaultValue: 1 - // negativeValues: true - // inclusive: JASP.MinMax - // } - - // FormulaField - // { - // visible: posteriorPlotIndividualType.currentValue === "support" - // name: "posteriorDistributionPlotIndividualCiBf" - // label: qsTr("BF") - // fieldWidth: 50 - // defaultValue: "1" - // min: 0 - // inclusive: JASP.None - // } - // } - // } - // } - - // // Common.PlotLayout {} // <- would be better but does not work... - // RadioButtonGroup - // { - // name: "posteriorDistributionPlotPanelLayout" - // 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 - // { - // name: "posteriorDistributionPlotAxes" - // title: qsTr("Axes") - // id: posteriorDistributionPlotAxes - - // RadioButton { value: "identical"; label: qsTr("Automatic"); checked: true } - // RadioButton { value: "automatic"; label: qsTr("Identical across panels"); enabled: posteriorDistributionPlotPanelLayout.value === "multiplePanels" } - // RadioButton { value: "custom"; label: qsTr("Custom axes"); } - // } - - // Group - // { - - // title: qsTr("Custom axes") - // enabled: posteriorDistributionPlotAxes.value === "custom" - // visible: 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: "custom_x_min"; id: custom_x_min; fieldWidth: customAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: true; max: custom_x_max.value} - // Item{} - // DoubleField { name: "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: "custom_y_min"; id: custom_y_min; fieldWidth: customAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: false; max: custom_y_max.value} - // Item{} - // DoubleField { name: "custom_y_max"; id: custom_y_max; fieldWidth: customAxesLayout.dbWidth; defaultValue: 1.00; negativeValues: false; min: custom_y_min.value} - // } - // } - - // CheckBox - // { - // name: "posteriorDistributionPlotPriorDistribution" - // label: qsTr("Show prior distribution") - // checked: false - // } - // } - // } - - // CheckBox - // { - // name: "priorDistributionPlot" - // label: qsTr("Prior distribution") - - // // 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 - // { - // CheckBox - // { - // label: qsTr("Point estimate") - // name: "priorDistributionPlotIndividualPointEstimate" - // childrenOnSameRow: true - - // DropDown - // { - // name: "priorDistributionPlotIndividualPointEstimateType" - // 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 - // { - // columns: 1 - // CheckBox - // { - // name: "priorDistributionPlotIndividualCi" - // label: qsTr("CI") - // id: priorPlotIndividualCI - // childrenOnSameRow: true - - // DropDown - // { - // name: "priorDistributionPlotIndividualCiType" - // label: "" - // id: priorPlotIndividualType - // 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: priorPlotIndividualCI.checked - - // CIField - // { - // visible: priorPlotIndividualType.currentValue === "central" || priorPlotIndividualType.currentValue === "HPD" - // name: "priorDistributionPlotIndividualCiMass" - // label: qsTr("Mass") - // fieldWidth: 50 - // defaultValue: 95 - // min: 1 - // max: 100 - // inclusive: JASP.MinMax - // } - - // DoubleField - // { - // visible: priorPlotIndividualType.currentValue === "custom" - // name: "priorDistributionPlotIndividualCiLower" - // label: qsTr("Lower") - // id: plotspriorLower - // fieldWidth: 50 - // defaultValue: 0 - // negativeValues: true - // inclusive: JASP.MinMax - // } - - // DoubleField - // { - // visible: priorPlotIndividualType.currentValue === "custom" - // name: "priorDistributionPlotIndividualCiUpper" - // label: qsTr("Upper") - // id: plotspriorUpper - // fieldWidth: 50 - // defaultValue: 1 - // negativeValues: true - // inclusive: JASP.MinMax - // } - - // FormulaField - // { - // visible: priorPlotIndividualType.currentValue === "support" - // name: "priorDistributionPlotIndividualCiBf" - // label: qsTr("BF") - // fieldWidth: 50 - // defaultValue: "1" - // min: 0 - // inclusive: JASP.None - // } - // } - // } - // } - - // RadioButtonGroup - // { - // name: "priorDistributionPlotPanelLayout" - // title: qsTr("Layout") - // id: priorDistributionPlotPanelLayout - - // RadioButton { value: "multiplePanels"; label: qsTr("One plot per metric"); checked: true } - // RadioButton { value: "singlePanel"; label: qsTr("All metrics in one plot") } - - // } - - // RadioButtonGroup - // { - // name: "priorDistributionPlotAxes" - // title: qsTr("Axes") - // id: priorDistributionPlotAxes - - // RadioButton { value: "identical"; label: qsTr("Automatic"); checked: true } - // RadioButton { value: "automatic"; label: qsTr("Identical across panels"); enabled: priorDistributionPlotPanelLayout.value === "multiplePanels" } - // RadioButton { value: "custom"; label: qsTr("Custom axes"); } - // } - - // Group - // { - - // title: qsTr("Custom axes") - // enabled: priorDistributionPlotAxes.value === "custom" - // visible: priorDistributionPlotAxes.value === "custom" - - // GridLayout - // { - // columns: 5 - // columnSpacing: 2 - // rowSpacing: jaspTheme.rowGridSpacing / 3 - // id: priorCustomAxesLayout - // 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: "prior_custom_x_min"; id: prior_custom_x_min; fieldWidth: priorCustomAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: true; max: prior_custom_x_max.value} - // Item{} - // DoubleField { name: "prior_custom_x_max"; id: prior_custom_x_max; fieldWidth: priorCustomAxesLayout.dbWidth; defaultValue: 1.00; negativeValues: true; min: prior_custom_x_min.value} - - // // Row 2: y axis - // Label { text: qsTr("y axis"); } - // Item{} - // DoubleField { name: "prior_custom_y_min"; id: prior_custom_y_min; fieldWidth: priorCustomAxesLayout.dbWidth; defaultValue: 0.00; negativeValues: false; max: prior_custom_y_max.value} - // Item{} - // DoubleField { name: "prior_custom_y_max"; id: prior_custom_y_max; fieldWidth: priorCustomAxesLayout.dbWidth; defaultValue: 1.00; negativeValues: false; min: prior_custom_y_min.value} - // } - // } - // } - // } - } Section @@ -821,93 +459,6 @@ Form hasType: true } - // CheckBox - // { - // name: "sequentialAnalysisPointEstimatePlot" - // label: qsTr("Point estimate plot") - // id: sequentialAnalysisPointEstimatePlot - - // Group - // { - // columns: 2 - // columnSpacing: 10 * jaspTheme.columnGroupSpacing - - // DropDown - // { - // label: qsTr("Type") - // name: "sequentialAnalysisPlotPointEstimateType" - // values: [ - // {label: qsTr("mean"), value: "mean"}, - // {label: qsTr("median"), value: "median"} - // // mode? - // ] - // } - - // CheckBox - // { - // name: "sequentialAnalysisPlotPointEstimateCi" - // label: qsTr("CI") - // childrenOnSameRow: true - - // CIField - // { - // name: "sequentialAnalysisPlotPointEstimateCiMass" - // label: qsTr("Mass") - // fieldWidth: 50 - // defaultValue: 95 - // min: 1 - // max: 100 - // inclusive: JASP.MaxOnly - // } - // } - - // } - // } - - // CheckBox - // { - // name: "sequentialAnalysisIntervalEstimatePlot" - // label: qsTr("Interval estimate plot") - // id: sequentialAnalysisIntervalEstimatePlot - - // DropDown - // { - // label: qsTr("Type") - // name: "sequentialAnalysisPlotPointEstimateType" - // values: [ - // {label: qsTr("mean"), value: "mean"}, - // {label: qsTr("median"), value: "median"} - // // mode? - // ] - // } - - // CheckBox - // { - // name: "sequentialAnalysisPlotCi" - // label: qsTr("CI") - // childrenOnSameRow: true - - // CIField - // { - // name: "sequentialAnalysisPlotCiMass" - // label: qsTr("Mass") - // fieldWidth: 50 - // defaultValue: 95 - // min: 1 - // max: 100 - // inclusive: JASP.MaxOnly - // } - // } - - // CheckBox - // { - // name: "sequentialAnalysisPlotAdditionalInfo" - // label: qsTr("Show process criteria") - // checked: true - // info: qsTr("Add a secondary right axis with condition bounds for the process") - // } - // } - Group { CheckBox @@ -931,9 +482,54 @@ Form Section { - title: qsTr("Prior distribution") + + title: qsTr("Prior and Posterior Predictive Plots") + + Common.PlotLayout + { + baseName: "posteriorPredictiveDistributionPlot" + baseLabel: qsTr("Posterior predictive distribution") + hasPrior: false + } + + Common.PlotLayout + { + baseName: "priorPredictiveDistributionPlot" + baseLabel: qsTr("Prior predictive distribution") + hasPrior: false + } + + } + +Section +{ + title: qsTr("Prior distributions") + + Common.Priors + { + baseName: "populationMeanPrior" + baseLabel: qsTr("Population mean") + fullRealLLine: true } + Common.Priors + { + baseName: "populationSigmaPrior" + baseLabel: qsTr("Population standard deviation") + fullRealLLine: false + } + + Common.Priors + { + baseName: "populationDfPrior" + baseLabel: qsTr("Population degrees of freedom") + fullRealLLine: false + enabled: capabilityStudyType.value === "tCapabilityAnalysis" + hasJeffreys: false + } + +} + Section { title: qsTr("Advanced options") diff --git a/inst/qml/common/Priors.qml b/inst/qml/common/Priors.qml new file mode 100644 index 00000000..e4b06d12 --- /dev/null +++ b/inst/qml/common/Priors.qml @@ -0,0 +1,210 @@ +// +// 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 + + 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 ? -Inf : 0 + max: upperTT.value + min: fullRealLLine ? -Inf : 0 + } + } + + CheckBox + { + name: baseName + "truncationUpperBound" + childrenOnSameRow: true + + FormulaField + { + id: upperTT + name: baseName + "truncationUpperBoundValue" + label: qsTr("Upper bound:") + fieldWidth: 50 + defaultValue: Inf + min: Math.max(fullRealLLine ? -Inf : 0, lowerTT.value) + + } + } + } + +} From 1b6bc89950f56e3172235942422ebfa42119b157 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Wed, 3 Dec 2025 09:21:49 +0100 Subject: [PATCH 10/15] tempcommit --- inst/qml/bayesianProcessCapabilityStudies.qml | 55 +-- inst/qml/common/Priors.qml | 1 + inst/qml/common/PriorsNew.qml | 346 ++++++++++++++++++ 3 files changed, 380 insertions(+), 22 deletions(-) create mode 100644 inst/qml/common/PriorsNew.qml diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 71785982..4daa8b1e 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -501,34 +501,45 @@ Form } -Section -{ - title: qsTr("Prior distributions") - - Common.Priors + Section { - baseName: "populationMeanPrior" - baseLabel: qsTr("Population mean") - fullRealLLine: true - } + title: qsTr("Prior distributions") + + Common.Priors + { + baseName: "populationMeanPrior" + baseLabel: qsTr("Population mean") + fullRealLLine: true + } + + Common.Priors + { + baseName: "populationSigmaPrior" + baseLabel: qsTr("Population standard deviation") + fullRealLLine: false + } + + Common.Priors + { + baseName: "populationDfPrior" + baseLabel: qsTr("Population degrees of freedom") + fullRealLLine: false + enabled: capabilityStudyType.value === "tCapabilityAnalysis" + hasJeffreys: false + } - Common.Priors - { - baseName: "populationSigmaPrior" - baseLabel: qsTr("Population standard deviation") - fullRealLLine: false } - Common.Priors + Section { - baseName: "populationDfPrior" - baseLabel: qsTr("Population degrees of freedom") - fullRealLLine: false - enabled: capabilityStudyType.value === "tCapabilityAnalysis" - hasJeffreys: false - } + title: qsTr("New Prior distributions") -} + Common.PriorsNew + { + priorType: capabilityStudyType.value === "normalCapabilityAnalysis" ? "normalModel" : "tModel" + } + + } Section { diff --git a/inst/qml/common/Priors.qml b/inst/qml/common/Priors.qml index e4b06d12..cff40b20 100644 --- a/inst/qml/common/Priors.qml +++ b/inst/qml/common/Priors.qml @@ -29,6 +29,7 @@ Group property bool fullRealLLine: true property bool hasJeffreys: true +// TODO: should be dropdown, see RBMAP RadioButtonGroup { id: priorChoice diff --git a/inst/qml/common/PriorsNew.qml b/inst/qml/common/PriorsNew.qml new file mode 100644 index 00000000..87557512 --- /dev/null +++ b/inst/qml/common/PriorsNew.qml @@ -0,0 +1,346 @@ +// +// 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" + + + onPriorTypeChanged: + { + console.log("Prior type changed to: " + priorType); + // componentType. + } + + // 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 currentComponentValues: { + 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 } + Label { text: qsTr("Truncation"); Layout.preferredWidth: width4 } + } + + ComponentsList + { + name: priorType + "ComponentsList" + optionKey: "name" + + addItemManually: false + + // headerLabels: [qsTr("Parameter"), qsTr("Distribution"), qsTr("Parameters"), qsTr("Truncation")] + defaultValues: { + currentComponentValues + // switch (priorType) { + // case "normalModel": + // return [ meanValues, sigmaValues ]; + // case "tModel": + // return [ meanValues, sigmaValues, dfValues ]; + // } + // switch (priorType) { + // case "normalModel": + // return [ + // { "name": "mean", "type": "normal", "mu": "0", "sigma": "1" }, + // { "name": "sigma", "type": "invgamma", "alpha": "1", "beta": "0.15", "truncationLower": 0 } + // ]; + // case "tModel": + // return [ + // { "name": "mean", "type": "normal", "mu": "0", "sigma": "1" }, + // { "name": "sigma", "type": "invgamma", "alpha": "1", "beta": "0.15", "truncationLower": 0 }, + // { "name": "t", "type": "invgamma", "alpha": "1", "beta": "0.15", "truncationLower": 0, "hasJeffreys": false } + // ]; + // } + } + + + rowComponent: RowLayout + { + Row + { + spacing: 4 * preferencesModel.uiScale + Layout.preferredWidth: width1 + + Label + { + text: rowValue + "|" + "|" + rowValue.type; + } + } + + Row + { + spacing: 4 * preferencesModel.uiScale + Layout.preferredWidth: width2 + + DropDown + { + id: typeItem + name: "type" + useExternalBorder: true + values: + [ + { 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"}//, + //{ label: qsTr("Spike(x₀)"), value: "spike"}, + // { label: qsTr("None"), value: "none"} + ] + + onValueChanged: { + console.log("Selected prior type: " + typeItem.currentValue); + console.log("rowValue equals: " + rowValue[1]); + } + } + } + + Row + { + spacing: 4 * preferencesModel.uiScale + Layout.preferredWidth: width3 + + FormulaField + { + label: "μ " + rowIndex + 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 + onValueChanged : { + console.log("Selected prior type: " + typeItem.currentValue); + console.log("rowValue equals: " + rowValue[1]); + } + } + 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: typeItem.currentValue !== "spike" && typeItem.currentValue !== "uniform" + value: + { + if(componentType === "priorsHeterogeneity" || componentType === "priorsHeterogeneityNull" || componentType === "priorsBiasPet" || componentType === "priorsBiasPetNull" || componentType === "priorsBiasPeese" || componentType === "priorsBiasPeeseNull") + 0 + else if (typeItem.currentValue === "gammaK0" || typeItem.currentValue === "gammaAB" || typeItem.currentValue === "invgamma" || typeItem.currentValue === "lognormal" || typeItem.currentValue === "beta") + 0 + else + "-Inf" + } + min: + { + if(componentType === "priorsHeterogeneity" || componentType === "priorsHeterogeneityNull" || componentType === "priorsBiasPet" || componentType === "priorsBiasPetNull" || componentType === "priorsBiasPeese" || componentType === "priorsBiasPeeseNull") + 0 + else if (typeItem.currentValue === "gammaK0" || typeItem.currentValue === "gammaAB" || typeItem.currentValue === "invgamma" || typeItem.currentValue === "lognormal" || typeItem.currentValue === "beta") + 0 + else + "-Inf" + } + max: truncationUpper.value + inclusive: JASP.MinOnly + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + FormulaField + { + id: truncationUpper + label: qsTr("upper") + name: "truncationUpper" + visible: typeItem.currentValue !== "spike" && typeItem.currentValue !== "uniform" + value: + { + if (typeItem.currentValue === "beta") + 1 + else + "Inf" + } + max: + { + if (typeItem.currentValue === "beta") + 1 + else + "Inf" + } + min: truncationLower ? truncationLower.value : 0 + inclusive: JASP.MaxOnly + fieldWidth: 40 * preferencesModel.uiScale + useExternalBorder: false + showBorder: true + } + } + } + } +} From 473ce6a3903c894019d5816f5e4a9e6bbe5be12b Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Wed, 3 Dec 2025 16:58:05 +0100 Subject: [PATCH 11/15] gui almost done, starting to make the R part functional again --- R/bayesianProcessCapabilityStudies.R | 40 +++++++---- inst/qml/bayesianProcessCapabilityStudies.qml | 72 ++++++++++++------- inst/qml/common/PlotLayout.qml | 15 ++-- inst/qml/common/Priors.qml | 8 +-- inst/qml/common/PriorsNew.qml | 55 ++++---------- 5 files changed, 101 insertions(+), 89 deletions(-) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index 112e6930..c2df2e64 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -43,11 +43,30 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { "target", "lowerSpecificationLimit", "upperSpecificationLimit", "targetValue", "lowerSpecificationLimitValue", "upperSpecificationLimitValue", # likelihood - "capabilityStudyType" + "capabilityStudyType", # TODO: prior + # MCMC settings + "noIterations", "noWarmup", "noChains" ) } +.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)) } @@ -240,29 +259,26 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { .bpcsSequentialPlot <- function(jaspResults, dataset, options, fit) { - if (!options[["sequentialAnalysisPlot"]] || !is.null(jaspResults[["sequentialAnalysisPlot"]])) + base <- "sequentialAnalysisPointEstimatePlot" + if (!options[[base]] || !is.null(jaspResults[[base]])) return() w <- 400 - plt <- createJaspPlot(title = gettext("Sequential Analysis"), width = 3*w, height = 2*w, + plt <- createJaspPlot(title = gettext("Sequential Analysis Point Estimate"), width = 3*w, height = 2*w, position = 2, dependencies = jaspDeps(c( .bpcsDefaultDeps(), - "sequentialAnalysisPlot", - "sequentialAnalysisPlotPointEstimateType", - "sequentialAnalysisPlotCi", - "sequentialAnalysisPlotCiMass", - "sequentialAnalysisPlotAdditionalInfo" + .bpcsPlotLayoutDeps(base) ))) - jaspResults[["sequentialAnalysisPlot"]] <- plt + jaspResults[[base]] <- plt if (!.bpcsIsReady(options) || jaspResults$getError()) return() tryCatch({ - - sequentialPlotData <- jaspResults[["sequentialAnalysisPlotData"]] %setOrRetrieve% ( + baseData <- paste0(base, "Data") + sequentialPlotData <- jaspResults[[baseData]] %setOrRetrieve% ( .bpcsComputeSequentialAnalysis(dataset, options, fit) |> - createJaspState(dependencies = jaspDeps(options = c(.bpcsDefaultDeps(), "sequentialAnalysisPlot", "sequentialAnalysisPlotCiMass"))) + createJaspState(dependencies = jaspDeps(options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps(base, hasAxes = FALSE)))) ) jaspResults[["sequentialAnalysisPlot"]]$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options) diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index 4daa8b1e..ec90e2fa 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -444,6 +444,7 @@ Form Common.PlotLayout { + id: sequentialAnalysisPointEstimatePlot baseName: "sequentialAnalysisPointEstimatePlot" baseLabel: qsTr("Point estimate plot") hasPrior: false @@ -451,6 +452,7 @@ Form Common.PlotLayout { + id: sequentialAnalysisIntervalEstimatePlot baseName: "sequentialAnalysisPointIntervalPlot" baseLabel: qsTr("Interval estimate plot") hasPrior: false @@ -463,6 +465,8 @@ Form { CheckBox { + enabled: sequentialAnalysisPointEstimatePlot.checked || sequentialAnalysisIntervalEstimatePlot.checked + id: sequentialAnalysisAdditionalInfo name: "sequentialAnalysisPlotAdditionalInfo" label: qsTr("Show process criteria") checked: true @@ -471,6 +475,7 @@ Form CheckBox { + // TODO: enabled: sequentialAnalysisPointEstimatePlot.checked || sequentialAnalysisIntervalEstimatePlot.checked name: "sequentialAnalysisUpdatingTable" label: qsTr("Posterior updating table") @@ -501,48 +506,65 @@ Form } + Section { title: qsTr("Prior distributions") - Common.Priors - { - baseName: "populationMeanPrior" - baseLabel: qsTr("Population mean") - fullRealLLine: true - } - - Common.Priors + DropDown { - baseName: "populationSigmaPrior" - baseLabel: qsTr("Population standard deviation") - fullRealLLine: false + name: "priorSettings" + label: qsTr("Prior distributions") + id: priorSettings + values: + [ + {label: qsTr("Uninformative"), value: "uninformative"}, + {label: qsTr("Weakly informative"), value: "weaklyInformative"}, + {label: qsTr("Custom informative"), value: "customInformative"}, + ] } - Common.Priors + Common.PriorsNew { - baseName: "populationDfPrior" - baseLabel: qsTr("Population degrees of freedom") - fullRealLLine: false - enabled: capabilityStudyType.value === "tCapabilityAnalysis" - hasJeffreys: false + visible: priorSettings.currentValue === "customInformative" + priorType: capabilityStudyType.value === "normalCapabilityAnalysis" ? "normalModel" : "tModel" } } - Section + Section { - title: qsTr("New Prior distributions") + title: qsTr("Advanced options") - Common.PriorsNew + IntegerField { - priorType: capabilityStudyType.value === "normalCapabilityAnalysis" ? "normalModel" : "tModel" + name: "noIterations" + label: qsTr("No. MCMC 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. MCMC warmup samples") + defaultValue: 1000 + min: 0 + max: 100000000 + info: qsTr("Number of initial MCMC samples to discard.") + } + IntegerField + { + name: "noChains" + label: qsTr("No. MCMC chains") + defaultValue: 1 + min: 1 + max: 128 + info: qsTr("Number of MCMC chains to run.") } + IntegerField - } - Section - { - title: qsTr("Advanced options") } } diff --git a/inst/qml/common/PlotLayout.qml b/inst/qml/common/PlotLayout.qml index e023dfc1..4ec876d3 100644 --- a/inst/qml/common/PlotLayout.qml +++ b/inst/qml/common/PlotLayout.qml @@ -17,10 +17,10 @@ // import QtQuick import QtQuick.Layouts +import JASP import JASP.Controls -// Item -// { + Group { id: root @@ -31,9 +31,11 @@ Group property bool hasCi: true property bool hasType: false + readonly property alias checked: mainCheckBox.checked + CheckBox { - + id: mainCheckBox name: baseName label: baseLabel @@ -247,13 +249,12 @@ Group CheckBox { - enabled: hasPrior - visible: hasPrior + enabled: hasPrior + visible: hasPrior name: baseName + "PriorDistribution" label: qsTr("Show prior distribution") checked: false } } } -} -// } \ No newline at end of file +} \ No newline at end of file diff --git a/inst/qml/common/Priors.qml b/inst/qml/common/Priors.qml index cff40b20..49bf75ba 100644 --- a/inst/qml/common/Priors.qml +++ b/inst/qml/common/Priors.qml @@ -184,9 +184,9 @@ Group name: baseName + "truncationLowerBoundValue" label: qsTr("Lower bound:") fieldWidth: 50 - defaultValue: fullRealLLine ? -Inf : 0 + defaultValue: fullRealLLine ? -Infinity : 0 max: upperTT.value - min: fullRealLLine ? -Inf : 0 + min: fullRealLLine ? -Infinity : 0 } } @@ -201,8 +201,8 @@ Group name: baseName + "truncationUpperBoundValue" label: qsTr("Upper bound:") fieldWidth: 50 - defaultValue: Inf - min: Math.max(fullRealLLine ? -Inf : 0, lowerTT.value) + defaultValue: Infinity + min: Math.max(fullRealLLine ? -Infinity : 0, lowerTT.value) } } diff --git a/inst/qml/common/PriorsNew.qml b/inst/qml/common/PriorsNew.qml index 87557512..6ba6040c 100644 --- a/inst/qml/common/PriorsNew.qml +++ b/inst/qml/common/PriorsNew.qml @@ -25,11 +25,14 @@ ColumnLayout spacing: 0 property string priorType: "normalModel" + Component.onCompleted: { + console.log("Component completed, priorType: " + priorType); + console.log("Current component values: " + JSON.stringify(currentComponentValues)); + } - onPriorTypeChanged: - { + onPriorTypeChanged: { + // this is not shown? console.log("Prior type changed to: " + priorType); - // componentType. } // TODO: these should not be fixed, no? @@ -46,6 +49,7 @@ ColumnLayout } } + // TODO: this could also be a gridLayout, no? property double width1: 70 * preferencesModel.uiScale; property double width2: 140 * preferencesModel.uiScale; @@ -67,30 +71,7 @@ ColumnLayout addItemManually: false - // headerLabels: [qsTr("Parameter"), qsTr("Distribution"), qsTr("Parameters"), qsTr("Truncation")] - defaultValues: { - currentComponentValues - // switch (priorType) { - // case "normalModel": - // return [ meanValues, sigmaValues ]; - // case "tModel": - // return [ meanValues, sigmaValues, dfValues ]; - // } - // switch (priorType) { - // case "normalModel": - // return [ - // { "name": "mean", "type": "normal", "mu": "0", "sigma": "1" }, - // { "name": "sigma", "type": "invgamma", "alpha": "1", "beta": "0.15", "truncationLower": 0 } - // ]; - // case "tModel": - // return [ - // { "name": "mean", "type": "normal", "mu": "0", "sigma": "1" }, - // { "name": "sigma", "type": "invgamma", "alpha": "1", "beta": "0.15", "truncationLower": 0 }, - // { "name": "t", "type": "invgamma", "alpha": "1", "beta": "0.15", "truncationLower": 0, "hasJeffreys": false } - // ]; - // } - } - + defaultValues: currentComponentValues rowComponent: RowLayout { @@ -101,7 +82,7 @@ ColumnLayout Label { - text: rowValue + "|" + "|" + rowValue.type; + text: rowValue// + "|" + "|" + rowValue.type; } } @@ -145,7 +126,7 @@ ColumnLayout FormulaField { - label: "μ " + rowIndex + label: "μ" name: "mu" visible: typeItem.currentValue === "normal" || typeItem.currentValue === "lognormal" || @@ -292,21 +273,13 @@ ColumnLayout visible: typeItem.currentValue !== "spike" && typeItem.currentValue !== "uniform" value: { - if(componentType === "priorsHeterogeneity" || componentType === "priorsHeterogeneityNull" || componentType === "priorsBiasPet" || componentType === "priorsBiasPetNull" || componentType === "priorsBiasPeese" || componentType === "priorsBiasPeeseNull") - 0 - else if (typeItem.currentValue === "gammaK0" || typeItem.currentValue === "gammaAB" || typeItem.currentValue === "invgamma" || typeItem.currentValue === "lognormal" || typeItem.currentValue === "beta") - 0 - else - "-Inf" + console.log("rowValue:" + rowValue) + "-Inf" } min: { - if(componentType === "priorsHeterogeneity" || componentType === "priorsHeterogeneityNull" || componentType === "priorsBiasPet" || componentType === "priorsBiasPetNull" || componentType === "priorsBiasPeese" || componentType === "priorsBiasPeeseNull") - 0 - else if (typeItem.currentValue === "gammaK0" || typeItem.currentValue === "gammaAB" || typeItem.currentValue === "invgamma" || typeItem.currentValue === "lognormal" || typeItem.currentValue === "beta") - 0 - else - "-Inf" + console.log("rowValue:" + rowValue) + "-Inf" } max: truncationUpper.value inclusive: JASP.MinOnly From 37fb2b861f857c1fedb4cef8abd68e6225dfe741 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Thu, 4 Dec 2025 12:56:34 +0100 Subject: [PATCH 12/15] GUI is mostly done, priors have reasonable defaults --- NAMESPACE | 1 + R/bayesianProcessCapabilityStudies.R | 262 ++++++++++++++++-- inst/qml/bayesianProcessCapabilityStudies.qml | 53 +++- inst/qml/common/PriorsNew.qml | 165 ++++++----- 4 files changed, 378 insertions(+), 103 deletions(-) diff --git a/NAMESPACE b/NAMESPACE index d55a27cb..f5ed363e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -28,3 +28,4 @@ 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 index c2df2e64..d910e88e 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -16,6 +16,12 @@ # #'@importFrom jaspBase jaspDeps %setOrRetrieve% +#'@importFrom rlang .data + +# Suppress R CMD check notes for ggplot2 aesthetics +if (getRversion() >= "2.15.1") { + utils::globalVariables(c(".data")) +} #'@export bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { @@ -23,13 +29,21 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { fit <- .bpcsCapabilityTable(jaspResults, dataset, options) .bpcsCapabilityPlot(jaspResults, options, fit) .bpcsIntervalTable(jaspResults, options, fit) - .bpcsSequentialPlot(jaspResults, dataset, options, fit) + .bpcsPriorPlot(jaspResults, options, fit) + .bpcsSequentialPointEstimatePlot(jaspResults, dataset, options, fit) + .bpcsSequentialIntervalEstimatePlot(jaspResults, dataset, options, fit) + .bpcsPosteriorPredictivePlot(jaspResults, options, fit) + .bpcsPriorPredictivePlot(jaspResults, options, fit) } .bpcsIsReady <- function(options) { - length(options[["measurementLongFormat"]]) > 0L && - options[["measurementLongFormat"]] != "" && + hasData <- if (options[["dataFormat"]] == "longFormat") { + length(options[["measurementLongFormat"]]) > 0L && options[["measurementLongFormat"]] != "" + } else { + length(options[["measurementsWideFormat"]]) > 0L + } + hasData && options[["lowerSpecificationLimit"]] && options[["upperSpecificationLimit"]] && options[["target"]] @@ -38,13 +52,18 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { .bpcsDefaultDeps <- function() { c( # data - "measurementLongFormat", + "dataFormat", "measurementLongFormat", "measurementsWideFormat", + "subgroupSizeType", "manualSubgroupSizeValue", "subgroup", "groupingVariableMethod", + "stagesLongFormat", "stagesWideFormat", "axisLabels", # specification "target", "lowerSpecificationLimit", "upperSpecificationLimit", "targetValue", "lowerSpecificationLimitValue", "upperSpecificationLimitValue", + # metrics + "Cp", "Cpu", "Cpl", "Cpk", "Cpc", "Cpm", # likelihood "capabilityStudyType", - # TODO: prior + # prior + "priorSettings", "normalModelComponentsList", "tModelComponentsList", # MCMC settings "noIterations", "noWarmup", "noChains" ) @@ -84,16 +103,17 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { # Tables ---- .bpcsCapabilityTable <- function(jaspResults, dataset, options) { - if (!is.null(options[["bpcsCapabilityTable"]])) - return() + # Check if we already have the results cached + if (!is.null(jaspResults[["bpcsResultsObject"]])) + return(jaspResults[["bpcsResultsObject"]]$object) table <- .bpcsCapabilityTableMeta(jaspResults, options) if (!.bpcsIsReady(options)) { - if (options[["measurementLongFormat"]] != "") + 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() + return(NULL) } rawfit <- jaspResults[["bpsState"]] %setOrRetrieve% ( @@ -121,6 +141,9 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { summaryObject = summaryObject ) + # Cache the results + jaspResults[["bpcsResultsObject"]] <- createJaspState(resultsObject) + .bpcsCapabilityTableFill(table, resultsObject, options) return(resultsObject) @@ -148,6 +171,20 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { .bpcsCapabilityTableFill <- function(table, resultsObject, options) { df <- as.data.frame(resultsObject[["summaryObject"]][["summary"]]) + + # Filter metrics based on user selection + selectedMetrics <- c() + if (options[["Cp"]]) selectedMetrics <- c(selectedMetrics, "Cp") + if (options[["Cpu"]]) selectedMetrics <- c(selectedMetrics, "CpU") + if (options[["Cpl"]]) selectedMetrics <- c(selectedMetrics, "CpL") + if (options[["Cpk"]]) selectedMetrics <- c(selectedMetrics, "Cpk") + if (options[["Cpc"]]) selectedMetrics <- c(selectedMetrics, "Cpc") + if (options[["Cpm"]]) selectedMetrics <- c(selectedMetrics, "Cpm") + + if (length(selectedMetrics) > 0) { + df <- df[df$metric %in% selectedMetrics, , drop = FALSE] + } + table$setData(df) } @@ -214,18 +251,32 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { createJaspPlot( title = gettext("Posterior Distribution"), plot = if (.bpcsIsReady(options) && !is.null(fit)) { - qc::plot_density( - fit$summaryObject, - what = c("Cp", "CpU", "CpL", "Cpk", "Cpc", "Cpm"), - point_estimate = with(options, if (posteriorDistributionPlotIndividualPointEstimate) posteriorDistributionPlotIndividualPointEstimateType else "none"), - ci = with(options, if (posteriorDistributionPlotIndividualCi) posteriorDistributionPlotIndividualCiType else "none"), - ci_level = options[["posteriorDistributionPlotIndividualCiMass"]], - ci_custom_left = options[["posteriorDistributionPlotIndividualCiLower"]], - ci_custom_right = options[["posteriorDistributionPlotIndividualCiUpper"]], - bf_support = options[["posteriorDistributionPlotIndividualCiBf"]] - ) + - jaspGraphs::geom_rangeframe() + - jaspGraphs::themeJaspRaw() + + # Get selected metrics + selectedMetrics <- c() + if (options[["Cp"]]) selectedMetrics <- c(selectedMetrics, "Cp") + if (options[["Cpu"]]) selectedMetrics <- c(selectedMetrics, "CpU") + if (options[["Cpl"]]) selectedMetrics <- c(selectedMetrics, "CpL") + if (options[["Cpk"]]) selectedMetrics <- c(selectedMetrics, "Cpk") + if (options[["Cpc"]]) selectedMetrics <- c(selectedMetrics, "Cpc") + if (options[["Cpm"]]) selectedMetrics <- c(selectedMetrics, "Cpm") + + if (length(selectedMetrics) == 0) { + NULL + } else { + qc::plot_density( + fit$summaryObject, + what = selectedMetrics, + point_estimate = with(options, if (posteriorDistributionPlotIndividualPointEstimate) posteriorDistributionPlotIndividualPointEstimateType else "none"), + ci = with(options, if (posteriorDistributionPlotIndividualCi) posteriorDistributionPlotIndividualCiType else "none"), + ci_level = options[["posteriorDistributionPlotIndividualCiMass"]], + ci_custom_left = options[["posteriorDistributionPlotIndividualCiLower"]], + ci_custom_right = options[["posteriorDistributionPlotIndividualCiUpper"]], + bf_support = options[["posteriorDistributionPlotIndividualCiBf"]] + ) + + jaspGraphs::geom_rangeframe() + + jaspGraphs::themeJaspRaw() + } } else { NULL }, @@ -257,7 +308,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { ) } -.bpcsSequentialPlot <- function(jaspResults, dataset, options, fit) { +.bpcsSequentialPointEstimatePlot <- function(jaspResults, dataset, options, fit) { base <- "sequentialAnalysisPointEstimatePlot" if (!options[[base]] || !is.null(jaspResults[[base]])) @@ -268,7 +319,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { position = 2, dependencies = jaspDeps(c( .bpcsDefaultDeps(), - .bpcsPlotLayoutDeps(base) + .bpcsPlotLayoutDeps(base), + "sequentialAnalysisPlotAdditionalInfo" ))) jaspResults[[base]] <- plt @@ -281,7 +333,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { createJaspState(dependencies = jaspDeps(options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps(base, hasAxes = FALSE)))) ) - jaspResults[["sequentialAnalysisPlot"]]$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options) + plt$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options, base) }, error = function(e) { @@ -337,7 +389,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { return(estimates) } -.bpcsMakeSequentialPlot <- function(estimates, options) { +.bpcsMakeSequentialPlot <- function(estimates, options, base) { # 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 @@ -350,7 +402,8 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { nseq <- attr(estimates, "nseq") - pointEstimateName <- if (options[["sequentialAnalysisPlotPointEstimateType"]] == "mean") "mean" else "median" + pointEstimateOption <- paste0(base, "IndividualPointEstimateType") + pointEstimateName <- if (options[[pointEstimateOption]] == "mean") "mean" else "median" tb <- tibble::tibble( metric = factor(rep(rownames(estimates), times = length(nseq))), n = rep(nseq, each = nrow(estimates)), @@ -394,8 +447,9 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { }, simplify = FALSE) ribbon <- NULL - if (options[["sequentialAnalysisPlotCi"]]) - ribbon <- ggplot2::geom_ribbon(ggplot2::aes(ymin = lower, ymax = upper), alpha = 0.3) + ciOption <- paste0(base, "IndividualCi") + if (options[[ciOption]]) + ribbon <- ggplot2::geom_ribbon(ggplot2::aes(ymin = .data$lower, ymax = .data$upper), alpha = 0.3) extraTheme <- gridLinesLayer <- NULL sides <- "bl" @@ -409,14 +463,14 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { # 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 = yintercept), + ggplot2::aes(yintercept = .data$yintercept), # show.legend = FALSE, linewidth = .5, color = "lightgray", linetype = "dashed" ) } - ggplot2::ggplot(tb, ggplot2::aes(x = n, y = mean)) + + ggplot2::ggplot(tb, ggplot2::aes(x = .data$n, y = .data$mean)) + gridLinesLayer + ribbon + ggplot2::geom_line(linewidth = 1) + @@ -431,3 +485,151 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { extraTheme } + +# Additional plot functions ---- +.bpcsPriorPlot <- function(jaspResults, options, fit) { + + if (!options[["priorDistributionPlot"]]) + return() + + jaspResults[["priorDistributionPlot"]] %setOrRetrieve% ( + createJaspPlot( + title = gettext("Prior Distribution"), + plot = if (.bpcsIsReady(options) && !is.null(fit)) { + # TODO: Implement prior distribution plotting + NULL + } else { + NULL + }, + width = 400 * 3, + height = 400 * 2, + position = 4, + dependencies = jaspDeps( + options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps("priorDistributionPlot", hasPrior = FALSE)) + ) + ) + ) +} + +.bpcsSequentialIntervalEstimatePlot <- function(jaspResults, dataset, options, fit) { + + 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 = 3, + dependencies = jaspDeps(c( + .bpcsDefaultDeps(), + .bpcsPlotLayoutDeps(base, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE), + "sequentialAnalysisPlotAdditionalInfo" + ))) + jaspResults[[base]] <- plt + + if (!.bpcsIsReady(options) || jaspResults$getError()) return() + + tryCatch({ + baseData <- paste0(base, "Data") + sequentialPlotData <- jaspResults[[baseData]] %setOrRetrieve% ( + .bpcsComputeSequentialAnalysis(dataset, options, fit) |> + createJaspState(dependencies = jaspDeps(options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps(base, hasAxes = FALSE, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE)))) + ) + + plt$plotObject <- .bpcsMakeSequentialIntervalPlot(sequentialPlotData, options, base) + + }, error = function(e) { + + plt$setError(gettextf("Unexpected error in sequential analysis interval plot: %s", e$message)) + + }) + +} + +.bpcsMakeSequentialIntervalPlot <- function(estimates, options, base) { + + nseq <- attr(estimates, "nseq") + + # Get the type bounds from options + typeLowerOption <- paste0(base, "TypeLower") + typeUpperOption <- paste0(base, "TypeUpper") + typeLower <- options[[typeLowerOption]] + typeUpper <- options[[typeUpperOption]] + + tb <- tibble::tibble( + metric = factor(rep(rownames(estimates), times = length(nseq))), + n = rep(nseq, each = nrow(estimates)), + lower = as.vector(estimates[, "lower", ]), + upper = as.vector(estimates[, "upper", ]), + ) + + # Calculate proportion in interval [typeLower, typeUpper] + # This is a simplified version - may need to access actual posterior samples + tb$proportion <- pmin(pmax((tb$lower + tb$upper) / 2, typeLower), typeUpper) + + y_breaks_per_scale <- tapply(tb, tb$metric, \(x) { + observedRange <- c(0, 1) + leftBreaks <- jaspGraphs::getPrettyAxisBreaks(observedRange) + ggplot2::scale_y_continuous(breaks = leftBreaks, limits = c(0, 1)) + }, simplify = FALSE) + + ggplot2::ggplot(tb, ggplot2::aes(x = .data$n, y = .data$proportion)) + + ggplot2::geom_line(linewidth = 1) + + ggplot2::facet_wrap(~ metric, scales = "free_y") + + ggh4x::facetted_pos_scales(y = y_breaks_per_scale) + + ggplot2::labs( + x = gettext("Number of observations"), + y = gettextf("P(%s < θ < %s)", typeLower, typeUpper) + ) + + jaspGraphs::geom_rangeframe(sides = "bl") + + jaspGraphs::themeJaspRaw() + +} + +.bpcsPosteriorPredictivePlot <- function(jaspResults, options, fit) { + + if (!options[["posteriorPredictiveDistributionPlot"]]) + return() + + jaspResults[["posteriorPredictiveDistributionPlot"]] %setOrRetrieve% ( + createJaspPlot( + title = gettext("Posterior Predictive Distribution"), + plot = if (.bpcsIsReady(options) && !is.null(fit)) { + # TODO: Implement posterior predictive distribution plotting + NULL + } else { + NULL + }, + width = 400 * 3, + height = 400 * 2, + position = 5, + dependencies = jaspDeps( + options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps("posteriorPredictiveDistributionPlot", hasPrior = FALSE)) + ) + ) + ) +} + +.bpcsPriorPredictivePlot <- function(jaspResults, options, fit) { + + if (!options[["priorPredictiveDistributionPlot"]]) + return() + + jaspResults[["priorPredictiveDistributionPlot"]] %setOrRetrieve% ( + createJaspPlot( + title = gettext("Prior Predictive Distribution"), + plot = if (.bpcsIsReady(options) && !is.null(fit)) { + # TODO: Implement prior predictive distribution plotting + NULL + } else { + NULL + }, + width = 400 * 3, + height = 400 * 2, + position = 6, + dependencies = jaspDeps( + options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps("priorPredictiveDistributionPlot", hasPrior = FALSE)) + ) + ) + ) +} diff --git a/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index ec90e2fa..b06d64a1 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -511,25 +511,66 @@ Form { 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") - id: priorSettings values: [ - {label: qsTr("Uninformative"), value: "uninformative"}, - {label: qsTr("Weakly informative"), value: "weaklyInformative"}, + {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" + + // 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 @@ -563,8 +604,6 @@ Form max: 128 info: qsTr("Number of MCMC chains to run.") } - IntegerField - } } diff --git a/inst/qml/common/PriorsNew.qml b/inst/qml/common/PriorsNew.qml index 6ba6040c..9d6105db 100644 --- a/inst/qml/common/PriorsNew.qml +++ b/inst/qml/common/PriorsNew.qml @@ -23,7 +23,9 @@ import JASP ColumnLayout { spacing: 0 - property string priorType: "normalModel" + property string priorType: "normalModel" + property bool hasTruncation: false + property bool hasParameters: true Component.onCompleted: { console.log("Component completed, priorType: " + priorType); @@ -36,17 +38,82 @@ ColumnLayout } // 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 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 [ meanValues, sigmaValues ]; + return [ "mean", "sigma" ]; case "tModel": - return [ meanValues, sigmaValues, dfValues ]; + return [ "mean", "sigma", "df" ]; } + // switch (priorType) { + // case "normalModel": + // return [ meanValues, sigmaValues ]; + // case "tModel": + // return [ meanValues, sigmaValues, dfValues ]; + // } } @@ -60,10 +127,11 @@ ColumnLayout { 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 } - Label { text: qsTr("Truncation"); Layout.preferredWidth: width4 } + Label { text: qsTr("Parameters"); Layout.preferredWidth: width3 ; visible: hasParameters } + Label { text: qsTr("Truncation"); Layout.preferredWidth: width4 ; visible: hasTruncation } } + ComponentsList { name: priorType + "ComponentsList" @@ -71,7 +139,8 @@ ColumnLayout addItemManually: false - defaultValues: currentComponentValues + // defaultValues: currentComponentValues + values: currentComponentValues rowComponent: RowLayout { @@ -79,11 +148,7 @@ ColumnLayout { spacing: 4 * preferencesModel.uiScale Layout.preferredWidth: width1 - - Label - { - text: rowValue// + "|" + "|" + rowValue.type; - } + Label { text: nameMap[rowValue] } } Row @@ -93,29 +158,18 @@ ColumnLayout DropDown { - id: typeItem - name: "type" - useExternalBorder: true - values: - [ - { 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"}//, - //{ label: qsTr("Spike(x₀)"), value: "spike"}, - // { label: qsTr("None"), value: "none"} - ] + visible: activeDropDownValuesMap[rowValue].length > 1 + id: typeItem + name: "type" + useExternalBorder: true + value: defaultDistributionMap[rowValue] + values: activeDropDownValuesMap[rowValue] + } - onValueChanged: { - console.log("Selected prior type: " + typeItem.currentValue); - console.log("rowValue equals: " + rowValue[1]); - } + Label + { + visible: activeDropDownValuesMap[rowValue].length === 1 + text: activeDropDownValuesMap[rowValue][0].label } } @@ -123,6 +177,7 @@ ColumnLayout { spacing: 4 * preferencesModel.uiScale Layout.preferredWidth: width3 + visible: hasParameters FormulaField { @@ -136,10 +191,7 @@ ColumnLayout fieldWidth: 40 * preferencesModel.uiScale useExternalBorder: false showBorder: true - onValueChanged : { - console.log("Selected prior type: " + typeItem.currentValue); - console.log("rowValue equals: " + rowValue[1]); - } + } FormulaField { @@ -270,17 +322,9 @@ ColumnLayout id: truncationLower label: qsTr("lower") name: "truncationLower" - visible: typeItem.currentValue !== "spike" && typeItem.currentValue !== "uniform" - value: - { - console.log("rowValue:" + rowValue) - "-Inf" - } - min: - { - console.log("rowValue:" + rowValue) - "-Inf" - } + 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 @@ -292,21 +336,9 @@ ColumnLayout id: truncationUpper label: qsTr("upper") name: "truncationUpper" - visible: typeItem.currentValue !== "spike" && typeItem.currentValue !== "uniform" - value: - { - if (typeItem.currentValue === "beta") - 1 - else - "Inf" - } - max: - { - if (typeItem.currentValue === "beta") - 1 - else - "Inf" - } + 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 @@ -316,4 +348,5 @@ ColumnLayout } } } + } From cf7f0fa264320c8ac887fc3b8915f0da639ced62 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Mon, 5 Jan 2026 10:22:12 +0100 Subject: [PATCH 13/15] analysis ready for review --- R/bayesianProcessCapabilityStudies.R | 820 +++++++++++++----- inst/qml/bayesianProcessCapabilityStudies.qml | 229 +---- inst/qml/common/PlotLayout.qml | 14 +- 3 files changed, 664 insertions(+), 399 deletions(-) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index d910e88e..7e6be85a 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -18,48 +18,49 @@ #'@importFrom jaspBase jaspDeps %setOrRetrieve% #'@importFrom rlang .data -# Suppress R CMD check notes for ggplot2 aesthetics -if (getRversion() >= "2.15.1") { - utils::globalVariables(c(".data")) -} #'@export bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { fit <- .bpcsCapabilityTable(jaspResults, dataset, options) - .bpcsCapabilityPlot(jaspResults, options, fit) + priorFit <- .bpcsSamplePosteriorOrPrior(jaspResults, dataset, options, prior = TRUE) + + .bpcsCapabilityPlot(jaspResults, options, fit, priorFit) + .bpcsCapabilityPlot(jaspResults, options, fit, priorFit, base = "priorDistributionPlot") + .bpcsIntervalTable(jaspResults, options, fit) - .bpcsPriorPlot(jaspResults, options, fit) - .bpcsSequentialPointEstimatePlot(jaspResults, dataset, options, fit) + + .bpcsSequentialPointEstimatePlot( jaspResults, dataset, options, fit) .bpcsSequentialIntervalEstimatePlot(jaspResults, dataset, options, fit) - .bpcsPosteriorPredictivePlot(jaspResults, options, fit) - .bpcsPriorPredictivePlot(jaspResults, options, fit) + + .bpcsPlotPredictive(jaspResults, options, fit, "posteriorPredictiveDistributionPlot") + .bpcsPlotPredictive(jaspResults, options, priorFit, "priorPredictiveDistributionPlot") } .bpcsIsReady <- function(options) { - hasData <- if (options[["dataFormat"]] == "longFormat") { - length(options[["measurementLongFormat"]]) > 0L && options[["measurementLongFormat"]] != "" - } else { - length(options[["measurementsWideFormat"]]) > 0L - } + # 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"]] } -.bpcsDefaultDeps <- function() { - c( +.bpcsStateDeps <- function() { + c( # data - "dataFormat", "measurementLongFormat", "measurementsWideFormat", - "subgroupSizeType", "manualSubgroupSizeValue", "subgroup", "groupingVariableMethod", - "stagesLongFormat", "stagesWideFormat", "axisLabels", + # "dataFormat", "measurementLongFormat", "measurementsWideFormat", + # "subgroupSizeType", "manualSubgroupSizeValue", "subgroup", "groupingVariableMethod", + # "stagesLongFormat", "stagesWideFormat", + "measurementLongFormat", # specification "target", "lowerSpecificationLimit", "upperSpecificationLimit", "targetValue", "lowerSpecificationLimitValue", "upperSpecificationLimitValue", - # metrics - "Cp", "Cpu", "Cpl", "Cpk", "Cpc", "Cpm", # likelihood "capabilityStudyType", # prior @@ -69,6 +70,15 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { ) } +.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, @@ -90,11 +100,78 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { c(paste0("interval", 1:4), paste0("intervalLabel", 1:5)) } -.bpcsTpriorFromOptions <- function(options) { +.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" = BayesTools::prior("exp", list(1)), # TODO: should be more generic + "tCapabilityAnalysis" = .bpcsPriorFromComponent(.bpcsPriorComponentByName(options, "df"), "df"), stop("Unknown capability study type: ", options[["capabilityStudyType"]]) ) @@ -104,35 +181,56 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { .bpcsCapabilityTable <- function(jaspResults, dataset, options) { # Check if we already have the results cached - if (!is.null(jaspResults[["bpcsResultsObject"]])) - return(jaspResults[["bpcsResultsObject"]]$object) + if (!is.null(jaspResults[["bpcsCapabilityTable"]])) + return(.bpcsSamplePosteriorOrPrior(jaspResults, dataset, options)) # will return object from state (if it exists) table <- .bpcsCapabilityTableMeta(jaspResults, options) 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.")) + table$addFootnote(gettext( + "Please specify the Lower Specification Limit, Upper Specification Limit, and Target Value to compute the capability measures." + )) return(NULL) } - rawfit <- jaspResults[["bpsState"]] %setOrRetrieve% ( + 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_nu = .bpcsTpriorFromOptions(options) - ) |> - createJaspState(jaspDeps(.bpcsDefaultDeps())) + 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[["bpsSummaryState"]] %setOrRetrieve% ( + summaryObject <- jaspResults[[paste0(base, "SummaryState")]] %setOrRetrieve% ( summary( rawfit, ci.level = options[["credibleIntervalWidth"]] ) |> createJaspState(jaspDeps( - options = c(.bpcsDefaultDeps(), "credibleIntervalWidth") + options = c(.bpcsStateDeps(), "credibleIntervalWidth") )) ) @@ -141,12 +239,13 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { summaryObject = summaryObject ) - # Cache the results - jaspResults[["bpcsResultsObject"]] <- createJaspState(resultsObject) + jaspResults[[paste0(base, "ResultsObject")]] <- createJaspState(resultsObject) - .bpcsCapabilityTableFill(table, resultsObject, options) return(resultsObject) +} +.bpcsCanSampleFromPriors <- function(options) { + options$priorSettings != "default" } .bpcsCapabilityTableMeta <- function(jaspResults, options) { @@ -161,25 +260,34 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { table$addColumnInfo(name = "lower", title = gettext("Lower"), type = "number", overtitle = overtitle) table$addColumnInfo(name = "upper", title = gettext("Upper"), type = "number", overtitle = overtitle) - table$dependOn(.bpcsDefaultDeps()) + 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 <- c() - if (options[["Cp"]]) selectedMetrics <- c(selectedMetrics, "Cp") - if (options[["Cpu"]]) selectedMetrics <- c(selectedMetrics, "CpU") - if (options[["Cpl"]]) selectedMetrics <- c(selectedMetrics, "CpL") - if (options[["Cpk"]]) selectedMetrics <- c(selectedMetrics, "Cpk") - if (options[["Cpc"]]) selectedMetrics <- c(selectedMetrics, "Cpc") - if (options[["Cpm"]]) selectedMetrics <- c(selectedMetrics, "Cpm") + selectedMetrics <- .bpcsGetSelectedMetrics(options) if (length(selectedMetrics) > 0) { df <- df[df$metric %in% selectedMetrics, , drop = FALSE] @@ -242,75 +350,104 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { # Plots ---- -.bpcsCapabilityPlot <- function(jaspResults, options, fit) { +.bpcsCapabilityPlot <- function(jaspResults, options, fit, priorFit, base = "posteriorDistributionPlot") { - if (!options[["posteriorDistributionPlot"]]) + if (!options[[base]] || !is.null(jaspResults[[base]])) return() - jaspResults[["posteriorDistributionPlot"]] %setOrRetrieve% ( - createJaspPlot( - title = gettext("Posterior Distribution"), - plot = if (.bpcsIsReady(options) && !is.null(fit)) { - - # Get selected metrics - selectedMetrics <- c() - if (options[["Cp"]]) selectedMetrics <- c(selectedMetrics, "Cp") - if (options[["Cpu"]]) selectedMetrics <- c(selectedMetrics, "CpU") - if (options[["Cpl"]]) selectedMetrics <- c(selectedMetrics, "CpL") - if (options[["Cpk"]]) selectedMetrics <- c(selectedMetrics, "Cpk") - if (options[["Cpc"]]) selectedMetrics <- c(selectedMetrics, "Cpc") - if (options[["Cpm"]]) selectedMetrics <- c(selectedMetrics, "Cpm") - - if (length(selectedMetrics) == 0) { - NULL - } else { - qc::plot_density( - fit$summaryObject, - what = selectedMetrics, - point_estimate = with(options, if (posteriorDistributionPlotIndividualPointEstimate) posteriorDistributionPlotIndividualPointEstimateType else "none"), - ci = with(options, if (posteriorDistributionPlotIndividualCi) posteriorDistributionPlotIndividualCiType else "none"), - ci_level = options[["posteriorDistributionPlotIndividualCiMass"]], - ci_custom_left = options[["posteriorDistributionPlotIndividualCiLower"]], - ci_custom_right = options[["posteriorDistributionPlotIndividualCiUpper"]], - bf_support = options[["posteriorDistributionPlotIndividualCiBf"]] - ) + - jaspGraphs::geom_rangeframe() + - jaspGraphs::themeJaspRaw() - } - } else { - NULL - }, - width = 400 * 3, - height = 400 * 2, - position = 1, - dependencies = jaspDeps( - options = c(.bpcsDefaultDeps(), .bpcsPosteriorPlotDeps(options)) + 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 = 1, + dependencies = jaspDeps( + options = c( + .bpcsDefaultDeps(), + # .bpcsPosteriorPlotDeps(options), + .bpcsPlotLayoutDeps(base, hasType = FALSE) ) ) ) -} + jaspResults[[base]] <- jaspPlt -.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" + 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) { base <- "sequentialAnalysisPointEstimatePlot" + # "sequentialAnalysisPointIntervalPlot" if (!options[[base]] || !is.null(jaspResults[[base]])) return() @@ -319,25 +456,81 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { position = 2, dependencies = jaspDeps(c( .bpcsDefaultDeps(), - .bpcsPlotLayoutDeps(base), + .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) { + + # 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 = 2, + 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({ - baseData <- paste0(base, "Data") sequentialPlotData <- jaspResults[[baseData]] %setOrRetrieve% ( .bpcsComputeSequentialAnalysis(dataset, options, fit) |> - createJaspState(dependencies = jaspDeps(options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps(base, hasAxes = FALSE)))) + createJaspState(dependencies = jaspDeps( + options = c(.bpcsStateDeps(), + paste0(base2, c("TypeLower", "TypeUpper"))) + )) ) - plt$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options, base) + return(list(data = sequentialPlotData, error = NULL)) }, error = function(e) { - plt$setError(gettextf("Unexpected error in sequential analysis plot: %s", e$message)) + return(list(data = NULL, error = e$message)) }) @@ -350,27 +543,40 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { nto <- n nby <- 1L nseq <- seq(nfrom, nto, by = nby) - estimates <- array(NA, c(6, 4, length(nseq))) + estimates <- array(NA, c(6, 5, length(nseq))) + + hasCustom <- options$sequentialAnalysisPointIntervalPlot + customBounds <- c(options$sequentialAnalysisPointIntervalPlotTypeLower, + options$sequentialAnalysisPointIntervalPlotTypeUpper) - keys <- c("mean", "median", "lower", "upper") + 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_nu = jaspQualityControl:::.bpcsTpriorFromOptions(options) + target = options[["targetValue"]], + LSL = options[["lowerSpecificationLimitValue"]], + USL = options[["upperSpecificationLimitValue"]], + prior_mu = priorMu, + prior_sigma = priorSigma, + prior_nu = priorNu ) - sum_i <- summary(fit_i)$summary + 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 @@ -389,7 +595,40 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { return(estimates) } -.bpcsMakeSequentialPlot <- function(estimates, options, base) { +.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 @@ -402,8 +641,6 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { nseq <- attr(estimates, "nseq") - pointEstimateOption <- paste0(base, "IndividualPointEstimateType") - pointEstimateName <- if (options[[pointEstimateOption]] == "mean") "mean" else "median" tb <- tibble::tibble( metric = factor(rep(rownames(estimates), times = length(nseq))), n = rep(nseq, each = nrow(estimates)), @@ -411,10 +648,14 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { 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 - y_breaks_per_scale <- tapply(tb, tb$metric, \(x) { - observedRange <- range(x$lower, x$upper, na.rm = TRUE) + 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) @@ -424,7 +665,7 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { leftLimits <- range(leftBreaks) rightAxis <- ggplot2::waiver() - if (options[["sequentialAnalysisPlotAdditionalInfo"]]) { + if (add_additional_info) { rightBreaksShown <- c( (leftLimits[1L] + gridLines[1L]) / 2, defaultCategoryPositions, @@ -441,24 +682,64 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { rightAxis <- ggplot2::sec_axis(identity, breaks = rightBreaks, labels = rightLabels) } - ggplot2::scale_y_continuous(breaks = leftBreaks, limits = range(leftBreaks), + y_breaks_per_scale <- ggplot2::scale_y_continuous(breaks = leftBreaks, limits = range(leftBreaks), minor_breaks = gridLines, sec.axis = rightAxis) - }, simplify = FALSE) + + } 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 - ciOption <- paste0(base, "IndividualCi") - if (options[[ciOption]]) + if (has_ci) ribbon <- ggplot2::geom_ribbon(ggplot2::aes(ymin = .data$lower, ymax = .data$upper), alpha = 0.3) extraTheme <- gridLinesLayer <- NULL sides <- "bl" - if (options[["sequentialAnalysisPlotAdditionalInfo"]]) { + 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)) + 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( @@ -470,80 +751,152 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { } - ggplot2::ggplot(tb, ggplot2::aes(x = .data$n, y = .data$mean)) + + 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) + - ggplot2::facet_wrap(~ metric, scales = "free_y") + - ggh4x::facetted_pos_scales(y = y_breaks_per_scale) + + facet + scale_facet + scale_x + ggplot2::labs( - x = gettext("Number of observations"), - y = gettext("Estimate with 95% credible interval") + x = gettext("Number of observations"), + y = y_title, + color = gettext("Metric"), + fill = gettext("Metric") ) + jaspGraphs::geom_rangeframe(sides = sides) + - jaspGraphs::themeJaspRaw() + + jaspGraphs::themeJaspRaw(legend.position = if (single_panel) "right" else "none") + extraTheme } # Additional plot functions ---- -.bpcsPriorPlot <- function(jaspResults, options, fit) { +.bpcsSequentialAnalysis <- function(jaspResults, dataset, options, fit) { - if (!options[["priorDistributionPlot"]]) - return() + base1 <- "sequentialAnalysisPointEstimatePlot" + base2 <- "sequentialAnalysisPointIntervalPlot" - jaspResults[["priorDistributionPlot"]] %setOrRetrieve% ( - createJaspPlot( - title = gettext("Prior Distribution"), - plot = if (.bpcsIsReady(options) && !is.null(fit)) { - # TODO: Implement prior distribution plotting - NULL - } else { - NULL - }, - width = 400 * 3, - height = 400 * 2, - position = 4, - dependencies = jaspDeps( - options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps("priorDistributionPlot", hasPrior = FALSE)) - ) - ) - ) -} - -.bpcsSequentialIntervalEstimatePlot <- function(jaspResults, dataset, options, fit) { - - base <- "sequentialAnalysisPointIntervalPlot" - if (!options[[base]] || !is.null(jaspResults[[base]])) + needsPointPlot <- options[[base1]] && is.null(jaspResults[[base1]]) + needsIntervalPlot <- options[[base2]] && is.null(jaspResults[[base2]]) + if (!needsPointPlot && !needsIntervalPlot) return() + needsPlt <- setNames(c(needsPointPlot, needsIntervalPlot), c(base1, base2)) w <- 400 - plt <- createJaspPlot(title = gettext("Sequential Analysis Interval Estimate"), width = 3*w, height = 2*w, - position = 3, - dependencies = jaspDeps(c( - .bpcsDefaultDeps(), - .bpcsPlotLayoutDeps(base, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE), - "sequentialAnalysisPlotAdditionalInfo" - ))) - jaspResults[[base]] <- plt + i <- 0 + for (base in c(base1, base2)) { + singlePanel <- options[[paste0(base, "PanelLayout")]] != "multiplePanels" + width <- w * (if (singlePanel) 1 else 3) + height <- w * (if (singlePanel) 1 else 3) + plt <- createJaspPlot( + title = gettext("Sequential Analysis Point Estimate"), width = width, height = height, + position = 3 + i, + dependencies = jaspDeps(c( + .bpcsDefaultDeps(), + .bpcsPlotLayoutDeps(base, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE), + "sequentialAnalysisPlotAdditionalInfo" + ))) + jaspResults[[base]] <- plt + i <- i + 1 + } if (!.bpcsIsReady(options) || jaspResults$getError()) return() + hasError <- FALSE + errorMessage <- FALSE tryCatch({ - baseData <- paste0(base, "Data") + baseData <- "sequentialAnalysisData" sequentialPlotData <- jaspResults[[baseData]] %setOrRetrieve% ( .bpcsComputeSequentialAnalysis(dataset, options, fit) |> - createJaspState(dependencies = jaspDeps(options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps(base, hasAxes = FALSE, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE)))) + createJaspState(dependencies = jaspDeps(options = c( + .bpcsDefaultDeps(), + if (needsPointPlot) .bpcsPlotLayoutDeps(base1, hasAxes = FALSE, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE), + if (needsIntervalPlot) .bpcsPlotLayoutDeps(base2, hasAxes = FALSE, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE) + ))) ) - plt$plotObject <- .bpcsMakeSequentialIntervalPlot(sequentialPlotData, options, base) - }, error = function(e) { - plt$setError(gettextf("Unexpected error in sequential analysis interval plot: %s", e$message)) + errorMessage <- gettextf("Unexpected error in sequential analysis interval plot: %s", e$message) }) + if (needsPointPlot) { + plt <- jaspResults[[base1]]$plotObject + if (hasError) { + plt$setError(errorMessage) + } else { + tryCatch({ + plt$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options, base1) + }, error = function(e) { + plt$setError(gettextf("Unexpected error in sequential analysis point estimate plot: %s", e$message)) + } + ) + } + } + + if (needsIntervalPlot) { + plt <- jaspResults[[base2]]$plotObject + if (hasError) { + plt$setError(errorMessage) + } else { + tryCatch({ + plt$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options, base2, custom = TRUE) + }, error = function(e) { + jaspResults[[base1]]$setError(gettextf("Unexpected error in sequential analysis interval estimate plot: %s", e$message)) + } + ) + } + } + } .bpcsMakeSequentialIntervalPlot <- function(estimates, options, base) { @@ -586,50 +939,103 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { } -.bpcsPosteriorPredictivePlot <- function(jaspResults, options, fit) { +.bpcsPlotPredictive <- function(jaspResults, options, fit, base = c("posteriorPredictiveDistributionPlot", "priorPredictiveDistributionPlot")) { + + base <- match.arg(base) + isPrior <- base == "priorPredictiveDistributionPlot" - if (!options[["posteriorPredictiveDistributionPlot"]]) + if (!options[[base]] || !is.null(jaspResults[[base]])) return() - jaspResults[["posteriorPredictiveDistributionPlot"]] %setOrRetrieve% ( - createJaspPlot( - title = gettext("Posterior Predictive Distribution"), - plot = if (.bpcsIsReady(options) && !is.null(fit)) { - # TODO: Implement posterior predictive distribution plotting - NULL - } else { - NULL - }, - width = 400 * 3, - height = 400 * 2, - position = 5, - dependencies = jaspDeps( - options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps("posteriorPredictiveDistributionPlot", hasPrior = FALSE)) - ) + plot <- createJaspPlot( + title = if (isPrior) gettext("Prior predictive distribution") else gettext("Posterior Predictive Distribution"), + width = 400, + height = 400, + 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 ) - ) -} -.bpcsPriorPredictivePlot <- function(jaspResults, options, fit) { + # 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 + ) + } - if (!options[["priorPredictiveDistributionPlot"]]) - return() + # Add CI if requested + if (options[[paste0(base, "IndividualCi")]]) { + ciType <- options[[paste0(base, "IndividualCiType")]] - jaspResults[["priorPredictiveDistributionPlot"]] %setOrRetrieve% ( - createJaspPlot( - title = gettext("Prior Predictive Distribution"), - plot = if (.bpcsIsReady(options) && !is.null(fit)) { - # TODO: Implement prior predictive distribution plotting - NULL + ciInterval <- if (ciType == "custom") { + c(options[[paste0(base, "IndividualCiLower")]], + options[[paste0(base, "IndividualCiUpper")]]) } else { - NULL - }, - width = 400 * 3, - height = 400 * 2, - position = 6, - dependencies = jaspDeps( - options = c(.bpcsDefaultDeps(), .bpcsPlotLayoutDeps("priorPredictiveDistributionPlot", hasPrior = FALSE)) + 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/inst/qml/bayesianProcessCapabilityStudies.qml b/inst/qml/bayesianProcessCapabilityStudies.qml index b06d64a1..9b0de256 100644 --- a/inst/qml/bayesianProcessCapabilityStudies.qml +++ b/inst/qml/bayesianProcessCapabilityStudies.qml @@ -58,28 +58,9 @@ Form } columns: 2 - DropDown - { - name: "dataFormat" - label: qsTr("Data format") - id: dataFormat - indexDefaultValue: 0 - values: - [ - {label: qsTr("Single column"), value: "longFormat"}, - {label: qsTr("Across rows"), value: "wideFormat"}, - ] - // onValueChanged: - // { - // measurementLongFormat.itemDoubleClicked(0) - // measurementsWideFormat.itemDoubleClicked(0) - // } - } - VariablesForm { - id: variablesFormLongFormat - visible: dataFormat.currentValue === "longFormat" + id: variablesFormLongFormat AvailableVariablesList { @@ -95,148 +76,12 @@ Form singleVariable: true } - AssignedVariablesList - { - name: "subgroup" - title: qsTr("Subgroup") - id: subgroup - singleVariable: true - allowedColumns: ["nominal"] - enabled: subgroupSizeType.value === "groupingVariable" - } - - AssignedVariablesList - { - id: stagesLongFormat - name: "stagesLongFormat" - title: qsTr("Stages") - singleVariable: true - allowedColumns: ["nominal"] - } } - VariablesForm - { - id: variablesFormWideFormat - visible: dataFormat.currentValue === "wideFormat" - - AvailableVariablesList - { - name: "variablesFormWideFormat" - } - - AssignedVariablesList - { - name: "measurementsWideFormat" - title: qsTr("Measurements") - id: measurementsWideFormat - allowedColumns: ["scale"] - } - - AssignedVariablesList - { - id: axisLabels - name: "axisLabels" - title: qsTr("Timestamp (optional)") - singleVariable: true - allowedColumns: ["nominal"] - } - - AssignedVariablesList - { - id: stagesWideFormat - name: "stagesWideFormat" - title: qsTr("Stages") - singleVariable: true - allowedColumns: ["nominal"] - } - } - - Group - { - columns: 2 - - RadioButtonGroup - { - name: "subgroupSizeType" - title: qsTr("Specify subgroups") - id: subgroupSizeType - visible: dataFormat.currentValue === "longFormat" - - RadioButton - { - value: "manual" - label: qsTr("Subgroup size") - checked: true - childrenOnSameRow: true - - DoubleField - { - name: "manualSubgroupSizeValue" - id: manualSubgroupSizeValue - min: 1 - max: dataSetModel.rowCount() - negativeValues: false - defaultValue: 5 - - } - } - - RadioButton - { - value: "groupingVariable" - label: qsTr("Through grouping variable") - - DropDown - { - name: "groupingVariableMethod" - id: groupingVariable - label: "Grouping method" - values: - [ - { label: qsTr("Subgroup value change"), value: "newLabel"}, - { label: qsTr("Same subgroup value"), value: "sameLabel"} - ] - indexDefaultValue: 0 - } - } - } - - /* - RadioButtonGroup - { - name: "subgroupSizeUnequal" - title: qsTr("Unequal subgroup sizes") - id: subgroupSizeUnequal - RadioButton - { - value: "actualSizes" - label: qsTr("Use actual sizes") - checked: true - } - - RadioButton - { - value: "fixedSubgroupSize" - label: qsTr("Use fixed subgroup size") - childrenOnSameRow: true - - IntegerField - { - name: "fixedSubgroupSizeValue" - fieldWidth: 30 - defaultValue: 5 - min: 2 - } - } - } - */ - } - - Section - { - title: qsTr("Process capability options") + // Section + // { + // title: qsTr("Process capability options") Group { @@ -269,6 +114,7 @@ Form Group { + columns: 2 title: qsTr("Metrics") info: qsTr("Select the process capability metrics to report.") CheckBox { name: "Cp"; label: qsTr("Cp"); checked: true } @@ -281,7 +127,7 @@ Form Group { - title: qsTr("Capability study") + title: qsTr("Capability Study") CheckBox { @@ -396,8 +242,7 @@ Form } } - - } + // } // Section // { @@ -495,6 +340,8 @@ Form baseName: "posteriorPredictiveDistributionPlot" baseLabel: qsTr("Posterior predictive distribution") hasPrior: false + hasAxes: false + hasPanels: false } Common.PlotLayout @@ -502,6 +349,8 @@ Form baseName: "priorPredictiveDistributionPlot" baseLabel: qsTr("Prior predictive distribution") hasPrior: false + hasAxes: false + hasPanels: false } } @@ -577,33 +426,37 @@ Form { title: qsTr("Advanced options") - IntegerField - { - name: "noIterations" - label: qsTr("No. MCMC 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. MCMC warmup samples") - defaultValue: 1000 - min: 0 - max: 100000000 - info: qsTr("Number of initial MCMC samples to discard.") - } - IntegerField + Group { - name: "noChains" - label: qsTr("No. MCMC chains") - defaultValue: 1 - min: 1 - max: 128 - info: qsTr("Number of MCMC chains to run.") + 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 index 4ec876d3..abc3957c 100644 --- a/inst/qml/common/PlotLayout.qml +++ b/inst/qml/common/PlotLayout.qml @@ -30,6 +30,8 @@ Group 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 @@ -188,6 +190,8 @@ Group RadioButtonGroup { + enabled: hasPanels + visible: hasPanels name: baseName + "PanelLayout" title: qsTr("Layout") id: posteriorDistributionPlotPanelLayout @@ -199,12 +203,14 @@ Group RadioButtonGroup { + enabled: hasAxes + visible: hasAxes name: baseName + "Axes" title: qsTr("Axes") id: posteriorDistributionPlotAxes - RadioButton { value: "identical"; label: qsTr("Automatic"); checked: true } - RadioButton { value: "automatic"; label: qsTr("Identical across panels"); enabled: posteriorDistributionPlotPanelLayout.value === "multiplePanels" } + 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"); } } @@ -212,8 +218,8 @@ Group { title: qsTr("Custom axes") - enabled: posteriorDistributionPlotAxes.value === "custom" - visible: posteriorDistributionPlotAxes.value === "custom" + enabled: hasAxes && posteriorDistributionPlotAxes.value === "custom" + visible: hasAxes && posteriorDistributionPlotAxes.value === "custom" GridLayout { From 075de88f763082447b16be33b8d6cfaeb02d815e Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Mon, 5 Jan 2026 11:06:41 +0100 Subject: [PATCH 14/15] adjust positions --- R/bayesianProcessCapabilityStudies.R | 167 ++++----------------------- 1 file changed, 24 insertions(+), 143 deletions(-) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index 7e6be85a..19cc9adb 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -22,19 +22,19 @@ #'@export bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { - fit <- .bpcsCapabilityTable(jaspResults, dataset, options) + fit <- .bpcsCapabilityTable(jaspResults, dataset, options, position = 1) priorFit <- .bpcsSamplePosteriorOrPrior(jaspResults, dataset, options, prior = TRUE) - .bpcsCapabilityPlot(jaspResults, options, fit, priorFit) - .bpcsCapabilityPlot(jaspResults, options, fit, priorFit, base = "priorDistributionPlot") + .bpcsCapabilityPlot(jaspResults, options, fit, priorFit, position = 2) + .bpcsCapabilityPlot(jaspResults, options, fit, priorFit, position = 3, base = "priorDistributionPlot") - .bpcsIntervalTable(jaspResults, options, fit) + .bpcsIntervalTable(jaspResults, options, fit, position = 4) - .bpcsSequentialPointEstimatePlot( jaspResults, dataset, options, fit) - .bpcsSequentialIntervalEstimatePlot(jaspResults, dataset, options, fit) + .bpcsSequentialPointEstimatePlot( jaspResults, dataset, options, fit, position = 5) + .bpcsSequentialIntervalEstimatePlot(jaspResults, dataset, options, fit, position = 6) - .bpcsPlotPredictive(jaspResults, options, fit, "posteriorPredictiveDistributionPlot") - .bpcsPlotPredictive(jaspResults, options, priorFit, "priorPredictiveDistributionPlot") + .bpcsPlotPredictive(jaspResults, options, fit, position = 7, base = "posteriorPredictiveDistributionPlot") + .bpcsPlotPredictive(jaspResults, options, priorFit, position = 8, base = "priorPredictiveDistributionPlot") } @@ -178,13 +178,13 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { } # Tables ---- -.bpcsCapabilityTable <- function(jaspResults, dataset, options) { +.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) + table <- .bpcsCapabilityTableMeta(jaspResults, options, position = position) if (!.bpcsIsReady(options)) { if (options[["measurementLongFormat"]] != "" || length(options[["measurementsWideFormat"]]) > 0) @@ -248,9 +248,9 @@ bayesianProcessCapabilityStudies <- function(jaspResults, dataset, options) { options$priorSettings != "default" } -.bpcsCapabilityTableMeta <- function(jaspResults, options) { +.bpcsCapabilityTableMeta <- function(jaspResults, options, position) { - table <- createJaspTable(title = gettext("Capability Table"), position = 0) + 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") @@ -297,12 +297,12 @@ getCustomAxisLimits <- function(options, base) { } -.bpcsIntervalTable <- function(jaspResults, options, fit) { +.bpcsIntervalTable <- function(jaspResults, options, fit, position) { if (!options[["intervalTable"]]) return() - table <- .bpcsIntervalTableMeta(jaspResults, options) + table <- .bpcsIntervalTableMeta(jaspResults, options, position) if (!.bpcsIsReady(options) || is.null(fit)) return() @@ -323,9 +323,9 @@ getCustomAxisLimits <- function(options, base) { return() } -.bpcsIntervalTableMeta <- function(jaspResults, options) { +.bpcsIntervalTableMeta <- function(jaspResults, options, position) { - table <- createJaspTable(title = gettext("Interval Table"), position = 3) + table <- createJaspTable(title = gettext("Interval Table"), position = position) table$addColumnInfo(name = "metric", title = gettext("Capability\nMeasure"), type = "string") @@ -350,7 +350,7 @@ getCustomAxisLimits <- function(options, base) { # Plots ---- -.bpcsCapabilityPlot <- function(jaspResults, options, fit, priorFit, base = "posteriorDistributionPlot") { +.bpcsCapabilityPlot <- function(jaspResults, options, fit, priorFit, position, base = "posteriorDistributionPlot") { if (!options[[base]] || !is.null(jaspResults[[base]])) return() @@ -366,7 +366,7 @@ getCustomAxisLimits <- function(options, base) { 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 = 1, + position = position, dependencies = jaspDeps( options = c( .bpcsDefaultDeps(), @@ -444,7 +444,7 @@ getCustomAxisLimits <- function(options, base) { # ) # } -.bpcsSequentialPointEstimatePlot <- function(jaspResults, dataset, options, fit) { +.bpcsSequentialPointEstimatePlot <- function(jaspResults, dataset, options, fit, position) { base <- "sequentialAnalysisPointEstimatePlot" # "sequentialAnalysisPointIntervalPlot" @@ -453,7 +453,7 @@ getCustomAxisLimits <- function(options, base) { w <- 400 plt <- createJaspPlot(title = gettext("Sequential Analysis Point Estimate"), width = 3*w, height = 2*w, - position = 2, + position = position, dependencies = jaspDeps(c( .bpcsDefaultDeps(), .bpcsPlotLayoutDeps(base, hasPrior = FALSE), @@ -477,7 +477,7 @@ getCustomAxisLimits <- function(options, base) { } } -.bpcsSequentialIntervalEstimatePlot <- function(jaspResults, dataset, options, fit) { +.bpcsSequentialIntervalEstimatePlot <- function(jaspResults, dataset, options, fit, position) { # base <- "sequentialAnalysisPointEstimatePlot" base <- "sequentialAnalysisPointIntervalPlot" @@ -486,7 +486,7 @@ getCustomAxisLimits <- function(options, base) { w <- 400 plt <- createJaspPlot(title = gettext("Sequential Analysis Interval Estimate"), width = 3*w, height = 2*w, - position = 2, + position = position, dependencies = jaspDeps(c( .bpcsDefaultDeps(), .bpcsPlotLayoutDeps(base, hasPrior = FALSE) @@ -819,127 +819,7 @@ getCustomAxisLimits <- function(options, base) { } # Additional plot functions ---- -.bpcsSequentialAnalysis <- function(jaspResults, dataset, options, fit) { - - base1 <- "sequentialAnalysisPointEstimatePlot" - base2 <- "sequentialAnalysisPointIntervalPlot" - - needsPointPlot <- options[[base1]] && is.null(jaspResults[[base1]]) - needsIntervalPlot <- options[[base2]] && is.null(jaspResults[[base2]]) - if (!needsPointPlot && !needsIntervalPlot) - return() - - needsPlt <- setNames(c(needsPointPlot, needsIntervalPlot), c(base1, base2)) - w <- 400 - i <- 0 - for (base in c(base1, base2)) { - singlePanel <- options[[paste0(base, "PanelLayout")]] != "multiplePanels" - width <- w * (if (singlePanel) 1 else 3) - height <- w * (if (singlePanel) 1 else 3) - plt <- createJaspPlot( - title = gettext("Sequential Analysis Point Estimate"), width = width, height = height, - position = 3 + i, - dependencies = jaspDeps(c( - .bpcsDefaultDeps(), - .bpcsPlotLayoutDeps(base, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE), - "sequentialAnalysisPlotAdditionalInfo" - ))) - jaspResults[[base]] <- plt - i <- i + 1 - } - - if (!.bpcsIsReady(options) || jaspResults$getError()) return() - - hasError <- FALSE - errorMessage <- FALSE - tryCatch({ - baseData <- "sequentialAnalysisData" - sequentialPlotData <- jaspResults[[baseData]] %setOrRetrieve% ( - .bpcsComputeSequentialAnalysis(dataset, options, fit) |> - createJaspState(dependencies = jaspDeps(options = c( - .bpcsDefaultDeps(), - if (needsPointPlot) .bpcsPlotLayoutDeps(base1, hasAxes = FALSE, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE), - if (needsIntervalPlot) .bpcsPlotLayoutDeps(base2, hasAxes = FALSE, hasEstimate = FALSE, hasCi = FALSE, hasType = TRUE) - ))) - ) - - }, error = function(e) { - - errorMessage <- gettextf("Unexpected error in sequential analysis interval plot: %s", e$message) - - }) - - if (needsPointPlot) { - plt <- jaspResults[[base1]]$plotObject - if (hasError) { - plt$setError(errorMessage) - } else { - tryCatch({ - plt$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options, base1) - }, error = function(e) { - plt$setError(gettextf("Unexpected error in sequential analysis point estimate plot: %s", e$message)) - } - ) - } - } - - if (needsIntervalPlot) { - plt <- jaspResults[[base2]]$plotObject - if (hasError) { - plt$setError(errorMessage) - } else { - tryCatch({ - plt$plotObject <- .bpcsMakeSequentialPlot(sequentialPlotData, options, base2, custom = TRUE) - }, error = function(e) { - jaspResults[[base1]]$setError(gettextf("Unexpected error in sequential analysis interval estimate plot: %s", e$message)) - } - ) - } - } - -} - -.bpcsMakeSequentialIntervalPlot <- function(estimates, options, base) { - - nseq <- attr(estimates, "nseq") - - # Get the type bounds from options - typeLowerOption <- paste0(base, "TypeLower") - typeUpperOption <- paste0(base, "TypeUpper") - typeLower <- options[[typeLowerOption]] - typeUpper <- options[[typeUpperOption]] - - tb <- tibble::tibble( - metric = factor(rep(rownames(estimates), times = length(nseq))), - n = rep(nseq, each = nrow(estimates)), - lower = as.vector(estimates[, "lower", ]), - upper = as.vector(estimates[, "upper", ]), - ) - - # Calculate proportion in interval [typeLower, typeUpper] - # This is a simplified version - may need to access actual posterior samples - tb$proportion <- pmin(pmax((tb$lower + tb$upper) / 2, typeLower), typeUpper) - - y_breaks_per_scale <- tapply(tb, tb$metric, \(x) { - observedRange <- c(0, 1) - leftBreaks <- jaspGraphs::getPrettyAxisBreaks(observedRange) - ggplot2::scale_y_continuous(breaks = leftBreaks, limits = c(0, 1)) - }, simplify = FALSE) - - ggplot2::ggplot(tb, ggplot2::aes(x = .data$n, y = .data$proportion)) + - ggplot2::geom_line(linewidth = 1) + - ggplot2::facet_wrap(~ metric, scales = "free_y") + - ggh4x::facetted_pos_scales(y = y_breaks_per_scale) + - ggplot2::labs( - x = gettext("Number of observations"), - y = gettextf("P(%s < θ < %s)", typeLower, typeUpper) - ) + - jaspGraphs::geom_rangeframe(sides = "bl") + - jaspGraphs::themeJaspRaw() - -} - -.bpcsPlotPredictive <- function(jaspResults, options, fit, base = c("posteriorPredictiveDistributionPlot", "priorPredictiveDistributionPlot")) { +.bpcsPlotPredictive <- function(jaspResults, options, fit, position, base = c("posteriorPredictiveDistributionPlot", "priorPredictiveDistributionPlot")) { base <- match.arg(base) isPrior <- base == "priorPredictiveDistributionPlot" @@ -951,6 +831,7 @@ getCustomAxisLimits <- function(options, base) { title = if (isPrior) gettext("Prior predictive distribution") else gettext("Posterior Predictive Distribution"), width = 400, height = 400, + position = position, dependencies = c( .bpcsDefaultDeps(), base, From 8a427754044a9fb42c23da7c92a09c8547cacc63 Mon Sep 17 00:00:00 2001 From: Don van den Bergh Date: Tue, 6 Jan 2026 14:40:08 +0100 Subject: [PATCH 15/15] show only selected metrics in interval table --- R/bayesianProcessCapabilityStudies.R | 2 ++ 1 file changed, 2 insertions(+) diff --git a/R/bayesianProcessCapabilityStudies.R b/R/bayesianProcessCapabilityStudies.R index 19cc9adb..5dfd1106 100644 --- a/R/bayesianProcessCapabilityStudies.R +++ b/R/bayesianProcessCapabilityStudies.R @@ -306,12 +306,14 @@ getCustomAxisLimits <- function(options, base) { 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) {