quickstats
Quickly compute simple statistics
Loading...
Searching...
No Matches
Quickly compute simple statistics

Unit tests Documentation Codecov

Overview

Pretty much as it says in the title. This library implements some utilities to compute simple statistics like quantiles and variances. Nothing too exotic or difficult, just annoying to re-implement for each application. Most of this code was migrated from tatami_stats or libscran for more general use.

Check out the reference documentation for more details.

Medians

Pretty much as it says - works for dense and sparse vectors:

std::vector<double> x{ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10. };
auto med = quickstats::median(x.size(), x.data(), {});
// Also works for sparse data - in this case, [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
std::vector<double> x_nonzero{ 1., 2., 3. };
auto med_sparse = quickstats::median(10, 3, x.data(), {});
Output_ median(const std::size_t num_total, Input_ *const ptr, const MedianOptions< Output_ > &options)
Definition median.hpp:47

Note that this will shuffle the input array.

Single quantiles

The SingleQuantileFixedNumber class will compute a pre-specified quantile from a fixed number of elements:

// We compute the 20th percentile from any array of 10 elements.
std::vector<double> x{ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10. };
auto fq20 = qfixed(x.data());
// Also works for sparse data - in this case, [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
std::vector<double> x_nonzero{ 1., 2., 3. };
auto fq20_sparse = qfixed(x_nonzero.size(), x_nonzero.data());
Calculate a single quantile from a fixed number of elements.
Definition SingleQuantile.hpp:69

The SingleQuantileVariableNumber class will compute a pre-specified quantile from a variable number of elements, up to a maximum:

// We compute the 20th percentile from any array of <= 10 elements.
std::vector<double> y{ 1., 2., 3., 4., 5., 6. };
auto vq20 = qvar(y.size(), y.data());
// Also works for sparse data - in this case, [1, 2, 3, 0, 0, 0]
std::vector<double> y_nonzero{ 1., 2., 3. };
auto vq20_sparse = qvar(6, y_nonzero.size(), y_nonzero.data());
Calculate a quantile from a variable number of elements.
Definition SingleQuantile.hpp:228

Multiple quantiles

To compute multiple quantiles at once, we can construct a MultipleQuantilesFixedNumber instance. This is more efficient than creating and invoking multiple SingleQuantileFixedNumber instances.

// We want to compute multiple quantiles from an array of 10 elements.
std::vector<double> quantiles{ 0.1, 0.2, 0.4, 0.8 }; // probabilities should be sorted.
std::vector<double> x{ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10. };
std::vector<double> res(quantiles.size());
qfixed(
x.data(),
// Custom storing function.
[&](std::size_t q, double val) -> void {
res[q] = val;
}
);
// Also works for sparse data - in this case, [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
std::vector<double> x_nonzero{ 1., 2., 3. };
qfixed(
x_nonzero.size(),
x_nonzero.data(),
[&](int q, double val) -> void {
res[q] = val;
}
);
Calculate multiple quantiles from a fixed number of elements.
Definition MultipleQuantiles.hpp:33

Similarly, the MultipleQuantilesVariableNumber class computes multiple quantiles from a variable number of elements, up to a maximum:

// We want to compute multiple quantiles from an array of 10 elements.
quickstats::MultipleQuantilesVariableNumber<double, decltype(&quantiles)> qvar(10, &quantiles, {});
std::vector<double> y{ 1., 2., 3., 4., 5., 6. };
qvar(
y.size(),
y.data(),
[&](int q, double val) -> void {
res[q] = val;
}
);
// Also works for sparse data - in this case, [1, 2, 3, 0, 0, 0]
std::vector<double> y_nonzero{ 1., 2., 3. };
qvar(
6,
y_nonzero.size(),
y_nonzero.data(),
[&](int q, double val) -> void {
res[q] = val;
}
);
Calculate multiple quantiles from a variable number of elements.
Definition MultipleQuantiles.hpp:382

Pairwise summation

We can perform pairwise summation to, well, compute the sum of an array. This mitigates loss of precision due to floating-point round-off error compared to naive std::accumulate().

// No precision is lost in this simple example, but it's just a demonstration.
std::vector<double> x{ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10. };
auto sum = quickstats::pairwise_sum(x.size(), x.data(), work, {});
Output_ pairwise_sum(const std::size_t num_total, const Input_ *const ptr, PairwiseSumWorkspace< Output_ > &work, const PairwiseSumOptions &options)
Definition pairwise_sum.hpp:215
Re-usable workspace for pairwise_sum() and pairwise_sum_abstract().
Definition pairwise_sum.hpp:24

We can also customize the source of the input data to perform accumulations over an abstract array:

