C++
Boost
mean calculation
standard deviation
programming tips

Calculate mean and standard deviation from a vector of samples in C using Boost

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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

cpp
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)

cpp
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

cpp
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)

cpp
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

bash
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

Course illustration
Course illustration

All Rights Reserved.