///|
/// One plotted observation and its three-sigma limits.
pub(all) struct ChartPoint {
value : Double
center : Double
upper : Double
lower : Double
out_of_control : Bool
} derive(Debug, ToJson)
///|
/// A deterministic control-chart data series.
pub(all) struct ControlChart {
kind : String
center : Double
points : Array[ChartPoint]
} derive(Debug, ToJson)
///|
pub(all) enum ChartResult {
ChartOk(ControlChart)
ChartInvalidInput(message~ : String)
} derive(Debug)
///|
fn make_point(
value : Double,
center : Double,
upper : Double,
lower : Double,
) -> ChartPoint {
{
value,
center,
upper,
lower,
out_of_control: value > upper || value < lower,
}
}
///|
fn chart_from_values(
kind : String,
values : Array[Double],
center : Double,
upper : Double,
lower : Double,
) -> ControlChart {
let points : Array[ChartPoint] = []
for value in values {
points.push(make_point(value, center, upper, lower))
}
{ kind, center, points }
}
///|
fn mean_of(values : Array[Double]) -> Double {
let mut total = 0.0
for value in values {
total += value
}
total / values.length().to_double()
}
///|
fn range_of(values : Array[Double]) -> Double {
let mut low = values[0]
let mut high = values[0]
for value in values {
if value < low {
low = value
}
if value > high {
high = value
}
}
high - low
}
///|
fn std_of(values : Array[Double], mean : Double) -> Double {
if values.length() < 2 {
0.0
} else {
let mut total = 0.0
for value in values {
let d = value - mean
total += d * d
}
(total / (values.length() - 1).to_double()).sqrt()
}
}
///|
/// X-bar/R chart. Subgroups are expected to have the same size.
pub fn ControlChart::xbar_r(groups : Array[Array[Double]]) -> ChartResult {
if groups.length() < 2 || groups[0].length() < 2 {
ChartInvalidInput(message="xbar-r needs at least two subgroups of size two")
} else {
let size = groups[0].length()
let means : Array[Double] = []
let ranges : Array[Double] = []
for group in groups {
if group.length() != size {
return ChartInvalidInput(message="xbar-r subgroup sizes must match")
}
means.push(mean_of(group))
ranges.push(range_of(group))
}
let xbar = mean_of(means)
let rbar = mean_of(ranges)
let a2 = match size {
2 => 1.88
3 => 1.023
4 => 0.729
5 => 0.577
6 => 0.483
7 => 0.419
8 => 0.373
9 => 0.337
10 => 0.308
_ => 0.0
}
if a2 == 0.0 {
ChartInvalidInput(message="xbar-r supports subgroup sizes from 2 to 10")
} else {
ChartOk(
chart_from_values(
"X-bar/R",
means,
xbar,
xbar + a2 * rbar,
xbar - a2 * rbar,
),
)
}
}
}
///|
/// X-bar/S chart using the average within-subgroup sample deviation.
pub fn ControlChart::xbar_s(groups : Array[Array[Double]]) -> ChartResult {
if groups.length() < 2 || groups[0].length() < 2 {
ChartInvalidInput(message="xbar-s needs at least two subgroups of size two")
} else {
let size = groups[0].length()
let means : Array[Double] = []
let deviations : Array[Double] = []
for group in groups {
if group.length() != size {
return ChartInvalidInput(message="xbar-s subgroup sizes must match")
}
let m = mean_of(group)
means.push(m)
deviations.push(std_of(group, m))
}
let xbar = mean_of(means)
let sbar = mean_of(deviations)
let c4 = match size {
2 => 0.7979
3 => 0.8862
4 => 0.9213
5 => 0.9399
_ => 0.94
}
let a3 = 3.0 / (c4 * size.to_double().sqrt())
ChartOk(
chart_from_values(
"X-bar/S",
means,
xbar,
xbar + a3 * sbar,
xbar - a3 * sbar,
),
)
}
}
///|
/// Individuals/moving-range chart.
pub fn ControlChart::imr(values : Array[Double]) -> ChartResult {
if values.length() < 2 {
ChartInvalidInput(message="i-mr needs at least two observations")
} else {
let moving : Array[Double] = []
for i in 1.. ChartResult {
if counts.length() == 0 || counts.length() != sizes.length() {
ChartInvalidInput(
message="counts and sizes must have equal non-zero length",
)
} else {
let rates : Array[Double] = []
for i in 0.. ChartResult {
counts_chart("P", counts, sizes, 3.0)
}
///|
/// Number defective chart for a constant sample size.
pub fn ControlChart::np(counts : Array[Int], sample_size : Int) -> ChartResult {
if sample_size <= 0 || counts.length() == 0 {
ChartInvalidInput(message="sample size and counts must be positive")
} else {
let sizes : Array[Int] = []
for _ in counts {
sizes.push(sample_size)
}
counts_chart("NP", counts, sizes, 3.0)
}
}
///|
/// Count defects chart for a constant opportunity count.
pub fn ControlChart::c(counts : Array[Int], opportunities : Int) -> ChartResult {
if opportunities <= 0 || counts.length() == 0 {
ChartInvalidInput(message="opportunities and counts must be positive")
} else {
let sizes : Array[Int] = []
for _ in counts {
sizes.push(opportunities)
}
counts_chart("C", counts, sizes, 3.0)
}
}
///|
/// Defects-per-unit chart with varying exposure.
pub fn ControlChart::u(
counts : Array[Int],
opportunities : Array[Int],
) -> ChartResult {
counts_chart("U", counts, opportunities, 3.0)
}