// No precision is lost in this simple example, but it's just a demonstration.
auto mean = sum / x.size();
x.size(),
[&](const std::size_t idx) -> auto {
const double delta = x[idx] - mean;
return delta * delta;
}
work,
{}
);
Output_ pairwise_sum_abstract(const std::size_t num_total, Input_ input, PairwiseSumWorkspace< Output_ > &work, const PairwiseSumOptions &options)
Definition pairwise_sum.hpp:102

Residual sum of squares

The rss() function will compute both the mean and the residual sum of squares (RSS) of an array:

std::vector<double> x{ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10. };
auto rssout = quickstats::rss(x.size(), x.data(), work, {});
rssout.mean;
rssout.rss;
// Sparse vectors, e.g., [1, 2, 3, 0, 0, 0, 0, 0, 0, 0]
std::vector<double> y{ 1., 2., 3. };
auto sprssout = quickstats::rss(10, y.size(), y.data(), work, {});
sprssout.mean;
sprssout.rss;
RssResult< Output_ > rss(const std::size_t num_total, const std::size_t num_non_zero, const Input_ *const ptr, RssWorkspace< Output_ > &work, const RssOptions< Output_ > &options)
Definition rss.hpp:98
Re-usable workspace for rss().
Definition rss.hpp:47

In some situations, we might want to update the RSS as new values become available. This can be done using the update_rss() function:

double curmean = 0, currss = 0;
for (std::size_t i = 0; i < 5; ++i) {
quickstats::update_rss(curmean, currss, x[i], i + 1);
}
// Do something with the mean/RSS of the first 5 elements.
for (std::size_t i = 5; i < 10; ++i) {
quickstats::update_rss(curmean, currss, x[i], i + 1);
}
// Do something with the mean/RSS of the full 'x' array.
// Works with sparse vectors as well.
quickstats::update_rss_with_zeros(curmean, currss, 5, x.size() + 5);
void update_rss_with_zeros(Output_ &mean, Output_ &rss, const Count_ num_zeros, const Count_ num_total)
Definition rss.hpp:263
void update_rss(Output_ &mean, Output_ &rss, const Input_ value, const Count_ num_total)
Definition rss.hpp:210

We can trivially convert each RSS into a variance estimate by dividing each variance with the number of observations (minus 1, for the sample variance). Note, some extra care may be required to handle zero denominators at low numbers of observations.

We can also use recenter_rss() to adjust the RSS for a different value of the mean. This is typically used to combine RSS values from multiple subarrays into a single RSS value for the entire array.

Median absolute deviation

Given the median, the mad() function will compute the median absolute deviation. Note that this will replace the input array with the absolute deviations.

std::vector<double> x{ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10. };
auto med = quickstats::median(x.size(), x.data(), {});
auto mad = quickstats::mad(x.size(), x.data(), med, {});
Output_ mad(const std::size_t num_total, Input_ *const ptr, const Input_ median, const MadOptions< Output_ > &options)
Definition mad.hpp:61

For infinite medians, we can optionally define the difference between infinities of the same sign as zero. This is occasionally helpful for providing a sane result when infinities are generated by mathematical transformations, e.g., log(0).

constexpr auto inf = std::numeric_limits<double>::infinity();
std::vector<double> x{ inf, 0, inf, inf, 0, inf, 0, 2, inf, inf };
auto med = quickstats::median(x.size(), x.data(), {});
auto mad = quickstats::mad(x.size(), x.data(), med, opt);
Options for mad() and mad_with_infinities().
Definition mad.hpp:22
bool difference_between_infinities_is_zero
Definition mad.hpp:34

As usual, the same functions are also available for sparse vectors.

Building projects

CMake with FetchContent

If you're using CMake, you just need to add something like this to your CMakeLists.txt:

include(FetchContent)
FetchContent_Declare(
quickstats
GIT_REPOSITORY https://github.com/LTLA/quickstats
GIT_TAG master # replace with a pinned version
)
FetchContent_MakeAvailable(quickstats)

Then you can link to quickstats to make the headers available during compilation:

# For executables:
target_link_libraries(myexe quickstats)
# For libaries
target_link_libraries(mylib INTERFACE quickstats)

CMake with find_package()

You can install the library by cloning a suitable version of this repository and running the following commands:

mkdir build && cd build
cmake .. -DQUICKSTATS_TESTS=OFF
cmake --build . --target install

Then you can use find_package() as usual:

find_package(ltla_quickstats CONFIG REQUIRED)
target_link_libraries(mylib INTERFACE ltla::quickstats)

Manual

If you're not using CMake, the simple approach is to just copy the files in the include/ subdirectory - either directly or with Git submodules - and include their path during compilation with, e.g., GCC's -I.