Introduction
Boost.Accumulators provides an efficient framework for computing statistical measures like mean and standard deviation from a vector of samples in C++. Instead of manually summing values and computing variance, you create an accumulator set, feed it your data, and extract results. Boost processes each value once in a streaming fashion, making it suitable for large datasets. For simpler cases, the C++ standard library's <numeric> header with std::accumulate works without any external dependency.
Using Boost.Accumulators
1#include <iostream>
2#include <vector>
3#include <boost/accumulators/accumulators.hpp>
4#include <boost/accumulators/statistics/stats.hpp>
5#include <boost/accumulators/statistics/mean.hpp>
6#include <boost/accumulators/statistics/variance.hpp>
7
8namespace ba = boost::accumulators;
9
10int main() {
11 std::vector<double> samples = {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
12
13 // Create accumulator with mean and variance
14 ba::accumulator_set<double, ba::stats<ba::tag::mean, ba::tag::variance>> acc;
15
16 // Feed all samples
17 for (double x : samples) {
18 acc(x);
19 }
20
21 double mean = ba::mean(acc);
22 double variance = ba::variance(acc);
23 double stddev = std::sqrt(variance);
24
25 std::cout << "Mean: " << mean << std::endl; // 5.0
26 std::cout << "Variance: " << variance << std::endl; // 4.0
27 std::cout << "Std Dev: " << stddev << std::endl; // 2.0
28
29 return 0;
30}
The accumulator processes each sample incrementally. ba::tag::variance computes the population variance by default. Extract results with ba::mean(acc) and ba::variance(acc).
Standard Library Only (No Boost)
1#include <iostream>
2#include <vector>
3#include <numeric>
4#include <cmath>
5
6int main() {
7 std::vector<double> samples = {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
8 int n = samples.size();
9
10 // Mean
11 double sum = std::accumulate(samples.begin(), samples.end(), 0.0);
12 double mean = sum / n;
13
14 // Standard deviation (population)
15 double sq_sum = std::inner_product(
16 samples.begin(), samples.end(), samples.begin(), 0.0
17 );
18 double stddev = std::sqrt(sq_sum / n - mean * mean);
19
20 std::cout << "Mean: " << mean << std::endl; // 5.0
21 std::cout << "Std Dev: " << stddev << std::endl; // 2.0
22
23 return 0;
24}
std::accumulate computes the sum, and std::inner_product with itself computes the sum of squares. This approach requires two passes over the data but needs no external library.
Sample vs Population Standard Deviation
1#include <vector>
2#include <numeric>
3#include <cmath>
4#include <iostream>
5
6double population_stddev(const std::vector<double>& data) {
7 double n = data.size();
8 double mean = std::accumulate(data.begin(), data.end(), 0.0) / n;
9 double sq_diff_sum = 0.0;
10 for (double x : data) {
11 sq_diff_sum += (x - mean) * (x - mean);
12 }
13 return std::sqrt(sq_diff_sum / n); // Divide by N
14}
15
16double sample_stddev(const std::vector<double>& data) {
17 double n = data.size();
18 double mean = std::accumulate(data.begin(), data.end(), 0.0) / n;
19 double sq_diff_sum = 0.0;
20 for (double x : data) {
21 sq_diff_sum += (x - mean) * (x - mean);
22 }
23 return std::sqrt(sq_diff_sum / (n - 1)); // Divide by N-1 (Bessel's correction)
24}
25
26int main() {
27 std::vector<double> data = {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
28 std::cout << "Population: " << population_stddev(data) << std::endl; // 2.0
29 std::cout << "Sample: " << sample_stddev(data) << std::endl; // 2.138
30 return 0;
31}
Use population standard deviation (divide by N) when you have the entire dataset. Use sample standard deviation (divide by N-1) when the data is a sample from a larger population.
Streaming Computation (Online Algorithm)
1#include <iostream>
2#include <cmath>
3
4// Welford's online algorithm — numerically stable, single pass
5class RunningStats {
6 int count = 0;
7 double mean_ = 0.0;
8 double m2 = 0.0;
9
10public:
11 void push(double x) {
12 count++;
13 double delta = x - mean_;
14 mean_ += delta / count;
15 double delta2 = x - mean_;
16 m2 += delta * delta2;
17 }
18
19 double mean() const { return mean_; }
20 double variance() const { return m2 / count; }
21 double stddev() const { return std::sqrt(variance()); }
22 double sample_variance() const { return m2 / (count - 1); }
23 double sample_stddev() const { return std::sqrt(sample_variance()); }
24};
25
26int main() {
27 RunningStats stats;
28 for (double x : {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0}) {
29 stats.push(x);
30 }
31 std::cout << "Mean: " << stats.mean() << std::endl;
32 std::cout << "Std Dev: " << stats.stddev() << std::endl;
33 return 0;
34}
Welford's algorithm is numerically stable and processes data in a single pass. It avoids the catastrophic cancellation that can occur with the sum-of-squares approach on large datasets with values close together.
Compiling with Boost
1# Boost.Accumulators is header-only — no linking required
2g++ -std=c++17 -I/usr/include/boost stats.cpp -o stats
3
4# On macOS with Homebrew
5g++ -std=c++17 -I$(brew --prefix boost)/include stats.cpp -o stats
6
7# With CMake
8# find_package(Boost REQUIRED)
9# target_include_directories(myapp PRIVATE ${Boost_INCLUDE_DIRS})
Common Pitfalls
Integer division in mean calculation: Using int for the sum or count produces integer division. std::accumulate(v.begin(), v.end(), 0) with an int initial value returns an int sum. Use 0.0 as the initial value to get a double result: std::accumulate(v.begin(), v.end(), 0.0).
Using population variance when sample variance is needed: Boost's ba::tag::variance computes population variance (divides by N). For sample variance (divides by N-1), use ba::tag::variance(ba::lazy) and manually apply Bessel's correction, or compute it manually.
Numerical instability with large datasets: The naive formula sqrt(sum(x^2)/n - mean^2) can produce negative values due to floating-point cancellation when values are large and close together. Use Welford's online algorithm or a two-pass approach that computes deviations from the mean.
Empty vector causing division by zero: Computing mean or standard deviation on an empty vector divides by zero. Always check if (data.empty()) return 0.0; or throw an exception before computing statistics.
Forgetting that Boost.Accumulators is header-only: Unlike many Boost libraries, Accumulators does not require linking. Only the include path is needed. If you get linker errors, the issue is likely a different Boost library, not Accumulators.
Summary
Use boost::accumulators with tag::mean and tag::variance for a clean, extensible solution
For no-dependency code, use std::accumulate and std::inner_product from <numeric>
Use Welford's online algorithm for numerically stable single-pass computation
Choose population (N) or sample (N-1) standard deviation based on whether your data is the full population or a sample
Always initialize accumulator sums with 0.0 (double), not 0 (int), to avoid integer division