13. Statistics
A strength test usually crushes several specimens of one mix and reports one value for them: their mean, often with their spread, and only after a specimen far from the others has been rejected. This chapter reduces the strengths of three specimens to a mean and a range, then rejects an outlier from five specimens and takes the mean of those kept. The trace says which specimen was rejected, in which pass, and why.
The program is self-contained: it declares its own quantities and does not build on the previous chapters' programs.
Summary statistics over a sample
The strengths of the specimens are one quantity at several points, a series (chapter 12). Their range is a quantity of its own, in the same unit:
using Strength = formula::Quantity<struct StrengthTag, "f_c", "compressive strength", unit::Megapascal>;
using StrengthRange = formula::Quantity<struct StrengthRangeTag, "R", "range of the strengths", unit::Megapascal>;
A statistic reduces a series to a single value, so it is an ordinary formula:
formula::sample_mean is the mean of the elements, and formula::sample_range
the highest less the lowest:
constexpr auto threeSpecimens = formula::series<Strength, 3>;
constexpr auto mean = formula::yields<Strength>(formula::sample_mean(threeSpecimens));
constexpr auto range = formula::yields<StrengthRange>(formula::sample_range(threeSpecimens));
The library also has formula::sample_count and formula::sample_variance.
A mean is taken only when every element is present: one specimen not
measured, and the mean is absent, never the mean of the rest.
The three specimens have strengths of 30.2, 29.8 and 31.0 MPa:
auto const three = formula::environment(formula::measured_series<Strength>(30.2_r, 29.8_r, 31));
auto const meanValue = formula::checked_evaluate(mean, three);
if (!meanValue)
{
std::println("no mean: {}", meanValue.error());
return 1;
}
auto const rangeValue = formula::checked_evaluate(range, three);
if (!rangeValue)
{
std::println("no range: {}", rangeValue.error());
return 1;
}
std::println("three specimens:");
std::println("{} = {}, for reading {:~.2HalfEven}", formula::render(mean), *meanValue, *meanValue);
std::println("{} = {}", formula::render(range), *rangeValue);
The three add up to 91 MPa, so the mean is 91/3 MPa exactly. It has no exact
decimal, so it prints as a fraction, and {:~.2HalfEven} rounds it to two
places for reading: ≈30.33 MPa. The range is 31.0 less 29.8, 1.2 MPa.
Outliers rejected pass by pass
Five specimens have strengths of 30.2, 29.8, 31.0, 30.4 and 36.0 MPa. An invented rule rejects a specimen more than 3 MPa from the mean, one specimen per pass. Some methods measure from the mean of the others; this one measures from the mean of each pass, the strength itself included. With these five strengths, both rules reject 36.0 MPa and nothing else.
formula::deviation_from_mean(limit) is that criterion, the limit here a
constant 3 MPa. formula::without_outliers applies it, pass by pass, and is
itself a sample that a statistic takes as it takes a series:
/// More than 3 MPa from the mean of the pass, the value itself included.
constexpr auto threeMegapascals = formula::deviation_from_mean(formula::constant<unit::Megapascal>(3));
constexpr auto withoutOutliers = formula::without_outliers<formula::PerPass::MostExtreme,
formula::OnLimit::Keep,
formula::AtMost<2>,
formula::KeepAtLeast<3>>(
formula::series<Strength, 5>, threeMegapascals, formula::Verdict { "test further specimens" });
constexpr auto meanKept = formula::yields<Strength>(formula::sample_mean(withoutOutliers));
Every template argument shapes the result, so none has a default:
formula::PerPass::MostExtremerejects only the specimen furthest from the mean in each pass, andformula::PerPass::EveryExceedingevery one past the limit;formula::OnLimit::Keepkeeps a specimen exactly on the limit, andformula::OnLimit::Rejectrejects it;formula::AtMost<2>rejects at most two specimens in all;formula::KeepAtLeast<3>keeps at least three.
When a rejection would break AtMost or KeepAtLeast, nothing more is
rejected and the result is the verdict, here "test further specimens", not
a mean of whatever was left.
The program prints the rejection, then the trace of the mean of the specimens kept:
auto const five = formula::environment(formula::measured_series<Strength>(30.2_r, 29.8_r, 31, 30.4_r, 36));
std::println("five specimens:");
std::println("{}", formula::render(withoutOutliers));
auto const kept = formula::checked_explain(meanKept, five);
if (!kept)
{
std::println("no mean of the specimens kept: {}", kept.error().error);
return 1;
}
std::print("{}", formula::render_trace(kept->trace, { .maxSteps = 20 }));
std::println("mean of the specimens kept = {}", kept->outcome);
The limit is evaluated again in every pass, so the trace states it at the start of each: lines 2 and 5, 3 MPa both times, since it is a constant. In pass 1, the mean of the five is 787/25 MPa, 31.48 MPa. 36.0 MPa is 113/25 MPa, 4.52 MPa, from it, more than 3 MPa, and the furthest of the five: the trace says it rejected element 5 of 5 in pass 1, and why. In pass 2, the mean of the four left is 607/20 MPa, 30.35 MPa, and none of them is more than 3 MPa from it, so the rejection settles with one rejected and four kept. The mean of the specimens kept is 30.35 MPa.
Output
three specimens:
sample_mean(f_c(i)) = 91/3 MPa, for reading ≈30.33 MPa
sample_range(f_c(i)) = 1.2 MPa
five specimens:
without outliers(f_c(i); abs(x - pass mean) > 3 MPa; most extreme per pass; keep on limit; at most 2; keep at least 3)
1. f_c = 151/5 MPa; 149/5 MPa; 31 MPa; 152/5 MPa; 36 MPa
2. 3 MPa
3. pass 1: 5 values, mean 787/25 MPa
4. rejected element 5 of 5 (36 MPa) in pass 1: abs(x - mean) = 113/25 MPa > 3 MPa (deviation from mean)
5. 3 MPa
6. pass 2: 4 values, mean 607/20 MPa
7. settled: 1 rejected, 4 remain
8. sample_mean(#7) = 607/20 MPa
mean of the specimens kept = 30.35 MPa
Summary
formula::sample_mean(sample)-- the mean of the sample's values.formula::sample_range(sample)-- the highest value less the lowest.formula::deviation_from_mean(limit)-- rejects a value further thanlimitfrom the mean of its pass.formula::without_outliers<PerPass, OnLimit, AtMost, KeepAtLeast>(sample, criterion, verdict)-- the sample with its outliers rejected, pass by pass, until a pass rejects nothing, or until a rejection would breakAtMostorKeepAtLeastand the verdict is given.
Further reading
- A sample
- Rejecting outliers
- Statistics, outliers and precision, which also covers the spread, other criteria and precision checks
- API reference