{ "cells": [ { "cell_type": "markdown", "id": "cell-0", "metadata": {}, "source": [ "# Bayesian capability: deciding from a small sample\n", "\n", "You have twelve parts off a new process and a decision to make: **ship the lot or hold\n", "it?** The requirement is a performance index of at least 1.33. Classical capability gives\n", "you one number with a confidence interval so wide it is hard to act on. This walkthrough\n", "takes the same twelve parts and answers the question a release meeting actually asks:\n", "*what is the probability this process meets the requirement?*\n", "\n", "Every cell runs. Open it in Colab and drop in your own measurements.\n", "\n", "
Run in ColabView on GitHubDownload notebook
" ] }, { "cell_type": "markdown", "id": "cell-primer", "metadata": {}, "source": [ "## What \"Bayesian\" means here, and why it helps\n", "\n", "Most quality training teaches one meaning of the word *probability*: the long-run\n", "frequency of an event over many repetitions. This is the basis of the p-values,\n", "confidence intervals, and capability indices in common use. It is sound, and it answers a\n", "specific question. It is not always the question you have.\n", "\n", "Consider a 95% confidence interval on Cpk. It is commonly read as *\"there is a 95% chance\n", "the true Cpk lies in this range.\"* That reading is wrong. In the frequentist account the\n", "true Cpk is one fixed number; it either lies in the interval or it does not, and no\n", "probability attaches to it. The 95% refers to the **procedure**: if the study were\n", "repeated many times, 95% of the intervals constructed this way would contain the true\n", "value. The sentence *\"the probability this process is capable is 0.93\"* is not available\n", "in that framework.\n", "\n", "The **Bayesian** account makes that sentence available. It treats the quantity you do not\n", "know -- the true Cpk, the true defect rate -- as uncertain, and represents that\n", "uncertainty as a probability distribution. You state what was known before the\n", "measurements (the **prior**), the data revise it, and the result is a **posterior**\n", "distribution for the quantity of interest. From the posterior you read the statement\n", "directly: given these parts, the probability that Cpk meets the requirement is 0.93. The\n", "accompanying **credible interval** carries the meaning the confidence interval is often\n", "mistaken for -- a 95% credible interval is a range that contains the true value with\n", "probability 0.95, given the data and the prior.\n", "\n", "| | Frequentist | Bayesian (this layer) |\n", "|---|---|---|\n", "| The true Cpk is | one fixed, unknown number | an uncertain quantity, described by a distribution |\n", "| A 95% interval is | a procedure: 95% of repeated studies would cover the true value | a range containing the true value with probability 0.95, given the data |\n", "| The result you report | a point index and an interval | a probability that the process conforms |\n", "| With a small sample | the interval is wide and hard to act on | the uncertainty is stated directly and can enter a decision |\n", "| Prior knowledge (history, a qualified line) | not used | included through the prior, and recorded |\n", "\n", "This matters in three situations common in manufacturing:\n", "\n", "1. **Small runs.** With a dozen parts, a confidence interval on Cpk is wide enough to be\n", " hard to act on. The posterior gives a probability of conformance that can enter a\n", " decision directly.\n", "2. **Existing knowledge.** A qualified sister process, a validated material, or prior\n", " production data is information about the process. The prior lets you use it; the\n", " frequentist estimate uses only the current sample.\n", "3. **The question of interest.** *\"What is the probability this lot conforms?\"* is a\n", " question about the unknown quantity, which is what a posterior describes. This layer\n", " answers it without translating through a p-value.\n", "\n", "The two accounts are not in opposition. Given a prior that expresses no prior knowledge\n", "and a large sample, the posterior and the classical estimate agree. The Bayesian layer is\n", "most useful where the classical estimate is weakest: small samples, and cases where a\n", "direct probability of conformance is what the decision requires. The rest of this\n", "walkthrough applies it to a ship-or-hold decision on twelve parts." ] }, { "cell_type": "markdown", "id": "cell-1", "metadata": {}, "source": [ "Install it first (skip this if mfgQC is already in your environment):" ] }, { "cell_type": "code", "execution_count": null, "id": "cell-2", "metadata": { "tags": [ "skip-execution" ] }, "outputs": [], "source": [ "!pip install mfgqc" ] }, { "cell_type": "markdown", "id": "cell-3", "metadata": {}, "source": [ "## 1. The twelve parts\n", "\n", "No DataFrame needed here: the Bayesian capability entry point takes a plain list of\n", "measurements. These are twelve widths, spec limits `[1.0, 2.0]`, target `1.5`." ] }, { "cell_type": "code", "execution_count": 1, "id": "cell-4", "metadata": { "execution": { "iopub.execute_input": "2026-07-07T05:51:48.631741Z", "iopub.status.busy": "2026-07-07T05:51:48.631574Z", "iopub.status.idle": "2026-07-07T05:51:49.254733Z", "shell.execute_reply": "2026-07-07T05:51:49.254324Z" } }, "outputs": [ { "data": { "text/plain": [ "12" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import numpy as np\n", "from mfgqc.bayes import capability_from_values\n", "\n", "y = [1.51, 1.546, 1.477, 1.403, 1.455, 1.391,\n", " 1.517, 1.671, 1.451, 1.436, 1.569, 1.553]\n", "\n", "len(y)" ] }, { "cell_type": "markdown", "id": "cell-5", "metadata": {}, "source": [ "## 2. Run it\n", "\n", "`capability_from_values` fits a Normal process with a conjugate Bayesian model and returns\n", "a result object, exactly like the rest of mfgQC. The one required extra is `seed=`, which\n", "makes the Monte Carlo step reproducible: the same seed and the same data always give the\n", "same answer." ] }, { "cell_type": "code", "execution_count": 2, "id": "cell-6", "metadata": { "execution": { "iopub.execute_input": "2026-07-07T05:51:49.255732Z", "iopub.status.busy": "2026-07-07T05:51:49.255642Z", "iopub.status.idle": "2026-07-07T05:51:49.266580Z", "shell.execute_reply": "2026-07-07T05:51:49.266220Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bayesian Capability (noninformative)\n", "====================================\n", "n = 12 mean = 1.4982 s (overall) = 0.07956\n", "mu 95% credible interval = (1.4477, 1.5488)\n", "Pp = 2.095 Ppk point = 2.088\n", "Ppk 95% credible interval = (1.15, 2.88)\n", "P(Ppk >= 1.33) = 0.931 +/- 0.0008 (MC)\n", "ppm point = 0\n", "\n", "Assumption checks:\n", " [PASS] small_sample (n >= 8): n=12; n=12 [low power]\n", " [PASS] normality (Anderson-Darling): AD=0.225, p=0.768; skew 0.614; n=12 [low power]\n" ] } ], "source": [ "cap = capability_from_values(y, lower=1.0, upper=2.0, target=1.5, seed=1)\n", "print(cap.report())" ] }, { "cell_type": "markdown", "id": "cell-7", "metadata": {}, "source": [ "Read the report top to bottom:\n", "\n", "- **`Ppk point = 2.088`** is the single-number estimate, the same value classical\n", " capability would give.\n", "- **`Ppk 95% credible interval = (1.15, 2.88)`** is the uncertainty. Twelve parts do not\n", " pin the index down, and the posterior reports that range rather than a single decimal.\n", "- **`P(Ppk >= 1.33) = 0.931`** is the headline: given these twelve parts, there is a 93%\n", " probability the true process clears the requirement.\n", "- The **assumption checks** run just as they do in the classical layer, flagged\n", " `[low power]` because twelve points cannot test much." ] }, { "cell_type": "markdown", "id": "cell-8", "metadata": {}, "source": [ "## 3. Turn the posterior into a number you can act on\n", "\n", "Two methods read straight off the posterior. `prob(...)` gives the probability of\n", "clearing a threshold (with its Monte Carlo error), and `interval(...)` gives a credible\n", "interval at any level you like." ] }, { "cell_type": "code", "execution_count": 3, "id": "cell-9", "metadata": { "execution": { "iopub.execute_input": "2026-07-07T05:51:49.267403Z", "iopub.status.busy": "2026-07-07T05:51:49.267356Z", "iopub.status.idle": "2026-07-07T05:51:49.270669Z", "shell.execute_reply": "2026-07-07T05:51:49.270358Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "P(Ppk >= 1.33) = 0.931 (+/- 0.0008 Monte Carlo)\n", "90% credible interval for Ppk = (1.27, 2.72)\n" ] } ], "source": [ "prob, mc_error = cap.prob(\"ppk\", 1.33)\n", "print(f\"P(Ppk >= 1.33) = {prob:.3f} (+/- {mc_error:.4f} Monte Carlo)\")\n", "\n", "low, high = cap.interval(\"ppk\", 0.90)\n", "print(f\"90% credible interval for Ppk = ({low:.2f}, {high:.2f})\")" ] }, { "cell_type": "markdown", "id": "cell-10", "metadata": {}, "source": [ "That first line is the whole point of the Bayesian layer: a probability of conformance,\n", "not a point index you have to interpret. You can wire it straight into a decision rule." ] }, { "cell_type": "code", "execution_count": 4, "id": "cell-11", "metadata": { "execution": { "iopub.execute_input": "2026-07-07T05:51:49.271452Z", "iopub.status.busy": "2026-07-07T05:51:49.271400Z", "iopub.status.idle": "2026-07-07T05:51:49.272890Z", "shell.execute_reply": "2026-07-07T05:51:49.272559Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "93% sure the process is capable -> SHIP\n" ] } ], "source": [ "THRESHOLD = 0.90 # ship only if we are at least 90% sure the process is capable\n", "decision = \"SHIP\" if prob >= THRESHOLD else \"HOLD\"\n", "print(f\"{prob:.0%} sure the process is capable -> {decision}\")" ] }, { "cell_type": "markdown", "id": "cell-12", "metadata": {}, "source": [ "## 4. Fold in what you already know\n", "\n", "Suppose this is not a brand-new process. A qualified, closely related line ran at a mean\n", "of 1.50 with a standard deviation near 0.11. That is real prior information, and throwing\n", "it away is wasteful. A `NormalPrior` carries it in. The strength parameters `k0` and `nu0`\n", "are an *equivalent prior sample size*: `k0=20` says \"trust the prior mean about as much as\n", "twenty parts.\"" ] }, { "cell_type": "code", "execution_count": 5, "id": "cell-13", "metadata": { "execution": { "iopub.execute_input": "2026-07-07T05:51:49.273626Z", "iopub.status.busy": "2026-07-07T05:51:49.273586Z", "iopub.status.idle": "2026-07-07T05:51:49.282194Z", "shell.execute_reply": "2026-07-07T05:51:49.281925Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Bayesian Capability (informative)\n", "=================================\n", "n = 12 mean = 1.4982 s (overall) = 0.07956\n", "mu 95% credible interval = (1.4638, 1.5349)\n", "Pp = 2.095 Ppk point = 2.088\n", "Ppk 95% credible interval = (1.22, 2.06)\n", "P(Ppk >= 1.33) = 0.922 +/- 0.00085 (MC)\n", "ppm point = 0\n", "prior = normal\n", "\n", "Assumption checks:\n", " [FAIL] prior_weight (prior weight w = k0/(k0+n)): prior=0.625; n=12\n", " [PASS] prior_data_conflict (prior predictive t on ybar): prior=0.0436, p=0.966; n=12\n", " [PASS] small_sample (n >= 8): n=12; n=12 [low power]\n", " [PASS] normality (Anderson-Darling): AD=0.225, p=0.768; skew 0.614; n=12 [low power]\n", "\n", "Recommendations:\n", " - The prior contributes 62% of the posterior; the report is prior dominated. Widen the prior or collect more data.\n" ] } ], "source": [ "from mfgqc.bayes.priors import NormalPrior\n", "\n", "prior = NormalPrior(mu0=1.50, k0=20.0, nu0=20.0, s20=0.11 ** 2)\n", "cap_prior = capability_from_values(y, lower=1.0, upper=2.0, target=1.5,\n", " prior=prior, seed=1)\n", "print(cap_prior.report())" ] }, { "cell_type": "markdown", "id": "cell-14", "metadata": {}, "source": [ "The credible interval tightened (the prior adds information), and two new guardrails\n", "appear:\n", "\n", "- **`prior_weight`** flags that the prior is driving 62% of the result. With only twelve\n", " parts against a `k0=20` prior, the prior is doing most of the work, and mfgQC says so\n", " rather than letting you present a prior-dominated number as if it were data. If you want\n", " the parts to speak louder, weaken the prior (lower `k0`) or collect more.\n", "- **`prior_data_conflict`** checks whether the parts agree with the prior. Here `p=0.97`,\n", " so they are consistent; a small value here would be a red flag that the prior and the\n", " process disagree, and you should stop and reconcile them before trusting the number.\n", "\n", "A prior is a recorded input, not a hidden adjustment. Its parameters, its strength, and\n", "both checks are stored in the result's provenance." ] }, { "cell_type": "markdown", "id": "cell-15", "metadata": {}, "source": [ "## 5. It is auditable\n", "\n", "Like every mfgQC result, a Bayesian result carries a tamper-evident provenance digest. Two\n", "runs on the same data and seed produce the same digest; change one measurement and it\n", "changes. That is what lets a Bayesian number, prior and all, survive an audit." ] }, { "cell_type": "code", "execution_count": 6, "id": "cell-16", "metadata": { "execution": { "iopub.execute_input": "2026-07-07T05:51:49.283007Z", "iopub.status.busy": "2026-07-07T05:51:49.282961Z", "iopub.status.idle": "2026-07-07T05:51:49.289780Z", "shell.execute_reply": "2026-07-07T05:51:49.289440Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "verifies: True\n", "reproducible: True\n" ] } ], "source": [ "digest = cap.provenance_digest()\n", "print(\"verifies:\", cap.verify_provenance(digest))\n", "\n", "again = capability_from_values(y, lower=1.0, upper=2.0, target=1.5, seed=1)\n", "print(\"reproducible:\", again.provenance_digest() == digest)" ] }, { "cell_type": "markdown", "id": "cell-17", "metadata": {}, "source": [ "## Where to go next\n", "\n", "- The [Bayesian analytics reference](../reference/bayesian.md) has the formulas, the\n", " conjugate engine, and the rest of the layer: attributes (pass/fail and defect rates),\n", " comparing two processes, sample-size assurance, guardbanding for gauge error, Phase-1\n", " monitoring, and hierarchical pooling of small groups.\n", "- The [Capability reference](../reference/capability.md) covers the classical\n", " $C_p$/$C_{pk}$/$P_{pk}$ layer this mirrors.\n", "- The [audit workflow](audit-workflow.ipynb) shows the provenance model end to end." ] } ], "metadata": { "jupytext": { "main_language": "python" }, "kernelspec": { "display_name": "Python 3", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.13.7" } }, "nbformat": 4, "nbformat_minor": 5 }